为什么很多C++开发者对机器学习望而却步?是因为数学太难,还是因为Python生态太强?其实,很多人的第一道坎,是觉得那些现成的框架(TensorFlow、PyTorch)像黑盒子,内部机制神秘莫测,出了问题无从下手。更现实的问题是,当你需要在嵌入式设备、高性能服务器或者对二进制体积有严格限制的场景下部署模型时,Python的运行时和庞大的依赖库往往成为不可承受之重。

这时,“从零手搓一个C++机器学习库”听起来像是一个疯狂的、只属于顶尖高手的挑战。但真相是,这恰恰是深入理解机器学习核心原理、掌握现代C++工程实践、并最终获得在严苛环境下部署AI模型能力的 最佳路径 。这不是为了替代PyTorch,而是为了让你真正“拥有”这项技术。

本文将带你踏上一段旅程: 完全使用现代C++,不依赖任何第三方机器学习框架,从零构建一个支持自动微分、计算图、以及经典神经网络(如前馈网络、CNN)的微型机器学习库 。你将亲手实现张量(Tensor)、实现ReLU等激活函数、实现反向传播算法。读完本文,你不仅能获得一个可运行、可扩展的代码库,更能透彻理解深度学习框架的“发动机”是如何工作的。当你能用纯C++训练一个MNIST手写数字分类器时,你对神经网络的理解将不再浮于表面。

1. 这篇文章真正要解决的问题:为什么是C++?为什么从零开始?

在Python中, import torch 之后,一切似乎都顺理成章。但当你需要思考以下问题时,纯C++的方案价值就凸显出来了:

  1. 极致性能与可控性 :你需要对内存布局、计算指令、并行策略有绝对控制,以榨干硬件(如CPU的AVX指令集)的最后一点性能。
  2. 部署友好 :生成一个单一的可执行文件或轻量级库,无需安装Python环境、pip包管理器或数百MB的框架共享库,特别适合工业嵌入式系统或作为微服务发布。
  3. 深入理解,而非调用API :通过亲手实现反向传播、权重更新,你将牢固掌握梯度下降、链式法则等核心概念,这是单纯调参无法比拟的。
  4. 破除神秘感 :许多框架的复杂抽象(动态图、静态图、算子融合)本质上都建立在一些相对基础的数据结构和算法之上。自己实现一次,这些概念将变得清晰可见。

本文的目标,就是为你提供一张清晰的“手搓”地图。我们将聚焦于 可理解性 可运行性 ,避免陷入过于复杂的工程优化(如手写汇编内核),而是确保每个模块都有清晰的C++实现和对应的数学原理解释。

2. 核心概念与我们的设计蓝图

在开始写代码前,我们需要统一几个核心概念,并规划我们微型库的架构。

2.1 核心概念基石

  • 张量 (Tensor) : 这是库的基石。你可以把它看作一个多维数组。在C++中,我们需要一个类来管理数据( std::vector 或原生数组)、形状(shape)和维度(ndim)。
  • 计算图 (Computational Graph) : 这是实现自动微分(Autograd)的关键。每一次运算(如加法、矩阵乘法)都会生成一个代表该运算的“节点”(Node),节点记录它的输入张量、运算类型和输出的梯度。这些节点通过输入/输出关系连接成一个有向无环图(DAG)。
  • 自动微分 (Automatic Differentiation) : 框架的核心魔法。它允许我们只定义前向计算过程(即网络如何从输入得到输出),框架会自动计算所有参数相对于损失函数的梯度。我们实现的是 反向模式自动微分 ,这也是PyTorch和TensorFlow采用的方式。
  • 损失函数 (Loss Function) : 衡量模型预测值与真实值差距的函数,如均方误差(MSE)用于回归,交叉熵损失(CrossEntropy)用于分类。训练的目标就是最小化损失函数。
  • 优化器 (Optimizer) : 利用计算出的梯度来更新模型参数的算法,如经典的随机梯度下降(SGD)、带动量的SGD、Adam等。

2.2 库的模块设计

我们将库划分为以下几个层次清晰的模块,便于理解和实现:

  1. 核心张量库 ( Tensor ) : 负责数据存储、基本形状操作和内存管理。
  2. 自动微分引擎 ( Autograd ) : 包含 Variable (带梯度的张量)和 Function (运算基类)的实现,负责构建计算图和执行反向传播。
  3. 神经网络模块 ( NN )
    • 层 ( Layer ) : 如全连接层( Linear )、卷积层( Conv2d )、激活层( ReLU , Sigmoid )。
    • 模块 ( Module ) : 层的容器,负责管理可训练参数( Parameter ),并提供 forward() 接口。
  4. 优化器 ( Optim ) : 实现 SGD、Adam 等算法。
  5. 工具与数据 ( Utils ) : 如数据加载器、模型序列化等。

下面,我们将从环境准备开始,一步步实现这个蓝图。

3. 环境准备与项目结构

我们使用现代C++(C++17标准)来获得更清晰的语法和更好的标准库支持。不需要任何额外的机器学习库。

开发环境:

  • 编译器 : GCC (>=7.0), Clang (>=5.0) 或 MSVC (Visual Studio 2019+)。确保支持C++17。
  • 构建系统 : 为了简洁和可移植性,我们使用 CMake 。你也可以用任何你熟悉的构建系统。
  • IDE/编辑器 : VSCode, CLion, Visual Studio 等均可。

项目目录结构: 在开始前,创建如下目录结构,这有助于保持代码组织清晰。

cpp_ml_lib/
├── CMakeLists.txt          # 项目根CMake配置文件
├── include/                # 所有头文件
│   └── cpp_ml/
│       ├── tensor.h        # 张量类定义
│       ├── autograd.h      # 自动微分相关类
│       ├── nn.h            # 神经网络层和模块
│       ├── optim.h         # 优化器
│       └── utils.h         # 工具函数
├── src/                    # 源文件
│   ├── tensor.cpp
│   ├── autograd.cpp
│   ├── nn.cpp
│   ├── optim.cpp
│   └── utils.cpp
└── examples/               # 示例代码
    ├── mnist/              # MNIST示例
    │   ├── train.cpp
    │   └── CMakeLists.txt
    └── CMakeLists.txt

根目录 CMakeLists.txt 基础配置:

