Küçük bir modelde fp16 neden bozuldu? 1330'luk aktivasyonlar ve RMSNorm taşması
4 Ekim 2026 · 3 dk okuma · Töz AI
Modeli PyTorch'ta kusursuz çalışıyordu, ONNX'e çevirip tarayıcıda fp16 ile açınca saçmalamaya başladı. Bulduğumuz şey, bir ölçümü yanlış donanımda yapmanın ne kadar yanıltıcı olabileceğini gösterdi.
Belirti
Modeli tarayıcıda çalıştırmak için ağırlıkları ONNX biçimine ve 16 bitlik kayan noktaya (fp16) çevirdik; indirme boyutu 442 MB'a, 4-bit ile 161 MB'a iniyordu. Tarayıcıda WebGPU ile açtığımızda çıktı şuydu:
"Türkiye'nin başkenti neresidir?" → "Türkiye'nin başkenti Ankara'ddır." (çift d)
"İstiklal Marşı'nın şairi kimdir?" → "Mehmet Akif Akif Akif Akif Akif …"
bazı istemler → hemen <|endoftext|>, yani sessizlik
Python'daki referans aynı istemlerde tertemiz çalışıyordu: "Türkiye'nin başkenti Ankara'dır." ve "İstiklal Marşı'nın şairi Mehmet Akif Ersoy'dur."
Önce neyi doğruladık
İlk refleks "model bozuk" olmaktı. Dönüşüm hattını halka halka doğruladık:
- Ağırlıklar: fp32 ONNX'in logit'leri PyTorch modeliyle bire bir aynı çıktı (en büyük fark 0,0001).
- Sohbet şablonu:
Kullanıcı: …\nTöz:doğru üretiliyordu, 14 token, sonu tek bir boşluk tokenı. - KV önbelleği: önbellekli token token üretimle, aynı diziyi önbelleksiz tek geçişte hesaplamak aynı argmax'ları verdi.
Üç halka da sağlamdı ve biz bu yüzden bir süre "sorun modelde" dedik. Yanılıyorduk: dördüncü halkayı, yani fp16'nın hedef donanımdaki sayısal davranışını hiç ölçmemiştik.
Teşhis
Model sahibinin Python tarafında yaptığı bir ölçümde, her katmanın çıkışındaki en büyük mutlak değere bakıldı. Gömme çıkışından başlayıp katman katman residual akışındaki max|h|:
0.2, 22, 38, 46, 68, 101, 98, 219, 407, 1310, 1324, 1327, 1327, 1329, 1330, 1327, 1317, 120, 31
Orta katmanlarda aktivasyonlar yaklaşık 1330'a çıkıyor. Bu tek başına sorun değil; fp16 65.504'e kadar sayıyı tutabilir. Sorun, RMSNorm'un mean(x²) hesabı: 1330² ≈ 1,77 milyon, yani fp16'nın tavanının yaklaşık 27 katı. Hesap gerçekten fp16'da yapılırsa sonsuza taşar, normalize edilen çıktı sıfıra gider ve model çöpe dönüşür.
PyTorch'taki Qwen3RMSNorm bu yüzden hesabı fp32'ye yükseltir. Modelin eğitim kodundaki RMSNorm de aynısını yapıyordu (x.float()); model bu varsayımla eğitilmişti. ONNX dönüşümü bu yükseltmeyi düşürmüştü.
Düzeltme ve kalan sorun
Dönüştürücüye RMSNorm'un bileşen işlemlerini (Pow, ReduceMean, Sqrt, Div ve benzerleri) fp32'de bırakmasını söyledik, ve üretilen grafta bu işlemlerin gerçekten fp16'ya inmediğini yapısal olarak doğrulayan bir kontrol ekledik. Saçmalama bitti, model gerçek cümleler kurmaya başladı. Ama çıktı hâlâ referansla birebir değildi (Ankara'ddır, Akif Akif …).
Sebep artık taşma değil, hassasiyet. fp16'da 1024 ile 2048 arasındaki sayıların komşu değerler arası adımı 1,0'dır: 1330,7 ile 1331,2 aynı sayıya yuvarlanır. Bu hata 18 katman boyunca birikiyor.
Bunu bir deneyle doğruladık: 4-bit ağırlıklı sürüm (q4f16), fp16 sürümüyle kelimesi kelimesine aynı bozuk çıktıyı verdi. Hata ağırlıklarda olsaydı 4 bit ile 16 bit aynı çıkamazdı; demek ki baskın hata aktivasyonlardan geliyor. Çözüm, ağırlıkları 8 bite indirirken aktivasyonları fp32'de tutan sürüm: 8-bit (q8), dört referans istemin dördünü de PyTorch ile birebir tutturdu ve zayıf bir CPU'da WebAssembly ile çalışıyor. Sitedeki sürüm bu.
Üç ders
- Doğrulamayı hedef donanımda yapın. fp16'yı ilk CPU'da doğruladık ve bozukluğu göremedik. Bizim açıklamamız (doğrulamadık): CPU çekirdekleri fp16 tensörlerini içeride fp32'ye yükseltiyor, taşma orada hiç oluşmuyor; yalnızca WebGPU'nun gerçek fp16 aritmetiğinde ortaya çıkıyor.
- Bir metriğin sinyali, ölçüldüğü ortamda geçerlidir. Aynı CPU ölçümü, v1'in q8'ini perplexity'de yaklaşık %20 kötü, v2'ninkini tamamen bozuk gösterdi; oysa tarayıcıda v1'in q8'i dört referans cevabı birebir tutturdu, v2'ninki de doğru ve akıcı cevaplar verdi. Bu çelişkiyi tam açıklayamıyoruz. Pratik sonuç: kuantize bir sürümün kullanılabilirliğine ancak hedef ortamda gerçek istemlerle üretim yaptırarak karar veriyoruz.
- Büyük aktivasyonlar bir dağıtım riskidir. Bir model PyTorch'ta sorunsuz çalışıp düşük hassasiyetli çıkarımda bozulabilir. Sonraki eğitimlerde aktivasyon büyüklüğünü sınırlamak (örneğin z-loss'u artırmak ya da residual ölçeklemek) fp16 ve 4-bit dağıtımı mümkün kılabilir; şimdilik bunu denemedik.
Modelin kendisi hakkında bilgi için Töz-1 nedir yazısına, çıkarımı neden tarayıcıda yaptığımız için sunucu ya da tarayıcı yazısına bakın.
Töz-1'i tarayıcınızda deneyin: kurulum yok, sunucu yok.
Töz-1'i deneDiğer yazılar
Töz-1 nedir?
Töz-1'in mimarisi, eğitimi ve sınırları: 4,42 milyar token Türkçe metinle sıfırdan eğitilmiş, tarayıcıda çalışan 195 milyon parametreli bir dil modeli.
4 Ekim 2026Sunucuda mı, tarayıcıda mı?
Küçük bir dil modelini ücretsiz bir bulut sunucusunda ve tarayıcıda çalıştırıp ölçtük: sunucuda yaklaşık 1,1 token/sn, tarayıcıda yaklaşık 13 token/sn.
4 Ekim 2026