Оптимізація локального виведення для Apple Silicon

Кастомний локальний рушій, який покращує пропускну здатність попереднього заповнення та декодування.

АвториPerplexity Engineering

Гібридні обчислення на Apple silicon оркеструють завдання між передовим інтелектом у хмарі та локальною моделлю на Mac. Хмарні моделі обробляють дослідження та міркування, тоді як локальна модель працює з приватними файлами та додатками на Mac.

Щоб цей розподіл праці здавався безшовним, локальне виведення повинно йти в ногу з рештою завдання. Для цього потрібен рушій, який може швидко обробляти промпти та підтримувати високу швидкість генерації токенів.

Lily, наш легкозбірний локальний рушій виведення, створений спеціально для Apple silicon та Qwen3.6-35B-A3B, з окремими оптимізаціями для попереднього заповнення та декодування. Незабаром рушій буде випущено з відкритим вихідним кодом.

Вступ

Поширеним способом запуску LLM на Mac є MLX, платформа машинної навчання Apple з відкритим вихідним кодом для Apple silicon. Її супутня бібліотека, MLX-LM, додає компоненти, необхідні для завантаження та генерації тексту за допомогою широкого спектра мовних моделей. Разом MLX та MLX-LM надають готовий універсальний стек для локального виведення LLM.

Qwen3.6-35B-A3B — це розріджена гібридна модель: вона використовує маршрутизацію суміші експертів (MoE) та поєднує рекурентні стани фіксованого розміру з повною увагою. Ці архітектурні рішення зменшують обсяг необхідних обчислень, але також створюють нерегулярні робочі навантаження. Токени спрямовуються на різні ваги експертів, а рекурентні стани мають послідовний характер.

MLX-LM вже вибирає оптимізовані ядра для фаз виведення та звичайних форм робочого навантаження, але її повторно використовувані операції повинні підтримувати багато архітектур моделей. Рушій, присвячений Qwen, може спеціалізуватися на рівні моделі та середовища виконання, координуючи ядра, переміщення даних і планування навколо фіксованої структури моделі.

Lily реалізує цю спеціалізацію end-to-end в одному процесі. Середовище виконання Rust завантажує контрольну точку моделі та керує станом сеансу й циклом генерації, сумісний з OpenAI API чат-доповнень приймає запити та передає потоком токени, а кастомні ядра Metal виконують специфічні для Qwen операції. Ані PyTorch, ані MLX не знаходяться на шляху виконання.

Розподіл універсальності та спеціалізації у двох стеках виведення. MLX-LM описує модель як композитні операції масивів MLX, які MLX планує за допомогою повторно використовуваних ядер. Lily ж розміщує структуру моделі, плани виконання для конкретних фаз та вибір ядер в єдиному середовищі виконання Rust, створеному навколо Qwen та Apple silicon.
Де в обох стеках виведення розташовуються загальність і спеціалізація. MLX-LM описує модель як компоновані операції масивів MLX, які MLX планує за допомогою повторно використовуваних ядер. Натомість Lily розміщує структуру моделі, специфічні для фази плани виконання та вибір ядер в єдиному середовищі виконання Rust, побудованому навколо Qwen та Apple silicon.

Ми вимірюємо продуктивність попереднього заповнення та декодування окремо. Пропускна здатність попереднього заповнення фіксує, як швидко рушій обробляє промпт; пропускна здатність декодування фіксує, як швидко він генерує вихідні токени.

Ми тестуємо Qwen3.6-35B-A3B на одному MacBook Pro на базі M5 Max із 40-ядерним GPU та 128 ГБ об'єднаної пам'яті. Для десяти довжин промптів для попереднього заповнення та десяти довжин контексту для декодування, від 256 до 128 тис. токенів (К = 1024), рушій демонструє середню пропускну здатність попереднього заповнення у 1,23× відносно MLX-LM та пропускну здатність декодування у 1,35×. За промпту на 4 тис. токенів і контексту декодування на 4 тис. токенів кастомний рушій досягає 5749,9 токена попереднього заповнення за секунду та 186,6 токена декодування за секунду порівняно з 4737,5 та 140,9 у MLX-LM. Протягом багатоступеневого сеансу ця економія часу накопичується з кожним додатковим викликом моделі.

