Skip to content

Laguna: mixed-precision quant support (APEX IQ4_XS/Q6_K, official BF16) + metadata-driven rope - #633

Open
jasontitus wants to merge 4 commits into
antirez:laguna-s2.1from
jasontitus:apex-pr-clean
Open

Laguna: mixed-precision quant support (APEX IQ4_XS/Q6_K, official BF16) + metadata-driven rope#633
jasontitus wants to merge 4 commits into
antirez:laguna-s2.1from
jasontitus:apex-pr-clean

Conversation

@jasontitus

Copy link
Copy Markdown

Laguna: mixed-precision quant support (APEX IQ4_XS/Q6_K, official BF16) + metadata-driven rope

Adds support for the mixed-precision Laguna S 2.1 exports that ds4 currently
rejects, closes the resulting prefill gap, and reports two artifact issues
found along the way.

Branch: laguna-apex-quants (four commits on top of laguna-s2.1).
All numbers measured on an M5 Max 128 GB, Metal backend, on this branch as
submitted
, so they should reproduce directly.

What this enables

Export Before After
Myric/Laguna-S-2.1-APEX-GGUF i-quality (68.9 GiB, 5.03 bpw) unsupported Laguna quantization layout marker q6_k loads and runs
poolside official Q4_K_Mx40-BF16x8 (89.4 GiB) routed bf16 rejected loads and runs
Any 1M-rope Laguna export expected context_length=262144 … got 1048576 loads

Commits

  1. d3935ba — APEX quant support. New IQ4_XS type end-to-end (block struct,
    codebook, CPU reference dequant, Metal QMV pair-swiglu/down kernels,
    mul_mm_id instantiations), a Q6_K routed pair-swiglu kernel, Q6_K
    attention/embedding/output acceptance, a third Laguna weight layout beside
    legacy and signal-q8, and Laguna context_length/rope scale+attn factors
    adopted from GGUF metadata after a coherence check
    (orig_ctx × factor == context_length) instead of pinning one export.
  2. 0f2d58d — first optimisations. Batched Q6_K dense matmuls routed
    through the tiled GEMM instead of a per-token QMV; vectorised IQ4_XS QMV
    inner loops.
  3. 552bc4c — close the prefill gap. Vectorised the dequantize_q6_K /
    dequantize_iq4_xs tile-staging hooks (word loads, branchless extraction,
    identical per-element arithmetic).
  4. 96a710c — bf16 routed experts for the official mixed-precision export.

Measured (vs the Q4_K_M export)

Perplexity: wikitext-style local corpus, 8160 scored tokens, ctx 8192.
Prefill: 15K-token prompt, 6 interleaved pairs with cooldowns.

Q4_K_M APEX i-quality
perplexity 14.098 13.265 (−5.9 %)
prefill 454 ±24 t/s 442 ±18 t/s (−2.6 % ±3.0 — parity)
decode 52–54 t/s parity
size 63.6 GiB 68.9 GiB
max context 262 144 1 048 576

The prefill deficit was −12.4 % ±1.7 before commit 552bc4c; the tile-dequant
hooks were essentially the whole gap, since they stage every mul_mm
(attention) and mul_mm_id (routed-expert) tile.

Two findings worth flagging

1. block_q6_K is 210 bytes, so its payload is not 4-byte aligned.
Consecutive blocks alternate between 0- and 2-mod-4 addresses, making ushort
the widest always-safe load. An earlier uint version of the dequant hook
silently misdecoded the misaligned half of the blocks: perplexity moved only
+0.25 %, but greedy generation degenerated into loops.
Teacher-forced scoring
barely notices; autoregressive decode compounds it. Every kernel change here is
gated on bit-exact perplexity (ppl=13.265259871 on this branch) rather than a tolerance, which
is what caught it. Alignment should be derived from the block stride, not the
field offset — block_iq4_xs (136 B) keeps 32-bit loads for that reason.

2. The three mixed-precision exports rank in a surprising order. Same
corpus, same settings:

export size perplexity
Q4_K_M 63.6 GiB 14.098
Myric/…-APEX-GGUF i-quality 68.9 GiB 13.265
poolside official Q4_K_Mx40-BF16x8 89.4 GiB 13.399

The community APEX build edges out the official mixed-precision build by
1.0 % while being 23 % smaller. The two recipes make opposite
bets: APEX raises precision on the edge layers (q5_k/q6_k on layers 1-9 and
38-47) and compresses the middle to iq4_xs, while the official build leaves
layers 1-39 at q4_k and raises only the last eight to bf16. On this corpus the
edge-weighted allocation wins, which is consistent with quantisation error
mattering more in early layers than late ones.

Sizing note for 128 GB machines: the official build plans 89.88 GiB at ctx
8192 and roughly 107 GiB at 256K, and Laguna has no --ssd-streaming
fallback (--ssd-streaming is not implemented for Laguna S 2.1 yet). It needs
a mostly idle machine — a first attempt with ~30 GiB held by other
applications thrashed and never became resident. Since it now ships under the
same laguna-s-2.1-Q4_K_M.gguf filename as the 63.6 GiB build it replaced,
users re-pulling "Q4_K_M" get a substantially larger model than before.

Notes for reviewers

  • Metal-4 tensor matmuls remain suppressed in the Laguna graph prefill path
    (existing ds4_gpu_set_tensor_matmul_suppressed(true) call and its
    route-stability comment). The Q6_K MPP direct-RHS instantiations added here
    are consequently dormant on that path by design; they engage wherever MPP is
    otherwise permitted.
  • The rope change validates rather than trusts: a GGUF whose
    context_length disagrees with original_context_length × factor is
    rejected with a diagnostic instead of loading.
  • APEX perplexity is bit-identical before and after the bf16 commit, i.e. the
    new type does not disturb existing paths.
  • Decode throughput across the three: official bf16 build 56.9 t/s, APEX
    51-57 t/s, Q4_K_M 52-54 t/s. bf16 needs no unpacking at all, so it is
    marginally the fastest per token despite being the largest to stream.

Related

  • Update Laguna to corrected GGUF revision #613 (corrected GGUF revision) — while testing this branch, the stale
    706fa69 revision pinned in download_model.sh reproduced the looping /
    "never emits the tool call" behaviour that revision e2ccc05 fixes. Weights
    are identical between the two (416-byte file delta; perplexity agrees to nine
    significant digits on the same build); only the chat template differs, by 121 lines including
    enable_thinking default falsetrue. With the stale artifact a thinking
    agent run never produced output; with the corrected one it succeeded in 3 of
    4 runs. Worth merging.
  • Fix Laguna KV ring commits for oversized prefills #614 (KV-ring commits for oversized prefills) — not exercised here, but
    sweep-style workloads prefill far past the 512-row window, so it likely
    affects the same users.

jasontitus and others added 4 commits July 30, 2026 16:55
Load and run the community APEX exports of Laguna S 2.1
(e.g. Myric/Laguna-S-2.1-APEX-GGUF): q6_k token embedding, attention,
and output head; q8_0 dense/shared experts; routed experts per-layer in
{iq4_xs, q5_k, q6_k}; and the full 1M-token yarn rope config.

- ds4.c: IQ4_XS tensor type (block struct, kvalues_iq4nl codebook, CPU
  reference dequant); routed-expert block support; a third "APEX" Laguna
  weight layout (marker: q6_k token_embd) beside legacy and signal-q8;
  Laguna context_length/rope scale+attn factors adopted from GGUF
  metadata after a coherence check (orig_ctx * factor == context_length)
  so the conservative 256K (factor 32) and full 1M (factor 128) exports
  both load; q6_k attention qkvg decode branch via the generic per-type
  matmul.
- metal/moe.metal: kernel_glm_iq4_xs_pair_swiglu_f32,
  kernel_glm_iq4_xs_down_f32, and kernel_glm_q6_K_pair_swiglu_f32
  (sub-block-per-thread simdgroup QMV, constant-memory codebook LUT,
  hoisted per-block scales); dequantize_iq4_xs template hook plus
  mul_mm_id instantiations (f32/f16/ff32, and pair_swiglu_f16 for q6_K
  and iq4_xs) for the prefill GEMM path.
- metal/get_rows.metal: kernel_get_rows_q6_K_f32 for the q6_k embedding.
- ds4_metal.m: IQ4_XS enum, new pipelines wired into creation/teardown
  and the required-pipeline check, gate-pair/down type whitelists, both
  QMV dispatch sites (pipeline pick, grid sizing, profile labels),
  mul_mm_id per-type switches, get_rows q6_K dispatch, and a Q6_K case
  in ds4_gpu_quant_row_bytes (batched embedding sizing).

Measured on an M5 Max 128 GB vs the Q4_K_M export (ctx 8192, greedy,
same corpus): perplexity 13.267 vs 14.027 (-5.4%); decode 50.7 vs
~57 t/s (-11%); prefill 108-119 vs ~190 t/s (-40%, dominated by the
unfused q6_k attention projections -- clear optimization headroom).
The 1M rope export also lifts Laguna's 256K session ceiling, making
--think-max (ctx >= 393216) reachable on this model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115FzobY8yCyKaughKNLQ1f
Two follow-ups to the APEX enablement, measured on an M5 Max 128 GB
against the same-binary pre-optimization baseline (greedy, interleaved
arms):

