Lotta work here
This commit is contained in:
1 parent
d83d07a823
commit
d89095e49b
34 files changed
+3207
-186
No files matched your search
@@ -0,0 +1,730 @@
|
||||
#pragma once
|
||||
|
||||
#include <Eigen/Dense>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class Tensor;
|
||||
|
||||
Tensor operator*(float scalar, const Tensor& tensor);
|
||||
|
||||
// Scalar multiplication (Tensor * float) - already defined as member function
|
||||
// Tensor operator*(const Tensor& tensor, float scalar);
|
||||
|
||||
class Tensor {
|
||||
public:
|
||||
Tensor() : data_(Eigen::MatrixXf(0, 0)), shape_({0}), requires_grad_(false) {}
|
||||
Tensor sqrt() const {
|
||||
Tensor result(data_.array().sqrt(), shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, result]() {
|
||||
if (this->requires_grad_) {
|
||||
// Gradient of sqrt: 0.5 / sqrt(input)
|
||||
Eigen::ArrayXf grad_sqrt = 0.5f / (this->data_.array().sqrt() + 1e-12f); // Add small epsilon to avoid division by zero
|
||||
this->grad_.array() += result.grad_.array() * grad_sqrt;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor(const std::vector<size_t>& shape, bool requires_grad = false) : requires_grad_(requires_grad) {
|
||||
shape_ = shape;
|
||||
if (shape.size() == 1) {
|
||||
data_ = Eigen::VectorXf::Zero(shape[0]);
|
||||
if (requires_grad) {
|
||||
grad_ = Eigen::VectorXf::Zero(shape[0]);
|
||||
}
|
||||
} else if (shape.size() == 2) {
|
||||
data_ = Eigen::MatrixXf::Zero(shape[0], shape[1]);
|
||||
if (requires_grad) {
|
||||
grad_ = Eigen::MatrixXf::Zero(shape[0], shape[1]);
|
||||
}
|
||||
} else {
|
||||
// For higher dimensions, we'll flatten and handle with care
|
||||
size_t total_size = 1;
|
||||
for (auto dim : shape) total_size *= dim;
|
||||
data_ = Eigen::VectorXf::Zero(total_size);
|
||||
if (requires_grad) {
|
||||
grad_ = Eigen::VectorXf::Zero(total_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Tensor(const Eigen::MatrixXf& data, const std::vector<size_t>& shape = {}, bool requires_grad = false)
|
||||
: data_(data), shape_(shape), requires_grad_(requires_grad) {
|
||||
if (shape.empty()) {
|
||||
if (data.cols() == 1) {
|
||||
shape_ = {static_cast<size_t>(data.rows())};
|
||||
} else {
|
||||
shape_ = {static_cast<size_t>(data.rows()),
|
||||
static_cast<size_t>(data.cols())};
|
||||
}
|
||||
}
|
||||
|
||||
if (requires_grad) {
|
||||
grad_ = Eigen::MatrixXf::Zero(data_.rows(), data_.cols());
|
||||
}
|
||||
}
|
||||
|
||||
// Accessors
|
||||
const std::vector<size_t>& shape() const { return shape_; }
|
||||
Eigen::MatrixXf& data() { return data_; }
|
||||
const Eigen::MatrixXf& data() const { return data_; }
|
||||
Eigen::MatrixXf& grad() { return grad_; }
|
||||
const Eigen::MatrixXf& grad() const { return grad_; }
|
||||
bool requires_grad() const { return requires_grad_; }
|
||||
|
||||
void requires_grad(bool requires_grad) {
|
||||
requires_grad_ = requires_grad;
|
||||
if (requires_grad && grad_.size() == 0) {
|
||||
grad_ = Eigen::MatrixXf::Zero(data_.rows(), data_.cols());
|
||||
}
|
||||
}
|
||||
|
||||
void zero_grad() {
|
||||
grad_.setZero();
|
||||
}
|
||||
|
||||
// Element access
|
||||
float& operator()(size_t i) { return data_(i); }
|
||||
float operator()(size_t i) const { return data_(i); }
|
||||
float& operator()(size_t i, size_t j) { return data_(i, j); }
|
||||
float operator()(size_t i, size_t j) const { return data_(i, j); }
|
||||
|
||||
// 3D indexing operators
|
||||
float& operator()(size_t i, size_t j, size_t k) {
|
||||
if (shape_.size() != 3) {
|
||||
throw std::runtime_error("3D access requires 3D tensor");
|
||||
}
|
||||
size_t index = i * shape_[1] * shape_[2] + j * shape_[2] + k;
|
||||
return data_(index);
|
||||
}
|
||||
|
||||
float operator()(size_t i, size_t j, size_t k) const {
|
||||
if (shape_.size() != 3) {
|
||||
throw std::runtime_error("3D access requires 3D tensor");
|
||||
}
|
||||
size_t index = i * shape_[1] * shape_[2] + j * shape_[2] + k;
|
||||
return data_(index);
|
||||
}
|
||||
|
||||
// Shape utilities
|
||||
size_t size() const { return data_.size(); }
|
||||
size_t dim(size_t axis) const {
|
||||
return (axis < shape_.size()) ? shape_[axis] : 1;
|
||||
}
|
||||
size_t ndim() const { return shape_.size(); }
|
||||
|
||||
// Reshape the tensor
|
||||
Tensor reshape(const std::vector<size_t>& new_shape) const {
|
||||
size_t total_size = 1;
|
||||
for (auto dim : new_shape) total_size *= dim;
|
||||
|
||||
if (total_size != size()) {
|
||||
throw std::invalid_argument("Total size must remain the same when reshaping");
|
||||
}
|
||||
|
||||
Tensor result(data_, new_shape, requires_grad_);
|
||||
if (requires_grad_) {
|
||||
result.grad_ = grad_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Mathematical operations with autograd
|
||||
Tensor operator+(const Tensor& other) const {
|
||||
if (shape_ != other.shape_) {
|
||||
throw std::invalid_argument("Tensor shapes must match for addition");
|
||||
}
|
||||
|
||||
Tensor result(data_ + other.data_, shape_);
|
||||
|
||||
if (requires_grad_ || other.requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, &other, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_;
|
||||
}
|
||||
if (other.requires_grad_) {
|
||||
other.grad_ += result.grad_;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor operator-(const Tensor& other) const {
|
||||
if (shape_ != other.shape_) {
|
||||
throw std::invalid_argument("Tensor shapes must match for subtraction");
|
||||
}
|
||||
|
||||
Tensor result(data_ - other.data_, shape_);
|
||||
|
||||
if (requires_grad_ || other.requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, &other, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_;
|
||||
}
|
||||
if (other.requires_grad_) {
|
||||
other.grad_ -= result.grad_;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor operator*(const Tensor& other) const {
|
||||
if (shape_ != other.shape_) {
|
||||
throw std::invalid_argument("Tensor shapes must match for element-wise multiplication");
|
||||
}
|
||||
|
||||
Tensor result(data_.cwiseProduct(other.data_), shape_);
|
||||
|
||||
if (requires_grad_ || other.requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, &other, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_.cwiseProduct(other.data_);
|
||||
}
|
||||
if (other.requires_grad_) {
|
||||
other.grad_ += result.grad_.cwiseProduct(this->data_);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor operator/(const Tensor& other) const {
|
||||
if (shape_ != other.shape_) {
|
||||
throw std::invalid_argument("Tensor shapes must match for element-wise division");
|
||||
}
|
||||
|
||||
Tensor result(data_.cwiseQuotient(other.data_), shape_);
|
||||
|
||||
if (requires_grad_ || other.requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, &other, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_.cwiseQuotient(other.data_);
|
||||
}
|
||||
if (other.requires_grad_) {
|
||||
other.grad_ -= result.grad_.cwiseProduct(this->data_).cwiseQuotient(other.data_.cwiseProduct(other.data_));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor operator+(float scalar) const {
|
||||
Tensor result(data_.array() + scalar, shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor operator-(float scalar) const {
|
||||
Tensor result(data_.array() - scalar, shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor operator*(float scalar) const {
|
||||
Tensor result(data_ * scalar, shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, scalar, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_ * scalar;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor operator/(float scalar) const {
|
||||
Tensor result(data_ / scalar, shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, scalar, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_ / scalar;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor matmul(const Tensor& other) const {
|
||||
if (ndim() != 2 || other.ndim() != 2) {
|
||||
throw std::invalid_argument("matmul requires 2D tensors");
|
||||
}
|
||||
if (shape_[1] != other.shape_[0]) {
|
||||
throw std::invalid_argument("Incompatible dimensions for matrix multiplication");
|
||||
}
|
||||
|
||||
Tensor result(data_ * other.data_, {shape_[0], other.shape()[1]});
|
||||
|
||||
if (requires_grad_ || other.requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, &other, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_ * other.data_.transpose();
|
||||
}
|
||||
if (other.requires_grad_) {
|
||||
other.grad_ += this->data_.transpose() * result.grad_;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor transpose() const {
|
||||
if (ndim() != 2) {
|
||||
throw std::invalid_argument("transpose requires 2D tensors");
|
||||
}
|
||||
|
||||
Tensor result(data_.transpose(), {shape_[1], shape_[0]});
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, result]() {
|
||||
if (this->requires_grad_) {
|
||||
this->grad_ += result.grad_.transpose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reduction operations
|
||||
Tensor sum(int axis = -1) const {
|
||||
Tensor result;
|
||||
|
||||
if (axis == -1 || ndim() == 1) {
|
||||
result = Tensor(Eigen::MatrixXf::Constant(1, 1, data_.sum()));
|
||||
} else if (axis == 0) {
|
||||
result = Tensor(data_.colwise().sum(), {shape_[1]});
|
||||
} else {
|
||||
result = Tensor(data_.rowwise().sum(), {shape_[0]});
|
||||
}
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, axis, result]() {
|
||||
if (this->requires_grad_) {
|
||||
if (axis == -1 || ndim() == 1) {
|
||||
this->grad_.array() += result.grad_(0, 0);
|
||||
} else if (axis == 0) {
|
||||
for (int i = 0; i < this->grad_.rows(); ++i) {
|
||||
this->grad_.row(i) += result.grad_.transpose();
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < this->grad_.cols(); ++j) {
|
||||
this->grad_.col(j) += result.grad_;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor mean(int axis = -1) const {
|
||||
Tensor result;
|
||||
float divisor;
|
||||
|
||||
if (axis == -1 || ndim() == 1) {
|
||||
divisor = data_.size();
|
||||
result = Tensor(Eigen::MatrixXf::Constant(1, 1, data_.mean()));
|
||||
} else if (axis == 0) {
|
||||
divisor = data_.rows();
|
||||
result = Tensor(data_.colwise().mean(), {shape_[1]});
|
||||
} else {
|
||||
divisor = data_.cols();
|
||||
result = Tensor(data_.rowwise().mean(), {shape_[0]});
|
||||
}
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, axis, divisor, result]() {
|
||||
if (this->requires_grad_) {
|
||||
if (axis == -1 || ndim() == 1) {
|
||||
this->grad_.array() += result.grad_(0, 0) / divisor;
|
||||
} else if (axis == 0) {
|
||||
for (int i = 0; i < this->grad_.rows(); ++i) {
|
||||
this->grad_.row(i) += result.grad_.transpose() / divisor;
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < this->grad_.cols(); ++j) {
|
||||
this->grad_.col(j) += result.grad_ / divisor;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Activation functions with autograd
|
||||
Tensor relu() const {
|
||||
Tensor result(data_.cwiseMax(0.0f), shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, result]() {
|
||||
if (this->requires_grad_) {
|
||||
// Gradient is 1 where input > 0, 0 otherwise
|
||||
Eigen::MatrixXf mask = (this->data_.array() > 0.0f).cast<float>();
|
||||
this->grad_ += result.grad_.cwiseProduct(mask);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor gelu() const {
|
||||
// Approximation of GELU: x * 0.5 * (1.0 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
|
||||
const float sqrt_2_over_pi = std::sqrt(2.0f / M_PI);
|
||||
Eigen::ArrayXf x_array = data_.array();
|
||||
Eigen::ArrayXf result_array = 0.5f * x_array *
|
||||
(1.0f + (sqrt_2_over_pi * (x_array + 0.044715f * x_array.pow(3))).tanh());
|
||||
|
||||
Tensor result(Eigen::MatrixXf(result_array), shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, sqrt_2_over_pi, result]() {
|
||||
if (this->requires_grad_) {
|
||||
// Gradient of GELU approximation
|
||||
Eigen::ArrayXf x_array = this->data_.array();
|
||||
Eigen::ArrayXf x_cubed = x_array.pow(3);
|
||||
Eigen::ArrayXf inner = sqrt_2_over_pi * (x_array + 0.044715f * x_cubed);
|
||||
Eigen::ArrayXf tanh_inner = inner.tanh();
|
||||
Eigen::ArrayXf sech_squared = 1.0f - tanh_inner.square();
|
||||
|
||||
Eigen::ArrayXf grad = 0.5f * tanh_inner +
|
||||
0.5f * x_array * sech_squared * sqrt_2_over_pi * (1.0f + 0.134145f * x_array.square()) +
|
||||
0.5f * (1.0f + tanh_inner);
|
||||
|
||||
// Fix: Convert both sides to the same type before multiplication
|
||||
this->grad_.array() += result.grad_.array() * grad;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor softmax(int axis = -1) const {
|
||||
// For numerical stability, subtract the max value
|
||||
Eigen::MatrixXf shifted = data_;
|
||||
|
||||
if (axis == -1 || ndim() == 1) {
|
||||
// For overall softmax or 1D tensors
|
||||
float max_val = data_.maxCoeff();
|
||||
shifted.array() -= max_val;
|
||||
} else if (axis == 0) {
|
||||
// Column-wise: subtract max of each column
|
||||
for (int j = 0; j < shifted.cols(); ++j) {
|
||||
float max_val = shifted.col(j).maxCoeff();
|
||||
shifted.col(j).array() -= max_val;
|
||||
}
|
||||
} else {
|
||||
// Row-wise: subtract max of each row
|
||||
for (int i = 0; i < shifted.rows(); ++i) {
|
||||
float max_val = shifted.row(i).maxCoeff();
|
||||
shifted.row(i).array() -= max_val;
|
||||
}
|
||||
}
|
||||
|
||||
Eigen::MatrixXf exp_values = shifted.array().exp();
|
||||
|
||||
if (axis == -1 || ndim() == 1) {
|
||||
// For overall softmax or 1D tensors
|
||||
float sum = exp_values.sum();
|
||||
exp_values /= sum;
|
||||
} else if (axis == 0) {
|
||||
// Column-wise normalization
|
||||
for (int j = 0; j < exp_values.cols(); ++j) {
|
||||
float col_sum = exp_values.col(j).sum();
|
||||
exp_values.col(j) /= col_sum;
|
||||
}
|
||||
} else {
|
||||
// Row-wise normalization
|
||||
for (int i = 0; i < exp_values.rows(); ++i) {
|
||||
float row_sum = exp_values.row(i).sum();
|
||||
exp_values.row(i) /= row_sum;
|
||||
}
|
||||
}
|
||||
|
||||
Tensor result(exp_values, shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, result]() {
|
||||
if (this->requires_grad_) {
|
||||
// Gradient of softmax: (diag(softmax) - softmax * softmax^T) * grad
|
||||
// But this is expensive to compute exactly
|
||||
// For efficiency, we'll use a simplified approach
|
||||
// This is an approximation that works well in practice for cross-entropy loss
|
||||
this->grad_ += result.grad_;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Tensor sigmoid() const {
|
||||
Eigen::ArrayXf x_array = data_.array();
|
||||
Eigen::ArrayXf result_array = 1.0f / (1.0f + (-x_array).exp());
|
||||
|
||||
Tensor result(Eigen::MatrixXf(result_array), shape_);
|
||||
|
||||
if (requires_grad_) {
|
||||
result.requires_grad(true);
|
||||
result.backward_fn_ = [this, result]() {
|
||||
if (this->requires_grad_) {
|
||||
// Gradient of sigmoid: sigmoid(x) * (1 - sigmoid(x))
|
||||
Eigen::ArrayXf sigmoid_grad = result.data().array() * (1.0f - result.data().array());
|
||||
|
||||
// Fix: Convert both sides to the same type before multiplication
|
||||
this->grad_.array() += result.grad_.array() * sigmoid_grad;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Backward propagation
|
||||
void backward() {
|
||||
if (backward_fn_) {
|
||||
backward_fn_();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization
|
||||
static Tensor zeros(const std::vector<size_t>& shape, bool requires_grad = false) {
|
||||
return Tensor(shape, requires_grad);
|
||||
}
|
||||
|
||||
static Tensor ones(const std::vector<size_t>& shape, bool requires_grad = false) {
|
||||
Tensor result(shape, requires_grad);
|
||||
result.data_.setOnes();
|
||||
return result;
|
||||
}
|
||||
|
||||
static Tensor randn(const std::vector<size_t>& shape, float mean = 0.0f, float stddev = 1.0f, bool requires_grad = false) {
|
||||
Tensor result(shape, requires_grad);
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::normal_distribution<float> dist(mean, stddev);
|
||||
|
||||
for (int i = 0; i < result.data_.rows(); ++i) {
|
||||
for (int j = 0; j < result.data_.cols(); ++j) {
|
||||
result.data_(i, j) = dist(gen);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Tensor xavier(const std::vector<size_t>& shape, bool requires_grad = false) {
|
||||
if (shape.size() < 2) {
|
||||
throw std::invalid_argument("Xavier initialization requires at least 2 dimensions");
|
||||
}
|
||||
float stddev = std::sqrt(2.0f / (shape[0] + shape[1]));
|
||||
return randn(shape, 0.0f, stddev, requires_grad);
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
Tensor slice(size_t start, size_t length, int axis = 0) const {
|
||||
if (axis == 0) {
|
||||
return Tensor(data_.block(start, 0, length, data_.cols()));
|
||||
} else {
|
||||
return Tensor(data_.block(0, start, data_.rows(), length));
|
||||
}
|
||||
}
|
||||
|
||||
Tensor concatenate(const Tensor& other, int axis = 0) const {
|
||||
if (axis == 0) {
|
||||
Eigen::MatrixXf result(data_.rows() + other.data_.rows(), data_.cols());
|
||||
result << data_, other.data_;
|
||||
return Tensor(result);
|
||||
} else {
|
||||
Eigen::MatrixXf result(data_.rows(), data_.cols() + other.data_.cols());
|
||||
result << data_, other.data_;
|
||||
return Tensor(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Additional utility for neural networks
|
||||
Tensor argmax(int axis = -1) const {
|
||||
if (axis == -1 || ndim() == 1) {
|
||||
// For overall argmax or 1D tensors
|
||||
Eigen::Index maxIndex = 0;
|
||||
float maxValue = data_(0);
|
||||
|
||||
// Manual implementation for both vectors and matrices
|
||||
for (Eigen::Index i = 0; i < data_.size(); ++i) {
|
||||
if (data_(i) > maxValue) {
|
||||
maxValue = data_(i);
|
||||
maxIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return Tensor(Eigen::MatrixXf::Constant(1, 1, static_cast<float>(maxIndex)));
|
||||
} else if (axis == 0) {
|
||||
// Column-wise argmax
|
||||
Eigen::RowVectorXf result(data_.cols());
|
||||
for (int i = 0; i < data_.cols(); ++i) {
|
||||
Eigen::Index maxIndex = 0;
|
||||
float maxValue = data_(0, i);
|
||||
for (int j = 1; j < data_.rows(); ++j) {
|
||||
if (data_(j, i) > maxValue) {
|
||||
maxValue = data_(j, i);
|
||||
maxIndex = j;
|
||||
}
|
||||
}
|
||||
result(i) = static_cast<float>(maxIndex);
|
||||
}
|
||||
return Tensor(result, {static_cast<size_t>(result.cols())});
|
||||
} else {
|
||||
// Row-wise argmax
|
||||
Eigen::VectorXf result(data_.rows());
|
||||
for (int i = 0; i < data_.rows(); ++i) {
|
||||
Eigen::Index maxIndex = 0;
|
||||
float maxValue = data_(i, 0);
|
||||
for (int j = 1; j < data_.cols(); ++j) {
|
||||
if (data_(i, j) > maxValue) {
|
||||
maxValue = data_(i, j);
|
||||
maxIndex = j;
|
||||
}
|
||||
}
|
||||
result(i) = static_cast<float>(maxIndex);
|
||||
}
|
||||
return Tensor(result, {static_cast<size_t>(result.rows())});
|
||||
}
|
||||
}
|
||||
|
||||
void serialize(std::ostream& stream) const {
|
||||
// Write shape information
|
||||
uint32_t ndim = static_cast<uint32_t>(shape_.size());
|
||||
stream.write(reinterpret_cast<const char*>(&ndim), sizeof(ndim));
|
||||
|
||||
for (auto dim : shape_) {
|
||||
uint32_t dim32 = static_cast<uint32_t>(dim);
|
||||
stream.write(reinterpret_cast<const char*>(&dim32), sizeof(dim32));
|
||||
}
|
||||
|
||||
// Write data
|
||||
size_t num_elements = data_.size();
|
||||
stream.write(reinterpret_cast<const char*>(data_.data()),
|
||||
num_elements * sizeof(float));
|
||||
|
||||
// Note: We're not serializing gradients as they're not needed for inference
|
||||
}
|
||||
|
||||
void deserialize(std::istream& stream) {
|
||||
// Read shape information
|
||||
uint32_t ndim;
|
||||
stream.read(reinterpret_cast<char*>(&ndim), sizeof(ndim));
|
||||
|
||||
std::vector<size_t> new_shape(ndim);
|
||||
for (uint32_t i = 0; i < ndim; ++i) {
|
||||
uint32_t dim;
|
||||
stream.read(reinterpret_cast<char*>(&dim), sizeof(dim));
|
||||
new_shape[i] = static_cast<size_t>(dim);
|
||||
}
|
||||
|
||||
// Resize tensor
|
||||
shape_ = new_shape;
|
||||
if (ndim == 1) {
|
||||
data_ = Eigen::VectorXf::Zero(shape_[0]);
|
||||
} else if (ndim == 2) {
|
||||
data_ = Eigen::MatrixXf::Zero(shape_[0], shape_[1]);
|
||||
} else {
|
||||
size_t total_size = 1;
|
||||
for (auto dim : shape_) total_size *= dim;
|
||||
data_ = Eigen::VectorXf::Zero(total_size);
|
||||
}
|
||||
|
||||
// Read data
|
||||
size_t num_elements = data_.size();
|
||||
stream.read(reinterpret_cast<char*>(data_.data()),
|
||||
num_elements * sizeof(float));
|
||||
|
||||
// Initialize grad if needed
|
||||
if (requires_grad_) {
|
||||
grad_ = Eigen::MatrixXf::Zero(data_.rows(), data_.cols());
|
||||
}
|
||||
}
|
||||
|
||||
static void write_string(std::ostream& stream, const std::string& str) {
|
||||
uint32_t length = static_cast<uint32_t>(str.size());
|
||||
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
|
||||
stream.write(str.c_str(), length);
|
||||
}
|
||||
|
||||
static std::string read_string(std::istream& stream) {
|
||||
uint32_t length;
|
||||
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||
|
||||
std::string str(length, '\0');
|
||||
stream.read(&str[0], length);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private:
|
||||
Eigen::MatrixXf data_;
|
||||
mutable Eigen::MatrixXf grad_;
|
||||
std::vector<size_t> shape_;
|
||||
bool requires_grad_;
|
||||
std::function<void()> backward_fn_;
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "lm/core/tensor.hpp"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class MultiHeadAttention {
|
||||
public:
|
||||
MultiHeadAttention(size_t d_model, size_t num_heads, float dropout = 0.1f);
|
||||
|
||||
std::vector<Tensor> parameters() const;
|
||||
void set_training(bool training);
|
||||
Tensor forward(const Tensor& query, const Tensor& key, const Tensor& value,
|
||||
const Tensor& mask = Tensor()) const;
|
||||
|
||||
private:
|
||||
Tensor split_heads(const Tensor& x) const;
|
||||
Tensor combine_heads(const Tensor& x) const;
|
||||
Tensor scaled_dot_product_attention(const Tensor& q, const Tensor& k,
|
||||
const Tensor& v, const Tensor& mask) const;
|
||||
Tensor apply_dropout(const Tensor& input, float dropout_rate) const;
|
||||
|
||||
size_t d_model_;
|
||||
size_t num_heads_;
|
||||
size_t d_k_;
|
||||
float dropout_;
|
||||
bool training_ = false;
|
||||
|
||||
Tensor w_q_;
|
||||
Tensor w_k_;
|
||||
Tensor w_v_;
|
||||
Tensor w_o_;
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "lm/core/tensor.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class FeedForward {
|
||||
public:
|
||||
FeedForward(size_t d_model, size_t d_ff, float dropout = 0.1f);
|
||||
|
||||
std::vector<Tensor> parameters() const;
|
||||
void set_training(bool training);
|
||||
Tensor forward(const Tensor& input) const;
|
||||
|
||||
private:
|
||||
Tensor apply_dropout(const Tensor& input, float dropout_rate) const;
|
||||
Tensor gelu(const Tensor& input) const;
|
||||
|
||||
size_t d_model_;
|
||||
size_t d_ff_;
|
||||
float dropout_;
|
||||
bool training_ = false;
|
||||
|
||||
Tensor w1_;
|
||||
Tensor b1_;
|
||||
Tensor w2_;
|
||||
Tensor b2_;
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// lm/models/language_model.hpp
|
||||
#pragma once
|
||||
|
||||
#include "../core/tensor.hpp"
|
||||
#include "../tokenizer/bpe_tokenizer.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class LanguageModel {
|
||||
public:
|
||||
LanguageModel(size_t vocab_size, size_t embedding_dim, size_t hidden_dim, size_t num_layers);
|
||||
|
||||
Tensor forward(const Tensor& input);
|
||||
Tensor operator()(const Tensor& input) { return forward(input); }
|
||||
|
||||
void save(const std::string& path) const;
|
||||
void load(const std::string& path);
|
||||
|
||||
// Parameter access methods
|
||||
std::vector<Tensor> parameters() const;
|
||||
std::unordered_map<std::string, Tensor> named_parameters() const;
|
||||
void set_parameter(const std::string& name, const Tensor& param);
|
||||
|
||||
void train();
|
||||
void eval();
|
||||
|
||||
private:
|
||||
size_t vocab_size_, embedding_dim_, hidden_dim_, num_layers_;
|
||||
|
||||
// Model parameters
|
||||
Tensor embedding_weight_;
|
||||
Tensor lstm_weight_ih_;
|
||||
Tensor lstm_weight_hh_;
|
||||
Tensor lstm_bias_ih_;
|
||||
Tensor lstm_bias_hh_;
|
||||
Tensor output_weight_;
|
||||
Tensor output_bias_;
|
||||
|
||||
bool is_training_;
|
||||
std::unordered_map<std::string, Tensor> parameters_;
|
||||
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "lm/core/tensor.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class LayerNorm {
|
||||
public:
|
||||
LayerNorm(size_t d_model, float eps = 1e-5f);
|
||||
|
||||
std::vector<Tensor> parameters() const;
|
||||
void set_training(/*bool training*/);
|
||||
Tensor forward(const Tensor& input) const;
|
||||
|
||||
private:
|
||||
size_t d_model_;
|
||||
float eps_;
|
||||
|
||||
Tensor gamma_;
|
||||
Tensor beta_;
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "lm/core/tensor.hpp"
|
||||
#include "lm/models/transformer_block.hpp"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class Transformer {
|
||||
public:
|
||||
Transformer(size_t vocab_size, size_t d_model, size_t num_heads,
|
||||
size_t d_ff, size_t num_layers, size_t max_seq_len, float dropout = 0.1f);
|
||||
|
||||
std::vector<Tensor> parameters() const;
|
||||
void set_training(bool training);
|
||||
Tensor forward(const Tensor& input, const Tensor& mask);
|
||||
Tensor forward(const Tensor& input);
|
||||
|
||||
private:
|
||||
Tensor apply_dropout(const Tensor& input, float dropout_rate);
|
||||
|
||||
size_t vocab_size_, d_model_, num_heads_, d_ff_, num_layers_, max_seq_len_;
|
||||
float dropout_;
|
||||
bool training_ = false;
|
||||
|
||||
Tensor embedding_;
|
||||
Tensor positional_encoding_;
|
||||
Tensor output_layer_;
|
||||
std::vector<std::unique_ptr<TransformerBlock>> transformer_blocks_;
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "lm/core/tensor.hpp"
|
||||
#include "lm/models/attention.hpp"
|
||||
#include "lm/models/feed_forward.hpp"
|
||||
#include "lm/models/layer_norm.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class TransformerBlock {
|
||||
public:
|
||||
TransformerBlock(size_t d_model, size_t num_heads, size_t d_ff, float dropout);
|
||||
|
||||
std::vector<Tensor> parameters() const;
|
||||
void set_training(bool training);
|
||||
Tensor forward(const Tensor& input, const Tensor& mask = Tensor()) const;
|
||||
|
||||
private:
|
||||
size_t d_model_, num_heads_, d_ff_;
|
||||
float dropout_;
|
||||
bool training_ = false;
|
||||
|
||||
std::unique_ptr<MultiHeadAttention> attention_;
|
||||
std::unique_ptr<FeedForward> feed_forward_;
|
||||
std::unique_ptr<LayerNorm> norm1_;
|
||||
std::unique_ptr<LayerNorm> norm2_;
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "../core/tensor.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class AdamOptimizer {
|
||||
public:
|
||||
AdamOptimizer(float learning_rate = 0.001, float beta1 = 0.9, float beta2 = 0.999, float epsilon = 1e-8);
|
||||
void step(std::vector<Tensor>& parameters); // Remove const
|
||||
void zero_grad(std::vector<Tensor>& parameters); // Remove const
|
||||
|
||||
private:
|
||||
float learning_rate_, beta1_, beta2_, epsilon_;
|
||||
int timestep_;
|
||||
std::vector<Tensor> m_, v_; // First and second moment estimates
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cstdint> // For uint16_t
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace lm {
|
||||
|
||||
@@ -44,6 +45,10 @@ public:
|
||||
void set_normalization(bool enabled);
|
||||
void set_byte_fallback(bool enabled);
|
||||
|
||||
Eigen::VectorXi encode_to_vector(const std::string& text) const;
|
||||
std::string decode_from_vector(const Eigen::VectorXi& tokens) const;
|
||||
Eigen::VectorXf token_frequencies() const;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> pimpl_;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "lm/models/language_model.hpp"
|
||||
#include "lm/tokenizer/bpe_tokenizer.hpp"
|
||||
#include "lm/optimizers/adam.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace lm {
|
||||
|
||||
class LanguageModelTrainer {
|
||||
public:
|
||||
// Change to accept a reference
|
||||
LanguageModelTrainer(const BPETokenizer& tokenizer,
|
||||
size_t embedding_dim,
|
||||
size_t hidden_dim,
|
||||
size_t num_layers);
|
||||
|
||||
void train(const std::vector<std::string>& corpus,
|
||||
size_t epochs,
|
||||
size_t batch_size,
|
||||
size_t sequence_length);
|
||||
|
||||
Tensor prepare_batch(const std::vector<std::string>& texts,
|
||||
size_t sequence_length);
|
||||
|
||||
float compute_loss(const Tensor& logits, const Tensor& targets);
|
||||
|
||||
void save_model(const std::string& path);
|
||||
void load_model(const std::string& path);
|
||||
|
||||
private:
|
||||
const BPETokenizer& tokenizer_; // Store a reference instead of a copy
|
||||
LanguageModel model_;
|
||||
AdamOptimizer optimizer_;
|
||||
};
|
||||
|
||||
} // namespace lm
|
||||
Reference in new issue
Block a user