项目背景
今年实习过程中,我从参与了一个简化版AI编译器的开发来学习有关AI的内容,这个项目旨在帮助初学者理解深度学习系统的核心组件。作为一个C++和深度学习爱好者,这次实践让我对AI编译器底层原理有了深刻认识。本文将分享我在实现八个核心组件过程中的思考与收获。
作业一:内存分配器实现
内存分配是编译器最基础的组件之一。我实现了一个基于空闲链表的高效内存分配器。
// include/core/allocator.h
std::map<size_t, size_t> free_blocks; // key:起始地址, value:块大小
// src/core/allocator.cc
size_t Allocator::alloc(size_t size) {
// 寻找第一个足够大的空闲块
for (auto it = free_blocks.begin(); it != free_blocks.end(); ++it) {
if (it->second >= size) {
size_t addr = it->first;
// 更新空闲块信息
if (it->second > size) {
free_blocks[addr + size] = it->second - size;
}
free_blocks.erase(it);
return addr;
}
}
throw std::bad_alloc();
}
void Allocator::free(size_t addr, size_t size) {
// 合并相邻空闲块
auto next = free_blocks.lower_bound(addr);
auto prev = next;
if (prev != free_blocks.begin()) --prev;
// 与前一块合并
if (prev != free_blocks.end() && prev->first + prev->second == addr) {
prev->second += size;
addr = prev->first;
size = prev->second;
free_blocks.erase(prev);
}
// 与后一块合并
if (next != free_blocks.end() && addr + size == next->first) {
size += next->second;
free_blocks.erase(next);
}
free_blocks[addr] = size;
}
作业二:Transpose算子实现
转置算子的形状推导相对简单,但需要考虑不同维度的排列组合。
optional<vector<Shape>> TransposeObj::inferShape(const TensorVec &inputs) {
const auto A = inputs[0];
auto input_dim = A->getDims();
auto output_dim = input_dim;
int rank = A->getRank();
// 默认反向转置 (0,1,2,...,rank-1) → (rank-1,...,2,1,0)
for (int i = 0; i < rank; ++i) {
output_dim[i] = input_dim[rank - 1 - i];
}
return {{output_dim}};
}
作业三:Clip算子实现
Clip算子的形状推导最为简单,输出形状与输入相同。
optional<vector<Shape>> ClipObj::inferShape(const TensorVec &inputs) {
return {{inputs[0]->getDims()}}; // 输出形状与输入相同
}
作业四:Cast算子实现
Cast算子需要处理数据类型转换和形状推导。
vector<DataType> CastObj::inferDataType(const TensorVec &inputs) const {
return {target_type}; // 返回目标数据类型
}
optional<vector<Shape>> CastObj::inferShape(const TensorVec &inputs) {
return {{inputs[0]->getDims()}}; // 形状不变
}
作业五:Concat算子实现
Concat算子需要沿指定轴连接多个张量。
optional<vector<Shape>> ConcatObj::inferShape(const TensorVec &inputs) {
Shape dims = inputs[0]->getDims();
auto rank = inputs[0]->getRank();
// 沿axis维度相加
for (size_t i = 1; i < inputs.size(); ++i) {
dims[axis] += inputs[i]->getDims()[axis];
}
return {{dims}};
}
作业六:双向广播实现
广播机制是深度学习中的核心概念之一。
Shape infer_broadcast(const Shape &A, const Shape &B) {
Shape result;
size_t max_len = std::max(A.size(), B.size());
// 从右向左逐维比较
for (size_t i = 0; i < max_len; ++i) {
size_t a_dim = i < A.size() ? A[A.size()-1-i] : 1;
size_t b_dim = i < B.size() ? B[B.size()-1-i] : 1;
if (a_dim == b_dim || a_dim == 1 || b_dim == 1) {
result.insert(result.begin(), std::max(a_dim, b_dim));
} else {
throw std::runtime_error("Incompatible shapes for broadcasting");
}
}
return result;
}
作业七:矩阵乘法实现
矩阵乘法是深度学习中最常用的运算之一。
optional<vector<Shape>> MatmulObj::inferShape(const TensorVec &inputs) {
const auto &A = inputs[0]->getDims();
const auto &B = inputs[1]->getDims();
// 处理二维矩阵乘法
if (A.size() == 2 && B.size() == 2) {
if (A[1] != B[0]) throw std::runtime_error("Matrix dimensions mismatch");
return {{Shape{A[0], B[1]}}};
}
// 处理高维张量的批量矩阵乘法
Shape result;
size_t batch_dims = std::max(A.size(), B.size()) - 2;
for (size_t i = 0; i < batch_dims; ++i) {
size_t a_dim = i < A.size()-2 ? A[i] : 1;
size_t b_dim = i < B.size()-2 ? B[i] : 1;
result.push_back(std::max(a_dim, b_dim));
}
result.push_back(A[A.size()-2]);
result.push_back(B[B.size()-1]);
return {{result}};
}
作业八:图优化实现
图优化是编译器性能提升的关键。
void GraphObj::optimize() {
// 1. 去除冗余Transpose算子
for (auto it = nodes.begin(); it != nodes.end(); ) {
if (auto transpose = dynamic_cast<TransposeObj*>(it->get())) {
// 检查相邻Transpose是否可抵消
if (auto next_transpose = dynamic_cast<TransposeObj*>(it->get()->outputs[0]->consumer)) {
if (canCancelTranspose(transpose, next_transpose)) {
// 连接上游到下游
it->get()->inputs[0]->consumer = next_transpose->outputs[0]->consumer;
it = nodes.erase(it);
continue;
}
}
}
// 2. 将Transpose融合到Matmul中
if (auto matmul = dynamic_cast<MatmulObj*>(it->get())) {
if (auto transpose = dynamic_cast<TransposeObj*>(matmul->inputs[0]->producer)) {
if (isLastTwoDimsTranspose(transpose)) {
matmul->transA = !matmul->transA;
matmul->inputs[0] = transpose->inputs[0];
nodes.erase(std::find(nodes.begin(), nodes.end(), transpose));
continue;
}
}
// 对B输入做同样处理...
}
++it;
}
}
开发心得
-
内存管理至关重要:内存分配器的实现让我理解了深度学习框架如何高效管理内存,特别是内存碎片问题。
-
形状推导是基础:每个算子都需要精确的形状推导,这对后续内存分配和优化至关重要。
-
图优化提升性能:通过消除冗余操作和融合算子,可以显著提升计算效率。
-
测试驱动开发:每个组件都对应一个测试用例,确保功能的正确性。
这个简化版AI编译器项目让我深入理解了深度学习系统的底层原理,为后续AI项目的实现打下了坚实基础。