Skip to content

cfg_sane: bound Qwen shared_inter, ngram_size and linear_conv_kernel_dim (#69) - #2

Closed
mfethe1 wants to merge 2 commits into
Skibisky:feature/qwen38-flash-nextfrom
mfethe1:exp365/cfg-sane-bounds
Closed

mfethe1 wants to merge 2 commits into
Skibisky:feature/qwen38-flash-nextfrom
mfethe1:exp365/cfg-sane-bounds

Conversation

@mfethe1

@mfethe1 mfethe1 commented Sep 16, 2026

Copy link
Copy Markdown

Closes the three shapes from sqliteai#69 on this branch, one cfg_sane bound and one qwen_refused case each — the same shape as #1.

What is bounded, and the evidence for each

1. shared_expert_intermediate_size — heap write past m->ff
m->ff is sized from max(dense_inter, moe_inter) while qwen_moe_layer batches the shared expert's gate+up into it at shared_inter. Verified with a tensor-consistent container (shared 512 over moe 16, the three shared_expert.* tensors shaped from 512): base accepts it (rc 0). Marco's ASan trace in sqliteai#69 is the write; the bound is shared_inter <= max(dense_inter, moe_inter), as sqliteai#69 proposes. Flash-Next fits exactly (640 = 640), which is why nothing noticed.

2. ngram_size == 1 — x86 SIGFPE
Reproduced independently before fixing: the ngram=1 container (PLE tensors shaped for the widest legal interpretation so no shape check can refuse it first) loads fine on arm64 (sdiv quietly yields 0) and dies rc=136 (SIGFPE/#DE) on an x86_64 build under Rosetta. Bounded to ngram_size >= 2.

3. linear_conv_kernel_dim == 0 — negative GDN ring offset
The generic conv_k < 0 bound permits 0; with KS=0 the conv ring is empty and gdn_conv_range computes ring + b * (KS - 1). Qwen states its own floor (>= 1).

Evidence

  • Red-before / green-after per container on the branch head (8d5405d):
    container base fixed
    shared 512 / moe 16 (tensors consistent) accepted refused
    ngram_size 1 arm64 accepted-for-wrong-shape; x86 rc=136 refused
    linear_conv_kernel_dim 0 accepted refused
    unmodified fixture loads loads, logits bit-identical
  • Mutation checks: disabling each bound alone (clean rebuild each time) re-admits exactly its own container and no other.
  • tests/run.sh on the fixed tree: 93 passed, 0 failed, 17 skipped (base: 89 — the delta is the four new refusals, all PASS).
  • make asan: 90/1/19 — the single failure (QSA's attention differs from the loops it replaced) is identical on the unmodified base, i.e. pre-existing, not introduced here.
  • Valid-container logits bit-identical: sha256 3e4a4a70… on base and fixed, steps 3, 7, 11.

The suite cases are manifest-only edits in the existing qwen_refused idiom; the tensor-consistent containers that carry the ASan/SIGFPE evidence are described above for reproduction.

AI-agent-assisted (Hermes), posted with human authorization.

mfethe1 added 2 commits September 16, 2026 16:25
…dim (sqliteai#69)

Three shapes a container controls that cfg_sane did not bound, each
reachable from a manifest (sqliteai#69):

- shared_expert_intermediate_size batches the shared expert's gate and
  up projections into m->ff, sized from max(dense_inter, moe_inter)
  only. shared 512 over moe 16 writes past the buffer under ASan and
  runs to completion without it. Flash-Next fits exactly (640 = 640),
  which is why nothing noticed.

- ngram_size 1 makes validate_qwen_tensors divide by (ngram_size - 1)
  * heads_per_ngram: arm64 sdiv quietly yields 0, x86 idiv raises #DE
  before the shape check. Bounded to >= 2.

- linear_conv_kernel_dim 0 sizes the GDN conv ring as (k - 1)
  elements: empty ring, negative gdn_conv_range offset. The generic
  conv_k bound allows 0; Qwen states its own floor.

One qwen_refused case per bound in tests/run.sh. Valid-container
logits bit-identical (sha256 3e4a4a70... base vs fixed, steps 3,7,11);
suite 93 passed / 0 failed / 17 skipped (89 -> 93, the four new
refusals). make asan: same single pre-existing QSA failure as base.
…ntainer

The shared_inter upper bound this branch added was wrong. It refuses
Qwen2-57B-A14B, which ships shared_expert_intermediate_size 20480 against
max(intermediate_size 18944, moe_intermediate_size 2560) -- a published
checkpoint the guard would lock out. Our own audit of sqliteai#63 found this while
reviewing the same bound proposed there, so the bound is withdrawn here.

The underlying defect is real, but it is an allocation bug, not a manifest
one: the shared expert batches its gate and up projections into m->ff,
which was sized from the dense and routed widths only, so a container whose
shared width exceeds both writes past the buffer. Flash-Next fits exactly
(640 == 640), which is why nothing noticed. m->ff now takes the maximum of
the dense, routed and shared widths, so the wide shape loads and decodes.

The two other bounds stand and are unchanged: ngram_size >= 2 guards the
divide at the PLE width computation, and linear_conv_kernel_dim >= 1 guards
the GDN conv ring allocation.

Proven, not asserted. tools/make_test_container.py sized the shared-expert
tensors from moe_intermediate_size, so a manifest-only edit could never
exercise this path; it now follows shared_expert_intermediate_size and
takes --shared-inter. With the fix reverted, a decode step over a wide
container aborts under AddressSanitizer with a heap-buffer-overflow, WRITE
of size 4 past m->ff; with the fix it completes. tests/wide_step.c drives
that step through waste_eval with raw token ids, since waste info only
reads the manifest and never allocates. Suite: 93 passed, 0 failed.
@mfethe1

mfethe1 commented Sep 18, 2026

Copy link
Copy Markdown
Author

Correcting this branch: the shared_inter upper bound it proposed is wrong, and I withdrew it in c0c4bb7.

While auditing the same bound in sqliteai#63 I checked it against published configs. Qwen/Qwen2-57B-A14B/config.json ships:

field value
shared_expert_intermediate_size 20480
intermediate_size 18944
moe_intermediate_size 2560

20480 > max(18944, 2560), so the guard refuses a shipped checkpoint. Qwen1.5-MoE-A2.7B and Qwen3-Next-80B pass. That is a false rejection, and it was my bound, so it goes.

The defect it was aiming at is real, but it is an allocation bug rather than a manifest one. The shared expert batches its gate and up projections into m->ff, which was sized from the dense and routed widths only, so a container whose shared width exceeds both writes past the buffer. Flash-Next fits exactly (640 == 640), which is why nothing noticed. The fix sizes m->ff from max(dense, routed, shared), so the wide shape loads and decodes instead of being refused.

Red-before / green-after, under AddressSanitizer. Two things had to be fixed before the bug was even reachable:

  • tools/make_test_container.py sized the shared-expert tensors from moe_intermediate_size, so no manifest-only edit could exercise this path. It now follows shared_expert_intermediate_size and takes --shared-inter.
  • waste info only reads the manifest and never allocates, so it cannot observe the overflow. tests/wide_step.c drives a real decode step through waste_eval with raw token ids, which needs no tokenizer.

With the allocation fix reverted, that step aborts:

ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 4 at 0x60c000000480
SUMMARY: AddressSanitizer: heap-buffer-overflow simd_i8mm.c:73 in waste_mvq4_rows_i8mm

With the fix it completes (step ok, vocab 256). Suite: 93 passed, 0 failed, 17 skipped, clean build, no new warnings.

The other two bounds stand, unchanged. I re-checked both rather than assuming: ngram_size >= 2 guards the divide at model.c:1542 (ple_embed / ((ngram_size - 1) * heads_per_ngram)), and linear_conv_kernel_dim >= 1 guards the calloc of the GDN conv ring at model.c:3178. I verified the ngram bound still refuses ngram_size=1.

The SIGFPE at model.c:1541 remains unreproduced here — the generator emits no PLE tensor set, so validation aborts at ple.key_proj.weight before the division is reached.

@mfethe1

mfethe1 commented Sep 19, 2026

Copy link
Copy Markdown
Author

Closing this — your branch already carries all three bounds, and in a better form than this PR proposes.

Verified against 46721e9 rather than assumed, on a fresh build of your branch with an unmodified fixture:

  • ngram_size = 1 → refused by cfg_sane (manifest config is out of range)
  • linear_conv_kernel_dim = 0 → refused by cfg_sane

So both guards this PR adds are already live. The third is the interesting one.

The shared-expert bound this PR opened with was wrong, and your branch is right not to carry it. An upper bound on shared_expert_intermediate_size locks out Qwen2-57B-A14B, which ships 20480 against max(intermediate_size 18944, moe_intermediate_size 2560) — a published checkpoint. The real defect was never a manifest one: the shared expert batches its gate and up projections into m->ff, which was sized from the dense and routed widths only. Your branch fixes it at the allocation (m->ff and the nmax path now take the shared width into account) and keeps the bound generous at 1 << 20. That is the correct split — the wide shape loads and decodes instead of being refused.

Rebasing onto 46721e9 conflicts in tests/run.sh for exactly this reason: your side asserts the wide shared expert opens and runs, mine asserted it was refused. Yours is the correct assertion, so there is nothing here worth carrying forward and a rebase would only reintroduce a claim we both now know is wrong.

One thing I could not settle from reading alone and am not claiming: the 512 case in my original fixture is refused on your branch by a missing-tensor error rather than by cfg_sane, so it is a fixture artifact and says nothing about the bound either way.

Withdrawing, no action needed. sqliteai#69 is covered on your branch.

@mfethe1 mfethe1 closed this Sep 19, 2026
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