На ниві українського токенізаторства
На конференції Generative AI Weekend, яка проходила 10 травня 2025 року у Львові, тільки й розмов було про національну LLM. Розглядали цю тему спікери, про неї питали на Q&A, йшли розмови в кулуарах . У певний момент я відчув наплив емоцій, навіяний спонтанною думкою — «А хоч токенізатор, токенізатор вже хтось зробив!?» Спочатку тиша... Потім згадки про спроби, які не закінчилися успіхом. Зрозуміло, тобто перший крок до створення «повноцінної» української великої мовної моделі, ймовірно, ще не було зроблено.
Популярні сьогодні LLM, принаймні ті, які доступні нам до споглядання у відкритому доступі, мають ряд спільних рис.
Сконцентруймося на обробці саме текстової інформації, ігноруючи мультимодальні можливості роботи з зображенням, аудіо і так далі.. Також не будемо розбиратися з першопричинами перелічених нижче особливостей та обмежень, просто спробуємо прийняти реальність такою, якою вона є.
Тож LLM при роботі з текстом:
1. 🛤 Працюють з послідовностями. Будь-яка послідовність складається з елементів, атомарних частинок, які називаються токенами. Вони можуть представляти з себе слова, знаки, цифри, або частинки слів.
2. 🔤 Варіантів токенів може бути вичерпна кількість. Сукупність всіх доступних токенів називається словником моделі.
3. ☯️ Послідовність умовно можна розділити на 2 складові: та, що в нас вже є — вхід (або промпт), і та, яку LLM згенерує — відповідь.
4. 🏋️ Пропрацювання промпту вимагає ресурсів, генерація кожного токену відповіді — вимагає меншої, але співставної кількості обчислень.
5. ↔️ Кількість токенів, які LLM може ефективно обробити за один раз — обмежена та називається максимальним контекстним вікном. Чим більше токенів у контекстному вікні, тим більше вимоги до hardware.
6. 📶 Уникаючи багатьох нюансів і деталей різних архітектур, умовимо, що LLM генерує токени один за одним, використовуючи при цьому всі свої активні ваги, що робить генерацію дорогим процесом.
7. ⚖️ Складність генерації кожного нового токену зокрема залежить і від розміру словника (пункт 2). Чим більше словник, тим більше обчислень на останньому шарі нейромережі. Але при цьому у більшому словнику можуть бути краще представлені слова, що дозволяє генерувати більші частини за раз.
Знаючи цю невелику кількість фактів, ми можемо почати формувати деяку інтуїцію.
Наприклад: якщо ми маємо великий словник, але токенів, які нас цікавлять, в ньому небагато (наприклад, українські літери та слова в переважно англомовному словнику), згідно з пунктом 7 будемо дорожче генерувати кожен токен. Також чим повніше мова представлена в токенізаторі (чим менше шматків потрібно, щоб зібрати слово), тим більше інформації може поміститися в максимальне можливе (через обмеження моделі або hardware) контекстне вікно.
Але що ж таке той токен, як він виглядає фізично?
Якщо розглядати текст, включно з тим, який ви зараз читаєте, найменшою одиницею (носієм інформації) буде символ. Це і літери, і пунктуація, і спецсимволи форматування. Чи може символ бути токеном? Чому ні, може, але є проблема — довжина послідовності, якщо вимірювати її в символах, буде великою. Згадуємо пункт 5 і розуміємо, що ми хочемо мінімізувати довжину послідовності. Ще дещо, чи дійсно ми спримайємо тескт на рівні літер, чи, може, хапаємо цілими словами? Попались? :)
Якщо хочемо зменшити розмір послідовності, можемо вдатися до доволі простого трюку — стиснути. Нечасто токенізатори представляють з позиції алгоритмів стиснення, але саме для цієї цілі багато з них і створювались ще в 1980–1990-х роках минулого сторіччя. З точки зору теорії інформації, чим більша ймовірність зустріти певну послідовність, тим менше нам потрібно байт, щоб її закодувати.
Слово «слово» зустрічається часто, тому потрапить в словник цілим та матиме свій ідентифікатор, а, наприклад, «аеропрес» — набирає популярність, тому зберемо його з частки «аеро», яку можемо використати ще для слова «аеробні», та частки «прес», яка взагалі в купі місць знадобиться. Ми ж не хотіли робити словник занадто великим через вже згаданий пункт 7, а тепер можемо обрати будь-який комфортний розмір та заповнити його.
Звідки брати статистику, щоб зрозуміти, які послідовності частіше зустрічаються в мові? Найпростіший варіант — це взяти великий корпус текстів та проаналізувати його. Цей процес називається тренуванням токенізатора.
Ось назбирали ми цих статистично значущих шматочків, що далі, як навчити нейронку їх розуміти? Кожному токену ми ставимо у відповідність математичний вектор. Найпростіше пояснити на кольорах. Наприклад, «червоний» можемо закодувати в RGB як [255, 0, 0], а «сірий» як [50, 50, 50]. Виходить вектор з розмірністю 3 (три елемента в кожному обʼєкті). В LLM ці розмірності зазвичай вимірюються тисячами і називаються ембеддінгами.
Модель в процесі тренування сама підбирає для них значення, які представлені переважно натуральними числами. Закріпимо на аналогії з кольорами: cлова «червоний», «сірий» це токени, а вектора [255, 0, 0], [50, 50, 50], це — ембеддінги. По суті токен — це унікальний ключ, який вказує на ембеддінг.
Так, з базовими поняттями розібралися, що далі? Як повинен виглядати токенізатор для української мови? Щоб відповісти на це питання, я пропоную почати мислити геополітично.
Уявимо потенційну українську LLM та те, що вона повинна вміти. Чи хочемо, щоб вона крім української на якомусь стерпному рівні знала англійську? Ну певно що так, потенційні користувачі в держорганах та в бізнесі, вірогідно, будуть стикатися з потребою опрацьовувати англійську. Добре, а чи хочемо, щоб вона базово розуміла основні мови Єврозони? Звичайно, нам євроінтеграцію ще робити й робити, а це чимало бюрократії, для якої була б в пригоді LLM. З’ясували!
Але чи хочемо ми вчити нашу рідну LLM англійської, німецької, французької? Та якось не дуже, нам аби української її навчити ресурсів вистачило...
І як між краплями вийти з цієї ситуації? В ідеалі взяти LLM, яка вже непогано знає англійську, та інші мови, що нас цікавлять, і вчити її лише української.
Ну це не щось нове, просто дотренувати існуючу LLM на текстах українською й готово? Нюанс в тому, що в існуючих LLM вже є власні токенізатори й вони обмежені факторами, які ми згадували на початку. Плюс, їх вчили ефективно представити мови, серед яких українська... Явно була не на першому місці. В тренувальних корпусах першу скрипку грали: англійська, бразильська португальська, мови ЄС, китайська.
З цього випливає наступне: генеруючи кожен токен (пункт 6), ми повинні пропрацювати кількість обчислень, пропорційну розміру словника (пункт 7). І якщо уявимо словник на 250 тисяч токенів, українських з яких буде, для прикладу,
Варіант, який задовольнив би обидві вимоги: (1) зберегти знання натренованої моделі про іноземні мови, (2) збільшити її місткість для української, — частково змінити вже існуючий токенізатор, додавши до нього українську, при цьому мінімізувавши втрати серед інших мов.
Перший крок — створення суто українського токенізатора, такого собі донора токенів.
Другий крок — заміна частини токенів в існуючому токенізаторі на українські.
Третій крок — тестування. Перевірка того, що все додане «прижилося».
В наступних частинах ми розглянемо процес українізації існуючих LLM токенізаторів на прикладі моїх AYAYA та Tereshchenko Blue.
AYAYA Tokenizer
Першим я вирішив адаптувати токенізатор aya-expense — він належить відносно популярній LLM в українській НЛП-спільноті, адже серед інших публічних конкурентів сама модель непогано репрезентує українську мову.
Токенізатор aya-expense побудований на byte-level BPE, тобто базовим компонентом токена є не символ, а байт. Цікава особливість: українські літери у стандартному UTF-8 представлені двома байтами. Наприклад, літера «ї» складається з байтів D1 та 97; для зручності у текстовому форматі в словнику вони кодуються символами «Ñ» та «Ĺ».
Тепер торкнімося того, як формуються токени. Частина токенізатора, створена алгоритмом BPE, — це merges. Якщо дуже просто, то це список правил «що з чим склеювати», щоб із дрібних шматків поступово зібрати більші токени.
У byte-level BPE ці «дрібні шматки» — байти. І merges можна показати буквально як ланцюжок склеювань. Наприклад, для токена «місто» (з пробілом спереду) це виглядає так:
[Ġ + Ð → ĠÐ] → [ĠÐ + ¼ → ’ м’] → [’ м’ + ’і’ → ’мі’] → [’мі’ + ’сто’ → ’місто’]
Важливо: «і» та «сто» тут уже показані як готові шматки, але насправді кожен із них теж збирається своїм ланцюжком merges — аж до рівня байтів.
Тобто merges — це інструкція складання: беремо те, що стоїть поруч, і за статистикою «найчастіше разом» склеюємо в один більший шматок.
План був наступний: замінити частину вже наявних токенів на українські, відрізаючи у токенізатора «хвіст» — ті токени, які були додані пізніше й, як наслідок, мають меншу статистичну значущість. При цьому зберегти розмір словника, щоб не було необхідності змінювати розмір матриці ембеддінгів моделі, оскільки, як ми пам’ятаємо, токен — це лише вказівник на конкретний індекс ембеддінга.
Але щоб додати українські токени, їх потрібно спочатку десь взяти. Тому першим етапом я натренував на корпусі Malyuk суто український токенізатор — тим самим підходом, яким був створений aya-expense. Паралельно я згадав, що українська НЛП-спільнота створила також корпус кримськотатарської мови. Подумав, що буде коректно додати його до датасету, на якому тренуватиметься токенізатор, щоб ця мова теж була представлена у вигляді токенів. Ці словники будемо вважати донорами.
Наступний крок — замінити частину оригіналу. Через те, що алгоритм aya-expense читає текст на рівні байтів, а не літер, це породжує цікаву поведінку: токенізатор на етапі створення не обмежений під час породження нового токена літерами — він може створити токен із байтів «між ними», якщо вони матимуть вищу статистичну значущість. Ще менш очевидний висновок у тому, що такі «крос-літерні» токени можуть використовуватися в різних системах письма для формування абсолютно різних символів.
Отже, байтовий BPE може створювати токени, які перетинають межі символів.
Наприклад, українське слово «місто» з точки зору послідовності байтів виглядає так:
м [U+043C] → [0xD0 0xBC]
і (U+0456) → [0xD1+0×96]
с (U+0441) → [0xD1+0×81]
т (U+0442) → [0xD1+0×82]
о (U+043E) → [0xD0+0xBE] 👈
А вірменське «Աստված» («Бог»):
Ա (U+0531) → [0xD4+0xB1]
ս (U+057D) → [0xD5 0xBD]
տ (U+057F) → [0xD5 0xBF]
վ (U+057E) → [0xD5 0xBE] 👈
ա (U+0561) → [0xD5 0xA1]
ծ (U+056E) → [0xD5 0xAE]
Ми бачимо, що для української літери «о» та вірменської «վ» використовується один і той самий «хвостовий» байт 0xBE. І оскільки байтовий токенізатор не стежить, щоб токени репрезентували коректну UTF-8 послідовність, словник може містити токени, які починаються з 0xBE і водночас «належать» до різних мов.
Ще одним важливим наслідком такої поведінки є те, що токени з донорського словника можуть мати інші байтові ланцюжки merges. Наприклад, токен «м» може збиратися в merges як [ĠÐ + ¼] або як [Ġ + м], і як наслідок усі подальші токени, що починаються на «м», матимуть різні ланцюги, які кодують одні й ті самі слова.
Наслідком цього є доволі жорстка взаємозалежність токенів, що створює серйозний виклик для другого пункту нашого початкового плану, а саме: «Заміна частини токенів в існуючому токенізаторі на українські». Щоб розв’язати цю проблему, я зробив велику кількість спроб створити алгоритм мерджингу, який «нанизує» нові українські токени на існуючі байтові послідовності, змінюючи їхню схему мерджингу.
Але, на жаль, універсального підходу виробити на той момент не вдалося, і в результаті частину merges довелося «перепаковувати» вручну — прямо у файлі! Умовно прописувати заміни на кшталт: Ġ + м → ĠÐ + ¼. Таким чином в AYAYA-токенізаторі залишилося близько 200 «мертвих» токенів, які присутні в словнику, але через конфлікт побудувати ланцюг merges для них було неможливо. Пізніше мені вдалося розробити підхід, який дозволяє універсально патчити існуючі байтові токенізатори, але про нього я розповім окремо.
Для AYAYA-tokenizer я замінив 118 985 токенів на українські, що покращило ключову метрику — fertility rate (середню кількість токенів для кодування одного слова) — на 27%: з 2.226 до 1.62. Тобто словник дозволяє генерувати українську мову на 27% швидше (на практиці — ще швидше через специфіку роботи KV-cache), а кількість інформації, яка поміщається в максимальне контекстне вікно, також зростає на 27%.
Tereshchenko Blue tokenizer
Під час роботи над адаптацією токенізатора для моделей aya, наприкінці травня 2025 року, мене запросили долучитися до команди розробників, що працювала над створенням української LLM — проєкту, для якого я пізніше запропонував назву Lapa, на честь Валентина Лапи — співавтора методу групового урахування аргументів. Як основу для майбутньої української мовної моделі було обрано Gemma-3.
Перед адаптацією наступного токенізатора я дійшов висновку, що тактика обрізати токени лише за частотністю, залишаючи найбільш імовірні, щоб звільнити місце під українську мову, — не найкращий підхід.
Як я писав на початку, ми очікуємо, що українська LLM на якомусь стерпному рівні знатиме англійську та мови Єврозони. Якщо ми виріжемо токени цих мов — навіть низької ймовірності, — це вплине на якість для відповідних систем письма. Також, оскільки токенізатори великих LLM навчаються на мультимовних корпусах даних, там представлені й інші мови, окрім англійської та європейських. Тому я вирішив перевірити, чи можу сконцентруватися на вирізанні саме тих писемностей, які належать до країн і народів, що географічно, культурно та економічно менше пов’язані з Україною, — і видалення яких меншою мірою вплине на потенціал застосування української LLM.
Одна з проблем: базуючись лише на символах і словах, ми не можемо точно детектувати мову — лише систему письма.
Щоб розв’язати це, скориставшись інструментами бібліотеки unicodedataplus, я ідентифікував 22 системи письма (в дужках — типові мовні групи, які вони покривають):
Han (китайська; також японська kanji), Hiragana (японська), Katakana (японська), Hangul (корейська), Arabic (арабська, перська, урду), Devanagari (гінді, непальська, маратхі), Bengali (бенгальська, асамська), Hebrew (іврит, їдиш), Gurmukhi (панджабі), Gujarati (гуджараті), Oriya/Odia (одія), Tamil (тамільська), Telugu (телугу), Ethiopic/Ge’ez (амхарська, тигринья), Kannada (каннада), Myanmar (бірманська та споріднені), Malayalam (малаялам), Thai (тайська).
Саме їх я вважав такими, що можуть бути заміненими без значного обмеження потенційного використання моделей. Водночас повністю залишити й не видаляти я вирішив скрипти для таких мов, як вірменська, грузинська, турецька та грецька, оскільки бачу потенційні сценарії використання для роботи з економічними та дипломатичними документами.
Далі, оскільки оригінальний токенізатор gemma-3 оперує на рівні символів, а не байтів (як aya), провести заміни в ньому було нескладно. Прибравши частину токенів з кожної системи письма, яку я визначив для скорочення (я залишав близько 20% для кожної, щоб зберегти базові можливості токенізації на всіх мовах), а також усі кириличні токени (їх було 13 398 в оригінальній gemma-3), загалом я звільнив близько 81 492 позицій під українську мову й замінив їх на токени, згенеровані на основі корпусу Malyuk.
Це покращило fertility rate (середню кількість токенів для кодування одного слова) на 35% — з 2.506 до 1.628.
Такий точковий підхід до видалення токенів, який включає аналіз словника на представленість різних писемностей, вважаю доволі просунутим способом підготовки токенізатора до навчання LLM, враховуючи її реальне потенційне застосування.
Важливі доповнення
В огляді я концентрувався на обчислювальному аспекті проблеми, відштовхуючись від здатності токенізаторів стискати текст. Окремої уваги заслуговує тема: залежності між розміром моделі, розміром словника та фінальною якістю. Є ряд досліджень (1, 2), які демонструють, що збільшення кількості токенів не завжди призводить до покращення результатів.
Також уже після завершення роботи над Tereshchenko Blue, на конференції UNLP2025 була представлена публікація «From English-Centric to Effective Bilingual: LLMs with Custom Tokenizers for Underrepresented Languages», у якій описано аналогічний спосіб модифікації існуючого токенізатора, але зі збереженням лише латинських токенів — на прикладі двох токенізаторів Mistral та Gemma-2. Обидва оперують на рівні цілих символів, а не байтів.
Ідея цього напряму розробок уперше з’явилась у моєму авторському блозі Задуха. Нещодавно я також опублікував там ще два токенізатори для новіших
Сподобалась стаття? Підписуйтесь на автора, щоб отримувати сповіщення про нові публікації на пошту.
8 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів