Skip to content

fix(launch): give single-node srun steps their GPUs' share of node DRAM - #3615

Open
cquil11 wants to merge 2 commits into
mainfrom
cpu-dram-per-gpu
Open

cquil11 wants to merge 2 commits into
mainfrom
cpu-dram-per-gpu

Conversation

@cquil11

@cquil11 cquil11 commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Single-node srt-slurm runs now get their host memory from runners.yaml and Slurm enforces it, instead of relying on the engine honoring TOTAL_CPU_DRAM_GB.

  • Each srun step, including the inference server container, gets --mem = available-cpu-dram-mib / gpus-per-node * GPU_COUNT through SRT_SRUN_OPTIONS. The cgroup OOM-kills a step that exceeds its GPUs' share.
  • The job itself requests --mem=<available-cpu-dram-mib>M, so the step request always fits the allocation (e.g. on clusters with a small DefMemPerCPU).

This applies by default to every single-node point on clusters that declare available-cpu-dram-mib. TOTAL_CPU_DRAM_GB is unchanged and still sizes the engines' offload pools. It is the same share scaled by dram-utilization, so it stays under the cap.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

run.cluster.available_cpu_dram_mib if request.env.get("KV_OFFLOADING") == "dram" else None
)
step_mib = node_mib and node_mib * request.gpu_count // run.cluster.gpus_per_node
if (options := config.srun_options(run.backend.settings, step_mib)) is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 This driver change caps srun/sbatch memory for single-node DRAM-offload jobs but the PR adds no inferencex-e2e/perf-changelog.yaml entry, though AGENTS.md says every change that can affect benchmark performance requires one. Capping srun steps to available-cpu-dram-mib / gpus-per-node * GPU_COUNT can newly OOM-kill DRAM-offload points that previously ran uncapped, which is exactly the kind of benchmark-affecting change the changelog exists to record. Fix: append a new entry to inferencex-e2e/perf-changelog.yaml describing this driver-level memory cap, preserving all existing bytes/whitespace and appending only at the tail, per AGENTS.md:125. [also at: inferencex-e2e/perf-changelog.yaml:1 - This PR changes srun/sbatch memory limits for every existing DRAM-offload (KV_OFFLOADING=dram) job cluster-wide, which can newly OOM-kill runs that previously completed, but ships with no perf-changelog.yaml entry.]

Why this was flagged

AGENTS.md:125 states every change that can affect benchmark performance requires a new append-only inferencex-e2e/perf-changelog.yaml entry. This PR (inferencex-e2e/infx/launch/drivers/srt/init.py:54-58, config.py:140-141,167-175) changes the sbatch mem directive and srun --mem option for every KV_OFFLOADING=dram single-node point, which can OOM-kill steps that previously had no memory cap. git diff d265460..HEAD touches only init.py, config.py and test_srt_config.py; perf-changelog.yaml is unmodified. No safeguard (CI check) is visible in this checkout enforcing the changelog rule, so the operational effect ships without the required record of the behavior change.

Verification: normal (mandatory repo invariant violated by this change). git diff d265460..HEAD --name-only shows only infx/launch/drivers/srt/init.py, config.py, and tests/launch/test_srt_config.py; inferencex-e2e/perf-changelog.yaml is NOT in the diff, and its tail still ends at PR #3602 with no new entry. init.py:54-58 computes node_mib/step_mib passed into srun_options and SrtJob; config.py:140-141 emits an sbatch mem directive and config.py:167-175 injects options["mem"] into SRT_SRUN_OPTIONS.

Comment on lines 54 to 61
node_mib = (
run.cluster.available_cpu_dram_mib if request.env.get("KV_OFFLOADING") == "dram" else None
)
step_mib = node_mib and node_mib * request.gpu_count // run.cluster.gpus_per_node
if (options := config.srun_options(run.backend.settings, step_mib)) is not None:
run.env["SRT_SRUN_OPTIONS"] = options
if rc := submit.bind_point(run, checkout, root / "arguments"):
return rc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) On clusters like h100-cw and h200-cw (available-cpu-dram-mib set plus srt-slurm.single-node-exclusive: false in runners.yaml), a DRAM-offload single-node job now gets sbatch --mem=M via mem_mib=node_mib (init.py:75, config.py:140-141), monopolizing the node's memory even though use_exclusive_sbatch_directive stays false. Requesting the whole node's DRAM without --exclusive blocks Slurm from co-scheduling any other job on that node even when GPUs/CPUs are free, defeating the cluster's intentional non-exclusive sharing for every KV_OFFLOADING=dram job there. Fix: scale the sbatch mem request to the job's own GPU share (like step_mib already does) or only apply the full node-DRAM --mem directive when single_node_exclusive is true.

Why this was flagged

