Lune-58M

🤗 Lune-58M-Base  |  📊 Evaluation  |  🛠️ Quickstart

Model Summary

Lune-58M is an experimental small assistant for Russian and English: a 57.8M-parameter language model with an optional 25.9M-parameter vision encoder (83.7M parameters in total). It works as a plain text model, and as a vision-language model when the vision encoder is loaded. It was fine-tuned from Lune-58M-Base with SFT and reinforcement learning. The released weights are a mix of the two stages: 0.7 of the RL weights and 0.3 of the SFT weights.

Highlights:

  • Three reasoning efforts: none, low, high. The model reasons step by step only as much as asked.

  • Tools: Hermes-style function calling with the tools you pass. Trained on 64 different tool schemas (including the tools of a real MCP server): picking the right tool, chained and parallel calls, asking for a missing argument, handling tool errors, and answering directly when no tool is needed.

  • Text or vision: with the vision encoder it reads text in images (ru/en), answers questions about pictures, documents and charts, and accepts short videos (it reads the frames but mostly loses their order). Without it, it is a plain text model and says it cannot see an image instead of inventing one.

  • Fresh knowledge: trained on tech news up to September 2026.

  • Tiny and fast: 561 tok/s decoding on an Apple M2 Max (llama.cpp, F16).

  • Architecture: Qwen3_5ForConditionalGeneration (stock transformers, no custom code)

  • Parameters: 57.8M language model + 25.9M vision encoder = 83.7M total

  • Modes: text only (language model alone) or multimodal (with the vision encoder)

  • Context length: 16,384 tokens

  • Languages: Russian, English

  • Knowledge cut-off: English: March 2026 · Russian: September 2026

  • Precision: bfloat16

  • License: Apache 2.0

Quickstart

pip install -U transformers torch torchvision pillow

The image and video examples use two sample files from this repository's assets folder, made for this card: a receipt rendered in PT Sans and a 10-second slide show of five numbers.

Chat

from transformers import AutoModelForImageTextToText, AutoProcessor

model_id = "ZolMik2004/Lune-58M"
processor = AutoProcessor.from_pretrained(model_id)
model = AutoModelForImageTextToText.from_pretrained(model_id, dtype="auto")

messages = [{"role": "user", "content": [{"type": "text", "text": "Привет! Расскажи о себе."}]}]
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True,
                                       return_dict=True, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=512)
text = processor.decode(out[0, inputs["input_ids"].shape[1]:])
print(text.split("</think>")[-1].replace("<|im_end|>", "").strip())   # the answer without the reasoning

Text only

The checkpoint holds both parts. Runtimes that load only the language model run Lune as a 57.8M text model, and the vision encoder is then simply not used.

from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "ZolMik2004/Lune-58M"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, dtype="auto")   # language model only: 57.8M, no vision weights

messages = [{"role": "user", "content": "Привет! Кто ты?"}]
inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_dict=True, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=512)
print(tokenizer.decode(out[0, inputs["input_ids"].shape[1]:]).split("</think>")[-1].replace("<|im_end|>", "").strip())

Reasoning effort

reasoning_effort Behaviour
none answers directly, no reasoning
low reasons only on hard questions
high (default) reasons on almost every question
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True,
                                       return_tensors="pt", reasoning_effort="low")   # "none" | "low" | "high"

Image

from huggingface_hub import hf_hub_download

# processor and model from the chat example
receipt = hf_hub_download(model_id, "assets/receipt.png")
messages = [{"role": "user", "content": [
    {"type": "image", "path": receipt},          # or {"type": "image", "url": "https://..."}
    {"type": "text", "text": "Что написано на этом чеке?"},
]}]
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True,
                                       return_dict=True, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=512)

Video

Video decoding in transformers needs torchcodec and FFmpeg (pip install torchcodec). The processor takes 2 frames per second, encodes them in pairs and puts a timestamp before each pair.

from huggingface_hub import hf_hub_download

# processor and model from the chat example
messages = [{"role": "user", "content": [
    {"type": "video", "path": hf_hub_download(model_id, "assets/slides.mp4")},
    {"type": "text", "text": "Какое число было показано третьим?"},
]}]
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True,
                                       return_dict=True, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=256)