Середньоарифметична пропускна здатність за десятьма рівноважними довжинами від 256 до 128 тис. токенів. Середні показники Lily становлять 4156 токенів попереднього заповнення/с порівняно з 3388 для MLX-LM (1,23×) та 170,0 токенів декодування/с порівняно з 126,4 (1,35×). Попереднє заповнення залежить від довжини запиту; декодування — від довжини контексту.
Середньоарифметична пропускна здатність за десятьох рівновідповідних довжин від 256 до 128 тис. токенів. Lily має в середньому 4156 токенів попереднього заповнення за секунду проти 3388 у MLX-LM (1,23×) та 170,0 токенів декодування за секунду проти 126,4 (1,35×). Попереднє заповнення варіює довжину промпту; декодування варіює довжину контексту.

Далі ми пояснюємо, як архітектура Qwen створює специфічні для моделі можливості оптимізації на Apple silicon. Потім ми розглядаємо отримані зміни попереднього заповнення та декодування. Ми також охоплюємо те, де додаткова оптимізація перестає окупатися, перш ніж завершити порівнянням end-to-end з MLX-LM.

Можливості оптимізації під Qwen на Apple silicon

Qwen створює три чіткі форми робочого навантаження

Qwen3.6-35B-A3B містить 35 мільярдів параметрів, але активує лише близько 3 мільярдів для кожного токена. Маршрутизатор оцінює 256 підмереж експертів і вибирає вісім разом із одним спільним експертом, який обробляє кожен токен. Цей розріджений дизайн MoE зменшує обчислення, але створює нерівномірну роботу: експерти отримують різну кількість токенів, і кожен токен вимагає ваг від різної комбінації експертів.

Qwen також поєднує 10 шарів повної уваги з 30 шарами Gated DeltaNet. Ці два типи шарів зберігають попередню інформацію різними способами.

Шари уваги використовують увагу зі згрупованими запитами (GQA). Qwen має 16 голів запитів і дві голови ключів–значень (KV), причому вісім голів запитів спільно використовують кожну голову KV. Спільне використання робить кеш KV меншим і дозволяє повторно використовувати кешовані дані між головами запитів. Кеш усе ще зберігає нові ключі та значення для кожного токена, тому кожен крок декодування читає більше даних у міру зростання контексту.

Натомість Gated DeltaNet стискає попередню інформацію в рекурентний стан фіксованого розміру. Навчений вентиль контролює, яку частину існуючого стану слід зберегти, тоді як дельта-оновлення включає інформацію з поточного токена. Модель визначає ці оновлення рекурентно, тому кожен токен залежить від стану, створеного попереднім токеном. Проте під час попереднього заповнення рушій може оцінювати одні й ті самі обчислення двома способами. Він може сканувати токени безпосередньо, переносячи стан далі, або реорганізувати оновлення в блоки, які виставляють більше матричних операцій та паралелізму на рівні токенів. Який підхід швидший, залежить від розмірностей моделі, робочого навантаження та апаратного забезпечення.

Разом ці структури створюють три обчислювальні патерни: нерівномірні групи експертів, увага над кешем, що зростає, і рекурентність фіксованого розміру, яку можна обчислювати безпосередньо або блоками.

Apple silicon надає різні шляхи для різних робочих навантажень

Попереднє заповнення обробляє багато рядків активації токенів промпту за раз. Локальне робоче навантаження, що розглядається тут, зазвичай декодує по одному запиту за раз (батч 1) і обробляє по одному новому рядку за крок. Ця різниця змінює те, як використовуються ті самі ваги моделі. Попереднє заповнення може повторно використовувати кожен блок ваг на сотнях або тисячах рядків. Декодування переважно не може цього робити, оскільки кожен новий токен вимагає ще одного проходу через ваги.

