Skip to content

Proposal: opt-in HQQ 5-bit AXIS=0 MoE weights — −25% bytes, +36% E2E on disk-bound hardware, quality within +2.4% ppl of int8 #1009

Description

@outtodata

Proposal: opt-in HQQ 5-bit AXIS=0 MoE weights — −25% bytes, +36% E2E on disk-bound hardware, quality within +2.4% ppl of int8

TL;DR. A converter-side, opt-in 5-bit weight format for MoE expert FFNs (attention stays int8), using HQQ AXIS=0 grouping so scales/zeros are per input channel. On a Core i5-7300U with weights streamed from disk: snapshot 7.0 GB → 5.4 GB (−25%), end-to-end 2.42 → 3.28 tok/s (+36%, medians of 3; up to 2.46× in more disk-bound conditions), HF-corpus perplexity 14.2420 vs 13.9144 int8 control (+2.36%), greedy token streams bit-identical to the int8 engine in all 8 test runs. Everything sits behind bits==5 in the converter + a snapshot field; default builds and int8 snapshots are untouched. Related: the axis finding in [issue draft: HQQ axis asymmetry].

Motivation. On disk-bound CPUs the expert pipe, not matmul, is the wall (profiled: pipe_wait ≈ 45% of decode time at Q8_0). The cheapest FLOP is the one you never load: cutting weight bytes 25% cuts pipe traffic 25%. But 5-bit only works if the grouping axis is chosen correctly — HQQ's AXIS=1 mixes 32 heterogeneous input channels under one scale and costs +9% ppl (separate issue); AXIS=0 keeps scales per input channel and lands at +2.4%.

Design (mirrors the existing i4 pattern)

  • Converter (convert_olmoe_hqq5.py): explicit --bits 5 choice at conversion time, per the repo's "quantization choice lives in the converter" policy. Writes merged_weight5_ax0 tensors + a snapshot header field. Weight layout is the natural flat order of the HQQ q tensor (no repacking needed — AXIS=0 groups are strided slabs, so the packed stream is identical in form to the axis=1 variant: 20 B per 32 weights = 16 B nibbles + 4 B hi-plane). Meta (scales|zeros) is contiguous per row, one 64 KB slab per 32 rows.
  • Kernel (dotrow_i5ax0_avx2 / matmul_i5ax0_avx2 in c/fused_simd.h): float FMA with per-element dequant from a hoisted per-group scale/zero. Microbench: 0.145 ms/row-equivalent vs 0.137 for int8 IDOT — parity (±10% run-to-run noise).
  • Dispatch (olmoe.c): on snapshot load, presence of merged_weight5_ax0 sets m->i5=2; runtime prints a MODE I5AX0 ... banner; missing AVX2 → hard refusal; int8 snapshots → today's path, untouched. Fallback to int8 tensors if both present.

Measured (i5-7300U, production flags; medians of 3 E2E reps)

Metric int8 (i5=0) i5ax0 Δ
Snapshot size 7.0 GB 5.4 GB −25%
E2E tok/s 2.42 3.28 +36% (up to 2.46× when more disk-bound)
pipe_wait (decode) 10.55 s 5.77 s −45%
pipe_io / sync_load — — −34% / −35%
time in matmul — — +14% (float vs IDOT — the trade)
HF-corpus ppl (harness-verified) 13.9144 14.2420 +2.36%
Engine TF-NLL on same corpus 7.7141 7.5187 −2.5% (consistent)
task_acc 0.8889 0.8889 0
Greedy stream vs int8 ref bit-identical, 8/8 runs —

How this answers the review gates from #906

  • (a) off by default, loud when on: nothing changes unless the converter is run with --bits 5 and the resulting snapshot is loaded. Loading such a snapshot prints a MODE I5AX0 banner naming the format, axis, and group size; per-layer [PROF] counters show which layers took the i5 path.
  • (b) explicit conversion-time choice: the int8 path is byte-for-byte untouched; 5-bit is a snapshot property, not a runtime flag. A snapshot declares what it is; the engine never silently approximates.
  • (c) token-exact when off: int8 snapshots produce bit-identical streams to master (verified by md5 across runs). Determinism within the 5-bit path also holds (8/8 identical greedy streams).

Caveats (honest list)

  • Quality cost is real: +2.36% ppl on the HF corpus. That's the price of −25% bytes; the axis=1 alternative (+9%) is strictly worse. Per the warmup-thread policy this belongs to the user at conversion time, and the proposal keeps it there.
  • Float-FMA dequant kernel is ~14% slower per FLOP than IDOT; net win exists only when disk-bound (which is the regime this repo targets). On NVMe the gain shrinks.
  • AXIS=0 quality depends on the flattening convention of the source checkpoint: per-input-channel scales are only meaningful if axis 0 of the HQQ tensor is the output dimension (true for these safetensors shards; the converter asserts it).
  • One machine tested (i5-7300U). No NEON kernel yet.

Happy to open this as a PR (converter + kernels + dispatch + harness, ~1k lines) or to split it (converter RFC first, kernels second). Also happy to run any eval corpus you point us at before anyone spends review time.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestperformanceVelocità / tok-s / ottimizzazioniqualityQualità del modello / quantizzazione

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions