Skip to content

perf(sse41): add SSE4.1 fallback tier for olmoe and qwen36 int8 GEMV - #1239

Merged
JustVugg merged 10 commits into
JustVugg:devfrom
jtinbergen:sse41-tier-upstream
Sep 21, 2026
Merged

JustVugg merged 10 commits into
JustVugg:devfrom
jtinbergen:sse41-tier-upstream

Conversation

@jtinbergen

@jtinbergen jtinbergen commented Aug 26, 2026

Copy link
Copy Markdown

Summary

  • add c/sse41_kernels.h, a shared 128-bit SIMD primitives header (COLIBRI_FMA
    emulation, loadu/storeu, min/max, prefetch) for hosts with SSE4.1 but no AVX2
    (Sandy Bridge and similar)
  • add an SSE4.1 canary consumer in c/olmoe.c (dot_i8_16), verified bit-for-bit
    identical to the scalar reference over 10k random int8 pairs
  • extract qwen36's matmul_q_gs into c/gsgemv.h and matmul_q into c/qgemv.h
    (previously inline in qwen36.c), and add a new SSE4.1 tier to each, routed
    through the shared header rather than a third copy of the intrinsics
  • qwen36 becomes a third consumer of sse41_kernels.h, alongside olmoe (this PR);
    the header's own docstring also names deepseek_v4.c, colibri.c, kimi_k3.c,
    and inkling.c as candidates, none of which are touched here

Scope

This adds one fallback tier below AVX2/FMA, above scalar, for the three
kernels touched here: olmoe's dot product and qwen36's two GEMVs.

The SSE4.1 branches in gsgemv.h/qgemv.h are new code, not a port of existing
output: their reduction tree differs from the scalar/AVX2 tiers (float addition
is not associative), so they're the one deliberate exception to this engine's
byte-identical requirement -- checked by tolerance in the new tests, not memcmp.
Scalar and AVX2 tiers are unchanged and remain memcmp-exact.

Measured

Build (both from commit 1f9f2d8):

cd c && make qwen36 ARCH=x86-64                                          # scalar
cd c && make qwen36 ARCH=native EXTRA_CFLAGS="-msse4.1 -mno-avx2 -mno-fma"  # SSE4.1

Run harness, identical form for every measured run:

SNAP=<qwen36-i4-gs64 snapshot path> N_NEW=<200|500|800> \
  ./qwen36_{scalar,sse41} 256 4 <fixed prompt file> > out.log 2> err.log

256 = expert cache slots/layer (256/256 = full per-layer residency -- the code's
own bare default is 16, which starves this model's 256-expert layers and produces
non-representative numbers; worth stating explicitly since this PR's own text
previously omitted it). 4 = quant bits, matching the snapshot. No
OMP_NUM_THREADS override (default 22 threads).

Storage: the snapshot sits on NVMe -> LUKS (dm-crypt) -> LVM -> ext4.

Warm-up policy: expert-weight reads are deliberately evicted from the OS page
cache after each use (pread + posix_fadvise(DONTNEED), by design, to bound
RSS), so standard page-cache warm-up does not apply to them -- a rerun is not
faster. The 200/500/800-token windowed-delta protocol (see below) is itself the
within-run warm-up control: the expert cache fills as generation progresses, and
the 500->800 "steady" window isolates throughput after that fill completes. Runs
across the two builds were interleaved in strict alternating order (scalar,
SSE4.1, scalar, SSE4.1, ...) to cancel any residual directional bias.

Methodology: generate 200, then 500, then 800 tokens from the same prompt (each a
fresh process, not a continuation) -- the first 200 tokens of the 800-token run do
identical work to the standalone 200-token run, so the wall-clock delta between
windows isolates tail-window throughput. The 500->800 delta is reported as
"steady." 5 repetitions per build.

Intel Core Ultra 7 155H (22 threads), qwen36-i4-gs64 snapshot:

  • scalar: steady-window median 1.544 tok/s (5-run spread 1.481-1.719)
  • SSE4.1: steady-window median 2.669 tok/s (5-run spread 2.309-2.904)
  • ~1.73x over scalar, median-to-median, same hardware/session