- Route batched q6_K dense matmuls (n_tok >= 32) through the tiled
  mul_mm GEMM instead of the per-token QMV, which re-read the whole
  weight matrix once per token.  One new kernel_mul_mm_q6_K_f32
  instantiation of dense.metal's existing template (moe.metal follows
  it in the concatenated library, so its block_q6_K/dequantize_q6_K
  are in scope).  APEX attention prefill at 8K tokens now runs within
  11% of the Q4_K_M export (376-391 vs 430-434 t/s) where the QMV
  path had been the dominant prefill cost.
- Vectorize the iq4_xs pair-swiglu and down QMV inner loops: four
  uint32 weight loads per 16-byte slice (was 16 byte loads per
  pointer) with in-register nibble extraction, float4 activation
  loads.  Decode reaches effective parity with the Q4_K_M export
  (~51-57 vs 52-54 t/s; pre-optimization 50.6-50.7).

Perplexity gate on the fixed corpus: 13.100 post vs 13.267 pre (the
small shift comes from the GEMM's f16-staged accumulation, the same
numerics class as the existing q8_0/q4_K dense GEMM paths; Q4_K_M
reference 14.027).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115FzobY8yCyKaughKNLQ1f
…oads

The dequantize_q6_K/dequantize_iq4_xs template hooks stage every
attention (mul_mm) and routed-expert (mul_mm_id) GEMM tile for the APEX
export; their scalar byte loads and branchy quarter decode accounted for
nearly all of the export's prefill deficit.  Rework both hooks with
word loads and branchless extraction, keeping the per-element arithmetic
byte-for-byte identical.

Alignment note: block_q6_K is 210 bytes, so consecutive blocks alternate
between 0- and 2-mod-4 addresses — 16-bit loads are the widest
always-aligned access for its payload.  (An earlier uint version of this
hook silently misdecoded the misaligned half of the blocks: teacher-
forced perplexity moved only +0.25%, but greedy generation degenerated —
caught by the bit-exactness gate, which reproduces ppl=13.100008639
exactly for the shipped form.)  block_iq4_xs is 136 bytes (0 mod 4), so
its hook keeps 32-bit loads.

Also adds Metal-4 MPP direct-RHS instantiations for the q6_K dense GEMM
with an aligned-prefix/remainder split in the host dispatch.  They are
dormant in the Laguna graph path, which deliberately suppresses tensor
matmuls for prefill route stability, but engage wherever MPP is
permitted.

Measured (M5 Max 128 GB, 15K-token prompt, 6 interleaved pairs with
cooldowns): APEX prefill 442 t/s vs Q4_K_M 454 t/s — -2.6% +-3.0%,
parity within noise, from -12.4% +-1.7% before this change.  Decode
unchanged (parity), perplexity unchanged (13.100 vs Q4_K_M's 14.027).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115FzobY8yCyKaughKNLQ1f
Poolside replaced laguna-s-2.1-Q4_K_M.gguf at repo main with a
mixed-precision build (HF fc4e481, "Q4_K_Mx40-BF16x8"): the existing
signal-q8 recipe -- q8_0 embedding/attention/output/dense/shared, q4_k
routed experts on layers 1-39 -- plus bf16 routed experts on layers
40-47.  Everything but the bf16 experts already loaded, so this adds
just that type:

- ds4.c: bf16 as a routed-expert type (2 bytes, 1 element per block)
  and in the Laguna layout validator's per-layer routed check.
- metal/moe.metal: dequantize_bf16_16 tile-staging hook with
  mul_mm_id f32/f16 instantiations for prefill, plus
  kernel_glm_bf16_pair_swiglu_f32 / kernel_glm_bf16_down_f32 for
  decode.  bf16 -> f32 is a 16-bit left shift into the f32 bit
  pattern; there are no blocks or scales to unpack.
- ds4_metal.m: type enum, pipelines (creation, required-pipeline
  check, teardown), gate/down type whitelists, both QMV dispatch
  sites with grid sizing and profile labels, mul_mm_id switches.

Verified on an M5 Max 128 GB: the model loads (89.43 GiB resident,
89.88 GiB planned at ctx 8192, ~10 s residency), generates coherently,
and scores perplexity 13.548 on the same corpus where the plain Q4_K_M
export scores 14.026 and the community APEX i-quality export scores
13.100.  Decode 56.9 t/s -- the fastest of the three, since bf16 needs
no unpacking.

Two practical notes for anyone on a 128 GB machine: the file is 89.4
GiB against the previous 63.6 GiB under the same name, so it needs a
mostly idle machine (a first attempt with ~30 GiB held by other
applications thrashed and never became resident), and Laguna has no
--ssd-streaming fallback ("--ssd-streaming is not implemented for
Laguna S 2.1 yet") if it does not fit.  At 256K context it would plan
~107 GiB, which leaves very little headroom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115FzobY8yCyKaughKNLQ1f
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