Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5d79351
feat(agentx): add H100 NVMe KV offload sweep
cquil11 Aug 28, 2026
44d1af3
feat(agentx): add tiered DRAM and NVMe sweep
cquil11 Aug 28, 2026
923f60d
test(agentx): cover tiered KV offload matrices
cquil11 Aug 28, 2026
3ebc733
refactor(agentx): represent tiered offload as tier list
cquil11 Aug 28, 2026
0e41fd1
perf: schedule H100 MiniMax-M3 offload sweep
cquil11 Aug 28, 2026
ef3c36e
fix(agentx): extend H100 allocation time
cquil11 Aug 28, 2026
366d271
fix(h100): clean NVMe before releasing allocation
cquil11 Aug 28, 2026
0545bda
fix(agentx): reject multinode NVMe offload
cquil11 Aug 28, 2026
8de9e47
fix(h100): exclude node with broken NVLink P2P
cquil11 Aug 28, 2026
ae673c5
fix(h100): clean stale vLLM offload memory
cquil11 Aug 28, 2026
d83dd9d
fix(h100): purge stale NVMe offload directories
cquil11 Aug 28, 2026
f436e23
fix(h100): bound offload cleanup steps
cquil11 Aug 28, 2026
e631356
simplify H100 offload lifecycle
cquil11 Aug 28, 2026
fc9433a
test updated cache-source image
cquil11 Aug 28, 2026
48389ac
Merge remote-tracking branch 'origin/main' into codex/h100-minimaxm3-…
cquil11 Aug 28, 2026
1a0793b
bound in-container NVMe cleanup
cquil11 Aug 28, 2026
3cb3641
simplify H100 offload launcher
cquil11 Aug 28, 2026
893cd1f
test tiering timeout image
cquil11 Aug 28, 2026
8a00c0a
clean stale vLLM offload mmap files
cquil11 Aug 31, 2026
a4c2a6c
document stale offload cleanup
cquil11 Aug 31, 2026
b52c639
Merge remote-tracking branch 'origin/main' into codex/h100-minimaxm3-…
cquil11 Aug 31, 2026
cb0fec2
Narrow H100 cache-source validation sweep
cquil11 Sep 1, 2026
90dc048
Link reduced cache-source validation PR
cquil11 Sep 1, 2026
fd88716
Merge remote-tracking branch 'origin/main' into codex/h100-cache-sour…
cquil11 Sep 1, 2026
c23f2c4
fix(agentx): allow tiered warmup to finish and isolate profiling metrics
cquil11 Sep 1, 2026
7fd501f
Merge remote-tracking branch 'origin/main' into codex/h100-cache-sour…
cquil11 Sep 1, 2026
a9fc330
Merge remote-tracking branch 'origin/main' into codex/h100-cache-sour…
cquil11 Sep 1, 2026
fe0551b
Merge upstream main before offload recovery sweep
cquil11 Sep 2, 2026
992df5d
fix(agentx): recover offload scheduler progress and budget NVMe warmup
cquil11 Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions benchmarks/benchmark_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,19 @@ require_agentic_kv_offload_backend() {
fi
return 1
;;
dram)
dram|nvme|dram+nvme)
if [[ "${KV_OFFLOAD_BACKEND:-}" != "$expected_backend" ]]; then
echo "Error: expected KV_OFFLOAD_BACKEND=$expected_backend when KV_OFFLOADING=dram, got '${KV_OFFLOAD_BACKEND:-}'" >&2
echo "Error: expected KV_OFFLOAD_BACKEND=$expected_backend when KV_OFFLOADING=$KV_OFFLOADING, got '${KV_OFFLOAD_BACKEND:-}'" >&2
exit 1
fi
if [[ ! "${TOTAL_CPU_DRAM_GB:-}" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: DRAM KV offloading requires a positive TOTAL_CPU_DRAM_GB capacity" >&2
if [[ "$KV_OFFLOADING" != "nvme" && ! "${TOTAL_CPU_DRAM_GB:-}" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: $KV_OFFLOADING KV offloading requires a positive TOTAL_CPU_DRAM_GB capacity" >&2
exit 1
fi
return 0
;;
*)
echo "Error: unsupported KV_OFFLOADING value '$KV_OFFLOADING' (expected one of: none, dram)" >&2
echo "Error: unsupported KV_OFFLOADING value '$KV_OFFLOADING' (expected one of: none, dram, nvme, dram+nvme)" >&2
exit 1
;;
esac
Expand Down Expand Up @@ -108,18 +108,18 @@ if [[ "$_benchmark_caller" == */agentic/* ||
exit 1
fi
;;
dram)
dram|nvme|dram+nvme)
if [[ -z "${KV_OFFLOAD_BACKEND:-}" || "${KV_OFFLOAD_BACKEND:-}" == "none" ]]; then
echo "Error: KV_OFFLOAD_BACKEND is required when KV_OFFLOADING=dram" >&2
echo "Error: KV_OFFLOAD_BACKEND is required when KV_OFFLOADING=$KV_OFFLOADING" >&2
exit 1
fi
if [[ ! "${TOTAL_CPU_DRAM_GB:-}" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: DRAM KV offloading requires a positive configured TOTAL_CPU_DRAM_GB capacity" >&2
if [[ "$KV_OFFLOADING" != "nvme" && ! "${TOTAL_CPU_DRAM_GB:-}" =~ ^[1-9][0-9]*$ ]]; then
echo "Error: $KV_OFFLOADING KV offloading requires a positive configured TOTAL_CPU_DRAM_GB capacity" >&2
exit 1
fi
;;
*)
echo "Error: unsupported KV_OFFLOADING value '$KV_OFFLOADING' (expected one of: none, dram)" >&2
echo "Error: unsupported KV_OFFLOADING value '$KV_OFFLOADING' (expected one of: none, dram, nvme, dram+nvme)" >&2
exit 1
;;
esac
Expand Down
28 changes: 26 additions & 2 deletions benchmarks/single_node/agentic/minimaxm3_fp8_h100_mtp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -eo pipefail
set -x

# H100 MiniMax-M3 MXFP8 AgentX with EAGLE3 and optional Mooncake DRAM KV offload.
# H100 MiniMax-M3 MXFP8 AgentX with EAGLE3 and optional DRAM or NVMe KV offload.

source "$(dirname "$0")/../../benchmark_lib.sh"

Expand Down Expand Up @@ -97,13 +97,37 @@ EOF
--kv-transfer-config
'{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_connector_extra_config":{"load_async":true}}'
)
elif [ "$KV_OFFLOADING" = "nvme" ]; then
require_agentic_kv_offload_backend vllm-simple
: "${NVME_OFFLOAD_DIR:?NVME_OFFLOAD_DIR must be mounted by the H100 launcher}"
NVME_OFFLOAD_TOTAL_BYTES=8000000000000
NVME_OFFLOAD_PER_RANK_BYTES=$((NVME_OFFLOAD_TOTAL_BYTES / TP))
# vLLM appends .rank_<CUDA device index> to give each TP rank its own file.
OFFLOAD_ARGS=(
--kv-transfer-config
"{\"kv_connector\":\"SimpleCPUOffloadConnector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{\"kv_offload_backend\":\"disk\",\"disk_path\":\"$NVME_OFFLOAD_DIR/cache.bin\",\"disk_capacity_bytes\":$NVME_OFFLOAD_PER_RANK_BYTES,\"disk_buffer_slots\":4,\"lazy_offload\":false}}"
)
elif [ "$KV_OFFLOADING" = "dram+nvme" ]; then
require_agentic_kv_offload_backend vllm-native
: "${NVME_OFFLOAD_DIR:?NVME_OFFLOAD_DIR must be mounted by the H100 launcher}"
TOTAL_CPU_DRAM_GIB=$((TOTAL_CPU_DRAM_GB * 1000000000 / 1073741824))
PER_RANK_GIB=$(((TOTAL_CPU_DRAM_GIB - MODEL_CHECKPOINT_PAGE_CACHE_GIB) / TP - MODEL_CPU_OFFLOAD_GB - MOONCAKE_LOCAL_BUFFER_GIB))
Comment on lines +113 to +114

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) The new dram+nvme branch copy-pastes the Mooncake DRAM budget formula and still subtracts MOONCAKE_LOCAL_BUFFER_GIB (4 GiB/rank) even though this path uses OffloadingConnector/TieringOffloadingSpec, which never starts Mooncake or allocates that buffer, so the derived per-rank DRAM tier is needlessly smaller than the base branch would give a Mooncake-only run, and can trip the 'CPU DRAM budget is too small' exit at configs the actual budget could otherwise support. Fix: only subtract MOONCAKE_LOCAL_BUFFER_GIB when the connector actually uses Mooncake; for the dram+nvme/OffloadingConnector path compute PER_RANK_GIB from TOTAL_CPU_DRAM_GIB, MODEL_CHECKPOINT_PAGE_CACHE_GIB, and MODEL_CPU_OFFLOAD_GB only.

Extended reasoning...

Line 114 reuses the exact expression from the dram/Mooncake branch (line 65) verbatim, including '- MOONCAKE_LOCAL_BUFFER_GIB', inside the elif KV_OFFLOADING=dram+nvme block that instead builds an OffloadingConnector kv-transfer-config (lines 120-123) which has no Mooncake component and never sets MOONCAKE_CONFIG_PATH or starts mooncake_master. The 4 GiB/rank/TP is therefore withheld from the DRAM tier for no reason tied to this connector, and at the lower end of the DRAM budget this extra deduction can push PER_RANK_GIB to <=0, aborting the run (line 115-118) at a TOTAL_CPU_DRAM_GB value that would otherwise succeed.

Verification: nit. Factually real but negligible impact. Line 114 (PER_RANK_GIB=$(((TOTAL_CPU_DRAM_GIB - MODEL_CHECKPOINT_PAGE_CACHE_GIB) / TP - MODEL_CPU_OFFLOAD_GB - MOONCAKE_LOCAL_BUFFER_GIB))) in the new dram+nvme branch copies line 65's Mooncake formula verbatim, still subtracting MOONCAKE_LOCAL_BUFFER_GIB=4 (set line 59). That branch builds an OffloadingConnector/TieringOffloadingSpec config…

if (( PER_RANK_GIB <= 0 )); then
echo "Error: CPU DRAM budget is too small for checkpoint cache, model, and DRAM+NVMe KV offload" >&2
exit 1
fi
CPU_OFFLOAD_TOTAL_BYTES=$((PER_RANK_GIB * TP * 1073741824))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Native tier budget subtracts Mooncake buffer

Low Severity

The dram+nvme branch reuses the Mooncake DRAM formula and still subtracts MOONCAKE_LOCAL_BUFFER_GIB even though this path uses OffloadingConnector and never starts Mooncake. cpu_bytes_to_use is therefore undersized by 4 GiB per rank relative to the configured host DRAM budget.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fd88716. Configure here.

OFFLOAD_ARGS=(
--kv-transfer-config
"{\"kv_connector\":\"OffloadingConnector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{\"spec_name\":\"TieringOffloadingSpec\",\"cpu_bytes_to_use\":$CPU_OFFLOAD_TOTAL_BYTES,\"eviction_policy\":\"lru\",\"secondary_tiers\":[{\"type\":\"fs\",\"root_dir\":\"$NVME_OFFLOAD_DIR\",\"n_read_threads\":32,\"n_write_threads\":16,\"locality\":\"LOCAL\"}]}}"
)
else
echo "Error: unsupported KV_OFFLOADING='$KV_OFFLOADING'" >&2
exit 1
fi

export AIPERF_SERVER_METRICS_URLS="http://localhost:${PORT}/metrics"
export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="vllm:"
export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="vllm:prompt_tokens_cached_by_source"

NUM_SPEC_TOKENS=3
TOKENS_PER_SEQ=$((1 + NUM_SPEC_TOKENS))
Expand Down
9 changes: 5 additions & 4 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7499,7 +7499,7 @@ qwen3.5-fp4-b200-trt-mtp:
- { tp: 8, ep: 8, dp-attn: true, spec-decoding: "mtp", conc-list: [128, 256, 1024] }

minimaxm3-fp8-h100-vllm-agentic-mtp:
image: vllm/vllm-openai:v0.27.1
image: ttl.sh/cquil11-vllm-tier-progress-b9e9d720-amd64-20260902:24h
model: MiniMaxAI/MiniMax-M3-MXFP8
model-prefix: minimaxm3
runner: cluster:h100-dgxc
Expand All @@ -7508,11 +7508,12 @@ minimaxm3-fp8-h100-vllm-agentic-mtp:
multinode: false
scenarios:
agentic-coding:
# The fast sweep places the resident HBM cliff between c5 and c6.
# Narrow validation sweep for physical cache-source attribution.
- dram-utilization: 0.80
search-space:
- { tp: 8, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 2, 3, 4, 5] }
- { tp: 8, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [6, 8] }
- { tp: 8, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [8] }
- { tp: 8, spec-decoding: mtp, kv-offloading: nvme, kv-offload-backend: { name: vllm-simple }, conc-list: [30] }
- { tp: 8, spec-decoding: mtp, kv-offloading: [dram, nvme], kv-offload-backend: { name: vllm-native }, conc-list: [20] }

minimaxm3-fp8-h200-vllm-agentic-mtp:
image: vllm/vllm-openai:v0.27.1
Expand Down
5 changes: 5 additions & 0 deletions docs/eval-agentx-procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ For a publishable SWE-bench score, omit `eval-limit`. Do not use `single-shot`,

Treat fast results as bring-up evidence, never as a replacement for the canonical candidate. A duration below 900 seconds or `AIPERF_UNSAFE_OVERRIDE=true` adds AIPerf's `--unsafe-override` and flags the submission invalid. Use it only for smoke diagnosis ([source](../benchmarks/benchmark_lib.sh#L2266-L2268)). After a fast run is healthy, run the exact candidate canonically before claiming benchmark success.

The H100 MiniMax-M3 NVMe and DRAM+NVMe AgentX launcher allows 420 minutes for Slurm:
measured warmup alone took over four hours before the one-hour profile.
Other H100 single-node cases retain the 300-minute default, and an explicit
`SALLOC_TIME_LIMIT` overrides either value. The canonical workload is unchanged.

## 8. Preserve trace and run provenance

AgentX defaults to recorded assistant-response replay. Live server outputs are measured but discarded when constructing later turns. Set `AIPERF_DATASET_WEKA_LIVE_ASSISTANT_RESPONSES=1` only for an explicitly different live-assistant experiment. The selected trace corpus is model-family dependent unless `WEKA_LOADER_OVERRIDE` pins it. The resolver logs both loader and Hugging Face dataset ([trace resolution](../benchmarks/benchmark_lib.sh#L2023-L2102), [replay semantics](../benchmarks/benchmark_lib.sh#L2104-L2270)).
Expand Down
5 changes: 5 additions & 0 deletions docs/eval-agentx-procedures_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ gh workflow run e2e-tests.yml --repo SemiAnalysisAI/InferenceX --ref "$REF" \

Fast 结果只能作为 bring-up 证据,绝不能替代 canonical candidate。小于 900 秒的 duration 或 `AIPERF_UNSAFE_OVERRIDE=true` 会添加 AIPerf 的 `--unsafe-override` 并将 submission 标记为无效;只能用于 smoke 诊断([源码](../benchmarks/benchmark_lib.sh#L2266-L2268))。Fast 运行健康后,必须对完全相同的 candidate 进行 canonical 运行,才能宣称 benchmark 成功。

H100 MiniMax-M3 NVMe 和 DRAM+NVMe AgentX 启动器的 Slurm 时限为 420 分钟:
实测 warmup 本身超过四小时,之后还需运行一小时的 profiling。
其他 H100 单节点场景仍默认使用 300 分钟;显式设置 `SALLOC_TIME_LIMIT`
可覆盖任一默认值。canonical 工作负载保持不变。

## 8. 保留 trace 与运行 provenance

AgentX 默认 replay 已记录的 assistant response。实时服务输出会被测量,但构造后续 turn 时会丢弃。只有在明确要进行不同的 live-assistant 实验时,才设置 `AIPERF_DATASET_WEKA_LIVE_ASSISTANT_RESPONSES=1`。除非用 `WEKA_LOADER_OVERRIDE` 固定,否则所选 trace corpus 依赖模型 family;resolver 会同时记录 loader 与 Hugging Face dataset([trace 解析](../benchmarks/benchmark_lib.sh#L2023-L2102)、[replay 语义](../benchmarks/benchmark_lib.sh#L2104-L2270))。
Expand Down
32 changes: 32 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6821,3 +6821,35 @@
description:
- "Refresh to collect TensorRT-LLM server metrics."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2774

- config-keys:
- minimaxm3-fp8-h100-vllm-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Run a narrow H100 MiniMax-M3 AgentX validation on the patched vLLM image that exports the bounded cached-token sources device, cpu, disk, p2p, and external and includes vLLM PR #53087's bounded fallback for stalled tier-primary writes."
- "Validate one Mooncake DRAM point at TP8 concurrency 8 and one NVMe-only point at TP8 concurrency 30 using SimpleCPUOffloadConnector's disk backend with 8 TB aggregate capacity."
- "Validate one declarative kv-offloading [dram, nvme] point at TP8 concurrency 20, mapped by the vLLM recipe to OffloadingConnector's TieringOffloadingSpec with an LRU DRAM primary tier and node-local filesystem secondary tier."
- "Mount a job-scoped directory from the H100 node's native NVMe filesystem into the Pyxis container."
- "Collect the vLLM Prometheus endpoint through AIPerf so artifacts include vllm:prompt_tokens_cached_by_source alongside the native KV-offload tiering counters."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2796

- config-keys:
- minimaxm3-fp8-h100-vllm-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Allow seven hours for the native DRAM+NVMe point after its canonical warmup exceeded four hours; retain the 300-minute H100 default for other cases."
- "Pin the AgentX client fix that excludes automatic warmup baselines from profiling server metrics and require the cached-token-source metric in JSON/CSV artifacts."
- "Keep exactly Mooncake DRAM c8, Simple NVMe c30, and native DRAM+NVMe c20, with unchanged canonical warmup, one-hour profiling, images, and full evals."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2796

- config-keys:
- minimaxm3-fp8-h100-vllm-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Use the Python-only token-source image b9e9d720 on official vLLM nightly 7c5dc571, retaining the HIT_PENDING fix and backporting upstream PR #45406 so an unschedulable queue head cannot strand completed async KV loads."
- "Allow seven hours for Simple NVMe as well as native DRAM+NVMe; the NVMe warmup took four hours and its previous five-hour allocation ended before profiling completed."
- "Preserve exactly Mooncake DRAM c8, Simple NVMe c30 with 8 TB aggregate capacity, and native DRAM+NVMe c20, with full evals and unchanged canonical warmup and one-hour profiling."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2796
35 changes: 30 additions & 5 deletions runners/launch_h100-dgxc-slurm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,40 @@ else

export GPU_COUNT="${GPU_COUNT:-${TP:?TP must be set}}"

salloc --partition=$SLURM_PARTITION --account=$SLURM_ACCOUNT --gres=gpu:$GPU_COUNT --exclusive --time=180 --no-shell --job-name="$RUNNER_NAME"
# These NVMe AgentX points can need >4 hours of warmup before a 1-hour profile.
if [[ "${MODEL_PREFIX:-}" == "minimaxm3" && "${SCENARIO_TYPE:-}" == "agentic-coding" && ( "${KV_OFFLOADING:-}" == "nvme" || "${KV_OFFLOADING:-}" == "dram+nvme" ) ]]; then
SALLOC_TIME_LIMIT="${SALLOC_TIME_LIMIT:-420}"
fi
SALLOC_TIME_LIMIT="${SALLOC_TIME_LIMIT:-300}"
salloc --partition="$SLURM_PARTITION" --account="$SLURM_ACCOUNT" \
--gres="gpu:$GPU_COUNT" --exclusive --time="$SALLOC_TIME_LIMIT" \
--no-shell --job-name="$RUNNER_NAME"
JOB_ID=$(squeue --name="$RUNNER_NAME" -u "$USER" -h -o %A | head -n1)
if [[ -z "$JOB_ID" ]]; then
echo "ERROR: failed to resolve H100 Slurm allocation" >&2
exit 1
fi
trap 'rc=$?; scancel "$JOB_ID" 2>/dev/null || true; exit "$rc"' EXIT
cleanup_allocation() {
local rc=$?
trap - EXIT INT TERM
scancel "$JOB_ID" 2>/dev/null || true
exit "$rc"
}
trap cleanup_allocation EXIT INT TERM

NVME_CONTAINER_MOUNT=""
if [[ "${KV_OFFLOADING:-none}" == "nvme" || "${KV_OFFLOADING:-none}" == "dram+nvme" ]]; then
NVME_HOST_ROOT="/mnt/numa0/enroot/cache/group-$(id -g)"
NVME_HOST_DIR="$NVME_HOST_ROOT/inferencex-kv-$JOB_ID"
srun --jobid="$JOB_ID" bash -c "
set -e
test -w '$NVME_HOST_ROOT'
mkdir -m 700 '$NVME_HOST_DIR'
findmnt -T '$NVME_HOST_DIR'
"
NVME_CONTAINER_MOUNT=",$NVME_HOST_DIR:/kv-offload"
export NVME_OFFLOAD_DIR=/kv-offload
Comment on lines +322 to +333

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.

🔴 The new nvme/dram+nvme branch creates a job-scoped NVMe cache directory (/mnt/numa0/enroot/cache/group-$(id -g)/inferencex-kv-$JOB_ID) and mounts it into the container, but nothing ever removes it — the cleanup_allocation trap only cancels the Slurm job, it never rm -rf's NVME_HOST_DIR. Fix: have cleanup_allocation (or an added trap step) delete NVME_HOST_DIR when it was created, so it also runs after cache.bin writes for both new tiers, not just on the happy path.

Extended reasoning...

Every sweep run using kv-offloading=nvme or dram+nvme allocates a unique per-JOB_ID directory on the shared host NVMe and writes disk_capacity_bytes-sized cache.bin/tiering files into it (up to ~1TB per TP rank), but cleanup_allocation() at lines 296-303 only calls scancel on EXIT/INT/TERM and never touches NVME_HOST_DIR. Unlike the pre-existing dram/none modes which never wrote to local NVMe, repeated CI sweep invocations now leave orphaned multi-hundred-GB directories under /mnt/numa0/enroot/cache/group-*/ that accumulate indefinitely across runs, eventually exhausting shared NVMe capacity on the H100 cluster.

Verification: Severity: normal — a resource leak this change introduces that the base branch does not have. The nvme/dram+nvme path creates a per-job directory on the compute node's local NVMe and mounts it into the container as the KV-offload target, but nothing ever deletes it: - runners/launch_h100-dgxc-slurm.sh:311-318 (in the diff) creates it via srun --jobid:… | normal (resource leak on shared host…

fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NVMe job cache is never cleaned up

High Severity

The launcher creates a job-scoped NVMe directory under the node's enroot cache and mounts it for nvme and dram+nvme runs, but cleanup_allocation only cancels the Slurm job. Up to 8 TB of KV files stay on the shared NVMe after every run, so later jobs and enroot imports can fail with no space.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fd88716. Configure here.


# Check the shared cache before opening its lock. A valid squash file is
# immutable, so readers do not need to touch a lock owned by another user.
Expand All @@ -327,12 +354,10 @@ else

srun --jobid=$JOB_ID \
--container-image=$SQUASH_FILE \
--container-mounts=$GITHUB_WORKSPACE:/workspace/,$HF_HUB_CACHE_MOUNT:$HF_HUB_CACHE,$AIPERF_MMAP_CACHE_HOST_PATH:/aiperf_mmap_cache \
--container-mounts=$GITHUB_WORKSPACE:/workspace/,$HF_HUB_CACHE_MOUNT:$HF_HUB_CACHE,$AIPERF_MMAP_CACHE_HOST_PATH:/aiperf_mmap_cache$NVME_CONTAINER_MOUNT \
--no-container-mount-home \
--container-workdir=/workspace/ \
--no-container-entrypoint --export=ALL,PORT=8888,AIPERF_DATASET_MMAP_CACHE_DIR=/aiperf_mmap_cache \
bash benchmarks/single_node/${SCENARIO_SUBDIR}${EXP_NAME%%_*}_${PRECISION}_h100${SPEC_SUFFIX}.sh

scancel $JOB_ID

fi
29 changes: 29 additions & 0 deletions runners/test_slurm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,35 @@ def run_bash(command: str, *args: Path | str) -> subprocess.CompletedProcess[str
)


def test_h100_nvme_agentx_time_limit_preserves_defaults_and_override() -> None:
launcher = (REPO_ROOT / "runners" / "launch_h100-dgxc-slurm.sh").read_text()
start = launcher.index(" # These NVMe AgentX points")
stop = launcher.index(" salloc ", start)
configure = launcher[start:stop]
cases = [
("minimaxm3", "agentic-coding", "dram+nvme", "", "420"),
("minimaxm3", "agentic-coding", "dram+nvme", "480", "480"),
("minimaxm3", "agentic-coding", "dram", "", "300"),
("minimaxm3", "agentic-coding", "nvme", "", "420"),
("minimaxm3", "agentic-coding", "nvme", "480", "480"),
("other", "agentic-coding", "dram+nvme", "", "300"),
("minimaxm3", "fixed-sequence", "dram+nvme", "", "300"),
]
for model, scenario, offload, override, expected in cases:
result = run_bash(
'MODEL_PREFIX="$1"; SCENARIO_TYPE="$2"; KV_OFFLOADING="$3"; '
'SALLOC_TIME_LIMIT="$4";\n'
+ configure
+ '\nprintf "%s" "$SALLOC_TIME_LIMIT"',
model,
scenario,
offload,
override,
)
assert result.returncode == 0, result.stderr
assert result.stdout == expected


def test_copy_agentic_results_stages_only_matching_points(tmp_path: Path) -> None:
source = tmp_path / "source"
workspace = tmp_path / "workspace"
Expand Down
2 changes: 1 addition & 1 deletion utils/aiperf
Loading
Loading