This machine had sustained background load throughout measurement (other
processes competing for CPU; 1-minute load average ranged 6.2-13.2 across the
run) -- not an idle box. The numbers above are honest for that condition, not a
best-case result.

Confirmed via objdump on the compiled binaries (not inferred from build flags)
that the SSE4.1 build's matmul_q/matmul_q_gs OpenMP-outlined bodies contain 0
ymm/vfmadd instructions and do contain cvtepi8/pmovsx conversions, and the
scalar build's equivalent bodies contain neither -- the SSE4.1 branch is what
actually executed, on the actual binary.

Verification

  • make check: pass, 0 new warnings (4 pre-existing -Wformat-truncation
    warnings in unrelated code, confirmed identical before/after)
  • new tests test_gsgemv/test_qgemv (AVX2/scalar, memcmp-exact) and
    test_gsgemv_sse41/test_qgemv_sse41 (forced -msse4.1 -mno-avx2 -mno-fma,
    tolerance diff <= 1e-5 + 1e-4*|ref|): all pass, including the SSE4.1 tier's
    own <8-element tail-drop case (P5), which required the test reference to
    carry a matching SSE4.1 branch -- otherwise that case is structurally
    unwinnable
  • new test test_olmoe_dot_i8_16/test_olmoe_dot_i8_16_sse41: dot_i8_16 is
    pure integer arithmetic (no rounding), so checked memcmp-exact -- not
    tolerance -- against a scalar reference over 10k random int8 pairs plus
    saturation-edge cases (-128/127), both natively and forced onto the SSE4.1
    path; this is the automated backing for the "bit-for-bit identical" claim
    above, which previously rested only on the original commit message
  • token-exact oracle (SNAP=./glm_tiny TF=1 ./colibri 64 16 16): 32/32
    teacher-forcing + 20/20 greedy

Verified bit-for-bit identical to scalar reference over 10k random int8 pairs.
Uses c/sse41_kernels.h for shared primitives (COLIBRI_FMA macro, 128-bit
loadu/storeu/min/max/prefetch). The FMA macro is the #1 source of correctness
risk on Sandy Bridge (no hardware FMA) - double-rounding artifact in 1-2 ULP.

Reference implementation for porting the same pattern to c/kimi_k3.c,
c/inkling.c, c/colibri.c, c/deepseek_v4.c via the same C struct.
…_kernels.h

matmul_q_gs (gsgemv.h) and matmul_q (qgemv.h) were inline in qwen36.c, each
with its own scalar/AVX2 branches. Extract both verbatim into their own
headers and add a new SSE4.1 branch to each, routed through the
sse41_kernels.h primitives introduced alongside olmoe's dot_i8_16 in the
previous commit -- qwen36 becomes a third consumer of that header rather
than a third copy of the intrinsics.

Unlike dot_i8_16's pure integer arithmetic, these are float GEMVs: their
SSE4.1 reduction tree is not bit-identical to the scalar/AVX2 tiers (float
addition is not associative), so the new SSE4.1 branches are the one
deliberate exception to this engine's byte-identical requirement. New tests
test_gsgemv_sse41/test_qgemv_sse41 check them by tolerance
(diff <= 1e-5 + 1e-4*|ref|) instead of memcmp; the plain test_gsgemv/test_qgemv
stay memcmp-exact against the unchanged scalar/AVX2 tiers. The SSE4.1 test
reference needed its own tail-drop branch to match gsgemv.h's <8-element
drop -- otherwise that edge case is structurally unwinnable.

Also add test_olmoe_dot_i8_16(_sse41): dot_i8_16 (previous commit) is pure
integer arithmetic, so unlike the above it's checked memcmp-exact against a
scalar reference over 10k random int8 pairs plus saturation-edge cases. This
was previously asserted only in a commit message with no automated test in
the repo to back it.
@OPS-NeoRetro

Copy link
Copy Markdown

@JustVugg, merge #1232 first then take a look at this

@JustVugg

Copy link
Copy Markdown
Owner

