English translation is not available yet. Showing Russian content.
Как устроена иерархия памяти GPU (Global, L2, Shared, Registers) и как это влияет на LLM инференс?
Краткий тезис
Иерархия памяти GPU — это многоуровневая система с разной скоростью и объёмом: Global Memory (HBM) — медленная, но большая (~80 ГБ на H100, ~2 ТБ/с), L2 Cache — промежуточный буфер, Shared Memory — быстрая ручная память внутри SM (~20 ТБ/с, до 228 КБ на SM), Registers — самые быстрые (регистры на поток). Для LLM инференса узким местом является memory bandwidth, а не compute, потому что веса и активации пересылаются из HBM в вычислительные блоки. Понимание иерархии позволяет применять техники вроде Flash Attention, kernel fusion и quantization, которые минимизируют обращения к медленной памяти и ускоряют инференс.
1. Зачем нужна иерархия памяти в GPU
GPU (Graphics Processing Unit) выполняет тысячи потоков параллельно. Чтобы питать вычислительные ядра данными, требуется огромная пропускная способность. Однако физически невозможно сделать всю память одновременно быстрой и большой — компромисс решается иерархией:
- Ближе к ядрам — меньше задержка, выше пропускная способность, но малый объём.
- Дальше от ядер — больше объём, но медленнее.
Для LLM инференса (вывод нейросети) характерны операции матричного умножения (GEMM) и attention, которые требуют многократного чтения весов и промежуточных активаций. Если данные лежат в медленной памяти, ядра простаивают в ожидании — это и есть bottleneck memory bandwidth.
2. Уровни иерархии памяти GPU (на примере NVIDIA H100)
2.1 Global Memory (HBM — High Bandwidth Memory)
- Расположение: вне чипа, на подложке.
- Объём: 80 ГБ (H100 SXM), до 144 ГБ (GH200).
- Пропускная способность: ~2 ТБ/с (H100), ~3.35 ТБ/с (H200).
- Латентность: ~200–400 тактов (несколько сотен наносекунд).
- Доступ: все потоки могут читать/писать, но медленно.
- Роль: хранит веса модели, входные данные, KV-cache, промежуточные результаты.
Термин: HBM — стек из DRAM-чипов, соединённых через interposer с GPU. Обеспечивает высокую пропускную способность за счёт широкой шины (1024 бит и более).
2.2 L2 Cache
- Расположение: на кристалле, общий для всех SM (Streaming Multiprocessors).
- Объём: 50–60 МБ (H100).
- Пропускная способность: ~4–5 ТБ/с (оценка).
- Латентность: ~50–100 тактов.
- Доступ: автоматический кеш для глобальной памяти.
- Роль: уменьшает количество обращений к HBM, кешируя часто используемые данные (например, веса при batch-инференсе).
2.3 Shared Memory
- Расположение: внутри каждого SM, распределяется между блоками потоков.
- Объём: до 228 КБ на SM (H100, конфигурируется).
- Пропускная способность: ~20 ТБ/с (на SM, суммарно по всем SM ~80 ТБ/с).
- Латентность: ~5–10 тактов.
- Доступ: ручное управление (программист явно копирует данные из глобальной памяти).
- Роль: используется для tiling — загрузки подматриц весов/активаций и повторного использования. Ключевая для Flash Attention.
Термин: SM — вычислительный блок GPU, содержит ядра CUDA, warp-планировщик, shared memory, регистры.
2.4 Registers
- Расположение: внутри каждого ядра CUDA (потока).
- Объём: 255 регистров на поток (H100, 32-битных), всего ~256 КБ на SM (распределяется между потоками).
- Пропускная способность: ~100 ТБ/с (на SM, оценка).
- Латентность: 0 тактов (доступ за один такт).
- Доступ: только текущий поток.
- Роль: хранение локальных переменных, промежуточных результатов вычислений.
3. Сравнительная таблица уровней памяти (H100 SXM)
| Уровень | Тип | Объём (на весь GPU) | Пропускная способность (приблизительно) | Латентность (такты) | Управление |
|---|---|---|---|---|---|
| Global (HBM) | DRAM | 80 ГБ | 2 ТБ/с | 200–400 | Автоматическое (load/store) |
| L2 Cache | SRAM | 50–60 МБ | 4–5 ТБ/с | 50–100 | Автоматическое (кеш) |
| Shared Memory | SRAM | 228 КБ на SM (всего ~30 МБ) | ~20 ТБ/с на SM | 5–10 | Ручное (__syncthreads, copy) |
| Registers | SRAM | 255 рег./поток (всего ~256 КБ на SM) | ~100 ТБ/с на SM | 0 | Компилятор / программист |
Ключевой вывод: пропускная способность растёт на 1–2 порядка при движении вниз по иерархии, но объём падает. LLM инференс упирается в Global Memory — именно её bandwidth лимитирует скорость.
4. Как иерархия влияет на LLM инференс
4.1 Bottleneck — memory bandwidth, не compute
LLM — это memory-bound задача. Рассмотрим матричное умножение Y = X * W (например, в линейном слое):
- X (активации) и W (веса) лежат в HBM.
- Для каждого элемента результата нужно прочитать целые строки/столбцы из HBM.
- Вычислительные ядра (Tensor Cores) могут выполнять умножение за несколько тактов, но данные подаются медленно.
Формула:
Время выполнения ≈ (объём данных) / (bandwidth HBM) + (вычислительное время)
Для LLM первое слагаемое доминирует.
Пример: слой с весами 4 ГБ (FP16), batch size = 1, bandwidth = 2 ТБ/с → минимальное время чтения = 4 ГБ / 2 ТБ/с = 2 мс. Реальное время больше из-за накладных расходов.
4.2 Влияние на attention
Attention (softmax(QK^T) V) требует чтения KV-cache из HBM для каждого токена. KV-cache растёт с длиной последовательности и batch size. Это делает long-context инференс особенно чувствительным к bandwidth.
4.3 Роль shared memory и L2
- Shared memory позволяет загрузить блок весов/активаций и переиспользовать его несколько раз (tiling). Это уменьшает количество обращений к HBM.
- L2 cache автоматически кеширует веса, если они используются повторно (например, при batch inference несколько запросов используют одни и те же веса). Но при batch size = 1 кеш малоэффективен.
5. Техники оптимизации, основанные на иерархии
5.1 Kernel Fusion (слияние ядер)
Объединение нескольких последовательных операций (например, layer norm + linear + activation) в одно ядро. Промежуточные результаты остаются в shared memory/registers, не записываются в HBM. Снижает число обращений к глобальной памяти.
5.2 Flash Attention
Алгоритм, который разбивает attention на блоки, помещающиеся в shared memory. Вычисляет softmax по частям, не сохраняя полную матрицу QK^T в HBM. Пропускная способность shared memory (~20 ТБ/с) используется вместо HBM (~2 ТБ/с). Результат: ускорение в 2–4 раза и линейная сложность по памяти.
5.3 Quantization (квантизация)
Снижение точности весов (FP16 → INT8/FP4) уменьшает объём данных, читаемых из HBM. Например, INT8 даёт 2x меньше данных → 2x ускорение (при фиксированной bandwidth). Это прямое воздействие на bottleneck.
5.4 Tiling (разбиение на блоки)
Ручное копирование подматриц из HBM в shared memory, вычисление, запись результата. Используется в оптимизированных библиотеках (cuBLAS, CUTLASS).
6. Пример: профилирование memory-bound операции в PyTorch
import torch
import time
# Параметры: матрица весов 4 ГБ (FP16)
N = 16384
M = 16384
dtype = torch.float16
W = torch.randn(N, M, dtype=dtype, device='cuda')
X = torch.randn(1, N, dtype=dtype, device='cuda')
# Прогрев
for _ in range(10):
Y = X @ W
torch.cuda.synchronize()
# Замер
start = time.time()
for _ in range(100):
Y = X @ W
torch.cuda.synchronize()
end = time.time()
avg_time = (end - start) / 100
print(f"Среднее время: {avg_time*1000:.2f} мс")
# Ожидаем ~2-3 мс (ограничение bandwidth HBM)
Результат: время близко к теоретическому минимуму (объём данных / bandwidth). Если бы compute был bottleneck, время было бы меньше.
7. Пет-проект для закрепления
Задача: Написать микро-бенчмарк, измеряющий пропускную способность разных уровней памяти GPU (Global, Shared, Registers) и показать, что LLM инференс ограничен Global Memory.
Инструменты: Python + PyTorch (или CUDA C), torch.cuda.Event для точного замера времени.
Шаги:
- Global Memory benchmark: загрузить большой тензор (например, 1 ГБ) и выполнить поэлементное сложение (bandwidth-bound). Измерить пропускную способность.
- Shared Memory benchmark: написать CUDA-ядро, которое копирует блок данных из глобальной в shared memory, выполняет операции и записывает обратно. Измерить скорость.
- Register benchmark: выполнить операции с локальными переменными (без обращений к памяти).
- Сравнить полученные значения с теоретическими (2 ТБ/с, 20 ТБ/с, 100 ТБ/с).
- Связать с LLM: оценить, сколько времени занимает чтение весов модели (например, Llama-7B ~14 ГБ FP16) и сравнить с реальным временем инференса.
Ожидаемый результат: Таблица с измеренными bandwidth, подтверждающая, что Global Memory — узкое место. Вывод: для ускорения инференса нужно уменьшать объём данных (квантизация) или эффективнее использовать shared memory (Flash Attention).
8. Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 302 | Как работает Flash Attention и почему он ускоряет инференс? |
| 303 | Какие методы квантизации (GPTQ, AWQ, bitsandbytes) вы знаете и как они влияют на latency? |
| 304 | Как вы оптимизируете KV-cache для long-context? |
| 305 | Что такое kernel fusion и как его применить к LLM? |
| 306 | Как вы профилируете производительность инференса LLM? |
| 307 | Как устроен Tensor Core и как он используется в LLM? |
9. Навигация
- Предыдущий: 300
- Следующий: 302
- Индекс: 00. Индекс разборов
Навигация
- Предыдущий: 300
- Следующий: 302
- Индекс: 00. Индекс разборов