Repository navigation
kernels: AVX2 multi-token i-quant row kernels (an AVX2-only CPU decoded every i-quant row once per token) - #43
Merged
Conversation
The native pack's i-quant gate/up rows fell back to ggml's single-token vec_dot on a CPU without AVX-512 (Ryzen 5000 series, Ice Lake, ...): the weights were decoded once per token, and the 5700X3D's pool was decode bound. iq_avx2.cpp adds multi-token row kernels for IQ2_XXS/IQ2_XS/IQ3_XXS/IQ3_S/IQ2_S and IQ4_NL down rows, and for IQ2_XS the vectorized sign decode of ggml's AVX2 vec_dot (the 8th sign bit by parity through the bit_helper pshufb, the sign vectors out of shuffles of one 32-byte load, the 8 scale bytes through the unpack trick), with an even_signs table equivalent to keven_signs_q2xs. A row is decoded once and every token in the verify window only pays a load, a sign, a maddubs, a madd and an add. Measured on a 5700X3D + RTX 5060 Ti, Swift native pack, greedy, 300 tokens, medians of interleaved A/B pairs: gate/up 15.16 -> 13.84 ms/round, rows 22.7 -> 23.8 GB/s, 48.5 ms/round vs 50.9 (-4.7%). Single thread, one expert's gate+up: IQ2_XS 3 tokens 536 -> 416 us, IQ2_XXS 390 -> 337 us. IQ4_NL down rows 248 -> 174 us for 3 tokens. native_expert_parity: rel 3.1e-08 vs ggml's vec_dot, 0 failures. STRATA_NO_IQ256 and STRATA_NO_IQ4NL fall back to ggml per format.
The tool called the AVX-512 row kernels and the AVX-512 quantizer unconditionally, so on an AVX2-only CPU it died with SIGILL before printing anything - the one tool you want on that machine. Both calls are now behind cpu_avx512_ok(). Adds a block that runs the new AVX2 kernels directly against ggml's vec_dot on Q8_0 activations (the IQ4_NL down rows' real input) and times them, so a kernel change can be validated and measured without the engine.
The notice printed 'the expert kernels run on AVX2' whether or not the AVX2 i-quant row kernels were used, and with STRATA_NO_IQ256 set they are not: the run is on ggml's vec_dot. Name the kernel, and name the switch that changed it.
This was referenced Sep 28, 2026
Owner
|
Merged, thank you - a careful piece of work. Checked here on a Ryzen 7600 (Zen 4), IQ3_S:
|
MistVVK
pushed a commit
to MistVVK/XeStrata
that referenced
this pull request
Oct 3, 2026
Hugua700
pushed a commit
to Hugua700/Strata
that referenced
this pull request
Oct 6, 2026
anon761
pushed a commit
to anon761/Strata-upstream
that referenced
this pull request
Oct 6, 2026
sskver
pushed a commit
to sskver/Strata
that referenced
this pull request
Oct 7, 2026
…cache Fix prefill cache handoff between agentic tool turns
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
An AVX2-only CPU (Ryzen 5000 / any X3D, Ice Lake, Rocket Lake) runs a native pack, but every i-quant
gate/up row goes through ggml's
ggml_vec_dot_iq*_q8_K, which decodes the row once per token. With--spec 4a verify window is 3-4 tokens over the same expert rows, so the decode ran 3-4 times fornothing.
iq_avx512.cppcovers 512-bit machines andq2_avx2.cppcovers Q2_0; the i-quants had no AVX2path at all.
This is also the concrete answer to #34 ("among AVX2, AVX512 and AVX-VNNI, which helps?"): on a native
pack AVX2 is enough, but only with these kernels — otherwise AVX2 leaves ~15-20% of the expert pool on the
table.
What
src/kernels/cpu/iq_avx2.cpp— multi-token row kernels, one decode per row for the whole window:Per token in the window: load,
_mm256_sign_epi8,maddubs,madd, add. That is the whole per-token cost.For IQ2_XS the decode follows ggml's own AVX2
vec_dotrather than a table lookup per sign group: the7-bit sign indices of all 16
u16of a 128-value group become sign vectors with two shifts, a xor, onepshufbagainst ggml'sbit_helperparity table and an or; the four 32-value sign vectors come out ofshuffles of that one register; the 8 scale bytes become all 16 half-scales with ggml's unpack trick plus the
k_shufflemask. The grid lookups stay scalar (ggml does the same). Alternating accumulators break theaddchain.even_signsis thekeven_signs_q2xsequivalent — that table isstaticinarch/x86/quants.cso itcannot be reused from here; it is built once at load time from
ksigns_iq2xs, so it cannot drift fromthe shared table (checked: bit 7 of
ksigns_iq2xs[i]ispopcount(i) & 1, which is exactly whatbit_helperreconstructs, so the u64 table and the parity trick agree).Dispatch is in
native_expert.cppnext to the existing AVX-512 one, withSTRATA_NO_IQ256(gate/up) andSTRATA_NO_IQ4NL(down) as per-format fallbacks to ggml, and no AVX-512 gate — so the path is exercisableon any AVX2 machine.
generate.cppnow prints which kernel is live, because the old line claimed AVX2kernels even when the code had fallen back to
vec_dot.Correctness
native_expert_parityagainst ggml'svec_dot, every layer of the pack: rel 3.1e-08 … 3.4e-08, 0failures. Full-expert rel vs the GPU (1.2e-2 cpu / 1.1e-2 gpu) is unchanged from before this PR, i.e. the
activation quantization still dominates and the kernels are equivalent to the reference.
Measured
Machine: Ryzen 7 5700X3D (Zen 3, AVX2 only, 8 cores), 64 GB DDR4-2666, RTX 5060 Ti 16 GB at PCIe 4.0 x8,
native
swift-iq3_xxspack,--spec 4.Microbench, one expert's rows, single thread, µs (3 tokens = one verify window):
vec_dotEnd to end, greedy fixed-prompt runs, median of 6 interleaved old/new pairs, metric ms/round (tok/s is
not usable here: draft acceptance moves the round count by ±5% between runs):
5 of the 6 pairs favoured the new kernels; the outlier is a run that routed the most experts of any run in
the set (5.67 vs 5.10-5.53 distinct per layer). The
downphase, which is byte-identical code in bothbuilds, also improved (-6%): a less CPU-saturated pool. On this box the whole engine went from 43-46 tok/s
as shipped to ~59 tok/s in a direct run.
Honest limits
stream), so further decode work will pay less than these numbers suggest; the next lever there is
streaming loads, not more unrolling.
verify window. A
--spec 1/ single-token decode run should not take this path, which is why thedispatch keeps the token-count gate.
Also in this PR: the parity tool was unusable on an AVX2-only CPU
native_expert_paritycalled the AVX-512 row kernels and the AVX-512 quantizer unconditionally, so onexactly the machine whose kernels you would want to validate it died with SIGILL before printing a single
line. Both calls are now behind
cpu_avx512_ok(). The tool also gained a block that runs these AVX2 kernelsagainst ggml's
vec_doton Q8_0 activations and times them, so a kernel change can be validated andmeasured without starting an engine.