Иерархия памяти AI-ускорителей: SRAM, HBM3e, GDDR7 и математика Memory Wall в инференсе LLM

13 минут чтения •


Оглавление

Оглавление

В проектировании высокопроизводительных вычислительных систем для машинного обучения ключевым фактором производительности все чаще выступает не пиковая вычислительная мощность тензорных ядер ($\text{TFLOPS}$), а пропускная способность подсистемы памяти ($\text{Memory Bandwidth}$, выражаемая в $\text{ГБ/с}$ или $\text{ТБ/с}$). Данный дисбаланс между темпами роста вычислительной мощности и скорости памяти известен как Memory Wall (стена памяти).

В задачах авторегрессионного инференса больших языковых моделей (LLM) фаза генерации токенов (Decode Phase) фундаментально ограничена скоростью извлечения весов из памяти, а не производительностью ALU. Ниже приводится системный разбор архитектурной иерархии памяти современных ускорителей (SRAM, HBM3e, GDDR7, DDR5), математическая формализация Roofline-модели, расчеты деградации утилизации GPU на примере модели Llama-3-70B и верифицируемый скрипт профилирования.

Первоисточники:

  • Видеоразбор концепции: SRAM vs DRAM vs HBM vs GDDR: What’s the Difference? (канал Learn with Whiteboard).
  • Аппаратные спецификации: Архитектурные whitepapers NVIDIA Hopper (H100/H200), Blackwell (B200), JEDEC HBM3e (JESD238) и JEDEC GDDR7 (JESD239).

1. Математика Memory Wall: Roofline-модель и операционная интенсивность

Предел производительности любого вычислительного ядра формализуется классической двухпараметрической Roofline-моделью (Williams et al., 2009). Предельная достижимая производительность алгоритма $P_{\text{attainable}}$ ($\text{TFLOPS}$) определяется как минимум между пиковой производительностью процессора $P_{\text{peak}}$ и произведением операционной (арифметической) интенсивности алгоритма $I$ на пропускную способность памяти $B_{\text{mem}}$:

$$P_{\text{attainable}} = \min \left( P_{\text{peak}}, ; I \cdot B_{\text{mem}} \right)$$

где:

$$I = \frac{\text{Количество операций (FLOPs)}}{\text{Объем переданных данных из памяти (Bytes)}}$$

Граница между двумя режимами называется точкой перегиба Roofline (Ridge Point) $I_{\text{ridge}}$:

$$I_{\text{ridge}} = \frac{P_{\text{peak}}}{B_{\text{mem}}}$$

Расчет Ridge Point для ускорителя NVIDIA H100 SXM5

Рассмотрим характеристики ускорителя NVIDIA H100 SXM5:

Рассчитаем критическую операционную интенсивность:

$$I_{\text{ridge}} = \frac{989.5 \times 10^{12}}{3.35 \times 10^{12}} \approx 295.37 ; \text{FLOPs/Byte}$$

Чтобы вычислительные ядра H100 работали со 100% утилизацией в режиме FP16, алгоритм обязан производить не менее 295.4 математических операций на каждый байт, загруженный из HBM3.


2. Prefill Phase vs Decode Phase: почему инференс LLM попадает в Memory-Bound

Жизненный цикл инференса трансформерной модели разделен на две фазы с кардинально различающейся операционной интенсивностью:

2.1. Фаза обработки контекста (Prefill / Prompt Processing)

На этапе Prefill модель обрабатывает входной промпт длиной $S_{\text{in}}$ токенов параллельно. Основная вычислительная нагрузка приходится на умножение матриц проекций весов $W \in \mathbb{R}^{d \times d}$ на матрицу активаций $X \in \mathbb{R}^{S_{\text{in}} \times d}$ (операция General Matrix Multiply, GEMM).

Для одного слоя трансформера с размером скрытого состояния $d$ и длиной последовательности $S_{\text{in}}$:

$$I_{\text{prefill}} \approx \frac{2 \cdot S_{\text{in}} \cdot d^2}{2 \cdot d^2} = S_{\text{in}} ; \text{FLOPs/Byte}$$

При длине промпта $S_{\text{in}} = 2048$ токенов значение $I_{\text{prefill}} = 2048 \gg 295.4$. Фаза Prefill является строго Compute-Bound, эффективно загружая тензорные ядра на 60–80% от теоретического максимума.

