


[InferLLM大模型推理框架项目](21)kern模块中naive的实现(src/kern/naive/naive.h+.cpp)
InferLLM 框架中 naive.h 和 naive.cpp 的代码结构与功能分析
naive.h
和 naive.cpp
是 InferLLM 框架中朴素实现的核心文件,提供了大语言模型推理所需的各种计算操作的基础实现。这些实现不依赖特定硬件优化,确保在任何平台上都能正常运行。
1. 文件结构概览
1.1 naive.h
naive.h
主要包含以下内容:
- 函数声明:声明各种计算函数
- 模板类定义:定义
Comp
和Space
模板类 - 内核注册:使用
PartialImplementKernel
和PartialImplementSpace
宏注册函数
1.2 naive.cpp
naive.cpp
主要包含各种计算函数的具体实现,每个函数都返回一个 TaskSet
,用于多线程执行。
2. 核心功能分类
通过分析代码,可以将 naive 模块的功能分为以下几类:
2.1 嵌入查找(Embedding Lookup)
TaskSet llm_embedding_get_int4_float(...);
TaskSet llm_embedding_get_int8_float(...);
TaskSet llm_embedding_get_float_float(...);
这些函数从嵌入表中查找 token 的嵌入向量,支持不同的数据类型(int4、int8、float)。实现过程:
- 根据索引找到对应的行
- 计算权重的步长
- 对于量化数据,调用反量化函数
- 对于浮点数据,直接复制
2.2 元素级操作(Elementwise Operations)
TaskSet llm_elemwise_compute_float(...);
TaskSet llm_elemwise_compute_float_scale(...);
TaskSet llm_elemwise_broadcast_dim0_src1_compute_float(...);
这些函数实现了元素级操作,如加法、乘法、Silu 和 Gelu 激活函数,以及带有广播的元素级操作。实现过程:
- 根据操作模式选择不同的 lambda 函数
- 对每个元素执行相应的操作
- 对于广播操作,处理不同维度的广播逻辑
2.3 归一化(Normalization)
TaskSet llm_norm_compute_float(...);
TaskSet llm_rms_norm_compute_float(...);
这些函数实现了层归一化和 RMS 归一化,用于稳定网络训练和推理。实现过程:
- 计算均值(对于层归一化)或均方根(对于 RMS 归一化)
- 计算缩放因子
- 应用归一化
2.4 Softmax
TaskSet llm_softmax_compute_float(...);
实现了 Softmax 函数,用于将输出转换为概率分布。实现过程:
- 找到每行的最大值
- 减去最大值并计算指数
- 计算和并归一化
2.5 矩阵乘法(Matrix Multiplication)
TaskSet llm_matmul_compute_int4_float(...);
TaskSet llm_matmul_compute_int4_float_packed(...);
TaskSet llm_matmul_compute_int8_float(...);
TaskSet llm_matmul_compute_float_float(...);
这些函数实现了不同精度的矩阵乘法,支持 int4、int8 和 float 数据类型,以及带有打包优化的版本。实现过程:
- 对于量化版本,先将输入量化为 int8
- 计算矩阵乘法(点积)
- 加上偏置(如果有)
2.6 注意力计算(Attention Computation)
TaskSet llm_matmul_compute_with_head_stride_float(...);
TaskSet llm_matmul_compute_with_head_strideq_broadcastk_float(...);
TaskSet llm_head_batched_matmul_compute_float(...);
TaskSet llm_head_batched_matmul_broadcastv_float(...);
这些函数实现了自注意力机制所需的矩阵运算,包括多头注意力和多查询注意力。实现过程:
- 计算 Q 和 K 的点积
- 对于多查询注意力,处理广播逻辑
- 计算注意力权重和 V 的加权和
2.7 位置编码(Position Encoding)
TaskSet llm_rope_compute_float(...);
TaskSet llm_glm_rope_compute_float(...);
这些函数实现了旋转位置编码(RoPE)和 GLM 模型特定的旋转位置编码。实现过程:
- 计算旋转角度
- 应用旋转变换
- 对于 GLM,处理特殊的位置编码逻辑
2.8 掩码操作(Masking Operations)
TaskSet llm_diag_mask_inf_float(...);
TaskSet llm_glm_gmask_inf_float(...);
TaskSet llm_scale_diag_mask_inf_float(...);
这些函数实现了注意力掩码操作,用于控制注意力的范围。实现过程:
- 对于自回归掩码,将上三角部分设为负无穷
- 对于 GLM 掩码,处理特殊的掩码逻辑
- 对于缩放掩码,先缩放再掩码
2.9 排列操作(Permutation)
TaskSet llm_permute_compute_float(...);
实现了张量的维度排列操作。实现过程:
- 根据排列参数重新排列维度
- 复制数据到新的位置
2.10 权重重排(Weight Reordering)
TaskSet llm_int4_matmul_weight_reorder(...);
实现了 int4 权重的重排操作,用于优化矩阵乘法。实现过程:
- 将权重按照特定的布局重新排列
- 优化内存访问模式
3. 实现细节分析
3.1 任务分解与多线程
每个计算函数都返回一个 TaskSet
,包含一个或多个任务及其子任务数量。每个任务都是一个 lambda 函数,接受一个 TaskId
参数,包含任务的起始索引、结束索引和线程 ID。
auto task = [=](const TaskId& id) {
for (uint32_t i = id.start; i < id.end; i++) {
// 计算逻辑
}
};
return TaskSet{{task, len}};
这种设计使得计算任务可以在多线程环境中高效执行,通过将大型计算任务分解为多个子任务,并分配给不同的线程处理。
3.2 量化计算
naive 模块支持 int4 和 int8 量化计算,通过 quantize.h
中定义的函数进行量化和反量化操作:
// 量化操作
quantize_row_q8_0_reference(src1 + m * K, q_src1, K);
// 反量化操作
dequantize_row_q4_0_reference(
(static_cast<const char*>(weights) + row * weight_stride),
dst + i * embd, embd);
量化计算可以减少内存占用和计算量,同时保持计算精度。
3.3 内存布局优化
代码中的内存布局经过优化,以提高访问效率:
- 对于矩阵乘法,使用行主序存储
- 对于注意力计算,使用特定的步长访问内存
- 对于位置编码,使用特定的内存布局
3.4 数学函数优化
代码中使用了各种数学函数,如 exp、tanh、sqrt 等,这些函数在不同平台上可能有不同的实现,但 naive 模块使用标准库函数,确保在任何平台上都能正常运行。
4. 内核注册机制
naive.h 中使用 PartialImplementKernel
和 PartialImplementSpace
宏注册函数,这些宏定义在 kernel_define.h
中,用于将函数注册到内核系统中:
PartialImplementKernel(ElemwiseFloat, llm_elemwise_compute_float);
PartialImplementKernel(RmsNormFloat, llm_rms_norm_compute_float);
// ... 其他内核注册
PartialImplementSpace(MatmulInt4Float, llm_matmul_get_workspace_float);
// ... 其他工作空间注册
这种设计使得不同平台的优化实现可以共享相同的接口,而具体实现由不同平台的优化代码提供。
5. 回退机制
naive 模块还提供了回退机制,当特定平台的优化实现不可用时,会回退到 naive 实现:
namespace opt {
template <KernelID Id, typename... Args>
struct Comp {
static TaskSet get_all_task(Args... args) {
//! if arm not implement, fallback to naive
return naive::Comp<Id, Args...>::get_all_task(std::forward<Args>(args)...);
}
};
template <KernelID Id, typename... Args>
struct Space {
//! if arm not implement, fallback to naive
static size_t get(Args... args) {
return naive::Space<Id, Args...>::get(std::forward<Args>(args)...);
}
};
} // namespace opt
这种设计确保在任何平台上都能正常运行,即使特定平台的优化实现不可用。
6. 性能考虑
虽然 naive 模块没有使用特定硬件的优化指令,但它仍然考虑了性能优化:
6.1 多线程并行
通过任务分解和 TaskSet
,支持多线程并行计算,提高计算效率。
6.2 量化计算
支持 int4 和 int8 量化,减少内存占用和计算量,提高计算效率。
6.3 内存布局优化
使用合适的内存布局,减少内存访问开销,提高缓存命中率。
6.4 算法优化
使用优化的算法实现各种计算操作,如矩阵乘法、注意力计算等。
7. 与其他模块的关系
naive 模块是 InferLLM 框架中的基础实现,与其他模块的关系如下:
7.1 与 kernel.h 的关系
kernel.h 定义了内核系统的接口,naive 模块实现了这些接口,提供了基础的计算实现。
7.2 与 quantize.h 的关系
quantize.h 定义了量化和反量化函数,naive 模块使用这些函数进行量化计算。
7.3 与优化实现的关系
naive 模块是优化实现的基础和后备方案,当特定平台的优化实现不可用时,会回退到 naive 实现。
8. 应用场景
naive 模块适用于以下场景:
8.1 跨平台推理
由于不依赖特定硬件优化,naive 模块可以在任何平台上运行,适用于跨平台推理。
8.2 功能验证
naive 模块提供了参考实现,可以用于验证优化实现的正确性。
8.3 后备方案
当特定平台的优化实现不可用时,naive 模块可以作为后备方案,确保系统正常运行。
9. 设计模式分析
naive 模块采用了多种设计模式:
9.1 策略模式
通过 Comp
和 Space
模板类,可以根据内核 ID 选择不同的计算策略。
9.2 模板方法模式
通过 PartialImplementKernel
和 PartialImplementSpace
宏定义的模板类,提供了统一的接口,而具体实现由不同的函数提供。
9.3 命令模式
通过 lambda 函数和 TaskSet
,将计算任务封装为命令对象,由线程池执行。
9.4 回退模式
通过 opt
命名空间中的模板类,如果没有优化实现则回退到 naive 实现,确保在任何平台上都能正常运行。
总结
naive.h 和 naive.cpp 是 InferLLM 框架中的基础计算实现,提供了大语言模型推理所需的各种计算操作的朴素实现。这些实现不依赖特定硬件优化,确保在任何平台上都能正常运行。通过任务分解和多线程、量化计算、内存布局优化和算法优化等技术,naive 模块在不依赖特定硬件优化的情况下,仍然能够提供相对高效的计算性能。同时,它作为其他优化实现的基础和后备方案,确保在任何平台上都能正常运行。
naive 模块的设计体现了 InferLLM 框架的可移植性和可扩展性,通过提供统一的接口和基础实现,使得框架可以在不同平台上运行,并且可以根据需要添加特定平台的优化实现。
