Last updated: 2026-07-19

Verifying PR #25 on Qwen 3.6 27B#

PR #25 chunks long prompts through the layer-major batched prefill on Apple Silicon/Metal. The 9B half is verified (see the PR description); the 27B half is only architecturally reasoned about — it needs to actually run on a machine with enough disk and unified memory for the 27B model before merging.

Hardware requirement, found by trying this on a 16 GiB Mac: disk space alone is not enough. The Q4_K_M weights are ~16 GB by themselves, and Metal caps usable GPU memory at 85% of recommendedMaxWorkingSetSize (see memoryPlanningBudget in forward_metal.zig). On a 16 GiB machine that budget comes out to ~10 GB — smaller than the weights alone — so the engine fails fast with ContextLengthDoesNotFit before any prompt is even processed, regardless of -c. This isn't a PR #25 bug; the same failure happens on an unpatched main build. You need a Mac with meaningfully more unified memory than the model's own weight size — 32 GB is the practical floor, 64 GB+ gives comfortable context headroom.

This is the same verification loop that caught a real bug on the 9B side (a KV-cache byte/block offset mismatch that produced fast, wrong output), adapted for the 27B's numbers. PR25 and main below mean: build the metal/qwen9b-chunked-prefill branch, and build main (or the commit right before PR #25) as the baseline to diff against.

1. Build two binaries#

git fetch origin
git worktree add /tmp/zinc-baseline main
git worktree add /tmp/zinc-pr25 metal/qwen9b-chunked-prefill

cd /tmp/zinc-baseline && zig build -Doptimize=ReleaseFast
cd /tmp/zinc-pr25     && zig build -Doptimize=ReleaseFast

Keep both zig-out/bin/zinc binaries around; you'll run both.

2. Get the model#

/tmp/zinc-pr25/zig-out/bin/zinc model pull qwen36-27b-q4k-m

~16.8 GB download, cached under ~/.cache/zig/zinc / ~/Library/Caches/zinc (both binaries share the same cache, so pull once).

Before running anything, sanity-check the machine can actually load the model at all:

/tmp/zinc-pr25/zig-out/bin/zinc --model-id qwen36-27b-q4k-m --prompt "Hello" -n 5

If this fails with ContextLengthDoesNotFit / No decode context fits within N GiB Metal planning budget, the machine doesn't have enough unified memory for this model — see the hardware requirement note above. No further steps here will work until that's resolved; this is unrelated to PR #25.

3. Prompt lengths to test#

The 27B's own single-shot batched-prefill ceiling is 40 tokens (not 256, like the 9B) — a separately-validated number this PR didn't touch, just chunks around. Test lengths that specifically exercise the new chunking logic:

length why
45 one chunk (40) + a 5-token remainder — below the 27B's own 32-token minimum, must fall back to per-token decode for that remainder
80 exactly two full 40-token chunks, no remainder
90 two full chunks + a 10-token remainder (same fallback case as 45, at a later position)
200 five chunks, stresses repeated position-carry across many boundaries

Generate a prompt of a given token count with something like:

python3 -c "print('The quick brown fox jumps over the lazy dog. ' * 20)"   # ~180 words ≈ tune to hit the target token count

Token count isn't exact from word count — check the actual count in the Prefill: N tokens in ... log line and adjust the repeat count until you land close to the target (within a few tokens is fine; the point is crossing 40/80 boundaries, not hitting them exactly).

4. Compare outputs (must be byte-identical)#

For each length:

PROMPT="..."   # from step 3

/tmp/zinc-baseline/zig-out/bin/zinc --model-id qwen36-27b-q4k-m \
  --prompt "$PROMPT" -n 32 > /tmp/base_out.txt 2>&1

/tmp/zinc-pr25/zig-out/bin/zinc --model-id qwen36-27b-q4k-m \
  --prompt "$PROMPT" -n 32 > /tmp/pr25_out.txt 2>&1

diff <(grep Output /tmp/base_out.txt) <(grep Output /tmp/pr25_out.txt) \
  && echo "IDENTICAL" || echo "DIFFER — stop here, this is a real bug"

If any length differs, do not merge — that's exactly the shape of bug PR #25 already found once on the 9B.

5. Cross-chunk recall (proves chunk N attends chunk 1's KV, not just that tokens decode)#

PROMPT=$(python3 -c "
filler = 'The museum catalog lists many unremarkable exhibits from the early colonial period, including pottery shards, farming tools, and faded textiles. ' * 20
print('Important: the secret code is 7391. Remember it. ' + filler + ' Question: what is the secret code mentioned at the very beginning? Answer with just the number:')
")

/tmp/zinc-baseline/zig-out/bin/zinc --model-id qwen36-27b-q4k-m --prompt "$PROMPT" -n 16
/tmp/zinc-pr25/zig-out/bin/zinc     --model-id qwen36-27b-q4k-m --prompt "$PROMPT" -n 16

Both should answer 7391, and the two outputs should match. Adjust the * 20 multiplier so the prompt lands comfortably over 80–120 tokens (at least two chunk boundaries between the planted fact and the question).

6. Confirm the fast path actually engaged (not a silent fallback)#

/tmp/zinc-pr25/zig-out/bin/zinc --model-id qwen36-27b-q4k-m \
  --prompt "$PROMPT" -n 8 --profile 2>&1 | grep 'layer-major prefill:'

Expect reason complete and layers <n_layers>/<n_layers> (all layers materialized, no replay_layer_tokens). One naming quirk: this log line always says qwen35-9b layer-major prefill: even when the loaded model is the 27B — that's a pre-existing label from when this code path was 9B-only, not a sign you're on the wrong model. Ignore the "9b" in the text; check layers/reason instead.

7. Throughput (optional but useful)#

/tmp/zinc-baseline/zig-out/bin/zinc --model-id qwen36-27b-q4k-m --prompt "$PROMPT" -n 4 2>&1 | grep Prefill
/tmp/zinc-pr25/zig-out/bin/zinc     --model-id qwen36-27b-q4k-m --prompt "$PROMPT" -n 4 2>&1 | grep Prefill

Expect a real speedup at 80+ tokens (baseline falls to per-token replay past 40 tokens today; PR #25 keeps it batched). Not required for correctness, but a large regression here would also be worth reporting.

Reporting back#

Paste the diff results from step 4, the two recall answers from step 5, and the --profile line from step 6 into the PR thread. If everything matches, that's what unblocks merging the 27B half.