Optimizarea inferenței pe dispozitiv pentru Apple silicon

Un motor local personalizat care îmbunătățește debitul de preumplere și decodare.

AutoriPerplexity Engineering

Calculul hibrid pe Apple silicon orchestrează o sarcină între inteligența de frontieră din cloud și un model local pe Mac. Modelele din cloud se ocupă de cercetare și raționament, în timp ce un model local lucrează cu fișiere și aplicații private de pe Mac.

Pentru ca această diviziune a muncii să se simtă fluidă, inferența locală trebuie să țină pasul cu restul sarcinii. Acest lucru necesită un motor care să poată procesa prompturile rapid și să susțină o rată ridicată de generare a tokenilor.

Lily, motorul nostru de inferență locală ușor, este construit special pentru Apple silicon și Qwen3.6-35B-A3B, cu optimizări separate pentru preumplere și decodare. Motorul va deveni open-source în curând.

Introducere

O modalitate comună de a rula modele lingvistice mari (LLM) pe un Mac este utilizarea MLX, cadrul open-source de învățare automată al Apple pentru Apple silicon. Biblioteca sa însoțitoare, MLX-LM, adaugă componentele necesare pentru a încărca și genera text cu o gamă largă de modele lingvistice. Împreună, MLX și MLX-LM oferă o stivă gata de utilizare, de uz general, pentru inferența locală a LLM-urilor.

Qwen3.6-35B-A3B este un model hibrid și rar: folosește rutare bazată pe amestec de experți (MoE) și combină stări recurente de dimensiune fixă cu atenție completă. Aceste alegeri arhitecturale reduc cantitatea de calcule necesară, dar creează și sarcini de lucru neregulate. Tokenii sunt direcționați către diferite ponderi de experți, iar stările recurente au o natură secvențială.

MLX-LM selectează deja kerneler optimizate pentru fazele de inferență și formele comune ale sarcinilor de lucru, dar operațiile sale reutilizabile trebuie să suporte multe arhitecturi de modele. Un motor dedicat Qwen se poate specializa la nivel de model și runtime, coordonând kernelurile, mișcarea datelor și planificarea în jurul structurii fixe a modelului.

Lily implementează această specializare end-to-end într-un singur proces. Un runtime Rust încarcă punctul de control al modelului și gestionează starea sesiunii și bucla de generare, un API de completare a chatului compatibil OpenAI acceptă cereri și transmite tokeni, iar kernelurile Metal personalizate execută operații specifice Qwen. Nici PyTorch, nici MLX nu se află în calea de execuție.

Unde se situează generalitatea și specializarea în cele două stive de inferență. MLX-LM descrie modelul ca operații pe tablouri MLX compozabile, pe care MLX le planifică prin kerneluri reutilizabile. În schimb, Lily plasează structura modelului, planurile de execuție specifice fazei și selecția kernelurilor într-un singur runtime Rust construit în jurul Qwen și Apple silicon.
Unde se situează generalitatea și specializarea în cele două stive de inferență. MLX-LM descrie modelul ca operații de matrice MLX compozibile, pe care MLX le programează prin kerneler reutilizabile. În schimb, Lily plasează structura modelului, planurile de execuție specifice fazelor și selecția kernelurilor într-un singur runtime Rust construit în jurul Qwen și Apple silicon.

Măsurăm performanța de preumplere și decodare separat. Debitul de preumplere surprinde cât de repede procesează motorul promptul; debitul de decodare surprinde cât de repede generează tokeni de ieșire.

Evaluăm Qwen3.6-35B-A3B pe un singur MacBook Pro dotat cu un cip M5 Max, un GPU cu 40 de nuclee și 128 GB de memorie unificată. Pe parcursul a zece lungimi de prompt pentru preumplere și zece lungimi de context pentru decodare, de la 256 la 128K tokeni (K = 1.024), motorul obține în medie un debit de preumplere de 1,23× față de MLX-LM și un debit de decodare de 1,35× față de acesta. La un prompt de 4K tokeni și un context de decodare de 4K tokeni, motorul personalizat atinge 5.749,9 tokeni de preumplere pe secundă și 186,6 tokeni de decodare pe secundă, comparativ cu 4.737,5 și 140,9 pentru MLX-LM. Pe parcursul unei Sesiune cu mai multe schimburi, aceste economii de timp se acumulează cu fiecare apel suplimentar la model.

Debitul mediu aritmetic pe zece lungimi egal ponderate de la 256 la 128K tokeni. Lily obține în medie 4.156 tokeni de prefill/s comparativ cu 3.388 pentru MLX-LM (1,23×) și 170,0 tokeni de decodare/s comparativ cu 126,4 (1,35×). Prefill-ul variază lungimea promptului; decodarea variază lungimea contextului.
Debitul mediu aritmetic pe zece lungimi egal ponderate de la 256 la 128K tokeni. Lily are o medie de 4.156 tokeni de preumplere/s față de 3.388 pentru MLX-LM (1,23×) și 170,0 tokeni de decodare/s față de 126,4 (1,35×). Preumplerea variază lungimea promptului; decodarea variază lungimea contextului.

În continuare, explicăm cum arhitectura lui Qwen creează oportunități de optimizare specifice modelului pe Apple silicon. Apoi parcurgem modificările rezultate pentru preumplere și decodare. De asemenea, abordăm momentul în care optimizarea suplimentară încetează să mai aducă beneficii, înainte de a încheia cu o comparație cap-la-cap împotriva MLX-LM.

Oportunități de optimizare specifice Qwen pe Apple silicon

Qwen creează trei forme distincte de sarcini de lucru

Qwen3.6-35B-A3B conține 35 de miliarde de parametri, dar activează doar aproximativ 3 miliarde pentru fiecare token. Un ruter punctează 256 de subrețele de experți și selectează opt, alături de un expert partajat care procesează fiecare token. Acest design MoE rar reduce calculul, dar produce o muncă neuniformă: experții primesc numere diferite de tokeni, iar fiecare token necesită ponderi de la o altă combinație de experți.

Qwen combină, de asemenea, 10 straturi de atenție completă cu 30 de straturi Gated DeltaNet. Aceste două tipuri de straturi păstrează informațiile anterioare în moduri diferite.

Straturile de atenție utilizează atenția cu interogări grupate (GQA). Qwen are 16 capete de interogare și două capete cheie-valoare (KV), opt capete de interogare partajând fiecare cap KV. Partajarea face ca cache-ul KV să fie mai mic și permite reutilizarea datelor stocate în cache în întreaga capete de interogare. Cache-ul stochează în continuare chei și valori noi pentru fiecare token, astfel încât fiecare pas de decodare citește mai multe date pe măsură ce contextul crește.

Gated DeltaNet comprimă în schimb informațiile anterioare într-o stare recurentă de dimensiune fixă. O poartă învățată controlează cât din starea existentă trebuie păstrată, în timp ce o actualizare delta încorporează informații din tokenul curent. Modelul definește aceste actualizări în mod recurent, astfel încât fiecare token depinde de starea produsă de tokenul precedent. În timpul preumplerii, totuși, un motor poate evalua același calcul în două moduri. Poate scana direct prin tokeni în timp ce transportă starea mai departe, sau poate reorganiza actualizările în blocuri care expun mai multe operații matriciale și paralelism la nivel de token. Abordarea care este mai rapidă depinde de dimensiunile modelului, de sarcina de lucru și de hardware.

Împreună, aceste structuri creează trei modele de calcul: grupuri de experți neuniforme, atenție asupra unui cache în creștere și o recurență de dimensiune fixă care poate fi evaluată direct sau în blocuri.

Apple silicon oferă căi diferite pentru sarcini de lucru diferite

Preumplerea procesează multe rânduri de activare a tokenilor de prompt simultan. Sarcina de lucru locală luată în considerare aici decodifică de obicei o cerere pe rând (lot 1) și procesează un rând nou pe pas. Această diferență schimbă modul în care sunt utilizate aceleași ponderi ale modelului. Preumplerea poate reutiliza fiecare bloc de ponderi pe sute sau mii de rânduri. Decodarea în mare măsură nu poate, deoarece fiecare nou token necesită o altă trecere prin ponderi.

