BagFix and auto apdate

This commit is contained in:
KoDer 2026-09-29 22:58:51 +07:00
parent 41aa593019
commit fdb943d3e6
37 changed files with 278 additions and 4670 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
models/

View File

@ -25,6 +25,7 @@ set(SOURCES
xenith/preprocessing/tokenizer.cpp
ollama_api/test.cpp
xenith/converter.cpp
help.h
)
# Заголовочные файлы (для удобства в IDE)

View File

@ -1,5 +1,9 @@
# Xenith
[![Platform](https://img.shields.io/badge/platform-Linux-blue)]()
[![C++](https://img.shields.io/badge/C++-20-blue)]()
[![Лицензия](https://img.shields.io/badge/license-MIT-green)]()
Трансформер на чистом C++: обучение и генерация текста на CPU, без внешних
зависимостей. Всё в одном бинарнике.
@ -52,6 +56,7 @@
--rope N база RoPE (10000)
--seed N зерно инициализации (1337)
--untied отдельная матрица выхода вместо связанной с эмбеддингом
--view-vocab вывести словарь нейросети
```
Словарь двухуровневый: частые слова берутся целиком, остальное режется на
@ -97,6 +102,15 @@
--show-tokens показать id токенов
```
### ollama — сгенерировать
```
--port PORT порт сервера (по умолчанию 11434)
--conf NAME.conf конфиг с настройками и списком моделей
```
### update - проверка обновления
### info / gradcheck / bench
```bash

Binary file not shown.

File diff suppressed because it is too large Load Diff

71
help.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef XENITH_HELP_H
#define XENITH_HELP_H
inline const std::string HELP_TEXT = R"(ИСПОЛЬЗОВАНИЕ
xenith <команда> [опции]
КОМАНДЫ
new создать новую модель из корпуса
train обучить существующую модель
gen сгенерировать текст
info показать конфигурацию модели
gradcheck проверить градиенты численно
bench замерить скорость форварда и шага обучения
ollama запустьтить сервер с api ollama
update Автомотическое обновление с гита
ПРИМЕРЫ
xenith new model.xnh --corpus data.txt --vocab 512 --embd 64 --layers 4 --heads 4
xenith train model.xnh --corpus data.txt --out model_trained.xnh --steps 3000
xenith train model_trained.xnh --corpus data.txt --out model_trained.xnh --steps 6000
xenith gen model_trained.xnh --prompt "Привет" --n 200 --temp 0.8
ОПЦИИ new
--corpus PATH текст для построения словаря (обязательно)
--out PATH файл модели (по умолчанию model.xnh)
--vocab N размер словаря (по умолчанию 512)
--embd N размер эмбеддинга (по умолчанию 128)
--layers N число слоёв (по умолчанию 4)
--heads N число голов (по умолчанию 4)
--ctx N максимальный контекст (по умолчанию 128)
--ffn N размер FFN (по умолчанию 4*embd)
--rope N база RoPE (по умолчанию 10000)
--seed N зерно инициализации (по умолчанию 1337)
--untied отдельная матрица выхода вместо привязанной к wte
--view-vocab выводит словарь нейросети
ОПЦИИ train
--corpus PATH обучающий текст (обязательно)
--out PATH куда сохранить (по умолчанию перезапись входного файла)
--steps N шагов (по умолчанию 1000)
--batch N батч (по умолчанию 8)
--block N длина окна (по умолчанию 64)
--lr F скорость (по умолчанию 3e-4)
--wd F weight decay (по умолчанию 0.01)
--clip F клип нормы градиента (по умолчанию 1.0)
--warmup N прогрев (по умолчанию 100)
--seed N зерно (по умолчанию 1337)
--log-every N как часто печатать (по умолчанию 50)
--ckpt-every N промежуточное сохранение каждые N шагов (0 = нет)
--val-every N валидация каждые N шагов (0 = нет)
--val-tokens N размер валидации (по умолчанию 20000)
--threads N потоков (0 = все ядра)
--resume продолжить с сохранённого step (Adam-state из файла)
ОПЦИИ gen
--prompt STR стартовый текст
--n N сколько токенов (по умолчанию 200)
--temp F температура, 0 = жадный выбор (по умолчанию 0.8)
--top-k N top-k (0 = выкл)
--top-p F top-p (1.0 = выкл)
--seed N зерно
--no-stream не печатать в процессе
--batch "a;b;c" несколько промптов через ;
--show-tokens показать id токенов
ОПЦИИ ollama
--port PORT порт на котором будет открыт сервер
--conf NAME.conf конфиг с настройками и списком моделей
)";
#endif

210
main.cpp
View File

@ -1,21 +1,26 @@
// main.cpp — командная строка Xenith
#include "xenith/core/checkpoint.h"
#include "xenith/core/generate.h"
#include "xenith/core/train.h"
#include "xenith/core/gradcheck.h"
#include <cstdio>
#include <cstring>
#include "help.h"
#include <cstdlib>
#include <filesystem>
#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <iostream>
#include <sstream>
#include <sys/stat.h>
namespace std::filesystem::__cxx11
{
class filesystem_error;
}
using namespace xt;
// ------------------------------------------------------------------ утилиты
static std::string read_all(const std::string& p, bool& ok) {
std::ifstream f(p, std::ios::binary);
if (!f) { ok = false; return {}; }
@ -34,7 +39,12 @@ static void mkdirs(const std::string& path) {
}
}
// Разбор аргументов вида --key value
struct Version {
int major = 0;
int minor = 0;
int patch = 0;
};
struct Args {
std::map<std::string, std::string> kv;
std::vector<std::string> pos;
@ -72,74 +82,6 @@ struct Args {
}
};
static void usage() {
printf(R"(ИСПОЛЬЗОВАНИЕ
xenith <команда> [опции]
КОМАНДЫ
new создать новую модель из корпуса
train обучить существующую модель
gen сгенерировать текст
info показать конфигурацию модели
gradcheck проверить градиенты численно
bench замерить скорость форварда и шага обучения
ollama запустьтить сервер с api ollama
ПРИМЕРЫ
xenith new model.xnh --corpus data.txt --vocab 512 --embd 64 --layers 4 --heads 4
xenith train model.xnh --corpus data.txt --out model_trained.xnh --steps 3000
xenith train model_trained.xnh --corpus data.txt --out model_trained.xnh --steps 6000
xenith gen model_trained.xnh --prompt "Привет" --n 200 --temp 0.8
ОПЦИИ new
--corpus PATH текст для построения словаря (обязательно)
--out PATH файл модели (по умолчанию model.xnh)
--vocab N размер словаря (по умолчанию 512)
--embd N размер эмбеддинга (по умолчанию 128)
--layers N число слоёв (по умолчанию 4)
--heads N число голов (по умолчанию 4)
--ctx N максимальный контекст (по умолчанию 128)
--ffn N размер FFN (по умолчанию 4*embd)
--rope N база RoPE (по умолчанию 10000)
--seed N зерно инициализации (по умолчанию 1337)
--untied отдельная матрица выхода вместо привязанной к wte
ОПЦИИ train
--corpus PATH обучающий текст (обязательно)
--out PATH куда сохранить (по умолчанию перезапись входного файла)
--steps N шагов (по умолчанию 1000)
--batch N батч (по умолчанию 8)
--block N длина окна (по умолчанию 64)
--lr F скорость (по умолчанию 3e-4)
--wd F weight decay (по умолчанию 0.01)
--clip F клип нормы градиента (по умолчанию 1.0)
--warmup N прогрев (по умолчанию 100)
--seed N зерно (по умолчанию 1337)
--log-every N как часто печатать (по умолчанию 50)
--ckpt-every N промежуточное сохранение каждые N шагов (0 = нет)
--val-every N валидация каждые N шагов (0 = нет)
--val-tokens N размер валидации (по умолчанию 20000)
--threads N потоков (0 = все ядра)
--resume продолжить с сохранённого step (Adam-state из файла)
ОПЦИИ gen
--prompt STR стартовый текст
--n N сколько токенов (по умолчанию 200)
--temp F температура, 0 = жадный выбор (по умолчанию 0.8)
--top-k N top-k (0 = выкл)
--top-p F top-p (1.0 = выкл)
--seed N зерно
--no-stream не печатать в процессе
--batch "a;b;c" несколько промптов через ;
--show-tokens показать id токенов
ОПЦИИ ollama
--port PORT порт на котором будет открыт сервер
--conf NAME.conf
)");
}
// ------------------------------------------------------------------ new
static int cmd_new(const Args& a) {
if (a.pos.empty()) { fprintf(stderr, "new: укажите путь модели\n"); return 1; }
const std::string out = a.pos[0];
@ -161,7 +103,7 @@ static int cmd_new(const Args& a) {
ck.params.cfg.rope_base = a.geti("rope", 10000);
if (a.has("untied")) ck.params.cfg.n_vocab_out = ck.params.cfg.vocab_size;
ck.tok.build_from_text(text, ck.params.cfg.vocab_size);
ck.tok.build_from_text(text, ck.params.cfg.vocab_size, a.has("view-vocab"));
ck.params.cfg.vocab_size = ck.tok.size();
std::string err;
@ -172,8 +114,6 @@ static int cmd_new(const Args& a) {
printf("создаю модель: %s\n", out.c_str());
print_model_info(ck);
// размер папки, если out = models/xxx.xnh
size_t slash = out.find_last_of('/');
if (slash != std::string::npos) mkdirs(out.substr(0, slash));
@ -183,7 +123,6 @@ static int cmd_new(const Args& a) {
return 0;
}
// ------------------------------------------------------------------ train
static int cmd_train(const Args& a) {
if (a.pos.empty()) { fprintf(stderr, "train: укажите файл модели\n"); return 1; }
const std::string path = a.pos[0];
@ -218,7 +157,6 @@ static int cmd_train(const Args& a) {
int resume = a.has("resume") ? ck.step : 0;
if (resume > 0) {
// Adam-state уже загружен из файла, но steps надо расширить
fprintf(stderr, "продолжаю с шага %d\n", resume);
}
@ -227,7 +165,6 @@ static int cmd_train(const Args& a) {
return 0;
}
// ------------------------------------------------------------------ gen
static int cmd_gen(const Args& a) {
if (a.pos.empty()) { fprintf(stderr, "gen: укажите файл модели\n"); return 1; }
Checkpoint ck;
@ -275,7 +212,6 @@ static int cmd_gen(const Args& a) {
return 0;
}
// ------------------------------------------------------------------ info
static int cmd_info(const Args& a) {
if (a.pos.empty()) { fprintf(stderr, "info: укажите файл модели\n"); return 1; }
Checkpoint ck;
@ -303,56 +239,87 @@ static int cmd_bench(const Args& a) {
}
static int cmd_ollama(const Args& a) {
if (a.pos.empty()) { fprintf(stderr, "gen: укажите файл модели\n"); return 1; }
Checkpoint ck;
std::string err;
if (!load_checkpoint(a.pos[0], ck, err)) { fprintf(stderr, "gen: %s\n", err.c_str()); return 1; }
fprintf(stderr, "На стадии разработки");
return 0;
}
GenConfig gc;
gc.max_new_tokens = a.geti("n", 200);
gc.temperature = a.getf("temp", 0.8f);
gc.top_k = a.geti("top-k", 40);
gc.top_p = a.getf("top-p", 0.95f);
gc.seed = a.geti("seed", -1);
gc.stream = !a.has("no-stream");
Version parse_version(const std::string& str) {
Version v;
std::stringstream ss(str);
std::string segment;
if (std::getline(ss, segment, '.')) v.major = std::stoi(segment);
if (std::getline(ss, segment, '.')) v.minor = std::stoi(segment);
if (std::getline(ss, segment, '.')) v.patch = std::stoi(segment);
return v;
}
if (a.has("batch")) {
std::vector<std::string> prompts;
std::string s = a.get("batch");
size_t start = 0;
while (start <= s.size()) {
size_t p = s.find(';', start);
if (p == std::string::npos) { prompts.push_back(s.substr(start)); break; }
prompts.push_back(s.substr(start, p - start));
start = p + 1;
}
gc.stream = false;
std::vector<std::string> outs = generate_batch(ck.params, ck.tok, prompts, gc);
for (size_t i = 0; i < outs.size(); ++i) {
printf("=== промпт %zu: %s\n%s\n\n", i + 1, prompts[i].c_str(), outs[i].c_str());
}
return 0;
static bool available_new_version()
{
std::string cmd = "curl -s https://git.bipfr.ru/BIPfR/Xenith/raw/branch/master/version.txt -o ./tmp/remote_version.txt";
system(cmd.c_str());
std::ifstream tmp_file("./tmp/remote_version.txt");
std::string version = "0.0.0";
if (tmp_file.is_open()) {
std::getline(tmp_file, version);
tmp_file.close();
}
Version remote_version = parse_version(version);
std::ifstream file("./version.txt");
version = "0.0.0";
if (file.is_open()) {
std::getline(file, version);
file.close();
}
Version current_version = parse_version(version);
if (remote_version.major > current_version.major) return true;
if (remote_version.minor > current_version.minor) return true;
if (remote_version.patch > current_version.patch) return true;
return false;
}
std::string prompt = a.get("prompt", "");
if (a.pos.size() > 1) prompt = a.pos[1];
static int cmd_update(const Args& a)
{
if (available_new_version())
{
namespace fs = std::filesystem;
std::cout << "Удаляем старые файлы..." << std::endl;
for (const auto& entry : fs::directory_iterator(".")) {
std::string name = entry.path().filename().string();
if (name == "models") {
continue;
}
try {
if (fs::is_directory(entry.status())) {
fs::remove_all(entry.path());
} else {
fs::remove(entry.path());
}
} catch (const std::filesystem::__cxx11::filesystem_error& e) {
std::cerr << "Ошибка удаления " << name << ": " << e.what() << std::endl;
}
}
std::cout << "Скачиваем новую версию..." << std::endl;
int result = system("git clone https://git.bipfr.ru/BIPfR/Xenith /tmp/xenith_update");
std::vector<int> ids;
std::string text = generate(ck.params, ck.tok, prompt, gc, &ids);
if (a.has("show-tokens")) {
printf("\n--- токены (%zu) ---\n", ids.size());
for (size_t i = 0; i < ids.size(); ++i)
printf("%d ", ids[i]);
printf("\n");
if (result != 0) {
std::cerr << "Ошибка при клонировании репозитория" << std::endl;
return -1;
}
result = system("cp -r /tmp/xenith_update/* .");
if (result != 0) {
std::cerr << "Ошибка при копировании файлов" << std::endl;
return -1;
}
system("rm -rf /tmp/xenith_update");
std::cout << "Обновление завершено!" << std::endl;
}
return 0;
}
int main(int argc, char** argv) {
if (argc < 2) { usage(); return 1; }
if (argc < 2) { printf(HELP_TEXT); return 1; }
const std::string cmd = argv[1];
if (cmd == "-h" || cmd == "--help" || cmd == "help") { usage(); return 0; }
if (cmd == "-h" || cmd == "--help" || cmd == "help") { printf(HELP_TEXT); return 0; }
Args a(argc, argv, 2);
if (cmd == "new") return cmd_new(a);
@ -361,9 +328,10 @@ int main(int argc, char** argv) {
if (cmd == "info") return cmd_info(a);
if (cmd == "gradcheck") return cmd_gradcheck(a);
if (cmd == "bench") return cmd_bench(a);
if (cmd == "ollama") return cmd_bench(a);
if (cmd == "ollama") return cmd_ollama(a);
if (cmd == "update") return cmd_update(a);
fprintf(stderr, "неизвестная команда: %s\n\n", cmd.c_str());
usage();
printf(HELP_TEXT);
return 1;
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,273 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
constexpr int TEST_VOCAB_SIZE = 64;
constexpr int TEST_EMBEDDING_DIM = 8;
constexpr int TEST_NUM_HEADS = 2;
constexpr int TEST_HEAD_DIM = TEST_EMBEDDING_DIM / TEST_NUM_HEADS;
constexpr int TEST_NUM_LAYERS = 2;
constexpr int TEST_MAX_CTX = 500;
inline const std::unordered_map<std::string, uint32_t> TEST_VOCAB = {
// Специальные токены
{"<s>", 0},
{"</s>", 1},
{"<think>", 2},
{"</think>", 3},
// Приветствия и базовые фразы
{"привет", 4},
{"здравствуй", 5},
{"как", 6},
{"дела", 7},
{"ты", 8},
{"я", 9},
{"мы", 10},
// Вопросы
{"что", 11},
{"где", 12},
{"когда", 13},
{"почему", 14},
{"сколько", 15},
// Существительные
{"мир", 16},
{"день", 17},
{"время", 18},
{"работа", 19},
{"код", 20},
{"программа", 21},
{"компьютер", 22},
{"система", 23},
{"данные", 24},
{"память", 25},
// Глаголы
{"делать", 26},
{"идти", 27},
{"работать", 28},
{"думать", 29},
{"писать", 30},
{"читать", 31},
{"понимать", 32},
// Прилагательные
{"хорошо", 33},
{"плохо", 34},
{"быстро", 35},
{"медленно", 36},
{"новый", 37},
{"старый", 38},
// Технические термины
{"функция", 39},
{"переменная", 40},
{"класс", 41},
{"объект", 42},
{"массив", 43},
{"указатель", 44},
{"алгоритм", 45},
// Союзы и предлоги
{"и", 46},
{"или", 47},
{"но", 48},
{"если", 49},
{"то", 50},
{"в", 51},
{"на", 52},
{"с", 53},
{"из", 54},
{"для", 55},
// Частицы
{"не", 56},
{"ли", 57},
{"же", 58},
{"бы", 59},
// Знаки препинания (опционально)
{".", 60},
{",", 61},
{"?", 62},
{"!", 63},
};
inline constexpr float TEST_W_EMB[TEST_VOCAB_SIZE][TEST_EMBEDDING_DIM] = {
// <s>
{0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f},
// </s>
{0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f},
// <think>
{0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f},
// </think>
{0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f},
// привет
{0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f},
// здравствуй
{0.85f, 0.75f, 0.65f, 0.55f, 0.45f, 0.35f, 0.25f, 0.15f},
// как
{0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.1f},
// дела
{0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.1f, 0.2f},
// ты
{0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 0.1f, 0.2f, 0.3f},
// я
{0.6f, 0.7f, 0.8f, 0.9f, 0.1f, 0.2f, 0.3f, 0.4f},
// мы
{0.7f, 0.8f, 0.9f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f},
// что
{0.15f, 0.25f, 0.35f, 0.45f, 0.55f, 0.65f, 0.75f, 0.85f},
// где
{0.25f, 0.35f, 0.45f, 0.55f, 0.65f, 0.75f, 0.85f, 0.95f},
// когда
{0.35f, 0.45f, 0.55f, 0.65f, 0.75f, 0.85f, 0.95f, 0.05f},
// почему
{0.45f, 0.55f, 0.65f, 0.75f, 0.85f, 0.95f, 0.05f, 0.15f},
// сколько
{0.55f, 0.65f, 0.75f, 0.85f, 0.95f, 0.05f, 0.15f, 0.25f},
// мир
{0.1f, 0.9f, 0.2f, 0.8f, 0.3f, 0.7f, 0.4f, 0.6f},
// день
{0.2f, 0.8f, 0.3f, 0.7f, 0.4f, 0.6f, 0.5f, 0.5f},
// время
{0.3f, 0.7f, 0.4f, 0.6f, 0.5f, 0.5f, 0.6f, 0.4f},
// работа
{0.4f, 0.6f, 0.5f, 0.5f, 0.6f, 0.4f, 0.7f, 0.3f},
// код
{0.5f, 0.5f, 0.6f, 0.4f, 0.7f, 0.3f, 0.8f, 0.2f},
// программа
{0.6f, 0.4f, 0.7f, 0.3f, 0.8f, 0.2f, 0.9f, 0.1f},
// компьютер
{0.7f, 0.3f, 0.8f, 0.2f, 0.9f, 0.1f, 0.0f, 0.0f},
// система
{0.8f, 0.2f, 0.9f, 0.1f, 0.0f, 0.0f, 0.1f, 0.9f},
// данные
{0.9f, 0.1f, 0.0f, 0.0f, 0.1f, 0.9f, 0.2f, 0.8f},
// память
{0.0f, 0.0f, 0.1f, 0.9f, 0.2f, 0.8f, 0.3f, 0.7f},
// делать
{0.12f, 0.23f, 0.34f, 0.45f, 0.56f, 0.67f, 0.78f, 0.89f},
// идти
{0.23f, 0.34f, 0.45f, 0.56f, 0.67f, 0.78f, 0.89f, 0.12f},
// работать
{0.34f, 0.45f, 0.56f, 0.67f, 0.78f, 0.89f, 0.12f, 0.23f},
// думать
{0.45f, 0.56f, 0.67f, 0.78f, 0.89f, 0.12f, 0.23f, 0.34f},
// писать
{0.56f, 0.67f, 0.78f, 0.89f, 0.12f, 0.23f, 0.34f, 0.45f},
// читать
{0.67f, 0.78f, 0.89f, 0.12f, 0.23f, 0.34f, 0.45f, 0.56f},
// понимать
{0.78f, 0.89f, 0.12f, 0.23f, 0.34f, 0.45f, 0.56f, 0.67f},
// хорошо
{0.89f, 0.12f, 0.23f, 0.34f, 0.45f, 0.56f, 0.67f, 0.78f},
// плохо
{0.11f, 0.22f, 0.33f, 0.44f, 0.55f, 0.66f, 0.77f, 0.88f},
// быстро
{0.22f, 0.33f, 0.44f, 0.55f, 0.66f, 0.77f, 0.88f, 0.99f},
// медленно
{0.33f, 0.44f, 0.55f, 0.66f, 0.77f, 0.88f, 0.99f, 0.11f},
// новый
{0.44f, 0.55f, 0.66f, 0.77f, 0.88f, 0.99f, 0.11f, 0.22f},
// старый
{0.55f, 0.66f, 0.77f, 0.88f, 0.99f, 0.11f, 0.22f, 0.33f},
// функция
{0.66f, 0.77f, 0.88f, 0.99f, 0.11f, 0.22f, 0.33f, 0.44f},
// переменная
{0.77f, 0.88f, 0.99f, 0.11f, 0.22f, 0.33f, 0.44f, 0.55f},
// класс
{0.88f, 0.99f, 0.11f, 0.22f, 0.33f, 0.44f, 0.55f, 0.66f},
// объект
{0.99f, 0.11f, 0.22f, 0.33f, 0.44f, 0.55f, 0.66f, 0.77f},
// массив
{0.13f, 0.24f, 0.35f, 0.46f, 0.57f, 0.68f, 0.79f, 0.80f},
// указатель
{0.24f, 0.35f, 0.46f, 0.57f, 0.68f, 0.79f, 0.80f, 0.13f},
// алгоритм
{0.35f, 0.46f, 0.57f, 0.68f, 0.79f, 0.80f, 0.13f, 0.24f},
// и
{0.46f, 0.57f, 0.68f, 0.79f, 0.80f, 0.13f, 0.24f, 0.35f},
// или
{0.57f, 0.68f, 0.79f, 0.80f, 0.13f, 0.24f, 0.35f, 0.46f},
// но
{0.68f, 0.79f, 0.80f, 0.13f, 0.24f, 0.35f, 0.46f, 0.57f},
// если
{0.79f, 0.80f, 0.13f, 0.24f, 0.35f, 0.46f, 0.57f, 0.68f},
// то
{0.80f, 0.13f, 0.24f, 0.35f, 0.46f, 0.57f, 0.68f, 0.79f},
// в
{0.14f, 0.25f, 0.36f, 0.47f, 0.58f, 0.69f, 0.70f, 0.81f},
// на
{0.25f, 0.36f, 0.47f, 0.58f, 0.69f, 0.70f, 0.81f, 0.14f},
// с
{0.36f, 0.47f, 0.58f, 0.69f, 0.70f, 0.81f, 0.14f, 0.25f},
// из
{0.47f, 0.58f, 0.69f, 0.70f, 0.81f, 0.14f, 0.25f, 0.36f},
// для
{0.58f, 0.69f, 0.70f, 0.81f, 0.14f, 0.25f, 0.36f, 0.47f},
// не
{0.69f, 0.70f, 0.81f, 0.14f, 0.25f, 0.36f, 0.47f, 0.58f},
// ли
{0.70f, 0.81f, 0.14f, 0.25f, 0.36f, 0.47f, 0.58f, 0.69f},
// же
{0.81f, 0.14f, 0.25f, 0.36f, 0.47f, 0.58f, 0.69f, 0.70f},
// бы
{0.16f, 0.27f, 0.38f, 0.49f, 0.50f, 0.61f, 0.72f, 0.83f},
// .
{0.27f, 0.38f, 0.49f, 0.50f, 0.61f, 0.72f, 0.83f, 0.16f},
// ,
{0.38f, 0.49f, 0.50f, 0.61f, 0.72f, 0.83f, 0.16f, 0.27f},
// ?
{0.49f, 0.50f, 0.61f, 0.72f, 0.83f, 0.16f, 0.27f, 0.38f},
// !
{0.50f, 0.61f, 0.72f, 0.83f, 0.16f, 0.27f, 0.38f, 0.49f},
};
inline constexpr float TEST_W_QKV[TEST_NUM_LAYERS][TEST_EMBEDDING_DIM][3][TEST_NUM_HEADS] = {
{
{{0.1f, 0.2f}, {0.3f, 0.4f}, {0.5f, 0.6f}},
{{0.7f, 0.8f}, {0.9f, 1.0f}, {1.1f, 1.2f}},
{{0.1f, 0.1f}, {0.2f, 0.2f}, {0.3f, 0.3f}},
{{0.4f, 0.4f}, {0.5f, 0.5f}, {0.6f, 0.6f}},
{{0.7f, 0.7f}, {0.8f, 0.8f}, {0.9f, 0.9f}},
{{1.0f, 1.0f}, {1.1f, 1.1f}, {1.2f, 1.2f}},
{{0.1f, 0.3f}, {0.5f, 0.7f}, {0.9f, 1.1f}},
{{0.2f, 0.4f}, {0.6f, 0.8f}, {1.0f, 1.2f}}
},
{
{{0.9f, 0.8f}, {0.7f, 0.6f}, {0.5f, 0.4f}},
{{0.3f, 0.2f}, {0.1f, 0.0f}, {-0.1f, -0.2f}},
{{0.5f, 0.5f}, {0.6f, 0.6f}, {0.7f, 0.7f}},
{{0.8f, 0.8f}, {0.9f, 0.9f}, {1.0f, 1.0f}},
{{0.2f, 0.4f}, {0.6f, 0.8f}, {1.0f, 1.2f}},
{{0.3f, 0.5f}, {0.7f, 0.9f}, {1.1f, 1.3f}},
{{0.4f, 0.6f}, {0.8f, 1.0f}, {1.2f, 1.4f}},
{{0.5f, 0.7f}, {0.9f, 1.1f}, {1.3f, 1.5f}}
}
};
inline constexpr float TEST_W_O[TEST_NUM_LAYERS][TEST_NUM_HEADS * TEST_HEAD_DIM][TEST_EMBEDDING_DIM] = {
{
{0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f},
{0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f},
{0.1f, 0.9f, 0.2f, 0.8f, 0.3f, 0.7f, 0.4f, 0.6f},
{0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f},
{0.2f, 0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.4f, 1.6f},
{0.3f, 0.5f, 0.7f, 0.9f, 1.1f, 1.3f, 1.5f, 1.7f},
{0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.4f, 1.6f, 1.8f},
{0.5f, 0.7f, 0.9f, 1.1f, 1.3f, 1.5f, 1.7f, 1.9f}
},
{
{0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f, 0.1f},
{0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f},
{0.9f, 0.1f, 0.8f, 0.2f, 0.7f, 0.3f, 0.6f, 0.4f},
{0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f},
{0.1f, 0.3f, 0.5f, 0.7f, 0.9f, 1.1f, 1.3f, 1.5f},
{0.2f, 0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.4f, 1.6f},
{0.3f, 0.5f, 0.7f, 0.9f, 1.1f, 1.3f, 1.5f, 1.7f},
{0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.4f, 1.6f, 1.8f}
}
};

1
version.txt Normal file
View File

@ -0,0 +1 @@
0.0.2

View File

@ -1,21 +1,18 @@
// backward.cpp — ручной обратный проход трансформера
#include "train.h"
#include <cmath>
#include <algorithm>
namespace xt {
// softmax + cross-entropy на одном ряду; сразу отдаёт градиент по логитам
static float softmax_ce(const float* logits, int V, int target, float* dlogits) {
float mx = -1e30f;
for (int j = 0; j < V; ++j) mx = std::max(mx, logits[j]);
float sum = 0.0f;
for (int j = 0; j < V; ++j) { float e = std::exp(logits[j] - mx); dlogits[j] = e; sum += e; }
const float inv = 1.0f / sum;
for (int j = 0; j < V; ++j) dlogits[j] *= inv; // теперь dlogits = вероятности
for (int j = 0; j < V; ++j) dlogits[j] *= inv;
float loss = -std::log(std::max(dlogits[target], 1e-30f));
dlogits[target] -= 1.0f; // dL/dlogits = p - onehot
// Второго умножения на inv здесь быть НЕ должно: вероятности уже нормированы.
dlogits[target] -= 1.0f;
return loss;
}
@ -32,54 +29,36 @@ float backward(Params& p, const ForwardCache& fc,
p.zero_grad();
// ------------------------------------------- 1. логиты -> dxf, градиенты выхода
Tensor dlogits(B, V);
double loss_sum = 0;
for (int t = 0; t < B; ++t)
loss_sum += softmax_ce(fc.logits.row(t), V, y[t], dlogits.row(t));
// Усредняем по батчу ДО умножения на матрицы — тогда все веса ниже
// автоматически получают правильный масштаб.
const float dl = 1.0f / (float)B;
for (auto& t : dlogits.d) t *= dl;
Tensor dxf(B, d);
if (p.tied()) {
gemm_nn(dlogits, p.wte, dxf); // dxf = dlogits @ wte
gemm_tn(dlogits, fc.xf, p.gwte, true); // dwte += dlogits^T @ xf
gemm_nn(dlogits, p.wte, dxf);
gemm_tn(dlogits, fc.xf, p.gwte, true);
} else {
gemm_nn(dlogits, p.lm_head, dxf);
gemm_tn(dlogits, fc.xf, p.glm_head, true);
}
// ------------------------------------------- 2. финальный RMSNorm
Tensor dx;
rmsnorm_backward(fc.x, p.rms_final, fc.inv_rms, dxf, dx, p.grms_final);
// ------------------------------------------- 3. слои в обратном порядке
//
// Структура слоя (та же, что в forward):
// x_mid = x_in + proj proj = attout @ wo
// x2 = x_mid (x2 копия residual'а)
// x_out = x2 + fo fo = FFN(x2b), x2b = RMS(x2)
//
// dfo = dL/dx_out = dres
// dproj = dL/dx_mid = dres + вклад FFN (proj добавляется ДО fo)
// dL/dx_in = dproj + вклад attention
Tensor tmp, dxb, dx2b, dha, dh1, dh3, dq, dk, dv, datt, dattout;
for (int L = c.n_layer - 1; L >= 0; --L) {
const LayerCache& lc = fc.layers[L];
// dres — градиент по ВЫХОДУ слоя residual'а.
const Tensor dres = dx;
// ================= FFN =================
// fo = ha @ w2
gemm_tn(lc.ha, dres, p.g2[L], true); // dW2
gemm_nt(dres, p.w2[L], dha); // dha = dres @ w2^T
// ha = silu(h1) * h3
dh1.resize(B, f);
dh3.resize(B, f);
for (int t = 0; t < B; ++t) {
@ -94,29 +73,23 @@ float backward(Params& p, const ForwardCache& fc,
d3[j] = ga * silu(g);
}
}
// dW = X^T @ dY => первым аргументом gemm_tn идёт X
gemm_tn(lc.x2b, dh1, p.g1[L], true); // dW1
gemm_tn(lc.x2b, dh3, p.g3[L], true); // dW3
// dx2b = dh1 @ w1^T + dh3 @ w3^T
dx2b.resize(B, d);
gemm_nt(dh1, p.w1[L], dx2b);
gemm_nt(dh3, p.w3[L], tmp);
for (size_t i = 0; i < dx2b.n(); ++i) dx2b.at(i) += tmp.at(i);
// RMSNorm перед FFN: вклад идёт в dx (градиент по x2 == x_mid)
{
Tensor g;
rmsnorm_backward(lc.x2, p.rms_ffn[L], lc.inv_rms2, dx2b, g, p.grms_ffn[L]);
for (size_t i = 0; i < dx.n(); ++i) dx.at(i) += g.at(i);
}
// ================= attention =================
// proj = attout @ wo
// Здесь читаем dx (== dL/dx_mid, уже с вкладом FFN), а НЕ dres.
gemm_tn(lc.attout, dx, p.go[L], true); // dWo
gemm_tn(lc.attout, dx, p.go[L], true);
dattout.resize(B, d);
gemm_nt(dx, p.wo[L], dattout); // dattout = dx @ wo^T
gemm_nt(dx, p.wo[L], dattout);
dq.resize(B, d); dq.zero();
dk.resize(B, d); dk.zero();
@ -132,7 +105,6 @@ float backward(Params& p, const ForwardCache& fc,
const float* ar = lc.att.row(hp * S + t1);
float* dq_row = dq.row(r1) + hh * hd;
// dv[t2] += att[t1][t2] * dattout[t1]
for (int t2 = 0; t2 <= t1; ++t2) {
const float a = ar[t2];
if (a == 0.0f) continue;
@ -140,7 +112,6 @@ float backward(Params& p, const ForwardCache& fc,
for (int j = 0; j < hd; ++j) dvr[j] += a * dar[j];
}
// datt[t1][t2] = dot(dattout[t1], v[t2])
float* darow = datt.row(hp * S + t1);
for (int t2 = 0; t2 <= t1; ++t2) {
const float* vr = lc.v.row(sq * S + t2) + hh * hd;
@ -149,13 +120,11 @@ float backward(Params& p, const ForwardCache& fc,
darow[t2] = s;
}
// якобиан softmax: dscores = a * (dp - sum(a*dp)) * scale
float dpdot = 0.0f;
for (int t2 = 0; t2 <= t1; ++t2) dpdot += ar[t2] * darow[t2];
for (int t2 = 0; t2 <= t1; ++t2)
darow[t2] = ar[t2] * (darow[t2] - dpdot) * scale;
// dq[t1] += dscores * k[t2]; dk[t2] += dscores * q[t1]
for (int t2 = 0; t2 <= t1; ++t2) {
const float ds = darow[t2];
if (ds == 0.0f) continue;
@ -171,7 +140,6 @@ float backward(Params& p, const ForwardCache& fc,
}
}
// ================= RoPE backward =================
for (int sq = 0; sq < n_seq; ++sq) {
for (int t = 0; t < S; ++t) {
const int row = sq * S + t;
@ -183,7 +151,7 @@ float backward(Params& p, const ForwardCache& fc,
for (int i = 0; i < half; ++i) {
const float c0 = cs[i], s0 = sn[i];
float a0 = dq_[2 * i], a1 = dq_[2 * i + 1];
dq_[2 * i] = a0 * c0 + a1 * s0; // транспонированный поворот
dq_[2 * i] = a0 * c0 + a1 * s0;
dq_[2 * i + 1] = -a0 * s0 + a1 * c0;
float b0 = dk_[2 * i], b1 = dk_[2 * i + 1];
dk_[2 * i] = b0 * c0 + b1 * s0;
@ -193,12 +161,10 @@ float backward(Params& p, const ForwardCache& fc,
}
}
// ================= QKV backward =================
gemm_tn(lc.xb, dq, p.gq[L], true); // dWq
gemm_tn(lc.xb, dk, p.gk[L], true); // dWk
gemm_tn(lc.xb, dv, p.gv[L], true); // dWv
gemm_tn(lc.xb, dq, p.gq[L], true);
gemm_tn(lc.xb, dk, p.gk[L], true);
gemm_tn(lc.xb, dv, p.gv[L], true);
// dxb = dq @ wq^T + dk @ wk^T + dv @ wv^T
dxb.resize(B, d);
gemm_nt(dq, p.wq[L], dxb);
gemm_nt(dk, p.wk[L], tmp);
@ -206,7 +172,6 @@ float backward(Params& p, const ForwardCache& fc,
gemm_nt(dv, p.wv[L], tmp);
for (size_t i = 0; i < dxb.n(); ++i) dxb.at(i) += tmp.at(i);
// ================= RMSNorm перед attention =================
{
Tensor g;
rmsnorm_backward(lc.x, p.rms_attn[L], lc.inv_rms, dxb, g, p.grms_attn[L]);
@ -214,7 +179,6 @@ float backward(Params& p, const ForwardCache& fc,
}
}
// ------------------------------------------- 4. градиент входных эмбеддингов
for (int t = 0; t < B; ++t) {
const float* gxr = dx.row(t);
float* gw = p.gwte.row(x[t]);
@ -239,4 +203,4 @@ float softmax_eval(const Params& p, const ForwardCache& fc,
return (float)(sum / (double)n_pairs);
}
} // namespace xt
}

View File

@ -1,6 +0,0 @@
xenith/core/backward.o: xenith/core/backward.cpp xenith/core/train.h \
xenith/core/model.h xenith/core/tensor.h xenith/core/checkpoint.h
xenith/core/train.h:
xenith/core/model.h:
xenith/core/tensor.h:
xenith/core/checkpoint.h:

Binary file not shown.

View File

@ -1,4 +1,3 @@
// checkpoint.cpp — бинарный формат XNH1, токенизатор уровня байт/слова
#include "checkpoint.h"
#include <cstdio>
#include <cstring>
@ -7,16 +6,12 @@
#include <unordered_map>
#include <algorithm>
#include <cctype>
#include <iostream>
namespace xt {
// ------------------------------------------------------- токенизатор
// Уровень 1: частое слова целиком. Уровень 2: отдельные байты.
// Так словарь остаётся маленьким, а любой текст кодируется без потерь.
namespace {
// Разбивает UTF-8 строку на "символы" (кодпоинты)
std::vector<std::string> utf8_chars(const std::string& s) {
std::vector<std::string> out;
for (size_t i = 0; i < s.size();) {
@ -36,16 +31,15 @@ bool is_space(const std::string& t) {
return t.size() == 1 && (t[0] == ' ' || t[0] == '\t' || t[0] == '\n' || t[0] == '\r');
}
} // namespace
}
void Tokenizer::build_from_text(const std::string& text, int max_vocab) {
void Tokenizer::build_from_text(const std::string& text, int max_vocab, bool debug) {
id2tok.clear();
id2tok.push_back("<unk>"); // 0
id2tok.push_back("<bos>"); // 1
id2tok.push_back("<eos>"); // 2
id2tok.push_back("\n"); // 3
id2tok.push_back("<unk>");
id2tok.push_back("<bos>");
id2tok.push_back("<eos>");
id2tok.push_back("\n");
// Считаем частоты слов (с сохранением регистра)
std::unordered_map<std::string, int> freq;
std::string word;
auto flush = [&]() {
@ -61,13 +55,11 @@ void Tokenizer::build_from_text(const std::string& text, int max_vocab) {
std::sort(items.begin(), items.end(),
[](const std::pair<std::string, int>& a, const std::pair<std::string, int>& b) {
if (a.second != b.second) return a.second > b.second;
return a.first < b.first; // детерминированный порядок при равенстве
return a.first < b.first;
});
const int room = max_vocab - (int)id2tok.size();
for (int i = 0; i < (int)items.size() && i < room; ++i) id2tok.push_back(items[i].first);
// Добираем оставшиеся слоты одиночными байтами, чтобы покрыть весь алфавит
std::vector<std::string> chars = utf8_chars(text);
std::sort(chars.begin(), chars.end());
chars.erase(std::unique(chars.begin(), chars.end()), chars.end());
@ -76,13 +68,26 @@ void Tokenizer::build_from_text(const std::string& text, int max_vocab) {
if (std::find(id2tok.begin(), id2tok.end(), c) == id2tok.end())
id2tok.push_back(c);
}
if (debug)
{
std::cout << "=== Vocabulary (Size: " << id2tok.size() << ") ===" << std::endl;
for (size_t i = 0; i < id2tok.size(); ++i) {
// Для наглядности заменяем невидимые символы на их представления
std::string display_tok = id2tok[i];
if (display_tok == "\n") display_tok = "\\n";
if (display_tok == " ") display_tok = "[space]";
std::cout << " [" << i << "] \"" << display_tok << "\"" << std::endl;
}
std::cout << "===============================" << std::endl;
}
}
std::vector<int> Tokenizer::encode(const std::string& text, bool add_bos) const {
// Карта: слово -> id. Строится лениво, один раз на вызов.
std::unordered_map<std::string, int> word2id;
std::unordered_map<std::string, int> char2id;
const int base = 4; // первые 4 — служебные
const int base = 4;
for (int i = base; i < size(); ++i) {
const std::string& t = id2tok[i];
if (t.size() == 1) char2id[t] = i;
@ -120,7 +125,6 @@ std::vector<int> Tokenizer::encode(const std::string& text, bool add_bos) const
if (it != char2id.end()) out.push_back(it->second);
else out.push_back(3);
}
// обычные пробелы не кодируем — слова склеиваются по границам словаря
} else {
word += ch;
}
@ -151,7 +155,6 @@ std::string Tokenizer::decode(const std::vector<int>& ids, bool skip_bos) const
return out;
}
// ------------------------------------------------------- ввод-вывод
namespace {
template <typename T>
@ -166,7 +169,7 @@ bool rd(std::ifstream& f, T& v) {
return (bool)f;
}
} // namespace
}
bool save_checkpoint(const std::string& path, const Checkpoint& ck, std::string& err) {
std::ofstream f(path, std::ios::binary);
@ -202,14 +205,12 @@ bool save_checkpoint(const std::string& path, const Checkpoint& ck, std::string&
f.write(ck.tok.id2tok[i].data(), len);
}
// веса
for (const Tensor* t : ck.params.all()) {
wr(f, (int32_t)t->R());
wr(f, (int32_t)t->C());
f.write(reinterpret_cast<const char*>(t->ptr()), (std::streamsize)t->n() * sizeof(float));
}
// оптимизатор: только если просили
if (!ck.params.m.empty()) {
wr(f, (uint32_t)1);
for (size_t i = 0; i < ck.params.m.size(); ++i) {
@ -285,11 +286,10 @@ bool load_checkpoint(const std::string& path, Checkpoint& ck, std::string& err)
return false;
}
// аллоцируем и читаем веса
const int d = c.n_embd, ffn = c.ffn();
ck.params.wte.resize(c.vocab_size, d);
if (!ck.params.tied()) ck.params.lm_head.resize(c.vocab_out(), d);
ck.params.wq.resize(c.n_layer, Tensor(d, d)); // q/k/v — три отдельных тензора [d,d]
ck.params.wq.resize(c.n_layer, Tensor(d, d));
ck.params.wk.resize(c.n_layer, Tensor(d, d));
ck.params.wv.resize(c.n_layer, Tensor(d, d));
ck.params.wo.resize(c.n_layer, Tensor(d, d));
@ -312,7 +312,6 @@ bool load_checkpoint(const std::string& path, Checkpoint& ck, std::string& err)
if (!f) { err = "файл обрывается при чтении весов"; return false; }
}
// моменты оптимизатора (необязательно)
uint32_t has_opt = 0;
if (rd(f, has_opt) && has_opt) {
auto ps = ck.params.all();
@ -351,4 +350,4 @@ void print_model_info(const Checkpoint& ck) {
printf(" шаг : %d, loss %.4f\n", ck.step, ck.loss);
}
} // namespace xt
}

View File

@ -1,5 +0,0 @@
xenith/core/checkpoint.o: xenith/core/checkpoint.cpp \
xenith/core/checkpoint.h xenith/core/model.h xenith/core/tensor.h
xenith/core/checkpoint.h:
xenith/core/model.h:
xenith/core/tensor.h:

View File

@ -1,4 +1,3 @@
// checkpoint.h — сохранение и загрузка модели
#pragma once
#include "model.h"
#include <string>
@ -7,14 +6,13 @@
namespace xt {
// Токенизатор: словарь хранится рядом с весами в том же файле
struct Tokenizer {
std::vector<std::string> id2tok; // id -> строка
std::vector<std::string> id2tok;
int bos = 1, eos = 2, unk = 0;
int size() const { return (int)id2tok.size(); }
void build_from_text(const std::string& text, int max_vocab);
void build_from_text(const std::string& text, int max_vocab, bool debug = false);
std::vector<int> encode(const std::string& text, bool add_bos = true) const;
std::string decode(const std::vector<int>& ids, bool skip_bos = true) const;
std::string decode_one(int id) const;
@ -31,7 +29,6 @@ struct Checkpoint {
bool save_checkpoint(const std::string& path, const Checkpoint& ck, std::string& err);
bool load_checkpoint(const std::string& path, Checkpoint& ck, std::string& err);
// Читаемый дамп конфигурации
void print_model_info(const Checkpoint& ck);
} // namespace xt

Binary file not shown.

View File

@ -1,5 +1,3 @@
// generate.cpp — инкрементальная генерация с KV-кэшем
// Кэш: [layer][token][head][dim] для K и для V отдельно.
#include "generate.h"
#include <cstdio>
#include <cmath>
@ -9,16 +7,15 @@ namespace xt {
namespace {
// Раскладка кэша: один плоский буфер на (K,V) оба сразу
struct KvCache {
int n_layer, max_ctx, n_head, head_dim, d_model;
std::vector<float> data; // K и V подряд: сначала все K, потом все V
int len = 0; // сколько позиций занято
std::vector<float> data;
int len = 0;
void init(int L, int ctx, int H, int hd) {
n_layer = L; max_ctx = ctx; n_head = H; head_dim = hd;
d_model = H * hd;
size_t per = (size_t)L * ctx * H * hd; // столько на K, столько на V
size_t per = (size_t)L * ctx * H * hd;
data.assign(per * 2, 0.0f);
len = 0;
}
@ -32,7 +29,6 @@ struct KvCache {
}
};
// RoPE на одном векторе длины head_dim, позиция pos
void rope_apply(float* x, int hd, int pos, const std::vector<float>& cs,
const std::vector<float>& sn) {
const int half = hd / 2;
@ -46,7 +42,6 @@ void rope_apply(float* x, int hd, int pos, const std::vector<float>& cs,
}
}
// Один токен через все слои. Возвращает логиты.
void forward_token(const Params& p, KvCache& kv, int token, int pos,
const std::vector<float>& cs, const std::vector<float>& sn,
Tensor& logits) {
@ -54,7 +49,6 @@ void forward_token(const Params& p, KvCache& kv, int token, int pos,
const int d = c.n_embd, H = c.n_head, hd = c.head_dim(), f = c.ffn();
const float scale = 1.0f / std::sqrt((float)hd);
// эмбеддинг
Tensor x(1, d);
const float* er = p.wte.row(token);
for (int j = 0; j < d; ++j) x.at(j) = er[j];
@ -120,12 +114,10 @@ void forward_token(const Params& p, KvCache& kv, int token, int pos,
else gemm_nn(lnf, p.lm_head, logits);
}
// Сэмплирование из логитов
int sample(const Tensor& logits, const GenConfig& gc, Rng& rng, const std::vector<int>& history) {
const int V = logits.C();
std::vector<float> lp(V);
// штраф за повтор
float rp = gc.repeat_penalty;
if (rp != 1.0f) {
for (int id : history) {
@ -143,7 +135,6 @@ int sample(const Tensor& logits, const GenConfig& gc, Rng& rng, const std::vecto
for (int j = 0; j < V; ++j) lp[j] /= gc.temperature;
// top-k
if (gc.top_k > 0 && gc.top_k < V) {
std::vector<std::pair<float, int>> v(V);
for (int j = 0; j < V; ++j) v[j] = {lp[j], j};
@ -161,7 +152,6 @@ int sample(const Tensor& logits, const GenConfig& gc, Rng& rng, const std::vecto
for (int j = 0; j < V; ++j) { lp[j] = std::exp(lp[j] - mx); sum += lp[j]; }
if (sum <= 0.0f) return 0;
// top-p (nucleus)
if (gc.top_p < 1.0f) {
std::vector<std::pair<float, int>> v(V);
for (int j = 0; j < V; ++j) v[j] = {lp[j] / sum, j};
@ -190,7 +180,7 @@ int sample(const Tensor& logits, const GenConfig& gc, Rng& rng, const std::vecto
return V - 1;
}
} // namespace
}
std::string generate(const Params& p, const Tokenizer& tok,
const std::string& prompt, const GenConfig& gc,
@ -217,13 +207,11 @@ std::string generate(const Params& p, const Tokenizer& tok,
for (int n = 0; n < gc.max_new_tokens; ++n) {
if (kv.len >= c.block_size) {
// контекст исчерпан: сдвигаем окно
const int keep = c.block_size / 2;
for (size_t i = 0; i < ids.size(); ++i)
ids[i] = ids[ids.size() - keep + i];
ids.resize(keep);
kv.reset();
// прогоняем окно заново
for (int t = 0; t < keep; ++t) {
forward_token(p, kv, ids[t], kv.len, cs, sn, logits);
kv.len++;
@ -272,4 +260,4 @@ std::vector<std::string> generate_batch(const Params& p, const Tokenizer& tok,
return out;
}
} // namespace xt
}

View File

@ -1,6 +0,0 @@
xenith/core/generate.o: xenith/core/generate.cpp xenith/core/generate.h \
xenith/core/checkpoint.h xenith/core/model.h xenith/core/tensor.h
xenith/core/generate.h:
xenith/core/checkpoint.h:
xenith/core/model.h:
xenith/core/tensor.h:

View File

@ -8,21 +8,20 @@ namespace xt {
struct GenConfig {
int max_new_tokens = 200;
float temperature = 0.8f; // <= 0 => greedy
int top_k = 40; // 0 => выключить
float top_p = 0.95f; // >= 1 => выключить
int seed = -1; // -1 => случайный
bool stream = true; // печатать по мере генерации
int repeat_penalty = 1; // штраф за повтор (окно = 64)
float temperature = 0.8f;
int top_k = 40;
float top_p = 0.95f;
int seed = -1;
bool stream = true;
int repeat_penalty = 1;
};
std::string generate(const Params& p, const Tokenizer& tok,
const std::string& prompt, const GenConfig& gc,
std::vector<int>* out_ids = nullptr);
// Батч из нескольких промптов — переиспользует один набор весов
std::vector<std::string> generate_batch(const Params& p, const Tokenizer& tok,
const std::vector<std::string>& prompts,
const GenConfig& gc);
} // namespace xt
}

Binary file not shown.

View File

@ -22,16 +22,15 @@ float eval_loss(const Params& p, const std::vector<int>& x, const std::vector<in
}
struct Probe {
Tensor* w; // указатель на веса
int idx; // какой элемент проверяем
Tensor* w;
int idx;
float analytic;
float numeric;
};
} // namespace
}
int run_gradcheck(bool verbose) {
// Маленькая модель: полная проверка всех весов за разумное время
Config c;
c.vocab_size = 24;
c.n_layer = 2;
@ -40,7 +39,7 @@ int run_gradcheck(bool verbose) {
c.block_size = 12;
c.ffn_dim = 16;
c.rope_base = 10000;
c.init_std = 0.3f; // умеренный масштаб
c.init_std = 0.3f;
Params p;
p.cfg = c;
@ -53,7 +52,6 @@ int run_gradcheck(bool verbose) {
y[i] = 1 + (i * 5 + 3) % (c.vocab_size - 1);
}
// Аналитические градиенты
ForwardCache fc;
forward(p, x.data(), n, n, fc);
backward(p, fc, x.data(), y.data(), n);
@ -74,7 +72,6 @@ int run_gradcheck(bool verbose) {
Tensor& W = *tensors[ti];
const Tensor& G = *grads[ti];
const int sz = (int)W.n();
// 3 случайных элемента на тензор
Rng rng(1000 + (uint64_t)ti);
int picks[3] = {(int)rng.below(sz), (int)rng.below(sz), (int)rng.below(sz)};
@ -90,10 +87,6 @@ int run_gradcheck(bool verbose) {
const float num = (lp - lm) / (2.0f * eps);
const float ana = G.at(i);
// Относительная ошибка у элементов с почти нулевым градиентом
// meaningless: численная производная там шумит сильнее самой
// величины. Поэтому нормируем на масштаб градиентов слоя,
// а не на сам градиент.
const float denom = std::max(1e-4f, G.L2() / std::sqrt((float)std::max(1, (int)G.n())));
const float rel = std::fabs(num - ana) / denom;
@ -154,7 +147,6 @@ int run_bench(Checkpoint& ck, int iters, int block) {
double d_all = std::chrono::duration<double>(t1 - t0).count();
float all_tps = (double)n * iters / d_all;
// генерация: замер на токен
GenConfig gc;
gc.max_new_tokens = 32;
gc.temperature = 0.0f;
@ -170,4 +162,4 @@ int run_bench(Checkpoint& ck, int iters, int block) {
return 0;
}
} // namespace xt
}

View File

@ -1,9 +0,0 @@
xenith/core/gradcheck.o: xenith/core/gradcheck.cpp \
xenith/core/gradcheck.h xenith/core/checkpoint.h xenith/core/model.h \
xenith/core/tensor.h xenith/core/generate.h xenith/core/train.h
xenith/core/gradcheck.h:
xenith/core/checkpoint.h:
xenith/core/model.h:
xenith/core/tensor.h:
xenith/core/generate.h:
xenith/core/train.h:

Binary file not shown.

View File

@ -1,4 +1,3 @@
// model.cpp — инициализация, прямой проход, RoPE, Adam
#include "model.h"
#include <cstdio>
#include <cmath>
@ -24,7 +23,6 @@ bool Config::valid(std::string& err) const {
return true;
}
// ------------------------------------------------------------------ init
void Params::init(uint64_t seed) {
Rng rng(seed);
const int d = cfg.n_embd, f = cfg.ffn();
@ -102,7 +100,6 @@ void Params::alloc_grads() {
grms_final = Tensor(1, d);
}
// Порядок фиксированный — от него зависит сохранение в файл
std::vector<const Tensor*> Params::all() const {
std::vector<const Tensor*> v;
v.push_back(&wte);
@ -155,11 +152,6 @@ void Params::adam_step(float lr, float b1, float b2, float eps, float wd, int st
m.clear(); v.clear();
for (Tensor* t : params) { m.push_back(Tensor(t->R(), t->C())); v.push_back(Tensor(t->R(), t->C())); }
}
// Единственный nan/inf в градиенте отравляет момент m и v этого
// элемента НАВСЕГДА: и Adam, и sqrt(V) будут nan, и вес больше не
// восстановится. Поэтому выкидываем нечисловые значения до того,
// как они попадут в моменты, и сигналим.
if (clip > 0.0f) {
double sq = 0;
bool dirty = false;
@ -198,7 +190,6 @@ void Params::adam_step(float lr, float b1, float b2, float eps, float wd, int st
}
}
// ----------------------------------------------------------------- RoPE
void rope_tables(int block, int head_dim, int base, std::vector<float>& cs, std::vector<float>& sn) {
const int half = head_dim / 2;
cs.resize((size_t)block * half);
@ -213,20 +204,14 @@ void rope_tables(int block, int head_dim, int base, std::vector<float>& cs, std:
}
}
// ------------------------------------------------------------- forward
// B = n_tok — общее число токенов
// S = seq_len — длина одной последовательности (B кратно S)
// Каждая последовательность b занимает строки [b*S, (b+1)*S) и имеет
// СВОИ позиции RoPE 0..S-1 и СВОЮ каузальную маску. Иначе токен из
// конца первого батча «видел» бы начало второго.
void forward(const Params& p, const int* tokens, int n_tok, int seq_len, ForwardCache& fc) {
const Config& c = p.cfg;
const int d = c.n_embd, H = c.n_head, hd = c.head_dim(), f = c.ffn();
int B = n_tok;
int S = seq_len;
if (S <= 0) S = B;
if (S > c.block_size) S = c.block_size; // RoPE-таблица не длиннее block_size
if (B % S != 0) B = (B / S) * S; // отбрасываем хвост
if (S > c.block_size) S = c.block_size;
if (B % S != 0) B = (B / S) * S;
const int n_seq = B / S;
fc.block = S;
@ -241,7 +226,6 @@ void forward(const Params& p, const int* tokens, int n_tok, int seq_len, Forward
const float* CS = fc.rope_cos.data();
const float* SN = fc.rope_sin.data();
// --- эмбеддинги ---
fc.x.resize(B, d);
for (int t = 0; t < B; ++t) {
float* xr = fc.x.row(t);
@ -257,13 +241,11 @@ void forward(const Params& p, const int* tokens, int n_tok, int seq_len, Forward
lc.x.resize(B, d);
for (size_t i = 0; i < fc.x.n(); ++i) lc.x.at(i) = fc.x.at(i);
// --- нормализация + QKV ---
rmsnorm_forward(lc.x, p.rms_attn[L], c.rms_eps, lc.xb, lc.inv_rms);
gemm_nn(lc.xb, p.wq[L], lc.q);
gemm_nn(lc.xb, p.wk[L], lc.k);
gemm_nn(lc.xb, p.wv[L], lc.v);
// --- RoPE: позиция считается ВНУТРИ последовательности ---
for (int b = 0; b < n_seq; ++b) {
for (int t = 0; t < S; ++t) {
const int row = b * S + t;
@ -285,14 +267,13 @@ void forward(const Params& p, const int* tokens, int n_tok, int seq_len, Forward
}
}
// --- causal attention (независимо в каждой последовательности) ---
lc.att.resize3d(n_seq * H, S, S);
lc.attout.resize(B, d);
lc.attout.zero();
for (int s = 0; s < n_seq; ++s) {
for (int hh = 0; hh < H; ++hh) {
const int hp = s * H + hh; // плоский индекс головы
const int hp = s * H + hh;
for (int t1 = 0; t1 < S; ++t1) {
const float* qr = lc.q.row(s * S + t1) + hh * hd;
float* sr = scores.row(t1);
@ -311,7 +292,6 @@ void forward(const Params& p, const int* tokens, int n_tok, int seq_len, Forward
float* ar = lc.att.row(hp * S + t1);
for (int t2 = 0; t2 < S; ++t2) ar[t2] = (t2 <= t1) ? sr[t2] * inv : 0.0f;
}
// out[t1] = sum_t2 att[t1][t2] * v[t2]
for (int t1 = 0; t1 < S; ++t1) {
const float* ar = lc.att.row(hp * S + t1);
float* orow = lc.attout.row(s * S + t1) + hh * hd;
@ -324,15 +304,12 @@ void forward(const Params& p, const int* tokens, int n_tok, int seq_len, Forward
}
}
}
// --- выход attention + residual ---
gemm_nn(lc.attout, p.wo[L], lc.proj);
for (size_t i = 0; i < fc.x.n(); ++i) fc.x.at(i) += lc.proj.at(i);
lc.x2.resize(B, d);
for (size_t i = 0; i < fc.x.n(); ++i) lc.x2.at(i) = fc.x.at(i);
// --- FFN (SwiGLU) ---
rmsnorm_forward(lc.x2, p.rms_ffn[L], c.rms_eps, lc.x2b, lc.inv_rms2);
gemm_nn(lc.x2b, p.w1[L], lc.h1);
gemm_nn(lc.x2b, p.w3[L], lc.h3);
@ -343,14 +320,12 @@ void forward(const Params& p, const int* tokens, int n_tok, int seq_len, Forward
for (size_t i = 0; i < fc.x.n(); ++i) fc.x.at(i) += lc.fo.at(i);
}
// --- финальная нормализация + выход ---
rmsnorm_forward(fc.x, p.rms_final, c.rms_eps, fc.xf, fc.inv_rms);
fc.logits.resize(B, c.vocab_out());
if (p.tied()) gemm_nt(fc.xf, p.wte, fc.logits);
else gemm_nn(fc.xf, p.lm_head, fc.logits);
}
// Одиночная последовательность: seq_len = n_tok
void forward(const Params& p, const int* tokens, int n_tok, ForwardCache& fc) {
forward(p, tokens, n_tok, n_tok, fc);
}
@ -363,4 +338,4 @@ void forward_last(const Params& p, const int* tokens, int n_tok, Tensor& logits)
for (int j = 0; j < V; ++j) logits.at(j) = fc.logits.at((n_tok - 1) * V + j);
}
} // namespace xt
}

View File

@ -1,4 +0,0 @@
xenith/core/model.o: xenith/core/model.cpp xenith/core/model.h \
xenith/core/tensor.h
xenith/core/model.h:
xenith/core/tensor.h:

View File

@ -1,4 +1,3 @@
// model.h — конфигурация, параметры и прямой проход трансформера
#pragma once
#include "tensor.h"
#include <string>
@ -6,7 +5,6 @@
namespace xt {
// Магия формата: 'X','N','H','1' в little-endian
constexpr uint32_t XNH_MAGIC = 0x31484E58;
struct Config {
@ -14,12 +12,12 @@ struct Config {
int n_layer = 4;
int n_head = 4;
int n_embd = 128;
int block_size = 128; // максимальный контекст
int ffn_dim = 0; // 0 => 4*n_embd
int block_size = 128;
int ffn_dim = 0;
int rope_base = 10000;
float rms_eps = 1e-5f;
float init_std = 0.02f;
int n_vocab_out = 0; // 0 => tied (используем wte как выходную матрицу)
int n_vocab_out = 0;
int head_dim() const { return n_embd / n_head; }
int ffn() const { return ffn_dim > 0 ? ffn_dim : 4 * n_embd; }
@ -27,22 +25,19 @@ struct Config {
bool valid(std::string& err) const;
};
// Все обучаемые веса. Именно этот список сериализуется в файл модели.
struct Params {
Config cfg;
Tensor wte; // [vocab, n_embd] — вход
Tensor lm_head; // [n_embd, vocab_out] — только если НЕ tied
Tensor wte;
Tensor lm_head;
std::vector<Tensor> wq, wk, wv, wo;
std::vector<Tensor> rms_attn, rms_ffn;
std::vector<Tensor> w1, w2, w3; // SwiGLU
Tensor rms_final; // [1, n_embd]
std::vector<Tensor> w1, w2, w3;
Tensor rms_final;
// Градиенты (тет же порядок, что у all())
Tensor gwte, glm_head, grms_final;
std::vector<Tensor> gq, gk, gv, go, grms_attn, grms_ffn, g1, g2, g3;
// Моменты Adam
std::vector<Tensor> m, v;
size_t n_params() const;
@ -53,52 +48,43 @@ struct Params {
void adam_step(float lr, float b1, float b2, float eps, float wd,
int step, float clip);
std::vector<const Tensor*> all() const; // только для чтения/сохранения
std::vector<Tensor*> all_w(); // для изменения весов
std::vector<const Tensor*> all() const;
std::vector<Tensor*> all_w();
std::vector<Tensor*> all_grads();
};
// Кэш прямого прохода, нужен обратному
struct LayerCache {
Tensor x; // вход в слой
Tensor xb; // нормализованный вход (attention)
Tensor q, k, v; // [B, n_embd], RoPE применён к q,k
Tensor att; // [n_head, B, B] — веса внимания
Tensor attout; // [B, n_embd] — конкатенация голов
Tensor proj; // [B, n_embd] — после wo
Tensor x2; // после residual attention
Tensor x2b; // нормализация перед FFN
Tensor x;
Tensor xb;
Tensor q, k, v;
Tensor att;
Tensor attout;
Tensor proj;
Tensor x2;
Tensor x2b;
Tensor h1, h3, ha;
Tensor fo; // [B, n_embd] — после w2
Tensor fo;
Tensor inv_rms, inv_rms2;
};
struct ForwardCache {
std::vector<LayerCache> layers;
Tensor x; // текущий residual-поток
Tensor xf; // после финального RMSNorm
Tensor logits; // [B, vocab_out]
Tensor inv_rms; // финальная нормализация
Tensor x;
Tensor xf;
Tensor logits;
Tensor inv_rms;
std::vector<float> rope_cos, rope_sin;
int block = 0; // длина одной последовательности
int seq_len = 0; // то же, явно
int n_seq = 1; // сколько последовательностей в батче
int block = 0;
int seq_len = 0;
int n_seq = 1;
};
void rope_tables(int block, int head_dim, int base,
std::vector<float>& cs, std::vector<float>& sn);
// Полный прямой проход.
// n_tok — сколько токенов всего (может быть кратно block_size: батч)
// seq_len — длина ОДНОЙ последовательности (<= block_size).
// Внимание и RoPE строятся независимо для каждой
// последовательности, токены разных батчей не видят друг друга.
void forward(const Params& p, const int* tokens, int n_tok, int seq_len, ForwardCache& fc);
// Обёртка для одиночной последовательности
void forward(const Params& p, const int* tokens, int n_tok, ForwardCache& fc);
// Только последний ряд логитов
void forward_last(const Params& p, const int* tokens, int n_tok, Tensor& logits);
} // namespace xt
}

Binary file not shown.

View File

@ -1,5 +1,3 @@
// tensor.h — минимальная матричная библиотека для Xenith
// Только то, что реально нужно трансформеру: GEMM-тройка, RNG, parallel_for.
#pragma once
#include <cstdint>
#include <cmath>
@ -13,8 +11,6 @@
namespace xt {
// ---------------------------------------------------------------- RNG
// xorshift128+ — быстрый, детерминированный
struct Rng {
uint64_t s0, s1;
explicit Rng(uint64_t seed = 0x853c49e6748fea9bULL) {
@ -40,7 +36,6 @@ struct Rng {
size_t below(size_t n) { return n ? (size_t)(next_u64() % n) : 0; }
};
// ------------------------------------------------------------ потоки
inline int& hw_threads() {
static int t = [] {
unsigned hc = std::thread::hardware_concurrency();
@ -68,8 +63,6 @@ inline void parallel_for(int n, const std::function<void(int, int)>& fn) {
for (auto& x : th) x.join();
}
// ------------------------------------------------------------ Tensor
// Row-major: d[r*cols + c]
struct Tensor {
int rows = 0, cols = 0;
std::vector<float> d;
@ -78,7 +71,6 @@ struct Tensor {
Tensor(int r, int c) : rows(r), cols(c), d((size_t)r * c, 0.0f) {}
void resize(int r, int c) { rows = r; cols = c; d.assign((size_t)r * c, 0.0f); }
// Для тензоров вида [H][B][B] (веса внимания): хранится плоско
void resize3d(int h, int r, int c) { rows = r; cols = c; d.assign((size_t)h * r * c, 0.0f); }
void zero() { std::fill(d.begin(), d.end(), 0.0f); }
int R() const { return rows; }
@ -105,10 +97,6 @@ struct Tensor {
}
};
// --------------------------------------------------------------- GEMM
// Три формы — ровно столько нужно для прямого и обратного прохода.
// C[N,M] = A[N,K] * B[K,M]
inline void gemm_nn(const Tensor& A, const Tensor& B, Tensor& C) {
const int N = A.R(), K = A.C(), M = B.C();
C.resize(N, M);
@ -126,7 +114,6 @@ inline void gemm_nn(const Tensor& A, const Tensor& B, Tensor& C) {
});
}
// C[N,M] = A[N,K] * B[M,K]^T (нужно для logits = X @ w_emb^T)
inline void gemm_nt(const Tensor& A, const Tensor& B, Tensor& C) {
const int N = A.R(), K = A.C(), M = B.R();
C.resize(N, M);
@ -144,15 +131,11 @@ inline void gemm_nt(const Tensor& A, const Tensor& B, Tensor& C) {
});
}
// C[K,M] = A[N,K]^T * B[N,M] (нужно для градиентов весов)
inline void gemm_tn(const Tensor& A, const Tensor& B, Tensor& C, bool accumulate = false) {
const int N = A.R(), K = A.C(), M = B.C();
if (!accumulate) {
C.resize(K, M);
} else {
// Молчаливый resize здесь маскировал ошибки раскладки градиентов.
// Если форма не совпала — это баг в вызывающем коде, а не повод
// перевыделять память и терять накопленное.
if (C.R() != K || C.C() != M) {
std::fprintf(stderr,
"gemm_tn: форма C %dx%d != ожидаемой %dx%d "
@ -174,15 +157,12 @@ inline void gemm_tn(const Tensor& A, const Tensor& B, Tensor& C, bool accumulate
});
}
// ------------------------------------------------------------ активации
inline float silu(float x) { return x / (1.0f + std::expf(-x)); }
inline float dsilu(float x) {
float s = 1.0f / (1.0f + std::expf(-x));
return s * (1.0f + x * (1.0f - s));
}
// RMSNorm: y = x / sqrt(mean(x^2)+eps) * w
// x:[N,D] w:[1,D] y:[N,D] inv_rms:[N,1]
inline void rmsnorm_forward(const Tensor& x, const Tensor& w, float eps,
Tensor& y, Tensor& inv_rms) {
const int N = x.R(), D = x.C();
@ -199,8 +179,6 @@ inline void rmsnorm_forward(const Tensor& x, const Tensor& w, float eps,
}
}
// dx, dw из dy. Нужны x, w, y (y = x*r*w, отсюда r*w = y/x — но делить нельзя),
// поэтому храним inv_rms и w.
inline void rmsnorm_backward(const Tensor& x, const Tensor& w, const Tensor& inv_rms,
const Tensor& dy, Tensor& dx, Tensor& dw) {
const int N = x.R(), D = x.C();
@ -212,18 +190,13 @@ inline void rmsnorm_backward(const Tensor& x, const Tensor& w, const Tensor& inv
const float* dr = dy.row(n);
float* gx = dx.row(n);
const float r = inv_rms.at(n);
// s = sum_j dy_j * w_j * x_j
float s = 0.0f;
for (int j = 0; j < D; ++j) s += dr[j] * w.at(j) * xr[j];
for (int j = 0; j < D; ++j) {
// dx_j = r*dy_j*w_j - r^3 * s * x_j / D
gx[j] = r * dr[j] * w.at(j) - r * r * r * s * xr[j] / (float)D;
// dw_j = sum_n dy_n * x_n * r
// (y_n = x_n * r * w_n, поэтому d y_n / d w_j = x_n * r * [n==j]).
// Множителя w здесь быть не должно — иначе это dL/dw^2, а не dL/dw.
dw.at(j) += dr[j] * xr[j] * r;
}
}
}
} // namespace xt
}

View File

@ -1,4 +1,3 @@
// train.h — обратный проход и оптимизатор
#pragma once
#include "model.h"
#include "checkpoint.h"
@ -6,19 +5,16 @@
namespace xt {
// Считает градиенты по всем параметрам и возвращает mean loss.
// x — входные токены [n-1], y — цели [n-1] (сдвиг на 1)
float backward(Params& p, const ForwardCache& fc,
const int* x, const int* y, int n_pairs);
// Только loss по готовому форварду (для валидации, без обратного прохода)
float softmax_eval(const Params& p, const ForwardCache& fc,
const int* x, const int* y, int n_pairs, Tensor& per_token_loss);
struct TrainConfig {
int steps = 1000;
int batch_size = 8;
int block = 64; // длина обучающего окна
int block = 64;
float lr = 3e-4f;
float beta1 = 0.9f;
float beta2 = 0.999f;
@ -26,16 +22,15 @@ struct TrainConfig {
float weight_decay = 0.01f;
float clip = 1.0f;
int warmup = 100;
float lr_min_frac = 0.1f; // нижняя граница после затухания
float lr_min_frac = 0.1f;
uint64_t seed = 1337;
int threads = 0; // 0 => все ядра
int threads = 0;
int log_every = 50;
int ckpt_every = 0; // 0 => без промежуточных сохранений
int val_every = 0; // 0 => без валидации
int ckpt_every = 0;
int val_every = 0;
int val_tokens = 20000;
};
// Кусочек корпуса: массив токенов + границы предложений
struct Dataset {
std::vector<int> ids;
size_t n() const { return ids.size(); }
@ -48,9 +43,8 @@ struct TrainStats {
double tokens_seen = 0;
};
// Основной цикл обучения
TrainStats train_model(Params& p, const Tokenizer& tok, const std::string& corpus_path,
const TrainConfig& tc, const std::string& out_path,
int resume_step = 0);
} // namespace xt
}

View File

@ -1,4 +1,3 @@
// trainer.cpp — цикл обучения
#include "train.h"
#include "checkpoint.h"
#include <cstdio>
@ -21,7 +20,6 @@ std::string read_file(const std::string& path, bool& ok) {
return ss.str();
}
// cosine с прогревом
float lr_at(const TrainConfig& tc, int step) {
if (step < tc.warmup) return tc.lr * (step + 1) / (float)std::max(1, tc.warmup);
const int t = step - tc.warmup;
@ -31,9 +29,8 @@ float lr_at(const TrainConfig& tc, int step) {
return tc.lr * (tc.lr_min_frac + (1.0f - tc.lr_min_frac) * cos);
}
} // namespace
}
// Обучение. Модель сохраняется в out_path, промежуточные — в out_path + ".stepN"
TrainStats train_model(Params& p, const Tokenizer& tok, const std::string& corpus_path,
const TrainConfig& tc, const std::string& out_path,
int resume_step) {
@ -59,7 +56,6 @@ TrainStats train_model(Params& p, const Tokenizer& tok, const std::string& corpu
return st;
}
// валидация
std::vector<int> val_ids;
bool has_val = false;
if (tc.val_every > 0 && ids.size() > (size_t)tc.val_tokens + block + 1) {
@ -75,7 +71,6 @@ TrainStats train_model(Params& p, const Tokenizer& tok, const std::string& corpu
const int start = resume_step;
for (int step = start; step < tc.steps; ++step) {
// набираем батч
for (int b = 0; b < B; ++b) {
size_t off = rng.below(ids.size() - block - 1);
for (int t = 0; t < block; ++t) {
@ -93,13 +88,12 @@ TrainStats train_model(Params& p, const Tokenizer& tok, const std::string& corpu
st.step = step;
st.tokens_seen += (double)B * block;
if (step % tc.log_every == 0 || step == tc.steps - 1) {
auto now = std::chrono::steady_clock::now();
double dt = std::chrono::duration<double>(now - t0).count();
double tps = dt > 0 ? st.tokens_seen / dt : 0;
fprintf(stderr, "шаг %5d/%d loss %.4f ppl %8.2f lr %.2e %6.0f ток/с\n",
step, tc.steps, loss, std::exp(loss), lr_at(tc, step), tps);
}
auto now = std::chrono::steady_clock::now();
double dt = std::chrono::duration<double>(now - t0).count();
double tps = dt > 0 ? st.tokens_seen / dt : 0;
fprintf(stderr, "шаг %5d/%d loss %.4f ppl %8.2f lr %.2e %6.0f ток/с\n",
step, tc.steps, loss, std::exp(loss), lr_at(tc, step), tps);
if (tc.val_every > 0 && has_val && (step + 1) % tc.val_every == 0) {
float vl = 0;
@ -137,4 +131,4 @@ TrainStats train_model(Params& p, const Tokenizer& tok, const std::string& corpu
return st;
}
} // namespace xt
}

View File

@ -1,6 +0,0 @@
xenith/core/trainer.o: xenith/core/trainer.cpp xenith/core/train.h \
xenith/core/model.h xenith/core/tensor.h xenith/core/checkpoint.h
xenith/core/train.h:
xenith/core/model.h:
xenith/core/tensor.h:
xenith/core/checkpoint.h:

Binary file not shown.