Skip to content

Fix: preserve explicit dependency flags in dep_gen replay - #2057

Merged
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
zmnobug:fix/issue-1827-dep-gen-explicit-flags
Aug 29, 2026
Merged

Fix: preserve explicit dependency flags in dep_gen replay#2057
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
zmnobug:fix/issue-1827-dep-gen-explicit-flags

Conversation

@zmnobug

@zmnobug zmnobug commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Capture per-dependency WAIT/RETAIN flags in inline and overflow dep_gen records.
  • Replay explicit edges with their recorded flags while preserving runtime OR-dedup semantics with creator dependencies, and reject records containing unknown flag bits.
  • Preserve DFX buffer ownership after a ready entry is published when the push gate times out. DeviceProfilerEngine is shared by PMU, scope stats, L2 phase/task profiling, dep_gen, and args dump; this independent correctness fix was uncovered while investigating [Code Health] dep_gen: DepGenRecord carries no per-dep DepFlags, so explicit add_dep_wait edges are mis-recorded in deps.json #1827.
  • Cover inline, single-overflow, multi-overflow, wait-only, and explicit/creator dedup cases on A2/A3 and A5, and update the DFX documentation.

Differential Gate Scope

Explicit dependencies are captured input shared by the oracle and annotated replay passes, so the differential gate cannot independently validate their recorded kind bytes. The A2/A3 and A5 dep_gen chain scene tests validate the capture/replay round trip across inline and overflow records.

DepGenRecord changes from 4672 to 4736 bytes. Host and device runtime artifacts must be rebuilt together rather than mixed with partial incremental outputs.

Testing

  • C++ unit tests: 125/125 passed, including test_dep_gen_replay, test_dep_gen_collector_aicpu, and test_profiler_device_engine.
  • A2/A3 simulation dep_gen scene tests with manual cases: 2 passed.
  • A5 simulation dep_gen scene tests with manual cases: 2 passed.
  • A2/A3 onboard dep_gen scene tests with manual cases: 2 passed.
  • pre-commit run --from-ref upstream/main --to-ref HEAD.
  • A5 onboard was not run locally because this host has A2/A3 silicon; PR CI covers A5 hardware.

Fixes #1827

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab0c76d9-26d0-4803-a3f9-1245e7662f11

📥 Commits

Reviewing files that changed from the base of the PR and between 482ae6b and 4e485df.

📒 Files selected for processing (18)
  • docs/dfx/dep-gen.md
  • docs/dfx/dfx-buffer-capacity-audit.md
  • src/a2a3/platform/include/common/platform_config.h
  • src/a2a3/runtime/tensormap_and_ringbuffer/host/dep_gen_replay.cpp
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/orchestrator.cpp
  • src/a2a3/runtime/tensormap_and_ringbuffer/runtime/types.h
  • src/a5/platform/include/common/platform_config.h
  • src/a5/runtime/tensormap_and_ringbuffer/host/dep_gen_replay.cpp
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/orchestrator.cpp
  • src/a5/runtime/tensormap_and_ringbuffer/runtime/types.h
  • src/common/platform/include/aicpu/dep_gen_collector_aicpu.h
  • src/common/platform/include/common/dep_gen.h
  • src/common/platform/shared/aicpu/dep_gen_collector_aicpu.cpp
  • tests/st/a2a3/tensormap_and_ringbuffer/dfx/dep_gen/kernels/orchestration/chain_barrier_orch.cpp
  • tests/st/a2a3/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen_chain.py
  • tests/st/a5/tensormap_and_ringbuffer/dfx/dep_gen/kernels/orchestration/chain_barrier_orch.cpp
  • tests/st/a5/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen_chain.py
  • tests/ut/cpp/common/test_dep_gen_collector_aicpu.cpp

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Dependency capture now stores per-explicit-dependency DepFlags in base and overflow records. Replay validates and preserves these flags, merges duplicate producer edges, and updates both platform implementations. Tests and documentation cover the new 4736-byte layout and 524-dependency overflow capacity.

