项目地址
https://github.com/freelw/cpp-transformer
C++ 实现的 Transformer
这是一个无需依赖特殊库的 Transformer 的 C++ 实现,涵盖了训练与推理功能。
本项目使用C++复刻了《Dive into Deep Learning》中关于 Transformer 的第 11 章11.7小节点内容。构建了一个英法机器翻译模型。本项目自主开发了自动求导框架,仅依赖 C++ 标准库,旨在助力用户理解 Transformer 的底层原理。
项目亮点
注重原理
从基础操作入手构建模型,不依赖深度学习框架。这种方式清晰地展示了 Transformer 的运行机制。
自动求导
自主研发的自动求导框架简化了梯度计算流程,有助于更好地理解反向传播算法。
低依赖性
该项目仅依赖 C++ 标准库。尽管其性能可能不如那些使用高级库的项目,但它清晰呈现了每一个计算细节。这一特性使用户能够深入理解反向传播算法以及 Transformer 架构的底层原理。
快速开始
构建
./build_all.sh
测试推理翻译
./test_translation.sh
输出
./test_translation.sh
~/project/cpp-transformer/checkpoints/save ~/project/cpp-transformer
~/project/cpp-transformer
OMP_THREADS: 8
epochs : 0
dropout : 0.2
lr : 0.001
tiny : 0
data loaded
warmUp done
parameter size = 21388
all parameters require_grad = true
loading from checkpoint : ./checkpoints/save/checkpoint_20250402_150847_40.bin
loaded from checkpoint
serving mode
go now . <eos>
translate res : <bos> allez-y maintenant maintenant maintenant . <eos>
i try . <eos>
translate res : <bos> j'essaye . <eos>
cheers ! <eos>
translate res : <bos> santé ! <eos>
get up . <eos>
translate res : <bos> lève-toi . <eos>
hug me . <eos>
translate res : <bos> <unk> dans vos bras ! <eos>
i know . <eos>
translate res : <bos> je sais . <eos>
no way ! <eos>
translate res : <bos> en aucune manière ! <eos>
be nice . <eos>
translate res : <bos> soyez gentille ! <eos>
i jumped . <eos>
translate res : <bos> j'ai sauté . <eos>
congratulations ! <eos>
translate res : <bos> à ! <eos>
测试训练
在tiny训练集上进行训练(300句英法对照语料)
./train_tiny.sh
输出
./train_tiny.sh
OMP_THREADS: 8
epochs : 10
dropout : 0.2
lr : 0.001
tiny : 0
data loaded
warmUp done
parameter size = 21388
all parameters require_grad = true
[300/300]checkpoint saved : ./checkpoints/checkpoint_20250402_164906_0.bin
epoch 0 loss : 9.0757 emit_clip : 3
[300/300]epoch 1 loss : 7.90043 emit_clip : 3
[300/300]epoch 2 loss : 6.8447 emit_clip : 3
[300/300]epoch 3 loss : 5.85042 emit_clip : 3
[300/300]epoch 4 loss : 5.00354 emit_clip : 3
[300/300]epoch 5 loss : 4.38405 emit_clip : 3
[300/300]epoch 6 loss : 3.96133 emit_clip : 3
[300/300]epoch 7 loss : 3.70218 emit_clip : 3
[300/300]epoch 8 loss : 3.51153 emit_clip : 3
[300/300]checkpoint saved : ./checkpoints/checkpoint_20250402_164906_9.bin
epoch 9 loss : 3.35273 emit_clip : 3
代码片段一览
前向
以PositionwiseFFN举例,我们只要声明前向过程即可,框架会自动生成计算图,在调用backward时自动求导
autograd::Node *PositionwiseFFN::forward(autograd::Node *x) {
return dense2->forward(dense1->forward(x)->Relu());
}
自动求导实现
以矩阵乘法为例,在node.h node.cpp中,乘法会生成一个结果节点,关联两条边到两个乘数。
Node *Node::operator*(Node *rhs) {
auto *node = allocNode(*w * *(rhs->w));
if (is_require_grad() || rhs->is_require_grad()) {
node->require_grad();
if (is_require_grad()) {
node->edges.push_back(MulEdge::create(this, rhs->get_weight()));
}
if (rhs->is_require_grad()) {
node->edges.push_back(MulEdge::create(rhs, w));
}
}
return node;
}
在边中实现梯度的反向传播,注意左边和右边的操作方式不同(是否需要专置)
class MatMulLEdge : public Edge {
public:
static Edge* create(Node *_node, Matrix *_param) {
Edge *edge = new MatMulLEdge(_node, _param);
edges.push_back(edge);
return edge;
}
MatMulLEdge(Node *_node, Matrix *_param)
: Edge(MatMulL, _node), param(_param) {}
virtual ~MatMulLEdge() {}
void backward(Matrix *grad) override {
assert(node->is_require_grad());
// *node->get_grad() is grad of W
*node->get_grad() += *(grad->at(*(param->transpose())));
}
private:
Matrix *param; // Input Vector
};
class MatMulREdge : public Edge {
public:
static Edge* create(Node *_node, Matrix *_param) {
Edge *edge = new MatMulREdge(_node, _param);
edges.push_back(edge);
return edge;
}
MatMulREdge(Node *_node, Matrix *_param)
: Edge(MatMulR, _node), param(_param) {}
virtual ~MatMulREdge() {}
void backward(Matrix *grad) override {
assert(node->is_require_grad());
// *node->get_grad() is grad of Input
*node->get_grad() += *(param->transpose()->at(*grad));
}
private:
Matrix *param; // W
};
训练
在main.cpp train函数中,逻辑和pytorch类似,都是要将模型的所有parameters引用/指针传递给优化器,然后依次清理grad,反向传播,裁剪梯度,执行权重调整
auto loss = dec_outputs->CrossEntropyMask(labels, mask);
assert(loss->get_weight()->getShape().rowCnt == 1);
assert(loss->get_weight()->getShape().colCnt == 1);
loss_sum += (*loss->get_weight())[0][0];
adam.zero_grad();
loss->backward();
if (adam.clip_grad(1)) {
emit_clip++;
}
adam.step();
反向传播梯度公式推导
主要三个比较复杂的层 softmax交叉熵 softmax layernorm
https://github.com/freelw/cpp-transformer/blob/main/doc/equations/readme.md