fix(dist): make raw IPC input pools usable — remove init_dist_env's vestigial signal/buffer block, add explicit raw-pool override - #4924
Conversation
Under PYTORCH_HIP_ALLOC_CONF=expandable_segments:True -- the very configuration the raw_cached input pool exists for (ROCm#4174) -- init failed twice over in init_dist_env's signal/buffer block (ROCm#4921): * register_input_buffer(signal) exports the signal tensor's pointer via hipIpcGetMemHandle, but the torch.zeros signal is VMM-backed under expandable segments and the export dies at custom_all_reduce.cu:417 with 'invalid argument'; * ca_comm.buffer = ca_comm._pool["input"].tensor raises by design, because the raw_cached pool is a plain hipMalloc region with no backing torch.Tensor. The block is removed rather than repaired, because all of it was vestigial: * ca_comm.signal / ca_comm.buffer are never read anywhere in the tree; * C++ register_input_buffer only inserts a pointer-translation entry keyed by the registered tensor's own address, which is consulted only when an allreduce is invoked with that exact tensor as input -- something that never happens for the signal tensor (open_ipc_handle's handle cache is filled on demand, so no pre-warming is lost either); * gfx1250 has skipped the entire block since its VMM bring-up (the vmm_exchange rendezvous deadlocks) and works without it. CustomAllreduce.__init__ already builds its own meta/input pools and forces the copy-in path under expandable segments, so nothing here was load-bearing. get_tp_group stays imported: this module is a re-export surface (downstream engines import set_custom_all_reduce through it). Adds op_tests/multigpu_tests/test_init_dist_env.py: brings up init_dist_env per rank under both allocator modes (default torch pool, and expandable_segments -> raw_cached) and checks one allreduce. The existing test_custom_allreduce.py performs its own init and never executes init_dist_env, which is how the regression shipped. Fixes ROCm#4921. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The raw (plain-hipMalloc) input pool previously had exactly one trigger: PyTorch expandable segments. But expandable segments break custom allreduce later anyway -- every capture-time output is a fresh torch.empty_like whose VMM pointer get_output_buffer_RD records for post-capture IPC export, which then fails in get_graph_buffer_ipc_meta -- so the raw pool's one trigger leads to an unusable configuration (ROCm#4921, third failure mode; ROCm#4621's copy-in guard covers inputs only). The override gives the raw pool a trigger that works: co-resident engines on one node, where a second engine's torch.empty input pool can fail hipIpcGetMemHandle outright. Under the default allocator everything else (meta pool, capture-time outputs, graph flush) stays exportable, so only the input pool needs to move to hipMalloc. Extends test_init_dist_env.py with a raw_override mode that asserts the flag actually selects the raw pool and allreduce stays correct. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
PR title tags: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e2a3a827ae
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| import torch | ||
|
|
||
| from aiter.test_common import checkAllclose |
There was a problem hiding this comment.
Set allocator configuration before importing aiter
Under the normal import path, this top-level import loads aiter, which imports custom_all_reduce.py; its module initialization calls _detect_gfx1250() and queries the CUDA device. Because spawned workers import this module before entering _worker, CUDA can be initialized before PYTORCH_HIP_ALLOC_CONF is set on lines 38–39. The allocator snapshot then remains non-expandable, and the warning-only branch on lines 96–103 lets the expandable run pass without exercising the regression. Move the aiter import after the environment assignment or launch workers with the allocator variable already present in their environment.
Useful? React with 👍 / 👎.
| checkAllclose( | ||
| ref, | ||
| out, | ||
| msg=f"init_dist_env allreduce: {tp_size=} mode={run_mode} pool={mode}", | ||
| ) |
There was a problem hiding this comment.
Assert the result of the allreduce comparison
checkAllclose returns a nonzero mismatch ratio for ordinary incorrect results and does not raise with its default catastrophic_check=False. Since this return value is discarded, even an allreduce whose entire output is wrong can leave every mode passing, so the advertised correctness regression check is ineffective. Assert an acceptable returned error ratio.
Useful? React with 👍 / 👎.
| pool_mode = "none" | ||
| if ca_comm is not None: | ||
| buf = ca_comm._pool["input"] | ||
| pool_mode = "raw_cached" if buf._raw_cached else "torch" |
There was a problem hiding this comment.
Skip raw-pool introspection for the VMM transport
On gfx1250 systems using the supported VMM path—ROCm older than the IPC threshold, an undetectable ROCm version, or AITER_CUSTOM_AR_FORCE_VMM=1—ca_comm._pool["input"] is a _GFX1250BufferProxy._Entry, which has data_ptr and max_size but no _raw_cached. This unconditional access therefore raises AttributeError in every run mode before the allreduce executes. Gate this IPC-specific assertion on not ca_comm._use_vmm or represent VMM as a separate pool mode.
Useful? React with 👍 / 👎.
A silently-inert pool trigger is indistinguishable from a working one by behaviour alone -- the engine serves fine single-engine either way, and the failure modes this pool exists to avoid (ROCm#4921) only appear in specific modes under specific co-residency. One INFO line per rank makes every run self-document which pool it actually got, so a mislabeled measurement is catchable from the log rather than by re-deriving the allocator state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes #4921.
Summary
Two commits:
init_dist_envno longer breaks raw IPC input pools — the vestigial signal/buffer block is removed.AITER_CUSTOM_AR_RAW_INPUT_POOL— an explicit trigger for the raw (plain-hipMalloc) input pool that works, because the existing trigger (expandable segments) turns out to be broken further down regardless.Root cause — three stacked failures, not one
#4921 reports the crash at
communication.py:78(ca_comm.buffer = ca_comm._pool["input"].tensor). Measured on 4×/8× MI350X (gfx950, both VF and bare metal), the full chain underPYTORCH_HIP_ALLOC_CONF=expandable_segments:Trueis:register_input_buffer(signal)exports thetorch.zerossignal tensor's pointer viahipIpcGetMemHandle, and under expandable segments that pointer is VMM-backed — the export dies atcustom_all_reduce.cu:417("invalid argument"). The issue's line-78 traceback is the signature of the explicit-override trigger, where the allocator stays default and registration survives.torch.Tensor, and.tensorraises by design.flush_graph_buffersdies atcustom_all_reduce.cuh:3448exporting capture-time output tensors — everyout = torch.empty_like(inp)allocated during capture is VMM-backed,get_output_buffer_RDrecords it, and the post-capture flush tries to IPC-export it. fix: car graph mode err when pytorch set expandable_segments:True #4621's copy-in guard covers inputs only.So expandable segments cannot arm the raw pool usefully today (failure 3 needs output staging — a separate, kernel-level change). The explicit override gives the raw pool a trigger that works: under the default allocator everything else (meta pool, capture-time outputs, graph flush) stays exportable, and only the input pool moves to
hipMalloc.Why removal instead of
.tensor→.data_ptrThe issue asked which contract
ca_comm.bufferserves. Answer: none —ca_comm.buffer/ca_comm.signalare only ever written, never read (swept aiter and a downstream engine, includinghasattrgates);_pool["input"].data_ptr;register_input_bufferonly inserts a pointer-translation entry keyed by the registered tensor's own address, consulted only when an allreduce is invoked with that exact tensor as input — which never happens for the signal tensor (open_ipc_handle's cache fills on demand, so no pre-warming is lost);The old comment said the attributes are "never read anywhere" while the code still ran the registration — removal makes the code match the comment. Numerics were validated, not just startup (see below), since a wrong guess here would corrupt allreduce silently.
Measured validation (GLM-5.2-MXFP4, TP4,
--level 3graphs,gpu_memory_utilization 0.90)Two environments, stated per row — provenance matters here because bring-up
behaviour in this problem space has proven state-sensitive (see motivation):
nightly_202608041536+ a source overlay of the two files this PR touches plus their csrc (JIT-rebuilt in-container; ≡ aitere404860+ this PR).nightly_202608191459(aiter4fa508e) with this PR's files bind-mounted, next to another tenant's live engine.cu:417(signal export — failure 1)cuh:3448post-capture (failure 3 — expected, out of scope)AITER_CUSTOM_AR_RAW_INPUT_POOL=1Numerics: inference through the raw pool produces correct output (spot-checked deterministic arithmetic prompts; allreduce path exercised end-to-end at TP4 with HIP graphs).
Operational notes for the co-resident-engines use case that motivated #4921:
--ipc host, engine 2--ipc private, verified viadocker inspect).docker inspect) had one successful bring-up of the0.62/128two-engine configuration — whose engine pair then stayed durable through six consecutive benchmark passes — and the identical command failed athipIpcGetMemHandlehours later on a node verified clean beforehand. Every envelope above0.62/128has failed in all unpatched attempts, and no configuration axis tried so far (image, IpcMode, GC settings, capabilities, shm size) separates success from failure. The failure is concentrated entirely in bring-up: it fails fast (~50 s), leaves the first engine healthy, and pairs that do come up stay durable — so the operational shape of the problem is a bring-up lottery, not a serving instability. That is the practical motivation for this override — with one scope limit found by the same investigation: under co-residency on at least one stack,NCCL_DEBUG=INFOshows RCCL's own P2P transport failinghipIpcGetMemHandlefirst (p2p.cc:256), with AITER'scu:417failure downstream of it. So this override removes the AITER-side export dependency (an exportable-by-construction hipMalloc pool instead of allocator-state-dependent pointers); it does not claim to make two co-resident engines reliable by itself, because RCCL's own export sits upstream and is outside this PR. (NCCL_DMABUF_ENABLE=1was evaluated for that layer and did not help: 0/3 with it and 0/3 without, interleaved,p2p.cc:256present in all six.) Side by side:0.62/128unpatched with an unquantified success rate — vs0.90util / 512 seqs patched (measured above, single-engine).Co-residency bring-up rates (appended after measurement — the promised control)
Measured on the matched image (
nightly_202608191459), sequential bring-up, second engine on disjoint GPUs,NCCL_DEBUGenabled, teardown + clean-node checks between attempts:iommu=ptp2p.cc:256every timeiommu=ptp2p.cc:256every timeiommuflagp2p.cc:256every timeConclusions, stated at their real strength:
hipIpcGetMemHandle → invalid argumentatp2p.cc:256), before any AITER pool exists. A separate issue against RCCL is being prepared with fullNCCL_DEBUG=INFOevidence from both hosts.Test
op_tests/multigpu_tests/test_init_dist_env.py(new): brings upinit_dist_envper rank and checks one allreduce under three modes —default,expandable,raw_override— asserting the override actually selects the raw pool. The existingtest_custom_allreduce.pyperforms its own init and never executesinit_dist_env, which is how the regression shipped.Not covered by CI: failure 3 (needs graph capture + expandable segments); documented here and reproducible with the table above.