llama.cpp počítal u sedmi architektur normalizaci jinak než jejich referenční kód
Vektory dotazů a klíčů v hybridní vrstvě Gated DeltaNet dělil llama.cpp odmocninou ořezanou zdola, kdežto knihovna, ke které se jejich referenční kód hlásí, k té odmocnině malé číslo přičítá. Oprava je od 6. září v sestavení b10829 a její autor u ní rovnou ukázal, že se na výstupu skoro neprojeví.

Hybridní modely posledních dvou let nepočítají pozornost v každé vrstvě klasicky. Část vrstev v nich obstarává lineární pozornost s hradly, u Qwenu a několika dalších rodin konkrétně Gated DeltaNet. Vektory dotazů a klíčů se v ní před vlastním výpočtem normalizují na jednotkovou délku – a přesně v tomhle kroku se llama.cpp rozcházel s kódem, ke kterému se referenční implementace těch modelů hlásí. Návrh změny od Daniela Hana z projektu Unsloth to 6. září srovnal.
Epsilon jako dolní mez, nebo jako přídavek
Funkce ggml_l2_norm počítá v llama.cpp měřítko takhle:
const float scale = 1.0f/fmaxf(sqrtf(sum), eps);
Malé číslo eps tu slouží jako dolní mez jmenovatele, tedy jako pojistka proti dělení nulou. Knihovna flash-linear-attention, na kterou se implementace Qwen3-Next v transformers výslovně odvolává, dělá totéž jinak – epsilon přičítá pod odmocninu:
b_rstd = 1 / tl.sqrt(tl.sum(b_x * b_x) + eps)
Pro běžná data je rozdíl nepatrný. Qwen3.8-27B i Qwen3.8-Flash-Next mají v config.json hodnotu rms_norm_eps rovnou 10⁻⁶. Když je druhá mocnina délky vektoru rovna jedné, vybere si fmaxf vždycky odmocninu a epsilon se neuplatní vůbec, kdežto reference dělí číslem √(1 + 10⁻⁶). Rozdíl mezi oběma jmenovateli vychází na pět desetimiliontin – to je náš přepočet z obou vzorců, v návrhu změny nestojí.
Podstatný začne být rozdíl až u vektorů s velmi krátkou délkou. Tam llama.cpp dělí nejméně hodnotou 10⁻⁶, kdežto reference nejméně její odmocninou, tedy číslem tisíckrát větším. Údržbář projektu vystupující pod jménem CISC na to v rozpravě namítl, že epsilon má bránit dělení nulou, ne posouvat hodnotu, a že se tedy chová divně ta knihovna.
Sedm architektur a jedna nová funkce
Změna se dotkla sedmi rodin: qwen35, qwen35moe, qwen3next, qwen4exp, kimi-linear, kimi-k3 a bailingmoe3. Jsou to jména architektur uvnitř llama.cpp, ne jména modelů; pole model_type v config.json je spojuje s konkrétními vahami. Qwen3.8-27B se hlásí jako qwen3_5, Qwen3.8-Flash-Next jako qwen4_exp a Ling-3.0-flash od InclusionAI jako bailing_hybrid.
Na samotnou ggml_l2_norm přitom nikdo nesáhl – používá ji i RWKV 7, kterého se změna netýká. Místo toho přibyla v src/models/models.h nová funkce poskládaná ze dvou existujících operací: normalizace střední kvadratické hodnoty s epsilonem vyděleným počtem prvků vektoru, a za ní vynásobení převrácenou odmocninou téhož počtu. Vyjde z toho přesně referenční tvar.
Tady je jedna pikantnost. Přesně tenhle postup měly v září 2025 transformers, když do sebe přidávaly Qwen3-Next – a o tři dny později ho vyhodily a nahradily přímým vzorcem. Rozdíl je v tom, že epsilon nedělily počtem prvků: při délce hlavy 128, kterou oba jmenované modely mají, se tím pod odmocninu dostane 1,28 · 10⁻⁴ místo žádaného 10⁻⁶, tedy hodnota 128krát větší. Opravu tehdy poslal člen týmu Qwen.
Kolik se toho na výstupu změnilo
Autor návrhu k němu přiložil vlastní měření odchylky rozdělení pravděpodobností, a to pro dvě ramena: jedno s opravou, druhé kontrolní s jinou velikostí dávky. Vůči čemu se ta odchylka počítá, u tabulky nestojí. U Qwen3.8-Flash-Next v kvantizaci UD-IQ1_S vyšla průměrná odchylka s opravou 0,032120 proti 0,032690 u kontroly, u Qwen3.8-27B v Q4_K_M pak 0,001750 proti 0,001769. Shoda nejpravděpodobnějšího tokenu byla u prvního modelu s opravou o něco horší (93,435 % proti 93,646 %) a u druhého o něco lepší (98,412 % proti 98,347 %). Nezopakoval to nikdo jiný.
Druhé měření přidal do rozpravy uživatel vystupující jako ovidiu-morar. Pustil čtyři otázky postavené na smyšlené premise a pět kontrolních otázek s premisou pravdivou, ve dvou kvantizacích a na dvou sestaveních, na notebooku Applu přes Metal, s nulovou teplotou a jedním během na buňku. Z osmi buněk se smyšlenou premisou se jich pět posunulo k bezpečnějšímu chování, tři zůstaly a žádná se nezhoršila; odpovědi na pravdivé otázky se nepohnuly vůbec, včetně jedné chyby, kterou obě sestavení udělala shodně na stejný počet tokenů.
Na tom měření je zajímavější jeho oprava než výsledek. První verzi autor sám stáhl: porovnával hlavní větev o 31 revizí starší, než na jaké návrh stál, takže rozdíl mohla způsobit kterákoli změna mezi nimi. Přestavěl proto hlavní větev přesně na revizi, ze které návrh vychází, a měřil znovu.
Proč se to sloučilo, když je dopad v šumu
Jiný přispěvatel v rozpravě připomněl, že si téhož nesouladu všiml už při psaní podpory Qwen3-Next a že mu tehdy z měření vyšel jako zanedbatelný. Rozhodl argument, který s numerikou nesouvisí. Podle CISC se ta knihovna použila při trénování, takže i kdyby byl její tvar technicky nesprávný, je pro hotové váhy závazný. Georgi Gerganov k tomu napsal, že se kloní ke shodě s referenční implementací bez ohledu na to, jak malý ten numerický dopad je (obojí přeloženo).
Návrh čekal na sloučení šest dní a změnil osm souborů, 27 přidaných řádků proti patnácti odebraným. Kdo si llama.cpp staví ze zdrojů, má opravu v sestavení b10829 a novějších; b10826, které vyšlo šest minut před sloučením, ji ještě nemá.