Это третья статья серии «AI без магии». В первой разобрали, что LLM — это огромный авторегрессивный предсказатель токенов. Во второй — что значит «думать по одному токену» и почему модель галлюцинирует. Теперь ныряем глубже: как именно текст превращается в числа, с которыми работает модель.

Зачем вообще нужны числа

Нейросеть умеет одно — умножать большие матрицы из чисел и применять к ним нелинейные функции. Буквы и слова она понимать не умеет. Поэтому первое, что делает любая LLM с вашим запросом, — переводит текст в числа. И не просто «buchstabe → ASCII код», а в два этапа.

Этап 1 — токенизатор: разбивает текст на куски (токены) и присваивает каждому целое число. «Привет, как дела?» → [Пр, ивет, ,, как, дела, ?][3914, 6128, 11, 1495, 1872, 30].

Этап 2 — эмбеддинг-слой: каждое число заменяется на длинный вектор из вещественных чисел длиной 768, 4096 или даже 12288. Эти векторы — вход для всей остальной модели.

То есть путь такой: текст → токены (целые числа) → векторы. Дальше с векторами уже разбирается attention и трансформер. Но сначала — про оба этапа по отдельности.

BPE: как появился словарь токенов

Здесь возникает первый дизайн-вопрос: на какие куски резать текст? Варианты:

По буквам. Словарь крошечный (~30-100 символов), но цепочки получаются гигантские. Слово «strawberry» — 10 шагов модели вместо одного. Дорого.

По словам. Цепочки короткие, но словарь раздувается до миллионов записей, и любая опечатка («strawbery») превращается в неизвестный токен.

Что-то посередине. Частые слова — целиком, редкие — на куски. Это и есть BPE (Byte Pair Encoding) — алгоритм, на котором стоят GPT-2, GPT-3, GPT-4, Claude, Llama, почти всё.

Идея простая: не угадываем заранее, а смотрим на корпус текстов и итеративно склеиваем самые частые пары символов в один токен.

корпус: «low low lower lowest»шаг 0 — буквыl · o · w · l · o · w · l · o · w · e · r · l · o · w · e · s · tшаг 1 — слили «l»+«o» → «lo»lo · w · lo · w · lo · w · e · r · lo · w · e · s · tшаг 2 — слили «lo»+«w» → «low»low · low · low · e · r · low · e · s · tшаг 3 — слили «e»+«r» → «er»low · low · low · er · low · e · s · tшаг 4 — слили «low»+«er» → «lower»low · low · lower · low · e · s · tитог: «low» и «lower» — отдельные токены, «lowest» = low + e + s + t
BPE начинает с букв и склеивает самые частые пары. Через несколько тысяч итераций «low» становится одним токеном, «lower» — тоже, а редкое «lowest» собирается из кусочков. Реальный GPT-2 делает 50000 таких слияний.

В реальности GPT-2 стартует с 256 байт (любой Unicode-символ можно представить как набор байт) и делает ~50000 таких слияний. Получается словарь из ~50257 токенов, где «the», «hello», «def» — целые токены, а слово «antidisestablishmentarianism» собирается из кусков.

Что на самом деле видит GPT

Давайте посмотрим на конкретные числа. Прогоняем через tiktoken (это официальная библиотека OpenAI) одну и ту же фразу на двух языках:

«Hello, how are you?»Hello,·how·are·you?6 токенов«Привет, как дела?»Привет,·как·дела?8 токеновпангам «Съешь же ещё этих мягких французских булок…»: 36 токенов«The quick brown fox jumps over the lazy dog»: 10 токенов
Один и тот же смысл, но в кириллице токенов в 1.5–3 раза больше. Это потому, что BPE учился на корпусе, где английского текста было на порядки больше. Кодировка cl100k_base, GPT-3.5/GPT-4.

Это объясняет несколько практических вещей:

Токены — единица оплаты. Один и тот же запрос по-русски стоит в API в 1.5-3 раза дороже, чем по-английски, и быстрее упирается в лимит контекста. Поэтому многие приложения внутри переводят запросы на английский.

Эмодзи и редкие символы часто превращаются в 3-4 токена каждый. Один смайлик на эмодзи-стене может «съесть» десяток токенов.

Проблема с буквами в словах (помните «сколько R в strawberry» из прошлого поста?) ровно отсюда: модель видит не последовательность букв s-t-r-a-w-b-e-r-r-y, а три токена [str][aw][berry]. Цепочка букв исчезает уже на этапе токенизации.

Новая кодировка o200k_base (GPT-4o, GPT-5) вдвое лучше работает с русским — на нашем пангаме 19 токенов вместо 36. Но даже она проигрывает английскому на ~2× по плотности.

Эмбеддинг: токен → вектор

Окей, текст превратили в список целых чисел: [3914, 6128, 11, 1495, 1872, 30]. Что с ними делать дальше? Просто скормить в матрицу — плохая идея: число 3914 ничем не лучше и не хуже числа 6128, между ними нет смысловой близости. ID — это просто адрес.

Поэтому первый слой LLM — embedding layer. Это огромная таблица:

— строк: размер словаря (~50000 для GPT-2, ~200000 для GPT-4o); — столбцов: размерность вектора (768 для маленького GPT-2, 12288 для GPT-3, ~16000+ для современных моделей).

Каждый токен ID заменяется на строку из этой таблицы — длинный вектор вещественных чисел. На старте обучения там просто случайные числа. Но в процессе тренировки эти числа подкручиваются вместе со всей моделью так, чтобы похожие по смыслу токены получали похожие векторы.

И тут начинается самое интересное.

Геометрия смысла

Если спроецировать обученные эмбеддинги в 2D (например, через t-SNE или UMAP), окажется, что слова кучкуются по смыслу. Птицы — рядом друг с другом. Страны — отдельным кластером. Времена года — своим. Эта картинка из учебника Раски стала классикой:

первое измерение →↑ второе измерениеeagleduckgooseGermanyBerlinFranceParisItalyRome«столица»longlongerlongest«степень сравнения»
Похожие по смыслу слова кучкуются. А разница между двумя словами часто кодирует осмысленную операцию: «France → Paris» и «Italy → Rome» — это одно и то же направление в пространстве, направление «столица».

Это и есть самое знаменитое наблюдение про эмбеддинги, которое в 2013 году опубликовал Mikolov et al. в работе про word2vec: операции над векторами слов имеют смысл.

king − man + woman ≈ queen

Paris − France + Italy ≈ Rome

walking − walk + swim ≈ swimming

То есть в пространстве эмбеддингов есть устойчивые направления: «пол», «столица», «прошедшее время». И их можно складывать с любым словом.

Звучит как магия — но это не магия. Модель училась на миллиардах текстов, где встречалось «Paris is the capital of France» и «Rome is the capital of Italy». Чтобы научиться предсказывать такие фразы, ей пришлось закодировать отношение «столица» как воспроизводимое направление в векторном пространстве. Иначе бы она не справилась с задачей предсказания.

Маленькое честное уточнение: классический пример с king − man + woman = queen работает только потому, что алгоритм при поиске ближайшего вектора исключает исходные слова из ответа. Если этого не делать, ближайшим к king − man + woman окажется… сам king. Просто queen — на втором месте. Но идея — что направления имеют смысл — от этого не ломается.

Контекстные эмбеддинги: один токен — много векторов

Word2vec (и его наследники: GloVe, fastText) выдавал один вектор на слово. Слово «коса» — один вектор, что бы оно ни значило: волосы, инструмент или песчаную полосу. Это статические эмбеддинги.

Современные LLM работают иначе. Эмбеддинг-слой даёт стартовый вектор — но дальше этот вектор проходит через десятки слоёв трансформера, и на каждом слое смешивается с векторами окружающих токенов. К моменту, когда токен «коса» добирается до выхода модели, его вектор уже зависит от того, шла ли речь о парикмахерской, поле или пляже.

Это и есть контекстные эмбеддинги. Один и тот же токен — разные векторы в зависимости от соседей. Именно поэтому LLM так хорошо различает омонимы и понимает иронию.

А механизм, который делает это смешивание, называется attention. Это наша следующая статья.

Что мы теперь знаем

Соберём картину.

Модель не работает с буквами и словами — она работает с токенами (целые числа из словаря на ~50000-200000 записей) и с эмбеддингами (вектор из ~1000-15000 чисел на каждый токен). Словарь токенов строится один раз на корпусе текстов алгоритмом BPE: частые подстроки склеиваются в отдельные токены, редкие остаются разрезанными на куски.

Эмбеддинг — это просто строка в гигантской таблице, обучаемой вместе с моделью. После обучения в этой таблице обнаруживается красивая геометрия: похожие по смыслу слова — рядом, отношения между словами — повторяющиеся направления в пространстве. Это не задумывалось специально — это побочный эффект задачи «предскажи следующий токен».

Но статические эмбеддинги — это только начало. Дальше векторы должны поговорить друг с другом, чтобы каждый токен «увидел» контекст. Это делает attention — и в следующей статье мы посмотрим на него подробно.

По мотивам Build a Large Language Model (From Scratch) Sebastian Raschka · manning.com Открыть оригинал →
Что ещё прочитать

← В архив