Gemma-4-E4B · Pruned RU+EN · Q4_K_M (+ MTP draft)
Русско-английская, кодоориентированная Gemma-4-E4B, у которой вырезан «лишний» кусок словаря. Не дистилляция, не файнтюн, не abliteration — веса модели не тронуты вообще. Изменён только токенизатор и две embedding-матрицы, из которых удалены токены письменностей, которыми почти никто не пользуется в русско-английском рабочем тексте: CJK, кана, арабский, иврит, деванагари, корейский, тайский, греческий, армянский, грузинский и т. п.
262 144 → 190 421 токенов (−71 723, −27.4%)
−0.95B параметров словаря
5.13 GB (UD-Q4_K_XL оригинала) → 4.55 GB (эта модель, Q4_K_M)
Перплексия на held-out: ru −0.2%, en −0.0%, code −0.0%
🔹 Главная фишка: вся модель и 131k контекста — в 6 ГБ видеопамяти
Модель собрана под ноутбушную RTX 3060 6 ГБ. Не «уменьшена до размера 6B», а оставлена полноценной 42-слойной Gemma-4-E4B с контекстом 131 072, из которой вырезан только тот кусок, который ест память и не нужен на машине, где печатают кириллицей: 27% словаря.
Итог: полные 128k контекста + сама модель + MTP-draft + 2 параллельных слота — влезают в 6 ГБ при квантованном KV-кэше. На этой же карте оригинал в UD-Q4_K_XL (5.13 ГБ) в 128k не влезает в принципе, а в 36k влезает на грани.
Измерено на живом сервере (
--ctx-size 128512 -np 2 -ctk q8_0 -ctv q8_0, MTP-draft): 5 493 MiB из 6 144 MiB, генерация 44 t/s на коротком контексте, промпт ~1 700 t/s. На 60k контекста генерация проседает до ~19 t/s — это цена attention по длинному KV, а не предел модели.Для сравнения: smoke-замеры на CPU той же машины дают 10.4 t/s без draft и 19.4 t/s с ним.
Сколько контекста влезает в 6 ГБ при другом KV-кванте — в разделе Сколько влезает в 6 ГБ.
Да, перплексия чуть улучшилась, а не ухудшилась. Это не магия и не артефакт — это прямое следствие того, что прунинг lossless по конструкции (см. ниже), а held-out корпуса лежат внутри сохранённой части словаря.
Откуда эта модель (цепочка происхождения)
Прунить квантованный GGUF нельзя: блоки Q4_K разбиты на суперблоки 32×32, и разрезать такую матрицу «по строкам словаря» посередине блока невозможно. Поэтому прунинг делался на исходной HF-модели, а GGUF собирается и квантуется заново.
| Этап | Артефакт | Источник |
|---|---|---|
| 1. Оригинал (источник весов) | gemma-4-E4B-it, BF16, ~16 ГБ |
google/gemma-4-E4B-it |
| 2. Референсный квант (точка сравнения по размеру) | gemma-4-E4B-it-UD-Q4_K_XL.gguf, 5 126 306 944 B |
unsloth/gemma-4-E4B-it-GGUF |
| 3. Прунинг словаря + embedding-весов | gemma-4-E4B-it с словарём 190 421, 13.43 ГБ |
lossless-прунинг BPE-словаря (метод — ниже) |
| 4. Сборка GGUF + квант | gemma-4-e4b-pruned-f16.gguf → gemma-4-e4b-pruned-Q4_K_M.gguf |
llama.cpp convert_hf_to_gguf.py + llama-quantize |
| 5. Draft для спекуляции | gemma-4-E4B-it-assistant, 4 слоя, hidden 256 |
google/gemma-4-E4B-it-assistant |
| 6. Прунинг draft той же маской | gemma-4-e4b-draft-pruned-ru-en-f16.gguf |
тот же keep_sorted-mask, что и у target |
Файл-референс gemma-4-E4B-it-UD-Q4_K_XL.gguf совпадает с файлом из
unsloth/gemma-4-E4B-it-GGUF побайтово (5 126 306 944 B, LFS SHA-256 3cf61de1…d7df80) —
то есть это именно unsloth-квант, а не самодельный. Он использовался как точка сравнения по
размеру, а не как источник весов: прунинг шёл от BF16-оригинала.
Почему прунинг словаря, а не слоёв
Словарь Gemma-4-E4B (262 144) участвует в двух матрицах, и обе огромные:
| Матрица | Форма | Параметров |
|---|---|---|
embed_tokens |
262 144 × 2560 | ≈ 0.67B |
per_layer_embeddings (MatFormer / Gemma-3n механизм) |
262 144 × 10 752 | ≈ 2.82B |
Итого ~3.5B из 8B модели (44%) — это словарь. Это самый дешёвый способ похудеть: он не трогает attention и MLP, то есть не трогает логику, код и рассуждение, которые живут в средних слоях. Прунинг attention-слоёв за тот же процент размера всегда стоит качества; прунинг неиспользуемых строк словаря — не стоит ничего, при условии что он сделан корректно.
Gemma-4 использует BPE-токенизатор (514 906 мержей, byte-fallback, маркер ▁) — как Qwen,
поэтому применима классическая логика lossless-прунинга. Идея взята из
Qwen-Tokenizer-Pruner, реализация
собственная, потому что оригинальный скрипт Qwen-специфичен.
Как гарантируется «lossless»
Четыре условия, все проверяются по факту, а не «на глаз»:
Сохраняются все special/служебные токены:
<pad>,<eos>,<bos>,<unk>,<|turn|>,<unused*>, byte-токены<0xNN>— их ровно 6 581, и они не удаляются никогда. Byte-fallback целая, поэтому любой редкий символ (иероглиф, эмодзи) остаётся достижимым побайтово, просто дороже по токенам.Сохраняются все токены опорного корпуса: en + ru + код. Корпус — 65 908 документов, 348.6M токенов (wikitext для en, wikipedia для ru, seed-корпус плюс реальный рабочий код для code).
Замыкание по merges: если оставлен родитель
a+b, то детиaиbобязаны остаться. Иначе BPE-алгоритм теряет часть правил и сегментация «плывёт». Дано:+1 592токена.Fixed-point проверка: опорный корпус сегментируется старым и новым токенизатором и сверяется токен-в-токен. Результат этого запуска:
Fixed-point iter 0: сегментация совпала для 65908/65908 документов; расхождений-кусков: 0Сходится с первой итерации, расхождений ноль. Это и есть формальная гарантия: для английского, русского и кода новый токенизатор выдаёт ровно те же id, что и оригинал. Никакого «переучивания» модели не требуется, поэтому веса можно не трогать.
Баланс маски:
Старый словарь: 262 144 токенов, 514 906 мержей
После фильтра письменностей: 179 952 (special/bytes 6 581)
Токены опорного корпуса: 131 502 уникальных, из них новых 8 877
Замыкание по merges: +1 592
Новый словарь: 190 421 (удалено 71 723 = 27.4%)
Сэкономлено параметров: 0.95B
HF-модель: 15.25 ГБ → 13.43 ГБ
Числа
Размер и квант
| оригинал | эта модель | |
|---|---|---|
| GGUF-квант | UD-Q4_K_XL | Q4_K_M |
| Файл | 5 126 306 944 B (4.77 GiB) | 4 548 138 816 B (4.24 GiB) |
| BPW | — | 5.53 |
| f16-промежуточный | — | 12 519.57 MiB (16.00 BPW) |
| Время квантования | — | 156 с, 8 тредов |
| Params (по подсчёту llama.cpp) | ~8B | 6 563 292 714 |
| Словарь | 262 144 | 190 421 |
Архитектура не менялась: 42 слоя, hidden 2560, GQA 8/2, sliding window 512, shared KV 18 слоёв,
final_logit_softcapping = 30, контекст 131 072, tied embeddings, general.size_label = 6.6B.
Chat-шаблон — канонический Gemma-4 (с <|turn|>-разметкой, thinking-каналом и полным
tool-calling: <|tool_call> / <|tool_response>).
Перплексия, held-out (не пересекается с опорным корпусом)
| Корпус | оригинал | прунутая | Δ | документов |
|---|---|---|---|---|
| ru | 299.60 | 299.12 | −0.2% | 8 122 |
| en | 15 066.62 | 15 059.96 | −0.0% | 1 638 |
| code | 27.48 | 27.47 | −0.0% | 5 776 |
Порог стоп-сигнала — рост PPL больше 10–15%. Здесь роста нет вообще.
Скорость на RTX 3060 Laptop 6 ГБ (замер по API на живом сервере)
Замер — не llama-cli, а llama-server с рабочей спекуляцией: запросы к /completion,
ignore_eos включён, вывод ровно 128 токенов, по два прогона на каждую строку.
Конфигурация сервера, снятая с /v1/models и /props:
model: gemma-4-e4b-pruned-ru-en-Q4_K_M.gguf ftype Q4_K - Medium
n_vocab 190 421 · n_params 6 563 292 714 · n_ctx_train 131 072
n_ctx 64 256 (на слот)
total_slots 2 → суммарный контекст 128 512
flash-attn on · -ctk q8_0 -ctv q8_0 · MTP draft активен
GPU: 5 493 MiB из 6 144 MiB (включая рабочий стол Windows)
| Контекст в слоте | Prompt eval | Generation |
|---|---|---|
| 53 (короткий чат) | 164–249 t/s | 44 t/s |
| 15 603 | 1 728–1 740 t/s | 30–34 t/s |
| 31 403 | 1 568 t/s | 27–43 t/s |
| 59 478 | 1 301–1 331 t/s | 14–23 t/s |
| 62 503 (предел слота) | 1 290 t/s | — |
Что из этого следует:
- 128k контекста на 6 ГБ — работает, и это не «на грани», а с запасом ~650 MiB.
- Генерация на коротком контексте — 44 t/s. Просадка до ~19 t/s на 60k — это не деградация модели, а цена длинного KV: attention по 60k ключей на карте среднего сегмента.
- Разброс в колонке Generation (14–43 t/s) реален, а не является ошибкой замера: он равен разбросу доли принятых черновиков. За замеры отвечает спекуляция, и она принимает от 24% до 77% предложенных draft-токенов в зависимости от текста. На связном русском тексте типично 24–30%.
- Prompt eval держится ~1300–1740 t/s во всём диапазоне до предела слота.
Скорость на CPU (справочно, из smoke-тестов пайплайна)
Это не GPU. Собрано на той же машине, но на CPU-бэкенде llama.cpp, llama-cli -st,
вывод 100 токенов, контекст 36k, KV q4_0:
| Конфигурация | Prompt | Generation |
|---|---|---|
| baseline (без draft) | 39.0–40.6 t/s | 10.0–10.4 t/s |
| + MTP draft | 49.7 t/s | 19.4 t/s |
| ускорение от draft | — | ≈ 1.9× |
Отношение draft/без-draft (≈1.9×) на CPU и на GPU совпало — это и есть главная цифра пайплайна, и она не зависит от бэкенда.
Draft-модель — официальный google/gemma-4-E4B-it-assistant (4 слоя, hidden 256,
nextn_predict_layers = 4), прунутый той же keep_sorted-маской, что и target:
embed_tokens режется по строкам, token_ordering фильтруется и ремаппится, токенизатор
копируется от прунутой target. Итог — словари совпадают id-в-id (llama.cpp требует
совпадения vocab с допуском 128 токенов).
gemma-4-e4b-draft-pruned-ru-en-f16.gguf
architecture: gemma4-assistant · 4 блока · hidden 256 · size_label 60M
vocab 190 421 = vocab target · 131 124 672 B (125 MiB)
Сколько влезает в 6 ГБ (бюджет памяти)
Разобрано по метаданным GGUF (gemma4.attention.*), контрольная точка сверена с реальным
запуском. 6 ГБ = 6 144 MiB.
Почему контекст здесь дешёвый
Архитектура Gemma-4-E4B сама по себе экономит KV-кэш, и прунинг словаря это не трогает:
| Механизм | Что даёт |
|---|---|
shared_kv_layers = 18 |
Последние 18 слоёв переиспользуют KV первых 18 → свой кэш только у 24 слоёв из 42 |
sliding_window = 512 |
20 из этих 24 слоёв — скользящие, их кэш обрезан 512 токенами и не растёт с контекстом |
key_length_swa = 256 |
В скользящих слоях KV вдвое уже, чем в полных (512) |
Из-за этого на полномерный контекст работает только 4 слоя из 42. Остальные либо делят чужой кэш, либо заперты в окне 512.
Целевая модель (ru+en, Q4_K_M)
Фиксированная часть: веса 4 326 MiB + draft 125 MiB + вычислительный буфер llama.cpp ~250 MiB (оценка) = ~4 701 MiB. На KV-кэш остаётся ~1 443 MiB.
При полном контексте 131 072 и np 2:
| KV-квант | target KV | скользящие | draft KV | всего KV | в 6 ГБ? |
|---|---|---|---|---|---|
| f16 | 2 048 MiB | 40 MiB | 2 560 MiB | 4 648 MiB | ❌ нужно 9 349 MiB |
| q8_0 | 1 088 MiB | 21 MiB | 1 360 MiB | 2 469 MiB | ❌ нужно 7 170 MiB |
| q4_0 | 576 MiB | 11 MiB | 720 MiB | 1 307 MiB | ✅ 6 008 MiB, запас 136 MiB |
Проверка расчёта живым запуском
Расчёт выше оказался консервативным, и живой запуск это показал. Конфигурация, которая
крутится прямо сейчас: --ctx-size 128512 -np 2 -ctk q8_0 -ctv q8_0 с MTP-draft.
| расчёт | факт | |
|---|---|---|
| KV-кэш (q8_0, 128 512, np 2) | ~1 753 MiB | — |
| Веса + draft + буфер + KV | ~6 454 MiB | ~5 493 MiB занято на GPU |
| Вердикт расчёта | ❌ «не влезает» | ✅ влезает, запас ~650 MiB |
То есть аналитика завышила потребление примерно на 1 ГБ. Главная причина: draft-модель в
llama.cpp не резервирует KV-кэш на весь контекст — у неё кольцевой буфер черновика, и
моё допущение «draft KV = полная длина контекста» оказалось неверным. В таблице выше строки
draft KV поэтому завышены, и именно они портят вердикт.
Практический вывод: q8_0 KV при 128k — рабочий вариант на 6 ГБ, а не только q4_0. Расчётную таблицу ниже оставляю как верхнюю оценку (она не занижает), но решать надо по счетчику памяти, который печатает сам llama.cpp при загрузке.
Максимальный контекст в 6 ГБ
| Конфигурация | KV f16 | KV q8_0 | KV q4_0 |
|---|---|---|---|
| ru+en Q4_K_M, с MTP-draft | 55 014 | 105 154 ⚠ | 131 072 (лимит архитектуры) |
| ru+en Q4_K_M, без draft | 97 784 | 131 072 | 131 072 |
| ru-only Q4_K_M (макс. сжатие), с draft | 78 844 | 131 072 | 131 072 |
| ru-only Q4_K_M, без draft | 131 072 | 131 072 | 131 072 |
⚠ Единственная строка таблицы, проверенная запуском — ru+en с draft при q8_0: расчёт
даёт 105 154, а фактически работает 128 512 (см. «Проверка расчёта живым запуском»).
Все остальные строки — аналитическая верхняя оценка, и она занижает допустимый контекст,
особенно в столбцах с draft.
Читается просто: с квантованным KV-кэшем целевая модель даёт 128k контекста на 6 ГБ вместе со спекулятивным декодированием — это подтверждено запуском. Без draft запас ещё больше.
Максимальное сжатие для русского
Тот же метод прунинга, доведённый до предела: из словаря вырезана английская проза, латиница сохранена только через кодовый корпус.
словарь 262 144 → 135 238 (−126 906, −48.4%)
файл 3 941 951 552 B = 3.67 GiB · 5.40 BPW · draft 94.7 MiB
фиксированная часть: ~4 001 MiB → на KV остаётся ~2 143 MiB
генерация 20.6 t/s против 10.7 t/s baseline (≈1.9×)
Это единственный вариант, который открывает все 131 072 токенов контекста на 6 ГБ при полном f16 KV-кэше — то есть без квантования KV вообще, с максимальным качеством кэша и с запасом ~1 400 MiB. Плата — деградация английской прозы (кириллица и код целые, byte-fallback сохранён).
Оговорки оценки
- Вычислительный буфер llama.cpp взят как ~250 MiB и не измерялся напрямую: на практике он
зависит от версии сборки,
flash-attn,-b/-ubи числа слотов. - Главная погрешность расчёта — KV черновика. Аналитика резервирует под draft полный
контекст, llama.cpp — кольцевой буфер. Из-за этого таблица завышает потребление примерно
на 1 ГБ и даёт ложный вердикт «не влезает» для
q8_0. Живой запуск 128k + draft показан в разделе выше: влезает с запасом ~650 MiB. np 2удваивает скользящий кэш и draft-кэш (окно 512 считается на слот), а--ctx-sizeпри этом делится между слотами.- Числа предназначены для оценки бюджета, а не как гарантия: точный счётчик памяти печатает
сам llama.cpp при загрузке (
llama_model_load: KV size).
Запуск в llama.cpp
Только target
.\llama-server.exe -m ".\models\gemma-4-e4b-pruned-Q4_K_M.gguf" ^
--ctx-size 128512 --flash-attn on -np 2 -b 512 -ub 512 ^
-ctk q8_0 -ctv q8_0 --host 0.0.0.0 --port 8080
Target + спекулятивное декодирование (то, что даёт 44 t/s на коротком контексте)
.\llama-server.exe -m ".\models\gemma-4-e4b-pruned-ru-en-Q4_K_M.gguf" ^
-md ".\models\gemma-4-e4b-draft-pruned-ru-en-f16.gguf" --spec-type draft-mtp --spec-draft-n-max 3 ^
--ctx-size 128512 --flash-attn on -np 2 -b 512 -ub 512 ^
-ctk q8_0 -ctv q8_0 --host 0.0.0.0 --port 8080
Пути .\models\... — условный пример.
⚠ Про «128k контекста»:
--ctx-sizeв сервере — это сумма на все слоты
-np 2 --ctx-size 128512даёт по 64 256 токенов на слот, а не по 128k. Проверить легко:GET /v1/models→n_ctx: 64256,GET /props→total_slots: 2.То есть «модель работает со 128k контекста на 6 ГБ» означает 128k суммарно при двух параллельных диалогах. Один диалог видит 64k. Чтобы один диалог видел все 128k, нужен
-np 1 --ctx-size 128512— и тогда в 6 ГБ он влезет с тем же запасом, потому что суммарный KV-кэш не изменится.Архитектурный потолок —
n_ctx_train = 131 072; выше него модель не имеет смысла гнать.
⚠
--spec-type draft-mtp, а неdraft-simple. Это не пожелание, а требование: assistant-модель Gemma-4 берёт скрытые состояния target черезembd_state, и сdraft-simplellama.cpp падает наGGML_ASSERT(ubatch->embd_state).⚠
-mdуказывает на draft из этого же репозитория. Чужой, не прунутый assistant не подойдёт: словари не совпадут.
Рекомендованные параметры сэмплирования (записаны в GGUF-метаданные): temp 1.0, top_k 64,
top_p 0.95.
Память
Замерено на этой карте: конфигурация выше (Q4_K_M + draft f16 + KV q8_0, 128 512, np 2)
занимает ~5 493 MiB из 6 144 — с запасом ~650 MiB. Тот же набор при KV q4_0 занимает
заметно меньше. Полный разбор и предельные размеры контекста под каждый KV-квант — в разделе
Сколько влезает в 6 ГБ.
Оригинал в UD-Q4_K_XL (5.13 ГБ) на этой же карте в 128k не влезает в принципе, а в 36k — на грани. Это и была мотивация всей сборки.
Запуск в LM Studio
My Models → + Add Model → Download from Hugging Face →
Igor282/gemma-4-e4b-pruned-ru-en-Q4_K_M.LM Studio подхватит GGUF и встроенный chat-шаблон из метаданных — отдельный JINJA не нужен.
Speculative decoding: Advanced Configuration → включить спекуляцию и указать draft-файл
gemma-4-e4b-draft-pruned-ru-en-f16.gguf, тип — MTP (не simple), max draft tokens — 3. Ключи вlmstudio.jsonвыглядят примерно так (имена ключей зависят от версии LM Studio):"speculativeDecoding.enabled": true, "speculativeDecoding.draftModelKey": "Igor282/gemma-4-e4b-pruned-ru-en-Q4_K_M/gemma-4-e4b-draft-pruned-ru-en-f16.gguf", "speculativeDecoding.specType": "draft-mtp", "speculativeDecoding.maxDraftTokens": 3Контекст 36k,
flashAttention: true, KV-квантq4_0.
Chat-шаблон ожидает <|turn>role … <turn|>; thinking-канал <|channel>thought … <channel|>
работает штатно, reasoning_content из ответа вручную не вырезается.
Что умеет и что сломалось
Работает как раньше:
- русский и английский проза/диалог — сегментация идентична оригиналу токен-в-токен;
- код (идентификаторы, ключевые слова, синтаксис, спецсимволы) — цел;
- tool-calling, thinking-канал, спекулятивное декодирование;
- контекст 131k, vision/audio энкодеры не тронуты (input-модальности целые).
Ухудшилось — и это и есть цель:
- генерация на вырезанных письменностях (китайский, японский, корейский, арабский, хинди, тайский и т. п.). Понимать их модель не перестала — она их не может произнести своими токенами и будет раскладывать через byte-fallback, то есть дорого и грязно.
- Редкие символы, математическая и типографская юникод-матрица вне Latin/Cyrillic/Common — аналогично, через байты.
- Output-аудио (any-to-any speech generation) не тестировалось; прунинг словаря на speech-декодер влияет, поэтому для речевой генерации модель не проверена.
Честные ограничения:
- Немецкий, французский, испанский и другие латинские языки частично выжили — латиница общая с английским, вырезать их lossless-способом, не повредив английскому, невозможно. Реально удалены CJK / Кана / Арабский / Иврит / Индийские / Корейский / Тайский / Армянский / Грузинский и т. п.
- Модель не дообучалась (CPT). Прунинг lossless, поэтому дообучение здесь — опциональное улучшение, а не починка.
- Это не «русская модель». Это Gemma-4, у которой убрали 27% словаря, чтобы она влезала в потребительскую видеопамять и не тратила байты на письменности, которые в русско-английском рабочем тексте и коде не встречаются.
Файлы
| Файл | Размер | Что это |
|---|---|---|
gemma-4-e4b-pruned-ru-en-Q4_K_M.gguf |
4 548 138 816 B | target, Q4_K_M, vocab 190 421 |
gemma-4-e4b-draft-pruned-ru-en-f16.gguf |
131 124 672 B | MTP-draft, f16, vocab 190 421 |
Метод допускает и агрессивнее
Та же маска-логика позволяет вырезать и английскую прозу, сохранив латиницу только через кодовый корпус: словарь падает до 135 238 (−48.4%), файл — до 3.67 GiB. Это вариант «максимальное сжатие для русского», он разбирается в разделе Сколько влезает в 6 ГБ. Эта карточка — про ru+en, компромиссный вариант: английский живой.
Лицензия и атрибуция
Apache-2.0 — та же лицензия, что у исходной модели, без дополнительных ограничений. Модель отдаётся свободно, использовать можно в том числе в коммерческих проектах.
- Веса: производные от google/gemma-4-E4B-it © Google, Apache-2.0.
- Draft: производный от google/gemma-4-E4B-it-assistant © Google, Apache-2.0.
- Условия использования Gemma (Acceptable Use Policy) остаются в силе: Gemma 4 license.
- Референсный квант для сравнения: unsloth/gemma-4-E4B-it-GGUF.
- Идея lossless-прунинга BPE-словаря: Qwen-Tokenizer-Pruner.
Маска словаря (keep_sorted) воспроизводима детерминированно из опорного корпуса и фильтра
письменностей — то есть любой может собрать свою вариацию процента вырезания и проверить её
fixed-point-инвариантом.