Files
club-3090/models/qwen3.6-27b/vllm/compose/single/autoround-int4/minimal.yml
noonghunna 510a76c885 fix(launch): runtime-agnostic GPU pinning via UUIDs — CDI/NixOS support (#610)
--gpus selection was NVIDIA_VISIBLE_DEVICES-only, which CDI runtimes
(NixOS hardware.nvidia-container-toolkit, nvidia-ctk cdi) IGNORE — mog's
3x3090 rig got all cards exposed and vLLM took the wrong two. Index-based
CUDA_VISIBLE_DEVICES can't fix it either: the classic runtime renumbers
the exposed set in-container.

UUIDs solve both at once:
- launch.sh --gpus N,M resolves indices -> GPU UUIDs (nvidia-smi -i) and
  exports BOTH NVIDIA_VISIBLE_DEVICES and CUDA_VISIBLE_DEVICES as UUIDs.
  Classic runtime: exposes-by-UUID; CUDA mask agrees (renumbering-proof).
  CDI: exposure comes from device_ids (nvidia.com/gpu=all) and the CUDA
  UUID mask is what pins the cards. Index fallback if the query fails.
- 26 composes gain a bare `- CUDA_VISIBLE_DEVICES` passthrough (unset ->
  absent -> zero change for existing users); stale commented hints removed.
- preflight: selector consumers are host-index-based -> new
  _preflight_selector_normalize maps GPU-xxxx tokens back to indices at
  the single choke point (the first live boot caught this: preflight
  rejected the UUID selector).
- new guard test-compose-gpu-mask-passthrough.sh: every compose with the
  NVIDIA env line must pass CUDA_VISIBLE_DEVICES through + launch.sh must
  keep the UUID resolution.
- docs/HARDWARE.md: "Pinning specific GPUs (and CDI / NixOS runtimes)" —
  the runtime table, the CDI deploy-block swap, the in-container
  renumbering gotcha, manual recipes.

LIVE-VERIFIED on this rig (classic runtime, 2x3090):
  launch.sh --variant vllm/minimal --gpus 1
  -> host GPU0 = 1 MiB (empty), GPU1 = 20.7 GB (model), both env vars
  UUID-set in-container. CDI leg validation goes to mog (NixOS reporter).

Follow-up (tracked on #610): estate ESTATE_GPUS UUID-ization for CDI
multi-instance rigs; post-boot visible-GPU-count sanity check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EfF565T9eSLaqGzidyJ1Pm
2026-07-06 22:59:51 +00:00

142 lines
6.2 KiB
YAML

# ===========================================================================
# Profile (at-a-glance):
# Model: Qwen3.6-27B (Lorbus AutoRound INT4 + BF16 mtp.fc preserved)
# Topology: Single 3090 (TP=1)
# Drafter: none (no spec-decode — simplest path)
# KV: fp8_e5m2 (no TQ3 to avoid cudagraph cascade bugs)
# Vision: no
# Max ctx: 65K (mem-util 0.95)
# Genesis: none — bare vLLM, isolation control
# Status: ✅ Production
# Best for: Debugging baseline / 20 GB Ampere fallback when TQ3 paths
# don't fit / first-time setup verification
# ---------------------------------------------------------------------------
# Minimal — no spec-decode, no Genesis patches, no .tolist() patcher.
#
# When to pick this variant:
# - You want the simplest possible stack (fewer moving parts to debug)
# - You don't need MTP / spec-decode speedup
# - You're OK with ~32 TPS instead of the ~50-70 TPS of the spec-decode variants
# - You want vision + tool calls to "just work" without any patches
#
# Trade-off vs the default `docker-compose.yml`:
# - This: no MTP, no Genesis → 32/33 TPS narr/code, 32K ctx, vision ✓, tools ✓
# - Default: MTP + Genesis P65 + TQ3 KV → 51/68 TPS, 48K ctx, vision ✓, tools ✓
#
# This file exists because community feedback (predecessor repo issue #1)
# showed users sometimes hit the spec-decode tool-call cascade by booting
# the wrong compose. This minimal path removes that whole class of failure
# modes by never enabling spec-decode in the first place.
#
# Run:
# cd compose && docker compose -f single/minimal.yml up -d
# ===========================================================================
# Hardware metadata (parsed by scripts/preflight.sh):
# Requires-min-vram-gb: 20
# Engine-profile: vllm-stable
# Requires-min-gpu-count: 1
# Tensor-parallel: 1
services:
vllm-qwen36-27b-minimal:
image: ${VLLM_IMAGE:-vllm/vllm-openai:v0.24.0}
container_name: "${ESTATE_CONTAINER:-vllm-qwen36-27b-minimal}"
restart: ${CLUB3090_RESTART:-unless-stopped}
ports:
- "${BIND_HOST:-0.0.0.0}:${ESTATE_PORT:-${PORT:-8020}}:8000"
volumes:
- ${MODEL_DIR:-../../../../../../models-cache}:/root/.cache/huggingface
# torch.compile + Triton kernel caches — first boot warms (~60-90 sec);
# subsequent boots reuse cached graphs. Pattern from Sander's PROD launch.
# Closes club-3090 #22.
- ../../../cache/torch_compile:/root/.cache/vllm/torch_compile_cache
- ../../../cache/triton:/root/.triton/cache
# (vLLM PR #35936 required-tool-fallback overlay dropped 2026-05-31 with the
# move to stable v0.22.0 — its serving.py did `import vllm.beam_search`, removed
# in newer vLLM, so it crashes on v0.22.0; the bug it patched doesn't trip on
# current stable. See docs/UPSTREAM.md #35936.)
# froggeric/Qwen-Fixed-Chat-Templates qwen3.6 — fixes 7 default-template
# bugs (empty <think></think> spam, </thinking> hallucination, unclosed
# think before tool call, no-user-query crash, developer role, etc.).
# See docs/UPSTREAM.md "Community templates / model assets" + the row at
# https://huggingface.co/froggeric/Qwen-Fixed-Chat-Templates
- ../../../patches/froggeric-chat-template/chat_template.jinja:/etc/qwen-froggeric-chat-template.jinja:ro
environment:
- NVIDIA_VISIBLE_DEVICES=${ESTATE_GPUS:-${NVIDIA_VISIBLE_DEVICES:-all}}
# CUDA-level GPU mask — the runtime-agnostic half of GPU selection
# (#610): launch.sh --gpus exports GPU UUIDs here. CDI runtimes (NixOS,
# nvidia-ctk cdi) IGNORE NVIDIA_VISIBLE_DEVICES, so this mask is what
# actually pins cards there; on the classic runtime the UUID form is
# renumbering-proof. Unset → absent (no masking).
- CUDA_VISIBLE_DEVICES
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN:-}
- HF_HUB_OFFLINE=${HF_HUB_OFFLINE:-${OFFLINE:-0}}
- TRANSFORMERS_OFFLINE=${TRANSFORMERS_OFFLINE:-${OFFLINE:-0}}
- VLLM_WORKER_MULTIPROC_METHOD=spawn
- NCCL_CUMEM_ENABLE=0
- NCCL_P2P_DISABLE=1
- VLLM_NO_USAGE_STATS=1
# expandable_segments:True crashes boot on some setups (likely cuMemMap path).
# Known: JusefPol on NVLink (PR #31), WSL2 single-card 3090 Ti.
# Override via .env: PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False
- PYTORCH_CUDA_ALLOC_CONF=${PYTORCH_CUDA_ALLOC_CONF:-expandable_segments:True}
- OMP_NUM_THREADS=1
shm_size: "16gb"
ipc: host
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
entrypoint:
- /bin/bash
- -c
- |
# VLLM_ENFORCE_EAGER=1 in .env disables CUDA graphs — use on
# hardware where graph capture causes OOM or instability (e.g. WSL2).
exec vllm serve ${VLLM_ENFORCE_EAGER:+--enforce-eager} "$@"
- --
command:
- --model
- /root/.cache/huggingface/qwen3.6-27b-autoround-int4
- --served-model-name
- qwen3.6-27b
- qwen3.6-27b-autoround
- --quantization
- auto_round
- --dtype
- float16
- --tensor-parallel-size
- "${TP:-1}"
- --pipeline-parallel-size
- "${PP:-1}"
- --max-model-len
- "${MAX_MODEL_LEN:-32768}"
- --gpu-memory-utilization
- "${GPU_MEMORY_UTILIZATION:-0.92}"
- --max-num-seqs
- "${MAX_NUM_SEQS:-1}"
- --kv-cache-dtype
- "${KV_CACHE_DTYPE:-fp8_e5m2}"
- --trust-remote-code
# froggeric chat-template override (see volumes block + docs/UPSTREAM.md)
- --chat-template
- /etc/qwen-froggeric-chat-template.jinja
- --reasoning-parser
- qwen3
- --default-chat-template-kwargs
- '{"enable_thinking": false}'
- --enable-auto-tool-choice
- --tool-call-parser
- qwen3_coder
- --enable-prefix-caching
- --enable-chunked-prefill
# No --speculative-config (this is the minimal variant — no spec-decode)
- --override-generation-config
- '{"temperature":${TEMP:-${TEMPERATURE:-0.6}},"top_p":${TOP_P:-0.95},"top_k":${TOP_K:-20},"min_p":${MIN_P:-0.0},"repetition_penalty":${REPEAT_PENALTY:-1.0}}'
- --host
- 0.0.0.0
- --port
- "8000"