Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ model

# Benchmark results
results/
dine-in/results_model/

# Copilot CLI skills (session-local, not part of the application)
.github/skills/

# Python
__pycache__/
Expand Down
17 changes: 15 additions & 2 deletions dine-in/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,25 @@ LOG_LEVEL=INFO
# Service Endpoints
# -----------------------------------------------------------------------------
OVMS_ENDPOINT=http://ovms-vlm:8000
OVMS_MODEL_NAME=Qwen/Qwen2.5-VL-7B-Instruct
VLM_PRECISION=int8
# Must match the model name registered in ovms-service/models/config.json,
# which setup_models.sh generates with the precision suffix (e.g. -int4).
# A mismatch makes every OVMS request return HTTP 404.
OVMS_MODEL_NAME=openbmb/MiniCPM-V-4_5-int4
# Weight format passed to the model export (--weight-format). This must agree
# with the precision suffix in OVMS_MODEL_NAME above: the suffix is only a
# directory label, so setting int8 here while naming the model -int4 produces
# INT8 weights in an "-int4" directory (~1.6x slower decode).
VLM_PRECISION=int4
TARGET_DEVICE=GPU
SEMANTIC_SERVICE_ENDPOINT=http://semantic-service:8080
API_TIMEOUT=60

# Optional Hugging Face authentication for gated models.
# Leave empty for public models (for example Qwen/Qwen2.5-VL-7B-Instruct).
# setup_models.sh also accepts HUGGINGFACE_HUB_TOKEN as an alternative name.
HF_TOKEN=


# -----------------------------------------------------------------------------
# Benchmark Mode Configuration (In-App Scaling)
# -----------------------------------------------------------------------------
Expand Down
22 changes: 19 additions & 3 deletions dine-in/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,19 @@ export TAG = 2026.1.0

# Dine-In image configuration
DINEIN_IMAGE_NAME ?= intel/order-accuracy-dine-in
DINEIN_IMAGE ?= $(DINEIN_IMAGE_NAME):$(TAG)
# Tag for the dine-in image only. Kept separate from TAG because TAG also
# selects the benchmark image (intel/retail-benchmark:$(TAG)), which is
# published per-version and has no "latest" tag.
DINEIN_TAG ?= latest
DINEIN_IMAGE ?= $(DINEIN_IMAGE_NAME):$(DINEIN_TAG)

# Host user identity, passed to the container so the app user is remapped to
# match. Keeps bind-mounted results/ owned by the invoking user on hosts where
# that user is not uid 1000 (e.g. EMT). See ITEP-93258.
HOST_UID ?= $(shell id -u)
HOST_GID ?= $(shell id -g)
export HOST_UID
export HOST_GID

# Registry configuration
# REGISTRY=false : Build and use local images
Expand All @@ -83,7 +95,7 @@ REGISTRY_BENCHMARK ?= intel/retail-benchmark:$(TAG)

# Benchmark Order Accuracy settings
BENCHMARK_WORKERS ?= 1
BENCHMARK_ITERATIONS ?= 1
BENCHMARK_ITERATIONS ?= 8
BENCHMARK_DURATION ?= 180
BENCHMARK_TARGET_FPS ?= 15.0
TARGET_DEVICE ?= GPU
Expand Down Expand Up @@ -263,7 +275,8 @@ check-device: ## Check that graph.pbtxt device matches TARGET_DEVICE (auto-updat

