Параллельные вычисления
SIMD и векторизация
Llama.cpp запускает LLaMA-7B на MacBook без GPU - 20+ токенов в секунду. Секрет: код использует ARM NEON для матричных умножений. Каждый матмул в 8-битном квантовании - это плотный SIMD kernel, обрабатывающий 8-16 элементов за такт.
- **NumPy/PyTorch** - все elementwise операции и матричное умножение реализованы через BLAS с AVX/NEON intrinsics под капотом
- **GGUF/llama.cpp** - квантованный inference LLM на CPU: 4-битные веса + NEON/AVX2 kernels = LLaMA-13B на обычном ноутбуке
- **Apple ANE + NEON** - Core ML использует Neural Engine для neural layers и NEON для предобработки данных в единой pipeline
AVX/AVX-512: широкие векторные регистры x86
SIMD - Single Instruction, Multiple Data. Одна инструкция обрабатывает несколько элементов данных одновременно. AVX-512 на современных Intel/AMD: регистры 512 бит. Это 16 float32 или 8 float64 за одну инструкцию. Пиковая производительность вычислений вырастает в 16x при правильном использовании.
История: SSE (1999) - 128 бит, 4 float. AVX (2011) - 256 бит, 8 float. AVX-512 (2017) - 512 бит, 16 float. Каждое расширение удваивало ширину. Но AVX-512 неоднозначен: запускает frequency throttling на многих CPU - если hot loop использует AVX-512, тактовая частота падает.
PyTorch и NumPy используют AVX автоматически через BLAS/MKL. Elementwise операции на тензорах - vectorized по умолчанию. `torch.compile()` в PyTorch 2.0 генерирует AVX-512 код для custom операций. Знание AVX важно для написания BLAS kernels и оценки теоретического пика производительности.
Сколько float32 может обработать одна AVX-512 инструкция?
ARM NEON/SVE: SIMD для мобильных и серверов
ARM NEON - SIMD расширение для ARM архитектуры. 128-битные векторные регистры, 16 float32 или 8 float64 пар за операцию. Apple M-series CPU, Qualcomm Snapdragon, AWS Graviton3 - все используют NEON. Именно NEON делает CoreML inference быстрым на iPhone.
SVE (Scalable Vector Extension, ARMv8.2+) - революционный подход: ширина вектора определяется аппаратурой, а не ISA. Код компилируется один раз и работает на SVE с 128, 256, 512 или 2048 битами без перекомпиляции. AWS Graviton3 использует 256-bit SVE.
TensorFlow Lite и Core ML aggressively use NEON для inference на мобильных. MLX (Apple) компилирует tensor операции в NEON/AMX инструкции для M-серии. Llama.cpp портирован под NEON: 7B модель на iPhone 15 Pro работает со скоростью ~10 токенов/сек благодаря NEON matrix ops.
Чем SVE принципиально отличается от AVX/NEON?
Автовекторизация: компилятор как союзник
Автовекторизация - компилятор автоматически трансформирует скалярные loop в SIMD инструкции. GCC/Clang с `-O2` или `-O3` делают это прозрачно. Главное условие: loop должен быть vectorizable - нет зависимостей между итерациями, нет pointer aliasing.
Проверка: `clang -O3 -Rpass=loop-vectorize -Rpass-missed=loop-vectorize` покажет какие циклы векторизованы и почему нет. GCC: `-fopt-info-vec`. Отчёт компилятора - быстрейший способ найти узкие места.
NumPy ufuncs полностью векторизованы под капотом (BLAS + LAPACK). Однако если NumPy-операция вызывается в Python цикле - вся производительность теряется на overhead PyObject. Jax.jit и torch.compile устраняют этот overhead, компилируя Python-уровневые операции в единый SIMD kernel.
Почему цикл с `arr[i] += arr[i-1]` не векторизуется?
Intrinsics: ручной SIMD без ассемблера
Intrinsics - C/C++ функции, которые компилятор транслирует напрямую в SIMD инструкции. Это middle ground между ассемблером (полный контроль, нечитаемо) и надеждой на автовекторизацию (удобно, непредсказуемо). Критичный performance код в BLAS, FFT библиотеках написан на intrinsics.
Когда нужны intrinsics: алгоритм не векторизуется автоматически из-за структуры данных (AoS vs SoA), нужен специфический shuffle/permute, или результат автовекторизации не оптимален. Apple vDSP, Intel SVML, ARM Performance Libraries предоставляют готовые оптимизированные kernels.
Intel Intrinsics Guide (intrinsics.intel.com) - обязательный ресурс: каждая инструкция с latency, throughput и описанием. FMA (Fused Multiply-Add) - ключевая для ML: a*b+c в одну инструкцию с одним округлением. На современных CPU FMA throughput 2 операции/такт = 64 GFLOP/s для AVX-512 при 4GHz.
Автовекторизация всегда находит оптимальный SIMD код
Компилятор ограничен тем, что может доказать - при сложной структуре данных или неочевидных зависимостях intrinsics дают значительно лучший результат
Компилятор консервативен: при малейшем сомнении в корректности он откажется от векторизации. Intrinsics позволяют разработчику взять ответственность на себя и выжать максимум из железа
Что такое FMA инструкция и почему она важна для ML?
Связанные темы
SIMD - нижний уровень стека параллелизма, на котором стоят GPU и параллельные алгоритмы:
- GPU Computing: CUDA основы — GPU - SIMD в масштабе тысяч cores
- GPU оптимизация — Те же принципы locality и width что и в SIMD
- Параллельные алгоритмы — SIMD leaf-level оптимизации для параллельных алгоритмов
Ключевые идеи
- **SIMD** обрабатывает 8-16 элементов за такт через широкие регистры (AVX2: 8 float32, AVX-512: 16 float32, NEON: 4 float32)
- **Автовекторизация** работает автоматически при отсутствии зависимостей между итерациями; `-O3 -mavx2` достаточно для типичных loops
- **SVE** - масштабируемый SIMD ARM: один код, разная ширина на разных CPU - будущее ARM ecosystem
- **FMA** (Fused Multiply-Add) удваивает throughput матричных умножений и улучшает точность: 1 инструкция вместо 2, 1 округление вместо 2
Вопросы для размышления
- Когда стоит писать SIMD intrinsics вручную, а когда достаточно автовекторизации + pragma?
- AVX-512 вызывает frequency throttling. Как решить: использовать AVX-512 для максимальной ширины или AVX2 для стабильной частоты?
- Почему Array of Structures (AoS) плохо векторизуется, а Structure of Arrays (SoA) - хорошо?
Связанные уроки
- par-11 — GPU CUDA - SIMD в экстремальном масштабе, SIMD CPU предшествует ему
- par-15 — NUMA-aware SIMD: размещение данных влияет на векторную производительность
- par-13 — GPU оптимизация - те же принципы data parallelism, но на тысячах CUDA cores
- par-18 — Параллельные алгоритмы опираются на SIMD для leaf-level оптимизаций
- arch-04-cpu
- la-04-matrix-ops