Глава 4.1 — Токенизация
Содержание
- Там, где заканчивается архитектура: текст — это не числа
- Посимвольная и пословная токенизация и почему обе терпят неудачу
- Токенизация по подсловам: практический компромисс
- Byte-Pair Encoding
- WordPiece
- SentencePiece и униграммная языковая модель
- Размер словаря как инженерный компромисс
- Взгляд с точки зрения собеседования
- Вопросы для самопроверки
- Источники
1. Там, где заканчивается архитектура: текст — это не числа
Часть III завершилась замечанием о том, что даже имея архитектуру, способную эффективно применять внимание на длинном контексте, дёшево выполнять инференс через KV-кэш, квантовать свои веса и обслуживать запросы в масштабе, вы всё ещё фактически не обучили саму модель. Каждое обсуждение до сих пор — оценки внимания, маршрутизация MoE, спекулятивное декодирование — молча предполагало, что на вход модели уже подаётся последовательность дискретных токенов, каждый из которых — целочисленный индекс в фиксированном словаре, каждый из которых отображается в вектор через таблицу эмбеддингов. Это допущение обходится не бесплатно. Прежде чем какой-либо слой трансформера, какое-либо позиционное кодирование, какой-либо softmax по словарю смогут выполнить свою работу, кто-то должен ответить на гораздо более базовый вопрос: как сырая строка символов Unicode вообще становится последовательностью целых чисел?
Это токенизация, и её легко недооценить, поскольку она находится выше по потоку от всего эффектного. Но выбор токенизатора определяет эффективный словарь, с которым рассуждает модель, насколько длинным становится данный фрагмент текста после кодирования, насколько изящно модель справляется со словами, которых никогда не видела, и — как мы увидим в конце этой главы — некоторые по-настоящему странные режимы отказа, проявляющиеся в развёрнутых LLM, которые интервьюеры любят проверять именно потому, что они выявляют, понимает ли кандидат конвейер под моделью, а не только саму модель.
2. Посимвольная и пословная токенизация и почему обе терпят неудачу
Самый буквальный способ превратить текст в токены — трактовать каждый символ как токен. У этого есть настоящие достоинства: словарь крошечный (максимум несколько сотен символов, покрывающих каждую букву, цифру и знак препинания, которые вам встретятся), и не существует такого понятия, как слово вне словаря, поскольку любую строку можно записать по буквам. Но это терпит неудачу по причине, которая должна быть знакома по обсуждениям разреженности и длины контекста ранее в этой книге: посимвольные последовательности длинные. Предложение, которое могло бы состоять из двадцати токенов-подслов, превращается в сотню с лишним символов, и каждая архитектурная стоимость, масштабирующаяся с длиной последовательности — квадратичный член внимания, память KV-кэша, диапазон позиционного кодирования, — оплачивается по этой значительно более высокой ставке. Хуже того, теперь модели приходится с нуля выучивать долгосрочную структуру (что «ing» обычно следует за определёнными буквенными паттернами, что «the» — связная единица), тратя ёмкость и контекст исключительно на восстановление единиц, которые более разумный токенизатор предоставил бы бесплатно.
Противоположная крайность — пословная токенизация: разбить текст по пробелам и знакам препинания и дать каждому отдельному слову собственный токен. Это напрямую возвращает нас к проблеме разреженности из главы 1.2. У естественного языка длинный хвост — закон Ципфа гарантирует, что большинство отдельных слов в любом большом корпусе редки, — так что пословный словарь либо должен быть огромным, чтобы покрыть этот хвост (что раздувает таблицу эмбеддингов и, что ещё дороже, выходной softmax, поскольку оба масштабируются линейно с размером словаря), либо словарь нужно ограничить сверху и трактовать каждое слово вне него как единый общий токен «неизвестно». Второй вариант — настоящая катастрофа для языковой модели: модель теряет всю информацию о том, чем на самом деле было это слово вне словаря, и любое слово, отсутствующее в обучающем словаре, — опечатка, редкое имя, слово из варианта языка, слабо представленного в обучающих данных, неологизм — становится невидимым. Модель, которая не может представить «криптовалюту» или «COVID», потому что они появились после фиксации её словаря, — не та модель, которую можно выпускать в продакшен.
3. Токенизация по подсловам: практический компромисс
Токенизация по подсловам решает эту проблему, выбирая единицы, которые не являются ни целыми словами, ни отдельными символами, а часто встречающимися фрагментами где-то посередине, обнаруживаемыми напрямую из статистики корпуса, а не навязываемыми лингвистическим представлением о том, что такое «слово». Распространённые слова становятся отдельными токенами («the», «is», «language»), тогда как редкие или ранее не встречавшиеся слова раскладываются на небольшое число знакомых частей («cryptocurrency» может стать «crypto» + «currency», или чем-то более близким к «crypt» + «o» + «currency» в зависимости от алгоритма и обучающего корпуса). Критически важно, что любая возможная строка по-прежнему может быть представлена, поскольку самые мелкозернистые единицы отступления — отдельные символы или даже отдельные байты — всегда доступны как токены сами по себе. Это даёт вам компактность пословного токенизатора для распространённого текста и полное покрытие посимвольного токенизатора как страховочную сетку, не оплачивая полностью издержки ни одного из них. Три доминирующих алгоритма построения такого словаря — BPE, WordPiece и униграммная модель, используемая внутри SentencePiece, — различаются критерием, который они используют, чтобы решить, какие единицы подслов заслуживают существования, но все они разделяют одну и ту же базовую ставку: пусть статистика частот на реальном тексте подскажет, где проводить границы токенов, а не решать это заранее.
4. Byte-Pair Encoding
Byte-Pair Encoding, или BPE, изначально был алгоритмом сжатия данных, не имевшим никакого отношения к языковому моделированию: имея строку, он многократно находит наиболее частую пару соседних символов и заменяет каждое вхождение новым символом, представляющим эту пару, сокращая представление с каждым слиянием. Sennrich, Haddow и Birch адаптировали именно эту идею для нейронного машинного перевода в 2016 году, мотивированные именно проблемой слов вне словаря, описанной выше, — система перевода с фиксированным словарём слов не имеет принципиального способа перевести редкое или ранее невстреченное слово, но система, способная перейти к единицам подслов, всё ещё может произвести что-то осмысленное для «Woltschach», разложив его на узнаваемые части, — так же, как компетентный переводчик-человек произнесёт по слогам незнакомое имя.
Алгоритм в применении к токенизации работает следующим образом. Начинают со словаря, состоящего из отдельных символов (или байтов), присутствующих в обучающем корпусе, при этом каждое слово представлено как последовательность этих атомарных символов, обычно со специальным маркером границы слова. Затем повторяют простую процедуру фиксированное число раз: подсчитывают частоту каждой соседней пары символов по всему корпусу, находят единственную самую частую пару и сливают её — заменяя каждое вхождение этой пары новым, единым символом, добавленным в словарь. Каждое слияние записывается по порядку как правило. Выполнение этого процесса, скажем, для 32 000 или 50 000 слияний даёт словарь приблизительно такого размера — точнее, $|V_{\text{final}}| = |V_{\text{base}}| + M$, где $M$ — число выполненных операций слияния, а $|V_{\text{base}}|$ — размер базового алфавита (256 для байтового BPE), так что «50 000 слияний» и «словарь на 50 000 токенов» совпадают лишь с точностью до этой небольшой аддитивной поправки. Словарь строится снизу вверх целиком из статистики корпуса, на котором вы обучались, причём наиболее частые последовательности символов становятся полноценными токенами, а самые редкие строки по-прежнему выразимы как последовательности более коротких, более частых частей. Применение обученного токенизатора BPE к новому тексту детерминировано: вы применяете записанные правила слияния в том же порядке, в котором они были выучены, жадно, пока больше не применимо ни одно слияние.
Современные реализации, начиная с GPT-2, применяют BPE на уровне байтов, а не символов Unicode: базовый алфавит — это 256 возможных значений байта, а не кодовые точки Unicode, и каждое слияние работает с последовательностями байтов. Это единственное изменение закрывает реальный пробел посимвольной версии — посимвольному базовому словарю всё равно приходится решать, что делать с символом, который он никогда не видел при собственном построении (эмодзи, необычная письменность, случайный управляющий символ), и любой такой пробел вынуждает к какому-то запасному варианту. У байтового BPE такого пробела нет по построению: любой возможный вход, на любом языке или в любой кодировке, уже является последовательностью байтов, так что 256-символьный байтовый алфавит гарантирует полное покрытие ещё до применения хотя бы одного правила слияния — «неизвестного байта» просто не существует. Цена в том, что символ вне латиницы обычно обходится в большее число байт-токенов, чем латинский символ, что отчасти объясняет, почему неанглийский текст в таких токенизаторах устойчиво разбивается на больше токенов на слово, чем английский, — реальная, измеримая издержка, напрямую сказывающаяся на бюджете контекста и цене API для неанглоязычных пользователей той же модели.
5. WordPiece
WordPiece, разработанный для систем распознавания речи и машинного перевода Google, а позже принятый как токенизатор в основе BERT, следует той же общей стратегии, что и BPE — начать с символов, итеративно сливать пары, строить словарь снизу вверх, — но меняет критерий выбора того, какую пару сливать на каждом шаге. Там, где BPE просто сливает ту соседнюю пару, что наиболее частая, WordPiece сливает ту пару, слияние которой сильнее всего увеличивает правдоподобие обучающего корпуса при языковой модели, построенной из получившегося словаря, — эквивалентно, он предпочитает слияния, где пара совместно встречается гораздо чаще, чем можно было бы ожидать, если бы два символа были независимы, — оценивая каждую пару-кандидата $(a, b)$ примерно как $\text{score}(a, b) = \dfrac{P(ab)}{P(a)\,P(b)} \approx \dfrac{\text{count}(ab)}{\text{count}(a)\cdot\text{count}(b)}$ — а не слияния, частые лишь потому, что оба символа по отдельности распространены. На практике это даёт словари, в целом похожие на BPE, но критерий отбора — подлинное методологическое различие, которое стоит уметь точно сформулировать на собеседовании: BPE оптимизирует сырую частоту пар, WordPiece оптимизирует критерий правдоподобия. Сам WordPiece был описан ещё до эпохи arXiv с лёгким цитированием и обычно цитируется из вторых рук через статью BERT, которая его популяризировала, — эту же конвенцию цитирования принимает и эта книга.
6. SentencePiece и униграммная языковая модель
BPE и WordPiece в исходном описании предполагают, что можно провести предварительную токенизацию по пробелам до применения слияний подслов — сначала разбить на слова, затем разбить редкие слова на части. Это разумное допущение для английского языка, но оно ломается для языков без чётких границ слов, разделённых пробелами (японский и китайский — стандартные примеры), а также отбрасывает информацию о самом пробеле, которая может быть важна для точного восстановления исходного текста. SentencePiece, предложенный Kudo и Richardson, обходит это, трактуя вход как сырой, языконезависимый поток символов — включая пробел, который он кодирует как обычный символ (условно изображаемый как «▁»), а не как разделитель, который нужно удалить перед началом токенизации. Это делает токенизатор полностью обратимым: детокенизация — это просто конкатенация, без необходимости в языкоспецифичных правилах для корректной вставки пробелов обратно, что имеет большое значение в продакшен-системах, которым нужно обрабатывать произвольный многоязычный ввод без батареи особых случаев.
SentencePiece поддерживает BPE как один из двух своих алгоритмов сегментации, но его более интересный вклад — второй вариант: токенизатор на основе униграммной языковой модели, описанный в сопутствующей статье Kudo о регуляризации подслов. Здесь словарь строится сверху вниз, а не снизу вверх: вы начинаете с большого набора кандидатов-подслов и итеративно его сокращаете, на каждом шаге удаляя те подслова, удаление которых меньше всего вредит правдоподобию обучающего корпуса при униграммной модели (модели, трактующей каждый токен в сегментации как независимый, так что вероятность сегментации $x_1, \dots, x_n$ равна просто $P(x_1,\dots,x_n) = \prod_{i=1}^{n} p(x_i)$, где вероятности отдельных токенов $p(x_i)$ оцениваются через алгоритм максимизации ожидания), пока словарь не достигнет целевого размера. По-настоящему полезный приём, который это открывает, — регуляризация подслов: поскольку униграммная модель может оценивать несколько разных допустимых сегментаций одной и той же входной строки, во время обучения можно сэмплировать среди них, а не всегда использовать единственную наилучшую (Витерби) сегментацию — ту, что максимизирует $P(x_1,\dots,x_n)$ среди всех допустимых сегментаций входной строки. Это подвергает модель более чем одному способу разбиения одного и того же слова на разных шагах обучения, действуя как форма аугментации данных и регуляризации, которая делает модель более устойчивой к тому, какая бы сегментация ей ни встретилась на этапе инференса, вместо переобучения на одной канонической токенизации каждой строки.
7. Размер словаря как инженерный компромисс
Какой бы алгоритм вы ни использовали, вам всё равно нужно выбрать размер словаря, и это реальное, значимое инженерное решение, а не гиперпараметр, который можно просто подобрать перебором. Больший словарь означает, что распространённые слова и даже распространённые короткие фразы схлопываются в отдельные токены, так что любой данный фрагмент текста превращается в более короткую последовательность токенов — что ценно, поскольку длина последовательности почти повсеместно в стеке определяет стоимость вычислений, бюджет контекста и задержку генерации. Но больший словарь также означает большую таблицу эмбеддингов и, что ещё дороже, большую итоговую проекционную матрицу, отображающую скрытые состояния в распределение по словарю — каждая из них содержит $|V| \times d_{\text{model}}$ параметров, — поскольку стоимость этого выходного слоя масштабируется линейно с размером словаря и находится на критическом пути каждого отдельного прямого прохода. Меньший словарь переворачивает эти компромиссы: более дешёвые слои эмбеддингов и вывода, но более длинные последовательности для того же текста, и — несколько нелогично — часто лучшее обобщение на по-настоящему редкие или новые слова, именно потому, что эти слова вынуждены раскладываться на более мелкие, чаще встречающиеся части, а не запоминаться как один большой, редко обновляемый эмбеддинг.
Этот компромисс не чисто теоретический; он прямая причина некоторых из более странных режимов отказа, видимых в развёрнутых LLM. Когда люди просят модель развернуть строку, посчитать буквы в слове или выполнить аккуратную посимвольную арифметику, и модель ошибается способами, которые кажутся странно несогласованными с её очевидной беглостью во всём остальном, токенизация обычно является непосредственной причиной. Число вроде «3752» может быть разбито правилами слияния одного токенизатора на «375» и «2», а другого — на «37» и «52», и ни одно из этих разбиений не соответствует позиционной структуре по основанию 10, которую человек использовал бы для выполнения арифметики; модели приходится учиться отменять произвольную, продиктованную частотой корпуса сегментацию, прежде чем она вообще сможет приступить к самой арифметике. Аналогично, вопрос о том, сколько букв в слове, которое модель воспринимает как единый непрозрачный токен, — это вопрос о внутренней структуре символа, который ей никогда не показывали в разобранном виде. Это по-настоящему полезный факт, который стоит держать в уме на собеседованиях: он демонстрирует понимание того, что кажущиеся способности и неудачи модели — функция не только её параметров и цели обучения, но и решения предобработки, принятого ещё до начала обучения, — и это задаёт тему главы 4.2 «Pretraining Objectives and Data» («Цели и данные предобучения»): на какой именно цели фактически обучать модель, как только её вход сведён к последовательности этих дискретных токенов.
Компромисс размера словаря объясняет и одно из более странных публичных открытий о развёрнутых токенизаторах — глитч-токены. В 2023 году Rumbelow и Watkins исследовали словари GPT-2 и GPT-3 и обнаружили отдельные токены — самый известный пример — «SolidGoldMagikarp», имя пользователя Reddit, попавшее в корпус, на котором строился словарь токенизатора, в достаточном объёме, чтобы заслужить собственный токен, но почти не встречавшееся (или не встречавшееся вовсе) в куда большем и иначе собранном корпусе, на котором позже обучались веса самой модели. Подача этого токена модели порождала дико бессвязные, порой тревожащие продолжения: модель уклонялась, порождала свободные ассоциации или откровенную бессмыслицу вместо чего-либо похожего на нормальный ответ. Механизм напрямую следует из компромисса выше: токен может существовать в словаре, потому что был частым в том корпусе, на котором строился сам токенизатор, при этом его эмбеддинг остаётся практически необученным, потому что та же самая строка не встретилась вовсе или встретилась лишь считаные разы в корпусе, на котором обучалась модель. Эмбеддинг такого токена остаётся близко к своей случайной инициализации, и модель не выучила ничего осмысленного на случай его появления — наглядный, конкретный пример общего наблюдения: токенизатор, построенный на одном этапе конвейера и на одном корпусе, может незаметно разойтись с данными, на которых модель на самом деле учится на другом этапе.
8. Взгляд с точки зрения собеседования
В: Почему токенизация по подсловам победила и посимвольную, и пословную токенизацию для больших языковых моделей? Сильный ответ точно формулирует компромисс, а не просто утверждает «подслова лучше»: посимвольная токенизация раздувает длину последовательности, что дорого, поскольку большинство архитектурных издержек и издержек инференса масштабируются с длиной последовательности, и заставляет модель переучивать структуру уровня слов с нуля. Пословная токенизация создаёт проблему разреженности словаря — длинный ципфовский хвост редких слов, который либо раздувает словарь, либо отображается на неинформативный токен «неизвестно», уничтожая информацию о по-настоящему важных редких строках (именах, новых терминах, опечатках). Токенизация по подсловам берёт лучшее из обоих: распространённые строки — компактные отдельные токены, редкие строки изящно раскладываются на более мелкие известные части, а покрытие полное, поскольку самые мелкозернистые единицы всегда доступны как запасной вариант.
В: В чём конкретно алгоритмическая разница между BPE и WordPiece? Сильный ответ не просто говорит «они похожи» — он утверждает, что оба строят словари снизу вверх через итеративные слияния соседних пар символов, но BPE сливает ту пару, что наиболее частая в корпусе, тогда как WordPiece сливает ту пару, слияние которой сильнее всего улучшает правдоподобие корпуса при получившемся словаре (эквивалентно, предпочитает пары, совместно встречающиеся гораздо чаще случайного, с учётом их индивидуальных частот). Ключевой контраст — «сырая частота» против «критерия на основе правдоподобия».
В: Какую проблему решает SentencePiece, которую не решают BPE и WordPiece в исходном описании? Сильный ответ отмечает, что классические BPE/WordPiece предполагают предварительную токенизацию по пробелам, что ломается для языков без чётких границ слов и отбрасывает информацию, нужную для точного восстановления пробелов. SentencePiece трактует вход как сырой поток символов, включая пробел как обычный символ, что делает его языконезависимым и превращает детокенизацию в чистую конкатенацию без необходимости в особых случаях.
В: Что такое регуляризация подслов и зачем она нужна? Сильный ответ объясняет, что при токенизаторе на основе униграммной языковой модели данная входная строка обычно имеет несколько допустимых сегментаций, каждая с вычислимой вероятностью; вместо того чтобы всегда токенизировать с единственной наилучшей сегментацией, во время обучения сэмплируют среди правдоподобных сегментаций. Это подвергает модель разным токенизациям одного и того же текста на разных шагах обучения, действуя как техника аугментации данных/регуляризации, снижающая чувствительность модели к конкретной сегментации, которая ей встретится на этапе инференса.
В: Почему LLM иногда терпят неудачу в, казалось бы, тривиальных задачах вроде подсчёта букв в слове или арифметики с многозначными числами? Сильный ответ напрямую связывает это с токенизацией: модель никогда не видит отдельных символов слова, токенизированного как единая непрозрачная единица, поэтому она не может напрямую «заглянуть внутрь» этого токена, а многозначные числа разбиваются на произвольные, продиктованные частотой куски, не согласующиеся с позиционным весом разрядов, вынуждая модель неявно учиться отменять несогласованную сегментацию, прежде чем приступить к самой арифметике. Это представляется как режим отказа, вызванный предобработкой, а не свидетельство фундаментального дефицита рассуждений, не связанного с представлением входа.
9. Вопросы для самопроверки
- Почему разложение языкового моделирования по цепному правилу из главы 1.1 предполагает, что текст уже преобразован в фиксированный, дискретный словарь?
- Какие конкретные издержки, масштабирующиеся с длиной последовательности, накладывает посимвольная токенизация, и на какие архитектурные компоненты из части III влияют эти издержки?
- Объясните в одном-двух предложениях, почему пословная токенизация воссоздаёт ту же проблему разреженности, что обсуждалась в трактовке n-граммных моделей в главе 1.2.
- Пройдите вручную один шаг слияния BPE: имея игрушечный корпус, как вы определите, какую пару сливать следующей, и что происходит со словарём в результате?
- В чём точная разница между критерием отбора, используемым BPE, и критерием, используемым WordPiece?
- Почему SentencePiece кодирует пробел как обычный токен, а не трактует его как разделитель, который нужно удалить перед токенизацией?
- Если бы вам пришлось выбрать меньший размер словаря для новой модели, что вы ожидали бы выиграть и что потерять с точки зрения длины последовательности, размера матрицы эмбеддингов/вывода и обобщения на редкие слова?
10. Источники
- Sennrich, R., Haddow, B., & Birch, A. (2016). Neural Machine Translation of Rare Words with Subword Units. ACL 2016. arXiv:1508.07909. https://arxiv.org/abs/1508.07909
- Kudo, T., & Richardson, J. (2018). SentencePiece: A Simple and Language Independent Subword Tokenizer and Detokenizer for Neural Text Processing. EMNLP 2018. arXiv:1808.06226. https://arxiv.org/abs/1808.06226
- Kudo, T. (2018). Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates. ACL 2018. arXiv:1804.10959. https://arxiv.org/abs/1804.10959
- Schuster, M., & Nakajima, K. (2012). Japanese and Korean Voice Search. ICASSP 2012. (WordPiece; отсутствует на arXiv — цитируется здесь в том виде, в каком он используется и описан в статье BERT авторства Devlin et al., arXiv:1810.04805, упомянутой в главах 2.3 и 4.2.)
- Radford, A., Wu, J., Child, R., Luan, D., Amodei, D., & Sutskever, I. (2019). Language Models are Unsupervised Multitask Learners. (GPT-2; вводит байтовый BPE.) https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf
- Rumbelow, J., & Watkins, M. (2023). SolidGoldMagikarp (plus, prompt generation). LessWrong. https://www.lesswrong.com/posts/aPeJE8bSo6rAFoLqg/solidgoldmagikarp-plus-prompt-generation