1 puan yazan GN⁺ 2023-07-23 | 1 yorum | WhatsApp'ta paylaş
  • 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_grammar ile filtreledikten sonra seçilen token'ı llama_grammar_accept_token ile dilbilgisi durumuna yansıtıyor
  • main içine --grammar ve --grammar-file argü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
  • main içine üretim dilbilgisini belirtmek için BNF benzeri dilbilgisi örnekleri ekleniyor

Grammar API yapısı

  • llama API'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, type ve value alanları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ıyor
    • llama_grammar_accept_token, seçilen token'ı dilbilgisi durumuna ekliyor

main içindeki dilbilgisi girdi özelliği

  • main içine --grammar ve --grammar-file argü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
  • root kuralı, 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.gbnf kullanıldığında 1. e4 e5, 2. Nf3 Nc6 gibi 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 =640 gibi aritmetik ifade biçimiyle sınırlandırılıyor
    • Dilbilgisi olmadığında Go kod parçacığı üretiliyor
  • JSON örneği
    • grammars/json.gbnf kullanı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.cpp iç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_grammar ikili 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 > 1 token 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

 
GN⁺ 2023-07-23
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 LLM 1600 Pennsylvania Ave NW" döndürür; kapanış tırnağını görür görmez kullanıcı ", "City": " enjekte eder ve ardından Washington, DC" alınır
    Ancak 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

    • Dahası, LLM tek tek tokenları doğrudan üretmez; bunun yerine ağırlıklandırılmış bir öneri listesi çıkarır. En olası token en yüksek ağırlığa sahiptir, ama tırnak işareti gibi JSON sembolleri dahil birden çok alternatif olabilir
      temperature ayarı, 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 de temperature'ın kontrol ettiği rastgelelik kalabilir
      Daha 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
    • Gerçekte çalışma şekli, mevcut çıktıyı ve örnekleme adayı olan sonraki tokenları gramerle doğrulamak, ardından uymayan tüm aday tokenları elemek şeklinde. Geriye kalan geçerli token listesine de normal örnekleme stratejisi uygulanıyor
    • Bu açıklama doğru görünmüyor. Daha önce de LLM'lerde tokenları tek tek okuyup bir durma karakteri ile karşılaşınca duracak şekilde çıktıyı kontrol etmek mümkündü
      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 uygulamaya dair daha ayrıntılı bir açıklama da var: https://github.com/normal-computing/outlines/pull/131
      Bunu Outlines kütüphanesinin geliştiricilerinden biri yazmış; Outlines da iyi bir LLM iş akışı kütüphanesi
    • Ne dendiğini pek anlayamadım. “passing up” kısmından başlayarak, sanki bu kişi terimleri yeniden uydurmuş gibi geliyor. Bir LLM'ye verebileceğiniz tek girdi, tokenlaştırılan bir istemdir
      İ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

    • Serbest biçimli bir dize ortaya çıktığında bunun, LLM'nin tamamen alakasız bir yöne sapması için açık bir davetiye olabildiği de önemli. Bu yüzden böyle yöntemleri, serbest metin alanına girildiğinde örneklemeyi tekrar cezası gibi başka sezgisel yöntemlerle yanlılaştıran yaklaşımlarla birlikte kullanmak iyi olur
    • Yine de Llama için birkaç örnek LoRA ile öğretilebilir
      Ö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ü

    • Şaşırtıcıydı ama Nous Hermes haiku yazma konusunda oldukça ikna edici
  • 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

    • Paylaşırsanız harika olur. LLM arayüzlerinden birinde token olasılık vektörü dökmeyi deniyordum; başka bir başlangıç noktası olması oldukça yardımcı olabilir
  • Ç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

    • Çıkarım API’sinde HTML, JSON, Python gibi standart dilbilgisi kısayollarını belirtebilmek kullanışlı olurdu. OpenAI’nin Code Interpreter modelini ince ayar yapmak için harcadığı çabayı düşününce, bunu hâlâ yapmamış olmaları açıkçası garip
    • ANTLR4 dilbilgisi sözdizimini desteklese gerçekten harika olurdu. Müthiş bir araç
  • 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

    • LLM, “bir sonraki token”ı doğrudan üretmez. Girdi metninde her konum için token’lara karşılık gelen bir olasılık vektörü üretir; her değer de o token’ın sıradaki olma olasılığına yakın düşünülebilir
      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’den JSON ya da dilbilgisi olan başka bir dil üretmesini istediğinizde bazen geçersiz sözdizimi üretir. Bu PR, örnekleme prosedürünü değiştirerek LLM’yi kullanıcı tarafından sağlanan dilbilgisine göre yalnızca geçerli sözdizimi üretmekle sınırlandırıyor
      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
    • LLM keyfi dizgileri memnuniyetle üretir. Siz “Alice: 42” gibi bir şey isterken o, “Merhaba, ben yardımcı bir modelim ve bana göre Alice tam olarak forty two ama ben sadece bir dil modeliyim” gibi bir şey döndürebilir
      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
    • Benim yorumuma buradan bakabilirsiniz: https://news.ycombinator.com/item?id=36820884
  • 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

    • Geçici bir çözüm olabilir ama önemli bir geçici çözüm. Önümüzdeki birkaç yıl içinde LLM’lerin kısıtlı metin üretimi problemlerini “doğal olarak” çözüp çözemeyeceği net değil
    • Hiç uzman değilim ama OpenAI’nin programlama için bazı GPT API’lerinde buna benzer bir yöntem kullandığını biliyorum. Basit dilbilgisi işlemeyi ona uygun basit bir sürece devretmenin, LLM’nin karmaşık işlerde hesaplama kaynaklarını daha verimli kullanmasını sağlayabileceğine dair tahminler de gördüm. Doğru mu bilmiyorum
  • 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.