Skip to content

Fix: make profiler host-ready hand-off lossless - #1658

Merged
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
Little-oil:fix/dep-gen-host-ready-wait
Aug 5, 2026
Merged

ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
Little-oil:fix/dep-gen-host-ready-wait

Conversation

@Little-oil

@Little-oil Little-oil commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

问题发生在 management thread 向 collector 的 Host ready ring 交接处。旧实现遇到满队列时会 retire 尚未收集的 buffer,导致 dep-gen reconciliation 不完整;新实现保留 buffer 所有权,等待 collector 腾出空位后重试,从而保证交接不丢数据。

修改前

flowchart LR
    A["management: process_entry()"] --> B["push_to_ready()"]
    B --> C{"Host ready ring 满?"}
    C -->|否| D["Host ready ring"]
    D --> E["collector 收集"]
    E --> F["reconciliation"]
    F --> G["生成 deps.json"]
    C -->|是| H["retire buffer"]
    H --> I["reconciliation 不完整"]
    I --> J["不生成 deps.json"]
Loading

修改后

flowchart LR
    A["management: process_entry()"] --> B["wait_push_to_ready()"]
    B --> C{"Host ready ring 满?"}
    C -->|否| D["Host ready ring"]
    C -->|是| H["保留 buffer 并等待"]
    H -->|"collector pop 后唤醒并重试"| B
    D --> E["collector 收集"]
    E --> F["reconciliation"]
    F --> G["生成 deps.json"]
Loading

问题

management thread 从 device ready queue 中 pop 并确认一个 entry 后,process_entry() 持有该 buffer 唯一仍可访问的副本。旧实现使用非阻塞的 push_to_ready();如果 management 到 collector 之间的 Host ready ring 已满,这个 buffer 会在 on_buffer_collected() 收集之前被 retire。

对于 dep-gen,这会导致采集计数无法完成 reconciliation,并在结束阶段阻止 deps.json 生成。

具体修改

Host ready ring 位于 src/common/platform/include/host/buffer_pool_manager.h:

  • BufferPoolManager::ready_shards_ 是 management 到 collector 的全部 Host ready shard 数组。
  • 每个 shard 真正的 ring 是 ready_shards_[normalize_shard(shard_index)].queue。
  • .queue 的类型是 ReadyRing,即 SpscRing<ReadyBufferInfo, kReadyQueueCapacity>。
  • 对 dep-gen,kReadyQueueCapacity 来自 DepGenModule::kReadyQueueSize,当前为 PLATFORM_DEP_GEN_READYQUEUE_SIZE = 4 * 2 = 8。
  • 本 PR 不扩大这个 ring,而是在 ring 满时等待。

1. 增加 Host ready ring 满状态查询

位置:SpscRing::full(),文件 buffer_pool_manager.h。

  • 为什么改:阻塞等待的 predicate 需要在不修改队列的情况下判断 collector 是否已经腾出空位。
  • 怎么改:读取 SPSC ring 的 head_ 和 tail_;当 head - tail >= Capacity 时返回满。

2. 增加阻塞式 publish 和双向唤醒

位置:BufferPoolManager::wait_push_to_ready()、try_pop_ready() 和 wait_pop_ready(),文件 buffer_pool_manager.h。

  • 为什么改:原来的 push_to_ready() 在 ring 满时立即返回 false,无法保证已经从 device ready queue 取出的 buffer 最终交给 collector。
  • 怎么改:新增 wait_push_to_ready()。它循环调用 shard.queue.push(info);失败时保留 info 的所有权,并在 shard.cv 上等待,直到 ring 状态变化或 queue.full() 变为 false,然后重试。
  • 将原来的 notify_epoch 改为 state_epoch。每次成功 push 或 pop 都执行 state_epoch++ 和 notify_one(),因此 producer 与 consumer 都能唤醒对方。
  • try_pop_ready() 在 collector 成功 pop 后通知等待空间的 producer;wait_pop_ready() 统一复用 try_pop_ready(),避免遗漏 pop 侧通知。
  • 保留 100 ms 定时重试,作为 condition variable 通知丢失时的兜底。

3. 删除 process_entry() 的丢弃路径

位置:ProfilerAlgorithms::process_entry(),文件 src/common/platform/include/host/profiler_base.h。

修改前:

if (!mgr.push_to_ready(site.info, q)) {
    (void)mgr.retire_unqueued_buffer(site.kind, site.info.dev_buffer_ptr, q);
}

修改后:

mgr.wait_push_to_ready(site.info, q);
  • 为什么改:执行到这里时 device ready entry 已经被 try_pop_aicpu_entry() pop 并确认。此时 site.info 是这个 buffer 唯一仍可到达的所有权路径;retire 会让 collector 永远看不到它。
  • 怎么改:management thread 必须等到 Host ready ring 有空间并成功 publish 后,process_entry() 才返回。

4. collector idle timeout 后继续存活

位置:ProfilerBase::poll_and_collect_loop(),文件 profiler_base.h。

  • 为什么改:如果 collector 因 idle timeout 提前 break,后续 final drain 可能在满 Host ready ring 上永久等待,因为已经没有 consumer 负责 pop。
  • 怎么改:idle timeout 仍然记录错误,但不再退出循环;它重置本轮 timeout 状态并继续等待。collector 只在 execution_complete_ == true 且最终 ready queue 排空后退出。

5. 增加回归测试

  • WaitPushReadyWakesOnConsumerAndPreservesFifo:填满 ready_shards_[0].queue,验证 producer 阻塞;collector pop 后 producer 被唤醒,并验证 FIFO 顺序。
  • ProcessEntryWaitsForReadySpaceInsteadOfRetiringBuffer:验证 process_entry() 在满 ring 上等待,新 buffer 最终进入 ready ring,没有进入 done queue,也没有被 release。
  • CollectorStaysAliveAfterArmedIdleTimeout:验证 collector 报告 idle timeout 后仍能收集晚到的 buffer。
  • 同步更新 docs/dfx/profiling-framework.md 中的所有权说明和 end-to-end 数据流。

为什么选择等待

已关闭的 #1594 使用扩大容量的方案,但这只能推迟溢出发生的时机。Host ring 的容量并不是 final flush 突发量的正确性上界。

保留 buffer 所有权并施加 backpressure,可以确保 device-ready 到 host-ready 的交接不会丢失数据;只要 collector 最终能够继续消费,任意有限突发都可以被完整处理。因此,本 PR 取代 #1594 中基于扩大容量的方案。

测试

  • pre-commit run --from-ref origin/main --to-ref HEAD:通过。
  • 完成 C++ 构建,并运行 ctest --test-dir tests/ut/cpp/build -LE requires_hardware --output-on-failure:rebase 到最新 origin/main 后 74/74 通过。
  • python -m simpler_setup.build_runtimes --platforms a2a3:在最终无冲突 rebase 前,host_build_graph 和 tensormap_and_ringbuffer 均构建通过。
  • A2A3 四卡 PyPTO-lib DeepSeek L3 decode:任务 task_20260803_021153_200471219670,设备 0,2,4,6,EP=4,TP=4,结果为 [RUN] PASS。
    • 使用了 --enable-l2-swimlane,没有使用 --enable-dep-gen 0。
    • 按预期生成 4/4 个非空且可解析的 rank0..3/d0/deps.json,合计包含 26,440 个 task 和 92,067 条 edge。
    • 四个 rank 均成功生成 downstream merged swimlane。
    • 观察到 4 次 backpressure trigger 和对应的 4 次 release;未发现 silent_loss、队列满导致的 retire、record count mismatch、reconciliation failure 或 fatal error。
    • 本次运行验证 execution 和 DFX 产物完整性;未配置 golden numerical data。

硬件日志没有直接记录是否进入 wait_push_to_ready();该阻塞分支由 C++ 回归测试直接覆盖。

Retain ownership after a device-ready entry is popped and wait for collector capacity instead of retiring the buffer when the host-ready ring is full. Keep collectors alive until execution completes so final-drain producers always have a consumer, and cover the blocking hand-off, FIFO order, and collector liveness with regression tests.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The ready-queue handoff now blocks when the host queue is full. Queue state changes notify waiting producers and consumers. Collectors retain buffers through backpressure and continue after idle-timeout diagnostics until execution completes. Tests and profiling documentation cover these behaviors.

Changes

Ready-queue backpressure and collector lifetime

Layer / File(s) Summary
Ready-queue synchronization
src/common/platform/include/host/buffer_pool_manager.h, tests/ut/cpp/common/test_buffer_pool_manager.cpp, docs/dfx/profiling-framework.md
SpscRing reports full capacity. Ready-queue pushes wait with timed retries, and successful pushes and pops update state_epoch and notify waiters. Tests verify blocking behavior and FIFO order.
Collector handoff and timeout lifecycle
src/common/platform/include/host/profiler_base.h, tests/ut/cpp/common/test_profiler_base.cpp, docs/dfx/profiling-framework.md
process_entry retains buffers until ready-queue space is available. Collector idle timeouts log diagnostics and do not stop collection before execution completes. Tests cover delayed collection after a zero-second timeout and blocked processing.
Estimated code review effort: 3 (Moderate) ~20 minutes

