|
@@ -0,0 +1,261 @@
|
|
|
|
|
+---
|
|
|
|
|
+bug_id: BUG-2026-08-03T204037
|
|
|
|
|
+status: open
|
|
|
|
|
+severity: high
|
|
|
|
|
+scope: recipes
|
|
|
|
|
+title: "DeepSeek-V4-Flash recipe fails to update to aidendle94/sparkrun-vllm-ds4-gb10:production-3.8"
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+# BUG-2026-08-03T204037: DeepSeek-V4-Flash recipe fails to update to production-3.8
|
|
|
|
|
+
|
|
|
|
|
+## Problem
|
|
|
|
|
+
|
|
|
|
|
+**Actual:** Updating the `deepseek-v4-flash` recipe + `deepseek-v4-flash-dspark`
|
|
|
|
|
+overlay from the working `production-3.75` image to the new
|
|
|
|
|
+`aidendle94/sparkrun-vllm-ds4-gb10:production-3.8` image fails to boot in three
|
|
|
|
|
+distinct, compounding ways:
|
|
|
|
|
+
|
|
|
|
|
+1. **Does not run with the V2 model runner** (`VLLM_USE_V2_MODEL_RUNNER=1`,
|
|
|
|
|
+ carried over from the 3.75 recipe).
|
|
|
|
|
+2. **OOMs with the "tiny kernel"** — the opt-in DSPARK tiny-A8 decode kernel
|
|
|
|
|
+ (`VLLM_B12X_TINY_DECODE=1`) exhausts GPU memory at load time.
|
|
|
|
|
+3. **DeepGEMM fails to compile** when both v2 and tiny are deactivated,
|
|
|
|
|
+ crashing with:
|
|
|
|
|
+ ```
|
|
|
|
|
+ Assertion error (/tmp/dsv4-vllm-...-3dnaf_2y/sources/DeepGEMM/csrc/apis/../
|
|
|
|
|
+ jit_kernels/impls/../heuristics/../../utils/layout.hpp:39): t.dim() == N
|
|
|
|
|
+ ```
|
|
|
|
|
+
|
|
|
|
|
+**Expected:** The recipe should boot on `production-3.8` as it does on
|
|
|
|
|
+`production-3.75`, with DSpark speculative decoding (k=5) and the b12x MoE
|
|
|
|
|
+backend.
|
|
|
|
|
+
|
|
|
|
|
+**Context:** The cluster is serving live traffic at the time of writing, so
|
|
|
|
|
+no runtime/boot experiment could be performed. This investigation is based on
|
|
|
|
|
+non-invasive image inspection only (`docker image inspect`, `docker history`,
|
|
|
|
|
+`docker run` without `--gpus` running `pip show` / source `grep` — never
|
|
|
|
|
+importing vLLM or torch, never allocating GPU memory). All four physical DGX
|
|
|
|
|
+Spark boxes are busy serving `glm52-nf4-lloyd-experts-gptq` on the
|
|
|
|
|
+`4nodes-switch` cluster; the `2nodes-dac` cluster shares the same hardware
|
|
|
|
|
+(different data-plane IPs, same management IPs).
|
|
|
|
|
+
|
|
|
|
|
+**Failed attempts recovered from git:**
|
|
|
|
|
+
|
|
|
|
|
+- Commit `72f40b3` (reverted by `3a783ee`): FROM→3.8, patch path
|
|
|
|
|
+ `/opt/venv`→`/opt/env`, dropped instanttensor, `--load-format auto`,
|
|
|
|
|
+ removed `--attention-backend FLASHINFER_MLA_SPARSE_DSV4` (correct — does
|
|
|
|
|
+ not exist in vLLM 0.21), set `VLLM_ATTENTION_BACKEND=B12X_MLA_SPARSE`,
|
|
|
|
|
+ raised k 5→7, gpu_util 0.84→0.9, max-len 524288→393216, dotted kwargs→
|
|
|
|
|
+ single JSON arg, added `VLLM_B12X_TINY_DECODE=1/MAX_M=8/CHUNK=8`, dropped
|
|
|
|
|
+ 5 stale env vars.
|
|
|
|
|
+- `stash@{0}`: same direction minus tiny-decode, k stays 5, gpu_util 0.84.
|
|
|
|
|
+
|
|
|
|
|
+Both kept `VLLM_USE_V2_MODEL_RUNNER=1` from the 3.75 recipe.
|
|
|
|
|
+
|
|
|
|
|
+## Root Cause Analysis
|
|
|
|
|
+
|
|
|
|
|
+### The two images are different forks, not version bumps
|
|
|
|
|
+
|
|
|
|
|
+`production-3.8` and the working `eugr/spark-vllm-b12x` are *fundamentally
|
|
|
|
|
+different b12x forks*, not a linear version bump from 3.75:
|
|
|
|
|
+
|
|
|
|
|
+| | **production-3.8** ("dsv4/apostolic" harness) | **eugr/spark-vllm-b12x** ("gilded-gnosis") | **production-3.75** (working) |
|
|
|
|
|
+|---|---|---|---|
|
|
|
|
|
+| vLLM | `0.21.1rc1.dev339+g1967a56` (2026-05-28) | `0.1.dev19023+g30038602b` (2026-08-02) | `0.11.2` eldritch |
|
|
|
|
|
+| torch | 2.11.0+cu130 | 2.12.0+cu130 | 2.11+cu130 |
|
|
|
|
|
+| flashinfer | 0.6.12 | 0.6.17 | — |
|
|
|
|
|
+| triton | 3.6.0 | 3.7.0 | — |
|
|
|
|
|
+| b12x | **standalone** `/opt/b12x` v0.15.3 | **integrated into vLLM** | standalone |
|
|
|
|
|
+| DeepGEMM | vendored src at `/opt/vllm-apostolic/.deps/deepgemm-src/` (commit `1f2f161`) | not visible | — |
|
|
|
|
|
+| base | micromamba/conda `/opt/env` | nvidia/cuda:13.0.2-devel-ubuntu24.04 | `/opt/venv` |
|
|
|
|
|
+| NCCL | 2.30.4 (conda) | 2.28.3 (apt) | 2.30.4 |
|
|
|
|
|
+| instanttensor | **absent** | 0.1.9 present | present |
|
|
|
|
|
+| launch | wrapper `/usr/local/bin/dsv4-vllm-entrypoint`; base `CMD sleep infinity` | `ENTRY /opt/nvidia/nvidia_entrypoint.sh` | direct `vllm` |
|
|
|
|
|
+
|
|
|
|
|
+**Provenance** (from `/opt/dsv4/provenance.json` in the 3.8 image):
|
|
|
|
|
+`production-3.8` is built by a "dsv4" harness (config sha
|
|
|
|
|
+`f9dcce6c...`, artifact `dsv4-vllm-sm120-20260528-arm64-nccl2304-linux-arm64-2a32b9cc4d9f`),
|
|
|
|
|
+base image `mambaorg/micromamba:2.3.3`, conda env `vllm` at `/opt/env`. The
|
|
|
|
|
+deepgemm source is a "sanitized DeepGEMM export via `DEEPGEMM_SRC_DIR` and
|
|
|
|
|
+`VLLM_DEEPGEMM_SRC`" (commit `1f2f161dba747b7c12671d017f7c88e1249c3d3e`).
|
|
|
|
|
+
|
|
|
|
|
+### production-3.8 is a "clean-overlay" base meant to be launched via a wrapper
|
|
|
|
|
+
|
|
|
|
|
+The decisive structural difference: `production-3.8` is a clean base
|
|
|
|
|
+(`CMD ["sleep","infinity"]`, `ENTRY null`) designed to be launched via
|
|
|
|
|
+`/usr/local/bin/dsv4-vllm-entrypoint`, which:
|
|
|
|
|
+
|
|
|
|
|
+- prepends `/opt/env/bin` to `PATH`,
|
|
|
|
|
+- sets the JIT toolchain env (`DG_JIT_NVCC_COMPILER=/opt/env/bin/nvcc`,
|
|
|
|
|
+ `FLASHINFER_NVCC`, `PYTORCH_NVCC`, `NVCC_PREPEND_FLAGS` with the CCCL
|
|
|
|
|
+ include path, `CUDAHOSTCXX`),
|
|
|
|
|
+- sets the JIT cache dirs (`DG_JIT_CACHE_DIR=/cache/deep_gemm_jit`,
|
|
|
|
|
+ `FLASHINFER_WORKSPACE_BASE`, `VLLM_CACHE_ROOT`, `TILELANG_CACHE_DIR`,
|
|
|
|
|
+ `TRITON_CACHE_DIR`, `TORCHINDUCTOR_CACHE_DIR`, `TORCH_EXTENSIONS_DIR`),
|
|
|
|
|
+- `mkdir -p`s those dirs (they do not exist in the bare image),
|
|
|
|
|
+- then `exec vllm "$@"`.
|
|
|
|
|
+
|
|
|
|
|
+By keeping `entrypoint = ["vllm", "serve"]` (the 3.75 contract), the recipe
|
|
|
|
|
+**bypasses the wrapper**, so the image's intended launch contract is broken.
|
|
|
|
|
+nvcc discovery happens to still work (it is on PATH and `CUDA_HOME` is baked
|
|
|
|
|
+into the image env), but the cache-dir layout and the CCCL include paths are
|
|
|
|
|
+unset — making DeepGEMM JIT fragile. `production-3.75` worked because it was a
|
|
|
|
|
+self-contained serving image where vLLM was directly runnable with all env
|
|
|
|
|
+baked in.
|
|
|
|
|
+
|
|
|
|
|
+### Symptom #3 — DeepGEMM `layout.hpp:39` `t.dim() == N` (the boot-blocker)
|
|
|
|
|
+
|
|
|
|
|
+The real failing file is
|
|
|
|
|
+`/opt/vllm-apostolic/.deps/deepgemm-src/csrc/utils/layout.hpp` (the stack path
|
|
|
|
|
+`apis/../jit_kernels/impls/../heuristics/../../utils/layout.hpp` resolves
|
|
|
|
|
+there). Line 39:
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+template <int N>
|
|
|
|
|
+static auto get_shape(const torch::Tensor& t) {
|
|
|
|
|
+ DG_HOST_ASSERT(t.dim() == N); // ← line 39, N is a compile-time template arg
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+This is **not** a missing-nvcc / cache-dir problem. It is a **tensor-rank
|
|
|
|
|
+contract violation**: vLLM calls DeepGEMM's `transform_sf_into_required_layout`
|
|
|
|
|
+(bound in `csrc/apis/layout.hpp`, which calls `get_shape<N>()`) passing a
|
|
|
|
|
+scale-factor tensor whose rank does not match the template specialization. The
|
|
|
|
|
+binding `check_sf_layout` asserts `sf.dim() == 2`; the grouped / mega-moe path
|
|
|
|
|
+expects rank-3 (`get_shape<3>`). A rank mismatch trips line 39.
|
|
|
|
|
+
|
|
|
|
|
+It fires during MoE weight-loading / scale-packing
|
|
|
|
|
+(`vllm/model_executor/layers/quantization/utils/fp8_utils.py:1048` and
|
|
|
|
|
+`.../fused_moe/oracle/mxfp4.py:758`), **before** any kernel runs. The recipe's
|
|
|
|
|
+`--moe-backend b12x` does *not* bypass it: `transform_sf_into_required_layout`
|
|
|
|
|
+is called during MoE weight prep regardless of the selected MoE compute
|
|
|
|
|
+backend.
|
|
|
|
|
+
|
|
|
|
|
+This is a **fork bug / version-skew** between this specific DeepGEMM fork
|
|
|
|
|
+(`1f2f161`) and what vLLM 0.21's `transform_sf_into_required_layout` callers
|
|
|
|
|
+feed it — not an env mistake, and the one that blocks boot entirely.
|
|
|
|
|
+
|
|
|
|
|
+### Symptom #2 — "OOMs with tiny kernel"
|
|
|
|
|
+
|
|
|
|
|
+The opt-in tiny-decode kernel (`/opt/tinydec/tiny_hook.py`,
|
|
|
|
|
+`/opt/tinydec/tiny_moe.py`) is a runtime monkey-patch on
|
|
|
|
|
+`B12xExperts.apply`/`_get_or_prepare_fp4_moe_weights` that routes small-M
|
|
|
|
|
+MoE decode through a custom w4a8mx Triton kernel. Its own docstring states
|
|
|
|
|
+the OOM mechanism directly:
|
|
|
|
|
+
|
|
|
|
|
+> *"Chunking is what keeps the load-time repack off the OOM path that
|
|
|
|
|
+> crashed the node."*
|
|
|
|
|
+> *"the `.int()` expansion plus 9-dim reshape temporaries are what OOM'd
|
|
|
|
|
+> the head node when run unchunked at E=256."*
|
|
|
|
|
+
|
|
|
|
|
+The hook repacks b12x's `w4a16` packed tensors into tiny's `w4a8_mx` format
|
|
|
|
|
+**on GPU at load time**, per expert-tensor, caching the result. The failed
|
|
|
|
|
+attempt set `--gpu-memory-utilization 0.9` **together with**
|
|
|
|
|
+`VLLM_B12X_TINY_DECODE=1` — the repack needs GPU headroom that 0.9 does not
|
|
|
|
|
+leave. `VLLM_B12X_TINY_CHUNK=8` exists precisely to bound the peak, but does
|
|
|
|
|
+not help when the utilization ceiling leaves no room for the repack buffers.
|
|
|
|
|
+
|
|
|
|
|
+The image label confirms the gating contract:
|
|
|
|
|
+`org.tinydec.flag=VLLM_B12X_TINY_DECODE`,
|
|
|
|
|
+`org.tinydec.kernel=w4a8mx-tiny-decode`,
|
|
|
|
|
+`org.tinydec.gates="A:cos0.999999 b0:0.81x b:cos0.999990 M:99%"`.
|
|
|
|
|
+
|
|
|
|
|
+### Symptom #1 — "does not run with v2"
|
|
|
|
|
+
|
|
|
|
|
+The V2 model runner is experimental in this fork. `gpu_worker.py:803` has a
|
|
|
|
|
+hard `assert not self.use_v2_model_runner` when **pipeline-parallel +
|
|
|
|
|
+sequence-parallel** is active (not the case here, TP-only), so the v2 failure
|
|
|
|
|
+here is elsewhere in the V2 warmup path (`warmup_kernels` runs a full
|
|
|
|
|
+`execute_model + sample_tokens` JIT compile). The known-working `glm52`
|
|
|
|
|
+recipe does **not** set `VLLM_USE_V2_MODEL_RUNNER`. Carrying `=1` over from
|
|
|
|
|
+the 3.75 recipe is a separate, independent source of fragility.
|
|
|
|
|
+
|
|
|
|
|
+## Reproduce
|
|
|
|
|
+
|
|
|
|
|
+Could not be reproduced at boot time (cluster busy serving). The diagnosis is
|
|
|
|
|
+based on static inspection of both images on `dgx1.lan` (management IP
|
|
|
|
|
+`192.168.88.14`, the `2nodes-dac` cluster entry, same physical box as the
|
|
|
|
|
+serving `4nodes-switch` rank 0):
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+# image versions (metadata only, no CUDA import, no GPU allocation):
|
|
|
|
|
+docker run --rm --entrypoint bash aidendle94/sparkrun-vllm-ds4-gb10:production-3.8 \
|
|
|
|
|
+ -lc '/opt/env/bin/pip3 show vllm torch flashinfer-python triton'
|
|
|
|
|
+
|
|
|
|
|
+# the wrapper the recipe must use:
|
|
|
|
|
+docker run --rm --entrypoint bash aidendle94/sparkrun-vllm-ds4-gb10:production-3.8 \
|
|
|
|
|
+ -lc 'cat /usr/local/bin/dsv4-vllm-entrypoint'
|
|
|
|
|
+
|
|
|
|
|
+# the failing assertion source:
|
|
|
|
|
+docker run --rm --entrypoint bash aidendle94/sparkrun-vllm-ds4-gb10:production-3.8 \
|
|
|
|
|
+ -lc 'sed -n "30,45p" /opt/vllm-apostolic/.deps/deepgemm-src/csrc/utils/layout.hpp'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+All inspection used `docker run` **without `--gpus`** (no GPU device
|
|
|
|
|
+passthrough → no competition with the live `glm52` serve), running only
|
|
|
|
|
+`pip show` / `find` / `grep` / `sed` — never importing vLLM or torch, never
|
|
|
|
|
+allocating GPU memory.
|
|
|
|
|
+
|
|
|
|
|
+The git-recovered failed attempts:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+git show 72f40b3 # reverted 3.8 attempt + tiny-decode
|
|
|
|
|
+git stash show -p stash@{0} # 3.8 attempt, no tiny-decode
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Fix direction
|
|
|
|
|
+
|
|
|
|
|
+Given all nodes are occupied (no runtime test possible), the safe path is:
|
|
|
|
|
+
|
|
|
|
|
+1. **Use the `dsv4-vllm-entrypoint` wrapper** as the recipe entrypoint so the
|
|
|
|
|
+ image's intended JIT env + cache dirs are active:
|
|
|
|
|
+ ```toml
|
|
|
|
|
+ entrypoint = ["/usr/local/bin/dsv4-vllm-entrypoint", "serve"]
|
|
|
|
|
+ ```
|
|
|
|
|
+2. **Mirror / rely on the wrapper env** (`DG_JIT_NVCC_COMPILER`,
|
|
|
|
|
+ `FLASHINFER_NVCC`, `DG_JIT_CACHE_DIR=/cache/deep_gemm_jit`, …) and add an
|
|
|
|
|
+ `[[extra_mounts]]` for `/cache` so the JIT compile cache persists across
|
|
|
|
|
+ restarts (the wrapper `mkdir -p`s these under `/cache`).
|
|
|
|
|
+3. **Drop the V2 runner** (unset `VLLM_USE_V2_MODEL_RUNNER`) — experimental in
|
|
|
|
|
+ this fork; the working `glm52` recipe does not use it.
|
|
|
|
|
+4. **Drop tiny-decode** (unset `VLLM_B12X_TINY_DECODE`) for first bring-up —
|
|
|
|
|
+ it is an opt-in *experimental* kernel with a load-time GPU repack that
|
|
|
|
|
+ needs real headroom. Get a stable boot first, then re-enable with
|
|
|
|
|
+ `--gpu-memory-utilization` ≤ 0.84 and `VLLM_B12X_TINY_CHUNK=8`.
|
|
|
|
|
+5. **The DeepGEMM `t.dim() == N` crash needs a real fix, not env** — it fires
|
|
|
|
|
+ during MoE scale packing on this fork. Options to investigate once a node
|
|
|
|
|
+ frees up:
|
|
|
|
|
+ - try `--moe-backend deep_gemm_mega_moe` or `--moe-backend auto` (instead
|
|
|
|
|
+ of `b12x`) to see if a different backend avoids the rank-3 scale path;
|
|
|
|
|
+ - check whether the model's quant config (NVFP4 vs MXFP4) selects a
|
|
|
|
|
+ DeepGEMM template that mismatches — `mxfp4.py`'s oracle `_pack` calls
|
|
|
|
|
+ `transform_sf_into_required_layout` with `recipe=(1,1,32)` and
|
|
|
|
|
+ `num_groups=num_experts`, feeding a 3D tensor;
|
|
|
|
|
+ - if it persists, this is a bug to report upstream against the `1f2f161`
|
|
|
|
|
+ DeepGEMM fork — the assertion message is the fork's own contract.
|
|
|
|
|
+
|
|
|
|
|
+The `eugr/spark-vllm-b12x` image (recipe `deepseek-v4-flash-eugr`,
|
|
|
|
|
+commit `a8e4932`) sidesteps *all* of this because it is a different, newer
|
|
|
|
|
+fork (`gilded-gnosis`, 2026-08-02) where b12x is integrated and the DSV4
|
|
|
|
|
+path is already wired — which is why that recipe boots cleanly. The two
|
|
|
|
|
+images are **not interchangeable** at the recipe level; a 3.8 recipe must
|
|
|
|
|
+respect the wrapper-entrypoint + clean-base contract.
|
|
|
|
|
+
|
|
|
|
|
+## Acceptance Criteria
|
|
|
|
|
+
|
|
|
|
|
+- [ ] `deepseek-v4-flash` (or a new `-dspark38` variant) boots on
|
|
|
|
|
+ `production-3.8` via the `dsv4-vllm-entrypoint` wrapper.
|
|
|
|
|
+- [ ] No `VLLM_USE_V2_MODEL_RUNNER`, no `VLLM_B12X_TINY_DECODE` in the first
|
|
|
|
|
+ stable 3.8 recipe.
|
|
|
|
|
+- [ ] `/cache` JIT dirs persist via an `[[extra_mounts]]` (restarts reuse
|
|
|
|
|
+ compiled kernels).
|
|
|
|
|
+- [ ] The DeepGEMM `t.dim() == N` crash is either resolved (backend/quant
|
|
|
|
|
+ config change) or confirmed as an upstream fork bug with a workaround.
|
|
|
|
|
+- [ ] DSpark k=5 decode acceptance + throughput verified against the 3.75
|
|
|
|
|
+ baseline once a node is free.
|
|
|
|
|
+
|
|
|
|
|
+## Resolution
|
|
|
|
|
+
|
|
|
|
|
+Open. A `deepseek-v4-flash-dspark38` recipe + overlay applying points 1–4 is
|
|
|
|
|
+the recommended next step (drafted but not yet written, pending node
|
|
|
|
|
+availability for a boot test). Point 5 (DeepGEMM rank contract) is the
|
|
|
|
|
+remaining unknown and the most likely blocker.
|