Главная / Статьи / От промпта к токену: как LLM превращает текст в числа и обратно

От промпта к токену: как LLM превращает текст в числа и обратно

Проследим путь короткого промпта через токенизацию, эмбеддинги, attention, feed-forward-слои, softmax и сэмплирование, чтобы увидеть, как модель на самом деле выбирает каждое следующее слово.

1615 слов

Внутри модели нет английского

Нажмите Enter, и GPU начнёт перемалывать миллиарды операций с плавающей точкой ещё до появления первого токена. Ни одна из этих операций не касается английского слова.

Возьмём знакомый фрагмент:

"The cat sat on the "

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

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

Чтобы по-настоящему понять языковые модели, отбросьте метафоры и проследите за данными. Вот маршрут, который проходит ваш текст.

Шаг 1: токенизатор

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

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

  • "The"
  • " cat"
  • " sat"
  • " on"
  • " the"

Обратите внимание на пробелы в начале. Это не опечатки. Современные токенизаторы приклеивают пробел перед словом к токену, поэтому "cat" и " cat" — два разных токена.

Каждый токен соответствует одному целому числу в фиксированной таблице словаря. При словаре, скажем, в 50257 токенов фраза превращается в пять чисел:

[464, 3797, 3372, 319, 262]

Ни один слой языковой модели ещё не запускался, а строка уже исчезла. Остались только пять целых чисел.

Шаг 2: таблица эмбеддингов

Теперь в дело вступает сама сеть, и первая её задача — решить базовую проблему. Целые числа бесполезны для матанализа. Число 3797, обозначающее « cat», ничуть не ближе к «dog», чем к «airplane». Это произвольные метки, а с метками геометрией не займёшься.

Решение — превратить каждое число в плотный список чисел с плавающей точкой, вектор эмбеддинга. Представьте гигантскую таблицу поиска в памяти GPU. Допустим, в словаре 32 000 токенов, а скрытая размерность — 4096. Тогда таблица — это матрица из 32 000 строк на 4096 столбцов, и модель просто ищет в ней:

  • Токен 464 достаёт строку 464.
  • Токен 3797 достаёт строку 3797.
  • Токен 3372 достаёт строку 3372.
  • Токен 319 достаёт строку 319.
  • Токен 262 достаёт строку 262.

На этом этапе нет никакой арифметики. Это обычное чтение из памяти. На выходе получается матрица формы [5, 4096] — пять строк по 4096 непрерывных значений.

Теперь каждый токен — точка в 4096-мерном пространстве, где направление несёт смысл, а положение кодирует связи, выученные на миллиардах обучающих примеров.

Прежде чем векторы пойдут дальше, добавляется ещё один ингредиент — информация о позиции. Матричные операции сами по себе могут перемешать порядок слов, поэтому модель подмешивает в координаты сигналы позиции. Без этого «The cat sat on the» и «The on sat cat the» выглядели бы для сети одинаково.

Шаг 3: стек трансформера

Матрица [5, 4096] входит в трансформер. В типичной архитектуре на 7 млрд параметров стек состоит из 32 одинаковых блоков. Матрица сохраняет форму на каждом из них: входит в слой 1 и выходит из слоя 32 как [5, 4096]. Меняется информация, хранящаяся в этих числах.

Сразу после таблицы эмбеддингов каждый вектор существует сам по себе. Вектор для " sat" знает, что означает «сидел», но не может сказать, кто сидел, где и почему. Это исправляют два механизма внутри каждого блока.

Attention

Здесь векторы общаются. Attention позволяет каждой позиции оглянуться на предыдущие и вытащить из них то, что важно.

Возьмём пятый токен, " the". Он измеряет сходство с "The", " cat", " sat" и " on". Поскольку ему нужно помочь предсказать, какое существительное идёт после предлога, его вектор активно опирается на действие и на подлежащее. Через скалярные произведения между этими позициями слой attention обновляет каждый вектор контекстом соседей.

Feed-forward-сеть

После того как токены обменялись информацией, каждая строка по отдельности проходит через feed-forward-сеть (MLP). Если attention — это разговор токенов друг с другом, то этот слой — каждый токен, считающий своё. Он проецирует только что собранный контекст через тысячи выученных весов, что позволяет модели включать фактические ассоциации, хранящиеся в её параметрах.