Apple silicon plasează CPU-ul și GPU-ul în spatele memoriei unificate, un singurunchi bazin de memorie fizică accesibil ambelor. Acest lucru permite modelului să rămână rezident fără a menține o copie GPU separată, dar nu face ca mișcarea datelor să fie gratuită. Citirea ponderilor și a valorilor intermediare consumă în continuare lățimea de bandă a memoriei, în timp ce registrele și alte stocări on-chip sunt mai rapide, dar mult mai mici.

GPU-ul M5 oferă, de asemenea, căi de calcul diferite. Straturile liniare ale preumplerii utilizează multiplicarea generală matrice-matrice (GEMM), aplicând o matrice de ponderi pe mai multe rânduri simultan. GEMM-urile compatibile pot utiliza acceleratorul neural din fiecare nucleu GPU prin operații pe tensoare Metal 4. Decodarea batch-1 utilizează în schimb multiplicarea generală matrice-vector (GEMV), aplicând aceleași ponderi pe un singur rând. Cu o reutilizare redusă a ponderilor, GEMV este limitat în principal de lățimea de bandă a memoriei și este mai bine suited pentru unitățile logice aritmetice (ALU) vectoriale ale GPU-ului decât pentru acceleratoarele neurale concepute pentru operații matriciale cu o reutilizare mai mare a datelor.

Aceste căi de execuție nu sunt unice pentru Lily. MLX operează peste aceeași memorie unificată și selectează kerneler matriciale și vectoriale optimizate în funcție de forma sarcinii de lucru. Implementarea Qwen a MLX-LM grupează deja munca experților, evaluează Gated DeltaNet cu un kernel Metal recurent fuzionat și utilizează atenția conștientă de GQA. Aceste capabilități sunt punctul de plecare partajat pentru inferența eficientă Qwen pe Apple silicon.

Strategie de optimizare

Domeniul mai restrâns al lui Lily îi permite să coordoneze aceste căi de execuție partajate în jurul arhitecturii și dimensiunilor exacte ale lui Qwen. Folosește căi GPU specifice fazelor, mapează sarcinile de lucru de tip expert, recurente și de atenție ale lui Qwen pentru a minimiza mișcarea datelor și selectează kerneler și layout-uri în funcție de forma măsurată a sarcinii de lucru. Strategia are trei părți:

  1. Potrivirea căii GPU cu faza de inferență. Utilizați execuția orientată spre matrice atunci când preumplerea poate reutiliza ponderi pe mai multe rânduri și execuția orientată spre vector atunci când decodarea batch-1 procesează un rând pe rând.
  2. Maparea structurii lui Qwen pe GPU, minimizând în același timp mișcarea datelor. Păstrați ponderile comprimate până când sunt utilizate, organizați munca experților rutanți fără a reveni la CPU, păstrați starea Gated DeltaNet on-chip prin scanarea sa recurentă și reutilizați datele KV partajate de atenția cu interogări grupate.
  3. Adaptarea kernelurilor la forma sarcinii de lucru. În cadrul fiecărei faze, selectați dimensiunile tile-urilor, layout-urile de execuție și căile de atenție din numărul disponibil de rânduri, distribuția rândurilor între experți, dimensiunile operației și lungimea curentă a contextului.

Secțiunile următoare explică aceste alegeri. Pentru optimizările evaluate în ablații potrivite pe un M5 Max, le estimăm efectele comparând configurații de motor altfel identice care diferă doar prin optimizarea studiată. Deoarece aceste experimente compară versiuni ale motorului nostru cu el însuși, ele explică mecanismele mai degrabă decât să descompună rezultatele finale în comparație cu MLX-LM.

Preumplere: reutilizarea ponderilor și menținerea rutării pe GPU

Preumplerea expune multe rânduri de tokeni simultan, dar Qwen direcționează aceste rânduri în mod neuniform între experți și actualizează starea recurentă prin secvență. Optimizările sale se împart în trei grupuri: organizarea muncii experților rari în jurul rândurilor rutanate, menținerea scanării Gated DeltaNet on-chip și împărțirea prompturilor lungi în bucăți delimitate.

