Продолжение статьи «Как создать переводчик для низкоресурсного языка».
Год назад я рассказывал, как дообучил две модели MarianMT: одну для перевода с русского на кабардинский, другую обратно. Сейчас вместо двух моделей одна, на 61 млн параметров, и она переводит в обе стороны. На FLORES-200 русско-кабардинский перевод вырос с 38.7 до 57.4 chrF, кабардинско-русский с 35.0 до 50.2. Для сравнения: в среднем по 30 «большим» языкам многоязычная SMaLL-100 даёт 47–48. Обучение заняло 6 часов на одной арендованной RTX 4090 и обошлось меньше чем в 10 долларов. Всё остальное делалось на Mac mini.
Архитектура почти не изменилась. Изменился корпус: как его собирали, чистили и как модель потом сама его доращивала. Об этом статья.
Пара слов о местоимениях. «Я» в тексте означает меня, автора и носителя языка: я ставил задачи и писал промты, утверждал алгоритм и блок-схему работы, читал выборки и принимал решения. Носитель кабардинского, который проверяет эталон и выборки дальше по тексту, тоже я, по совместительству. «Мы» означает меня и Claude Code, который писал код по промтам, гонял проверки и выкладывал мне результаты на просмотр.
Коротко
Модель kubataba/ru-kbd-bidirectional: MarianMT 6+6 слоёв, свой токенизатор ru+kbd, направление задаёт метка
>>kbd<<или>>ru<<.Два открытых синтетических корпуса, уже версии v2: 584 тыс. настоящих кабардинских предложений с русским переводом и 586 тыс. настоящих русских с кабардинским.
На iPhone модель занимает 80 МБ (int8 ONNX), тратит 50–100 мс на предложение и уже работает в приложении SayFable.
Схема алгоритма целиком

