glm53.yaml 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. # GLM-5.3-Flash NVFP4, TP=4 across four GB10 nodes (one head, three workers),
  2. # with the DFlash2 drafter.
  3. #
  4. # Run the SAME file on every node:
  5. # docker compose -f compose/glm53.yaml up -d
  6. # compose/.env is optional. Addresses and roles come from mentat, and paths
  7. # and the image have defaults (.env.example lists the overrides). Compose
  8. # reads .env from this file's directory, whatever the working directory.
  9. #
  10. # Ordering: from cold, any rank may start first -- registration retries
  11. # forever, and the host-level mentatd (mentatd.yaml from
  12. # https://github.com/mmastrac/mentat) just has to be running on every box. Replacing a running stack is different: take all four
  13. # ranks down first (README, "Replacing a running stack").
  14. #
  15. # Nothing else fits beside it. GLM's weights alone are ~90.7 GiB per node out
  16. # of 121.6 GiB, so stop any other model on these boxes first.
  17. #
  18. # network_mode: host is required -- RoCE/NCCL needs the real interfaces, and
  19. # Ray's actor ports are not practically mappable. It is also how the
  20. # container reaches the box's mentatd, on 127.0.0.1:6379 and :6380.
  21. #
  22. # Pinned so the project is not named after this directory ("compose"). Before
  23. # the first `up`, take down a stack started under another project name: this
  24. # one reuses container_name glm53 and would collide with it.
  25. name: glm53
  26. services:
  27. glm53:
  28. # image/build.sh's default tag. Keep the two in step: the entrypoint's
  29. # defaults live in the image, and an older tag boots silently mistuned.
  30. image: ${IMAGE:-spark-glm53:v9}
  31. container_name: glm53
  32. network_mode: host
  33. ipc: host
  34. ulimits:
  35. memlock: -1
  36. stack: 67108864
  37. # A crash must not dump core. The engine maps ~90 GiB, and the dumping
  38. # thread holds the NVIDIA RM lock the whole time it writes: on
  39. # 2026-08-27 that turned one CUDA OOM on a node into a box with no
  40. # ssh, no userspace and no way back except a power cycle, while the
  41. # kernel sat in nvidia_close for 860+ seconds. core_pattern on these
  42. # hosts is a plain file, so nothing rate-limits or discards it either.
  43. core: 0
  44. devices:
  45. - /dev/infiniband:/dev/infiniband
  46. volumes:
  47. # The full 181 GiB checkpoint, read-only. There is no transcoded cache:
  48. # this image loads plain safetensors, so EVERY rank reads the whole
  49. # checkpoint and it must be present on EACH node's local disk.
  50. # Local rather than NFS on purpose -- an NFS mount races the network at
  51. # boot and fails (seen on one node on 2026-08-26), and a model that only
  52. # starts when the NAS is awake will eventually fail to start.
  53. - ${MODEL_HOST_DIR:-/srv/models/glm-5.3-flash-nvfp4}:/models/glm-5.3-flash-nvfp4:ro
  54. # The host's list of running systemd units, read by the preflight to warn
  55. # about a desktop session (README, "What you need").
  56. - /run/systemd/units:/host/systemd-units:ro
  57. # The DFlash2 drafter, also on every node: speculation runs on every TP
  58. # rank. A separate Qwen3-based model that reads auxiliary hidden states
  59. # from target layers 5, 14, 24, 33 and 42 (image/patches/glm53_eagle3_aux.py
  60. # supplies them; upstream GLM5next does not).
  61. - ${DFLASH_HOST_DIR:-/srv/models/glm-5.3-flash-dflash2}:/models/glm-5.3-flash-dflash2:ro
  62. # JIT caches (FlashInfer, torch.compile, Triton, TileLang), and the
  63. # experimental weight snapshots (~48 GB a node). WITHOUT this they live
  64. # inside the container and are discarded on every `compose down`, so
  65. # each restart recompiles. The FlashInfer autotune cache is the
  66. # exception: at TP>1 the entrypoint keeps it in /tmp and wipes it.
  67. # Unset, a named volume. `down -v` deletes it.
  68. - ${CACHE_HOME:-cache}:/root/.cache
  69. # The engine log and the allocator counters behind /memory, read through
  70. # the status server's MCP tools. Unset, a named volume.
  71. - ${LOG_DIR:-logs}:/logs
  72. # RULE: any knob that also has a default in entrypoint.sh is left EMPTY
  73. # here. A default in both places means two places to change and one
  74. # silently winning -- which happened repeatedly on 2026-08-27, with the
  75. # compose defaults from the pre-SM90 attempts quietly overriding tuned
  76. # values (MTP off, GPU_MEM_UTIL 0.86, MAX_MODEL_LEN 131072). The exceptions
  77. # are the knobs the status server reads itself (SERVICE_NAME, SERVED_NAME,
  78. # PEERS, MCP_LOG_DIR, THROUGHPUT_INTERVAL_S). Its own defaults are
  79. # generic, not this model's, so they are set here explicitly.
  80. environment:
  81. # Passed through only when .env (or the shell running compose) sets
  82. # them. Unset, the entrypoint takes them from the box's mentatd.
  83. #
  84. # ROLE and HEAD_HOST, both unset: the boxes elect the live agent with
  85. # the lowest address as head. Set, the roles are fixed: ROLE is head on
  86. # one box and worker on the rest, and HEAD_HOST is the head's LAN
  87. # address on every box, the head included.
  88. - ROLE
  89. - HEAD_HOST
  90. # This node's LAN address. mentat identifies a node by it, and the agent
  91. # and daemon must agree on one string. Unset = the address mentatd tags
  92. # lan.
  93. - VLLM_HOST_IP
  94. # Address prefix of the fabric, with its trailing dot. Unset = the first
  95. # of FABRIC_SUBNETS.
  96. - CLUSTER_SUBNET
  97. # The status server reads these two itself, and its default name is
  98. # "vllm", so they carry a default here too.
  99. - SERVICE_NAME=${SERVICE_NAME:-glm53}
  100. - SERVED_NAME=${SERVED_NAME:-glm53}
  101. # mentat subcluster identity; empty = SERVICE_NAME (the entrypoint's
  102. # default). Set it only to run the same model as a second parallel
  103. # stack, which needs a distinct group per stack.
  104. - MENTAT_GROUP=${MENTAT_GROUP:-}
  105. # Empty = 127.0.0.1:6379 when the boxes elect a head, else HEAD_HOST:6379.
  106. - RAY_ADDRESS=${RAY_ADDRESS:-}
  107. # One subnet per ConnectX PCIe root NCCL should use, space-separated. Each
  108. # names an interface; the entrypoint maps it to its RDMA device and GID.
  109. # Both roots made a 126k prefill 11% faster on TP=4 (2026-09-26). Empty =
  110. # CLUSTER_SUBNET when that is set, else one subnet per address mentatd
  111. # tags rdma.
  112. - FABRIC_SUBNETS=${FABRIC_SUBNETS:-}
  113. # How long to wait for the fabric and for the peer's GPUs before giving
  114. # up. Both default to waiting forever, because the usual reason either is
  115. # missing is a switch reboot, and a container that exits then just races
  116. # it. Set a bound only where a stuck stack must surface as a failure.
  117. - FABRIC_WAIT_S=${FABRIC_WAIT_S:-}
  118. - WORKER_WAIT_S=${WORKER_WAIT_S:-}
  119. # How long the RoCE device and its GID table get to appear once the
  120. # address is already up. Local state, so it settles in seconds or is
  121. # broken: this one is deliberately bounded, and the container exits so
  122. # the failure is visible rather than a container that waits forever.
  123. - ROCE_SETTLE_S=${ROCE_SETTLE_S:-}
  124. # LEFT EMPTY ON PURPOSE so the entrypoint derives it at start. There is
  125. # no correct constant: the GID table is indexed by (address, RoCE version)
  126. # with first-free allocation, so the index differs per node AND per boot.
  127. # On 2026-08-26 the right entry was 6 on one node and 5 on its peer, and the
  128. # first node's table had an empty slot 3 left by four failed DHCP cycles plus
  129. # a profile replacement during an earlier boot. Reboot that node with the static
  130. # profile in place and its correct index moves 6 -> 5, so a pinned value
  131. # is a latent NCCL failure rather than a fix. Set it only to override.
  132. - NCCL_IB_GID_INDEX=${NCCL_IB_GID_INDEX:-}
  133. # Empty = all four RDMA devices, which is what reaches 196 Gb/s rather
  134. # than 112. Override only to restrict NCCL to one PCIe root.
  135. - NCCL_IB_HCA=${NCCL_IB_HCA:-}
  136. # Empty = the interface holding VLLM_HOST_IP, derived in the entrypoint.
  137. - NCCL_SOCKET_IFNAME=${NCCL_SOCKET_IFNAME:-}
  138. # Channel count and NCCL logging; defaults and why in the entrypoint.
  139. - NCCL_MIN_NCHANNELS=${NCCL_MIN_NCHANNELS:-}
  140. - NCCL_MAX_NCHANNELS=${NCCL_MAX_NCHANNELS:-}
  141. - NCCL_DEBUG=${NCCL_DEBUG:-}
  142. - TP=${TP:-}
  143. # 1 lets the stack start without any experimental/ overlay (stock
  144. # kernels, about half the README's speed); otherwise the entrypoint refuses.
  145. - ALLOW_STOCK=${ALLOW_STOCK:-}
  146. - PREFLIGHT=${PREFLIGHT:-}
  147. - FABRIC_CHECK=${FABRIC_CHECK:-}
  148. # Speculation. SPEC_METHOD is dflash, mtp or none (entrypoint default
  149. # dflash, k=7); MTP=0 turns it off whichever is named. An MTP drafter
  150. # costs ~3.4 GiB and a rejection-sampler warmup, which were what killed
  151. # rank 0 on 2026-08-27 while this comment wrongly said MTP was off.
  152. - MTP=${MTP:-}
  153. - SPEC_METHOD=${SPEC_METHOD:-}
  154. - SPEC_TOKENS=${SPEC_TOKENS:-}
  155. - SPEC_EXTRA=${SPEC_EXTRA:-}
  156. - DFLASH_MODEL=${DFLASH_MODEL:-}
  157. - MODEL_DIR=${MODEL_DIR:-}
  158. # Default and KV arithmetic in the entrypoint. Raise it against the
  159. # "GPU KV cache size" line at boot.
  160. - MAX_MODEL_LEN=${MAX_MODEL_LEN:-}
  161. # Per-prompt multimodal caps. Raising the image cap raises the peak a
  162. # single request can demand. With SKIP_MM_PROFILING=1 the vision encoder
  163. # is never budgeted at startup, so those bytes come out of the same
  164. # headroom a long prefill needs.
  165. - LIMIT_MM=${LIMIT_MM:-}
  166. - SKIP_MM_PROFILING=${SKIP_MM_PROFILING:-}
  167. # lazy|eager|prefetch|torchao; eager in the entrypoint, which says why.
  168. - SAFETENSORS_LOAD_STRATEGY=${SAFETENSORS_LOAD_STRATEGY:-}
  169. # Which NVFP4 MoE kernel: flashinfer_cutlass (entrypoint default) or
  170. # marlin. The entrypoint has the history.
  171. - MOE_BACKEND=${MOE_BACKEND:-}
  172. # 0.88 is the measured ceiling under mentat (2026-08-27): CUDA-visible
  173. # free memory on the head is 108.0 GiB regardless of page cache, 0.90
  174. # wants 109.46 and failed the startup check four times, 0.88 wants
  175. # 107.03 and served with 0.63 GiB to spare -- KV 1,842,951 tokens vs
  176. # ~1.07M at the old 0.84. Under real Ray the ceiling was lower still
  177. # (its object store and daemons sat in this pool).
  178. #
  179. # 0.90 starts on a freshly booted box, which is the trap. It is worth
  180. # real KV -- 1,541,820 tokens against 1,200,343 here, +28%, because KV
  181. # gets only what the weights leave -- and it still must not be used: it
  182. # ran the driver out of memory 90 seconds into serving
  183. # (NV_ERR_NO_MEMORY in dmesg, hours before anything visible went wrong)
  184. # and both ranks died with the boxes needing a power cycle. A driver OOM
  185. # during startup PROFILING is normal at any setting; one during steady
  186. # serving is the signal.
  187. - GPU_MEM_UTIL=${GPU_MEM_UTIL:-}
  188. # Allocator and per-worker memory ceiling; defaults and why in the
  189. # entrypoint.
  190. - PYTORCH_CUDA_ALLOC_CONF=${PYTORCH_CUDA_ALLOC_CONF:-}
  191. - TORCH_MEM_FRACTION=${TORCH_MEM_FRACTION:-}
  192. # Set to a writable directory to record an allocation stack per block
  193. # and dump a snapshot the first time the ceiling above is crossed. The
  194. # stack is taken in the allocating thread, so it names the caller. It
  195. # costs time and memory to record, so leave it empty outside an
  196. # investigation. /logs is already mounted and MCP-readable.
  197. - TORCH_MEM_TRACE_DIR=${TORCH_MEM_TRACE_DIR:-}
  198. # Where each worker publishes its allocator counters, which is how the
  199. # status page's /memory answers whether torch or something else is
  200. # holding the node's memory. One counter read per worker per interval,
  201. # so unlike the trace above this is cheap enough to leave on.
  202. - TORCH_MEM_STATS_DIR=${TORCH_MEM_STATS_DIR:-/logs}
  203. - TORCH_MEM_STATS_INTERVAL=${TORCH_MEM_STATS_INTERVAL:-}
  204. # Tool-call parser. Unset = the image's default: the stock glm47, or
  205. # glm47_failclosed on an image built before that became the default
  206. # (the default lives in the image's entrypoint). Set glm47 or
  207. # glm47_failclosed (image/patches/glm47_failclosed.py) to choose.
  208. - TOOL_PARSER=${TOOL_PARSER:-}
  209. # Appended to the serve command verbatim. Nothing the entrypoint selects
  210. # belongs here: EXTRA_ARGS is one string, and an override restating it
  211. # drops whatever else it carried -- how the fail-closed tool parser once
  212. # vanished from production.
  213. - EXTRA_ARGS=${EXTRA_ARGS:-}
  214. # KV pool in bytes; the entrypoint pins 26 GiB. Pinned, vLLM "skips
  215. # memory profiling" AND "does not respect the gpu_memory_utilization
  216. # config", so the pin is not a fix for a warmup OOM -- lower GPU_MEM_UTIL
  217. # or the pin for that.
  218. - KV_CACHE_MEMORY=${KV_CACHE_MEMORY:-}
  219. # Empty = fp8_e4m3, the entrypoint's default and what every measurement
  220. # here used. Never plain `fp8`: that selects the fp8_ds_mla packed format,
  221. # which hardcodes pe_dim == 64. This model is NoPE sparse MLA
  222. # (qk_rope_head_dim is 0), so that kernel refuses at KV cache init --
  223. # after a full 10-minute weight load.
  224. - KV_CACHE_DTYPE=${KV_CACHE_DTYPE:-}
  225. # Left EMPTY on purpose so the entrypoint owns the defaults. A default
  226. # here as well as there means two places to change and one silently
  227. # winning.
  228. - MAX_NUM_BATCHED_TOKENS=${MAX_NUM_BATCHED_TOKENS:-}
  229. # Capped by the KDA recurrent state, not by throughput. 34 of 45 layers
  230. # are linear attention and each keeps one fixed-size state per sequence
  231. # ("Mamba cache block"); exactly 32 fit after weights at GPU_MEM_UTIL
  232. # 0.86. vLLM's default of 256 aborts at CUDA graph capture AFTER the full
  233. # weight load. Empty here so the entrypoint owns the default.
  234. - MAX_NUM_SEQS=${MAX_NUM_SEQS:-}
  235. - LONG_PREFILL_TOKEN_THRESHOLD=${LONG_PREFILL_TOKEN_THRESHOLD:-}
  236. - DECODE_RESERVE_TOKENS=${DECODE_RESERVE_TOKENS:-}
  237. - CUDAGRAPH_CAPTURE_SIZES=${CUDAGRAPH_CAPTURE_SIZES:-}
  238. - CUDAGRAPH_METRICS=${CUDAGRAPH_METRICS:-}
  239. - ITERATION_DETAILS=${ITERATION_DETAILS:-}
  240. # Empty = 8002 (API) and 8082 (status page and MCP), from the
  241. # entrypoint.
  242. - API_PORT=${API_PORT:-}
  243. - STATUS_PORT=${STATUS_PORT:-}
  244. # Status-page peers, host:port, comma-separated. Empty shows this node
  245. # alone; mentatd-serve gives the cluster view.
  246. - PEERS=${PEERS:-}
  247. - MCP_LOG_DIR=${MCP_LOG_DIR:-/logs}
  248. # mentat's agent id is group@container@node_ip. Without this the agent
  249. # falls back to the hostname, which under network_mode: host is the
  250. # HOST's name, so every container on a node would claim the same id.
  251. - CONTAINER_NAME=${CONTAINER_NAME:-glm53}
  252. # What the agent announces to mentatd-serve. Empty = port form (8002/v1,
  253. # 8082/mcp), which the router resolves against every address the node
  254. # announces. A URL pins one address. A worker holds a TP rank and
  255. # answers no requests, so the entrypoint drops its OpenAI endpoint
  256. # however this is set.
  257. - MENTAT_OPENAI_API=${MENTAT_OPENAI_API:-}
  258. - MENTAT_MCP_API=${MENTAT_MCP_API:-}
  259. - MENTAT_MODEL_PROVIDER=${MENTAT_MODEL_PROVIDER:-}
  260. - MAX_JOBS=${MAX_JOBS:-}
  261. - SELF_TEST=${SELF_TEST:-}
  262. - THROUGHPUT_INTERVAL_S=${THROUGHPUT_INTERVAL_S:-60}
  263. - NVIDIA_VISIBLE_DEVICES=all
  264. deploy:
  265. resources:
  266. reservations:
  267. devices:
  268. - driver: nvidia
  269. count: all
  270. capabilities: [gpu]
  271. restart: unless-stopped
  272. # Used only when CACHE_HOME or LOG_DIR is unset.
  273. volumes:
  274. cache:
  275. logs: