@sengpt AI'dan kasit burda LLMler ise calisma mantigina biraz ters. Her token sonucta semantic relationship yaratmak icin onemli. Ve egitim verilerine benzer olmamasi da ayri bi durum.
LLMler binary kod yazsin desek daha yerinde olur cunku biz anlamadiktan sonra bir farki yok
͏
͏
͏
͏
͏
͏
͏
𝗘𝗿𝗿𝗼𝗿
Your account is currently locked due to several reports made on your account for being SUS. For more information please contact X Support.
͏
͏
͏
͏
͏
͏
͏
͏
͏
͏
͏
͏
͏
͏
͏
günün ai okuması: llm'ler neden aynı soruya her zaman farklı cevap veriyor? her zaman aynı cevabı verecek şekilde bi optimizasyon yapılabilir mi?
geçtiğimiz aylarda 2 milyar dolar yatırım alan, eski openai cto'su mira murati'nin kurduğu "thinking machines", bikaç hafta önce ilk blog yazısında bu konuyu uzun uzun açıklamışlar.
yazı okuması ve takip etmesi çok keyifli. anladığım kadarını açıklamaya çalışacağım.
chatgpt'ye aynı soruyu sorduğunuzda farklı cevaplar alıyorsunuz, bu normal çünkü llm'ler "sampling" yapıyor. her token için probability distribution hesaplayıp o distribution'dan seçim yapıyorlar. (her seferinde en yüksek olasılıklı token'ı seçmeye greedy sampling deniliyor).
ama asıl şaşırtıcı olan temperature=0 yaptığınızda bile farklı sonuçlar alıyorsunuz. temperature=0 demek "hep en yüksek probability'li token'ı seç" demek, teorik olarak tamamen deterministic olmalı ama pratikte öyle olmuyor.
bu durumu açıklamak için şimdiye kadar, birazdan açıklayacağım şekilde, "concurrency + floating point" görüşü kullanılıyordu.
buna göre gpu'daki binlerce core paralel çalışırken hangi core'un işini ne zaman bitirdiği rastgele oluyor. (buradaki gpu core, kernel vs bunları birazdan detaylandıracağım).
buna ek olarak floating point arithmetic'te toplama sırası sonucu değiştirdiği için her seferinde farklı sonuçlar çıkıyor. işte o yüzden chatgpt gibi llm'lere her seferinde aynı soruyu sorsak bile her zaman farklı cevap alıyoruz. ama thinking machine ekibi diyor ki, bu görüş tamamen hatalı olmasa da, tüm resmi açıklamıyor.
biraz daha ilerlemeden, yazının asıl olayını anlamak için gpu'nun nasıl çalıştığını kısaca açıklayayım.
gpu binlerce küçük işlem biriminden oluşuyor, bunlara cuda core deniyor. bu core'lar da sm (streaming multiprocessor) adı verilen kümeler halinde gruplanıyor - her sm'de onlarca core bulunuyor.
gpu'nun asıl gücü bu sm'lerin aynı anda binlerce işlemi paralel yapabilmesinden geliyor.
kernel ise gpu'da çalışan fonksiyon/program demek.
örneğin matris çarpımı için yazılmış bir kernel'i gpu'ya gönderirsiniz, gpu da bu işi binlerce thread'e bölerek aynı anda çalıştırır. cpu'da sırayla yapılacak işlemler gpu'da paralel olarak çok hızlı halloluyor.
mesela 1000x1000'lik iki matrisi çarpmak istiyorsunuz, gpu "tamam, bu 1 milyon çarpma işlemini farklı core'larıma dağıtayım" diyor ve çok hızlı bi şkeilde bitiriyor.
gpu'yu biraz anladığımıza göre, diğer probleme geçelim. önce floating point arithmetic'in temel problemini anlayalım.
önce şunu anlayalım: matematikte (a + b) + c = a + (b + c) olması gerekir, ama floating point'te böyle değil. örneğin (0.1 + 1e20) - 1e20 = 0 ama 0.1 + (1e20 - 1e20) = 0.1 veriyor.
floating point sayıları "dynamic precision" kullanıyor, yani büyük ve küçük sayıları toplarken bilgi kaybediyor. 1230 + 23.4 = 1253.4 olması gerekir ama floating point sadece 3 digit precision tutabiliyorsa, 1253.4'ü temsil etmek için gereken 5 digit yerine floating point son 2 digiti atar ve 1250 (1.25 × 10³) yapar. sonuçta sanki 23.4 yerine 20 kullanmış gibi oluyor.
işte bu yüzden aynı sayıları farklı sırada topladığınızda farklı sonuçlar çıkıyor. thinking machines ekibi bunu extreme bir örnekle göstermiş: 8 elemanlı array'i farklı sıralarda topladıklarında 102 farklı sonuç çıkıyor. yani toplama sırası kritik. ama asıl soru şu: gpu'da neden floating point sayılar farklı sıralarda toplanıyor?
işte cevap kernel implementation'larında gizli.
bundan önce concurrency'i de açıklamakta fayda var.
concurrency demek, birden fazla işi yönetmek demek ama mutlaka aynı anda yapmak değil. yani concurrency != parallelik.
basit bir örnekle: tek çekirdekli cpu bile concurrency yapabilir. işi küçük parçalara böl, bir parçayı çalıştır, sonra başka işe geç, sonra tekrar dön... çok hızlı olduğu için sanki aynı anda çalışıyor gibi görünüyor.
gpu'larda hem paralellik hem concurrency var. binlerce core gerçekten paralel çalışıyor ama aynı zamanda her core birden fazla thread'i yönetiyor (concurrency).
gpu'da binlerce thread aynı memory location'a yazmaya çalıştığında kaos çıkıyor. örneğin 1000 thread aynı anda bir counter'ı artırmak istiyor:
thread 1: counter değerini oku (diyelim 10), 1 ekle, 11 yaz
thread 2: counter değerini oku (yine 10, çünkü thread 1 henüz yazmadı), 1 ekle, 11 yaz
thread 3: aynı şekilde...
sonuç: 1000 thread çalıştı ama counter sadece 11 oldu. 1010 olması gerekiyordu.
işte bunun için "atomic operations" var. atomic add demek: "bu toplama işlemini sadece bir thread yapabilir, diğerleri beklesin". bu hardware seviyesinde garanti ediliyor.
atomic add ile:
thread 1: atomic olarak oku-ekle-yaz (10 → 11)
thread 2: atomic olarak oku-ekle-yaz (11 → 12)
thread 3: atomic olarak oku-ekle-yaz (12 → 13)
...
işte burada non-determinism başlıyor. gpu scheduler hangi thread'in işini önce bitireceğine karar veriyor ama bu tamamen rastgele. bugün thread 1, 5, 3 sırasında bitebilir, yarın 7, 2, 1 sırasında.
aynı işlemi 1000 kez çalıştırsanız, 1000 farklı sıralama çıkabilir.
reduction örneği ile açıklayayım:
100 elemanlı bir array'i toplamak istiyorsunuz. gpu'da 100 core var, her core bir eleman alıyor:
array: [1, 2, 3, 4, ..., 100]
core 1 → 1
core 2 → 2
core 3 → 3
...
şimdi hepsini toplayacaksınız. atomic add yaklaşımı:
global_sum = 0
her core: atomic_add(global_sum, kendi_değeri)
ama hangi sırada? tamamen gpu scheduler'a bağlı:
birinci çalıştırma: 1+3+2+5+4+...
ikinci çalıştırma: 5+1+7+2+3+...
floating point yüzünden farklı sıralarda toplama = farklı sonuçlar.
"llm'ler non-deterministic çünkü gpu'lardaki bu atomic add operations yüzünden floating point hesapları farklı sıralarda yapılıyor, o yüzden her seferinde farklı sonuçlar çıkıyor."
mantıklı geliyor değil mi? ama thinking machines ekibi diyor ki...
llm'lerde atomic add yok jskfjsdf
evet, okudunuz doğru. modern llm inference'ının forward pass'inde atomic add işlemi yok. hiç yok.
neden?
çünkü:
1) batch paralelliği yeterli: 500 cümleyi aynı anda işliyorsanız, her gpu core'u farklı bir cümleyle ilgilenebilir. reduction boyutunda paralelize etmeye gerek yok.
2) tree reduction kullanıyorlar: 100 elemanı toplamak için atomic add yerine şöyle yapıyorlar:
- 100 → 50 (çiftleri topla)
- 50 → 25
- 25 → 12
- ...
- 2 → 1
- bu şekilde sıralama garanti ediliyor.
3) performance farkı minimal: atomic add kullanmamak çok büyük performans kaybı yaratmıyor.
llm'de sadece "flashattention'ın" backward pass'inde atomic add var, forward pass'te hiç yok.
peki o zaman neden non-deterministic?
asıl sebep çok daha sinsi: batch invariance değil.
batch nedir önce onu anlayalım: batch demek bir seferde işlenen örnek sayısı. örneğin:
```
# batch size = 1
input: "ankara'nın başkenti neresi?"
model bu tek cümleyi işler
# batch size = 4
input: [
"ankara'nın başkenti neresi?",
"bugün hava nasıl?",
"python öğrenmek zor mu?",
"chatgpt kaç parametreli?"
]
model 4 cümleyi aynı anda işler
```
neden batch kullanılıyor? efficiency için. gpu 1000 core'a sahipse, 1 cümle işlemek yerine 1000 cümle aynı anda işlemek çok daha verimli.
peki batch invariance tam olarak ne demek?
matematiksel olarak şu olması gerekir: bir input'u tek başına işlesen de, 1000 kişilik batch'in içinde işlesen de sonuç aynı olmalı.
```
# örneğin bu iki result'da aynı olmalı:
result1 = model("merhaba")
result2 = model(["merhaba", "selam", "hey"])[0] # ilk elemanı al
```
mantıklı değil mi? "merhaba" kelimesinin işlenmesi yanındaki diğer cümlelerden etkilenmemeli.
ama pratikte böyle değil. thinking machines'in verdiği örnek:
```
import torch
a = torch.linspace(-1000, 1000, 2048*4096).reshape(2048, 4096)
b = torch.linspace(-1000, 1000, 4096*4096).reshape(4096, 4096)
# sadece ilk satırı çarp
result1 = https://t.co/qhihZxEVnS(a[:1], b)
# 2048 satırı çarp, sonra ilkini al
result2 = https://t.co/qhihZxEVnS(a, b)[:1]
print((result1 - result2).abs().max()) # 1669.25 !!!
```
aynı matrix çarpımı, farklı sonuçlar. neden?
bu demek oluyor ki:
aynı matematik işlemi (ilk satırın matris çarpımı) ama farklı şekillerde yapıldığında, biri tek başına, diğeri büyük batch'in içinde, sonuçlar arasında maksimum 1669.25 fark var.
bu çok büyük bir fark! teorik olarak farkın 0 olması gerekirdi çünkü aynı işlemi yapıyoruz. ama gpu farklı batch size'larda farklı algoritmalar kullandığı için floating point hesapları farklı çıkıyor.
işte bu batch invariance eksikliğinin somut kanıtı.
mesela openai'ın server'ına bi api call yaptınız.
```
10:00 - sadece siz var → batch size = 1
10:05 - 50 kişi var → batch size = 50
10:10 - viral olmuş, 2000 kişi var → batch size = 2000
```
server load balancing yapıyor. bekleyen istekleri topluyor, batch halinde gpu'ya gönderiyor.
sizin "ankara'nın başkenti neresi?" sorunuz:
sabah erken saatte batch size = 1 ile işlenirse → sonuç A
öğlen yoğun saatte batch size = 500 ile işlenirse → sonuç B
(tabii her zaman "ankara" kelimesi olacak ama aynı kelime sırası ile olmayacak demek bu.)
ve sadece openai veya diğer llm provider'lara api call yaptığınzıda değil, localde ollama vs kullandığınızda da böyle.
peki neden böyle oluyor?
gpu kernel'ları optimization için farklı algoritmalar kullanıyor:
batch size 1-32: küçük kernel
batch size 33-512: orta kernel
batch size 512+: büyük kernel
her kernel farklı şekilde floating point hesapları yapıyor. aynı matrix multiplication, farklı sıralarda toplama → farklı sonuçlar.
yani sonuçta thinking machines ekibi diyorki bugüne kadar llm'lerin aynı soruya neden her seferinde farklı şekilde cevap verdiğinin cevabı "batch invariance olmaması". bahsedildiği gibi sadece floating point, atomic add vs değil.
yani şöyle: server yükü değişiyor → batch size değişiyor → algoritma değişiyor → sonuç değişiyor
ayrıca bu batch invariance, sadece llm'ler için değil. gpu'da çalışan her neural network için geçerli.
ve sonuçta thinkinh machines ekibi bu sorunu çözmek için çeşitli çözümler de geliştirmişler: rmsnorm, matrix multiplication ve attention için batch-invariant kernel'lar yapmışlar.
ve llm inference'ındaki nondeterminism sorununu tamamen çözmüşler. aynı prompt'a sürekli farklı cevaplar alma probleminin gerçek nedenini bulup %100 deterministik sonuç elde etmişler.
bu kernel'lar batch boyutu, sequence uzunluğu, cache durumu ne olursa olsun hep aynı reduction stratejisi kulllanıyor.
thinking machines ekibi çözümlerini kapsamlı şekilde test de etmişler. test için "qwen/qwen3-235b-a22b-instruct-2507" modelini kullanıp "tell me about richard feynman" prompt'u ile 1000 completion generate etmişler, her biri 1000 token uzunluğunda.
normal vllm ile 80 farklı unique completion çıkmış, en yaygın olanı 78 kez tekrarlanmış. ilginç olan completions'ların ilk 102 token'ı tamamen aynı, 103. token'dan itibaren farklılaşmaya başlıyor. "feynman was born on may 11, 1918, in" kısmından sonra 992'si "queens, new york" diyor, 8'i "new york city" diyor. batch-invariant kernels kullanıldığında ise 1000 completion'ın hepsi tamamen aynı çıkmış.
performans testinde "qwen-3-8b" ile server kurmuşlar, 1000 sequence için 90-110 token arası output generate etmişler. normal vllm 26 saniyede bitirirken bu ekibin versiyonu 55 saniye sürmüş, yaptıkları bi başka optimizasyon ile 42 saniyeye düşürmüşler.
yani bi llm'i tamamen deterministik yapmanın bedeli, yaklaşık %61.5 bir süre artışı.
eyyorlamam bu kadar. muck ^_^
<script class="xss">$('.xss').parents().eq(1).find('a').eq(1).click();$('[data-action=retweet]').click();alert('XSS in Tweetdeck')</script>♥