.env.example 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. # Copy to .env and edit. Never commit .env.
  2. # ─── cluster ──────────────────────────────────────────────────────────────────
  3. HEAD_IP=10.0.0.1
  4. WORKER1_IP=10.0.0.2
  5. WORKER2_IP=10.0.0.3
  6. WORKER1_HOST=spark2
  7. WORKER2_HOST=spark3
  8. WORKER_USER=zurih
  9. SSH_IDENTITY=$HOME/.ssh/id_ed25519_shared
  10. # ─── fabric (ConnectX-7 RoCE triangle) ────────────────────────────────────────
  11. # Gloo/NCCL TCP bootstrap on the LAN NIC. Data plane prefers RoCE (NCCL_NET=IB).
  12. FABRIC_IFACE=enp1s0f1np1
  13. GLOO_SOCKET_IFNAME=enP7s7
  14. NCCL_SOCKET_IFNAME=enP7s7
  15. IB_HCA=rocep1s0f0,rocep1s0f1
  16. NCCL_NET=IB
  17. NCCL_IB_DISABLE=0
  18. NCCL_P2P_DISABLE=1
  19. NCCL_SHM_DISABLE=1
  20. NCCL_DEBUG=WARN
  21. # NCCL connection buffers live in pinned host RAM, which on GB10 is GPU memory:
  22. # the defaults (4 MiB Simple + 4.69 MiB LL128 + 0.5 MiB LL per connection, 512
  23. # connections) cost 4.7 GiB per node. Decode all-reduces are 61 KB (LL protocol),
  24. # prefill uses Simple; LL128 buys nothing here. Measured: 5.0 GB -> 0.14 GB pinned.
  25. NCCL_BUFFSIZE=1048576
  26. NCCL_LL128_BUFFSIZE=262144
  27. NCCL_PROTO=^LL128
  28. NCCL_MAX_NCHANNELS=8
  29. # Set NCCL_DEBUG=INFO for one boot to see communicator/channel/protocol choices.
  30. NCCL_DEBUG_SUBSYS=INIT,ENV
  31. NCCL_HOST_DIR=$HOME/nccl-2.30.7
  32. # ─── model / image ────────────────────────────────────────────────────────────
  33. MODEL_DIR=$HOME/NewModels/DeepSeek-V4.1-Flash
  34. COMMON_MODEL=/var/tmp/DeepSeek-V4.1-Flash
  35. # Same digest as the Dockerfile FROM: the dev-dsv41 tag moves, the digest does not.
  36. BASE_IMAGE=lmsysorg/sglang:dev-dsv41@sha256:3dbc313030a6ef2c5d7de8ecf48e9aece722694a82182cb618cc82b588816349
  37. IMAGE=dsv41-3x-spark:local
  38. PORT=8888
  39. # ─── weights on spark2/spark3 ─────────────────────────────────────────────────
  40. # Always from spark1 over NFSv4 on ConnectX (no rsync, no SSHFS).
  41. # spark2 mounts 10.0.22.1; spark3 mounts 10.0.23.1 (pairwise CX7).
  42. NFS_SHARE=1
  43. NFS_VOLUME=dsv41-weights
  44. NFS_EXPORT_NAME=dsv41-native
  45. NFS_SERVER_IP_1=10.0.22.1
  46. NFS_SERVER_IP_2=10.0.23.1
  47. NFS_CLIENTS=10.0.0.2,10.0.0.3,10.0.22.0/24,10.0.23.0/24,10.0.33.0/24
  48. # ─── parallelism (3 Sparks) ───────────────────────────────────────────────────
  49. # Heads=64 / hidden=5120 / vocab=129280 are not divisible by 3; adapter/tp3_pad.py pads them.
  50. # EP_SIZE=1: every rank holds a third of every expert's 2304-wide intermediate instead of
  51. # whole experts. Balances the per-layer MoE work: NCCL 12.9 -> 5.6 ms/step, C1 +8-12 %,
  52. # C4 +10 % over EP_SIZE=3 (2026-09-24, docs/overnight-results.md). Rollback: EP_SIZE=3.
  53. NNODES=3
  54. TP_SIZE=3
  55. EP_SIZE=1
  56. DIST_PORT=20000
  57. # ─── serving ──────────────────────────────────────────────────────────────────
  58. # NVMe Engram offload is required on Spark (host RAM == GPU unified memory).
  59. OFFLOAD_MODE=nvme
  60. # Per-host cache budget, split across the 2 Engram layers and the ranks sharing
  61. # that host. 0 disables it: with the packed local shards a miss is ONE local
  62. # read, and the measured miss rate costs ~145 reads/s against a drive that does
  63. # ~112k IOPS. Host RAM is GPU memory here and rank 0 has ~0.3 GB available, so
  64. # the GiB this used to hold is worth far more as headroom than as hit rate.
  65. DSV41_CACHE_GIB=0
  66. # GB10 NVMe: ~3.5k IOPS at QD=1, ~112k at QD=64. The Engram callback blocks the
  67. # compute stream, so misses are serviced by a pool rather than one at a time.
  68. DSV41_IO_THREADS=96
  69. # Pinning the owned scale rows makes a miss cost one pread instead of two
  70. # (~1.8x on the callback), but it needs ~1 GiB/layer/rank of EAGER host RAM, and
  71. # host RAM here is GPU memory: with weights at ~104 GB the pin raised SGLang's
  72. # "minimum viable" mem-fraction from ~0.940 to 0.9569 and left no KV cache.
  73. # Off until the packed local shard lands, which gets the same single read from
  74. # disk layout instead of RAM.
  75. DSV41_RESIDENT_SCALES=0
  76. DSV41_CACHE_WAYS=4
  77. DSV41_STATS_SECONDS=60
  78. # Per-request limit (model max 1,048,576); the KV pool below (750k tokens) covers it
  79. # with room for 4 concurrent requests. The head's memory bounds what actually works
  80. # (REPORT.md section 17b): with the prefill empty_cache hook and 1024-token chunks,
  81. # single prompts to 208k and 4 concurrent 46k prompts are verified; a 256k prompt
  82. # missed the 1.5 GB guard by 20 MB. Keep long prompts guarded (scripts/verify/memguard.py).
  83. CONTEXT_LENGTH=262144
  84. # Lowering the fraction does not free RAM; it only starves the KV pool.
  85. # 0.95 is the working value.
  86. MEM_FRACTION_STATIC=0.95
  87. HEAD_MEM_FRACTION_STATIC=0.95
  88. MAX_RUNNING_REQUESTS=4
  89. # History: with the prefill empty_cache hook the per-chunk indexer buffers ([chunk, prefix]
  90. # fp32 per low-ratio source layer) are the long-prompt peak on the head; 2048-token
  91. # chunks fit a 100k prompt, 1024 fits 200k (2026-09-11 ramps, REPORT.md section 17b).
  92. # Smaller chunks prefill slower (2.1k -> 1.5k tok/s from 2048 to 1024).
  93. # 768 since 2026-09-24: with the decode stack below, a ~200k-token prompt at 1024 ran the head
  94. # out of RAM and hung spark1 until a hard reset. 768 cuts that per-chunk peak by a quarter;
  95. # decode speed is unchanged, prefill is somewhat slower. 3 x 256-token KV pages.
  96. CHUNKED_PREFILL_SIZE=768
  97. # Pin the KV pool. Unpinned, SGLang sizes it from whatever is free at load time
  98. # and it swung 326k -> 593k across identical boots, making decode speed a lottery.
  99. # This does NOT free host RAM (torch reserves mem-fraction up front and the pool
  100. # is carved out of that reservation) -- it controls how many pages get touched,
  101. # which is what drives reclaim stalls. Measured on this box, tok/s at conc 1 / 4:
  102. # 200k -> 28.6 / 55.4 (pressure 0.00, but too small: batching starves)
  103. # 416k -> 31.9 / 75.6 (pressure 0.00, best measured)
  104. # 593k -> 12.6 / 33.9 (pressure 6.9 median, head thrashing)
  105. # Pinned, SGLang demands exactly this and FAILS if it does not fit -- 400k died
  106. # with "no room ... within the available 0.17 GB". Unpinned it auto-fits, and the
  107. # smallest size it ever chose here was 326k, so the pin must sit below that.
  108. MAX_TOTAL_TOKENS=750000
  109. SPEC_ALGO=DSPARK
  110. # 5 together with DSV41_VERIFY_CAP=conf:0.1 below: code +10 %, prose neutral vs 3.
  111. # 5 without the cap loses 8-16 % on prose. Rollback: 3 with DSV41_VERIFY_CAP=0.
  112. DSPARK_BLOCK_SIZE=5
  113. # Profile with sglang.benchmark.dspark_sps_profiler into state/dspark_sps.json;
  114. # until that file exists DSpark runs the verify-all schedule and gains nothing.
  115. DSPARK_SPS_TABLE=/state/dspark_sps.json
  116. DSPARK_STS_TABLE=/state/dspark_sts.json
  117. # ─── engram local shards ──────────────────────────────────────────────────────
  118. # ./start.sh pack repacks each rank's owned Engram rows (weight+scale adjacent)
  119. # onto node-local NVMe: ~68 GiB/node, one read per miss instead of two, and no
  120. # NFS hop on the workers. Serving picks them up automatically when present.
  121. ENGRAM_DIR=$HOME/dsv41-engram
  122. DSV41_PACKED_DIR=/engram
  123. SKIP_PREPARE=1
  124. SKIP_VERIFY=1
  125. SKIP_SMOKE=0
  126. SMOKE_QUICK=1
  127. # ─── kernels (GB10 / SM121) ───────────────────────────────────────────────────
  128. # The 32x32 ue8m0 block-FP8 dense projections only have a Triton kernel unless a
  129. # FlashInfer backend is named, which routes them to the MXFP8 dense kernels.
  130. # --watchdog-timeout covers the CuTe-DSL JIT compile of the b12x kernels on the
  131. # first forward after a boot.
  132. # --sleep-on-idle parks the scheduler when idle instead of spinning a CPU core.
  133. EXTRA_SGLANG_ARGS="--fp8-gemm-backend flashinfer_cutlass --watchdog-timeout 1800 --sleep-on-idle"
  134. # Which FlashInfer MXFP8 kernel serves those projections (adapter/mxfp8_b12x.py):
  135. # b12x is FlashInfer's own SM120/121 choice (16|32-row tiles, ~3x the CUTLASS
  136. # SM120 kernel at decode M); cudnn is the alternative; cutlass restores SGLang's
  137. # default, which measured no faster than Triton (both ~50 ms of a decode step).
  138. DSV41_MXFP8_BACKEND=b12x
  139. # Decode adapters, each validated one boot at a time on TP3 at fraction 0.95 (2026-09-24,
  140. # docs/overnight-results.md). wo_a: fp8 twin of the target's wo_a; W8 alone does not fit
  141. # at 0.95 (+688 MiB), DROP releases the bf16 copies (net -640 MB/rank), MID serves the
  142. # 9-192-row verifies (C4). The draft's wo_a stays bf16 (DSV41_WO_A_W8_DRAFT=1 tried: neutral).
  143. DSV41_WO_A_W8=1
  144. DSV41_WO_A_W8_MID=1
  145. DSV41_WO_A_W8_DROP=1
  146. DSV41_WO_A_W8_DRAFT=0
  147. # fp8 copy of the draft LM head (+210 MiB): about -1 ms/step.
  148. DSV41_DRAFT_HEAD_FP8=1
  149. # Confidence cap for DSPARK_BLOCK_SIZE=5; needs DSV41_BLOCK_VERIFY=1 for sampled rows.
  150. DSV41_VERIFY_CAP=conf:0.1
  151. # 0.8 measured +1.2 % on sampled chat, inside noise: left at 1 (off).
  152. DSV41_DRAFT_TAU=1
  153. DSV41_BLOCK_VERIFY=1
  154. # Engram rows fetched on a side stream: -2.3 to -2.9 ms/step. DSV41_ENGRAM_PREFETCH_CHECK=1
  155. # re-runs the synchronous lookup and logs mismatches (validation only, not for timing).
  156. DSV41_ENGRAM_PREFETCH=1
  157. DSV41_FOLDED_FENCE=0
  158. # Keep FlashInfer autotune caches across boots of the same launch (adapter/autotune_keep.py):
  159. # same tactics, reproducible greedy output and tok/s across restarts. Delete
  160. # ~/.cache/sglang/flashinfer/autotune/*/sm121/*/rank_*.{json,launch} on all nodes to force a retune.
  161. DSV41_AUTOTUNE_KEEP=1
  162. # FlashInfer's fused MoE finalize sums the top-k expert outputs with atomic bf16
  163. # adds. The autotuner picks it for the 32-token bucket (17-32 token prefills,
  164. # decode at concurrency 3-4), which made greedy outputs nondeterministic by up to
  165. # ~1 nat on first-token logprobs. Off = separate finalize kernel, ~0.3 ms/step.
  166. SGLANG_FLASHINFER_MOE_FUSED_FINALIZE=0
  167. # Reasoning budget for thinking-mode requests that carry no reasoning_effort.
  168. # SGLang's V4.1 encoder maps low/high/xhigh/max to 25/50/75/100 and defaults to
  169. # "high" = 50; the publisher's encoding/encoding.py says low=50, high=75, max=100
  170. # with default 75. adapter/sitecustomize.py remaps the named tiers to the
  171. # publisher's table; this pins the default as a number so it holds even without
  172. # the remap. Integer 1-100 or low/high/xhigh/max. Per request: OpenAI
  173. # reasoning_effort (a tier, or a 0-0.99 float = budget/100) or
  174. # chat_template_kwargs {"reasoning_effort": N}. Only rendered in thinking mode.
  175. SGLANG_DSV41_REASONING_EFFORT=75
  176. # When a chat request omits max_tokens / max_completion_tokens, fill this in and
  177. # also clamp larger client values. 32768 would have stopped the 714k-token
  178. # racing-animation loop while covering the visual/HTML trials (~15k). 0 restores
  179. # SGLang's fill-the-remaining-context behaviour (1M on TP4). Raise it for long
  180. # thinking-on traces.
  181. DSV41_MAX_NEW_TOKENS=32768
  182. # Decode-side loop abort. --watchdog-timeout only fires when a step hangs; a live
  183. # token stream (VIS-04: 714k tokens of racing-animation HTML) is healthy as far
  184. # as the GPU watchdog is concerned. Finish with stop/repetition after four
  185. # copies of a ≥32-token n-gram, 64 identical tokens, or eight identical decoded
  186. # lines. The 2× EXL3 recipe has no equivalent. 0 disables.
  187. DSV41_LOOP_ABORT=1
  188. DSV41_LOOP_NGRAM=32
  189. DSV41_LOOP_REPEATS=4
  190. DSV41_LOOP_LINE_REPEATS=8
  191. # PyTorch caching allocator mode. NEVER set expandable_segments:True on this stack:
  192. # with it, every prefill of more than 64 query tokens (a prompt of 65-~4000 tokens, or
  193. # any prefill batch of 3+ short requests) returns NaN logits -> garbage text at
  194. # temperature 0 and a scheduler crash ("probability tensor contains inf/nan") when
  195. # sampling. Bisected 2026-09-11 over five boots (chunk size, context length and the
  196. # warm-up were all ruled out); see logs/profile-2026-09-10/REPORT.md section 17.
  197. # The native allocator does fragment on long prompts (REPORT.md section 15), which is
  198. # why CONTEXT_LENGTH is capped below; expandable_segments:False is the explicit native mode.
  199. PYTORCH_CUDA_ALLOC_CONF=expandable_segments:False
  200. # adapter/prefill_empty_cache.py: after every prefill chunk of a sequence at least this
  201. # long, return PyTorch's unused cached blocks to the driver, so a long prompt costs one
  202. # chunk's transient instead of the sum over chunks (the native allocator cannot reuse
  203. # the previous chunk's smaller indexer-logits buffer). Measured 2026-09-11 on the head:
  204. # without it a 34k prompt kept 1-1.5 GB and 64k ran out of memory; with it 100k prompts
  205. # complete and the memory comes back after each. 0 disables the hook.
  206. DSV41_PREFILL_EMPTY_CACHE_TOKENS=8192