NanoMind0.5-23M

NanoMind0.5-23M — моя первая самостоятельно обученная языковая модель на архитектуре GRU.

Меня зовут Ян, мне 15 лет, и я давно мечтал обучить собственную языковую модель. Эта модель — мой первый большой эксперимент: я собрал пайплайн токенизации, подготовки данных, обучения, валидации и генерации текста с нуля.

Модель обучена на русском языке по примерно 15 000 статьям с Habr.

⚠️ Важно: не ждите от модели сильных рассуждений, точных фактов, программирования или нормального диалога. Это очень маленькая экспериментальная GRU-модель, обученная на ограниченном корпусе. Она может выдавать бессвязный, повторяющийся или полностью выдуманный текст.

Модель

Параметр Значение
Архитектура GRU language model
Количество параметров ~23M
Размер словаря 32 000
Размер эмбеддинга 384
Размер скрытого состояния 384
Количество слоёв GRU 2
Контекст 128 токенов
Язык Русский
Данные ~15 000 статей Habr
Токенизатор Hugging Face tokenizers, tokenizer.json

Результат обучения

Модель обучалась в течение 18 эпох:

Epoch 18 | Train Loss: 4.7311 | Train PPL: 113.43
Epoch 18 | Val Loss:   4.8420 | Val PPL:   126.72
  • Train Loss: 4.7311
  • Train Perplexity: 113.43
  • Validation Loss: 4.8420
  • Validation Perplexity: 126.72

Perplexity около 113–127 означает, что модель всё ещё плохо предсказывает следующий токен. Для крошечной GRU-модели, обученной на сравнительно небольшом корпусе, это ожидаемый результат.

Гиперпараметры обучения

Гиперпараметр Значение
Эпохи 18
Длина последовательности 128 токенов
Batch size 256
Learning rate 5e-4
Оптимизатор AdamW
Betas 0.9, 0.95
Weight decay 0.0
Gradient clipping 1.0
Dropout 0.0
LR scheduler CosineAnnealingLR
Минимальный LR 1e-5
Точность обучения FP16 на CUDA, FP32 на CPU
Разделение данных 90% train / 10% validation
Seed для split 42

Примеры формируются без перекрытия: каждая обучающая последовательность начинается через SEQ_LEN токенов от предыдущей.

Файлы

  • config.json — конфигурация модели
  • model.safetensors — веса модели
  • tokenizer.json — токенизатор
  • README.md — этот файл

Установка

pip install torch tokenizers safetensors huggingface_hub

Для запуска на GPU также потребуется совместимая установка PyTorch с CUDA:

