Skip to content

feat(nvsnap): multi-GPU checkpoint/restore on criu-v2 - #1184

Draft
balajinvda wants to merge 12 commits into
mainfrom
nvsnap/multigpu-nvls
Draft

feat(nvsnap): multi-GPU checkpoint/restore on criu-v2#1184
balajinvda wants to merge 12 commits into
mainfrom
nvsnap/multigpu-nvls

Conversation

@balajinvda

@balajinvda balajinvda commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Draft. The mechanism works and is measured. The reason it is draft is the configuration compromise below, which a reviewer should push back on before this is more than opt-in.

Why

Multi-GPU workloads fall back to the cachedir path, which captures the container filesystem and no GPU state, so a restored pod reloads weights from scratch. This makes criu-v2 work for tensor-parallel workloads, with weights and KV cache in the checkpoint.

The compromise

cuda-checkpoint refuses to checkpoint a process holding cross-GPU state, and every high-performance TP transport creates exactly that. So capture only succeeds once all of them are off, and the workload has to be started that way. This is the cost of the feature, not a tuning detail:

vLLM

--enforce-eager --disable-custom-all-reduce
NCCL_P2P_DISABLE=1 NCCL_NVLS_ENABLE=0 NCCL_SHM_DISABLE=1
VLLM_ALLREDUCE_USE_SYMM_MEM=0

NIM / TRT-LLM

NCCL_P2P_DISABLE=1 NCCL_NVLS_ENABLE=0 NCCL_SHM_DISABLE=1

What that costs:

  • Tensor-parallel all-reduce falls back to sockets instead of NVLink. Every layer boundary in a TP model pays it. Not benchmarked here, and it should be before anyone judges the tradeoff acceptable.
  • vLLM additionally runs without CUDA graphs, adding per-token launch overhead.

--enforce-eager is worth separating from the rest, because it is the one item with an exit. A captured CUDA graph holds GPU resources the checkpoint destroys, so replaying it after restore drives dead handles. It fails late and misleadingly: capture succeeds, restore succeeds, /v1/models answers, and the first real inference hangs. Nothing outside the engine can fix it, since NCCL has no record of which graphs captured its kernels and cannot invalidate them. An engine that re-captured graphs after restore would remove this flag. The NCCL knobs need either a transparent sever or newer driver support.

NIM is the interesting exception: TRT-LLM configures graphs at engine build time and the image exposes no knob, so it ran with graphs and both capture and post-restore inference still worked. That is observed, not explained, and I would not generalise from it.

A bisect showed the vLLM set is close to irreducible: --enforce-eager and NCCL_P2P_DISABLE each break capture on their own, and one of SHM/SYMM_MEM is also required. --disable-custom-all-reduce was not isolated individually, so treat the set as known-good, not minimal.

The NVLS entry was previously spelled NCCL_NVLS_DISABLE=1, which is not an NCCL variable at all - the shipped libnccl.so.2 contains NCCL_NVLS_ENABLE but no NCCL_NVLS_DISABLE. It had been a no-op the whole time, which is why the bisect could never attribute anything to it. NVLS is off regardless (NCCL_DEBUG=INFO reports 0 nvls channels, since P2P and SHM disabled make NCCL treat the ranks as separate nodes), so this corrects the spelling rather than the behaviour. Re-validated end to end on the corrected flag: checkpoint 2m35s / 56G, restore 1m00s, inference OK - identical to the original run.

What changed

Multi-GPU capture on criu-v2, opt-in via NVSNAP_MULTI_GPU_CRIU=1. Ordinary criu-v2 engine throughout: in-namespace dump, cuda_plugin driving cuda-checkpoint per rank, no interception library and no D2H. NVSNAP_LEGACY_MULTI_GPU_D2H=1 selects the old path. These were previously one switch, which silently routed runs down the path nobody meant to test. Neither is on by default.

New workloads rather than edits to existing ones, so the two engines never share a manifest: vllm-tp2-criu, vllm-70b-criu, nim-qwen3-32b-criu, sglang-tp2-criu.

