update the cv modal inference proxy server with optimization

This commit is contained in:
DatTT127
2026-07-15 23:24:34 +07:00
parent 3fbbca1eaa
commit 1f5e71b46a
51 changed files with 2067 additions and 245 deletions

View File

@@ -18,6 +18,10 @@ from backend.implementation.triton_batch import TRITON_MAX_BATCH_SIZE
from backend.services import cv_result_cache
from backend.services import triton_runtime_service as triton_runtime
from backend.services.cv_inference_service import CvBatchResult, CvInferenceOptions, run_batch, run_single
from backend.services import cv_celery_service
from backend.logging.logging_config import setup_logging
setup_logging()
logger = logging.getLogger(__name__)
@@ -135,14 +139,15 @@ async def cv_inference_health():
)
@router.post("/analyze")
@router.post("/analyze") # deprecated
async def analyze_upload(
image: UploadFile = File(...),
calibration: str | None = Form(default=None),
):
"""Spec-compliant CV pipeline: CLAHE → angle → inflammation → conditional segmentation."""
image_pil = await _load_upload_image(image)
options = _build_options(_parse_calibration_form(calibration), use_cache=False)
options = _build_options(_parse_calibration_form(calibration), use_cache=True)
try:
result = await run_single(image_pil, frame_id=None, options=options)
@@ -168,11 +173,13 @@ async def analyze_batch_upload(
calibration: str | None = Form(default=None),
):
"""Spec-compliant CV batch — one full pipeline per frame (angle-first, gated segmentation)."""
logger.info("Starting analyze batch upload")
if not images:
raise HTTPException(status_code=400, detail="At least one image is required")
try:
id_list = json.loads(frame_ids)
# logger.info("Start to check the id_list")
except json.JSONDecodeError as exc:
raise HTTPException(status_code=400, detail="frame_ids must be a JSON array of strings") from exc
@@ -213,6 +220,68 @@ async def analyze_batch_upload(
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.post("/analyze/batch/celery")
async def analyze_batch_celery(
images: list[UploadFile] = File(...),
frame_ids: str = Form(...),
calibration: str | None = Form(default=None),
):
"""Experiment: async chunk fan-out via Celery + Redis."""
if not images:
raise HTTPException(status_code=400, detail="At least one image is required")
try:
id_list = json.loads(frame_ids)
except json.JSONDecodeError as exc:
raise HTTPException(status_code=400, detail="frame_ids must be a JSON array of strings") from exc
if not isinstance(id_list, list) or not all(isinstance(x, str) for x in id_list):
raise HTTPException(status_code=400, detail="frame_ids must be a JSON array of strings")
if len(id_list) != len(images):
raise HTTPException(status_code=400, detail="frame_ids length must match images count")
image_pils: list[Image.Image] = []
for upload in images:
image_pils.append(await _load_upload_image(upload))
options = _build_options(_parse_calibration_form(calibration))
try:
job_id = cv_celery_service.submit_celery_batch(image_pils, id_list, options)
return JSONResponse({
"success": True,
"job_id": job_id,
"image_count": len(image_pils),
"mode": "celery-chunk-fanout",
"chunk_size": cv_celery_service.CELERY_CHUNK_SIZE,
})
except Exception as exc:
logger.exception("Celery batch submit failed")
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.get("/analyze/batch/celery/{job_id}")
async def analyze_batch_celery_result(job_id: str):
"""Poll result for a Celery chunk-fan-out batch job."""
try:
result = await cv_celery_service.get_celery_batch_result(job_id)
status = result.get("status")
if status == "pending":
return JSONResponse(
status_code=202,
content=result,
)
if status == "unknown":
return JSONResponse(
status_code=404,
content=result,
)
return JSONResponse(result)
except Exception as exc:
logger.exception("Celery batch result fetch failed")
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.post("/segment")
@router.post("/segment/batch")
@router.post("/angle")

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
#
# Launch the standalone CV inference FastAPI server (Modal Triton).
#
# Usage:
# ./backend/run_cv_inference.sh
#
# Works from any directory: it resolves the CODEBASE root relative to this
# script, sets PYTHONPATH so `import backend...` resolves, then runs the
# server as a module.
#
# Override defaults via env vars, e.g.:
# CV_INFERENCE_PORT=8080 ./backend/run_cv_inference.sh
#
set -euo pipefail
# CODEBASE root = grandparent dir of this script's directory (script lives in backend/routers/).
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CODEBASE_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "${CODEBASE_ROOT}"
export PYTHONPATH="${CODEBASE_ROOT}:${PYTHONPATH:-}"
# exec python -m backend.cv_inference_server
exec uvicorn backend.cv_inference_server:app --host 0.0.0.0 --port ${CV_INFERENCE_PORT:-8001}