Skip to content

test(cpu-ep): measure nested dispatch slot pressure in the task pool - #1377

Merged
justinchuby merged 1 commit into
mainfrom
squad/sebastian-nested-dispatch
Aug 19, 2026
Merged

justinchuby merged 1 commit into
mainfrom
squad/sebastian-nested-dispatch

Conversation

@justinchuby

Copy link
Copy Markdown
Owner

Closes the last open item from phases 16 and 18. Test-only — no kernel or
runtime code changes.

The gap

§36.8 and §38.7 both said the same thing: packed_nbits_output_row and
int8_row dispatch into the task pool from inside a par_chunks_mut for
m > 1 with parallel_columns set, so several Rayon workers can dispatch
concurrently. The pool's eight job slots bound the surplus by declining it back
to the caller to run inline — "correct and bounded", but that is an argument,
not a measurement, and it has not been measured.

The measurement

nested_dispatch_slot_pressure joins the existing task_runtime_latency
harness and reproduces the shape directly: an outer par_chunks_mut over rows,
an inner task_runtime::for_each_range in each, with the pool counters read
across it. #[ignore]d like its neighbour, since it is a measurement.

Four runs against a 16-lane pool:

outer dispatchers wall dispatches declined
1 ~1.9–2.4 ms 1 0
2 0.29–0.44 ms 2 0
4 0.45–0.74 ms 4 0
8 0.84–1.51 ms 8 0
16 1.35–1.84 ms 13–16 0 (3 once)

Slot exhaustion essentially does not happen. Three of the four runs declined
nothing at any width; the single run that declined 3 of 16 was a cold process's
first dispatch. The slots turn around faster than sixteen Rayon workers can
collide on them, so the inline fallback is a real safety net that is almost
never used.

Per-row cost also improves monotonically (175 → 99 µs/row from 2 to 16
dispatchers), so the nesting is not serialising. The ~2 ms one-dispatcher row is
pool construction on the process's first dispatch.

What it asserts

Timing is reported, not asserted. What is asserted is the property that
matters under nesting: every element covered exactly once (so the disjoint
start..end reconstruction never overlaps), and no task body panicking.

Validation

  • cargo test -p onnx-runtime-ep-cpu --release: 1447 passed, 0 failed, 17 ignored
  • cargo clippy -p onnx-runtime-ep-cpu --release --all-targets -- -D warnings: clean
  • cargo fmt --all -- --check: clean
  • all 8 Rust quality guard scripts: pass

Phases 16 and 18 both left the same item open: packed_nbits_output_row and
int8_row dispatch into the task pool from inside a par_chunks_mut for m > 1
with parallel_columns set, so several Rayon workers can dispatch concurrently.
The pool's eight job slots were said to bound the surplus by declining it back
to the caller, which is correct -- but it was an argument, not a number.

Adds nested_dispatch_slot_pressure to the existing task_runtime_latency
measurement harness, reproducing the shape directly and reading the pool
counters across it. Ignored by default like its neighbour, since it is a
measurement.

Result: slot exhaustion essentially does not happen. Three of four runs
declined nothing at any dispatcher count up to 16 against a 16-lane pool; the
single run that declined 3 of 16 was a cold process's first dispatch. Per-row
cost improves monotonically from 2 to 16 dispatchers, so nesting is not
serialising either.

The test asserts the property that actually matters under nesting -- every
element covered exactly once, no task body panicking -- and reports the timing.
Updates the benchmark ledger's open-items list accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby

Copy link
Copy Markdown
Owner Author

Merging with admin, consistent with the rest of this campaign: the Actions queue on this repo is saturated (every run sits queued, none reach in_progress), so the two required checks — Fast (Linux x86_64) and Rust quality — cannot be scheduled.

Both lanes were reproduced locally on the merge base (c3910e0a3, rustc 1.97.1 — byte-identical to CI's toolchain).

Fast (Linux x86_64) equivalent

cargo test -p onnx-runtime-ep-cpu --release
test result: ok. 1447 passed; 0 failed; 17 ignored

That is +1 against the pre-change baseline of 1446, and the added test is #[ignore]d, so it contributes no runtime to the lane. No non-test code is touched — the diff is one new test function, plus a docs update:

crates/onnx-runtime-ep-cpu/tests/task_runtime_latency.rs | 71 ++
docs/benchmarks/2026-08-15-cpu-ep-vs-ort-attention-moe.md | 34 +-

Rust quality equivalent

step result
cargo fmt --all -- --check clean
cargo clippy -p onnx-runtime-ep-cpu --release --all-targets -- -D warnings clean
check_publish_order.py OK
check_profile_table.py OK
check_platform_naming.py OK
check_dispatch_reachability.py OK
check_dispatch_manifest.py OK
check_feature_gate_coverage.py OK
verify_documented_env_vars.py OK
workspace_test_packages.py verify OK

--all-targets is what covers the new test file for clippy, and it is clean.

@justinchuby
justinchuby merged commit 06f6266 into main Aug 19, 2026
7 checks passed
@justinchuby
justinchuby deleted the squad/sebastian-nested-dispatch branch August 19, 2026 02:59
justinchuby pushed a commit that referenced this pull request Aug 19, 2026
The nesting probe added in #1377 reconstructs its row inside each task as

    std::slice::from_raw_parts_mut(base as *mut u64, NEST_INNER)

so every concurrent task materialises a `&mut [u64]` over the *entire* row and
then indexes `[start..end]`. The writes are disjoint but the references are not,
which is a data race under Stacked Borrows. The comment above it already claimed
"the reconstructed slice never overlaps another task's" — that was the intent,
not what the code did.

Reconstruct only `start..end`, which is the shape production already uses in
`parallel_output_rows_repeated`.

Miri, minimal reproduction of both shapes:

  whole-row (before): error: Undefined Behavior: Data race detected between
                      (1) non-atomic write on thread `unnamed-1` and
                      (2) retag write of type `[u64]` on thread `unnamed-2`
  own-range  (after): clean

Test-only and `#[ignore]`d, so nothing shipped was affected, and the Miri lane
covers `--lib task_runtime::` rather than this integration binary, which is why
neither caught it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby pushed a commit that referenced this pull request Aug 19, 2026
The nesting probe added in #1377 reconstructs its row inside each task as

    std::slice::from_raw_parts_mut(base as *mut u64, NEST_INNER)

so every concurrent task materialises a `&mut [u64]` over the *entire* row and
then indexes `[start..end]`. The writes are disjoint but the references are not,
which is a data race under Stacked Borrows. The comment above it already claimed
"the reconstructed slice never overlaps another task's" — that was the intent,
not what the code did.

Reconstruct only `start..end`, which is the shape production already uses in
`parallel_output_rows_repeated`.

Miri, minimal reproduction of both shapes:

  whole-row (before): error: Undefined Behavior: Data race detected between
                      (1) non-atomic write on thread `unnamed-1` and
                      (2) retag write of type `[u64]` on thread `unnamed-2`
  own-range  (after): clean

Test-only and `#[ignore]`d, so nothing shipped was affected, and the Miri lane
covers `--lib task_runtime::` rather than this integration binary, which is why
neither caught it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 19, 2026
… misreading

The claimed '111 of 915 dispatches declined' was never a number
`nested_dispatch_slot_pressure` could produce — it issues ~25-30 dispatches
in total. Re-measured over four runs it declines 0, 1, 2 and 6 of ~25-30,
always in the 16-dispatcher row and never below it, which brackets #1377's
zero rather than contradicting it.

The count tracks host load for the same reason §41.7 gives: when co-tenants
hold cores, workers free their slots later and more nested dispatches take
the inline fallback. That fallback is the designed behaviour.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby pushed a commit that referenced this pull request Aug 19, 2026
The nesting probe added in #1377 reconstructs its row inside each task as

    std::slice::from_raw_parts_mut(base as *mut u64, NEST_INNER)

so every concurrent task materialises a `&mut [u64]` over the *entire* row and
then indexes `[start..end]`. The writes are disjoint but the references are not,
which is a data race under Stacked Borrows. The comment above it already claimed
"the reconstructed slice never overlaps another task's" — that was the intent,
not what the code did.

Reconstruct only `start..end`, which is the shape production already uses in
`parallel_output_rows_repeated`.

Miri, minimal reproduction of both shapes:

  whole-row (before): error: Undefined Behavior: Data race detected between
                      (1) non-atomic write on thread `unnamed-1` and
                      (2) retag write of type `[u64]` on thread `unnamed-2`
  own-range  (after): clean

Test-only and `#[ignore]`d, so nothing shipped was affected, and the Miri lane
covers `--lib task_runtime::` rather than this integration binary, which is why
neither caught it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby pushed a commit that referenced this pull request Aug 19, 2026
The nesting probe added in #1377 reconstructs its row inside each task as

    std::slice::from_raw_parts_mut(base as *mut u64, NEST_INNER)

so every concurrent task materialises a `&mut [u64]` over the *entire* row and
then indexes `[start..end]`. The writes are disjoint but the references are not,
which is a data race under Stacked Borrows. The comment above it already claimed
"the reconstructed slice never overlaps another task's" — that was the intent,
not what the code did.

Reconstruct only `start..end`, which is the shape production already uses in
`parallel_output_rows_repeated`.

Miri, minimal reproduction of both shapes:

  whole-row (before): error: Undefined Behavior: Data race detected between
                      (1) non-atomic write on thread `unnamed-1` and
                      (2) retag write of type `[u64]` on thread `unnamed-2`
  own-range  (after): clean

Test-only and `#[ignore]`d, so nothing shipped was affected, and the Miri lane
covers `--lib task_runtime::` rather than this integration binary, which is why
neither caught it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby pushed a commit that referenced this pull request Aug 19, 2026
The nesting probe added in #1377 reconstructs its row inside each task as

    std::slice::from_raw_parts_mut(base as *mut u64, NEST_INNER)

so every concurrent task materialises a `&mut [u64]` over the *entire* row and
then indexes `[start..end]`. The writes are disjoint but the references are not,
which is a data race under Stacked Borrows. The comment above it already claimed
"the reconstructed slice never overlaps another task's" — that was the intent,
not what the code did.

Reconstruct only `start..end`, which is the shape production already uses in
`parallel_output_rows_repeated`.

Miri, minimal reproduction of both shapes:

  whole-row (before): error: Undefined Behavior: Data race detected between
                      (1) non-atomic write on thread `unnamed-1` and
                      (2) retag write of type `[u64]` on thread `unnamed-2`
  own-range  (after): clean

Test-only and `#[ignore]`d, so nothing shipped was affected, and the Miri lane
covers `--lib task_runtime::` rather than this integration binary, which is why
neither caught it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 19, 2026
The retag fix alone leaves the hole it came through wide open. Every
other step in the Miri lane is `--lib`, so nothing in
`crates/onnx-runtime-ep-cpu/tests/` is ever compiled by Miri, let alone
run. #1377's whole-row retag did not slip past a weak check; it slipped
past no check at all.

This adds the missing step, and the two flags it needs are not
cosmetic:

  * `-Zmiri-tree-borrows`. The test drives the pool through Rayon, and
    `crossbeam-epoch` violates Stacked Borrows in its own
    `internal.rs:562`. That is a third-party defect; the alternatives
    were to skip the test (which is how we got here) or to switch
    aliasing models. Tree Borrows still catches the defect this step
    exists for, which is the only reason the substitution is acceptable.

  * `-Zmiri-ignore-leaks`. The pool's workers are resident by design and
    outlive main, so Miri otherwise fails with "main thread terminated
    without waiting for all remaining threads" -- a property of the
    thing under test, not a bug in it.

`NEST_DISPATCHERS` and `NEST_INNER` shrink under `cfg(miri)` so the
interpreter sees the same *shape* -- several dispatchers, each row split
across several concurrent tasks -- at ~7s instead of the native
measurement scale. Native behaviour is unchanged.

Verified in both directions, which is the only thing that makes this
step worth having:

  fixed          test result: ok. 1 passed  (6.87s)
  #1377 retag    error: Undefined Behavior: Data race detected between
                 (1) non-atomic write on thread `nxrt-task-2` and
                 (2) retag read of type `[u64]` on thread `nxrt-task-0`

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby pushed a commit that referenced this pull request Aug 19, 2026
The nesting probe added in #1377 reconstructs its row inside each task as

    std::slice::from_raw_parts_mut(base as *mut u64, NEST_INNER)

so every concurrent task materialises a `&mut [u64]` over the *entire* row and
then indexes `[start..end]`. The writes are disjoint but the references are not,
which is a data race under Stacked Borrows. The comment above it already claimed
"the reconstructed slice never overlaps another task's" — that was the intent,
not what the code did.

Reconstruct only `start..end`, which is the shape production already uses in
`parallel_output_rows_repeated`.

Miri, minimal reproduction of both shapes:

  whole-row (before): error: Undefined Behavior: Data race detected between
                      (1) non-atomic write on thread `unnamed-1` and
                      (2) retag write of type `[u64]` on thread `unnamed-2`
  own-range  (after): clean

Test-only and `#[ignore]`d, so nothing shipped was affected, and the Miri lane
covers `--lib task_runtime::` rather than this integration binary, which is why
neither caught it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

🔴 Benchmark Regression Detected

Comparison of criterion micro-benchmarks: PR head vs merge-base, measured on the same runner in the same job (base first → PR second).

ℹ️ Absolute times are informational only — they vary with runner load. The % change column is the reliable signal because both sides ran under identical conditions.

Status Scenario Base PR Change
🔴 matmul/medium_generic_f32_threads=8/32x512x512 1.35 ms 1.91 ms +40.9%
🔴 block_quantized_matmul_cached_dense/mxfp4_uncached_dequant_each_call/1x1024x1024 579.73 µs 810.08 µs +39.7%
🔴 add/medium_f32_threads=1-internal/262144 26.22 µs 36.01 µs +37.3%
⚠️ matmul/medium_generic_f32_threads=1/32x512x512 2.37 ms 3.04 ms +28.2%
⚠️ grammar_masking/llguidance_compute_mask/32 90.11 µs 115.50 µs +28.2%
⚠️ matmul/large_generic_bf16_threads=8/32x1024x1024 1.33 ms 1.64 ms +23.6%
⚠️ logit_processing/seven_processor_chain_per_step 371.01 µs 456.94 µs +23.2%
⚠️ sampling_latency/top_k_per_token 61.58 µs 74.70 µs +21.3%
⚠️ qwen3_sampling_processors/top_k_top_p_full_sort_baseline 6.96 ms 8.32 ms +19.5%
⚠️ kv_cache/alloc_dealloc_pages 43.76 µs 52.11 µs +19.1%
✅ qwen3_sampling_processors/top_k_full_sort_baseline 2.55 ms 2.92 ms +14.7%
✅ matmul/small_generic_f16_threads=1/1x256x256 45.19 µs 50.61 µs +12.0%
✅ qwen3_sampling_processors/top_k_partial_selection 187.88 µs 209.50 µs +11.5%
✅ add/large_f16_threads=1-internal/4194304 1.91 ms 2.12 ms +10.7%
✅ matmul/medium_generic_f16_threads=1/32x512x512 44.66 µs 49.27 µs +10.3%
✅ matmul/large_generic_f32_threads=1/32x1024x1024 10.48 ms 11.55 ms +10.2%
✅ sampling_latency/min_p_per_token 258.97 µs 284.01 µs +9.7%
✅ matmul/small_generic_f32_threads=1/1x256x256 52.12 µs 56.59 µs +8.6%
✅ matmul/large_generic_bf16_threads=1/32x1024x1024 2.09 ms 2.27 ms +8.5%
✅ qwen3_sampling_processors/top_p_fast_after_top_k 581.56 µs 624.22 µs +7.3%
✅ qwen3_sampling_processors/top_k_top_p_fast 752.72 µs 805.85 µs +7.1%
✅ sampling_latency/top_p_per_token 460.42 µs 492.57 µs +7.0%
✅ matmul/large_generic_f16_threads=8/32x1024x1024 106.58 µs 113.92 µs +6.9%
✅ matmul/medium_generic_bf16_threads=8/32x512x512 558.76 µs 592.30 µs +6.0%
✅ sampling_latency/greedy_per_token 3.91 µs 4.15 µs +6.0%
✅ add/medium_bf16_threads=1-internal/262144 125.47 µs 132.52 µs +5.6%
✅ matmul/small_generic_bf16_threads=1/1x256x256 37.73 µs 38.80 µs +2.8%
✅ qwen3_sampling_processors/top_p_full_sort_after_top_k_baseline 4.62 ms 4.71 ms +2.0%
✅ add/medium_f16_threads=1-internal/262144 136.70 µs 136.84 µs +0.1%
✅ matmul/medium_generic_f16_threads=8/32x512x512 55.55 µs 55.42 µs -0.2%
✅ reduce_mean/medium_f32_threads=1-internal/65536 303.38 µs 298.51 µs -1.6%
✅ matmul/small_generic_bf16_threads=8/1x256x256 33.94 µs 33.33 µs -1.8%
✅ gather/small_f16_threads=1-internal/4096 641.6 ns 618.1 ns -3.7%
✅ add/large_f32_threads=1-internal/4194304 894.15 µs 855.38 µs -4.3%
✅ add/small_f32_threads=1-internal/1024 275.6 ns 256.9 ns -6.8%
✅ matmul/large_generic_f16_threads=1/32x1024x1024 92.96 µs 86.23 µs -7.2%
✅ gather/medium_f32_threads=1-internal/32768 4.83 µs 4.46 µs -7.8%
✅ matmul/large_generic_f32_threads=8/32x1024x1024 5.86 ms 5.40 ms -7.9%
✅ reduce_mean/small_f32_threads=1-internal/4096 18.83 µs 17.20 µs -8.7%
✅ reduce_mean/large_f32_threads=1-internal/262144 1.23 ms 1.11 ms -9.4%
✅ gather/large_bf16_threads=1-internal/131072 18.91 µs 17.06 µs -9.8%
✅ matmul/small_generic_f32_threads=8/1x256x256 65.63 µs 58.86 µs -10.3%
✅ tokenization/encode_tokens_per_second 457.98 µs 397.08 µs -13.3%
✅ add/small_f16_threads=1-internal/1024 650.3 ns 563.5 ns -13.4%
✅ matmul/small_generic_f16_threads=8/1x256x256 45.44 µs 39.09 µs -14.0%
✅ tokenization/decode_tokens_per_second 7.89 ms 6.76 ms -14.3%
✅ gather/small_bf16_threads=1-internal/4096 701.8 ns 600.0 ns -14.5%
✅ gather/medium_f16_threads=1-internal/32768 3.48 µs 2.97 µs -14.6%
🟢 gather/small_f32_threads=1-internal/4096 917.2 ns 775.5 ns -15.5%
🟢 matmul/medium_generic_bf16_threads=1/32x512x512 791.46 µs 661.72 µs -16.4%
🟢 add/small_bf16_threads=1-internal/1024 666.3 ns 555.2 ns -16.7%
🟢 gather/large_f16_threads=1-internal/131072 21.19 µs 17.13 µs -19.2%
🟢 block_quantized_matmul_cached_dense/mxfp4_preexpanded_dense_oncelock_like_proxy/1x1024x1024 120.37 µs 94.49 µs -21.5%
🟢 gather/medium_bf16_threads=1-internal/32768 3.64 µs 2.86 µs -21.6%
🟢 gather/large_f32_threads=1-internal/131072 57.94 µs 44.84 µs -22.6%
🟢 add/large_bf16_threads=1-internal/4194304 2.65 ms 2.01 ms -24.1%
🟢 block_quantized_matmul_cached_dense/mxfp4_cached_dense_repeated_call/1x1024x1024 144.27 µs 85.03 µs -41.1%
🟢 block_quantized_moe_cached_dense/mxfp4_uncached_expert_dequant_each_call/rows=1,H=256,I=256,E=4,top_k=1 679.66 µs 386.93 µs -43.1%
🟢 block_quantized_moe_cached_dense/mxfp4_cached_dense_expert_repeated_call/rows=1,H=256,I=256,E=4,top_k=1 127.66 µs 56.77 µs -55.5%

Visual flags: ⚠️ ≥ 15% slower, 🔴 ≥ 30% slower — calibrated against measured runner noise (~27% worst-case on multi-threaded matmul)

Host info
CPU: Apple M1 (Virtual)
Cores: 3
OS: Darwin 25.5.0 arm64
Rust: rustc 1.97.1 (8bab26f4f 2026-07-14)
Load avg: { 8.90 5.60 7.83 }
What this cannot catch
  • Regressions in code paths not covered by these benchmarks (e.g., end-to-end decode with a real model)
  • Sub-threshold regressions that compound over multiple PRs
  • Performance changes that only manifest under GPU execution
  • Latency changes in the ORT integration path (these benchmarks exercise the native Rust kernels)

justinchuby added a commit that referenced this pull request Aug 19, 2026
The concurrent-retag step failed with "the main thread terminated
without waiting for all remaining threads" even though the test it
runs passes. The two facts are connected: `-Zmiri-num-cpus=4` is what
makes the test do real work, and doing real work is what spawns the
process-lifetime pool workers that Miri then objects to at exit. The
pool parks its workers instead of joining them by design, so there is
nothing for the test to clean up.

At the default one CPU the test returns early on its `pool_width() < 2`
guard and spawns nothing, which is why the broad `task_runtime::` step
never hit this and why the failure only appeared once the lane started
being useful.

`-Zmiri-ignore-leaks` suppresses only that exit-time check. Verified it
does not blunt the lane: with the flag set, widening the retag back to
#1377's whole-row form still reports "Data race detected between (1)
retag write on thread `task_runtime::t` and (2) retag write of type
`[u64]` on thread `nxrt-task-2`". The falsifier is intact.

Matches the nested-dispatch integration step, which already needed the
same flag for the same reason.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 19, 2026
…e it (#1407)

## The UB

`nested_dispatch_slot_pressure` (which I added in #1377) reconstructs a
`&mut [u64]` over the **entire** row in every task and only then
narrows:

```rust
let row = unsafe { std::slice::from_raw_parts_mut(base as *mut u64, NEST_INNER) };
for slot in &mut row[start..end] { *slot += 1; }
```

The *stores* are disjoint. The *retags* are not. Under Stacked Borrows
the violation is the retag: each task pushes a `Unique` covering all of
`NEST_INNER`, popping the previous task's tag. Narrowing before the
retag fixes it, and is the shape `parallel_output_rows_repeated` already
uses in production.

**Diagnosis and fix are Pris's, from #1385.** This PR carries them
because that one is still a draft with auto-merge off and the UB is on
`main` today. If #1385 goes ready first, close this and I will rebase
the Miri half onto it — the two halves are independent.

## Why it survived, which is the part worth keeping

CI's Miri lane runs `-p onnx-runtime-ep-cpu --lib task_runtime::`.
**Integration tests under `tests/` are never Miri-checked at all.**
Fixing this one instance would have left that gap open for the next one,
so this also puts the shape under the lane as a lib test.

That was not sufficient either, and the first attempt is the useful
part: **the new test passed under Miri with the bad retag still in it.**

Miri reports `available_parallelism() == 1`, so `resolve_width` builds a
one-lane pool, every fan-out returns `Backend::Serial`, and no two tasks
ever run against each other. Probe under Miri:

```
pool_width=1 backend=Serial tasks=1
```

The lane has been type-checking the unsafe blocks in this module without
exercising the concurrency they exist for. The workflow comment claims
it "runs real threads under Stacked Borrows" — it runs one.

So I would have shipped a canary that passes for a reason unrelated to
its claim, which is precisely the class Pris named in #1385. Two fixes:

1. **The test asserts it actually fanned out** (`Backend::Native`), so
it fails loudly if it ever degenerates to one task instead of passing
silently.
2. **A dedicated lane step with `-Zmiri-num-cpus=4`**, scoped to that
single test rather than all of `task_runtime::` — multi-CPU Miri
multiplies the runtime of what is already the slowest step in the lane.
The targeted step costs **18s**.

## Verified in both directions

| retag shape | `-Zmiri-num-cpus=4` | result |
| --- | --- | --- |
| whole row (**#1377 as merged**) | yes | `error: Undefined Behavior:
Data race detected between (1) retag write on thread task_runtime::t and
(2) retag write of type [u64] on thread nxrt-task-0` |
| whole row | **no** (lane default) | **passes** |
| own range (**this PR**) | yes | passes |

The middle row is the finding: without the flag the falsifier does not
falsify.

## Scope

Test-only plus one workflow step. No production change —
`for_each_chunk_mut` was always correct. 30/30 `task_runtime::` lib
tests pass natively and under Miri; the repaired benchmark still runs
(`1 of 30 dispatches declined`); fmt and clippy clean.

Includes the one-line rustfmt repair of
`onnx-runtime-ep-cuda/src/runtime.rs` that `main` is currently failing
on (same as #1393/#1395/#1398 — whichever lands first makes the rest a
no-op).

🤖 Generated with [GitHub Copilot
CLI](https://github.com/features/copilot/cli)

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
justinchuby added a commit that referenced this pull request Aug 19, 2026
> **Process note, stated up front.** This defect entered `main` via
#1363, which I merged with an admin bypass while every required check
was still `queued`. That was wrong, I am not repeating it, and this PR
goes through the normal gates. Full disclosure of what I bypassed is in
the comment below.

`parallel_output_rows_dispatches_to_the_task_runtime` **fails on every
stock CI runner** and is live on `main` today.

## The defect

The test asserts the flat fan-out reaches the task runtime. But routing
reads `rayon::current_num_threads()`, and `flat_fan_out`'s *first* gate
is deliberately "stay on Rayon below `MIN_ROUTED_FAN_OUT_WIDTH` (16)".
Below that width the test asserts something policy never promised.

Measured on unrepaired `main`:

| `RAYON_NUM_THREADS` | 4 | 8 | 15 | 16 | 32 |
|---|---|---|---|---|---|
| result | **FAILED** | **FAILED** | **FAILED** | ok | ok |

`ubuntu-latest` is 4 vCPU. The whole `onnx-runtime-ep-cpu` lib suite on
unrepaired main at that width:

```
test result: FAILED. 1447 passed; 1 failed; 17 ignored
    kernels::matmul_nbits::tests::parallel_output_rows_dispatches_to_the_task_runtime
```

It passed for me only because this development host is 16C/32T — the
defect needs a *narrower* machine to appear, which is exactly the kind
of thing the CI I bypassed exists to find.

The existing `task_runtime::width() <= 1` guard does not cover it:
task-runtime width and Rayon width are different numbers, and on a
4-vCPU box the first is `> 1` while the second is `< 16`.

## The fix, and the trap in it

Install a Rayon pool of exactly the routing width so the decision under
test is host-independent.

My first attempt only wrapped the fan-out — and **still failed at
`rayon=1`**, because `output_chunk_len` reads the same Rayon width and
the test's *precondition* carried the identical defect. Moving the
precondition inside the pool too is what actually removes the host
dependency rather than relocating it.

Skipping below the threshold would have been the weaker fix: the test
would silently no-op on every real runner and guard nothing.

- passes at rayon = **1, 2, 4, 8, 15, 16, 32**
- **still falsifies** — forcing `PrefillFanOut::Wide` makes it fail, so
it is not vacuous
- adds the coverage assertion it should always have had (every output
row written exactly once)

## Scope

Test-only. No production behaviour changes.

Deliberately **not** included:
- the **aarch64 dead-code break** #1363 also shipped
(`WIDE_PREFILL_MACS`, `prefill_fan_out`, `prefill_column_grain` are dead
on a non-mlas ARM64 build, failing `-D warnings`) → **#1382** by @pris
was open first and is already armed. I had written the same three
`allow(dead_code)` restorations, verified they clear `cargo clippy
--target aarch64-unknown-linux-gnu -- -D warnings`, then dropped them
from this branch rather than ship a conflicting duplicate.
- the **Stacked-Borrows UB** in #1377's test → **#1385** by @pris, and
**#1407** (mine, which additionally closes the Miri lane gap that let it
through: `miri.yml` runs `--lib task_runtime::` only, so integration
tests under `tests/` are never Miri-checked).

## Validation

| check | result |
|---|---|
| `cargo fmt --all -- --check` | clean |
| `cargo test -p onnx-runtime-ep-cpu --lib` (host width) | 1447 passed,
0 failed |
| `cargo test -p onnx-runtime-ep-cpu --lib` at `RAYON_NUM_THREADS=4` |
**1447 passed, 0 failed** (main: 1 failed) |
| target test at rayon 1/2/4/8/15/16/32 | all pass |
| falsification probe (force `Wide`) | fails as required |

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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