Apple silicon розміщує CPU та GPU за об'єднаною пам'яттю — єдиним пулом фізичної пам'яті, доступним для обох. Це дозволяє моделі залишатися в пам'яті без підтримки окремої копії для GPU, але це не робить переміщення даних безплатним. Читання ваг і проміжних значень усе ще споживає пропускну здатність пам'яті, тоді як регістри та інше сховище на чипі швидші, але набагато менші.

GPU M5 також надає різні обчислювальні шляхи. Лінійні шари попереднього заповнення використовують загальне множення матриць на матрицю (GEMM), застосовуючи матрицю ваг до багатьох рядків одночасно. Сумісні GEMM можуть використовувати нейронний прискорювач у кожному ядрі GPU через тензорні операції Metal 4. Натомість декодування з батчем 1 використовує загальне множення матриці на вектор (GEMV), застосовуючи ті самі ваги до одного рядка. За невеликого повторного використання ваг GEMV обмежений переважно пропускною здатністю пам'яті і краще підходить до векторних арифметично-логічних блоків (ALU) GPU, ніж до нейронних прискорювачів, розроблених для матричних операцій із більшим повторним використанням даних.

Ці шляхи виконання не є унікальними для Lily. MLX працює з тією ж об'єднаною пам'яттю та вибирає оптимізовані матричні та векторні ядра відповідно до форми робочого навантаження. Реалізація Qwen у MLX-LM вже згруповує роботу експертів, оцінює Gated DeltaNet за допомогою об'єднаного рекурентного ядра Metal та використовує увагу з урахуванням GQA. Ці можливості є спільним відправним пунктом для ефективного виведення Qwen на Apple silicon.

Стратегія оптимізації

Вужча область застосування Lily дозволяє їй координувати ці спільні шляхи виконання відповідно до точної архітектури та розмірностей Qwen. Вона використовує специфічні для фази шляхи GPU, зіставляє робочі навантаження експертів, рекурентні та робочі навантаження уваги Qwen для мінімізації переміщення даних, а також вибирає ядра і макети на основі виміряної форми робочого навантаження. Стратегія складається з трьох частин:

  1. Зіставте шлях GPU з фазою виведення. Використовуйте матрично-орієнтоване виконання, коли попереднє заповнення може повторно використовувати ваги на багатьох рядках, і векторно-орієнтоване виконання, коли декодування з батчем 1 обробляє по одному рядку за раз.
  2. Відображення структури Qwen на GPU з мінімізацією переміщення даних. Зберігайте ваги стиснутими до їх використання, організовуйте роботу маршрутизованих експертів без повернення до CPU, зберігайте стан Gated DeltaNet на чипі під час його рекурентного сканування та повторно використовуйте дані KV, спільні для уваги зі згрупованими запитами.
  3. Адаптація ядер до форми робочого навантаження. У межах кожної фази вибирайте розміри тайлів, макети виконання та шляхи уваги на основі доступної кількості рядків, розподілу рядків між експертами, розмірностей операції та поточної довжини контексту.

У наступних розділах пояснюються ці вибори. Для оптимізацій, оцінених у зіставлених абляціях на M5 Max, ми оцінюємо їхні ефекти шляхом порівняння інакше ідентичних конфігурацій рушія, які відрізняються лише досліджуваною оптимізацією. Оскільки ці експерименти порівнюють версії нашого рушія самі з собою, вони пояснюють механізми, а не декомпонують останні результати відносно MLX-LM.

Попереднє заповнення: повторне використання ваг і збереження маршрутизації на GPU

Попереднє заповнення виставляє багато рядків токенів одночасно, але Qwen спрямовує ці рядки нерівномірно між експертами та оновлює рекурентний стан через послідовність. Його оптимізації поділяються на три групи: організація роботи розріджених експертів навколо спрямованих рядків, збереження сканування Gated DeltaNet на чипі та поділ довгих промптів на обмежені фрагменти.

Виконання та розміщення даних для одного обмеженого фрагмента попереднього заповнення через шар Qwen. Увага розширює кеш KV, тоді як Gated DeltaNet зберігає свій робочий рекурентний стан у регістрах. Метадані маршрутизації експертів залишаються на GPU, ваги Q4 залишаються упакованими до їх деквантування всередині згрупованого GEMM, а тимчасові активації обмежені поточним фрагментом.
Виконання та розміщення даних для одного обмеженого фрагмента попереднього заповнення через шар Qwen. Увага розширює кеш KV, тоді як Gated DeltaNet переносить робочий рекурентний стан у регістрах. Метадані маршрутизації експертів залишаються на GPU, ваги Q4 залишаються упакованими до деквантування всередині згрупованої GEMM, а тимчасові активації обмежені поточним фрагментом.

Оптимізація розріджених обчислень експертів

Деквантування ваг під час множення матриць

Контрольна точка Qwen3.6-35B-A3B використовує групове афінне 4-бітне квантування. Кожна вага зберігається як 4-бітний цілочисельний код, тоді як кожна група з 64 ваг спільно використовує масштаб і зміщення bfloat16, які використовуються для реконструкції її значень. Це зменшує модель із 35 мільярдами параметрів із приблизно 70 ГБ ваг bfloat16 до контрольної точки об'ємом 19,4 ГБ, що робить практичним збереження моделі на Mac.

Тензорна операція Metal 4, що використовується для множення матриць, споживає операнди bfloat16, а не упаковане 4-бітне представлення. Перед множенням GPU повинен реконструювати ваги в bfloat16. Оптимізована згрупована GEMM у Lily виконує це перетворення для одного невеликого тайла ваг за раз і утримує результат у пам'яті групи тредів на чипі лише стільки часу, скільки потрібно для множення його на спрямовані рядки активації. Накопичення використовує 32-бітну рухому кому, а вихід записується у форматі bfloat16. Повний розгорнутий масив ваг ніколи не створюється в об'єднаній пам'яті.

В абляції деквантування виконується як окрема операція: воно розгортає 4-бітні ваги в масив bfloat16 в об'єднаній пам'яті, після чого матричне ядро читає цей масив назад. За промпту на 512 токенів перенесення деквантування у згруповану GEMM збільшило пропускну здатність попереднього заповнення end-to-end на 77,4% за рахунок усунення цього проміжного запису та читання.

Збереження маршрутизації експертів на GPU

Згрупована GEMM вимагає, щоб рядки активації, призначені кожному експерту, зберігалися разом. Після вибору восьми експертів на токен гістограма підраховує, скільки призначень дісталося кожному експерту. Сканування префіксів перетворює ці підрахунки на початкові зсуви, етап розкидання розміщує рядки в їхні групи експертів, а блоковий макет перелічує блоки матриці фіксованого розміру, які повинна обробити згрупована GEMM.

Оптимізований шлях зберігає всю цю послідовність в одному командному буфері — впорядкованому батчі операцій GPU — для кожного фрагмента промпту. Натомість абляція робить паузу, щоб CPU міг перевірити проміжні результати маршрутизації та подати наступну операцію. Збереження гістограми та сканування префіксів на GPU додає два ядра, але усуває синхронізацію CPU–GPU всередині кожного шару MoE.

За промпту на 512 токенів увімкнення маршрутизації на GPU збільшило попереднє заповнення end-to-end на 89%. Це також показує, чому сама лише кількість ядер може ввести в оману: швидший шлях запускає більше ядер, але ніколи не чекає на CPU всередині шару.

Відповідність розміру тайла навантаженню експертів

За промпту на 2 тис. токенів маршрутизація кожного токену до восьми з 256 експертів створює 16 384 призначення токена до експерта, або в середньому 64 рядки активації на експерта. Фактичний розподіл є нерівномірним: деякі експерти отримують багато рядків, тоді як інші отримують мало.

Згрупована GEMM ділить вихід кожного експерта на тайли, які є невеликими прямокутними блоками виходу множення матриць. Кожен тайл призначається одній групі тредів GPU. На GPU Apple silicon група тредів містить одну або кілька simdgroup, кожна з яких складається з 32 тредів, що виконують інструкції синхронно.

Більші тайли розподіляють витрати на налаштування між більшою кількістю рядків і виставляють більше паралельної роботи, але частина великого тайла залишається простою, коли експерт отримує лише кілька рядків. Тому розмір тайла та кількість simdgroup зв'язані.

Абляція фіксує тайл на рівні 16 рядків. У порівнянні з цим контролем увімкнення 32-рядкового тайла з чотирма simdgroup покращило попереднє заповнення end-to-end на 13,2% за 2 тис. токенів.

Збереження рекурентного стану на чипі

Під час попереднього заповнення кожен шар Gated DeltaNet сканує промпт послідовно, переносячи свій рекурентний стан далі. Якщо розміщення в регістрах вимкнено, абляція використовує блокове сканування. За промпту на 2 тис. токенів цей шлях переміщує 256 МіБ (мебібайт) стану на шар і багаторазово зупиняє взаємодіючі треди на бар'єрах — точках синхронізації, де всі задіяні треди повинні чекати один на одного.

Рекурентний стан є матрицею. Оптимізоване ядро призначає кожну стовпчик одній simdgroup. Simdgroup поділяє стовпчик між своїми тредами, завантажує стовпчик у їхні регістри один раз і проводить стан крізь усе сканування. Треди обмінюються проміжними результатами через операції simdgroup, а не пам'ять групи тредів — сховище на чипі, спільне для групи тредів. Завершений стан записується назад лише після сканування.

Стан та його вентиль використовують 32-бітний формат з рухомою комою, оскільки невеликі помилки заокруглення накопичуються при послідовних оновленнях. Активації запитів і ключів залишаються у форматі bfloat16.

За промпту на 2 тис. токенів увімкнення сканування в регістрах покращило попереднє заповнення end-to-end на 5,6%. GEMM експертів становили близько 90% часу попереднього заповнення. Послідовне сканування не виставляє достатньо повторно використовуваної матричної роботи, щоб отримати користь від нейронних прискорювачів.

Обмеження тимчасової пам'яті за допомогою фрагментації промпту

Середовище виконання обробляє довгий промпт як послідовність обмежених фрагментів, а не зберігає тимчасові дані для кожного токена промпту в пам'яті одночасно. Ваги моделі залишаються в об'єднаній пам'яті, тоді як рекурентний стан та кеш KV переносять контекст від одного фрагмента до наступного. Жоден попередній контекст не відкидається.

Без фрагментації масиви тимчасових активацій зростають разом із повним промптом і конкурують із вагами моделі, рекурентним станом та кешем KV за об'єднану пам'ять. Фрагментація зберігає активними тимчасові значення лише одного сегмента за раз, а потім звільнює або повторно використовує це сховище перед обробкою наступного сегмента. Це обмежує пікову робочу пам'ять і дозволяє рушію обробляти довші промпти без зміни виходу моделі.

Фрагментоване попереднє заповнення є популярним у багатьох рушіях і має вирішальне значення для обслуговування довгих багатоступеневих траєкторій у цих середовищах із обмеженою пам'яттю. Загальний час попереднього заповнення для шарів уваги залишається квадратичним від довжини промпту з деякими додатковими накладними витратами від повторюваних завантажень KV попередніх фрагментів.

Декодування: мінімізація байтів, що переміщуються на один токен

Декодування з батчем 1 обробляє по одному новому рядку за раз. За невеликого повторного використання ваг його пропускна здатність залежить переважно від кількості байтів, які рушій переміщує для кожного токена. Зміни декодування поділяються на чотири групи: оптимізація шляху ваг для одного рядка, збереження кожного кроку на GPU, зменшення проміжного трафіку та трафіку стану, а також ефективне читання кешу уваги.