Execuția și rezidența datelor pentru un segment de prefill limitat printr-un strat Qwen. Atenția extinde cache-ul KV, în timp ce Gated DeltaNet își păstrează starea recurentă de lucru în registre. Metadatele de rutare a experților rămân pe GPU, ponderile Q4 rămân împachetate până când sunt de cuantificate în interiorul GEMM grupat, iar activările temporare sunt limitate la segmentul curent.
Execuția și rezidența datelor pentru o bucată de preumplere delimitată printr-un strat Qwen. Atenția extinde cache-ul KV, în timp ce Gated DeltaNet își transportă starea recurentă de lucru în registre. Metadatele de rutare a experților rămân pe GPU, ponderile Q4 rămân împachetate până când sunt decuantizate în interiorul GEMM-ului grupat, iar activările temporare sunt limitate la bucata curentă.

Optimizarea calculului cu experți rari

Decuantizarea ponderilor în timpul multiplicării matriciale

Punctul de control Qwen3.6-35B-A3B utilizează cuantizare afină pe grupe de 4 biți. Fiecare pondere este stocată ca un cod întreg pe 4 biți, în timp ce fiecare grup de 64 de ponderi partajează o scală și un termen de bias bfloat16 utilizate pentru a reconstrui valorile sale. Acest lucru reduce modelul de 35 de miliarde de parametri de la aproximativ 70 GB de ponderi bfloat16 la un punct de control de 19,4 GB, făcând practic ca modelul să rămână rezident pe Mac.

Operația pe tensoare Metal 4 utilizată pentru multiplicarea matricială consumă operanzi bfloat16 în loc de reprezentarea împachetată pe 4 biți. Înainte de multiplicare, GPU-ul trebuie să reconstruiască ponderile în bfloat16. GEMM-ul grupat optimizat din Lily efectuează această conversie câte un tile mic de ponderi la un moment dat și ține rezultatul în memoria threadgroup on-chip doar atât timp cât este necesar pentru a-l înmulți cu rândurile de activare rutanate. Acumularea utilizează virgulă mobilă pe 32 de biți, iar ieșirea este scrisă în bfloat16. Matricea completă de ponderi extinse nu este creată niciodată în memoria unificată.

În ablație, decuantizarea rulează ca o operație separată: extinde ponderile pe 4 biți într-o matrice bfloat16 în memoria unificată, după care kernelul matricial citește acea matrice înapoi. La un prompt de 512 tokeni, mutarea decuantizării în GEMM-ul grupat a crescut debitul de preumplere end-to-end cu 77,4% prin eliminarea acestei scrieri și citiri intermediare.

Menținerea rutării experților pe GPU

GEMM-ul grupat necesită ca rândurile de activare atribuite fiecărui expert să fie stocate împreună. După selectarea a opt experți per token, o histogramă numără câte atribuiri au mers la fiecare expert. O scanare de prefix transformă acele numări în offset-uri de pornire, un pas de dispersie plasează rândurile în grupurile lor de experți, iar o hartă de blocuri listează blocurile matriciale de dimensiune fixă pe care GEMM-ul grupat trebuie să le proceseze.

Calea optimizată păstrează întreaga această secvență într-un singur tampon de comenzi (command buffer), un lot ordonat de operații GPU, pentru fiecare bucată de prompt. O ablație face în schimb o pauză pentru ca CPU-ul să poată inspecta intermediarii de rutare și să trimită operația următoare. Menținerea histogramei și a scanării de prefix pe GPU adaugă două kerneler, dar elimină sincronizarea CPU-GPU în interiorul fiecărui strat MoE.

La un prompt de 512 tokeni, activarea rutării rezidente pe GPU a crescut preumplerea end-to-end cu 89%. Acest lucru arată, de asemenea, de ce numărul de kerneluri singur poate fi înșelător: ruta mai rapidă lansează mai multe kerneluri, dar nu așteaptă niciodată CPU-ul în interiorul stratului.

Potrivirea dimensiunii tile-ului cu sarcina expertului

La un prompt de 2K tokeni, rutarea fiecărui token către opt din 256 de experți produce 16.384 de atribuiri token-expert, sau o medie de 64 de rânduri de activare per expert. Distribuția efectivă este neuniformă: unii experți primesc multe rânduri, în timp ce alții primesc puține.

