GPU'larda Hızlı Gömmeler

Hızlı ve doğru arama, Arama ve Bilgisayar'dan API Platformumuza kadar tüm Perplexity için hayati önem taşır. Perde arkasında ağır işler, sistemlerimizin belirli bir sorgu için en alakalı sonuçları belirlemesine yardımcı olan gömme ve sıralama modelleri tarafından yapılır. En son teknolojiye ulaşıyoruz

YazarlarPerplexity Engineering

Hızlı ve doğru arama, Arama ve Bilgisayar'dan API Platformumuza kadar tüm Perplexity için hayati önem taşır. Perde arkasında ağır işler, sistemlerimizin belirli bir sorgu için en alakalı sonuçları belirlemesine yardımcı olan gömme ve sıralama modelleri tarafından yapılır. pplx-embed gibi kendi modellerimizi eğitip sunarak en son teknoloji kalite ve gecikmeyi elde ediyoruz.

Bu makale, bu özel model sınıfı için Perplexity'nin sunum altyapısının kaputunun altındaki görünümünü sunmaktadır. Yapay zeka tabanlı aramanın çıkarım ihtiyaçlarını verimli bir şekilde karşılamaya yönelik tekniklerimizi ele alıyor, egabayt ölçekli arama dizinimizi desteklerken modellerin hızlı prototiplendirilmesini ve değerlendirilmesini sağlıyoruz. Bu teknikler toplu olarak arama kalitesi ve verimliliğinin Pareto sınırını genişleterek, en düşük maliyet ve gecikmeyle aracılara ve kullanıcılara mümkün olan en iyi sonuçları sunmamızı sağlar.

Arama için Gömmeler

Tipik bir arama kurulumunda, dizine eklenen belgeler bir gömme modeli kullanılarak yüksek boyutlu bir Alan vektörüne eşlenir ve bir vektör veritabanında saklanır. Aynı model kullanılarak bir sorgu gömülerek, sorgunun vektörüne en yakın vektörler bulunarak benzer belgeler bulunabilir. Bu durum, bir çıkarım motorunun hizmet vermesi için iki farklı trafik modelini ortaya çıkarır:

  • Toplu Gömmeler: veritabanı oluşturulurken, genişletilirken veya yeniden indekslenirken, maliyeti en aza indirmek için iş hacmini en üst düzeye çıkararak toplu belgelerin Alan içine gömülmesi gerekir.

Vektör aramasının ardından, iş hacmi ile gecikme arasında bir denge kurularak büyük belge yığınlarının puanlanması gerekir.

  • Çevrim İçi Gömmeler: veritabanı sorgulanırken, aramalar için kısa bir sorgu gömülmeli ve gecikme en aza indirilmelidir.

Çıkarım altyapımızı, kullanım örnekleri arasında olabildiğince çok ortak bileşenden yararlanacak şekilde oluşturduk. Gömmeler üretmek için genellikle küçük Transformer modelleri kullandığımız için, uygulamanın büyük kısmını LLM çıkarım kodumuzla paylaşıyoruz: toplu gömmeler hesaplama ağırlıklı ön doldurmaya benzerken, genellikle birkaç belirteç üzerinde çalışan çevrim içi gömmeler bellek ağırlıklı kod çözmeye hesapsal olarak benzerdir. Bu nedenle, gömme modellerine hizmet etmek için optimize edilmiş ön doldurma ve kod çözme çekirdeklerimizi yeniden kullanıyoruz. Sonuç olarak, çevrim içi gömme iş yükleri için düşük gecikmeyi korurken minimum ek mühendislik çalışmasıyla devasa toplu çıkarım iş hacmi elde edebiliriz.

Tulip'ler, Gül'ler ve biraz Ivy

İçeride ve API Platformumuz aracılığıyla dışarıda standartlaştırılmış API'ler aracılığıyla çıkarımı sunuyoruz. Kaputun altında, bir gömme isteğinin işlenmesinde birden fazla servis yer alır:

  • Ivy, Perplexity servislerinin çağırdığı bir Rust HTTP ağ geçididir.

JSON ayrıştırma, belirteçleştirme, girdi şablonu oluşturma ve yığın bölme gibi istekler için CPU tarafındaki çalışmaları ele alır ve istekleri aşağı akış sunucuları için özel bir gRPC protokolüne çevirir. Bu ayrım, daha ağır çıkarım örneklerine dokunmak zorunda kalmadan belirteçleştirme ve girdi biçimlendirme etrafındaki belirli parametreleri yapılandırmamıza olanak tanır.

  • Tulip, çıkarım sunucusu arayüzüdür.

