Железо: почему для ИИ нужны видеокарты
Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 36:39 — отсюда взято содержание этой главы.
На вопрос «почему для ИИ нужны видеокарты» обычно отвечают «потому что они быстрые». Это не ответ, а тавтология: быстрые на чём и почему именно на этом. Разберём честно, и заодно станет понятно, откуда берутся три вещи, которые волнуют пользователя гораздо сильнее устройства железа: цена запроса, скорость ответа и возможность запустить модель у себя.
Отправная точка — то, чем закончилась глава про токены: ваш текст превращён в последовательность целых чисел, и она отправляется на инференс — прямой проход обученной модели. Обучение здесь ни при чём: веса уже посчитаны и заморожены, происходит только применение.
Что именно считается при генерации одного токена
Прямой проход трансформера — это не «алгоритм с ветвлениями», а фиксированная последовательность линейно-алгебраических операций. На каждом из десятков блоков модели происходит примерно одно и то же:
- Номера токенов заменяются на векторы из таблицы эмбеддингов — обычная выборка строк.
- Вектор умножается на три большие матрицы весов, получаются запросы, ключи и значения (Q, K, V).
- Считается внимание: скалярные произведения запросов на ключи, нормировка, взвешенная сумма значений.
- Результат прогоняется через двухслойную полносвязную сеть — снова два умножения на большие матрицы.
- Нормализации и нелинейности — поэлементные операции, дешёвые на фоне остального.
- В самом конце вектор умножается на матрицу размером «словарь × размерность» и получаются логиты — по одному числу на каждый токен словаря. Из них после softmax выходит распределение вероятностей следующего токена.
Внутреннее устройство внимания разбирается в соседнем треке — «Внимание и трансформеры». Здесь важно другое: подавляющая часть работы — это умножение матриц (в терминах библиотек — GEMM, general matrix multiply). У такой нагрузки три свойства:
- Массовая параллельность. Каждый элемент результата считается независимо от остальных.
- Однородность. Над всеми числами выполняется одна и та же операция «умножить и прибавить». Ветвлений нет — вычисления не зависят от значений данных.
- Предсказуемый доступ к памяти. Заранее известно, какие блоки чисел понадобятся и в каком порядке.
Это ровно тот класс задач, под который спроектирован графический ускоритель. Видеокарта появилась не потому, что кто-то решил ускорить ИИ, — она изначально решала ту же математику для геометрии и освещения сцены. Языковая модель просто попала в существующую нишу.
Процессор и ускоритель: два разных компромисса
В лекции видеокарта названа «по сути процессором» — и это верная мысль, если развернуть её точнее. Современный GPU — это самостоятельный вычислительный модуль со своими ядрами, своей памятью и своим планировщиком, подключённый к системе по шине PCIe. Он не «помогает» центральному процессору выполнять его инструкции, он исполняет собственные программы над собственными данными.
Оба чипа сделаны из транзисторов по схожим техпроцессам. Разница — в том, на что потрачен бюджет этих транзисторов.
| Ось сравнения | CPU | GPU |
|---|---|---|
| Число ядер | Единицы — десятки | Тысячи простых ALU, сгруппированных в блоки; плюс тензорные ядра под матричные операции |
| Что внутри ядра | Внеочередное исполнение, предсказание переходов, большие кеши | Минимум логики управления, максимум арифметики |
| Модель исполнения | Независимые потоки, каждый со своим ходом | SIMT: одна инструкция на группу потоков (варп из 32) над разными данными |
| Целевая нагрузка | Ветвления, указатели, разнородный код, задачи с зависимостями | Однородная арифметика над большими массивами |
| Пропускная способность памяти | Десятки — сотня ГБ/с (DDR5, двухканально ~90 ГБ/с) | Единицы ТБ/с (HBM: у NVIDIA V100 около 0,9 ТБ/с, у A100 около 2 ТБ/с, у H100 около 3,3 ТБ/с) |
| Латентность одной операции | Оптимизирована: важно закончить быстро | Высокая и намеренно скрывается переключением между тысячами потоков |
| Чем плох на чужой задаче | Мало параллельных умножителей и узкая память | Ветвление внутри варпа исполняется последовательно по веткам |
Ключевая строка — предпоследняя. CPU оптимизирован под задержку, GPU — под пропускную способность (модель исполнения SIMT описана в CUDA C++ Programming Guide). Процессор старается выполнить одну цепочку операций как можно быстрее; ускоритель не пытается ускорить одну операцию, он выполняет их десятками тысяч одновременно и прячет задержки за счёт того, что всегда есть готовый к работе поток.
Отсюда же ответ на возражение «а многопоточность?». Многопоточность на CPU делит между ядрами ту же самую узкую память и те же несколько десятков векторных блоков. Прирост от восьми ядер — восьмикратный в лучшем случае, а разрыв в пиковой арифметике между процессором и ускорителем — два порядка. Проблема не в числе потоков, а в том, сколько умножителей и сколько байт в секунду доступно.
Правильная инженерная формулировка того, зачем вообще нужен отдельный чип, звучит так: специализированная нагрузка выносится на специализированный ускоритель. Это тот же принцип, по которому кодированием видео занимается аппаратный кодек, криптографией — отдельные инструкции процессора, а сетевыми пакетами — контроллер сетевой карты. Центральный процессор при этом остаётся свободен для того, что умеет только он: логика приложения, ввод-вывод, планирование.
сборка батча"] end subgraph ACC["Ускоритель"] D[("Видеопамять:
веса модели")] F[("Видеопамять:
KV-кеш запросов")] E["Тензорные ядра:
умножение матриц"] end C -->|"по PCIe уходят только
номера токенов"| E D -->|"чтение всех весов
на каждом шаге"| E F -->|"чтение истории"| E E -->|"дозапись K и V
нового токена"| F E --> G["Логиты:
число на каждый токен словаря"] G --> H["Выбор следующего токена"] H -->|"следующий шаг генерации"| E H --> I["Детокенизация на CPU"]
Обратите внимание на объём трафика по PCIe: туда уходят несколько килобайт номеров токенов, обратно — распределение вероятностей. Всё тяжёлое остаётся внутри карты. Именно поэтому веса должны жить в видеопамяти постоянно: подгружать их по шине на каждом шаге было бы медленнее в десятки раз.
Главное узкое место — не арифметика, а память
Генерация идёт по одному токену за шаг. Чтобы посчитать один-единственный следующий токен, нужно прогнать вектор через все слои модели, то есть прочитать из видеопамяти все веса целиком. Все 14 гигабайт для модели на 7 млрд параметров в 16 битах. И так на каждый токен.
Посчитаем баланс. При чтении одного веса в формате bfloat16 (2 байта) над ним выполняется одно умножение и одно сложение — 2 операции с плавающей точкой. Получается примерно 1 операция на прочитанный байт. А у современного ускорителя отношение «пиковая производительность к пропускной способности памяти» — порядка 300 операций на байт (для H100: около 990 TFLOPS в bf16 при 3,35 ТБ/с). Разрыв в сотни раз.
Вывод жёсткий и проверяемый: при генерации для одного пользователя видеокарта загружена по арифметике на доли процента и на 100% занята чтением памяти. Это классическая memory-bound задача в терминах модели Roofline. Скорость выдачи токенов упирается в байты в секунду, а не в операции в секунду.
Отсюда простая оценка потолка: токенов в секунду ≈ пропускная способность памяти / размер весов в байтах. Для модели на 7 млрд параметров в 16 битах (14 ГБ) на карте с 2 ТБ/с это около 140 токенов в секунду при одном запросе. Реальность обычно даёт 30–60% от потолка: мешают неидеальная утилизация шины, накладные расходы ядер, чтение KV-кеша.
Две фазы запроса ведут себя по-разному
Обработка входного текста и генерация ответа — принципиально разные режимы, хотя математика в них одна.
- Префилл (prefill) — прогон всего промпта. Токены обрабатываются параллельно, матрицы получаются широкими, на каждый прочитанный вес приходится столько операций, сколько токенов в промпте. Это compute-bound фаза, ускоритель загружен арифметикой почти полностью.
- Декодирование (decode) — генерация ответа по токену. Матрица вырождается в один вектор, арифметическая интенсивность падает до единицы. Это memory-bound фаза.
Практическое следствие, которое видно в счетах провайдеров: выходной токен дороже и в разы медленнее входного. Тысяча токенов промпта обрабатывается за один параллельный проход, тысяча токенов ответа — за тысячу последовательных.
Отсюда батчинг
Если на каждом шаге всё равно приходится читать все веса, разумно за это чтение получить больше одного токена. Сервер инференса собирает запросы разных пользователей в батч и прогоняет их через модель одновременно: веса читаются один раз, а токенов на выходе столько, сколько запросов в батче.
С ростом батча растёт не чтение весов,
а объём KV-кеша и время на арифметику.
Именно батчинг делает облачный инференс экономически возможным. При батче в несколько десятков запросов арифметическая интенсивность поднимается с 1 до десятков операций на байт, и карта наконец начинает считать, а не ждать. Современные серверы вроде vLLM идут дальше и применяют непрерывный батчинг: закончившийся запрос сразу заменяется новым из очереди, без ожидания всей пачки.
Почему второй пользователь замедляет генерацию
В лекции это наблюдение сформулировано верно: если подключить к локально запущенной модели второго пользователя, скорость перестанет быть прежней. Причина не в «жадности» и не в загрузке процессора — причина в разделяемом ресурсе видеопамяти, и она распадается на три части.
- Если запросы не батчатся (типичный случай, когда модель поднята простым локальным раннером на один поток), шаги генерации чередуются. Каждый пользователь получает примерно половину шагов — и, соответственно, половину скорости. Суммарная производительность системы не меняется.
- Если запросы батчатся, суммарная пропускная способность растёт почти линейно (веса читаются один раз на всех), но скорость каждого отдельного пользователя всё равно падает: шаг батча длится дольше одиночного из-за возросшей арифметики и чтения нескольких KV-кешей.
- Ёмкость видеопамяти конечна. Каждый параллельный запрос держит собственный KV-кеш. Когда кеши перестают помещаться, планировщик начинает вытеснять запросы и пересчитывать их префилл заново — производительность падает уже нелинейно.
Общее правило: батчинг обменивает задержку отдельного пользователя на пропускную способность системы. Провайдер настраивает этот обмен, вы его чувствуете как разброс скорости ответа в часы пик.
Сколько видеопамяти нужно модели
Это единственная часть темы, где считать можно точно и без оговорок. Формула тривиальна: байт под веса = число параметров × байт на параметр, где байт на параметр задаётся разрядностью — 16 бит это 2 байта, 8 бит — 1 байт, 4 бита — 0,5 байта.
Возьмём масштаб DeepSeek-V3/R1 — 671 млрд параметров (на момент лекции, май 2026, это одна из самых крупных моделей с открытыми весами; технический отчёт).
| Разрядность весов | Байт на параметр | Только веса | Карт по 80 ГБ (минимум, без кеша) |
|---|---|---|---|
| 16 бит (bf16) | 2 | ~1,34 ТБ (1,22 ТиБ) | 17 |
| 8 бит | 1 | ~671 ГБ | 9 |
| 4 бита | 0,5 | ~336 ГБ | 5 |
Отсюда прямой ответ на вопрос «почему её нельзя запустить на домашней видеокарте»: веса просто не помещаются. Даже в 4 битах модель требует пяти ускорителей верхнего класса только под веса, а с учётом KV-кеша, активаций и фрагментации памяти — заметно больше. Такие модели живут на кластерах: восемь карт в узле, несколько узлов, связанных быстрым интерконнектом, и модель, разрезанная между ними по слоям или по тензорам.
Про MoE. DeepSeek-V3 — модель со смесью экспертов: из 671 млрд параметров на обработку одного токена задействуется около 37 млрд. Это меняет арифметику асимметрично: в память нужно поместить все веса, потому что заранее неизвестно, какие эксперты понадобятся, но читать на каждом шаге приходится только активную часть. Поэтому MoE-модель требует много памяти и при этом генерирует быстрее, чем плотная модель того же размера.
Про выгрузку на диск. Часть весов действительно можно держать в системной ОЗУ или на SSD и подгружать по мере необходимости — так делают локальные раннеры. Работает, но упирается в ту же формулу: PCIe даёт десятки ГБ/с, NVMe — единицы, тогда как HBM — тысячи. Скорость падает пропорционально самому узкому звену, то есть на один-два порядка.
Про деньги. Оценки стоимости такого парка железа в лекции звучат, но без источника, поэтому здесь не приводятся. Считать в любом случае стоит не цену карты, а стоимость GPU-часа у провайдера, умноженную на реальную загрузку, — методика в статье «Продакшн и стоимость».
Квантизация: чем платят за экономию памяти
Из таблицы выше видно: разрядность весов входит в требования к памяти линейным множителем. Квантизация — это перевод весов из 16-битного формата в 8-, 6- или 4-битный с сохранением приемлемого качества.
Механика в первом приближении простая: веса каждого блока делятся на группы, для группы запоминается масштабный коэффициент, а сами значения хранятся маленькими целыми числами. При умножении масштаб возвращается обратно. Тонкость в том, что распределение весов неравномерно, и наивное округление ломает модель на выбросах, — на решении этой проблемы построены методы LLM.int8(), GPTQ, AWQ и SmoothQuant.
Получаете вы два выигрыша сразу: кратное сокращение видеопамяти и пропорциональный рост скорости генерации — читать на каждом шаге нужно меньше байт. Платите точностью, и деградация неравномерна: сильнее всего страдают редкие домены, длинный контекст и точные вычисления, поэтому качество приходится проверять на своих задачах, а не по общим бенчмаркам.
Практический ориентир, устоявшийся в сообществе: 8 бит почти бесплатны по качеству, 4 бита — рабочий компромисс для локального запуска, ниже 4 бит стоит идти только осознанно. Практика локального запуска, форматы вроде GGUF и раннеры разбираются в статье «Локальные модели».
KV-кеш: память, которая растёт вместе с диалогом
Вернёмся к тому, что генерация идёт по одному токену и каждый раз модель смотрит на весь префикс. Наивная реализация пересчитывала бы внимание по всей истории заново на каждом шаге — квадратичная работа на пустом месте.
Этого не происходит, потому что ключи и значения (K и V) для уже обработанных токенов не меняются. Их считают один раз и складывают в KV-кеш. На каждом новом шаге считаются K и V только для одного нового токена, дописываются в кеш, и внимание берётся по всему кешу.
Плата за это — память, пропорциональная длине контекста: байт кеша = 2 (K и V) × слои × KV-головы × размерность головы × байт на число × длина контекста × число запросов.
Подставим типичную конфигурацию открытой модели на 70 млрд параметров: 80 слоёв, 8 KV-голов, размерность головы 128, формат fp16.
- На один токен:
2 × 80 × 8 × 128 × 2 = 327 680байт — около 320 КиБ. - На контекст 32 000 токенов: около 10 ГиБ на один запрос.
- На десять параллельных запросов с таким контекстом — 100 ГиБ, что уже больше памяти одной карты.
Если бы в этой модели не было группированного внимания (GQA) и все 64 головы имели собственные K и V, тот же кеш занимал бы около 82 ГиБ на один запрос. Именно поэтому MQA и GQA стали стандартом: они сокращают не арифметику, а размер кеша, то есть именно тот ресурс, который заканчивается первым. По той же причине в DeepSeek-V3 применяется multi-head latent attention, сжимающее KV-кеш до низкоразмерного представления.
Три следствия, которые полезно держать в голове:
- Длинный контекст стоит памяти, а не только денег. Окно в миллион токенов — это в первую очередь вопрос, куда положить кеш.
- Число одновременных пользователей ограничено ёмкостью кеша, а не числом ядер.
- Фрагментация кеша — реальная проблема, ради решения которой придумали PagedAttention: кеш хранится страницами, как виртуальная память в ОС.
Экономическая сторона того же механизма — почему каждое следующее сообщение диалога дороже предыдущего — разобрана в главе «Токены, контекстное окно и память диалога».
От чего на самом деле зависит скорость выдачи
Конкретное число токенов в секунду называть бессмысленно: оно зависит сразу от нескольких множителей и устаревает вместе с железом. Полезнее знать список факторов — по убыванию влияния.
- Размер весов в байтах — прямое деление в формуле потолка. Квантизация в 8 бит примерно удваивает скорость декодирования.
- Пропускная способность памяти карты — второй множитель той же формулы. Разница между поколениями ускорителей здесь больше, чем разница в пиковых FLOPS.
- Размер батча — определяет, делите ли вы стоимость чтения весов с кем-то ещё.
- Длина контекста — чем длиннее кеш, тем больше читается на каждом шаге поверх весов.
- Разрезание модели между картами — межкарточные обмены добавляют задержку на каждом слое.
- Архитектура — MoE читает только активных экспертов; спекулятивное декодирование позволяет проверять несколько предсказанных токенов за один проход.
Инженерные детали развёртывания — квантизация, батчинг, спекулятивное декодирование, выбор сервера — разбираются в статье «Инференс и развёртывание».
Маршрутизация запросов: почему на вас отвечает не всегда большая модель
Из всего сказанного следует важный продуктовый вывод: каждый токен большой модели стоит реальных денег и реального времени, а огромная доля запросов этого не требует. Приветствие, переформулировка, простая классификация, извлечение даты из текста прекрасно решаются моделью на пару миллиардов параметров, которая помещается в одну карту и отвечает почти мгновенно.
Отсюда стандартный инженерный приём — роутинг моделей (он же каскад): дешёвая быстрая модель разбирает запрос и либо отвечает сама, либо передаёт его дальше по цепочке.
маленькая быстрая модель
или классификатор"} R -->|"тривиальный: приветствие,
формат, короткий факт"| S["Малая модель
отвечает сама"] R -->|"нужны ваши данные"| RAG["Поиск по документам
плюс средняя модель"] R -->|"сложное рассуждение,
код, длинный контекст"| BIG["Большая модель"] S --> CHK{"Ответ прошёл
проверку качества?"} CHK -->|"нет"| BIG CHK -->|"да"| OUT["Ответ"] RAG --> OUT BIG --> OUT OUT --> LOG[("Трейс: какая модель,
сколько токенов,
сколько времени")]
Экономический мотив здесь очевиден и его незачем прятать: провайдер платит за GPU-часы и заинтересован обслужить запрос дешевле. Но сводить каскад к «экономии на пользователе» неверно — приём даёт три эффекта сразу:
- Латентность. Маленькая модель отвечает за доли секунды там, где большая думала бы секунды.
- Стоимость. Существенная доля запросов в любом продукте тривиальна; перевод их на младшую модель сокращает счёт в разы.
- Пропускная способность. Освобождённые мощности большой модели достаются тем запросам, которым они действительно нужны.
Тот же приём вы применяете сами, когда строите систему на API: раскладка запросов по моделям — базовый рычаг продакшн-экономики, наравне с кэшированием префикса («Продакшн и стоимость»). И важная оговорка про наблюдаемый эффект: если ответ выглядит поверхностным, это не обязательно роутинг на слабую модель — тот же результат дают короткий системный промпт, обрезанный контекст или неудачная формулировка запроса. Роутинг тут объяснение, а не диагноз.
Практика: калькулятор требований к видеопамяти
Соберём всё в один скрипт. Он отвечает на вопрос, который возникает каждый раз перед локальным запуском: влезет ли.
from dataclasses import dataclass
# Байт на один параметр для распространённых разрядностей весов.
BYTES_PER_PARAM = {16: 2.0, 8: 1.0, 6: 0.75, 4: 0.5}
GB = 1024 ** 3 # считаем в гибибайтах: именно их показывает nvidia-smi
@dataclass
class ModelSpec:
"""Всё это есть в config.json карточки модели на Hugging Face."""
name: str
params: float # общее число параметров
layers: int # число блоков трансформера
kv_heads: int # число KV-голов (при GQA меньше числа голов внимания)
head_dim: int # размерность одной головы
active_params: float | None = None # для MoE: сколько параметров работает на токен
def weights_gb(spec: ModelSpec, bits: int = 16) -> float:
"""Веса целиком должны лежать в видеопамяти — это неснижаемый минимум."""
return spec.params * BYTES_PER_PARAM[bits] / GB
def kv_cache_gb(spec: ModelSpec, context: int, batch: int = 1, bits: int = 16) -> float:
"""KV-кеш: по ключу и значению на каждый токен каждого слоя каждой KV-головы."""
per_token = 2 * spec.layers * spec.kv_heads * spec.head_dim * BYTES_PER_PARAM[bits]
return per_token * context * batch / GB
def report(spec: ModelSpec, context: int, batch: int = 1,
weight_bits: int = 16, kv_bits: int = 16,
card_gb: float = 80.0, bandwidth_tb_s: float = 2.0) -> None:
w = weights_gb(spec, weight_bits)
kv = kv_cache_gb(spec, context, batch, kv_bits)
# Активации и рабочие буферы сервера: эмпирическая надбавка порядка 10-20%.
overhead = 0.15 * (w + kv)
total = w + kv + overhead
# Потолок скорости: на каждый токен читаются все активные веса.
# Для MoE читается только активная часть, для плотной модели — всё.
read_params = spec.active_params or spec.params
read_gb = read_params * BYTES_PER_PARAM[weight_bits] / GB
ceiling = bandwidth_tb_s * 1000 / read_gb # токенов в секунду, верхняя граница
print(f"{spec.name}: контекст {context}, батч {batch}, веса {weight_bits} бит")
print(f" веса {w:.1f} ГиБ, KV-кеш {kv:.1f} ГиБ, буферы {overhead:.1f} ГиБ")
print(f" итого {total:.1f} ГиБ -> карт по {card_gb:.0f} ГиБ: {-(-total // card_gb):.0f}")
print(f" потолок скорости {ceiling:.0f} ток/с (реально 30-60% от этого)")
dense_70b = ModelSpec("Плотная 70B", 70e9, layers=80, kv_heads=8, head_dim=128)
# У DeepSeek-V3 кеш сжат механизмом MLA, поэтому общая формула даёт верхнюю оценку.
moe_671b = ModelSpec("MoE 671B", 671e9, layers=61, kv_heads=128, head_dim=128,
active_params=37e9)
report(dense_70b, context=8_192, weight_bits=16)
report(dense_70b, context=32_768, batch=8, weight_bits=4, kv_bits=8)
report(moe_671b, context=8_192, weight_bits=8)
Что показывает запуск и на что смотреть:
- У плотной модели на 70 млрд параметров в 16 битах веса дают около 130 ГиБ — две карты по 80 ГиБ, и это ещё до кеша.
- Квантизация в 4 бита уводит те же веса под 33 ГиБ — модель начинает помещаться в одну карту, но при контексте 32k и батче 8 KV-кеш добавляет десятки гигабайт и снова выталкивает за её пределы.
- У MoE-модели веса огромны — десяток карт даже в 8 битах, — но потолок скорости считается по активным 37 млрд: на каждом шаге она читает меньше байт, чем плотная модель на 70 млрд, и потому генерирует быстрее неё.
Сложность для полноты картины. Прямой проход на один токен — примерно 2 × P операций умножения-сложения, где P — число активных параметров, плюс O(n · d) на чтение кеша при длине контекста n. Префилл на n токенов — O(n · P) арифметики плюс O(n² · d) во внимании. По памяти: O(P) под веса плюс O(n · L · H_kv · d_head) под кеш на каждую активную последовательность.
Почему в этой картине нет места мышлению
Соберём главу в одну мысль. Всё, что происходит между вашим вопросом и ответом модели, — это чтение фиксированного массива чисел из видеопамяти и умножение матриц над ним. Ни одна операция в этом конвейере не зависит от смысла: одинаковые по форме данные дают одинаковый объём работы независимо от того, спрашиваете вы рецепт или доказательство теоремы. Модель не «думает дольше над сложным вопросом» — она тратит ровно столько же арифметики на токен, а разница во времени ответа объясняется только числом сгенерированных токенов. Это не приговор полезности моделей, но это точная граница того, что мы наблюдаем.
Источники
- Williams, Waterman, Patterson, Roofline: An Insightful Visual Performance Model — модель, объясняющая memory-bound и compute-bound режимы
- Kwon et al., Efficient Memory Management for LLM Serving with PagedAttention — как устроен vLLM и почему KV-кеш хранится страницами
- Shazeer, Fast Transformer Decoding (MQA) и Ainslie et al., GQA — сокращение KV-кеша
- Dettmers et al., LLM.int8(), Frantar et al., GPTQ, Lin et al., AWQ — квантизация без обвала качества
- DeepSeek-AI, DeepSeek-V3 Technical Report — архитектура MoE и multi-head latent attention
- llama.cpp — практика локального запуска и формат GGUF
Мини-итог
- Инференс языковой модели — это последовательность умножений матриц: массово параллельная однородная работа без ветвлений, то есть ровно та задача, под которую спроектирован GPU.
- CPU оптимизирован под задержку одной операции, GPU — под пропускную способность: тысячи простых ядер, модель SIMT и память с полосой в единицы ТБ/с против десятков ГБ/с у процессора.
- Генерация упирается не в арифметику, а в память: на каждый токен читаются все активные веса, поэтому потолок скорости равен пропускной способности памяти, делённой на размер весов.
- Батчинг размазывает стоимость чтения весов по нескольким запросам — он поднимает пропускную способность системы ценой скорости каждого отдельного пользователя; второй пользователь замедляет генерацию именно поэтому, а не из-за нехватки ядер.
- Память под веса считается точно: параметры × байт на параметр. Для 671 млрд параметров (масштаб DeepSeek-V3/R1, на момент лекции — май 2026) это ~1,34 ТБ в 16 битах, ~671 ГБ в 8 и ~336 ГБ в 4 — отсюда кластеры вместо одной карты.
- KV-кеш убирает лишний пересчёт истории, но занимает видеопамять пропорционально длине контекста и числу параллельных запросов — часто именно он, а не веса, ограничивает число пользователей.
- Роутинг моделей — штатный инженерный приём: дешёвая модель разбирает запрос, тяжёлая подключается по необходимости; это выигрыш и по деньгам, и по латентности одновременно.
Что дальше
Мы прошли весь путь от текста до чисел и обратно: токенизация, умножение матриц в видеопамяти, распределение вероятностей, выбор следующего токена. В этом конвейере нет ни одного места, где могло бы поместиться понимание, — и одновременно результат его работы выглядит осмысленным настолько, что спорить об этом продолжают всерьёз. Следующая глава разбирает самый частый вопрос лекции без уклончивости: что именно мы называем мышлением, чего в модели точно нет и почему иллюзия всё равно возникает.