GEMM-ul grupat împarte ieșirea fiecărui expert în tile-uri, care sunt blocuri dreptunghiulare mici ale ieșirii unei multiplicări matriciale. Fiecare tile este atribuit unui threadgroup GPU. Pe GPU-urile Apple silicon, un threadgroup conține unul sau mai multe simdgroup-uri, fiecare constând din 32 de fire care execută instrucțiuni în pas sincron (lockstep).

Tile-urile mai mari răspândesc costul de configurare pe mai multe rânduri și expun mai multă muncă paralelă, dar o parte dintr-un tile mare rămâne inactivă atunci când un expert primește doar câteva rânduri. Prin urmare, dimensiunea tile-ului și numărul de simdgroup-uri sunt cuplate.

O ablație fixează tile-ul la 16 rânduri. Față de acest control, activarea tile-ului de 32 de rânduri cu patru simdgroup-uri a îmbunătățit preumplerea end-to-end cu 13,2% la 2K tokeni.

Menținerea stării recurente on-chip

În timpul preumplerii, fiecare strat Gated DeltaNet scanează promptul în ordine, ducând starea recurentă mai departe. Cu rezidența în registre dezactivată, ablația folosește o scanare pe blocuri. La un prompt de 2K tokeni, acea cale mută 256 MiB (mebibiți) de stare per strat și oprește în mod repetat firele cooperante la bariere, puncte de sincronizare în care toate firele participante trebuie să aștepte unul după celălalt.

Starea recurentă este o matrice. Kernelul optimizat atribuie fiecare coloană unui simdgroup. Simdgroup-ul împarte coloana între firele sale, încarcă coloana în registrele acestora o singură dată și transportă starea prin întreaga scanare. Firele schimbă rezultate intermediare prin operații de simdgroup în loc de memoria threadgroup, stocare on-chip partajată între un threadgroup. Starea completată este scrisă înapoi numai după scanare.

Starea și poarta sa utilizează un format cu virgulă mobilă pe 32 de biți, deoarece erorile mici de rotunjire se acumulează în urma actualizărilor secvențiale. Activările pentru interogări și chei rămân în bfloat16.

La un prompt de 2K tokeni, activarea scanării rezidente în registre a îmbunătățit preumplerea end-to-end cu 5,6%. GEMM-urile de experți au reprezentat aproximativ 90% din timpul de preumplere. Scanarea secvențială nu expune suficientă muncă matricială reutilizabilă pentru a beneficia de pe urma acceleratoarelor neurale.

Limitarea memoriei temporare prin fragmentarea prompturilor

Runtime-ul procesează un prompt lung ca o secvență de bucăți delimitate, în loc să păstreze simultan în memorie date temporare pentru fiecare token din prompt. Ponderile modelului rămân rezidente în memoria unificată, în timp ce starea recurentă și cache-ul KV transportă contextul de la o bucată la alta. Niciun context anterior nu este descărcat.

Fără fragmentare, matricele de activare temporare cresc odată cu promptul complet și concurează cu ponderile modelului, starea recurentă și cache-ul KV pentru memoria unificată. Fragmentarea menține active valorile temporare ale unui singur segment la un moment dat, apoi eliberează sau reutilizează stocarea respectivă înainte de a procesa segmentul următor. Acest lucru limitează memoria de lucru maximă și permite motorului să proceseze prompturi mai lungi fără a modifica ieșirea modelului.

Preumplerea fragmentată este populară în multe motoare și este crucială pentru servirea traiectoriilor lungi cu mai multe schimburi în aceste medii cu memorie constrânsă. Timpul total de preumplere pentru straturile de atenție rămâne pătratic în funcție de lungimea promptului, cu un oarecare overhead suplimentar din cauza încărcărilor KV repetate ale bucăților anterioare.

Decodare: minimizarea biților mutați per token

Decodarea batch-1 procesează câte un rând nou pe rând. Având o reutilizare redusă a ponderilor, debitul său depinde în principal de numărul de biți pe care motorul îi mută pentru fiecare token. Modificările de decodare se împart în patru grupuri: optimizarea căii de ponderi cu un singur rând, menținerea fiecărui pas pe GPU, reducerea traficului intermediar și de stare și citirea eficientă a cache-ului de atenție.

