Prefill ve Decode: Bir LLM'in İki Hızı
Bir sohbet modeline mesaj gönderdiğinizde çoğu zaman kısa bir bekleme olur. Sonra cevap, birkaç kelime birkaç kelime akmaya başlar. O bekleme ve o akış iki ayrı iştir ve model bunları iki farklı şekilde yapar.
Önce prefill gelir: Model promptunuzun tamamını okur. Sonra decode gelir: Cevabı token token yazar. (Token, küçük bir metin parçasıdır; çoğu zaman bir kelime ya da bir kelimenin parçası.) İki aşamada da aynı model çalışır. Çalıştır'a basın ve metnin modelden kaç kez geçtiğini sayın.
Çalıştır'a basın ve geçişleri sayın.
Elle seçilmiş örnek tokenlar; hız da epey yavaşlatıldı. Her geçiş kutusu, modele kaç token girdiğini gösterir. Cevabın ilk tokenı prefill geçişinden çıkar, bu yüzden kenarlığı mavidir. KV cache aşağıda daha ayrıntılı anlatılıyor.
Prefill sırasında prompttaki her token zaten bellidir, bu yüzden model hepsi üzerinde aynı anda çalışabilir. Modelden tek bir geçiş promptun tamamını okur ve cevabın ilk tokenını da verir. Ayrıca prompttaki her token için birkaç sayıyı KV cache içine kaydeder; buna aşağıda bakacağız. (Çok uzun promptlar çoğu zaman birkaç büyük parçaya bölünür, ama fikir aynıdır.)
Decode bunu yapamaz. Sıradaki token bir öncekine bağlıdır ve o henüz yoktur. Bu yüzden model bir token üretir, onu metne ekler ve bir sonraki için yeniden çalışır; bu, cevap bitene kadar sürer. İlkinden sonraki her yeni token kendi geçişine ihtiyaç duyar. (Speculative decoding gibi bazı hileler, tahmin edilmiş birkaç tokenı tek geçişte kontrol edebilir, ama temel döngü her geçişte bir tokendır.)
Bu neden önemli? Her geçişte model iki iş yapmak zorundadır. Bellekten ağırlıklarını okumalıdır, hem de hepsini; bir de geçişteki her token için hesabı yapmalıdır. Ağırlıkları okumak, kaç token olursa olsun aşağı yukarı aynı sürer. Hesap ise her tokenla büyür.
Malzemeleri koridorun sonundaki bir depoda duran bir aşçı düşünün. Her pişirme turundan önce aşçı bütün malzemeleri taşımak zorundadır. Bir tabak da yapsa yüz tabak da yapsa bu yürüyüş aynı sürer, ama yüz tabağı pişirmek çok daha uzun sürer. Bir geçişteki token sayısını değiştirin ve hangi işin diğerini beklediğine bakın.
Decode gibi: Tek bir sohbet için tek bir yeni token.
Memory-bound: Hesap erken biter ve ağırlıkları bekler.
Gerçek ölçümler değil, basitleştirilmiş bir model: Saniyede 1.000 GB okuyan ve saniyede 100 trilyon işlem yapan örnek bir ekran kartı, 16 bitlik 8 milyar ağırlığa (16 GB) sahip bir modeli çalıştırıyor. Her token, ağırlık başına yaklaşık 2 işlem ister. Demo, okuma ile hesabın tamamen aynı anda yürüdüğünü varsayar; attention ve KV cache'i hesaba katmaz. Çubuklar 40 kat yavaşlatıldı.
Tek bir tokenla hesap çok küçüktür ve çip zamanının çoğunu ağırlıkların bellekten gelmesini bekleyerek geçirir. Bu yüzden decode için genellikle memory-bound, yani belleğe bağlı deriz: Hızı çoğunlukla belleğin ne kadar hızlı olduğuna (bant genişliğine) ve modelin ne kadar büyük olduğuna bağlıdır. Ağırlıkları küçültülmüş, quantization'dan geçmiş bir modelin çoğu zaman daha hızlı yazmasının bir nedeni de budur.
Uzun bir promptta prefill, yüzlerce ya da binlerce tokenı tek bir geçişe koyar. Ağırlıklar bir kez okunur ve hepsi için kullanılır, bu yüzden artık yavaş kısım hesaptır. Prefill genellikle compute-bound, yani hesaba bağlıdır: Hızı çoğunlukla çipin saniyede ne kadar hesap yapabildiğine bağlıdır.
Sunucular aynı hileyi decode için de kullanır. Batching ile birçok sohbetin sıradaki tokenını tek bir geçişe koyarlar. Ağırlıklar yine bir kez okunur, bu yüzden sunucu toplamda saniyede çok daha fazla token üretir; tek tek her sohbet ise hızlanmaz, hatta biraz yavaşlayabilir.
Bir ayrıntı daha var. Model yeni bir token yazarken attention ile kendinden önceki bütün tokenlara geri bakar. Bunun için önceki her tokena ait, key ve value denen bazı sayılara ihtiyaç duyar. Bu sayılar değişmez; bu yüzden model onları her geçişte yeniden hesaplamak yerine KV cache içinde saklar. Oynat'a basın ve satırların büyümesini izleyin, sonra cache'i açıp yeniden oynatın.
Her satır, bir cevap tokenı üreten bir geçiştir. Her sütun bir tokendır: 4 prompt tokenı için mavi, modele geri verilen cevap tokenları için sarı. Basitleştirilmiş bir resim: Gerçek modeller bunu her katmanda yapar.
Cache olmadan her geçiş bütün metni modelden yeniden geçirir ve cevap uzadıkça iş çok hızlı büyür. Cache ile her geçiş yalnızca en yeni tokenı çalıştırır. Gerçek sistemlerin cache tutmasının nedeni budur: Ek bellek iyi bir takastır.
Ama bu belleğin bir bedeli var ve sohbetteki her tokenla büyür. Uzun sohbetlerin iki bedeli vardır: Cache daha çok bellek kaplar ve her yeni token onun daha büyük bir kısmını okumak zorunda kalır, bu yüzden yazma da yavaşlar. Kaydırıcıyı sürükleyin ve cache'in ağırlıkların yanında büyümesini izleyin.
Ağırlıklar ve cache 24 GB'lık karta sığıyor.
Llama 3.1 8B yapısında bir model: 32 katman, her biri 128 sayılık 8 key/value head. Token başına 2 (key'ler ve value'lar) × 32 × 8 × 128 sayı saklar; bu, 16 bitte 131.072 bayttır. Ağırlıklar 16 bit. Gerçek 8 bitlik cache biçimleri birkaç ölçek sayısı da saklar, bu yüzden tasarruf yarıdan biraz azdır. Süre, yukarıdaki örnek kart (saniyede 1.000 GB okuyan) için basitleştirilmiş bir tahmindir; yalnızca bellek okumalarını sayar. Burada GB bir milyar bayt demektir.
Bunun gibi bir modelde 128.000 tokenlık bir sohbet, cache için aşağı yukarı bütün ağırlıkları kadar bellek ister. llama.cpp gibi bazı araçlar cache'i en büyük uzunluk için baştan ayırır, bu yüzden bu bellek sohbet uzamadan önce de kullanılır.
Kullandığınız araç destekliyorsa işe yarayan birkaç yol var: Yeni bir sohbet başlatmak, eski mesajları özetlemek, daha küçük bir en büyük uzunluk ayarlamak ya da cache'i daha az bitle saklamak. Sonuncusu, ağırlıklar için kullanılan quantization ile aynı fikirdir.
İki aşama, bir modelin ne kadar hızlı hissettirdiğini anlatmak için kullanılan sayıları da açıklar. İlk tokena kadar geçen süre (time to first token), ekranda bir şey görünmeden önce ne kadar beklediğinizdir ve çoğunlukla prefill'den gelir. Token başına süre (time per output token), cevaptaki iki token arasındaki aralıktır ve decode'dan gelir. İkisi birlikte toplam bekleyişi aşağı yukarı verir: İlk tokena kadar geçen süre, artı cevaptaki token sayısı çarpı token başına süre. Bir örnek seçin ve beklemeyi hissetmek için Gönder'e basın.
Burada beklemenin çoğu decode: cevabın yazılması.
Gerçek zamanlı oynar. Hızlar, llama.app rehberindeki llama bench örneğinden: 1 milyar ağırlıklı küçük bir model (yaklaşık 4 bite quantize edilmiş Gemma 3 1B) saniyede yaklaşık 2.184 prompt tokenı okuyor ve yaklaşık 115 cevap tokenı yazıyor. Gerçekte hızlar sohbet uzadıkça da düşer, bu yüzden uzun promptlar ve uzun cevaplar bu düz tahminden biraz daha uzun sürer.
Uzun bir prompt, örneğin yapıştırılmış bir belge, cevap başlamadan önce daha uzun beklemenize yol açar. Uzun bir cevap ise akışı uzatır. Bu yüzden "saniyede token" gibi tek bir sayı bir modeli anlatmaya yetmez: Her zaman promptun okunmasını mı, cevabın yazılmasını mı kastettiğine bakın. Rehberdeki örnekte model, prompt tokenlarını cevap tokenlarını yazdığından yaklaşık 19 kat daha hızlı okudu.
Yani aynı model iki çok farklı hızda çalışır. Prefill promptun tamamını bir kerede okur ve genellikle hesapla sınırlıdır. Decode her seferinde bir token yazar ve genellikle bellekle sınırlıdır. KV cache, decode'un eski işi yeniden yapmasını önler; bunun bedeli, sohbetle birlikte büyüyen bellektir. Hangi aşamayı beklediğinizi bildiğinizde, ilk kelimeden önceki bekleme de akışın hızı da anlam kazanır.
Yararlanılan kaynakPrefill vs. Decode · llama.appBu içeriği sen destekleyebilirsinBu alanı sen alhello@interactively.info0/50 alkış
yapay zeka, yazılım ve tasarım üzerine etkileşimli yazı ve kurslardan haberdar olmak için bültene katılabilirsiniz. Ayda en fazla birkaç e-posta alırsınız.
800’den fazla meraklı okura sen de katıl.