Possibly related PRs

Poem

A rabbit guards the ready queue,
While buffers wait their turn.
The collector stays awake,
Though idle clocks may warn.
FIFO hops from ring to ring—
No tail buffer left to mourn.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies issue #1594 by preserving tail buffers and enabling complete dependency graphs through backpressure and collector liveness.
Out of Scope Changes check ✅ Passed The code, documentation, and tests directly support the lossless host-ready hand-off objective in issue #1594.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly summarizes the main change: making the profiler host-ready hand-off lossless.
Description check ✅ Passed The description directly explains the lossless hand-off changes, backpressure behavior, collector liveness, tests, and validation results.

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.

🧹 Nitpick comments (2)
src/common/platform/include/host/buffer_pool_manager.h (1)

611-663: 🩺 Stability & Availability | 🔵 Trivial

Blocking hand-off design is correct; no lost-wakeup or cross-wake ambiguity found.

The epoch capture happens after push()/try_pop_ready() fails, but the predicate always re-checks !shard.queue.full() / !shard.queue.empty() directly, so even if the counterpart already advanced state_epoch before the epoch snapshot, the direct state check still unblocks the wait. Since full() and empty() are mutually exclusive for Capacity > 0, a producer wait and a consumer wait can never both be pending on the same cv at once, so notify_one() cannot wake the wrong side.

One trade-off worth flagging for operational awareness: wait_push_to_ready has no upper bound. If Derived::on_buffer_collected (invoked synchronously from the collector's consume()) ever stalls — for example on blocked I/O — this loop blocks forever, and stop()'s join on mgmt_drain_threads_ would hang indefinitely since nothing here checks mgmt_running_ as an escape path. This appears to be an accepted trade-off given the PR's explicit goal of a lossless hand-off for any finite burst the collector can eventually drain, so no code change is requested. Consider whether the collector-side idle-timeout log (in profiler_base.h) is wired to any operational alerting, since it is the only visible signal of a drain thread being stuck this way in production.

🤖 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/include/host/buffer_pool_manager.h` around lines 611 -
663, No code change is requested. Retain the current blocking behavior in
wait_push_to_ready, including its lossless hand-off semantics; only consider
operationally wiring the collector idle-timeout signal from profiler_base.h to
alerting.
src/common/platform/include/host/profiler_base.h (1)

1226-1235: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Downgrade the idle-timeout log to LOG_WARN.

This timeout is diagnosed as a hang warning where the collector stays alive, but the call still routes through LOG_ERROR. Since LOG_WARN is available in this codebase, use it here to match the non-fatal semantics and avoid log-monitoring alert noise for a recovered condition.

🤖 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/include/host/profiler_base.h` around lines 1226 - 1235,
Change the idle-timeout log in the collector monitoring block from LOG_ERROR to
LOG_WARN while preserving its message, arguments, and one-time detector reset
behavior.
🤖 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.

Nitpick comments:
In `@src/common/platform/include/host/buffer_pool_manager.h`:
- Around line 611-663: No code change is requested. Retain the current blocking
behavior in wait_push_to_ready, including its lossless hand-off semantics; only
consider operationally wiring the collector idle-timeout signal from
profiler_base.h to alerting.

In `@src/common/platform/include/host/profiler_base.h`:
- Around line 1226-1235: Change the idle-timeout log in the collector monitoring
block from LOG_ERROR to LOG_WARN while preserving its message, arguments, and
one-time detector reset behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 68e19f65-00dc-4365-a095-41ca27267b48

📥 Commits

Reviewing files that changed from the base of the PR and between c975b61 and 3f27f7f.

📒 Files selected for processing (5)
  • docs/dfx/profiling-framework.md
  • src/common/platform/include/host/buffer_pool_manager.h
  • src/common/platform/include/host/profiler_base.h
  • tests/ut/cpp/common/test_buffer_pool_manager.cpp
  • tests/ut/cpp/common/test_profiler_base.cpp

@ChaoZheng109
ChaoZheng109 merged commit 45ab867 into hw-native-sys:main Aug 5, 2026
18 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.

2 participants