Optymalizacja wnioskowania na urządzeniu dla Apple Silicon

Niestandardowy silnik lokalny, który poprawia przepustowość prefill i dekodowania.

AutorzyPerplexity Engineering

Hybrydowe obliczenia na układzie Apple silicon koordynują zadanie pomiędzy inteligencją brzegową w chmurze a lokalnym modelem na komputerze Mac. Modele chmurowe obsługują badania i rozumowanie, podczas gdy model lokalny współpracuje z prywatnymi plikami i aplikacjami na komputerze Mac.

Aby ten podział pracy wydawał się płynny, wnioskowanie lokalne musi dotrzymywać kroku reszcie zadania. Wymaga to silnika, który potrafi szybko przetwarzać prompty i utrzymywać wysoką szybkość generowania tokenów.

Lily, nasz lekki lokalny silnik wnioskowania, został zbudowany specjalnie z myślą o procesorach Apple Silicon i Qwen3.6-35B-A3B, ze osobnymi optymalizacjami dla fazy wstępnego wypełniania (prefill) i dekodowania. Samodzielne demo jest publicznie dostępne na GitHub.

Wprowadzenie

Częstym sposobem uruchamiania modeli LLM na komputerach Mac jest użycie MLX, czyli otwartoźródłowego frameworka uczenia maszynowego Apple dla układów Apple silicon. Towarzysząca mu biblioteka MLX-LM dodaje komponenty niezbędne do ładowania i generowania tekstu za pomocą szerokiej gamy modeli językowych. Razem MLX i MLX-LM zapewniają gotowy do użycia, uniwersalny stos do lokalnego wnioskowania modeli LLM.

Qwen3.6-35B-A3B to rzadki model hybrydowy: wykorzystuje routing oparty na mieszaninie ekspertów (MoE) i łączy stany rekurencyjne o stałym rozmiarze z pełną mechaniką uwagi. Te wybory architektoniczne zmniejszają ilość wymaganych obliczeń, ale generują również nieregularne obciążenia robocze. Tokeny są kierowane do różnych wag ekspertów, a stany rekurencyjne mają z natury charakter sekwencyjny.

MLX-LM wybiera już zoptymalizowane jądra dla faz wnioskowania i typowych kształtów obciążeń, ale jego operacje wielokrotnego użytku muszą obsługiwać wiele architektur modeli. Silnik dedykowany modelowi Qwen może specjalizować się na poziomie modelu i środowiska uruchomieniowego, koordynując jądra, przemieszczanie danych i harmonogramowanie wokół stałej struktury modelu.

Lily wdraża tę specjalizację end-to-end w ramach jednego procesu. Środowisko uruchomieniowe w języku Rust ładuje punkt kontrolny modelu i zarządza stanem sesji oraz pętlą generowania, kompatybilny z OpenAI interfejs API czatu (chat-completions) przyjmuje żądania i przesyła strumieniowo tokeny, a niestandardowe jądra Metal wykonują operacje specyficzne dla Qwen. W ścieżce wykonania nie występują ani PyTorch, ani MLX.

Miejsce uniwersalności i specjalizacji w obu stosach wnioskowania. MLX-LM opisuje model jako kompozycyjne operacje na tablicach MLX, które MLX harmonogramuje za pomocą jądra wielokrotnego użytku. Lily zamiast tego umieszcza strukturę modelu, plany wykonania specyficzne dla fazy i wybór jąder w jednym środowisku uruchomieniowym (runtime) języka Rust zbudowanym wokół Qwen i układów Apple silicon.

Wydajność prefill i dekodowania mierzymy osobno. Przepustowość prefill odzwierciedla, jak szybko silnik przetwarza prompt; przepustowość dekodowania odzwierciedla, jak szybko generuje tokeny wyjściowe.

Porównujemy Qwen3.6-35B-A3B na jednym MacBooku Pro z układem M5 Max, 40-rdzeniowym GPU i 128 GB pamięci zunifikowanej. Dla dziesięciu długości promptu dla etapu prefillingu i dziesięciu długości kontekstu dla dekodowania, od 256 do 128 tys. tokenów (K = 1 024), silnik osiąga średnio 1,22× przepustowości prefillingu MLX-LM i 1,32× jego przepustowości dekodowania. Przy prompcie o długości 4 tys. tokenów i kontekście dekodowania o długości 4 tys. tokenów, niestandardowy silnik osiąga 5 749,9 tokenów prefillingu na sekundę i 186,6 tokenów dekodowania na sekundę, w porównaniu z 4 737,5 i 140,9 dla MLX-LM. W trakcie wieloturowej sesji te oszczędności czasu kumulują się z każdym kolejnym wywołaniem modelu.

Arytmetyczna średnia przepustowość dla dziesięciu równoważnie ważonych długości od 256 do 128 tys. tokenów. Lily osiąga średnio 4 135 tokenów prefillingu/s wobec 3 383 dla MLX-LM (1,22×) oraz 167,1 tokena dekodowania/s wobec 126,9 (1,32×). Prefilling zależy od długości promptu; dekodowanie zależy od długości kontekstu.

Następnie wyjaśniamy, w jaki sposób architektura modelu Qwen stwarza specyficzne dla niego możliwości optymalizacji na układach Apple silicon. Omówimy również wynikające z tego zmiany w fazach prefill i dekodowania. Przedstawimy także obszary, w których dalsza optymalizacja przestaje być opłacalna, a na koniec zamieścimy porównanie typu end-to-end z MLX-LM.

Możliwości optymalizacji specyficzne dla Qwen na układach Apple silicon

Qwen tworzy trzy różne kształty obciążeń

Qwen3.6-35B-A3B zawiera 35 miliardów parametrów, ale aktywuje tylko około 3 miliardy dla każdego tokena. Router ocenia 256 podsieci ekspertów i wybiera osiem, obok jednego współdzielonego eksperta, który przetwarza każdy token. Ta rzadka konstrukcja MoE ogranicza obliczenia, ale generuje nierównomierną pracę: eksperci otrzymują różne liczby tokenów, a każdy token wymaga wag z innej kombinacji ekspertów.

Model Qwen łączy również 10 warstw pełnej uwagi z 30 warstwami Gated DeltaNet. Te dwa typy warstw zachowują wcześniejsze informacje w różny sposób.

Warstwy uwagi wykorzystują uwagę z zapytaniami grupowanymi (GQA). Qwen ma 16 głowic zapytań i dwie głowice klucz–wartość (KV), przy czym osiem głowic zapytań współdzieli każdą głowicę KV. Współdzielenie sprawia, że pamięć podręczna KV jest mniejsza i pozwala na ponowne wykorzystanie zbuforowanych danych przez głowice zapytań. Pamięć podręczna nadal przechowuje nowe klucze i wartości dla każdego tokena, więc każdy krok dekodowania odczytuje więcej danych wraz ze wzrostem kontekstu.

Zamiast tego Gated DeltaNet kompresuje wcześniejsze informacje do stanu rekurencyjnego o stałym rozmiarze. Wyuczona bramka kontroluje, jaka część istniejacego stanu ma zostać zachowana, podczas gdy aktualizacja typu delta włącza informacje z bieżącego tokena. Model definiuje te aktualizacje w sposób rekurencyjny, więc każdy token zależy od stanu wyprodukowanego przez poprzedni token. Jednak podczas fazy prefill silnik może ocenić te same obliczenia na dwa sposoby. Może skanować tokeny bezpośrednio, przenosząc stan dalej, lub zreorganizować aktualizacje w bloki, które uwidaczniają więcej operacji macierzystych i równoległości na poziomie tokenów. To, które podejście jest szybsze, zależy od wymiarów modelu, obciążenia oraz sprzętu.

Razem struktury te tworzą trzy wzorce obliczeniowe: nierównomierne grupy ekspertów, mechanizm uwagi (attention) działający na rosnącej pamięci podręcznej oraz rekurencję o stałym rozmiarze, którą można obliczać bezpośrednio lub w blokach.

Układ Apple silicon zapewnia różne ścieżki dla różnych obciążeń

Faza prefill przetwarza wiele wierszy aktywacji tokenów promptu jednocześnie. Rozważane tutaj obciążenie lokalne zazwyczaj dekoduje jedno żądanie naraz (batch 1) i przetwarza jeden nowy wiersz na krok. Ta różnica zmienia sposób wykorzystania tych samych wag modelu. Prefill może ponownie wykorzystać każdy blok wag setki lub tysiące razy dla różnych wierszy. W fazie dekodowania jest to w dużej mierze niemożliwe, ponieważ każdy nowy token wymaga kolejnego przejścia przez wagi.

Układ Apple silicon umieszcza procesor CPU i GPU za zunifikowaną pamięcią, która jest pojedynczą pulą pamięci fizycznej dostępną dla obu układów. Pozwala to modelowi pozostawać reydentnym bez utrzymywania osobnej kopii na GPU, ale nie sprawia, że przemieszczanie danych jest darmowe. Odczytywanie wag i wartości pośrednich nadal zużywa przepustowość pamięci, podczas gdy rejestry i inna pamięć na chipie są szybsze, ale znacznie mniejsze.

