
[InferLLM大模型推理框架项目](20)kern中naive模块的概述(src/kern/naive)
InferLLM 框架中 naive 模块的代码结构与功能分析
naive
模块是 InferLLM 框架中的基础计算实现,提供了不依赖特定硬件优化的计算内核。这个模块作为其他优化实现的基础和后备方案,确保在任何平台上都能正常运行。
1. 目录结构
naive
模块包含以下文件:
kern/naive/
├── naive.cpp
├── naive.h
└── quantize.h
naive.h
:声明各种计算函数和注册内核naive.cpp
:实现各种计算函数quantize.h
:实现量化和反量化相关的函数
2. 核心文件分析
2.1 naive.h
naive.h
文件定义了各种计算函数的声明,并使用 PartialImplementKernel
和 PartialImplementSpace
宏注册这些函数到内核系统中:
namespace inferllm {
namespace naive {
// 函数声明
TaskSet llm_embedding_get_int4_float(...);
TaskSet llm_elemwise_compute_float(...);
// ... 其他函数声明
// 模板类定义
template <KernelID Id, typename... Args>
struct Comp {
static TaskSet get_all_task(Args... args);
};
template <KernelID Id, typename... Args>
struct Space {
static size_t get(Args... args);
};
// 注册内核
PartialImplementKernel(ElemwiseFloat, llm_elemwise_compute_float);
PartialImplementKernel(RmsNormFloat, llm_rms_norm_compute_float);
// ... 其他内核注册
// 注册工作空间计算函数
PartialImplementSpace(MatmulInt4Float, llm_matmul_get_workspace_float);
// ... 其他工作空间注册
} // namespace naive
namespace opt {
// 优化实现的模板类,如果没有优化实现则回退到 naive 实现
template <KernelID Id, typename... Args>
struct Comp {
static TaskSet get_all_task(Args... args) {
return naive::Comp<Id, Args...>::get_all_task(std::forward<Args>(args)...);
}
};
template <KernelID Id, typename... Args>
struct Space {
static size_t get(Args... args) {
return naive::Space<Id, Args...>::get(std::forward<Args>(args)...);
}
};
} // namespace opt
} // namespace inferllm
2.2 naive.cpp
naive.cpp
文件实现了各种计算函数,每个函数都返回一个 TaskSet
,用于多线程执行:
namespace inferllm {
namespace naive {
// 嵌入查找
TaskSet llm_embedding_get_int4_float(
const void* weights, const uint32_t* index, float* dst, uint32_t len_seq,
uint32_t embd) {
auto task = [=](const TaskId& id) {
for (uint32_t i = id.start; i < id.end; ++i) {
const int row = index[i];
const int weight_stride =
embd * dtype_in_byte(DType::Int4) / dtype_block_size(DType::Int4);
dequantize_row_q4_0_reference(
(static_cast<const char*>(weights) + row * weight_stride),
dst + i * embd, embd);
}
};
return TaskSet{{task, len_seq}};
}
// ... 其他函数实现
} // namespace naive
} // namespace inferllm
3. 功能分析
通过分析代码,可以看出 naive
模块实现了以下主要功能:
3.1 嵌入查找
TaskSet llm_embedding_get_int4_float(...);
TaskSet llm_embedding_get_int8_float(...);
TaskSet llm_embedding_get_float_float(...);
这些函数从嵌入表中查找 token 的嵌入向量,支持不同的数据类型(int4、int8、float)。
3.2 元素级操作
TaskSet llm_elemwise_compute_float(...);
TaskSet llm_elemwise_compute_float_scale(...);
TaskSet llm_elemwise_broadcast_dim0_src1_compute_float(...);
这些函数实现了元素级操作,如加法、乘法、Silu 和 Gelu 激活函数,以及带有广播的元素级操作。
3.3 归一化
TaskSet llm_norm_compute_float(...);
TaskSet llm_rms_norm_compute_float(...);
这些函数实现了层归一化和 RMS 归一化,用于稳定网络训练和推理。
3.4 Softmax
TaskSet llm_softmax_compute_float(...);
实现了 Softmax 函数,用于将输出转换为概率分布。
3.5 矩阵乘法
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 数据类型,以及带有打包优化的版本。
3.6 注意力计算
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(...);
这些函数实现了自注意力机制所需的矩阵运算,包括多头注意力和多查询注意力。
3.7 位置编码
TaskSet llm_rope_compute_float(...);
TaskSet llm_glm_rope_compute_float(...);
这些函数实现了旋转位置编码(RoPE)和 GLM 模型特定的旋转位置编码。
3.8 掩码操作
TaskSet llm_diag_mask_inf_float(...);
TaskSet llm_glm_gmask_inf_float(...);
TaskSet llm_scale_diag_mask_inf_float(...);
这些函数实现了注意力掩码操作,用于控制注意力的范围。
3.9 排列操作
TaskSet llm_permute_compute_float(...);
实现了张量的维度排列操作。
3.10 权重重排
TaskSet llm_int4_matmul_weight_reorder(...);
实现了 int4 权重的重排操作,用于优化矩阵乘法。
4. 实现细节分析
4.1 任务分解与多线程
每个计算函数都返回一个 TaskSet
,包含一个或多个任务及其子任务数量。每个任务都是一个 lambda 函数,接受一个 TaskId
参数,包含任务的起始索引、结束索引和线程 ID。
auto task = [=](const TaskId& id) {
for (uint32_t i = id.start; i < id.end; i++) {
// 计算逻辑
}
};
return TaskSet{{task, len}};
这种设计使得计算任务可以在多线程环境中高效执行,通过将大型计算任务分解为多个子任务,并分配给不同的线程处理。
4.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);
量化计算可以减少内存占用和计算量,同时保持计算精度。
4.3 矩阵乘法优化
矩阵乘法是深度学习中最耗时的操作之一,naive
模块通过以下方式优化矩阵乘法:
- 量化计算:使用 int4 和 int8 量化减少内存占用和计算量
- 打包优化:
llm_matmul_compute_int4_float_packed
函数实现了打包优化,一次处理多个权重 - 工作空间优化:使用工作空间进行中间计算,减少内存分配和释放的开销
4.4 注意力计算优化
注意力计算是 Transformer 模型的核心,naive
模块通过以下方式优化注意力计算:
- 多头并行:将不同的注意力头分配给不同的线程处理
- 内存布局优化:使用合适的内存布局减少内存访问开销
- 广播优化:对于多查询注意力,使用广播优化减少内存占用和计算量
5. 设计模式分析
naive
模块采用了多种设计模式:
5.1 策略模式
通过 Comp
和 Space
模板类,可以根据内核 ID 选择不同的计算策略。
5.2 模板方法模式
通过 PartialImplementKernel
和 PartialImplementSpace
宏定义的模板类,提供了统一的接口,而具体实现由不同的函数提供。
5.3 命令模式
通过 lambda 函数和 TaskSet
,将计算任务封装为命令对象,由线程池执行。
5.4 回退模式
通过 opt
命名空间中的模板类,如果没有优化实现则回退到 naive
实现,确保在任何平台上都能正常运行。
6. 性能考虑
虽然 naive
模块没有使用特定硬件的优化指令,但它仍然考虑了性能优化:
- 多线程并行:通过任务分解和
TaskSet
,支持多线程并行计算 - 量化计算:支持 int4 和 int8 量化,减少内存占用和计算量
- 内存布局优化:使用合适的内存布局减少内存访问开销
- 算法优化:使用优化的算法实现各种计算操作
总结
naive
模块是 InferLLM 框架中的基础计算实现,提供了不依赖特定硬件优化的计算内核。它实现了大语言模型推理所需的各种计算操作,包括嵌入查找、元素级操作、归一化、Softmax、矩阵乘法、注意力计算、位置编码、掩码操作、排列操作和权重重排等。
通过任务分解和多线程、量化计算、矩阵乘法优化和注意力计算优化等技术,naive
模块在不依赖特定硬件优化的情况下,仍然能够提供相对高效的计算性能。同时,它作为其他优化实现的基础和后备方案,确保在任何平台上都能正常运行。
naive
模块的设计体现了 InferLLM 框架的可移植性和可扩展性,通过提供统一的接口和基础实现,使得框架可以在不同平台上运行,并且可以根据需要添加特定平台的优化实现。
