十年 C++ 后端 GAP 六个月,写了一个近 3 万行的LLM-TFFInfer推理框架项目解析(二)

4 阅读6分钟

十年 C++ 后端 GAP 六个月,写了一个近 3 万行的LLM-TFFInfer推理框架项目解析(二)

目录


6. 关键技术实现

6.1 Q8_0 量化

量化格式:

32 个元素为一组:
- 32 × int8 (权重)
- 1 × fp16 (scale)
- 1 × fp16 (可选 zero point)

实际存储: 32 bytes (weights) + 2 bytes (scale) = 34 bytes
压缩率: 34 / (32 * 2) = 53.125% (相比 FP16)

量化 Kernel:

__global__ void quant_q8_0_kernel(const float* src, int8_t* dst, 
                                   half* scales, int num_elements) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int group_idx = idx / 32;
    int intra_group = idx % 32;
  
    // 1. 计算每组最大值
    __shared__ float max_val[256];
    float abs_val = fabs(src[idx]);
    max_val[threadIdx.x] = abs_val;
    __syncthreads();
  
    // Reduction 找最大值
    for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {
        if (threadIdx.x < stride) {
            max_val[threadIdx.x] = max(max_val[threadIdx.x], 
                                       max_val[threadIdx.x + stride]);
        }
        __syncthreads();
    }
  
    // 2. 计算 scale
    if (intra_group == 0) {
        float max = max_val[0];
        scales[group_idx] = __float2half(max / 127.0f);
    }
    __syncthreads();
  
    // 3. 量化
    float scale = __half2float(scales[group_idx]);
    float scaled = src[idx] / scale;
    dst[idx] = __float2int_rn(scaled);
}

量化矩阵乘法:

__global__ void quant_mat_mul_kernel(const int8_t* A, const int8_t* B,
                                     const half* scales_a, const half* scales_b,
                                     float* C, int M, int N, int K) {
    // 使用 WMMA 或 IMMA 指令
    // 累加时使用 FP32 精度
    // 最后乘以 scale_a * scale_b
}

6.2 RoPE (Rotary Positional Embedding)

数学公式:

RoPE(x, pos) = x · R(pos)

其中 R(pos) 是旋转矩阵:
R(pos) = diag(R_0(pos), R_1(pos), ..., R_{d/2}(pos))

R_i(pos) = [cos(i·θ·pos)  -sin(i·θ·pos)]
           [sin(i·θ·pos)   cos(i·θ·pos)]

θ = 10000^(-2i/d)

预计算优化:

// 预计算 cos/sin 表
void precompute_rope_tables(int max_seq_len, int dim, float base) {
    for (int pos = 0; pos < max_seq_len; ++pos) {
        for (int i = 0; i < dim / 2; ++i) {
            float freq = 1.0f / pow(base, 2.0f * i / dim);
            rope_table[pos * dim + 2 * i] = cos(pos * freq);
            rope_table[pos * dim + 2 * i + 1] = sin(pos * freq);
        }
    }
}

Kernel 实现:

__device__ half2 complex_mul_half2(half2 a, half2 b) {
    // (a.x + a.y*i) * (b.x + b.y*i)
    // = (a.x*b.x - a.y*b.y) + (a.x*b.y + a.y*b.x)*i
    float ax = __half2float(a.x);
    float ay = __half2float(a.y);
    float bx = __half2float(b.x);
    float by = __half2float(b.y);
  
    return __float22half2_rn(make_float2(
        ax * bx - ay * by,
        ax * by + ay * bx
    ));
}

__global__ void rope_kernel(half* q, half* k, 
                           const float* cos_sin_table,
                           int pos, int dim) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int head = idx / dim;
    int d = idx % dim;
  
    if (d < dim / 2) {
        half2 q_val = reinterpret_cast<half2*>(q)[idx];
        half2 cos_sin = __float22half2_rn(
            make_float2(cos_sin_table[pos * dim + 2 * d],
                       cos_sin_table[pos * dim + 2 * d + 1]));
    
        reinterpret_cast<half2*>(q)[idx] = complex_mul_half2(q_val, cos_sin);
    }
}

6.3 Flash Attention v2