Trigger: a KV_OFFLOADING=dram single-node point on h100-cw or h200-cw, which set available-cpu-dram-mib (runners.yaml:306,361) and srt-slurm.single-node-exclusive: false (runners.yaml:323,379). init.py:54-57,75 sets node_mib=run.cluster.available_cpu_dram_mib unconditionally and passes it as SrtJob.mem_mib with no dependency on single_node_exclusive. config.py:140-141 then always adds default_sbatch_directives.mem=f'{node_mib}M' when job.mem_mib is set, while use_exclusive_sbatch_directive (config.py:133-135) is computed separately and stays false for these clusters. Base behavior: before this diff no mem directive was ever emitted, so such nodes could host multiple concurrent single-node jobs by GPU/gres sharing; after merging, one job's full-node --mem request prevents Slurm from allocating any other job to that node until it finishes, silently serializing throughput the non-exclusive setting was meant to allow.

Verification: Nit; acknowledged in diff (partially). init.py:54-57,75 sets node_mib = run.cluster.available_cpu_dram_mib for any KV_OFFLOADING=dram single-node point and passes it as SrtJob.mem_mib with no dependency on exclusivity; config.py:140-141 always emits the sbatch mem directive, while config.py:133-134 sets use_exclusive_sbatch_directive = srt.single_node_exclusive independently.

@cquil11 cquil11 changed the title fix(launch): cap single-node DRAM offload srun steps at their GPUs' share of node DRAM fix(launch): give single-node srun steps their GPUs' share of node DRAM Sep 30, 2026
adibarra added a commit that referenced this pull request Oct 16, 2026
…it host pools to measured DRAM (#3804)

* fix(launch): cap single-node srun steps at their GPUs' share of node DRAM

Port of #3615 onto the current srt driver. Every single-node srun step gets
--mem = available-cpu-dram-mib / gpus-per-node * GPU_COUNT through
SRT_SRUN_OPTIONS, so a point using half the node can no longer take more than
half its host DRAM.

The sbatch job requests the whole available-cpu-dram-mib only when
slurm.srt-slurm.single-node-exclusive is true; on shared nodes it requests the
same GPU share, so it leaves the rest of the node to other jobs. Clusters
without available-cpu-dram-mib keep their previous behavior.

Co-authored-by: Cam Quilici <cjquilici@gmail.com>

* fix(agentx): keep single-node DRAM pools 32 GiB under the srun step cap

Three single-node AgentX DRAM arms pinned host KV pools at or within a few
GiB of their TP4 srun step's new --mem share of node DRAM:

- minimaxm3-fp4-b200-vllm-agentic-mtp TP4 SimpleCPU: dram-utilization
  1.0 -> 0.96, cpu_bytes_to_use and TOTAL_CPU_DRAM_GB 1082 -> 1038 GB
  (headroom 0.08 -> 41.1 GiB under 1,031,960 MiB).
- minimaxm3-fp4-b200-trtllm-agentic-mtp TP4: per-rank host_cache_size
  250 -> 240 GiB (headroom 7.8 -> 47.8 GiB under 1,031,960 MiB).
- qwen3.5-fp8-mi355x-sglang-agentic-mtp TP4 conc 64-80: per-rank
  hicache-size 400 -> 395 GB (headroom 21.5 -> 40.1 GiB under
  1,547,890 MiB).

Also trims the MiniMax-M3 B200 vLLM master-config comment to its rationale.

* fix(srt): request --mem=0 for exclusive single-node jobs

Slurm's RealMemory on b200-nscale and b300-dsxe is below available-cpu-dram-mib, so sbatch rejected the whole-node --mem. Exclusive jobs now take all the memory Slurm has, and a step that spans the whole node uses the job's memory instead of a share cap.

* fix(runners): set available-cpu-dram-mib to what Slurm can allocate

Measured on each cluster's GPU nodes as the lower of RealMemory - MemSpecLimit and MemTotal less 16 GiB: h100-dgxc and b300-dsxe reserve 128 GiB through MemSpecLimit, b200-nscale configures RealMemory 15.5 GiB under MemTotal, and the AMD clusters set RealMemory to MemTotal.

* fix(recipes): match TOTAL_CPU_DRAM_GB to measured node DRAM

The lower available-cpu-dram-mib on h100-dgxc, b200-nscale and mi300x-amd
lowers the matrix total-cpu-dram-gb of their single-node DRAM offload points,
and the single-node binder rejected every recipe variant still naming the old
TOTAL_CPU_DRAM_GB. Each variant now names the new budget, and host pools sized
from it follow their recipe's rule:

- DSv4 B200 vLLM: SimpleCPU bytes per rank = budget / 8, 243.375 -> 241.5 GB.
- MiniMax-M3 B200 vLLM: cpu_bytes_to_use = budget, 1038 -> 1030 GB.
- MiniMax-M3 H100 vLLM: Mooncake segment = (budget GiB - 414) / 8 - 30,
  119 -> 105 GB.
- MiniMax-M3 MI300X vLLM: LMCache L1 = 8 x budget / 8, 1296 -> 1280 GB.
- Qwen3.5 B200 SGLang: hicache-size = (budget - TP) * 15 / TP / 31, 104 -> 103.
- Qwen3.5 H100 SGLang: hicache-size = budget / TP8 / 2 pools, 108 -> 100.

GLM-5.2 B200's 169 GB/rank pool (~1,662 GB with the DSA indexer) and the
MiniMax-M3 B200 TRT-LLM per-rank pools, which are not derived from the budget,
still fit. Every pool stays 32+ GiB under its srun step's share of node DRAM.

* fix(recipes): keep single-node DRAM pools within the 3 TB policy share

Validation OOM-killed the Qwen3.5 MI355X FP8 TP4 CONC 64 HiCache point
inside its srun step cap: 4 x 395 GB per-rank pools also exceeded the 3 TB
policy share of four GPUs (1500 GB). The MiniMax-M3 B300 TRT-LLM TP2/TP4
host caches (388.6 GB per rank) exceeded theirs too (750/1500 GB).

- Qwen3.5 MI355X FP8: dram-utilization 0.80 -> 1.0, hicache-size at
  CONC 64-80 395 -> 374 GB per rank (budget 1199 -> 1499 GB).
- MiniMax-M3 B300 TRT-LLM: dram-utilization 0.8 -> 1.0, TP2/TP4
  host_cache_size 388554555392 -> 374500000000/374750000000 bytes;
  TOTAL_CPU_DRAM_GB 599/1199/2399 -> 749/1499/2999.

The budgets of two formula-sized pools now name the pool itself, as the
refactor stack's '@dram' references expect: MiniMax-M3 MI300X 0.80 -> 0.7972
(TOTAL_CPU_DRAM_GB 1284 -> 1280, the LMCache L1) and MiniMax-M3 H100
0.80 -> 0.449 (1607 -> 902, eight 105 GiB Mooncake segments).

* fix(srt): cap single-node steps only on shared nodes

A step capped at its GPUs' share of node DRAM leaves no room for the
engine and page cache once its host pools fill the share:
qwen3.5-fp8-mi355x-sglang-agentic-mtp TP4 HiCache failed inside the step
cgroup ("Requesting 150.27 GB but only have 15.04 GB free") with its
pools at the DRAM policy limit.

Exclusive single-node jobs keep requesting --mem=0 and no longer cap
their srun steps, so pools must fit the node. On shared nodes the job and
its step still get the serving GPUs' share, which pools must fit.

* fix(runners): restore h100-dgxc DRAM; earlier value was measured on another cluster

8c9da40 took h100-dgxc's 1,915,904 MiB from login-h100, which fronts the
DSXE H100 cluster (partitions batch_1/batch_all, /data), not the DGX Cloud
cluster runners.yaml calls h100-dgxc (partition hpc-gpu-1, /mnt/nfs). The
128 GiB MemSpecLimit that commit's message attributes to h100-dgxc is the
DSXE cluster's; h100-dgxc was not measured.

Restore 2,063,837 MiB and undo the H100 values derived from the wrong one:
- Qwen3.5 H100 SGLang: hicache-size 100 -> 108 GB, TOTAL_CPU_DRAM_GB
  1607 -> 1731.
- MiniMax-M3 H100 vLLM: dram-utilization 0.449 -> 0.80, Mooncake segment
  105 -> 119 GB, TOTAL_CPU_DRAM_GB 902 -> 1731.
Every h100-dgxc and h100 matrix row and H100 recipe again matches main.

CONFIGS.md gives the Slurm rule as how to measure the field, not as what
every recorded value is. runners.yaml h100-dgxc has no registered runners;
the DSXE H100 runners (cluster:h100-dsxe) have no runners.yaml entry.

* refactor(srt): tighten single-node DRAM test, comments and docs

Condense the driver's DRAM cap comment, the available-cpu-dram-mib doc and
the MiniMax-M3 B200 dram-utilization comment; drop the test comment that
repeated the driver's and name the test for all three of its cases; fix the
MI300X LMCache recipe comment, whose L1 is now the whole 1280 GB budget
rather than shares of 1284 GB.

精简驱动中的 DRAM 上限注释、available-cpu-dram-mib 文档和 MiniMax-M3 B200
的 dram-utilization 注释;删除重复驱动注释的测试注释,并按三种情形重命名该
测试;修正 MI300X LMCache 配方注释:其 L1 现为完整的 1280 GB 预算,而非
1284 GB 的分摊份额。

* chore: drop comments that break the code comment rules

Main now keeps infx free of explanatory comments and bans header comments in configs. Remove the driver's DRAM cap comment, which CONFIGS.md already documents, and the MiniMax-M3 B200 vLLM master-config header.

* test(srt): give the DRAM-cap test cluster a GPU exporter

Main's native single-node power telemetry (#3781) stages the cluster's GPU exporter for every non-eval single-node point and stops preparation when the cluster record has none, so the DRAM-cap driver test's ad hoc cluster now needs default_gpu_exporter to reach submission.

* fix(recipes): size minimaxm3 b200 host pools to their whole-node DRAM budget

b200-nscale runs single-node jobs whole-node (--mem=0), so the TP4 pools no
longer need headroom under a per-GPU step cap. The vLLM SimpleCPU pool and the
TRT-LLM per-rank host cache now take the full TP4 budget (dram-utilization 1.0,
1073 GB), and the TRT TP4 rows report the budget their pool actually uses
instead of a nominal 0.8 one.

---------

Co-authored-by: Cam Quilici <cjquilici@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant