services: llama-server: # Pin this. `server-cuda` is a MOVING tag while everything else here is pinned (Pi, # adapter, GGUF). The MTP/draft flags below are new and have already changed names once; # a silent pull can break startup or drafting determinism. Set LLAMA_IMAGE in .env to an # immutable digest before relying on this in production: # docker buildx imagetools inspect ghcr.io/ggml-org/llama.cpp:server-cuda # copy sha256 # LLAMA_IMAGE=ghcr.io/ggml-org/llama.cpp@sha256: image: ${LLAMA_IMAGE:-ghcr.io/ggml-org/llama.cpp:server-cuda} container_name: daas-llama ports: - "8080:8080" volumes: - ./models:/models - ./templates:/templates deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] restart: unless-stopped # Qwen3.6-35B-A3B MTP UD-Q4_K_XL on a single L4 24GB. Params follow the reference MTP tune # (github.com/hanxiao/Qwen3.6-35B-A3B-MTP-L4): q4_0 KV, --spec-draft-n-max 2 (no p-min), # -ub 256 -b 2048, mmap ON + -ngl auto-fit (no --no-mmap, no -ngl 999 -- forcing all layers # to GPU OOMs once MTP + KV load). Q4_K_XL weights ~22GB nearly fill the card. # This is a HYBRID GDN+MoE model: only 10 of 40 layers carry a per-token KV cache (the rest # keep a small fixed recurrent state), so per-token KV is tiny (~0.65GB at q4_0/131072). # Real headroom is dominated by the weights, the GDN recurrent-state pool, compute buffers, # and the v5-nano embedder (~0.5GB). Measured: ~22.2GB used at full 131072 ctx, no OOM. # Lower CTX_SIZE (98304/65536) if nvidia-smi is tight. # # NOTE: --cache-reuse is intentionally OMITTED from the command below. This hybrid (Gated # DeltaNet) model has documented recurrent-state cache drift that can silently corrupt # digits across the long --continue + auto-compaction loop (llama.cpp#21681), fatal for a # factual dataroom. Re-add `--cache-reuse 256` only once the fix is confirmed in the pinned # image and verified by the numeric multi-turn smoke test in docs/DEPLOY.md. command: > --model /models/${MODEL_FILE:-Qwen3.6-35B-A3B-UD-Q4_K_XL.gguf} --host 0.0.0.0 --port 8080 --metrics --ctx-size ${CTX_SIZE:-131072} --parallel 1 --flash-attn 1 --cache-type-k q4_0 --cache-type-v q4_0 --threads 8 -ub 256 -b 2048 ${SPEC_ARGS:---spec-type draft-mtp --spec-draft-n-max 2} --n-predict 8192 --jinja --chat-template-file ${CHAT_TEMPLATE_FILE:-/templates/chat_template.jinja} -fitt 512 daas: # Prebuilt image (default): `docker compose pull` fetches it. To build from source instead, # `docker compose up -d --build` builds `.` and tags it under this same name. image: ${DAAS_IMAGE:-ghcr.io/hanxiao/dataroom:latest} build: . container_name: daas-app ports: - "8000:8000" env_file: - .env environment: - LLAMA_URL=http://llama-server:8080 - JOBS_DIR=/data/jobs - CONTEXT_WINDOW=${CTX_SIZE:-131072} # match llama-server --ctx-size (dashboard + compaction) - EMBED_DEVICE=cpu # v5-nano on CPU: frees ~0.5GB so Q4 (~22GB) doesn't OOM the L4 - TOKENIZERS_PARALLELISM=false deploy: resources: reservations: devices: - driver: nvidia count: all capabilities: [gpu] volumes: - ./data:/data # Skills + extensions are read-only methodology. Bind them back over the image copy as # :ro so a job (which runs as root in this container) physically cannot create, overwrite, # touch or delete anything under pi/ - the kernel returns EROFS. Defense in depth on top of # the prompt anchor that already tells the agent the dataroom root is its own job dir. - ./pi:/app/pi:ro depends_on: - llama-server restart: unless-stopped # Reverse proxy for dataroom.hanxiao.io (Cloudflare -> here). Terminates :80/:443 and # forwards to the app. Self-signed origin cert lives in ./nginx/certs (gitignored, made by # setup.sh / generate-cert). See nginx/dataroom.conf for the rate-limit + proxy config. nginx: image: nginx:alpine container_name: daas-nginx ports: - "80:80" - "443:443" volumes: - ./nginx/dataroom.conf:/etc/nginx/conf.d/default.conf:ro - ./nginx/certs:/etc/nginx/certs:ro depends_on: - daas restart: unless-stopped