Skip to content

feat(distributed): support ragged (non-divisible) lengths in HOST ring allreduce - #2603

Merged
YunjiQin merged 2 commits into
hw-native-sys:mainfrom
georgebisbas:feat/host-ring-allreduce-ragged
Sep 3, 2026
Merged

feat(distributed): support ragged (non-divisible) lengths in HOST ring allreduce#2603
YunjiQin merged 2 commits into
hw-native-sys:mainfrom
georgebisbas:feat/host-ring-allreduce-ragged

Conversation

@georgebisbas

@georgebisbas georgebisbas commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the HOST-ring ragged / arbitrary-length leftover of issue #2242.

The host builtin ring allreduce previously required a statically-known src shape with numel % NR == 0. This PR relaxes both enforcement points and makes the kernel partition each rank's buffer into NR balanced, potentially ragged chunks at runtime — chunk r spans [floor(numel·r/NR), floor(numel·(r+1)/NR)) — mirroring the InCore composite ring (#2161).

Changes

  • CheckRingChunkConstraints (src/ir/transforms/lower_host_tensor_collectives_pass.cpp): dropped the static-shape + src_numel % NR == 0 CHECK_SPAN. Only the distributed-type internal check remains, plus a comment documenting the runtime ragged partition.
  • Ring builtin deducer (src/ir/op/distributed/collective.cpp): dropped the compile-time static-shape + numel % nr validation block (the kMaxSupportedRanks check stays).
  • allreduce_ring kernel template (python/pypto/runtime/builtins/collectives/allreduce_ring/templates/kernel.cpp.in): added RingChunkStart(); the soft guard no longer rejects non-divisible numel; max_chunk_elems = ceil(numel/nranks); tile_cols = min(max_chunk_elems, kTileCount); both RS/AG loops iterate ragged send spans, with each TPUT transfer narrowed to the chunk's exact extent via the staging tile's valid shape (ColMaskInternal).
  • ST (tests/st/distributed/test_l3_host_tensor_allreduce_ring.py): new ragged test {1, 17, 4097, 65537} × P=2/P=4 (all non-divisible by both P=2 and P=4; 65537 > UB); stage-in/out chunked (STAGE_CHUNK=8192, 32-byte [8,1] tile for SIZE=1, stage cols rounded to 32-byte row alignment).
  • UT (tests/ut/ir/transforms/test_lower_host_tensor_collectives.py): test_host_allreduce_ring_rejects_nondivisible_numeltest_host_allreduce_ring_accepts_nondivisible_numel (SIZE=27, P=4).

Verification

Gate Result
UT test_lower_host_tensor_collectives.py 51/51
a2a3sim test_l3_host_tensor_allreduce_ring.py (uniform + ragged + signal-reuse) 12/12
NPU P=2 (devices 4-5) 6/6
NPU P=4 (devices 4-7) 12/12
pre-commit (all hooks on the changed files) green

The NPU runs are the ordering gate: TPUT push + NeighborBarrier + pipe_barrier(PIPE_ALL)/dsb ordering is not modelled in sim, and is proven on silicon for every ragged size × rank count.

Scope notes

  • Sum-only (deliberate): the HOST ring kernel is Sum-only by construction (entry.cpp.in enum ReduceOp { kSum = 0 }; TPUT<AtomicType::AtomicAdd> cannot Prod). Prod on the HOST ring requires the atomic-add → non-atomic-push algorithm change, tracked separately. The ragged ST mirrors the InCore ring ragged ST, which is also Sum-only.
  • No perf regression on the previously-supported path by construction: for numel % NR == 0 the floor boundaries coincide exactly with the old chunk_elems multiples, so send_base/send_end/send_len/max_chunk_elems/tile_cols are identical and the generated kernel is unchanged (the only removed code is the numel % nranks != 0 guard branch, which never fired on the supported path).
  • Pre-existing pyright failure (unrelated): pre-commit run --all-files reports 4 pyright errors in python/pypto/runtime/distributed_runner.py (DataType/AccessMode/BackendKind unknown import symbol) that reproduce identically on a pristine origin/main worktree @ f1a2f35f — an environment artifact of the isolated hook env (missing _task_interface C-extension the installed simpler re-exports from), not caused by this PR. pyright passes on all files changed here.

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: e34bb2c6-4731-4bb1-9a24-caa47a538fbb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: cbd0f59c-b116-432c-b30b-33cc4f2ec502

📥 Commits

Reviewing files that changed from the base of the PR and between ce2fff9 and bb1fb3f.

📒 Files selected for processing (5)
  • python/pypto/runtime/builtins/collectives/allreduce_ring/templates/kernel.cpp.in
  • src/ir/op/distributed/collective.cpp
  • src/ir/transforms/lower_host_tensor_collectives_pass.cpp
  • tests/st/distributed/test_l3_host_tensor_allreduce_ring.py
  • tests/ut/ir/transforms/test_lower_host_tensor_collectives.py

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Changes

Ragged ring allreduce

Layer / File(s) Summary
Kernel ragged chunk geometry
python/pypto/runtime/builtins/collectives/allreduce_ring/templates/kernel.cpp.in
The kernel computes balanced chunk boundaries with RingChunkStart. Reduce-scatter and allgather use exact ragged transfer lengths.
Host validation and lowering
src/ir/op/distributed/collective.cpp, src/ir/transforms/lower_host_tensor_collectives_pass.cpp
Host validation permits dynamic and non-divisible source sizes. Lowering emits the ring allreduce builtin dispatch.
Ragged staging and test coverage
tests/st/distributed/test_l3_host_tensor_allreduce_ring.py, tests/ut/ir/transforms/test_lower_host_tensor_collectives.py
Tests stage variable-size windows in bounded tiles and verify lowering and execution for ragged sizes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟡 Moderate · up to bb1fb

