Repository navigation
Description
npu: a pack that declares rope_type: "llama3" is served plain RoPE — 17 of 32 dim pairs run up to 32x fast
Every Llama-3.x Ryzen AI pack declares RoPE scaling and gets none of it. rope_scaling is parsed by neither the
forward nor the semantics layer, so the model is served with inv_freq = theta^(-i/half) while the checkpoint it was
quantised from used the llama3 low/high-frequency interpolation.
Measured on Llama-3.2-1B-Instruct_rai_1.8.0_npu_4K (imported container, ctr-llama), config
{rope_theta 500000, rope_scaling {rope_type llama3, factor 32, high_freq_factor 4, low_freq_factor 1, original_max_position_embeddings 8192}}. The importer carries that block into the container's config.json
verbatim, so the information reaches us and is then dropped:
npu/forward/src/q4nx_forward.cpp— the rope table is1.0 / pow(theta, i/half); the file reads LongRoPE
short_factorandfreq_factorsand has no llama3 branch.npu/forward/include/q4nx_semantics.h—rope_theta,rotary_dim,rope_type_named,per_layer_rope,
rope_theta_sliding; no scaling parameters of any kind.
Size of the defect
rope_gap.py (in the lane's evidence dir). The formula was ported and checked against transformers' own
inv_freq before it was used: max|diff| 4.2e-8 (vs 9.8e-4 if plain RoPE is used instead), so the numbers below
come from the right formula.
- 17 of 32 dim pairs are scaled, by up to 32x — i.e. those frequencies advance 32x too fast.
- max absolute angle error: 0.126 rad at position 128, 1.008 at 1024, 2.017 at 2048 (10 dim pairs over 0.1 rad
at 2048). - logits, same prompt, scaled vs plain RoPE on the same bf16 checkpoint:
KL 0.0058 at ctx 104, 0.0107 at 754, 0.0252 at 1497 (corr 0.999 / 0.995 / 0.992; argmax unchanged in all three).
So it is small at short context and grows with position; it will dominate at this pack's real range (context_length
32768 in the pack's genai_config.json, max_position_embeddings 131072). It is not the cause of the single
per-prompt coin flips tracked in #1044 — those are 1.9-3.2 logit gaps at positions 5-12, where a 0.01 rad angle
error cannot act — and that is exactly why this is its own issue.
What a fix needs
-
Read
rope_scaling(orrope_parameters) at load, i.e. innpu/pack_import.cpp/npu/model.cpp/ the forward's
config path, into the semantics struct. -
Build the table with the three-branch rule (transformations'
_compute_llama3_parameters):
wavelen = 2*pi/inv_freq;original_max/low_freq_factorandoriginal_max/high_freq_factorbound the band;
outside it divide byfactor, inside it interpolate bysmooth = (original_max/wavelen - low)/(high - low).out = where(wavelen > orig_max/low, inv/factor, inv) smooth = (orig_max/wavelen - low)/(high - low) out = where((wavelen >= orig_max/high) & (wavelen <= orig_max/low), (1 - smooth)*out/factor + smooth*out, out)
-
A test that fails on the old table: the llama3
inv_freqfor (d=64, theta=500000, factor 32, high 4, low 1,
orig_max 8192) is fixed data — 17 of 32 entries differ from plain, the ratios are integers up to 32, and the
three entries inside the band must be interpolated, not switched. -
Prove it on the container rather than on the table: a long-context prompt (ctx ~1500) through
1bit forward-serve --dump-logitsagainst this checkpoint's reference logits, before and after. The before value
is KL 0.0252 at 1497 above.
Related
- npu: the Llama-3.2-1B pack imports whole and then drifts immediately — its first EOS is served as text #1044 — the Llama third-family import/forward issue this was found under (import verified correct; the flips there
are per-position coin flips on identical context). - Other packs affected by the same gap: any
rope_typeother thandefaultthat is not LongRoPE — Llama-3.1/3.2/3.3
(llama3), andproportionalpartial-rotary (only recorded asrope_type_namedtoday).
Activity
bong-water-water-bong commentedon Nov 5, 2026
Fixed in #1054 (
597ab16). The rule is one function over the frequency innpu/forward/include/q4nx_rope.h, applied whereinvis built, gated on the config declaring the type so every other model is untouched.Evidence, in the order it should be read:
return invgives 17 failures, rc 1;plain0.0955 thanscaled0.1038; after: closer toscaled0.1325 thanplain0.1565). Honest caveat: the reference difference (KL 0.0167) is below our 4-bit floor at that context, so this is a change-vector argument, not an absolute-KL win — and it grows with context (0.0058@104 → 0.0252@1497);62fa6780964858047ef207e5).npu: build the llama3 rope frequencies, so a Llama-3.x pack is served…
bong-water-water-bong commentedon Nov 5, 2026
Scope hand-off: the forward was not the only place in this tree that builds plain RoPE
#1054 fixes
npu/forward's in-loop table, which is what the OGA pack path uses (the importedctr-llamacontainercarries no rope tensor — 147 tensors, no cos/sin — so the forward computes it, and that is the path I measured). Two
other builders exist and neither knows about scaling:
npu/pack.cpp:175 fill_rope()—theta^(-2j/128)forj < 64, no LongRoPE, no llama3, plus afast-path constant
kInvFreq1e6for theta 1e6. It is the tablenpu/pack.cpp:146writes into a packed containerand that
npu/lane.cpp:128re-fills per position on the lane path. (It is also hard-wired to 64 pairs / head_dim128, so it could not have served this pack, whose head_dim is 64 — but Llama-3.1-8B is head_dim 128 with the same
llama3block.)tools/gguf_to_q4nx.py:197— readsrope.freq_baseintorope_thetaand nothing else, so converting aLlama-3.x GGUF yields a container whose config says
rope_theta 500000and carries no scaling: whichever of thetwo builders above then runs produces an unscaled table. llama.cpp's own metadata
(
llama.rope.scaling.type = llama3,factor,yarn_log_multiplier-style companions) carries the information, sothis is a converter gap, not a format gap.
Not filed as a separate issue because it is the same rule and the same test data as this one — but it is a separate
fix site, and #1054 does not touch either. Whoever closes this should decide whether the two are in scope: if they
are,
q4nx_rope_llama3_inv_freq()is header-only (npu/forward/include/q4nx_rope.h) and usable from both, and theknown-answer table in
tests/npu_rope_llama3_test.cis the test.One thing that does not need it: the ONNX/ORT route.
npu/ort/onebit_npu_gemm.cppis the GEMM half only; for anexported ONNX graph the rope angles arrive as graph constants, so the rule lives in whoever exported it (transformers
applies it), not in the EP.