Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Кривая «бит на вес против бита на токен»: шесть точек, один и тот же протокол

Дата: 18.08.2026. Продолжение `README_ru.md`. Тот же протокол: окно 512, 40 окон (20 480 токенов),
`-t 2`, seed 42, тот же рантайм, те же два корпуса с теми же sha256.

Бит на вес считается как `размер файла × 8 / 2 031 000 000 параметров` (полный файл, включая
эмбеддинги и fp16-масштабы) — это `[смоделировано]` поверх точных размеров файлов, а не
заявленная битность блока.

## Результат `[измерено]`

| Модель | бит/вес | Английский, PPL | Русский, PPL | к Q6_K (EN) |
|---|---:|---:|---:|---:|
| Ternary-Bonsai-1.7B-Q2_0 (троичная) | 1,825 | 21,2119 ± 0,69302 | 6,8611 ± 0,17512 | +29,5 % |
| Qwen3-1.7B-Q2_K | 3,064 | 21,7566 ± 0,68516 | 6,6817 ± 0,17448 | +32,8 % |
| Qwen3-1.7B-Q3_K_M | 3,701 | 17,3925 ± 0,54170 | 5,7318 ± 0,15185 | +6,2 % |
| Qwen3-1.7B-Q4_K_M | 4,362 | 16,5360 ± 0,51957 | 5,5113 ± 0,14769 | +1,0 % |
| Qwen3-1.7B-Q6_K | 5,584 | **16,3794 ± 0,51749** | **5,4614 ± 0,14672** | 0 |
| Qwen3-1.7B-Q8_0 | 7,226 | 16,6104 ± 0,53106 | 5,4776 ± 0,14849 | +1,4 % |

## Три вывода

**1. Выше ≈4,4 бита/вес кривая плоская.** Q4_K_M, Q6_K и Q8_0 различаются на 0,3–1,4 % при
погрешности отношения 3,8–4,6 п.п. — то есть неразличимы. Q8_0 даже чуть хуже Q6_K
(+1,41 ± 4,56 п.п.), что при перекрывающихся интервалах означает «то же самое», а не
«8 бит хуже 6». Платить за 7,2 бита вместо 4,4 нечем.

**2. Обрыв лежит между 3,7 и 3,1 бита, и он не про троичность.** Q3_K_M → Q2_K даёт
+25,1 ± 5,5 п.п. на английском и +16,6 ± 4,3 п.п. на русском. Ровно тот же порядок штрафа,
что мы вчера назвали «ценой троичности». **Значит наблюдённые +25…28 % — это цена ухода
ниже ≈3,5 бита вообще, а не свойство троичного представления.** Формулировку «цена
троичности» в наших материалах надо заменить на «цена низкой битности».

**3. Троичная модель на 1,825 бита неотличима от 2-битной PTQ-квантизации на 3,064 бита.**
Английский: −2,50 ± 4,42 п.п. в пользу троичной; русский: +2,68 ± 3,75 п.п. в пользу Q2_K.
Оба интервала накрывают нуль. То есть при **на 40 % меньшем числе бит** качество то же.
Это и есть содержательный аргумент за троичное обучение — не «лучше», а **«столько же
качества за существенно меньше бит»**, и только в сравнении с агрессивной пост-квантизацией,
не с 4-битной.

## Границы

