Explorar o código

spec: dsv41-1017 tuning A/B (EP2 decode win, fp4-indexer negative, engram MAX_IDS vs chunked-prefill coupling, slow-NAS boot desync)

Alexander Korolev hai 5 días
pai
achega
000f022481
Modificáronse 1 ficheiros con 121 adicións e 0 borrados
  1. 121 0
      specs/tech-architecture/dsv41-1017-tuning.md

+ 121 - 0
specs/tech-architecture/dsv41-1017-tuning.md

@@ -0,0 +1,121 @@
+# DSV4.1-1017 4x SGLang — performance A/B (2026-10-18)
+
+Lane: `dsv41-flash-1017-4x-sglang` (Aiden `production-1.7.1` + the baked 1017
+encoder patch, image `dsv41-sglang-1017:v1`). Venue: **4nodes-switch (dgx1-4)**,
+200G RoCE switch, weights mmap'd from the NAS HF cache, Engram rows node-local
+(`[[staging]]` sparse-copy). The live production lane on `dgx58-switch` was not
+touched during this campaign.
+
+Question: the lane measures pp ~3.0k t/s / tg ~45 tok/s (c1) while a comparable
+community lane (knapcio v2.4 TP4, SGLang `dev-dsv41`, same 1017 checkpoint on 4
+Sparks) reports pp 5,385 / tg 63.9 (c1) and 94.1 (c4). Which of the visible
+config differences actually move our numbers?
+
+## Method
+
+Two instruments, both run from `pluto.lan` (off the Mac's WiFi path):
+
+1. `sm121 bench --perf-only` — llama-benchy pp2048/tg128, c1/c2, depth 0/4K/8K.
+   Same harness/version as every other lane report (v2.8.0).
+2. `/tmp/f1017/probe.py` (kept with the campaign) — 2 discarded warm-up passes +
+   N=5 measured passes of a **cold ~2048-token prompt** (unique nonce → no radix
+   hit) asking for a 150-word paragraph; decode rate is
+   `(completion_tokens-1)/(last_delta - ttft)` using the streamed `usage`
+   block (speculative decoding emits multi-token deltas, so counting SSE events
+   undercounts by ~2x). Median of the 5. This is the tie-breaker: llama-benchy
+   rows on this stack scatter ±30% run to run, the probe does not.
+
+Boot cost per config on this lane is 26-40 min (476 GB of fp8 weights off the
+NAS + CUDA-graph capture), so variants were kept few and chosen from the
+community configs rather than swept blindly.
+
+## Results
+
+llama-benchy (pp t/s / tg t/s), single pass each:
+
+| config | d0 c1 | d0 c2 | d4K c1 | d8K c1 | d8K c2 |
+|---|---|---|---|---|---|
+| baseline (`ep-size 4`, chunk 2048, ctx 512K) | 3,019 / **44.9** | 2,597 / 52.6 | 3,038 / 46.6 | 2,870 / 39.6 | 2,430 / 53.6 |
+| `--enable-deepseek-v4-fp4-indexer` | 2,942 / 45.1 | 2,128 / 55.5 | 3,019 / 48.5 | 2,829 / 43.6 | 2,132 / 51.3 |
+| `ep-size 2` | 3,054 / 61.0 | 1,089 / 58.8 | 2,372 / 42.3 | 1,965 / 41.1 | 1,753 / 56.9 |
+| `ep-size 2` (repeat) | 2,485 / 43.2 | 2,607 / 60.5 | 3,009 / 44.1 | 2,692 / 48.6 | 1,874 / 65.5 |
+| `ep-size 4` + chunk 8192 + ctx 1M | 3,275 / 42.3 | — (crash, see below) | — | — | — |
+| **final: `ep-size 2`, chunk 2048, ctx 1M** (quality run) | 2,358 / 45.6 | 1,916 / 67.8 | 2,533 / 45.4 | 2,859 / 45.8 | 2,677 / 38.5 |
+
+probe.py (median of 5, cold 2K prompt → decode tok/s and TTFT≈prefill):
+
+| config | TTFT | decode c1 |
+|---|---|---|
+| `ep-size 2`, chunk 2048, ctx 512K | 0.754 s | **41.7** |
+| `ep-size 4` + chunk 8192 + ctx 1M | 0.711 s | 37.8 / 37.5 (two runs) |
+| `ep-size 2` + chunk 8192 + ctx 1M | 0.702 s | **43.0** |
+| `ep-size 2`, chunk 2048, ctx 1M (final) | 0.76 s | 38.8 / 41.8 (two runs) |
+
+Quality (tool-eval-bench v2.8.0, seed 42, hardmode, thinking on):
+
+| config | score | responsiveness | median turn | failures |
+|---|---|---|---|---|
+| baseline EP4 / 512K | 87/100 (168/194) | 71 | 1.7 s | TC-68/74/88/89/91/92 |
+| final EP2 / 1M | 87/100 (168/194) | **75** | **1.4 s** | TC-68/71/74/88/89/92 |
+
+The two runs swap one scenario each way (TC-91 vs TC-71) — single-trial noise
+at the same score. Adopted in `recipes/dsv41-flash-1017-4x-sglang` (commit
+`fe0d386` in the recipe repo): `--ep-size 2`, `--context-length 1048576`,
+`--chunked-prefill-size 2048`, `SPARK_ENGRAM_MAX_IDS=65536`.
+
+## Findings
+
+1. **`--ep-size 2` (TP4/EP2, the knapcio shape) is the one real decode win.**
+   Probe: 41.7-43.0 vs 37.5-37.8 at EP4. It halves the per-step MoE
+   dispatch/combine fan-out (2 nodes instead of 4) at the cost of keeping the
+   full expert set on each node pair — memory is fine (KV pool stays ~9.2M
+   tokens, ~12-13 GB GPU headroom left). Adopted as the lane default.
+2. **`--enable-deepseek-v4-fp4-indexer` is neutral-to-negative here.** Decode
+   unchanged within noise, c2/8K rows dropped ~15-20%. Reverted; the flag is
+   `experimental` in this build and the DSA indexer is not our bottleneck.
+3. **`--chunked-prefill-size 8192` is blocked by an Engram coupling, not by
+   performance.** The per-chunk Engram lookup is `max_prefill_tokens x hash
+   columns` (~2 x chunk x 12): chunk 2048 → 49,152 ids, which is exactly what
+   the recipe's `SPARK_ENGRAM_MAX_IDS=65536` covers; chunk 8192 → 190,608 ids
+   and the engine dies mid-request with
+   `RuntimeError: engram layer 1: a lookup of 190608 ids exceeds
+   SPARK_ENGRAM_MAX_IDS=131072`. Raising the id budget (≥ 196,608, e.g.
+   `262144`) is the prerequisite for any larger chunk. Kept at 2048 by choice:
+   per the agent-serving findings, 2048 gives ~2.9x the decode share of 8192
+   under a concurrent prefill, which is the interactive-agent trade we want.
+   (At a 2048-token prompt the chunk size does not change pp at all — the first
+   cell of the 8192 run was 3,275 vs 3,019 baseline, inside noise.)
+4. **`--context-length 1048576` costs nothing** (the KV pool is sized by
+   `--mem-fraction-static`, not by the context cap: 9.18M tokens with the 1M
+   cap vs 9.57M at 512K) and matches DeepSeek's 1017 recommendation. Adopted.
+5. The remaining gap to the knapcio number is **not** in these flags: their
+   stack (`dev-dsv41` + Mia adapter: fast loader, L2 prefetch, chunked indexer
+   v3, mxfp8 b12x MoE, loop-abort) is a different overlay. Porting that is a
+   separate project, not a flag flip.
+
+## Operational note: slow-NAS boots
+
+One boot attempt died with `TP rank 0 could finish the model loading, but there
+are other ranks that didn't finish loading` while ranks 1/3 still had shards in
+flight — the NAS read rate had collapsed to ~1/6 of the morning's (48 shards took
+~95 min instead of 13), rank 0 finished first and the TCPStore closed under the
+stragglers. Nothing in the config was wrong; a retry on the same config booted
+cleanly with 13.5 GB GPU headroom. Two things follow: (a) a boot during NAS
+contention can fail on rank desync, so retry before suspecting the config;
+(b) the engram/weight pre-staging on node-local storage is what keeps a
+*restart* cheap — the 26-95 min here is all NAS weight read.
+
+## Not yet measured
+
+- `SGLANG_DSV41_REASONING_EFFORT=75` (knapcio default; stu.miller measured 1017
+  at `max` using ~40% of the old model's reasoning tokens). Changes token volume
+  and therefore responsiveness far more than any flag above — needs its own
+  quality + responsiveness A/B.
+- DSPARK `--speculative-dspark-block-size` sweep (3/5/7); accept length on the
+  live lane sits at 3.0-5.2 with block 5.
+- `--depth` sweep (16K..256K) on the final config; the 1M context cap is adopted
+  but the long-context rows are unmeasured on this venue.
+- The 2x alternative: thread 386121 / `x00byte/DeepSeek-V4.1-Flash-1017-Dual-Spark`
+  (TensorFold + vLLM on EXL3 2.9bpw, decode c1 59-103 tok/s on two Sparks) is a
+  different checkpoint + engine; a dgx1+dgx2 (`2nodes-dac`) lane is the natural
+  place to try it.