Skip to content

Refactor/mmm pool and candidates - #2696

Open
kali wants to merge 6 commits into
mainfrom
refactor/mmm-pool-and-candidates
Open

Refactor/mmm pool and candidates#2696
kali wants to merge 6 commits into
mainfrom
refactor/mmm-pool-and-candidates

Conversation

@kali

@kali kali commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

kali added 2 commits August 21, 2026 19:42
Every plug pushed its own kernels into `ops.mmm_impls`, so what the pool holds was spread
over ten functions and, in three places, gated on conditions the kernels did not carry:
the avx tier stood down for the fma one, armvfpv2 for NEON, the 16x16 VNNI tile for
dual-FMA cores, and `fma_mmm_f32_32x1` wanted f16c. Give those four their real
`where` predicate and the pool follows from the inventory — every routine this build
assembled that `is_supported_here`, portable kernels first and each group by name, since
`inventory` yields link order and tie-breaking downstream reads pool position. The two
generic 3x2 kernels stay out of the inventory, which is what keeps them out of dispatch.
Both matmul lowerings walked the kernel pool themselves — einsum in `list_impls`, conv in
its block-quant branch — with filters that had drifted apart: conv checked neither
`stores()` nor, before the pool carried it, `is_supported_here`, and neither knew about the
other. `Ops::candidates` now answers "how can this build multiply these two operands",
kernel, packing and panel extractor, and both callers ask it; `Ops::filter_impls`, a third
unused variant of the same query, goes. Which candidate wins is still each caller's own
business.
@github-actions

Copy link
Copy Markdown

🔴 Bench vs main — 1 speed regression(s) · ⚠️ 3 secondary

Reference: 2026-08-21 morning nightly run (0d old) · full report → run

Speed — evaltime · prefill · decode

Δ metric device main → PR
🔴 +3.1% llama_3_2_1B_instruct_q40ef16_541
decode · metal
apple-m1-max 5.75 ms/tok
173.9 tok/s → 5.93 ms/tok
168.7 tok/s

Improvements

Δ metric device main → PR
🟢 -6.1% dummy_conmer_12M
evaltime · pulse_120ms
cortex-a53 51.2 ms/pulse
0.427 RTF → 48.1 ms/pulse
0.401 RTF
⚠️ 3 secondary regression(s)
Δ metric device main → PR
⚠️ +27.7% arm_ml_kws_cnn_m
load · pass
cortex-a9 83 ms → 106 ms
⚠️ +21.7% arm_ml_kws_cnn_m
load+optimize · pass
cortex-a9 129 ms → 157 ms
⚠️ +11.7% arm_ml_kws_cnn_m
load · pass
cortex-a7 77 ms → 86 ms

kali added 2 commits August 22, 2026 07:42
`best()` picked the arch policy with a `#[cfg]` per architecture, so which policies exist was
a compile-time fact no host could enumerate — and a policy could not be examined from
anywhere but the machine it was written for. Each arch tree now submits a `PlatformSelector`
carrying its plug and whether this build is native to it, and `best()` applies the native
ones, cfg-free. `platform::inspect` pairs a target's policy with its kernels, from the new
`mmm_routines::pool_for`, so one host can ask what another platform would dispatch: faithful
to the letter on the native target, and elsewhere as faithful as the `TRACT_CPU_*` knobs make
the hardware probes. The cost model's fallback stops unwrapping on a pool that lacks its
fitted kernels, which is exactly what a foreign pool is.
`Target` named only the arches carrying a kernel tree, so a riscv64 build could not say what it
runs on and a riscv64 kernel could not be declared at all. `Target::RiscV64` fills that in —
naming a platform is now separate from having kernels for it, `native()` answers `Some` on
riscv64 with no tree and no selector behind it — and every arch-keyed codegen macro grows a
riscv64 arm, so the tree can land a kernel at a time.
@github-actions

Copy link
Copy Markdown

🔴 Bench vs main — 4 speed regression(s) · ⚠️ 4 secondary

Reference: 2026-08-22 morning nightly run (0d old) · full report → run

Speed — evaltime · prefill · decode

Δ metric device main → PR
🔴 +24.4% inceptionv1q
evaltime · pass
beaglev-ahead 2.22 s → 2.76 s
🔴 +9.2% nemotron_3_5_asr_streaming_0_6b_f32f32_preprocessor_pulse100ms
evaltime · cpu
apple-m1-max 0.106 ms/pulse
0.00106 RTF → 0.116 ms/pulse
0.00116 RTF
🔴 +8.4% nemotron_3_5_asr_streaming_0_6b_f32f32_preprocessor_1s
evaltime · cpu
apple-m1-max 0.938 ms → 1.02 ms
🔴 +5.8% mdl_en_2019_Q3_librispeech_onnx
evaltime · 2600ms
cortex-a55 89.7 ms → 94.9 ms
⚠️ 4 secondary regression(s)
Δ metric device main → PR
⚠️ +23.8% arm_ml_kws_cnn_m
load · pass
cortex-a9 84 ms → 104 ms
⚠️ +19.2% arm_ml_kws_cnn_m
load+optimize · pass
cortex-a9 130 ms → 155 ms
⚠️ +15.7% hey_snips_v4_model17
load+optimize · 2sec
cortex-a7 4.95 s → 5.73 s
⚠️ +14.8% hey_snips_v4_model17
load · 2sec
cortex-a7 4.25 s → 4.89 s

kali added 2 commits August 22, 2026 09:23
`candidates()` took four positional arguments and no notion of shape, so the enumeration could
never consider a dim and each caller assembled the operand types on the spot. It now takes a
`Query` holding the operand types plus the dims the caller can pin, and returns `Candidate`,
the triple core's `Impl` alias was spelling out by hand. Selection is unchanged — the query's
dims are not read yet.
The rules any platform falls back on — keep the best quality tier, then choose on n — lived in
einsum, so linalg could not answer a selection question without a caller to score for it.
`retain_best_quality` and `pick_by_shape` now sit beside `candidates`, and `pick_by_shape`
abstains on a symbolic n, which is precisely where einsum's packing-group reasoning takes over.
`list_impls` becomes `query`: the caller needs the query itself, and enumerating is one call.
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