Two agent defects fixed on the way:

  • GPU device counting passed nvidia-smi an LD_LIBRARY_PATH with the driver tree ahead of the container's. The driver tree ships its own libc, nvidia-smi aborted on the symbol mismatch, the query "failed", and every workload was counted as single-GPU. Fail-open, and it hid that it had failed.
  • Restore placeholders now run as root. They write /proc/sys/kernel/ns_last_pid, privileged does not confer root, and an image defaulting to a non-root uid fails that write silently, so the pid range is never reserved and restore dies with Can't fork for 336: File exists. This was known only as a hand-written note on one manifest; it applies to any non-root image and belongs in the generator.

Plus --gpu-map, exposing the r580 GPU-migration gpuPairs on restore so a checkpoint is not pinned to the GPU slot it came from. Needs CUDA 13 headers, so the builder stage moves to 13.0.3-devel-ubuntu22.04, staying on ubuntu22.04 to match the criu bundle's glibc 2.35.

Customer Release Notes

Not customer visible. Off by default, and the configuration compromise above has to be resolved before it is offered.

Plan Summary

Not applicable.

Usage

# agent: agent.extraEnv NVSNAP_MULTI_GPU_CRIU=1
CAPTURE_PATH=criu-v2 ./scripts/test-e2e.sh vllm-tp2-criu
CAPTURE_PATH=criu-v2 CHECKPOINT_TIMEOUT=2400 ./scripts/test-e2e.sh vllm-70b-criu

Testing

8x H100 80GB (p5.48xlarge), driver 580.126.16:

workload                 engine    checkpoint      restore   result
TinyLlama TP=2           vLLM      2m34s   56G     1m00s     PASS (x3)
TinyLlama TP=4           vLLM      4m16s  110G     1m14s     PASS
Llama-3.1-70B TP=4       vLLM      9m56s  290G     2m02s     PASS
Qwen3-32B TP=2 (NIM)     TRT-LLM   3m03s  103G     1m55s     PASS
Llama-3.1-8B TP=2        SGLang    hangs at capture          FAIL

Passes were checked for completeness, not exit status: a partial capture still exits zero and still writes a well-formed checkpoint, just a smaller one. Verified every rank present in the dumped tree, cuda_plugin pausing every GPU pid, one GPU image per rank matching the per-GPU memory budget, and the restored pod serving live inference.

Restore scales better than size: 5x the data costs 2x the time, and the 70B restore moved 290G at roughly 2.4 GB/s against 0.9 GB/s on single GPU. Per-rank restores overlap rather than serialise, which contradicts the premise behind the deferred parallelisation work in #437.

Unit tests: 18 cases for --gpu-map parsing, 13 for multi-pid save ordering, 6 for the capture guard. Load-bearing assertions were mutation-checked, including a transposed GPU pair, which parses cleanly and would migrate onto the wrong device.

Notes

SGLang hangs at capture with criu and cuda-checkpoint both wedged in a plain pipe read. Four hypotheses are dead: device fds and peer mappings match a passing vLLM rank, --disable-overlap-schedule does not help, and its peer features are opt-in and were never on. The workload ships so the failure is reproducible rather than a note.

This extends the custom cuda-checkpoint CLI that #730 proposes removing. Upstream's CLI does not expose GPU migration, so adopting it would drop --gpu-map unless NVIDIA adds it. Worth a decision, not a merge conflict discovered later.

Only the four criu-v2 restore placeholders are regenerated. The rest need the same runAsUser correction but carry hand-edits that #965 fixes, so regenerating now would collide.

References

Relates to #1183
Relates to #437
Relates to #730

Related Pull Requests

#965 (touches the same generated placeholders; land first)

Dependencies

None. CUDA builder image moves 12.8.1 to 13.0.3, both nvidia/cuda:*-devel-ubuntu22.04.

balaji-g and others added 10 commits August 22, 2026 15:42
A checkpoint is currently pinned to the physical GPU it was captured on:
restore recreates its device state on the same device or not at all. That makes
a restore un-schedulable anywhere except the slot it came from, which is the
wrong constraint for a platform whose whole argument is that a restore can land
wherever there is capacity.

The driver has supported remapping since r580 through gpuPairs on the restore
args, taking a source device UUID and a target device UUID per GPU. Neither our
CLI nor upstream's exposed it. This adds --gpu-map, applied on restore, resume,
and the restore half of toggle, and ignored with a diagnostic on lock and
checkpoint so a map given on the capture half is not silently dropped.