Procesor GPU M5 zapewnia również różne ścieżki obliczeniowe. Warstwy liniowe fazy prefill używają ogólnego mnożenia macierzy przez macierz (GEMM), aplikując macierz wag do wielu wierszy naraz. Zgodne operacje GEMM mogą wykorzystywać akcelerator Neural Accelerator w każdym rdzeniu GPU za pomocą operacji tensorowych Metal 4. Dekodowanie batch-1 używa zamiast tego ogólnego mnożenia macierzy przez wektor (GEMV), aplikując te same wagi do jednego wiersza. Przy małym ponownym wykorzystaniu wag, operacja GEMV jest ograniczona głównie przez przepustowość pamięci i lepiej nadaje się do wektorowych logicznych jednostek arytmetycznych (ALU) procesora GPU niż do akceleratorów Neural Accelerators zaprojektowanych dla operacji macierzystych o większym ponownym wykorzystaniu danych.

Te ścieżki wykonania nie są unikalne dla Lily. MLX działa na tej samej zunifikowanej pamięci i wybiera zoptymalizowane jądra macierzyste oraz wektorowe zgodnie z kształtem obciążenia. Implementacja modelu Qwen w MLX-LM grupuje już pracę ekspertów, ocenia Gated DeltaNet za pomocą połączonego rekurencyjnego jądra Metal oraz wykorzystuje mechanizm uwagi świadomy GQA. Te możliwości stanowią wspólny punkt wyjścia dla wydajnego wnioskowania modelu Qwen na układach Apple silicon.

Strategia optymalizacji

Węższy zakres Lily pozwala koordynować te współdzielone ścieżki wykonania wokół dokładnej architektury i wymiarów modelu Qwen. Silnik wykorzystuje ścieżki GPU specyficzne dla fazy, odwzorowuje obciążenia eksperckie, rekurencyjne i uwagi modelu Qwen tak, aby zminimalizować przemieszczanie danych, oraz dobiera jądra i układy na podstawie zmierzonych kształtów obciążenia. Strategia ta składa się z trzech części:

  1. Dopasuj ścieżkę GPU do fazy wnioskowania. Użyj wykonania zorientowanego na macierze, gdy faza prefill może ponownie wykorzystać wagi w wielu wierszach, oraz wykonania zorientowanego na wektory, gdy dekodowanie batch-1 przetwarza jeden wiersz naraz.
  2. Odwzoruj strukturę modelu Qwen na procesorze GPU przy jednoczesnym zminimalizowaniu przemieszczania danych. Utrzymuj wagi w stanie skompresowanym do momentu ich użycia, organizuj pracę routowanych ekspertów bez powracania do procesora CPU, zachowaj stan Gated DeltaNet w pamięci chipa podczas jego skanowania rekurencyjnego oraz wykorzystuj ponownie dane KV współdzielone przez mechanizm uwagi z zapytaniami grupowanymi.
  3. Dopasuj jądra do kształtu obciążenia. W ramach każdej fazy wybieraj rozmiary kafelków, układy wykonania i ścieżki uwagi na podstawie dostępnej liczby wierszy, rozkładu wierszy między ekspertami, wymiarów operacji oraz bieżącej długości kontekstu.

Poniższe sekcje wyjaśniają te wybory. W przypadku optymalizacji ocenianych w dopasowanych testach ablacyjnych na układzie M5 Max szacujemy ich skutki, porównując identyczne pod innymi względami konfiguracje silnika, które różnią się wyłącznie badaną optymalizacją. Ponieważ eksperymenty te porównują wersje naszego silnika z nim samym, wyjaśniają one mechanizmy, a nie rozbijają ostateczne wyniki na składowe względem MLX-LM.

Prefill: ponowne wykorzystanie wag i utrzymanie routingu na GPU

Faza prefill ujawnia wiele wierszy tokenów naraz, ale Qwen kieruje te wiersze nierównomiernie do ekspertów i aktualizuje stan rekurencyjny w całej sekwencji. Optymalizacje dzielą się na trzy grupy: organizacja pracy rzadkich ekspertów wokół skierowanych wierszy, utrzymanie skanowania Gated DeltaNet w pamięci podręcznej chipa oraz podzielenie długich promptów na ograniczone fragmenty.

Wykonanie i rezydencja danych dla jednego ograniczonego fragmentu prefill przechodzącego przez warstwę Qwen. Uwaga rozszerza pamięć podręczną KV, podczas gdy Gated DeltaNet utrzymuje swój roboczy stan rekurencyjny w rejestrach. Metadane routingu ekspertów pozostają na GPU, wagi Q4 pozostają spakowane do momentu ich dekwantyzacji wewnątrz pogrupowanej macierzy GEMM, a tymczasowe aktywacje są ograniczone do bieżącego fragmentu.

Optymalizacja obliczeń rzadkich ekspertów

Dekwantyzacja wag podczas mnożenia macierzy

Punkt kontrolny Qwen3.6-35B-A3B wykorzystuje grupową kwantyzację afiniczną 4-bitową. Każda waga jest przechowywana jako 4-bitowy kod całkowity, podczas gdy każda grupa 64 wag współdzieli skalę i przesunięcie bfloat16 używane do rekonstrukcji jej wartości. Zmniejsza to model o 35 miliardach parametrów z około 70 GB wag bfloat16 do punktu kontrolnego o wielkości 19,4 GB, co sprawia, że utrzymanie modelu w pamięci komputera Mac staje się praktyczne.

Operacja tensorowa Metal 4 używana do mnożenia macierzy zużywa operandy bfloat16 zamiast spakowanej reprezentacji 4-bitowej. Przed mnożeniem procesor GPU musi zrekonstruować wagi w formacie bfloat16. Zoptymalizowana pogrupowana operacja GEMM w Lily wykonuje tę konwersję dla jednego małego kafelka wag naraz i utrzymuje wynik w pamięci wątków (threadgroup memory) na chipie tylko tak długo, jak to konieczne do pomnożenia go przez skierowane wiersze aktywacji. Akumulacja wykorzystuje 32-bitowy format zmiennoprzecinkowy, a wynik jest zapisywany w formacie bfloat16. Pełna rozbudowana tablica wag nigdy nie jest tworzona w pamięci zunifikowanej.

W teście ablacyjnym dekwantyzacja działa jako osobna operacja: rozwija 4-bitowe wagi do tablicy bfloat16 w pamięci zunifikowanej, po czym jądro macierzyste odczytuje tę tablicę z powrotem. Przy prompcie o długości 512 tokenów przeniesienie dekwantyzacji do wnętrza pogrupowanej operacji GEMM zwiększyło przepustowość fazy prefill end-to-end o 77,4% poprzez wyeliminowanie tego pośredniego zapisu i odczytu.

Utrzymanie routingu ekspertów na procesorze GPU

Pogrupowana operacja GEMM wymaga, aby wiersze aktywacji przypisane do każdego eksperta były przechowywane razem. Po wybraniu ośmiu ekspertów na token histogram zlicza, ile przypisań trafiło do każdego eksperta. Skanowanie prefiksów zamienia te liczby w przesunięcia początkowe, krok rozproszenia (scatter) umieszcza wiersze w ich grupach ekspertów, a mapa bloków wyszczególnia bloki macierzy o stałym rozmiarze, które pogrupowana macierz GEMM musi przetworzyć.

Zoptymalizowana ścieżka utrzymuje całą tę sekwencję w jednym buforze poleceń (uporządkowanej partii operacji GPU) dla każdego fragmentu promptu. W ramach testu ablacyjnego silnik zatrzymuje się, aby procesor CPU mógł sprawdzić dane pośrednie routingu i przesłać kolejną operację. Utrzymywanie histogramu i skanowania prefiksów na GPU dodaje dwa jądra, ale eliminuje synchronizację między procesorami CPU i GPU wewnątrz każdej warstwy MoE.

Przy prompcie o długości 512 tokenów włączenie routingu reydentnego na GPU zwiększyło fazę prefill end-to-end o 89%. Pokazuje to również, dlaczego sama liczba jąder może być myląca: szybsza ścieżka uruchamia więcej jąder, ale nigdy nie czeka na procesor CPU wewnątrz warstwy.

Dopasowanie rozmiaru kafelka do obciążenia eksperta

Przy prompcie o długości 2K tokenów skierowanie każdego tokena do ośmiu z 256 ekspertów generuje 16 384 przypisań token-ekspert, czyli średnio 64 wiersze aktywacji na eksperta. Rzeczywisty rozkład jest nierównomierny: niektórzy eksperci otrzymują wiele wierszy, podczas gdy inni tylko kilka.

Pogrupowana operacja GEMM dzieli wynik każdego eksperta na kafelki, które są małymi prostokątnymi blokami wyniku mnożenia macierzy. Każdy kafelek jest przypisany do jednej grupy wątków (threadgroup) GPU. W procesorach GPU układu Apple silicon grupa wątków zawiera jedną lub więcej grup simdgroup, z których każda składa się z 32 wątków wykonujących instrukcje w trybie lockstep.

Większe kafelki rozkładają koszt konfiguracji na większą liczbę wierszy i ujawniają więcej pracy równoległej, ale część dużego kafelka pozostaje bezczynna, gdy ekspert otrzymuje tylko kilka wierszy. Rozmiar kafelka i liczba grup simdgroup są zatem ze sobą powiązane.

Test ablacyjny ustala rozmiar kafelka na 16 wierszy. W porównaniu z tym punktem kontrolnym włączenie kafelka o rozmiarze 32 wierszy z czterema grupami simdgroup poprawiło fazę prefill end-to-end o 13,2% przy 2K tokenów.

Utrzymanie stanu rekurencyjnego w pamięci chipa

Podczas fazy prefill każda warstwa Gated DeltaNet sekwencyjnie skanuje prompt, jednocześnie przenosząc swój stan rekurencyjny. Przy wyłączonej rezydencji w rejestrach test ablacyjny korzysta ze skanowania blokowego. Przy prompcie o długości 2K tokenów ta ścieżka przemieszcza 256 MiB (mebibajtów) stanu na warstwę i wielokrotnie zatrzymuje współdziałające wątki na barierach, czyli punktach synchronizacji, w których wszystkie uczestniczące wątki muszą czekać na pozostałe.

Stan rekurencyjny jest macierzą. Zoptymalizowane jądro przypisuje każdą kolumnę do jednej grupy simdgroup. Grupa simdgroup dzieli kolumnę między swoje wątki, jednokrotnie łaci kolumnę do ich rejestrów i przenosi stan przez cały proces skanowania. Wątki wymieniają wyniki pośrednie za pomocą operacji simdgroup zamiast pamięci threadgroup, czyli pamięci w układzie współdzielonej przez całą grupę wątków. Ukończony stan jest zapisywany z powrotem dopiero po zakończeniu skanowania.

Stan i jego bramka wykorzystują 32-bitowy format zmiennoprzecinkowy, ponieważ małe błędy zaokrągleń kumulują się w kolejnych aktualizacjach. Aktywacje zapytań i kluczy pozostają w formacie bfloat16.

Przy prompcie o długości 2K tokenów włączenie skanowania w rejestrach poprawiło fazę prefill end-to-end o 5,6%. Operacje Expert GEMM odpowiadały za około 90% czasu trwania fazy prefill. Skanowanie sekwencyjne nie ujawnia wystarczającej ilości pracy macierzystej nadającej się do ponownego wykorzystania, aby czerpać korzyści z akceleratorów Neural Accelerators.

Ograniczenie pamięci tymczasowej za pomocą dzielenia promptów na fragmenty

Środowisko uruchomieniowe przetwarza długi prompt jako sekwencję ograniczonych fragmentów, zamiast utrzymywać w pamięci dane tymczasowe dla każdego tokena promptu naraz. Wagi modelu pozostają reydentne w pamięci zunifikowanej, podczas gdy stan rekurencyjny i pamięć podręczna KV przenoszą kontekst z jednego fragmentu do następnego. Żaden wcześniejszy kontekst nie jest odrzucany.

Bez podziału na fragmenty tymczasowe tablice aktywacji rosną wraz z całym promptem i konkurują z wagami modelu, stanem rekurencyjnym oraz pamięcią podręczną KV o pamięć zunifikowaną. Podział na fragmenty utrzymuje jako aktywne wartości tymczasowe tylko jednego segmentu naraz, a następnie zwalnia lub ponownie wykorzystuje tę pamięć przed przetworzeniem kolejnego segmentu. Pozwala to ograniczyć szczytowe zużycie pamięci roboczej i umożliwia silnikowi przetwarzanie dłuższych promptów bez zmiany danych wyjściowych modelu.

Dzielenie promptu na fragmenty jest popularne w wielu silnikach i ma kluczowe znaczenie dla obsługi długich trajektorii z wieloma turami w tych środowiskach o ograniczonych zasobach pamięci. Całkowity czas prefill dla warstw uwagi pozostaje kwadratowy względem długości promptu, z pewnym dodatkowym narzutem wynikającym z powtarzanych odczytów KV wcześniejszych fragmentów.

Dekodowanie: zminimalizowanie liczby przesyłanych bajtów na token

Dekodowanie batch-1 przetwarza jeden nowy wiersz naraz. Przy niewielkim ponownym wykorzystaniu wag jego przepustowość zależy głównie od tego, jak wiele bajtów silnik przemieszcza dla każdego tokena. Zmiany w dekodowaniu dzielą się na cztery grupy: optymalizacja ścieżki wag jednowierszowych, utrzymanie każdego kroku na GPU, redukcja ruchu danych pośrednich i stanu oraz wydajny odczyt pamięci podręcznej uwagi.

Przepływ danych dla jednego kroku dekodowania batch-1 oraz dwa mechanizmy, które redukują czas bezczynności i ruch w pamięci podręcznej. (A) GPU przesyła strumieniowo wagi Q4 oraz stan modelu przez mechanizm uwagi, Gated DeltaNet, routing i połączone jądra ekspertów, a następnie zapisuje wybrany token bezpośrednio w gnieździe wejściowym kolejnego kroku, wysyłając jednocześnie kopię do procesora CPU. (B) Harmonogramowanie uwzględniające zależności pozwala na nakładanie się niezależnych jąder. (C) Pakowanie GQA pozwala czterem głowicom zapytań współdzielić odczyt każdego wiersza KV, redukując osiem niezależnych żądań do dwóch współdzielonych odczytów.

Optymalizacja ścieżki wag jednowierszowych

MLX wysyła już pracę jednowierszową do wyspecjalizowanych jąder macierzowo-wektorowych. Ponieważ Lily nie korzysta z MLX, niestandardowe środowisko uruchomieniowe musi zapewnić tę samą podstawową strategiju. Nasza macierz GEMV zrównoleglona co do wiersza została zaprojektowana dla jednego wiersza aktywacji. Grupa simdgroup współdziała nad wynikiem, odczytując jednocześnie różne części macierzy wag.

Utrzymanie każdego kroku dekodowania na procesorze GPU

Utrzymanie przekazania tokena na procesorze GPU

Każdy krok dekodowania kończy się wyborem kolejnego tokena; kolejny krok zaczyna się od tego tokena jako danych wejściowych. Wysłanie wyboru do procesora CPU, a następnie z powrotem do GPU dodaje punkt synchronizacji do każdego tokena. Nasze środowisko uruchomieniowe zamiast tego przełącza się pomiędzy dwoma buforami poleceń a dwoma slotami tokenów reydentnymi na GPU. GPU wybiera token z najwyższym wynikiem i zapisuje jego identyfikator bezpośrednio w slocie wejściowym dla kolejnego kroku dekodowania, podczas gdy CPU przygotowuje kolejne prace.

Nakładanie na siebie niezależnych prac GPU

W jednym zarejestrowanym kroku dekodowania batch-1 wygenerowanie tokena uruchomiło 795 jąder GPU. Ich zależności tworzyły 555 etapów sekwencyjnych, co pozwoliło niektórym jądrom na jednoczesne uruchomienie. Mimo to tryb wykonania szeregowego w Metal uruchomił każde jądro po kolei.

Zoptymalizowana ścieżka dekodowania rejestruje rzeczywiste zależności danych w równoległym przebiegu Metal. Niezależne uruchomienia jąder mogą działać w tym samym czasie, gdy pozwalają na to zasoby GPU. Bariera jest wstawiana tylko wtedy, gdy późniejsza praca wymaga wcześniejszego wyniku.

Ograniczenie ruchu danych pośrednich i stanu

Osobne jądra często materializują wynik pośredni: jedno jądro zapisuje wynik tymczasowy do pamięci, a kolejne odczytuje go z powrotem. Zoptymalizowana ścieżka dekodowania łączy cztery łańcuchy: dwie projekcje wejściowe ekspertów z ich bramkowaną aktywacją; projekcję wyjściową eksperta z jego wynikiem routingu i wynikiem eksperta współdzielonego; przygotowanie zapytań i kluczy przed mechanizmem uwagi; oraz aktualizację rekurencyjną wraz z jej normalizacją. Każde połączone jądro utrzymuje wartości tymczasowe w rejestrach zamiast wysyłać je przez pamięć.

Fuzja skraca również graf zależności: gdy znika zapis pośredni, znika również bariera, która chroniła jego konsumenta.

Wydajny odczyt pamięci podręcznej uwagi

Agregowanie odczytów z pamięci podręcznej uwagi

Mechanizm uwagi odczytuje klucze i wartości z pamięci podręcznej KV podczas każdego kroku dekodowania. W teście ablacyjnym sąsiadujące wątki GPU nie zawsze żądają sąsiadujących bajtów, co zmusza system pamięci do obsługi większej liczby osobnych transakcji. Włączenie zaggregowanych odczytów sprawia, że sąsiadujące wątki żądają sąsiadujących bajtów, dzięki czemu sprzęt może połączyć ich odczyty.

W konfiguracji bfloat16 agregacja zwiększyła przepustowość kluczy z 33,8 do 47,9 GB/s, zwiększyła przepustowość wartości z 42,0 do 61.8 GB/s oraz poprawiła dekodowanie end-to-end o 2,1% przy kontekście 3840 tokenów.

Pakowanie głowic zapytań w celu ponownego wykorzystania wierszy KV

Uwaga z zapytaniami grupowanymi (GQA) pozwala ośmiu głowicom zapytań dzielić jedną głowicę KV. W ramach testu ablacyjnego każda głowica zapytań działa w osobnej grupie simdgroup, więc wszystkie osiem niezależnie żąda tego samego wiersza KV z pamięci podręcznej. Zoptymalizowany jądro (kernel) pakuje cztery głowice zapytań w jedną grupę wątków (threadgroup), która wczytuje każdy wiersz KV raz i wykorzystuje go ponownie w czterech obliczeniach uwagi. Druga grupa wątków obsługuje pozostałe cztery głowice.

Ta technika, powszechnie nazywana pakowaniem GQA, wykonuje tę samą arytmetykę i generuje identyczne bajty wyjściowe, redukując jednocześnie osiem niezależnych żądań KV do dwóch współdzielonych odczytów. W porównaniu z niepakowanym testem ablacyjnym poprawiła ona przepustowość dekodowania end-to-end o 23,8% przy kontekście o długości 32K tokenów.

Przełączanie układów uwagi przy długich kontekstach

Każdy krok dekodowania w warstwie pełnej uwagi przeszukuje istniejącą pamięć podręczną KV. Układ o stałych blokach dzieli tę pamięć na równe części, które procesor graficzny (GPU) może przetwarzać równolegle. Jego dodatkowe planowanie nie przynosi korzyści, gdy pamięć podręczna jest mała, ale układ stałych bloków bardziej równomiernie bilansuje pracę wraz ze wzrostem kontekstu.

Dla tego modelu środowisko uruchomieniowe utrzymuje ogólną ścieżkę uwagi poniżej 32K tokenów i używa ścieżki stałych bloków przy 32K lub więcej. Przełączenie następuje, gdy każda głowica ma 256 wartości, a osiem głowic zapytań współdzieli głowicę KV; inne kształty pozostają na ścieżce ogólnej. Test ablacyjny wyłącza to przełączanie i stale używa ścieżki ogólnej. Włączenie routingu stałych bloków poprawiło dekodowanie end-to-end o 7,7% przy 32K, 27,4% przy 64K oraz 40,2% przy 128K.

Ograniczenia dalszej optymalizacji

Niektóre zmiany poprawiły wyizolowaną operację, ale nie wpłynęły na poprawę wnioskowania end-to-end.

Dekodowanie spekulatywne, które wykorzystuje mniejszy model do proponowania tokenów w celu ich weryfikacji przez pełny model, sprawiło, że dekodowanie w trybie batch-1 było o 18% wolniejsze. Weryfikacja przetwarzała grupy od dwóch do pięciu wierszy, co stanowi nieefektywny kształt dla tego sprzętu, a wiersze często wybierały różnych ekspertów, zwiększając ilość odczytywanych danych wag ekspertów. Zmniejszenie słownictwa wyjściowego modelu pomocniczego poprawiło jego przepustowość o 4,7–5,1%, ale nie przyspieszyło kompletnej pętli spekulatywnej. Wynik ten jest specyficzny dla obciążenia: nasze wsadowe wdrożenie modelu Qwen na architekturze Blackwell używa dekodowania spekulatywnego w innych warunkach.

Inne eksperymenty obejmowały redukcję wywołań GPU, nakładanie na siebie całościowych faz, używanie większych kafelków prefill, stosowanie szerszego łączenia (fuzji), przyspieszanie routera oraz łączenie projekcji wyjściowej z wyborem tokena. Żaden z nich nie poprawił kompletnej pętli wnioskowania.

Pomiary limitów sprzętowych wykazały również niewielki zapas wydajności w głównych operacjach prefill i dekodowania. Operacje MoE GEMM i GEMV osiągnęły odpowiednio 97,9% i 90,3% najszybszych trwałych prędkości odczytu wag dla swoich wzorców dostępu. Usunięcie operacji arytmetycznych z rzadkiej macierzy GEMV zmieniło przepustowość o zaledwie 0,2%, co potwierdza, że ograniczeniem były odczyty wag, a nie same obliczenia. Mnożenie macierzy w fazie prefill osiągnęło podobnie 93% teoretycznego limitu macierzy w izolacji oraz 80–86% w testowanych modelach.

Wydajność end-to-end

Porównanie end-to-end łagodzi identyczne 4-bitowe bajty punktu kontrolnego w obu silnikach i uruchamia jedno żądanie naraz na jednym 40-rdzeniowym układzie M5 Max ze 128 GB pamięci. W każdej rundzie oba silniki działają w kolejności naprzemiennej, aby zmniejszyć obciążenie wynikające z obciążenia tła i zmian temperatury chipa. Porównujemy najszybszą ścieżkę generowania bezpośredniego MLX-LM, a nie jego serwer, dzięki czemu pomiar skupia się na wykonywaniu modelu, a nie na narzucie związanym z obsługą serwera.

