Temper 0.5B — моя небольшая модель, дообученная на базе Qwen2.5-Coder-0.5B‑Instruct, упор которой сделан на языке Rust. Узкая специализация позволила ей обойти модели крупнее, вплоть до x3 по параметрам. Целью дообучения было проверить возможности крошечной модели в узкой задаче, сравнить с общими того же размера и выше.

Первая версия Temper

Изначально в качестве учителя был выбран Qwen3.6–35B‑A3B, который на карте NVIDIA A40 48GB за ~9 часов смог выдать 2700 примеров на Rust. Формат данных для обучения был проще, чем в итоговой версии Temper и хуже фильтровался от мусора. В каждом примере была функция с интерфейсом, doc‑комментарием, просьбой дописать и пустым телом с одним todo!(). Первая попытка с этим датасетом уже дала заметный рост по сравнению с базовым Qwen2.5-Coder-0.5B.

После трёх эпох обучения на этих примерах первая версия решала с первой попытки 64,6% тривиальных задач против 45,6% у базовой Qwen2.5-Coder-0.5B. На простых задачах (уровень немного выше тривиальных) её результат вырос больше чем вдвое, с 15,0% до 34,3%. Прирост на наборах MultiPL‑E был меньше, составил 10 пунктов на humaneval‑rs и 7 на mbpp‑rs.

Модель

Тривиальные

Простые

Средние

HumanEval‑RS

MBPP‑RS

Temper 0.5B

81,8%

59,0%

18,0%

34,7%

38,8%

Первая версия Temper

64,6%

34,3%

5,0%

27,1%

30,8%

Qwen2.5-Coder-0.5B

45,6%

15,0%

1,0%

16,9%

23,4%

Таблица 1. Процент задач, решённых с первой попытки (pass@1). Первая версия Temper и итоговая Temper 0.5B против базовой Qwen2.5-Coder-0.5B.

Второй датасет

Дальше датасет вырос до 10 тысяч примеров с новым учителем DeepSeek‑V4-Flash. Он в целом сильнее Qwen3.6–35B‑A3B в программировании и к тому же дешевле по API. Данные стали качественнее, из них убрали дубликаты и утечки бенчмарков. Из генерации DeepSeek были приняты ~60% примеров, которые прошли компиляцию и тесты. Из них ~65% составляли просьбы без кода, ~35% были задачами с заготовками и около 35% всего датасета пришлось на алгоритмические примеры. После обучения на этом датасете Temper был дообучен с подкреплением (RL). Модель писала по несколько решений каждой задачи, награду получали те из них, что проходят тесты.

Результаты

По сравнению с первой версией эта заметно прибавила на Rust‑версиях классических бенчмарков из MultiPL‑E. Доля задач, решённых с первой попытки, на humaneval‑rs выросла на 7,6 пункта, а на mbpp‑rs на 7,9.

Модель

Тривиальные¹

Простые¹

Средние¹

HumanEval‑RS

MBPP‑RS

Temper 0.5B

81,8% (94,0%)

59,0% (76,7%)

18,0% (35,0%)

34,7% (57,1%)

38,8% (54,8%)

ThoxEdge‑RustCoder 0.5B

55,8% (88,0%)

13,0% (50,0%)

1,5% (10,0%)

13,3% (41,7%)

12,8% (44,4%)

rust‑mentor 0.6B

35,4% (68,0%)

13,7% (30,0%)

0,0% (0,0%)

4,6% (16,7%)

9,7% (27,1%)

Qwen2.5-Coder 1.5B

70,0% (94,0%)

42,7% (73,3%)

11,0% (45,0%)

31,0% (69,2%)

34,4% (64,7%)

Qwen2.5-Coder 0.5B

45,6% (88,0%)

15,0% (50,0%)

1,0% (5,0%)

16,9% (42,3%)

23,4% (50,8%)

Qwen2.5-Coder 3B (для сравнения)

77,0% (96,0%)

51,0% (83,3%)

25,0% ▲ (50,0%)

50,6% ▲ (87,2%)

41,3% ▲ (72,9%)

DeepSeek‑V4-Flash (учитель, 284B)

97,8% (100,0%)

97,3% (100,0%)

79,5% (100,0%)

89,6% (98,1%)

81,2% (89,8%)

Таблица 2. Решено с первой попытки (pass@1). В скобках процент задач, решённых хотя бы в одной из 10 попыток (pass@10). Жирным выделен лучший pass@1 среди моделей до 1.5B. Знаком ▲ отмечена 3B там, где она выше всех моделей до 1.5B. Все модели Qwen взяты в версиях Instruct. Замер проводился с temperature 0.8 и top_p 0.95, по 10 ответов на задачу, pass@k считался по несмещённой оценке из статьи Codex. ¹ Внутренний набор Rust‑задач.

Кроме моделей Qwen в сравнение вошли две открытые модели того же размера, которые другие авторы дообучили на Rust. ThoxEdge‑RustCoder-0.5B от THOX.ai построена на той же базе, что и Temper, и обучена на открытом датасете Strandset‑Rust‑v1. rust‑mentor-0.6b получена из Qwen3-0.6B через QLoRA на том же датасете и задумана как помощник для изучения Rust и ревью кода. Её LoRA‑адаптер перед замером был слит с исходной Qwen3-0.6B. Обе модели проверялись в тех же условиях, что и остальные, без системного промпта и без рассуждений.

Свой бенчмарк

Для Rust почти нет бенчмарков, на которых имеет смысл мерить крошечную модель. Например MultiPL‑E переводит на Rust задачи HumanEval и MBPP, но это алгоритмические функции над числами и списками, исходно написанные для Python. Структуры с методами, трейты, обработка ошибок через Result и работа с крейтами в них почти не встречаются, хотя именно из этого состоит повседневный код на Rust. Было решено к двум наборам MultiPL‑E добавить собственный бенчмарк из 100 задач трёх уровней сложности: каждая задача состоит из просьбы обычным текстом, публичного интерфейса и скрытых тестов. Модель возвращает полный код, он компилируется вместе с тестами, и задача засчитывается, только если проходят все тесты. Тесты, которые модель напишет сама, на результат не влияют. Для каждой задачи проверено, что эталонное решение проходит все тесты, заготовка с todo!() компилируется и не проходит ни одного.

  • Тривиальные (50 задач). Одна короткая функция в несколько строк над числами, строками, срезами, Option и Result.

  • Простые (30 задач). Небольшой тип и работа с ним, структура с методами, enum и match, HashMap, Display, итераторы и оператор ?.

  • Средние (20 задач). Крейты axum, serde, tokio, clap, regex и thiserror, Rc<RefCell>, потоки и времена жизни.

Пример тривиальной задачи

Напиши функцию, которая возвращает число символов в строке. Каждый символ должен считаться отдельно, чтобы функция одинаково работала с пустой строкой, обычным английским текстом и строкой с неанглийскими буквами. Используй только стандартную библиотеку Rust. Используй ровно такой интерфейс:

pub fn char_count(s: &str) -> usize;

Пример простой задачи

Сделай банковский счёт с балансом, который можно посмотреть, пополнить и с которого можно снять деньги. Если денег не хватает, снятие должно вернуть ошибку ровно с текстом «insufficient funds», а баланс при этом не должен меняться. Используй только стандартную библиотеку Rust. Используй ровно такой интерфейс:

pub struct Account { /* private fields */ }
impl Account {    pub fn new(balance: u64) -> Self;    pub fn balance(&self) -> u64;    pub fn deposit(&mut self, amount: u64);    pub fn withdraw(&mut self, amount: u64) -> Result<(), String>;
}

Пример средней задачи

Напиши тип дерева со значением и списком детей, где дети могут быть общими (один и тот же ребёнок встречается в нескольких местах). Добавь две функции. Одна возвращает сумму значений узла и всего, что под ним, другая прибавляет число к значению узла и всего, что под ним. Общее поддерево нужно обходить и учитывать один раз для каждого места, где оно встречается. Используй только стандартную библиотеку Rust. Используй ровно такой интерфейс:

use std::cell::RefCell;
use std::rc::Rc;
pub struct Node {    pub value: i32,    pub children: Vec<Rc<RefCell<Node>>>,
}
pub fn tree_sum(node: &Rc<RefCell<Node>>) -> i32;
pub fn add_to_all(node: &Rc<RefCell<Node>>, delta: i32);

Ограничения и планы

Модель лучше всех среди сравнимых по размеру решает задачу с первой попытки, но при десяти попытках её преимущество пропадает. По pass@10 Qwen2.5-Coder-1.5B обходит её на medium, HumanEval‑RS и MBPP‑RS, и на последних двух разница доходит до 10–12 пунктов. Отчасти это цена обучения с подкреплением, которое сделало ответы модели увереннее и однообразнее. Qwen2.5-Coder-3B остаётся сильнее Temper на средних задачах и на обоих наборах MultiPL‑E. Собственные задачи бенчмарка написаны в стиле обучающих данных Temper, поэтому независимую оценку дают только наборы MultiPL‑E.

Датасет будет расти, сейчас в нём не хватает задач на веб‑фреймворки и асинхронный код. Для автодополнения в редакторе ещё предстоит решить, нужен ли модели режим заполнения середины кода (FIM).

Веса: huggingface.co/temper‑ai