.env.tp4.example 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. # 4x DGX Spark profile (TP4/EP4). Copy to .env.tp4 and edit. Never commit .env.tp4.
  2. # Used by ./start-tp4.sh; ./start.sh keeps using .env (3 Sparks).
  3. # ─── cluster ──────────────────────────────────────────────────────────────────
  4. HEAD_IP=10.0.0.1
  5. WORKER_IPS="10.0.0.2 10.0.0.3 10.0.0.4"
  6. WORKER_HOSTS="spark2 spark3 spark4"
  7. WORKER_USER=zurih
  8. SSH_IDENTITY=$HOME/.ssh/id_ed25519_shared
  9. # ─── fabric (ConnectX-7 RoCE) ─────────────────────────────────────────────────
  10. # Gloo/NCCL TCP bootstrap on the LAN NIC. Data plane prefers RoCE (NCCL_NET=IB).
  11. FABRIC_IFACE=enp1s0f1np1
  12. GLOO_SOCKET_IFNAME=enP7s7
  13. NCCL_SOCKET_IFNAME=enP7s7
  14. IB_HCA=rocep1s0f0,rocep1s0f1
  15. NCCL_NET=IB
  16. NCCL_IB_DISABLE=0
  17. NCCL_P2P_DISABLE=1
  18. NCCL_SHM_DISABLE=1
  19. NCCL_DEBUG=WARN
  20. # NCCL connection buffers live in pinned host RAM, which on GB10 is GPU memory:
  21. # the defaults (4 MiB Simple + 4.69 MiB LL128 + 0.5 MiB LL per connection, 512
  22. # connections) cost 4.7 GiB per node. Decode all-reduces are 61 KB (LL protocol),
  23. # prefill uses Simple; LL128 buys nothing here. Measured: 5.0 GB -> 0.14 GB pinned.
  24. NCCL_BUFFSIZE=1048576
  25. NCCL_LL128_BUFFSIZE=262144
  26. NCCL_PROTO=^LL128
  27. NCCL_MAX_NCHANNELS=8
  28. # Set NCCL_DEBUG=INFO for one boot to see communicator/channel/protocol choices.
  29. NCCL_DEBUG_SUBSYS=INIT,ENV
  30. NCCL_HOST_DIR=$HOME/nccl-2.30.7
  31. # ─── switchless ring (optional, 4 nodes without a RoCE switch) ────────────────
  32. # A 4-node ring has no direct fabric path between opposite nodes (rank0 <->
  33. # rank2), and RoCE queue pairs do not follow IP routing, so NCCL's Tree/PAT
  34. # transports fail to connect. Setting this to 1:
  35. # 1. injects ring-only NCCL settings into every rank, and
  36. # 2. overlays the patched host NCCL (NCCL_HOST_DIR) onto the container's pip
  37. # NCCL path (NCCL_PIP_SO) instead of using LD_LIBRARY_PATH, because DeepEP
  38. # refuses to start when two NCCL runtimes are visible.
  39. # Step 2 needs a NCCL carrying the sparkring switchless-cycle patch; stock NCCL
  40. # silently ignores NCCL_SWITCHLESS_RING_ONLY and still fails on the Tree
  41. # transport. See docs/switchless-ring.md for the patch set and a build recipe.
  42. #NCCL_SWITCHLESS_RING_ONLY=1
  43. #NCCL_HOST_DIR=$HOME/nccl-2.30.7
  44. #NCCL_WORKER_DIR=/opt/patched-nccl # optional absolute path on every worker
  45. #NCCL_ALGO=Ring
  46. #NCCL_IB_SUBNET_PREFIX_LEN=24
  47. #NCCL_MIN_NCHANNELS=4
  48. #NCCL_MAX_NCHANNELS=4
  49. #NCCL_P2P_LEVEL=SYS
  50. # Four channels keep pinned host RAM low on the tested ring.
  51. # GB10 UMA safeguard used by the tested ring: consume each checkpoint tensor before
  52. # queueing the next host-to-device copy. Not part of the switched production line
  53. # below (measured with the default loader and DSV41_FAST_LOAD's own pacing).
  54. #DSV41_SERIAL_WEIGHT_LOAD=1
  55. # Local weights on every node (a ring has no fabric-wide NFS path): docs/switchless-ring.md.
  56. #NFS_SHARE=0
  57. #
  58. # Both ConnectX-7 cards (four RDMA devices) on the ring need a NCCL build with sparkring's
  59. # dual-PCI-domain patch plus four NCCL_IB_* flags on every rank, passed through
  60. # EXTRA_CONTAINER_ENV: docs/switchless-ring.md ("Devices past the second are never advertised").
  61. #
  62. # RoCEnante on the ring. Its one-shot all-reduce/all-gather writes into every peer, and a ring has no
  63. # link between opposite nodes, so without more a ring runs the production line with
  64. # SGLANG_ROCE_ALLREDUCE=0 and no DSV41_ROCE_GATHER. docs/switchless-ring.md ("RoCEnante on the ring")
  65. # builds the opposite-node path in the neighbours' ConnectX-7 hardware (FujitsuPolycom/sparkring) and
  66. # scripts/ring_mesh/plan.py prints the line for your fleet: the peer maps depend on the cabling and
  67. # on the TP rank order. Append it to the production EXTRA_CONTAINER_ENV, replacing that line's
  68. # B12X_ROCE_HCA, SGLANG_ROCE_MAX_SIZE and DSV41_ROCE_GATHER values:
  69. # DSV41_ROCE_RING=1 SGLANG_ROCE_ALLREDUCE=1 SGLANG_ROCE_MAX_SIZE=262144 DSV41_ROCE_GATHER=262144 B12X_ROCE_HCA=rocep1s0f0,rocep1s0f1,roceP2p1s0f0,roceP2p1s0f1 B12X_ROCE_PEER_HCA_MAPS=<plan.py> B12X_ROCE_TWO_WAVE_THRESHOLD_BYTES=0
  70. # 262144 needs hairpin_queue_size 8192 on every fabric function (dsv41-mesh.service sets it at boot);
  71. # at the driver default of 1024 use 81920.
  72. # ─── model / image ────────────────────────────────────────────────────────────
  73. MODEL_DIR=$HOME/NewModels/DeepSeek-V4.1-Flash
  74. COMMON_MODEL=/var/tmp/DeepSeek-V4.1-Flash
  75. # Same digest as the Dockerfile FROM: the dev-dsv41 tag moves, the digest does not.
  76. BASE_IMAGE=lmsysorg/sglang:dev-dsv41@sha256:3dbc313030a6ef2c5d7de8ecf48e9aece722694a82182cb618cc82b588816349
  77. IMAGE=dsv41-4x-spark:local
  78. PORT=8888
  79. # ─── weights on spark2/spark3/spark4 ─────────────────────────────────────────────────
  80. # Default: from the head over NFSv4. On a switched fabric give one server
  81. # address for all workers, or one per worker in WORKER_IPS order. For a ring,
  82. # use local worker bind volumes with NFS_SHARE=0 (docs/switchless-ring.md), or
  83. # supply a separately reachable NFS TCP path and matching ACLs.
  84. NFS_SHARE=1
  85. NFS_VOLUME=dsv41-weights
  86. NFS_EXPORT_NAME=dsv41-native
  87. NFS_SERVER_IPS="10.0.22.1"
  88. NFS_CLIENTS=10.0.0.2,10.0.0.3,10.0.0.4,10.0.22.0/24
  89. # ─── parallelism (4 Sparks) ──────────────────────────────────────────────────
  90. # TP=4 EP=4 matches world size. heads=64 / o_groups=8 / draft experts=128 /
  91. # vocab=129280 all divide by 4, so no TP padding is needed (DSV41_TP_PAD=0 below).
  92. NNODES=4
  93. TP_SIZE=4
  94. # 2 for the base and canary images (FlashInfer's MXFP4 MoE needs the per-rank expert width to be
  95. # a multiple of 128). The production line below runs EP_SIZE=1 (every rank a 576-wide slice of all
  96. # 384 experts, routed MoE on b12x main via DSV41_MOE_B12X_NEXT): no EP-group straggler at the MoE
  97. # all-reduce. Set EP_SIZE=1 together with that line.
  98. EP_SIZE=2
  99. DIST_PORT=20000
  100. # ─── serving ──────────────────────────────────────────────────────────────────
  101. # NVMe Engram offload is required on Spark (host RAM == GPU unified memory).
  102. OFFLOAD_MODE=nvme
  103. # Per-host cache budget, split across the 2 Engram layers and the ranks sharing
  104. # that host. 0 disables it: with the packed local shards a miss is ONE local
  105. # read, and the measured miss rate costs ~145 reads/s against a drive that does
  106. # ~112k IOPS. Host RAM is GPU memory here and rank 0 has ~0.3 GB available, so
  107. # the GiB this used to hold is worth far more as headroom than as hit rate.
  108. DSV41_CACHE_GIB=4
  109. # GB10 NVMe: ~3.5k IOPS at QD=1, ~112k at QD=64. The Engram callback blocks the
  110. # compute stream, so misses are serviced by a pool rather than one at a time.
  111. DSV41_IO_THREADS=96
  112. # Pinning the owned scale rows makes a miss cost one pread instead of two
  113. # (~1.8x on the callback), but it needs ~1 GiB/layer/rank of EAGER host RAM, and
  114. # host RAM here is GPU memory: with weights at ~104 GB the pin raised SGLang's
  115. # "minimum viable" mem-fraction from ~0.940 to 0.9569 and left no KV cache.
  116. # Off until the packed local shard lands, which gets the same single read from
  117. # disk layout instead of RAM.
  118. DSV41_RESIDENT_SCALES=0
  119. DSV41_CACHE_WAYS=16
  120. DSV41_STATS_SECONDS=60
  121. CONTEXT_LENGTH=1048576
  122. # 0.80 of each rank's 121.7 GiB is the static pool (weights + KV + CUDA graphs);
  123. # the other ~24 GiB stays outside it for the prefill transient (the indexer's
  124. # per-chunk buffers, see CHUNKED_PREFILL_SIZE) and runtime slack. The KV budget
  125. # SGLang reports on the boot line (full_token=) shrinks with this fraction, so
  126. # MAX_TOTAL_TOKENS below is sized for the 0.80 budget, not the old 0.90 one.
  127. MEM_FRACTION_STATIC=0.80
  128. # Rank 0 pays ~1.3 GiB the workers do not (HTTP server + tokenizer +
  129. # detokenizer) and also serves the checkpoint over NFS. The knob exists to tune
  130. # it separately, but note LOWERING it does not give the head headroom: weights
  131. # cost the same either way, so a smaller fraction only starves the KV pool.
  132. # Measured on this box: 0.9436 is where KV goes negative, and 0.945 still leaves
  133. # only 0.12 GB -- less than the 0.34 GB DSV4 SWA cap. 0.95 is the working value.
  134. # Those are TP3 measurements; at TP4 the head simply tracks MEM_FRACTION_STATIC.
  135. HEAD_MEM_FRACTION_STATIC=0.80
  136. # 8 concurrent requests. This also sets --cuda-graph-max-bs-decode (start.sh
  137. # passes the same value through), so CUDA-graph capture covers batch tiers 1-8.
  138. MAX_RUNNING_REQUESTS=16
  139. # 1024-token prefill chunks. The indexer's per-chunk transient peaks at about
  140. # 14 B x chunk x prefix (measured, docs/chunked-prefill-memory.md): ~15 GB for a
  141. # 1024-token chunk at 1M context, ~60 GB for 4096, which no rank has. Verified:
  142. # a 1M-context needle test passed on TP4 at 1024 (the 3-Spark fleet reached
  143. # 208k single / 186k concurrent at the same chunk). Raise it for faster
  144. # short-prompt prefill only after a 1M prompt has passed
  145. # scripts/verify/memguard.py at the larger size.
  146. CHUNKED_PREFILL_SIZE=4096
  147. # Pin the KV pool. Unpinned, SGLang sizes it from whatever is free at load time,
  148. # which on a unified-memory box is the OS MemAvailable: it swings with the page
  149. # cache and makes the pool (and decode speed) a boot lottery. Pinned, SGLang
  150. # demands exactly this, and a pin that does not fit either dies ("no room ...
  151. # within the available X GB") or is silently clamped down -- both have happened
  152. # on this stack -- so the pin has to sit under the smallest budget a boot will
  153. # ever compute, not at the ceiling.
  154. # Read the budget off the boot log:
  155. # DSV4 memory calculation: bytes_per_full_token=1670.75 ... full_token=<N>
  156. # At TP4 with MEM_FRACTION_STATIC=0.90 that line reported ~16M tokens (~26.7 GB
  157. # per rank). This profile runs 0.80, which takes ~13 GB off that budget, and
  158. # pins 8,000,000 x 1,670.75 B = 13.4 GB of KV per rank under it. Tested: the
  159. # profile boots and serves at these values and a 1M-context needle test passed.
  160. # If the pool ever has to shrink, lower this pin rather than raising the memory
  161. # fraction: the fraction is what keeps the prefill transient safe. With
  162. # MAX_RUNNING_REQUESTS=8 the pool holds 8 requests averaging 1,000,000 tokens
  163. # each (8 x 1Mi = 8,388,608 would be the pin for all 8 at the full context).
  164. # Pages are touched only when used, so the pool costs nothing until it fills.
  165. MAX_TOTAL_TOKENS=8000000
  166. SPEC_ALGO=DSPARK
  167. DSPARK_BLOCK_SIZE=5
  168. # Profile with sglang.benchmark.dspark_sps_profiler into state/dspark_sps.json;
  169. # until that file exists DSpark runs the verify-all schedule and gains nothing.
  170. DSPARK_SPS_TABLE=/state/dspark_sps.json
  171. DSPARK_STS_TABLE=/state/dspark_sts.json
  172. # ─── engram local shards ──────────────────────────────────────────────────────
  173. # ./start.sh pack repacks each rank's owned Engram rows (weight+scale adjacent)
  174. # onto node-local NVMe: ~68 GiB/node, one read per miss instead of two, and no
  175. # NFS hop on the workers. Serving picks them up automatically when present.
  176. ENGRAM_DIR=$HOME/dsv41-engram
  177. DSV41_PACKED_DIR=/engram
  178. SKIP_PREPARE=1
  179. SKIP_VERIFY=1
  180. SKIP_SMOKE=0
  181. SMOKE_QUICK=1
  182. # ─── kernels (GB10 / SM121) ───────────────────────────────────────────────────
  183. # The 32x32 ue8m0 block-FP8 dense projections only have a Triton kernel unless a
  184. # FlashInfer backend is named, which routes them to the MXFP8 dense kernels.
  185. # --watchdog-timeout covers the CuTe-DSL JIT compile of the b12x kernels on the
  186. # first forward after a boot.
  187. # --min-free-slots-delay 1 disables the admission delayer, which is what makes
  188. # MAX_RUNNING_REQUESTS=8 actually reachable. SGLang treats DSPARK as
  189. # DFlash-family (spec_info.py: is_dflash_family = is_dflash or is_dspark), and
  190. # for that family with max-running-requests >= 8 it auto-enables a threshold of
  191. # min(4, max(2, (max_running_requests + 5) // 6))
  192. # (srt/managers/min_free_slots_delayer.py), i.e. 2 at 8 requests and 3 at 16.
  193. # New prefills are then held until that many slots are free, so with steady
  194. # arrivals the batch never sustains the configured max -- ~7 of 8, ~13-14 of 16.
  195. # An explicit value wins and <= 1 disables it. Externally reported: +43% c8
  196. # aggregate on a 3-Spark fleet. Not yet measured here. Drop this flag to get the
  197. # batched admissions back if per-admission cost (DSpark's draft prefill pass)
  198. # ever dominates at high concurrency.
  199. # --sleep-on-idle: the scheduler stops busy-polling when there is nothing to do. Measured
  200. # 2026-09-18: head scheduler idle CPU 47 % -> 14 %, workers 5 %; first response after 20 s
  201. # of idle 0.18-0.21 s either way; decode unchanged. Suggested by RustyAiLab (Mia #16).
  202. EXTRA_SGLANG_ARGS="--fp8-gemm-backend flashinfer_cutlass --watchdog-timeout 1800 --min-free-slots-delay 1 --enable-deepseek-v4-fp4-indexer --enable-cache-report --sleep-on-idle"
  203. # With DSV41_FAST_LOAD=1 (below) the loader's shard window is what bounds the host
  204. # memory the eager reads hold: num_threads + 1 shards, ~3.4 GB each on TP4/EP2.
  205. # Append to the line above:
  206. # --model-loader-extra-config {\"num_threads\":1}
  207. # Which FlashInfer MXFP8 kernel serves those projections (adapter/mxfp8_b12x.py):
  208. # b12x is FlashInfer's own SM120/121 choice (16|32-row tiles, ~3x the CUTLASS
  209. # SM120 kernel at decode M); cudnn is the alternative; cutlass restores SGLang's
  210. # default, which measured no faster than Triton (both ~50 ms of a decode step).
  211. DSV41_MXFP8_BACKEND=b12x
  212. # adapter/shared_pad_k.py: at TP4 the shared expert's down_proj is N=5120 K=576, and
  213. # 576 is not a multiple of 128, so b12x rejects it and this ONE shape in the model
  214. # falls back to a CUTLASS kernel that pads M to 128 and runs at 25% occupancy
  215. # (52 GB/s against the 196 GB/s b12x reaches on the same data). Padding K to 640 with
  216. # zero weights puts it back on b12x: measured bit-identical (0 of 30720 elements
  217. # differ), costs 14 MB per rank, and removes 0.90 ms from a 61.3 ms decode step.
  218. # Padding alone buys nothing -- CUTLASS at K=640 measured 0.7% SLOWER than at 576 --
  219. # so this is worth nothing on a stack where b12x is not the MXFP8 backend.
  220. DSV41_SHARED_PAD_K=1
  221. # FlashInfer's fused MoE finalize sums the top-k expert outputs with atomic bf16
  222. # adds. The autotuner picks it for the 32-token bucket (17-32 token prefills,
  223. # decode at concurrency 3-4), which made greedy outputs nondeterministic by up to
  224. # ~1 nat on first-token logprobs. Off = separate finalize kernel, ~0.3 ms/step.
  225. SGLANG_FLASHINFER_MOE_FUSED_FINALIZE=0
  226. # Reasoning budget for thinking-mode requests that carry no reasoning_effort.
  227. # SGLang's V4.1 encoder maps low/high/xhigh/max to 25/50/75/100 and defaults to
  228. # "high" = 50; the publisher's encoding/encoding.py says low=50, high=75, max=100
  229. # with default 75. adapter/sitecustomize.py remaps the named tiers to the
  230. # publisher's table; this pins the default as a number so it holds even without
  231. # the remap. Integer 1-100 or low/high/xhigh/max. Per request: OpenAI
  232. # reasoning_effort (a tier, or a 0-0.99 float = budget/100) or
  233. # chat_template_kwargs {"reasoning_effort": N}. Only rendered in thinking mode.
  234. SGLANG_DSV41_REASONING_EFFORT=75
  235. # When a chat request omits max_tokens / max_completion_tokens, fill this in and
  236. # also clamp larger client values. 32768 would have stopped the 714k-token
  237. # racing-animation loop while covering the visual/HTML trials (~15k). 0 restores
  238. # SGLang's fill-the-remaining-context behaviour (1M on TP4). Raise it for long
  239. # thinking-on traces.
  240. DSV41_MAX_NEW_TOKENS=32768
  241. # Decode-side loop abort. --watchdog-timeout only fires when a step hangs; a live
  242. # token stream (VIS-04: 714k tokens of racing-animation HTML) is healthy as far
  243. # as the GPU watchdog is concerned. Finish with stop/repetition after four
  244. # copies of a ≥32-token n-gram, 64 identical tokens, or eight identical decoded
  245. # lines. The 2× EXL3 recipe has no equivalent. 0 disables.
  246. DSV41_LOOP_ABORT=1
  247. DSV41_LOOP_NGRAM=32
  248. DSV41_LOOP_REPEATS=4
  249. DSV41_LOOP_LINE_REPEATS=8
  250. # PyTorch caching allocator mode. The DSV4 prefill allocates a larger indexer-logits
  251. # buffer for every 2048-token chunk (each chunk scores the whole prefix so far); the
  252. # native allocator cannot reuse smaller freed blocks for the next larger request and
  253. # reserved memory grows with the SQUARE of the prompt length (measured: 8 GiB reserved,
  254. # 0 live, after 64 growing steps), which is the 64k-128k prefill OOM on the head.
  255. # expandable_segments coalesces freed space: 0.6 GiB after 128 steps in the same test.
  256. # NOT expandable_segments:True: on the 3-node GB10 fleet it made every prefill of more
  257. # than 64 query tokens return NaN logits (REPORT.md section 17). Untested at TP4.
  258. PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False
  259. # adapter/prefill_empty_cache.py (in the shared image): after every prefill chunk of a
  260. # sequence at least this long, return PyTorch's unused cached blocks so a long prompt
  261. # costs one chunk's transient instead of the sum over chunks (REPORT.md section 17b).
  262. # The per-chunk indexer buffer is CHUNKED_PREFILL_SIZE x prefix x 4 B per ratio-1 source
  263. # layer, ~14 B x chunk x prefix at the peak across layers: at 1M context a 1024-token
  264. # chunk peaks around 15 GB, against the ~24 GiB per rank that MEM_FRACTION_STATIC=0.80
  265. # leaves outside the static pool (verified: 1M-context needle test passed on TP4). Use
  266. # scripts/verify/memguard.py when ramping new prompt shapes, and drop the chunk to 512
  267. # if the guard fires. 0 disables the hook.
  268. DSV41_PREFILL_EMPTY_CACHE_TOKENS=8192
  269. # ─── TP4 runtime ──────────────────────────────────────────────────────────────
  270. # Memory per rank at TP4: ~77 GiB of MXFP4 experts + FP8 dense weights and a
  271. # ~2.5 GiB draft, so ~40 GB of the 121.7 GiB stays free (vs ~6 GB at TP3). That
  272. # pays for the settings above: the full 1M context, an 8M-token KV pool
  273. # (1,670.75 B/token/rank = 13.4 GB), 8 concurrent requests, 1024-token prefill
  274. # chunks (~15 GB peak indexer transient at 1M), and a 0.80 memory fraction that
  275. # keeps ~24 GiB per rank outside the static pool for that transient. Packed Engram
  276. # shards are named engram-l<layer>-r<rank>of4.bin and can share ENGRAM_DIR with a
  277. # 3-Spark profile. At TP4 every shard holds real weights, so no head padding.
  278. DSV41_TP_PAD=0
  279. WORKER_DIR=/home/$WORKER_USER/dsv41-4x-spark
  280. # ─── Extra container env (mirrored to every rank by start.sh) ───────────────
  281. # DSV41_INDEXER_CHUNKED=1 arms the sglang#39187 backport (adapter/indexer_chunked*.py):
  282. # the dense prefill indexer scores each request in row chunks of at most
  283. # DSV41_INDEXER_LOGITS_BUDGET_BYTES (default 2 GiB) of fp32 logits and publishes only
  284. # the tail rows of the candidate masks. That bounds the per-chunk transient that made
  285. # CHUNKED_PREFILL_SIZE=1024 necessary (docs/chunked-prefill-memory.md), so the 4096
  286. # chunk above is safe at any context: measured head MemAvailable low-water 7.0-7.7 GiB
  287. # during a 262k cold prefill on both images (2026-09-16 window, knapcio/DeepSeek-V4.1-Flash-4x-DGX-Spark-TP4).
  288. # adapter/fast_load.py (docs/fast-load.md): this rank's checkpoint tensors are read
  289. # eagerly by a 16-thread pread pool into anonymous host memory instead of being
  290. # page-faulted through the loader's mmap at 0.5 GB/s, copies are paced to
  291. # DSV41_FAST_LOAD_INFLIGHT_GB so the reads stay just ahead of them, and the DSpark
  292. # draft load opens only the 3 shards that hold mtp.* tensors. load_weight 285 s -> 77 s,
  293. # engine start 356 s -> 125 s, values bitwise identical (tests/test_fast_load_pacing.py,
  294. # and the in-image checkpoint test described in the docs). EP_SIZE / N_EXPERTS are
  295. # fallbacks for when the parallel context is not readable from the loader.
  296. # The eager tensors are views into 256 MiB pinned slabs (DSV41_FAST_LOAD_SLAB_MB, 0 = one
  297. # pinned buffer per tensor); DSV41_FAST_LOAD_MEMLOG=1 logs host counters and pinned
  298. # allocation counts at the end of each load (diagnostics only).
  299. # DSV41_FAST_LOAD=1 DSV41_FAST_LOAD_EP_SIZE=2 DSV41_FAST_LOAD_N_EXPERTS=384 DSV41_FAST_LOAD_INFLIGHT_GB=6
  300. # adapter/engram_prefetch.py: the Engram row lookups (NVMe misses inside a host callback,
  301. # 2.2-2.4 ms of GPU idle per step on real text) run on a side stream right after the
  302. # hash ids exist and join only at the layer's gather. Same-image A/B 2026-09-18:
  303. # step 51.7 -> 49.5 ms, English essay c1 42.6 -> 45.1 tok/s, sparkDash prose c1 57-59 ->
  304. # 60-61, prose c4 120 -> 124, structured 116 -> 121; bitwise identical rows
  305. # (DSV41_ENGRAM_PREFETCH_CHECK=1 re-runs the old path and counts differences: 0).
  306. # DSV41_ENGRAM_PREFETCH=1
  307. # adapter/wo_a_w8.py: the verify/draft wo_a projection (2-8 rows) reads the checkpoint's own
  308. # e4m3 bytes plus one exponent per 32x32 block instead of the bf16 copy the engine makes at load,
  309. # in a copy of the stock Triton kernel that rebuilds the same bf16 tile in registers. A layer is
  310. # converted only when its fp8 twin reproduces the bf16 weight exactly (43 of 43 here). 43 layers
  311. # at M=6: 3.43 -> 2.20 ms; decode step at c1 52.9 -> 51.7 ms; the MXFP8-epilogue path is bitwise
  312. # identical, the plain bf16 path differs in fp32 accumulation order only.
  313. # DSV41_WO_A_W8=1
  314. # adapter/draft_tau.py: draft proposal temperature for sampled requests. Proposal and acceptance
  315. # use the same tempered q, so the output distribution is unchanged (speculative sampling is exact
  316. # for any q); at the model card's T=1 / top_p=0.95, 0.8 raises accepted tokens per step by ~1.2 %
  317. # (offline, held-out traffic). Needs SGLANG_DSPARK_FOLDED_SAMPLING=2.
  318. # DSV41_DRAFT_TAU=0.7
  319. # adapter/draft_head_fp8.py: the draft reads an fp8 copy of the (shared) LM head; the target's
  320. # logits are untouched, so outputs cannot change. 1405 -> 721 us per step, acceptance unchanged.
  321. # DSV41_DRAFT_HEAD_FP8=1
  322. # DSV41_DRAFT_HEAD_FP8_IMPL=tp4 selects adapter/draft_head_fp8_tp4.py: row tiles sized to the batch
  323. # (16/64/128), so the fp8 head is streamed once per call; same logits per row, faster at 40-64 rows.
  324. # DSV41_DRAFT_HEAD_FP8_IMPL=tp4
  325. # adapter/block_verify.py: block verification (Sun et al., ICLR 2025) instead of token-by-token
  326. # rejection for sampled rows; exact output distribution (chi-square test in tests/), more tokens
  327. # accepted per step: +1.8 % on prose, +2.5 % on coding-with-thinking (engine-exact p and q).
  328. # DSV41_BLOCK_VERIFY=1
  329. # adapter/folded_result_fence.py: folded all-greedy verify results are cloned off the verify
  330. # graph's persistent buffers before the overlapped copy to host (sglang#40919 race).
  331. # DSV41_FOLDED_FENCE=1
  332. # adapter/verify_cap.py: per-request verify length. conf:T verifies only the leading drafts whose
  333. # running product of the draft confidence head's survival stays >= T; the other verify rows reuse
  334. # the anchor row's experts (no extra expert reads, ~2 ms per row at c1) and acceptance is capped at
  335. # the verified drafts. Exact (greedy identical, sampled via block_verify). conf:0.1 measured best:
  336. # prose c1 +5 %, sampled thinking +5 %, code flat. 1..5 = fixed verified drafts (measurement only).
  337. # DSV41_VERIFY_CAP=conf:0.1
  338. # adapter/autotune_keep.py: keep the FlashInfer autotune cache across boots under EP (sglang#40320).
  339. # DSV41_AUTOTUNE_KEEP=1
  340. # adapter/wo_a_w8.py, two more switches (need DSV41_WO_A_W8=1): MID runs verify/draft wo_a at 9-192
  341. # rows (c2..c32) from the fp8 twin too; DROP then releases the bf16 copy (722 MB per rank -> KV pool).
  342. # DSV41_WO_A_W8_MID=1 DSV41_WO_A_W8_DROP=1
  343. # adapter/row_store.cpp: keep one Engram layer's row cache in the GB10 display reservation (outside
  344. # MemAvailable; host needs nvidia_drm modeset=1 fbdev=0 and no desktop, see docs/display-reserve.md).
  345. # DSV41_ENGRAM_DRM_NODE=/dev/dri/card0
  346. # adapter/replicated_split.py: ReplicatedLinear layers whose prefix ends with one of the listed
  347. # suffixes are column-split over TP (each rank runs the same quantized linear on its 128-row weight
  348. # tiles, then an all-gather). Per layer and only if the slice is bit-identical to the stock layer on
  349. # every rank (checked at boot). engram.wkv (183 MB read by every rank at both Engram layers) and
  350. # wqkv_a: c1 step -1.4 ms, prose c1 +3.7 %, greedy identical.
  351. # DSV41_REPLICATED_SPLIT=wqkv_a,engram.wkv
  352. # adapter/router_live.py (needs DSV41_VERIFY_CAP): the router kernel loads the anchor row's scores for
  353. # dead verify rows, so they get the anchor's experts without the separate remap launch (40 per step);
  354. # built from the engine's own router source, bit-identical outputs.
  355. # DSV41_ROUTER_LIVE=1
  356. # adapter/draft_main_proj.py: the draft's replicated main_proj (105 MB fp8 on every rank) as a
  357. # 1/TP column shard (fp8, exact weight bytes) + all-gather; draft only, target untouched.
  358. # DSV41_DRAFT_MAIN_PROJ_SPLIT=1
  359. # adapter/roce_gather.py (canary-roce image): TP all-gathers up to N bytes per rank (the draft's
  360. # vocab-parallel logits, the splits above) take the RoCEnante one-shot kernel instead of NCCL.
  361. # DSV41_ROCE_GATHER=2097152
  362. EXTRA_CONTAINER_ENV="DSV41_INDEXER_CHUNKED=1"
  363. # The dsv4.1 branch (canary images) probes a Rust toolchain to build its image
  364. # preprocessor and can hang in `cargo --version` before the HTTP server starts
  365. # (seen 2026-09-17: launch_server stuck 20+ min, workers up, /health 000).
  366. # `never` skips Cargo and keeps the PIL path. Harmless on the base image.
  367. SGLANG_RUST_BUILD_MODE=never
  368. # ─── Optional: upstream dsv4.1 branch image (Dockerfile.canary) ──────────────
  369. # ./start-tp4.sh build builds it on every node with BUILD_DOCKERFILE below (the
  370. # pinned branch tree is fetched and hash-checked during the build; #BUILD_ARGS passes
  371. # extra docker build flags, e.g. --build-arg PIP_INDEX=<mirror>/simple without
  372. # pypi.org). Then point IMAGE at it and force the graph-folded draft sampler
  373. # (greedy-folded by default):
  374. #IMAGE=dsv41-4x-spark:canary
  375. #BUILD_DOCKERFILE=Dockerfile.canary
  376. #EXTRA_CONTAINER_ENV="DSV41_INDEXER_CHUNKED=1 SGLANG_DSPARK_FOLDED_SAMPLING=2 SGLANG_RUST_BUILD_MODE=never"
  377. # ─── Optional: canary + RoCEnante (Dockerfile.canary-roce) ─────────────────────
  378. # Routes TP SUM all-reduces up to SGLANG_ROCE_MAX_SIZE bytes (2 MiB covers the
  379. # 16-request decode step) to the b12x one-shot RDMA all-reduce over both rails.
  380. #IMAGE=dsv41-4x-spark:canary-roce
  381. #BUILD_DOCKERFILE=Dockerfile.canary-roce
  382. #EXTRA_CONTAINER_ENV="DSV41_INDEXER_CHUNKED=1 SGLANG_DSPARK_FOLDED_SAMPLING=2 SGLANG_ROCE_ALLREDUCE=1 SGLANG_ROCE_MAX_SIZE=2097152 B12X_ROCE_HCA=rocep1s0f0,roceP2p1s0f0 B12X_ROCE_CACHE_DIR=/state/b12x-roce B12X_COMPILE_CACHE_DIR=/state/b12x-compile SGLANG_RUST_BUILD_MODE=never"
  383. # Prefill TP split (SG18) on top of either canary image, from 32k context:
  384. #EXTRA_CONTAINER_ENV="DSV41_INDEXER_CHUNKED=1 SGLANG_DSPARK_FOLDED_SAMPLING=2 SGLANG_ROCE_ALLREDUCE=1 SGLANG_ROCE_MAX_SIZE=2097152 B12X_ROCE_HCA=rocep1s0f0,roceP2p1s0f0 B12X_ROCE_CACHE_DIR=/state/b12x-roce B12X_COMPILE_CACHE_DIR=/state/b12x-compile SPARK_PREFILL_TP_SPLIT=1 SPARK_PREFILL_TP_MIN_CONTEXT=32768 SPARK_PREFILL_TP_MIN_ROWS=1024 SGLANG_RUST_BUILD_MODE=never"
  385. # The full production profile of docs/tp4.md (canary-roce image, EP_SIZE=1 above):
  386. #EXTRA_CONTAINER_ENV="DSV41_INDEXER_CHUNKED=1 SGLANG_DSPARK_FOLDED_SAMPLING=2 SGLANG_ROCE_ALLREDUCE=1 SGLANG_ROCE_MAX_SIZE=2097152 B12X_ROCE_HCA=rocep1s0f0,roceP2p1s0f0 B12X_ROCE_CACHE_DIR=/state/b12x-roce B12X_COMPILE_CACHE_DIR=/state/b12x-compile SPARK_PREFILL_TP_SPLIT=1 SPARK_PREFILL_TP_MIN_CONTEXT=32768 SPARK_PREFILL_TP_MIN_ROWS=1024 SGLANG_RUST_BUILD_MODE=never DSV41_FAST_LOAD=1 DSV41_FAST_LOAD_EP_SIZE=1 DSV41_FAST_LOAD_TP_SLICE=auto DSV41_FAST_LOAD_N_EXPERTS=384 DSV41_FAST_LOAD_INFLIGHT_GB=6 DSV41_ENGRAM_PREFETCH=1 DSV41_SHARED_PAD_K=1 DSV41_WO_A_W8=1 DSV41_DRAFT_TAU=0.7 DSV41_DRAFT_HEAD_FP8=1 DSV41_DRAFT_HEAD_FP8_IMPL=tp4 DSV41_BLOCK_VERIFY=1 DSV41_FOLDED_FENCE=1 DSV41_VERIFY_CAP=conf:0.1 DSV41_AUTOTUNE_KEEP=1 DSV41_WO_A_W8_MID=1 DSV41_WO_A_W8_DROP=1 DSV41_REPLICATED_SPLIT=wqkv_a,engram.wkv DSV41_DRAFT_MAIN_PROJ_SPLIT=1 DSV41_ROCE_GATHER=2097152 DSV41_ROUTER_LIVE=1 DSV41_MOE_B12X_NEXT=1 DSV41_MOE_B12X_NEXT_DETERMINISTIC=1 DSV41_HC_FUSED=1 DSV41_PREFILL_SP=1 DSV41_PREFILL_SP_FP8=1 DSV41_L2_PREFETCH=1"