Each side of a pair is a device index or a UUID, accepted with or without the
GPU- prefix and dashes so operators can paste what nvidia-smi or the device
plugin prints. Indices are resolved to UUIDs during parsing: an index only means
something relative to one process's CUDA_VISIBLE_DEVICES on one node, and the
entire point of migration is that the target enumeration differs. The map is
also parsed before any state transition, so a typo cannot leave the target
process locked or half-restored, and a map that does not cover every visible GPU
is refused here rather than producing a considerably less specific failure from
the driver.

Requires CUDA 13 headers: 12.x declares CUcheckpointRestoreArgs as an opaque
reserved[8] with no gpuPairs member, so the builder stage moves to
13.0.3-devel-ubuntu22.04. The ubuntu22.04 base is kept deliberately, because the
binary runs against the criu bundle's glibc 2.35 and a 24.04 builder would raise
the floor and break that contract.

Tested against stubs, so no GPU is needed: 18 cases covering both UUID spellings,
uppercase, index resolution, ordering, and the malformed and miscounted maps.
Three mutations were checked rather than assumed -- not skipping dashes, dropping
the count check, and transposing old and new in a pair -- and each turns cases
red. The transposition matters most: it parses cleanly and would migrate onto the
wrong device.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
…ogether

The CRIU plugin runs cuda-checkpoint one pid per exec, so an N-rank job means N
separate processes. On a single-GPU workload that only costs a driver attach per
spawn. On a multi-GPU job the gaps between those processes are where collective
traffic resumes: rank 0 is locked while rank 1 is still running, rank 1 posts a
collective rank 0 will never service, and rank 1's own lock then waits on an
operation that cannot complete. That is the shape of the "hangs on 2nd rank
(lock timeout)" failure recorded the last time multi-GPU capture was attempted.

--pid now repeats and accepts comma-separated lists, and the actions apply to
every pid from the one process. A single --pid behaves exactly as before,
including exit codes, so the plugin's existing calls are untouched.

Adds --action save, which locks every rank before checkpointing any of them.
The two-phase order is the hypothesis being tested, not an implementation
detail. A failed lock rolls back the ranks already locked, because a rank left
LOCKED is a hung workload and worse than a failed capture; a failed checkpoint
stops immediately and leaves locks in place, since the ranks already
checkpointed are recoverable only by restoring them, which the caller drives.

This is deliberately a mechanism test rather than a daemon. If lock ordering is
what multi-GPU capture is failing on, one invocation against a live TP=2 pod
shows it. If it hangs identically, the ordering is not the problem and a
resident daemon built on the same idea would not have helped either.

13 stub-driven cases covering the ordering, both rollback paths, single-pid
equivalence, and pid parsing. The ordering assertion was mutation-checked:
degrading save to interleaved lock+checkpoint turns three cases red. That
mattered to verify, because the degraded form still succeeds on every
single-GPU workload and would only resurface as a multi-GPU hang.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
The agent enumerates every process holding GPU state, uses the first one to
pick a dump target, and then never looks at the list again. Nothing checks that
those processes actually reached the images, so a capture can lose the ranks it
exists for and still be published with a checkpoint ID and a zero exit code.

Reproduced on dev1: quiescing NCCL on a live TP=2 vLLM took the executor down
with it, both TP workers exited, and CRIU dumped the surviving API server
correctly in 21 seconds. Four GPU processes were enumerated; one was captured.
The result was a well-formed 1.5G checkpoint of a workload whose weights and KV
cache were never in it -- about 3 percent of the expected size, and the size was
the only symptom.

dumpV2 now resolves each GPU pid to its in-namespace pid before dumping, and
afterwards requires a core image for each. Missing any fails the capture and
names the pids. The resolve has to happen first because a dump that is not
leave-running kills the tree, so /proc is gone by the time the images exist.

A pid that cannot be resolved is skipped rather than fatal -- it has usually
just exited on its own, and failing a healthy capture because one short-lived
helper raced us would be worse than the gap. A workload with no GPU processes
is unaffected.

This is the capture-side counterpart to the restore guards: those stop a cold
start being published as a restore, this stops an incomplete capture being
published as a checkpoint. Both exist because a green result that is quietly
wrong costs more than a failure.