CI ran for the first time on this branch (there were no runs at all before — the workflow had never been queued, so I updated the branch onto current dev to trigger it). 21 pass, 2 fail, and both failures are in this PR's own test scaffolding rather than in the kernels. I reproduced and isolated both locally, so here is the diagnosis rather than just the log.

1. Sanitizers: test_gsgemv is exact only when the compiler contracts

make test-asan builds at -O1 (ASAN_CFLAGS = -O1 -g ...), and there test_gsgemv reports 6 failures. The cases that still pass are P2 O=1 and the two P3 scalar-fallback ones; everything that goes through the SIMD path fails.

It is not the sanitizer and not the optimisation level as such. It is FMA contraction:

-O1                     FAIL
-O2                     pass
-O3                     pass
-O1 -ffp-contract=off   FAIL
-O3 -ffp-contract=off   FAIL

The last line is the one that identifies it: at full optimisation, removing only contraction reproduces the failure. So the memcmp-exact comparison holds because the compiler happens to fuse a*b+c in the reference the same way the implementation does, and stops holding the moment it does not.

That makes the assertion accidental rather than structural, which matters more than the red mark: it passes at -O2/-O3 for a reason unrelated to the kernel being correct. Two ways out, both fine by me — have the reference perform an explicit FMA so it expresses the same operation the implementation does regardless of flags, or compare with tolerance as the _sse41 variants already do and reserve memcmp for the integer paths.

Scoped: test_qgemv and test_olmoe_dot_i8_16 both pass at -O1. This is only test_gsgemv.

2. macOS: the _sse41 targets are unconditionally x86

clang: error: unsupported option '-msse4.1' for target 'arm64-apple-darwin25.5.0'
clang: error: unsupported option '-mno-avx2' ...
clang: error: unsupported option '-mno-fma' ...
make[1]: *** [tests/test_gsgemv_sse41] Error 1

tests/test_gsgemv_sse41 and tests/test_qgemv_sse41 pass -msse4.1 -mno-avx2 -mno-fma with no architecture guard, and there is no such guard anywhere in the Makefile, so the whole check target dies on Apple Silicon. Mechanical, but it takes macOS CI down entirely.

What I checked that is fine

I would rather say what held up than only what broke.

  • The tier dispatch is compile-time and tests __AVX2__ && __FMA__ before #elif defined(__SSE4_1__), so an AVX2 host never compiles the new path.
  • AVX2 behaviour is unchanged: qwen36 is token-exact at caps 1, 8 and 16 on this branch, identical to dev. Extracting matmul_q_gs and matmul_q into gsgemv.h/qgemv.h did not disturb the existing tiers.
  • All six new tests pass natively on an AVX2 host, including the three forced onto SSE4.1.
  • No conflict with qwen36: vectorise the int4 expert unpack (2.10x CPU decode, bit-exact) #1271, which touches unpack_int4_to_int8 in the same file.

The objdump check on the compiled binaries is the right way to make the 1.73x claim, and stating the machine's background load rather than quietly running on an idle box is the reason the number reads as credible.

Worth adding that this PR has a concrete beneficiary already in the tracker: #1253 is a Sandy Bridge ThinkPad W520 with no AVX2, running the scalar path today.

Not merging until those two are addressed. Neither touches the kernels, so it should be a short round trip.

…s to x86_64

Two CI failures flagged in review on JustVugg#1239, both in test scaffolding, not
the kernels.

- test_gsgemv's AVX2 reference (reference_gs_gemv) computed the per-group
  scale step as `acc += x * sc[gi]`, an implicit multiply-add the compiler
  only fuses into the same hardware FMA gsgemv.h's real kernel uses
  explicitly (fmaf) when contraction is on -- true at -O2/-O3 with the
  default -ffp-contract=fast, false at -O1 (where ASan/UBSan CI builds) or
  with contraction off even at -O3. The memcmp-exact comparison held by
  accident, not by construction. Switched the reference to the same
  explicit fmaf so it matches regardless of optimisation flags. Verified:
  ALL PASS under ASan/UBSan at -O1 and at -O3 -ffp-contract=off, both of
  which previously failed.

