PhonePixelGhost commited on
Commit
537d300
·
verified ·
1 Parent(s): c3c7e98

Upload 14 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ models/resnet18.onnx.data filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Create a user to avoid running as root (Required for Hugging Face Spaces)
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV PATH="/home/user/.local/bin:${PATH}"
7
+
8
+ WORKDIR /app
9
+
10
+ # Copy requirements and install dependencies
11
+ COPY --chown=user requirements.txt .
12
+ RUN pip install --no-cache-dir --user -r requirements.txt
13
+
14
+ # Copy the application code and models
15
+ COPY --chown=user app/ ./app/
16
+ COPY --chown=user models/ ./models/
17
+
18
+ # Use port 7860 (Standard for Hugging Face Spaces)
19
+ EXPOSE 7860
20
+
21
+ # Run with multiple workers for higher throughput
22
+ # Note: On Linux/Docker, uvicorn workers work perfectly
23
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "4", "--log-level", "warning"]
app/__init__.py ADDED
File without changes
app/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (162 Bytes). View file
 
app/__pycache__/main.cpython-313.pyc ADDED
Binary file (2.03 kB). View file
 
app/__pycache__/model.cpython-313.pyc ADDED
Binary file (2.4 kB). View file
 
app/__pycache__/schemas.cpython-313.pyc ADDED
Binary file (836 Bytes). View file
 
app/main.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException
2
+ from concurrent.futures import ProcessPoolExecutor
3
+ import asyncio
4
+ from app.model import run_inference
5
+ from app.schemas import PredictionResponse
6
+
7
+ app = FastAPI(title="ResNet-18 Image Classifier", version="1.0.0")
8
+
9
+ # ใช้ 6 Workers ตามจำนวน Physical Cores ของ Ryzen 7500F
10
+ executor = ProcessPoolExecutor(max_workers=6)
11
+
12
+ MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB
13
+ ALLOWED_CONTENT_TYPES = {"image/jpeg", "image/png", "image/webp", "image/gif"}
14
+
15
+ @app.get("/health")
16
+ async def health():
17
+ return {"status": "ok"}
18
+
19
+ @app.post("/predict", response_model=PredictionResponse)
20
+ async def predict(file: UploadFile = File(...)):
21
+ if file.content_type not in ALLOWED_CONTENT_TYPES:
22
+ raise HTTPException(status_code=415, detail="Unsupported media type")
23
+
24
+ image_bytes = await file.read()
25
+
26
+ if len(image_bytes) > MAX_FILE_SIZE:
27
+ raise HTTPException(status_code=413, detail="File too large")
28
+
29
+ # รัน Inference ใน ProcessPoolExecutor เพื่อกระจายโหลดลง 6 Cores
30
+ loop = asyncio.get_event_loop()
31
+ try:
32
+ result = await loop.run_in_executor(executor, run_inference, image_bytes)
33
+ return result
34
+ except Exception as e:
35
+ raise HTTPException(status_code=500, detail=f"Inference error: {str(e)}")
app/model.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import onnxruntime as ort
2
+ import numpy as np
3
+ from PIL import Image
4
+ import io
5
+ import time
6
+ from transformers import AutoImageProcessor, ResNetForImageClassification
7
+
8
+ # Load feature extractor
9
+ processor = AutoImageProcessor.from_pretrained("microsoft/resnet-18")
10
+
11
+ # Optimize session for multi-process environment
12
+ sess_options = ort.SessionOptions()
13
+ sess_options.intra_op_num_threads = 1 # One thread per process worker
14
+ sess_options.inter_op_num_threads = 1
15
+ sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
16
+
17
+ # Load ONNX session
18
+ session = ort.InferenceSession(
19
+ "models/resnet18_quantized.onnx",
20
+ sess_options=sess_options,
21
+ providers=["CPUExecutionProvider"]
22
+ )
23
+
24
+ # Load label mapping
25
+ cfg = ResNetForImageClassification.from_pretrained("microsoft/resnet-18").config
26
+
27
+ def run_inference(image_bytes: bytes) -> dict:
28
+ img = Image.open(io.BytesIO(image_bytes)).convert("RGB")
29
+ inputs = processor(images=img, return_tensors="np")
30
+ pixel_values = inputs["pixel_values"].astype(np.float32)
31
+
32
+ t0 = time.perf_counter()
33
+ outputs = session.run(["logits"], {"pixel_values": pixel_values})
34
+ elapsed = (time.perf_counter() - t0) * 1000
35
+
36
+ logits = outputs[0][0]
37
+ predicted_class_id = int(np.argmax(logits))
38
+
39
+ return {
40
+ "label": cfg.id2label[predicted_class_id],
41
+ "score": float(np.exp(logits[predicted_class_id]) / np.sum(np.exp(logits))),
42
+ "label_id": predicted_class_id,
43
+ "inference_time_ms": elapsed
44
+ }
app/schemas.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class PredictionResponse(BaseModel):
4
+ label: str
5
+ score: float
6
+ label_id: int
7
+ inference_time_ms: float
8
+
9
+ class ErrorResponse(BaseModel):
10
+ detail: str
11
+ error_code: str
models/resnet18.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67045d45797b2be759e40bb17417045699bab836398694cdf7a2b5ff144d7ab6
3
+ size 180730
models/resnet18.onnx.data ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:30b1aa4bdeba9751b30d868ef749ccb8c7fcb1be491e854eeca3f52369a00267
3
+ size 46792704
models/resnet18_quantized.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5dc64fbf9cf470ed1db8c391577040069500cce3a1b7139a9194a07a0c98d63d
3
+ size 11828572
models/resnet18_temp.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:108be9f23944c1ee1a0c9e97d97ac27550c06eb468a9cf1265cdc4a8a686be43
3
+ size 46916000
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ python-multipart
4
+ onnxruntime
5
+ numpy
6
+ Pillow
7
+ transformers
8
+ torch
9
+ torchvision
10
+ pydantic
11
+ pytest
12
+ httpx
13
+ onnx