The PR enables ragged and dynamic tensor lengths, but inconsistent runtime metadata across ranks could cause one rank to exit while peers wait, potentially hanging the collective and leaving communication state unsuitable for reuse. Merge should require explicit owner acceptance or a coordinated failure and reset plan.

Sequence Diagram(s)

sequenceDiagram
  participant HostProgram
  participant LoweringPass
  participant RingKernel
  participant RankOutputs
  HostProgram->>LoweringPass: lower non-divisible allreduce
  LoweringPass->>RingKernel: emit builtin.tensor.allreduce_ring
  RingKernel->>RankOutputs: process balanced ragged chunks
  RankOutputs-->>HostProgram: return summed tensors
Loading

Poem

A rabbit reads each line,
The patch grows clear beneath the moon,
Small changes hop in place,
Tests guard the garden path,
Reviews bloom before the dawn.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 4 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the main change: adding support for ragged, non-divisible lengths in HOST ring allreduce.
Description check ✅ Passed The description directly explains the changes, affected components, tests, verification results, and the intentional Sum-only scope.
Full details: Docstring Coverage

Explanation

Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 4 files. (1 skipped: 1 unsupported.)


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.

❤️ Share

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb1fb3fa6f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ir/op/distributed/collective.cpp Outdated
@georgebisbas
georgebisbas force-pushed the feat/host-ring-allreduce-ragged branch 2 times, most recently from daafcb9 to 0cbbd47 Compare September 1, 2026 08:25

@YunjiQin YunjiQin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Two review notes on the compile-time-check removals — the runtime ragged geometry itself looks right to me (the floor(numel*r/NR) boundaries match the InCore composite ring's FP32 convention in lower_composite_ops_pass.cpp:1413, numel is already accumulated at runtime from data_tensor->shapes[], empty chunks at numel < NR are handled, and the divisible path is numerically identical to before). Both notes are about scope and dead code, not the algorithm.

Comment thread src/ir/op/distributed/collective.cpp
Comment thread src/ir/transforms/lower_host_tensor_collectives_pass.cpp Outdated
…g allreduce (hw-native-sys#2242)

Relax the ring allreduce numel % NR == 0 constraint so the host builtin ring
allreduce handles arbitrary/ragged lengths (issue hw-native-sys#2242).

- lower_host_tensor_collectives_pass.cpp: delete the static-shape + divisibility
  checks and the now-redundant CheckRingChunkConstraints wrapper (its remaining
  DistributedTensorType assert is duplicated by MakeBuiltinAllReduce and the ring
  builtin deducer); relocate the ragged-partition comment to the ring-builtin
  selection in MakeBuiltinAllReduce. The kernel partitions src into NR balanced,
  potentially ragged chunks at runtime ([floor(numel*r/NR), floor(numel*(r+1)/NR))).
- Ring builtin deducer (collective.cpp): drop the compile-time static-shape +
  numel % nr validation block (the kMaxSupportedRanks check stays).
- allreduce_ring kernel template: RingChunkStart() helper, soft guard no longer
  rejects non-divisible numel, max_chunk_elems = ceil(numel/nranks), tile_cols
  capped at kTileCount, and both RS/AG loops iterate ragged send spans.
- ST: ragged host ring test {1,17,4097,65537} x P=2/P=4 (Sum), stage-in/out
  chunked with 32-byte-aligned sub-tiles.
- UT: reject_nondivisible -> accept_nondivisible (SIZE=27, P=4); ring dynamic-
  extent deducer test (dynamic src + [7,4] INT32 signal type-deduces).
- docs: update the ring allreduce contract in 43-lower_host_tensor_collectives.md
  (EN+ZH) and 01-faq.md (EN+ZH) — drop the obsolete static + numel%NR==0
  restriction; document the runtime ragged partitioning.

Verified: UT 51/51; a2a3sim 12/12; NPU P=2 (d4-5) 6/6 and P=4 (d4-7) 12/12;
pre-commit green for the changed files.
…ring site

`builtin.tensor.allreduce_ring` is `set_internal_only(true)`, so its type
deducer is only ever reached through `LowerHostTensorCollectives` ->
`MakeBuiltinAllReduce` -> `OpRegistry::CreateInternal`. A UT that builds the
user-level `pld.tensor.allreduce(mode="ring")` runs `DeduceTensorAllReduceType`
instead, which never inspected `shape_` and never read `mode` — so it cannot
observe the static-shape requirement this branch removed.

Add the lowering-side companion: a host_orch whose ring `src` window is
`[pld.world_size(), SIZE]`, i.e. a `pld.system.world_size` Call rather than a
`ConstInt` dim, which is exactly what the deleted check rejected. Verified
non-vacuous by temporarily restoring that check: this test fails with
"requires a statically-known src shape", while the existing
test_tensor_allreduce_ring_accepts_dynamic_shape still passes.
@YunjiQin
YunjiQin merged commit cfc63e1 into hw-native-sys:main Sep 3, 2026
20 checks passed
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.

2 participants