Status: FIXED. Loader now parses rms_norm_eps and the structured
rope_scaling field from config.json; the three architectures that
exposed bugs (Llama 3.2, Mistral 7B, Gemma 3 4B) all match HF/MLX to
within 0.003%. Last updated 2026-05-16.
scripts/diagnose_models.py now passes 4/4 with zero env-var
overrides — the production loader path is correct.
Empirical bisection identified three independent bugs in LARQL's
config-loading path. All three are now fixed in larql-models directly;
the env vars used to diagnose them stay in tree as instruments.
| # | Bug | Pre-fix gap | Permanent fix | Post-fix gap |
|---|---|---|---|---|
| 1 | rope_scaling per-layer-type (Gemma 3 applies linear factor only on full-attention layers) was not honoured |
Gemma 3 4B: +5.4% | parser.rs parses the structured {full_attention, sliding_attention} shape into RopeScaling { gemma3_global_only: true }; Gemma3Arch::rope_position_divisor_for_layer returns the factor on global layers, 1.0 on sliding |
+0.000% |
| 2 | rms_norm_eps from config.json was ignored — ModelArchitecture::norm_eps() hardcoded to 1e-6 |
Mistral 7B: +8.2%; Llama 3.2 1B: +0.59% (partial) | parser.rs parses rms_norm_eps / layer_norm_eps into ModelConfig.norm_eps; default norm_eps() reads the parsed value, falls back to 1e-6 only when absent. CPU forward callers in forward/{layer,ops}.rs use new rms_norm_for_arch helper |
+0.001% Mistral; +0.003% Llama (combined w/ bug 3) |
| 3 | rope_scaling = llama3 (wavelength-dependent per-channel factors) was not implemented |
Llama 3.2 1B residual after bug 2 | New Llama3RopeScaling type in larql-models/config.rs; LlamaArch::llama3_rope_scaling returns it when rope_type=llama3. attention/rope.rs has Llama3Scaling::apply (mirrors HF's _compute_llama3_parameters). Forward path goes through apply_rope_partial_at_full |
+0.003% (with bug 2 fix) |
All three are independent: each closes its specific gap in isolation, and they compose without interaction.
Verification: .venv/bin/python scripts/diagnose_models.py —
4/4 PASS at <0.5% delta, zero env-var overrides applied:
SmolLM2-135M llama (small, no SWA) 0.001% PASS
Llama-3.2-1B llama (llama3 rope_scaling) 0.003% PASS
Mistral-7B-v0.1 mistral (all SWA) 0.001% PASS
Gemma-3-4B-it gemma3 (mixed SWA + global) 0.000% PASS
+ Q4K Metal: Gemma 3 4B F32=1.0712 Q4K=1.4680 37.0% PASS
The five env vars used to localise the bugs remain available for future
bisection on new architectures or for verifying changes to the forward
path. All are gated via OnceLock and are no-ops when unset.
| Var | Purpose |
|---|---|
LARQL_FORCE_GLOBAL_LAYERS=all|<csv> |
Force listed layers onto the global rope_base. Useful for ruling out sliding-vs-global routing as a cause. |
LARQL_ROPE_POS_DIVISOR=<f64> |
Apply linear rope-scaling factor to every layer's positions. |
LARQL_ROPE_POS_DIVISOR_GLOBAL=<f64> |
Same but only on !is_sliding_window_layer(layer) layers. |
LARQL_LLAMA3_ROPE_SCALING=factor,low,high,old_ctx |
Force HF llama3 scaling params (overrides the arch's parsed value). |
LARQL_NORM_EPS_OVERRIDE=<f64> |
Override arch.norm_eps() (overrides config-parsed value). |
LARQL Rust's F32 forward pass produces systematically lower next-token
probability on Gemma 3 4B (+5.4% bits/char) and Mistral 7B v0.1 (+8.2%)
than two mutually-validated F32 references (HF/PyTorch and MLX, which
agree with each other to within 0.06% on every model tested).
Architecturally-simpler models are clean (SmolLM2-135M <0.01%, Llama-3.2-1B
+0.59%). The instrument that surfaced this is larql shannon verify.
The initial hypothesis was sliding-window attention. Empirical bisection ruled that out. The remaining suspects are arch-wide and need code-level inspection.
larql shannon verify MODEL --corpus FILE
Runs the LARQL Rust forward in-process and spawns the HF/PyTorch and MLX
reference scorers as Python subprocesses on the same corpus, with the
same windowing logic (context, stride). Prints a delta table and
exits non-zero if any pair-wise delta exceeds --threshold. ~17s on a 4B
model end-to-end on M3 Max.
Underlying scripts: scripts/shannon_score_mlx.py,
scripts/shannon_score_hf.py, and the existing larql shannon score.
CRLF and BOS gotchas across the three engines are documented in
scripts/README_shannon_score.md.
Corpus: 997 chars (1KB Frankenstein header, CRLF-normalized). All three engines at F32. Same context=512 / stride=256 windowing.
| Engine | total bits | bits/char | Δ vs HF |
|---|---|---|---|
| HF F32 (torch CPU) | 1068.0 | 1.0712 | — |
| MLX F32 | 1068.6 | 1.0718 | +0.06% |
| LARQL Rust F32 | 1126.3 | 1.130 | +5.4% |
| Engine | total bits | bits/char | Δ vs HF |
|---|---|---|---|
| HF F32 (torch CPU) | 508.86 | 0.5104 | — |
| MLX F32 | 508.86 | 0.5104 | <0.01% |
| LARQL Rust F32 | 550.4 | 0.552 | +8.2% |
| Engine | total bits | bits/char | Δ vs HF |
|---|---|---|---|
| HF F32 (torch CPU) | 579.39 | 0.5811 | — |
| MLX F32 | 579.40 | 0.5811 | <0.01% |
| LARQL Rust F32 | 582.8 | 0.585 | +0.59% |
| Engine | total bits | bits/char | Δ vs HF |
|---|---|---|---|
| HF F32 | 812.830 | 0.8153 | — |
| MLX F32 | 812.840 | 0.8153 | +0.001% |
| LARQL Rust F32 | 812.767 | 0.8152 | -0.008% |
| Model | Layer pattern | Hidden | LARQL Rust vs HF |
|---|---|---|---|
| SmolLM2-135M | 30 std, no SWA | 576 | <0.01% |
| Llama-3.2-1B | 16 std, no SWA | 2048 | +0.59% |
| Gemma 3 4B | 28 sliding + 6 global | 2560 | +5.4% |
| Mistral 7B v0.1 | 32 all-sliding | 4096 | +8.2% |
The two reference engines mutually validate to <0.1%. LARQL Rust is the unique outlier; the divergence is always in the same direction (lower probability on the true next token); magnitude scales with something that correlates with sliding-window architectures and/or model size.
A priori suspect: the sliding-window attention code path. Reasoning:
- Llama-class (no SWA): small gap.
- Gemma 3 (mixed SWA + global): larger gap.
- Mistral (all SWA): largest gap.
Diagnostic instruments added to the LARQL Rust forward path
(crates/larql-inference/src/layer_graph/pipeline_layer.rs,
attention/block.rs, attention/decode.rs, attention/rope.rs):
LARQL_FORCE_GLOBAL_LAYERS=all|<csv>— for the listed layer indices, forcesrope_base = arch.config().rope_base(the global rope_theta) regardless ofarch.is_sliding_window_layer(layer). Effectively collapses the sliding-vs-global routing.LARQL_ROPE_POS_DIVISOR=<f64>— divides the RoPE position before phase computation, emulating HF'srope_scaling = {type: linear, factor: N}semantics that LARQL does not currently honour.
Both are gated via OnceLock. No behaviour change when unset.
| Configuration | Total bits | Δ vs HF (1068) |
|---|---|---|
| Baseline (no override) | 1126.3 | +5.4% |
LARQL_FORCE_GLOBAL_LAYERS=all |
1387.4 | +29.9% (much worse) |
LARQL_ROPE_POS_DIVISOR=8 (uniform) |
1649.9 | +54.5% (much worse) |
LARQL_ROPE_POS_DIVISOR_GLOBAL=8 (global layers only) |
1068.0 | +0.000% (fixed) |
-
LARQL's per-layer rope_base routing is correct. Forcing global rope_base on every layer makes the gap dramatically worse, which means the sliding-layer rope_base = 10,000 is what HF actually expects. LARQL was already routing correctly.
-
HF's
rope_scalingis per-layer-type. Querying HF's effective config exposed the structured form{sliding_attention: {rope_type: default, rope_theta: 10000}, full_attention: {rope_type: linear, factor: 8.0, rope_theta: 1000000}}. Thefactor: 8.0applies only on full-attention (global) layers, not on sliding layers. The raw config.json shows the flat form{factor: 8.0, rope_type: linear}which HF's Gemma3 config class expands to the structured form on load. LARQL was reading the flat form and not honouring either the per-layer-type structure or the factor at all. -
Applying the factor only on global layers closes Gemma 3's gap exactly.
LARQL_ROPE_POS_DIVISOR_GLOBAL=8drops LARQL Rust to 1068.014 bits — matching HF's 1068.011 to within +0.000% (well under MLX's own +0.056% noise from HF). This is the bug.
After fixing bug 1, the remaining gap on Mistral (+8.2%) and Llama (+0.59%) was traced via the same toggle-and-measure approach.
Mistral 7B v0.1:
| Configuration | Total bits | Δ vs HF (508.86) |
|---|---|---|
| Baseline (no override) | 550.4 | +8.2% |
LARQL_NORM_EPS_OVERRIDE=1e-5 |
508.9 | +0.001% (fixed) |
The HF effective config for Mistral has rms_norm_eps: 1e-05. LARQL's
norm_eps() returns the hardcoded default 1e-6 from config.rs:786
for every architecture; the rms_norm_eps field from config.json is
never read by the parser. The 10× difference matters when residual
magnitudes are small relative to eps — which happens enough during a
34-layer (or 32-layer) forward to compound to 8.2% on bits/char.
Llama-3.2-1B (after applying bug 2 fix):
| Configuration | Total bits | Δ vs HF (579.39) |
|---|---|---|
| Baseline | 582.8 | +0.59% |
LARQL_NORM_EPS_OVERRIDE=1e-5 |
582.09 | +0.47% (partial) |
LARQL_NORM_EPS_OVERRIDE=1e-5 + LARQL_LLAMA3_ROPE_SCALING=32,1,4,8192 |
579.40 | +0.003% (fixed) |
Llama-3.2-1B has rope_scaling = {rope_type: llama3, factor: 32, low_freq_factor: 1, high_freq_factor: 4, original_max_position_embeddings: 8192}. The llama3 type is wavelength-dependent: per-channel
inv_freq[i] is divided by factor for slow-rotating channels
(wavelength > old_ctx / low_freq), passed through for fast-rotating
channels (wavelength < old_ctx / high_freq), and smoothly interpolated
in between. LARQL was implementing standard RoPE for all channels with
no scaling. The fix is in crates/larql-inference/src/attention/rope.rs
(Llama3Scaling struct + apply_rope_partial_at_full).
The env-var diagnostics confirm the bugs exhaustively. The permanent fix is in three small parts:
crates/larql-models/src/detect/parser.rs: readtext_config["rms_norm_eps"]and store it onModelConfig. Default to 1e-6 only when absent.crates/larql-models/src/config/: overridenorm_eps()to return the parsed config value via the default trait impl.crates/larql-models/src/architectures/gemma3.rs: parse the structuredrope_scalingfield, exposerope_position_divisor_for_layer, and haveapply_rope_partial_at_fullconsult it. Similar plumbing forarchitectures/llama.rsto expose the llama3 scaling parameters.
The env vars stay in tree as the diagnostic instrument set — they're useful for future regression bisection on new architectures, and for verifying that the permanent fix matches what the env-var diagnostic proved.
What this divergence does and does not affect:
- Unaffected: any result produced by Python + HF transformers (atlas, Lazarus mechanistic experiments, hierarchical surgery experiment 78, the published mech-interp work). HF agreed with itself across the triangle.
- Affected, in proportion to the gap: any LARQL Rust forward pass result on Gemma 3 4B or Mistral 7B v0.1 where absolute probabilities or gate values are the load-bearing claim. Top-1 rankings and KNN retrieval are likely preserved (the gap is small and consistent in direction) but absolute claims need an asterisk until the bug is fixed.
- Q4_K vindex path stacks an additional ~30% on top of the F32 gap. That's typical Q4_K_M quantization loss, but worth knowing the total gap from F32 reference is roughly: 5.4% (forward) + 30% (quant) for Gemma 3 4B Q4_K.
The multi-architecture sweep is the easiest entry point — runs all four test cases with the env-var fixes applied and prints a pass/fail table:
.venv/bin/python scripts/diagnose_models.pyExpected output: 4/4 PASS at <0.5% threshold, plus the Q4K Metal path showing ~37% Q4K quantization gap for Gemma 3 4B (expected).
To reproduce the individual bisection steps:
# Prep corpus (strip BOM + CRLF).
tail -c +4 data/gutenberg/frankenstein.txt | head -c 1024 \
| tr -d '\r' > /tmp/larql_shannon_corpus_lf.txt
# Triangle on Gemma 3 4B without fix. ~17s. Expected: FAIL +5.4%.
larql shannon verify google/gemma-3-4b-it \
--corpus /tmp/larql_shannon_corpus_lf.txt \
--threshold 0.5
# Same with fix. Expected: PASS +0.000%.
LARQL_ROPE_POS_DIVISOR_GLOBAL=8 \
larql shannon verify google/gemma-3-4b-it \
--corpus /tmp/larql_shannon_corpus_lf.txt \
--threshold 0.5
# Mistral 7B v0.1 with fix. Expected: PASS +0.001%.
LARQL_NORM_EPS_OVERRIDE=1e-5 \
larql shannon verify mistralai/Mistral-7B-v0.1 \
--corpus /tmp/larql_shannon_corpus_lf.txt \
--threshold 0.5
# Llama-3.2-1B with both fixes. Expected: PASS +0.003%.
LARQL_NORM_EPS_OVERRIDE=1e-5 \
LARQL_LLAMA3_ROPE_SCALING=32,1,4,8192 \
larql shannon verify meta-llama/Llama-3.2-1B \
--corpus /tmp/larql_shannon_corpus_lf.txt \
--threshold 0.5Requires torch and mlx_lm in .venv for the HF and MLX reference
legs.
memory/project_swa_forward_divergence.md— quick-recall summary, links to this document.memory/reference_shannon_scorer_triangle.md— the instrument itself.