Downloads · 30 days
102
5% of all-time downloads
skypro1111/gemma-3-270m-uk-verbalizer
gemma-3-270m-uk-verbalizer is a text generation model from skypro1111. Use it when you need the model to write or continue text. It is set up for transformers. The card lists the license as gemma.
Перетворює письмовий український текст на те, як його вимовляють: числа, дати, час, гроші, одиниці, скорочення, коди, телефони, IBAN, домени, пошту, римські цифри, латинські вкраплення. Це передостанній крок у конвеєр…
Downloads · 30 days
102
5% of all-time downloads
All-time downloads
1.9K
Public
Parameters
125M
2.2 GB on disk
Likes
3
Public
Click a slice to open those files.
.onnx600 MB · 54%
From the Hugging Face model README
Перетворює письмовий український текст на те, як його вимовляють: числа, дати, час, гроші, одиниці, скорочення, коди, телефони, IBAN, домени, пошту, римські цифри, латинські вкраплення. Це передостанній крок у конвеєрі синтезу мовлення — те, що подають у TTS замість сирого тексту.
Рахунок на 1111 386,40 грн треба сплатити до 25.07.2026.
→ Рахунок на один мільйон сто одинадцять тисяч триста вісімдесят шість гривень
сорок копійок треба сплатити до двадцять п'ятого липня дві тисячі двадцять
шостого року.
Пиши на [email protected] або дзвони 067 123 45 67.
→ Пиши на супорт ет шоп крапка ком крапка юа або дзвони нуль шістдесят сім
сто двадцять три сорок п'ять шістдесят сім.
Борг сягнув 2625414042726 гривень.
→ Борг сягнув двох трильйонів шестисот двадцяти п'яти мільярдів чотирьохсот
чотирнадцяти мільйонів сорока двох тисяч семисот двадцяти шести гривень.
Модель відмінює числівники за контекстом («при двох тисячах восьмистах
тридцяти відвідувачах»), узгоджує їх з іменником і розрізняє конвенції
читання за рамкою: вітамін D → «вітамін де», але роз'єм типу C →
«роз'єм типу сі».
| формат | шлях | розмір | для чого |
|---|---|---|---|
| safetensors (bf16) | корінь | 250 МБ | transformers, дотренування |
| GGUF (F16) | gguf/model-f16.gguf | 250 МБ | llama.cpp — найшвидший шлях на GPU |
| ONNX (з KV-кешем) | onnx/ | ~600 МБ | onnxruntime, CPU-інференс, edge |
| пруноний SPM | tokenizer.model | 725 КБ | без нього GGUF-конвертація розсипається |
| код | normalize.py, digit_router.py, bignum_spacing.py | — | повний конвеєр |
| скрипти | scripts/ | — | прунінг і обидва експорти, відтворювано |
from normalize import Normalizer # normalize.py лежить у цьому репозиторії
n = Normalizer("skypro1111/gemma-3-270m-uk-verbalizer")
print(n("Зустріч о 08:05 у кабінеті 12."))
# Зустріч о восьмій нуль п'ять у кабінеті дванадцять.
Без обгортки — але тоді самі поставте кроки препроцесингу (див. нижче):
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
tok = AutoTokenizer.from_pretrained("skypro1111/gemma-3-270m-uk-verbalizer")
model = AutoModelForCausalLM.from_pretrained("skypro1111/gemma-3-270m-uk-verbalizer", dtype=torch.bfloat16).cuda().eval()
bad = [[i] for t, i in tok.get_vocab().items() if any(c.isdigit() for c in t)]
ids = tok.apply_chat_template([{"role": "user", "content": "Ціна 1250 грн."}],
add_generation_prompt=True, return_tensors="pt").cuda()
out = model.generate(ids, max_new_tokens=192, do_sample=False, bad_words_ids=bad)
print(tok.decode(out[0, ids.shape[-1]:], skip_special_tokens=True))
У репозиторії лежить gguf/model-f16.gguf і tokenizer.model — пруноний
SentencePiece, потрібний, якщо конвертуватимете самі. Без нього
convert_hf_to_gguf.py іде BPE-гілкою і токенізація розсипається.
llama-server -m gguf/model-f16.gguf --host 127.0.0.1 --port 8901 \
-c 8192 --parallel 4 -ngl 99 -fa on
Рекомендований декод — greedy плюс заборона цифрових токенів:
{"messages": [{"role": "user", "content": "<текст>"}],
"temperature": 0, "top_k": 1, "n_predict": 192,
"logit_bias": [[<кожен токен із цифрою>, -100]]}
Це найшвидший шлях із трьох, і з великим відривом — див. таблицю нижче.
Граф експортовано з KV-кешем, тому декод лінійний, а не квадратичний.
from optimum.onnxruntime import ORTModelForCausalLM
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("skypro1111/gemma-3-270m-uk-verbalizer")
model = ORTModelForCausalLM.from_pretrained("skypro1111/gemma-3-270m-uk-verbalizer", subfolder="onnx")
ids = tok.apply_chat_template([{"role": "user", "content": "Ціна 1250 грн."}],
add_generation_prompt=True, return_tensors="pt")
out = model.generate(ids, max_new_tokens=192, do_sample=False)
print(tok.decode(out[0, ids.shape[-1]:], skip_special_tokens=True))
На CUDA обов'язково вимикайте io-binding — інакше граф падає з
cudaErrorIllegalAddress на embed_tokens/Mul:
model = ORTModelForCausalLM.from_pretrained(
"skypro1111/gemma-3-270m-uk-verbalizer", subfolder="onnx",
provider="CUDAExecutionProvider", use_io_binding=False)
І майте на увазі: без явного provider optimum мовчки бере CPU, навіть коли
CUDA доступна.
Заміряно на RTX 3090 Ti: один рядок за раз (саме так модель і працює — речення за реченням), greedy, до 192 нових токенів, медіана з п'яти прогонів.
| шлях | токенів/с |
|---|---|
| transformers, bf16, GPU | 27 |
| onnxruntime, fp32, CPU | 31 |
| onnxruntime, fp32, GPU | 69 |
| llama.cpp, GGUF F16, GPU | 532 |
llama.cpp швидший за transformers у 20 разів — і це не про якість реалізації матричних множень. На моделі такого розміру крок декоду впирається не в обчислення, а в накладні витрати на запуск CUDA-ядер: сама робота мізерна, а ядер на крок запускається стільки ж, скільки у великої моделі. llama.cpp тримає CUDA-графи й тому цей оверхед майже прибирає.
Звідси практичний висновок: якщо потрібна пропускна здатність — беріть GGUF. ONNX має сенс там, де llama.cpp незручний, і особливо на CPU: 31 проти 69 ток/с на GPU означає, що на процесорі модель втрачає лише вдвічі, а не в десятки разів.
Два детерміновані кроки перед моделлю. Це не косметика — без них модель провалює конкретні класи входу, і кожен фікс заміряний.
digit_router перехоплює голий рядок цифр і читає його поцифрово, повз
модель. Причина: рахувати однакові цифри поспіль модель не вміє — 000000
перетворюється на чотири «нуль».
bignum_spacing вставляє роздільники розрядів у довгі числа. Парний зонд
на тому самому наборі чисел: суцільним рядком 4/14, з пробілами 11/14.
Провалюються саме числа з довгими серіями нулів (7000000000 → «сімсот
мільярдів»). На пакеті великих чисел цей крок дав 13/20 → 20/20.
Обидва модулі (digit_router.py, bignum_spacing.py) лежать у корені
репозиторію поруч із normalize.py і не мають залежностей, крім стандартної
бібліотеки. У кожного всередині є самотести: python bignum_spacing.py.
Тестові набори вирізані з тренувальних даних і виключені з тренування; еталони в них канонічні за побудовою. Числа нижче — чиста модель, greedy, без препроцесингу.
| зріз | результат |
|---|---|
| 139 доменів, збалансовано (2564 рядки) | 91.7% |
| якір, змішані складні речення (557) | 98.2% толерантно · 91.2% суворо |
| транслітерація (380: бачені й небачені слова) | 75.5% |
| узгодження числівник+іменник (63) | 100% |
Розкладка по 139 доменах є в датасеті. Найсильніше: час, дати, узгодження, складені числівники у відмінках, гроші — 95-100%. Найслабше — див. обмеження.
Для орієнтира, як це росло за шість ітерацій даних на тих самих тестсетах: домени 87.6% → 89.8% → 91.0% → 91.7%; транслітерація 31.8% → 55.0% → 61.8% → 75.5%. Майже весь приріст дали зміни в даних, а не в архітектурі чи гіперпараметрах.
Останню ітерацію робили не заради агрегату, а за живими баг-репортами, і
там ефект зовсім іншого масштабу. Зріз із 22 повідомлених поломок (одиниці
вимірювання, дефіс у ролі тире, латинізовані українські імена):
22.7% → 95.5%. Класи, яких у даних не було зовсім, модель не просто
плутала з сусідніми — вона вигадувала неіснуючі слова: 85 дБ читалось як
«дбайів», 200 кПа як «кіпатих». Коли покриття з'явилось, це зникло.
Транслітерація — відкритий клас. Модель вивчила список із ~12.7 тис. слів, а не правило. На небачених англійських словах точність близько 50%, і щодня з'являються нові назви продуктів. Це стеля підходу, а не дефект ваг.
Одне речення за раз. Навчена на окремих реченнях; n_predict=192 обрізає
довгий абзац. Ріжте текст на речення перед подачею.
Найслабші домени: римські цифри в неканонічному записі, списки з кількох англійських слів поспіль (композиція: сім слів по 85% дають 0.85⁷ ≈ 32%, окремої вади немає), голий фрагмент-email, наука й техніка.
Діапазон без пробілів. 5-7 млн модель зараз читає як «п'ять — сім
мільйонів», хоча усталене — «від п'яти до семи». Це наслідок того, що в
останню ітерацію додали читання дефіса як тире й не звірили його з наявною
конвенцією діапазонів. Виправляється наступним колом даних.
Стеля метрики нижча за 100%. Шість незалежних аудиторів розсудили 425 спірних випадків: 360 на користь моделі проти еталона, 26 на користь еталона, 39 «обидва прийнятні». Тобто близько 15% «помилок» — дефекти самих еталонів, неминучі там, де норма допускає два читання.
Базова gemma-3-270m-it має словник на 262 144 токени. Для української це
абсурдно багато: матриця ембедингів 262144 × 640 — це 168M параметрів із
270M, тобто 62% моделі витрачено на словник, більшість якого ніколи не
трапиться в українському тексті.
Словник урізано до 38 651 токена. Прунінг тут — чиста хірургія, без жодного дотренування:
index_select потрібних рядків із embed_tokens і lm_head,
resize_token_embeddings(len(keep)), копіювання рядків на місце.bos/eos/pad у конфігу за таблицею old → new.Ключова властивість: збережені рядки не змінюються, тож логіти для
збережених токенів ідентичні (max |diff| = 0). Якість не падає ні на йоту,
і дотренування не потрібне. Токени, що випали, покриває byte_fallback —
заміряно: інфляція довжини послідовності +0.10%, поломок 0.
Результат: 270M → ~125M параметрів, −53% ваг за нуль GPU-годин.
Дві пастки, на які варто зважати, якщо повторюватимете:
Merges треба замикати за фікспоінтом. Викинувши токен, ви можете зламати
BPE-злиття, яке відтворювало написання захищеного слова. Тому keep-set
ітеративно розширюється (до 10 проходів): токенізуємо список захищених рядків
старим і новим токенізатором і повертаємо в keep усе, чого бракує для
точка-в-точку однакової токенізації. Захищали римські цифри (II…XL) і
абревіатури, що читаються як слово (НАТО, ООН, США, NASA, IKEA).
generation_config.json ремап НЕ зачіпає. Хірургія править config.json,
а generation_config.json лишається від бази — з eos_token_id: [1, 106].
Після прунінгу <end_of_turn> має id 7, а 106 — це вже <sup>. Наслідок:
generate() не має на чому зупинитись, видає правильну відповідь і продовжує
молоти до ліміту токенів. У цьому репозиторії виправлено (eos_token_id: [1, 7]),
але якщо прунитимете самі — перелічуйте EOS заново через
tok.convert_tokens_to_ids("<end_of_turn>"), а не копіюйте з бази.
Скрипт: scripts/apply_surgery.py разом із scripts/keepset.json. Він
застосовується до будь-якого чекпоінта з оригінальним словником Gemma 3 —
хоч до бази, хоч до вже навченої моделі:
python scripts/apply_surgery.py --in google/gemma-3-270m-it --out ./pruned
Робиться штатним convert_hf_to_gguf.py з llama.cpp, але з однією умовою,
без якої результат мовчки зіпсований.
llama.cpp читає цей словник правильно лише в SPM-режимі, а той вмикається
тільки за наявності tokenizer.model (Gemma3Model.set_vocab()). Без нього
конвертер іде BPE-гілкою з GPT-2 byte-level декодером — і токенізація
розсипається: 1 збіг із 52, декод дає сміття («ĸ14» замість «Зустріч»).
Помилки при цьому немає, файл конвертується «успішно».
Готового SPM на 38 651 після прунінгу не лишається, тож його треба зібрати: взяти оригінальний Gemma-3 proto з усіма 262k pieces та їхніми score і лишити тільки наші, строго в порядку наших ID. Score копіюються як є — у SPM саме вони задають порядок злиття, а прунінг відносного порядку не змінював.
python scripts/build_pruned_spm.py <оригінальний_tokenizer.model> tokenizer.model
cp tokenizer.model <тека_моделі>/
python convert_hf_to_gguf.py <тека_моделі> --outfile model-f16.gguf --outtype f16
Перевіряйте токенізацію після конвертації — HF і SPM мають давати
ідентичні ID один-в-один. Без збігу 52/52 результат вважати недійсним.
Готовий tokenizer.model лежить у корені цього репозиторію.
Навіщо взагалі GGUF: це найшвидший шлях із трьох, 532 токени/с проти 27 у transformers — при побайтово тій самій якості (перевірено, відповіді збігаються слово в слово).
Через optimum, задача text-generation-with-past (тобто з KV-кешем — без
нього декод квадратичний):
python scripts/export_onnx.py --model <тека_моделі> --out ./onnx
Готовий граф лежить у onnx/. Він fp32, тому вдвічі більший за
safetensors — це очікувано, а не помилка експорту.
Дві речі, на які пішов час і які варто знати наперед:
provider optimum бере CPU, навіть коли CUDA доступна — мовчки;use_io_binding=False, інакше граф падає з
cudaErrorIllegalAddress на embed_tokens/Mul.Чесна засторога щодо швидкості: на GPU ONNX програє llama.cpp у 7-8 разів (69 проти 532 ток/с), і причина не в якості реалізації, а в тому, що на такому розмірі все впирається в оверхед запуску ядер, який CUDA-графи llama.cpp прибирають, а onnxruntime для цього графа — ні. Натомість ONNX добре тримається на CPU: 31 ток/с проти 69 на GPU, тобто втрата лише вдвічі. Саме тут його ніша, а не в перегонах на відеокарті.
Повний fine-tune (не LoRA), три сіди, model soup із трьох найкращих чекпоінтів.
Дані — 176 705 пар, синтетичні й детерміновано згенеровані: числівники
відмінює власний рушій ukr_num, а не мовна модель. Датасет:
skypro1111/uk-text-normalization.
Похідна від Gemma 3, тож поширюється на умовах Gemma Terms of Use. Використовуючи модель, ви приймаєте ці умови й Gemma Prohibited Use Policy.