Dimitris commited on
Commit
1e5b8be
Β·
1 Parent(s): b6e892a

feat(deploy): offline Docker Space (llama.cpp + baked MiniCPM-V GGUF)

Browse files

Builds current llama.cpp, bakes the MiniCPM-V 4.6 GGUF + mmproj into the image at build time,
and launches llama-server + the Gradio app. Zero external network at runtime (off-grid):
EXTRACTOR_BACKEND=local, HF_HUB_OFFLINE=1. Swap the MODEL_REPO build-arg to ship the
fine-tuned model. See DEPLOY.md.

Files changed (4) hide show
  1. .dockerignore +14 -0
  2. DEPLOY.md +50 -0
  3. Dockerfile +46 -0
  4. start.sh +24 -0
.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Keep the build context small; the model is downloaded inside the image, not copied in.
2
+ .git/
3
+ .venv/
4
+ venv/
5
+ models/
6
+ __pycache__/
7
+ **/__pycache__/
8
+ *.pyc
9
+ .pytest_cache/
10
+ .gradio/
11
+ flagged/
12
+ train/data/
13
+ eval/data/synth*
14
+ .DS_Store
DEPLOY.md ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deploying the offline Space (Docker)
2
+
3
+ The submission Space runs the model **on-device** via a Docker image: it builds current
4
+ llama.cpp, **bakes the MiniCPM-V GGUF into the image at build time**, and launches
5
+ `llama-server` + the Gradio app. No external API at runtime β†’ **off-grid**.
6
+
7
+ ## 1. Make the Space a Docker Space (under the org)
8
+ The Space `build-small-hackathon/blood-test-explainer` must use **`sdk: docker`**. Set its
9
+ `README.md` frontmatter to exactly:
10
+
11
+ ```yaml
12
+ ---
13
+ title: Blood Test Explainer
14
+ emoji: πŸ“Š
15
+ colorFrom: green
16
+ colorTo: green
17
+ sdk: docker
18
+ app_port: 7860
19
+ pinned: false
20
+ ---
21
+ ```
22
+
23
+ ## 2. Get these files into the Space repo
24
+ The Space needs `Dockerfile`, `start.sh`, `.dockerignore`, `app.py`, and `src/` (plus the
25
+ sample reports under `eval/data/real/`). If your Space mirrors this GitHub repo, push this
26
+ branch's contents. If the Space is a separate HF git repo, copy these files into it and push.
27
+
28
+ ## 3. HF builds it
29
+ First build takes ~10–15 min (compiles llama.cpp + downloads the ~1.6 GB model). When it's
30
+ up, upload a report β†’ it extracts fully offline.
31
+
32
+ ## 4. Ship the fine-tuned model (later)
33
+ Once you've fine-tuned and converted to GGUF, upload it to an HF model repo, then rebuild the
34
+ Space with the build-args pointing at it:
35
+
36
+ ```
37
+ MODEL_REPO=<you>/minicpmv-lab-gguf MODEL_FILE=<your-model>.Q4_K_M.gguf MMPROJ_FILE=mmproj-model-f16.gguf
38
+ ```
39
+ (In a Space, set these as build-time variables, or edit the `ARG` defaults in the Dockerfile.)
40
+
41
+ ## 5. Verify off-grid (badge)
42
+ - Model is baked into the image at build time; `HF_HUB_OFFLINE=1` + `TRANSFORMERS_OFFLINE=1`.
43
+ - The only network at runtime is `127.0.0.1:8080` (llama-server in the same container).
44
+ - Grep check: nothing on the request path hits an external host β€” the API backend (the only
45
+ outbound HTTP) is disabled when `EXTRACTOR_BACKEND=local`.
46
+
47
+ ## Hardware
48
+ Free **CPU** Space works (first inference is slower than your M3, ~30–60s). Upgrade the Space
49
+ hardware if you want it snappier for live demos; the demo **video** should be recorded locally
50
+ where it's fast.
Dockerfile ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Offline Blood Test Explainer β€” MiniCPM-V 4.6 served on-device by llama.cpp, no external APIs.
2
+ # HF Docker Space. The model is baked into the image at build time, so there is zero network
3
+ # at runtime (off-grid badge). Swap MODEL_REPO to your fine-tuned GGUF repo when it's ready.
4
+ FROM python:3.11-slim
5
+
6
+ ENV DEBIAN_FRONTEND=noninteractive
7
+ RUN apt-get update && apt-get install -y --no-install-recommends \
8
+ git cmake build-essential curl ca-certificates libgomp1 \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # 1) Build current llama.cpp (its server supports MiniCPM-V 4.6). GGML_NATIVE=OFF for portability.
12
+ WORKDIR /opt
13
+ RUN git clone --depth 1 https://github.com/ggml-org/llama.cpp.git && \
14
+ cmake -S llama.cpp -B llama.cpp/build \
15
+ -DGGML_NATIVE=OFF -DLLAMA_CURL=OFF -DBUILD_SHARED_LIBS=OFF -DLLAMA_BUILD_TESTS=OFF && \
16
+ cmake --build llama.cpp/build --config Release -j --target llama-server && \
17
+ cp llama.cpp/build/bin/llama-server /usr/local/bin/llama-server && \
18
+ rm -rf /opt/llama.cpp
19
+
20
+ # 2) Bake the model + vision projector into the image (no runtime download).
21
+ # Override these build-args to ship your fine-tuned model instead of the base.
22
+ ARG MODEL_REPO=openbmb/MiniCPM-V-4.6-gguf
23
+ ARG MODEL_FILE=MiniCPM-V-4_6-Q4_K_M.gguf
24
+ ARG MMPROJ_FILE=mmproj-model-f16.gguf
25
+ RUN pip install --no-cache-dir "huggingface_hub[cli]" && \
26
+ mkdir -p /models && \
27
+ hf download "$MODEL_REPO" "$MODEL_FILE" "$MMPROJ_FILE" --local-dir /models
28
+
29
+ # 3) App + runtime deps (no llama-cpp-python: we use the llama-server backend).
30
+ WORKDIR /app
31
+ RUN pip install --no-cache-dir \
32
+ gradio==6.17.3 requests==2.32.5 pillow==12.0.0 pymupdf==1.26.6 json-repair==0.60.1
33
+ COPY . /app
34
+
35
+ ENV EXTRACTOR_BACKEND=local \
36
+ LLAMA_SERVER_URL=http://127.0.0.1:8080/v1/chat/completions \
37
+ LLAMA_SERVER_MODEL=minicpm-v \
38
+ LOCAL_MODEL_PATH=/models/MiniCPM-V-4_6-Q4_K_M.gguf \
39
+ LOCAL_MMPROJ_PATH=/models/mmproj-model-f16.gguf \
40
+ GRADIO_SERVER_NAME=0.0.0.0 \
41
+ GRADIO_SERVER_PORT=7860 \
42
+ HF_HUB_OFFLINE=1 \
43
+ TRANSFORMERS_OFFLINE=1
44
+
45
+ EXPOSE 7860
46
+ CMD ["bash", "start.sh"]
start.sh ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Launch the on-device model server, wait for it, then start the Gradio UI.
3
+ # Everything stays on localhost β€” no external network β€” so this is fully off-grid.
4
+ set -euo pipefail
5
+
6
+ echo "Starting llama-server (on-device MiniCPM-V)..."
7
+ llama-server \
8
+ -m "${LOCAL_MODEL_PATH}" \
9
+ --mmproj "${LOCAL_MMPROJ_PATH}" \
10
+ --host 127.0.0.1 --port 8080 \
11
+ --ctx-size 4096 &
12
+
13
+ # Wait until the model is loaded and the server answers /health (model load can take a while).
14
+ echo "Waiting for llama-server to be ready..."
15
+ for _ in $(seq 1 240); do
16
+ if curl -sf http://127.0.0.1:8080/health >/dev/null 2>&1; then
17
+ echo "llama-server is ready."
18
+ break
19
+ fi
20
+ sleep 2
21
+ done
22
+
23
+ echo "Starting Gradio app on port ${GRADIO_SERVER_PORT:-7860}..."
24
+ exec python app.py