1. Троичная модель — **другая обученная модель**, а не Qwen3 после ternarization. Сравнение
отвечает на вопрос «что доступно сегодня при данном бюджете бит», а не «что делает
ternarization с этой моделью» — `[not-evaluated]`.
2. Точки Q2_K…Q6_K взяты из [unsloth/Qwen3-1.7B-GGUF](https://huggingface.co/unsloth/Qwen3-1.7B-GGUF)
(imatrix-калиброванные), а Q8_0 — из официального
[Qwen/Qwen3-1.7B-GGUF](https://huggingface.co/Qwen/Qwen3-1.7B-GGUF). Это разные конвейеры
квантизации; сравнивать Q8_0 с остальными точками как «одну кривую» строго нельзя.
Отсюда же возможное объяснение, почему Q8_0 не лучше Q6_K.
3. 20 480 токенов на точку, не полный wikitext-2 — `[not-evaluated]`.
4. Всё на процессоре; на плате AX7203 не измерено ничего — `[not-evaluated]`.
5. Битность считается по всему файлу; сравнение по «бит на вес» не учитывает, что у разных
схем разная доля неквантованных тензоров.

## Провенанс

sha256 всех скачанных файлов и сырые логи — в этом каталоге (`ppl_en_qw_*.log`,
`ppl_ru_v2_qw_*.log`), машинная сводка — `curve_results.json`. Протокол воспроизведения —
`run.sh` с переменными `CORPUS` и `CHUNKS`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Цена троичности в перплексии: русский и английский корпус, 40 окон

Дата: 18.08.2026. Пакет закрывает пункт «downstream-качество троичной модели = not-evaluated»
и одновременно **отменяет** одно наше собственное утверждение, сделанное 17.08.2026.

Машинная сводка — `results.json`. Протокол — `run.sh`. Пересборка корпусов — `build_corpus.py`.

## Что сравнивалось

| | модель | бит/вес | sha256 (начало) |
|---|---|---:|---|
| троичная | Ternary-Bonsai-1.7B-Q2_0 | 2,125 | `d97d94eb5645` |
| база | Qwen3-1.7B-Q8_0 | 8,5 | `061b54daade0` |

Сравнение перплексии допустимо только при одинаковой сегментации. Таблицы merges у двух
файлов весов **побитово совпадают** (`bf731b62088078b8`), словари расходятся ровно на 267
хвостовых `[PAD…]`-токенов (151 936 − 151 669), в тексте не встречающихся `[доказано — сверка артефактов]`.

Это **две разные обученные модели** одной архитектуры (qwen3), а не одна модель до и после
квантизации. Корректная формулировка результата — «доступная сегодня троичная 1,7B против
доступной сегодня 8-битной 1,7B», а не «цена ternarization этой модели».

## Результат `[измерено]`

Окно 512 токенов, 40 окон = 20 480 токенов на прогон, `-t 2`, seed 42.

| Корпус | троичная Q2_0 | база Q8_0 | отношение | цена троичности |
|---|---:|---:|---:|---:|
| Английский (Gutenberg, 300 000 знаков) | **21,2119 ± 0,69302** | **16,6104 ± 0,53106** | 1,2770 ± 0,0584 | **+27,70 ± 5,84 п.п.** = 0,353 бита/токен |
| Русский (12 статей Википедии) | **6,8611 ± 0,17512** | **5,4776 ± 0,14849** | 1,2526 ± 0,0466 | **+25,26 ± 4,66 п.п.** = 0,325 бита/токен |

Интервалы самих перплексий не перекрываются ни на одном корпусе (20,52 > 17,14 и 6,69 > 5,63):
разрыв между троичной моделью и 8-битной базой при этих окнах разбросом не объясняется.

Погрешность отношения получена переносом относительных погрешностей
(`σ_r/r = √((σ_a/a)² + (σ_b/b)²)`), а не измерена независимо — это `[смоделировано]` поверх
двух `[измерено]`.

## Что отменяется (самокритика)

17.08.2026 у нас было напечатано: «на русском тексте троичность стоит дороже, чем на
английском» — на основании 10 русских окон (+30,77 %) против 16 английских (+22,81 %).

При 40 окнах **порядок переворачивается**: английский +27,70 п.п., русский +25,26 п.п.,
и интервалы перекрываются почти целиком. Из этих данных **никакой разницы между языками
заявлять нельзя**. Утверждение снимается как артефакт малой выборки.

Проверка, что старые числа не были ошибкой прогона, а именно префиксом: в новых логах
окно 16 английского корпуса печатает ровно `15,7985`, окно 10 русского — ровно `5,7293`,
то есть значения 17.08 воспроизвелись **бит-в-бит** и просто перестали быть представительными.
Это и есть цена короткой выборки: не «неверно измерено», а «измерено слишком мало».

## Границы (читать до цитирования)

1. 20 480 токенов на корпус, не полный wikitext-2 (≈336 окон) — `[not-evaluated]`.
2. Русский корпус v2 = 12 статей Википедии; первая часть — корпус v1 дословно, поэтому
v1 и v2 **не независимые выборки**, и их числа нельзя приводить как два подтверждения.
3. Опора — Q8_0, практически беспотерьная, но это не fp16-эталон — `[not-evaluated]`.
4. Перплексия ≠ полезность: инструкции, рассуждение и код ею не покрыты.
5. Всё измерено на процессоре. На плате AX7203 не измерено ничего — `[not-evaluated]`.
6. Контейнер Q2_0 держит четыре кода (−1, 0, +1, +2); код `+2` в полном перечислении
1 719 904 256 весов встречается 0 раз, но формат от этого не становится трёхсимвольным.

## Провенанс

Рантайм: fork [PrismML-Eng/llama.cpp](https://github.com/PrismML-Eng/llama.cpp), коммит `9ca265a`,
cmake Release, GGML_NATIVE=ON. Хост: 2 vCPU Intel Xeon 2,90 ГГц, 8 ГБ, посторонних процессов
на ядрах в момент замера нет. Корпуса не выкладываются в репозиторий (тексты Википедии —
CC BY-SA 4.0, Gutenberg — общественное достояние): `build_corpus.py` их восстанавливает,
sha256 для сверки — в `results.json`. Логи прогонов: `llama-perplexity` stdout, значения
из строк `Final estimate`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
"""Rebuild the evaluation corpora byte-for-byte checkable against the SHA-256 values in README_ru.md.

We do not redistribute the corpus text: Wikipedia article text is CC BY-SA 4.0 and the
English slice comes from Project Gutenberg. This script fetches the sources and applies
the exact same slicing, so anyone can regenerate the file and compare its hash.

Usage: python3 build_corpus.py en -> /tmp/eval_en.txt
python3 build_corpus.py ru2 -> /tmp/eval_ru_v2.txt
"""
import sys, hashlib, urllib.request

EN_URL = "https://www.gutenberg.org/cache/epub/2600/pg2600.txt" # War and Peace, public domain
EN_START_MARK = "CHAPTER I"
EN_CHARS = 300_000

RU_V1 = ["Троичная_логика", "Сетунь_(компьютер)", "Число_с_плавающей_запятой", "Нейронная_сеть"]
RU_V2_EXTRA = ["Троичная_система_счисления", "Квантование_(обработка_сигналов)",
"Программируемая_пользователем_вентильная_матрица",
"Трансформер_(модель_машинного_обучения)", "Машинное_обучение",
"Оперативная_память", "Золотое_сечение", "Verilog"]

def sha(path):
return hashlib.sha256(open(path, "rb").read()).hexdigest()

def get(url):
req = urllib.request.Request(url, headers={"User-Agent": "trinity-measurement/1.0"})
return urllib.request.urlopen(req, timeout=60).read().decode("utf-8", "replace")

if __name__ == "__main__":
which = sys.argv[1] if len(sys.argv) > 1 else "ru2"
if which == "en":
txt = get(EN_URL)
i = txt.index(EN_START_MARK)
out = txt[i:i + EN_CHARS]
path = "/tmp/eval_en.txt"
else:
# NOTE: the numbers in README_ru.md were produced from plain-text article renderings.
# Any extractor change alters the bytes, so always compare the printed SHA-256 before
# citing a perplexity value as reproduced.
parts = []
for title in (RU_V1 + RU_V2_EXTRA):
parts.append(get(f"https://ru.wikipedia.org/api/rest_v1/page/html/{title}"))
out = "\n\n".join(parts)
path = "/tmp/eval_ru_v2.txt"
open(path, "w").write(out)
print(path, len(out), "chars, sha256 =", sha(path))
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
[
{
"model": "Bonsai-1.7B-Q2_0 (ternary)",
"bytes": 463290464,
"bpw": 1.825,
"en": 21.2119,
"en_err": 0.69302,
"ru": 6.8611,
"ru_err": 0.17512,
"en_vs_q6k_pct": 29.5,
"ru_vs_q6k_pct": 25.63
},
{
"model": "Qwen3-1.7B-Q2_K",
"bytes": 777796160,
"bpw": 3.064,
"en": 21.7566,
"en_err": 0.68516,
"ru": 6.6817,
"ru_err": 0.17448,
"en_vs_q6k_pct": 32.83,
"ru_vs_q6k_pct": 22.34
},
{
"model": "Qwen3-1.7B-Q3_K_M",
"bytes": 939539008,
"bpw": 3.701,
"en": 17.3925,
"en_err": 0.5417,
"ru": 5.7318,
"ru_err": 0.15185,
"en_vs_q6k_pct": 6.19,
"ru_vs_q6k_pct": 4.95
},
{
"model": "Qwen3-1.7B-Q4_K_M",
"bytes": 1107409472,
"bpw": 4.362,
"en": 16.536,
"en_err": 0.51957,
"ru": 5.5113,
"ru_err": 0.14769,
"en_vs_q6k_pct": 0.96,
"ru_vs_q6k_pct": 0.91
},
{
"model": "Qwen3-1.7B-Q6_K",
"bytes": 1417755200,
"bpw": 5.584,
"en": 16.3794,
"en_err": 0.51749,
"ru": 5.4614,
"ru_err": 0.14672,
"en_vs_q6k_pct": 0.0,
"ru_vs_q6k_pct": 0.0
},
{
"model": "Qwen3-1.7B-Q8_0 (official)",
"bytes": 1834426016,
"bpw": 7.226,
"en": 16.6104,
"en_err": 0.53106,
"ru": 5.4776,
"ru_err": 0.14849,
"en_vs_q6k_pct": 1.41,
"ru_vs_q6k_pct": 0.3
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
62b3fb705434cb57fabc59d59aa7b4c6fb558fff7c7c4b2ce67456373bc30fd3 /tmp/qw_Q2_K.gguf
8070f21b9763ef653289e20252592d0aab6680948f36eb5b4362d403fef09170 /tmp/qw_Q3_K_M.gguf
b139949c5bd74937ad8ed8c8cf3d9ffb1e99c866c823204dc42c0d91fa181897 /tmp/qw_Q4_K_M.gguf
6a9cadec4883df6f3efcf337103479dcc6a3efa9f5f8cc64a904dba57808207a /tmp/qw_Q6_K.gguf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
0.00.005.006 I common_init_result: fitting params to device memory ...
0.00.005.014 I common_init_result: (for bugs during this step try to reproduce them with -fit off, or provide --verbose logs if the bug only occurs with -fit on)
0.00.284.329 I common_params_fit_impl: projected to use 607 MiB of host memory vs. 7966 MiB of total host memory
0.00.432.016 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden
0.02.787.438 W llama_context: n_ctx_seq (512) < n_ctx_train (32768) -- the full capacity of the model will not be utilized
0.02.971.625 I common_init_from_params: warming up the model with an empty run - please wait ... (--no-warmup to disable)
0.03.467.500 I
0.03.473.679 I system_info: n_threads = 2 (n_threads_batch = 2) / 2 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | AVX512 = 1 | AVX512_VBMI = 1 | AVX512_VNNI = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 |
0.03.476.076 I perplexity: tokenizing the input ..
0.03.619.870 I perplexity: tokenization took 143.78 ms
0.03.620.060 I perplexity: calculating perplexity over 40 chunks, n_ctx=512, batch_size=2048, n_seq=4
1.06.158.728 I perplexity: 62.54 seconds per pass - ETA 10.42 minutes
[1]1.1919,[2]1.1573,[3]1.1543,[4]2.6160,[5]4.4228,[6]6.3488,[7]7.6489,[8]9.0232,[9]10.5707,[10]11.5861,[11]12.4797,[12]13.1252,[13]13.9896,[14]14.4826,[15]15.0859,[16]15.7985,[17]16.0136,[18]16.1902,[19]16.2447,[20]16.8470,[21]17.2922,[22]17.5743,[23]17.7306,[24]18.0904,[25]18.4654,[26]18.8100,[27]18.8751,[28]19.0183,[29]19.0478,[30]19.3529,[31]19.6970,[32]20.1190,[33]20.2624,[34]20.4440,[35]20.7602,[36]20.9670,[37]21.2339,[38]21.2643,[39]21.0750,[40]21.2119,
10.19.536.995 I Final estimate: PPL = 21.2119 +/- 0.69302

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
0.00.013.671 I common_init_result: fitting params to device memory ...
0.00.013.680 I common_init_result: (for bugs during this step try to reproduce them with -fit off, or provide --verbose logs if the bug only occurs with -fit on)
0.00.530.017 I common_params_fit_impl: projected to use 2272 MiB of host memory vs. 7966 MiB of total host memory
0.00.663.078 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden
0.00.861.662 W llama_context: n_ctx_seq (512) < n_ctx_train (40960) -- the full capacity of the model will not be utilized
0.00.980.223 I common_init_from_params: warming up the model with an empty run - please wait ... (--no-warmup to disable)
0.03.423.862 I
0.03.427.881 I system_info: n_threads = 2 (n_threads_batch = 2) / 2 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | AVX512 = 1 | AVX512_VBMI = 1 | AVX512_VNNI = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 |
0.03.429.366 I perplexity: tokenizing the input ..
0.03.559.483 I perplexity: tokenization took 130.1 ms
0.03.560.943 I perplexity: calculating perplexity over 40 chunks, n_ctx=512, batch_size=2048, n_seq=4
0.43.852.905 I perplexity: 40.29 seconds per pass - ETA 6.70 minutes
[1]1.3198,[2]1.2149,[3]1.2058,[4]2.4092,[5]3.8908,[6]5.4936,[7]6.4697,[8]7.5432,[9]8.8432,[10]9.6800,[11]10.4509,[12]10.9678,[13]11.5381,[14]11.9592,[15]12.3591,[16]12.8639,[17]12.9701,[18]13.2088,[19]13.3124,[20]13.6863,[21]13.9383,[22]14.1895,[23]14.3317,[24]14.5437,[25]14.8641,[26]15.1372,[27]15.1890,[28]15.2527,[29]15.3237,[30]15.4635,[31]15.6963,[32]15.8906,[33]15.9694,[34]16.0862,[35]16.2802,[36]16.4230,[37]16.6290,[38]16.6453,[39]16.5324,[40]16.6104,
6.49.588.433 I Final estimate: PPL = 16.6104 +/- 0.53106

Loading