Procházet zdrojové kódy

glm53-full-mxfp4-4x-dspark8: full GLM-5.3 + DSpark K8 drafter recipe (131K DCP1, 9 GiB KV pin, skip-layers for draft KV, in-container patch for shared-page inheritance); boot 6 up, quality gate clean

Alexander Korolev před 1 měsícem
rodič
revize
960dc62483

+ 70 - 0
patches/patch_dspark_draft_page.py

@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+"""Patch the r37 DFlash draft attention (model_executor/models/qwen3_dflash.py)
+so the DRAFT model's SlidingWindowSpec does not inherit the target's
+sparse-MLA shared-page padding.
+
+Why: the target's B12X sparse-MLA path (GlmMoeDsa, full GLM-5.3) writes
+``cache_config.skip_page_size_padded`` during backend resolution
+(platforms/interface.py). The draft model's DFlash sliding-window layers
+(qwen3_dflash.py DFlashAttention.get_kv_cache_spec) then inherit that value
+into their SlidingWindowSpec. The draft's bf16 page (head 64, block 256) is
+larger than the target's padded MLA page, so KV layout resolution trips
+    assert page_size_padded >= unpadded_page_size_bytes
+(vllm/v1/kv_cache_interface.py).
+
+The draft KV group is independent of the target's; it must use its own
+(unpadded) page size — page_size_padded=None does exactly that.
+
+Usage (in-container recipe mod, idempotent, refuses on drift):
+    python3 patch_dspark_draft_page.py [--check]
+"""
+import os
+import sys
+import vllm
+
+FILE = os.path.join(vllm.__path__[0], "model_executor/models/qwen3_dflash.py")
+
+OLDER = (
+    "page_size_padded=getattr(\n"
+    '                    vllm_config.cache_config, "skip_page_size_padded", None\n'
+    "                ),"
+)
+NEWER = (
+    "page_size_padded=None,  # sm121: draft KV group must not inherit the\n"
+    "                    # target sparse-MLA shared-page padding (see\n"
+    "                    # patches/patch_dspark_draft_page.py header)\n"
+    "                "
+)
+
+
+def _consistent(src, old, new):
+    co, cn = src.count(old), src.count(new)
+    if cn >= 1:
+        return cn == 1 and co <= new.count(old)
+    return co == 1
+
+
+def main():
+    check = "--check" in sys.argv
+    src = open(FILE).read()
+    if check:
+        if not _consistent(src, OLDER, NEWER):
+            raise SystemExit(f"CHECK FAIL: {FILE} is neither pristine nor patched")
+        print(f"check OK: {FILE} state is consistent")
+        return
+    if src.count(OLDER) != 1:
+        raise SystemExit(
+            f"refusing: {FILE} old anchor matched {src.count(OLDER)}x; "
+            f"re-sync patch"
+        )
+    out = src.replace(OLDER, NEWER)
+    assert out.count(NEWER) == 1
+    import py_compile
+
+    open(FILE, "w").write(out)
+    py_compile.compile(FILE, doraise=True)
+    print(f"patched {FILE} (draft SlidingWindowSpec: no shared-page inheritance)")
+
+
+if __name__ == "__main__":
+    main()

+ 327 - 0
recipes/glm53-full-mxfp4-4x-dspark8/recipe.toml

