Add: support mixed local and remote L4 CommDomains - #1456
Conversation
📝 WalkthroughWalkthroughThis change adds Global CommDomain support across remote L3 workers, including wire codecs, transactional allocation and cleanup, simulation and A3 backend implementations, Python APIs, remote control handling, tests, and end-to-end smoke workflows. ChangesGlobal CommDomain
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
tests/ut/py/test_global_comm_domain.py (1)
176-189: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winFixed
time.sleep(0.3)daemon warmup is racy.worker.init()connects to the freshly spawned daemons, but there is no guarantee they have bound their ports within 300 ms on a loaded CI host, which can cause intermittent connection failures. Consider polling each(127.0.0.1, port)for accept-readiness (short connect-retry loop with a bounded deadline) beforeadd_remote_worker/initinstead of a static sleep.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/ut/py/test_global_comm_domain.py` around lines 176 - 189, Replace the fixed time.sleep(0.3) in the worker setup test with bounded polling that attempts short TCP connections to each 127.0.0.1 port until all daemons accept connections or a deadline is reached. Perform this readiness check before the add_remote_worker calls and worker.init(), and fail clearly if any daemon remains unavailable.src/common/platform_comm/comm.h (1)
48-57: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winPin the descriptor ABI used by the wire codec.
ChipWorker::comm_global_domain_prepareserializessizeof(CommGlobalDomainDescriptor), while the Python wire contract fixes this descriptor at 288 bytes. Add size/offsetstatic_asserts or explicit field serialization so compiler packing changes cannot silently break remote imports.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/common/platform_comm/comm.h` around lines 48 - 57, Pin the CommGlobalDomainDescriptor wire layout used by ChipWorker::comm_global_domain_prepare: add compile-time size and field-offset assertions matching the 288-byte Python contract, including the handle field, so packing changes fail at build time rather than altering serialized imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/remote-l3-worker-design/implementation-record.md`:
- Around line 91-94: Update the L4-brokered Global CommDomain setup description
to state that domains are released after the L4 DAG drain by default, while
explicitly noting that retain_after_run=True preserves them for a later run.
In `@python/simpler/task_interface.py`:
- Around line 910-921: Sort the entries alphabetically in both __slots__ tuples
in the relevant classes, including the declaration containing name and members
and the second declaration around the referenced location, while preserving
every existing slot name.
- Around line 975-979: Update release so _released is set to True only after
_release_fn(self) completes successfully. If the remote cleanup raises,
propagate the error while leaving the handle unreleased so a later release()
retries the cleanup.
---
Nitpick comments:
In `@src/common/platform_comm/comm.h`:
- Around line 48-57: Pin the CommGlobalDomainDescriptor wire layout used by
ChipWorker::comm_global_domain_prepare: add compile-time size and field-offset
assertions matching the 288-byte Python contract, including the handle field, so
packing changes fail at build time rather than altering serialized imports.
In `@tests/ut/py/test_global_comm_domain.py`:
- Around line 176-189: Replace the fixed time.sleep(0.3) in the worker setup
test with bounded polling that attempts short TCP connections to each 127.0.0.1
port until all daemons accept connections or a deadline is reached. Perform this
readiness check before the add_remote_worker calls and worker.init(), and fail
clearly if any daemon remains unavailable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f217e7f8-c970-4de9-9e33-aa5539b441f5
📒 Files selected for processing (41)
docs/comm-domain.mddocs/remote-l3-worker-design.mddocs/remote-l3-worker-design/implementation-record.mddocs/remote-l3-worker-design/protocol.mdpython/bindings/CMakeLists.txtpython/bindings/task_interface.cpppython/bindings/worker_bind.hpython/simpler/global_comm_domain.pypython/simpler/global_comm_smoke.pypython/simpler/orchestrator.pypython/simpler/remote_l3_protocol.pypython/simpler/remote_l3_session.pypython/simpler/remote_l3_worker.pypython/simpler/task_interface.pypython/simpler/worker.pysrc/a2a3/platform/onboard/host/comm_hccl.cppsrc/a5/platform/onboard/host/comm_hccl.cppsrc/common/hierarchical/remote_endpoint.cppsrc/common/hierarchical/remote_endpoint.hsrc/common/hierarchical/remote_wire.cppsrc/common/hierarchical/remote_wire.hsrc/common/hierarchical/worker.hsrc/common/hierarchical/worker_manager.cppsrc/common/hierarchical/worker_manager.hsrc/common/platform_comm/comm.hsrc/common/platform_comm/comm_sim.cppsrc/common/worker/chip_worker.cppsrc/common/worker/chip_worker.htests/ut/py/test_callable_identity.pytests/ut/py/test_global_comm_domain.pytools/a3_l4_tcp_smoke/README.mdtools/a3_l4_tcp_smoke/compute_then_tload_smoke.pytools/a3_l4_tcp_smoke/global_tload_smoke.pytools/a3_l4_tcp_smoke/kernels/aiv/global_tload_kernel.cpptools/a3_l4_tcp_smoke/kernels/aiv/local_add_kernel.cpptools/a3_l4_tcp_smoke/kernels/orchestration/global_tload_orch.cpptools/a3_l4_tcp_smoke/kernels/orchestration/local_add_orch.cpptools/remote_l4_npu/README.mdtools/remote_l4_npu/remote_l4_npu_smoke.pytools/remote_l4_npu/run_parent_smoke.shtools/remote_l4_npu/start_machine_daemon.sh
a49e5f4 to
d7afa04
Compare
|
Also addressed the two CodeRabbit nitpicks listed in the review summary:
|
e8bfbf8 to
cd902af
Compare
3f0ba5a to
32f8fe6
Compare
162944b to
c293351
Compare
c293351 to
d1eff1d
Compare
|
Re-reviewed at What changed since
|
d1eff1d to
a05b944
Compare
|
@ChaoWao Thanks for the detailed re-review. Addressed the four blockers and the remaining follow-ups in
Also fixed the five local control calls to honor The PR body and docs now state the A3 scope explicitly: repository CI covers sim; the earlier two-machine A3 evidence is external and is not current-head A3 validation after the smoke tools were removed. All executable local hooks passed except Linux-only clang-tidy/pytest entry points blocked on Windows by POSIX |
|
CI follow-up for |
05cbba0 to
adc312d
Compare
adc312d to
cafbe06
Compare
|
@sunkaixuan2018 Re-reviewed at Your four blockers are all closed — verified against
|
| Fix | |
|---|---|
M1 remote_export on a node with chip devices |
_RemoteBufferEntry.shm_name falls back to owner._host_buffer_shm_name(...) for a HostBuffer backing |
| M2 release did not invalidate the committed view | _release_global_domain_node sets phase = ABORT and clears view._committed before the first destructive child call |
| M3 import leaked on failure | GlobalPeerMapping / VmmWindow are now move-only RAII, so every return -1 in the peer loop unwinds |
| M4 handle release not bound to its allocating run | _release_fn=lambda released, owner=resources: ..., plus handling for a retained handle released while a later run builds |
The RAII approach in M3 is better than the per-branch cleanup I suggested — a failure path can't forget to unmap. The timeout asymmetry is closed too, and the failure-injection tests got real depth: test_two_remote_daemons_... and test_local_and_remote_l3_build_and_copy_global_domain_without_mpirun spawn actual simpler.remote_l3_worker daemons over TCP and fork a real local L3, so node-side code genuinely runs on the mixed topology.
Removing tools/ also no longer leaves A3 Fabric unverified — wiring the two examples into _st-pod.yml means CI now exercises a3-fabric-v1 on real two-machine hardware. I read the job log rather than trusting the green check: [global-tload-mixed-l3] remote rank=1 max_diff=0.000e+00, job 92743892751.
What I pushed: the ABORT path now refuses admission instead of reporting
My earlier comment called this "silent contextlib.suppress on the ABORT fan-out". That wording was on me — it emphasised silence, so making it loud (sys.stderr.write) was a reasonable reading. But visibility was never the requirement here, which is why I fixed it rather than just re-raising it:
On the ABORT path the domain is never registered — self._live_global_domains[name] and resources.live_global_domains[name] are both assigned after the try, and requires_ordered_cleanup is never set. So when an ABORT leg fails, there is no handle for the run's fence to fail on and nothing for close() to sweep, yet the run is still classified as not cleanup-bearing and the next run is admitted at full pipeline depth as if teardown had succeeded.
Both sites now route through Worker._record_unreclaimable(...), whose docstring describes exactly this case — "For device state that no cleanup can reach: nothing tracks it, so there is no handle for a run's fence to fail on and no sweep that could retry it." It is first-wins and returns the error, so the control flow is unchanged:
Worker._allocate_global_domain— ABORT fan-outexceptWorker._release_all_global_domain_nodes— L3-node close path (this one also didn't re-raise)
Worth noting how mechanical this is: _record_unreclaimable had 10 call sites in worker.py and the whole global-domain section had none, so these two were the exception rather than a new requirement.
test_global_domain_abort_failure_preserves_primary_error_and_reports_cleanup asserted on captured stderr, so it is now ..._poisons_admission and asserts the worker refuses further work with the injected error as __cause__. I confirmed it fails against the pre-fix code (assert None is not None, with the swallowed warning visible in captured stderr) and passes after. Full py UT: 1216 passed / 13 skipped.
Two heads-ups — explicitly not change requests
1. GlobalDomainMember will need a deployment component — please don't add one here. encode_domain_command puts len(members) plus each _put_member on the cross-node control-plane wire, and members are (node_worker_id, local_worker_id, ...) — positions in the orchestration tree. Whether an endpoint can join a comm domain is ultimately a function of where it is deployed and what it is physically connected to, not of its tree position, so that field is expected to grow a deployment component.
The registry that owns this vocabulary is in flight in #1696; adding a second independent enum now would be worse than waiting. Flagging it only because #1623/#1624 stack on this same encoding — adding a field is cheap while the stack is still moving and a protocol migration afterwards.
2. One backing per domain, and the domain-wide comm_profile, are known future breaking changes — CommBufferSpec becoming an independent Buffer rather than an offset slice of one window, and transport/medium selection moving from a node-level string to a per-(member, buffer) decision. Both need the same registry first. Nothing to do here; noting it so the current shape isn't mistaken for the final one.
One question
Are the two pod machines in the same A3 super-pod / fabric? Your peer TLOAD result is the first in-repo evidence that cross-machine device-peer direct-map works, and I'd like to record it accurately — specifically whether "cross-machine" here means within one fabric (control plane over TCP, data plane over fabric) or something broader. I can read the code path but not your pod topology.
cafbe06 to
f1b8bd9
Compare
- Relay rank-ordered Fabric descriptors through L4 to local and remote L3 nodes - Route local Global CommDomain control through NEXT_LEVEL mailboxes - Reject unsupported platform/profile capabilities before allocation and at COMM_INIT - Make allocation, import, and release rollback invalidate partial state and clean staged mappings - Refuse further work when a rollback leg itself fails, since the domain is unregistered there and no run fence or close() sweep can reach what it leaves mapped - Bind Global CommDomain release to run fences and configured control timeouts - Keep sim remote buffer export working for device-backed HostBuffer allocations - Add transaction, node rollback, and local/remote sim integration coverage - Add two-machine pod STs global_tload_mixed_l3 and compute_then_tload_mixed_l3, registered in the pod workflow - Document no-mpirun setup, retained lifetimes, and the pod job that covers a3-fabric-v1 Co-authored-by: Leaf-Salix <2503954024@qq.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
mpirun.add_worker) and TCP-connected remote L3 workers (add_remote_worker).sim, real A2/A3 acceptsa3-fabric-v1, and unsupported platform/profile pairs are rejected beforePREPAREand again at L3COMM_INIT.close()sweep can reach what the failed rollback leaves mapped; both theALLOC_DOMAINABORT fan-out and the L3-node close path route throughWorker._record_unreclaimable.global_tload_mixed_l3(L4-brokered peerTLOAD) andcompute_then_tload_mixed_l3(an L2 compute round, then cross-machine communication through the same retained domain), registered in_st-pod.yml.Testing
f1b8bd9fis a single commit on top ofupstream/main.tests/ut/py/test_global_comm_domain.pyis 25 passed.test_global_domain_abort_failure_preserves_primary_error_and_poisons_admissionis the regression barrier for the rollback-failure path: it asserts the primary error still propagates, every prepared node still receives ABORT, and the worker then refuses further work with the injected failure as__cause__. Confirmed failing against the pre-fix code and passing after.test_two_remote_daemons_build_and_copy_global_domain_without_mpirunandtest_local_and_remote_l3_build_and_copy_global_domain_without_mpirunspawn realsimpler.remote_l3_workerdaemons over TCP and fork a real local L3, then build, copy into, read back and release a domain across the mixed topology.A3 Fabric coverage
a3-fabric-v1backend is covered by repository CI. The two-machinest-pod-onboard-a2a3job runs both new examples with their default--platform a2a3 --comm-profile a3-fabric-v1;pod-run-exampleinjects only the endpoint, device lists and session settings, so the Fabric profile is the one actually exercised.[global-tload-mixed-l3] local rank=0 max_diff=0.000e+00/remote rank=1 max_diff=0.000e+00.aclrtMemFabricHandleVMM import, so the imported window is read directly by the AICore kernel.simbackend still covers the transaction, rollback and mixed local/remote paths in the GitHub-hosted jobs.