Корпус готовил Claude Code, решал носитель
Код подготовки корпуса писал Claude Code: инвентарь корпусов, детекторы мусора, классификатор языка, фильтры синтетики, всего около сорока скриптов. Моя роль сместилась: для прошлой модели код я писал сам, а теперь читал выборки, которые он выкладывал по каждому правилу, и решал, что чинить, а что выбрасывать. Правило работы было такое: до прогона записать ожидание, а после прогона сначала смотреть на примеры и только потом верить числу. Самые важные находки появлялись там, где одно с другим не сходилось:
«Мусор с цифрами» оказался потерянными числами. При нарезке на предложения в кабардинской стороне пропадал год: «В 1965 году…» → «гъэм…». Такие пары учат модель выбрасывать даты. 2 387 пар мы починили, 1 285 выбросили.
Тест протёк. 78% тестовых пар adiga встречаются в обучающих корпусах. Поэтому мерили на FLORES-200: 200 предложений русской части переведены на кабардинский Яндекс Переводчиком. Первые 50 я вычитал как носитель и исправил ошибки в 10 из них (потерянное отрицание, перестроенные фразы). Пересчёт по исправленному эталону изменил итог не больше чем на 0.1 chrF, так что и остальным 150 можно доверять. Сам эталон мы не публикуем и не использовали эти предложения в обучении: они служат только внутренней проверкой.
Под видом кабардинского встречался адыгейский. Классификатор на символьных n-граммах (точность 0.999) убрал 860 пар.
Из 307 тыс. пар осталось 300 984. Это немного, но такой набор уже можно растить.
Как выглядел промт: ожидание до прогона против замера
Каждый промт начинался с того, что мы ждём увидеть, и эти числа записывались до прогона. Разошлось с замером: остановка и разбор, а не правка кода. Фрагмент из аудита корпуса:
что проверяли | ждали | вышло |
|---|---|---|
газетные названия без опоры в людском корпусе | меньше 50 пар | 0 |
«Спартак» без опоры в обратном переводе | не меньше 30% | 21% (720 из 3 361) |
перечисление в кавычках в людском корпусе | до 0.5% пар | 1 пара |
повтор фразы трижды подряд в людском корпусе | до 0.2% | 25 пар (0.008%) |
русское слово, оставленное как есть | 3–8% пар | 0.31%: люди почти не оставляют русских слов |
«демон» в корпусе | меньше 3 пар, «нет в корпусе» | 14 пар, и все кальки из прессы |
Фильтр синтетики: петли, копии, обрывы
WORD = re.compile(r"\w+") def cycling(s, times=3): """кусок из 1–6 слов повторён 3 раза подряд""" w = [x.lower() for x in WORD.findall(s)] for n in range(1, 7): for i in range(0, len(w) - n * times + 1): if all(w[i + k*n:i + (k+1)*n] == w[i:i + n] for k in range(1, times)): return True return False # синтетическая сторона против настоящей same = key(ru) == key(kbd) # перевода нет copy = (jaccard_5grams(ru, kbd) >= 0.5) & ~same # почти копия short = (len_words(ru) / len_words(kbd) < 0.4) # оборванный перевод loop = cycling(synthetic) & ~cycling(real) # петля, которой нет в оригинале keep = ~(same | copy | short | loop) # обратный перевод 620 952 → 613 258 (−1.24%): 4 985 петель, 1 637 копий, 282 обрыва
Mac mini делает всё, кроме обучения
На GPU дороже всего обходится время, потраченное на проверку идей. Поэтому на под уходил только готовый пакет, а всё остальное делалось на Mac.
Свой токенизатор. Токенизатор opus-mt резал кабардинский почти по буквам: 5.5–7.6 куска на слово против 1.5 у русского. Общий SentencePiece unigram на 32 тыс. кусков сократил это до 1.7. Тело модели перенесли из старой: вектор нового куска равен среднему векторов старых, на которые он распадается. Стоит ли он того, проверили заранее прямо на процессоре Mac: дообучили два варианта на одних и тех же 60 тыс. пар по 3000 шагов. Старая модель со старым словарём на чистом корпусе стояла на месте (38.6 → 36.7), а модель с новым словарём росла (16.6 → 20.3). Она начинала ниже, потому что векторы новых кусков ещё не обучены, но было видно, что расти ей есть куда.
Свой токенизатор и перенос весов
spm.SentencePieceTrainer.train( input="spm_input.txt", # русская и кабардинская стороны вперемешку model_prefix="spm", vocab_size=32000, model_type="unigram", character_coverage=1.0, # палочка ӏ (U+04CF) остаётся своей буквой, без подмены на I input_sentence_size=2_000_000, shuffle_input_sentence=True, eos_id=0, unk_id=1, bos_id=-1, pad_id=-1, user_defined_symbols=[">>kbd<<", ">>ru<<"]) # итог: kbd 1.73 токена на слово (было 5.5–7.6), ru 1.48 # перенос тела старой модели: вектор нового куска = среднее старых, на которые он режется new_emb[i] = old_emb[old_tokenizer_ids(piece)].mean(0) # в среднем 3.3 старых на новый # 61 млн параметров вместо 73.6 + 76.0 у двух прежних моделей
На под в итоге уехал пакет на 270 МБ с одним скриптом запуска.
Выручили одноязычные корпуса
Одного параллельного корпуса не хватило. 300 тыс. пар мало, и пары эти короткие: в среднем 6.8 слова, длиннее 15 слов лишь 1% (в FLORES в среднем 21 слово). Отсюда привычка прежних моделей терять куски длинных фраз. И проверка на Mac выше показала: одна чистка прибавки не даёт, корпус выжат.
Готовый параллельный корпус для малого языка найти трудно, а просто текст на нём легко. Им мы и расширили обучение, причём каждый раз так, чтобы настоящий текст стоял на стороне цели:
Кабардинский. Открытый корпус anzorq/kbd_monolingual: тексты сайтов adyghepsale.ru, apkbr.ru, adygabza.ru, около 11 млн слов. После нарезки и чистки (длина, адыгейский, русские вставки, повторы, пересечения с тестом и параллельным корпусом) из 967 тыс. предложений осталось 621 тыс. Их перевела на русский прежняя модель за одну ночь: 12.5 часа на Mac mini M4 без GPU. Получились пары «машинный русский → живой кабардинский» для направления ru→kbd. Набор опубликован: kubataba/kbd-ru-synthetic-backtranslation.
Русский мы собрали сами: проза XIX века (463 книги), Викитека и Википедия, всего 600 тыс. предложений, в среднем 17 слов. Фильтры: 6–40 слов, законченное предложение, без дореформенной орфографии (одна Викитека потеряла на этом 64 тыс.), без знаков ударения внутри слов. Этот текст перевела на кабардинский уже наша модель, и пары «машинный кабардинский → живой русский» пошли в направление kbd→ru. Набор: kubataba/ru-kbd-synthetic-forward-translation.
В сумме это вчетверо больше данных, чем в параллельном корпусе, и ни одной новой ручной пары.
Лестница: направления подтягивают друг друга по очереди
Обе стороны мы обучали не сразу, а по очереди. Каждый шаг улучшает одно направление, и уже оно готовит данные для другого. Правило одно: на стороне цели всегда живой текст, синтетика только на входе. Ошибки машинного входа модель переживает, а ошибки в цели выучила бы как норму.
Сначала кабардинский корпус переводим на русский. Переводила прежняя модель kbd→ru из первой части. Её основа, Helsinki-NLP/opus-mt-en-ru, изначально училась писать по-русски, поэтому в русский она переводит увереннее. (Тело новой общей модели мы взяли из прежней ru→kbd, у которой основа opus-mt-ru-uk.) Пары «машинный русский → живой кабардинский» подтягивают слабое направление ru→kbd: 38.7 → 47.6.
Окрепшей моделью переводим русский корпус на кабардинский. Теперь пары «машинный кабардинский → живой русский» учат направление kbd→ru: 35.7 → 45.1.
Второй круг: те же два шага, но лучшей моделью. Кабардинский корпус переводим на русский заново: машинный русский стал чище, и ru→kbd вырастает до 55.1. Затем так же заново переводим русский корпус, и kbd→ru доходит до 49.6.
Остановились, когда метод перестал заметно работать. На последнем шаге прибавка была 1.7. Третий круг дал бы, по оценке, около полупункта, а это уже в пределах шума.
шаг | какой корпус переведён и чьей моделью | кого подтянули | ru→kbd | kbd→ru |
|---|---|---|---|---|
0 | прежние модели из первой статьи | 38.7 | 35.0 | |
1 | кабардинский → русский, прежней моделью (+ свой токенизатор, одна модель на оба направления) | ru→kbd | 47.6 | 35.7 |
2 | русский → кабардинский, моделью шага 1 | kbd→ru | 49.0 | 45.1 |
2½ | починка словаря: не было дефиса и половины латиницы («что-то» = | оба | 50.5 | 47.2 |
3 | кабардинский → русский заново, моделью шага 2½ | ru→kbd | 55.1 | 47.9 |
4 | русский → кабардинский заново, моделью шага 3 | kbd→ru | 55.9 | 49.6 |
app | int8, перевод по предложениям, сторож, постобработка | 57.4 | 50.2 |
chrF на FLORES-200 devtest (200 предложений), луч 4. Модель одна на оба направления, поэтому шаг для одной стороны немного помогает и другой.
В первой части я мерил BLEU на своём тесте (28.1 / 18.7). Здесь chrF на FLORES-200, и прежние модели перемерены тем же способом: 38.7 / 35.0.
Как это выглядит на одном предложении:
Абы Швециемрэ нэгъуэщӏ къэрал 28-мрэ папщӏэ маркэ 1000-м щӏигъу къызэригъэпэщащ. было: «Для этого Швеция и другие 28 стран создали более 1000 марок.» стало: «Он создал более 1000 марк для Швеции и 28 других стран.»
«Он сказал, что создал дверной звонок, работающий от WiFi.» было: Абы жиӏащ Чехоххехейкӏэ лажьэ бжэ уэзджынэр ищӏауэ стало: Абы жиӏащ WiFi игъэлажьэ бжэ уэзджынэр къызэригъэщӏар
В первом прежняя модель перевернула смысл: марки у неё создают страны. Во втором вместо WiFi появилось несуществующее слово, а новая модель латиницу переносит как есть.
Настройки обучения (шаг 3, второй круг)
python train_marian.py train --init out/bi3/final \ --train sets/train_408.tsv --dev flores200.tsv --dir both \ --extra sets/bt2_ru_kbd_clean.tsv sets/ft_ru_kbd_clean.tsv sets/latin_aug.tsv \ --extra-dir ru-kbd kbd-ru both \ --steps 15000 --lr 1e-4 --batch 128 --eval-steps 1000
# внутри Seq2SeqTrainer: Seq2SeqTrainingArguments( max_steps=15000, learning_rate=1e-4, per_device_train_batch_size=128, lr_scheduler_type="inverse_sqrt", warmup_steps=0.05, weight_decay=0.01, label_smoothing_factor=0.1, max_grad_norm=1.0, bf16=True, metric_for_best_model="chrf", load_best_model_at_end=True) # ~14 шагов/с на RTX 4090, 15k шагов ≈ 20 минут
Волшебникым: русские слова на кабардинский манер
Больше всего меня удивило, как зазвучал по-кабардински «Амулет Самарканда» Джонатана Страуда. Его я перевёл в приложении целиком: 3 956 абзацев, 13 тыс. предложений, 716 тыс. знаков. На iPhone 15 Pro Max это заняло 19 минут, а по-кабардински книга звучит примерно 10–12 часов. Перевод идёт раз в тридцать пять быстрее, чем её можно прослушать. Я ждал подстрочника, а услышал художественный текст. Было очень странно слушать про джиннов и волшебников на родном языке так, будто книгу переводил профессиональный переводчик. Кажется, помогла русская проза, на которой училась модель: она нашла ей соответствие и в кабардинском.
Конечно, слова, которых модель не знает, она переносит из русского. Но не оставляет их как есть, а склоняет по-кабардински и встраивает во фразу так органично, что слух не спотыкается:
«Волшебник никогда не должен забывать о своих манерах.» Волшебникым зэи зыщигъэгъупщэ хъунукъым и зыӏыгъыкӏэр.
«Натаниэль знал это заклинание — он давным-давно его выучил.» Натаниэль ищӏэрт а заклинаниер — ар куэд щӏауэ зригъэщӏащ.
«Волшебник» и «заклинание» получили кабардинские окончания, а «манеры» модель передала родным словом. Этому её специально никто не учил, она вывела это из корпуса сама: волшебникым, заклинаниер, театрым, университетым щеджащ. Так говорят носители, и в людской части корпуса пишут так же. Такие заимствования (их нашлось 677) я решил не трогать.
Не подумайте при этом, что без русских слов модель переводить не умеет. Где родные слова есть, она пишет на чистом кабардинском. Вот, например:
«Вдруг все окна заброшенного дома на том берегу озарились тошнотворным зелёным светом, неясные тени принялись сновать там в поисках вора.» Арыххэу а ӏуфэм ӏут унэ бгынэжам и щхьэгъубжэ псори нэху пӏащӏэкӏэ зэщӏэнащ, жьауэ мыӏупщӏхэм дыгъуакӏуэ лъыхъуэу абдеж щызэтраублэжащ.
Плохо, когда калька встаёт вместо распространённого родного слова: «самолётым» при кхъухьлъатэм, «демоныр» при иблисыр. Я отметил на странице проверки 26 таких слов. Теперь после модели работает замена с сохранением формы («самолётым» → «кхъухьлъатэм»), и срабатывает она, только если слово действительно было в русском исходнике.
Приложение как стенд: откуда взялся «Спартак»
FLORES содержит всего 200 предложений, а целая книга находит то, чего тест не видит. Первый перевод «Амулета» на телефоне показал и изъян: модель вставляла в художественный текст газетные названия, которых в оригинале не было. Они появились в 30 абзацах из 3 955, а один только «Спартак-Налшык» встретился 62 раза в 20 абзацах. Причина нашлась в корпусе. Модель, делавшая обратный перевод газет, выбрасывала названия:
Кӏэщӏу жыпӏэмэ, “Спартак-Налшыкым” мы гъатхэм бэнэныгъэ гуащӏэ къыпэщылъщ → «Коротко говоря, эта весна предстоит ожесточенной борьбе» (машинный перевод дословно, с его грамматикой; по смыслу: «Коротко говоря, этой весной „Спартак-Нальчик“ ждёт ожесточённая борьба»)
Таких пар оказалось 1 680, и каждая учила писать «Спартак» из ничего. В приложении это ловит сторож: если в переводе появилось название без опоры в исходнике, пропало число или повторяется фраза, предложение переводится заново другим декодером. Те же правила почистили корпус, и обе версии наборов на Hugging Face обновились до v2. В обратном переводе отсеяно 7 426 пар. В прямом 16 465 дефектных пар переведены заново, 2 203 отсеяны. В обоих наборах исправлены кальки.
Цикл замкнулся: модель доращивает корпус, приложение находит её ошибки, правила из приложения чистят корпус. Следующее дообучение пойдёт на v2. Рассчитываем, что оно уберёт часть ошибок, которые пока исправляет постобработка, и немного поднимет качество. А раз сторожу реже придётся переводить предложения заново, подрастёт и скорость.
Приземление на iOS, или притча о компрачикосах
У Гюго компрачикосы сажали ребёнка в фарфоровую вазу, и он вырастал в её форме. С моделью для телефона то же самое, форму задаёт сосуд: ONNX Runtime в Objective-C, сотня мегабайт памяти, без Python и без GPU. Задача была втиснуть модель так, чтобы она продолжала переводить. Каждая стенка вазы обошлась в находку:
int8 поканально, и до слияния декодеров.
quantize_dynamicне заходит в ветвиIf: файл записывается, размер не меняется, ошибки нет. Квантование стоило 0.1 chrF, архив весит 80 МБ.В Objective-C API ONNX Runtime нет bool: флаг ветви декодера сделан
int32сCastвнутри графа.Токенизатор на Swift. SentencePiece unigram переписан с нуля и читает
.spmнапрямую; номера токенов совпали на 100% на 3 792 трудных строках.Луч 4 идёт одной пачкой из четырёх гипотез: 90–99 мс на предложение на iPhone 15 Pro Max, жадный поиск 49–55 мс.
autoreleasepoolна каждый шаг декодера: без него первый прогон система убивала по памяти.Совпадение токенов не мера качества. ONNX Runtime одной версии на macOS и iOS расходится на почти-ничьих (дословно 134 из 200), а качество то же: 55.7 chrF на телефоне против 55.4 на Mac.
Вокруг модели работает тонкий слой правил. Палочку приводим к одному символу U+04CF на входе и выходе. Переводим по предложениям, прямую речь в кавычках отдельно, иначе модель копирует её по-русски. Дальше сторож и замена калек. Английский идёт цепочкой: MADLAD-400 en→ru, затем наша модель. Так мы перевели рассказ о Шерлоке Холмсе, и я прослушал его голосом Silero. MADLAD, правда, перевёл «Пёструю ленту» на русский как «Приключения пятнистого мальчика», пришлось поправить вручную.
Ваза вышла в шесть раз меньше двух прежних моделей, а модель в ней по-прежнему переводит. Пожалуй, единственный случай, когда компрачикосом быть не стыдно.
iOS-модели на компьютере: Kabardian Translator 3
Ещё до первой части я выкладывал на Хабре Kabardian Translator: локальный переводчик с озвучкой моим голосом. Он работал на PyTorch, скачивал около 15 ГБ моделей и занимал до 8 ГБ памяти, так что жил только на Mac с 16 ГБ памяти минимум. После iOS он получил третью версию: в него переехали ровно те же файлы, что работают в SayFable, и вместе со всеми моделями он теперь занимает на диске 0.8–1.7 ГБ.
Русский ↔ кабардинский: модель из этой статьи вместе со сторожем и заменой калек.
Остальные пары: два многоязычных переводчика, тем же методом компрачикосов облегчённые до нужных нам языков. На Mac с Apple Silicon это MADLAD-400 3B в Core ML на Neural Engine: 37 языков, было 11.8 ГБ, стало 1.3 ГБ. На Windows и Linux SMaLL-100 в int8 ONNX: 30 языков, было 1.3 ГБ, стало 289 МБ. Кабардинский с любым языком идёт через русский.
Озвучка: Silero v5 на ONNX без PyTorch, наша балтийская модель синтеза (латышский, литовский, эстонский, 18 голосов) и системные голоса Apple и Windows.
Метод компрачикосов сработал и здесь: модели почти не потеряли в качестве, а размер и требования к ресурсам упали примерно в десять раз. Из монстров, которым место в облаке на видеокартах, они превратились в программу для самого простого ноутбука с 4 ГБ памяти, без видеокарты и вообще без PyTorch: всё работает на ONNX Runtime, а на Mac ещё и на Neural Engine.
По скорости все три быстрые. На компьютере, будь то Mac или Windows, наша модель и SMaLL-100 тратят на предложение около 0.1 секунды, MADLAD около секунды. Даже самый медленный из них переводит быстрее, чем предложение успевают прочитать вслух.
Изначально это был кабардинский переводчик, при котором многоязычная модель служила подпоркой: довести любой язык до кабардинского через русский мост. Теперь он сам по себе стал переводчиком с озвучкой для более чем 30 языков, и пригодится не только кабардинцам, но и тем, кто говорит или учится говорить на других небольших языках: латышском, литовском, эстонском, татарском, башкирском, казахском. И это уже не только Mac: переводчик работает на macOS, Windows и Linux и ставится одной командой pip install kabardian-translator. Код: github.com/kubataba/kabardian-translator.
Что внутри третьей версии
пакет | что | размер | на предложение |
|---|---|---|---|
ru↔kbd | наша модель, int8 ONNX | 80 МБ | ≈0.1 с |
MADLAD-400 3B | Core ML, 4 бита, словарь урезан до 135 801 куска из 256 000; только Mac с Apple Silicon | 11.8 ГБ → 1.33 ГБ | ≈1 с |
SMaLL-100 | int8 ONNX, словарь урезан до 67 166 токенов из 128 104; Windows, Linux, Intel Mac | 1.33 ГБ → 289 МБ | ≈0.1 с |
Silero v5 | ONNX, плюс ударения и омографы для русского | 88 + 30 МБ | |
балтийская модель | Piper, латышский, литовский, эстонский | 94 МБ |
Размер «было» указан по исходным весам на Hugging Face (google/madlad400-3b-mt, alirezamsh/small100). Скорость примерная, на Mac и Windows.
Все пакеты берутся из релизов kubataba/sayfable-models, теми же файлами, что использует приложение, и проверяются по SHA-256.
Код приложения перенесён со Swift на Python и сверен построчно: текстовый слой и фонемизаторы балтийской модели совпадают со Swift на 600 контрольных строках и 3 036 предложениях FLORES, ударения Silero на numpy совпадают с оригиналом на 200 из 200 предложений, токенизатор кабардинской модели совпадает с MarianTokenizer на 800 из 800 строк.
Пивоты выбраны замером, а не на глаз: MADLAD переводит башкирский, белорусский, татарский, таджикский и грузинский через русский, а русский → армянский и русский → турецкий через английский.
SMaLL-100 легче и примерно в десять раз быстрее MADLAD, но в среднем слабее на 5–10 chrF. На Mac движок переключается одним щелчком.
Тексты любой длины и документы
.txt,.md,.docx; сохранение перевода или двуязычного файла по предложениям. Интерфейс на русском, английском и латышском.
Сколько это заняло бы без Claude Code
Честный ответ: без Claude Code этой модели не было бы вовсе. Через фильтры прошло больше 2 млн машинно переведённых предложений: два круга по 600 тыс. в каждую сторону. Эти 600 тыс. сами отобраны фильтрами: кабардинские из почти миллиона предложений, русские из нескольких миллионов. Плюс 300 тыс. пар параллельного корпуса. Вычитать и исправить их вручную мне не хватило бы жизни: даже по 10 секунд на предложение это около 6 700 часов, больше трёх лет работы без отпусков, а с исправлением в разы больше.
Значит, нужны автоматические фильтры, а их код тоже кто-то должен написать. Всё описанное заняло три дня, с 3 по 5 октября. За это время написано около 6 200 строк Python и shell (36 скриптов: чистка, детекторы, обучение, экспорт, проверки) и около 4 000 строк Swift вместе с тестами для приложения. Чтобы написать одни только скрипты фильтрации самому, мне пришлось бы бросить все дела примерно на полгода, а если бросить совсем всё, то месяца на три. Главное даже не скорость: половину проверок вручную я бы просто не стал делать, потому что детектор ради одной гипотезы обходится дороже самой гипотезы. Так и вышло с прошлыми моделями. Чистки корпуса тогда были простые (длина, дубли, русизмы), и их код я написал сам за пару дней, вместе с кодом обучения, а гонял всё на Colab. Модель получилась слабой, и я её бросил: сил на вычитку корпуса просто не было. Здесь детектор появлялся за минуты, вместе с выборкой для проверки.
Но скорость требует проверки: «правило выглядит верным» и «правило верно» не одно и то же. Правило «палочка рядом с цифрой означает единицу» вылечило годы вроде «ӏ928гъэм» и заодно сломало химические формулы: йодид алюминия «AlI3» превратился в «Al13». Поймала это построчная сверка v1 и v2, а не тесты. Поэтому решение по каждому правилу оставалось за человеком, который читает по-кабардински.
Главный вывод для малых языков: качество ограничивает не размер модели, а аккуратность корпуса. При современных инструментах малоресурсный язык требует нескольких дней работы и меньше 10 долларов за аренду GPU (при условии, что у вас уже есть подписка Claude Pro с Claude Code).
Модель лежит на Hugging Face: kubataba/ru-kbd-bidirectional. Для некоммерческих целей она бесплатна (лицензия CC-BY-NC-4.0): учитесь, исследуйте, переводите. Если при этом у кого-то созреют коммерческие идеи, напишите мне личное сообщение на Хабре, обсудим.
Благодарности
Спасибо Анзору Кунашеву за открытый корпус кабардинских текстов (anzorq/kbd_monolingual). На нём выросло направление ru→kbd, а через него и обратное.
Спасибо Борису Орехову за открытый набор русской прозы XIX века (nevmenandr/accentual-syllabic-verse-in-russian-prose). Из него взята художественная часть русского корпуса, на которой выросло направление kbd→ru.
Без открытых корпусов этой работы не было бы.
Спасибо Anthropic за Claude Code и модель Claude Opus. Для меня это лучший инструмент 2026 года: он изменил то, как мы обучаем модели и создаём приложения.
Ссылки
Модель: kubataba/ru-kbd-bidirectional (CC-BY-NC-4.0)
Корпуса: kbd-ru-synthetic-backtranslation (MIT), ru-kbd-synthetic-forward-translation (CC-BY-SA-4.0)
Исходный параллельный корпус: adiga-ai/circassian-parallel-corpus; кабардинские тексты: anzorq/kbd_monolingual; русская проза XIX века: nevmenandr/accentual-syllabic-verse-in-russian-prose
SMaLL-100 (ориентир качества по 30 языкам): github.com/alirezamshi/small100, статья arXiv:2210.11621 (EMNLP 2022)
Kabardian Translator 3 (переводчик и озвучка на компьютере): github.com/kubataba/kabardian-translator; статья о первой версии: habr.com/ru/articles/972350
Предыдущие статьи на Хабре: «Локальный переводчик и синтез голоса на кабардинском языке» (первая версия Kabardian Translator); «Как создать переводчик для низкоресурсного языка» (часть 1); «SayFable — офлайн-библиотека с NLP-разметкой, синтезом на 68 языков и чатом по книге»

