Skip to content

cost: extrapolate WidthCosts linearly above the largest anchor; variable anchors (#6 stage 1); threadteam 0.1.1 - #7

Merged
shinaoka merged 4 commits into
mainfrom
feat/6-width-cost-extrapolation
Oct 29, 2026
Merged

shinaoka merged 4 commits into
mainfrom
feat/6-width-cost-extrapolation

Conversation

@shinaoka

Copy link
Copy Markdown
Member

Part of #6 (stage 1: proposals 1 and 2). Stage 2 (the broccoli Zen2 row from tensor4all/faer#91) and the faer migration come in separate PRs.

Summary

  • Linear extrapolation above the largest anchor. Above w_m, WidthCosts::at(w) is cost(w_m) * w / w_m (saturating) instead of the flat cost(6). A region that does not pay at 6 therefore does not pay at 64.
    • Widths up to the largest anchor keep the existing model: linear interpolation, and the first value below the first anchor.
    • Every existing row is bit-identical at widths 1 to 6, pinned by a test.
  • Variable anchors. WidthCosts::from_anchors(&[(width, Duration)]) -> Option<Self> takes up to WidthCosts::MAX_ANCHORS (12) strictly increasing anchors with widths from 2 to u16::MAX.
    • It is const (usable in a const item), allocation-free, and never panics.
    • It returns None for empty input, too many anchors, a width below 2, or widths that are not strictly increasing.
    • new([2, 4, 6]) and flat keep their signatures.
  • Storage. WidthCosts now stores its anchors (widths u16, ns u64, length) and interpolates at lookup, with a 64-bit fast path that falls back to 128 bits only when a product overflows.
    • A 64-entry table would have made DispatchCosts about 1.5 KB. TeamExec and faer copy it by value.
  • AVX-512 team row gains its measured width-8 anchor (0.98 us), from the same post-fix run as its 2/4/6 values (docs/design/thresholds.md). Without it the extrapolation would charge 1.2 us at width 8 on the measured 8-thread host.
    • The in-pool and outside width-8 numbers come from the pre-fix run and are not used.
  • Docs. WidthCosts, for_class, README, docs/design/placement.md and docs/design/thresholds.md now describe the new contract. The thresholds doc keeps one historical note on why the model was flat before.
  • threadteam 0.1.1 for the approved patch release. The decisions above width 6 change by design, no API is removed, and the constructor is additive.

Done-when checks (from #6)

  • A flat 1 us row gives 1, 2, 4 and 8 us at widths 6, 12, 24 and 48. Above w_m the row is non-decreasing and at least linear, checked on five rows.
  • A region that does not pay at 6 does not pay at 64.
  • Interior width: width_for_by(24 us, 1, budget 64) picks 12 on the 1-us row, and a large enough estimate picks 64.
  • Anchors at 2, 4, 8, 16, 32 and 64 interpolate and extrapolate as specified.
  • from_anchors rejects invalid input and works in a const.
  • Equality ignores unused capacity, and a Duration::MAX row never pays.
  • The 128-bit fallback gives the same values as the 64-bit path, and falling interpolation truncates toward zero.
  • tests/dispatch_cost.rs now expects the extrapolated values instead of the flat ones. tests/alloc_free.rs and tests/team_alloc_free.rs pass.

Lookup cost

Measured with benches/cost_lookup.rs, extended in this PR. Same bench file on base f29c8ce and on this branch, taskset -c 56-59 on primerose, best of 9 x 2M calls, ns per call:

base this PR
at (widths 2..8) 0.89 4.12
pays 1.97 7.84
width_for budget 8 25.9 61.8
width_for budget 64 222 522
min_cost 6.7 4.8
copy DispatchCosts 4.5 11.0
Exec::dispatch_cost (team) 3.3 6.3
Exec::width_for (budget 8) 25.5 63.5

size_of: WidthCosts 48 -> 128 B, DispatchCosts 144 -> 384 B. The extra cost is far below one section, which the rows charge at 0.6 us or more.

Verification

The AGENTS.md pre-push checklist passes locally:

  • cargo fmt --check
  • cargo clippy --all-targets --all-features and --no-default-features, both with -D warnings
  • cargo test, --all-features and --no-default-features
  • RUSTFLAGS="--cfg loom" cargo test --release --test loom_park (the park/wake core is untouched)
  • cargo deny check bans
  • scripts/check-no-public-rayon.sh
  • cargo doc --no-deps

Independent pre- and post-implementation reviews (gpt-6.1-sol) were done. The post-review had no BLOCKER or IMPORTANT findings, and its minor ones are fixed.

Downstream

tensor4all/faer mirrors the flat model for InPool and Outside and asserts equality with threadteam through width 12. Those assertions change when faer moves to 0.1.1, which is the stage-4 PR that also deletes team_section_cost.

🤖 Generated with Claude Code

shinaoka and others added 4 commits October 29, 2026 09:56
…age 1)

`WidthCosts` no longer holds a table of widths 1..=6 with the width-6 value
held flat above it. It stores the measured anchors (widths and nanoseconds,
at most `MAX_ANCHORS` = 12) and evaluates the model per lookup:

* below the first anchor: the first value, between two anchors: linear
  interpolation as before (i128, clamp at 0), above the largest anchor `w_m`:
  `ns[w_m] * w / w_m` in u128, saturating at `u64::MAX`. A wide section is
  never charged the cost of the largest anchor, so `width_for` no longer
  answers the full width just because the width-6 section pays.
* `from_anchors(&[(usize, Duration)]) -> Option<Self>` adds measured anchors
  up to `u16::MAX` (const, allocation-free, no panic); `new` and `flat` keep
  their signatures and their 2/4/6 anchors. `min()` is the minimum over the
  anchors, which is the minimum over all widths.
* `for_class(X86_64Avx512)` gains the measured width-8 team anchor 0.98 us
  from the post-fix run; in pool and outside keep 2/4/6 (their width-8 values
  are pre-fix data).

Values at widths 1..=6 of every existing row are unchanged and pinned in a
test; the generic row keeps 3/15/40 us at the anchors and now grows above
them. Unit tests cover the flat-row law (1 us at 6/12/24/48), non-decreasing
and at-least-linear extrapolation, anchors up to 64, `from_anchors`
rejections and const use, equality ignoring unused capacity, a
`Duration::MAX` row, and `width_for_by(24 us, 1, 64)` = 12 on a 1-us row.

`benches/cost_lookup.rs` gains a budget-64 `width_for_by`, `min_cost`, a
by-value `DispatchCosts` copy and the team-token `Exec` queries; the design
records and the README state the new contract and keep the historical reason
for the earlier flat model.
The 128-bit division is a library call; the 64-bit one gives the same
truncated value whenever the product fits. Lookup at widths 2..8: 7.35 ->
4.12 ns (base table index 0.89 ns).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on test)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anchor; variable anchors)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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