Skip to content

feat(moe): FUSED3=1 opt-in AVX2 expert matmul — 40% less matmul time, bit-identical output, off by default - #1024

Open
outtodata wants to merge 2 commits into
JustVugg:devfrom
outtodata:pr/fused3
Open

feat(moe): FUSED3=1 opt-in AVX2 expert matmul — 40% less matmul time, bit-identical output, off by default#1024
outtodata wants to merge 2 commits into
JustVugg:devfrom
outtodata:pr/fused3

Conversation

@outtodata

Copy link
Copy Markdown

FUSED3: vectorized activation quantizer + 4-way IDOT + gate/up pair matmul — 40% less matmul time, bit-identical output, off by default

TL;DR. Three opt-in kernel upgrades in c/fused_simd.h (env FUSED3=1, default off): (1) AVX2-vectorized activation quantization replacing the scalar tail of quant_x_q8, (2) a 4-accumulator IDOT matmul variant with prefetch, (3) a paired gate/up matmul that shares the activation stream. On a Core i5-7300U (AVX2, 2C/4T), time inside expert matmul drops ~40%, greedy token streams are bit-identical to master, and end-to-end throughput on our production config goes 2.08 → 2.38 tok/s. No change to file formats, no approximation anywhere: all arithmetic is still exact integer IDOT.

Setting. OLMoE-1B-7B Q8_0 snapshot, our production flags, Windows + WSL pipes (i.e. the noisy setting — numbers below are conservative; on a quiet machine the deltas are larger). MoE weights streamed from disk via the async expert pipe.

What changes (all under FUSED3, default OFF)

Kernel Before After
quant_x_q8 scalar loop for tail + row quant quant_x_q8_avx2: vector abs-max + quant, exact same round-to-nearest-even semantics
matmul_q_idot_v2 2-way unroll matmul_q_idot_v3: 4 independent int32 accumulators + _mm_prefetch of the next weight row
gate/up (proj0/proj2) two separate matmuls, activation re-read matmul_q_idot_pair_v3: single pass, both outputs, shared activation cache traffic

Dispatch is a single if (g_fused3) in olmoe.c at the MoE FFN call site; nothing else is touched.

Measured (bench_fused3_inproc, in-process, isolated kernels; medians of ≥10)

Metric master FUSED3 Δ
time inside expert matmul baseline −40% matmul_ms 7664→4172 per 200-token decode (bench_fused3_tokens, 8 experts, dim 2048)
E2E (quiet machine, all our prod flags) 2.08 tok/s 2.38 tok/s +14%

Output equivalence: md5 of the 200-token greedy stream identical with and without the flag across three independent runs (d6cb4e151fa83994d62982f3cbba4190), and identical to a separately-built reference binary. This is expected — every path is exact integer arithmetic; the flag only changes instruction scheduling, never values.

How this answers the review gates from #906

  • (a) off by default, loud when on: FUSED3 defaults to 0; when set, startup prints MODE FUSED3 ... banner plus per-layer [PROF] counters already present in the build.
  • (b) gate semantics exact: the flag selects between two exact implementations of the same integer kernel. No approximation, no numeric mode change. Documented as such at the dispatch site.
  • (c) token-exact with flag off: when off, the code path is byte-for-byte today's master (the new kernels are not even linked into the call path). Verified by identical md5 on flag-off vs master build.

What this is NOT

  • Not a quantization change: weights stay Q8_0, activations stay Q8_0. Pairs naturally with the int8 path; orthogonal to any 4/5-bit weight work.
  • We also tested a 2-worker parallel expert matmul (EXPERT_WORKERS=2) in the same series: it is a NO-GO from us (pipe interference eats the gain, and it introduces thread-count-dependent scheduling). Not proposed.

Caveats

  • Numbers are from one machine (i5-7300U, AVX2). No ARM/NEON variant of the new kernels — the flag should refuse or fall back on non-AVX2 builds (current code: compile-time #if defined(__AVX2__) around v3, scalar/v2 fallback otherwise).
  • The +14% E2E is on a disk-bound setup; on NVMe the relative gain is smaller (matmul is a smaller slice). The −40% matmul-time figure is the portable number.

Diff is ~200 lines in c/fused_simd.h + a dispatch flag + a bench binary. Happy to open the PR if the direction looks right, or to carve it differently (e.g. three separate flags) if you prefer finer-grained gating.

Related

JustVugg and others added 2 commits August 14, 2026 01:09
release: v1.6.1 — the first day of real users, fixed the same day
… quant + gate/up pair

Adds c/fused_simd.h with exact-integer IDOT kernels (quant_x_q8_avx2,
matmul_q_idot_v2/v3, matmul_q_idot_pair_v3) and a FUSED3 env-gated dispatch
at the MoE expert FFN call site. OFF by default; with the flag unset the
code path is byte-for-byte today's main. Loud when on: startup banner prints
fused3=%d. Bit-exactness verified by memcmp harness in
c/tests/bench_fused3.c and by identical greedy token streams vs main
(flag off and on).
@JustVugg
JustVugg changed the base branch from main to dev August 14, 2026 17:30
BrianHeeseIs added a commit to BrianHeeseIs/colibri that referenced this pull request Aug 15, 2026
Records the JustVugg/colibri survey: we are +125 commits, upstream +198 from the v1.5.0
merge-base; upstream has no Metal for deepseek_v4 (backend_metal_v4.mm is ours) though it
does have c/backend_metal.mm for the older engines. Lists the three commits that
cherry-pick cleanly and the ones that conflict.

Marks B7 RESOLVED: the prerequisite implementation it demanded was built as
COLI_V4_MOE_BATCHED, and measured against B7's own criterion (Metal off vs on,
end-to-end) it gives 1.330x on p064, clearing the 1.15x bar. Corrects two premises B7
recorded that measurement has since falsified.