pip install torch --index-url [https://download.pytorch.org/whl/cu121](https://download.pytorch.org/whl/cu121)

Загрузка модели

from huggingface_hub import hf_hub_download

repo_id = "YanchikDev/NanoMind0.5-23M"

config_path = hf_hub_download(repo_id, "config.json")
model_path = hf_hub_download(repo_id, "model.safetensors")
tokenizer_path = hf_hub_download(repo_id, "tokenizer.json")

print(config_path)
print(model_path)
print(tokenizer_path)

Код для генерации

Сохраните этот файл как generate.py:

import json
import torch
import torch.nn as nn
from safetensors.torch import load_file
from tokenizers import Tokenizer
from huggingface_hub import hf_hub_download

REPO_ID = "YanchikDev/NanoMind0.5-23M"
SEQ_LEN = 128
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"


class GRULM(nn.Module):
    def __init__(
        self,
        vocab_size,
        embedding_dim,
        hidden_size,
        num_layers,
        dropout=0.0,
    ):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.gru = nn.GRU(
            input_size=embedding_dim,
            hidden_size=hidden_size,
            num_layers=num_layers,
            batch_first=True,
            dropout=dropout if num_layers > 1 else 0.0,
        )
        self.head = nn.Linear(hidden_size, vocab_size)

    def forward(self, x, hidden=None):
        emb = self.embedding(x)
        out, hidden = self.gru(emb, hidden)
        logits = self.head(out)
        return logits, hidden


def load_model():
    config_path = hf_hub_download(REPO_ID, "config.json")
    model_path = hf_hub_download(REPO_ID, "model.safetensors")
    tokenizer_path = hf_hub_download(REPO_ID, "tokenizer.json")

    with open(config_path, "r", encoding="utf-8") as f:
        config = json.load(f)

    model = GRULM(
        vocab_size=config.get("vocab_size", 32_000),
        embedding_dim=config.get("embedding_dim", 384),
        hidden_size=config.get("hidden_size", 384),
        num_layers=config.get("num_layers", 2),
        dropout=config.get("dropout", 0.0),
    )

    state_dict = load_file(model_path)
    model.load_state_dict(state_dict)
    model.to(DEVICE)
    model.eval()

    tokenizer = Tokenizer.from_file(tokenizer_path)
    return model, tokenizer


@torch.no_grad()
def generate(
    model,
    tokenizer,
    prompt,
    max_new_tokens=128,
    temperature=0.7,
    top_k=50,
):
    tokens = tokenizer.encode(prompt).ids

    hidden = None

    for _ in range(max_new_tokens):
        context = tokens[-SEQ_LEN:]
        x = torch.tensor([context], dtype=torch.long, device=DEVICE)

        logits, hidden = model(x, hidden)
        next_logits = logits[0, -1, :] / max(temperature, 1e-5)

        if top_k is not None and top_k > 0:
            values, indices = torch.topk(next_logits, k=top_k)
            probs = torch.softmax(values, dim=-1)
            next_token = indices[torch.multinomial(probs, num_samples=1)].item()
        else:
            probs = torch.softmax(next_logits, dim=-1)
            next_token = torch.multinomial(probs, num_samples=1).item()

        tokens.append(next_token)

    return tokenizer.decode(tokens)


if __name__ == "__main__":
    model, tokenizer = load_model()

    prompt = input("Промпт: ")
    result = generate(
        model,
        tokenizer,
        prompt,
        max_new_tokens=128,
        temperature=0.7,
        top_k=50,
    )

    print("\n--- Результат ---")
    print(result)

Запуск:

python generate.py

Простой чат

Это не полноценный чат-бот: модель не обучалась специально на диалогах и не умеет надёжно помнить длинный контекст.

Сохраните как chat.py:

from generate import load_model, generate

model, tokenizer = load_model()

print("NanoMind0.5-23M. Введите 'exit' или 'quit', чтобы выйти.")
print("Помните: это экспериментальная модель, а не полезный ассистент.\n")

history = ""

while True:
    user_input = input("Вы: ").strip()

    if user_input.lower() in {"exit", "quit", "выход"}:
        break

    if not user_input:
        continue

    prompt = history + f"Пользователь: {user_input}\nМодель:"

    reply = generate(
        model,
        tokenizer,
        prompt,
        max_new_tokens=96,
        temperature=0.7,
        top_k=50,
    )

    # Отрезаем всё после ответа модели, если модель ушла в новый диалог
    if "Пользователь:" in reply:
        reply = reply.split("Пользователь:")

    answer = reply.split("Модель:")[-1].strip()

    print(f"NanoMind: {answer}\n")

    history = f"{history}Пользователь: {user_input}\nМодель: {answer}\n"

    # Не даём истории превысить контекст модели
    tokens = tokenizer.encode(history).ids
    if len(tokens) > SEQ_LEN:
        history = tokenizer.decode(tokens[-SEQ_LEN:])

Запуск:

python chat.py

Как использовать

from generate import load_model, generate

model, tokenizer = load_model()

text = generate(
    model,
    tokenizer,
    prompt="Привет",
    max_new_tokens=128,
    temperature=0.7,
    top_k=50,
)

print(text)

Ограничения

  • Модель очень маленькая: около 23M параметров.
  • Контекст составляет только 128 токенов.
  • Она обучена преимущественно на статьях Habr, поэтому лучше всего может имитировать технический стиль русского текста.
  • Модель не предназначена для ответов на вопросы, программирования, математики, медицины, права или других задач, где важна точность.
  • Ответы могут быть бессмысленными, противоречивыми, повторяющимися или выдуманными.
  • GRU обрабатывает текст последовательно, а не через механизм внимания, как современные трансформерные LLM.
  • Это учебный проект, а не production-модель.

Почему эта модель важна для меня

Это моя первая полностью самостоятельно обученная языковая модель. Я хотел понять, как устроено обучение языковых моделей: токенизация, подготовка датасета, GRU, loss, perplexity, mixed precision, gradient clipping, cosine scheduler, чекпоинты и генерация текста.

Downloads last month
22
Safetensors
Model size
26.4M params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support