up: check-device
@echo "Starting dine-in services..."
@mkdir -p results && chmod 777 results
@mkdir -p results
@chmod 777 results 2>/dev/null || true
@if [ "$(REGISTRY)" = "false" ]; then \
echo "$(BLUE)Using locally built image: $(DINEIN_IMAGE)$(NC)"; \
DINEIN_IMAGE=$(DINEIN_IMAGE) docker compose up -d --no-build; \
Expand Down Expand Up @@ -440,6 +453,9 @@ benchmark: check-device
@echo "Target Device: $(TARGET_DEVICE)"
@echo "Results Dir: $(RESULTS_DIR)"
@echo ""
@echo "Cleaning up any stale containers from previous runs..."
@docker rm -f metrics-collector dinein_app dinein_ovms_vlm dinein_semantic_service >/dev/null 2>&1 || true
@docker compose -f $(COMPOSE_FILE) --profile benchmark down --remove-orphans >/dev/null 2>&1 || true
mkdir -p $(RESULTS_DIR)
cd $(PERF_TOOLS_DIR) && \
( \
Expand Down
5 changes: 4 additions & 1 deletion dine-in/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,12 @@ cd ../dine-in

This step:

- Downloads Qwen2.5-VL-7B-Instruct from HuggingFace (~7 GB)
- Reads `OVMS_MODEL_NAME` from `dine-in/.env` (default: `openbmb/MiniCPM-V-4_5`)
- Downloads the selected model from HuggingFace
- Converts to OpenVINO™ INT8 format

If the selected model is gated on Hugging Face, add `HF_TOKEN=<your_token>` to `dine-in/.env` (or run `huggingface-cli login`). Public models such as `Qwen/Qwen2.5-VL-7B-Instruct` do not require authentication.

### 3. Build and Start

**Option A: Using Registry Images (default)**
Expand Down
8 changes: 4 additions & 4 deletions dine-in/configs/orders.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
"restaurant": "McDonald's",
"table_number": "T25",
"items_ordered": [
{ "item": "Filet-O-Fish", "quantity": 1 },
{ "item": "Cheesy Fries", "quantity": 1 }
{ "item": "Chicken Nuggets", "quantity": 1 },
{ "item": "McChicken", "quantity": 1 }
],
"order_id": "MCD-1003"
},
Expand All @@ -36,8 +36,8 @@
"restaurant": "McDonald's",
"table_number": "T31",
"items_ordered": [
{ "item": "Chicken Nuggets", "quantity": 1 },
{ "item": "McChicken", "quantity": 1 }
{ "item": "Cheeseburger", "quantity": 1 },
{ "item": "French Fries", "quantity": 1 }
],
"order_id": "MCD-1004"
}
Expand Down
22 changes: 22 additions & 0 deletions dine-in/configs/prompt_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema_version": "1.0",
"_comment": "VLM prompt templates for food item detection. Edit this file to tune wording/rules without touching code or rebuilding the container. Templates support the placeholder {inventory_list} (Phi variant, newline list) or {inventory_text} (generic variant, comma-separated) which is substituted with the live contents of inventory.json at request time. Restart the dine-in container to pick up changes (no rebuild required — this file is read at runtime).",

"max_output_tokens": 96,

"quantity_rule": "\"quantity\" = number of separate ORDER SERVINGS (boxes/wrappers), NEVER the piece count inside one serving. Do NOT count individual nuggets, fries, or pieces. Example: a box containing 6 nuggets is quantity 1, not 6. Only increase quantity if you see 2+ separate boxes/wrappers of the same item.",

"quantity_field_description": "Number of separate order servings (boxes/wrappers), NOT the piece count inside one serving. One box of nuggets or fries = 1, regardless of how many pieces are visible inside it.",

"json_schema_example": "{\"items\":[{\"name\":\"item\",\"quantity\":1}]}",

"phi": {
"with_inventory_template": "You are a food item detector for a restaurant tray.\n\nInventory (the ONLY item names you may use):\n{inventory_list}\n\nTask: Identify which of the inventory items above are present in the image.\n\nReturn ONLY valid JSON using exactly this schema:\n{json_schema_example}\n\nRules:\n- Only detect items from the inventory list above. Never invent names outside this list.\n- Match each visible food item to the closest inventory item name and use that exact name.\n- Carefully scan the entire image before answering.\n- Use all visual evidence in the scene before deciding items.\n- Read visible text on wrappers, cartons, drink cups, and packaging.\n- If product names are visible on packaging, use those names to match an inventory item.\n- Do not rely only on food appearance.\n- Detect every visible food item before generating the response.\n- Do not stop after finding the first item.\n- Include partially visible food items when reasonably confident.\n- Ignore trays, napkins, and background objects.\n- Detect only food items clearly visible in the image.\n- {quantity_rule}\n- Return only valid JSON.\n- Do not output explanations.\n- Do not output reasoning.\n- Never repeat or explain the prompt.\n- Never include markdown.\n- If no inventory items are detected, return exactly: {{\"items\":[]}}"
},

"generic": {
"with_inventory_template": "Inventory (the ONLY item names you may use): {inventory_text}\n\nIdentify which of the inventory items above are CLEARLY VISIBLE in this image.\nUse only names from the inventory list. Do NOT guess or include items you cannot see.\n{quantity_rule}\nRespond with MINIFIED JSON on a single line only (no spaces, no newlines, no markdown).\nJSON schema: {json_schema_example}",

"without_inventory_template": "ONLY list food items CLEARLY VISIBLE in this image. Do NOT guess.\n{quantity_rule}\nRespond with MINIFIED JSON on a single line only (no spaces, no newlines, no markdown).\nJSON: {json_schema_example}"
}
}
12 changes: 10 additions & 2 deletions dine-in/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ services:

