Skip to content

Refactor: reduce the clock anchor to one offset reading - #2125

Closed
doraemonmj wants to merge 1 commit into
hw-native-sys:mainfrom
doraemonmj:refactor/clock-anchor-single-point
Closed

Refactor: reduce the clock anchor to one offset reading#2125
doraemonmj wants to merge 1 commit into
hw-native-sys:mainfrom
doraemonmj:refactor/clock-anchor-single-point

Conversation

@doraemonmj

@doraemonmj doraemonmj commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Map device timestamps onto the Host clock from a single paired reading. The
anchor supplies an offset and nothing else, because the scale is the platform's
counter frequency.

Follow-up to the L3 multi-rank swimlane (#2058 / #2099); closes no issue of its
own.

What the device records are missing is an origin, not a scale

A capture carries its two domains explicitly — device_clock_domain: device_syscnt_cycles against orchestrator_clock_domain: host_monotonic_ns
and clock_freq_hz is known a priori from the platform. So every interval
inside the device domain is already computable: a duration, a gap between two
tasks, the span of a scheduler phase are all cycles x 1e9 / clock_freq_hz and
need no calibration at all.

What the device side cannot supply is where any of that sits on the Host clock.
Its timestamps are syscnt counts against a device-boot epoch with no defined
relation to CLOCK_MONOTONIC. The records give spans without a start.

The same shape shows up one layer up, in the host trace. to_host_swimlane
keeps every clk=dev span out of Perfetto's visible traceEvents and parks it
in unalignedDeviceSpans, because "Chrome Trace JSON has one timestamp axis, so
raw clk=dev events cannot be rendered alongside host events without either a
false clock alignment or a huge empty interval". Those spans, device_wall
among them, carry lengths and nothing that says where to put them. An offset is
the missing half in both places.

That leaves exactly one unknown scalar, and metadata.timeline_relation: host_orchestration_precedes_device is what the format could assert without it:
an ordering, never a distance. An anchor measures that one scalar. A second
reading would be measuring the scale, which was never missing.

How accurate the stitch has to be

A swimlane is read to see where work sits relative to other work: which Rank
started late, how long a submit gap is, whether a device segment overlaps the
host orchestration that queued it. Those features are milliseconds wide — the
device segments in the captures below run 0.1 to 10.7 ms, the submit gaps 1.5 to
3.0 ms. Placing a block within tens of microseconds is comfortably inside what
the picture needs, and the error is declared rather than assumed: each block
carries max_uncertainty_ns (14.0-28.5 us in these runs) and a cross-domain
comparison costs the sum of two (49.3 us for four Ranks).

This is the reason the residual correction is not worth its cost. It is not
merely that a second reading is noisy — it is that the quantity it corrects,
1.8-8.1 us, sits below the bound the consumer already tolerates.

Why one reading is enough in practice

The second reading only contributed a residual the calibration interval was too
short to measure. Across four Ranks of a TestAllreduceTwophaseP4 capture the
implied frequency swung between -813 and +2105 ppm with inconsistent sign,
while the correction it produced stayed an order of magnitude below the
sampling noise it was computed from (30-58 us).

host_ns(cycles) = anchor.host_mid_ns + (cycles - anchor.device_cycles) x 1e9 / clock_freq_hz

Supplying an offset pins the two clock domains rather than bounding an interval
the records must fall inside, so a timestamp either side of it maps by the same
arithmetic. That removes the placement constraint that made each runtime open
its interval at a different point, and both now read at the launch boundary.

What changes

  • Anchor the clocks for any enabled chip swimlane instead of level 4 alone. A
    capture below that level is placed on the Host timeline too, rather than on
    its own relative one, so the seam a fail-soft conversion used to leave
    unmeasured is gone for single-card captures as well. Cross-Rank merging still
    requires level 4, now because _validate_l3_rank_data asks for the
    orchestrator phase records only that level carries.
  • Drop the closing reading and the coverage check it fed: with the anchor a pin
    there is no interval left for a timestamp to fall outside of.
  • CallConfig::capture_clock_anchors stays on the wire as the ChipWorker
    marker it already was, but no longer gates anything on its own.

Verification

pytest tests/ut/py — 2121 passed, 24 skipped.

Onboard a2a3, both runtimes at level 4:

  • TestAllreduceTwophaseP4, 4 cards. All four Ranks calibrated;
    global_origin_ns equals the minimum per-Rank origin, so the anchor supplies
    the offset and the earliest event still sets the origin. The four Ranks end
    within an 11 us window (7798-7809 us) — a collective rendezvous is what a
    correct alignment should show, and the agreement sits inside the declared
    49.3 us cross-Rank bound. No negative timestamps.
  • TestBatchPagedAttentionHostBuildGraph, 1 card. layout: clock_aligned,
    no unaligned device spans, 4096 AICore tasks all after the anchor. The host
    orchestration lane (0-3362 us) and the device lanes (38208-40831 us) sit on
    one axis; the fail-soft layout would have placed the device segment at
    307.9-345.7 us, 36 ms off, with the error unmeasurable by construction.
  • Recomputing the formula by hand for each Rank reproduces the emitted
    timestamps exactly.

New unit tests cover the mapping either side of the anchor and the toward-zero
truncation that keeps the two directions symmetric.

Map device timestamps onto the Host clock from a single paired reading.
The anchor supplies an offset and nothing else, because the scale is the
platform's counter frequency. A second reading only contributed a
residual the calibration interval was too short to measure: across four
Ranks the implied frequency swung between -813 and +2105 ppm with
inconsistent sign, while the correction it produced stayed an order of
magnitude below the sampling noise it was computed from.

One reading pins the two clock domains rather than bounding an interval
the records must fall inside, so a timestamp either side of it maps by
the same arithmetic. That removes the placement constraint that made
each runtime open its interval at a different point, and both now read
at the launch boundary.

Anchor the clocks for any enabled chip swimlane instead of level 4
alone. A capture below that level is therefore placed on the Host
timeline too, rather than on its own relative one, and the seam a
fail-soft conversion used to leave unmeasured is gone for single-card
captures as well. Cross-Rank merging still requires level 4, now because
_validate_l3_rank_data asks for the orchestrator phase records only that
level carries.

Drop the closing reading and the coverage check it fed: with the anchor
a pin there is no interval left for a timestamp to fall outside of.
CallConfig::capture_clock_anchors stays on the wire as the ChipWorker
marker it already was, but no longer gates anything on its own.

Cover the mapping either side of the anchor, including the toward-zero
truncation that keeps the two directions symmetric.
@coderabbitai

coderabbitai Bot commented Sep 4, 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: Team

Run ID: 50fc74fd-d092-45ae-a0a1-4bdcd109bb2f

📥 Commits

Reviewing files that changed from the base of the PR and between 4e4d3a4 and a823b8a.

📒 Files selected for processing (21)
  • docs/dfx/chip-swimlane-profiling.md
  • docs/task-flow.md
  • docs/user/reference/python-api.md
  • simpler_setup/tools/clock_correlation.py
  • simpler_setup/tools/swimlane_converter.py
  • src/a2a3/platform/onboard/host/device_runner.cpp
  • src/a2a3/platform/sim/host/device_runner.cpp
  • src/a5/platform/onboard/host/device_runner.cpp
  • src/a5/platform/sim/host/device_runner.cpp
  • src/common/platform/include/host/clock_correlation.h
  • src/common/platform/onboard/host/c_api_shared.cpp
  • src/common/platform/onboard/host/device_runner_base.cpp
  • src/common/platform/onboard/host/device_runner_base.h
  • src/common/platform/shared/host/chip_swimlane_collector.cpp
  • src/common/platform/shared/host/clock_correlation.cpp
  • src/common/platform/sim/host/c_api_shared.cpp
  • src/common/platform/sim/host/device_runner_base.cpp
  • src/common/platform/sim/host/device_runner_base.h
  • src/common/task_interface/call_config.h
  • tests/ut/py/test_clock_correlation.py
  • tests/ut/py/test_swimlane_converter.py

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


📝 Walkthrough

Walkthrough

Chip swimlane profiling now captures one HostOffset reading when enabled. Python alignment maps device cycles using that offset and nominal frequency. Runtime finalization APIs no longer use device-completion flags. Documentation and tests reflect the new behavior.

Changes

Clock correlation and swimlane profiling

Layer / File(s) Summary
Single HostOffset anchor contract
src/common/platform/include/host/clock_correlation.h, src/common/platform/shared/host/clock_correlation.cpp, src/common/platform/shared/host/chip_swimlane_collector.cpp, docs/dfx/chip-swimlane-profiling.md
Clock correlation uses one HostOffset sample. The serialized name remains pre_host_orchestration. Lower swimlane levels collect anchors, while cross-Rank merging still requires level 4 orchestrator records.
Runtime anchor capture lifecycle
src/common/platform/onboard/host/*, src/common/platform/sim/host/*, src/a2a3/platform/onboard/host/device_runner.cpp, src/a5/platform/onboard/host/device_runner.cpp, src/common/task_interface/call_config.h, docs/task-flow.md, docs/user/reference/python-api.md
Enabled chip swimlanes start clock correlation at launch. Runtimes capture a HostOffset sample and finalize sessions without device-completion parameters.
Single-anchor alignment and conversion
simpler_setup/tools/clock_correlation.py, simpler_setup/tools/swimlane_converter.py
ClockAlignment stores one anchor. build_clock_alignment selects the narrowest valid pre-host-orchestration sample and maps timestamps with nominal frequency.
Alignment and converter validation
tests/ut/py/test_clock_correlation.py, tests/ut/py/test_swimlane_converter.py
Tests cover single-anchor mapping, symmetric extrapolation, truncation, uncertainty, failure reasons, metadata, and updated converted timestamps.

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

Merge Risk: ⚪ Minimal · up to a823b

The clock-anchor refactor is ready to merge; no actionable current behavior or compatibility risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant DeviceRunnerBase
  participant ClockCorrelationSession
  participant SwimlaneConverter
  participant ClockAlignment
  DeviceRunnerBase->>ClockCorrelationSession: begin at launch boundary
  ClockCorrelationSession->>ClockCorrelationSession: capture HostOffset sample
  SwimlaneConverter->>ClockAlignment: build alignment from clock anchors
  ClockAlignment->>ClockAlignment: select valid anchor
  SwimlaneConverter->>ClockAlignment: map device cycles to Host time
Loading

Poem

A rabbit found one anchor bright
It pinned two clocks in morning light
No closing mark was needed there
Just offset, frequency, and care
The swimlanes hopped in time tonight

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.79% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 18 files. (3 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 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.
Description check ✅ Passed The description clearly explains the single-offset clock anchoring refactor, related behavior changes, and verification results.
Title check ✅ Passed The title concisely and accurately summarizes the main change: reducing clock correlation to one offset reading.
Full details: Docstring Coverage

Explanation

Docstring coverage is 13.79% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 58 functions across 18 files. (3 skipped: 3 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.

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