#include "neuroflow/train_lm.hpp" #include #include #include #include #include #include #include #ifdef USE_CUDA #include "cuda_context.hpp" #include "cuda_kernels.hpp" #endif namespace neuroflow { TrainLM::TrainLM(const TrainLMConfig& config) : cfg_(config) { CausalLMConfig lm_cfg; lm_cfg.vocab_size = cfg_.vocab_size; lm_cfg.d_model = cfg_.d_model; lm_cfg.max_seq_len = cfg_.max_seq_len; lm_cfg.num_attn_layers = cfg_.num_attn_layers; lm_cfg.num_attn_heads = cfg_.num_attn_heads; lm_cfg.causal_window_size = cfg_.causal_window_size; lm_cfg.sae_k = cfg_.sae_k; lm_cfg.ntm_memory_slots = cfg_.ntm_memory_slots; lm_cfg.weight_tying = cfg_.weight_tying; lm_cfg.use_rope = cfg_.use_rope; lm_cfg.use_bridge = cfg_.use_bridge; lm_cfg.use_swiglu = cfg_.use_swiglu; lm_cfg.use_qk_norm = cfg_.use_qk_norm; lm_cfg.swiglu_intermediate_size = cfg_.swiglu_intermediate_size; lm_cfg.pooling = cfg_.pooling; lm_head_ = std::make_unique(lm_cfg); setup_optimizer(); size_t total = cfg_.total_steps > 0 ? cfg_.total_steps : cfg_.epochs * 1000; scheduler_ = std::make_unique( cfg_.learning_rate, total, cfg_.lr_min_ratio, cfg_.warmup_ratio); } void TrainLM::setup_optimizer() { optimizer_ = std::make_unique( cfg_.learning_rate, cfg_.adam_beta1, cfg_.adam_beta2, cfg_.adam_eps, cfg_.adam_weight_decay); lm_head_->register_trainable_params(*optimizer_, cfg_.learning_rate, cfg_.adam_weight_decay); } float TrainLM::compute_loss_and_grad(const std::vector& input_ids, const std::vector& target_ids, Tensor& logits_grad) { Tensor logits = lm_head_->forward_for_training(input_ids); size_t seq_len = logits.shape_[0]; size_t vocab_size = logits.shape_[1]; size_t n_targets = std::min(seq_len, target_ids.size()); float total_loss = 0.0f; logits_grad = Tensor(logits.shape_, QuantType::FP32); float* lg = logits_grad.as_fp32(); const float* lp = logits.as_fp32(); memset(lg, 0, logits_grad.data_size_); float grad_norm = 0.0f; for (size_t t = 0; t < n_targets; ++t) { size_t target_id = target_ids[t]; if (target_id >= vocab_size) continue; const float* row = &lp[t * vocab_size]; float* grad_row = &lg[t * vocab_size]; float max_val = -1e30f; for (size_t j = 0; j < vocab_size; ++j) { max_val = std::max(max_val, row[j]); } float sum_exp = 0.0f; for (size_t j = 0; j < vocab_size; ++j) { sum_exp += std::exp(row[j] - max_val); } float log_sum_exp = max_val + std::log(sum_exp); float loss = -(row[target_id] - log_sum_exp); total_loss += loss; for (size_t j = 0; j < vocab_size; ++j) { float softmax_val = std::exp(row[j] - max_val) / sum_exp; grad_row[j] = softmax_val; if (j == target_id) grad_row[j] -= 1.0f; grad_norm += grad_row[j] * grad_row[j]; } } if (n_targets > 0) { float inv_n = 1.0f / static_cast(n_targets); total_loss *= inv_n; for (size_t i = 0; i < logits_grad.numel(); ++i) { lg[i] *= inv_n; } } float gn = std::sqrt(grad_norm); if (cfg_.grad_clip > 0.0f && gn > cfg_.grad_clip && std::isfinite(gn)) { float scale = cfg_.grad_clip / gn; for (size_t i = 0; i < logits_grad.numel(); ++i) { lg[i] *= scale; } } return total_loss; } void TrainLM::train(const std::vector>& dataset) { if (dataset.empty()) { std::cerr << "[ERROR] Empty dataset" << std::endl; return; } std::cerr << "=== TrainLM: Standard Causal LM Training ===" << std::endl; std::cerr << "Dataset: " << dataset.size() << " samples" << std::endl; std::cerr << "Optimizer: AdamW (lr=" << cfg_.learning_rate << ", wd=" << cfg_.adam_weight_decay << ")" << std::endl; std::cerr << "Scheduler: Cosine with " << cfg_.warmup_ratio * 100 << "% warmup" << std::endl; size_t global_step = 0; std::mt19937 rng(42); for (size_t epoch = 0; epoch < cfg_.epochs; ++epoch) { std::vector indices(dataset.size()); std::iota(indices.begin(), indices.end(), 0); std::shuffle(indices.begin(), indices.end(), rng); float epoch_loss = 0.0f; size_t step_count = 0; for (size_t idx : indices) { const auto& sample = dataset[idx]; if (sample.size() < 2) continue; std::vector input_ids(sample.begin(), sample.end() - 1); std::vector target_ids(sample.begin() + 1, sample.end()); float lr = scheduler_->get_lr(global_step); optimizer_->set_lr(lr); Tensor logits_grad; float loss = compute_loss_and_grad(input_ids, target_ids, logits_grad); auto lm_grads = lm_head_->backward_from_logits(logits_grad); lm_head_->assign_grads_to_optimizer(*optimizer_, lm_grads); optimizer_->step(); global_step++; epoch_loss += loss; step_count++; if (step_count % cfg_.log_interval == 0) { std::cerr << "Epoch " << epoch + 1 << " Step " << step_count << " loss=" << loss << " lr=" << lr << std::endl; } if (cfg_.save_interval > 0 && global_step % cfg_.save_interval == 0) { save_checkpoint(global_step, epoch_loss / step_count); } } if (step_count > 0) { std::cerr << "=== Epoch " << epoch + 1 << " avg_loss=" << epoch_loss / step_count << " ===" << std::endl; } } save_checkpoint(global_step, 0.0f); std::cerr << "Training complete. Total steps: " << global_step << std::endl; } void TrainLM::save_checkpoint(size_t step, float loss) { std::string path = cfg_.output_dir + "/lm_head_step" + std::to_string(step) + ".nfv1"; #ifdef _WIN32 std::string mkdir_cmd = "if not exist \"" + cfg_.output_dir + "\" mkdir \"" + cfg_.output_dir + "\""; system(mkdir_cmd.c_str()); #else std::string mkdir_cmd = "mkdir -p " + cfg_.output_dir; system(mkdir_cmd.c_str()); #endif std::ofstream ofs(path, std::ios::binary); if (!ofs) { std::cerr << "[ERROR] Cannot save checkpoint: " << path << std::endl; return; } ofs.write("LMH2", 4); auto sl = [&ofs](const std::string& n, const Tensor& t) { uint32_t nl = static_cast(n.size()); ofs.write(reinterpret_cast(&nl), 4); ofs.write(n.data(), nl); uint32_t nd = static_cast(t.shape_.size()); ofs.write(reinterpret_cast(&nd), 4); for (auto d : t.shape_) { uint32_t dd = static_cast(d); ofs.write(reinterpret_cast(&dd), 4); } uint32_t ds = static_cast(t.data_size_); ofs.write(reinterpret_cast(&ds), 4); ofs.write(reinterpret_cast(t.data_.get()), ds); }; sl("w_embed", lm_head_->w_embed_); sl("w_pos", lm_head_->w_pos_); sl("dw_kernel", lm_head_->dw_kernel_); sl("pw_conv.weight", lm_head_->pw_conv_->weight); sl("sae_encode.weight", lm_head_->sae_w_encode_->weight); sl("sae_decode.weight", lm_head_->sae_w_decode_->weight); sl("ntm_read.weight", lm_head_->ntm_w_read_->weight); sl("ntm_write.weight", lm_head_->ntm_w_write_->weight); sl("ntm_erase.weight", lm_head_->ntm_w_erase_->weight); sl("ntm_memory", lm_head_->ntm_memory_); sl("w_proj.weight", lm_head_->w_proj_->weight); sl("w_proj.bias", lm_head_->w_proj_->bias); if (lm_head_->bridge_) { sl("bridge.weight", lm_head_->bridge_->weight); sl("bridge.bias", lm_head_->bridge_->bias); } sl("w_out.weight", lm_head_->w_out_->weight); if (lm_head_->w_out_->bias.data_) sl("w_out.bias", lm_head_->w_out_->bias); sl("ln.weight", lm_head_->ln_->weight); sl("ln.bias", lm_head_->ln_->bias); for (size_t i = 0; i < lm_head_->attn_layers_.size(); ++i) { std::string prefix = "attn" + std::to_string(i) + "."; sl(prefix + "w_q.weight", lm_head_->attn_layers_[i]->w_q->weight); sl(prefix + "w_q.bias", lm_head_->attn_layers_[i]->w_q->bias); sl(prefix + "w_k.weight", lm_head_->attn_layers_[i]->w_k->weight); sl(prefix + "w_k.bias", lm_head_->attn_layers_[i]->w_k->bias); sl(prefix + "w_v.weight", lm_head_->attn_layers_[i]->w_v->weight); sl(prefix + "w_v.bias", lm_head_->attn_layers_[i]->w_v->bias); sl(prefix + "w_out.weight", lm_head_->attn_layers_[i]->w_out->weight); sl(prefix + "w_out.bias", lm_head_->attn_layers_[i]->w_out->bias); sl(prefix + "norm.weight", lm_head_->attn_layers_[i]->norm->weight); sl(prefix + "norm.bias", lm_head_->attn_layers_[i]->norm->bias); } ofs.close(); std::cerr << "[CHECKPOINT] Saved: " << path << " (loss=" << loss << ")" << std::endl; } } // namespace neuroflow