Dependency kind capture and replay

Layer / File(s) Summary
Record layout and submission contract
src/common/platform/include/common/dep_gen.h, src/common/platform/include/aicpu/dep_gen_collector_aicpu.h, src/{a2a3,a5}/runtime/.../types.h, src/{a2a3,a5}/platform/.../platform_config.h
Records include parallel kind arrays. The submission API accepts per-dependency kinds and a default kind.
Kind-aware dependency capture
src/common/platform/shared/aicpu/dep_gen_collector_aicpu.cpp, src/{a2a3,a5}/runtime/.../orchestrator.cpp
Submission paths pass dependency kinds. The collector copies supplied kinds or applies the default to base and overflow records.
Kind-aware replay and edge merging
src/{a2a3,a5}/runtime/.../host/dep_gen_replay.cpp
Replay reads and validates recorded kinds. Duplicate explicit and creator edges merge flags by producer.
Boundary and flag validation
tests/ut/cpp/common/test_dep_gen_collector_aicpu.cpp, tests/st/{a2a3,a5}/tensormap_and_ringbuffer/dfx/dep_gen/*, docs/dfx/*.md
Tests cover default kinds, mixed overflow records, updated boundaries, and cross-source deduplication. Documentation reflects the new record size and capacity.

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

Merge Risk: 🟡 Moderate · up to 4e485

The change preserves dependency flags but also reduces overflow capacity, which can expose a timeout path that reuses a buffer after it has been published, potentially corrupting dependency metadata used for host replay. Merge should wait for this ownership issue to be fixed or explicitly accepted by the responsible owner.

Sequence Diagram(s)

sequenceDiagram
  participant TaskSubmission
  participant AICPUCollector
  participant DepGenRecord
  participant DepGenReplay
  participant DepsJson
  TaskSubmission->>AICPUCollector: submit explicit dependency kinds
  AICPUCollector->>DepGenRecord: store dependency IDs and kind bytes
  DepGenRecord->>DepGenReplay: provide base and overflow records
  DepGenReplay->>DepsJson: emit validated, merged edge flags
Loading

Poem

I hop through records, neat and bright

With wait and retain tucked just right
Overflow berries line the chain
Replay joins duplicates again
A tidy burrow guards the byte

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 28 functions across 16 files. (2 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 The changes satisfy issue #1827 by adding per-dependency flags to base and overflow records, capturing and replaying them, preserving explicit/creator OR-deduplication, and updating related tests and …
Out of Scope Changes check ✅ Passed The changes remain within scope. Header layout updates, platform documentation, replay logic, tests, and DFX documentation directly support the explicit dependency flag fix.
Title check ✅ Passed The title clearly and concisely identifies the primary change: preserving explicit dependency flags during dep_gen replay.
Description check ✅ Passed The description explains the dependency-flag capture and replay changes, deduplication behavior, testing, documentation updates, and related DFX correctness work.
Full details: Linked Issues check

Explanation

The changes satisfy issue #1827 by adding per-dependency flags to base and overflow records, capturing and replaying them, preserving explicit/creator OR-deduplication, and updating related tests and documentation.

Full details: Docstring Coverage

Explanation

Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 28 functions across 16 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI

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.

@zmnobug
zmnobug force-pushed the fix/issue-1827-dep-gen-explicit-flags branch 2 times, most recently from 09f9959 to a1ce5e6 Compare August 28, 2026 07:40
@zmnobug

zmnobug commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Addressed the published-buffer ownership risk in a1ce5e6: once the ready-queue tail advances, a push-gate timeout no longer reports enqueue failure, so callers cannot clear or reuse a buffer already owned by the host. Added a DeviceProfilerEngine regression test for this timeout path.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@zmnobug Thanks for the fix. The updated timeout result preserves host ownership after the ready-queue tail advances. The DeviceProfilerEngine regression test covers the required path.

🐇


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

You are interacting with an AI system.

@doraemonmj doraemonmj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed against merge-base 89ba2a3f. Verified the wire-layout arithmetic by hand, diffed the a2a3 and a5 trees against each other for parity, and traced each item back to #1827. Two blocking items, both small; no technical objection to the approach.

What this gets right

Not just "it works" — a few things here required actually understanding the subsystem rather than following the issue's Proposed Fix:

  • The layout arithmetic holds together. 4736 B, tensors at offset 640, and DEP_GEN_OVERFLOW_DEPS_PER_RECORD = 524 are mutually consistent (16 + 9N <= 4736), and 640 preserves the original intent that each 128 B tensor blob covers exactly two cache lines instead of straddling three. The static_assert set was updated in step rather than left to rot.
  • The new is_trivially_copyable_v / is_standard_layout_v guards close a pre-existing gap on these host/device wire structs — that is required by the codestyle rule for device-copied structs and was missing before this PR.
  • The OR-dedup handling is the substantive part. Once an explicit edge can be wait-only, emit_creator's !first early-return silently drops the creator's retain. Introducing explicit_edge_index to fold the creator flags back into the explicit edge keeps replay aligned with FaninBuilder's collapse semantics — and it is documented in docs/dfx/dep-gen.md rather than left as folklore. I checked the .at() on that map: it is reachable only from the first == false branch, and annot_preds is cleared per record with STEP 1 as its only writer, so it cannot throw today.
  • a2a3/a5 are strictly symmetric. I diffed the two subtrees' patches against each other; the only differences are block_num vs core_num and line offsets.
  • The tests are real. The scene-test cases are manual only on a2a3sim, so the new flag assertions genuinely execute in st-onboard-a2a3 and st-onboard-a5. Pinning the UT at kDepCount = 64 + 524 + 1 anchors the new constant, and the trailing creator+explicit probe covers the dedup path that the explicit_edge_index change exists for.

Blocking

1. docs/dfx/global-backpressure-design.md now contradicts the code.

The "Timeout backstop" section still states:

When it expires the writer breaks to its single failure exit (return false), and the caller (switch_buffer) accounts the dropped records.

After this change that is only true for the pop side (wait_for_ready_queue_space / wait_for_free_queue_entry). On the push side the timeout is now discarded and nothing is accounted as dropped. Per the repo's doc-consistency rule the fix lands in the same commit as the code change. Splitting the paragraph to distinguish the two sides is enough.

2. return -5 collides with an existing code in the same function.

dep_gen_replay.cpp already returns -5 for write_deps_json() failure (near the end of dep_gen_replay_emit_deps_json). The new invalid-flags path reuses it. Callers in all four device_runner.cpp sites only log dep_gen replay failed (%d), and the header documents codes as "see source", so -5 becomes genuinely ambiguous between "a record carried a corrupt flags byte" and "deps.json could not be written". -7 in both the a2a3 and a5 copies.

Non-blocking, but worth addressing

3. The DFX-engine fix deserves more than one bullet. The commit message does carry Preserve host ownership when a published DFX buffer hits a gate timeout, and the rationale is in this PR's conversation — so this is not an undeclared change. The gap is scope visibility: DeviceProfilerEngine is shared by pmu, scope_stats, l2 phase, l2 AicoreTask, dep_gen and args_dump, so this is a behavior change across six subsystems riding in a PR whose title and body are entirely about dep_gen. The PR body's Summary omits it, and once this squash-merges the conversation context does not follow into git history. A short paragraph in the body — what the bug was, that it affects every DFX module, and that it is independent of #1827 — costs little and is what a future bisect will actually find. Splitting it into its own PR would be cleaner still, since it is independently testable and independently valuable.

4. #1827's third item is dropped silently. The issue also asks to "make the differential gate explicit-kind-aware". Both oracle_preds and annot_preds are still seeded from the same kinds_data[i] in STEP 1, so an explicit-kind divergence remains undetectable by construction — the shared hard-coded constant was replaced by a shared recorded byte. I think this is genuinely infeasible in the current design: the oracle is compute_task_fanin, which does not process explicit deps at all, so there is no independent second source to compare against. That is a fine answer, but it should be stated, because Fixes #1827 will close the issue and take the item with it. Either say so in the body (and ideally in a comment next to STEP 1), or leave a follow-up issue.

5. Rejecting the whole trace over one bad byte is inconsistent with the surrounding code. The same function handles the same class of input — values read back from device shared memory that could be corrupt — by clamping and continuing:

// ...we clamp on read too so a corrupted record never drives an OOB read
if (base_dc > DEP_GEN_MAX_EXPLICIT_DEPS) { LOG_ERROR(...); base_dc = DEP_GEN_MAX_EXPLICIT_DEPS; }

The new raw_kind check instead tears down both tensormaps and discards the entire deps.json. Unknown bits cannot cause an OOB read, and this is a DFX artifact rather than a correctness path, so LOG_ERROR plus raw_kind &= kKnownDepFlags (or falling back to WAIT|RETAIN) would match the established posture and still surface the anomaly.

6. The push-gate timeout is now entirely unobservable. A 30 s expiry means "the host is gone" — previously it at least showed up as an inflated dropped count; now there is no counter, no log, no sticky flag. Worth an on_push_gate_timeout trait hook or a one-shot log (not per-iteration — the AICPU hot-path logging rule applies).

Consider

  • The scene test maps alternating kinds back to producers via sorted(barrier_explicit_edges.items()), i.e. it assumes TaskId::raw ordering equals submission order. True today for a single ring, but a change in id allocation would surface as a confusing "flags mismatch" rather than an obvious failure. Deriving the producer sequence from the order of deps["tasks"] would be sturdier.
  • No test covers the new rejection path. A host-side case feeding a record with explicit_dep_kinds[0] = 0xFF is cheap. (If item 5 is adopted, assert the clamped flags instead.)
  • reinterpret_cast<const uint8_t *>(args.explicit_dep_kinds_data()) relies on enum DepFlags : uint8_t. A static_assert(sizeof(DepFlags) == 1) next to the cast beats making the reader go look it up.
  • The WAIT|RETAIN default is now spelled in three places (Arg::explicit_dep_kind(), both orchestrator call sites, and the UT's bare 3u). A named constant would help; at minimum the UT could use DEP_WAIT | DEP_RETAIN instead of 3u.
  • The flags row in the deps.json schema table is marked always, but host_build_graph's writer (dep_gen_host_graph.cpp) emits no flags field at all. Pre-existing, not introduced here — but since that row is already being rewritten, noting "tensormap_and_ringbuffer only" is nearly free.

Notes

  • DepGenRecord's layout changed with no version field on the host/device boundary, and static_assert only guards one side at compile time. A partially-rebuilt tree (host runtime on 4672, AICPU on 4736) would produce a plausible-looking but garbage deps.json with nothing to detect it. Not a defect in this PR — the format never had a version — but worth a line in the body telling reviewers to do a full rebuild rather than an incremental one.
  • Capacity impact is real but bounded: overflow slots drop 582 -> 524 (-10%), so a submit with e.g. dc = 600 now needs 3 slots where 2 sufficed. The theoretical per-submit ceiling falls from ~595k to ~536k deps, which CHIP_DEP_LIST_POOL_SIZE (16384) puts far out of reach anyway. Record memory grows 1.37% and only when --enable-dep-gen is set, since DepGenCollector::init() is gated on it. The audit doc's added sentence is an accurate characterization.
  • The scene test now makes half the barrier's deps ordering-only, which is a real runtime semantic change and not just an assertion change. Safe here because the producers write the external ext_X rather than a runtime-owned output, so there is no lifetime to lose — worth keeping in mind if that kernel is ever reworked to use owned outputs.
  • pto_isa.pin is untouched and no pto-isa header references changed, so no pin bump is implied.

Items 1 and 2 are the only things holding this up; both are a few lines. The core change is well-built and I have no objection to the approach.

@doraemonmj

Copy link
Copy Markdown
Contributor

A few additional review notes after tracing the updated paths:

  1. The source-level engine contract is stale too. The file-level comment in src/common/platform/include/aicpu/profiler_device_engine.h still says the 30-second backstop always breaks to the single failure exit. With the new post-publication push-gate behavior, that is no longer true. Please update it together with global-backpressure-design.md, distinguishing a pre-publication enqueue failure from a post-publication gate timeout.

  2. The value check does not prove that the explicit barrier waited for every producer. The consumer calls add_input(ext_X), which independently creates a tensormap dependency on the producer chain. It can therefore still observe SENTINEL even if the barrier explicit edges are incomplete. The deps.json assertions are what verify the barrier here; please describe the value check as an execution sanity check, or redesign the observable if runtime barrier enforcement is intended to be tested. This wording occurs in both the orchestration source and compute_golden comments for a2a3/a5.

  3. The barrier-edge check can hide duplicate emitted edges. Projecting raw edges into a set, and then into a dict keyed by producer ID, proves that all n unique predecessors are present but not that exactly n explicit edge records were emitted. Please retain the raw barrier explicit-edge list and assert its length is exactly n before building the producer-indexed map.

None of these changes the earlier conclusion on the core implementation. Also, the include-guard conversions to #pragma once are consistent with .claude/rules/codestyle.md rule 12 and should remain.

- Capture per-dependency flags in base and overflow dep_gen records
- Replay explicit edges with runtime WAIT/RETAIN dedup semantics
- Reject unknown recorded flag bits with a distinct replay error
- Preserve host ownership when a published DFX buffer hits a gate timeout
- Cover inline and chained records on a2a3 and a5 and update docs

Fixes hw-native-sys#1827
@zmnobug
zmnobug force-pushed the fix/issue-1827-dep-gen-explicit-flags branch from a1ce5e6 to 4725432 Compare August 29, 2026 03:06
@zmnobug

zmnobug commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

@doraemonmj Addressed the blocking items and the follow-up notes:

  • Updated both the source-level DeviceProfilerEngine contract and global-backpressure-design.md to distinguish pre-publication failures from a post-publication push-gate timeout after ownership transfer.
  • Assigned invalid explicit flags the distinct replay result -7 on A2/A3 and A5, and added a host-side regression test that verifies the trace is rejected without writing deps.json.
  • Reworded the A2/A3 and A5 value checks as execution sanity checks; the deps.json assertions are now explicitly responsible for validating the barrier.
  • Asserted the raw explicit-edge list has exactly n entries before indexing it, and derived producer order from tasks[] rather than TaskId numeric order.
  • Documented the differential gate's explicit-kind limitation beside STEP 1 and in the PR body, along with the shared DFX-engine scope and the full-rebuild requirement for the wire-layout change.
  • Corrected the schema to scope flags to tensormap_and_ringbuffer, and replaced the UT's bare flag literal with DEP_WAIT | DEP_RETAIN.

I kept invalid flags fail-fast rather than masking them. An unknown semantics byte makes the dependency artifact untrustworthy; emitting a plausible graph with altered flags would be more misleading than suppressing the artifact. The new return code keeps that failure distinguishable from JSON I/O errors.

I did not add push-gate timeout telemetry in this PR: the engine is shared across six DFX consumers, so choosing a logging/counter contract and ownership should be handled as a separate cross-module change. I also left out static_assert(sizeof(DepFlags) == 1) because the enum's fixed uint8_t underlying type already provides that guarantee.

Validation completed: all 125 non-hardware C++ tests passed, A2/A3 and A5 simulation dep_gen scenes passed, A2/A3 onboard dep_gen scenes passed, and the full PR pre-commit suite passed.

@ChaoZheng109
ChaoZheng109 merged commit 35f195b into hw-native-sys:main Aug 29, 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

None yet

Development

Successfully merging this pull request may close these issues.

[Code Health] dep_gen: DepGenRecord carries no per-dep DepFlags, so explicit add_dep_wait edges are mis-recorded in deps.json

3 participants