@@ -0,0 +1,327 @@
+# GLM-5.3 (FULL, non-Flash) MXFP4 — RedHatAI official quant — 4× DGX Spark, TP=4.
+#
+# REPLACES the failed full-model variant: `local-inference-lab/GLM-5.3-NVFP4-Spark`
+# (406 GiB → 101.5 GiB weights/node → OOM-proven on 4 nodes 2026-09-08/09: the
+# engine crash-restarted and began a SECOND full load while the first copy was
+# still resident → 2×101.5 > 121.63 GiB → all four nodes OOM-thrashed, sshd down
+# on 3 of 4, HA power-cycle required). That checkpoint is abandoned (recipes
+# glm53-full-nvfp4-spark-lab / -aiden / glm53-spark-nvfp4-4x deleted).
+#
+# THIS checkpoint: RedHatAI/GLM-5.3-MXFP4 — 403.4 GB (375.6 GiB) → 100.85 GB
+# (93.9 GiB) weights/node on 4x. ~7.6 GiB/node more headroom than the NVFP4
+# attempt, which is what makes 4x plausible at all. LLM-Compressor
+# (compressed-tensors) quant: "mxfp4-pack-quantized", weights FP4-e2m1 group-32
+# with E8M0 scales, dynamic per-group FP4 activations; mlp.gate / lm_head /
+# embed_tokens / eh_proj / indexer.{wk,weights_proj} stay bf16.
+#   Model:  https://huggingface.co/RedHatAI/GLM-5.3-MXFP4
+#   Parent: zai-org/GLM-5.3 (FP8). Card: gsm8k 96.36 vs 97.60 (FP8) = 98.7%
+#   recovery; ifeval 89.25 vs 93.53 = 95.4%.
+#   Card's serving recipe (4× B200): --kv-cache-dtype fp8_e4m3 --tp 4
+#   --reasoning-parser glm45 --speculative-config '{"method":"mtp","num_speculative_tokens":5}'
+#
+# GEOMETRY (config.json, verified 2026-09-13): arch GlmMoeDsaForCausalLM
+# (model_type glm_moe_dsa), 78 layers, hidden 6144, 256 routed experts + 1
+# shared, top-8, moe_inter 2048, MLA (kv_lora 512 / q_lora 2048 / qk_nope 192 /
+# qk_rope 64 / v 256), DSA lightning indexer (index_topk 2048), 1M native ctx,
+# first_k_dense_replace 3, num_nextn_predict_layers 1 (MTP head IN the
+# checkpoint), 141 shards, no separate drafter.
+# ~740B total params / ~40B active. The Flash variant we serve in prod is
+# 320B/18B (45 layers, hidden 4096, 288 experts) — this is a different model.
+#
+# ── BASE STACK: our own jovian r35 build (docker-images/glm53-jovian-r27 +
+#    build_profile glm53-r35 → image glm53-jovian-r35:local) ────────────────
+# Selected 2026-09-13. Evidence (all verified by read-only inspection of the
+# images on dgx1):
+#   * registers the full-model arch: registry.py →
+#     "GlmMoeDsaForCausalLM": ("vllm.models.deepseek_v32", ...) — the NEW
+#     `deepseek_v32` implementation, which is "MLA + a lightning indexer that
+#     selects the top-k tokens for a sparse MLA" and dispatches to an
+#     NVIDIA-specific path (deepseek_v32/nvidia/model.py:
+#     DeepseekV32ForCausalLM as GlmMoeDsaForCausalLM). That is exactly the
+#     GB10/sm_121a target; the lab fork and aiden's image register the same
+#     class from the older `deepseek_v2` module instead.
+#   * ships the MXFP4 scheme: model_executor/layers/quantization/
+#     compressed_tensors/schemes/compressed_tensors_w4a4_mxfp4.py
+#     ("mxfp4-pack-quantized") → the RedHat checkpoint loads natively.
+#   * it is OUR reproducible clone-at-pin build (vLLM de982a50 + b12x 98086604,
+#     r35 lock 2026-09-11 = newest published lab composition), all gates green,
+#     patches ours to write — and it is the SAME image our GLM-5.3-Flash 4x
+#     prod runs, so the A/B vs prod is clean and the JIT caches are warm.
+#   * the b12x MoE/attention backends are the GB10-tuned ones our GLM prod
+#     uses (--moe-backend b12x / --attention-backend B12X).
+# Rejected: `glm53-flash-nvfp4-lab:local` and aiden's
+# `aidendle94/sparkrun-vllm-glm53-gb10:production-1.2` — both older vLLM
+# (0.1.dev20051+g487ecf187, `deepseek_v2` GLM class); the lab fork needed two
+# SM120 mla_attention hotfix mods to boot the full model at all
+# (masked_mha_available AttributeError + forward_mha NotImplementedError, both
+# never re-tested — the lane parked). Keep them as a fallback reference only:
+# if the r35 deepseek_v32 path trips on the DSA/indexer kernels, port those
+# two sed mods (documented in the deleted glm53-full-nvfp4-spark-lab history)
+# onto the lab image and re-point docker_image/docker_image_dir.
+#
+# ── MEMORY PLAN (the binding constraint — read before the first boot) ──────
+#   121.63 GiB unified/node. Idle free: 116-117 GiB.
+#     weights          93.9 GiB (403.4 GB / 4)  ← ~101.5 was fatal
+#     vLLM procs+docker ~12 GiB
+#     KV pin            8 GiB (--kv-cache-memory-bytes)
+#     activations + CUDA graphs ~4 GiB
+#   ⇒ ~118 GiB — tight but inside the node. GMU 0.85 is the ADMISSION
+#   guardrail only (free ≥ 0.85×121.63 = 103.4 GiB; 0.88 = 107.0 GiB fails
+#   once the ~12 GiB of starting processes are counted, the 2026-09-08
+#   #48140 class) — the KV pool is pinned, so GMU does not change capacity.
+#
+#   WHY DCP4: the full model's attention KV is NOT the Flash lane's compact
+#   MLA cache. The parked run measured ~54 KB/token (4 GiB ↔ ~79.6K tokens;
+#   "524288 would need 26.34 GiB") vs the Flash lane's ~2 KB/token at 512K.
+#   Decode-context-parallel-4 shards the KV across the 4 ranks, so the 8 GiB
+#   pin buys 32 GiB aggregate → a single 524288-token request needs ~27 GiB
+#   aggregate (≈6.8 GiB/rank) and just fits. Without DCP a 512K request is
+#   impossible behind these weights. CAVEAT: DCP4 is qualified in r35 for the
+#   Flash model (CP1/CP4 shapes); it is UNVERIFIED for the full model — if the
+#   boot rejects it (the GLM C4 page-stride / interleave-multiple-of-4 gates
+#   that block DFlash2 on 2x), drop --decode-context-parallel-size +
+#   --cp-kv-cache-interleave-size and lower --max-model-len to fit ~10 GiB of
+#   aggregate KV (~180K tokens).
+#
+#   --max-model-len 524288 = the operator's floor (128K/131K profiles are not
+#   usable for us). --max-num-seqs 8 keeps the pool from being shredded by
+#   short requests; the admission scheduler handles the rest.
+#
+# ── BOOT PLAN / HAZARD ─────────────────────────────────────────────────────
+#   The 2026-09-08 disaster was NOT a bad weight footprint per se — it was a
+#   post-load crash + auto-restart that started a SECOND full load on top of
+#   the first. Watch the first boot: if the engine logs a second
+#   "Loading safetensors" pass, kill immediately (`sm121 stop`), do not wait.
+#   Always `sm121 serve --keep` for the first/unknown boot (default is --rm,
+#   which wipes the crash logs).
+#   Ladder if the boot dies: (1) disable DCP4, max-model-len 131072, KV pin
+#   4 GiB (prove stack+checkpoint end-to-end, the parked lane's approach);
+#   (2) then re-add DCP4 + 512K; (3) only then add spec.
+#   Boot markers to record: "Model loading took X GiB", the KV line
+#   ("GPU KV cache size: N tokens, Maximum concurrency for 524288 tokens per
+#   request: Mx"), "Application startup complete". MAX_JOBS=2 below is the
+#   compile-OOM guard (12 parallel nvcc jobs × ~3 GiB behind 94 GiB of weights
+#   = cicc OOM-killed, 2026-09-04 lesson).
+#
+# ── MXFP4 vs NVFP4: what changed, and what is already in our tree ──────────
+# The full-model NVFP4 lane is retired (see above), so this checkpoint is the
+# lab's MXFP4 family: routed-expert weights FP4-e2m1 with power-of-two (E8M0)
+# group-32 scales — the same shape as their own GLM-5.1 MXFP4 repack, which is
+# why their GLM-5.1 runbook (models/glm-5.1-mxfp4.md) is the reference for
+# this lane even though GLM-5.3 moved on: the MXFP4 ingestion path, the
+# FlashInfer Cutlass MXFP4xMXFP8 MoE route, the standard-SwiGLU rule and the
+# "do not set B12X_MOE_FORCE_A16" warning all still apply. That runbook also
+# unsets empty NCCL graph variables before launch
+# (NCCL_GRAPH_FILE / NCCL_GRAPH_DUMP_FILE / VLLM_B12X_MLA_EXTEND_MAX_CHUNKS)
+# — unset them in the shell if a previous lane exported them empty; an empty
+# value is not the same as an unset one to NCCL.
+#
+# ── SPECULATIVE DECODING ───────────────────────────────────────────────────
+#   NOT enabled on the first boot: the checkpoint's MTP head is real
+#   (num_nextn_predict_layers=1) and the card suggests mtp k=5, but spec adds
+#   draft KV + graph shapes onto a memory budget that has no room for
+#   surprises, and the full-model MTP path is unproven on this stack. After a
+#   clean boot + smoke + bench, add:
+#     "--speculative-config", """'{"method":"mtp","num_speculative_tokens":3,"attention_backend":"B12X"}'"""
+#   (k=3 = our Flash prod's verified depth; try 5 after k=3 is clean).
+#
+# PREREQUISITE: weights in the shared NAS HF cache on all 4 nodes
+#   (models--RedHatAI--GLM-5.3-MXFP4, 403.4 GB, 141 shards) —
+#   `hf download RedHatAI/GLM-5.3-MXFP4`; HF_HUB_OFFLINE=1 below makes a
+#   missing shard a hard load failure. NAS had 16 TB free at 2026-09-13.
+#
+# A/B TARGET: our GLM-5.3-Flash r35 MTP3 4x prod (dev65/seed 42, hardmode):
+#   163/176 (93), 1,554 pp / 49.6 tg / 1,544 ms TTFT @d0 c1, KV 8.06M @512K.
+#   This lane is a capability/quality lane first (does the full model fit and
+#   hold 512K on 4 nodes), throughput second.
+
+engine = "vllm"
+name = "GLM-5.3 (full) MXFP4 (r37 img, 4x, DSpark K8 @131K)"
+# Image: the r37 DS4.1 build (2026-09-14, night shift) - the r35 tree's DCP4
+# sparse-INDEXER path asserts interleave==1 and then crashes in the DCP sparse
+# decode reshape during graph capture ("shape '[16, 16, 512]' is invalid for
+# input of size 524288" - the x4 DCP factor in the attention.py _sparse_
+# decode). r37 is the newer DS4.1 lock whose DCP sparse-MLA path is the
+# qualified one (same tree family as our DCP2/DFlash2 work); the GlmMoeDsa
+# arch + MTP + b12x ship in the r37 image (verified 2026-09-13).
+docker_image = "jovian-r37-ds41:local"
+docker_image_dir = "glm53-jovian-r27"
+# NOTE: build_profile must stay a TOP-LEVEL key — a bare key after
+# [[extra_mounts]] would parse into the last mount table and be silently
+# ignored (2026-09-08).
+build_profile = "jovian-r37-ds41"
+entrypoint = ["vllm", "serve"]
+model = "RedHatAI/GLM-5.3-MXFP4"
+parent_model = "zai-org/GLM-5.3"
+required_nodes = 4
+tensor_parallel_size = 4
+pipeline_parallel_size = 1
+distributed_backend = "mp"
+shm_size = "32gb"
+
+args = [
+    # R31/r35 delta (their qualified locks): adaptive prefill/decode
+    # compute-time sharing with a fixed 40% prefill floor.
+    "--prefill-compute-share", "0.4",
+    # Stable served name — quant-agnostic (shared across glm53 lanes)
+    "--served-model-name", "glm53",
+    "--trust-remote-code",
+    "--distributed-executor-backend", "mp",
+    # Admission guardrail only (KV is pinned below): 0.85×121.63 = 103.4 GiB.
+    # 0.88 (107.0) is the Flash lane's value and is too tight here — the full
+    # model's weights leave ~24 GiB before the engine's own ~12 GiB.
+    "--gpu-memory-utilization", "0.85",
+    # 9 GiB/rank pin (DCP1, no context parallel). The no-spec 131K anchor ran
+    # at 8 GiB (pool 159,232 tokens); +1 GiB covers the draft model's own
+    # KV group: the engine needs 8.08 GiB for one 131072-token request + the
+    # drafter, and the 8.0 GiB pin was 80 MB short (boot 5, 2026-09-18).
+    "--kv-cache-memory-bytes", "9663676416",
+    # NO DCP here: the DCP>1 sparse-decode path (deepseek_v32/attention.py)
+    # crashes on both r35 and r37 trees (the 512K-on-4x blocker). 512K on
+    # 4x is out; long context is the 8x DCP1 story (P2).
+    # No --kv-cache-dtype: let the r35 fork pick its native default for
+    # GlmMoeDsa attention (the Flash lane's explicit fp8/fp8_ds_mla spellings
+    # both failed auto-selection on this build, and those were Flash-MLA
+    # knobs). The card's --kv-cache-dtype fp8_e4m3 is the fallback spelling
+    # if the pool needs to shrink.
+    # DSpark drafter fix (boot 1, 2026-09-18): the target's B12X sparse-MLA
+    # layer normalizes the shared cache config to fp8_ds_mla (mla_attention
+    # _canonicalize_sparse_mla_kv_cache_dtype rewrites cache_config.cache
+    # dtype in place); the draft model's non-MLA DFlash attention (head 64)
+    # is built after that and the r37 selector finds no backend for
+    # (head 64, bf16, fp8_ds_mla, non-MLA) → ValueError at init. The
+    # drafter's DFlash layers are sliding-window (2048), the target's DSA
+    # layers are not — skipping KV quant for the sliding_window pattern
+    # gives the draft layers auto (bf16) KV while the target keeps
+    # fp8_ds_mla. Explicit --kv-cache-dtype is NOT the lever: the MLA
+    # normalization overrode fp8_e4m3 back to fp8_ds_mla (boot 2).
+    "--kv-cache-dtype-skip-layers", "sliding_window",
+    "--attention-backend", "B12X",
+    # MXFP4 kernel routing (measured on the first boot, 2026-09-13 ~23:10 UTC):
+    # - MoE: the compressed-tensors W4A4-MXFP4 scheme picks MarlinExperts on
+    #   its own (A16 dequant GEMM - correct on GB10). The --moe-backend flag
+    #   does not reach that scheme (verified inert: it still logged "Using
+    #   MarlinExperts for MXFP4 MoE" with the flag set), so we do not set it.
+    # - LINEAR: the default priority [FlashInfer, Marlin, Humming, B12x]
+    #   starts with the FlashInfer kernel, whose is_supported() wrongly
+    #   accepts capability 121 (the numeric has_device_capability(100) check
+    #   passes on GB10) while the cute-dsl mm_fp4 GEMM it calls rejects it at
+    #   runtime with:
+    #     BackendSupportedError: mm_fp4 does not support backend 'cute-dsl'
+    #     with capability 121
+    #   (the first probe boot died there, kv_b_proj dequant in mla_attention.py).
+    # --linear-backend b12x forces the B12xMxFp4LinearKernel - "MXFP4 linear
+    # through the native B12X SM120 dense GEMM" - the right path on GB10,
+    # verified per component: is_device_capability_family(120)=True, b12x
+    # blockscaled.is_supported()=True, and the cute-dsl input quantize
+    # (flashinfer_mxfp4_quantize) works on capability 121; only the cute-dsl
+    # mm_fp4 GEMM is unsupported there. Boot log to look for:
+    #   "Using B12xMxFp4LinearKernel for MXFP4 GEMM"
+    # (The lab's GLM-5.1 runbook describes the same kernel family for the
+    # quant_method=mxfp4 ingestion path - this checkpoint goes through the
+    # compressed-tensors path, which is why --moe-backend was the wrong lever.)
+    # First attempt: b12x (the native SM120 GEMM) - correct selection, but the
+    # unconditional b12x warmup (b12x_warmup.py, no config gate) crashes on
+    # this checkpoint's layer shapes during kernel compile:
+    #   RuntimeError: Expected source and destination tensors to have the same
+    #   rank, but got 3 and 4  (b12x/gemm/blockscaled/_linear.py dense_gemm,
+    #   via _apply_b12x_mxfp4_linear on a synthetic zero source)
+    # so the mxfp4 LINEAR layers run on Marlin (weight-only A16 dequant GEMM -
+    # the same family as the MarlinExperts the MoE scheme picked). Slower than
+    # native FP4 for the attention projections, but the MoE experts (the
+    # dominant cost) are Marlin either way. The b12x warmup rank bug is
+    # filed-worthy for the lab (RedHatAI GLM-5.3-MXFP4 on GB10/sm_121a).
+    "--linear-backend", "marlin",
+    # pure I/O loader (kept across every GLM config)
+    "--load-format", "instanttensor",
+    "--block-size", "256",
+    "--max-model-len", "131072",
+    "--max-num-seqs", "8",
+    "--max-num-batched-tokens", "4096",
+    "--enable-chunked-prefill",
+    "--async-scheduling",
+    "--enable-prefix-caching",
+    # Their proven boot: no flashinfer autotune, no CuTe-DSL warmup (the
+    # autotune scratch at 512K shapes does not fit the headroom, 2026-08-28).
+    "--kernel-config", """
+'{"enable_cutedsl_warmup":false,"enable_flashinfer_autotune":false}'""",
+    "--language-model-only",
+    # A/B vs the no-spec 131K anchor (158/176, 934 pp / 19.0 tg / 2,124 ms):
+    # external DSpark K8 drafter, RedHatAI/GLM-5.3-speculator.dspark (09-16,
+    # v1.0) — a 3-layer Qwen3-style 2.5 GB drafter with Markov + confidence
+    # heads, trained on zai-org/GLM-5.3 (BF16) hidden states, layers
+    # 2/20/39/58/75 aux. Card contract (num_speculative_tokens 8, method
+    # dspark). r37 tree verified to carry the integration (DSparkDraftModel
+    # in config/speculative.py + speculators config loader). Drafter rides
+    # the TP group (draft_tensor_parallel_size defaults to TP). The repo is
+    # in the shared HF cache (snapshot 198b3ced), HF_HUB_OFFLINE=1 below
+    # resolves it from there.
+    "--speculative-config", """
+'{"model":"RedHatAI/GLM-5.3-speculator.dspark","num_speculative_tokens":8,"method":"dspark"}'""",
+    "--tool-call-parser", "glm47",
+    "--enable-auto-tool-choice",
+    "--reasoning-parser", "glm45",
+]
+
+[mods]
+commands = [
+    # r37 tree: the draft SlidingWindowSpec inherits the target sparse-MLA
+    # shared-page padding (cache_config.skip_page_size_padded) and the KV
+    # layout assert fails (page_size_padded < unpadded bf16 draft page).
+    # The patcher lives on the shared HF cache (sm121-patches/) and is
+    # idempotent per boot; repo copy: patches/patch_dspark_draft_page.py.
+    "python3 /root/.cache/huggingface/sm121-patches/patch_dspark_draft_page.py",
+]
+
+[env]
+
+# R31 lock: 16 NCCL channels (their qualified runtime).
+NCCL_NCHANNELS = "16"
+HF_HUB_OFFLINE = "1"
+# RoCEnante (b12x one-shot RoCE all-reduce/all-gather) — default on for r27+
+# lanes since the 2026-09-08 A/B (4x DCP4: decode +31..+90%, quality 160 vs
+# 155; specs/tech-architecture/glm53-recipe-state.md). Needs the CX-7 RoCE
+# fabric (all of ours); single-node serves skip it silently.
+VLLM_ENABLE_ROCE_ALLREDUCE = "1"
+# Compile-OOM guard: first boot JIT-compiles the mxfp8 gemm module with 12
+# parallel nvcc jobs (~3 GiB each) behind ~94 GiB of resident weights →
+# cicc OOM-killed (2026-09-04). flashinfer's run_ninja reads MAX_JOBS.
+MAX_JOBS = "2"
+# NoPE-MLA rope-pad runtime gate (mod baked into the image)
+VLLM_MLA_NOPE_PAD_ROPE = "1"
+VLLM_ENGINE_READY_TIMEOUT_S = "3600"
+# Upstream launch-script env (memory + compile knobs)
+PYTORCH_CUDA_ALLOC_CONF = "expandable_segments:True"
+VLLM_USE_AOT_COMPILE = "0"
+VLLM_USE_STANDALONE_COMPILE = "1"
+VLLM_USE_FLASHINFER_SAMPLER = "1"
+VLLM_USE_BREAKABLE_CUDAGRAPH = "0"
+TORCH_USE_RTLD_GLOBAL = "1"
+VLLM_NO_USAGE_STATS = "1"
+
+# Architecture (aarch64 / sm_121a DGX Spark)
+TORCH_CUDA_ARCH_LIST = "12.1a"
+FLASHINFER_CUDA_ARCH_LIST = "12.1f"
+CUTE_DSL_ARCH = "sm_121a"
+CMAKE_CUDA_ARCHITECTURES = "121"
+OMP_NUM_THREADS = "2"
+
+[[extra_mounts]]
+host = "~/sm121-cache/glm53-jit-121f/b12x"
+container = "/root/.cache/b12x"
+
+# JIT caches persist per-node across serves (was: cold /root/.cache per
+# container -> ~20 min flashinfer/torch recompile every serve). The host
+# dirs are shared between lanes of the same arch — each cache
+# self-namespaces by its engine version key internally.
+[[extra_mounts]]
+host = "~/sm121-cache/jit-121f/flashinfer"
+container = "/root/.cache/flashinfer"
+
+[[extra_mounts]]
+host = "~/sm121-cache/jit-121f/torchinductor"
+container = "/root/.cache/torchinductor"
+
+[[extra_mounts]]
+host = "~/sm121-cache/jit-121f/vllm"
+container = "/root/.cache/vllm"

+ 8 - 0
recipes/glm53-full-mxfp4-4x-dspark8/reports/2026-09-18_134322_dspark8.log

@@ -0,0 +1,8 @@
+^D{"event": "backend_detected", "backend": "vllm"}
+
+╭──────────────────── ⚡ llama-benchy Throughput Benchmark ────────────────────╮
+│ glm53                                                                        │
+│ pp=[2048]  tg=[128]  depth=[0, 4096, 8192]  concurrency=[1, 2]  runs=3       │
+│ latency=generation                                                           │
+╰──────────────────────────────────────────────────────────────────────────────╯
+