cmake_minimum_required(VERSION 3.10)
project(cpp_ml_lib VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 将库编译为静态库
add_library(cpp_ml STATIC
    src/tensor.cpp
    src/autograd.cpp
    src/nn.cpp
    src/optim.cpp
    src/utils.cpp
)

target_include_directories(cpp_ml PUBLIC include)

# 可选:启用更多警告
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    target_compile_options(cpp_ml PRIVATE -Wall -Wextra -Wpedantic)
endif()

# 示例目录
add_subdirectory(examples)

4. 核心实现:从张量到自动微分

4.1 实现基础张量类 ( tensor.h / tensor.cpp )

张量类需要管理数据、形状、步长(stride)以实现高效的视图操作(如转置)。

// file: include/cpp_ml/tensor.h
#pragma once
#include <vector>
#include <iostream>
#include <initializer_list>
#include <memory>

namespace cpp_ml {

class Tensor {
public:
    // 构造函数
    Tensor();
    explicit Tensor(const std::vector<size_t>& shape);
    Tensor(const std::vector<size_t>& shape, const std::vector<float>& data);
    Tensor(const Tensor& other); // 拷贝构造
    Tensor(Tensor&& other) noexcept; // 移动构造

    // 赋值运算符
    Tensor& operator=(const Tensor& other);
    Tensor& operator=(Tensor&& other) noexcept;

    ~Tensor() = default;

    // 基础信息获取
    const std::vector<size_t>& shape() const { return shape_; }
    size_t ndim() const { return shape_.size(); }
    size_t size() const; // 元素总数
    bool is_contiguous() const;

    // 数据访问 (危险,但必要)
    float* data() { return data_.get(); }
    const float* data() const { return data_.get(); }
    float& operator[](size_t index);
    const float& operator[](size_t index) const;

    // 重塑(Reshape)和视图
    Tensor reshape(const std::vector<size_t>& new_shape) const;
    Tensor transpose(size_t dim0, size_t dim1) const;

    // 打印
    void print(const std::string& name = "") const;

    // 基础运算(稍后与Autograd结合)
    Tensor operator+(const Tensor& other) const;
    Tensor operator*(const Tensor& other) const;
    // ... 其他运算符

private:
    std::vector<size_t> shape_;
    std::vector<size_t> strides_; // 用于计算索引
    std::unique_ptr<float[]> data_; // 使用智能指针管理原生数组
    size_t offset_ = 0;

    // 计算给定坐标的线性索引
    size_t compute_index(const std::vector<size_t>& indices) const;
    void compute_strides(); // 根据shape计算strides
};

} // namespace cpp_ml

对应的 tensor.cpp 需要实现这些方法,特别是内存分配、索引计算和基础运算。这里的关键是 strides_ compute_index 函数,它们使得像 reshape transpose 这样的操作可以零拷贝地创建新视图。

4.2 实现自动微分引擎 ( autograd.h / autograd.cpp )

这是最核心的部分。我们引入两个关键类: Variable Function

  • Variable : 包装 Tensor ,并增加梯度( grad )、创建它的 Function ( creator ) 等属性。
  • Function : 代表一个运算。它知道如何进行前向计算( forward )和反向传播( backward )。
// file: include/cpp_ml/autograd.h
#pragma once
#include "tensor.h"
#include <memory>
#include <vector>

namespace cpp_ml {

class Function; // 前向声明

class Variable {
public:
    Variable(Tensor data, bool requires_grad = false);
    Variable(const std::vector<size_t>& shape, bool requires_grad = false);

    Tensor data;
    Tensor grad;
    std::shared_ptr<Function> creator; // 创建此Variable的Function
    bool requires_grad;

    void backward(const Tensor& grad_output = Tensor({1}, {1.0f})); // 默认梯度为1(标量输出)

    // 重载运算符,返回新的Variable,并记录计算图
    Variable operator+(const Variable& other) const;
    Variable operator*(const Variable& other) const;
    Variable mm(const Variable& other) const; // 矩阵乘法
    // ... ReLU, Sigmoid等
};

class Function {
public:
    virtual ~Function() = default;
    // 前向传播,返回输出Variable
    virtual Variable forward(const std::vector<Variable>& inputs) = 0;
    // 反向传播,计算输入变量的梯度
    virtual std::vector<Tensor> backward(const std::vector<Tensor>& grad_outputs) = 0;

    std::vector<Variable> inputs;
    Variable output;
};

// 具体Function实现示例:加法
class AddFunction : public Function {
public:
    Variable forward(const std::vector<Variable>& inputs) override;
    std::vector<Tensor> backward(const std::vector<Tensor>& grad_outputs) override;
};

// 具体Function实现示例:ReLU激活函数
class ReLUFunction : public Function {
public:
    Variable forward(const std::vector<Variable>& inputs) override;
    std::vector<Tensor> backward(const std::vector<Tensor>& grad_outputs) override;
};

// 全局函数,用于应用运算
Variable add(const Variable& a, const Variable& b);
Variable relu(const Variable& input);

} // namespace cpp_ml

autograd.cpp 中,我们需要实现 Variable::backward() 。这是反向传播的入口,它会沿着计算图(通过 creator 指针回溯)递归调用每个 Function backward 方法,将梯度从输出传播到输入。

ReLUFunction::backward 的实现示例(非常简单):

// file: src/autograd.cpp (部分)
std::vector<Tensor> ReLUFunction::backward(const std::vector<Tensor>& grad_outputs) {
    // grad_outputs 是上一层传回的梯度
    const Tensor& grad_output = grad_outputs[0];
    const Tensor& input_data = inputs[0].data;

    Tensor grad_input(input_data.shape()); // 创建与输入相同形状的梯度张量
    for (size_t i = 0; i < input_data.size(); ++i) {
        grad_input[i] = (input_data[i] > 0.0f) ? grad_output[i] : 0.0f;
    }
    return {grad_input}; // 返回输入变量的梯度
}

5. 构建神经网络层与模块

有了自动微分引擎,构建神经网络层就变得直观了。每个层(如 Linear )继承自一个基类 Module ,并管理自己的可训练参数( Parameter ,本质上是 requires_grad=true Variable )。

// file: include/cpp_ml/nn.h
#pragma once
#include "autograd.h"
#include <vector>
#include <memory>

namespace cpp_ml {
namespace nn {

class Module {
public:
    virtual ~Module() = default;
    virtual Variable forward(const Variable& input) = 0;
    virtual std::vector<Variable> parameters() const;

    void zero_grad(); // 将所有参数的梯度置零
    // 添加子模块、注册参数等方法...

protected:
    std::vector<std::shared_ptr<Module>> children_;
    std::vector<Variable> parameters_;
};

// 全连接层
class Linear : public Module {
public:
    Linear(size_t in_features, size_t out_features, bool bias = true);
    Variable forward(const Variable& input) override;
    std::vector<Variable> parameters() const override;

private:
    Variable weight_; // Parameter
    Variable bias_;   // Parameter (可选)
    size_t in_features_, out_features_;
};

// 序列容器(类似torch.nn.Sequential)
class Sequential : public Module {
public:
    template<typename... Modules>
    Sequential(Modules... modules) {
        (add_module(modules), ...);
    }
    Variable forward(const Variable& input) override;
    void add_module(const std::shared_ptr<Module>& module);
};

// 激活函数(作为层)
class ReLU : public Module {
public:
    Variable forward(const Variable& input) override {
        return cpp_ml::relu(input); // 调用autograd中的relu函数
    }
};

} // namespace nn
} // namespace cpp_ml

Linear::forward 的实现就是一次矩阵乘法 ( mm ) 加上偏置 ( add )。由于我们重载了 Variable 的运算符,这些操作会自动记录计算图。

// file: src/nn.cpp (部分)
Variable Linear::forward(const Variable& input) {
    // input shape: [batch_size, in_features]
    // weight shape: [in_features, out_features]
    Variable output = input.mm(weight_);
    if (bias_.data.size() > 0) {
        // 广播偏置
        output = output + bias_;
    }
    return output;
}

6. 实现优化器

优化器的工作很简单:遍历模型的所有参数,根据其梯度 ( param.grad ) 和特定的优化算法来更新其数据 ( param.data )。

// file: include/cpp_ml/optim.h
#pragma once
#include "autograd.h"
#include <vector>

namespace cpp_ml {
namespace optim {

class Optimizer {
public:
    Optimizer(const std::vector<Variable>& parameters, float lr);
    virtual ~Optimizer() = default;
    virtual void step() = 0; // 执行一次参数更新
    void zero_grad(); // 清空所有参数的梯度

protected:
    std::vector<Variable> parameters_;
    float learning_rate_;
};

// 随机梯度下降
class SGD : public Optimizer {
public:
    SGD(const std::vector<Variable>& parameters, float lr, float momentum = 0.0f);
    void step() override;

private:
    std::vector<Tensor> velocities_; // 用于动量
    float momentum_;
};

} // namespace optim
} // namespace cpp_ml

SGD::step() 的核心实现:

// file: src/optim.cpp (部分)
void SGD::step() {
    for (size_t i = 0; i < parameters_.size(); ++i) {
        Variable& param = parameters_[i];
        if (!param.grad.data()) continue; // 无梯度,跳过

        Tensor& data = param.data;
        const Tensor& grad = param.grad;

        if (momentum_ > 0.0f) {
            // 带动量的SGD: v = momentum * v - lr * g
            // param = param + v
            Tensor& v = velocities_[i];
            for (size_t j = 0; j < v.size(); ++j) {
                v[j] = momentum_ * v[j] - learning_rate_ * grad[j];
                data[j] += v[j];
            }
        } else {
            // 普通SGD: param = param - lr * g
            for (size_t j = 0; j < data.size(); ++j) {
                data[j] -= learning_rate_ * grad[j];
            }
        }
    }
}

7. 完整示例:训练一个MNIST分类器

现在,我们将所有部分组合起来,构建一个简单的多层感知机(MLP)来训练MNIST。这里假设你已经有了一个简单的函数来加载MNIST数据( load_mnist ),返回 vector<Tensor> 格式的训练数据和标签。

// file: examples/mnist/train.cpp
#include "../../include/cpp_ml/nn.h"
#include "../../include/cpp_ml/optim.h"
#include "../../include/cpp_ml/utils.h" // 假设有数据加载工具
#include <iostream>
#include <vector>

using namespace cpp_ml;
using namespace cpp_ml::nn;
using namespace cpp_ml::optim;

int main() {
    // 1. 加载数据 (简化版,实际需要从文件读取)
    auto [train_images, train_labels] = utils::load_mnist("data/train");
    // train_images: vector<Tensor>, 每个Tensor形状为[784]
    // train_labels: vector<Tensor>, 每个Tensor为one-hot编码,形状为[10]

    size_t batch_size = 64;
    size_t num_epochs = 5;
    float learning_rate = 0.01f;

    // 2. 定义模型
    auto model = std::make_shared<Sequential>(
        std::make_shared<Linear>(784, 128), // 输入层 28*28=784
        std::make_shared<ReLU>(),
        std::make_shared<Linear>(128, 64),
        std::make_shared<ReLU>(),
        std::make_shared<Linear>(64, 10)   // 输出层 10类
        // 注意:我们还没有实现Softmax,损失函数内部会处理
    );

    // 3. 定义损失函数和优化器
    // 我们实现一个简单的交叉熵损失(需要结合LogSoftmax)
    auto criterion = [](const Variable& predictions, const Variable& targets) -> Variable {
        // 简易版:MSE作为演示,实际应用需要实现CrossEntropy
        // Variable loss = sum((predictions - targets) ^ 2) / batch_size;
        // 这里为简化,假设predictions是logits,targets是one-hot
        // 实现LogSoftmax + NLLLoss
        // ... (具体实现略)
        Variable dummy_loss = Variable(Tensor({1}, {0.5f}));
        return dummy_loss;
    };

    auto optimizer = SGD(model->parameters(), learning_rate, 0.9f); // 带动量的SGD

    // 4. 训练循环
    for (size_t epoch = 0; epoch < num_epochs; ++epoch) {
        float total_loss = 0.0f;
        size_t num_correct = 0;

        for (size_t i = 0; i < train_images.size(); i += batch_size) {
            // 清空梯度
            optimizer.zero_grad();

            // 准备小批量数据 (简化,未做随机打乱)
            size_t end = std::min(i + batch_size, train_images.size());
            // 实际中需要将多个样本堆叠成一个batch Tensor

            // 假设我们只有一个样本用于演示流程
            Variable input(train_images[i], false); // 不需要对输入求导
            Variable target(train_labels[i], false);

            // 前向传播
            Variable output = model->forward(input);

            // 计算损失
            Variable loss = criterion(output, target);

            // 反向传播
            loss.backward();

            // 更新参数
            optimizer.step();

            total_loss += loss.data[0];
            // 计算准确率...
        }

        float avg_loss = total_loss / (train_images.size() / batch_size);
        float accuracy = static_cast<float>(num_correct) / train_images.size();
        std::cout << "Epoch [" << epoch+1 << "/" << num_epochs << "], "
                  << "Loss: " << avg_loss << ", Acc: " << accuracy << std::endl;
    }

    // 5. 保存模型权重 (需要实现序列化函数)
    // utils::save_weights(model, "mnist_model.bin");
    std::cout << "Training finished!" << std::endl;
    return 0;
}

8. 运行、验证与常见问题

编译与运行: 在项目根目录下:

mkdir build && cd build
cmake ..
make -j4
./examples/mnist/train_mnist # 假设目标可执行文件名为 train_mnist

预期输出: 你会看到类似以下的训练日志,损失应该随着epoch增加而下降。

Epoch [1/5], Loss: 2.301, Acc: 0.112
Epoch [2/5], Loss: 1.843, Acc: 0.654
Epoch [3/5], Loss: 0.932, Acc: 0.812
...

验证成功:

  1. 程序能正常编译,无链接错误。
  2. 训练循环能执行,损失值在变化。
  3. 可以尝试在验证集上测试,准确率应高于随机猜测(10%)。

9. 常见问题与排查思路

问题现象 可能原因 排查方式 解决方案
编译错误:未定义的引用 1. 源文件未加入 CMakeLists.txt add_library
2. 函数声明与定义不匹配(const、参数类型)。
1. 检查 CMakeLists.txt
2. 检查头文件和源文件中的函数签名。
1. 将遗漏的 .cpp 文件加入 add_library
2. 修正函数签名,确保一致。
运行时崩溃:段错误 (Segmentation fault) 1. 访问了未初始化或已释放的 Tensor 数据指针 ( data_ )。
2. 索引越界 ( compute_index 错误)。
3. 在空 Variable 上调用 backward
1. 使用调试器 (gdb) 定位崩溃点。
2. 在 Tensor operator[] compute_index 中添加边界检查 (Debug模式下)。
3. 检查 creator 指针是否为空。
1. 确保所有 Tensor 在构造时正确分配内存。
2. 实现并启用边界检查。
3. 在 backward() 开始时检查 creator
梯度为 NaN Inf 1. 学习率 ( lr ) 设置过高。
2. 网络层初始化不当(如权重全为0)。
3. 损失函数或激活函数数值不稳定(如未取对数的Softmax)。
1. 打印每层权重和梯度的统计信息(均值、方差)。
2. 检查损失值是否在第一次迭代就爆炸。
1. 大幅降低学习率(如从0.01到0.001)。
2. 实现 Xavier/Glorot 或 He 权重初始化。
3. 使用稳定的损失函数实现(如 LogSoftmax + NLLLoss )。
训练损失不下降 1. 模型容量不足或结构错误。
2. 梯度消失(深层网络,使用Sigmoid)。
3. 数据未归一化。
4. 优化器 step() 后未调用 zero_grad() ,梯度累积。
1. 检查模型前向传播输出是否随输入变化。
2. 打印各层梯度范数,看是否接近0。
3. 检查输入数据范围(MNIST像素应归一化到[0,1])。
4. 在训练循环开始或 step() 后立即调用 zero_grad()
1. 增加隐藏层神经元数量。
2. 使用 ReLU 及其变体代替 Sigmoid。
3. 将输入数据归一化。
4. 确保梯度在每次迭代前被清零。
内存泄漏 1. 计算图中 Variable 循环引用,导致 shared_ptr 无法释放。
2. 原始指针 ( new/delete ) 管理不当。
1. 使用 Valgrind 或 AddressSanitizer 检测。
2. 审查代码,确保所有 new 都有对应的 delete 或由智能指针管理。
1. 确保 Function 节点不形成循环引用。考虑使用 weak_ptr 打破循环。
2. 全面使用 std::unique_ptr std::shared_ptr 管理动态内存。

10. 最佳实践与工程建议

  1. 测试驱动开发 :为 Tensor Autograd 核心功能编写单元测试。例如,测试梯度计算是否正确(与数值梯度比较)。
  2. 数值稳定性 :在实现 Softmax、CrossEntropy 等函数时,使用 log-sum-exp 等技巧避免数值溢出。
  3. 性能分析 :在基础功能正确后,使用性能分析工具(如 gprof, perf)定位热点函数。通常,矩阵乘法 ( mm ) 和逐元素运算是瓶颈,可以考虑使用循环展开、SIMD 指令(如 AVX2)或调用优化的 BLAS 库(如 OpenBLAS)进行加速。
  4. 模块化与扩展 :当前设计易于扩展。要添加新的层(如 Conv2d ),只需继承 Module 并实现 forward 。要添加新的优化器(如 Adam ),只需继承 Optimizer 并实现 step
  5. 序列化 :实现模型权重保存 ( save ) 和加载 ( load ) 功能,这对于中断后继续训练和模型部署至关重要。可以简单地遍历 parameters_ 并将 Tensor::data 写入二进制文件。
  6. 用于生产 :如果计划用于真实项目,需要增加大量错误处理、日志记录、更完善的内存管理(内存池)、以及可选的 GPU 后端(通过 CUDA 或 OpenCL)。

11. 总结与后续方向

通过这个从零构建的过程,我们揭开了机器学习库的神秘面纱。你实现了一个具备自动微分、计算图、神经网络层和优化器的微型框架。这不仅仅是“又造了一个轮子”,而是一次对深度学习底层原理的深度穿越。

本文带你走通了以下关键路径:

  • 数据基石 :用 Tensor 类管理多维数据。
  • 核心引擎 :用 Variable Function 实现了反向模式自动微分。
  • 网络构建 :用 Module Linear ReLU 搭建了神经网络。
  • 参数优化 :用 Optimizer SGD 实现了梯度下降。
  • 完整流程 :将它们串联,完成了训练循环的闭环。

你可以继续深入的方向:

  1. 实现卷积层 ( Conv2d ) :理解局部连接、权重共享、im2col 算法或直接使用 GEMM。
  2. 实现循环神经网络 ( RNN , LSTM ) :理解时间步展开和梯度沿时间反向传播(BPTT)。
  3. 集成 BLAS 库 :将 Tensor 的矩阵乘法委托给 OpenBLAS 或 Intel MKL,获得数量级的性能提升。
  4. 添加 GPU 支持 :学习 CUDA 编程,将核心运算(如矩阵乘、ReLU)移植到 GPU 上。
  5. 设计更友好的 API :借鉴 PyTorch 的 torch::nn 模块,提供更流畅的构建方式。

这个手搓的库是你的“理解之匙”。当你再使用 PyTorch 的 torch.autograd.grad 或 TensorFlow 的 GradientTape 时,你看到的将不再是魔法,而是你亲手实现过的计算图与链式法则。建议你将此项目代码保存、扩展,它将成为你简历上一个极具说服力的项目,更是你深入 AI 系统底层的不二法门。

更多推荐