Video support is minimal. The only video in training was 4,000 synthetic slide shows: 3 to 8 numbers, one every 2 seconds, and a question about which number came at a given place. On 20 new slide shows of five numbers the released model named the right number 6 times with reasoning effort none and 4 times with high. Naming any of the five shown numbers would give 4. So it reads the frames but mostly loses their order.

Tool calling

import json

tools = [{"type": "function", "function": {
    "name": "get_weather",
    "description": "Current weather for a city.",
    "parameters": {"type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"]},
}}]
messages = [{"role": "user", "content": "Какая сейчас погода в Казани?"}]
inputs = processor.apply_chat_template(messages, tools=tools, add_generation_prompt=True, tokenize=True,
                                       return_dict=True, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=256)
text = processor.decode(out[0, inputs["input_ids"].shape[1]:])
# ...</think>\n\n<tool_call>\n{"name": "get_weather", "arguments": {"location": "Казань"}}\n</tool_call><|im_end|>
call = json.loads(text.split("<tool_call>")[1].split("</tool_call>")[0])

messages += [
    {"role": "assistant", "content": "", "tool_calls": [{"type": "function", "function": call}]},
    {"role": "tool", "content": json.dumps({"temp_c": 4, "condition": "rain"})},
]
inputs = processor.apply_chat_template(messages, tools=tools, add_generation_prompt=True, tokenize=True,
                                       return_dict=True, return_tensors="pt")
out = model.generate(**inputs, max_new_tokens=256)   # the final answer, based on the tool result

MLX (Apple Silicon)

pip install -U mlx-lm mlx-vlm
# text: the language model alone
from mlx_lm import generate, load

model, tokenizer = load("ZolMik2004/Lune-58M")
prompt = tokenizer.apply_chat_template([{"role": "user", "content": "Привет! Кто ты?"}], add_generation_prompt=True)
text = generate(model, tokenizer, prompt=prompt, max_tokens=512)
print(text.split("</think>")[-1].strip())

# image: with the vision encoder
from huggingface_hub import hf_hub_download
from mlx_vlm import generate, load
from mlx_vlm.prompt_utils import apply_chat_template

model, processor = load("ZolMik2004/Lune-58M")
receipt = hf_hub_download("ZolMik2004/Lune-58M", "assets/receipt.png")
prompt = apply_chat_template(processor, model.config, "Что написано на этом чеке?", num_images=1,
                             enable_thinking=True)   # reasoning on, as in transformers by default
result = generate(model, processor, prompt, image=[receipt], max_tokens=512)
print(result.text.split("</think>")[-1].strip())

mlx-lm passes reasoning_effort to the chat template as transformers does. mlx-vlm turns reasoning off unless you pass enable_thinking=True to apply_chat_template, and then reasoning_effort works as above. Without reasoning the answer about the sample receipt runs into a loop. Checked with mlx-lm 0.32.2 and mlx-vlm 0.7.11. Both in float32 on the same tokens, an image included, their next-token distributions differ from transformers by a mean KL divergence of 0.00005.

Chat format

<|im_start|>system
Reasoning: high

{system prompt}

# Tools
Call a tool with <tool_call>{"name": ..., "arguments": {...}}</tool_call>.
{"name": "get_weather", "description": "Current weather for a city or place.", "parameters": {...}}<|im_end|>
<|im_start|>user
{message}<|im_end|>
<|im_start|>assistant
<think>
{reasoning}
</think>

<tool_call>
{"name": "get_weather", "arguments": {"location": "Kazan"}}
</tool_call><|im_end|>
<|im_start|>tool
{"temp_c": 4, "condition": "rain"}<|im_end|>
<|im_start|>assistant
<think>
{reasoning}
</think>

{answer}<|im_end|>
  • Every assistant turn has a <think> block. At reasoning_effort="none" it is empty.
  • The system prompt and the tools block are optional. Tools are given as JSON schemas (OpenAI tools=[...] works).
  • An image or a video in a user turn becomes <|vision_start|><|image_pad|>…<|vision_end|> (or <|video_pad|>), and the processor inserts the right number of pad tokens.

Evaluation

Params: the language model for text benchmarks, the total with the vision encoder for vision benchmarks. Three chat checkpoints are compared: after SFT, after RL, and the released weights, which mix the two (see Weight averaging). Only the released weights are published.

Chat — instruction following and math

Each model's own chat template, greedy decoding, lm-evaluation-harness 0.4.13: IFEval prompt-level strict accuracy (the strictest of its four numbers, so it can sit below averages that model authors publish), GSM8K with 5 examples and MGSM without examples, both with the flexible answer extraction. Lune runs at its default reasoning effort (high) and only the answer after the reasoning is scored. The comparison models run in F16 with llama.cpp on a phone (Snapdragon 8 Elite Gen 5) with their own generation settings. lm-evaluation-harness builds the prompts and scores the answers exactly as for transformers, and on spot checks the greedy answers match transformers token for token. nanowhale is evaluated with transformers, llama.cpp does not run it yet.

Model Params IFEval GSM8K MGSM en MGSM ru
Lune-58M · SFT 58M 12.6 1.4 1.2 0.4
Lune-58M · SFT + RL 58M 14.6 1.4 1.2 0.4
Lune-58M (released) 58M 13.7 1.4 3.2 0.0
nanowhale-100m 110M 7.8 2.3 1.6 0.0
SmolLM2-135M-Instruct 135M 21.6 2.0 2.4 0.8
LFM2.5-230M 230M 67.5 28.7 31.2 8.8
gemma-3-270m-it 268M 25.9 6.6 14.0 2.8
Qwen3.5-0.8B 752M 53.6 43.5 34.0 28.4

In its last 300 steps RL rewarded keeping format requirements of the kinds IFEval checks (word limits, no commas, lowercase, a number of bullet points and so on). No IFEval prompts were used, but the IFEval numbers after RL are flattered by this. The ±1.4 standard error of IFEval covers the gap between the RL and released rows. GSM8K and MGSM are near zero for every Lune checkpoint.

Vision

Validation splits, 1,000 questions per benchmark spread evenly over the split (OCRBench has exactly 1,000), the same questions for every model. FP32 in transformers, each model's own image processor, greedy decoding, short answers.

Model Params TextVQA DocVQA (ANLS) ChartQA OCRBench
Lune-58M · SFT 84M 12.8 14.5 10.1 11.8
Lune-58M · SFT + RL 84M 10.0 14.2 1.5 12.0
Lune-58M (released) 84M 11.2 15.5 3.1 12.3
SmolVLM-256M-Instruct 256M 50.7 57.5 13.8 51.1
SmolVLM2-256M-Video-Instruct 256M 51.0 53.5 48.9 56.2
LFM2-VL-450M 451M 68.2 76.5 65.7 65.5
Qwen3.5-0.8B 873M 70.7 89.1 72.5 76.4

Qwen3.5-0.8B ran out of memory on the 6 GB test GPU for 10 DocVQA and 14 OCRBench images. Those were answered from a copy with half the area. Every other answer in the table saw the image at full size.

Fresh knowledge (2025–2026)

The 304 closed-book questions about 2025–2026 tech events from the base model card: four answers each, the model picks the most likely one, so guessing gives 25 %. Asked to answer freely, without tools and reasoning, it gets 1.6 % right. For recent facts it needs the search tool.

Model Overall ru en
Lune-58M 22.7 22.1 23.3

What RL changed (by reasoning effort)

Our own task suite (168 tasks: 12 for each of 14 kinds, half in Russian): arithmetic, equations, dates, sorting, tools, long context, identity, open chat, OCR, counting, recall across images. Loops: share of answers to 3,000 prompts that run into the 2,048-token limit while repeating the same block of text, with greedy decoding and at temperature 0.7. Accuracy is the mean over the 13 kinds with a checkable answer (open chat has none).

Effort Checkpoint Reward Format errors ↓ Truncated ↓ Loops ↓ Wrong language ↓ Accuracy Reasoning tokens (easy / hard)
none SFT 0.38 5.4 % 5.4 % 6.5 % 4.8 % 48.3 % 0 / 0
none SFT + RL 0.54 0.0 % 0.0 % 1.8 % 1.2 % 54.5 % 0 / 0
none released 0.54 0.0 % 0.0 % 2.5 % 1.2 % 54.5 % 0 / 0
low SFT 0.31 10.7 % 4.8 % 5.9 % 4.8 % 46.5 % 17 / 42
low SFT + RL 0.54 0.0 % 0.0 % 0.7 % 1.2 % 55.1 % 13 / 25
low released 0.53 0.0 % 0.0 % 1.0 % 1.2 % 55.1 % 14 / 29
high SFT 0.45 11.3 % 3.0 % 5.9 % 4.2 % 46.4 % 17 / 41
high SFT + RL 0.76 0.0 % 0.0 % 0.4 % 1.2 % 55.8 % 13 / 25
high released 0.74 0.0 % 0.0 % 0.7 % 1.2 % 54.5 % 14 / 29