Zestawienie obejmuje dziesięć długości promptu dla fazy prefill i dziesięć długości kontekstu dla dekodowania, od 256 do 128K tokenów. Przepustowość prefill najpierw rośnie, ponieważ silnik rozkłada stałe koszty konfiguracji na większą liczbę tokenów. Przepustowość prefill osiąga szczyt przy prompcie o długości około 4K tokenów, a następnie spada, ponieważ dziesięć warstw pełnej uwagi wykonuje więcej pracy w miarę wzrostu promptu. Dekodowanie pozostaje niemal płaskie przy krótkich kontekstach i spada, gdy odczyt rosnącej pamięci podręcznej KV staje się znaczący. Niestandardowy silnik jest szybszy przy każdej zarejestrowanej długości.

Ponieważ specjalizowane wykonanie może zmienić kolejność operacji zmiennoprzecinkowych, sprawdziliśmy również spójność numeryczną względem MLX-LM. W porównaniu wymuszonym przez model nauczycielski (teacher-forced) oba silniki przewidziały kolejny token z tego samego prefiksu referencyjnego w każdej ze 192 pozycji, zapobiegając wpływowi wcześniejszych różnic na późniejsze dane wejściowe. Perpleksność Lily była wyższa o zaledwie 0,04%, a silnik wybrał ten sam najwyżej oceniony token w 96,35% testowanych pozycji.

Przepustowość prefillingu według długości promptu i przepustowość dekodowania według długości kontekstu dla Qwen3.6-35B-A3B Q4, batch 1, na jednym 40-rdzeniowym M5 Max z 128 GB pamięci. Dla dziesięciu długości od 256 do 128 tys. tokenów Lily jest szybszy w każdym zarejestrowanym punkcie: 1,13–1,50× przepustowości prefillingu MLX-LM oraz 1,26–1,34× jego przepustowości dekodowania. Porównanie wykorzystuje najszybszą ścieżkę bezpośredniego generowania w MLX-LM. Obie osie poziome używają skali logarytmicznej; żadna oś pionowa nie zaczyna się od zera.

Stworzony dla platformy lokalnej

Układ Apple silicon nie jest pomniejszonym procesorem GPU dla centrów danych. To kompletna platforma wnioskowania lokalnego z własną charakterystyką sprzętową i programową. Pamięć zunifikowana zapewnia pojedynczemu węzłowi bardzo wysoki limit przechowywania modelu i stanu. Akceleratory M5 Neural Accelerators przejmują gęstą pracę macierzystą w fazie prefill. Wektorowe jednostki ALU obsługują obciążoną ograniczeniami przepustowości i charakteryzującą się niskim wskaźnikiem ponownego użycia resztę w fazie dekodowania.

Model Qwen stwarza dalsze możliwości specjalizacji: utrzymanie routingu ekspertów i stanu rekurencyjnego na GPU, wyeliminowanie niepotrzebnych danych pośrednich, nakładanie niezależnych prac, ponowne wykorzystanie współdzielonych danych KV oraz dostosowanie jąder do kształtu obciążenia.

Dzięki optymalizacji specyficznej dla modelu i platformy jeden komputer Mac może wydajnie uruchamiać duży, rzadki model. Przyszłe prace pozwolą poszerzyć zasięg na kolejne modele, czipy i obciążenia serwerowe oraz przekształcić zweryfikowane tutaj mechanizmy w ogólniejszą politykę środowiska uruchomieniowego.

Szerszą zasadą jest dopasowanie silnika zarówno do architektury modelu, jak i do konkretnych ścieżek obliczeniowych oraz pamięciowych sprzętu. W miarę ewolucji najnowocześniejszych otwartych modeli wagowych i sprzętu, wysokowydajne wnioskowanie lokalne będzie w coraz większym stopniu zależeć od silników dostosowanych do obu tych aspektów, a nie od takich, które abstrahują od ich różnic.

Cytat

BibTeX
@online{perplexity2026OptimizingOnDeviceInferenceForAppleSilicon,
  author = {{Perplexity} and Yibo Wu and Lequn Chen and Kevin Hu and Phi Hoang and Claire Beamer and Jeremy Yang and Denis Yarats},
  title = {{Optimizing on-device inference for Apple Silicon}},
  date = {2026-09-01},
  url = {https://www.perplexity.ai/hub/blog/optimizing-on-device-inference-for-apple-silicon}
}