Six tests, and the two load-bearing assertions were mutation-checked rather than
assumed: accepting a partial capture when any process is present, and resolving
the outer NSpid field instead of the innermost, each turn cases red. The second
matters because CRIU names images after the in-namespace pid, so using the host
pid would look for files that never exist and fail every capture instead.

Note: the internal/agent suite has a pre-existing flake, seen once in about ten
runs and not reproducible in eight further attempts. It is unrelated to this
change, which adds no concurrency or timing, but it is worth chasing separately.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
Multi-GPU CRIU was refused outright in the agent, on the reasoning that
cuda-checkpoint blocks on peer state and the D2H path could never reconstruct
CUDA context state on restore, so multi-GPU had to use rootfs/cachedir. The
first half is true. The conclusion was too strong: with peer state absent,
criu-v2 captures and restores a tensor-parallel workload intact.

Measured on 8x H100 80GB, driver 580.126.16, TinyLlama at TP=2 and TP=4:

    TP=2   checkpoint 2m34s  56G   restore 1m00s   PASS x3
    TP=4   checkpoint 4m16s  110G  restore 1m14s   PASS

The captures are complete, not merely successful: every Worker_TP rank appears
in the dumped tree, cuda_plugin pauses every GPU pid, and there is one ~28.4G
image per rank, matching the per-GPU memory budget. The restored pods served
live inference. Restore scaled far better than the size did, 2x the data for
23 percent more time, which suggests the per-rank GPU restores overlap rather
than serialising.

NVSNAP_MULTI_GPU_CRIU=1 lifts the refusal and keeps the ordinary criu-v2
engine: in-namespace dump, cuda_plugin driving cuda-checkpoint per rank, no
interception library and no D2H. It inherits the CRIU-layer fixes that made
the injection stack unnecessary for single GPU rather than reviving it.
NVSNAP_LEGACY_MULTI_GPU_D2H=1 selects the old quiesce plus D2H path instead;
the two were previously one switch, which sent runs down the path nobody meant
to test. Neither is on by default.

Also fixes GPU device counting, which fed nvidia-smi a LD_LIBRARY_PATH with the
driver tree ahead of the container's. The driver tree ships its own libc, so
nvidia-smi aborted on a symbol mismatch, the query "failed", and every workload
was reported single-GPU. That failure was logged at warning and returned 1,
which is fail-open in the worst way: it both skipped multi-GPU handling and hid
that it had been skipped.

The workload is a new manifest rather than a change to vllm-tp2, which stays on
the rootfs path. Its restore placeholder is generated from the nvsnap.io/path
annotation, so the two paths no longer share a file.

What this does not do is remove the configuration constraint. Every cross-GPU
transport must be off before capture, and a bisect showed that set is close to
irreducible: --enforce-eager and NCCL_P2P_DISABLE each break it individually,
and one of NCCL_SHM_DISABLE / VLLM_ALLREDUCE_USE_SYMM_MEM is required as well.
NCCL reaches a peer several independent ways and vLLM adds its own, so closing
one door leaves the others open. --enforce-eager is the exception worth
tracking: it stands in for post-restore CUDA graph re-capture, which only the
engine can do, rather than for a mechanism nobody has.

docs/proposals/multi-gpu-criu-v2.md records the measurements, the bisect, and
what remains open.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
The multi-GPU result so far was TinyLlama, which proves the topology but not the
scale anyone cares about. This adds the production-shaped case and it works.

    Llama-3.1-70B, TP=4, --gpu-memory-utilization 0.85
    checkpoint  9m56s   290G
    restore     2m02s
    restored pod served a completion correctly

Complete, not merely successful: seven tasks dumped including all four
Worker_TP ranks, cuda_plugin paused six GPU pids, and four 76.5G images, one per
rank, matching the per-GPU budget.

Restore scales much better than size. Across TinyLlama TP=2 (56G, 60s),
TinyLlama TP=4 (110G, 74s) and 70B TP=4 (290G, 122s), five times the data costs
twice the time, and the 70B restore moved 290G at about 2.4 GB/s against roughly
0.9 GB/s measured on single GPU. The per-rank GPU restores are overlapping
rather than serialising. That contradicts the premise behind the deferred
per-pid context parallelisation work, which assumed they serialise, and is worth
re-examining before anyone invests there.