2.2. Фаза пошаговой генерации (Decode Phase)

На этапе Decode модель генерирует токены строго последовательно (авторегрессия). На каждом шаге $t$ генерируется ровно один новый токен ($S = 1$). Матричное умножение вырождается в умножение матрицы на вектор (General Matrix-Vector Multiply, GEMV).

Для извлечения одного токена при размере пакета (batch size) $B = 1$:

$$I_{\text{decode}} = \frac{2 \cdot P}{2 \cdot P} = 1.0 ; \text{FLOP/Byte}$$

При переходе на квантование FP8 ($b_{\text{param}} = 1$ байт):

$$I_{\text{decode, FP8}} = \frac{2 \cdot P}{1 \cdot P} = 2.0 ; \text{FLOPs/Byte}$$

Сравним $I_{\text{decode}} = 1.0$ с порогом $I_{\text{ridge}} = 295.4$. Операционная интенсивность деривации токена в 295 раз ниже порога насыщения вычислителя.

Фактическая производительность H100 в фазе Decode при $B = 1$:

$$P_{\text{decode}} = I_{\text{decode}} \cdot B_{\text{mem}} = 1.0 ; \text{FLOP/Byte} \times 3.35 \times 10^{12} ; \text{Byte/s} = 3.35 ; \text{TFLOPS}$$

$$\text{Утилизация ядер} = \frac{3.35 ; \text{TFLOPS}}{989.5 ; \text{TFLOPS}} \approx 0.338%$$

Тензорные ядра простаивают 99.66% времени, ожидая доставки весов по шине памяти. Скорость генерации токенов определяется исключительно физической пропускной способностью интерфейса памяти.


3. Сравнительная архитектура уровней памяти: SRAM, HBM3e, GDDR7, DDR5

Для преодоления физических ограничений кремния в современных вычислительных системах применяется многоуровневая иерархия памяти, в которой каждый уровень оптимизирован под компромисс между емкостью, латентностью, пропускной способностью и энергопотреблением.

flowchart TD
    subgraph Chip["Кристалл ускорителя (On-Die)"]
        ALU["Tensor Cores / CUDA Cores<br/>(Вычислительные ядра)"]
        Reg["Регистровый файл (~30-40 МБ)<br/>Латентность: ~0.3 нс | ПСП: >150 ТБ/с"]
        SRAM["L2 Кэш / SRAM (~50-256 МБ)<br/>Латентность: ~1-3 нс | ПСП: ~12-15 ТБ/с"]
        ALU <--> Reg
        Reg <--> SRAM
    end

    subgraph Package["Интерпозер кремниевый (In-Package 2.5D)"]
        HBM["Стеки HBM3e (4096-8192 линий шины)<br/>TSV + микробугорки CoWoS<br/>Латентность: ~50-70 нс | ПСП: 3.35-8.0 ТБ/с"]
    end

    subgraph PCB["Печатная плата (Off-Package Board)"]
        GDDR["Чипы GDDR7 (PAM3 кодирование)<br/>Шина: 256-384 bit<br/>Латентность: ~40-60 нс | ПСП: 1.0-1.7 ТБ/с"]
        DDR["Системная RAM DDR5 / PCIe 5.0 x16<br/>Шина: 64-128 bit<br/>Латентность: ~80-120 нс | ПСП: 50-128 ГБ/с"]
    end

    SRAM <==>|"Широкая шина CoWoS (1024 bit / стек)"| HBM
    SRAM <==>|"Интерфейс памяти GDDR (256-384 bit)"| GDDR
    SRAM <==>|"Шина хоста PCIe Gen5 (64 ГБ/с)"| DDR

3.1. SRAM (Static RAM): верстак на кристалле

3.2. HBM3e и HBM4: трехмерная интеграция памяти

$$\text{Ширина шины} = 6 \times 1024 = 6144 ; \text{бит}$$

При эффективной частоте передачи данных $6.4 ; \text{Гбит/с}$ на контакт пропускная способность достигает:

$$B_{\text{mem}} = \frac{6144 \times 6.4 \times 10^9}{8 \times 10^{12}} \approx 4.915 ; \text{TB/s}$$

3.3. GDDR7: экстремальные частоты на печатной плате

$$B_{\text{GDDR7}} = \frac{384 \times 32 \times 10^9}{8 \times 10^{12}} = 1.536 ; \text{TB/s}$$