核心思想:

  • Tiling: 分块计算,适应 SRAM
  • Recomputation: 重计算替代存储
  • Online Softmax: 在线归一化

算法伪代码:

FlashAttention(Q, K, V):
    split Q, K, V into tiles
  
    for each tile Q_i:
        m_i = -inf, l_i = 0, O_i = 0
    
        for each tile K_j, V_j:
            S_ij = Q_i @ K_j.T / sqrt(d)
        
            m_ij = max(m_i, rowmax(S_ij))
            P_ij = exp(S_ij - m_ij)
            l_ij = l_i * exp(m_i - m_ij) + rowsum(P_ij)
        
            O_i = O_i * (l_i * exp(m_i - m_ij) / l_ij) + 
                  P_ij @ V_j * (exp(m_i - m_ij) / l_ij)
        
            m_i = m_ij
            l_i = l_ij
    
        O_i = O_i / l_i
  
    return O

CUDA 实现要点:

  1. Shared Memory Layout: K/V 分块加载
  2. Register Accumulation: O/m/l 存储在寄存器
  3. Async Copy: Hopper 架构异步拷贝
  4. Swizzle: 避免 Shared Memory Bank Conflict
  5. Fused RoPE: 加载时直接应用 RoPE

6.4 Graph Optimization

6.4.1 算子融合 (Operator Fusion)

融合规则:

Element-wise Ops 融合:
Add + Mul → FusedAddMul
Norm + Mul → FusedNormMul
Reshape + Transpose → FusedReshapeTranspose

Vertical Fusion:
MatMul + Add → FusedGEMM+Bias
MatMul + Norm → FusedGEMM+Norm

Horizontal Fusion:
Multiple Element-wise on same input → Single Kernel

实现:

void GraphOptimizer::fuse_operators(Graph& graph) {
    for (auto& node : graph.nodes()) {
        // 检查是否可以与前驱节点融合
        if (can_fuse_with_predecessor(node)) {
            auto fused_node = create_fused_node(node, node->predecessor());
            graph.replace_nodes({node, node->predecessor()}, fused_node);
        }
    }
}

6.4.2 死代码消除 (Dead Code Elimination)
void GraphOptimizer::eliminate_dead_code(Graph& graph) {
    // 1. 标记所有可达节点
    std::unordered_set<std::shared_ptr<GraphNode>> reachable;
    mark_reachable(graph.output(), reachable);
  
    // 2. 删除不可达节点
    for (auto it = graph.nodes().begin(); it != graph.nodes().end();) {
        if (!reachable.count(*it)) {
            it = graph.nodes().erase(it);
        } else {
            ++it;
        }
    }
}

7. 性能优化策略

7.1 CUDA Kernel 优化

7.1.1 内存访问优化
  • Coalesced Access: 确保线程束内连续访问
  • Shared Memory Tiling: 减少全局内存访问
  • Swizzle: 避免 Bank Conflict
  • Async Copy: Hopper 架构异步拷贝
7.1.2 计算优化
  • WMMA/IMMA: Tensor Core 加速矩阵乘法
  • Loop Unrolling: #pragma unroll
  • Instruction Level Parallelism: 隐藏延迟
  • Fused Operations: RoPE + Load, Softmax + Scale
7.1.3 Occupancy 优化
  • Register Pressure: 控制寄存器使用
  • Shared Memory: 合理分配共享内存
  • Thread Block Size: 选择合适的块大小

7.2 显存优化

7.2.1 内存池
  • 预分配大块显存
  • 避免频繁 malloc/free
  • 256 字节对齐
7.2.2 生命周期分析
  • 精确计算张量活跃区间
  • 非重叠区间复用内存
  • 降低峰值显存占用 30-50%
7.2.3 延迟回收
  • CUDA Event 异步通知
  • 避免阻塞计算流
  • 提高并发度

7.3 计算图优化

7.3.1 算子融合
  • 减少 Kernel Launch 开销
  • 提高数据局部性
  • 降低显存带宽压力
7.3.2 常量折叠
  • 编译期计算常量表达式
  • 减少运行时计算
7.3.3 公共子表达式消除
  • 识别重复计算
  • 缓存中间结果

7.4 KV Cache 优化

7.4.1 Paged Attention
  • 消除内存碎片
  • 支持可变长度
  • 提高内存利用率 20-30%