The workload is a new manifest rather than a change to vllm-70b, which stays on
the rootfs path, matching how vllm-tp2-criu was split from vllm-tp2.

Two harness constants blocked this and neither is a mechanism limit. The
checkpoint step defaults to a 600s timeout and the capture alone took 596s, so
CHECKPOINT_TIMEOUT has to be raised. More seriously, any workload matching *70b*
had POD_READY_TIMEOUT pinned to 1800s, and a cold run needs more than 32 minutes
just to pull and load ~140G of weights; the first attempt failed there and
reported "Pod ready FAIL", which reads like a capture problem and is not one.
Raised to 4200s with an override hook. A warm cache finishes far inside it.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
…s not transfer

vLLM multi-GPU works on criu-v2 once every cross-GPU transport is off. The
obvious next question is whether that recipe is about peer mappings in general
or about vLLM specifically. It is about vLLM specifically.

SGLang TP=2 (Llama-3.1-8B) with the equivalent set - --disable-cuda-graph in
place of --enforce-eager, plus --disable-custom-all-reduce and the same three
NCCL_*_DISABLE vars, which are engine-independent - hangs at capture.

Not slowly. A criu and a cuda-checkpoint were still wedged three hours later,
both blocked in anon_pipe_read: CRIU waiting on a cuda-checkpoint that never
returns. That is worth distinguishing from a slow capture, because the harness
reports both the same way. The first attempt returned an empty response after
its 600s curl timeout, which looks like "too slow"; the retry then failed with
"text file busy" on the cuda-checkpoint binary, which is the tell that the
original process is still holding it.

So the flag names transfer and the coverage does not. Something in SGLang still
establishes cross-GPU mappings these flags leave open. Do not assume the recipe
generalises to another engine without measuring.

Adds the workload and its generated placeholder so the failure is reproducible
rather than a note, and records in the proposal that a wedged capture leaves
host processes behind which block later attempts on that node.

NIM is not covered here. nim-qwen3-32b defines no command or args, so it runs
the image entrypoint, and the criu-v2 generator requires the setsid
stdio-redirect convention; it needs a command wrapper written before it can be
tested at all.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
…g dead ends

Two corrections and two eliminated hypotheses, all measured.

The proposal claimed the sever flags work by leaving "no peer mappings at all".
That is wrong. A vLLM rank that captures cleanly still holds mappings to the
peer GPU's device node, 3 to its own and 2 to the peer's. Whatever the flags
remove is not visible as device mappings, and the mechanism is not established.
Stating it that confidently was not supported by anything measured.

For the SGLang hang, two hypotheses were tested and both died. SGLang processes
hold fds on all eight GPUs despite CUDA_VISIBLE_DEVICES naming two, which looked
conclusive until vLLM turned out to have an identical per-device count. Peer
device mappings looked like the next candidate until vLLM turned out to have
those too. The only measured difference left is that SGLang holds about 40
/dev/nvidiactl mappings per rank against vLLM's 28, which is a lead rather than
a cause.

Flag coverage is now verified instead of assumed. --disable-cuda-graph and
--disable-custom-all-reduce both exist and were accepted. SGLang's peer features
are all opt-in (--enable-nccl-nvls, --enable-symm-mem, --enable-torch-symm-mem,
--enable-p2p-check) and were never enabled, so the NCCL env vars were suppressing
things that were already inactive. --disable-piecewise-cuda-graph and
--disable-decode-cuda-graph exist and were not set; they are unlikely to affect a
capture hang, since graphs fail late at restore-inference rather than at capture,
but that is untested.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
…sign

Every measurement in this proposal captures an engine that has just served a
request and gone idle, which is what NVCF does: a pod is checkpointed when it
has no traffic and later pods restore from that same artifact.

Recorded because capture under live traffic behaves differently and someone
would otherwise rediscover it from the data and file it as a defect. With eight
concurrent requests against a TP=2 pod, rank 0 locks in about 3 seconds and rank
1 times out after 60. That is a deadlock, not slowness: locking rank 0 stops it
servicing the collective rank 1 waits on, so locking the ranks together does not
help either. It does not apply to the NVCF flow.

The model also decides which numbers matter. Capture is amortised over every pod
restoring from the artifact, so the 70B's ten minutes is paid once; restore is
paid per pod, which makes the sub-linear restore scaling worth more than the
capture cost.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
…confirm the hang

