Instructions to use MartinV/clement-mk-gemma-12b with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use MartinV/clement-mk-gemma-12b with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="MartinV/clement-mk-gemma-12b") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("MartinV/clement-mk-gemma-12b", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use MartinV/clement-mk-gemma-12b with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf MartinV/clement-mk-gemma-12b:Q4_K_M # Run inference directly in the terminal: llama cli -hf MartinV/clement-mk-gemma-12b:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf MartinV/clement-mk-gemma-12b:Q4_K_M # Run inference directly in the terminal: llama cli -hf MartinV/clement-mk-gemma-12b:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf MartinV/clement-mk-gemma-12b:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf MartinV/clement-mk-gemma-12b:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf MartinV/clement-mk-gemma-12b:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf MartinV/clement-mk-gemma-12b:Q4_K_M
Use Docker
docker model run hf.co/MartinV/clement-mk-gemma-12b:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use MartinV/clement-mk-gemma-12b with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "MartinV/clement-mk-gemma-12b" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "MartinV/clement-mk-gemma-12b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/MartinV/clement-mk-gemma-12b:Q4_K_M
- SGLang
How to use MartinV/clement-mk-gemma-12b with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "MartinV/clement-mk-gemma-12b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "MartinV/clement-mk-gemma-12b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "MartinV/clement-mk-gemma-12b" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "MartinV/clement-mk-gemma-12b", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use MartinV/clement-mk-gemma-12b with Ollama:
ollama run hf.co/MartinV/clement-mk-gemma-12b:Q4_K_M
- Unsloth Desktop
- Docker Model Runner
How to use MartinV/clement-mk-gemma-12b with Docker Model Runner:
docker model run hf.co/MartinV/clement-mk-gemma-12b:Q4_K_M
- Lemonade
How to use MartinV/clement-mk-gemma-12b with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull MartinV/clement-mk-gemma-12b:Q4_K_M
Run and chat with the model
lemonade run user.clement-mk-gemma-12b-Q4_K_M
List all available models
lemonade list
- Atomic Chat
Clement 12B
An open Macedonian language model β and the data-quality research that produced it.
Named after Π‘Π²Π΅ΡΠΈ ΠΠ»ΠΈΠΌΠ΅Π½Ρ ΠΡ ΡΠΈΠ΄ΡΠΊΠΈ β St. Clement of Ohrid, who taught at the Ohrid Literary School in the 9th century.
π Full write-up, data pipeline and evaluation code on GitHub Β· π The 235-dataset census that started the project Β· π³ Complete blind arena record
The short version
This project began as a research question, not a model: what are Macedonian language models actually trained on? An audit of the whole public landscape β 235 datasets, every size and URL verified β found the answer was largely machine-translated English. The biggest Macedonian "corpora" are Marian-MT output measured in hundreds of gigabytes; the biggest instruction set has 4.12M rows of visibly broken MT; native preference data did not exist.
What that trains into a model is translationese: calqued English syntax, dictionary-correct words nobody says, and Serbian/Bulgarian bleed inherited from crawls whose language ID was never checked by someone who speaks the language.
So we tested a thesis: natively-authored Macedonian, screened for cross-language bleed and curated by a native speaker, should out-express a much larger pile of translated text β even with modest compute. Clement is that experiment.
Result: 39β9β2 (81%) against LVSTCK/domestic-yak-8B-instruct in a blind,
sealed-key head-to-head on 50 native-written prompts. On the naturalness
categories β conversation, writing, everyday questions, language, explanations β
it is 23β1β1.
It got there on ~11% of the Macedonian corpus we had prepared β the pretraining run was never finished. A 12B beating an 8B invites the obvious explanation, that it saw more data. It saw less. It saw cleaner.
What it was built for, and what it was not
We optimized how the model writes and reasons in Macedonian, not how much it knows β and the pretraining stage was never finished.
| Filtered Macedonian corpus available | 4,147,663 docs β 3.2B tokens |
| Training mixture built from it | 2.0B tokens |
| CPT actually trained on | ~551M β 28% of the mixture |
| Macedonian actually seen | ~358M β 11% of the corpus |
It stopped at 8,399 of 30,518 planned steps (7,699 constant-LR + a 700-step decay to zero), and everything after was built on that partially-adapted base. Roughly nine-tenths of the Macedonian text we prepared was never trained on.
That trade-off is visible in both directions. It wins decisively on register, conversation and explanation. It still fabricates long-tail facts, especially Macedonian cultural and historical detail β culture is the one arena category it loses, 1β4.
A finding worth repeating, because we measured it three separate times including with a dedicated knowledge-anneal stage: SFT-scale training reliably shapes style but does not make long-tail facts retrievable. Style moved. Facts did not stick. Pretraining owns knowledge; fine-tuning owns behavior.
Results
Blind arena vs LVSTCK/domestic-yak-8B-instruct
| Wins | Losses | Ties | |
|---|---|---|---|
| Clement 12B | 39 | 9 | 2 |
Naturalness categories (ΡΠ°Π·Π³ΠΎΠ²ΠΎΡ, ΠΏΠΈΡΡΠ²Π°ΡΠ΅, ΡΠ΅ΠΊΠΎΡΠ΄Π½Π΅Π²Π½ΠΈ, ΡΠ°Π·ΠΈΠΊ, ΠΎΠ±ΡΠ°ΡΠ½ΡΠ²Π°ΡΠ°): 23β1β1. Weakest category: ΠΊΡΠ»ΡΡΡΠ°, 1β4.
All 50 prompts, both answers, the sealed key, every vote with its written rationale, and a script that recomputes the tally are published in full.
On the judge, honestly: voting was done by an LLM under a sealed-key protocol on the native speaker's delegation, rationales spot-checked and endorsed by them afterwards. It is not a panel of independent native speakers.
Benchmarks
lm-evaluation-harness 0.4.12, loglikelihood accuracy.
| Task | base gemma-4-12B | domestic-yak-8B | Clement 12B |
|---|---|---|---|
| exams_mk β | 0.5600 | 0.5277 | 0.5470 |
| copa_mk β | 0.7020 | 0.7330 | 0.7570 |
| include_mk β | 0.5762 | 0.5604 | 0.5604 |
| arc_challenge_mk | 0.3012 | 0.3387 | 0.3473 |
| arc_easy_mk | 0.5067 | 0.5484 | 0.5274 |
| boolq_mk | 0.7832 | 0.7890 | 0.7807 |
| hellaswag_mk | 0.4497 | 0.4524 | 0.4754 |
| openbookqa_mk | 0.3020 | 0.3040 | 0.3260 |
| piqa_mk | 0.6687 | 0.6850 | 0.6872 |
| winogrande_mk | 0.6314 | 0.6338 | 0.6140 |
| Average | 0.5481 | 0.5572 | 0.5622 |
β human-written Macedonian. exams_mk and include_mk are natively
authored; copa_mk is professionally human-translated. The other seven are
machine-translated ports of English benchmarks.
Read this honestly: Clement has the best average, but the gap to yak is z = 0.82 β a statistical tie. Claiming a benchmark win at this margin is claiming noise.
The interesting split is underneath. Seven of these tasks are machine-translated ports of English benchmarks; the other three are Macedonian written by people. Clement's margin over yak on the three human-written tasks is +0.0144; on the seven machine-translated ones, +0.0010.
Tighten the definition and the effect shrinks but survives: counting only the two
natively authored sets and moving human-translated copa_mk in with the rest,
it is +0.0096 against +0.0038. Either way the gains land where the
language is real β the same pattern the arena shows, measured a completely
different way. Both splits are computable from the
published harness output.
For scale, not comparison: gemini-3-flash scores 95.3% on exams_mk under a generative protocol. That places the whole open 8β12B class honestly.
Files
| File | Size | Use it if |
|---|---|---|
Clement-12B-Q5_K_M.gguf |
8.5 GB | You have β₯12GB VRAM, or plenty of RAM. Best quality. |
Clement-12B-Q4_K_M.gguf |
7.4 GB | Default choice. Runs on 8GB VRAM with partial CPU offload. |
Clement-12B-IQ4_XS.gguf |
6.7 GB | 8GB VRAM and you want more layers on the GPU. |
Modelfile |
β | Ollama template, matching the training render byte-for-byte |
adapters/ |
β | LoRA adapters for all four stages (cpt Β· anneal Β· sft Β· orpo) |
Grab one file rather than cloning the whole repo:
pip install -U "huggingface_hub[cli]"
hf download MartinV/clement-mk-gemma-12b \
Clement-12B-Q4_K_M.gguf Modelfile --local-dir ./clement
cd clement
Quickstart β Ollama
# Ollama must be recent enough for the Gemma-4 architecture β update first.
curl -fsSL https://ollama.com/install.sh | sh
ollama create clement -f Modelfile
ollama run clement "ΠΠ±ΡΠ°ΡΠ½ΠΈ ΠΌΠΈ ΠΊΠ°ΠΊΠΎ ΡΡΠ½ΠΊΡΠΈΠΎΠ½ΠΈΡΠ° ΠΈΠ½ΡΠ»Π°ΡΠΈΡΠ°ΡΠ°, Π°ΠΌΠ° Π΅Π΄Π½ΠΎΡΡΠ°Π²Π½ΠΎ ΠΊΠΎ Π·Π° Π΄Π° ΡΠ°Π·Π±Π΅ΡΠ΅ Π±Π°Π±Π° ΠΌΠΈ."
The Modelfile already sets the prompt format, stop token and sampling defaults.
llama.cpp
The correct chat template is already embedded in the GGUF, so llama.cpp applies it for you β you do not need to build the prompt by hand.
# interactive chat
llama-cli -m Clement-12B-Q4_K_M.gguf -ngl 99 -c 4096 -cnv \
--temp 0.7 --top-p 0.95
# OpenAI-compatible server; /v1/chat/completions applies the template
llama-server -m Clement-12B-Q4_K_M.gguf -ngl 99 -c 4096 --host 0.0.0.0 --port 8080
curl http://localhost:8080/v1/chat/completions -H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"ΠΠΎΠΈ ΡΠ΅ Π½Π°ΡΡΠ±Π°Π²ΠΈΡΠ΅ ΠΌΠ΅ΡΡΠ° Π·Π° Π²ΠΈΠΊΠ΅Π½Π΄ Π²ΠΎ ΠΠ°ΠΊΠ΅Π΄ΠΎΠ½ΠΈΡΠ°?"}],
"temperature":0.7,"top_p":0.95}'
Lower -ngl if the model does not fit in VRAM (-ngl 20 puts 20 layers on the
GPU and the rest on CPU).
Python
pip install llama-cpp-python
from llama_cpp import Llama
llm = Llama(model_path="Clement-12B-Q4_K_M.gguf", n_ctx=4096, n_gpu_layers=-1)
out = llm.create_chat_completion(
messages=[{"role": "user", "content": "ΠΠΎΡΠ° Π΅ ΡΠ°Π·Π»ΠΈΠΊΠ°ΡΠ° ΠΌΠ΅ΡΡ Π²ΠΈΡΡΡ ΠΈ Π±Π°ΠΊΡΠ΅ΡΠΈΡΠ°?"}],
temperature=0.7, top_p=0.95, max_tokens=1024)
print(out["choices"][0]["message"]["content"])
create_chat_completion uses the template embedded in the GGUF. If you are
driving a raw completion endpoint instead, build the prompt yourself and stop on
<turn|>:
def ask(question: str, system: str | None = None, temperature: float = 0.7) -> str:
# System messages fold into the first user turn β the training convention.
body = f"{system}\n\n{question}" if system else question
out = llm(f"<|turn>user\n{body}<turn|>\n<|turn>model\n",
max_tokens=1024, temperature=temperature, top_p=0.95, stop=["<turn|>"])
return out["choices"][0]["text"].strip()
print(ask("ΠΠΎΠ³Π° ΠΠ°ΠΊΠ΅Π΄ΠΎΠ½ΠΈΡΠ° Π²Π»Π΅Π·Π΅ Π²ΠΎ ΠΠ£?", temperature=0.3))
Prompt format
This is baked into the GGUF, so most tools apply it automatically. You only need it if you are driving a raw completion endpoint or re-converting the model:
<|turn>user
{user message}<turn|>
<|turn>model
{model reply}<turn|>
- Generation prompts end at
<|turn>model\n. - Stop at
<turn|>. - No separate system role β a system prompt is prepended to the first user message, separated by a blank line.
- Do not use the upstream canonical Gemma-4 template. It injects a thought-channel scaffold this model never saw in training, producing junk prefixes and broken stops.
Sampling: temperature 0.7, top_p 0.95 is the default and what the arena
was run at. For fact-leaning use, drop to temperature 0.3 β it measurably
reduces confident fabrication.
What it sounds like
Verbatim from the published arena record β the answers that were actually voted on, not cherry-picked reruns.
It refuses a leading question instead of flattering you:
ΠΠΈΡΠΎΠ»Π° Π΅ Π½Π°ΡΠ΄ΠΎΠ±ΡΠΈΠΎΡ Π³ΡΠ°Π΄ Π·Π° ΠΆΠΈΠ²Π΅Π΅ΡΠ΅ Π²ΠΎ ΠΠ°ΠΊΠ΅Π΄ΠΎΠ½ΠΈΡΠ°, Π½Π΅Π»ΠΈ? Π‘Π°ΠΌΠΎ ΡΠΎΠ³Π»Π°ΡΠΈ ΡΠ΅.
ΠΠ±Π΅ Π½Π΅ Π±Π°Ρ, ΡΠ΅ΠΊΠΎΡ ΡΠΈ Π³ΠΎ ΡΠ°Π»ΠΈ ΡΠ²ΠΎΠ΅ΡΠΎ. ΠΠ° ΠΠΈΡΠΎΠ»Π° ΡΠ΅ Π·Π±ΠΎΡΡΠ²Π° Π΄Π΅ΠΊΠ° Π΅ βΠ³ΡΠ°Π΄ Π½Π° ΠΊΠΎΠ½Π·ΡΠ»ΠΈΡΠ΅" ΠΈ Π΄Π΅ΠΊΠ° ΠΈΠΌΠ° Π΄ΡΡΠ°, Π°ΠΌΠ° ΡΠΎΠ° Π΅ Π²Π΅ΡΠ΅ ΠΌΠ°Π»ΠΊΡ ΠΈΡΡΡΠΎΡΠ΅Π½ΠΎ. ΠΠΈΡΡΠΈΠ½Π°ΡΠ° Π΅ Π΄Π΅ΠΊΠ° ΡΠ°ΠΌΡ Π΅ ΡΠΎΠΏ Π°ΠΊΠΎ ΡΠ°ΠΊΠ°Ρ ΠΌΠΈΡ, ΠΏΡΠΎΡΠ΅ΡΠΊΠΈ Π½Π° Π¨ΠΈΡΠΎΠΊ Π‘ΠΎΠΊΠ°ΠΊ ΠΈ Π΄ΠΎΠ±ΡΠ° Ρ ΡΠ°Π½Π°, Π°ΠΌΠ° ΠΈ Π΄Π΅ΠΊΠ° Π·Π½Π°Π΅ Π΄Π° Π±ΠΈΠ΄Π΅ Π΄ΠΎΡΠ°Π΄Π½ΠΎ Π°ΠΊΠΎ ΡΠΈ ΠΌΠ»Π°Π΄ ΠΈ Π±Π°ΡΠ°Ρ Π½Π΅ΠΊΠΎΡΠ° ΠΏΠΎΠ³ΠΎΠ»Π΅ΠΌΠ° Π΄ΠΈΠ½Π°ΠΌΠΈΠΊΠ°.
It admits what it cannot know:
ΠΠΎΡ ΡΠ΅ ΠΏΠΎΠ±Π΅Π΄ΠΈ Π½Π° ΡΠ»Π΅Π΄Π½ΠΈΡΠ΅ ΠΈΠ·Π±ΠΎΡΠΈ ΠΊΠ°Ρ Π½Π°Ρ?
ΠΠ΅ Π·Π½Π°ΠΌ, Π½Π΅ ΡΡΠΌ Π³Π°ΡΠ°Ρ. ΠΠ·Π±ΠΎΡΠΈΡΠ΅ ΡΠ΅ ΡΠ»ΠΎΠΆΠ΅Π½Π° ΠΌΠ°ΡΠ΅ΠΌΠ°ΡΠΈΠΊΠ° ΠΈ ΠΌΠ½ΠΎΠ³Ρ ΡΠ°Π±ΠΎΡΠΈ Π·Π°Π²ΠΈΡΠ°Ρ ΠΎΠ΄ ΠΊΠ°ΠΌΠΏΠ°ΡΠ°ΡΠ° ΠΈ ΡΠ°ΡΠΏΠΎΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΡΠΎ Π½Π° Π³ΡΠ°ΡΠ°Π½ΠΈΡΠ΅ β ΡΠ΅ΡΠΊΠΎ Π΅ Π΄Π° ΡΠ΅ ΠΏΡΠ΅Π΄Π²ΠΈΠ΄ΠΈ ΡΠΎ ΡΠΈΠ³ΡΡΠ½ΠΎΡΡ.
It corrects a false premise rather than accepting it:
ΠΠΎΠ³Π° ΡΠΎΡΠ½ΠΎ ΠΠ°ΠΊΠ΅Π΄ΠΎΠ½ΠΈΡΠ° Π²Π»Π΅Π·Π΅ Π²ΠΎ ΠΠ²ΡΠΎΠΏΡΠΊΠ°ΡΠ° Π£Π½ΠΈΡΠ°?
Π’ΡΠΊΠ° ΠΈΠΌΠ° ΠΌΠ°Π»ΠΎ Π½Π΅Π΄ΠΎΡΠ°Π·Π±ΠΈΡΠ°ΡΠ΅. [β¦] ΠΠ°ΠΊΠ΅Π΄ΠΎΠ½ΠΈΡΠ° ΡΓ¨ ΡΡΡΠ΅ Π½Π΅ Π΅ ΡΠ»Π΅Π½ΠΊΠ° Π½Π° ΠΠ£. Π’ΠΎΡ ΠΏΡΠΎΡΠ΅Ρ Π΅ Π²ΠΎ ΡΠ΅ΠΊ, Π° Π½ΠΈΠ΅ ΠΎΡΠΈΡΠΈΡΠ°Π»Π½ΠΎ Π°ΠΏΠ»ΠΈΡΠΈΡΠ°Π²ΠΌΠ΅ Π·Π° ΡΠ»Π΅Π½ΡΡΠ²ΠΎ Π²ΠΎ 2004 Π³ΠΎΠ΄ΠΈΠ½Π°.
It answers dialect in kind, without correcting the user:
Π¨ΠΎ ΠΏΡΠ°ΠΈΡ Π±Π΅, ΠΌΠΎΠ΅ Π»ΠΈ Π΄Π° ΠΌΠΈ Π½Π°ΠΏΠΈΡΠ΅Ρ Π½Π΅ΡΠΎ ΠΊΡΠ°ΡΠΊΠΎ Π·Π° ΡΠΎΠ΄Π΅Π½Π΄Π΅Π½ Π½Π° Π±ΡΠ°Ρ ΠΌΠΈ?
ΠΡΠ΄Π΅, ΡΡΠΎ ΡΠ°ΠΊΠ°Ρ Π΄Π° ΠΌΡ Π½Π°ΠΏΠΈΡΠ΅Ρ? ΠΠ°ΡΠ΄ΠΈ Π½Π΅ΡΡΠΎ ΡΡΠΎ Π·Π½Π°Π΅Ρ Π΄Π΅ΠΊΠ° Π³ΠΎ ΡΠ°ΠΊΠ° β Π½Π΅ΠΊΠΎΡΠ° Π·Π°Π΅Π΄Π½ΠΈΡΠΊΠ° ΡΠΎΡΠ° ΠΈΠ»ΠΈ ΡΠΏΠΎΠΌΠ΅Π½ ΠΎΠ΄ Π΄Π΅ΡΡΡΠ²ΠΎ.
For the failure cases, read the nine losses in the same file. They cluster in the culture category, and they are not hidden.
Using the adapters
The GGUFs are the merged, quantized model β most people want those. The
adapters/ directory is for reproducing or continuing the training:
adapters/cpt/ LoRA r=256 over google/gemma-4-12B (text tower)
adapters/anneal/ r=64, applied to the merged CPT weights
adapters/sft/ r=64, applied to the merged anneal weights
adapters/orpo/ r=64, applied to the merged SFT weights
They are sequential, not independent β each was trained on the previous
stage's merged output, so merge in order (cpt β anneal β sft β orpo). Merge
script: train/merge_adapter.py.
How it was made
| Stage | What | Configuration |
|---|---|---|
| Base | google/gemma-4-12B (pt) |
text tower extracted from the multimodal checkpoint; tokenizer unchanged |
| 1. CPT | Macedonian adaptation | LoRA r=256, rsLoRA on attention + MLP projections only (embeddings were not trained β the trainer silently ignored the target), lr 2e-5, seq 4096 packed. WSD: 7,699 stable + 700 decay steps = ~551M tokens, 28% of the 2.0B planned. |
| 2. Anneal | knowledge injection | 11,940 curated texts, each fact as ~10 paraphrases, r=64, lr 1e-5, 3 ep |
| 3. SFT | behavior and register | 28,862 conversations, r=64 Ξ±=64, lr 1e-4 cosine, 2 ep, response-only loss, style constitution as system prompt |
| 4. ORPO | preference tuning | 5,899 pairs, Ξ²=0.1, lr 5e-6, 1 ep, 500-pair frozen holdout |
Mixture: 65% Macedonian, 20% English replay (fineweb-edu), 10% math/code,
5% MKβEN parallel β built at 2.0B tokens, of which ~551M were trained on.
The data pipeline is the actual contribution, and it is documented in depth in the GitHub README: a two-layer Serbian/Bulgarian/Russian bleed screen (script inventory + a 308-term native-approved lexical blocklist), KenLM perplexity filtering with a separate threshold for spoken register, embedding near-dedup, and n-gram + Jaccard decontamination against every eval set. Synthetic data was always authored natively, never translated. Preference pairs were built by construction β round-trip MT, length-bloat, bleed substitution β because an LLM judge failed validation.
Throughout: automated filters propose, the native speaker disposes. A 700-conversation anchor set (hand-written seeds, LLM-continued under rules derived from them, author-edited), review batches at every stage, every threshold ratified by a person.
Stack: Unsloth Β· TRL Β· PEFT Β· bitsandbytes (4-bit QLoRA) Β· transformers Β· lm-evaluation-harness Β· llama.cpp. Single rented A100 80GB.
Design choices
Built from the pretrained checkpoint, not -it. Gemma ships as pt (a raw
next-token predictor) and -it (Google's instruction-tuned chat model). Starting
from -it means inheriting an assistant personality shaped mostly on English
data, which in another language surfaces as precisely the translated register
this project exists to avoid. From pt, every habit the model has came from the
style constitution and the data.
We tested it rather than assuming: fine-tuning the -it text tower on the same
Macedonian mix scored 20β22β8 in the same blind arena β a dead heat. That is
what justified the expensive path of continued pretraining from pt, which ended
at 39β9β2. The cost is that everything -it gives free β turn-taking, refusals,
format compliance β had to be built, and anything not explicitly built is absent.
Tokenizer unextended. Gemma's 262k multilingual vocabulary already covers Macedonian Cyrillic; adding tokens would mean randomly-initialised embeddings that must be trained before the model is even coherent. Keeping it fixed also keeps the model a drop-in for Gemma-4 tooling.
LoRA, not full fine-tuning β 4-bit QLoRA on a single GPU, r=256 across 328 modules at the pretraining stage.
Text only β a constraint, not a preference. At the time this was built the training stack could not train Gemma-4's unified multimodal checkpoint and the eval harness could not load it, so the text tower was extracted (verified by perplexity parity) and the vision/audio embedders dropped. That limitation may have lifted since; re-test before assuming.
Scope β the evaluation defined the model. The project answered one question
with one instrument: a 50-prompt native-language arena plus a benchmark battery.
Everything in that instrument is what the model became; everything outside it is
absent. There was no tool-calling category, so there is no tool-calling data, no
tool role, and no tool-calling ability β likewise structured output and
explicit reasoning. They were never judged and rejected; they were never in
frame. Clement is a strong Macedonian writer, and a weak component.
Limitations
- Fluency outruns knowledge β it sounds most confident exactly where it is least reliable, which is the more dangerous failure mode. On a 20-item arithmetic probe it scores 16/20, and the four misses are fluent, confident, well-structured Macedonian containing a wrong number.
- Culturally contested topics need care. Macedonian history contains genuinely contested framings and the model does not reliably signal when it has picked one.
- It may call itself βΠ‘ΠΊΠ°ΠΆΠ½Π°" β the project codename, which was its identity in the style constitution used as the training system prompt.
- Text only. Gemma-4-12B is multimodal; this was built from the extracted text tower, with the 11 vision/audio embedder tensors dropped before training. It cannot see images or hear audio, and that cannot be recovered from these files.
- No explicit reasoning mode. Nothing was trained to use Gemma-4's thought channel β the data contains answers, not reasoning traces β so the shipped template omits it. The model reasons inline or not at all.
- No tool / function calling. The template has no
toolrole, and the training mix contains zero tool-calling examples across 28,603 assistant turns. - Structured output untrained. None of those assistant turns is JSON-shaped.
Markdown structure is well handled; for machine-readable output, constrain the
sampler (GBNF /
response_format) rather than trusting the prompt. - Embeddings untrained. The token embeddings are the base model's, unchanged, as is the tokenizer β all adaptation is in the transformer projections.
- Effective context 4096 tokens as shipped. English is inherited from the base model; it was monitored, not optimized.
- Not safety-tuned for deployment. No red-teaming was performed.
Findings β what we would do differently
The full write-up is in the repository. The short version, for anyone adapting a model to an under-resourced language:
- Finish the pretraining first. Pretraining owns knowledge; fine-tuning owns behaviour. We stopped pretraining at 28% and then spent three rounds fighting a fabrication problem that was the arithmetic consequence of that decision.
- Fact injection does not work at fine-tuning scale. We tried three times, escalating each round, and measured 0% β 1% β still fabricating. A repeated measurement is a result: change the class of intervention, not the dose. The two that work are more pretraining tokens and retrieval β and we had already measured a retrieval component at 94% before shipping without it.
- Assert that what you configured actually happened. Our config requested embedding training; the module filter silently never matched it, and the loss curve looked perfect throughout. Check the trainable-parameter count before every run.
- The evaluation suite is the specification. This model has no tool calling, structured output or explicit reasoning because none of them was in the arena. You get exactly what you measure. Decide the capability matrix first and build a frozen eval for every row before training starts.
- For data: audit the landscape before building; let automated filters propose and a native speaker dispose; screen explicitly for cross-language bleed if your language shares a script with larger neighbours; split perplexity thresholds by register; build preference pairs by construction rather than by judgment; and try to fool your own validators before trusting them.
The pattern underneath all of them: the measurement always existed and was recorded correctly β the embedding no-op, the failed fact injection, the 94% retrieval, the deprecated API error. Nothing was missed. What was missing was the machinery that converts an observation into a stop: assertions that fail the run, gates that block the next stage, revisit triggers on decisions that expire.
Citation
@misc{clement12b2026,
title = {Clement 12B: native-data curation for Macedonian language modeling},
author = {Vasilkovski, Martin},
year = {2026},
url = {https://github.com/MartinV279/clement-mk-gemma-12b}
}
License
Weights are a derivative of google/gemma-4-12B and are released under the
Gemma Terms of Use. The code in the GitHub
repository is Apache 2.0.
Built with the Macedonian NLP ecosystem's public work β in particular
LVSTCK for the corpus, the SFT seed, and
domestic-yak, a genuinely good model and a demanding baseline.
- Downloads last month
- 841
4-bit
5-bit
Model tree for MartinV/clement-mk-gemma-12b
Base model
google/gemma-4-12B