Всё это повторяется 32 раза. К слою 32 пятый вектор уже не общий заменитель слова " the": он впитал историю всего предложения. Теперь он обозначает определённый артикль, следующий за сидящим котом.

Обратная проекция (unembedding)

Для следующего слова первые четыре строки больше не нужны, поэтому модель берёт только последнюю: один вектор из 4096 чисел. Теперь задача эмбеддинга решается в обратную сторону. У модели есть точка в 4096-мерном пространстве, а нужно слово.

Вторая матрица, матрица unembedding (часто это просто транспонированная таблица эмбеддингов), умножается на этот вектор и разворачивает его 4096 значений обратно на весь словарь. Результат — одна ненормированная оценка для каждого токена словаря, например:

Наверху вы бы увидели " mat" с 16,2 и " rug" с 13,8, за ними " floor" с 12,1 и " couch" с 10,4. Далеко внизу списка "LeBron James" с -12,5.

Эти сырые оценки называются логитами. Чем выше логит, тем сильнее выученные параметры сети нашли геометрическое соответствие между итоговым состоянием промпта и этим кандидатом.

Softmax

Логиты нельзя использовать как есть. Некоторые большие и положительные, другие отрицательные, а их сумма произвольна. Поэтому модель прогоняет весь вектор через softmax: для каждого элемента берётся e в степени его оценки и делится на сумму таких экспонент по всем элементам.

Softmax делает две вещи. Экспонента делает каждое значение строго положительным. Деление на сумму заставляет всё в сумме давать 1,0. Получается настоящее распределение вероятностей по всем 32 000 токенам, примерно такое:

Лидер, " mat", получает 68,4%. " rug" — 17,1%, " floor" — 9,2%, " couch" — 4,1%. На дальнем конце " LeBron James" остаётся с 0,000001%.

Сэмплирование

Всё до этого момента детерминировано. При одинаковых весах и одинаковом входе вероятности не разойдутся даже в одном знаке после запятой. Непредсказуемость LLM появляется на последнем шаге — сэмплировании.

Если бы система всегда брала самый вероятный токен, здесь " mat" с 68,4%, это было бы жадное декодирование. Оно детерминировано, но на длинных ответах склонно сваливаться в однообразные, неестественные циклы. Поэтому программа бросает взвешенную кость. Примерно в 68% случаев выпадает " mat", примерно в 17% — " rug".

Параметр temperature в вызове API меняет форму этой кривой до броска. Низкая температура усиливает разрывы и может подтолкнуть " mat" к 95%. Высокая температура сглаживает кривую, и у токенов пониже в рейтинге появляется куда больше шансов быть выбранными.

Кость брошена, и побеждает токен с ID 2603.

Детокенизация и цикл

Программа ищет ID 2603 в обратной карте словаря, находит строку " mat" и отправляет её в ваш терминал или браузер.

Здесь проявляется главный факт инференса: модель ни в какой момент не знает, что идёт после " mat". Она не может планировать на три слова вперёд и не хранит план абзаца, который пишет. Чтобы продвинуться дальше, она добавляет новое слово ко входу и формирует совершенно новый промпт:

"The cat sat on the mat"

Она токенизирует все шесть слов, получает эмбеддинги всех шести строк, прогоняет все шесть позиций через все 32 слоя, вычисляет логиты для шестой позиции, применяет softmax, бросает кость и выбирает седьмой токен. Этот полный цикл, от строки к тензору и к броску кости, повторяется для каждого токена, появляющегося на вашем экране.

Вопросы, которые вызывает этот конвейер

Если смотреть глазами инженера, эта механика вызывает неудобные вопросы. Похоже, каждое сгенерированное слово требует прогнать весь накопленный диалог через миллиарды операций, так что длинные чаты должны бы ставить железо на колени. (Почему этого не происходит, объясняет кэширование в памяти — это тема для отдельного разговора.)

Ещё загадочнее первый шаг — прыжок от человеческого языка к числам. Ещё до срабатывания attention и запуска хоть одного слоя промпт уже был нарезан на произвольные числовые куски. Почему подслова, а не символы, целые слова или сырые байты? Почему необычные строки или строки кода на Python заставляют количество токенов вести себя непредсказуемо? Токенизация — первый блок в цепочке, и именно его стоит открыть следующим.