Большие языковые модели: от T9 до трансформера
Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 30:13 — отсюда взято содержание этой главы.
К этому месту трека собраны все детали. Глава «Цепи Маркова» дала идею предсказания следующего слова по предыдущим. Главы «Машинное обучение» и «Нейросети: как обучают модель» объяснили, откуда берётся функция, которую никто не программировал руками. Глава «Представления» показала, что внутри модели нет ни букв, ни котиков — только векторы чисел. Теперь эти детали складываются в одну конструкцию: задача главы — показать, что большая языковая модель делает ровно одну вещь, и объяснить, что именно добавила к этой одной вещи архитектура трансформера.
Определение, из которого следует всё остальное
Языковая модель — это функция, которая по последовательности токенов возвращает распределение вероятностей следующего токена. В записи: P(токен_t | токен_1, токен_2, ..., токен_t-1). На входе — весь текст, который уже есть. На выходе — число для каждого элемента словаря: насколько правдоподобно, что дальше идёт именно он.
Всё, что делает LLM, — это многократное применение этой функции к собственному выходу: выбрали токен, дописали его в конец последовательности, применили функцию заново. Такой режим называется авторегрессионным. Из определения сразу следуют три вещи, которые обычно объясняют как «странности» моделей.
Ответа не существует целиком до того, как он сгенерирован. Нет объекта «готовый ответ», который модель затем печатает. Есть последовательность решений, каждое из которых принимается на один токен вперёд. Оговорка: внутренние представления модели, судя по работам по интерпретируемости, могут содержать признаки будущих токенов — это активная и не закрытая область исследований, см. интерпретируемость и безопасность. Но на уровне интерфейса контракт именно такой: один вызов — одно распределение на один следующий токен.
«Правдоподобно» и «верно» — для модели одно и то же. Функция оптимизирована на правдоподобие продолжения, а не на истинность. Отсюда уверенная выдумка: несуществующий метод API — статистически отличное продолжение текста про этот API.
Стоимость и латентность заданы прямо в определении. Ответ длиной 500 токенов — это 500 применений функции, одно за другим. Никакого способа получить их разом не существует, и это следствие не реализации, а постановки задачи.
Связь с цепями Маркова: что именно поменялось
В лекции связь сформулирована как «LLM — это цепи Маркова плюс нейросети плюс надстройки». Это неточно, и неточность мешает понять главное. Корректная формулировка другая: цепь Маркова и LLM решают одну и ту же задачу — задают распределение следующего элемента, — но радикально расходятся в том, что считается «состоянием» и чем задана вероятность.
| Цепь Маркова (n-грамма) | Большая языковая модель | |
|---|---|---|
| Состояние | Последние n-1 токенов, длина фиксирована жёстко |
Весь предшествующий текст в пределах контекстного окна |
| Чем задана вероятность | Таблицей частот, посчитанной по корпусу | Обученной функцией с миллиардами параметров |
| Невиданная комбинация | Вероятность ноль, нужны приёмы сглаживания | Оценивается по сходству с тем, что модель видела |
| Цена увеличения памяти | Размер таблицы растёт как размер_словаря в степени n |
Растёт длина входа, размер модели не меняется |
| Что можно выучить | Локальные сочетаемости слов | Согласование на сотни токенов, структуру, формат, роль |
Строка «цена увеличения памяти» объясняет, почему марковский подход упёрся в потолок. Чтобы модель помнила контекст всего на пять слов назад при словаре в 50 тысяч единиц, таблица должна содержать 50000^5 строк — это порядка 3 * 10^23, величина уровня числа Авогадро, причём подавляющее большинство строк осталось бы пустыми. Трансформер решает ровно эту проблему: он не увеличивает таблицу, а выбрасывает её и заменяет обученной функцией фиксированного размера, на вход которой подаётся вся последовательность целиком. Память перестаёт быть таблицей и становится вычислением.
Почему вероятности, а не правила
В лекции причина названа так: мы не понимаем паттерны образования слов, поэтому подход вероятностный. Это перебор. Лингвистика описывает язык весьма подробно: есть морфология, есть синтаксис, есть формальные грамматики, и системы на правилах реально работали — правиловый машинный перевод и экспертные системы 1970–1990-х годов существовали и решали задачи. Дело не в незнании. Настоящих причин две, и обе инженерные:
- Правила не покрывают живой язык целиком. Исключения, идиомы, неологизмы, опечатки, смешение языков, зависимость смысла от контекста и от знаний о мире. Каждое новое правило чинит один класс случаев и ломает соседний; система из десятков тысяч правил становится неподдерживаемой раньше, чем достигает приемлемого качества.
- Грамматика не выбирает продолжение. После «я налил себе чашку» грамматически корректны тысячи продолжений. Правила отсеивают неграмматичное, но не ранжируют допустимое. А задача — именно ранжировать.
Статистическая модель обобщает лучше набора правил ровно потому, что не пытается быть исчерпывающей: она даёт оценку правдоподобия любому продолжению, включая то, которого не было в обучающих данных. Это не капитуляция перед сложностью языка, а другой инженерный выбор, который на практике выиграл.
«T9 с накопителем»: удачное сжатие и его границы
В лекции звучит формула, которую стоит запомнить именно в авторской редакции:
LLM — это T9 с накопителем.
Как сжатие механизма это очень удачно. T9 в кнопочном телефоне решал ту же задачу: смотрел на начатое слово и предлагал продолжение, ранжируя варианты по частоте. Разница, если оставаться внутри метафоры, — в размере «накопителя»: вместо словаря на десятки тысяч слов и вашей личной истории набора — функция, обученная на корпусе в триллионы токенов.
Метафора полезна против магического мышления и вредна, если принять её буквально. Вот три места, где она ломается — это авторская формула, и честно показать её границы важнее, чем красиво её процитировать.
| Что умеет LLM | Почему T9 этого не умеет в принципе |
|---|---|
| Следовать инструкции: «перепиши короче», «ответь только JSON» | У T9 нет входа для инструкций. Он видит начатое слово, а не задачу. Инструктивность у LLM появляется не от объёма данных, а от отдельной фазы дообучения на парах «инструкция — ответ» и обратной связи людей (InstructGPT, 2022) |
| Удерживать роль и стиль на длинном тексте | Состояние T9 — текущее слово. Согласование на сотни токенов требует, чтобы в состояние входил весь предшествующий текст |
| Решать задачи, которых не было в обучении: неизвестный формат, чужой домен, смешение двух тем | T9 переносит частоты. Обобщение на невиданную задачу требует представлений, в которых похожие по смыслу вещи лежат рядом — см. главу про представления |
Резюме: механизм действительно тот же — предсказание продолжения. Различие в объёме и в устройстве состояния оказалось не количественным, а качественным. Вопрос, считать ли это «пониманием», разбирается отдельно в главе «Могут ли нейросети думать».
Конвейер одного запроса
Вы написали «привет» и нажали Enter. Дальше происходит ровно следующее.
Разберём шаги словами.
1. Токенизация. Текст режется на куски из фиксированного словаря — токены. Это не буквы и не слова: частые слова становятся одним токеном, редкие распадаются на несколько кусков. Здесь нужна поправка к лекции: токенизатор работает не по заранее прописанным лингвистическим правилам и не отделяет приставки. Современные токенизаторы — BPE и SentencePiece — выучивают словарь по частотам подстрок на корпусе: алгоритм жадно склеивает самые частые пары символов, пока словарь не наберёт нужный размер. Морфологии он не знает вовсе, и границы токенов регулярно не совпадают с границами морфем. Подробно — в главе «Токены, контекстное окно и память диалога».
2. Эмбеддинги. Каждый идентификатор токена заменяется вектором из обучаемой таблицы — обычно несколько тысяч чисел. С этого момента текста нет, есть матрица чисел. Почему так и что это даёт — глава про представления и эмбеддинги в треке нейросетей.
3. Позиционная информация. Сама по себе операция внимания не различает порядок: для неё вход — множество, а не последовательность. Порядок добавляется отдельно, позиционным кодированием.
4. Слои трансформера. Десятки одинаковых по устройству блоков подряд. Каждый блок делает две вещи: смешивает информацию между позициями (внимание) и обрабатывает каждую позицию отдельно (полносвязная часть). После каждого блока вектор каждой позиции становится «более осведомлённым» о контексте.
5. Логиты, softmax и выбор. Из вектора последней позиции получается вектор длиной со словарь — по одному числу на каждый возможный токен. Это ещё не вероятности, а произвольные вещественные числа: чем больше, тем правдоподобнее продолжение. Softmax нормализует их в распределение, из которого выбирается один токен; как именно выбирается — отдельный разговор ниже.
6. Петля. Выбранный токен дописывается к последовательности, и всё повторяется с шага 4. Останов — по служебному стоп-токену либо по лимиту max_tokens.
Механизм внимания без математики
Именно этот механизм снимает ограничение марковской модели, поэтому его стоит понять на уровне идеи.
На каждом слое каждый токен смотрит на все предыдущие токены и сам решает, какие из них важны для его собственного представления. Не «последние три», не «в пределах окна из пяти» — все, до самого начала контекста. Токен формирует запрос вида «мне сейчас нужно вот такое», сравнивает его с тем, что предлагают предыдущие позиции, получает веса релевантности и подмешивает в себя их содержимое пропорционально этим весам.
Связь «Он → Штирлиц» не задана правилом, она выучена.
Три следствия, которые важны дальше по треку:
- Расстояние перестаёт иметь значение. Связь между токеном 5 и токеном 1 стоит ровно столько же, сколько связь между соседями. В рекуррентной сети информация от далёкого токена должна была пройти через все промежуточные шаги и по дороге размывалась; здесь путь всегда длиной один.
- Веса релевантности вычисляются заново на каждом слое и для каждого токена. Это не одна таблица связей, а десятки слоёв по-разному сфокусированного взгляда на одну и ту же последовательность.
- Цена — квадратичная. Каждый токен смотрит на каждый: при длине
nэто порядкаn^2сравнений на слой. Отсюда растёт стоимость длинного контекста, о которой глава про токены, и требования к железу, о которых глава про видеокарты.
Матрицы запросов, ключей и значений, масштабирование, многоголовое внимание, позиционные кодировки — всё это разобрано в соседнем треке: «Внимание и трансформеры». Здесь достаточно идеи «каждый смотрит на всех предыдущих и сам решает, кто важен».
Кто на самом деле предложил трансформер
В лекции сказано, что идею трансформеров предложила OpenAI. Это неверно. Архитектуру Transformer предложили исследователи Google в статье Vaswani et al., «Attention Is All You Need», 2017. Сам механизм внимания появился ещё раньше — в работе Bahdanau et al., 2014, как надстройка над рекуррентным переводчиком. Вклад статьи 2017 года в том, что рекуррентность убрали совсем, оставив только внимание: отсюда и название.
OpenAI сделала другое, и тоже важное: взяла декодерную половину этой архитектуры и построила на ней линейку GPT — Radford et al., «Improving Language Understanding by Generative Pre-Training», 2018. То есть архитектура — Google, масштабирование декодер-онли модели до продукта — OpenAI.
Заодно вторая поправка к лекции: Model Context Protocol создала не OpenAI. Это открытый протокол, представленный Anthropic в ноябре 2024 года (modelcontextprotocol.io); к архитектуре модели он отношения не имеет и решает задачу подключения инструментов и данных. Тема разбирается в главе «RAG, MCP и агенты».
Энкодер, декодер и почему победил декодер-онли
В исходном трансформере 2017 года было две стопки слоёв, потому что задача была переводческая: прочитать предложение на одном языке, породить на другом.
| Тип | Как смотрит на текст | Для чего годится | Примеры |
|---|---|---|---|
| Энкодер | Каждый токен видит и левый, и правый контекст | Классификация, извлечение сущностей, эмбеддинги для поиска | BERT и его потомки |
| Энкодер-декодер | Энкодер читает вход целиком, декодер порождает выход и подсматривает в энкодер | Перевод, суммаризация с явной парой «вход — выход» | T5, исходный Transformer |
| Декодер-онли | Каждый токен видит только левый контекст (причинная маска) | Порождение текста в общем виде | Линейка GPT и большинство современных чат-моделей |
Почему современные генеративные LLM — декодер-онли:
- Одна задача вместо двух. Разделение на «вход» и «выход» оказалось лишним. Инструкция, диалог, документ и ответ — всё это один текст, а задача одна: продолжить его. Системный промпт, история и ваш вопрос просто лежат в одной последовательности друг за другом.
- Обучение на любом тексте без разметки. Целевая функция «предскажи следующий токен» не требует пар «вопрос — ответ»: обучающим примером служит любой текст из интернета. Это то, что позволило масштабировать данные.
- Простота масштабирования. Одна однородная стопка слоёв растёт по числу слоёв и ширине без архитектурных решений на каждом шаге.
Причинная маска — ключ к тому, почему вход и выход обрабатываются по-разному. Правило простое: токен на позиции i может смотреть только на позиции до i включительно. Отсюда всё остальное.
Ваш промпт уже известен целиком. Значит, все его позиции можно посчитать одновременно, за один проход — ничто ни на что не ждёт. Эта фаза называется prefill, она хорошо параллелится и упирается в вычислительную мощность.
Токенов ответа ещё нет. Токен n+1 невозможно посчитать, пока не выбран токен n, потому что он должен войти во вход. Значит, каждый новый токен — это отдельный проход через всю модель. Эта фаза называется decode, она принципиально последовательна и упирается в скорость памяти.
Это различие — прямой ответ на два вопроса, которые задают чаще всего. Почему выходные токены дороже входных в несколько раз? Потому что вход считается пачкой, а выход — по одному. Почему ответ «печатается», а не появляется целиком? По той же причине.
Как выбирается токен
Модель выдаёт распределение. Превратить его в один конкретный токен — отдельное решение, и оно принимается кодом вокруг модели, а не самой моделью.
| Стратегия | Что делает | Когда уместно |
|---|---|---|
| Жадный выбор (greedy) | Всегда берёт токен с максимальной вероятностью | Извлечение данных, классификация, код — там, где нужна воспроизводимость |
| Сэмплирование | Тянет токен случайно, пропорционально вероятностям | Тексты, где нужно разнообразие |
| Temperature | Делит логиты до нормализации: T < 1 обостряет распределение, T > 1 сглаживает, T → 0 вырождается в жадный выбор |
Ручка «предсказуемость против разнообразия» |
| Top-k | Оставляет k самых вероятных токенов, остальное обнуляет |
Грубое отсечение хвоста |
| Top-p (nucleus) | Оставляет минимальный набор токенов с суммарной вероятностью не меньше p |
Адаптивное отсечение: набор шире там, где модель не уверена (Holtzman et al., 2019) |
Зачем вообще отсекать хвост. В словаре порядка сотни тысяч токенов, и у каждого вероятность строго больше нуля. Суммарно хвост из почти невозможных вариантов набирает заметную массу, и при честном сэмплировании модель рано или поздно вытащит оттуда что-то бессвязное — а дальше будет продолжать уже испорченный текст. Top-p и top-k существуют ровно для того, чтобы этого не происходило. И две практические оговорки:
- Жадный выбор не гарантирует полной воспроизводимости. На проде запросы объединяются в пачки, а операции с плавающей точкой не ассоциативны: тот же промпт при другом составе пачки может дать другой порядок сложений и, изредка, другой токен на границе.
- Параметров сэмплирования может не быть вовсе. На момент лекции (май 2026) в новых API некоторых провайдеров
temperatureиtop_pубраны из интерфейса: поведение задаётся промптом и отдельным параметром «усилия», а не температурой. Что это меняет для инженера — в статье «Основы LLM для инженера».
Тот же цикл кодом
Ниже — форма авторегрессионного цикла. Модель и токенизатор оставлены заглушками намеренно: цель кода — показать структуру, а не запустить генерацию.
"""Авторегрессионная генерация: то же самое делает любой чат с LLM."""
import math
import random
EOS = 2 # идентификатор служебного токена «конец ответа»
def tokenize(text: str) -> list[int]: ... # текст -> идентификаторы токенов, BPE
def detokenize(ids: list[int]) -> str: ... # идентификаторы -> текст
def model_forward(new_ids: list[int], cache: dict | None) -> tuple[list[float], dict]:
"""Прямой проход через все слои. Возвращает логиты — по одному числу
на каждый токен словаря — и обновлённый KV-кэш. На вход подаются
только НОВЫЕ позиции: всё, что было раньше, уже лежит в кэше."""
...
def softmax(logits: list[float], temperature: float = 1.0) -> list[float]:
"""Логиты -> вероятности. Температура делит логиты ДО экспоненты."""
if temperature <= 0: # вырожденный случай: это и есть жадный выбор
best = max(range(len(logits)), key=logits.__getitem__)
return [1.0 if i == best else 0.0 for i in range(len(logits))]
scaled = [x / temperature for x in logits]
shift = max(scaled) # сдвиг ради численной устойчивости экспоненты
exps = [math.exp(x - shift) for x in scaled]
total = sum(exps)
return [e / total for e in exps]
def nucleus(probs: list[float], top_p: float = 0.9) -> list[tuple[int, float]]:
"""Ядро распределения: минимальный набор токенов с суммарной
вероятностью не меньше top_p. Хвост из десятков тысяч почти
невозможных вариантов отбрасывается и перенормируется."""
order = sorted(range(len(probs)), key=lambda i: probs[i], reverse=True)
kept, acc = [], 0.0
for i in order:
kept.append((i, probs[i]))
acc += probs[i]
if acc >= top_p:
break
mass = sum(w for _, w in kept)
return [(i, w / mass) for i, w in kept]
def draw(candidates: list[tuple[int, float]]) -> int:
r, acc = random.random(), 0.0 # случайный выбор пропорционально весам
for token_id, w in candidates:
acc += w
if r < acc:
return token_id
return candidates[-1][0]
def generate(prompt: str, max_new_tokens: int = 256,
temperature: float = 0.7, top_p: float = 0.9) -> str:
ids = tokenize(prompt) # шаг 1: текст -> токены
logits, cache = model_forward(ids, None) # шаг 2: prefill, весь промпт разом
produced: list[int] = []
for _ in range(max_new_tokens):
probs = softmax(logits, temperature) # шаг 3: логиты -> распределение
nxt = draw(nucleus(probs, top_p)) # шаг 4: выбор одного токена
if nxt == EOS: # шаг 5: условие останова
break
produced.append(nxt)
# шаг 6: новый токен становится входом следующего прохода.
# В модель уходит ОДИН токен — префикс уже посчитан и лежит в кэше.
logits, cache = model_forward([nxt], cache)
return detokenize(produced)
Тело цикла — три строки, и в них вся экономика работы с моделями. Пусть n — длина промпта, m — длина ответа, d — ширина внутренних представлений.
- Prefill: один проход, внимание стоит порядка
O(n^2 * d), веса —O(n * d^2). Всё считается параллельно. - Decode:
mпроходов, каждый при текущей длинеLстоитO(L * d + d^2). СуммарноO((n*m + m^2) * d + m * d^2), и всё это строго последовательно. - Память: KV-кэш растёт линейно по длине последовательности и умножается на число слоёв. В какой-то момент он, а не веса модели, становится тем, что не влезает в видеокарту.
Отсюда три вывода, которые вы будете встречать до конца трека: выходные токены дороже и много медленнее входных; длинный диалог дорожает нелинейно, потому что каждое новое сообщение удлиняет префикс для всех последующих; экономия на длине ответа даёт больший эффект, чем любая микрооптимизация вокруг. Детали — в главах про токены и про железо.
Веса лежат в файле
Ещё одно место, где лекция смешивает разные вещи: «есть декодеры, момент загрузки модели в оперативную память… он может лежать на SSD, на харде, в Redis, в Excel». Разделим.
- Архитектура — описание вычисления: сколько слоёв, какой ширины, как соединены, что делает каждый блок. Несколько сотен строк кода, ничего не знающих про конкретную модель.
- Веса — числа, подставляемые в это вычисление. Обычный файл на диске формата
safetensorsилиGGUF, размер которого считается арифметически: число параметров умножить на число байт на параметр. Модель на 7 миллиардов параметров в bfloat16 — около 14 гигабайт; она же в четырёхбитной квантизации — около 4 гигабайт. - Загрузка — копирование этих чисел в память вычислительного устройства. Влияет на время старта и ни на что больше.
Отсюда практические следствия:
- В интернете весов нет. Модель — это файл, который можно скачать, скопировать на флешку и запустить без сети, если она с открытыми весами. Когда вы обращаетесь к облачному провайдеру, вы просто пользуетесь чужим компьютером, на котором этот файл уже лежит. Как запускать модель у себя — «Локальные модели».
- Где лежит файл, безразлично для качества ответов. SSD, сетевое хранилище, объектное хранилище — это влияет на время загрузки, а не на то, что модель считает. При этом инференс требует, чтобы веса лежали в памяти устройства: «читать веса из Redis по мере надобности» — не режим работы, а описание очень медленного варианта загрузки.
- Обучение и инференс — разные операции над одним файлом. Обучение веса меняет, инференс только читает. Модель в проде не «дообучается на ваших запросах» сама по себе; чтобы веса изменились, нужен отдельный процесс — см. главу про обучение.
Что в лекции сказано неточно
Сводка поправок, сделанных выше по тексту, — чтобы их можно было проверить одним взглядом.
| В лекции | Как на самом деле |
|---|---|
| «Интерференц», «инкодеры» | Инференс — прогон обученной модели на новых данных; энкодеры — стопка слоёв, читающая вход двунаправленно |
| Идею трансформеров предложила OpenAI | Архитектуру предложили в Google: Vaswani et al., «Attention Is All You Need», 2017. OpenAI построила на её декодерной половине линейку GPT (Radford et al., 2018) |
| MCP сделала OpenAI | Model Context Protocol представлен Anthropic в ноябре 2024 года, modelcontextprotocol.io |
| «LLM — это цепи Маркова плюс нейросети плюс надстройки» | Обе модели задают распределение следующего элемента. Разница в том, что в состояние LLM входит весь префикс, а таблица частот заменена обученной функцией |
| Токенизация идёт «по заранее прописанным правилам, отделять приставку» | BPE и SentencePiece выучивают словарь по частотам подстрок на корпусе; морфологию токенизатор не знает |
| «Мы ни черта не понимаем паттерны образования слов» | Морфология и синтаксис описаны неплохо. Причина вероятностного подхода другая: правила не покрывают язык целиком и не ранжируют допустимые продолжения |
| Загрузка модели в память подаётся как часть описания архитектуры | Архитектура, веса в файле и загрузка весов в память — три разные вещи |
Мини-итог
- Языковая модель — функция, задающая распределение вероятностей следующего токена при условии всего предшествующего текста; генерация — многократное применение той же функции к собственному выходу по неизменному конвейеру: текст → токены → векторы → слои трансформера → логиты → распределение → выбор токена → повтор.
- Цепь Маркова решает ту же задачу, но её состояние — несколько последних слов и таблица частот; у LLM в состояние входит весь контекст, а таблица заменена обученной функцией фиксированного размера.
- Формула «LLM — это T9 с накопителем» верно передаёт механизм и ломается на трёх вещах: следование инструкции, удержание роли на длинном тексте, обобщение на невиданные задачи.
- Механизм внимания позволяет каждому токену на каждом слое смотреть на все предыдущие и самому решать, какие релевантны; ценой квадратичной сложности это снимает ограничение марковской памяти.
- Причинная маска объясняет асимметрию: промпт считается за один параллельный проход (prefill), ответ рождается по одному токену за отдельный проход (decode) — отсюда и цена, и латентность.
- Архитектуру Transformer предложили в Google в 2017 году, OpenAI построила на её декодерной половине GPT; MCP — открытый протокол Anthropic 2024 года, к архитектуре модели отношения не имеющий.
- Веса — это файл на диске, размер которого равен числу параметров, умноженному на байты на параметр; архитектура, веса и загрузка весов в память — три разные сущности.
Что дальше
Мы дважды упёрлись в токены: сначала при токенизации, потом в квадратичной цене внимания. И оба раза откладывали разговор о том, что происходит с диалогом, который растёт. Следующая глава разбирает, что такое контекстное окно, почему у модели нет памяти между сообщениями и всю историю приходится пересылать заново, отчего каждое следующее сообщение в чате стоит дороже предыдущего — и что с этим делают на практике.