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