# Dine-In Order Accuracy Gradio UI
dine-in:
image: ${DINEIN_IMAGE:-intel/order-accuracy-dine-in:2026.1.0}
image: ${DINEIN_IMAGE:-intel/order-accuracy-dine-in:latest}
build:
context: .
dockerfile: Dockerfile
Expand All @@ -93,6 +93,10 @@ services:
- ./results:/app/results
- ./configs:/app/configs:ro
environment:
# Align the container's app user with the host user so bind-mounted
# results/ stays owned by the invoking user (ITEP-93258).
- HOST_UID=${HOST_UID:-1000}
- HOST_GID=${HOST_GID:-1000}
- SEMANTIC_SERVICE_ENDPOINT=${SEMANTIC_SERVICE_ENDPOINT:-http://semantic-service:8080}
- OVMS_ENDPOINT=${OVMS_ENDPOINT:-http://ovms-vlm:8000}
- OVMS_MODEL_NAME=${OVMS_MODEL_NAME:-Qwen/Qwen2.5-VL-7B-Instruct}
Expand All @@ -116,7 +120,7 @@ services:
# Scale with: docker compose up -d --scale dinein-worker=4
# Or set WORKERS env variable and use profile: docker compose --profile benchmark up -d
dinein-worker:
image: ${DINEIN_IMAGE:-intel/order-accuracy-dine-in:2026.1.0}
image: ${DINEIN_IMAGE:-intel/order-accuracy-dine-in:latest}
build:
context: .
dockerfile: Dockerfile
Expand All @@ -137,6 +141,10 @@ services:
- ./results:/app/results
- ./configs:/app/configs:ro
environment:
# Align the container's app user with the host user so bind-mounted
# results/ stays owned by the invoking user (ITEP-93258).
- HOST_UID=${HOST_UID:-1000}
- HOST_GID=${HOST_GID:-1000}
- WORKERS=${WORKERS:-1}
- ITERATIONS=${ITERATIONS:-0}
- REQUEST_DELAY=${REQUEST_DELAY:-0}
Expand Down
53 changes: 47 additions & 6 deletions dine-in/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
#!/bin/bash
set -e

# Fix ownership of mounted volumes that Docker may create as root
# This runs as root before dropping to dlstreamer
if [ -d "/app/results" ]; then
chown -R dlstreamer:dlstreamer /app/results
# Align the in-container application user with the host user that owns the
# bind-mounted volumes. Without this, files written to ./results are owned by
# the container's UID (1000); on hosts where the invoking user is not uid 1000
# (e.g. EMT) the host user then cannot chmod/remove them, which breaks a
# second `make up`. See ITEP-93258.
APP_USER=dlstreamer
TARGET_UID="${HOST_UID:-}"
TARGET_GID="${HOST_GID:-}"

if [ "$(id -u)" = "0" ] && [ -n "$TARGET_UID" ] && [ -n "$TARGET_GID" ]; then
CURRENT_UID="$(id -u "$APP_USER")"
CURRENT_GID="$(id -g "$APP_USER")"

# Never remap to root; that would defeat the privilege drop below.
if [ "$TARGET_UID" = "0" ] || [ "$TARGET_GID" = "0" ]; then
echo "entrypoint: refusing to remap $APP_USER to root; keeping ${CURRENT_UID}:${CURRENT_GID}" >&2
elif [ "$TARGET_UID" != "$CURRENT_UID" ] || [ "$TARGET_GID" != "$CURRENT_GID" ]; then
# -o permits a non-unique id, in case it collides with an existing entry.
if [ "$TARGET_GID" != "$CURRENT_GID" ]; then
groupmod -o -g "$TARGET_GID" "$APP_USER"
fi
if [ "$TARGET_UID" != "$CURRENT_UID" ]; then
usermod -o -u "$TARGET_UID" "$APP_USER"
fi
echo "entrypoint: remapped $APP_USER ${CURRENT_UID}:${CURRENT_GID} -> ${TARGET_UID}:${TARGET_GID}"
# Re-own files still carrying the old ids. Restricted to paths owned by
# the previous uid/gid so large bind mounts are not rewritten wholesale.
find /app -xdev \( -uid "$CURRENT_UID" -o -gid "$CURRENT_GID" \) \
-exec chown -h "$TARGET_UID:$TARGET_GID" {} + 2>/dev/null || true
if [ -n "$HOME" ] && [ -d "$HOME" ]; then
chown -R "$TARGET_UID:$TARGET_GID" "$HOME" 2>/dev/null || true
fi
fi
fi

# Fix ownership of mounted volumes that Docker may create as root.
# Runs as root before dropping privileges.
if [ "$(id -u)" = "0" ] && [ -d "/app/results" ]; then
chown -R "$APP_USER:$APP_USER" /app/results 2>/dev/null || true
fi

# Drop privileges and exec the CMD as the application user. Using the user name
# (not a numeric id) preserves supplementary groups such as `video`, which GPU
# access depends on.
if [ "$(id -u)" = "0" ]; then
exec gosu "$APP_USER" "$@"
fi

# Drop privileges and exec the CMD as dlstreamer
exec gosu dlstreamer "$@"
exec "$@"
Loading
Loading