Skip to content

WidthCosts is flat above width 6: extrapolate conservatively and accept measured anchors up to 64 #6

Closed

Description

@shinaoka

Problem

WidthCosts is measured at widths 2, 4 and 6 and is flat above the last anchor (TABLE = 6; ns_at reads the last entry for every wider width). On a 32 to 64 thread team every consumer therefore sees the cost of a 6-wide section for a 64-wide one, so width_for / width_for_by answer the full width whenever the 6-wide section pays, and a region opens all 64 participants for work that only pays at a few.

This affects every consumer that reads the table:

  • tensor4all/tcontract-rs: Exec::width_for and width_for_within (contraction planning plan/mod.rs, batch lanes faer_auto);
  • tensor4all/tlinalg-rs through tcontract-exec;
  • tensor4all/faer, which works around it locally since #97 (utils::cost::team_section_cost: for widths above the largest measured width w_m, at least cost(w_m) * w / w_m).

The local workaround is a second reading of the cost in one consumer; it belongs here, once, for all of them.

Proposal

  1. Conservative extrapolation in WidthCosts. Above the largest measured anchor w_m, the cost is at least cost(w_m) * w / w_m (linear in the width, never flat). Widths up to the largest anchor keep their measured values, so no existing decision at those widths changes.
  2. Variable anchors. Let a row carry measured anchors beyond 6 (up to 64 and more), so a row measured on a many-core host replaces the extrapolation where it has data. The table length follows the largest anchor (keep it allocation-free and const-constructible).
  3. A many-core row. 並列ゲートの dispatch cost が幅 6 で頭打ち、通れば常に全幅: p=32〜64 で floor 付近の region が過剰に開く faer#91 measures the team dispatch cost at widths 2 to 64 on broccoli (AMD EPYC 7702P, Zen2, 64 cores, boost off, performance governor). Add that row for the class it belongs to (Zen2 is X86_64Avx2; today that class takes the conservative generic row) with its provenance, the same way the existing rows are documented.
  4. Then faer drops team_section_cost and reads DispatchCosts directly, and tcontract-exec / tlinalg pick the change up through their lock.

Done when

  • WidthCosts::at is non-decreasing beyond the largest anchor and at least linear in the width there; a unit test pins it (a flat 1 us row gives 1/2/4/8 us at widths 6/12/24/48, and a region that does not pay at 6 does not pay at 64).
  • Rows with anchors beyond 6 are supported and tested; existing rows give identical values at widths 1 to 6.
  • The pre-push checklist in AGENTS.md passes (including tests/alloc_free.rs).
  • A patch release (0.1.x); publishing needs the maintainer's approval.

Related: tensor4all/faer#91 (measurement), tensor4all/faer#95 (audit), tensor4all/faer#97 (local workaround), tensor4all/tcontract-rs#148.

🤖 Generated with Claude Code

Activity

  1. self-assigned this
    on Oct 29, 2026
  2. shinaoka commented on Oct 29, 2026

    @shinaoka
    MemberAuthor

    A X86_64Avx2 (Zen2) row at widths 2-64, measured on broccoli

    This is the data for item 3 of the issue (the many-core row). Method is benches/dispatch_cost.rs
    itself, not a re-implementation: the Linux port that takes the pool width and the measured widths
    from the environment (TT_POOL, TT_WIDTHS), copied unchanged from faer-benchmark
    origin/bench/awake-team-linux data/probes/20261026-awake-team-linux/bench/tt/src/bin/dispatch_cost.rs,
    built against threadteam 0.1.0 from crates.io. I picked team/load10us-minus-10us as the row
    because that is the quantity the existing anchors document ("back to back sections whose shares
    carry 10 us of work", cost.rs:277-289).

    team/load10us-minus-10us, µs

    width 2 4 6 8 12 16 24 32 48 64
    measured 1.42 1.99 2.33 2.74 3.56 4.08 4.29 4.84 6.40 9.33

    team/hot (empty shares) for the same widths: 1.33, 2.11, 2.53, 2.95, 3.93, 4.58, 4.62, 5.04,
    6.04, 10.33 µs. Raw CSV (kind,width,ns, every row the bench emits): attached below.

    The two other kinds for the same run, if they are useful as a control (plain/outside-hot and
    plain/in-pool, pinned to 0-63 with a 64-wide pool): 32.6/9.4 µs at width 8, 63.6/42.3 at 32,
    76.8/58.7 at 64. They are much larger than the team row because a plain token wakes a spinning pool,
    which is not what a team section pays.

    Provenance

    • Host broccoli: AMD EPYC 7702P (Zen2), 64 physical cores, SMT off, NUMA 1, L3 = 16 MiB per
      4-core CCX (16 domains); cpufreq/boost = 0, governor performance (set by the maintainer, both
      read back before the run); 257 GB RAM; Linux.
    • Pool 64, process pinned with taskset -c 0-63 (the analogue of the 8-thread evo row, which was
      pinned to exactly its eight cores). Nothing else ran on the host during the run — the agent was
      idle, verified with a 3 s per-CPU busy window.
    • threadteam 0.1.0 (crates.io), parallel feature; TT_POOL=64, TT_WIDTHS=2,4,6,8,12,16,24,32,48,64.
    • 2026-10-29 09:50-09:55 JST. host_default() on this host reports the generic row
      (team 3 µs, in pool 15, outside 40) because HostClass::detect needs AVX-512F for the measured
      row and Zen2 has AVX2+FMA.

    Note for the row that lands

    With the three existing anchors the row would be [1.42, 1.99, 2.33] µs, and with TABLE = 6 the
    model then answers 2.33 µs at width 64 against 9.33 µs measured — a 4x understatement in exactly the
    direction item 1 of the issue is about (it permits a 64-wide section whenever a 6-wide one pays). If
    item 2 (variable anchors) lands, this row should carry the full ten widths above; the measured shape
    is sublinear (≈ 2.33 · (w/6)^0.45 between 6 and 64) so the linear extrapolation in item 1 stays
    conservative against it.

    Cross-reference: the same measurement, with faer's three caller routes as empty sections, is in
    tensor4all/faer#91.

    kind,width,ns
    team/hot,2,1333.7
    team/gap1ms,2,1740.0
    team/gap10ms,2,2410.0
    team/gap300ms,2,10450.0
    team/load10us-minus-10us,2,1423.0
    team/fork-hot,2,1659.6
    team/hot,4,2111.0
    team/gap1ms,4,2490.0
    team/gap10ms,4,3050.0
    team/gap300ms,4,16680.0
    team/load10us-minus-10us,4,1991.6
    team/fork-hot,4,2258.6
    team/hot,6,2525.1
    team/gap1ms,6,2820.0
    team/gap10ms,6,3420.0
    team/gap300ms,6,21030.0
    team/load10us-minus-10us,6,2328.6
    team/fork-hot,6,2655.1
    team/hot,8,2950.1
    team/gap1ms,8,3250.0
    team/gap10ms,8,3890.0
    team/gap300ms,8,23771.0
    team/load10us-minus-10us,8,2737.7
    team/fork-hot,8,3277.8
    team/hot,12,3931.7
    team/gap1ms,12,3980.0
    team/gap10ms,12,4710.0
    team/gap300ms,12,31351.0
    team/load10us-minus-10us,12,3557.5
    team/fork-hot,12,4162.5
    team/hot,16,4581.1
    team/gap1ms,16,4710.0
    team/gap10ms,16,5460.0
    team/gap300ms,16,41570.0
    team/load10us-minus-10us,16,4076.0
    team/fork-hot,16,5161.3
    team/hot,24,4619.9
    team/gap1ms,24,4640.0
    team/gap10ms,24,5550.0
    team/gap300ms,24,58701.0
    team/load10us-minus-10us,24,4292.1
    team/fork-hot,24,5467.6
    team/hot,32,5044.2
    team/gap1ms,32,4750.0
    team/gap10ms,32,5700.0
    team/gap300ms,32,81231.0
    team/load10us-minus-10us,32,4837.8
    team/fork-hot,32,5679.8
    team/hot,48,6041.5
    team/gap1ms,48,5740.0
    team/gap10ms,48,6220.0
    team/gap300ms,48,107962.0
    team/load10us-minus-10us,48,6401.9
    team/fork-hot,48,6396.0
    team/hot,64,10334.5
    team/gap1ms,64,7670.0
    team/gap10ms,64,8620658.0
    team/gap300ms,64,143273.0
    team/load10us-minus-10us,64,9332.5
    team/fork-hot,64,8112.1
    plain/outside-hot,2,14398.6
    plain/outside-gap1ms,2,21711.0
    plain/outside-gap10ms,2,46121.0
    plain/outside-load10us-minus-10us,2,42975.1
    external/outside-hot,2,10932.7
    external/outside-gap1ms,2,20711.0
    plain/in-pool,2,816.3
    plain/in-pool-load10us-minus-10us,2,27529.1
    plain/outside-hot,4,21425.4
    plain/outside-gap1ms,4,24051.0
    plain/outside-gap10ms,4,49971.0
    plain/outside-load10us-minus-10us,4,70437.9
    external/outside-hot,4,10103.6
    external/outside-gap1ms,4,19851.0
    plain/in-pool,4,2761.1
    plain/in-pool-load10us-minus-10us,4,54263.4
    plain/outside-hot,6,28269.5
    plain/outside-gap1ms,6,26930.0
    plain/outside-gap10ms,6,54001.0
    plain/outside-load10us-minus-10us,6,83079.7
    external/outside-hot,6,12181.5
    external/outside-gap1ms,6,20200.0
    plain/in-pool,6,5834.0
    plain/in-pool-load10us-minus-10us,6,72908.3
    plain/outside-hot,8,32576.6
    plain/outside-gap1ms,8,71521.0
    plain/outside-gap10ms,8,59012.0
    plain/outside-load10us-minus-10us,8,76791.7
    external/outside-hot,8,9062.7
    external/outside-gap1ms,8,19560.0
    plain/in-pool,8,9396.2
    plain/in-pool-load10us-minus-10us,8,67186.3
    plain/outside-hot,12,39899.4
    plain/outside-gap1ms,12,74381.0
    plain/outside-gap10ms,12,275665.0
    plain/outside-load10us-minus-10us,12,96968.4
    external/outside-hot,12,9433.1
    external/outside-gap1ms,12,21260.0
    plain/in-pool,12,16428.7
    plain/in-pool-load10us-minus-10us,12,84907.8
    plain/outside-hot,16,45699.4
    plain/outside-gap1ms,16,146703.0
    plain/outside-gap10ms,16,75732.0
    plain/outside-load10us-minus-10us,16,91376.0
    external/outside-hot,16,12133.7
    external/outside-gap1ms,16,18720.0
    plain/in-pool,16,23073.7
    plain/in-pool-load10us-minus-10us,16,77622.0
    plain/outside-hot,24,56698.6
    plain/outside-gap1ms,24,146253.0
    plain/outside-gap10ms,24,395218.0
    plain/outside-load10us-minus-10us,24,100128.0
    external/outside-hot,24,11789.4
    external/outside-gap1ms,24,20230.0
    plain/in-pool,24,36303.3
    plain/in-pool-load10us-minus-10us,24,90913.5
    plain/outside-hot,32,63561.6
    plain/outside-gap1ms,32,266016.0
    plain/outside-gap10ms,32,386408.0
    plain/outside-load10us-minus-10us,32,83287.2
    external/outside-hot,32,8614.3
    external/outside-gap1ms,32,21001.0
    plain/in-pool,32,42314.6
    plain/in-pool-load10us-minus-10us,32,82865.7
    plain/outside-hot,48,75502.5
    plain/outside-gap1ms,48,268636.0
    plain/outside-gap10ms,48,396378.0
    plain/outside-load10us-minus-10us,48,95110.1
    external/outside-hot,48,10625.6
    external/outside-gap1ms,48,20660.0
    plain/in-pool,48,54374.3
    plain/in-pool-load10us-minus-10us,48,96198.8
    plain/outside-hot,64,76841.3
    plain/outside-gap1ms,64,323237.0
    plain/outside-gap10ms,64,390518.0
    plain/outside-load10us-minus-10us,64,99850.3
    external/outside-hot,64,9728.1
    external/outside-gap1ms,64,20520.0
    plain/in-pool,64,58670.7
    plain/in-pool-load10us-minus-10us,64,93638.2
    
  3. shinaoka commented on Oct 29, 2026

    @shinaoka
    MemberAuthor

    Stage 1 (proposals 1 and 2) is merged in #7 and released as threadteam 0.1.1 (tag v0.1.1, b2a030c): linear extrapolation above the largest anchor, and WidthCosts::from_anchors for up to 12 anchors. Remaining: the broccoli Zen2 row for X86_64Avx2 once tensor4all/faer#91 has the width 2-64 data (separate patch), then faer drops team_section_cost and its mirrored flat model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions