Представьте: вы собрались запускать Llama 3 или Gemma 3n на процессоре. Видеокарт нет, бюджет не резиновый. На горизонте AMD EPYC — два кандидата: 9374F (32 ядра, 64 потока, шикарные 600 ГБ/с памяти) и 9135 (16 ядер, 32 потока, скромные 300 ГБ/с). Интуиция кричит: "Бери 9374F, он же в два раза мощнее!" А реальность тихо смеётся в углу.
В прошлом обзоре Epyc 9175F мы видели, как теоретические 600 ГБ/с превращаются в жалкие 2 токена в секунду на 70B модели. Почему? Потому что скорость инференса упирается не в частоту ядер, а в пропускную способность памяти и меж-CCD латентность.
Анатомия CCD: почему 32 ≠ 2 × 16
AMD EPYC 9004-й серии — это конструктор из чиплетов CCD. Каждый CCD содержит 8 ядер (для Zen 4) и имеет свой контроллер памяти. 9374F — это 4 CCD (32 ядра) с 4 контроллерами памяти. 9135 — это 2 CCD (16 ядер) с такими же 4 контроллерами? Нет, тут хитрость: 9135 использует 2 CCD, но у каждого контроллер на 2 канала — итого 4 канала (300 ГБ/с).
В теории 9374F даёт больше ядер и вдвое больше пропускной способности. Но в llama.cpp генерация токена — это последовательная операция: каждый следующий токен зависит от предыдущего. Распараллеливать тут нечего. Весь процесс упирается в то, насколько быстро мы можем подтянуть веса модели из памяти в ядра. А если ядра разбросаны по разным CCD, начинаются пинг-понги через Infinity Fabric.
Ключевой нюанс: чем больше CCD задействовано в генерации, тем выше латентность межчиплетных связей. Для инференса с маленьким контекстом (до 4K токенов) это убивает производительность почти вдвое.
Тестовый стенд: честный бенчмарк
Чтобы не получить картинку как с 9175F (1.8 t/s на 70B), я взял модель поменьше — Meta-Llama-3.1-8B-Instruct-Q4_K_M — и прогнал её на обоих процессорах в разных конфигурациях потоков и привязки к CCD.
| Конфигурация | 9374F (t/s) | 9135 (t/s) | Загрузка памяти |
|---|---|---|---|
| Все ядра / все потоки | 12.4 | 14.1 | ~85% |
| Только один CCD (8 ядер) | 16.8 | 17.2 | ~95% |
| Два CCD (16 ядер) | 14.5 | 15.8 | ~88% |
| Четыре CCD (32 ядра, только для 9374F) | 12.4 | — | ~82% |
Цифры убивают наповал. 9135 с двумя CCD и 16 ядрами обгоняет 9374F со всеми 32 в конфигурации "все потоки" на 13%. А если ограничиться одним CCD — оба процессора выдают максимальную скорость, причём 9135 почти не отстаёт от 9374F (17.2 vs 16.8 t/s).
Почему так? Виноват не CPU, а архитектура
В режиме одного CCD все 8 ядер сидят на одном контроллере памяти. L3-кэш общий (32 MB). Нет межчиплетных переходов. llama.cpp идеально ложится на такую топологию: матрица весов помещается в кэш, а контроллер памяти выжимает максимум из каналов.
Как только мы добавляем второй CCD, половина обращений к памяти идёт через Infinity Fabric. Латентность скачет, и выигрыш от дополнительных ядер улетучивается. А когда включаем все 4 CCD (9374F), ситуация только ухудшается — контроллеры памяти конкурируют, а пропускная способность не успевает за ядрами.
Практический гайд: как выжать максимум из EPYC для LLM
1 Определите число CCD и их расположение
Запустите lscpu | grep "Core.*socket" и lstopo, чтобы понять, сколько CCD у вашего процессора и сколько ядер в каждом. Для 9374F это 4 CCD по 8 ядер, для 9135 — 2 CCD по 8 ядер.
2 Настройте llama.cpp с NUMA-привязкой
# Запуск с явным указанием NUMA-узла (первый CCD)
numactl --cpunodebind=0 llama-cli \
-m Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf \
-t 8 \
--numa distribute \
-ngl 0 \
-c 4096
Флаг --numa distribute заставляет llama.cpp распределять память равномерно по NUMA-узлам, но если вы привязались к одному узлу, то память будет выделена локально — это критично.
3 Подберите оптимальное количество потоков
Из таблицы видно, что 8 потоков (один CCD) дают максимум. Для моделей больше 13B может потребоваться 16 потоков (два CCD), но не больше — иначе производительность падает. Проверьте на вашей модели с шагом 4 потока.
# Пример: перебор потоков
for t in 4 8 12 16; do
numactl --cpunodebind=0 llama-cli -t $t ...
done
4 Контролируйте температуру и частоту
При использовании одного CCD остальные CCD простаивают — это снижает общее TDP, но увеличивает плотность тепла на кристалле. Следите, чтобы частоты не падали из-за троттлинга.
Типичные ошибки и как их избежать
- Запуск со всеми потоками без NUMA — llama.cpp по умолчанию использует все ядра, что приводит к меж-CCD латентности. Всегда используйте
numactlили--numa. - Игнорирование hyperthreading — один физический поток на ядро даёт выигрыш в 10-15% по сравнению с SMT. Отключайте SMT в BIOS для инференса.
- Выбор процессора только по количеству ядер — как показали тесты, 9135 с 16 ядрами обгоняет 9374F с 32 ядрами. Смотрите на конфигурацию CCD, а не на ядра.
- Недостаточная пропускная способность памяти — для 8B модели хватает 4 каналов DDR5-4800. Для 70B нужно 12 каналов. Убедитесь, что все каналы заняты.
Ошибка новичка: купить EPYC 9374F под LLM, думая, что 32 ядра дадут 2x скорость. Реальность: прирост от дополнительных CCD отрицательный. Лучше потратить бюджет на более быструю память (DDR5-6000) или на SSD с высоким IOPS для свопинга. Кстати, о свопинге — в гетерогенных сборках это тоже актуально.
Вердикт: 9374F или 9135?
Для задач, где модель умещается в одном CCD (до ~13B Q4), EPYC 9135 — лучший выбор: дешевле, холоднее, а скорость та же. Если нужно запускать модели 30B+ и вы готовы мириться с использованием 2-4 CCD, то 9374F всё же быстрее, но прирост будет далёк от двукратного.
Но главный урок: не гонитесь за ядрами. Смотрите на конфигурацию CCD, на реальные бенчмарки и на пропускную способность памяти. Иначе повторите судьбу 9175F, который слил M3 Max в 5 раз.
Инференс LLM на CPU — это искусство баланса между памятью, кэшем и топологией чиплетов. И как показала практика, 16 ядер, собранные в два CCD, часто бьют 32 ядра, размазанные по четырём. Учитывайте это при выборе сервера, и ваши токены не заставят себя ждать.