NVSNAP_SECCOMP_ENABLED, USE_LIBUV and HF_HUB_ENABLE_HF_TRANSFER were all set to
"0,1" instead of "0". They were collateral from deriving this manifest with a
value-scoped substitution (replace value "0" with "0,1" for CUDA_VISIBLE_DEVICES),
which matched every variable holding that value rather than the intended one.

USE_LIBUV mattered: the single-GPU SGLang manifest sets it "0" deliberately, and
libuv plus io_uring is the C/R hazard the patched-library stack exists for. A
non-zero string may well read as enabled, so the run that produced the recorded
SGLang hang could not be trusted.

Repeated with the values corrected and a 30 minute timeout. Identical hang, and
the same wedged-process signature: criu and cuda-checkpoint both blocked in
anon_pipe_read. The confound was real and was not the cause, so the conclusion
stands, now on clean inputs.

Recorded in the proposal so the result is not re-litigated later.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
…pid bump works

Qwen3-32B on TRT-LLM captures and restores through criu-v2: checkpoint 3m03s at
103G, restore 1m55s, restored pod served inference. Its process shape differs
from vLLM's, with start_server.sh, an orted MPI daemon, four python3 ranks, and
asymmetric GPU images of 83.5G and 24.7G rather than an even per-rank split.

That makes multi-GPU criu-v2 work on two engines, and makes the SGLang failure
specific to SGLang rather than a property of anything except vLLM.

Three fixes were needed and only the first is NIM-shaped.

The stock image has no command: it runs /opt/nvidia/nvidia_entrypoint.sh with
cmd `bash -c $SERVER_START_SCRIPT_PATH`. The manifest reproduces that startup
inside the setsid convention rather than replacing it, so the entrypoint still
runs and still execs the script the image names.

Stdio cannot go to /tmp. isRuntimeGeneratedPath treats /tmp as runtime-generated
and drops it from the rootfs diff, so the placeholder restores an empty file and
CRIU refuses with "File tmp/nim.out has bad size 0 (expect 21443)". It cannot go
to the container root either, because the image runs as uid 1000. /opt/nim
satisfies both and matches none of the excluded patterns.

The placeholder must run as root, and this is the general one. It writes
/proc/sys/kernel/ns_last_pid, privileged does not confer root, and an image
defaulting to a non-root uid fails that write silently, leaves its pid range
unreserved, and the restore dies with "Can't fork for 336: File exists" - the
exact failure the pid reservation exists to prevent. The generator now emits
runAsUser 0 for every placeholder. The restored workload's own uid comes from
the checkpoint, so this does not change what it runs as. This was previously
known only as a hand-written note on one manifest; it is a property of any
non-root image and belonged in the generator.

The bump's failure message was also parenthetical, which is why an unreserved
range presented as a confusing restore error rather than as itself. It now says
what will happen.

Only the criu-v2 placeholders are regenerated here. The others would also pick
up the same correction, but they carry hand-edits that #965 already fixes, and
rewriting them now would collide with it.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Comment @coderabbitai help to get the list of available commands.

gazelle had not been re-run after checkpoint_v2_gpuguard_test.go was added, so
the file was missing from the go_test srcs. The bazel target built and passed
without ever compiling those six cases, and the check-gazelle CI step failed.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
The sever set carried NCCL_NVLS_DISABLE=1, which is not an NCCL variable.
Grepping the shipped libnccl.so.2 finds NCCL_P2P_DISABLE, NCCL_SHM_DISABLE,
NCCL_CUMEM_ENABLE and NCCL_NVLS_ENABLE, but no NCCL_NVLS_DISABLE. It had been
a no-op for as long as it had been set, which is why the earlier bisect could
never attribute anything to it.

NVLS was off in practice anyway: with P2P and SHM disabled NCCL treats the
ranks as separate nodes and builds no NVLS channels (confirmed by
NCCL_DEBUG=INFO, "0 nvls channels"). So this corrects the spelling rather than
the behaviour, but the set now says what it does.

Re-ran vllm-tp2-criu end to end on the corrected flag: checkpoint 2m35s / 56G,
restore 1m00s, post-restore inference OK. Matches the run this recipe was
originally validated against.

Co-Authored-By: Balaji Ganesan <bganesan@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants