Apple M3: нашли баг Neural Engine, из-за которого ИИ на чипе тормозил вдвое-втрое
Eileen Yoon профилировала, с какой скоростью нейронный процессор (Neural Engine, ANE) чипа Apple M3 читает веса модели из оперативной памяти при потоковой генерации токенов. Она обнаружила аппаратную ошибку (erratum) в самой микроархитектуре ANE: если суммарный размер передаваемых весов оказывается ровно кратен 1 МиБ, пропускная способность DMA-канала обрушивается с номинальных 45, 60 ГБ/с до 17, 19 ГБ/с, то есть ANE работает в 2,5, 3,5 раза медленнее, чем должен. Проблема затрагивает 7 из 15 моделей открытого проекта ANEMLL (github.com/anemll/anemll), который переносит LLM на ANE.
При N=4096 значение D=2048 давало пропускную способность 16,93 ГБ/с, а соседнее D=2016, 44,5 ГБ/с: падение на 27,57 ГБ/с, то есть на 61,96%. Замеры повторили 40 раз на M3 Air при одинаковых тепловых условиях. Yoon сначала проверила и отвергла версию с конфликтом ядер: провал сохранялся даже при одном активном ядре ANE из 16, значит, проблема не в конкуренции между ядрами, а внутри каждого из них. Проверила и версию с коллизиями адресов в банках DRAM: случайное перемешивание физических адресов почти не изменило скорость (31,37 ГБ/с у обычного порядка против 32,29 ГБ/с у перемешанного), этого недостаточно, чтобы объяснить обвал.
Ключевая зацепка, периодичность: провал повторяется ровно на каждом кратном 0x4000 (16384) 64-байтных строк передачи, что при такой гранулярности равно 1 МиБ, и полностью восстанавливается через 256 строк (16 КиБ), ровно размер страницы виртуальной памяти в Apple Silicon. Отсюда гипотеза Yoon о причине: кольцевой буфер спекулятивной предвыборки (prefetch) в контроллере DMA считает «запас» предвыборки как разницу между указателями чтения и записи, используя только младшие 14 бит адреса (по модулю 0x4000), без бита переполнения (epoch bit). Когда передача укладывается ровно в целое число таких 16-килобайтных периодов, указатель после каждого полного оборота возвращается в ту же точку, и разница вычисляется как ноль: «остался ещё целый круг» ошибочно читается как «предвыборки не осталось». Предвыборка на весь объём передачи не выдаётся, и вместо потоковой передачи получается «рваная», с постоянными простоями DMA-шины, при этом сама передача данных всё равно завершается корректно, ошибок целостности нет.
Программный обход: не запрашивать DMA-передачи объёмом ровно 1 МиБ, а дробить такую задачу на не кратные 1 МиБ куски, например, на две передачи по 512 КиБ. Накладные расходы на диспетчеризацию дополнительных задач, доли миллисекунды, ничтожные на фоне потерь пропускной способности. Проверка на одной 1-МиБ передаче на ядро: без дробления, 17,25 ГБ/с; на два куска по 0x2000 строк, 45,52 ГБ/с (ускорение в 2,66 раза); на четыре куска по 0x1000 строк, 44,83 ГБ/с (в 2,60 раза). Контрольный тест подтвердил, что ускорение даёт именно обход бага: дробление передачи, изначально НЕ кратной 1 МиБ (значит, багом не задетой), не дало прироста скорости вовсе.
На развёрнутом наборе размеров передач (D от 2048 до 16384, то есть от 1 до 8 МиБ на ядро) дробление стабильно поднимает пропускную способность с 17,3, 19,1 ГБ/с до 43,5, 60,5 ГБ/с, ускорение от 2,51 до 3,16 раза в зависимости от размера. На реальных моделях это дало ощутимый прирост в токенах в секунду при однопотоковой генерации: Llama 3.2 1B, с 10,0 до 24,3 токена/с (использование DRAM выросло с 24,7 до 60,0 ГБ/с), Qwen3-8B, с 1,36 до 2,97 токена/с (DRAM, с 22,4 до 48,7 ГБ/с). Теоретический потолок пропускной способности памяти M3 (LPDDR-6400, шина 128 бит на скорости 6,4 ГТ/с), 102,4 ГБ/с, что соответствует заявленным Apple примерно 100 ГБ/с.
Материал опубликован 10 августа 2026 года на личном блоге автора (eiln.github.io) и обсуждается на Hacker News (194 балла, 29 комментариев). Апелляции к первопричине на уровне RTL-схемы, это гипотеза автора по итогам реверс-инжиниринга, а не подтверждение Apple: компания в источнике не упоминается и бага не комментировала. Измерения сделаны только на одном устройстве, M3 Air; про другие чипы Apple Silicon (M1, M2, M4) источник ничего не утверждает.
Ключевые факты
- На Apple M3 Neural Engine нашли аппаратную ошибку: если объём передаваемых весов модели кратен ровно 1 МиБ, пропускная способность DMA падает с 45, 60 ГБ/с до 17, 19 ГБ/с, в 2,5, 3,5 раза.
- Баг затрагивает 7 из 15 моделей открытого проекта ANEMLL для запуска LLM на Neural Engine.
- Вероятная причина, 14-битная арифметика в кольце спекулятивной предвыборки DMA: на границе, кратной 0x4000 строк (=1 МиБ), счётчик запаса предвыборки обнуляется по ошибке, и предвыборка для всей передачи не выдаётся; сама передача данных при этом остаётся корректной.
- Программный обход, дробить 1-МиБ передачи на не кратные 1 МиБ куски (например, два по 512 КиБ), поднял пропускную способность в 2,5, 3,2 раза в зависимости от размера передачи.
- На реальных моделях это ускорило генерацию токенов: Llama 3.2 1B, с 10,0 до 24,3 токена/с, Qwen3-8B, с 1,36 до 2,97 токена/с.
Почему это важно
Локальный запуск ИИ-моделей на Apple Silicon через Neural Engine, обычная практика, и найденная ошибка бьёт по нему тихо и массово: она срабатывает всякий раз, когда суммарный размер весовых тензоров ровно кратен 1 МиБ, а такие размеры типичны для моделей с размерностями по степеням двойки. То есть часть реальных моделей и проектов вроде ANEMLL годами теряла половину и больше доступной пропускной способности памяти, и никто, включая Apple, об этом публично не сообщал. Нашла это не компания, а сторонний разработчик собственным реверс-инжинирингом железа.
Кому это важно
Разработчикам, которые переносят LLM на Apple Neural Engine и проекту ANEMLL напрямую (у 7 из 15 его моделей есть уязвимые к багу слои); инженерам, пишущим низкоуровневые ANE-кернелы и планировщики DMA-задач; всем, кто гоняет квантованные модели локально на Mac с M3 и ждёт от «Neural Engine» заявленной скорости; внутренней команде Apple, отвечающей за прошивку и драйверы Neural Engine, баг живёт на уровне RTL-схемы контроллера.
Как это применить
Правило простое: если задача DMA для Neural Engine компилируется в передачу весов ровно по 1 МиБ на ядро, дробить её на не кратные 1 МиБ куски, например на два по 512 КиБ; в статье это уже сделано для затронутых моделей ANEMLL прямым патчем на уровне разбиения свёрток 1×1. Накладные расходы на дополнительную DMA-задачу, доли миллисекунды, а выигрыш, до 3,16 раза по пропускной способности. Патч не требует изменения самой модели, только способа, которым кернел ANE запрашивает передачу весов.
Можно ли доверять
Методология тщательная: 40 повторов замера при контролируемом тепловом режиме, сведение к минимуму переменных (менялись только адрес и размер DMA-задачи), последовательное исключение альтернативных версий (конкуренция ядер, исключена сведением к одному активному ядру; коллизии банков DRAM, проверены и отвергнуты перемешиванием адресов), и, что особенно убедительно, контрольный эксперимент: дробление передачи, изначально не задетой багом, не дало ускорения вовсе, значит, прирост объясняется именно устранённым багом, а не побочным эффектом самого дробления. При этом конкретная причина на уровне RTL-схемы (14-битная арифметика кольца предвыборки), гипотеза автора по совпадающим числам (0x4000 строк = 1 МиБ = размер страницы Apple Silicon), а не официально подтверждённый Apple диагноз: компания в материале не упоминается и бага не комментировала.
Риски и подводные камни
Это программный обход, а не исправление самой аппаратной ошибки, Apple может (или не может) устранить её в будущих ревизиях чипа или прошивке, а до тех пор каждый разработчик кернелов ANE обязан помнить о границе 1 МиБ сам. Измерения сделаны только на одном устройстве, M3 Air; перенесётся ли та же ошибка и тот же обход на M1, M2 или M4, источник не проверяет и не утверждает. Наконец, диагноз первопричины остаётся гипотезой одного независимого исследования без подтверждения производителя, сама возможность обхода бага (дробление передачи) доказана экспериментально, а вот точная схема ошибки внутри контроллера, нет.
«Это не ошибка корректности RTL-схемы: DMA-контроллер ядра всё равно завершает передачу верно. Но запросы вокруг границы в 1 МиБ загоняются в отдельный, лишённый кредитов режим выдачи запросов, который необоснованно съедает 28, 43 ГБ/с пропускной способности, причём именно на тех размерах передач, которые встречаются чаще всего.»
— Eileen Yoon, автор блога eiln.github.io