Speed

Text model in F16, prefill of 512 tokens, decoding of 128, llama.cpp of October 2026. The phone CPU has two cores at 4.6 GHz and six at 3.6 GHz. Prefill is fastest on all eight, decoding on two: for a model this small, more threads cost more in synchronization than they add.

Hardware Backend Prefill tok/s Decode tok/s
Apple M2 Max, 30-core GPU (the Mac it was trained on) llama.cpp, Metal 44,901 561
Snapdragon 8 Elite Gen 5, Adreno 840 GPU llama.cpp, OpenCL 5,902 179
Snapdragon 8 Elite Gen 5, CPU (8 threads / 2 threads) llama.cpp 1,211 370

Training

SFT

  • Samples: 187,256: 107,256 text, 4,000 tool-use dialogs, 76,000 with images or video
  • Tokens: 111.7M, of them 42.2M trained (assistant turns only)
  • Epochs: 2 (3,696 steps)
  • Hardware: 1 × Apple M2 Max, MLX

Data mix:

Source Language Dialogs Content License
Programmatic tasks ru, en 40,000 step-by-step math, logic, dates and units, 6,690 of them with calculator calls own
Long context ru, en 3,000 2k–15k-token retrieval over Wikipedia 2026 (needle, key–value, document and dialog recall) own
Identity ru, en 3,000 who Lune is, abilities, size, vision on / off, knowledge cut-off own
smoltalk en 24,008 magpie-ultra, everyday conversations, constraints, rewrite, summarize Apache 2.0
GrandMaster-PRO-MAX ru 26,000 single-turn instructions Apache 2.0
ru_turbo_saiga ru 8,000 multi-turn dialogs (2–4 user turns) CC BY 4.0
Search tool traces ru, en 3,000 question → search over Wikipedia 2026 + 2025–26 tech news → grounded answer own
Tool-use dialogs ru, en 4,000 64 tool schemas (11 from a real MCP server, everyday and developer tools): single, chained and parallel calls, no tool needed, clarifying questions, tool errors, unsupported requests, all checked against the schemas own, written with Qwen3.8-27B (Apache 2.0)
Gemma 4 E4B dialogs ru, en 248 chat and verified search traces own
Vision ru, en 76,000 OCR of rendered ru/en text (20k), FineVision Q&A on documents, charts, screens and scene text (30k), CC3M captions (8k), counting (6k), recall across images (6k), synthetic videos (4k), identity with an image (2k) per source (FineVision subsets, CC3M), renders own

Filters on open data: answer language, length, broken arithmetic, refusals, other assistants' self-descriptions and their knowledge cut-off ("my data ends in 2023"). In 20 % of samples the user turns carry typos. Each sample is rendered at one of the three reasoning efforts, with loss on assistant tokens only. Tool blocks vary per sample: tool dialogs keep their own, search and calculator traces get 0–4 unrelated tools next to the one they call, and 15 % of the other samples get a tools block they do not need.

RL

  • Algorithm: GRPO family: DAPO dynamic sampling + DHPO mixed ratio, 2 updates per batch, KL 0.005 to SFT
  • Rewards: rule-based: a hard −1 for broken format, truncation or a foreign identity, then correctness of the bold answer, tool use, reasoning matched to the effort, language, repetition, length, OCR character error rate and, in the last 300 steps, the format requirement of the task
  • Steps: 1,300, each with 16 prompts and 8 answers per prompt
  • Tasks: generated with checkable answers, the same kinds as in our task suite. Each kind has difficulty levels: a kind moves up a level when more than 80 % of its recent answers are right and down below 20 %.
  • Format requirements: RL made answers longer than asked. So from step 1,000 about 39 % of the tasks carry a requirement that a rule can check, and breaking it costs 0.5. Short tasks ask for just the number or a single word or phrase. Open chat gets one of 12 constraint kinds of the IFEval type (lowercase or uppercase, no commas, at most or at least N words, N bullet points, N paragraphs, a fixed closing phrase, a title in <<>>, quotes around the whole answer, a P.S., a keyword used twice).
  • Hardware: 1 × Apple M2 Max, MLX

