Sakhanda Wire
NVDA $230.86 +1.09% MSFT $512.80 -0.02% GOOGL $338.24 -1.70% META $725.93 +0.10% AMZN $248.23 -0.37%
← До новин

Perplexity докладно описує свій стек ембедингів на GPU: як Ivy, Tulip і ROSE обслуговують pplx-embed

Якість пошуку в продукті ШІ обмежена двома чинниками: якістю моделі embedding і вартістю її запуску для всього індексу. Цього тижня команда Perplexity Engineering опублікувала матеріал Швидкі embedding на GPU — детальний опис другого аспекту, тобто інфраструктури обслуговування, що лежить в основі pplx-embed і моделей ранжування, які використовуються в Perplexity Search, Computer та API Platform.

Команда Perplexity зазначає, що інференс embedding на стороні GPU значною мірою зблизився між різними рушіями на зрілих апаратних платформах Hopper і Blackwell. Основні переваги забезпечують середовище виконання та обв’язка навколо моделі: керування CUDA-графами, абстракція асинхронного відстеження результатів і шлях обробки запитів на Rust.

Два шаблони трафіку, один рушій

Perplexity розглядає обслуговування embedding як два типи навантаження. Пакетне створення embedding відбувається під час побудови або повторної індексації векторної бази даних, де пропускна здатність мінімізує витрати. Онлайн-створення embedding відбувається під час виконання запиту, коли короткий запит потрібно перетворити на embedding якнайшвидше. Ранжування займає проміжне місце: після векторного пошуку ранжуються великі пакети документів, що дає змогу збалансувати обидва показники.

Ключове рішення полягає в тому, що Perplexity не створювала окремий рушій для embedding. Оскільки моделі embedding є невеликими Transformer-моделями, пакетне створення embedding подібне до обчислювально обмеженого prefill, а онлайн-створення embedding, яке часто працює з кількома токенами, подібне до memory-bound decode. Тому дослідницька команда повторно використовує ядра prefill і decode зі свого стеку LLM.

Ivy, Tulip і ROSE

Запит обробляють три сервіси:

  • Ivy — це HTTP-шлюз на Rust. Він виконує роботу на стороні CPU — розбір JSON, токенізацію, шаблонізацію вхідних даних, розділення пакетів — і перетворює запити на власний протокол gRPC. Він також розділяє запити великих пакетів на частини та розподіляє навантаження між репліками, усуваючи дисбаланс навантаження, який виникає, коли розмір даних у робочих запитах відрізняється.
  • Tulip — це інтерфейс сервера інференсу: gRPC-сервер, створений за допомогою Rust, tokio і tonic, який виконує планування та пакетування перед передаванням даних рушію.
  • ROSE (Runtime-Optimized Serving Engine) реалізує інференс моделі. Він переважно написаний на Python, надає ядра, шари й визначення моделей, керує CUDA-графами та відкриває для Tulip функцію step().

Чому планувальник навмисно простий

Tulip обирає послідовності в порядку надходження, поки запити накопичуються. Таку простоту виправдовує вимірювання: для невеликих моделей embedding і довжин послідовностей, які обслуговує Perplexity, лінійна вартість щільних шарів переважає квадратичну вартість механізму attention. Тому затримка приблизно пропорційна кількості токенів, а не кількості послідовностей. Коли пакет заповнює GPU — приблизно 512 токенів у моделі з менш ніж мільярдом параметрів — додавання нових послідовностей уже не підвищує ефективність.

CUDA-графи та LazyTensors

Для невеликих пакетів запуск ядер на стороні CPU може тривати довше, ніж їхнє виконання на GPU. Perplexity створює CUDA-графи всієї моделі для всіх моделей embedding, об’єднуючи всі запуски в один виклик драйвера. Оскільки моделі embedding невеликі, точка, у якій робота GPU починає перевищувати вартість запуску, настає для пакетів із тисячами токенів і десятками послідовностей. Деякі реалізації attention блокують графи всієї моделі, оскільки залежать від динамічних вхідних даних на стороні хоста; Perplexity передала зміни до основної гілки FlashInfer, щоб уможливити захоплення.

Графи потрібно захоплювати для кожної конфігурації, тому кількість токенів доповнюється до сегментів, кратних 64 або 256. У результаті для кожної моделі створюються тисячі графів, а захоплення триває кілька хвилин. Виправленням стало ліниве захоплення: для кожної конфігурації спочатку виконується прогрівання в eager-режимі, а під час другого звернення запускаються захоплення та відтворення. Це збільшує затримку p99 під час запуску, але розподіляє кількахвилинну роботу в eager-режимі на кілька годин.

Другий компонент — це LazyTensor, який відстежує буфер хоста з фіксованою сторінкою, а також cudaMemcpyAsync і подію CUDA. Замість того щоб step() блокувався на пристрої, він повертає LazyTensor, завдяки чому асинхронне завдання Rust може очікувати пакет N, поки CPU ставить у чергу пакет N+1.

Send a request</button></div> </div> </div> <div class="pe-panel" id="peP1"> <div class="pe-card"> <div class="pe-txt">Для невеликих пакетів запуск ядер на стороні CPU може тривати довше, ніж робота GPU. Граф <b>CUDA</b> всієї моделі об’єднує всі запуски в один виклик драйвера, тому CPU звільняється для постановки в чергу наступного пакета. Перемикайтеся між двома режимами.</div> <div class="pe-ctl" style="margin:0 0 12px"> <button class="pe-btn ghost on" id="peEager">Запуски eager</button> <button class="pe-btn ghost" id="peGraph">CUDA-граф</button> </div> <div class="pe-lane"><div class="pe-lbl">Хост / CPU</div><div class="pe-track" id="peCpuT"></div></div> <div class="pe-lane"><div class="pe-lbl">Пристрій / GPU</div><div class="pe-track" id="peGpuT"></div></div> <div class="pe-stats"> <div class="pe-stat"><div class="v" id="peLaunches">—</div><div class="k">Виклики драйвера</div></div> <div class="pe-stat"><div class="v" id="peGap">—</div><div class="k">Проміжки простою GPU</div></div> </div> <div class="pe-txt" style="margin:12px 0 0;font-size:11.5px;color:#6E8285">Схематичне зображення. Ширина блоків ілюструє описану в публікації структуру накладних витрат на запуск, а не виміряні часові показники.</div> </div> </div> <div class="pe-panel" id="peP2"> <div class="pe-card"> <div class="pe-txt">Зчитування результатів у звичайному режимі зазвичай змушує синхронізуватися з хостом. <b>LazyTensor</b> відстежує буфер хоста з фіксованою сторінкою, а також асинхронне копіювання з пристрою на хост і подію CUDA, тому Tulip може очікувати пакет N, поки CPU вже готує пакет N+1.</div> <div class="pe-lane"><div class="pe-lbl">CPU — підготовка / синхронізація</div><div class="pe-track" id="peLzC"></div></div> <div class="pe-lane"><div class="pe-lbl">GPU — прямий прохід</div><div class="pe-track" id="peLzG"></div></div> <div class="pe-ctl"> <button class="pe-btn" id="peLzRun"> Запустити 3 пакети</button> <button class="pe-btn ghost on" id="peLzOn">З перекриттям</button> <button class="pe-btn ghost" id="peLzOff">Блокувальний режим</button> </div> <div class="pe-note" style="margin-top:12px" id="peLzNote">З перекриттям: поки GPU обробляє пакет N, CPU вже токенізує та пакує пакет N+1.</div> </div> </div> <div class="pe-panel" id="peP3"> <div class="pe-card"> <div class="pe-txt">Для невеликих моделей embedding із такими довжинами послідовностей лінійна вартість щільних шарів переважає квадратичну вартість attention, тому затримка залежить від <b>кількості токенів, а не кількості послідовностей</b>. Після приблизно <b>512 токенів</b> у моделі з менш ніж 1 млрд параметрів GPU заповнюється, і додавання нових послідовностей перестає допомагати.</div> <div class="pe-lbl" style="margin-top:6px">Токени в пакеті: <span id="peTokV" style="color:#3FB6C4">512</span></div> <input type="range" id="peTok" min="32" max="4096" step="32" value="512"> <div class="pe-lbl">Використання GPU</div> <div class="pe-bar"><div class="pe-fill" id="peUtil"></div></div> <div class="pe-stats"> <div class="pe-stat"><div class="v" id="peUtilV">—</div><div class="k">Насичення</div></div> <div class="pe-stat"><div class="v" id="peState">—</div><div class="k">Режим</div></div> </div> <div class="pe-txt" style="margin:12px 0 0;font-size:11.5px;color:#6E8285">Ілюстративна крива. Точка насичення приблизно на рівні 512 токенів — це значення, наведене в публікації; форма кривої між точками є умовною, а не результатом бенчмарку.</div> </div> </div> <div class="pe-foot"> <span>Джерело: Perplexity Engineering, "Швидкі embedding на GPU" (4 вересня 2026 року)</span> <span><a href="https://www.marktechpost.com">Створено Marktechpost</a></span> </div> <script> (function(){ var R=document.getElementById('pplxEmbedExplainer'); var NOTES=[ '<b>Ivy</b> — розбирає JSON, токенізує за допомогою внутрішнього unigram-токенізатора, застосовує шаблонізацію вхідних даних і розділяє великі пакети, а потім перетворює їх на власний протокол gRPC. Він також розподіляє частини між репліками.', '<b>Tulip</b> — gRPC-сервер на Rust на базі tokio і tonic. Запити накопичуються, поки сервер передає їх далі або очікує; послідовності обираються в порядку надходження та пакуються в пакет для прискорювача.', '<b>ROSE</b> — Runtime-Optimized Serving Engine. Ядра й шари, визначені на Python, керування CUDA-графами та функція step(), яка повертає дескриптор обчислення на GPU. Для embedding KV-кеш не виділяється.' ]; function q(s){return R.querySelector(s)} function qa(s){return R.querySelectorAll(s)} /* tabs */ qa('.pe-tab').forEach(function(t){t.addEventListener('click',function(){ qa('.pe-tab').forEach(function(x){x.classList.remove('on')});t.classList.add('on'); qa('.pe-panel').forEach(function(p,i){p.classList.toggle('on',i==+t.dataset.p)});});}); /* 1 flow */ var note=q('#peNote'),dot=q('#peDot'); function sel(i){qa('.pe-node').forEach(function(n,k){n.classList.toggle('hot',k==i)});note.innerHTML=NOTES[i];} qa('.pe-node').forEach(function(n){n.addEventListener('click',function(){sel(+n.dataset.i)})}); sel(0); var busy=false; q('#peGo').addEventListener('click',function(){ if(busy)return;busy=true;var steps=[[0,'3%'],[1,'40%'],[2,'76%']],k=0; dot.style.transition='none';dot.style.left='3%';dot.style.opacity='1'; sel(0); var iv=setInterval(function(){k++;if(k>2){clearInterval(iv);dot.style.opacity='0';busy=false;return;} dot.style.transition='left .8s cubic-bezier(.4,0,.2,1)';dot.style.left=steps[k][1];sel(k);},900); }); /* 2 cuda graphs */ var cpuT=q('#peCpuT'),gpuT=q('#peGpuT'),mode='eager'; function blk(p,l,w,cls,txt){var d=document.createElement('div');d.className='pe-blk '+cls;d.style.left=l+'%';d.style.width=w+'%';d.textContent=txt||'';p.appendChild(d);} function drawG(){ cpuT.innerHTML='';gpuT.innerHTML=''; if(mode=='eager'){ for(var i=0;i<6;i++){blk(cpuT,1+i*16.4,7,'pe-cpu','запуск');blk(gpuT,8.4+i*16.4,7.6,'pe-gpu','ядро');if(i<5)blk(gpuT,16+i*16.4,7.2,'pe-idle','простій');} q('#peLaunches').textContent='6';q('#peGap').textContent='5'; }else{ blk(cpuT,1,12,'pe-cpu','запуск графа');blk(cpuT,15,26,'pe-cpu','підготовка наступного пакета'); blk(gpuT,13.5,84,'pe-gpu','6 ядер — одне відтворення, без проміжків'); q('#peLaunches').textContent='1';q('#peGap').textContent='0'; }} q('#peEager').addEventListener('click',function(){mode='eager';q('#peEager').classList.add('on');q('#peGraph').classList.remove('on');drawG();}); q('#peGraph').addEventListener('click',function(){mode='graph';q('#peGraph').classList.add('on');q('#peEager').classList.remove('on');drawG();}); drawG(); /* 3 lazytensor */ var lzC=q('#peLzC'),lzG=q('#peLzG'),lzMode='on',lzBusy=false,lzNote=q('#peLzNote'); function drawLz(){ lzC.innerHTML='';lzG.innerHTML=''; for(var i=0;i<3;i++){ if(lzMode=='on'){blk(lzC,2+i*32,14,'pe-cpu','підготовка '+(i+1));blk(lzG,17+i*32,26,'pe-gpu','пакет '+(i+1));} else{blk(lzC,2+i*32,12,'pe-cpu','підготовка '+(i+1));blk(lzC,15+i*32,16,'pe-idle','очікування');blk(lzG,15+i*32,15,'pe-gpu','пакет '+(i+1));} }} function lzRun(){ if(lzBusy)return;lzBusy=true;drawLz(); var bs=lzG.querySelectorAll('.pe-blk'),cs=lzC.querySelectorAll('.pe-blk'); [].forEach.call(bs,function(b){b.style.opacity='.15'});[].forEach.call(cs,function(b){b.style.opacity='.15'}); var all=[].concat([].slice.call(cs),[].slice.call(bs)),j=0; var iv=setInterval(function(){if(j>=all.length){clearInterval(iv);lzBusy=false;return;}all[j].style.opacity='1';j++;},220); } q('#peLzRun').addEventListener('click',lzRun); q('#peLzOn').addEventListener('click',function(){lzMode='on';q('#peLzOn').classList.add('on');q('#peLzOff').classList.remove('on');lzNote.innerHTML='З перекриттям: поки GPU обробляє пакет N, CPU вже токенізує та пакує пакет N+1.';drawLz();}); q('#peLzOff').addEventListener('click',function(){lzMode='off';q('#peLzOff').classList.add('on');q('#peLzOn').classList.remove('on');lzNote.innerHTML='Блокувальний режим: кожен step() очікує пристрій, тому CPU простоює, а GPU починає роботу із запізненням.';drawLz();}); drawLz(); /* 4 batch shape */ var tok=q('#peTok'); function drawT(){ var v=+tok.value;q('#peTokV').textContent=v; var u=Math.min(100,Math.round(100*(1-Math.exp(-v/230)))); q('#peUtil').style.width=u+'%';q('#peUtilV').textContent=u+'%'; q('#peState').textContent=v<512?'Недозаповнений':(v<1200?'Насичений':'Обмежений пропускною здатністю'); q('#peState').style.color=v<512?'#C7A24E':'#3FB6C4'; } tok.addEventListener('input',drawT);drawT(); })(); </script> </div> <script> (function(){function h(){var e=document.getElementById('pplxEmbedExplainer');if(!e)return;parent.postMessage({pplxEmbedH:e.offsetHeight+40},'*');} window.addEventListener('load',h);setTimeout(h,300);setTimeout(h,1200); document.addEventListener('click',function(){setTimeout(h,250)}); document.addEventListener('input',function(){setTimeout(h,120)}); if(window.ResizeObserver){var e=document.getElementById('pplxEmbedExplainer');if(e)new ResizeObserver(h).observe(e);}})(); </script> </body></html>">

