BagFix and auto apdate
This commit is contained in:
parent
41aa593019
commit
fdb943d3e6
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
models/
|
||||
@ -25,6 +25,7 @@ set(SOURCES
|
||||
xenith/preprocessing/tokenizer.cpp
|
||||
ollama_api/test.cpp
|
||||
xenith/converter.cpp
|
||||
help.h
|
||||
)
|
||||
|
||||
# Заголовочные файлы (для удобства в IDE)
|
||||
|
||||
14
README.md
14
README.md
@ -1,5 +1,9 @@
|
||||
# Xenith
|
||||
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
Трансформер на чистом 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
|
||||
|
||||
BIN
bin/Xenith
BIN
bin/Xenith
Binary file not shown.
4000
corpus/demo.txt
4000
corpus/demo.txt
File diff suppressed because it is too large
Load Diff
71
help.h
Normal file
71
help.h
Normal 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
210
main.cpp
@ -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;
|
||||
}
|
||||
}
|
||||
BIN
models/demo.xnh
BIN
models/demo.xnh
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
models/my.xnh
BIN
models/my.xnh
Binary file not shown.
Binary file not shown.
@ -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
1
version.txt
Normal file
@ -0,0 +1 @@
|
||||
0.0.2
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
@ -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
|
||||
}
|
||||
|
||||
@ -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:
|
||||
@ -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.
@ -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
|
||||
}
|
||||
|
||||
@ -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:
|
||||
@ -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.
@ -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
|
||||
}
|
||||
|
||||
@ -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.
@ -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
|
||||
}
|
||||
|
||||
@ -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:
|
||||
@ -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.
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
Loading…
x
Reference in New Issue
Block a user