Skip to content

tensormap_and_ringbuffer: split fanin edges into orthogonal WAIT/RETAIN flags (#1375) - #1806

Merged
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
ChaoZheng109:feat/issue-1375-split-wait-retain-deps
Aug 13, 2026
Merged

tensormap_and_ringbuffer: split fanin edges into orthogonal WAIT/RETAIN flags (#1375)#1806
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
ChaoZheng109:feat/issue-1375-split-wait-retain-deps

Conversation

@ChaoZheng109

@ChaoZheng109 ChaoZheng109 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #1375 (PR1 of 2 — the WAIT/RETAIN infrastructure).

In tensormap_and_ringbuffer, one fanin edge coupled two semantics: WAIT
(the consumer waits for the producer) and RETAIN (the producer's slot +
packed output stay alive until the consumer releases them). Coupling them
retains modifier producers for the consumer's whole lifetime even though only
ordering is needed, and blocks the transitive-reduction work that must drop a
redundant ordering edge while keeping a required lifetime reference.

This PR makes the two semantics orthogonal per edge and splits the accounting.
It lands identically in a2a3 and a5.

What changed

  • DepFlags { DEP_WAIT, DEP_RETAIN } in pto_types.h.
  • Storage: pack the 2 flag bits into the low bits of the producer slot
    pointer, for both the inline fanin array and the spill pool
    (PTO2TaskSlotState is alignas(64)). sizeof unchanged; no new payload
    field — which also removes the separate-mask/init()-order hazard the
    reference prototype had.
  • Construction: creator (owner_task_id) → WAIT|RETAIN; tensormap
    modifier → WAIT; explicit deps default WAIT|RETAIN, with a new
    CoreTaskArgsWithDeps::add_dep_wait() for ordering-only deps. Duplicate
    producers OR-accumulate their flags on dedup (no first-wins ordering).
  • Accounting: readiness (fanin_count, fanout_head linkage, dep_pool
    reservation) counts DEP_WAIT edges only. A WAIT-without-RETAIN edge
    releases its submit→wire pin at wiring (and on the all-completed fast
    path), so a modifier producer can be CONSUMED without waiting for this
    consumer. on_task_release releases DEP_RETAIN edges only.
  • DFX: dep_gen replay threads DepFlags through the oracle, records
    "flags" per edge in deps.json, and the differential gate now compares the
    (producer → flags) mapping instead of the producer-id set alone.

No RETAIN-only edge is produced yet — nothing generates one until the
transitive-reduction pass, which is the follow-up PR2 (device-side 1-hop,
per the discussion on #1375). This PR is the enabling representation +
accounting it builds on, plus the modifier early-consume win.

Tests

  • cpput (a2a3 + a5, 97/97): fanin-pool DepFlags round-trip across inline
    and spill; wiring regressions (ordering-only producer released at wiring
    vs. retention held until on_task_release, including a spilled RETAIN
    edge); orchestrator-level tests for a WAIT-only explicit dep, OR-fold of two
    discovery kinds on one producer (inline and spill region, asserting the
    spilled edge's folded flags), and the all-completed fast-path pin release.
  • sim scene tests: dfx/dep_gen on a2a3sim + a5sim (exercises the
    flag-aware replay gate and deps.json flags end-to-end); dummy_task and
    mixed_example on both sims.
  • onboard (a2a3, real silicon via task-submit): full
    tests/st/a2a3/tensormap_and_ringbuffer scene-test suite — 33 passed; a
    30-iteration stress loop over the dependency-heavy mixed_example +
    alternating_matmul_add (exercising the modifier WAIT-only early-release path
    and slot reuse) — all passed, no 507018 / hang / wrong result. This is
    the concurrency validation for the one real behavior change (early pin release
    makes COMPLETED → CONSUMED → reset_for_reuse a common path).

Notes for review

  • The a2a3 and a5 diffs are symmetric (identical per-file line counts).
  • host_build_graph has its own append_fanin_or_fail and is otherwise
    untouched; the only hbg change is one comment line, fixing a now-dangling
    fanin_inline_slot_states reference to hbg's actual fanin_local_ids after
    the payload field rename.
  • One open point for the team: or_flags_into_existing uses always_assert
    when a deduped producer is somehow absent from the builder (a seen-set/builder
    desync — currently unreachable). This is a loud device failure rather than a
    silent weak-semantics degrade; flagging the "crash-not-continue" choice for
    confirmation. Can switch to report_fatal if preferred.

@coderabbitai

coderabbitai Bot commented Aug 12, 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: 97c40951-b310-42a4-89b9-99a8781fb414

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

The runtime now separates dependency readiness (DEP_WAIT) from producer lifetime retention (DEP_RETAIN). Fanin storage, orchestration, scheduling, dependency replay, JSON output, documentation, and unit tests now preserve and apply these flags.

Changes

Dependency semantics and orchestration

Layer / File(s) Summary
Dependency contracts and fanin storage
src/a2a3/runtime/tensormap_and_ringbuffer/{orchestration,runtime}/*, src/a5/runtime/tensormap_and_ringbuffer/{orchestration,runtime}/*, */docs/RUNTIME_LOGIC.md
Adds DepFlags, per-dependency APIs, packed fanin entries, flag-aware traversal callbacks, and flag-aware dependency emission.
Flag-aware fanin orchestration
src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_orchestrator.cpp, src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_orchestrator.cpp, */runtime/scheduler/pto_scheduler.h
Deduplicates producers by merging flags, counts only WAIT edges for readiness, releases WAIT-only pins during wiring, and releases RETAIN edges on task release.
Dependency replay and schema validation
docs/dfx/dep-gen.md, src/a2a3/runtime/tensormap_and_ringbuffer/host/dep_gen_replay.cpp, src/a5/runtime/tensormap_and_ringbuffer/host/dep_gen_replay.cpp
Replay accumulates producer-to-flag mappings, serializes flags in deps.json, and reports producer or flag mismatches.
Runtime and replay validation
tests/ut/cpp/{a2a3,a5}/test_{fanin_pool,orchestrator_fanin,wiring}.cpp
Tests cover inline and spill flag propagation, duplicate dependency defaults, ordering-only release, retained release, and full wiring paths.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit hops through flags anew,
WAIT keeps order, RETAIN holds true.
Fanin stores each careful sign,
Spill entries pass it down the line.
Replay checks the edges bright—
“Hop safely home,” says moonlit night.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds the flag infrastructure but does not implement transitive reduction or produce RETAIN-only edges required by issue #1375. Implement the WAIT-graph transitive-reduction pass and add full-path tests that convert redundant edges to RETAIN-only while preserving producer lifetime.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The code, documentation, replay updates, and tests are directly related to the WAIT/RETAIN dependency objectives in issue #1375.
Description check ✅ Passed The description clearly explains the WAIT/RETAIN dependency changes, runtime accounting, replay updates, and tests covered by the changeset.
Title check ✅ Passed The title concisely and accurately identifies the main change: separating fanin edges into orthogonal WAIT and RETAIN flags.

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.

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

🧹 Nitpick comments (1)
tests/ut/cpp/a2a3/test_wiring.cpp (1)

1087-1125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Neither wiring test suite covers a DEP_RETAIN-only edge. Both suites test DEP_WAIT and DEP_WAIT | DEP_RETAIN only, so the retention-only contract required by issue #1375 stays unverified in both runtimes.

  • tests/ut/cpp/a2a3/test_wiring.cpp#L1087-L1125: add a test with a DEP_RETAIN-only edge that asserts no fanout_head link, no readiness contribution, no pin release at wiring, and a pin release at on_task_release.
  • tests/ut/cpp/a5/test_wiring.cpp#L476-L520: add the mirrored test so the a2a3 and a5 suites stay aligned.
🤖 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/cpp/a2a3/test_wiring.cpp` around lines 1087 - 1125, Add mirrored
tests for a DEP_RETAIN-only edge in tests/ut/cpp/a2a3/test_wiring.cpp:1087-1125
and tests/ut/cpp/a5/test_wiring.cpp:476-520. Using the existing wiring test
patterns, assert that the edge creates no fanout_head link, contributes nothing
to readiness, does not release its pin during wiring, and releases the pin
exactly once through sched.on_task_release.
🤖 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/dfx/dep-gen.md`:
- Line 217: Update the `flags` schema documentation to state that explicit edges
default to `["wait","retain"]`, while allowing `["wait"]` for explicit edges
created through the ordering-only `CoreTaskArgsWithDeps::add_dep_wait()` API;
retain the existing descriptions for creator and tensormap edges.

In `@src/a2a3/runtime/tensormap_and_ringbuffer/docs/RUNTIME_LOGIC.md`:
- Line 439: Update the fanin metadata and wiring descriptions in
src/a2a3/runtime/tensormap_and_ringbuffer/docs/RUNTIME_LOGIC.md:439-439 and
src/a5/runtime/tensormap_and_ringbuffer/docs/RUNTIME_LOGIC.md:440-440 to
distinguish WAIT-only, RETAIN-only, and combined producer edges: only DEP_WAIT
contributes to fanin_count and fanout notifications, DEP_WAIT-only pins are
released after wiring, and DEP_RETAIN pins are released when the consumer
completes. Keep both documents in parity.

In `@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_orchestrator.cpp`:
- Around line 457-486: Store fanin_builder.wait_count in the payload as the
DEP_WAIT edge count, then replace fanin_actual_count with that value for
early-dispatch threshold checks at the code paths corresponding to lines 497,
988, and 1102. Keep DEP_RETAIN-only edges excluded so thresholds align with
dispatch_fanin.

In `@src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_types.h`:
- Around line 452-477: Update the documentation comment for
set_dependencies_with_kinds to state that set_dependencies() uses the DEP_WAIT |
DEP_RETAIN default for every dependency, matching explicit_dep_kind() and the
actual API behavior. Leave the implementation unchanged.

In `@src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_orchestrator.cpp`:
- Around line 452-481: Align early-dispatch counting in this fanin handling with
the denominator used at the comparisons against payload->fanin_actual_count:
either count all fanin edges consistently or use a matching DEP_WAIT-only
denominator. Apply the same correction to the corresponding a2a3 mirror and
preserve correct behavior when DEP_RETAIN-only edges are present, including the
transitive-reduction follow-up.

In `@src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_types.h`:
- Around line 453-459: Correct the set_dependencies() reference in the
documentation for the per-dependency DepFlags overload so it states that the
default is DEP_WAIT | DEP_RETAIN, not “all-RETAIN.”
- Around line 461-478: Update set_dependencies_with_kinds to validate every
DepFlags value before storing the dependency arrays, rejecting any bits outside
the supported mask 0x3 via set_error. Ensure validation occurs before mutating
explicit_deps_, explicit_dep_kinds_, or explicit_dep_count_, and preserve the
existing zero-count and null-input behavior.

---

Nitpick comments:
In `@tests/ut/cpp/a2a3/test_wiring.cpp`:
- Around line 1087-1125: Add mirrored tests for a DEP_RETAIN-only edge in
tests/ut/cpp/a2a3/test_wiring.cpp:1087-1125 and
tests/ut/cpp/a5/test_wiring.cpp:476-520. Using the existing wiring test
patterns, assert that the edge creates no fanout_head link, contributes nothing
to readiness, does not release its pin during wiring, and releases the pin
exactly once through sched.on_task_release.
🪄 Autofix

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: 92f5031d-146c-4c6f-9d7b-b458fd4b382b

📥 Commits

Reviewing files that changed from the base of the PR and between 50c0660 and e39d55c.

📒 Files selected for processing (25)
  • docs/dfx/dep-gen.md
  • src/a2a3/runtime/tensormap_and_ringbuffer/docs/RUNTIME_LOGIC.md
  • src/a2a3/runtime/tensormap_and_ringbuffer/host/dep_gen_replay.cpp
  • src/a2a3/runtime/tensormap_and_ringbuffer/orchestration/pto_arg_with_deps.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_dep_compute.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_orchestrator.cpp
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_ring_buffer.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_runtime2_types.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_types.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/scheduler/pto_scheduler.h
  • src/a5/runtime/tensormap_and_ringbuffer/docs/RUNTIME_LOGIC.md
  • src/a5/runtime/tensormap_and_ringbuffer/host/dep_gen_replay.cpp
  • src/a5/runtime/tensormap_and_ringbuffer/orchestration/pto_arg_with_deps.h
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_dep_compute.h
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_orchestrator.cpp
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_ring_buffer.h
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_runtime2_types.h
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_types.h
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/scheduler/pto_scheduler.h
  • tests/ut/cpp/a2a3/test_fanin_pool.cpp
  • tests/ut/cpp/a2a3/test_orchestrator_fanin.cpp
  • tests/ut/cpp/a2a3/test_wiring.cpp
  • tests/ut/cpp/a5/test_fanin_pool.cpp
  • tests/ut/cpp/a5/test_orchestrator_fanin.cpp
  • tests/ut/cpp/a5/test_wiring.cpp

Comment thread docs/dfx/dep-gen.md Outdated
Comment thread src/a2a3/runtime/tensormap_and_ringbuffer/docs/RUNTIME_LOGIC.md Outdated
Comment thread src/a2a3/runtime/tensormap_and_ringbuffer/runtime/pto_types.h
Comment thread src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_types.h Outdated
Comment thread src/a5/runtime/tensormap_and_ringbuffer/runtime/pto_types.h
@ChaoZheng109
ChaoZheng109 force-pushed the feat/issue-1375-split-wait-retain-deps branch 4 times, most recently from 961de27 to 4d8a1d2 Compare August 13, 2026 11:52
…IN flags

Fixes hw-native-sys#1375

A fanin edge previously carried two coupled semantics at once: readiness
(the consumer waits for the producer) and lifetime (the producer's slot and
packed output stay alive until the consumer releases them). Coupling them
means a modifier producer is retained for the consumer's whole lifetime even
though only ordering is needed, and it blocks the transitive-reduction work
that must drop a redundant ordering edge while keeping a required lifetime
reference.

Introduce DepFlags { DEP_WAIT, DEP_RETAIN } and carry the flags per edge:

- Storage: pack the flags into the low 2 bits of the producer slot pointer,
  for both the inline fanin array and the spill pool. PTO2TaskSlotState is
  alignas(64), so the low bits are free and sizeof is unchanged. This unifies
  the inline and spill representations and needs no extra payload field.
- Construction: a creator (owner_task_id) edge is DEP_WAIT|DEP_RETAIN; a
  tensormap-modifier edge is DEP_WAIT. Explicit deps default to
  DEP_WAIT|DEP_RETAIN (conservative); CoreTaskArgsWithDeps gains add_dep_wait()
  for ordering-only deps. When a producer is reached for several reasons, the
  flags are OR-accumulated on dedup rather than first-wins.
- Accounting: readiness (fanin_count, fanout_head linkage, dep_pool
  reservation) counts DEP_WAIT edges only. A DEP_WAIT edge without DEP_RETAIN
  releases its submit->wire pin at wiring (and on the all-completed fast path),
  so a modifier producer can be CONSUMED without waiting for this consumer.
  on_task_release releases DEP_RETAIN edges only.
- DFX: dep_gen replay threads DepFlags through the oracle, records them per
  edge in deps.json, and the differential gate now compares the
  (producer -> flags) mapping instead of the producer-id set alone.

Storing the flags inside the edge pointers (written when the builder flushes
to the payload) also avoids the separate-mask/init-order hazard, since there
is no field for payload.init() to zero after the fact.

Lands identically in a2a3 and a5. No RETAIN-only edge is produced yet; the
transitive-reduction pass that creates them is a follow-up.

Tests: fanin-pool flag round-trip across inline and spill; a wiring
regression asserting an ordering-only producer is released at wiring while a
retention producer is held until on_task_release, including a spilled RETAIN
edge; the duplicate-explicit-dep test now checks the folded flags.

PTO2FaninSpillEntry::set/add_flags mask the flags to the tag bits so a malformed
DepFlags value can never corrupt the packed slot pointer, and an assert pins the
PR1 invariant that every fanin edge carries DEP_WAIT (so fanin_actual_count is
the early-dispatch WAIT denominator).

The fanin edge stays trivially default-constructible (no in-class initializer),
so the payload's inline edge array and the per-submit builder array are not
zeroed on the orchestrator hot path; a static_assert pins the slot-pointer
alignment the tag bits rely on. The payload field is renamed fanin_inline_edges
to match its packed-edge contents, or_flags_into_existing folds through a single
index helper and fatals rather than silently degrading if a deduped producer is
missing, and the early-dispatch WAIT-denominator invariant uses always_assert so
it survives release builds. deps.json records explicit edges as wait+retain (the
DepGenRecord carries no per-dep kind) and the retention-safety invariant behind
the modifier=WAIT downgrade is documented at compute_task_fanin Step B.
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.

[Feature] Split wait and retain dependency semantics for safe transitive reduction

1 participant