Skip to content

Add: investigation on the host worker dispatch latency budget - #1502

Merged
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:investigation-dispatch-latency
Jul 27, 2026
Merged

Add: investigation on the host worker dispatch latency budget#1502
ChaoWao merged 1 commit into
hw-native-sys:mainfrom
ChaoWao:investigation-dispatch-latency

Conversation

@ChaoWao

@ChaoWao ChaoWao commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Question

#1499 removed the parent's 50 us TASK_DONE sleep and took a single Worker.run() from 150 us to ~51 us. Two obvious follow-ups: is there another 3x in what remains, and is a blocking wakeup primitive (#1498) how to get it?

Measured. Both answers are no. Recording it so the next person does not re-derive it.

The headline number is a trap

bare mailbox round trip (fork + shm, no simpler runtime):    3.5 us
Worker.run(), one sub task:                                 53.8 us
  => runtime overhead per dispatch:                         50.3 us  (94%)

"94% of dispatch latency is our code, not the IPC" is the reading that invites a big optimisation push. It is an artifact of benchmarking a single task.

Sweeping tasks per run():

tasks per run() total per task
1 51.4 us 51.38 us
4 80.2 us 20.04 us
16 177.0 us 11.06 us
64 538.7 us 8.42 us
256 2113.0 us 8.25 us

43.3 us fixed per run(), 8.08 us marginal per task.

The cost is entering and leaving an orchestration, not dispatching work — and a production run submits a graph per run(), not one task. At 256 tasks the fixed part is 2% of the invocation. cProfile agrees on the shape: ~113 Python calls and ~15 threading lock/notify operations per no-op dispatch, spread thin with no hot frame to attack.

The marginal 8.08 us against a 3.5 us bare round trip is ~2.3x, and that 2.3x buys arg marshalling, callable-identity resolution and per-task error reporting the bare pool does not do.

It also settles #1498 against itself

A pipe wake measures 4.4 us one way on this box (8.85 us round trip, p99 9.62 us). Replacing the poll with a blocking wait therefore adds more than the entire 3.5 us IPC floor to every dispatch, in exchange for CPU — while the latency it was supposed to recover is not in the poll at all.

What survives from #1498 is narrower and has nothing to do with latency: a child spinning while nothing is outstanding holds a core for as long as its parent lives. That is a CPU question for narrow-core hosts, and it got smaller when #1495 made orphans exit with their parent.

Conditional, and the entry says so

Both conclusions have reconsideration triggers recorded, because neither is unconditional:

  • A workload that issues many small run() calls (deep L3/L4 hierarchies, per-token orchestration) stops amortizing the 43 us. Measure its tasks-per-run() first — above ~16 the fixed cost is already under 25%.
  • The CPU argument for a spin-then-block hybrid stays open, as long as it never reaches the blocking path while work is in flight.
  • Note the 8.08 us marginal is the Python-callable path; NPU tasks do not go through it.

Per .claude/rules/discipline.md §4 this lands in docs/investigations/ with an index entry, since the outcome is "we measured it and did not do it".

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@ChaoWao, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 63d41707-181d-45d4-a4f3-338b2c20363a

📥 Commits

Reviewing files that changed from the base of the PR and between 22f2d53 and 4cf2cee.

📒 Files selected for processing (2)
  • docs/investigations/2026-07-host-dispatch-latency-budget.md
  • docs/investigations/README.md

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.

After hw-native-sys#1499 took a single `Worker.run()` from 150 us to ~51 us, the
obvious follow-up was whether another factor of three was available in
what remained, and whether a blocking wakeup primitive (hw-native-sys#1498) was how to
get it. Measured, and the answer to both is no. Recording it so the next
person does not re-derive it.

A bare fork+shm mailbox round trip with no simpler runtime in it costs
3.5 us against `Worker.run()`'s 53.8 us, which reads as "94% of dispatch
latency is our code, not the IPC". That headline is an artifact of
benchmarking a single task. Sweeping tasks per `run()` from 1 to 256
splits it into **43.3 us fixed per run() and 8.08 us marginal per task**:
the cost is entering and leaving an orchestration, not dispatching work,
and a production run submits a graph per `run()` rather than one task. At
256 tasks the fixed part is 2% of the invocation.

The same numbers settle hw-native-sys#1498 against itself on latency grounds. A pipe
wake measures 4.4 us one way on this box, so replacing the poll with a
blocking wait adds more than the entire 3.5 us IPC floor to every
dispatch, in exchange for CPU, while the latency it was meant to recover
is not in the poll at all. What survives is narrower and unrelated: a
child spinning while nothing is outstanding holds a core for as long as
its parent lives — a CPU question for narrow-core hosts, already smaller
since hw-native-sys#1495 made orphans exit with their parent.

Also records the reconsideration triggers, since both conclusions are
conditional: a workload that issues many small `run()` calls stops
amortizing the fixed cost, and the CPU argument for a spin-then-block
hybrid remains open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ChaoWao
ChaoWao force-pushed the investigation-dispatch-latency branch from 9794ce1 to 4cf2cee Compare July 27, 2026 00:46
@ChaoWao
ChaoWao merged commit 9922afd into hw-native-sys:main Jul 27, 2026
14 checks passed
@ChaoWao
ChaoWao deleted the investigation-dispatch-latency branch July 27, 2026 01:34
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.

1 participant