Skip to content

fix(codegen): size the GM pipe workspace for both rings of a bidirectional pipe - #2271

Open
vloncar wants to merge 2 commits into
hw-native-sys:mainfrom
vloncar:fix/gm-pipe-workspace-two-rings
Open

fix(codegen): size the GM pipe workspace for both rings of a bidirectional pipe#2271
vloncar wants to merge 2 commits into
hw-native-sys:mainfrom
vloncar:fix/gm-pipe-workspace-two-rings

Conversation

@vloncar

@vloncar vloncar commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What

Fixes #2269. ComputeGMPipeWorkspaceElements sized the injected __gm_pipe_buffer as slot_count * slot_size — one ring's worth — and re-derived slot_count from dir_mask. Two changes:

  1. Multiply by a new GetGMPipeRingCount(dir_mask) — 2 for bidirectional, 1 otherwise — so a bidirectional pipe gets 2 * slot_num * slot_size, matching the A2A3 GM layout specified in pto-isa's docs/HL_ptoisa_newfeature20260306_TPUSH_TPOP.md:169 and drawn at :288 (C2V ring at offset 0, V2C ring at slot_num * slot_size).
  2. Honour the slot_num kwarg on initialize_pipe when present, falling back to the dir_mask default when absent. Mismatched slot_num values for one pipe id are a CHECK, mirroring the existing slot_size consistency check.
File Change
src/codegen/orchestration/orchestration_analysis.cpp Ring count × slot count × slot size; honour explicit slot_num
tests/ut/codegen/test_orchestration_tensor_rw.py Two new tests; one existing expectation updated

Test changes

test_submit_dispatched_pipe_group_sizes_workspace_and_resolves_callees asserted ["512"] elements for a dir_mask=3, slot_size=512 pipe — one ring, i.e. the bug written down as an expectation. It becomes ["1024"]. Two new tests pin the rule:

  • test_gm_pipe_buffer_bidirectional_reserves_two_ringsdir_mask=3, slot_size=5122 * 4 * 512 B = 1024 floats. Fails on main with 512.
  • test_gm_pipe_buffer_honours_explicit_slot_numdir_mask=2, slot_size=512, slot_num=1616 * 512 B = 2048 floats. Fails on main with 1024, the dir_mask default of 8 slots.

Behaviour

The GM workspace for bidirectional pipes doubles (4 KiB → 8 KiB per pipe at the default depth) and follows slot_num where given. No IR-structural change: the injected __gm_pipe_buffer parameter, its placement and the pass pipeline are untouched — only the element count on the orchestration-side tensor.create.

Why this is worth landing on its own, and first

Today this is a latent sizing bug: the under-allocation is consistent with a matching pto-isa defect where the device never applies the per-direction ring offset either, so both rings really do live in the first half and the short allocation "fits". Fixing this side alone changes nothing observable — measured, the resulting corruption is bit-identical.