Ядра все ще мають значення

ROSE підтримує кілька бекендів attention для нерегулярних вхідних даних: FlashInfer 2, FlashInfer 3 і FlashAttention 4. Команда Perplexity повідомляє, що FlashAttention 4 зазвичай працює швидше, але FlashInfer 3 перевершує його на моделях на основі Qwen за дуже великої довжини послідовностей, тому бекенд обирається в кожному випадку окремо. Важливо, що під час обслуговування моделі embedding ROSE не створює KV-кеш і використовує варіанти нерегулярного attention, щоб уникати доповнення.

Бенчмарки

Perplexity порівнює систему з vLLM v0.22.0 у BF16 на реальних вагах і вхідних даних, отриманих із наборів для оцінювання, а запуски прогрівання підтверджують, що розбіжність косинусної подібності не перевищує 0,1%. Побудовано графіки для чотирьох наборів: embedding із низькою затримкою (пакет 1; 128/512/4096 токенів), ранжування з низькою затримкою (пакети 5/25/50 по 512 токенів), embedding із високою пропускною здатністю (пакет 100, чотири одночасні процеси) та embedding із високою паралельністю (від 1 до 16 одночасних запитів, включно з токенізацією Ivy і мережевими накладними витратами).

Основні висновки

  • Стек embedding Perplexity повторно використовує ядра prefill/decode для LLM, а не запускає окремий рушій.
  • Затримка залежить від кількості токенів, а не кількості послідовностей; приблизно 512 токенів насичують модель із менш ніж 1 млрд параметрів.
  • CUDA-графи всієї моделі разом із лінивим захопленням зменшують накладні витрати на запуск без багатохвилинного старту.
  • LazyTensor перекриває підготовку пакетів на CPU з поточною роботою GPU, замість блокування на синхронізації.
  • Ivy, Tulip і ROSE є внутрішніми компонентами; отримати доступ до pplx-embed можна через Embeddings API Perplexity.

Ознайомтеся з технічними деталями. Також підписуйтеся на нас у Twitter і не забудьте приєднатися до нашого ML SubReddit із понад 150 тисячами учасників та підписатися на нашу розсилку. Стривайте! Ви є в Telegram? Тепер ви також можете приєднатися до нас у Telegram.

Потрібна співпраця з нами для просування вашого репозиторію GitHub, сторінки Hugging Face, запуску продукту, вебінару тощо? Зв’яжіться з нами

Перекладено автоматично з англійської. Оригінал статті — за посиланням нижче.

Вперше опубліковано виданням MarkTechPost

Читати оригінал на MarkTechPost ↗

Текст і зображення належать MarkTechPost і наводяться тут із зазначенням авторства та посиланням на оригінальну публікацію.

← До новин

Ще новини

Усі останні новини