Как вы диагностируете, что проблема в memory bandwidth, а не в compute?
Краткий тезис
Диагностика узкого места между memory bandwidth (пропускной способностью памяти) и compute (вычислительной мощностью) основана на анализе поведения модели при изменении batch size, измерении утилизации вычислительных ядер и памяти, а также использовании профилировщиков. Ключевые индикаторы: рост FLOPs не приводит к увеличению latency, насыщение bandwidth при увеличении batch size, высокий memory stall ratio (доля тактов ожидания памяти) в профилировщике. Для точной диагностики применяют roofline model и сравнивают арифметическую интенсивность операций с пиковой производительностью.
1. Термины: Compute-bound и Memory-bound
В контексте инференса LLM (и любых нейросетей) операции делятся на два типа:
- Compute-bound (ограниченные вычислениями) — время выполнения определяется скоростью арифметических операций (FP16, FP8 matmul). Пример: умножение матриц большого размера (например, в слоях attention при длинном контексте).
- Memory-bound (ограниченные памятью) — время выполнения определяется скоростью доступа к данным (чтение/запись в HBM или SRAM). Пример: загрузка весов модели, операции нормализации, softmax, поэлементные функции.
В LLM инференсе авторегрессивная генерация (по одному токену) почти всегда memory-bound, потому что batch size мал (часто 1), а веса модели огромны — их приходится каждый раз загружать из HBM в SRAM. Prefill (обработка промпта) может быть compute-bound при достаточно длинном контексте.
2. Основные индикаторы memory bandwidth bottleneck
2.1 Поведение при изменении batch size
- Compute-bound: при увеличении batch size latency почти не меняется (пока не упрётся в память), throughput растёт линейно.
- Memory-bound: latency растёт пропорционально batch size (каждый дополнительный пример требует загрузки тех же весов), throughput быстро насыщается.
2.2 Низкая утилизация вычислительных ядер (MFU)
MFU (Model FLOPs Utilization) — отношение реально выполненных FLOPs к пиковой производительности устройства. Если MFU < 30% при batch size = 1, это сильный признак memory-bound. Высокий MFU (50–80%) указывает на compute-bound.
2.3 Memory stall ratio
Профилировщики (NVIDIA Nsight, PyTorch Profiler) показывают долю тактов, когда ядро GPU простаивает в ожидании данных из памяти. Если этот показатель > 40–50% — bottleneck в memory bandwidth.
2.4 Эффект от оптимизаций памяти
- FlashAttention (уменьшает чтение/запись HBM) даёт значительный прирост скорости на memory-bound операциях, но почти не влияет на compute-bound.
- Kernel fusion (объединение нескольких операций в один kernel) полезен для compute-bound, но для memory-bound он бесполезен, так как узкое место — не количество запусков, а пропускная способность памяти.
3. Инструменты профилирования
| Инструмент | Назначение | Ключевые метрики |
|---|---|---|
| NVIDIA Nsight Systems | Системное профилирование | Timeline, utilization, memory throughput |
| NVIDIA Nsight Compute | Детальный анализ kernel | Memory stall, occupancy, arithmetic intensity |
| PyTorch Profiler | Встроенный профилировщик | Self CUDA time, memory bandwidth, FLOPs |
| Roofline model | Теоретическая модель | Арифметическая интенсивность vs пиковая производительность |
Roofline model — график, где по оси X — арифметическая интенсивность (FLOPs / byte), по оси Y — производительность (FLOPs/s). Точка пересечения наклонной линии (bandwidth) и горизонтальной (compute) — ridge point. Если арифметическая интенсивность операции ниже ridge point — она memory-bound, выше — compute-bound.
4. Экспериментальный подход
4.1 Измерение latency при разных batch size
Запустите инференс модели с batch size 1, 2, 4, 8, 16 (насколько позволяет память). Замерьте среднюю latency на один запрос и throughput (запросов/сек).
- Если latency растёт линейно, а throughput выходит на плато → memory-bound.
- Если latency почти не меняется, а throughput растёт линейно → compute-bound (до точки насыщения).
4.2 Расчёт арифметической интенсивности
Для каждой операции (например, matmul в attention) можно оценить:
- Количество FLOPs: 2 * M * N * K (для умножения матриц M×K и K×N).
- Количество переданных байт: (MK + KN + M*N) * sizeof(element).
Если арифметическая интенсивность < 100–200 FLOPs/byte (зависит от GPU), операция memory-bound.
4.3 Использование профилировщика
import torch
import torch.profiler
model = ... # загруженная модель (например, GPT-2)
input_ids = torch.randint(0, 1000, (1, 128)).cuda()
with torch.profiler.profile(
activities=[torch.profiler.ProfilerActivity.CUDA],
record_shapes=True,
with_stack=True
) as prof:
with torch.no_grad():
output = model(input_ids)
print(prof.key_averages().table(
sort_by="self_cuda_time_total", row_limit=20
))
В выводе обратите внимание на kernels с высоким self_cuda_time_total и низкой occupancy. Если среди лидеров — операции загрузки весов (например, aten::mm с маленькими размерами), это memory-bound.
5. Таблица сравнения compute-bound vs memory-bound
| Характеристика | Compute-bound | Memory-bound |
|---|---|---|
| Latency при увеличении batch size | Почти не меняется | Растёт линейно |
| Throughput при увеличении batch size | Растёт линейно | Насыщается |
| MFU | 50–80% | <30% |
| Memory stall ratio | Низкий (<20%) | Высокий (>40%) |
| Эффект FlashAttention | Небольшой | Значительный (2–5x) |
| Эффект kernel fusion | Полезен | Бесполезен |
| Примеры операций | MatMul (большие размеры), GEMM | LayerNorm, Softmax, загрузка весов, поэлементные операции |
6. Связь с RAG и Agentic RAG
В Agentic RAG система часто выполняет множество маленьких инференсов: генерация одного токена, вызов инструмента, повторный retrieval. Каждый такой вызов — memory-bound из-за малого batch size. Это приводит к низкой утилизации GPU и высокому latency.
Способы смягчения
- Batching запросов от разных агентов (если возможно).
- KV cache — уменьшает объём пересылаемых данных при повторной генерации.
- Speculative decoding — уменьшает число шагов генерации.
- Модели меньшего размера (например, 7B вместо 70B) — снижают объём весов, но могут ухудшить качество.
Диагностика memory bandwidth bottleneck помогает выбрать правильную стратегию оптимизации: вместо ускорения вычислений (которые и так быстры) нужно уменьшать объём передаваемых данных.
7. Пет-проект для закрепления
Задача Профилировать инференс небольшой LLM (GPT-2) и определить, является ли генерация токенов memory-bound или compute-bound.
Инструменты PyTorch, transformers, PyTorch Profiler, matplotlib (для графиков).
Шаги:
- Загрузите модель GPT-2 (
from transformers import GPT2LMHeadModel). - Напишите функцию генерации одного токена (без
model.generate, вручную цикл по токенам). - Для batch size 1, 2, 4, 8 замерьте среднее время на один токен (latency) и throughput (токенов/сек).
- Используйте PyTorch Profiler для каждого batch size, запишите
self_cuda_time_totalдля top-10 kernels. - Постройте графики latency vs batch size и throughput vs batch size.
- Рассчитайте MFU: оцените теоретические FLOPs для одного forward pass (например, 2 * число параметров * длину последовательности) и сравните с реальным временем.
- Сделайте вывод: при каком batch size модель переходит из memory-bound в compute-bound?
Ожидаемый результат При batch size = 1 latency высокая, MFU низкий (<20%), throughput насыщается уже при batch size = 4–8. Профилировщик покажет, что основное время тратится на загрузку весов (kernels типа aten::mm с маленькими размерами). Это подтверждает memory-bound bottleneck.
8. Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 312 | Как оптимизировать latency инференса LLM? |
| 314 | Как работает FlashAttention? |
| 315 | Как профилировать RAG-систему? |
| 310 | Что такое KV cache и как он влияет на производительность? |
| 311 | Как работает batching в LLM? |
| 309 | Какие архитектурные решения влияют на throughput LLM? |
9. Навигация
- Предыдущий: 312
- Следующий: 314
- Индекс: 00. Индекс разборов
Навигация
- Предыдущий: 312
- Следующий: 314
- Индекс: 00. Индекс разборов