7.4.2 Prefix Cache (未来)
  • 相同 prompt 复用 KV
  • 减少重复计算
7.4.3 Quantized KV (未来)
  • INT8/FP8 KV Cache
  • 降低显存占用 50%

7.5 批处理优化

7.5.1 Static Batching
  • 固定 batch size
  • 最大化 GPU 利用率
7.5.2 Continuous Batching (未来)
  • 动态 batch 大小
  • 请求级调度
  • 提高吞吐量 2-5x

8. 扩展性与可维护性

8.1 如何添加新模型

步骤:

  1. 创建模型检测器:
class MyModelDetector : public ModelDetectorBase {
    bool matches(const std::string& format) const override {
        return format == "mymodel";
    }
  
    std::shared_ptr<ModelLoaderBase> create_loader() override {
        return std::make_shared<MyModelLoader>();
    }
};

REGISTER_MODULE_OBJECT(MyModelDetector, ModelDetectorBase, 
                      MODEL_DETECTOR_FLAG, "mymodel");
  1. 创建模型加载器:
class MyModelLoader : public ModelLoaderBase {
    ModelLoadResult load_from_file(...) override {
        // 解析模型格式
        // 读取元数据
        // 构建权重映射
    }
};
  1. 创建模型创建器:
class MyModelCreator : public ModelCreatorBase {
    void build_graph(...) override {
        // 定义模型结构
        // 构建计算图
    }
};
  1. 注册到工厂:
REGISTER_MODULE_OBJECT(MyModelCreator, ModelCreatorBase,
                      MODEL_CREATOR_FLAG, "mymodel_arch");

8.2 如何添加新算子

步骤:

  1. 定义算子类:
template<typename T, typename DeviceTag>
class MyCustomOp;
  1. 创建参数构建器:
class MyCustomOpBuilder : public OpParamBuilderBase<MyCustomOpBuilder> {
    struct Params {
        static constexpr const char* Input = "input";
        static constexpr const char* Output = "output";
    };
  
public:
    template<typename T>
    MyCustomOpBuilder& input(T&& value) {
        return set(Params::Input, value);
    }
};
  1. 实现 CUDA Kernel:
__global__ void my_custom_op_kernel(...) {
    // Kernel 实现
}
  1. 注册到 FunctionFactory:
FunctionFactory::instance()->register_callback(
    "kernel", "my_custom_op_cuda",
    [](...) { my_custom_op_kernel<<<...>>>(...); }
);

8.3 如何添加新设备后端

步骤:

  1. 创建设备类:
class DeviceNewBackend : public DeviceBaseObject {
    void device_init() override {
        // 初始化设备
    }
  
    std::shared_ptr<MemBufferAllocatorBaseObject> 
    get_device_buffer_allocator(int device_id) override {
        return std::make_shared<MemBufferAllocatorNew>();
    }
};
  1. 创建内存分配器:
class MemBufferAllocatorNew : public MemBufferAllocatorBaseObject {
    void* allocate(size_t size) override {
        // 新后端的内存分配
    }
  
    void release(void* ptr) override {
        // 新后端的内存释放
    }
};
  1. 注册到工厂:
REGISTER_MODULE_OBJECT(DeviceNewBackend, DeviceBaseObject,
                      DEVICE_BACKEND_FLAG, DEVICE_BACKEND_TYPE_NEW);

注:目前更新的项目解析是概览版的,给感兴趣的小伙伴一个大概的项目轮廓。后面会根据内容拆分细节分享。欢迎关注。

附录

A. 关键文件索引

模块关键文件行数
运行时src/core/runtime/InferRuntime.h269
计算图src/core/graph/Graph.h168
张量src/core/mem/Tensor.h485
KV Cachesrc/core/runtime/KVCache.h476
内存管理src/core/runtime/MemManager.h277
任务调度src/taskgraph/include/TaskFlowSchedule.h124
算子创建src/kernel/include/TFFOPCreator.h1645
Flash Attentionsrc/kernel/cuda/flash_attention.cu822
模块工厂include/ModuleFactory.h331

B. 联系方式


文档版本: v1.0 最后更新: 2026-04-19 适用版本: TFFInfer v0.x