Rust, tokio ve `tonic` ile uygulanmış bir gRPC sunucusudur. Tulip, gRPC çıkarım isteklerini alır, zamanlama ve paketlemeyi yönetir. Ardından yığınları ROSE motoruna gönderir ve tamamlanan yanıtları istemcilere döndürür.

  • **ROSE** (Runtime-Optimized Serving Engine) model çıkarımını gerçekleştirir.

Öncelikle Python ile tanımlanmış olup çok çeşitli modeller için çekirdekler, katmanlar ve tanımlar sağlar. ROSE, modeller aracılığıyla ileri yönlü geçişleri gerçekleştirir ve ayrıca gömmeler için özelleştirilmiş CUDA grafik yönetimi sağlar. Bir yığın alan ve hızlandırıcıda gerçekleştirdiği hesaplamaya bir referans döndüren bir step() işlevi aracılığıyla Tulip'e köprülenir.

Bir gömme isteğinden Ivy aracılığıyla çoğaltılmış Tulip sunucularına kadar sunum mimarisi

Çekirdeğin Ötesinde Dikkat Göstermek

Hem Transformer tabanlı modeller hem de alttaki Hopper/Blackwell mimarileri olgun teknolojilerdir; bu nedenle GPU tarafında çıkarım gömme, çeşitli çıkarım motorlarında büyük ölçüde optimal bir uygulamada birleşmiştir. Yine de, modelleri uçtan uca bir istemciye sunan çalışma zamanlarında ve donanımlarda ek iyileştirme fırsatları keşfettik. Özellikle, CUDA grafiklerini dikkatli bir şekilde yöneterek ve yerel Rust motorunda GPU tarafındaki bir sonucu eşzamansız olarak izlemek için bir LazyTensor soyutlaması oluşturarak gecikmeleri iyileştirebileceğimizi gördük. Bu özelliklerin Tulip'e entegrasyonunu sağladık, böylece ROSE model uygulamalarıyla etkili bir şekilde arayüz oluşturabildi.

Tulip

Tulip'i model sunumumuz üzerinde olabildiğince hafif bir arayüz olacak şekilde tasarladık. Gelen istekleri Tokio eşzamansız görevlerinde ele alır, izlediği ve hızlandırıcıya göndermek üzere yığınları zamanladığı bir istek havuzu tutar. Tulip'teki zamanlama mekanizması çok basittir: Tulip iş gönderirken veya sonuçları beklerken istekler birikir. Biriken isteklerden, model üzerinden çalıştırılmak üzere ilk gelen, ilk alır esasına göre diziler seçilir.

Basit zamanlama mekanizması, model performansı üzerindeki bir gözlemden motive edilmiştir. Küçük gömme modelleri için, hizmet verdiğimiz dizi uzunluklarında, yoğun katmanların doğrusal maliyetinin dikkatin kuadratik maliyetinden baskın olduğunu fark ettik. Bu nedenle, gecikme çoğunlukla dizi sayısıyla değil, belirteç sayısıyla orantılıdır. Sonuç olarak, bir yığın GPU'yu doyuracak kadar büyük olduğunda (bir milyar parametrenin altındaki bir modelde yaklaşık 512 belirteçtir), içine daha fazla dizi paketlemek verimliliği artırmaz.

Modelle etkili bir şekilde arayüz oluşturmak için Tulip, GPU ve CPU çalışmalarını çakıştırmak ve mevcut kaynaklardan tam olarak yararlanmak için CUDA grafiklerine ve lazy sonuç takibine güvenir.

CUDA Grafik Yönetimi

Bir modelin ileri yönlü geçişini çalıştırmak hem CPU tarafında hem de GPU tarafında çalışma gerektirir. CPU, yığınları planlamaktan ve uygun parametrelerle çekirdekleri başlatmaktan sorumluyken, GPU ilgili matris çarpımı, dikkat, norm veya aktivasyon çekirdeklerini yürütür. Eğitim ve yeniden indeksleme gibi yüksek iş hacimli iş yükleri için, yığın boyutları ve GPU tarafındaki gecikme büyük olduğundan CPU tarafındaki ek yükler ihmal edilebilir düzeydedir. Ancak daha küçük yığın boyutlarında, CPU tarafındaki çalışma GPU tarafındaki çalışmadan ağır basabilir.

Hevesli ileri yönlü geçiş: cihaz çekirdekleriyle araya eklenmiş ana bilgisayar çağrıları

Ek yükleri azaltmak için, bağımsız çekirdekler başlatmak yerine, bir ileri yönlü geçişin tüm çekirdeklerini tek bir CUDA sürücüsü çağrısıyla başlatmak için gereken meta verileri yakalamak üzere bir CUDA grafiği oluşturulabilir. Bu, CUDA grafiklerinin yakalanabildiği yapılandırmalar için pahalı Python ve PyTorch kodunu yeniden çalıştırma ihtiyacını ortadan kaldırır.

Her modelde, GPU yürütmenin CPU tarafındaki çekirdek başlatmadan daha maliyetli olduğu minimum belirteç sayısını belirleyen bir bükülme noktasını takip ediyoruz. Gömme modelleri küçük olduğundan, bu bükülme noktasının binlerce belirteç ve ondan fazla diziden oluşan toplu işlemlerde ortaya çıktığını gözlemliyoruz. Bazı dikkat uygulamaları, çekirdek başlatmalarını yapılandırmak için dinamik ana bilgisayar tarafı girdilerine güvenir ve bu da tam model ön doldurma/yoğun CUDA grafiklerini engeller. Çıkarım motorumuzda etkinleştirmek için ilgili çekirdeklere yönelik değişiklikleri üst akışa taşıdık.

Ek yükleri ele almak için, tüm gömme modelleri için tüm model CUDA grafikleri oluşturuyoruz ve CPU çalışmasını GPU çalışmasıyla çakıştırıyoruz. CUDA grafikleri CPU tarafındaki ek yükleri en aza indirdiğinden, bir grafik başlatıldıktan sonra, bir sonraki toplu iş ne zaman uygun olursa olsun yürütülmesini başlatmak ve sıraya koymak için boş zamanımız olur. Bekleyen toplu işin sonuçları, Rust'taki bir eşzamansız görevin önceki toplu işin yürütülmesi bitene kadar bloke olmasını sağlayan bir LazyTensor ile izlenir. CUDA grafikleri, çekirdek başlatma maliyeti nedeniyle engellenmememizi sağlayarak düşük gecikmeli sunuma yardımcı olur ve CPU'yu sonraki toplu iş üzerinde daha erken çalışmak üzere serbest bırakarak yüksek iş hacimli durumda gelişmiş zamanlamayı kolaylaştırır.

Cudagraph ileri yönlü geçişi

CUDA grafiklerinin her farklı yapılandırma için yakalanması gerekir; bu da gömmeler için dizi sayısı ve belirteç sayısı kombinasyonu başına bir grafik anlamına gelir. Bu ızgara geniş olduğundan, belirteç sayılarını 64 veya 256'nın katı olan demetlere doldururuz. Bu durum yine de, tipik bir model için yakalanması birkaç dakika sürebilecek binlerce grafikle sonuçlanır. Yakalamanın maliyeti iki kaynaktan gelir: çekirdekleri derlemek ve bunlara ihtiyaç duyan çeşitli çekirdekler için arabellekler ayarlamak üzere yürütülmesi gereken hevesli bir ileri yönlü geçiş, ardından Python kodunu yeniden yürüten yakalama çalıştırması.

Motor hizmet verdikçe CUDA grafiklerini lazy olarak yakalayarak başlangıç maliyetlerini azaltıyoruz. Her yapılandırmayı takip ediyor ve ikinci isabet üzerinde grafik yakalama ve yeniden oynatmayı tetiklemeden önce hevesli bir ısınma çalıştırmasından geçmesini sağlıyoruz. Aynı grafik yapılandırmasının sonraki tüm yürütmeleri daha sonra CUDA grafik yeniden oynatmasından geçer. Lazy grafik yakalamanın başlangıç sırasında p99 gecikmeleri üzerinde bir etkisi vardır; ancak birkaç dakikalık hevesli çalışmayı birkaç saate yaymada değerlidir. Daha hızlı başlangıç süreleri, gömme dağıtımlarını daha iyi ölçeklendirmemize ve yönetmemize olanak tanır.

Lazy Tensor'lar

