Llama: Dilbilgisi Tabanlı Örnekleme Eklendi
(github.com/ggerganov)- llama.cpp PR #1773, örnekleme adaylarını bağlamdan bağımsız dilbilgisi ile sınırlayan bir API ekleyerek, üretim sonucunun belirtilen biçimin dışına çıkmamasını sağlayan bir özellik öneriyor
- API, 32 bit kod noktası tabanlı dilbilgisi veri yapısını alıyor, aday token'ları
llama_sample_grammarile filtreledikten sonra seçilen token'ıllama_grammar_accept_tokenile dilbilgisi durumuna yansıtıyor mainiçine--grammarve--grammar-fileargümanları eklenerek genişletilmiş BNF biçiminde dilbilgisi girişi verilebiliyor; örnek olarak Chess, aritmetik ifadeler, JSON ve Japonca karakter aralığı üretimi sunuluyor- Testler M2 Max ve 30B Q4_0 modelinde yapıldı; dilbilgisi uygulandığında çıktı satranç notasyonu, aritmetik ifade, JSON ve Japonca karakter aralığına uygun şekilde sınırlandırılırken, dilbilgisi olmadığında istemden farklı genel metin veya kod üretiliyor
- Tartışmalarda dilbilgisi dosyası desteği, boş satır ve yorum işleme, parser'ın API'ye dahil edilip edilmeyeceği, GPU tabanlı logit masking, sabit token toplu işleme ve performans ek yükü gibi gerçek kullanım kısıtları üzerinde yoğunlaşılıyor
PR'ın temel değişikliği
- Bu, llama.cpp'ye dilbilgisi tabanlı örnekleme ekleyen bir PR
- Önceki çalışmalar olarak #1397 ve grantslatton'ın CFG çalışması referans alınıyor
- Yeni API, serileştirilmiş bir bağlamdan bağımsız dilbilgisi alarak örneklemeyi yönlendiriyor ve kısıtlıyor
mainiçine üretim dilbilgisini belirtmek için BNF benzeri dilbilgisi örnekleri ekleniyor
Grammar API yapısı
llamaAPI'si, 32 bit kod noktaları için bağlamdan bağımsız dilbilgisi veri yapısını alıyor- Dilbilgisi öğesi türleri; kural sonu, alternatif başlangıcı, kural referansı, karakter, karakter aralığı üst sınırı ve alternatif karakter eklemeyi ifade ediyor
- Başlatma işlevi şu bilgileri alıyor
- kural dizisi
- kural sayısı
- başlangıç kuralı indeksi
llama_grammar_element,typevevaluealanlarına sahip;value, Unicode kod noktası veya kural ID'si olarak kullanılıyor
Örnekleme yöntemi
- Dilbilgisi örnekleme kodu, deterministik olmayan yığıtlı otomatı modelliyor
- Olası ayrıştırma durumlarını göstermek için N adet yığın tutuluyor
- Token örnekleme iki aşamada çalışıyor
llama_sample_grammar, aday token'lar içinden en az bir ayrıştırma yığınıyla eşleşenleri bırakıyorllama_grammar_accept_token, seçilen token'ı dilbilgisi durumuna ekliyor
main içindeki dilbilgisi girdi özelliği
mainiçine--grammarve--grammar-fileargümanları ekleniyor- Bu iki argüman, üretim sonucunu kısıtlamak için basit bir genişletilmiş BNF dilbilgisini alıyor
- Dilbilgisi parser'ı
examples/grammar-parser.{h,cpp}içinde uygulanıyor - Desteklenen dilbilgisi özellikleri karakter aralıkları, gruplama ve tekrar operatörleri
rootkuralı, dilbilgisinin başlangıç noktasını tanımlıyor- Sonraki güncellemelerde dilbilgisi dosyası desteği, örnekler, shell tarzı yorumlar, kurallar arasındaki boş satırlar ve parantezli gruplar içinde satır sonlarına izin ekleniyor
Test örnekleri
- Test ortamı olarak M2 Max, 30B model belirtiliyor
- Chess örneği
--grammar-file grammars/chess.gbnfkullanıldığında1. e4 e5,2. Nf3 Nc6gibi satranç notasyonu biçimi üretiliyor- Aynı istem dilbilgisi olmadan çalıştırıldığında Sir Thomas Gresham ile ilgili genel bir cümle üretiliyor
- Aritmetik ifade örneği
- Satır içi dilbilgisi ile
expr "=" ws num "\n"biçimi zorunlu tutuluyor - Sonuç,
10 *a*1 +b*2 =640gibi aritmetik ifade biçimiyle sınırlandırılıyor - Dilbilgisi olmadığında Go kod parçacığı üretiliyor
- Satır içi dilbilgisi ile
- JSON örneği
grammars/json.gbnfkullanıldığında{ "fullName": ..., "address": ... }biçiminde JSON yapısı üretiliyor- Dilbilgisi olmadığında kendini tanıtan düz yazı üretiliyor
- Japonca örneği
grammars/japanese.gbnf, hiragana, katakana, noktalama ve CJK aralığına izin veriyor- Dilbilgisi uygulandığında Japonca karakter tabanlı bir liste üretiliyor
- Dilbilgisi olmadan çalıştırıldığında İngilizce adım listesi üretiliyor
İnceleme ve tasarım tartışmaları
- Dilbilgisinin dosyadan alınmasına yönelik öneri geldi ve sonrasında grammar file desteği ile örnekler eklendi
- İlk kullanım sırasında
--prompt-cacheçakışması ve dilbilgisi içindeki boş satırların çökme yaratması gibi sorunlar bildirildi - Boş satır ve yorum desteği ile parantezli gruplar içinde satır sonuna izin, sonraki commit'lere dahil edildi
- Parser'ın llama.cpp API'sine dahil edilip edilmeyeceği de tartışıldı
- Parser API dışında kalırsa, downstream kullanıcıların bu özelliği desteklemek için parser'ı kopyalaması gerekeceği yönünde kaygı dile getirildi
- Yazar,
llama.cppiçindeki değişiklikleri azaltmak istediğini ancak parser'ın API'ye konmasının daha kullanışlı olacağına katıldığını belirtti
llama_grammarikili dilbilgisi kopyasını saklarsa, kullanıcının verdiği kopyanın yaşam süresini koruması gerekmez önerisi de sunuldu
Performans ve optimizasyon tartışmaları
- Sonraki
N > 1token dilbilgisi tarafından tekil biçimde belirlenmiş olsa bile, şu anda bunların yine tek tek örneklendiği anlaşılıyor - Birden fazla sabit token'ı toplu değerlendirme ile işlemek, bu tür durumlarda çıkarım hızını önemli ölçüde artırabilir şeklinde tartışıldı
- Yazar, token'ların yine de değerlendirilmesi gerektiğini ve darboğazın değerlendirme tarafında göründüğünü söyledi; dize düzeyinde toplu değerlendirmenin bir optimizasyon noktası olabileceğini düşündü
- Dilbilgisinin GPU üzerinde durum geçiş tensörüne derlenmesi veya tüm token kümesi için GPU logit masking yapılması fikri de ortaya atıldı
torch-grammar, tüm token kümesi üzerinde GPU'da dilbilgisi zorlamalı logit masking yapan benzer bir yaklaşım olarak anıldı
Performans gözlemleri
- Yazar, yalnızca CPU çıkarımı denediğini ve kendi test ettiği kapsamda performans etkisinin büyük olmadığını belirtti
- M2 Max üzerinde gözlenen değerler
- Kısıtsız örnekleme yaklaşık 0.5ms/token
- Dilbilgisi uygulanmış örnekleme yaklaşık 6ms/token
- 13B Q4_K için token değerlendirmesi yaklaşık 70ms/token
- Başka bir kullanıcı, 13B modelde yaklaşık 20T/s'den 13T/s'ye düşen bir örnek bildirdi
- Yazar, dilbilgisi ek yükünü genelde yaklaşık 5ms/token düzeyinde gördüğünü, ancak bazı dilbilgilerinde etkinin daha büyük olduğunu ve patolojik durumlar olabileceğini söyledi
İlgili genişleme tartışmaları
- JSON Schema girdisini BNF'ye dönüştürerek kullanma yönü anıldı
- Yazar, jsonformer README örneğine uygun çalışan yerel bir dalı olduğunu ve Python betiğiyle JSON Schema'ya uygun JSON BNF ürettiğini belirtti
- Sonrasında
examples : generate JSON according to schema #1887, bu PR ile bağlantılı olarak anıldı - Bu yaklaşımın model varyantlarından bağımsız olduğu ve fine-tune modellerle birlikte de kullanılabileceği yanıtı verildi
- whisper.cpp için de dilbilgisi tabanlı örnekleme ekleyen ayrı bir PR'dan bahsedildi
1 yorum
Hacker News görüşleri
Benim anladığım çalışma şekli şöyle. Dil modeli, istemi başlangıç noktası alıp tokenları teker teker üretir; LLM ile sohbeti de kullanıcının bir token dizisi verip modelin bir kısmını üretmesi, sonra kullanıcının devamını eklemesi şeklinde görebiliriz
Bu gramer tekniği, tokenlar üzerinde çok daha ince kontrol sağlıyor. Örneğin
Give me the address of the White House as JSON: {"street": "kısmına kadar verirseniz LLM1600 Pennsylvania Ave NW"döndürür; kapanış tırnağını görür görmez kullanıcı", "City": "enjekte eder ve ardındanWashington, DC"alınırAncak bu gramer tabanlı olduğu için yalnızca JSON değil, çok daha fazlasını yapabilir. Daha önce Twitter'da gördüğüm bir öneri de iyiydi; OpenAI'nin deterministik bağlamdan bağımsız grameri API parametresi olarak kabul etmesi ya da daha da ileri gidip örnekleyicinin kendisi olan küçük bir WASM ikilisini birkaç KB boyutunda alıp birkaç MB bellekle çalıştırması hâlinde LLM yeteneklerinin büyük ölçüde genişleyebileceği söyleniyordu
https://twitter.com/grantslatton/status/1637692033115762688
temperatureayarı, en üst sıradaki olmayan tokenların seçilme olasılığını ayarlayarak tekrarlayan çıktıyı azaltır. LLM'yi grameri izlemeye zorlamak genelde token seçilmeden önce bu listeyi filtrelemek demektir; yine detemperature'ın kontrol ettiği rastgelelik kalabilirDaha gelişmiş bir özellik olarak, yapay zekanın sıkışıp geçerli bir çıktı üretemediği durumda geri izlemeye izin veren bir yaklaşım da var
Bence gramer tabanlı örnekleme PR'ının özü, llama.cpp'nin gramer kullanarak bir sonraki çıktı tokenını olası tokenların sınırlı bir kümesine daraltması
Bunu Outlines kütüphanesinin geliştiricilerinden biri yazmış; Outlines da iyi bir LLM iş akışı kütüphanesi
İsteğin parçası olarak DCFG kurallarını ya da derlenmiş sürümünü gönderseniz bile, bunun token tahmin yöntemini temelden nasıl değiştirdiğini anlamıyorum. Model istenen gramerle uyuşmayan bir şey tahmin ederse, uyana kadar istemi yeniden mi vereceğiz denmek isteniyor?
Bunun, modelin ürettiği metne yalnızca gramersel kısıtlar koyduğunu; içerik hizalamasını gerçekten sağlamadığını belirtmek gerekir. Sunucunun düzgün biçimlendirilmiş JSON üretmesini garanti etmek istediğinizde faydalı ama mevcut dil üretimindeki hizalama sorunlarının çoğunu çözecek gibi görünmüyor
Örneğin bugün Llama ya da GPT, Markdown kod bloklarının etiketlerini sık sık yanlış koyuyor. Gramer tabanlı örneklemeyle etiketi koymaya zorlayabilirsiniz, ama doğru etiket olup olmadığı bağlama bağlıdır ve zorlanamaz. Yeni bir alan özel dil oluşturup sonra o dile göre hizalama yapmadan iyi çıktı beklemek de zor
Örneğin, Markdown için bir LoRA'yı ve bir Markdown gramer dosyasını ihtiyaca göre hot-swap eden bir sistem hayal edilebilir
Buna gerçekten bayıldım. Daha önce Constrained Text Generation Studio'yu yapmayı denemiştim(https://github.com/Hellisotherpeople/Constrained-Text-Genera...) ve ilgili çalışma COLING 2022'de de yayımlandı(https://paperswithcode.com/paper/most-language-models-can-be...)
Yine de her zaman, bu yaklaşımın ya da bu makalede sıralanan ilişkili fikirlerin gidilecek yön olduğunu düşündüm: https://arxiv.org/abs/2306.03081
Şimdi hece sayısı ya da sözdizim kuralları gibi şeyleri zorlayabilen gramerleri nasıl yazabileceğimizi düşünmem gerekiyor. Bugünkü LLM'ler, tokenlaştırma biçimleri nedeniyle bu tür işlerde çok kötü
Bunu PyTorch için de uyguladım: https://github.com/Shopify/torch-grammar. Bunu kullanan, hack'lenmiş bir text-generation-inference sürümü de var; gerekirse paylaşabilirim
Çoktan seçmeli dize enum'ları, yani fiilen bir açılır liste söz konusu olduğunda, son seçimi yaparken açgözlü algoritma yerine verilen logitler altındaki tam birleşik olasılığı hesaba katmanın daha iyi çalışıp çalışmayacağını merak ediyorum
Başlangıç tokenı listedeki birden fazla öğe tarafından paylaşıldığında, bu yöntem en yaygın başlangıç tokenını içeren seçeneği değil doğru seçeneği tercih etmeye yardımcı olabilir. Elbette bazı logitler 0 olduktan sonra olasılıkları yeniden ayarlamak gerekir ki gerçekten anlamlı olsun
Bu dilbilgisi “kütüphanesi” biçim örneği olarak gösterilmiş: https://github.com/antlr/grammars-v4
Burada assembly ve C++’tan GLSL’e, betik dillerine, aritmetiğe, oyunlara, freedesktop kısayollarına, LLVM IR’a, Verilog gibi sıra dışı biçimlere kadar her şey var
Bunu biri basitçe açıklayabilir mi? LLM’leri bir ölçüde biliyorum ama Georgi’nin burada ne yaptığı ve neden bazılarının bu kadar heyecanlandığı bana pek oturmuyor
ChatGPT gibi programlar bu olasılık vektörünü “yorumlayıp” üst sıralardaki token’lardan birini seçer, yani örnekleyerek metin oluşturur. Ancak bu yöntem bazen fazla esnektir; JSON çıktısı isteseniz bile JSON dilbilgisine uymayan bir token seçip bozuk JSON üretebilir
LLM’yi JSON gibi bir şey üretmeye “zorlamanın” yolu örnekleme sürecini değiştirmektir. Rastgele üst sıralardaki herhangi bir token’ı seçmek yerine, önce yalnızca JSON dilbilgisine uyan token’ları bırakırsınız, sonra örneklemeyi bu alt küme içinden yaparsınız
LLM metni token token üretir. Önce dev bir sinir ağı mümkün olan tüm token’lara olasılık atar, sonra örnekleme prosedürü bu olasılıkları kullanarak bir token seçer ve bu süreç tekrar eder
Örnekleme prosedürü sinir ağının kendisi değildir, bu yüzden çeşitli şekillerde değiştirilebilir. Her zaman olasılığı en yüksek token’ı seçen açgözlü örnekleme yapılabilir, ancak genelde olasılıkla ağırlıklandırılmış rastgele seçim daha iyidir. Çeşitlilik sağlar ve döngüye girme ihtimalini azaltır. Ama olasılığı sıfır olmayan herhangi bir token seçilebileceği için bozuk JSON çıkabilir. Bu PR, dilbilgisi açısından geçersiz olan tüm token’ların olasılığını 0 yaparak seçilmelerini engelliyor
Örnekleme sürecinde başka ilginç değişiklikler de yapılabilir. Token’ları tek tek örneklerken çıkmaz bir noktaya girip seçenek kalmayabilir; bu durumda geri izlemeye izin verilebilir. Hatta her adımda birden fazla seçeneği değerlendirip olası çıktılardan bir ağaç oluşturabilir, sonunda toplam olasılığı en yüksek yolu seçebilirsiniz. Tüm seçenekleri değerlendirmek, olası token sayısı kadar dallanan tam bir ağaç anlamına geldiğinden üstel olarak büyür; bu yüzden her adımda örneğin en iyi 5 yolu bırakıp geri kalanını budayabilirsiniz. Buna beam search denir. Olasılıkları üreten sinir ağı çalıştırmasının maliyeti çok yüksek olduğundan, maliyeti 5 kat artırmak LLM’lerde genelde tercih edilmez; ama mümkündür ve sonuçları da bir ölçüde iyileştirir. Satranç motorlarında olduğu gibi Monte Carlo ağaç araması kullanmak da düşünülebilir
Bu yüzden yanıta, bir büyük harf, ardından küçük harfler, iki nokta üst üste, bir boşluk ve rakamlar gelip bitmesi gerektiğini söyleyen bir dilbilgisi verirsiniz. Böylece ilk token aranırken yalnızca bu desenle uyumlu token’lar dikkate alınır; sonrasında da yalnızca bir sonraki desenle uyumlu token’lar değerlendirilmeye devam eder
Bu tür dilbilgileri bunu esnek ve kullanışlı bir desenle yapar
Bu ilgimi çekti, yaptığım işe katmayı düşünüyorum. Yine de bunun, Bitter Lesson açısından çok kısa vadeli durumlar dışında en iyi yaklaşım olmayabileceği hissine kapılıyorum: http://www.incompleteideas.net/IncIdeas/BitterLesson.html
Benzer bir yöntem kullanan bir proje de var: https://github.com/automorphic-ai/trex
Playground: https://automorphic.ai/playground
Uçtan uca sinir ağları çağında sampling/decoding işleminin nasıl yapıldığını anlatan makale ya da genel bakış önerileri almak isterim. HMM döneminde makine çevirisi ve konuşma tanımada decoding’in nasıl yapıldığını biliyorum; örneğin https://en.wikipedia.org/wiki/Viterbi_algorithm veya https://en.wikipedia.org/wiki/Beam_search gibi şeyler.
Bugünlerde insanların bunu sadece “greedy” yöntemle yapıyormuş gibi göründüğü izlenimine sahibim ama çok emin değilim. Bu konuda kaynak önerisi olursa sevinirim.
https://platform.openai.com/docs/api-reference/completions/c...
Elbette artık GPT-4’ün mixture of experts mimarisi olduğunu bildiğimiz için içeride hesaplamayı paralelleştiriyor. Ayrıca logits’i presence/frequency penalty terimleriyle değiştirme yöntemi de buna dahil.