
[InferLLM大模型推理框架项目](24)kern中optimized模块整体分析(src/kern/optimized)
InferLLM 框架中 optimized 模块分析
InferLLM 框架中的 optimized 模块是针对不同硬件平台的优化实现,主要包括 ARM、RISC-V 向量扩展(RVV)和 x86 平台的优化。这些优化实现利用了各平台特有的指令集和特性,提高了大语言模型推理的性能。
1. 目录结构
optimized 模块按照硬件平台分为三个子目录:
optimized/
├── arm/ # ARM 平台优化
│ ├── kernel.cpp
│ ├── kernel.h
│ ├── optimized.h
│ └── quantize.h
├── rvv/ # RISC-V 向量扩展优化
│ ├── common.cpp
│ ├── common.h
│ ├── kernel.cpp
│ ├── kernel.h
│ ├── optimized.h
│ └── quantize.h
└── x86/ # x86 平台优化
├── common.h
├── kernel.cpp
├── kernel.h
├── optimized.h
└── quantize.h
2. 核心功能模块
2.1 基础向量运算
每个平台都实现了一系列基础向量运算函数,这些函数是高级操作的基础:
2.1.1 元素级操作
// 向量加法
inline void elemwise_vector_add(
const int n, const float* __restrict x, const float* __restrict y,
float* __restrict z);
// 向量乘法
inline void elemwise_vector_mul(
const int n, const float* __restrict x, const float* __restrict y,
float* __restrict z);
// 向量缩放
inline void elemwise_vec_scale(
const int n, const float* __restrict x, float scale, float* __restrict z);
2.1.2 激活函数
// SiLU 激活函数 (x * sigmoid(x))
inline void elemwise_vector_silu(
const int n, const float* __restrict x, float* __restrict z);
// GELU 激活函数
inline void elemwise_vector_gelu(
const int n, const float* __restrict x, float* __restrict z);
2.1.3 归约操作
// 求最大值
inline float reduce_max(const int n, const float* __restrict x);
// 求平方和
inline float reduce_square_sum(const int n, const float* __restrict x);
// 减去最大值后求指数和(用于 softmax)
inline float select_sub_max_and_reduce_sum(
const int n, const float* __restrict x, float* __restrict y, const float max);
2.2 量化计算
每个平台都实现了量化和反量化操作,以及量化数据之间的点积计算:
// 4位整数量化的反量化
inline void dequantize_row_q4_0(const void* x, float* y, int k);
// 8位整数量化的反量化
inline void dequantize_row_q8_0(const void* x, float* y, int k);
// 4位整数量化与8位整数量化的点积
inline float vec_vec_dot_q40_with_q80(const int n, const void* vx, const void* vy);
2.3 矩阵运算
实现了矩阵乘法和注意力计算所需的特殊矩阵运算:
// 带偏移的矩阵乘法(用于注意力计算)
inline void compute_src_offset_embd_matmul(
const float* __restrict srcq, uint32_t srcq_stride, const float* __restrict srck,
uint32_t srck_stride, float* __restrict dst, uint32_t dst_rows,
uint32_t dst_cols, uint32_t embd);
// 带不连续目标的矩阵乘法(用于注意力计算)
inline void comput_matmul_with_dst_uncontinue(
float* __restrict dst, uint32_t dst_stride, const float* __restrict src0,
uint32_t src0_stride, const float* __restrict src1, uint32_t src1_rows,
uint32_t src1_cols, uint32_t embd);
3. 平台特定优化
3.1 ARM 平台优化
ARM 平台的优化主要利用 NEON 指令集进行向量化计算:
// ARM NEON 优化的向量加法
inline void elemwise_vector_add(
const int n, const float* __restrict x, const float* __restrict y,
float* __restrict z) {
int i = 0;
#if defined(__ARM_NEON)
for (; i + 15 < n; i += 16) {
float32x4_t vx0 = vld1q_f32(x + i);
float32x4_t vy0 = vld1q_f32(y + i);
float32x4_t vx1 = vld1q_f32(x + i + 4);
float32x4_t vy1 = vld1q_f32(y + i + 4);
float32x4_t vx2 = vld1q_f32(x + i + 8);
float32x4_t vy2 = vld1q_f32(y + i + 8);
float32x4_t vx3 = vld1q_f32(x + i + 12);
float32x4_t vy3 = vld1q_f32(y + i + 12);
float32x4_t vz0 = vaddq_f32(vx0, vy0);
float32x4_t vz1 = vaddq_f32(vx1, vy1);
float32x4_t vz2 = vaddq_f32(vx2, vy2);
float32x4_t vz3 = vaddq_f32(vx3, vy3);
vst1q_f32(z + i, vz0);
vst1q_f32(z + i + 4, vz1);
vst1q_f32(z + i + 8, vz2);
vst1q_f32(z + i + 12, vz3);
}
for (; i + 3 < n; i += 4) {
float32x4_t vx = vld1q_f32(x + i);
float32x4_t vy = vld1q_f32(y + i);
float32x4_t vz = vaddq_f32(vx, vy);
vst1q_f32(z + i, vz);
}
#endif
for (; i < n; i++) {
z[i] = x[i] + y[i];
}
}
3.2 RISC-V 向量扩展优化
RISC-V 向量扩展(RVV)的优化利用了 RVV 指令集进行向量化计算:
// RVV 优化的向量加法
inline void elemwise_vector_add(
const int n, const float* __restrict x, const float* __restrict y,
float* __restrict z) {
vadd(n, x, y, z);
}
// RVV 优化的向量乘法
inline void elemwise_vector_mul(
const int n, const float* __restrict x, const float* __restrict y,
float* __restrict z) {
vmul(n, x, y, z);
}
RVV 模块还包含了一些辅助函数,如 vadd
、vmul
、vmax
等,这些函数封装了 RVV 指令集的使用。
3.3 x86 平台优化
x86 平台的优化主要利用 SSE、AVX 和 AVX2 指令集进行向量化计算:
// x86 AVX 优化的向量乘法
inline void elemwise_vector_mul(
const int n, const float* __restrict x, const float* __restrict y,
float* __restrict z) {
const int nb32 = n / 32;
const int nb8 = (n - nb32 * 32) / 8;
const int left = n - nb32 * 32 - nb8 * 8;
for (int b32 = 0; b32 < nb32; b32++) {
const float* __restrict x32 = (const float*)x + b32 * 32;
const float* __restrict y32 = (const float*)y + b32 * 32;
float* __restrict z32 = z + b32 * 32;
__m256 vx0 = _mm256_loadu_ps(x32 + 0);
__m256 vx1 = _mm256_loadu_ps(x32 + 8);
__m256 vx2 = _mm256_loadu_ps(x32 + 16);
__m256 vx3 = _mm256_loadu_ps(x32 + 24);
__m256 vy0 = _mm256_loadu_ps(y32 + 0);
__m256 vy1 = _mm256_loadu_ps(y32 + 8);
__m256 vy2 = _mm256_loadu_ps(y32 + 16);
__m256 vy3 = _mm256_loadu_ps(y32 + 24);
__m256 vz0 = _mm256_mul_ps(vx0, vy0);
__m256 vz1 = _mm256_mul_ps(vx1, vy1);
__m256 vz2 = _mm256_mul_ps(vx2, vy2);
__m256 vz3 = _mm256_mul_ps(vx3, vy3);
_mm256_storeu_ps(z32 + 0, vz0);
_mm256_storeu_ps(z32 + 8, vz1);
_mm256_storeu_ps(z32 + 16, vz2);
_mm256_storeu_ps(z32 + 24, vz3);
}
// ... 处理剩余元素 ...
}
x86 模块还包含了一些辅助函数,如 exp256_ps
等,这些函数实现了数学函数的向量化版本。
4. 实现策略分析
4.1 分块处理
所有平台的优化实现都采用了分块处理策略,将数据分成多个块进行处理:
- 大块:使用最宽的向量指令处理(如 x86 上的 32 个元素一块)
- 中块:使用较窄的向量指令处理(如 x86 上的 8 个元素一块)
- 剩余元素:使用标量指令处理
这种策略可以最大化利用向量指令的并行性,同时处理所有数据。
4.2 内存访问优化
优化实现注重内存访问模式,尽量使用连续的内存访问:
// 连续加载和存储
__m256 vx0 = _mm256_loadu_ps(x32 + 0);
__m256 vx1 = _mm256_loadu_ps(x32 + 8);
// ...
_mm256_storeu_ps(z32 + 0, vz0);
_mm256_storeu_ps(z32 + 8, vz1);
4.3 计算优化
优化实现使用了各种计算优化技术:
- 向量化:使用 SIMD 指令并行处理多个数据
- 指令级并行:安排指令顺序,减少依赖
- 数学函数优化:使用近似计算或查表法加速数学函数
4.4 回退机制
所有优化实现都包含回退机制,当特定平台的指令集不可用时,使用标量实现:
inline void elemwise_vector_add(
const int n, const float* __restrict x, const float* __restrict y,
float* __restrict z) {
int i = 0;
#if defined(__ARM_NEON)
// NEON 优化实现
// ...
#endif
// 标量回退实现
for (; i < n; i++) {
z[i] = x[i] + y[i];
}
}
5. 性能优化分析
5.1 ARM 平台性能优化
ARM 平台的性能优化主要体现在:
- 使用 NEON 指令集进行向量化计算
- 每次处理 16 个元素(4个 float32x4_t 寄存器)
- 针对不同数据大小使用不同的处理策略
ARM 平台的优化相对简单,主要依赖 NEON 指令集的基本向量操作。
5.2 RISC-V 向量扩展性能优化
RISC-V 向量扩展的性能优化主要体现在:
- 使用 RVV 指令集进行向量化计算
- 利用 RVV 的可变长度向量特性,适应不同硬件实现
- 封装底层 RVV 指令,提供简洁的接口
RVV 的优化利用了 RISC-V 向量扩展的灵活性,可以适应不同的硬件实现。
5.3 x86 平台性能优化
x86 平台的性能优化最为复杂和全面:
- 使用 AVX/AVX2 指令集进行向量化计算
- 每次处理 32 个元素(4个 __m256 寄存器)
- 实现了复杂数学函数的向量化版本(如 exp256_ps)
- 针对不同操作使用不同的优化策略
x86 平台的优化利用了 x86 丰富的 SIMD 指令集和寄存器资源,实现了最全面的优化。
6. 与 naive 实现的比较
optimized 模块与 naive 模块的主要区别:
- naive 模块使用标量实现,optimized 模块使用向量化实现
- naive 模块适用于所有平台,optimized 模块针对特定平台优化
- naive 模块实现简单直观,optimized 模块实现复杂但性能更高
- naive 模块作为参考实现和后备方案,optimized 模块作为主要实现
在实际应用中,系统会优先使用 optimized 模块的实现,当特定平台的优化实现不可用时,会回退到 naive 模块的实现。
7. 多线程并行策略
optimized 模块使用 TaskSet 实现多线程并行,每个计算函数都返回一个 TaskSet,包含一个或多个任务及其子任务数量:
TaskSet llm_rms_norm_compute_float(
const float* src, float* dst, uint32_t seq_len, uint32_t embd, float eps) {
auto task = [=](const TaskId& id) {
for (uint32_t i = id.start; i < id.end; i++) {
// 计算逻辑
}
};
return TaskSet{{task, seq_len}};
}
这种设计使得计算任务可以在多线程环境中高效执行,通过将大型计算任务分解为多个子任务,并分配给不同的线程处理。
7.1 任务分解策略
不同的计算函数使用不同的任务分解策略:
- 按序列长度分解:如
llm_rms_norm_compute_float
,每个任务处理一个或多个序列位置 - 按头数分解:如
llm_matmul_compute_with_head_stride_float
,每个任务处理一个或多个注意力头 - 按矩阵行列分解:如
llm_matmul_compute_int4_float
,使用两个任务集,一个按行分解,一个按列分解
7.2 多阶段任务
某些计算函数使用多阶段任务,如 llm_matmul_compute_int4_float
:
TaskSet llm_matmul_compute_int4_float(
float* dst, const void* src0, const float* bias, const float* src1, uint32_t M,
uint32_t N, uint32_t K, void* workspace, uint32_t size) {
// 第一阶段:量化输入
auto task1 = [=](const TaskId& id) {
// 量化逻辑
};
// 第二阶段:计算矩阵乘法
auto task2 = [=](const TaskId& id) {
// 矩阵乘法逻辑
};
return TaskSet{{task1, M}, {task2, N}};
}
这种设计使得不同阶段的任务可以并行执行,提高计算效率。
8. 内存优化策略
8.1 工作空间管理
optimized 模块使用工作空间进行临时计算,减少内存分配和释放的开销:
size_t llm_matmul_get_workspace_float(uint32_t, uint32_t M, uint32_t N, uint32_t K) {
return sizeof(float) * K * M;
}
工作空间由调用者分配和管理,计算函数只负责使用。
8.2 量化计算
optimized 模块使用量化计算减少内存占用和计算量:
TaskSet llm_matmul_compute_int4_float(
float* dst, const void* src0, const float* bias, const float* src1, uint32_t M,
uint32_t N, uint32_t K, void* workspace, uint32_t size) {
// 量化输入
auto task1 = [=](const TaskId& id) {
for (uint32_t m = id.start; m < id.end; m++) {
BlockQ80* q_src1 = (BlockQ80*)(static_cast<uint8_t*>(workspace) +
m * weight_q80_stride);
quantize_row_q8_0(src1 + m * K, q_src1, K);
}
};
// 使用量化数据计算
auto task2 = [=](const TaskId& id) {
// 使用 vec_vec_dot_q40_with_q80 计算点积
};
return TaskSet{{task1, M}, {task2, N}};
}
这种设计减少了内存占用和内存带宽需求,提高了计算效率。
8.3 内存访问模式
optimized 模块优化了内存访问模式,减少内存访问开销:
TaskSet llm_matmul_compute_int4_float(
float* dst, const void* src0, const float* bias, const float* src1, uint32_t M,
uint32_t N, uint32_t K, void* workspace, uint32_t size) {
// 按块处理,每次处理 4 列
auto task2 = [=](const TaskId& id) {
uint32_t N_len = id.end - id.start;
uint32_t n_block_4 = N_len / 4;
uint32_t n_block_4_left = N_len - n_block_4 * 4;
for (uint32_t block4 = 0; block4 < n_block_4; block4++) {
uint32_t n = block4 * 4 + id.start;
// 处理 4 列
for (uint32_t m = 0; m < M; m++) {
int8_t* src = q_src + m * weight_q80_stride;
dst[m * N + n] = vec_vec_dot_q40_with_q80(K, q_weight0, src) + b0;
dst[m * N + n + 1] = vec_vec_dot_q40_with_q80(K, q_weight1, src) + b1;
dst[m * N + n + 2] = vec_vec_dot_q40_with_q80(K, q_weight2, src) + b2;
dst[m * N + n + 3] = vec_vec_dot_q40_with_q80(K, q_weight3, src) + b3;
}
}
// 处理剩余列
};
}
这种设计提高了缓存命中率,减少了内存访问开销。
9. 注意力计算优化
optimized 模块对注意力计算进行了特殊优化:
9.1 多头注意力
TaskSet llm_matmul_compute_with_head_stride_float(
float* dst, const float* srck, const float* srcq, uint32_t seqlen,
uint32_t embd, uint32_t head, uint32_t nr_past) {
uint32_t sub_embd = embd / head;
uint32_t length = nr_past + seqlen;
uint32_t line_stride = embd;
auto task = [=](const TaskId& id) {
for (uint32_t h = id.start; h < id.end; h++) {
auto dst_head = dst + h * seqlen * (nr_past + seqlen);
auto srck_head = srck + h * sub_embd;
auto srcq_head = srcq + h * sub_embd;
compute_src_offset_embd_matmul(
srcq_head, embd, srck_head, embd, dst_head, seqlen, length,
sub_embd);
}
};
return TaskSet{{task, head}};
}
这个函数实现了多头注意力中的 Q 和 K 的矩阵乘法,每个任务处理一个注意力头。
9.2 注意力输出计算
TaskSet llm_head_batched_matmul_compute_float(
float* dst, const float* v, const float* qk, uint32_t seqlen, uint32_t embd,
uint32_t head, uint32_t nr_past) {
uint32_t sub_embd = embd / head;
uint32_t length = nr_past + seqlen;
uint32_t line_stride = embd;
auto task = [=](const TaskId& id) {
for (uint32_t h = id.start; h < id.end; h++) {
float* dst_head = dst + h * sub_embd;
const float* v_head = v + h * sub_embd;
const float* qk_head = qk + h * seqlen * length;
comput_matmul_with_dst_uncontinue(
dst_head, embd, v_head, embd, qk_head, seqlen, length, sub_embd);
}
};
return TaskSet{{task, head}};
}
这个函数实现了多头注意力中的 QK 和 V 的矩阵乘法,每个任务处理一个注意力头。
10. 矩阵乘法优化
矩阵乘法是大语言模型中最耗时的操作,optimized 模块对矩阵乘法进行了特殊优化:
10.1 量化矩阵乘法
TaskSet llm_matmul_compute_int4_float(
float* dst, const void* src0, const float* bias, const float* src1, uint32_t M,
uint32_t N, uint32_t K, void* workspace, uint32_t size) {
// 量化输入
auto task1 = [=](const TaskId& id) {
for (uint32_t m = id.start; m < id.end; m++) {
BlockQ80* q_src1 = (BlockQ80*)(static_cast<uint8_t*>(workspace) +
m * weight_q80_stride);
quantize_row_q8_0(src1 + m * K, q_src1, K);
}
};
// 使用量化数据计算
auto task2 = [=](const TaskId& id) {
// 按块处理,每次处理 4 列
uint32_t N_len = id.end - id.start;
uint32_t n_block_4 = N_len / 4;
uint32_t n_block_4_left = N_len - n_block_4 * 4;
for (uint32_t block4 = 0; block4 < n_block_4; block4++) {
// 处理 4 列
}
// 处理剩余列
};
return TaskSet{{task1, M}, {task2, N}};
}
这个函数实现了 4 位整数权重与浮点数激活值的矩阵乘法,使用量化计算减少内存占用和计算量。
10.2 特殊矩阵乘法
void compute_src_offset_embd_matmul(
const float* __restrict srcq, uint32_t srcq_stride, const float* __restrict srck,
uint32_t srck_stride, float* __restrict dst, uint32_t dst_rows,
uint32_t dst_cols, uint32_t embd);
void comput_matmul_with_dst_uncontinue(
float* __restrict dst, uint32_t dst_stride, const float* __restrict src0,
uint32_t src0_stride, const float* __restrict src1, uint32_t src1_rows,
uint32_t src1_cols, uint32_t embd);
这些函数实现了特殊的矩阵乘法,用于注意力计算中的特殊需求。
11. 未来优化方向
基于当前实现,可以考虑以下优化方向:
11.1 更多硬件平台支持
- 支持更多 ARM 架构(如 ARMv8.2-A 的 SVE 指令集)
- 支持更多 RISC-V 扩展(如 RISC-V P 扩展)
- 支持更多 x86 指令集(如 AVX-512)
11.2 更高效的算法
- 使用 Winograd 算法优化矩阵乘法
- 使用 Flash Attention 算法优化注意力计算
- 使用混合精度计算提高性能
11.3 更多量化方法
- 支持 3 位、2 位甚至 1 位量化
- 支持非对称量化
- 支持组量化
11.4 更高级的并行策略
- 使用流水线并行减少内存占用
- 使用张量并行和模型并行处理大型模型
- 使用异步计算提高计算效率
总结
InferLLM 框架中的 optimized 模块提供了针对不同硬件平台的优化实现,包括 ARM、RISC-V 向量扩展和 x86 平台。这些优化实现利用了各平台特有的指令集和特性,提高了大语言模型推理的性能。
optimized 模块使用多线程并行、量化计算、内存优化和特殊算法等技术,实现了高效的计算。与 naive 模块相比,optimized 模块的实现更复杂,但性能更高。在实际应用中,系统会优先使用 optimized 模块的实现,当特定平台的优化实现不可用时,会回退到 naive 模块的实现。
未来可以考虑支持更多硬件平台、使用更高效的算法、支持更多量化方法和使用更高级的并行策略等方向进行优化,进一步提高大语言模型推理的性能。
