You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
その結果、floor をわずかに超えた region (f64 GEMM でおよそ 74^3、serial で約 26 us) を p = 64 で開くと、1 スレッドあたりの仕事は約 0.4 us になります。
実際の起床・barrier コストは CCD をまたぐと p とともに増えるはずなので (推測、未計測)、floor 付近の region は p = 64 で serial より遅くなりえます。
影響範囲は、PATCHSET の "Parallel floors from the dispatch cost" / "remaining regions" / "join regions and the block apply" / "Architecture keyed serial rates and Rayon costs" が置き換えたすべてのゲートです。代表例:
Design note for the fix (monitoring side, 2026-10-29): after #97 only the kernel path picks a narrower width. team::kernel::kernel_width searches 2..=size for the widest width that still pays on the extrapolated cost and runs on a sub-team of that width (with_fork; the other members stay parked). The other gates (par_pays_time call sites in the factorizations, for_each regions) are all-or-nothing at par.degree(): either the full team or sequential. The fix for this issue should give every region the same choice through one helper (widest paying width, then a sub-team of that width), so a region whose work only pays at 4 or 8 participants runs there instead of opening all 64 or none.
Item 1 measured on broccoli (AMD EPYC 7702P, Zen2, 64 cores, SMT off, NUMA 1, 16 MiB L3 per 4-core CCX), boost 0, governor performance
Measurement only, nothing changed. 2026-10-29 09:40-10:05 JST. Two instruments, both dispatching through
the crates the library itself uses:
dprobe (untracked probe): one empty section, back to back, 1000 iterations after 300 warm-up,
through spindle::for_each_raw inside spindle::with_lock (held), a caller-owned Rayon pool
entered with install (outside), and TeamExec::run on a threadteam team (team). The process
is pinned to a whole-CCX mask of exactly the section's width, excluding the observer CCX (the
campaign's convention: the caller thread is one of the participants).
threadteam's own benches/dispatch_cost.rs (the Linux port from faer-benchmark origin/bench/awake-team-linux, unchanged, TT_POOL=64 TT_WIDTHS=…), i.e. the method the existing WidthCosts anchors came from: back-to-back sections whose shares carry 10 µs of work, the section
cost being the per-call time minus those 10 µs. Pool 64, process on CPUs 0-63, nothing else on the
host.
1. Empty section, µs (median; the three caller routes)
width
outside (install)
held (in pool)
team (TeamExec::run)
2
4.49
0.78
0.69
4
4.61
1.03
0.83
6
5.49
2.78
(anomaly, see below)
8
6.74
3.17
1.92
12
9.88
4.51
2.11
16
13.22
6.01
2.58
24
20.27
7.63
3.94
32
29.71
8.76
5.34
48
62.92
12.14
4.64
60
78.61
14.00
9.44
64
81.19
15.04
8.91
2. Team section with 10 µs shares, µs (threadteam's instrument, the WidthCosts method)
width
2
4
6
8
12
16
24
32
48
64
team, load − 10 µs
1.42
1.99
2.33
2.74
3.56
4.08
4.29
4.84
6.40
9.33
team, empty (hot)
1.33
2.11
2.53
2.95
3.93
4.58
4.62
5.04
6.04
10.33
What this says about the premise
Cost is not flat above width 6. Empty-section cost grows 14.6x from width 4 to 64 in the pool
(1.03 → 15.04 µs), 17.6x from outside (4.61 → 81.19), and the team grows 6.7x from width 2 to 64
(1.42 → 9.33 µs with the anchor method). The team row is sublinear but nowhere near flat
(9.33 µs at 64 against 2.33 at 6).
And the model this host actually uses is the flat generic row. Zen2 has AVX2+FMA and no
AVX-512F, so HostClass::detect gives X86_64Avx2, which takes [15 µs; 3] in pool and [40 µs; 3] outside at every width (utils/cost.rs:172-194; threadteam::DispatchCosts:: host_default() agrees on this host). Against the measurements that is:
width
in-pool measured
model
outside measured
model
4
1.03
15
4.61
40
8
3.17
15
6.74
40
16
6.01
15
13.22
40
32
8.76
15
29.71
40
64
15.04
15
81.19
40
So at mid widths the floor is 2.5-15x too conservative (regions that would pay stay serial on this
host), and at 64 the in-pool row happens to land on the truth while the outside row is 2x too low
(over-splitting) — the issue's concern shows up at the top end, and its opposite at the bottom end.
The 64-wide direction also confirms the per-thread share worry: a 6-wide section's cost is used as
the 64-wide cost, so a region whose serial estimate is ~45 µs passes the gate at 64 threads and gives
each of 64 participants under a microsecond.
An observation I am not concluding from
The TeamExec route intermittently shows a ~0.62 ms section instead of the ~1-9 µs in the tables:
same width, same pinning, run to run (widths 6, 32, 60 at different attempts, and reproducibly at
width 6 in one configuration), with a bimodal distribution (p10 ≈ 623 µs, p90 ≈ 646 µs in one
sample; 1.6 µs in the next run of the same cell). A fresh Team::exec(w).team(…) per call is what
the probe pays and what a faer caller pays; threadteam's own bench, which reuses one Exec, never
shows it. It looks like a wake/park path (the tree-wake note in the campaign's awake-team record
quotes 55-60 µs per parked core), but I have not attributed it and it needs its own measurement
rather than a guess. It is reported here because a caller that pays it would see 0.6 ms per section.
Scope
Items 2 (floor-adjacent scaling with par-trace) is not measured here. No code changed; the
probes are untracked in faer-benchmark/probes/hh91/. The row data for tensor4all/threadteam-rs#6 is posted there.
Activity
cost: extrapolate the team dispatch cost beyond the measured widths
design: faer's parallel route is a threadteam the caller owns (#74)
shinaoka commentedon Oct 29, 2026
Design note for the fix (monitoring side, 2026-10-29): after #97 only the kernel path picks a narrower width.
team::kernel::kernel_widthsearches 2..=size for the widest width that still pays on the extrapolated cost and runs on a sub-team of that width (with_fork; the other members stay parked). The other gates (par_pays_timecall sites in the factorizations,for_eachregions) are all-or-nothing atpar.degree(): either the full team or sequential. The fix for this issue should give every region the same choice through one helper (widest paying width, then a sub-team of that width), so a region whose work only pays at 4 or 8 participants runs there instead of opening all 64 or none.shinaoka commentedon Oct 29, 2026
Item 1 measured on broccoli (AMD EPYC 7702P, Zen2, 64 cores, SMT off, NUMA 1, 16 MiB L3 per 4-core CCX), boost 0, governor
performanceMeasurement only, nothing changed. 2026-10-29 09:40-10:05 JST. Two instruments, both dispatching through
the crates the library itself uses:
dprobe(untracked probe): one empty section, back to back, 1000 iterations after 300 warm-up,through
spindle::for_each_rawinsidespindle::with_lock(held), a caller-owned Rayon poolentered with
install(outside), andTeamExec::runon athreadteamteam (team). The processis pinned to a whole-CCX mask of exactly the section's width, excluding the observer CCX (the
campaign's convention: the caller thread is one of the participants).
benches/dispatch_cost.rs(the Linux port fromfaer-benchmarkorigin/bench/awake-team-linux, unchanged,TT_POOL=64 TT_WIDTHS=…), i.e. the method the existingWidthCostsanchors came from: back-to-back sections whose shares carry 10 µs of work, the sectioncost being the per-call time minus those 10 µs. Pool 64, process on CPUs 0-63, nothing else on the
host.
1. Empty section, µs (median; the three caller routes)
outside(install)held(in pool)team(TeamExec::run)2. Team section with 10 µs shares, µs (threadteam's instrument, the
WidthCostsmethod)hot)What this says about the premise
Cost is not flat above width 6. Empty-section cost grows 14.6x from width 4 to 64 in the pool
(1.03 → 15.04 µs), 17.6x from outside (4.61 → 81.19), and the team grows 6.7x from width 2 to 64
(1.42 → 9.33 µs with the anchor method). The team row is sublinear but nowhere near flat
(9.33 µs at 64 against 2.33 at 6).
And the model this host actually uses is the flat generic row. Zen2 has AVX2+FMA and no
AVX-512F, so
HostClass::detectgivesX86_64Avx2, which takes[15 µs; 3]in pool and[40 µs; 3]outside at every width (utils/cost.rs:172-194;threadteam::DispatchCosts:: host_default()agrees on this host). Against the measurements that is:So at mid widths the floor is 2.5-15x too conservative (regions that would pay stay serial on this
host), and at 64 the in-pool row happens to land on the truth while the outside row is 2x too low
(over-splitting) — the issue's concern shows up at the top end, and its opposite at the bottom end.
The 64-wide direction also confirms the per-thread share worry: a 6-wide section's cost is used as
the 64-wide cost, so a region whose serial estimate is ~45 µs passes the gate at 64 threads and gives
each of 64 participants under a microsecond.
An observation I am not concluding from
The
TeamExecroute intermittently shows a ~0.62 ms section instead of the ~1-9 µs in the tables:same width, same pinning, run to run (widths 6, 32, 60 at different attempts, and reproducibly at
width 6 in one configuration), with a bimodal distribution (
p10 ≈ 623 µs, p90 ≈ 646 µsin onesample; 1.6 µs in the next run of the same cell). A fresh
Team::exec(w).team(…)per call is whatthe probe pays and what a faer caller pays; threadteam's own bench, which reuses one
Exec, nevershows it. It looks like a wake/park path (the tree-wake note in the campaign's awake-team record
quotes 55-60 µs per parked core), but I have not attributed it and it needs its own measurement
rather than a guess. It is reported here because a caller that pays it would see 0.6 ms per section.
Scope
Items 2 (floor-adjacent scaling with
par-trace) is not measured here. No code changed; theprobes are untracked in
faer-benchmark/probes/hh91/. The row data fortensor4all/threadteam-rs#6 is posted there.
linalg: split the layout copies along the destination's major dimensi…
par: every region at the widest width that pays, on a sub-team of tha…
par: no heap allocation inside a parallel region (#111)
par: no heap allocation inside a parallel region (#111) (#126)
t4a-faer 0.26.0, t4a-faer-traits 0.24.2: release the changes since 0.…
shinaoka commentedon Oct 30, 2026
Folded into the umbrella #168 (parallel structure of the factorizations). This issue is re-scoped or closed against its items.