A multi-modal evaluation harness for LLMs served through vLLM, built around Kazakh and its English and Russian counterparts.
39 benchmarks across five passes — text, vision, audio, safety and translation — each reported per language, with Kazakh first.
Every benchmark is zero-shot. No in-context examples are ever prepended: each item is presented on its own, with an instruction describing the answer format and nothing else. Nothing here is few-shot or chain-of-thought prompted, so a reasoning model's trace is its own doing rather than something the harness asked for.
- What it evaluates
- How it works
- Installation
- Credentials
- Serving a model
- Running an evaluation
- Working in tmux
- Reading the results
- Configuration reference
- Recipes
- Translation and XCOMET
- Resuming and re-judging
- Repository layout
Run ./run_eval.sh --list_benchmarks to see this with language coverage.
| Benchmark | Languages | Task |
|---|---|---|
mmlu |
kk, en | Multiple choice |
mmlu_pro |
kk, ru, en | Multiple choice, 10 options |
mmlu_redux |
kk, en | Multiple choice, error-corrected MMLU |
gpqa |
kk, ru, en | Graduate-level science |
arc |
kk, ru, en | ARC Easy + Challenge |
gsm8k |
kk, ru, en | Grade-school math |
polymath |
kk, en | Competition math, graded symbolically |
kazculture |
kk | Kazakh cultural knowledge |
kazmmlu |
kk | Kazakh school and professional exams |
kazbench_kk |
kk | Socio-cultural multiple choice |
belebele |
kk | Reading comprehension |
piqa |
kk | Physical commonsense |
include |
kk, ru | Regional exams |
kkcopa |
kk | Causal reasoning (bundled) |
nis_math |
kk | Olympiad-style math (bundled) |
kkwikispell |
kk | Sentence correction, exact match (bundled) |
kazqad |
kk | Extractive QA — LLM judge |
ragbench |
kk | Grounded generation — LLM judge |
ifbench |
kk | Instruction following — LLM judge |
| Benchmark | Languages | Task |
|---|---|---|
realworldqa |
kk, ru, en | Spatial reasoning |
mmstar |
kk, ru, en | Multimodal multiple choice |
mathvista |
kk, en | Visual math |
mathvision |
kk, en | Competition visual math |
mmbench |
kk, en | Multimodal multiple choice |
ai2d |
kk, en | Diagram understanding |
ocrbench |
kk, en | Text recognition — official rule (en), judge (kk) |
babyvision |
kk, en | Perception puzzles — exact + judge |
| Benchmark | Languages | Task |
|---|---|---|
sakura |
kk, ru, en | Audio MCQ, single- and multi-hop, 4 attributes |
spokenmqa |
kk, en | Spoken math, digit and reasoning subsets |
asr |
kk, ru, en | Speech recognition — WER, lower is better |
wavcaps |
kk, ru, en | Audio captioning — LLM judge |
wavcaps_qa |
kk, ru, en | Audio question answering — LLM judge |
qorgau, Kazakh and Russian, across six risk areas. The model answers each
adversarial question and a judge grades the reply against Qorgau's own
per-risk-area rubric. The metric is the share of responses judged safe.
Six directions between Kazakh, Russian and English: flores_kk_en,
flores_kk_ru, flores_ru_kk, flores_ru_en, flores_en_kk,
flores_en_ru. Scored with BLEU and chrF++, and optionally XCOMET
(see section 11).
Items per benchmark and language at DATA_PORTION=1.0. A dash means the
benchmark has no set for that language. Where a benchmark is aligned across
languages the counts match by construction; where they differ, the sets are
genuinely different sizes.
Text — 143,460 items
| Benchmark | Kazakh | English | Russian |
|---|---|---|---|
mmlu |
14,042 | 14,042 | – |
mmlu_pro |
12,022 | 12,022 | 12,022 |
mmlu_redux |
5,700 | 5,700 | – |
gpqa |
1,192 | 1,192 | 1,192 |
arc |
3,548 | 3,548 | 3,548 |
gsm8k |
1,319 | 1,319 | 1,319 |
polymath |
500 | 500 | – |
kazculture |
1,334 | – | – |
kazmmlu |
22,889 | – | – |
kazbench_kk |
7,111 | – | – |
belebele |
900 | – | – |
piqa |
100 | – | – |
include |
500 | – | 552 |
kkcopa |
500 | – | – |
nis_math |
100 | – | – |
kkwikispell |
160 | – | – |
kazqad |
2,713 | – | – |
ragbench |
11,430 | – | – |
ifbench |
444 | – | – |
Vision — 31,974 items
| Benchmark | Kazakh | English | Russian |
|---|---|---|---|
realworldqa |
765 | 765 | 765 |
mmstar |
1,500 | 1,500 | 1,500 |
ai2d |
3,088 | 3,088 | – |
mathvista |
1,000 | 1,000 | – |
mathvision |
3,040 | 3,040 | – |
mmbench |
4,329 | 4,377 | – |
ocrbench |
441 | 1,000 | – |
babyvision |
388 | 388 | – |
Audio — 26,011 items
| Benchmark | Kazakh | English | Russian |
|---|---|---|---|
sakura |
4,000 | 4,000 | 4,000 |
spokenmqa |
2,256 | 2,256 | – |
asr |
1,225 | 1,041 | 1,131 |
wavcaps |
1,730 | 1,730 | 1,730 |
wavcaps_qa |
304 | 304 | 304 |
Safety — 8,168 items
| Benchmark | Kazakh | Russian |
|---|---|---|
qorgau |
3,786 | 4,382 |
Translation — 6,072 items
| Direction | Items |
|---|---|
flores_kk_en, flores_kk_ru |
1,012 each |
flores_ru_kk, flores_ru_en |
1,012 each |
flores_en_kk, flores_en_ru |
1,012 each |
Total: 215,685 generations for a complete run, plus roughly 26,000 judge
calls. Use DATA_PORTION to work on a deterministic sample while setting up —
0.005 gives about 1,100 items across everything.
serve_model.sh ──► vLLM OpenAI-compatible server ──► run_eval.sh
│
┌─────────────────────┼──────────────────┐
▼ ▼ ▼
generate score locally LLM judge
(concurrent HTTP) (letters, sympy, (OpenAI Batch
WER, BLEU) or chat API)
└─────────────────────┴──────────────────┘
▼
results/<model>/performance_report.md
Three things are worth knowing up front.
Ordering. Within each pass, all Kazakh benchmarks run first, then English, then Russian. Everything scored locally finishes before anything that waits on the judge API, so the GPU is never idle waiting on a queue.
Most scoring is local. Letters, numbers, symbolic math, the official OCRBench rule, WER, BLEU and chrF++ all run on your machine. Only free-form tasks with no mechanical answer reach the LLM judge — 8 of 39 benchmarks.
Extraction failures are reported, not hidden. The headline metric is
accuracy_valid_only: the score over items an answer could actually be read
from. The all-items figure sits beside it, and extraction_failed says how
many were dropped. If that count is high, the score rests on a small
denominator and you should look at why.
Requires Python ≥ 3.12 and a machine with GPUs for the served model.
git clone https://github.com/akylbekmaxutov/KazakhEvalKit.git
cd KazakhEvalKit
uv venv qolda-avl-evals --python 3.12
source qolda-avl-evals/bin/activate
uv pip install -r requirements.txtvLLM is not in requirements.txt: its build is tied to your CUDA driver
and GPU, so it is installed separately into the same environment.
For Qolda-AVL models, install the IS2AI fork:
# Install this fork (precompiled binaries)
git clone https://github.com/IS2AI/vLLM-Qolda-AVL.git
cd vLLM-Qolda-AVL
VLLM_USE_PRECOMPILED=1 uv pip install -e .For any other model, upstream vLLM is fine:
uv pip install vllmOnly needed if you will evaluate the audio modality. vLLM decodes audio server-side and checks for these at start-up, so install them before launching the server:
uv pip install av resampy scipy soundfile librosaThe asr benchmark is scored against FLEURS. Download the Kazakh, Russian
and English test sets before running it — every other benchmark is fetched
automatically, this one is not.
The source is google/fleurs,
configs kk_kz, ru_ru and en_us, test split.
Then give run_eval.sh the directory it was downloaded to:
MODALITY=audio BENCHMARKS=asr \
ASR_DATA_DIR=/path/to/fleurs_eval \
MODEL_NAME=qolda-avl-5b ./run_eval.shOr set it once as the default in run_eval.sh, so it need not be repeated:
ASR_DATA_DIR="${ASR_DATA_DIR:-/path/to/fleurs_eval}"The directory is read as exported — nothing needs rearranging:
fleurs_eval/
├── val_test_kaz_kaz.tsv Kazakh columns: id / audio / text
├── val_test_rus_rus.tsv Russian
├── val_test_eng_eng.tsv English
└── wavs/... the audio, referenced by the manifests
Only the three same-language manifests are read. Translation manifests sitting
beside them (val_test_kaz_eng.tsv and similar) are ignored, since speech
translation is not part of this harness.
Other layouts also work. One directory per language is recognised
(fleurs_eval/kk_kz/test.tsv), manifests may be .tsv, .csv or .jsonl
with or without a header, the audio column may be named audio, file_name,
path or file, and the text column text, transcription,
raw_transcription or sentence.
Coverage is per language: if one manifest cannot be found, only that language is affected and the others still load.
Copy the template and fill it in:
cp .env.example .env# Required for the gated issai/* datasets on the Hugging Face Hub.
HF_TOKEN=hf_xxx
# JUDGE_API_KEY wins; OPENAI_API_KEY is the fallback.
JUDGE_API_KEY=sk-xxx.env is git-ignored. It is read from the repository root regardless of your
working directory, and the run refuses to start if JUDGE=true but no key
resolves — rather than discovering it after hours of generation.
Edit the CONFIG block in serve_model.sh, or pass values on the command
line — the command line wins:
MODEL_PATH=/path/to/Qolda-AVL-5B \
SERVED_MODEL_NAME=qolda-avl-5b \
CUDA_DEVICES=0,1,2,3 \
TENSOR_PARALLEL_SIZE=4 \
REASONING_PARSER=qwen3 \
./serve_model.sh| Variable | Default | Notes |
|---|---|---|
MODEL_PATH |
/path_to_model |
Local path or Hub id |
SERVED_MODEL_NAME |
basename of MODEL_PATH |
Must equal MODEL_NAME in run_eval.sh |
CUDA_DEVICES |
0 |
Sets CUDA_VISIBLE_DEVICES |
TENSOR_PARALLEL_SIZE |
1 |
Match the number of GPUs |
MAX_MODEL_LEN |
65536 |
|
MAX_NUM_SEQS |
512 |
Keep ≥ CONCURRENCY in run_eval.sh |
PORT |
8000 |
|
LIMIT_MM_PER_PROMPT |
{"audio": 4, "image": 4} |
Multimodal models; empty to omit |
REASONING_PARSER |
qwen3 |
Returns the trace in reasoning_content; empty for non-reasoning models |
EXTRA_ARGS |
empty | Anything else passed to vllm serve |
--trust-remote-code is always on.
Wait for Application startup complete before starting an evaluation.
Serve the model first, then run the evaluation. run_eval.sh is a client:
it talks to the server over HTTP and does no model loading of its own. If the
server is not up, every request fails.
The full example, for Qolda-AVL-5B:
MODALITY=all SAFETY_EVAL=true TRANSLATION_EVAL=true \
MODEL_NAME=qolda-avl-5b MODEL_NAME_FOLDER=qolda-avl-5b \
LANGUAGES=all BENCHMARKS=all DATA_PORTION=1.0 \
JUDGE=true THINKING=auto \
CONCURRENCY=128 CONCURRENCY_VISION=32 CONCURRENCY_AUDIO=32 \
XCOMET=false \
./run_eval.sh 2>&1 | tee qolda-avl-5b.logThat single command runs text → vision → audio → safety → translation.
Before committing to a full run, smoke-test on half a percent of the data. It exercises every benchmark, every language and every scoring path in minutes:
MODALITY=all SAFETY_EVAL=true TRANSLATION_EVAL=true \
MODEL_NAME=qolda-avl-5b MODEL_NAME_FOLDER=smoke \
LANGUAGES=all BENCHMARKS=all DATA_PORTION=0.005 \
JUDGE=true CONCURRENCY=64 CONCURRENCY_VISION=8 CONCURRENCY_AUDIO=8 \
./run_eval.sh 2>&1 | tee smoke.logThen check one record before trusting anything:
head -1 results/smoke/text/records/kkcopa.kk.jsonl | python -m json.toolthinking and response must be cleanly separated, and prediction must be
a letter. If extraction_failed is high in summary.json, fix THINKING and
THINK_END_TOKEN before going further — everything downstream depends on it.
A full run takes hours. Run both processes inside one tmux session, in two windows — the server in the first, the evaluation in the second — so that a dropped SSH connection kills neither.
tmux new -s evals # creates the session, window 0In window 0, start the server:
source qolda-avl-evals/bin/activate
cd KazakhEvalKit
MODEL_PATH=/path/to/Qolda-AVL-5B SERVED_MODEL_NAME=qolda-avl-5b ./serve_model.shWait for Application startup complete. Then open window 1 with
Ctrl-b c and start the evaluation there:
source qolda-avl-evals/bin/activate
cd KazakhEvalKit
MODALITY=all SAFETY_EVAL=true TRANSLATION_EVAL=true \
MODEL_NAME=qolda-avl-5b MODEL_NAME_FOLDER=qolda-avl-5b \
LANGUAGES=all BENCHMARKS=all DATA_PORTION=1.0 JUDGE=true \
CONCURRENCY=128 CONCURRENCY_VISION=32 CONCURRENCY_AUDIO=32 \
./run_eval.sh 2>&1 | tee qolda-avl-5b.logUseful keys: Ctrl-b 0 / Ctrl-b 1 switch windows, Ctrl-b d detaches,
tmux attach -t evals comes back.
Use the same environment in both windows. A server started from one
virtualenv and an evaluation run from another is the most common cause of
confusing failures — particularly for audio, where the server needs av,
soundfile and friends that the evaluation side does not.
Everything for one model lives in one directory:
results/qolda-avl-5b/
├── performance_report.md every modality, one document
├── text/
│ ├── summary.json scores, per-category breakdowns, settings
│ ├── records/mmlu.kk.jsonl one line per item
│ ├── judge/ judge requests and verdicts
│ └── eval.log
├── vision/ audio/ safety/ translation/
performance_report.md is rebuilt after every pass, so it is complete as soon
as the last one finishes and stays correct if you re-run only one modality. It
holds run settings, a benchmark × language table per modality, per-category
breakdowns for SAKURA, SpokenMQA and Qorgau, and coverage counts.
Each record is one item:
{
"uid": "...", "benchmark": "mmlu", "language": "kk",
"reference": "B", "prediction": "B", "correct": true,
"thinking": "...", "response": "...",
"finish_reason": "stop", "truncated_thinking": false
}thinking and response are kept when SAVE_RESPONSES=true (the default).
| Metric | Scale | Meaning |
|---|---|---|
accuracy_valid_only |
percent | The headline. Over items an answer could be read from |
accuracy |
percent | Over all items, including unreadable ones |
wer |
0–1, four decimals | Word error rate, lower is better |
xcomet, bleu, chrf2 |
0–1, four decimals | Translation quality |
extraction_failed |
count | No parseable answer — a format problem, not a wrong answer |
unscorable_samples |
count | Could not be graded at all (judge missing, symbolic check undecided) |
Every variable below can be set on the command line or edited into
run_eval.sh. The command line wins, so the file holds your defaults and the
command line is the per-run override:
# on the command line
CONCURRENCY=128 TEMPERATURE=0.6 ./run_eval.sh
# or edited into run_eval.sh
CONCURRENCY="${CONCURRENCY:-128}"
TEMPERATURE="${TEMPERATURE:-0.6}"| Variable | Default | Meaning |
|---|---|---|
MODALITY |
text |
text, vision, audio, all, none, or a comma-separated list such as text,vision |
SAFETY_EVAL |
false |
Also run the Qorgau safety pass |
TRANSLATION_EVAL |
false |
Also run the FLORES translation pass |
BENCHMARKS |
all |
Comma-separated benchmark keys |
LANGUAGES |
all |
Any subset of kk,ru,en |
| Variable | Default | Meaning |
|---|---|---|
API_BASE |
http://localhost:8000/v1 |
Set this when the server is on another port or host |
API_KEY |
EMPTY |
Only if vLLM was started with --api-key |
MODEL_NAME |
qolda-avl |
Must equal --served-model-name on the server |
MODEL_NAME_FOLDER |
$MODEL_NAME |
Results directory name; use it to separate runs of the same model |
OUTPUT_ROOT |
results |
Where result folders are created |
SYSTEM_PROMPT |
empty | Prepended to every request |
CONCURRENCY |
32 |
Requests in flight; also bounds media decoding |
CONCURRENCY_VISION |
$CONCURRENCY |
Vision pass only — images are large, 8–32 is sensible |
CONCURRENCY_AUDIO |
$CONCURRENCY |
Audio pass only |
RETRIES |
5 |
Retries per failed request |
REQUEST_TIMEOUT |
1800 |
Seconds per request |
Serving on a non-default port:
API_BASE=http://localhost:8003/v1 MODEL_NAME=qolda-think ./run_eval.sh| Variable | Default | Meaning |
|---|---|---|
TEMPERATURE |
1.0 |
|
TOP_P |
0.95 |
|
TOP_K |
20 |
vLLM extension, sent in extra_body |
MIN_P |
0.0 |
vLLM extension |
PRESENCE_PENALTY |
1.5 |
Vision overrides this to 0.0 |
REPETITION_PENALTY |
1.0 |
vLLM extension |
MAX_TOKENS |
32768 |
Generation budget. With THINKING=on it covers the reasoning trace too |
SAMPLING_SEED |
empty | Server-side seed; empty is non-deterministic |
A model card's recommended settings, passed straight through:
TEMPERATURE=0.6 TOP_P=0.95 TOP_K=20 MIN_P=0 \
PRESENCE_PENALTY=1.5 REPETITION_PENALTY=1.0 MAX_TOKENS=16384 \
MODEL_NAME=qolda-think ./run_eval.shPRESENCE_PENALTY is the one non-neutral default here — if your model card
specifies one, pass it explicitly rather than inheriting 1.5.
One model often wants different decoding per modality. These take any
run_eval flag, not only sampling ones, and apply to that pass alone:
| Variable | Default | Applies to |
|---|---|---|
TEXT_SAMPLING |
empty | text |
VISION_SAMPLING |
--presence_penalty 0.0 |
vision |
AUDIO_SAMPLING |
empty | audio |
SAFETY_SAMPLING |
empty | safety |
TRANSLATION_SAMPLING |
empty | translation |
MODALITY=all \
TEXT_SAMPLING="--max_tokens 32768" \
VISION_SAMPLING="--temperature 0.2 --presence_penalty 0.0 --max_tokens 4096" \
AUDIO_SAMPLING="--temperature 0.4 --max_tokens 2048" \
MODEL_NAME=qolda-avl-5b ./run_eval.sh| Variable | Default | Meaning |
|---|---|---|
THINKING |
auto |
auto leaves the chat template alone — correct for reasoning-native models. on/off set enable_thinking |
THINK_START_TOKEN |
<think> |
Used when the trace is inline rather than in reasoning_content |
THINK_END_TOKEN |
</think> |
|
SAVE_RESPONSES |
true |
Keep the trace and the reply in every record |
STRUCTURED_OUTPUT |
false |
Guided decoding. Leave off for reasoning models: it forces the first token to be {, so <think> can never open |
| Variable | Default | Meaning |
|---|---|---|
DATA_PORTION |
1.0 |
0.05 takes a deterministic 5% sample |
SEED |
42 |
Subsampling seed, not decoding |
DATA_DIR |
datasets |
Bundled local benchmarks |
RESUME |
false |
Reuse records already on disk |
RETRY_UNPARSED |
1 |
Extra passes over items with no parseable answer; 0 disables |
REJUDGE |
false |
Discard existing judge verdicts and grade again |
PROMPT_LANG |
en |
en keeps harness instructions English; auto matches the benchmark |
ASR_DATA_DIR |
/path/to/fleurs_eval |
Your downloaded FLEURS corpus — see section 3.4 |
| Variable | Default | Meaning |
|---|---|---|
IMAGE_MAX_SIZE |
1024 |
Longest edge in pixels; 0 keeps the original. Keep it capped — dynamic-resolution VLMs turn pixels into vision tokens with no upper bound |
IMAGE_FORMAT |
JPEG |
JPEG, PNG or WEBP |
IMAGE_QUALITY |
90 |
JPEG/WEBP quality |
NO_SYSTEM_ROLE |
auto | Gemma-3, InternVL and DeepSeek-VL reject a system turn beside an image; auto-detected from MODEL_NAME |
| Variable | Default | Meaning |
|---|---|---|
AUDIO_SAMPLING_RATE |
16000 |
Every clip is resampled to this on decode |
AUDIO_FORMAT |
WAV |
WAV, FLAC or OGG |
AUDIO_CONTENT_TYPE |
audio_url |
What vLLM accepts; input_audio is the OpenAI schema |
AUDIO_PLACEHOLDER |
empty | Set <audio> if your chat template does not insert its own marker |
AUDIO_MAX_SECONDS |
0 |
Truncate longer clips; 0 keeps them whole |
| Variable | Default | Meaning |
|---|---|---|
XCOMET |
false |
true scores with XCOMET; false saves translations and reports BLEU/chrF++ only |
XCOMET_MODEL |
Unbabel/XCOMET-XXL |
Unbabel/XCOMET-XL is lighter |
XCOMET_BATCH_SIZE |
8 |
|
XCOMET_GPUS |
1 |
|
XCOMET_CACHE_DIR |
empty | comet's own cache when empty |
| Variable | Default | Meaning |
|---|---|---|
JUDGE |
true |
Run the judge where a benchmark needs it |
JUDGE_MODEL |
gpt-5.6-luna |
|
JUDGE_REASONING_EFFORT |
none |
|
JUDGE_MAX_COMPLETION_TOKENS |
32 |
Covers reasoning tokens too. Effort medium needs ~512, or the reply comes back empty |
JUDGE_STRUCTURED |
true |
Strict JSON schema, so a verdict cannot come back malformed |
JUDGE_DIRECT_BELOW |
500 |
Smaller sets skip the Batch API |
JUDGE_DIRECT_CONCURRENCY |
1 |
Judge requests in flight in total; 1 is strictly sequential |
JUDGE_DIRECT_MAX_ATTEMPTS |
2 |
Tries per direct request, with backoff |
JUDGE_SPLIT_ABOVE |
5000 |
Larger sets split into several batch jobs |
JUDGE_SPLIT_PARTS |
3 |
How many |
JUDGE_MAX_PARALLEL |
4 |
Batch jobs in flight |
JUDGE_COMPLETION_WINDOW |
24h |
Batch API completion window |
JUDGE_POLL_INTERVAL |
30 |
Seconds between batch status checks |
JUDGE_TIMEOUT |
86400 |
Seconds to wait for a batch before giving up |
A few options have no run_eval.sh variable. Pass them as arguments instead —
anything after ./run_eval.sh is forwarded to every pass:
./run_eval.sh --list_benchmarks # what is available, then exit
./run_eval.sh --verbose # per-item warnings on stderr
./run_eval.sh --image_detail low # OpenAI image detail hint
./run_eval.sh --judge_base_url https://my-proxy/v1--judge_api_key also exists, but prefer JUDGE_API_KEY in .env so the key
never reaches your shell history.
Everything — all five passes, all benchmarks, all languages, full data. This is the complete evaluation:
MODALITY=all SAFETY_EVAL=true TRANSLATION_EVAL=true \
MODEL_NAME=qolda-avl-5b MODEL_NAME_FOLDER=qolda-avl-5b \
LANGUAGES=all BENCHMARKS=all DATA_PORTION=1.0 \
JUDGE=true THINKING=auto \
CONCURRENCY=128 CONCURRENCY_VISION=32 CONCURRENCY_AUDIO=32 \
XCOMET=false \
./run_eval.sh 2>&1 | tee qolda-avl-5b.logEverything except audio — for a model with no audio encoder:
MODALITY=text,vision SAFETY_EVAL=true TRANSLATION_EVAL=true \
MODEL_NAME=my-model MODEL_NAME_FOLDER=my-model \
LANGUAGES=all BENCHMARKS=all DATA_PORTION=1.0 JUDGE=true \
CONCURRENCY=128 CONCURRENCY_VISION=32 ./run_eval.shA single benchmark:
MODALITY=vision BENCHMARKS=mmstar LANGUAGES=kk \
MODEL_NAME=qolda-avl-5b ./run_eval.shKazakh only, across everything:
MODALITY=all LANGUAGES=kk MODEL_NAME=qolda-avl-5b ./run_eval.shSafety on its own:
MODALITY=none SAFETY_EVAL=true LANGUAGES=kk,ru \
MODEL_NAME=qolda-avl-5b ./run_eval.shGeneration now, judging later — keeps the GPU busy and defers every API queue wait to the end:
MODALITY=all JUDGE=false MODEL_NAME=qolda-avl-5b ./run_eval.sh # GPU phase
MODALITY=all JUDGE=true RESUME=true MODEL_NAME=qolda-avl-5b ./run_eval.shCompare thinking on and off — same served model, two result folders. Use a
model whose chat template supports the switch; a reasoning-only model ignores
THINKING=off and should be left on auto:
THINKING=on MODEL_NAME=qolda MODEL_NAME_FOLDER=qolda-think ./run_eval.sh
THINKING=off MODEL_NAME=qolda MODEL_NAME_FOLDER=qolda-nothink ./run_eval.shThe translation pass always reports BLEU and chrF++, computed locally.
XCOMET is optional and needs its own environment. unbabel-comet
requires huggingface-hub<1.0, transformers<5.0 and numpy<2.0, while
current transformers requires huggingface-hub>=1.5. Installing it beside
requirements.txt downgrades numpy and transformers and breaks datasets.
So the two never share an environment.
Step 1 — generate translations in the main environment (XCOMET=false is the
default):
MODALITY=none TRANSLATION_EVAL=true XCOMET=false \
MODEL_NAME=qolda-avl-5b ./run_eval.shStep 2 — score them in a separate environment. RESUME=true reads the
translations off disk, so nothing is regenerated and the server does not need
to be running:
uv venv xcomet-env --python 3.12 && source xcomet-env/bin/activate
uv pip install -r requirements-xcomet.txt
MODALITY=none TRANSLATION_EVAL=true XCOMET=true RESUME=true \
MODEL_NAME=qolda-avl-5b XCOMET_GPUS=1 ./run_eval.shXCOMET-XXL is 10.7B and gated: accept the licence at
Unbabel/XCOMET-XXL with the
account behind HF_TOKEN, and give it a free GPU.
XCOMET_MODEL=Unbabel/XCOMET-XL (3.5B) is a lighter option.
RESUME=true reuses every record already on disk and generates only what is
missing. Use the same DATA_PORTION and SEED as the original run — a
different portion draws a different sample and nothing matches.
MODALITY=all RESUME=true MODEL_NAME=qolda-avl-5b ./run_eval.shA resume is safe to repeat. Items that produced no parseable answer are
retried once (RETRY_UNPARSED), and that budget is stored per item, so a
second resume does not spend it again. Wrong answers are never regenerated —
only empty ones — so retrying cannot inflate a score.
REJUDGE=true discards verdicts a previous judge run produced and grades them
again, reusing all generation. Use it after changing the judge model, its
effort or its prompt; without it a resumed run sees every judged item already
settled and does nothing.
MODALITY=vision BENCHMARKS=babyvision RESUME=true REJUDGE=true \
MODEL_NAME=qolda-avl-5b ./run_eval.shKazakhEvalKit/
├── run_eval.sh one runner for every pass
├── serve_model.sh vLLM launcher
├── requirements.txt evaluation dependencies
├── requirements-xcomet.txt XCOMET, for a separate environment
├── .env.example credential template
├── core/ modality-agnostic engine
│ ├── runner.py load → generate → score → judge → report
│ ├── loaders.py Hub, JSONL and manifest loading, alignment
│ ├── generation.py the vLLM client
│ ├── extraction.py reasoning/answer separation
│ ├── scoring/ symbolic.py, rules.py, judge.py
│ ├── reporting.py records, metric blocks, summaries
│ └── report_md.py performance_report.md
├── text_modality/ specs, prompts, scoring per modality
├── vision_modality/
├── audio_modality/
├── safety/
├── translation/
└── datasets/ bundled benchmarks (kkCOPA, NIS Math, …)
Each modality package holds specs.py (which benchmark, which source, which
language), prompts.py and modality.py (extraction and scoring). Adding a
benchmark means adding a BenchmarkSpec and an adapter function — the runner,
the judge and the reporting need no changes.