Skip to content

Run an affine qmv_wide verify of 6 or 7 rows as one tile (gemma4-qat #191) - #9

Merged
jeethu merged 5 commits into
prism-0.32.2-fixesfrom
fork/191-qmv-wide-8
Oct 5, 2026
Merged

jeethu merged 5 commits into
prism-0.32.2-fixesfrom
fork/191-qmv-wide-8

Conversation

@jeethu

@jeethu jeethu commented Oct 4, 2026

Copy link
Copy Markdown
Member

Part of numen-tech/gemma4-qat#191 (epic numen-tech/gemma4-qat#178). Fork only; no upstream PR.

What

qmv_wide (quantized.cpp) runs an affine call of M = 6 or 7 input vectors as one tile (vecs_per_tg = M). Main's cap of 5 split them 3 + 3 and 4 + 3, and each tile re-reads every weight.

A speculative verify with k drafts runs M = k + 1 rows. The shipped k = 6 verifies M = 7, so after this change it streams the weights once instead of twice.

  • Unchanged: every other M and every other mode keep the cap-5 tiling and their numerics.
  • M = 8 stays 4 + 4. On the M3 Max, a tile of 8 costs 10–16% more than a tile of 7, the same occupancy cliff that makes MLX's dense gemv_wide cap at 5.
  • Where it applies: affine qmv_wide only runs on GPU gen ≥ 15 (use_qmv_wide). For K, N in the shipped range, gen-15+ get_qmv_batch_limit is ≥ 12, so M = 6/7 stays on qmv_wide.
  • quantized.metal: instantiates affine_qmv_wide (plain and _ib, batched and not) for tile sizes 6 and 7.
    • Codex checked that all 324 newly reachable variants have a matching instantiation.
    • Cost: about a third more affine_qmv_wide variants in the metallib. Trimming them to the shipped widths would need a second host-side predicate, so I left them all in.
  • Tests:
    • a C++ doctest: 2/3/4/6/8-bit, explicit and implied bias, fp16/bf16/fp32, K = 512/1536, M = 6/7/8;
    • a Python test, which also covers M = 11/12 for the unchanged cap.
    • Both assert that one M-row call is bit-identical to the same rows run in chunks of at most 5, one tile each. The doctest also checks against an fp32 dequantize-then-matmul reference.

Numerics

A vector's accumulation does not depend on which tile it sits in, so outputs are bit-identical to the old split. End to end at k = 6, greedy output is byte-identical on E2B, E4B and the 12B.

Verification

  • C++ doctest: 290/290 test cases, 5,660 assertions.
  • Python test_quantized.py: 56 passed, 7,900 subtests.
  • Codex adversarial review: no findings.
  • /simplify pass: folded the doctest's per-group random draws into a lambda.

The tok/s and verify-cost evidence is on the gemma4-qat PR.

Merge order

  1. this PR;
  2. numen-tech/mlx-swift: re-point Source/Cmlx/mlx at the merged prism-0.32.2-fixes commit and regenerate;
  3. numen-tech/gemma4-qat: re-pin Package.resolved.

jeethu added 2 commits October 4, 2026 21:51
affine qmv_wide tiled at most 5 input vectors per weight pass, so a
speculative verify of 6 or 7 rows (gemma4-qat k = 6 verifies M = 7) ran
two tiles and streamed every weight twice. affine M = 6..7 is now one
tile. A vector's accumulation does not depend on the tile, so the output
bits match the old split. The implied-bias (_ib) path is the same
templated kernel and needs no kernel change.

Every other M and mode keeps the cap of 5 byte for byte: M = 8 stays
4 + 4 and M = 11/12 stay three tiles. On M3 Max a tile of 8 costs 10-16%
more than a tile of 7, which made M = 8 break-even or worse against
4 + 4; MLX's dense gemv_wide also caps at 5 for occupancy. affine
qmv_wide runs only on gen-15+ (use_qmv_wide); the fp modes run on every
generation and are unchanged.

The non-JIT library instantiates affine_qmv_wide and its _ib variant for
tiles 6 and 7. A C++ doctest and a Python test check that M rows in one
call match the same rows in chunks of at most 5, with explicit biases
and the implied-bias factor.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-05T03:18:59.804641Z d5da054 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c74cbf19da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mlx/backend/metal/quantized.cpp Outdated
Comment on lines +642 to +647
// affine (gen-15+ only, see use_qmv_wide) runs M = 6..7 as one tile, so a
// speculative verify of up to 7 rows streams the weights once; a vector's
// accumulation does not depend on the tile, so the bits match the split.
// Every other M and mode keeps the cap of 5. M3 Max: a tile of 8 costs
// 10-16% more than a tile of 7 (MLX's dense gemv_wide caps at 5 for
// occupancy), so M = 8 stays 4 + 4.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Shorten the qmv_wide implementation comment

Reduce this six-line comment to the essential invariant. The code already shows the affine limit and the surrounding function explains tile selection, while the speculative-decoding and benchmark details add excessive inline commentary that the repository standards explicitly prohibit.

AGENTS.md reference: AGENTS.md:L29-L33

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d23d9b96d8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread python/tests/test_quantized.py Outdated
Comment on lines +888 to +892
# affine qmv_wide runs M = 6..7 as one tile; every other M keeps the
# cap of 5 (8 is 4 + 4, 11 is 4 + 4 + 3, 12 is 4 + 4 + 4). A vector's
# accumulation does not depend on the tile, so one call over M rows
# must match, bit for bit, the same rows run in chunks of at most 5
# (one tile each); the tiling itself is not observable from here. All

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Shorten the Python qmv_wide test comment

Reduce this seven-line block to the essential assertion invariant. It repeats the split values defined immediately below and includes internal routing details that the test cannot observe, contrary to the repository requirement to keep comments concise and avoid redundant inline commentary.

AGENTS.md reference: AGENTS.md:L31-L32

Useful? React with 👍 / 👎.

Comment thread tests/ops_tests.cpp Outdated
Comment on lines +3755 to +3759
// affine qmv_wide runs M = 6..7 as one tile; M = 8 keeps main's 4 + 4. A
// vector's accumulation does not depend on the tile, so each call must match,
// bit for bit, the same rows run in chunks of at most 5 (one tile each), and
// stay close to an fp32 dequantize-then-matmul reference. The tiling itself is
// not observable from here. Explicit biases and the 0-d implied-bias factor.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Shorten the C++ qmv_wide test comment

Reduce this five-line comment to the essential test invariant. The test name, split table, bitwise comparison, and reference calculation already show these details, so the block adds redundant commentary beyond the repository's usual one-to-two-line limit.

AGENTS.md reference: AGENTS.md:L31-L32

Useful? React with 👍 / 👎.

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