Fluxul de date pentru un pas de decodare batch-1 și două mecanisme care reduc timpul de inactivitate și traficul din cache. (A) GPU-ul transmite ponderile Q4 și starea modelului prin atenție, Gated DeltaNet, rutare și kerneluri de experți fuzionate, apoi scrie tokenul selectat direct în slotul de intrare al pasului următor, trimițând în același timp o copie către CPU. (B) Planificarea conștientă de dependențe permite kernelurilor independente să se suprapună. (C) Împachetarea GQA permite ca patru capete de interogare să împartă fiecare sarcină pe rând KV, reducând opt cereri independente la două sarcini partajate.
Fluxul de date pentru un pas de decodare batch-1 și două mecanisme care reduc timpul de inactivitate și traficul în cache. (A) GPU-ul transmite ponderi Q4 și starea modelului prin atenție, Gated DeltaNet, rutare și kerneler de experți fuzionați, apoi scrie tokenul selectat direct în slotul de intrare al pasului următor, trimitând în același timp o copie către CPU. (B) Planificarea conștientă de dependențe permite suprapunerea kernelurilor independente. (C) Împachetarea GQA permite ca patru capete de interogare să partajeze fiecare încărcare de rând KV, reducând opt cereri independente la două încărcări partajate.

Optimizarea căii de ponderi cu un singur rând

MLX expediază deja munca pe un singur rând către kerneler specializate de matrice-vector. Deoarece Lily nu folosește MLX, runtime-ul personalizat trebuie să asigure aceeași strategie de bază. GEMV-ul nostru paralel pe rânduri este proiectat pentru un singur rând de activare. Un simdgroup cooperează la ieșire în timp ce citește diferite părți ale matricei de ponderi în paralel.

Menținerea fiecărui pas de decodare pe GPU

Menținerea transferului de tokeni pe GPU

Fiecare pas de decodare se încheie prin selectarea următorului token; pasul următor începe cu acel token ca intrare. Trimiterea selecției către CPU și apoi înapoi la GPU adaugă un punct de sincronizare la fiecare token. În schimb, runtime-ul nostru alternează între două tampoane de comenzi și două sloturi de tokeni rezidente în GPU. GPU-ul selectează tokenul cu cel mai mare scor și scrie ID-ul său de token direct în slotul de intrare pentru pasul de decodare următor, în timp ce CPU-ul pregătește munca ulterioară.

Suprapunerea muncii GPU independente

Într-un pas de decodare batch-1 înregistrat, generarea unui token a lansat 795 de kerneler GPU. Dependențele lor au format 555 de etape secvențiale, lăsând unele kerneler libere să ruleze simultan. Cu toate acestea, modul de execuție serială al Metal a rulat fiecare kernel în ordine.

Calea de decodare optimizată înregistrează dependențele reale de date într-o trecere Metal concurentă. Lansările de kerneluri independente pot rula în același timp când resursele GPU permit acest lucru. O barieră este introdusă numai atunci când munca ulterioară necesită un rezultat anterior.

Reducerea traficului intermediar și de stare

Kernelurile separate materializează adesea un intermediar: un kernel scrie un rezultat temporar în memorie, iar următorul citește rezultatul înapoi. Calea de decodare optimizată fuzionează patru lanțuri: cele două proiecții de intrare ale experților cu activarea lor gătată; proiecția de ieșire a expertului cu scorul său de rutare și rezultatul expertului partajat; pregătirea interogării și a cheii înainte de atenție; și actualizarea recurentă cu normalizarea sa. Fiecare kernel fuzionat păstrează valorile temporare în registre în loc să le trimită prin memorie.

Fuziunea scurtează, de asemenea, graful de dependențe: atunci când o scriere intermediară dispare, dispare și bariera care îi proteja consumatorul.

Citirea eficientă a cache-ului de atenție

Coalescing-ul citirilor din cache-ul de atenție

Atenția citește chei și valori din cache-ul KV în timpul fiecărui pas de decodare. În ablație, firele GPU învecinate nu solicită întotdeauna biți vecini, forțând sistemul de memorie să deservească mai multe tranzacții separate. Activarea încărcărilor coalesced face ca firele adiacente să solicite biți adiacenti, astfel încât hardware-ul să își poată combina citirile.