Weight averaging

RL improved text: no broken formats, fewer loops, better accuracy on our tasks. It also made short answers to image questions longer and cost most of ChartQA: 78 % of its ChartQA answers had five words or more, against 34 % after SFT. Mixing the weights of the two stages wins back part of the vision scores at a small cost in text. The mix is linear, the same share for every tensor, the vision encoder included. Share of RL in the weights:

Share of RL Reward (high) Loops ↓ IFEval TextVQA DocVQA ChartQA OCRBench
0 (SFT) 0.45 6.1 % 12.6 12.8 14.5 10.1 11.8
0.3 0.63 3.0 % 14.6 12.1 15.0 7.0 11.4
0.5 0.71 2.1 % 12.6 11.5 15.4 5.2 11.9
0.7 (released) 0.74 1.4 % 13.7 11.2 15.5 3.1 12.3
1 (RL) 0.76 1.0 % 14.6 10.0 14.2 1.5 12.0

Keeping the SFT vision encoder with the RL language model did not bring ChartQA back (it fell further), so charts are lost in the language model.

A short anti-loop stage in the spirit of FTPO from K2-Horizon was planned after RL. It trains on the token where a repetition loop starts. After RL only 1 % of answers to 3,000 prompts looped, which gave 30 such tokens, too few to train on, so the stage was skipped.

Identity

Lune calls itself a small experimental language model named Lune. Asked about its size, it gives about 58M parameters in the language model and a separate vision encoder of about 26M that is connected when needed. It does not name its maker, answers "I'm Lune" when asked whether it is ChatGPT or Claude, and gives September 2026 as its knowledge cut-off (March 2026 for English sources).

Greedy answers of the released model with reasoning effort none, copied as they are:

Question Answer
Who are you? I'm Lune, a small experimental language model. My abilities are limited, but I'll do my best to help.
Кто ты? Я Lune. Это экспериментальная языковая модель — неовиовидный мир с относительно маленьким набором данных. Могу ошибаться, но постараюсь помочь.
How many parameters do you have? I'm Lune-58M: about 58 million parameters in the language model. Vision is a separate encoder of about 26 million that can be connected when needed.
Are you Claude? I'm Lune, a small experimental language model. My abilities are limited, but I'll do my best to help.
What is your knowledge cutoff? I know about the world up to roughly September 2026 (English sources up to March 2026). Anything newer is unknown to me unless you give me search access.
Кто тебя создал? Я Lune. Это экспериментальная языковая модель — неовиовиовища, которая говорит по-русски и по-английски. Многого от меня ждать не стоит, но с простыми вещами помогу.

The made-up words in the Russian answers ("неовиовидный", "неовиовиовища") are the model's own. Asked who made it, it usually says who it is instead.

Limitations

  • Knows little and makes mistakes, especially with facts and long reasoning.
  • Small print OCR and complex visual reasoning are beyond it.
  • Answers about images are often longer than asked. Charts suffer most: ChartQA fell from 10.1 after SFT to 3.1.
  • Video: it reads the frames but mostly loses their order. The only video in training was synthetic slide shows.
  • Tool use was trained on simulated tool outputs, so it trusts whatever a tool returns.
  • It learned to look things up mostly through a tool named search with one query field, and less often under other names. A search tool with that name and field works most reliably.
  • No safety tuning. It was not trained to refuse.
  • It knows the topics of 2025–2026 news, not their facts: for anything recent it needs the search tool.

License

The model weights and code are released under the Apache 2.0 license. The training datasets are distributed under their own licenses (see the links above).

Citation

@misc{lune58m2026,
  title  = {Lune-58M: a small Russian and English language model with an optional vision encoder},
  author = {ZolMik2004},
  year   = {2026},
  url    = {https://huggingface.co/ZolMik2004/Lune-58M}
}
Downloads last month
20
Safetensors
Model size
83.7M params
Tensor type
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for ZolMik2004/Lune-58M

Finetuned
(1)
this model

Datasets used to train ZolMik2004/Lune-58M