ruTron-quality-classifier-lite
Russian only. The model was trained and evaluated only on Russian web documents (FineWeb-2
rus_Cyrl). It was not tested on other languages; its scores for them are meaningless.
A fast quality model for Russian web text, made to filter pretraining data at corpus
scale. It has the same outputs as
krysyuk/ruTron-quality-classifier-base
(base): an overall pretraining value, 13 quality axes on a 1–5 scale, 8 content flags,
topic, format, audience and region. Both imitate the labels that Claude Sonnet 5.5 gave
to 26,000 FineWeb-2 documents
(krysyuk/fineweb-2-russian-quality-annotations-26k).
This model scored all 699M documents of FineWeb-2 rus_Cyrl:
krysyuk/fineweb-2-russian-quality-full.
Its main job is to cut junk cheaply: spam, doorways, machine-generated and broken text. For finer ranking of good documents the base model is noticeably better.
What is different from the base model
| base | lite | |
|---|---|---|
| Encoder | Nemotron-3-Embed-1B, all 16 layers |
first 4 layers only |
| Text read | up to 4,096 tokens (first 10,000 + last 1,700 characters) | first 512 tokens of the same view |
| Document vector | learned mix of all 17 hidden states + attention pooling over 256-token chunks (layers 4/8/12/16) | two learned mixes of hidden states 0–4 of one 512-token window; no attention over chunks |
| Heads | trained on base features | retrained on lite features, same labels, same 5-fold scheme (not distilled from base) |
| Parameters, file size | 1.14B encoder + 46M heads, 2.5 GB | 487M encoder (268M of them the embedding table) + 46M heads, 1.2 GB |
| Speed, Apple M4 Pro | ~1.2 docs/s | ~19 docs/s |
| Speed, one RTX PRO 4000 | not measured | ~500 docs/s with FP8 + torch.compile |
overall QWK / axes QWK (test) |
0.876 / 0.870 | 0.820 / 0.832 |
Details:
- Pruned encoder. Layers 5–16 are simply not run; the kept weights are unchanged (bf16). The encoder's final norm is not applied: the heads read raw hidden states, as in training.
- 512 tokens. The model does not see anything after about the first 1,700 characters. Problems further in a long document are missed.
- Same 32 text statistics as base, computed on the full 10,000 + 1,700 character
view.
heuristics.pyis a faster implementation with identical values (checked on 500 documents; base uses the same file now). - Production script
score_parquet.py: FP8,torch.compile, parallel CPU workers, resume after interruption. This is the code that scored the full dataset. - The tokenizer is pinned to the behaviour it had in training
(
fix_mistral_regex=False). Do not "fix" it: the scores would drift.
Why 4 layers and 512 tokens
Heads trained on the outputs of the first L layers of the frozen encoder (exactly what
the pruned model computes); test split, overall_direct head:
| Encoder layers | Text read | Axes QWK | overall QWK |
PR-AUC overall ≥ 4 |
|---|---|---|---|---|
| 16 (base) | 4,096 tokens | 0.870 | 0.873 | 0.763 |
| 4 | 4,096 tokens | 0.856 | 0.851 | 0.669 |
| 8 | first 512 | 0.853 | 0.851 | 0.644 |
| 6 | first 512 | 0.847 | 0.842 | 0.625 |
| 4 (lite) | first 512 | 0.832 | 0.818 | 0.595 |
Three seeds of the chosen setup: axes QWK 0.830–0.832, overall QWK 0.818–0.822. The
loss is mostly at the top; for the junk end it is small (test split):
| Task | base | lite |
|---|---|---|
Sonnet gave 1, overall < 1.5 |
precision 0.93, recall 0.89 | precision 0.92, recall 0.82 |
Sonnet gave 1–2, overall < 2.5 |
precision 0.92, recall 0.91 | precision 0.89, recall 0.88 |
Spearman between base and lite overall |
0.93 |
Quick start
For a first look and small jobs: predict.py, plain PyTorch, any device.
pip install torch "transformers>=5.18" safetensors pyarrow huggingface_hub
hf download krysyuk/ruTron-quality-classifier-lite --local-dir ruTron-quality-classifier-lite
cd ruTron-quality-classifier-lite
python predict.py --input docs.jsonl --output scores.jsonl # one {"id", "text"} per line
python predict.py --input docs.parquet --output scores.jsonl --device cuda
import sys
from huggingface_hub import snapshot_download
path = snapshot_download("krysyuk/ruTron-quality-classifier-lite")
sys.path.insert(0, path)
from predict import LiteScorer
scorer = LiteScorer(path) # cuda / mps / cpu picked automatically
scorer.score(["Текст документа ..."])[0]
# {"overall": 2.856, "overall_direct": 2.939, "overall_stacked": 2.773, "p_overall_4plus": 0.0002,
# "axes": {"cleanliness": 3.45, ...}, "flags": {"unsafe_content": 0.0096, ..., "answer_dump": false},
# "topic": {"medicine": 0.925, ...}, "format": "popular_article", "audience": "general_adult", "region": "none"}
Tested with torch 2.14 and transformers 5.18.
Production: millions of documents
score_parquet.py reads parquet files and writes one parquet of scores per input file,
with the same 30 score columns as
the full dataset
plus id.
pip install torchao # for --fp8
# local parquet files with a "text" column
python score_parquet.py --input "data/*.parquet" --out scores/ --fp8 --compile
# files of a Hub dataset: the next file downloads while the current one is scored,
# each file is deleted after scoring
python score_parquet.py --hf-dataset HuggingFaceFW/fineweb-2 \
--hf-files "data/rus_Cyrl/train/*.parquet" --out scores/ --fp8 --compile
# several GPUs: one process per GPU, files split between them
for g in 0 1 2 3; do
CUDA_VISIBLE_DEVICES=$g python score_parquet.py --input "data/*.parquet" --out scores/ \
--fp8 --compile --shard $g --num-shards 4 > log_$g.txt 2>&1 &
done
How it works and what to know:
- CPU workers (
--workers, default: all cores but two, at most 16) truncate, tokenize and compute the text statistics while the GPU runs the model. The CPU is often the bottleneck: plan 10–16 cores per GPU. - FP8 (
--fp8): the encoder's linear layers in FP8 viatorchao, with per-row activation scales, so FP8 does not make a document's score depend on its batch neighbours (padding to a common length still adds bf16 noise of about ±0.005). Needs a GPU with FP8: compute capability 8.9 or newer (RTX 40xx / Ada, H100, Blackwell). On A100 and older, drop--fp8. FP8 vs bf16 on 500 test documents:overallcorrelation 0.9994, mean shift 0.01; 0 and 1 of 500 documents change sides of the 1.5 and 2.0 thresholds. --compile:torch.compileof the encoder with dynamic shapes. It needs a C compiler (gcc) for Triton; the first batches are slow while it compiles.- Resume: each file is scored in parts of 200,000 rows (
--part-rows), and a part is saved only when complete. After a crash, run the same command again: finished files and parts are skipped, at most one part is redone. - Documents are sorted by length inside a part to reduce padding; the output keeps the
input order. If the input has no
idcolumn, the row number is used.
Measured throughput (documents per second):
| Setup | docs/s |
|---|---|
Apple M4 Pro, MPS, bf16 (predict.py) |
~19 |
| RTX PRO 4000 Blackwell, bf16, GPU only | 257 |
+ torch.compile |
318 |
| + FP8 | 501 |
| Same card, whole pipeline (reading, tokenization, statistics, writing; 32 CPU cores) | ~450–500 |
| RTX PRO 6000 Max-Q, per card (31 CPU cores for 2 cards) | ~1,080 |
All of rus_Cyrl (699M documents) took about two days on rented single-GPU machines
(up to 10 at once, RTX PRO 4000–6000).
Output
| Field | Meaning |
|---|---|
overall |
Pretraining value, expected level on 1–4 (4 = "4 or 5"): mean of the two overall heads. Use this one |
overall_direct, overall_stacked |
The two heads: from the text directly, and from the predicted axes, flags and categories |
p_overall_4plus |
Probability that the annotator would give 4 or 5; use it to pick the top |
axes.* |
Expected score 1–5 for each of the 13 axes |
flags.* |
Probabilities of 8 flags; answer_dump is a keyword rule (true / false) |
topic |
Top-3 topics with probabilities in predict.py; the most likely topic in score_parquet.py |
format, audience, region |
Most likely class |
overall is an expected value, so on the lite model it practically never reaches 4: on
all of rus_Cyrl its maximum is about 3.9 and 2.0% of documents are ≥ 3.5. To pick the
top, use p_overall_4plus. Share of the corpus kept at each threshold:
full dataset, "Choosing a threshold".
Rating criteria
The model reproduces the rubric of the annotations dataset.
overall |
Decision | Typical documents |
|---|---|---|
| 1 | Exclude | Spam, doorways, garbage, any unsafe content |
| 2 | Rather exclude | Ads, filler |
| 3 | Keep: ordinary acceptable web | News, forum post, blog |
| 4 | Keep with a higher weight | Substantive, well-written text: explanations or facts |
| 5 | Reference quality, upsample | Textbook, encyclopedic article, deep analysis |
Levels 4 and 5 are merged into "4+": 5 is only ~0.1% of the data.
| Output | Count | Names |
|---|---|---|
| Quality axes, 1–5 | 13 | cleanliness, language_quality, naturalness, information_density, factual_specificity, educational_value, reasoning_depth, expertise_level, credibility, originality, commercial_neutrality, durability, domain_depth |
| Flags, probability | 8 | unsafe_content, seo_doorway, machine_generated, truncated_or_fragment, suspected_mt, pii_present, official_document, mixed_language |
| Rule flag, true / false | 1 | answer_dump |
| Categories | 4 | topic (30 topics), format (26), audience (7), region (5); names in labels.json |
What every axis, flag and category means, with a description of each level:
annotations dataset, "Rating criteria".
The exact annotation prompt is in that repo, prompt/annotate_v6.system.md.
Architecture
text → first 512 tokens → Nemotron-3-Embed-1B, embeddings + layers 1–4 (bf16, final norm not applied)
→ mean pooling of each of the 5 hidden states over the tokens
├─ learned layer mix #1
├─ learned layer mix #2 (the chunk branch of base: one window, so no attention)
└─ 32 text statistics (Gopher / C4-style: script shares, duplicate lines/n-grams, ...)
→ trunk MLP (1024) → 13 ordinal axis towers (CORN) · 8 flag logits · topic · format · audience · region
→ overall: direct ordinal head + stacked head over the predicted
axis / flag / category probabilities (trained out-of-fold)
The heads are the same as in base: same trunk, towers and stacked overall head, the
same 5-fold training, all five head sets averaged at inference. What changes is the
input to the trunk. Base mixes all 17 hidden states of up to 4,096 tokens and pools
256-token chunks with attention, so it can notice a good article with a junk tail. The
lite model has one 512-token window: the attention over chunks has nothing to choose
from, and both branches become learned mixes of the same layers 0–4.
Evaluation
Test split: 2,501 held-out documents (half uniform random, half from a "probably good"
pool, the same mix as training). run05: 200 random documents labelled by Claude Opus
agents instead of Sonnet, which shows how the model agrees with a different annotator.
| test | run05 | |
|---|---|---|
| Axes, mean QWK | 0.832 | 0.831 |
overall QWK (1–4) |
0.820 | 0.823 |
overall Spearman |
0.853 | 0.853 |
PR-AUC overall ≥ 4 |
0.60 (base rate 4.7%) | 0.65 (base rate 6%) |
Junk: Sonnet gave 1, overall < 1.5 |
precision 0.92, recall 0.82 | precision 0.85, recall 0.82 |
Per-axis QWK, test:
| Axis | base | lite |
|---|---|---|
cleanliness |
0.89 | 0.86 |
language_quality |
0.85 | 0.83 |
naturalness |
0.87 | 0.85 |
information_density |
0.88 | 0.85 |
factual_specificity |
0.89 | 0.86 |
educational_value |
0.87 | 0.82 |
reasoning_depth |
0.82 | 0.77 |
expertise_level |
0.87 | 0.83 |
credibility |
0.85 | 0.80 |
originality |
0.85 | 0.80 |
commercial_neutrality |
0.92 | 0.88 |
durability |
0.90 | 0.86 |
domain_depth |
0.85 | 0.80 |
Flags, PR-AUC on test. The three marked ones are unreliable: the annotator disagrees with itself on them too.
| Flag | base | lite |
|---|---|---|
seo_doorway |
0.99 | 0.97 |
unsafe_content |
0.94 | 0.89 |
machine_generated |
0.90 | 0.89 |
mixed_language |
0.89 | 0.89 |
official_document |
0.70 | 0.71 |
pii_present (unreliable) |
0.48 | 0.42 |
truncated_or_fragment (unreliable) |
0.47 | 0.39 |
suspected_mt (unreliable) |
0.36 | 0.38 |
Categories, test:
| Category | Metric | base | lite |
|---|---|---|---|
topic |
top-1 accuracy | 0.78 | 0.69 |
region |
macro-F1 | 0.82 | 0.70 |
audience |
macro-F1 | 0.75 | 0.56 |
format |
macro-F1 | 0.63 | 0.54 |
Categories lose the most from the short context.
The packaged model was checked against the training run on 200 raw test texts (mean
overall difference 0.001, bf16 noise), and score_parquet.py against predict.py
(≤ 0.0005). Full numbers: metrics.json.
Limitations
- Russian only (see the top of the card).
- Reads only the first 512 tokens.
- It imitates one LLM annotator (Claude Sonnet 5.5, prompt v6) and inherits its biases; the rubric favours educational and factual text.
- Trained on FineWeb-2 web pages; books, code and chat are out of distribution.
- Weaker than base on the top end and on categories. Use scores to rank and filter in bulk, not to judge a single document.
Files
| File | |
|---|---|
model.safetensors |
Encoder (embeddings + 4 layers, bf16) + 5 cross-validation head sets + the stacked overall head |
tokenizer.json, tokenizer_config.json |
Tokenizer of the encoder |
config.json, labels.json |
Encoder and head config, class names, order of text statistics |
predict.py |
Simple inference: command line and LiteScorer |
score_parquet.py |
Production inference: FP8, torch.compile, CPU workers, resume |
modeling.py, heuristics.py |
Model code and the 32 text statistics |
metrics.json |
Validation / test / run05 metrics |
LICENSE, NOTICE |
OpenMDW-1.1, the encoder's license, and its attribution notices |
License
OpenMDW-1.1, the license of the base encoder
nvidia/Nemotron-3-Embed-1B-BF16,
whose first 4 layers are included here unchanged. The heads were trained on labels
produced by Claude Sonnet 5.5 for FineWeb-2 texts (ODC-By 1.0).
Русский
Только для русского языка. Модель обучена и проверена только на русских веб-документах (FineWeb-2
rus_Cyrl). На других языках её не проверяли, и её оценки для них не имеют смысла.
Быстрая модель качества русского веб-текста для фильтрации данных предобучения в
масштабе целых корпусов. Выходы те же, что у
krysyuk/ruTron-quality-classifier-base
(base): общая ценность для предобучения overall, 13 осей качества по шкале 1–5, 8
флагов содержания, тема, формат, аудитория и регион. Обе модели повторяют разметку,
которую Claude Sonnet 5.5 сделал для 26 000 документов FineWeb-2
(krysyuk/fineweb-2-russian-quality-annotations-26k).
Эта модель оценила все 699 млн документов FineWeb-2 rus_Cyrl:
krysyuk/fineweb-2-russian-quality-full.
Её главная задача - дёшево отсечь мусор: спам, дорвеи, сгенерированный и битый текст. Для тонкого ранжирования хороших документов base-модель заметно лучше.
Чем отличается от base
| base | lite | |
|---|---|---|
| Энкодер | Nemotron-3-Embed-1B, все 16 слоёв |
только первые 4 слоя |
| Что читает | до 4 096 токенов (первые 10 000 + последние 1 700 символов) | первые 512 токенов того же текста |
| Вектор документа | обучаемая смесь всех 17 скрытых состояний + attention pooling по кускам из 256 токенов (слои 4/8/12/16) | две обучаемые смеси скрытых состояний 0–4 одного окна из 512 токенов; attention по кускам нет |
| Головы | обучены на признаках base | обучены заново на признаках lite, те же метки, та же 5-фолдовая схема (не дистилляция из base) |
| Параметры, размер файла | энкодер 1,14 млрд + головы 46M, 2,5 ГБ | энкодер 487M (из них 268M - таблица эмбеддингов) + головы 46M, 1,2 ГБ |
| Скорость, Apple M4 Pro | ~1,2 док/с | ~19 док/с |
| Скорость, одна RTX PRO 4000 | не замеряли | ~500 док/с с FP8 + torch.compile |
QWK overall / осей (test) |
0.876 / 0.870 | 0.820 / 0.832 |
Подробнее:
- Урезанный энкодер. Слои 5–16 просто не запускаются, оставшиеся веса не менялись (bf16). Финальная нормализация энкодера не применяется: головы читают сырые скрытые состояния, как при обучении.
- 512 токенов. Модель не видит ничего дальше примерно первых 1 700 символов. Проблемы в глубине длинного документа она пропускает.
- Те же 32 текстовые статистики, что у base, считаются по полному тексту из
10 000 + 1 700 символов.
heuristics.py- более быстрая реализация с теми же значениями (проверено на 500 документах; base теперь использует этот же файл). - Продакшн-скрипт
score_parquet.py: FP8,torch.compile, параллельные CPU-воркеры, продолжение после обрыва. Именно этим кодом размечен полный датасет. - Токенизатор зафиксирован в том поведении, что было при обучении
(
fix_mistral_regex=False). Не «исправляйте» его: оценки поплывут.
Почему 4 слоя и 512 токенов
Головы обучены на выходах первых L слоёв замороженного энкодера (ровно то, что считает
урезанная модель); test, голова overall_direct:
| Слоёв энкодера | Что читает | QWK осей | QWK overall |
PR-AUC overall ≥ 4 |
|---|---|---|---|---|
| 16 (base) | 4 096 токенов | 0.870 | 0.873 | 0.763 |
| 4 | 4 096 токенов | 0.856 | 0.851 | 0.669 |
| 8 | первые 512 | 0.853 | 0.851 | 0.644 |
| 6 | первые 512 | 0.847 | 0.842 | 0.625 |
| 4 (lite) | первые 512 | 0.832 | 0.818 | 0.595 |
Три сида выбранной конфигурации: QWK осей 0.830–0.832, QWK overall 0.818–0.822.
Теряется в основном верх; на мусорном конце потеря небольшая (test):
| Задача | base | lite |
|---|---|---|
Sonnet поставил 1, overall < 1.5 |
точность 0.93, полнота 0.89 | точность 0.92, полнота 0.82 |
Sonnet поставил 1–2, overall < 2.5 |
точность 0.92, полнота 0.91 | точность 0.89, полнота 0.88 |
Спирмен между overall base и lite |
0.93 |
Быстрый старт
Чтобы посмотреть, как всё работает, и для небольших объёмов: predict.py, обычный
PyTorch, любое устройство.
pip install torch "transformers>=5.18" safetensors pyarrow huggingface_hub
hf download krysyuk/ruTron-quality-classifier-lite --local-dir ruTron-quality-classifier-lite
cd ruTron-quality-classifier-lite
python predict.py --input docs.jsonl --output scores.jsonl # по строке {"id", "text"}
python predict.py --input docs.parquet --output scores.jsonl --device cuda
import sys
from huggingface_hub import snapshot_download
path = snapshot_download("krysyuk/ruTron-quality-classifier-lite")
sys.path.insert(0, path)
from predict import LiteScorer
scorer = LiteScorer(path) # cuda / mps / cpu выбирается сам
scorer.score(["Текст документа ..."])[0]
Проверено на torch 2.14 и transformers 5.18.
Продакшн: миллионы документов
score_parquet.py читает parquet-файлы и на каждый входной файл пишет parquet с
оценками: те же 30 колонок, что в
полном датасете,
плюс id.
pip install torchao # для --fp8
# локальные parquet с колонкой "text"
python score_parquet.py --input "data/*.parquet" --out scores/ --fp8 --compile
# файлы датасета с Hub: следующий файл качается, пока считается текущий,
# каждый файл удаляется после оценки
python score_parquet.py --hf-dataset HuggingFaceFW/fineweb-2 \
--hf-files "data/rus_Cyrl/train/*.parquet" --out scores/ --fp8 --compile
# несколько GPU: по процессу на карту, файлы делятся между ними
for g in 0 1 2 3; do
CUDA_VISIBLE_DEVICES=$g python score_parquet.py --input "data/*.parquet" --out scores/ \
--fp8 --compile --shard $g --num-shards 4 > log_$g.txt 2>&1 &
done
Как это работает и что важно знать:
- CPU-воркеры (
--workers, по умолчанию все ядра кроме двух, не больше 16) обрезают текст, токенизируют и считают текстовые статистики, пока GPU считает модель. Узким местом часто становится CPU: закладывайте 10–16 ядер на карту. - FP8 (
--fp8): линейные слои энкодера в FP8 черезtorchao, масштаб активаций на строку, поэтому FP8 не делает оценку документа зависимой от соседей по батчу (паддинг до общей длины всё равно даёт шум bf16 около ±0.005). Нужна карта с FP8: compute capability 8.9 и новее (RTX 40xx / Ada, H100, Blackwell). На A100 и старше уберите--fp8. FP8 против bf16 на 500 документах test: корреляцияoverall0.9994, средний сдвиг 0.01; через пороги 1.5 и 2.0 переходят 0 и 1 документ из 500. --compile:torch.compileэнкодера с динамическими формами. Тритону нужен компилятор C (gcc); первые батчи медленные, пока идёт компиляция.- Продолжение: каждый файл считается частями по 200 000 строк (
--part-rows), часть сохраняется только целиком. После падения запустите ту же команду: готовые файлы и части пропускаются, заново считается не больше одной части. - Внутри части документы сортируются по длине, чтобы было меньше паддинга; на выходе
порядок входа сохраняется. Если во входе нет колонки
id, берётся номер строки.
Замеры скорости (документов в секунду):
| Конфигурация | док/с |
|---|---|
Apple M4 Pro, MPS, bf16 (predict.py) |
~19 |
| RTX PRO 4000 Blackwell, bf16, только GPU | 257 |
+ torch.compile |
318 |
| + FP8 | 501 |
| Та же карта, весь конвейер (чтение, токенизация, статистики, запись; 32 ядра CPU) | ~450–500 |
| RTX PRO 6000 Max-Q, на карту (31 ядро CPU на 2 карты) | ~1 080 |
Весь rus_Cyrl (699 млн документов) посчитан примерно за двое суток на арендованных
машинах с одной GPU (до 10 одновременно, RTX PRO 4000–6000).
Выход
| Поле | Смысл |
|---|---|
overall |
Ценность для предобучения, ожидаемый уровень 1–4 (4 = «4 или 5»): среднее двух голов overall. Используйте его |
overall_direct, overall_stacked |
Две головы: прямо по тексту и по предсказанным осям, флагам и категориям |
p_overall_4plus |
Вероятность, что аннотатор поставил бы 4 или 5; по ней удобно отбирать лучшее |
axes.* |
Ожидаемая оценка 1–5 по каждой из 13 осей |
flags.* |
Вероятности 8 флагов; answer_dump - правило по ключевым словам (да / нет) |
topic |
3 самые вероятные темы с вероятностями в predict.py; самая вероятная тема в score_parquet.py |
format, audience, region |
Самый вероятный класс |
overall - математическое ожидание, поэтому у lite он практически не доходит до 4: на
всём rus_Cyrl максимум около 3.9, а ≥ 3.5 у 2,0% документов. Для отбора лучшего берите
p_overall_4plus. Какая доля корпуса остаётся при каждом пороге:
полный датасет, раздел «Как выбрать порог».
Критерии оценки
Модель воспроизводит рубрику датасета с разметкой.
overall |
Решение | Типичные документы |
|---|---|---|
| 1 | Исключить | Спам, дорвеи, мусор, любой небезопасный контент |
| 2 | Скорее исключить | Реклама, вода |
| 3 | Оставить: обычный приемлемый веб | Новость, пост на форуме, блог |
| 4 | Оставить с повышенным весом | Содержательный, хорошо написанный текст: объяснения или факты |
| 5 | Эталон, апсемплить | Учебник, энциклопедическая статья, глубокий анализ |
Уровни 4 и 5 объединены в «4+»: оценка 5 - всего около 0,1% данных.
| Выход | Сколько | Названия |
|---|---|---|
| Оси качества, 1–5 | 13 | cleanliness, language_quality, naturalness, information_density, factual_specificity, educational_value, reasoning_depth, expertise_level, credibility, originality, commercial_neutrality, durability, domain_depth |
| Флаги, вероятность | 8 | unsafe_content, seo_doorway, machine_generated, truncated_or_fragment, suspected_mt, pii_present, official_document, mixed_language |
| Флаг-правило, да / нет | 1 | answer_dump |
| Категории | 4 | topic (30 тем), format (26), audience (7), region (5); названия в labels.json |
Что значит каждая ось, флаг и категория, с описанием каждого уровня:
датасет с разметкой, раздел «Критерии оценки».
Точный промпт разметки лежит там же, prompt/annotate_v6.system.md.
Архитектура
текст → первые 512 токенов → Nemotron-3-Embed-1B, эмбеддинги + слои 1–4 (bf16, без финальной нормализации)
→ mean pooling каждого из 5 скрытых состояний по токенам
├─ обучаемая смесь слоёв №1
├─ обучаемая смесь слоёв №2 (ветка кусков из base: окно одно, поэтому без attention)
└─ 32 текстовые статистики (в духе Gopher / C4: доли алфавитов, повторы строк и n-грамм, ...)
→ MLP-ствол (1024) → 13 порядковых башен осей (CORN) · 8 логитов флагов · тема · формат · аудитория · регион
→ overall: прямая порядковая голова + стекинг-голова по предсказанным
вероятностям осей, флагов и категорий (обучена out-of-fold)
Головы те же, что у base: тот же ствол, башни и стекинг-голова overall, то же обучение
на 5 фолдах, на инференсе все пять наборов голов усредняются. Меняется вход ствола. Base
смешивает все 17 скрытых состояний по 4 096 токенам и пулит куски по 256 токенов через
attention, поэтому замечает, например, хорошую статью с мусорным хвостом. У lite одно
окно из 512 токенов: attention по кускам выбирать не из чего, и обе ветки становятся
обучаемыми смесями одних и тех же слоёв 0–4.
Качество
Test: 2 501 отложенный документ (половина - равномерно случайные, половина - из пула
«вероятно хороших», как в обучении). run05: 200 случайных документов, размеченных
агентами Claude Opus вместо Sonnet; показывает согласие модели с другим аннотатором.
| test | run05 | |
|---|---|---|
| Оси, средний QWK | 0.832 | 0.831 |
QWK overall (1–4) |
0.820 | 0.823 |
Спирмен overall |
0.853 | 0.853 |
PR-AUC overall ≥ 4 |
0.60 (база 4.7%) | 0.65 (база 6%) |
Мусор: Sonnet поставил 1, overall < 1.5 |
точность 0.92, полнота 0.82 | точность 0.85, полнота 0.82 |
QWK по осям, test:
| Ось | base | lite |
|---|---|---|
cleanliness |
0.89 | 0.86 |
language_quality |
0.85 | 0.83 |
naturalness |
0.87 | 0.85 |
information_density |
0.88 | 0.85 |
factual_specificity |
0.89 | 0.86 |
educational_value |
0.87 | 0.82 |
reasoning_depth |
0.82 | 0.77 |
expertise_level |
0.87 | 0.83 |
credibility |
0.85 | 0.80 |
originality |
0.85 | 0.80 |
commercial_neutrality |
0.92 | 0.88 |
durability |
0.90 | 0.86 |
domain_depth |
0.85 | 0.80 |
Флаги, PR-AUC на test. Трём отмеченным доверять нельзя: аннотатор и сам с собой по ним не согласен.
| Флаг | base | lite |
|---|---|---|
seo_doorway |
0.99 | 0.97 |
unsafe_content |
0.94 | 0.89 |
machine_generated |
0.90 | 0.89 |
mixed_language |
0.89 | 0.89 |
official_document |
0.70 | 0.71 |
pii_present (ненадёжный) |
0.48 | 0.42 |
truncated_or_fragment (ненадёжный) |
0.47 | 0.39 |
suspected_mt (ненадёжный) |
0.36 | 0.38 |
Категории, test:
| Категория | Метрика | base | lite |
|---|---|---|---|
topic |
точность top-1 | 0.78 | 0.69 |
region |
macro-F1 | 0.82 | 0.70 |
audience |
macro-F1 | 0.75 | 0.56 |
format |
macro-F1 | 0.63 | 0.54 |
Сильнее всего от короткого контекста страдают категории.
Упакованная модель сверена с прогоном обучения на 200 сырых текстах test (среднее
расхождение overall 0.001, шум bf16), а score_parquet.py - с predict.py (≤ 0.0005). Все цифры: metrics.json.
Ограничения
- Только русский язык (см. начало карточки).
- Читает только первые 512 токенов.
- Модель подражает одному LLM-аннотатору (Claude Sonnet 5.5, промпт v6) и наследует его предвзятость; рубрика предпочитает образовательные и фактологичные тексты.
- Обучена на веб-страницах FineWeb-2; книги, код и чаты - вне распределения.
- Слабее base в верхнем диапазоне и на категориях. Оценки - для массового ранжирования и фильтрации, а не для вердикта по одному документу.
Файлы
| Файл | |
|---|---|
model.safetensors |
Энкодер (эмбеддинги + 4 слоя, bf16) + 5 наборов голов кросс-валидации + стекинг-голова overall |
tokenizer.json, tokenizer_config.json |
Токенизатор энкодера |
config.json, labels.json |
Конфиг энкодера и голов, имена классов, порядок текстовых статистик |
predict.py |
Простой инференс: командная строка и LiteScorer |
score_parquet.py |
Продакшн-инференс: FP8, torch.compile, CPU-воркеры, продолжение после обрыва |
modeling.py, heuristics.py |
Код модели и 32 текстовые статистики |
metrics.json |
Метрики на validation / test / run05 |
LICENSE, NOTICE |
OpenMDW-1.1, лицензия энкодера, и его уведомления об авторстве |
Лицензия
OpenMDW-1.1, лицензия базового энкодера
nvidia/Nemotron-3-Embed-1B-BF16,
первые 4 слоя которого входят сюда без изменений. Головы обучены на разметке Claude
Sonnet 5.5 для текстов FineWeb-2 (ODC-By 1.0).
- Downloads last month
- 6
Model tree for krysyuk/ruTron-quality-classifier-lite
Base model
mistralai/Ministral-3-3B-Base-2512