3.4. Сводная спецификация архитектурных уровней памяти

ПараметрSRAM (L2 Cache)HBM3e (SXM / OAM)GDDR7 (Discrete)DDR5 (System DIMM)
РасположениеНа кристалле (On-die)В упаковке (2.5D CoWoS)На плате (PCB)В слоте (DIMM PCB)
Структура ячейки6T (транзисторы)1T1C (3D TSV стек)1T1C (планарный)1T1C (планарный)
Ширина шины данныхВнутренняя шина кристалла4096 – 8192 бит128 – 384 бит64 – 128 бит на канал
Скорость на контактЧастота процессора6.4 – 9.6 Гбит/с28.0 – 36.0 Гбит/с (PAM3)4.8 – 6.4 Гбит/с
Пиковая пропускная способность10 – 15 ТБ/с3.35 – 8.0 ТБ/с1.0 – 1.7 ТБ/с50 – 128 ГБ/с
Латентность (случайный доступ)0.8 – 2.0 нс55 – 75 нс45 – 65 нс85 – 120 нс
Типичная емкость на чип50 – 256 МБ80 – 144 ГБ16 – 32 ГБ32 – 128 ГБ (модуль)
Энергия на перенос бита< 0.5 пДж/бит3.0 – 4.2 пДж/бит7.0 – 9.5 пДж/бит14.0 – 22.0 пДж/бит
Стоимость производстваЭкстремальная (площадь кристалла)Высокая (CoWoS + стеки)Умеренная (SMD монтаж)Низкая (массовая)

4. Количественный расчет инференса: Llama-3-70B на H100 SXM5

Рассчитаем предельную теоретическую и практическую скорость генерации токенов для модели Llama-3-70B ($70.55 \times 10^9$ параметров) на одном ускорителе NVIDIA H100 SXM5 ($B_{\text{mem}} = 3.35 ; \text{TB/s}$).

4.1. Объем передаваемых данных на один шаг генерации

Вес модели при разрядности параметров:

Для генерации каждого токена при размере пакета $B = 1$ вся матрица весов считывается из HBM ровно один раз. Предельная частота генерации токенов (Tokens Per Second, TPS) ограничена пропускной способностью памяти:

$$\text{TPS}_{\max} = \frac{B_{\text{mem}}}{M_{\text{weights}}}$$

4.2. Расчет предела генерации при B = 1

  1. Формат FP16: Модель 141.1 ГБ физически не помещается в один H100 (80 ГБ). Требуется распределение на 2 GPU (Tensor Parallelism $TP = 2$). Суммарная пропускная способность двух карт: $B_{\text{total}} = 2 \times 3.35 = 6.70 ; \text{ТБ/с}$:

$$\text{TPS}_{\max, \text{FP16}} = \frac{6.70 \times 10^{12} ; \text{байт/с}}{141.1 \times 10^9 ; \text{байт}} \approx 47.48 ; \text{токенов/с}$$

  1. Формат FP8: Модель 70.55 ГБ помещается в память одного H100 (80 ГБ):

$$\text{TPS}_{\max, \text{FP8}} = \frac{3.35 \times 10^{12} ; \text{байт/с}}{70.55 \times 10^9 ; \text{байт}} \approx 47.48 ; \text{токенов/с}$$

  1. Формат INT4: Вес модели 35.28 ГБ:

$$\text{TPS}_{\max, \text{INT4}} = \frac{3.35 \times 10^{12} ; \text{байт/с}}{35.28 \times 10^9 ; \text{байт}} \approx 94.95 ; \text{токенов/с}$$

4.3. Влияние непрерывного батчинга (Continuous Batching)

При увеличении размера пакета параллельно обрабатываемых запросов до $B > 1$ загруженные из памяти веса модели повторно переиспользуются для всех $B$ токенов пакета.

Операционная интенсивность масштабируется пропорционально размеру пакета:

$$I_{\text{decode}}(B) \approx B \cdot \frac{2 \cdot P}{P \cdot b_{\text{param}}} = B \cdot \frac{2}{b_{\text{param}}} ; \text{FLOPs/Byte}$$

Для формата FP8 ($b_{\text{param}} = 1$) значение $I_{\text{decode}}(B) = 2 \cdot B$.

Найдем размер пакета $B_{\text{sat}}$, при котором ускоритель достигает точки насыщения Roofline ($I_{\text{ridge}} = 295.4$):

