
InferLLM大模型推理框架项目(11)——Op类的实现(src/core/op.h+.cpp)
Op 类代码结构与功能实现分析
op.h
和 op.cpp
文件定义了 InferLLM 框架中的各种算子(Operator)类,这些算子是构建神经网络计算图的基本单元。下面对这些文件的代码结构和功能实现进行详细分析。
1. 基础定义
using OpIOs = std::vector<std::shared_ptr<Tensor>>;
constexpr static size_t PACK_SIZE = 8;
OpIOs
是一个类型别名,表示算子的输入、输出或权重,它是一个Tensor
指针的向量。PACK_SIZE
是一个常量,用于 Int4 量化权重的打包大小,设置为 8。
2. OpBase 类
OpBase
是所有算子的基类,定义了算子的基本接口和通用功能:
class OpBase {
public:
OpBase(Device* device, const std::string& name, OpIOs inputs)
: m_device(device), m_inputs(inputs), m_name(name) {
for (auto input : m_inputs) {
input->add_user();
}
}
virtual void pre_execute();
virtual void execute(WorkSpace* workspace, uint32_t nr_past) {}
virtual void end_execute();
virtual void deduce_output_shape();
virtual size_t get_workspace_in_byte() { return 0; }
virtual void load_weights(std::ifstream&){};
virtual uint32_t nr_weights() { return 1; };
virtual void init(OpIOs, OpIOs, WorkSpace*){};
// 其他方法...
};
2.1 OpBase 的主要方法
- 构造函数:初始化设备、输入和名称,并增加输入张量的用户计数。
- pre_execute:执行前准备,包括准备权重和输出张量的数据。
- execute:执行算子的计算,这是一个虚函数,由子类实现。
- end_execute:执行后清理,减少输入张量的当前用户计数。
- deduce_output_shape:推导输出张量的形状,默认与第一个输入相同。
- get_workspace_in_byte:获取执行时需要的工作空间大小。
- load_weights:从文件加载权重。
- init:初始化算子,设置输入、输出和工作空间。
2.2 OpBase 的辅助方法
- device:获取设备指针。
- get_kernel:获取内核指针。
- set_weights:设置权重张量。
- add_outputs:添加输出张量。
- set_name:设置算子名称。
- weights/inputs/outputs/name:获取权重、输入、输出和名称。
- need_preprocess_weight:判断是否需要预处理权重。
- preprocess_weight:预处理权重,使其更适合计算内核。
3. 具体算子类
3.1 LayerNorm 类
class LayerNorm : public OpBase {
public:
LayerNorm(
Device* device, const std::string& name, OpIOs inputs, size_t embd,
bool mul = true, bool bias = false, bool rms = true, float eps = 1e-5);
void execute(WorkSpace* workspace, uint32_t nr_past) override;
private:
bool m_mul;
bool m_bias;
bool m_rms;
float m_norm_eps = 1e-5;
};
LayerNorm
实现了层归一化操作,支持 RMS 归一化和普通归一化,可选是否使用乘法和偏置。
3.1.1 LayerNorm::execute 实现
void LayerNorm::execute(WorkSpace* workspace, uint32_t nr_past) {
std::shared_ptr<Tensor> weight = nullptr, bias = nullptr;
int weight_idx = 0;
if (m_mul) {
weight = weights()[weight_idx++];
DType weight_type = weight->dtype();
INFER_ASSERT(
weight_type == DType::Float32, "layer norm weights must be float32.");
}
if (m_bias) {
bias = weights()[weight_idx++];
}
auto input = inputs()[0];
auto output = outputs()[0];
uint32_t seq_len = input->shape()[0];
uint32_t embd = input->shape()[1];
auto kernel = get_kernel();
if (input->dtype() == DType::Float32) {
const float* src = input->ptr<float>();
float* dst = output->ptr<float>();
float *weight_ptr = nullptr, *bias_ptr = nullptr;
if (m_mul) {
weight_ptr = weight->ptr<float>();
}
if (m_bias) {
bias_ptr = bias->ptr<float>();
}
if (m_rms) {
kernel->operator()<KernelID::RmsNormFloat>(
src, dst, seq_len, embd, m_norm_eps);
} else {
kernel->operator()<KernelID::NormFloat>(
src, dst, seq_len, embd, m_norm_eps);
}
if (weight_ptr) {
kernel->operator()<KernelID::ElemwiseBroadcastDim0Src1Float>(
dst, weight_ptr, dst, seq_len, embd, ElemMode::Mul);
}
if (bias_ptr) {
kernel->operator()<KernelID::ElemwiseBroadcastDim0Src1Float>(
dst, bias_ptr, dst, seq_len, embd, ElemMode::Add);
}
}
}
这个方法执行层归一化操作:
- 获取权重和偏置(如果有)
- 获取输入和输出张量
- 根据
m_rms
标志选择 RMS 归一化或普通归一化 - 如果有权重,执行元素级乘法
- 如果有偏置,执行元素级加法
3.2 MatMul 类
class MatMul : public OpBase {
public:
MatMul(Device* device, const std::string& name, OpIOs inputs,
std::vector<size_t> shape, bool bias = false);
virtual void deduce_output_shape() override;
virtual bool need_preprocess_weight(Tensor* weight) override;
virtual std::vector<size_t> preprocess_weight(
Tensor* tensor, void* src, void* dst) override;
virtual void execute(WorkSpace* workspace, uint32_t nr_past) override;
size_t get_workspace_in_byte() override;
bool m_bias = false;
bool m_weight_packed = false;
};
MatMul
实现了矩阵乘法操作,支持不同数据类型(Float32、Int8、Int4)的权重,以及可选的偏置。
3.2.1 MatMul::execute 实现
void MatMul::execute(WorkSpace* workspace, uint32_t) {
auto N = weights()[0]->shape()[0];
auto K = inputs()[0]->shape()[1];
auto M = inputs()[0]->shape()[0];
auto src_dtype = inputs()[0]->dtype();
auto weight_dtype = weights()[0]->dtype();
void* p_workspace = workspace->ptr();
uint32_t p_workspace_size = workspace->length();
auto kernel = get_kernel();
if (src_dtype == DType::Float32) {
float* dst = outputs()[0]->ptr<float>();
const float* bias = nullptr;
if (m_bias) {
bias = weights()[1]->ptr<float>();
}
const float* src = inputs()[0]->ptr<float>();
switch (weight_dtype) {
case DType::Int4:
if (!m_weight_packed) {
kernel->operator()<KernelID::MatmulInt4Float>(
dst, weights()[0]->ptr(), bias, src, M, N, K, p_workspace,
p_workspace_size);
} else {
kernel->operator()<KernelID::MatmulInt4FloatPacked>(
dst, weights()[0]->ptr(), bias, src, M, N * PACK_SIZE, K,
p_workspace, p_workspace_size);
}
break;
case DType::Int8:
kernel->operator()<KernelID::MatmulInt8Float>(
dst, weights()[0]->ptr(), bias, src, M, N, K, p_workspace,
p_workspace_size);
break;
case DType::Float32:
kernel->operator()<KernelID::MatmulFloatFloat>(
dst, weights()[0]->ptr<float>(), bias, src, M, N, K,
p_workspace, p_workspace_size);
break;
default:
INFER_ASSERT(0, "not support");
}
}
}
这个方法执行矩阵乘法操作:
- 获取矩阵的维度(M、N、K)
- 获取输入和权重的数据类型
- 根据权重的数据类型选择不同的矩阵乘法内核
- 如果权重是 Int4 类型且已打包,使用优化的打包版本
3.3 MatMulLast 类
class MatMulLast : public MatMul {
public:
using MatMul::MatMul;
void deduce_output_shape() override;
void execute(WorkSpace* workspace, uint32_t nr_past) override;
virtual bool need_preprocess_weight(Tensor*) override { return false; }
size_t get_workspace_in_byte() override;
};
MatMulLast
是 MatMul
的子类,专门用于计算序列中最后一个 token 的矩阵乘法,这在生成阶段很常见。
3.3.1 MatMulLast::execute 实现
void MatMulLast::execute(WorkSpace* workspace, uint32_t) {
auto N = weights()[0]->shape()[0];
auto K = weights()[0]->shape()[1];
//! only compute the last token
auto M = 1;
auto row = inputs()[0]->shape()[0];
auto src_dtype = inputs()[0]->dtype();
auto weight_dtype = weights()[0]->dtype();
void* p_workspace = workspace->ptr();
uint32_t p_workspace_size = workspace->length();
auto kernel = get_kernel();
if (src_dtype == DType::Float32) {
float* dst = outputs()[0]->ptr<float>();
const float* bias = nullptr;
if (m_bias) {
bias = weights()[1]->ptr<float>();
}
const float* src = inputs()[0]->ptr<float>() + (row - 1) * K;
// 根据权重类型选择不同的矩阵乘法内核
// ...
}
}
与 MatMul::execute
类似,但只计算最后一个 token(src
指向最后一行)。
3.4 SoftMax 类
class SoftMax : public OpBase {
public:
SoftMax(Device* device, const std::string& name, OpIOs inputs);
void execute(WorkSpace* workspace, uint32_t nr_past) override;
};
SoftMax
实现了 softmax 操作,通常用于注意力机制中。
3.4.1 SoftMax::execute 实现
void SoftMax::execute(WorkSpace*, uint32_t) {
auto input = inputs()[0];
auto output = outputs()[0];
uint32_t seq_len = input->shape()[0];
uint32_t embd = input->shape()[1];
auto kernel = get_kernel();
if (output->dtype() == DType::Float32) {
float* src = input->ptr<float>();
float* dst = output->ptr<float>();
kernel->operator()<KernelID::SoftmaxFloat>(src, dst, seq_len, embd);
} else {
//! fp16
}
}
这个方法执行 softmax 操作,对每一行(通常是注意力分数)应用 softmax 函数。
3.5 Reshape 类
class Reshape : public OpBase {
public:
Reshape(Device* device, const std::string& name, OpIOs inputs,
std::vector<int> shape);
void deduce_output_shape() override;
private:
std::vector<int> m_target_shape;
};
Reshape
改变张量的形状,保持元素总数不变。
3.5.1 Reshape::deduce_output_shape 实现
void Reshape::deduce_output_shape() override {
size_t len = inputs()[0]->length();
std::vector<size_t> out_shape;
out_shape.resize(m_target_shape.size());
int count = 0;
for (size_t i = 0; i < m_target_shape.size(); i++) {
if (m_target_shape[i] != -1) {
out_shape[i] = m_target_shape[i];
INFER_ASSERT(len % m_target_shape[i] == 0, "Reshape error.\n");
len = len / m_target_shape[i];
} else {
count++;
}
}
INFER_ASSERT(count == 1, "multi -1 in Reshape param.\n");
for (size_t i = 0; i < m_target_shape.size(); i++) {
if (m_target_shape[i] == -1) {
out_shape[i] = len;
}
}
outputs()[0]->set_shape(out_shape, inputs()[0]->dtype());
}
这个方法推导输出张量的形状:
- 计算输入张量的元素总数
- 根据目标形状计算每个维度的大小
- 如果某个维度是 -1,则自动计算该维度的大小
3.6 Elemwise 类
class Elemwise : public OpBase {
public:
Elemwise(
Device* device, const std::string& name, OpIOs inputs, ElemMode mode,
float scale = -INFINITY);
void execute(WorkSpace* workspace, uint32_t nr_past) override;
private:
float m_scale;
ElemMode m_mode;
};
Elemwise
实现了元素级操作,如加法、乘法、GELU 激活等。
void Elemwise::execute(WorkSpace*, uint32_t) {
auto input = inputs()[0];
auto output = outputs()[0];
uint32_t seq_len = input->shape()[0];
uint32_t embd = input->shape()[1];
auto kernel = get_kernel();
if (input->dtype() == DType::Float32) {
float* src = input->ptr<float>();
float* dst = output->ptr<float>();
if (inputs().size() == 1) {
if (m_mode == ElemMode::Gelu) {
kernel->operator()<KernelID::GeluFloat>(src, dst, seq_len * embd);
} else if (m_mode == ElemMode::Silu) {
kernel->operator()<KernelID::SiluFloat>(src, dst, seq_len * embd);
} else if (m_mode == ElemMode::Scale) {
kernel->operator()<KernelID::ScaleFloat>(
src, dst, seq_len * embd, m_scale);
}
} else if (inputs().size() == 2) {
float* src1 = inputs()[1]->ptr<float>();
if (m_mode == ElemMode::Add) {
kernel->operator()<KernelID::ElemwiseFloat>(
src, src1, dst, seq_len * embd, ElemMode::Add);
} else if (m_mode == ElemMode::Mul) {
kernel->operator()<KernelID::ElemwiseFloat>(
src, src1, dst, seq_len * embd, ElemMode::Mul);
}
}
}
}
这个方法执行元素级操作:
- 获取输入和输出张量
- 根据操作模式(
m_mode
)选择不同的元素级操作内核 - 如果只有一个输入,执行单输入操作(如 GELU、Silu、Scale)
- 如果有两个输入,执行双输入操作(如 Add、Mul)
3.7 SpliteHalfActiveMul 类
SpliteHalfActiveMul
实现了将输入张量分成两半,对一半应用激活函数,然后与另一半相乘的操作。这在一些模型架构中很常见,如 SwiGLU。
void SpliteHalfActiveMul::execute(WorkSpace*, uint32_t) {
auto input = inputs()[0];
auto output = outputs()[0];
uint32_t seq_len = input->shape()[0];
uint32_t embd = input->shape()[1];
uint32_t half_embd = embd / 2;
auto kernel = get_kernel();
if (input->dtype() == DType::Float32) {
float* src = input->ptr<float>();
float* dst = output->ptr<float>();
if (m_mode == ElemMode::Silu) {
kernel->operator()<KernelID::SplitSiluMulFloat>(
src, dst, seq_len, half_embd);
} else if (m_mode == ElemMode::Gelu) {
kernel->operator()<KernelID::SplitGeluMulFloat>(
src, dst, seq_len, half_embd);
}
}
}
这个方法执行分半激活乘法操作:
- 获取输入和输出张量
- 计算每半部分的嵌入维度
- 根据激活模式选择不同的内核(SiluMul 或 GeluMul)
3.8 DiagMask 类
DiagMask
实现了对角线掩码操作,通常用于自回归模型中的注意力机制,防止模型看到未来的 token。
void DiagMask::execute(WorkSpace*, uint32_t nr_past) {
auto input = inputs()[0];
auto output = outputs()[0];
uint32_t seq_len = input->shape()[0];
uint32_t embd = input->shape()[1];
auto kernel = get_kernel();
if (input->dtype() == DType::Float32) {
float* src = input->ptr<float>();
float* dst = output->ptr<float>();
kernel->operator()<KernelID::DiagMaskFloat>(
src, dst, seq_len, embd, nr_past);
}
}
这个方法执行对角线掩码操作:
- 获取输入和输出张量
- 调用对角线掩码内核,将未来 token 的注意力分数设为负无穷大
3.9 AttentionBase 类
AttentionBase
是注意力机制的基类,提供了注意力机制的通用功能,如 KV 缓存管理。
size_t AttentionBase::get_workspace_in_byte() {
auto input = inputs()[0];
uint32_t seq_len = input->shape()[0];
uint32_t embd = input->shape()[1];
uint32_t head_dim = embd / m_head;
size_t workspace_size = 0;
// Q, K, V 矩阵的大小
workspace_size += seq_len * embd * sizeof(float); // Q
workspace_size += seq_len * embd * sizeof(float); // K
workspace_size += seq_len * embd * sizeof(float); // V
// QK 矩阵的大小
workspace_size += seq_len * seq_len * m_head * sizeof(float); // QK
return workspace_size;
}
std::vector<size_t> AttentionBase::preprocess_weight(
Tensor* tensor, void* src, void* dst) {
auto kernel = get_kernel();
size_t M = tensor->shape()[0];
size_t K = tensor->shape()[1];
kernel->operator()<KernelID::MatmulInt4Reorder>(src, dst, M, K);
m_packed_weight = true;
return std::vector<size_t>{M / PACK_SIZE, K};
}
AttentionBase
类提供了:
- 工作空间大小计算,用于分配足够的内存进行注意力计算
- 权重预处理,优化 Int4 量化权重的内存布局
3.10 LlamaAttention 类
LlamaAttention
实现了 Llama 模型的注意力机制,包括旋转位置编码(RoPE)。
void LlamaAttention::execute(WorkSpace* workspace, uint32_t nr_past) {
auto input = inputs()[0];
auto output = outputs()[0];
uint32_t seq_len = input->shape()[0];
uint32_t embd = input->shape()[1];
uint32_t head_dim = embd / m_head;
auto kernel = get_kernel();
// 获取工作空间指针
float* p_q = (float*)workspace->ptr();
float* p_k = p_q + seq_len * embd;
float* p_v = p_k + seq_len * embd;
float* p_qk = p_v + seq_len * embd;
// 计算 Q, K, V
if (input->dtype() == DType::Float32) {
float* src = input->ptr<float>();
float* dst = output->ptr<float>();
// 根据是否使用融合权重选择不同的计算路径
if (m_fused_weights) {
// 使用融合权重计算 Q, K, V
// ...
} else {
// 使用分离权重计算 Q, K, V
// ...
}
// 应用旋转位置编码
kernel->operator()<KernelID::ApplyRotaryPosEmb>(
p_q, p_k, seq_len, embd, m_head, m_rot, nr_past, m_rotary_mode);
// 将 K, V 存储到缓存中
kernel->operator()<KernelID::CopyKVCache>(
p_k, p_v, m_kstorage->ptr<float>(), m_vstorage->ptr<float>(), seq_len,
embd, m_kstorage->get_id());
// 计算注意力分数 QK
kernel->operator()<KernelID::BatchMatMul>(
p_q, m_kstorage->ptr<float>(), p_qk, seq_len,
m_kstorage->get_id() + seq_len, embd, m_head);
// 应用掩码和 softmax
kernel->operator()<KernelID::DiagMaskSoftmaxKernel>(
p_qk, p_qk, seq_len, m_kstorage->get_id() + seq_len, m_head, nr_past);
// 计算注意力输出
kernel->operator()<KernelID::BatchMatMulTransB>(
p_qk, m_vstorage->ptr<float>(), dst, seq_len,
m_vstorage->get_id() + seq_len, head_dim, m_head);
}
}
这个方法执行 Llama 注意力机制:
- 获取工作空间指针,为 Q, K, V, QK 分配内存
- 计算 Q, K, V 矩阵
- 应用旋转位置编码(RoPE)
- 将 K, V 存储到缓存中
- 计算注意力分数 QK
- 应用掩码和 softmax
- 计算注意力输出
3.11 GlmAttention 类
GlmAttention
实现了 GLM 模型的注意力机制,与 Llama 注意力机制类似,但有一些特定于 GLM 的处理。
3.12 Glm2MultiQueryAttention 类
Glm2MultiQueryAttention
实现了 GLM-2 模型的多查询注意力机制,这是一种优化的注意力变体,减少了 KV 缓存的大小。
3.13 Embedding 类
Embedding
实现了嵌入层,将 token ID 转换为嵌入向量。
void Embedding::execute(WorkSpace*, uint32_t) {
auto input = inputs()[0];
auto output = outputs()[0];
auto weight = weights()[0];
uint32_t seq_len = input->shape()[0];
auto kernel = get_kernel();
if (output->dtype() == DType::Float32) {
int32_t* ids = input->ptr<int32_t>();
float* dst = output->ptr<float>();
if (weight->dtype() == DType::Float32) {
float* weight_ptr = weight->ptr<float>();
kernel->operator()<KernelID::EmbeddingFloat>(
ids, weight_ptr, dst, seq_len, m_embd);
} else if (weight->dtype() == DType::Float16) {
half* weight_ptr = weight->ptr<half>();
kernel->operator()<KernelID::EmbeddingFp16>(
ids, weight_ptr, dst, seq_len, m_embd);
}
}
}
这个方法执行嵌入操作:
- 获取输入 token ID、输出嵌入向量和嵌入权重
- 根据权重的数据类型选择不同的嵌入内核
- 将 token ID 转换为嵌入向量
4. 算子执行流程
算子的典型执行流程如下:
-
创建算子:
auto layernorm = std::make_shared<LayerNorm>(device, "layernorm", inputs, embd);
-
推导输出形状:
layernorm->deduce_output_shape();
-
执行前准备:
layernorm->pre_execute();
-
执行计算:
layernorm->execute(workspace, nr_past);
-
执行后清理:
layernorm->end_execute();
5. 内存管理
算子的内存管理主要通过以下机制实现:
-
引用计数:
add_user()
:增加张量的用户计数decrease_curr_user_count()
:减少张量的当前用户计数resume_user_count()
:恢复张量的用户计数
-
数据准备和回收:
prepare_data()
:准备张量的数据,可能涉及内存分配或从文件加载recall_data()
:回收张量的数据,可能涉及内存释放
-
工作空间:
get_workspace_in_byte()
:获取执行时需要的工作空间大小workspace->ptr()
:获取工作空间的指针
6. 优化策略
算子实现了多种优化策略:
-
权重预处理:
need_preprocess_weight()
:判断是否需要预处理权重preprocess_weight()
:预处理权重,优化内存布局
-
KV 缓存:
m_kstorage
和m_vstorage
:存储注意力机制的 K 和 V 矩阵prepare_data_with_length()
:准备指定长度的缓存数据add_id()
:更新缓存的 ID
-
数据类型优化:
- 支持多种数据类型(Float32、Float16、Int8、Int4)
- 为不同数据类型提供专门的计算内核
-
内存复用:
- 通过工作空间复用内存
- 通过张量共享减少内存分配
总结
op.h
和 op.cpp
文件定义了 InferLLM 框架中的各种算子类,这些算子是构建神经网络计算图的基本单元。它们提供了丰富的功能,包括层归一化、矩阵乘法、softmax、元素级操作和注意力机制等。这些算子的实现考虑了性能优化,如内存复用、数据类型优化和权重预处理等。
7. 算子的设计模式
InferLLM 框架中的算子设计采用了以下设计模式:
-
工厂模式:
通过Graph::make_graph
创建计算图,然后在计算图中创建各种算子。 -
策略模式:
通过虚函数和多态实现不同算子的不同行为,如execute
、deduce_output_shape
等。 -
组合模式:
算子可以组合成更复杂的计算图,形成层次结构。 -
观察者模式:
通过引用计数机制,张量可以观察到它的用户(算子)何时不再需要它,从而释放资源。
8. 算子的扩展性
InferLLM 框架的算子设计具有良好的扩展性:
-
新算子添加:
只需继承OpBase
类并实现相应的虚函数,即可添加新的算子。 -
新模型支持:
通过组合现有算子或添加新算子,可以支持新的模型架构,如 Llama、GLM 等。 -
新硬件支持:
通过在Kernel
类中添加新的硬件后端实现,可以支持新的硬件平台。
9. 算子的性能优化
InferLLM 框架的算子实现了多种性能优化技术:
-
内存优化:
- 工作空间复用
- 张量共享
- KV 缓存
-
计算优化:
- 权重预处理
- 融合算子(如
SpliteHalfActiveMul
) - 特化实现(如
MatMulLast
)
-
数据类型优化:
- 支持多种数据类型(Float32、Float16、Int8、Int4)
- 为不同数据类型提供专门的计算内核
10. 算子的调试支持
InferLLM 框架的算子提供了一些调试支持:
-
命名机制:
每个算子和张量都有一个名称,便于调试和跟踪。 -
断言检查:
使用INFER_ASSERT
宏进行断言检查,确保算子的正确性。 -
形状推导:
通过deduce_output_shape
方法推导输出张量的形状,便于检查形状是否正确。
11. 与其他组件的交互
算子与框架的其他组件有密切的交互:
-
与 Device 的交互:
算子通过m_device
获取设备信息和内核实现。 -
与 Kernel 的交互:
算子通过get_kernel()
获取内核实现,调用相应的计算函数。 -
与 Tensor 的交互:
算子管理输入、输出和权重张量,控制它们的生命周期。 -
与 KvStorage 的交互:
注意力算子使用KvStorage
存储 K 和 V 矩阵,实现缓存机制。
12. 算子的使用示例
以下是一个使用算子构建简单计算图的示例:
// 创建设备和计算图
auto device = std::make_unique<CPUDevice>(KernelType::X86, 4);
auto graph = Graph::make_graph(user_config, device.get(), "llama");
// 创建输入张量
auto input = std::make_shared<Tensor>(device.get(), "input");
input->set_shape({1, 4096}, DType::Float32);
// 创建算子
auto layernorm = std::make_shared<LayerNorm>(device.get(), "layernorm", {input}, 4096);
auto matmul = std::make_shared<MatMul>(device.get(), "matmul", {layernorm->outputs()[0]}, {4096, 4096});
auto softmax = std::make_shared<SoftMax>(device.get(), "softmax", {matmul->outputs()[0]});
// 推导输出形状
layernorm->deduce_output_shape();
matmul->deduce_output_shape();
softmax->deduce_output_shape();
// 创建工作空间
size_t workspace_size = std::max({layernorm->get_workspace_in_byte(), matmul->get_workspace_in_byte(), softmax->get_workspace_in_byte()});
auto workspace = std::make_unique<WorkSpace>(device.get(), workspace_size);
// 执行计算
layernorm->pre_execute();
layernorm->execute(workspace.get(), 0);
layernorm->end_execute();
matmul->pre_execute();
matmul->execute(workspace.get(), 0);
matmul->end_execute();
softmax->pre_execute();
softmax->execute(workspace.get(), 0);
softmax->end_execute();
通过这种方式,可以构建复杂的计算图,实现大型语言模型的推理。
总的来说,op.h
和 op.cpp
文件定义了 InferLLM 框架中的算子系统,它是框架的核心组件之一,负责实现各种神经网络操作。它的设计考虑了性能、扩展性和易用性,为大型语言模型的推理提供了高效的计算支持。

- Op 类代码结构与功能实现分析
- 1. 基础定义
- 2. OpBase 类
- 3. 具体算子类
- 4. 算子执行流程
- 5. 内存管理
- 6. 优化策略
- 总结
- 7. 算子的设计模式
- 8. 算子的扩展性
- 9. 算子的性能优化
- 10. 算子的调试支持
- 11. 与其他组件的交互
- 12. 算子的使用示例