But the device-side fix (hw-native-sys/pto-isa#227, for hw-native-sys/pto-isa#226) puts the V2C ring at base + slot_num * slot_size, which with the current sizing is past the end of the allocation. So this must ship first. It is safe to merge ahead of the device change, and it is wrong on its own terms regardless — against a spec that predates both bugs.

Together the two fix a silent cross-core corruption on A2A3 hardware. Full analysis, minimal repro and the prior history (including hw-native-sys/pto-isa#195, which reported the device half in July and was withdrawn) are in hw-native-sys/pto-isa#226.

A related inconsistency, deliberately left out

BuildAutomaticPipeSetup (src/ir/transforms/utils/cross_core_pipe.cpp:348,352) passes the raw slot_num_override to CreateInitializePipe while sizing the reserved buffer from effective_slot_num, so in the default case the emitted initialize_pipe carries no slot_num attribute even though a concrete depth was used. That is harmless — every consumer re-derives the same default from dir_mask — and orthogonal to this fix, which reads slot_num only when a pipe actually carries one. Passing effective_slot_num instead makes the attribute always explicit, but it adds a kwarg to every auto-generated pipe and so changes the expected IR in 12 structural-equality tests (test_expand_mixed_kernel_a2a3.py, _a5.py, _split_aiv.py). Happy to send that as a follow-up if you want the attribute made explicit.

Validation

tests/ut/codegen + tests/ut/ir/transforms: 3195 passed.

On hardware (Atlas A2/A3, fp32), with the pto-isa half applied, at the stock ring depth and with no DSL overrides: a GLA/ZeCO sequence-parallel forward that failed 5/12 configurations at C=16 and 6/12 at C=32 now passes 24/24 over C ∈ {16,32} × P ∈ {1,2,4} × N ∈ {2,4,8,16}, max abs error ≤ 3.1e-5; and a standalone cube↔vector loop repro goes from 12/21 corrupted dispatches to 21/21 clean.

@coderabbitai

coderabbitai Bot commented Aug 3, 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: Pro Plus

Run ID: 7166b730-ebd0-408b-b61c-cec685d308b8

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
📝 Walkthrough

Walkthrough

GM pipe workspace sizing now accepts explicit slot_num values, validates consistency across pipe uses and directions, and defaults from direction when unspecified. Bidirectional pipes allocate two rings. Tests cover both sizing modes and updated workspace expectations.

Changes

GM pipe workspace sizing

Layer / File(s) Summary
Workspace sizing rules
src/codegen/orchestration/orchestration_analysis.cpp
The analysis validates explicit slot_num values, derives ring counts, applies direction-based defaults, and multiplies workspace allocation for bidirectional pipes.
Workspace sizing regression tests
tests/ut/codegen/test_orchestration_tensor_rw.py
Tests verify two-ring bidirectional allocation, explicit 16-slot sizing, and the updated submitted workspace size.

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

Possibly related issues

  • Issue 2269: The pull request implements the issue’s explicit slot_num handling and two-ring allocation for bidirectional GM pipes.

Poem

A rabbit counts each pipe with care,
Two rings turn through workspace air.
Sixteen slots stand neat in line,
Defaults guide the rest in time.
Tests thump paws: the sizes align!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: sizing GM pipe workspace for both rings of bidirectional pipes.
Description check ✅ Passed The description directly explains the workspace sizing fix, explicit slot count handling, tests, and validation results.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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: c8960f9283

ℹ️ 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 on lines +187 to +188
const int64_t pipe_bytes = static_cast<int64_t>(ring_count) * static_cast<int64_t>(slot_count) *
static_cast<int64_t>(slot_size);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep PTO offsets in sync with larger pipe footprints

When a function contains multiple frontend pipe IDs and an earlier pipe is bidirectional or has an explicit slot_num, this allocation now reserves ring_count * slot_count * slot_size, but PTOCodegen::PrepareGMSlotBufferLayout still advances gm_slot_buffer_offsets_ by only GetGMPipeSlotCount(dir_mask) * slot_size and ignores both the second ring and explicit slot_num (src/codegen/pto/pto_codegen.cpp:496-503). The next pipe is therefore handed an addptr into the previous pipe's second ring/extra slots, so two independent GM pipes can alias even though the total orchestration allocation is now large enough; please make the PTO offset layout use the same footprint calculation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c3dd15c — thank you, this was a real gap and I had missed it entirely.

You are right on both counts: PrepareGMSlotBufferLayout advanced by GetGMPipeSlotCount(dir_mask) * slot_size, so with more than one pipe in a function the next pipe's addptr landed inside the previous pipe's region — the second ring of a bidirectional pipe, or the tail of an explicit slot_num, whichever applied. The workspace being big enough overall does not help when the per-pipe offsets are wrong.

Rather than patch the second copy to match, I hoisted the rule into include/pypto/codegen/gm_pipe_layout.h and had both sites derive from it, since there were two independent copies of GetGMPipeSlotCount that had to agree and no way to tell when they stopped agreeing. Regression added in test_gm_slot_buffer_regions_do_not_overlap_across_pipes: pipe 0 is bidirectional and carries slot_num=8, so pipe 1's emitted offset is %c4096_index (2 rings x 8 slots x 1024 B). Before the fix it was %c1024_index — inside pipe 0.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🤖 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 `@src/codegen/orchestration/orchestration_analysis.cpp`:
- Around line 129-137: Update the slot-count validation in the initialize_pipe
handling around slot_num_by_pipe so the direction-specific default is resolved
before comparison. Validate the resulting effective slot count on every call for
the same pipe_id and dir_mask, including calls that omit slot_num, while
preserving the existing consistency error for mismatched values.
- Around line 180-186: Update the validation in the pipe initialization logic
around slot_count and ring_count so dir_mask-derived values are validated before
accepting an explicit slot_num. Ensure invalid dir_mask values such as those
producing a nonpositive slot or ring count are rejected even when
slot_num_by_pipe contains a positive override, while preserving valid explicit
slot_num behavior.
- Around line 180-191: Update PTOCodegen::PrepareGMSlotBufferLayout to compute
each pipe’s byte range using the explicit slot_num when present, otherwise
GetGMPipeSlotCount(dir_mask), and multiply by GetGMPipeRingCount(dir_mask)
before advancing byte_offset. Add a regression covering two pipe IDs, including
an explicit slot_num and bidirectional ring, and verify their computed regions
do not overlap.
🪄 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: 669ac948-079c-4b46-8b1e-663b05cb1295

📥 Commits

Reviewing files that changed from the base of the PR and between 4389165 and c8960f9.

📒 Files selected for processing (2)
  • src/codegen/orchestration/orchestration_analysis.cpp
  • tests/ut/codegen/test_orchestration_tensor_rw.py

Comment thread src/codegen/orchestration/orchestration_analysis.cpp Outdated
Comment thread src/codegen/orchestration/orchestration_analysis.cpp Outdated
Comment thread src/codegen/orchestration/orchestration_analysis.cpp Outdated
…ional pipe

ComputeGMPipeWorkspaceElements sized __gm_pipe_buffer as slot_count *
slot_size, one ring's worth, and re-derived slot_count from dir_mask.
A bidirectional pipe is two rings — C2V at offset 0 and V2C at
slot_num * slot_size, total 2 * slot_num * slot_size, per the A2A3 GM
layout in pto-isa's HL_ptoisa_newfeature20260306_TPUSH_TPOP.md — so a
DIR_BOTH pipe got half the memory it needs, and an explicit
pl.cross_core_slot(slot_num=N) was ignored entirely.

Multiply by a ring count derived from dir_mask, and honour the slot_num
kwarg on initialize_pipe when it carries one, falling back to the
dir_mask default when it does not.

The under-allocation was masked by a matching pto-isa defect that never
applied the per-direction ring offset, so both rings really did live in
the first half. This must land before that fix (hw-native-sys/pto-isa#227),
which otherwise writes past the end of the workspace.

Fixes hw-native-sys#2269
@vloncar
vloncar force-pushed the fix/gm-pipe-workspace-two-rings branch from c8960f9 to 2142c39 Compare August 3, 2026 14:24
@lyfne123

lyfne123 commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

please fix ai comment

vloncar added a commit to vloncar/pypto that referenced this pull request Aug 4, 2026
Review of hw-native-sys#2271 found a second copy of the GM pipe layout rule.
PTOCodegen::PrepareGMSlotBufferLayout advances each frontend pipe's byte
offset by GetGMPipeSlotCount(dir_mask) * slot_size, ignoring both the
second ring of a bidirectional pipe and an explicit slot_num. With more
than one pipe in a function, the next pipe's addptr therefore lands
inside the previous pipe's region and two independent pipes alias — even
though the orchestration workspace is now large enough to hold both.

Hoist the rule into include/pypto/codegen/gm_pipe_layout.h and derive
both the workspace size and the per-pipe offsets from it, so the two can
no longer drift apart. Two smaller fixes fall out of the shared helper:
the effective slot count is now validated on every initialize_pipe call
for a pipe (a call that omits slot_num while another states a
non-default value is a mismatch, not a silent win for the explicit one),
and dir_mask is validated before any explicit slot_num is honoured, so
an unlayoutable direction is still rejected.

Adds a regression asserting a second pipe starts past the first pipe's
full two-ring, explicit-slot_num footprint.
@vloncar

vloncar commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Pushed c3dd15c addressing all four review comments — the Codex P1 and CodeRabbit's three. Summary for anyone reading the PR rather than the threads:

The substantive one (flagged independently by both bots) was real and I had missed it: PTOCodegen::PrepareGMSlotBufferLayout computes each frontend pipe's byte offset within the workspace, and it advanced by GetGMPipeSlotCount(dir_mask) * slot_size — ignoring the second ring of a bidirectional pipe and any explicit slot_num. Sizing the workspace correctly does not help if the offsets inside it are wrong: with two or more pipes in a function, pipe N+1's addptr pointed inside pipe N's region.

The root problem was duplication — two independent copies of GetGMPipeSlotCount that had to agree, with nothing to catch them drifting. That is the same shape as the bug this PR fixes, one layer up. So instead of patching the second copy, the rules now live in include/pypto/codegen/gm_pipe_layout.h (SlotCountForDirMask, RingCountForDirMask, EffectiveSlotCount, FootprintBytes) and both sites derive from it. If a third consumer appears, it gets the same answer.

The two smaller findings fall out of the shared helper: the effective slot count is validated on every initialize_pipe call for a pipe, and dir_mask is validated before an explicit slot_num is honoured.

New regression test_gm_slot_buffer_regions_do_not_overlap_across_pipes: two pipe IDs, the first bidirectional and carrying slot_num=8, asserting pipe 1's emitted offset is %c4096_index (2 rings x 8 slots x 1024 B). Pre-fix it was %c1024_index.

tests/ut/codegen + tests/ut/ir/transforms: 3275 passed. pre-commit clean on all changed files.

Review of hw-native-sys#2271 found a second copy of the GM pipe layout rule.
PTOCodegen::PrepareGMSlotBufferLayout advances each frontend pipe's byte
offset by GetGMPipeSlotCount(dir_mask) * slot_size, ignoring both the
second ring of a bidirectional pipe and an explicit slot_num. With more
than one pipe in a function, the next pipe's addptr therefore lands
inside the previous pipe's region and two independent pipes alias — even
though the orchestration workspace is now large enough to hold both.

Hoist the rule into include/pypto/codegen/gm_pipe_layout.h and derive
both the workspace size and the per-pipe offsets from it, so the two can
no longer drift apart. Two smaller fixes fall out of the shared helper:
the effective slot count is now validated on every initialize_pipe call
for a pipe (a call that omits slot_num while another states a
non-default value is a mismatch, not a silent win for the explicit one),
and dir_mask is validated before any explicit slot_num is honoured, so
an unlayoutable direction is still rejected.

Adds a regression asserting a second pipe starts past the first pipe's
full two-ring, explicit-slot_num footprint.
@vloncar
vloncar force-pushed the fix/gm-pipe-workspace-two-rings branch from c3dd15c to a06c857 Compare August 4, 2026 09:45
@vloncar

vloncar commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

CI on a06c857 is red on four device jobs (system-tests, system-tests-direct, pypto-lib-model, dist-system-tests). I looked into whether this PR caused them, and the evidence says no — details so you can check my reasoning rather than take my word.

1. Three of the four fail identically on main. Run 30887648177 (push on main) fails pypto-lib-model, system-tests and system-tests-direct, including the same tests/st/runtime/scheduling/test_phase_fence_dep_compression.py that dominates my system-tests failures. Those are pre-existing.

2. Every failure is the same device-bootstrap error, before any generated kernel runs:

BootstrapDispatcher: [load_aicpu_op.cpp:210] aclrtSynchronizeStream failed: 507018
LoadAicpuOp::BootstrapDispatcher failed: 507018
RuntimeError: simpler_init failed with code 507018   # at worker.init(prewarm_config=cfg)

135 occurrences in the dist job alone. This is AICPU op loading at worker.init() — it fails before any codegen output is executed, so it cannot be a consequence of a workspace-layout change.

3. The failing tests cannot reach this code path. dist-system-tests is the one job that passed on main and failed here, so I checked it specifically: every failing test is an L3 collective (allgather, allreduce, broadcast, reduce_scatter, all_to_all, …), and grep -rlE "tpush|tpop|initialize_pipe|cross_core|split_aiv" tests/st/distributed/collectives/ returns nothing. With no initialize_pipe in the program, ComputeGMPipeWorkspaceElements returns 0 and PrepareGMSlotBufferLayout assigns no offsets — this PR is a no-op for them.

4. The same commit's sim and compile jobs are green: unit-tests (ubuntu + macos), codegen-tests, system-tests-a5sim, clang-tidy, pre-commit, build, toolchain. And the previous commit on this branch (c8960f9, same layout change minus an unused include) had all of these device jobs green ~20h earlier.

Reads like device/runner state on the NPU runners rather than anything in this change. Could someone with permissions re-run the failed jobs? Happy to be proven wrong — if the collectives keep failing on a clean runner while main passes, I will dig further.

For completeness, the last push itself was small: clang-tidy flagged misc-include-cleaner because moving the kDirMask* uses into the new shared header left #include "pypto/ir/transforms/utils/core_affinity.h" unused-direct in both .cpp files. Removed from both; verified locally with python tests/lint/clang_tidy.py --diff-base=origin/main (clean), a full rebuild, and the affected unit suites (121 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