- tests/test_gsgemv_sse41 and tests/test_qgemv_sse41 pass
  -msse4.1/-mno-avx2/-mno-fma unconditionally, flags gcc/clang only
  recognise on x86 -- unconditionally in TEST_BINS they took down `make
  check` entirely on arm64 (e.g. macOS: "unsupported option '-msse4.1' for
  target arm64-apple-darwin"). Excluded by default and appended back only
  under X86_64, mirroring the existing COLI_V4_SUPPORTED pattern in the
  same file. Being an allow-list rather than an arm64-specific deny-list,
  this also correctly excludes PPC64 and any future target without needing
  a target-specific branch. Windows (x86_64, UCRT64) is unaffected --
  confirmed via the same TARGET_CPU detection used elsewhere in this
  Makefile, which reads the compiler's -dumpmachine triple rather than
  uname, so it is correct there too.

Verified make check green end to end after rebasing onto the current PR
branch state (JustVugg merged dev into it to get CI running).

@OPS-NeoRetro OPS-NeoRetro left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job @jtinbergen, but please add the new test.o files and test.exes to the main or tests folder .gitignore to avoid something like #874 happening again

@jtinbergen

Copy link
Copy Markdown
Author

Thanks for taking a look. Could you point to which specific files would escape the current .gitignore? I checked before this review came in:

  • c/.gitignore already has tests/test_* (with !tests/test_*.c/.cu/.mm/.py exceptions for the sources), and the repo-root .gitignore has the same pattern for c/tests/test_*.
  • git status --ignored confirms all six new binaries (test_gsgemv, test_gsgemv_sse41, test_qgemv, test_qgemv_sse41, test_olmoe_dot_i8_16, test_olmoe_dot_i8_16_sse41) are already listed as ignored (!!).
  • None of this PR's commits contain any .o/.exe files (git diff --name-only origin/dev..HEAD is source-only).
  • All six Makefile rules compile source straight to binary in one step — no intermediate .o, no separate build subdirectory (unlike .gitignore: the four ownership objects #868 did not reach #874, where the gap was a single-segment glob missing a nested build/ownership/ path).

Happy to add a rule if there's a concrete file I'm missing — just want to fix the actual gap rather than a rule that duplicates existing coverage.

@OPS-NeoRetro

Copy link
Copy Markdown

Thanks for taking a look. Could you point to which specific files would escape the current .gitignore? I checked before this review came in:

  • c/.gitignore already has tests/test_* (with !tests/test_*.c/.cu/.mm/.py exceptions for the sources), and the repo-root .gitignore has the same pattern for c/tests/test_*.
  • git status --ignored confirms all six new binaries (test_gsgemv, test_gsgemv_sse41, test_qgemv, test_qgemv_sse41, test_olmoe_dot_i8_16, test_olmoe_dot_i8_16_sse41) are already listed as ignored (!!).
  • None of this PR's commits contain any .o/.exe files (git diff --name-only origin/dev..HEAD is source-only).
  • All six Makefile rules compile source straight to binary in one step — no intermediate .o, no separate build subdirectory (unlike .gitignore: the four ownership objects #868 did not reach #874, where the gap was a single-segment glob missing a nested build/ownership/ path).

Happy to add a rule if there's a concrete file I'm missing — just want to fix the actual gap rather than a rule that duplicates existing coverage.

Okay... Approved?

@JustVugg

JustVugg commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Reviewed and tested locally against current dev, and it holds:

  • merges cleanly (I merged dev into it here, no conflicts);
  • test_gsgemv, test_qgemv, test_olmoe_dot_i8_16 all pass, the last one bit-exact over 10,000 random pairs;
  • the same tests pass in a pure SSE4.1 build (-mno-avx -mno-avx2 -msse4.1), which is the configuration this PR exists for;
  • perf(sse41): vectorize grouped-int4 GEMV #1286 (cameron's grouped-int4 GEMV) shares your sse41_kernels.h and merges cleanly on top of this, so the order is this PR first.

The one thing missing is CI, and it has never run: as a first-time contribution the workflows wait for a maintainer, and the run objects only appear once the branch moves. I tried to create them by pushing a merge of dev to your branch; that push fails for your fork specifically while it worked for three others today, so I would rather not keep hammering it.

Could you push one commit to sse41-tier-upstream, a merge of current dev or even an empty commit? I will approve the runs the moment they exist. If they come back green, this goes in, then #1286.

…equisites

test_makefile_deps.py (added on dev) flagged that olmoe$(EXE) and
qwen36$(EXE) include headers this PR introduced without listing them
as prerequisites -- editing sse41_kernels.h, gsgemv.h or qgemv.h alone
would make report success and leave a stale binary.
@JustVugg

Copy link
Copy Markdown
Owner

Ten days of drift on a branch I verified myself. On 2026-09-07 I merged dev into this branch, found no conflicts, and confirmed all three tests green including bit-exactness over 10,000 random pairs. Since then c/Makefile has moved 32 commits, c/qwen36.c 12 and c/olmoe.c 9, and the Makefile part is test-binary registration rather than anything structural.

Could you forward-merge dev once more? c/sse41_kernels.h, gsgemv.h and qgemv.h are all still absent from dev, so nothing has overtaken it, and #1232 which you were waiting on is merged.

On the open CHANGES_REQUESTED about gitignoring the test binaries: your rebuttal is right and I am clearing it. Generated test binaries belong in .gitignore.

# Conflicts:
#	c/Makefile
#	c/qwen36.c
CI's macOS job failed the same way the original arm64 fix was meant to
prevent: this third _sse41 target (forces -msse4.1 -mno-avx2 -mno-fma)
was never added to the x86_64-only TEST_BINS guard, only
test_gsgemv_sse41 and test_qgemv_sse41 were. Verified no other test
rule uses an x86-only -m flag (swept the whole Makefile), and that
TARGET_CPU=arm64 now excludes all three _sse41 targets.

@OPS-NeoRetro OPS-NeoRetro left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPROVED! 👍

cameron pushed a commit to cameron/colibri that referenced this pull request Sep 20, 2026
…s to x86_64

Two CI failures flagged in review on JustVugg#1239, both in test scaffolding, not
the kernels.

- test_gsgemv's AVX2 reference (reference_gs_gemv) computed the per-group
  scale step as `acc += x * sc[gi]`, an implicit multiply-add the compiler
  only fuses into the same hardware FMA gsgemv.h's real kernel uses
  explicitly (fmaf) when contraction is on -- true at -O2/-O3 with the
  default -ffp-contract=fast, false at -O1 (where ASan/UBSan CI builds) or
  with contraction off even at -O3. The memcmp-exact comparison held by
  accident, not by construction. Switched the reference to the same
  explicit fmaf so it matches regardless of optimisation flags. Verified:
  ALL PASS under ASan/UBSan at -O1 and at -O3 -ffp-contract=off, both of
  which previously failed.

- tests/test_gsgemv_sse41 and tests/test_qgemv_sse41 pass
  -msse4.1/-mno-avx2/-mno-fma unconditionally, flags gcc/clang only
  recognise on x86 -- unconditionally in TEST_BINS they took down `make
  check` entirely on arm64 (e.g. macOS: "unsupported option '-msse4.1' for
  target arm64-apple-darwin"). Excluded by default and appended back only
  under X86_64, mirroring the existing COLI_V4_SUPPORTED pattern in the
  same file. Being an allow-list rather than an arm64-specific deny-list,
  this also correctly excludes PPC64 and any future target without needing
  a target-specific branch. Windows (x86_64, UCRT64) is unaffected --
  confirmed via the same TARGET_CPU detection used elsewhere in this
  Makefile, which reads the compiler's -dumpmachine triple rather than
  uname, so it is correct there too.

Verified make check green end to end after rebasing onto the current PR
branch state (JustVugg merged dev into it to get CI running).
@JustVugg
JustVugg merged commit 3827f42 into JustVugg:dev Sep 21, 2026
28 checks passed
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.

3 participants