$$2 \cdot B_{\text{sat}} \ge 295.4 \implies B_{\text{sat}} \ge 148$$

При размере пакета $B \ge 148$ инференс Llama-3-70B на H100 полностью переходит из режима Memory-Bound в режим Compute-Bound, достигая предельной утилизации тензорных ядер. Однако увеличение $B$ требует пропорционального роста емкости под KV-Cache в HBM:

$$\text{Память KV-Cache} = 2 \times L \times n_{\text{kv}} \times d_{\text{head}} \times S_{\text{ctx}} \times B \times b_{\text{kv}}$$

Для Llama-3-70B ($L = 80$, $n_{\text{kv}} = 8$, $d_{\text{head}} = 128$, контекст $S_{\text{ctx}} = 4096$, $b_{\text{kv}} = 2$ байта):


5. Программный контракт: профилирование Roofline и симуляция задержки

Ниже представлен верифицируемый скрипт на Python 3 без внешних зависимостей, рассчитывающий параметры Roofline-модели, точки перегиба $I_{\text{ridge}}$ и время генерации токенов для различных конфигураций оборудования и разрядностей весов.

"""
Скрипт профилирования Roofline-модели и расчета Memory Wall для LLM.
Проверяет параметры насыщения памяти и прогнозирует Tokens/s (TPS).
"""

from typing import Dict, Any


def calculate_roofline_metrics(
    peak_tflops: float,
    mem_bandwidth_tbs: float,
    model_params_billions: float,
    bytes_per_param: float,
    batch_size: int,
) -> Dict[str, Any]:
    """Рассчитывает точку перегиба Roofline и достижимую производительность.

    :param peak_tflops: Пиковая мощность ядра в TFLOPS (FP16/FP8).
    :param mem_bandwidth_tbs: Пропускная способность памяти в ТБ/с.
    :param model_params_billions: Число параметров модели (млрд).
    :param bytes_per_param: Байт на параметр (2 для FP16, 1 для FP8).
    :param batch_size: Размер батча (параллельных последовательностей).
    :return: Словарь метрик утилизации и пропускной способности.
    """
    # 1. Точка перегиба Roofline (Ridge Point) в FLOPs/Byte
    # 1 TFLOPS / 1 TB/s = 1 FLOP/Byte
    ridge_point: float = peak_tflops / mem_bandwidth_tbs

    # 2. Операционная интенсивность алгоритма Decode
    # FLOPs = 2 * P * B, Bytes = P * bytes_per_param
    operational_intensity: float = (2.0 * batch_size) / bytes_per_param

    # 3. Достижимая производительность по Roofline формуле
    attainable_tflops: float = min(
        peak_tflops, operational_intensity * mem_bandwidth_tbs
    )
    core_utilization_pct: float = (attainable_tflops / peak_tflops) * 100.0

    # 4. Расчет генерации токенов (TPS)
    model_size_bytes: float = model_params_billions * 1e9 * bytes_per_param
    time_per_step_sec: float = model_size_bytes / (
        mem_bandwidth_tbs * 1e12
    )
    tokens_per_second: float = batch_size / time_per_step_sec

    is_memory_bound: bool = operational_intensity < ridge_point

    return {
        "ridge_point_flops_per_byte": round(ridge_point, 2),
        "operational_intensity": round(operational_intensity, 2),
        "is_memory_bound": is_memory_bound,
        "attainable_tflops": round(attainable_tflops, 2),
        "core_utilization_pct": round(core_utilization_pct, 3),
        "step_latency_ms": round(time_per_step_sec * 1000.0, 3),
        "tokens_per_second_total": round(tokens_per_second, 2),
    }


