Dockerfile 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. # GLM-5.3-Flash NVFP4 on ASUS GX10 / GB10 (sm_121), TP=4 over RoCE.
  2. #
  3. # Built on a stock vLLM NIGHTLY. GLM-5.3-Flash (glm5_next) landed on main, so
  4. # the per-model pre-merge image this used to start from
  5. # (vllm/vllm-openai:glm53-flash-arm64-cu130) is no longer needed, and neither
  6. # are most of the patches that sat on top of it. Audited 2026-09-22 against
  7. # nightly 0961bbae, patch by patch, and again 2026-09-26 against ddd6fbca:
  8. #
  9. # dropped, now upstream:
  10. # thinking_budget_guard.py the V2 sampler applies thinking_token_budget
  11. # kpool-fix indexer.py tail slot mapping written into a persistent
  12. # buffer (out=slot_mapping_buffer)
  13. # kpool-fix mamba_hybrid.py positions= passed to build_attn_metadata
  14. # dflash registry/qwen3_dflash2/speculator/spec_decode_init
  15. # glm53_kpool_tail_ring.py vllm#58454
  16. # dropped, now a flag:
  17. # gb10_topk_fallback.py --sparse-indexer-topk-backend per_row
  18. # dropped, no longer needed:
  19. # flashinfer nightly pin the nightly ships 0.6.18.post1
  20. # NCCL 2.30.7 pin the nightly ships 2.30.7
  21. # cutlass-dsl 4.6.2 pin the nightly ships 4.7.1; watch this one
  22. # model_runner.py mount PP-only; this runs PP=1
  23. # added:
  24. # glm53_mtp_bf16.py MTP layer is BF16 but config.json says NVFP4
  25. # sm90_fp8_kv_dtype.py SM90 builder planned fp8 KV as uint8
  26. # glm53_reasoning_always_parsed.py thinking off = low effort, still <think>
  27. # FlashKDA 17a037d vllm#58846, still open: fp32 recurrent state
  28. #
  29. # Re-pinned 2026-10-15 to the v0.31.0 release: Docker Hub pruned the whole old
  30. # nightly-<sha> tag scheme, so the 09-26 base tag became unresolvable. v0.31.0
  31. # (cut 2026-10-02, 6 days after ddd6fbca) is the first release containing it.
  32. # Drift audit ddd6fbca..v0.31.0 over the files this build patches: every target
  33. # unchanged, except routed_experts.py where vllm#58720 landed verbatim upstream
  34. # (the patch is dropped below), and flashinfer 0.7.0 -> 0.7.0.post1.
  35. # torch/NCCL/cutlass-dsl pins unchanged.
  36. #
  37. # The experimental/ overlays (baked by build.sh) were written against the
  38. # ddd6fbca tree. Rebased 2026-10-15 onto v0.31.0: for every overlay whose
  39. # base file drifted, new = v0.31.0 file + (overlay - ddd6fbca) delta; 21
  40. # applied cleanly, 2 needed a hand merge (rejection_sampler: the
  41. # gather_draft_sampled() refactor; flashinfer_mla_sparse_sm90: the
  42. # 3-tuple _kv_lens_host + exact flag). Unrebaked overlays reintroduce
  43. # ddd6fbca-era import blocks on top of the v0.31.0 tree and die at import
  44. # time (e.g. hisparse get_hisparse_gpu_memory_usage).
  45. # kept:
  46. # glm53-flash_SM121.py (MiaAI) 5 of 7 edits apply unchanged; see below
  47. # gb10_plugin_backend.py MiaAI vs LibertAI sparse-MLA switch
  48. # worker_memory_cap.py ours: TORCH_MEM_FRACTION
  49. # spin_wait.py was a whole-file shm_broadcast.py mount
  50. # glm47_failclosed.py tool parser plugin, import path ported
  51. # glm53_eagle3_aux.py upstream GLM5next exposes no aux hidden states;
  52. # retargeted to models/glm5next/common/model.py
  53. # glm53_dflash2_kv_groups.py drafter KV groups on the GLM-5-Next layout
  54. # (the old whole-file kv_cache_utils.py fork,
  55. # reduced to what the nightly lacks)
  56. #
  57. # GB10 is sm_121 and this torch reports up to sm_120. That is correct: sm_120
  58. # cubins run on sm_121. Do not widen TORCH_CUDA_ARCH_LIST.
  59. ARG BASE=vllm/vllm-openai:v0.31.0
  60. # vLLM source the FlashKDA registration shim comes from. Keep it at BASE's commit
  61. # (the release tag's own commit — its installed vllm was built from this ref).
  62. ARG VLLM_REF=db9527a46873454610df6dbedf79a36d6bf1a7f6
  63. # Pulled registry-qualified: a bare `mentat-artifacts:<ver>` tag resolves to a
  64. # local image, and those have been mislabelled before -- every image "pinned"
  65. # to 0.4.0 once silently ran 0.1.0. Verify with `mentatd --version`.
  66. # Must match the host daemons: the README asks for one mentat version on every box.
  67. ARG MENTAT_VERSION=0.17.1
  68. FROM mmastrac/mentat-artifacts:${MENTAT_VERSION} AS mentat
  69. # GLM's KDA layers run chunked prefill through vllm/_flashkda_C on SM12x. The
  70. # nightly builds FlashKDA b59532f, which rounds the recurrent state to bf16
  71. # after every 16-token tile, and long prefills corrupt tool-call output.
  72. # vllm#58846 bumps it to 17a037d, which keeps that state in fp32. Same
  73. # flash_kda.h and cutlass pin, so only the .so changes.
  74. FROM ${BASE} AS flashkda
  75. ARG VLLM_REF
  76. ARG FLASHKDA_REF=17a037d98da546deb4591e967cf961a43c034d8b
  77. ARG CUTLASS_REF=5c149f52a436782210263fb2f19b354443a61c6a
  78. COPY image/flashkda/ /build/
  79. RUN FLASHKDA_REF=${FLASHKDA_REF} CUTLASS_REF=${CUTLASS_REF} VLLM_REF=${VLLM_REF} /build/build.sh
  80. FROM ${BASE}
  81. # nvcc's include path is missing the headers whose libraries this base ships,
  82. # which breaks any FlashInfer JIT path (nvrtc.h, then -lnvrtc).
  83. COPY image/patches/link_cuda_headers.sh /tmp/link_cuda_headers.sh
  84. RUN bash /tmp/link_cuda_headers.sh && rm /tmp/link_cuda_headers.sh
  85. # --- sm_121 sparse MLA -------------------------------------------------------
  86. # This checkpoint is NoPE (qk_rope_head_dim 0). On capability 12 the nightly
  87. # still offers only TRITON_MLA and FLASHINFER_MLA_SPARSE_SM120, and SM120
  88. # mandates the packed fp8_ds_mla layout, which NoPE cannot use -- the engine
  89. # dies in concat_and_cache_mla after a ~10 minute weight load. The SM90 backend
  90. # already covers NoPE (kpe in {0, 64}) but is gated to Hopper; FA3 is, FA2 is
  91. # not. MiaAI's patcher lists SM90 on capability 12 and swaps FA2 in off Hopper,
  92. # plus FA2's fp8 gate and CTA tile for ~101 KB smem, PDL off (a KDA race on
  93. # this hardware), a kpool pid clamp, and initialising the indexer's top-k
  94. # buffer to -1 instead of torch.empty.
  95. #
  96. # Two of those edits target code that moved since MiaAI wrote them: the indexer
  97. # allocation and FlashInfer 0.7.0's fp8 gate. mia_retarget.py rewrites their
  98. # paths and anchors in a copy, so the vendored file stays verbatim (MIT,
  99. # licence beside it). The patcher still asserts every anchor matches once.
  100. COPY image/patches/glm53-flash_SM121.py image/patches/LICENSE.MiaAI-Lab image/patches/mia_retarget.py /tmp/mia/
  101. RUN python3 /tmp/mia/mia_retarget.py /tmp/mia/glm53-flash_SM121.py \
  102. && python3 /tmp/mia/glm53-flash_SM121.py && rm -rf /tmp/mia
  103. # MiaAI's patcher and LibertAI's plugin both solve the sm_121 MLA problem, and
  104. # the first shuts the second out. Make the choice an env var -- see the script.
  105. COPY image/patches/gb10_plugin_backend.py /tmp/gb10_plugin_backend.py
  106. RUN python3 /tmp/gb10_plugin_backend.py && rm /tmp/gb10_plugin_backend.py
  107. # The SM90 backend's metadata builder plans FlashInfer's MLA wrapper with
  108. # kv_cache_spec.dtype, which for an fp8 cache is the uint8 STORAGE dtype, and
  109. # plan() rejects it at the first autotune run. The pre-merge image passed
  110. # float8_e4m3fn; this restores that. Not GB10-specific.
  111. COPY image/patches/sm90_fp8_kv_dtype.py /tmp/sm90_fp8_kv_dtype.py
  112. RUN python3 /tmp/sm90_fp8_kv_dtype.py && rm /tmp/sm90_fp8_kv_dtype.py
  113. # Nothing in vLLM bounds a worker's memory, and on unified memory an unbounded
  114. # worker takes the node down rather than its own request.
  115. COPY image/patches/spark_mem_trace.py /usr/local/lib/python3.12/dist-packages/spark_mem_trace.py
  116. COPY image/patches/worker_memory_cap.py /tmp/worker_memory_cap.py
  117. RUN python3 /tmp/worker_memory_cap.py && rm /tmp/worker_memory_cap.py
  118. COPY image/patches/spin_wait.py /tmp/spin_wait.py
  119. RUN python3 /tmp/spin_wait.py && rm /tmp/spin_wait.py
  120. # DFlash2 reads auxiliary hidden states from target layers 5, 14, 24, 33, 42.
  121. # Upstream serves the DFlash2 drafter but its GLM5next model exposes no aux
  122. # states, so without this `--speculative-config dflash` fails at load with
  123. # "Model does not support EAGLE3 interface". Inert unless a drafter asks for
  124. # aux layers. Only the target path changed from the vendor-base version.
  125. COPY image/patches/glm53_eagle3_aux.py /tmp/glm53_eagle3_aux.py
  126. RUN python3 /tmp/glm53_eagle3_aux.py && rm /tmp/glm53_eagle3_aux.py
  127. # nvidia/GLM-5.3-Flash-NVFP4 stores its MTP layer (45) in BF16, but its
  128. # config.json declares every Linear NVFP4 and does not list layer 45 in
  129. # `ignore`, so MTP dies at load: "size of tensor a (256) must match ... (512)"
  130. # in RoutedExperts._load_w2. This excludes the MTP layer from quantisation when
  131. # the checkpoint holds no scales for it -- driven by the weights, so a
  132. # checkpoint that does quantise its MTP layer is left alone.
  133. COPY image/patches/glm53_mtp_bf16.py /tmp/glm53_mtp_bf16.py
  134. RUN python3 /tmp/glm53_mtp_bf16.py && rm /tmp/glm53_mtp_bf16.py
  135. # Thinking off no longer closes an empty <think></think> (out of distribution for
  136. # this model; it breaks long structured output). The template maps it to
  137. # `Reasoning Effort: Low` and still opens <think>, so the reasoning parser must
  138. # track <think> even when the request says thinking is off.
  139. COPY image/patches/glm53_reasoning_always_parsed.py /tmp/glm53_reasoning_always_parsed.py
  140. RUN python3 /tmp/glm53_reasoning_always_parsed.py && rm /tmp/glm53_reasoning_always_parsed.py
  141. # The kpool indexer's top-k returns the right pools in a run-dependent order and
  142. # keeps a run-dependent subset of pools tied at the k-th score. The sparse MLA
  143. # kernel sums keys in list order, so identical requests past 2048 tokens rounded
  144. # differently and then selected different keys. The patch breaks ties by pool id
  145. # and sorts each row.
  146. COPY image/patches/glm53_deterministic_topk.py /tmp/glm53_deterministic_topk.py
  147. RUN python3 /tmp/glm53_deterministic_topk.py && rm /tmp/glm53_deterministic_topk.py
  148. # vllm#58720 (RoutedExperts indexes the expert mapping once) was carried as a
  149. # patch until 2026-10-15; it is verbatim in the v0.31.0 base, so the patch is
  150. # dropped. verify-base.py still asserts the fix is present.
  151. COPY --from=flashkda /out/_flashkda_C.abi3.so /out/FLASHKDA_REF /usr/local/lib/python3.12/dist-packages/vllm/
  152. # DFlash2's drafter adds five sliding-window attention layers, and the nightly's
  153. # GLM-5-Next KV grouper gives up on any spec that is not MLA, so the model falls
  154. # onto the generic path and dies in unify_kv_cache_spec_page_size ("page size is
  155. # not divisible by the maximum page size"). This keeps the target's groups and
  156. # gives the drafter layers a group of their own, laid out after the indexer
  157. # regions and opted out of prefix caching. Inert without a drafter.
  158. COPY image/patches/glm53_dflash2_kv_groups.py /tmp/glm53_dflash2_kv_groups.py
  159. RUN python3 /tmp/glm53_dflash2_kv_groups.py && rm /tmp/glm53_dflash2_kv_groups.py
  160. # The fail-closed GLM tool parser, loaded with --tool-parser-plugin. Baked and
  161. # selected by the entrypoint rather than passed through EXTRA_ARGS: a compose
  162. # override that restated EXTRA_ARGS dropped it, twice, and production ran the
  163. # stock parser without anyone noticing.
  164. COPY image/patches/glm47_failclosed.py /usr/local/share/glm47_failclosed.py
  165. # iproute2 the entrypoint finds this node's ConnectX address by scanning
  166. # `ip -o -4 addr` for CLUSTER_SUBNET, and derives GLOO_SOCKET_IFNAME
  167. # from whichever interface owns it; the port differs per box.
  168. # ibverbs NCCL's RoCE data plane. Missing, NCCL silently falls back to TCP
  169. # over the LAN and every TP step crawls.
  170. # ripgrep/pciutils/usbutils are for the MCP inventory and search tools.
  171. RUN apt-get update && apt-get install -y --no-install-recommends \
  172. iproute2 libibverbs1 librdmacm1 ibverbs-providers \
  173. ripgrep pciutils usbutils \
  174. && rm -rf /var/lib/apt/lists/* \
  175. && command -v ip && command -v rg
  176. # mentat replaces Ray. Unlike the single-GPU tenants, TP=4 across four boxes
  177. # really does need placement, so this installs the daemon binary and the `ray`
  178. # symlink as well as the shim package. Real Ray on unified memory caused two
  179. # outages this exists to prevent: its memory monitor killed healthy ranks at 95%
  180. # node memory (the ~89 GiB of weights ARE host memory), and its object store
  181. # took up to ~36 GiB for a model that moves every tensor over NCCL. On a vLLM
  182. # bump, re-audit the shim with: grep -rn 'ray\.' <site-packages>/vllm/v1/executor/
  183. COPY --from=mentat /out/ /tmp/mentat-out/
  184. # mentatd-probe-machine is how the agent learns this box's GPUs from 0.9.0 on
  185. # ("Move the machine inventory into a probe script"). Without it `ray start`
  186. # registers ZERO GPUs, and vLLM's placement dies with "required GPUs exceeds
  187. # the total number of available GPUs". It must sit beside mentatd or on PATH.
  188. RUN install -m755 /tmp/mentat-out/mentatd /usr/local/bin/mentatd \
  189. && install -m755 /tmp/mentat-out/mentatd-probe-machine /usr/local/bin/mentatd-probe-machine \
  190. && ln -sf /usr/local/bin/mentatd /usr/local/bin/ray \
  191. && pip install --no-cache-dir --no-deps /tmp/mentat-out/mentatd-*-py3-none-any.whl \
  192. && rm -rf /tmp/mentat-out \
  193. && ray --version
  194. COPY image/verify-base.py /tmp/verify-base.py
  195. RUN python3 /tmp/verify-base.py && rm /tmp/verify-base.py
  196. COPY image/chat-template.jinja /usr/local/share/glm53-chat-template.jinja
  197. COPY vllm/status-server.py image/self-test.py image/fabric-check.py /usr/local/bin/
  198. COPY image/entrypoint.sh image/discover.sh /
  199. # Per-rank NCCL and arx devices on a ring (TP=RING4), set before vLLM loads NCCL.
  200. COPY image/fabric_ring.py /usr/local/lib/python3.12/dist-packages/
  201. RUN echo "import fabric_ring" > /usr/local/lib/python3.12/dist-packages/fabric_ring.pth
  202. RUN chmod +x /usr/local/bin/status-server.py /usr/local/bin/self-test.py /usr/local/bin/fabric-check.py /entrypoint.sh
  203. # 8002 = OpenAI API, 8082 = startup status page, bound from container start
  204. EXPOSE 8002 8082
  205. ENTRYPOINT ["/entrypoint.sh"]