Потік даних для одного кроку декодування з розміром батчу 1 та два механізми, які зменшують час простою та трафік кешу. (A) GPU передає потоком ваги Q4 та стан моделі через увагу, Gated DeltaNet, маршрутизацію та об'єднані ядра експертів, після чого записує обраний токен безпосередньо у вхідний слот наступного кроку та водночас надсилає копію на CPU. (B) Планування з урахуванням залежностей дозволяє незалежним ядрам перекриватися. (C) Упаковка GQA дозволяє чотирьом главам запитів спільно використовувати завантаження кожного рядка KV, зменшуючи вісім незалежних запитів до двох спільних завантажень.
Потік даних для одного кроку декодування з батчем 1 і двох механізмів, які зменшують час простоя та трафік кешу. (A) GPU передає ваги Q4 та стан моделі через увагу, Gated DeltaNet, маршрутизацію та об'єднані ядра експертів, а потім записує вибраний токен безпосередньо у вхідний слот наступного кроку, надсилаючи копію на CPU. (B) Планування з урахуванням залежностей дозволяє незалежним ядрам перекриватися. (C) Пакування GQA дозволяє чотирьом головам запитів спільно використовувати завантаження кожного рядка KV, зменшуючи вісім незалежних запитів до двох спільних завантажень.

Оптимізація шляху ваг для одного рядка

MLX вже надсилає роботу для одного рядка до спеціалізованих матрично-векторних ядер. Оскільки Lily не використовує MLX, кастомне середовище виконання повинно забезпечувати ту саму базову стратегію. Наш рядково-паралельний GEMV розроблений для одного рядка активації. Simdgroup співпрацює над виходом, одночасно читаючи різні частини матриці ваг паралельно.

Збереження кожного кроку декодування на GPU

Збереження передачі токена на GPU

Кожен крок декодування завершується вибором наступного токена; наступний крок починається з цього токена як вхідних даних. Надсилання вибору на CPU, а потім назад на GPU додає точку синхронізації до кожного токена. Натомість наше середовище виконання чергує два командні буфери та два слоти токенів, що розміщені на GPU. GPU вибирає токен із найвищим балом і записує його ідентифікатор токена безпосередньо у вхідний слот для наступного кроку декодування, тоді як CPU готує подальшу роботу.

Перекриття незалежної роботи GPU

В одному зареєстрованому кроці декодування для батчу 1 генерація токена залучила 795 ядер GPU. Їхні залежності утворили 555 послідовних етапів, залишаючи деякі ядра вільними для одночасного виконання. Проте послідовний режим виконання Metal запусків кожне ядро по черзі.

Оптимізований шлях декодування записує фактичні залежності даних у паралельному проході Metal. Незалежні запуск ядер можуть виконуватися одночасно, коли це дозволяють ресурси GPU. Бар'єр вставляється лише тоді, коли подальша робота вимагає попереднього результату.

Зменшення проміжного трафіку та трафіку стану

Окремі ядра часто матеріалізують проміжний результат: одне ядро записує тимчасовий результат у пам'ять, а наступне читає результат назад. Оптимізований шлях декодування об'єднує чотири ланцюжки: дві вхідні проєкції експертів із їхньою керованою активацією; вихідну проєкцію експерта з його оцінкою маршрутизації та результатом спільного експерта; підготовку запитів і ключів перед увагою; а також рекурентне оновлення з його нормалізацією. Кожне об'єднане ядро зберігає тимчасові значення в регістрах замість надсилання їх через пам'ять.

Злиття також скорочує граф залежностей: коли зникає проміжний запис, зникає і бар'єр, який захищав його споживача.

Ефективне читання кешу уваги

Об'єднання читань кешу уваги

Увага читає ключі та значення з кешу KV під час кожного кроку декодування. В абляції сусідні треди GPU не завжди запитують сусідні байти, змушуючи систему пам'яті обслуговувати більше окремих транзакцій. Увімкнення об'єднаних завантажень змушує сусідні треди запитувати сусідні байти, щоб апаратне забезпечення могло об'єднати їхні читання.

У конфігурації bfloat16 об'єднання збільшило пропускну здатність ключів з 33,8 до 47,9 ГБ/с, збільшило пропускну здатність значень із 42,0 до 61,8 ГБ/с та покращило декодування end-to-end на 2,1% за контексту в 3840 токенів.

Пакування голів запитів для повторного використання рядків KV

Увага із згрупованими запитами дозволяє вісьмом головам запитів спільно використовувати одну голову KV. В абляції кожна голова запиту виконується в окремій simdgroup, тому всі вісім незалежно запитують той самий кешований рядок KV. Оптимізоване ядро пакує чотири голови запитів в одну групу тредів, яка завантажує кожен рядок KV один раз і повторно використовує його в чотирьох обчисленнях уваги. Друга група тредів обробляє решту чотирьох голів.

Ця техніка, яку зазвичай називають пакуванням GQA, виконує ту саму арифметику та створює ідентичні вихідні байти, водночас зменшуючи вісім незалежних запитів KV до двох спільних завантажень. Проти распакованої абляції вона покращила пропускну здатність декодування end-to-end на 23,8% за контексту в 32 тис. токенів.

Зміна макетів уваги на довгих контекстах

Кожен крок декодування у шарі повної уваги сканує наявний кеш KV. Макет із фіксованими блоками поділяє цей кеш на рівні частини, які GPU може обробляти паралельно. Його додаткове планування не виправдане, коли кеш малий, але макет із фіксованими блоками рівномірніше розподіляє роботу в міру зростання контексту.

Для цієї моделі середовище виконання підтримує загальний шлях уваги нижче 32 тис. токенів і використовує шлях із фіксованими блоками на 32 тис. токенів або більше. Перемикання застосовується, коли кожна голова має 256 значень і вісім голів запитів спільно використовують голову KV; інші форми залишаються на загальному шляху. Абляція вимикає це перемикання і завжди використовує загальний шлях. Увімкнення маршруту з фіксованими блоками покращило декодування end-to-end на 7,7% на 32 тис., 27,4% на 64 тис. та 40,2% на 128 тис.

Межі подальшої оптимізації

Деякі зміни покращили ізольовану операцію, але не покращили виведення end-to-end.

Спекулятивне декодування, яке використовує меншу модель для пропонування токенів для перевірки повною моделлю, зробило декодування з батчем 1 на 18% повільнішим. Перевірка обробляла групи від двох до п'яти рядків — неефективну форму для цього апаратного забезпечення, і рядки часто вибирали різних експертів, збільшуючи обсяг зчитаних даних ваг експертів. Зменшення вихідного словника черговика покращило пропускну здатність черговика на 4,7–5,1%, але не зробило повний спекулятивний цикл швидшим. Цей результат залежить від робочого навантаження: наше пакетне розгортання Qwen на Blackwell використовує спекулятивне декодування за інших умов.

Інші експерименти включали зменшення запусків GPU, перекриття цілих фаз, використання більших тайлів попереднього заповнення, застосування ширшого злиття, прискорення маршрутизатора та об'єднання проєкції виходу з вибором токенів. Жодне з них не покращило повний цикл виведення.

Вимірювання апаратних лімітів також показало невеликий запас, що залишився в основних операціях попереднього заповнення та декодування. MoE GEMM і GEMV досягли 97,9% і 90,3% найшвидших стійких швидкостей читання ваг для їхніх патернів доступу. Видалення арифметики з розрідженого GEMV змі змінило пропускну здатність лише на 0,2%, що підтверджує, що читання ваг, а не обчислення, є лімітуючим ресурсом. Множення матриць попереднього заповнення подібним чином досягло 93% теоретичної межі матриці ізольовано та 80–86% всередині тестованих моделей.

Продуктивність end-to-end

Порівняння end-to-end завантажує ідентичні батч-файли 4-бітних контрольних точок в обох рушіях і запускає по одному запиту за раз на одному 40-ядерному M5 Max із 128 ГБ. У межах кожного раунду два рушії запускаються в черговому порядку, щоб зменшити зміщення від фонового навантаження та змін температури чипа. Ми порівнюємо з найшвидшим шляхом прямої генерації MLX-LM, а не з її сервером, тому вимірювання зосереджено на виконанні моделі, а не накладних витратах обслуговування.

Діапазон охоплює десять довжин промпту для попереднього заповнення та десять довжин контексту для декодування, від 256 до 128 тис. токенів. Пропускна здатність попереднього заповнення спочатку зростає, оскільки рушій розподіляє фіксовані витрати на налаштування між більшою кількістю токенів. Попереднє заповнення досягає піку приблизно за промпту на 4 тис. токенів, а потім падає, оскільки десять шарів повної уваги виконують більше роботи в міру зростання промпту. Декодування залишається майже пласким на коротких контекстах і знижується, коли читання зростаючого кешу KV стає суттєвим. Кастомний рушій швидший за кожної зареєстрованої довжини.

Оскільки спеціалізоване виконання може змінювати порядок операцій з рухомою комою, ми також перевірили чисельну узгодженість з MLX-LM. У порівнянні з примусовим вчителем обидва рушії передбачили наступний токен з того самого опорного префікса на кожній із 192 позицій, запобігаючи впливу попередніх відмінностей на подальші вхідні дані. Перплексія Lily була лише на 0,04% вищою, і вона вибрала той самий токен із найвищим рейтингом у 96,35% протестованих позицій.

Пропускна здатність попереднього заповнення за довжиною запиту та пропускна здатність декодування за довжиною контексту для Qwen3.6-35B-A3B Q4, батч 1, на одному 40-ядерному M5 Max зі 128 ГБ пам'яті. На десяти довжинах від 256 до 128 тис. токенів Lily швидша в кожній зареєстрованій точці: у 1,12–1,42 раза перевищує пропускну здатність попереднього заповнення MLX-LM та у 1,31–1,37 раза — його пропускну здатність декодування. У порівнянні використовується найшвидший шлях прямої генерації MLX-LM. Обидві горизонтальні оші використовують логарифмічні шкали; жодна вертикальна вісь не починається з нуля.
Пропускна здатність попереднього заповнення за довжиною промпту та пропускна здатність декодування за довжиною контексту для Qwen3.6-35B-A3B Q4, батч 1, на одному 40-ядерному M5 Max із 128 ГБ. Серед десяти довжин від 256 до 128 тис. токенів Lily швидша в кожній зареєстрованій точці: 1,12–1,42× пропускної здатності попереднього заповнення MLX-LM та 1,31–1,37× її пропускної здатності декодування. Порівняння використовує найшвидший шлях прямої генерації MLX-LM. Обидві горизонтальні оші використовують логарифмічні шкали; жодна вертикальна вісь не починається з нуля.

Створено для локальної платформи

Apple silicon — це не зменшений GPU датацентру. Це повноцінна локальна платформа виведення з власними апаратними та програмними характеристиками. Об'єднана пам'ять надає єдиному вузлу дуже високу стелю щодо того, скільки моделі та стану він може вмістити. Нейронні прискорювачі M5 поглинають роботу з щільними матрицями при попередньому заповненні. Векторні ALU обробляють обмежений пропускною здатністю залишок із низьким повторним використанням при декодуванні.

Qwen додає подальші можливості для спеціалізації: збереження маршрутизації експертів і рекурентного стану на GPU, усунення непотрібних проміжних результатів, перекриття незалежної роботи, повторне використання спільних даних KV та адаптація ядер до форми робочого навантаження.

Завдяки оптимізації під конкретну модель і платформу один Mac може ефективно запускати велику розріджену модель. Майбутня робота розширить охоплення моделей, чипів і робочих навантажень обслуговування, а також перетворить валідовані тут механізми на одній конфігурації на більш загальну політику середовища виконання.

Ширшим принципом є узгодження рушія як з архітектурою моделі, так і зі специфічними обчислювальними шляхами та шляхами пам'яті апаратного забезпечення. З еволюцією передових моделей із відкритими вагами та апаратного забезпечення високопродуктивне локальне виведення все більше залежатиме від рушіїв, пристосованих до обох компонентів, а не тих, що абстрагують їхні відмінності.