Pe configurația bfloat16, coalescing-ul a crescut lățimea de bandă pentru chei de la 33,8 la 47,9 GB/s, a crescut lățimea de bandă pentru valori de la 42,0 la 61,8 GB/s și a îmbunătățit decodarea end-to-end cu 2,1% la un context de 3.840 tokeni.

Împachetarea capetelor de interogare pentru reutilizarea rândurilor KV

Atenția cu interogări grupate permite ca opt capete de interogare să partajeze un singur cap KV. În ablație, fiecare cap de interogare rulează într-un simdgroup separat, astfel încât toate cele opt solicită în mod independent același rând KV din cache. Kernelul optimizat împachetează patru capete de interogare într-un singur threadgroup, care încarcă fiecare rând KV o dată și îl reutilizează în patru calcule de atenție. Un al doilea threadgroup gestionează celelalte patru capete.

Această tehnică, numită în mod obișnuit împachetare GQA, efectuează aceleași calcule și produce biți de ieșire identici, reducând în același timp opt cereri KV independente la două încărcări partajate. Față de ablația neîmpachetată, a îmbunătățit debitul de decodare end-to-end cu 23,8% la un context de 32K tokeni.

Comutarea layout-urilor de atenție la contexte lungi

Fiecare pas de decodare dintr-un strat de atenție completă scanează cache-ul KV existent. Un layout cu blocuri fixe împarte acest cache în bucăți egale pe care GPU-ul le poate procesa în paralel. Planificarea sa suplimentară nu merită când cache-ul este mic, dar layout-ul cu blocuri fixe echilibrează munca mai uniform pe măsură ce contextul crește.

Pentru acest model, runtime-ul menține calea de atenție generală sub 32K tokeni și folosește calea cu blocuri fixe la 32K sau mai mult. Comutarea se aplică atunci când fiecare cap are 256 de valori și opt capete de interogare partajează un cap KV; alte forme rămân pe calea generală. O ablație dezactivează această comutare și folosește întotdeauna calea generală. Activarea rutei cu blocuri fixe a îmbunătățit decodarea end-to-end cu 7,7% la 32K, 27,4% la 64K și 40,2% la 128K.

Limitele optimizării ulterioare

Unele modificări au îmbunătățit o operație izolată, dar nu au îmbunătățit inferența end-to-end.

Decodarea speculativă, care utilizează un model mai mic pentru a propune tokeni pe care modelul complet să îi verifice, a făcut ca decodarea batch-1 să fie cu 18% mai lentă. Verificarea a procesat grupuri de două până la cinci rânduri, o formă ineficientă pentru acest hardware, iar rândurile au selectat adesea experți diferiți, crescând cantitatea de date de ponderi de experți citite. Reducerea vocabularului de ieșire al generatorului a îmbunătățit debitul generatorului cu 4,7–5,1%, dar nu a făcut ca bucla speculativă completă să fie mai rapidă. Acest rezultat este specific sarcinii de lucru: implementarea noastră cu loturi Qwen pe Blackwell utilizează decodarea speculativă în diferite condiții.

Alte experimente au inclus reducerea lansărilor GPU, suprapunerea întregilor faze, utilizarea de tile-uri de preumplere mai mari, aplicarea unei fuziuni mai ample, accelerarea ruterului și combinarea proiecției de ieșire cu selecția tokenilor. Niciunul nu a îmbunătățit bucla completă de inferență.

Măsurătorile limitelor hardware au arătat, de asemenea, o marjă mică rămasă în operațiunile principale de preumplere și decodare. GEMM-urile și GEMV-urile MoE au atins 97,9% și respectiv 90,3% din cele mai rapide rate susținute de citire a ponderilor pentru modelele lor de acces. Eliminarea aritmeticii din GEMV-ul rar a modificat debitul cu doar 0,2%, confirmând că citirile de ponderi, mai degrabă decât calculul, au fost resursa limitativă. Multiplicarea matricială a preumplerii a atins în mod similar 93% din limita teoretică a matricei în izolare și 80–86% în interiorul modelelor testate.

Performanță end-to-end

