Files
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

268 lines
15 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ===========================================================================
# Profile (at-a-glance):
# Model: Gemma 4 31B (Intel AutoRound INT4)
# Topology: Dual 3090 (TP=2, NVLink auto-detected via NVLINK_MODE)
# Drafter: MTP n=4 (Google's official `gemma-4-31B-it-assistant`, BF16 0.5B)
# KV: int8_per_token_head (1 byte/token, via vendored PR #40391 overlay)
# Vision: yes
# Max ctx: 262K native default (dual-card max-ctx rule) @ 4-stream cap; INT8 PTH pool ~2× BF16
# Genesis: N/A — Genesis is Qwen3-Next-specific
# Status: 🗑️ Deprecated
# Caveats: Superseded by vllm/gemma-31b-dual (cyankiwi bf16 @224K, stock v0.24.0, overlay-free) in the
# v0.24.0 consolidation (2026-07-02). This was the 262K int8-PTH+#40391 path on v0.22.0 — NOT
# migrated to v0.24.0 (int8-PTH silently craters recall there; #40391 open/unmerged upstream).
# The 262K int8-PTH path returns when #40391 merges. Use vllm/gemma-31b-dual (bf16, ~224K).
# Quality: 105/150 (70%) thinking-OFF default — det 59/75 · sandbox 46/75
# TC 13/15 · IF 8/15 · SO 14/15 · DE 10/15 · RM 14/15 · BF 13/15 · HA 14/20 · CLI 19/40
# (--full, v0.22.0, 2026-05-31). Reasoning-ON A/B (with --reasoning-parser gemma4) = 107/150
# — a WASH (+2, within ±5-7 8-pack noise: IF+6/BF+2 offset by RM-2/HA-2/DE-1/CLI-1) at higher
# latency, so thinking-off stays the default; reasoning-on is a clean opt-in. Soak (continuous)
# PASS: 0 errors, 0 VRAM growth, 100% TPS retention, p50 87.96 TPS.
# Best for: Gemma 4 dual-card default — full 262K ctx + vision + 4 streams ⭐
# (stable-but-131K alternative without the overlay: vllm/gemma-bf16-mtp)
# ---------------------------------------------------------------------------
# Gemma-4-31B-it (Intel AutoRound INT4) + Google MTP "assistant" drafter
# 2× RTX 3090 TP=2 + per-token-head INT8 KV + ~120K ctx target.
#
# Default KV_DTYPE is int8_per_token_head because that's what runs on Ampere
# sm_86. fp8 PTH variants require Ada/Blackwell — see hardware compatibility
# table below. Override KV_DTYPE=fp8_per_token_head if you're on Ada/Blackwell
# and want to A/B against fp8 precision.
#
# Status: COMMUNITY-CONTRIBUTED, EXPERIMENTAL.
# Post-rebase of vLLM PR #40391 by @lisp19 (Gemma 4 KV cache page-size
# alignment for per-token-head quantization). PR is upstream-open + stalled
# on maintainer review; cross-rig validation by @cferra (sm_120 Blackwell
# TP=4) and @noonghunna (sm_86 Ampere — this rig). Local rebase resolves
# the conflict against post-Mamba-hybrid-support main.
#
# Companion composes (post-2026-05-31 rename):
# bf16-mtp.yml — vllm/gemma-bf16-mtp: BF16 KV, 131K (no overlays, stock v0.22.0)
# int8.yml — vllm/gemma-int8-mtp: INT8 PTH KV, 262K native (this file)
# single (TP=1) — boot-OOMs on Ampere 24 GB → vllm/gemma-mtp-tp1 DEPRECATED;
# single-card Gemma → beellama/gemma-dflash
#
# Hardware × per-token-head dtype compatibility:
#
# | | INT8 PTH | FP8 e4m3 PTH | FP8 e5m2 PTH | NVFP4 |
# | sm_90 Hopper | ✅ | ✅ | ✅ | ✅ |
# | sm_89 Ada | ✅ | ✅ | ✅ | ✅ |
# | sm_120 Black. | ✅ | ✅ | ✅ | ✅ |
# | sm_86 Ampere | ✅ | ❌ | ❌ | ❌ |
#
# Ampere blocker for fp8 PTH: Triton kernel uses `fp8e4nv` which sm_86
# doesn't implement (Ampere only supports `fp8e4b15` and `fp8e5`).
# ValueError: "type fp8e4nv not supported in this architecture"
# → fires from `_initialize_kv_caches` boot path. Confirmed 2026-05-08.
#
# INT8 PTH dispatches to standard PyTorch torch.int8 ops (not Triton fp8
# kernel) so it runs on every consumer GPU including Ampere. Same KV
# memory savings as FP8 (1 byte/element); slightly different precision
# characteristics (better near-zero, narrower dynamic range than e5m2).
#
# For Ada/Blackwell users: override KV_DTYPE=fp8_per_token_head if you
# want to benchmark FP8 vs INT8 PTH precision on your hardware.
#
# Why this exists (the upstream-blocker chain, summarized):
#
# Gemma 4 has interleaved attention with two head_dims:
# - sliding/local layers: head_dim=256
# - global/full layers: head_dim=512
# ANY per-token-head KV format adds scale metadata per token, breaking
# vLLM's `unify_kv_cache_spec_page_size()` because the resulting page
# sizes don't share a clean ratio. PR #40391 pre-pads the global layers
# to a 1040-byte factor and routes standard attention through a new
# `get_padded_attention_kv_cache_shape` helper to unblock this family.
#
# Without PR #40391, dual.yml is forced to bf16 KV + 32K ctx ceiling
# (KV pool exhausted at higher). With PR #40391 + INT8 PTH KV, ctx
# ceiling lifts to ~120K (4×) at the same TP=2 mem-util budget.
#
# Bench targets (TBD post-validation on this rig):
# narrative ~109 wall TPS (parity with bf16 path; KV format affects
# ctx ceiling, not per-token TPS materially)
# code ~142 wall TPS
# Max ctx ~120K (4× lift vs dual.yml's 32K)
#
# Models:
# target: Intel/gemma-4-31B-it-int4-AutoRound (21.2 GB, vision preserved)
# draft : google/gemma-4-31B-it-assistant (0.5B / 927 MB BF16)
#
# Vendored overlay — ONE remaining (2026-05-31 pin bump v0.21.0 → v0.22.0):
# vLLM PR #40391 — gemma4 per-token-head KV page-size alignment (head_dim
# 256 SWA vs 512 global). This is the ONLY reason the compose is pinned: it
# makes INT8 PTH KV bootable on Gemma 4. STILL OPEN upstream → REBASED onto
# stock v0.22.0 (the old v0.21.0-era full-module copies ImportError'd on
# v0.22.0 — they lacked v0.22.0's get_kv_cache_spec_kind) and re-delivered as
# a LEAN boot-time diff-apply (~240-line patch + 1 new file via install.sh),
# not 7 full-module mounts. Live-validated 2026-05-31 (boots, KV pool 447K @
# 262K, coherent generation). See: ../../../patches/vllm-pr40391-v0.22.0/README.md
#
# The 2 tool-parser/truncate overlays this compose used to carry are now IN
# stock v0.22.0 (PR #41800 merged 2026-05-06, PR #41991 merged 2026-05-08;
# both predate the 2026-05-15 v0.21.0 tag too) and PR #42006 was dropped by
# maintainer call (bf16-mtp ships prod without it). Verify before any pin bump:
# gh api repos/vllm-project/vllm/pulls/40391 --jq '.state, .merged_at' # drop overlay when MERGED
# gh api repos/vllm-project/vllm/pulls/42006 --jq '.state, .merged_at' # re-test trigger (docs/UPSTREAM.md)
# ===========================================================================
# Hardware metadata (parsed by scripts/preflight.sh):
# Requires-min-vram-gb: 24
# Engine-profile: vllm-gemma-stable
# Requires-min-gpu-count: 2
# Tensor-parallel: 2
services:
vllm-gemma-4-31b-mtp-int8:
image: ${VLLM_IMAGE:-vllm/vllm-openai:v0.22.0}
container_name: "${ESTATE_CONTAINER:-vllm-gemma-4-31b-mtp-int8}"
restart: ${CLUB3090_RESTART:-unless-stopped}
ports:
- "${ESTATE_PORT:-${PORT:-8032}}:8000"
volumes:
- ${MODEL_DIR:-../../../../../../models-cache}:/root/.cache/huggingface
# torch.compile + Triton kernel caches — first boot warms (~5-7 min);
# subsequent boots reuse cached graphs (~3 min). Separate cache dirs
# from dual.yml because the overlay file set differs (cudagraph
# capture would re-key on the patched kernel paths anyway).
- ../../../cache/torch_compile_int8:/root/.cache/vllm/torch_compile_cache
- ../../../cache/triton_int8:/root/.triton/cache
# ---- vLLM PR #40391 — LEAN diff-apply overlay (install_script) ----
# The ONLY remaining overlay. Mounts the patch dir (the ~240-line #40391
# diff + the one new helper file) and applies it to stock v0.22.0 at boot
# via install.sh — NOT 7 full-module file mounts (the old, drift-prone
# vllm-pr40391-rebased/ approach). Drop the whole dir when #40391 merges.
# See ../../../patches/vllm-pr40391-v0.22.0/README.md.
- ../../../patches/vllm-pr40391-v0.22.0:/etc/club3090/pr40391:ro
# vLLM PR #42006 — Gemma 4 MTP streaming multi-tool-call fix (both duals
# carry it; stock v0.22.0 drops non-last tool-call args when streaming).
- ../../../patches/vllm-pr42006-v0.22.0:/etc/club3090/pr42006:ro
# NVLink auto-detection — runs inside container at boot.
- ../../../../../../scripts/detect_nvlink.sh:/etc/club3090/detect_nvlink.sh:ro
# --------------------------------------------------------------------
# 2026-05-31: dropped 2 overlays that are now IN stock v0.22.0 (verified
# against the running image) + 1 by maintainer call, when bumping the pin
# v0.21.0 → v0.22.0 (only #40391 survives — still open upstream):
# - PR #41800 (truncate_prompt_tokens) — merged 2026-05-06, in v0.22.0 stock.
# - PR #41991 (tool-parser bounds) — merged 2026-05-08, in v0.22.0 stock.
# - PR #42006 (MTP streaming multi-tool) — still OPEN, but bf16-mtp ships
# production on v0.22.0 without it → dropped from both for an identical
# minimal overlay surface. Tracked in docs/UPSTREAM.md (re-test trigger:
# streaming multi-tool-call regression).
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
- NVLINK_MODE=${NVLINK_MODE:-auto}
- NCCL_CUMEM_ENABLE=0
- NCCL_P2P_DISABLE=1
- VLLM_NO_USAGE_STATS=1
- OMP_NUM_THREADS=1
- PYTORCH_CUDA_ALLOC_CONF=${PYTORCH_CUDA_ALLOC_CONF:-expandable_segments:True,max_split_size_mb:512}
- VLLM_ALLOW_LONG_MAX_MODEL_LEN=1
- TRITON_CACHE_DIR=/root/.triton/cache
shm_size: "16gb"
ipc: host
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
entrypoint:
- /bin/bash
- -c
- |
# vLLM PR #40391 (Gemma 4 per-token-head KV) — apply the lean diff to
# stock v0.22.0 BEFORE vllm imports. Fail-loud: a non-zero exit aborts
# boot rather than serving a half-patched engine.
bash /etc/club3090/pr40391/install.sh
# vLLM PR #42006 (Gemma 4 streaming multi-tool-call fix).
bash /etc/club3090/pr42006/install.sh
# NVLink auto-detection (sets NCCL env vars, _NVLINK_ENABLED).
source /etc/club3090/detect_nvlink.sh
if [ "$${_NVLINK_ENABLED:-0}" = "1" ]; then
exec vllm serve "$$@"
else
exec vllm serve --disable-custom-all-reduce "$$@"
fi
- --
command:
- --override-generation-config
- '{"temperature":${TEMP:-${TEMPERATURE:-1.0}},"top_p":${TOP_P:-0.95},"top_k":${TOP_K:-64},"min_p":${MIN_P:-0.0},"repetition_penalty":${REPEAT_PENALTY:-1.0}}'
- --host
- 0.0.0.0
- --port
- "8000"
- --model
- /root/.cache/huggingface/gemma-4-31b-autoround-int4
- --served-model-name
- gemma-4-31b
- gemma-4-31b-autoround
- --tensor-parallel-size
- "${TP:-2}"
- --pipeline-parallel-size
- "${PP:-1}"
# ---- KV format ----
# Default `int8_per_token_head` runs on every consumer GPU including
# sm_86 Ampere (uses standard PyTorch torch.int8 ops, not the Triton
# fp8e4nv kernel that sm_86 doesn't implement).
# Ada/Blackwell users can override KV_DTYPE=fp8_per_token_head to A/B
# against fp8 PTH precision.
# NOT `fp8_e5m2` (legacy tensor-wide fp8, rejected by query_quant
# allowlist on Gemma 4 even with PR #40391 — that's a separate query
# quantization compatibility check). NOT `fp8_e4m3` either (Triton
# fp8e4nv kernel not supported on sm_86 Ampere).
- --kv-cache-dtype
- "${KV_DTYPE:-int8_per_token_head}"
# ---- max-model-len (TP=2, INT8 PTH KV) — DUAL-CARD RULE: prioritize max
# context over concurrency (see docs/DUAL_CARD.md). --max-num-seqs is a
# CAP, not a reservation; the INT8 PTH pool (~2× the BF16 pool, measured
# ~354K-455K tok @ 0.95) holds a single 262K request with room to spare,
# so defaulting the ceiling to the model's NATIVE 262144 is ~free for
# short-request concurrency (4-wide opportunistic) — it just lets one
# request reach full native context.
- --max-model-len
- "${MAX_MODEL_LEN:-${CTX:-262144}}"
- --gpu-memory-utilization
- "${GPU_MEMORY_UTILIZATION:-0.95}"
# Lower --max-num-seqs only to GUARANTEE long requests are never preempted:
# max-num-seqs=4 + 262144 (default — native ctx, 4-wide for shorter reqs)
# max-num-seqs=2 (2 concurrent long-ctx agents, un-preempted)
# max-num-seqs=1 (one long request, never queued)
# ⚠️ vision + near-max ctx: drop max-num-seqs/mem-util (thin headroom at 262K).
- --max-num-seqs
- "${MAX_NUM_SEQS:-4}"
# Vision tower: max_tokens_per_mm_item=2496 must fit in batched tokens.
- --max-num-batched-tokens
- "4096"
- --trust-remote-code
# Tool-call support — required for soak / agent traffic that includes
# `tools: [...]` array. The vendored tool-parser overlay above also
# contributes here.
- --enable-auto-tool-choice
- --tool-call-parser
- gemma4
# Route Gemma-4's <|channel>thought…<channel|> reasoning trace into
# reasoning_content (vLLM ships Gemma4ReasoningParser). Without it,
# enable_thinking responses leave the trace in `content` → breaks
# JSON/structured output. Safe with thinking OFF (no-ops when the model
# emits no channel tokens). Validated 2026-05-31.
- --reasoning-parser
- gemma4
- --chat-template
- /vllm-workspace/examples/tool_chat_template_gemma4.jinja
# SPEC_N_MAX overridable for matched-config head-to-head against
# qwen3.6-27b/vllm/compose/dual/autoround-int4/int8.yml (Qwen's built-in MTP caps at
# n=3 architecturally, so the canonical apples-to-apples bench uses
# SPEC_N_MAX=3 here).
- --speculative-config
- '{"model":"/root/.cache/huggingface/gemma-4-31b-it-assistant","num_speculative_tokens":${SPEC_N_MAX:-4}}'