Adds the deferred upstream PRs with reasons: JustVugg#1024 is AVX2-only with no NEON variant,
JustVugg#934 is ARM NEON but fmt=6 E8/IQ3 rather than MXFP4, JustVugg#1017 helps the single-token decode
path which our TTFT metric excludes.
@JustVugg

Copy link
Copy Markdown
Owner

Cross-linking a decision that affects how this PR should be described: #1044 established that olmoe's IDOT branch quantizes the activations, so it is not token-exact against the fp32 path — and the decision there is to return IDOT to off by default on x86.

Two consequences for FUSED3:

  1. Correctness is unaffected. Your kernels accelerate the IDOT path and are bit-identical to it; I verified that independently (I rebuilt bench_fused3 on an AVX2 box: v3_quant_bitexact=yes, v3_output_bitidentical_v2=yes, and ~1.21× on the kernels here).

  2. The wording needs scoping. I also compared FUSED3 against the stock matmul_q IDOT branch bit-for-bit and got 915/1024 outputs differing (ulp-level; the FMA plus the two accumulator chains change the rounding). So "bit-identical output" reads as "token-exact", which it isn't — it's "bit-identical to the IDOT path". With IDOT=0 becoming the default, it's also no longer the shipped configuration, so the 40% figure needs that caveat.

Please retitle/reword along those lines and this is good to go — the engineering is sound, the isolation is exemplary (one new header, one engine, opt-in, __AVX2__-guarded), and the speedup is real. It's only the claim I want precise, because exactness is the one thing this project can't be sloppy about.

zh-Processor added a commit to zh-Processor/colibri that referenced this pull request Aug 16, 2026
- colibri.c: cache RoPE inv_freq keyed on (theta,qk) instead of rerunning
  half x powf per position; parallelize the prefill RoPE/KV-write loop on
  CPU-only builds (iterations write distinct KV rows, rope cache is
  _Thread_local; CUDA/Vulkan shadow-shrink keeps GPU builds serial);
  cache the compiled grammar per serve slot when the identical schema
  text is resent (grammar_reset == fresh setup end state)
- deepseek_v4.c: stack routing buffers (<=512 experts, malloc fallback
  beyond); COLI_MODEL_DIRS multi-directory shard lookup via st_init_multi
- resource_plan.py: sidecar cache for analyze_model (atomic tmp+replace
  writes, (size,mtime) signature self-invalidation)