CUDA aracılığıyla GPU çalışması eşzamansızdır. Bir çekirdeği eşzamansız olarak başlatmak onu bir akışa sıraya koyduğundan, ana bilgisayar kodunun ortaya çıkan vektörleri okumak için açıkça eşitlemesi gerekir. Daha yüksek derecede paralelliği kolaylaştırmak ve cihazda öncekinin tamamlanmasını beklerken gelecekteki toplu işleri başlatabilmek için, değerleri izlemek üzere bir LazyTensor soyutlamasına güveniriz.

LazyTensor, sayfayla kilitlenmiş bellekteki bir ana bilgisayar arabelleğini ve cihazdan veri kopyalayan bir olay aracılığıyla bir cudaMemcpyAsync işlemini izler. Aynı akışta ileri yönlü geçişin başlatılmasından sonra başlatılır. Kopyalama işleminin, akıştaki önceki tüm çekirdeklerin yürütülmesini beklemesi gerektiğinden, ilgili olay hem ileri yönlü geçişin tamamlanmasını hem de sonucun CPU'da kullanılabilir olmasını izler.

LazyTensor yürütmesi

GPU ve CPU çalışmalarını çakıştırmak için ROSE kodlayıcı motorumuzda LazyTensor'lardan yararlanıyoruz. Her step() çağrısının CUDA grafiğini çalıştırması ve bitmesini beklemek yerine, step() sonucunu eşzamansız olarak izlemek için bir LazyTensor döndürür. CUDA grafikleriyle birleştirilen bu yaklaşım, düşük gecikmeler ve daha iyi iş hacmi elde etmemize yardımcı olur.

Ardışık GPU yığınlarını çakıştıran CPU hazırlığı ve eşitlemesini gösteren zaman çizelgesi

ROSE

Orijinal olarak LLM sunumu için oluşturduğumuz ROSE motorumuzu, gömme modellerinin yürütülmesini de ele alacak şekilde uyarladık. Gömme modellerini desteklemek için gereken çabayı en aza indirmek üzere ROSE, LLM'ler ve gömmeler arasında kodu yoğun bir şekilde yeniden kullanır. Örneğin, pplx-embed sunumu ve Qwen3.5 LLM kod çözme işlemlerinin tümü aynı çekirdeklerden geçer. Bu paylaşım, prototipleme, değerlendirme ve üretim çıkarımı için orijinal olarak bir LLM'den ince ayar yapılmış bir gömme modelini kolayca sunmamıza olanak tanır.

Yoğun katmanlar için, belirteç vektörleri bağımsız olarak işlendiğinden gömme ve LLM çıkarımı aynıdır. Dikkat katmanlarında, LLM'ler tarafından gereksinim duyulan sayfalı ön doldurma ve kod çözme kurulumlarının yanı sıra dağınık girdiler için destek eklenerek farklar ele alınır. Bir gömme modeline hizmet verirken, KV önbelleği örneklendirmeyiz ve dolguyu önlemek için dağınık biçimi destekleyen dikkat çekirdeği varyantlarına yönlendiririz. Destekleyen dönüştürme ve kalibrasyon rutinleri de LLM'lerle paylaşılır.

Ivy

Çıkarım HTTP proxy katmanımız Ivy de performansta önemli bir rol oynar. Üretimde istek yükleri değiştiğinden, bireysel istekleri bireysel çoğaltmalara yönlendirmek yük dengesizliğine neden olabilir. Ivy, büyük toplu istekleri parçalara ayırır ve çoğaltmalar arasında yük dengesini sağlar, böylece kullanım oranını artırır ve gecikmeyi düzene sokar. Ivy'de tamamen kullanıma sunulan şirket içi unigram belirteçleştirme üzerindeki son çalışmalarımız, kullanıma hazır belirteçleştiricilere kıyasla gecikmeleri büyük ölçüde iyileştirir.

...ancak Çekirdekler Hala Önemli

ROSE, çeşitli dikkat arka uçlarını destekler. Farklı çekirdekler belirli sorun boyutlarına uygun olabilir. Zaman içinde, dağınık dikkati uygulamak için FlashInfer 2, FlashInfer 3 ve FlashAttention 4 çekirdeklerini entegre ettik.

Model şekline ve sorun boyutuna göre dikkat çekirdeği performansı