Comparația end-to-end încarcă octeți de punct de control pe 4 biți identici în ambele motoare și rulează câte o cerere pe rând pe un M5 Max cu 40 de nuclee și 128 GB. În cadrul fiecărei runde, cele două motoare rulează în ordine alternativă pentru a reduce părtinirea cauzată de sarcina de fundal și de modificările temperaturii cipurilor. Comparăm cu cea mai rapidă cale de generare directă a MLX-LM, nu cu serverul său, astfel încât măsurătoarea se concentrează pe execuția modelului mai degrabă decât pe overhead-ul de servire.

Scanarea acoperă zece lungimi de prompt pentru preumplere și zece lungimi de context pentru decodare, de la 256 la 128K tokeni. Debitul de preumplere crește mai întâi pe măsură ce motorul răspândește costurile fixe de configurare pe mai mulți tokeni. Preumplerea atinge vârful în jurul unui prompt de 4K tokeni, apoi scade deoarece cele zece straturi de atenție completă efectuează mai multă muncă pe măsură ce promptul crește. Decodarea rămâne aproape plană la contexte scurte și scade odată ce citirea cache-ului KV în creștere devine semnificativă. Motorul personalizat este mai rapid la fiecare lungime înregistrată.

Deoarece execuția specializată poate modifica ordinea operațiilor cu virgulă mobilă, am verificat, de asemenea, consistența numerică față de MLX-LM. Într-o comparație forțată de profesor (teacher-forced), ambele motoare au ghicit următorul token din același prefix de referință la fiecare dintre cele 192 de poziții, împiedicând diferențele anterioare să afecteze intrările ulterioare. Perplexitatea lui Lily a fost cu doar 0,04% mai mare și a selectat același token clasat pe primul loc la 96,35% din pozițiile testate.

Debitul de prefill în funcție de lungimea promptului și debitul de decodare în funcție de lungimea contextului pentru Qwen3.6-35B-A3B Q4, batch 1, pe un M5 Max cu 40 de nuclee și 128 GB. Pe zece lungimi de la 256 la 128K tokeni, Lily este mai rapid în fiecare punct înregistrat: debit de prefill de 1,12–1,42× față de MLX-LM și debit de decodare de 1,31–1,37×. Comparația utilizează cea mai rapidă cale de generare directă a MLX-LM. Ambele axe orizontale utilizează scale logaritmice; nicio axă verticală nu începe de la zero.
Debitul de preumplere în funcție de lungimea promptului și debitul de decodare în funcție de lungimea contextului pentru Qwen3.6-35B-A3B Q4, lot 1, pe un singur M5 Max cu 40 de nuclee și 128 GB. Pe parcursul a zece lungimi de la 256 la 128K tokeni, Lily este mai rapid în fiecare punct înregistrat: 1,12–1,42× debitul de preumplere al MLX-LM și 1,31–1,37× debitul său de decodare. Comparația folosește cea mai rapidă cale de generare directă a MLX-LM. Ambele axe orizontale utilizează scări logaritmice; nicio axă verticală nu începe de la zero.

Construit pentru platforma locală

Apple silicon nu este un GPU de centru de date mai mic. Este o platformă completă de inferență locală cu propriile sale caracteristici hardware și software. Memoria unificată oferă unui singur nod o limită superioară foarte mare privind cantitatea de model și stare pe care o poate reține. Acceleratoarele neurale M5 absorb munca matricială densă în preumplere. Unitațile ALU vectoriale gestionează restul limitat de lățimea de bandă și cu reutilizare redusă în decodare.

Qwen adaugă oportunități suplimentare de specializare: menținerea rutării experților și a stării recurente pe GPU, eliminarea intermediarilor inutili, suprapunerea muncii independente, reutilizarea datelor KV partajate și adaptarea kernelurilor la forma sarcinii de lucru.

Cu optimizare specifică modelului și platformei, un Mac poate rula eficient un model mare și rar. Activitatea viitoare va extinde acoperirea între modele, cipuri și sarcini de lucru de servire și va transforma mecanismele validate aici pe o singură configurație într-o politică de runtime mai generală.

Principiul mai larg este de a potrivi motorul atât cu arhitectura modelului, cât și cu căile specifice de calcul și memorie ale hardware-ului. Pe măsură ce modelele cu ponderi deschise de frontieră și hardware-ul evoluează, inferența locală de înaltă performanță va depinde din ce în ce mai mult de motoare adaptate la ambele, mai degrabă decât de unele care le ascund diferențele.