L2 swimlane: defer per-task wmb() to buffer publication¶
Date: 2026-06-01 (investigated), 2026-07-08 (landed)
Verdict: landed — the per-task wmb() in l2_swimlane_aicpu_complete_task
is removed. Correctness verified onboard a2a3. The speed benefit is below the
hardware noise floor; the change is kept for uniformity, not measured speed.
Change¶
l2_swimlane_aicpu_complete_task used to issue one wmb() (ARM64 dsb st) per
AICPU task record, right after l2_swimlane_buf->count = new_count. It was the
only record-writer on the AICPU collector still doing a per-record barrier; the
phase-record and aicore_rotate paths already defer their barrier to
publication. The per-record wmb() is removed; nothing is added.
Correctness¶
The host reads an AicpuTask buffer only after the AICPU publishes it to the
per-thread ready queue. Both publication paths — full-buffer rotation
(switch_task_buffer → L2SwimlaneTaskEngine::switch_buffer) and end-of-run
flush (l2_swimlane_aicpu_flush) — go through
L2SwimlaneTaskEngine::enqueue_ready (profiler_device_engine.h), which issues
wmb() (dsb st) before advancing queue_tails. A dsb st drains every prior
store on that thread, so this single barrier covers the whole buffer's record +
count stores before the tail the host polls (with rmb()).
The AicpuTask buffer has one producer (the scheduler thread that owns the
core) and one consumer (the host, post-enqueue). The AICore never reads it — it
owns a separate pool keyed on its own current_buf_seq. So there is no
concurrent observer between complete_task returning and publication.
Because the collector was unified into
src/common/platform/shared/aicpu/l2_swimlane_collector_aicpu.cpp (#1262) with
publication funneled through enqueue_ready's barrier, the landed change is a
single-line removal — unlike the 2026-06 exploration, which predated that engine
and had to add wmb() at each publication site.
Onboard validation¶
a2a3 via task-submit:
- 2026-07-08:
tests/st/a2a3/tensormap_and_ringbuffer/dfx/l2_swimlaneat@scene_test(level=2)(the level wherecomplete_taskruns and writes the AICPU task record) — 2 passed. The DFX suite validates record-count reconciliation (no silent loss), the failure mode a missing barrier produces. - 2026-06-01:
l2_swimlaneSTs (--enable-l2-swimlane --enable-dep-gen) andpaged_attention_unrollCase1 at level 4 — pass.
Measured benefit: below noise¶
paged_attention_unroll Case1 at swimlane level 4, 16 iterations each
direction, sum of per-thread sched_cost from the device debug log
(log_l2_perf_summary Thread N: sched_cost=X.YYYus). 4 scheduler threads,
1024 complete_task invocations per launch (~256 per thread).
| Condition | mean sum-sched_cost (16 iter) | median |
|---|---|---|
| Baseline (per-task wmb) | 3574.2 µs | ~3564 µs |
| Patch (wmb at publication) | 3591.0 µs | ~3568 µs |
Per-iteration standard deviation is ~30 µs, with 100–250 µs cold-cache / contention outliers on iterations 1 and 11. The difference between conditions is within the noise envelope and flips sign under outlier trimming. Back-of-envelope expected saving: 256 wmbs × ~30 ns ≈ 7.7 µs/thread, ≈ 30 µs across 4 threads — below the per-iter variance, so the benchmark cannot discriminate it.
The AICPU per-record dsb st is the cheaper of swimlane's two per-record
barriers. The load-bearing cost is the AICore-side per-record record write-back,
which is not removable: AICore cache is non-coherent with the consumer, so
each AICore record must be pushed out before its buffer is handed over.
Risk carried forward¶
A sibling barrier change (hoisting wmb across distinct tasks in one pop) caused
a spmd_sync_start_stress 507018 drain-barrier hang — see
2026-06-cross-task-batched-publish.md.
This deferral is narrower (a single per-record barrier whose only observer
publishes through its own barrier) and is onboard-validated above.
To measure a real signal (future)¶
If a profile ever needs to attribute this barrier's cost, size the workload so the signal clears the noise floor:
- ≥10k AICPU complete events per launch (short, sub-microsecond tasks, where the
dsb ststops being amortized by record-write store-buffer latency). - A single
--rounds Ninvocation rather than N separate launches, so the per-launch outlier tail does not dominate the variance. - Extract
sched_complete_cyclefromSIMPLER_SCHED_PROFILINGbuilds — the bucket the barrier contributes to — not justsched_cost.
References¶
- Landed in PR #1301.
aicore_rotateandacquire_phase_slotuse the same publication-point barrier shape, insrc/common/platform/shared/aicpu/l2_swimlane_collector_aicpu.cpp.- DFX path prioritizes accuracy and simplicity over performance; per-record barrier micro-optimizations on the swimlane path are otherwise not pursued.