- download_fp8.py: --dest / $GLM_DEST instead of a hardcoded I:\ path
- Makefile: opt-in LTO=1 (off by default: cross-TU inlining can change
  FP contraction and the token-exactness oracles are validated against
  the default build's regime, JustVugg#1044/JustVugg#1024)
- tests: test_rope_invfreq (15.3M float pairs, 0 bit diffs),
  test_grammar_cache (ASan/UBSan ownership), test_analysis_cache
zh-Processor added a commit to zh-Processor/colibri that referenced this pull request Aug 16, 2026
- colibri.c: cache RoPE inv_freq keyed on (theta,qk) instead of rerunning
  half x powf per position; parallelize the prefill RoPE/KV-write loop on
  CPU-only builds (iterations write distinct KV rows, rope cache is
  _Thread_local; CUDA/Vulkan shadow-shrink keeps GPU builds serial);
  cache the compiled grammar per serve slot when the identical schema
  text is resent (grammar_reset == fresh setup end state)
- deepseek_v4.c: stack routing buffers (<=512 experts, malloc fallback
  beyond); COLI_MODEL_DIRS multi-directory shard lookup via st_init_multi
- resource_plan.py: sidecar cache for analyze_model (atomic tmp+replace
  writes, (size,mtime) signature self-invalidation)
- download_fp8.py: --dest / $GLM_DEST instead of a hardcoded I:\ path
- Makefile: opt-in LTO=1 (off by default: cross-TU inlining can change
  FP contraction and the token-exactness oracles are validated against
  the default build's regime, JustVugg#1044/JustVugg#1024)
- tests: test_rope_invfreq (15.3M float pairs, 0 bit diffs),
  test_grammar_cache (ASan/UBSan ownership), test_analysis_cache
@JustVugg

Copy link
Copy Markdown
Owner

Status ping: this is currently dirty against dev, which has moved a good deal in the last day — the CUDA tier for DeepSeek V4 landed (#1054, #1055), the dual-SSD mirror (#988), and #1063 made model families registry-owned across coli / the gateway / doctor.py / resource_plan.py.

A rebase would put it back in the queue. I'm not asking as a formality — the change is still wanted, it just can't be reviewed or merged in this state.

If you'd rather not carry it any further, say so and I'll close it with the reasoning recorded, so the work is findable rather than silently dropped. Either answer is fine; what I'd like to avoid is it sitting here indefinitely because nobody said anything.

zh-Processor added a commit to zh-Processor/colibri that referenced this pull request Aug 17, 2026
- colibri.c: cache RoPE inv_freq keyed on (theta,qk) instead of rerunning
  half x powf per position; parallelize the prefill RoPE/KV-write loop on
  CPU-only builds (iterations write distinct KV rows, rope cache is
  _Thread_local; CUDA/Vulkan shadow-shrink keeps GPU builds serial);
  cache the compiled grammar per serve slot when the identical schema
  text is resent (grammar_reset == fresh setup end state)
- deepseek_v4.c: stack routing buffers (<=512 experts, malloc fallback
  beyond); COLI_MODEL_DIRS multi-directory shard lookup via st_init_multi
- resource_plan.py: sidecar cache for analyze_model (atomic tmp+replace
  writes, (size,mtime) signature self-invalidation; the non-serializable
  resolved_family registry object is rebuilt from a cheap resolve_model()
  on cache hits, with only the scan-derived indexer flag persisted)
- download_fp8.py: --dest / $GLM_DEST instead of a hardcoded I:\ path
- Makefile: opt-in LTO=1 (off by default: cross-TU inlining can change
  FP contraction and the token-exactness oracles are validated against
  the default build's regime, JustVugg#1044/JustVugg#1024)
- tests: test_rope_invfreq (15.3M float pairs, 0 bit diffs; built with
  -ffp-contract=off so the reference-vs-engine compare does not depend
  on contraction luck), test_grammar_cache (ASan/UBSan ownership),
  test_analysis_cache (temp dir resolved for macOS/Windows canonical paths)
zh-Processor added a commit to zh-Processor/colibri that referenced this pull request Aug 17, 2026
- colibri.c: cache RoPE inv_freq keyed on (theta,qk) instead of rerunning
  half x powf per position; parallelize the prefill RoPE/KV-write loop on
  CPU-only builds (iterations write distinct KV rows, rope cache is
  _Thread_local; CUDA/Vulkan shadow-shrink keeps GPU builds serial);
  cache the compiled grammar per serve slot when the identical schema
  text is resent (grammar_reset == fresh setup end state)
- deepseek_v4.c: stack routing buffers (<=512 experts, malloc fallback
  beyond); COLI_MODEL_DIRS multi-directory shard lookup via st_init_multi
- resource_plan.py: sidecar cache for analyze_model (atomic tmp+replace
  writes, (size,mtime) signature self-invalidation; the non-serializable
  resolved_family registry object is rebuilt from a cheap resolve_model()
  on cache hits, with only the scan-derived indexer flag persisted)
- download_fp8.py: --dest / $GLM_DEST instead of a hardcoded I:\ path
- Makefile: opt-in LTO=1 (off by default: cross-TU inlining can change
  FP contraction and the token-exactness oracles are validated against
  the default build's regime, JustVugg#1044/JustVugg#1024)
- tests: test_rope_invfreq (15.3M float pairs, 0 bit diffs; built with
  -ffp-contract=off so the reference-vs-engine compare does not depend
  on contraction luck), test_grammar_cache (ASan/UBSan ownership),
  test_analysis_cache (temp dir resolved for macOS/Windows canonical paths)
@JustVugg

Copy link
Copy Markdown
Owner

This PR just became more strategic than when it was opened, and we would like to get it in — could you rebase onto current dev?

Context: we have been auditing the expert hot path across engines, and the audit's conclusion matches what FUSED3 does — the win is fusing gate+up+silu into fewer passes while keeping bit-identical output. Your measured "40% less matmul time, bit-identical, off by default" on olmoe is exactly the shape we want to replicate on the GLM engine next, so having yours land first gives us a reviewed reference in-tree.

Two things while you rebase, if you don't mind:

  1. dev moved today (feat: distributed expert workers — rebased, fmt=6 rotation fixed, token-exact parity gate #1036, [deepseek_v4] performance: use coli_v4_route_bf16 in moe_token #1017, refactor(core): finish registry-owned family dispatch #1068 merged) — the conflict is likely trivial but real.
  2. If you have the A/B numbers handy (tok/s before/after on your box, and the bit-exactness check you used), putting them in the PR description helps the review go fast.

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.

2 participants