def run_verification_suite() -> None:
    # Конфигурация: NVIDIA H100 SXM5 (80GB HBM3)
    h100_peak_fp8_tflops = 1979.0  # FP8 Tensor Core peak
    h100_hbm3_bandwidth_tbs = 3.35

    # Модель: Llama-3-70B в формате FP8 (1 байт на вес)
    model_params = 70.55

    # Тест 1: Инференс одного пользователя (Batch Size = 1)
    res_b1 = calculate_roofline_metrics(
        peak_tflops=h100_peak_fp8_tflops,
        mem_bandwidth_tbs=h100_hbm3_bandwidth_tbs,
        model_params_billions=model_params,
        bytes_per_param=1.0,
        batch_size=1,
    )

    assert res_b1["is_memory_bound"] is True
    assert res_b1["operational_intensity"] == 2.0
    assert 47.0 <= res_b1["tokens_per_second_total"] <= 48.0
    assert res_b1["core_utilization_pct"] < 1.0

    # Тест 2: Промышленный батчинг (Batch Size = 128)
    res_b128 = calculate_roofline_metrics(
        peak_tflops=h100_peak_fp8_tflops,
        mem_bandwidth_tbs=h100_hbm3_bandwidth_tbs,
        model_params_billions=model_params,
        bytes_per_param=1.0,
        batch_size=128,
    )

    assert res_b128["operational_intensity"] == 256.0
    assert res_b128["tokens_per_second_total"] > 5000.0
    assert res_b128["core_utilization_pct"] > 40.0

    print("Проверочные сценарии Roofline успешно пройдены:")
    print(f"Batch=1: TPS={res_b1['tokens_per_second_total']}, Утилизация ядер={res_b1['core_utilization_pct']}%")
    print(f"Batch=128: TPS={res_b1['tokens_per_second_total']}, Утилизация ядер={res_b128['core_utilization_pct']}%")


if __name__ == "__main__":
    run_verification_suite()

6. Системные методы преодоления Memory Wall в современных AI-стеках

Для минимизации задержек и повышения утилизации ядер в современных архитектурах применяются 4 инженерных метода:

  1. Тайлинг внимания (FlashAttention-2/3): Традиционный механизм внимания $\text{Softmax}(Q K^T) V$ требует материализации промежуточной матрицы $S \in \mathbb{R}^{S \times S}$ в HBM, вызывая $O(S^2)$ операций чтения/записи. FlashAttention разбивает матрицы $Q, K, V$ на блоки (тайлы), полностью помещающиеся в сверхбыструю память SRAM (L2 кэш), вычисляя online-softmax за один проход и сокращая обращения к HBM с квадратичных до линейных $O(S)$.
  2. Пейджинг памяти KV-Cache (PagedAttention): В библиотеке vLLM память под ключи и значения организуется по аналогии с виртуальной памятью ОС блоками по 16 токенов. Это исключает фрагментацию памяти (падение потерь с 60–80% до менее 4%), высвобождая HBM для увеличения размера пакета $B$.
  3. Спекулятивное декодирование (Speculative Decoding): Компактная модель-черновик (Draft Model, например Llama-3-8B) быстро генерирует блок из $K$ кандидатов токенов. Основная модель 70B валидирует все $K$ токенов параллельно за один прямой проход GEMM в режиме Compute-Bound. При коэффициенте подтверждения $\alpha \approx 0.75$ эффективная скорость генерации возрастает в 2–3 раза без изменения весов.
  4. Квантование весов и активаций (W4A16, FP8, FP4): Сокращение разрядности весов в 2–4 раза пропорционально снижает объем передачи данных по шине HBM на каждый шаг генерации, удваивая предельную скорость вывода в режиме Memory-Bound.

Ключевые выводы

  1. Приоритет пропускной способности памяти: В авторегрессионном выводе LLM скорость генерации токенов определяется не терафлопсами ядер, а пропускной способностью памяти $B_{\text{mem}}$. При $B = 1$ вычислительные ядра GPU утилизированы менее чем на 1%.
  2. Roofline и Ridge Point: Точка насыщения $I_{\text{ridge}} = P_{\text{peak}} / B_{\text{mem}}$ для ускорителя NVIDIA H100 составляет 295.4 FLOPs/Byte. Одиночный Decode шаг имеет интенсивность $I = 1.0$ FLOP/Byte, оставаясь глубоко в зоне Memory-Bound.
  3. Преимущество 3D-стекирования HBM3e: За счет сверхширокой параллельной шины (4096–8192 бит) и монтажа на кремниевом интерпозере CoWoS память HBM3e обеспечивает до 4.9–8.0 ТБ/с пропускной способности при энергоэффективности 3–4 пДж/бит, превосходя GDDR7 и DDR5 в разы.
  4. Батчинг как мост к Compute-Bound: Увеличение размера непрерывного пакета $B$ поднимает операционную интенсивность пропорционально $B$, переводя инференс в эффективный режим Compute-Bound ценой пропорционального расхода емкости HBM под KV-Cache.

Первоисточники и материалы