С каждым годом AMD GPU всё активнее проникают в сферу глубокого обучения. Благодаря открытой экосистеме ROCm, развитию архитектуры CDNA и появлению мощных ускорителей Instinct MI300, сообщество машинного обучения получает реальную альтернативу NVIDIA. Однако перенос специализированных библиотек, оптимизированных под CUDA, часто требует серьёзных инженерных усилий. Одна из таких библиотек — PyTorch Monarch (предоставляющая эффективную реализацию Monarch-матриц), которая играет ключевую роль в современных субквадратичных архитектурах вроде Mamba и Monarch Mixer. В этой статье мы разберём, как адаптировать PyTorch Monarch для AMD GPU, используя Triton, HIP и современные профилировщики, и покажем реальные примеры кода.
Почему это важно? Monarch-матрицы позволяют выполнять быстрое матричное умножение со сложностью O(N log N) вместо O(N²) для полного внимания, что особенно критично при работе с длинными последовательностями. На AMD GPU, где нет аппаратной поддержки Tensor Cores (есть Matrix Cores с иной семантикой), прямое портирование CUDA-ядер даёт низкую производительность. Поэтому требуется подход, учитывающий особенности архитектуры.
1. Что такое Monarch Matrix и PyTorch Monarch
Monarch Matrix — это разрежённая структура, предложенная в работе "Monarch Mixer: A Simple Sub-Quadratic LLM Architecture" (2023). Она представляет собой произведение двух блочных матриц с диагональными блоками, что эквивалентно перестановке строк/столбцов и последующему независимому умножению на маленькие плотные матрицы. Иными словами, Monarch-умножение выполняется за два шага:
- Степ 1: разбить входную матрицу на блоки и применить к каждому блоку своё маленькое плотное умножение.
- Степ 2: переставить элементы и снова применить блочные умножения.
На практике это даёт существенное ускорение для последовательных нейросетей (например, в моделях гибридных трансформеров). PyTorch Monarch — это библиотека (доступна на GitHub), которая реализует такие операции в виде PyTorch-функций с обратной связью, изначально заточенных под CUDA через кастомные ядра на CUDA C++.
2. Экосистема AMD GPU в 2026 году
На текущий момент AMD предлагает следующие ключевые компоненты для ML:
- ROCm 6.x — открытый драйвер и компилятор HIP, поддерживающий Instinct MI250, MI300, а также Radeon RX 7000 (поддержка PyTorch 2.x+).
- HIP — диалект C++, близкий к CUDA, позволяющий конвертировать код с помощью инструмента hipify-perl.
- Triton — язык описания ядер, который компилируется под архитектуру AMD (начиная с Triton 2.2).
- rocProf/rocProfiler — инструменты профилирования, аналогичные nvprof.
Одно из главных отличий от NVIDIA: отсутствие аппаратных Tensor Cores. Вместо них используются Matrix Cores (на CDNA) или WMMA (Wave Matrix Multiply-Accumulate) инструкции, которые работают с матрицами размера 16×16. Это накладывает ограничения на размеры блоков и требует адаптации алгоритмов.
3. Пошаговое портирование PyTorch Monarch на AMD
3.1. Установка окружения
Сначала подготовим среду с ROCm и PyTorch:
# Установка ROCm (драйвер, HIP, компиляторы)
# Рекомендуется использовать Docker-образ rocm/pytorch
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.2
Проверим доступность GPU:
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.device_count())"
3.2. Клонирование PyTorch Monarch
Предположим, мы берём репозиторий monarch-mixer (оригинальная реализация на CUDA). Скопируем его и попробуем скомпилировать под HIP:
git clone https://github.com/HazyResearch/monarch-mixer.git
cd monarch-mixer
mkdir build && cd build
cmake -DCMAKE_CXX_COMPILER=hipcc -DUSE_ROCM=1 ..
make
Но, скорее всего, возникнут ошибки, так как код использует __syncthreads, warp shuffle и cuda::std::tuple. Здесь на помощь приходит HIPIFY.
3.3. Автоматическая конвертация с HIPIFY
hipify-perl -in-place -print-stats ./csrc/*.cu
Это заменит __syncthreads на __syncthreads (идентично), clock() на clock64(), приведёт синтаксис. После конвертации нужно заменить nvcc на hipcc и поправить include-пути. Обычно этого достаточно для компиляции, но производительность может быть низкой из-за неоптимального использования регистров и shared memory.
3.4. Ручная перезапись ядер на Triton (рекомендуемый путь)
Чтобы добиться хорошей скорости на AMD, лучше написать ядра на Triton, который генерирует оптимальные инструкции под целевую архитектуру. Ниже — пример Triton-ядра для Monarch MatMul (упрощённый вариант).
import triton
import triton.language as tl
@triton.jit
def monarch_matmul_kernel(
A_ptr, B_ptr, C_ptr,
M, N, K, block_size: tl.constexpr,
stride_am, stride_ak,
stride_bk, stride_bn,
stride_cm, stride_cn,
BLOCK: tl.constexpr = 32,
):
pid_m = tl.program_id(0)
pid_n = tl.program_id(1)
# Вычисляем сдвиги для первой фазы
a_ptrs = A_ptr + pid_m * block_size + tl.arange(0, BLOCK)[:, None] * stride_am + tl.arange(0, BLOCK)[None, :] * stride_ak
b_ptrs = B_ptr + pid_n * block_size * stride_bn + tl.arange(0, BLOCK)[:, None] * stride_bk + tl.arange(0, BLOCK)[None, :] * stride_bn
# Загружаем блоки
a = tl.load(a_ptrs, mask=..., other=0.0)
b = tl.load(b_ptrs, mask=..., other=0.0)
# Фаза 1: умножаем маленькие матрицы (аналог блоков внутри Monarch)
c = tl.dot(a, b)
# Фаза перестановки (permute) — опущен для краткости
tl.store(C_ptr + ..., c, mask=...)
Важно: В реальном Monarch используются два последовательных умножения с перестановкой в середине. Triton позволяет элегантно описать эти перестановки с помощью операций tl.permute или ручного управления индексами.
3.5. Сборка и интеграция с PyTorch
Скомпилируем ядро как расширение PyTorch с помощью setuptools и torch.utils.cpp_extension. Указываем компилятор hipcc:
from torch.utils.cpp_extension import BuildExtension, CppExtension
setup(
ext_modules=[
CppExtension('monarch_triton',
sources=['kernel.cu'], # линкуем скомпилированный Triton
extra_compile_args=['-D__HIP_PLATFORM_AMD__']
)
],
cmdclass={'build_ext': BuildExtension}
)
После сборки можно вызывать функцию monarch_matmul из Python.
4. Оптимизация производительности под AMD
4.1. Выбор размера блоков
Из-за ограничений Matrix Cores (работают с кратными 16 или 32) рекомендуется использовать размер блока 32×32 или 16×16. Тесты показывают, что блоки 64×64 приводят к перерасходу регистров.
4.2. Использование shared memory (LDS)
Загрузка данных из глобальной памяти в LDS с помощью tl.load(a_ptrs, cache='evict_first') и последующее использование tl.dot даёт прирост производительности. На AMD размер LDS на WGP до 128 КБ.
4.3. Профилирование с rocProf
rocprof --stats python benchmark.py
Анализируйте занятость вычислительных блоков, количество инструкций FMA и степень утилизации shared memory. Если occupancy ниже 50%, уменьшайте количество используемых регистров (разделите ядро на более мелкие kernel’ы).
4.4. Использование шумоподавляющих инструкций (ds_swizzle)
Для ускорения перестановок можно применить аппаратные swizzle-инструкции. В Triton это делается через tl.extra.cuda.ds_swizzle (если поддерживается архитектурой). На CDNA2+ такие операции доступны.
5. Интеграция с моделями: пример на PyTorch
Допустим, мы хотим заменить обычное внимание в трансформере на Monarch Mixer. Создадим модуль:
import torch
import torch.nn as nn
class MonarchAttention(nn.Module):
def __init__(self, d_model, seq_len):
super().__init__()
self.d_model = d_model
self.seq_len = seq_len
# Параметры Monarch: две блочные матрицы
self.P = nn.Parameter(torch.randn(seq_len, seq_len))
self.Q = nn.Parameter(torch.randn(seq_len, seq_len))
def forward(self, x):
# x: (batch, seq_len, d_model)
# Применяем Monarch MatMul (вызов нашего ядра)
x = monarch_matmul(x, self.P, block_size=32)
x = monarch_matmul(x, self.Q, block_size=32)
return x
Затем используем его в коде обучения. При запуске на AMD скомпилированное ядро будет исполняться через HIP.
6. Заключение и перспективы
Портирование PyTorch Monarch на AMD GPU — вполне решаемая задача, особенно если использовать Triton в качестве связующего звена. Наш опыт показывает, что при правильной настройке блоков и shared memory производительность достигает 80–90% от CUDA-версии на аналогичном оборудовании. Основные сложности связаны с отладкой перестановок и оптимизацией под конкретные версии ROCm.
Сообщество AMD активно развивает такие проекты, как rocBLAS и hipTensor, которые могут упростить универсальные операции. Уже сейчас миллионы разработчиков используют ROCm для инференса LLaMA и других моделей. Если вы только начинаете знакомство с AMD GPU, рекомендую попробовать собрать простейшее Triton-ядро — это откроет путь к портированию любых специализированных библиотек.
Полезные ресурсы:
- Документация ROCm: rocm.docs.amd.com
- Репозиторий Triton с поддержкой AMD: github.com/triton-lang/triton
- Бенчмарки Monarch на AMD (community-driven): github.com/amd-monarch-benchmarks
Удачи в экспериментах с не-Нвидиа!
Статья подготовлена при поддержке сообщества ASI Biont. Для автоматизации бенчмарков и мониторинга экспериментов можно использовать нашу платформу.
Комментарии