Genel olarak FlashAttention 4'ün daha hızlı olduğunu gözlemliyoruz. Ancak FlashInfer 3, çok uzun dizi uzunluklarında Qwen tabanlı modellerde daha iyi performans gösteriyor. Performans ve ayarlama, dikkat başlıklarının sayısına ve boyutuna göre değişebileceğinden, birden fazla yapılandırmayı desteklemeye devam ediyor ve sunum sırasında duruma göre karar veriyoruz.

Kıyaslamalar

Değerlendirme veri kümelerinden türetilen gerçek model ağırlıkları ve girdileri üzerinde BF16 hassasiyetinde çıkarım çalıştırarak vLLM v0.22.0 ile kıyaslama yapıyoruz. Tüm zamanlama çalıştırmalarından önce, kosinüs benzerliğindeki sapmanın %0,1 içinde olduğunu doğrulayan ısınma çalıştırmaları gerçekleştirilmiştir.

Düşük Gecikmeli Gömmeler (p50 / p90 / p99 / maks. ms)

Önceden belirteçleştirilmiş istek yığını boyutu 1, tamamen sıralı istekler, 128, 512 ve 4096 belirteçten oluşan dizi uzunlukları için çalışma zamanlarını bildiriyoruz.

Düşük gecikmeli gömmeler kıyaslama sonuçları

Düşük Gecikmeli Puanlama (p50 / p90 / p99 / maks. ms)

Önceden belirteçleştirilmiş istek yığını boyutları 5, 25 ve 50, dizi uzunluğu 512 belirteç.

Düşük gecikmeli puanlama kıyaslama sonuçları

Yüksek İş Hacimli Gömmeler (gömme/sn)

İstek yığını boyutu 100, istek gönderen dört eşzamanlı süreç, 512, 1024 ve 4096 belirteçten oluşan dizi uzunlukları.

Yüksek iş hacimli gömmeler kıyaslama sonuçları

Yüksek Eşzamanlılıkta Gömmeler (p50 / p90 / p99 / maks. ms)

Dizi uzunluğu 512, yığın boyutu 1, ancak 1, 2, 4, 8 ve 16 eşzamanlı istek gönderiyoruz. Bu kıyaslama, Ivy üzerinden belirteçleştirme maliyetlerinin yanı sıra Ivy ve Tulip arasındaki ağ ek yükünü de içerir.

Yüksek eşzamanlılıkta gömmeler kıyaslama sonuçları

Sonuç ve Gelecek Çalışmalar

Ivy, Tulip ve ROSE'dan oluşan sunum altyapısı, Perplexity için gömmeleri daha düşük gecikmeyle ve daha iyi iş hacmiyle sunmamızı sağlar; bu da kullanıma hazır çözümlere kıyasla daha düşük maliyetle daha doğru arama yapılmasına olanak tanır.

Belirli modellere odaklanıp tüm yığının sahipliğini üstlenerek, yüksek düzeyde yeniden kullanılabilir ve yüksek performanslı Rust ilkellerini daha genel Python modelleme koduyla harmanlayıp performans ile esneklik arasında etkili bir denge kurmak için gereken özgürlüğü elde ediyoruz. vLLM, SGLang ve TokenSpeed gibi birçok açık kaynaklı çıkarım motoru, Rust ve C++ gibi dilleri yığınlarına entegre ediyor. Son iki yıldır Rust'a yatırım yaptık ve hem performans hem de sürdürülebilirlik açısından büyük ödüller elde ettik. Gösterme uygulamasının çoğunu LLM sunum yığınımızla paylaşarak, gömme modellerinin bakımı için önemli bir mühendislik çabası harcamak zorunda kalmadan iş hacminde de kazançlar elde ediyoruz.

Modeller evrim geçirdikçe, hem CPU ağırlıklı hem de GPU ağırlıklı gecikmeleri azaltmak için yığınımızın her katmanını geliştirmeye devam edeceğiz. Ivy ve Tulip içindeki özel gRPC tabanlı protokollerimiz, ağ gecikmelerini azaltmak için iletişimi ayarlamamıza olanak tanırken ROSE, hesaplama iş hacmini iyileştirmek için bir temel sağlar. Ek olarak, ekosistem genelinde serbest iş parçacıklı Python desteği büyüdükçe, ek yükleri azaltmak için Python-Rust birlikte çalışabilirliğini daha da geliştirebileceğiz.

Kaynakça

())}}}})();