第十二章:LLM推理加速

本章深入探讨大语言模型(LLM)在FPGA上的推理加速技术。随着GPT、LLaMA等模型参数规模达到数百亿甚至万亿级别,推理延迟和吞吐量成为关键瓶颈。我们将分析Transformer架构的计算特征,重点关注注意力机制的硬件优化、KV-cache管理策略以及量化技术。通过对比FPGA与GPU在LLM推理中的优劣势,您将掌握设计高效能、低延迟推理加速器的核心技术,特别是针对边缘部署和实时服务场景的优化方法。

12.1 LLM计算特征与挑战

12.1.1 Transformer架构分析

大语言模型的核心是Transformer架构,其推理过程具有独特的计算模式:

// Transformer层基本结构抽象
module transformer_layer #(
    parameter MODEL_DIM = 4096,      // 模型维度(如LLaMA-7B)
    parameter NUM_HEADS = 32,        // 注意力头数
    parameter HEAD_DIM = 128,        // 每个头的维度
    parameter FFN_DIM = 11008,       // FFN隐藏层维度
    parameter SEQ_LEN = 2048         // 最大序列长度
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // 输入token嵌入
    input  logic [MODEL_DIM-1:0]    token_embed,
    input  logic [9:0]              position,      // 当前位置
    input  logic                    is_prefill,    // 预填充/生成模式

    // KV-cache接口
    output logic [MODEL_DIM-1:0]    k_cache_write,
    output logic [MODEL_DIM-1:0]    v_cache_write,
    input  logic [MODEL_DIM-1:0]    k_cache_read[SEQ_LEN],
    input  logic [MODEL_DIM-1:0]    v_cache_read[SEQ_LEN],

    // 输出
    output logic [MODEL_DIM-1:0]    layer_output
);

计算特征分析:

  1. 内存带宽瓶颈

    • 权重参数量:7B模型约需14GB(FP16)
    • KV-cache:每token需要 2 × layers × model_dim × 2 bytes
    • 对于2048长度序列,KV-cache可达1.6GB
  2. 计算模式差异

    • 预填充阶段:计算密集型,可并行处理所有token
    • 生成阶段:内存密集型,逐token自回归生成
  3. 动态性挑战

    • 序列长度可变(1~32K tokens)
    • Batch size动态调整
    • 注意力稀疏模式不规则

12.1.2 性能瓶颈定位

通过profiling分析,LLM推理的性能瓶颈主要集中在:

// 性能计数器模块
module llm_perf_counter (
    input  logic        clk,
    input  logic        rst_n,

    // 监控信号
    input  logic        attn_compute_active,
    input  logic        ffn_compute_active,
    input  logic        memory_stall,
    input  logic        cache_miss,

    // 性能统计输出
    output logic [31:0] attn_cycles,
    output logic [31:0] ffn_cycles,
    output logic [31:0] memory_stall_cycles,
    output logic [31:0] total_cycles
);

典型性能分布(LLaMA-7B on Xilinx VU9P):

  • 注意力计算:45% 时间
  • FFN计算:35% 时间
  • 内存访问等待:15% 时间
  • 其他开销:5% 时间

12.1.3 FPGA优势分析

相比GPU,FPGA在LLM推理中的独特优势:

  1. 定制化数据通路

    • 可针对特定量化位宽优化(INT4/INT8混合精度)
    • 消除不必要的数据移动和格式转换
    • 支持非标准数值格式(如BF16、TF32变种)
    • 计算与数据流紧密耦合设计
  2. 流水线并行

    • 层间流水线重叠计算,隐藏内存延迟
    • 细粒度任务并行(头级、层级、操作级)
    • 自定义缓冲区深度匹配计算吞吐
    • 动态流水线深度调整
  3. 低批处理延迟

    • 适合batch=1的实时服务(首token延迟<10ms)
    • 确定性延迟保证(抖动<1ms)
    • 无需等待批次聚合的调度开销
    • 支持优先级抢占调度
  4. 能效比优势

    • 典型功耗:75W (VU9P) vs 300W (A100)
    • 适合边缘部署场景(车载、移动基站)
    • 每瓦特推理吞吐量提升3-5倍
    • 无需主动散热的被动冷却设计

12.1.4 硬件资源估算

针对不同规模的LLM模型,FPGA资源需求估算:

// 资源估算辅助模块
module resource_estimator #(
    parameter MODEL_SIZE = "7B",     // 7B, 13B, 30B, 65B
    parameter PRECISION = "INT8",    // FP16, INT8, INT4
    parameter BATCH_SIZE = 1,
    parameter SEQ_LENGTH = 2048
);

    // 基础参数映射
    localparam int MODEL_PARAMS = (MODEL_SIZE == "7B") ? 7_000_000_000 :
                                  (MODEL_SIZE == "13B") ? 13_000_000_000 :
                                  (MODEL_SIZE == "30B") ? 30_000_000_000 :
                                  65_000_000_000;

    localparam int BYTES_PER_PARAM = (PRECISION == "FP16") ? 2 :
                                     (PRECISION == "INT8") ? 1 :
                                     0.5;  // INT4

    // 计算资源需求
    localparam int WEIGHT_SIZE_MB = MODEL_PARAMS * BYTES_PER_PARAM / 1024 / 1024;
    localparam int KV_CACHE_MB = BATCH_SIZE * SEQ_LENGTH * 4096 * 2 * 32 * 2 / 1024 / 1024;

    // DSP需求(矩阵乘法单元)
    localparam int DSP_REQUIRED = (PRECISION == "INT8") ? 2048 : 4096;

    // BRAM需求(MB)
    localparam int BRAM_REQUIRED_MB = 32;  // 局部权重缓存

    // HBM带宽需求(GB/s)
    localparam int HBM_BW_REQUIRED = (MODEL_SIZE == "7B") ? 200 :
                                     (MODEL_SIZE == "13B") ? 400 :
                                     800;
endmodule

典型FPGA平台资源对比:

FPGA型号 DSP BRAM(MB) HBM(GB) HBM带宽(GB/s) 适合模型
VU9P 6,840 75 0 19 (DDR4) 1.5B量化
VU13P 12,288 94 0 38 (DDR4) 3B量化
VU37P 9,024 112 8 460 7B INT8
VU47P 9,024 112 16 460 13B INT8
Versal AI Core 1,968 32 32 820 7B混合精度

12.1.5 性能建模与预测

准确的性能预测对于架构决策至关重要:

// 性能预测模型
module performance_model #(
    parameter COMPUTE_UNITS = 32,
    parameter MEMORY_BW_GBPS = 460,
    parameter PRECISION = "INT8"
) (
    input  model_config_t config,
    output perf_metrics_t metrics
);

    // 计算理论峰值性能
    localparam real TOPS = COMPUTE_UNITS * 2.0 * 1.5;  // 1.5GHz假设

    // 分析计算密度
    real compute_intensity;
    always_comb begin
        // FLOPs per byte
        compute_intensity = (config.hidden_dim * 2.0) / 
                           (PRECISION == "INT8" ? 1 : 2);

        // Roofline分析
        if (compute_intensity < MEMORY_BW_GBPS / TOPS) begin
            metrics.bottleneck = MEMORY_BOUND;
            metrics.utilization = compute_intensity * MEMORY_BW_GBPS / TOPS;
        end else begin
            metrics.bottleneck = COMPUTE_BOUND;
            metrics.utilization = 1.0;
        end

        // 预测token生成延迟
        metrics.ms_per_token = (config.num_params * 2) / 
                              (TOPS * 1e12 * metrics.utilization) * 1000;
    end
endmodule

实测性能数据(Xilinx VU37P):

  • LLaMA-7B INT8:45 tokens/s @ batch=1
  • LLaMA-13B INT8:23 tokens/s @ batch=1
  • GPT-J-6B FP16:18 tokens/s @ batch=1
  • 首token延迟:8-15ms(取决于prompt长度)

12.2 注意力机制硬件加速

12.2.1 标准注意力计算优化

多头注意力是LLM的核心计算,其数学表达式为:

$$\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

硬件实现的关键挑战在于高效计算QK^T矩阵乘法和softmax归一化:

// 优化的注意力计算引擎
module attention_engine #(
    parameter NUM_HEADS = 32,
    parameter HEAD_DIM = 128,
    parameter SEQ_LEN = 2048,
    parameter DATA_WIDTH = 16    // FP16/INT8
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // Query输入 (单token生成模式)
    input  logic [DATA_WIDTH-1:0]   q_vector[NUM_HEADS][HEAD_DIM],

    // Key/Value来自cache
    input  logic [DATA_WIDTH-1:0]   k_matrix[NUM_HEADS][SEQ_LEN][HEAD_DIM],
    input  logic [DATA_WIDTH-1:0]   v_matrix[NUM_HEADS][SEQ_LEN][HEAD_DIM],

    // 注意力掩码
    input  logic                    mask[SEQ_LEN],

    // 输出
    output logic [DATA_WIDTH-1:0]   attn_output[NUM_HEADS][HEAD_DIM],
    output logic                    valid_out
);

    // 并行计算所有头的QK^T
    logic [DATA_WIDTH-1:0] qk_scores[NUM_HEADS][SEQ_LEN];

    // 分块矩阵乘法单元
    genvar h, s;
    generate
        for (h = 0; h < NUM_HEADS; h++) begin : head_loop
            for (s = 0; s < SEQ_LEN; s++) begin : seq_loop
                dot_product_unit #(
                    .VECTOR_LEN(HEAD_DIM),
                    .DATA_WIDTH(DATA_WIDTH)
                ) qk_dp (
                    .clk(clk),
                    .a(q_vector[h]),
                    .b(k_matrix[h][s]),
                    .result(qk_scores[h][s])
                );
            end
        end
    endgenerate

    // Softmax处理流水线
    softmax_pipeline #(
        .SEQ_LEN(SEQ_LEN),
        .NUM_HEADS(NUM_HEADS)
    ) softmax_inst (
        .clk(clk),
        .scores_in(qk_scores),
        .mask(mask),
        .weights_out(attn_weights)
    );
endmodule

关键优化技术:

  1. 分块计算:将大矩阵分解为适合片上存储的小块
  2. 数值稳定性:使用log-sum-exp技巧避免溢出
  3. 流水线设计:重叠QK计算、softmax和加权求和

12.2.2 FlashAttention硬件实现

FlashAttention通过分块和重计算减少内存访问:

// FlashAttention分块计算控制器
module flash_attention_controller #(
    parameter BLOCK_SIZE = 64,
    parameter SEQ_LEN = 2048,
    parameter HEAD_DIM = 128
) (
    input  logic clk,
    input  logic start,

    // 分块调度输出
    output logic [11:0] q_block_idx,
    output logic [11:0] kv_block_idx,
    output logic        block_valid,

    // 累加控制
    output logic        accumulate_en,
    output logic        normalize_en
);

    // 双层循环遍历Q和KV块
    typedef enum {IDLE, Q_LOOP, KV_LOOP, ACCUMULATE, NORMALIZE} state_t;
    state_t state;

    always_ff @(posedge clk) begin
        case(state)
            Q_LOOP: begin
                // 外层循环:遍历Query块
                for (int q_blk = 0; q_blk < SEQ_LEN/BLOCK_SIZE; q_blk++) begin
                    state <= KV_LOOP;
                end
            end

            KV_LOOP: begin
                // 内层循环:遍历KV块
                // 仅计算因果掩码允许的块
                if (kv_block_idx <= q_block_idx) begin
                    block_valid <= 1'b1;
                end
            end
        endcase
    end
endmodule

内存访问优化:

  • 标准注意力:O(N²) 内存访问
  • FlashAttention:O(N²/M) 其中M是块大小
  • 实测带宽需求降低8-16倍

12.2.3 稀疏注意力加速

针对长序列,稀疏注意力模式可大幅减少计算量:

// 稀疏模式生成器
module sparse_pattern_generator #(
    parameter SEQ_LEN = 2048,
    parameter WINDOW_SIZE = 256,
    parameter GLOBAL_TOKENS = 32
) (
    input  logic [11:0] query_pos,
    input  logic [11:0] key_pos,

    output logic        is_attended
);

    always_comb begin
        is_attended = 1'b0;

        // 局部窗口注意力
        if (key_pos >= query_pos - WINDOW_SIZE && 
            key_pos <= query_pos) begin
            is_attended = 1'b1;
        end

        // 全局token始终被关注
        if (key_pos < GLOBAL_TOKENS) begin
            is_attended = 1'b1;
        end

        // 跨步注意力(每隔固定距离)
        if ((query_pos - key_pos) % 128 == 0) begin
            is_attended = 1'b1;
        end
    end
endmodule

稀疏模式效果:

  • Sliding Window:计算量从O(N²)降至O(N×W)
  • Dilated Attention:保持长程依赖,计算量O(N×log N)
  • Block Sparse:适合结构化文本,可预测访问模式

12.2.4 多查询注意力(MQA)与分组查询注意力(GQA)

MQA和GQA通过共享KV头减少内存需求和带宽压力:

// MQA/GQA优化的注意力引擎
module mqa_gqa_attention #(
    parameter NUM_Q_HEADS = 32,      // Query头数
    parameter NUM_KV_HEADS = 8,      // KV头数(GQA=8, MQA=1)
    parameter HEAD_DIM = 128,
    parameter SEQ_LEN = 2048
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // Query输入 - 完整的32个头
    input  logic [15:0]             q_heads[NUM_Q_HEADS][HEAD_DIM],

    // KV输入 - 仅8个头(GQA)或1个头(MQA)
    input  logic [15:0]             k_heads[NUM_KV_HEADS][SEQ_LEN][HEAD_DIM],
    input  logic [15:0]             v_heads[NUM_KV_HEADS][SEQ_LEN][HEAD_DIM],

    output logic [15:0]             output_heads[NUM_Q_HEADS][HEAD_DIM]
);

    // 计算Q头到KV头的映射
    localparam HEADS_PER_GROUP = NUM_Q_HEADS / NUM_KV_HEADS;

    genvar q, g;
    generate
        for (g = 0; g < NUM_KV_HEADS; g++) begin : kv_group
            for (q = 0; q < HEADS_PER_GROUP; q++) begin : q_in_group

                // 每组内的Q头共享同一个KV头
                attention_head #(
                    .HEAD_DIM(HEAD_DIM),
                    .SEQ_LEN(SEQ_LEN)
                ) head_inst (
                    .clk(clk),
                    .q(q_heads[g * HEADS_PER_GROUP + q]),
                    .k(k_heads[g]),  // 共享K
                    .v(v_heads[g]),  // 共享V
                    .out(output_heads[g * HEADS_PER_GROUP + q])
                );
            end
        end
    endgenerate
endmodule

MQA/GQA优势分析:

  • 内存节省:KV-cache减少75%(GQA)或96.9%(MQA)
  • 带宽优化:HBM读取减少4-32倍
  • 精度权衡:GQA精度损失<0.5%,MQA约1-2%

12.2.5 线性注意力机制

线性注意力通过kernel技巧降低复杂度到O(N):

// 线性注意力实现(如Performer)
module linear_attention #(
    parameter MODEL_DIM = 4096,
    parameter NUM_FEATURES = 256,    // 随机特征数
    parameter SEQ_LEN = 2048
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // 输入
    input  logic [15:0]             q[SEQ_LEN][MODEL_DIM],
    input  logic [15:0]             k[SEQ_LEN][MODEL_DIM],
    input  logic [15:0]             v[SEQ_LEN][MODEL_DIM],

    // 输出
    output logic [15:0]             out[SEQ_LEN][MODEL_DIM]
);

    // 随机投影矩阵(预计算)
    logic [15:0] random_matrix[MODEL_DIM][NUM_FEATURES];

    // 特征映射: φ(x) = exp(Rx - ||x||²/2)
    logic [15:0] q_features[SEQ_LEN][NUM_FEATURES];
    logic [15:0] k_features[SEQ_LEN][NUM_FEATURES];

    // 计算特征
    feature_map_unit feat_q (
        .input_seq(q),
        .random_mat(random_matrix),
        .features(q_features)
    );

    feature_map_unit feat_k (
        .input_seq(k),
        .random_mat(random_matrix),
        .features(k_features)
    );

    // 累积器: S = Σ(k_features ⊗ v)
    logic [31:0] kv_sum[NUM_FEATURES][MODEL_DIM];
    logic [31:0] k_sum[NUM_FEATURES];

    always_ff @(posedge clk) begin
        // 因果掩码的累积计算
        for (int pos = 0; pos < SEQ_LEN; pos++) begin
            // 更新累积器
            for (int f = 0; f < NUM_FEATURES; f++) begin
                k_sum[f] <= k_sum[f] + k_features[pos][f];

                for (int d = 0; d < MODEL_DIM; d++) begin
                    kv_sum[f][d] <= kv_sum[f][d] + 
                                    k_features[pos][f] * v[pos][d];
                end
            end

            // 计算输出: out = (q_features · kv_sum) / (q_features · k_sum)
            automatic logic [31:0] numerator[MODEL_DIM];
            automatic logic [31:0] denominator;

            denominator = 0;
            for (int f = 0; f < NUM_FEATURES; f++) begin
                denominator += q_features[pos][f] * k_sum[f];
            end

            for (int d = 0; d < MODEL_DIM; d++) begin
                numerator[d] = 0;
                for (int f = 0; f < NUM_FEATURES; f++) begin
                    numerator[d] += q_features[pos][f] * kv_sum[f][d];
                end
                out[pos][d] <= numerator[d] / denominator;
            end
        end
    end
endmodule

线性注意力性能对比:

  • 复杂度:O(N×D×F) vs O(N²×D)
  • 内存需求:O(D×F) vs O(N×D)
  • 适用场景:超长序列(>8K tokens)

12.3 KV-Cache优化策略

12.3.1 分层存储架构

KV-cache是LLM推理的内存瓶颈,需要精心设计的分层存储:

// KV-Cache分层存储管理器
module kv_cache_manager #(
    parameter NUM_LAYERS = 32,
    parameter NUM_HEADS = 32,
    parameter HEAD_DIM = 128,
    parameter MAX_SEQ_LEN = 4096,
    parameter CACHE_WAYS = 4        // 4路组相联
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // 请求接口
    input  logic [11:0]             layer_id,
    input  logic [4:0]              head_id,
    input  logic [11:0]             position,
    input  logic                    is_write,
    input  logic [HEAD_DIM*16-1:0]  write_data,  // FP16

    // 响应接口
    output logic [HEAD_DIM*16-1:0]  read_data,
    output logic                    cache_hit,
    output logic                    ready,

    // DDR接口
    output logic                    ddr_req,
    output logic [39:0]             ddr_addr,
    input  logic                    ddr_ready
);

    // L1 Cache: 片上BRAM存储最近使用的KV
    typedef struct packed {
        logic [11:0] tag;           // position标签
        logic [HEAD_DIM*16-1:0] k_data;
        logic [HEAD_DIM*16-1:0] v_data;
        logic        valid;
        logic [7:0]  lru_count;     // LRU计数器
    } cache_line_t;

    cache_line_t l1_cache[NUM_HEADS][CACHE_WAYS];

    // L2 Cache: HBM/URAM存储中等频率访问的KV
    logic [HEAD_DIM*32-1:0] l2_cache[NUM_LAYERS][MAX_SEQ_LEN/16]; // 压缩存储

    // 缓存查找逻辑
    always_ff @(posedge clk) begin
        cache_hit <= 1'b0;

        // 并行搜索所有way
        for (int way = 0; way < CACHE_WAYS; way++) begin
            if (l1_cache[head_id][way].valid && 
                l1_cache[head_id][way].tag == position) begin
                cache_hit <= 1'b1;
                read_data <= is_k_request ? 
                            l1_cache[head_id][way].k_data :
                            l1_cache[head_id][way].v_data;

                // 更新LRU
                l1_cache[head_id][way].lru_count <= 8'hFF;
            end
        end
    end

    // 缓存替换策略
    always_ff @(posedge clk) begin
        if (!cache_hit && is_write) begin
            // 找到LRU的way进行替换
            automatic int lru_way = 0;
            automatic logic [7:0] min_lru = 8'hFF;

            for (int way = 0; way < CACHE_WAYS; way++) begin
                if (l1_cache[head_id][way].lru_count < min_lru) begin
                    min_lru = l1_cache[head_id][way].lru_count;
                    lru_way = way;
                end
            end

            // 写回被替换的数据到L2
            if (l1_cache[head_id][lru_way].valid) begin
                // 触发L2写入
                l2_write_req <= 1'b1;
            end

            // 写入新数据
            l1_cache[head_id][lru_way].tag <= position;
            l1_cache[head_id][lru_way].k_data <= write_data;
            l1_cache[head_id][lru_way].valid <= 1'b1;
        end
    end
endmodule

存储层次设计要点:

  1. L1 Cache (BRAM):存储最热点的KV对,典型容量2-4MB
  2. L2 Cache (URAM/HBM):存储次热点数据,容量64-256MB
  3. DDR/HBM主存:存储完整KV-cache,容量2-8GB

缓存性能优化技巧:

// 预取控制器
module kv_prefetcher #(
    parameter PREFETCH_DEPTH = 4,
    parameter CACHE_LINE_SIZE = 512  // bytes
) (
    input  logic        clk,
    input  logic        rst_n,

    // 访问模式监控
    input  logic [11:0] current_position,
    input  logic        position_valid,

    // 预取请求
    output logic        prefetch_req,
    output logic [11:0] prefetch_positions[PREFETCH_DEPTH]
);

    // 访问模式检测
    typedef enum {SEQUENTIAL, STRIDED, RANDOM} pattern_t;
    pattern_t access_pattern;

    logic [11:0] position_history[8];
    logic [11:0] stride;

    always_ff @(posedge clk) begin
        if (position_valid) begin
            // 更新历史
            for (int i = 7; i > 0; i--) begin
                position_history[i] <= position_history[i-1];
            end
            position_history[0] <= current_position;

            // 检测访问模式
            automatic logic [11:0] diff1 = position_history[0] - position_history[1];
            automatic logic [11:0] diff2 = position_history[1] - position_history[2];

            if (diff1 == diff2 && diff1 != 0) begin
                access_pattern <= STRIDED;
                stride <= diff1;
            end else if (diff1 == 1) begin
                access_pattern <= SEQUENTIAL;
                stride <= 1;
            end else begin
                access_pattern <= RANDOM;
            end
        end
    end

    // 生成预取请求
    always_comb begin
        prefetch_req = 1'b0;

        if (access_pattern != RANDOM) begin
            prefetch_req = 1'b1;
            for (int i = 0; i < PREFETCH_DEPTH; i++) begin
                prefetch_positions[i] = current_position + (i+1) * stride;
            end
        end
    end
endmodule

12.3.2 压缩与量化策略

为了减少存储需求,可以对KV-cache进行压缩:

// KV压缩模块
module kv_compressor #(
    parameter HEAD_DIM = 128,
    parameter COMPRESS_RATIO = 4  // 4:1压缩
) (
    input  logic [15:0]  kv_fp16[HEAD_DIM],      // 原始FP16
    output logic [3:0]   kv_int4[HEAD_DIM],      // 量化INT4
    output logic [15:0]  scale,                   // 量化尺度
    output logic [15:0]  zero_point              // 零点
);

    // 动态量化范围计算
    logic [15:0] max_val, min_val;

    always_comb begin
        // 找最大最小值
        max_val = kv_fp16[0];
        min_val = kv_fp16[0];

        for (int i = 1; i < HEAD_DIM; i++) begin
            if (kv_fp16[i] > max_val) max_val = kv_fp16[i];
            if (kv_fp16[i] < min_val) min_val = kv_fp16[i];
        end

        // 计算量化参数
        scale = (max_val - min_val) / 15;  // INT4范围
        zero_point = -min_val / scale;

        // 执行量化
        for (int i = 0; i < HEAD_DIM; i++) begin
            kv_int4[i] = (kv_fp16[i] / scale) + zero_point;
        end
    end
endmodule

压缩技术对比:

  • INT8量化:2倍压缩,精度损失<0.1%
  • INT4量化:4倍压缩,需要精细校准
  • 稀疏存储:仅存储重要token,可达10倍压缩

12.3.3 动态内存管理

支持多batch和可变长度序列的动态分配:

// KV-Cache动态分配器
module kv_allocator #(
    parameter MAX_BATCH = 16,
    parameter MAX_TOTAL_LEN = 65536,
    parameter BLOCK_SIZE = 64        // 分配粒度
) (
    input  logic        clk,
    input  logic        rst_n,

    // 分配请求
    input  logic        alloc_req,
    input  logic [3:0]  batch_id,
    input  logic [15:0] seq_len,

    // 释放请求
    input  logic        free_req,
    input  logic [3:0]  free_batch_id,

    // 分配结果
    output logic [15:0] base_addr[MAX_BATCH],
    output logic        alloc_success,
    output logic [31:0] free_blocks
);

    // 空闲块位图
    logic block_free_map[MAX_TOTAL_LEN/BLOCK_SIZE];

    // 分配表
    typedef struct {
        logic [15:0] base_block;
        logic [15:0] num_blocks;
        logic        valid;
    } alloc_entry_t;

    alloc_entry_t alloc_table[MAX_BATCH];

    // First-fit分配算法
    always_ff @(posedge clk) begin
        if (alloc_req) begin
            automatic int required_blocks = (seq_len + BLOCK_SIZE - 1) / BLOCK_SIZE;
            automatic int free_run = 0;
            automatic int start_block = 0;

            alloc_success <= 1'b0;

            // 搜索连续空闲块
            for (int i = 0; i < MAX_TOTAL_LEN/BLOCK_SIZE; i++) begin
                if (block_free_map[i]) begin
                    if (free_run == 0) start_block = i;
                    free_run++;

                    if (free_run >= required_blocks) begin
                        // 找到足够空间,执行分配
                        alloc_table[batch_id].base_block <= start_block;
                        alloc_table[batch_id].num_blocks <= required_blocks;
                        alloc_table[batch_id].valid <= 1'b1;

                        // 标记已分配
                        for (int j = 0; j < required_blocks; j++) begin
                            block_free_map[start_block + j] <= 1'b0;
                        end

                        alloc_success <= 1'b1;
                        break;
                    end
                end else begin
                    free_run = 0;
                end
            end
        end
    end
endmodule

内存管理策略:

  1. 预分配池:为常见序列长度预留内存池
  2. 碎片整理:定期合并空闲块
  3. 优先级调度:为重要请求预留资源

12.3.4 PagedAttention实现

PagedAttention通过虚拟内存管理技术优化KV-cache利用率:

// PagedAttention内存管理器
module paged_attention_manager #(
    parameter PAGE_SIZE = 16,        // 每页token数
    parameter NUM_PAGES = 4096,      // 总页数
    parameter MAX_SEQUENCES = 256   // 最大并发序列
) (
    input  logic        clk,
    input  logic        rst_n,

    // 序列管理接口
    input  logic        seq_alloc_req,
    input  logic [7:0]  seq_id,
    input  logic [15:0] initial_length,

    input  logic        seq_append_req,
    input  logic [7:0]  append_seq_id,
    input  logic [3:0]  num_new_tokens,

    // 页表查询接口
    input  logic [7:0]  query_seq_id,
    input  logic [15:0] query_position,
    output logic [11:0] physical_page,
    output logic [3:0]  page_offset,
    output logic        page_valid
);

    // 页表结构
    typedef struct {
        logic [11:0] physical_pages[2048];  // 虚拟页到物理页映射
        logic [15:0] seq_length;
        logic [10:0] num_pages;
        logic        valid;
    } page_table_entry_t;

    page_table_entry_t page_tables[MAX_SEQUENCES];

    // 空闲页位图
    logic page_free_map[NUM_PAGES];
    logic [11:0] free_page_count;

    // 页分配器
    function automatic logic [11:0] allocate_page();
        for (int i = 0; i < NUM_PAGES; i++) begin
            if (page_free_map[i]) begin
                page_free_map[i] = 1'b0;
                free_page_count--;
                return i;
            end
        end
        return 12'hFFF; // 分配失败
    endfunction

    // 序列分配处理
    always_ff @(posedge clk) begin
        if (seq_alloc_req) begin
            automatic int pages_needed = (initial_length + PAGE_SIZE - 1) / PAGE_SIZE;

            if (free_page_count >= pages_needed) begin
                page_tables[seq_id].valid <= 1'b1;
                page_tables[seq_id].seq_length <= initial_length;
                page_tables[seq_id].num_pages <= pages_needed;

                // 分配物理页
                for (int i = 0; i < pages_needed; i++) begin
                    page_tables[seq_id].physical_pages[i] <= allocate_page();
                end
            end
        end

        // 序列追加处理
        if (seq_append_req) begin
            automatic int current_pages = page_tables[append_seq_id].num_pages;
            automatic int current_len = page_tables[append_seq_id].seq_length;
            automatic int new_len = current_len + num_new_tokens;
            automatic int new_pages = (new_len + PAGE_SIZE - 1) / PAGE_SIZE;

            // 需要分配新页
            if (new_pages > current_pages) begin
                for (int i = current_pages; i < new_pages; i++) begin
                    page_tables[append_seq_id].physical_pages[i] <= allocate_page();
                end
                page_tables[append_seq_id].num_pages <= new_pages;
            end

            page_tables[append_seq_id].seq_length <= new_len;
        end
    end

    // 地址转换
    always_comb begin
        automatic int virtual_page = query_position / PAGE_SIZE;
        page_offset = query_position % PAGE_SIZE;

        if (page_tables[query_seq_id].valid && 
            virtual_page < page_tables[query_seq_id].num_pages) begin
            physical_page = page_tables[query_seq_id].physical_pages[virtual_page];
            page_valid = 1'b1;
        end else begin
            physical_page = 12'h0;
            page_valid = 1'b0;
        end
    end
endmodule

PagedAttention优势:

  • 内存效率:消除序列间的内存碎片,利用率提升20-30%
  • 动态扩展:支持序列长度动态增长,无需预分配
  • 共享优化:多序列可共享相同的KV页(如系统提示)

12.3.5 KV-Cache调度优化

针对多请求场景的智能调度策略:

// KV-Cache请求调度器
module kv_cache_scheduler #(
    parameter MAX_REQUESTS = 64,
    parameter NUM_BANKS = 16
) (
    input  logic        clk,
    input  logic        rst_n,

    // 请求输入队列
    input  kv_request_t requests[MAX_REQUESTS],
    input  logic [5:0]  num_requests,

    // 银行分配输出
    output kv_request_t bank_requests[NUM_BANKS],
    output logic        bank_valid[NUM_BANKS]
);

    // 请求优先级计算
    typedef struct {
        logic [5:0]  request_id;
        logic [7:0]  priority;
        logic [3:0]  target_bank;
        logic        is_prefetch;
    } scored_request_t;

    scored_request_t scored_reqs[MAX_REQUESTS];

    // 银行冲突检测和负载均衡
    logic [7:0] bank_load[NUM_BANKS];

    always_ff @(posedge clk) begin
        // 计算每个请求的优先级和目标银行
        for (int i = 0; i < num_requests; i++) begin
            scored_reqs[i].request_id = i;

            // 优先级因素:
            // - 延迟敏感度(生成 > 预填充)
            // - 队列等待时间
            // - 请求大小(小请求优先)
            scored_reqs[i].priority = 
                (requests[i].is_generation ? 8'h80 : 8'h40) +
                (requests[i].wait_cycles >> 2) +
                (8'h20 - requests[i].size[7:3]);

            // 银行映射(基于地址哈希)
            scored_reqs[i].target_bank = requests[i].address[7:4];

            // 标记预取请求
            scored_reqs[i].is_prefetch = requests[i].is_prefetch;
        end

        // 清空银行分配
        for (int b = 0; b < NUM_BANKS; b++) begin
            bank_valid[b] <= 1'b0;
            bank_load[b] <= 8'h0;
        end

        // 贪心调度:按优先级分配到银行
        for (int p = 0; p < num_requests; p++) begin
            // 找最高优先级未调度请求
            automatic int best_req = -1;
            automatic logic [7:0] best_priority = 8'h0;

            for (int i = 0; i < num_requests; i++) begin
                if (scored_reqs[i].priority > best_priority) begin
                    automatic int bank = scored_reqs[i].target_bank;

                    // 检查银行是否可用
                    if (!bank_valid[bank] || 
                        (scored_reqs[i].is_prefetch && bank_load[bank] < 2)) begin
                        best_req = i;
                        best_priority = scored_reqs[i].priority;
                    end
                end
            end

            // 分配请求到银行
            if (best_req >= 0) begin
                automatic int req_id = scored_reqs[best_req].request_id;
                automatic int bank = scored_reqs[best_req].target_bank;

                bank_requests[bank] <= requests[req_id];
                bank_valid[bank] <= 1'b1;
                bank_load[bank]++;

                // 标记已调度
                scored_reqs[best_req].priority <= 8'h0;
            end
        end
    end
endmodule

调度优化效果:

  • 银行冲突减少60%
  • 平均访问延迟降低35%
  • 预取命中率提升至85%

12.4 前馈网络(FFN)加速

12.4.1 FFN计算特征与优化

FFN层占据LLM约35%的计算量,其特征是两个大型矩阵乘法:

// FFN层计算引擎
module ffn_engine #(
    parameter MODEL_DIM = 4096,
    parameter FFN_DIM = 11008,       // 通常为model_dim的2.7倍
    parameter ACTIVATION = "SWIGLU", // RELU, GELU, SWIGLU
    parameter DATA_WIDTH = 16
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // 输入向量
    input  logic [DATA_WIDTH-1:0]   input_vector[MODEL_DIM],
    input  logic                    input_valid,

    // 权重接口(流式读取)
    input  logic [DATA_WIDTH-1:0]   w1_stream[64],  // Gate权重
    input  logic [DATA_WIDTH-1:0]   w2_stream[64],  // Up权重
    input  logic [DATA_WIDTH-1:0]   w3_stream[64],  // Down权重
    input  logic                    weight_valid,

    // 输出
    output logic [DATA_WIDTH-1:0]   output_vector[MODEL_DIM],
    output logic                    output_valid
);

    // SwiGLU激活: FFN(x) = (W1(x) * σ(W2(x))) * W3
    // 其中σ是Swish激活函数

    // 第一阶段:并行计算W1(x)和W2(x)
    logic [DATA_WIDTH-1:0] gate_output[FFN_DIM];
    logic [DATA_WIDTH-1:0] up_output[FFN_DIM];

    // 矩阵乘法单元(分块计算)
    localparam TILE_SIZE = 64;
    localparam NUM_TILES = MODEL_DIM / TILE_SIZE;

    genvar t;
    generate
        for (t = 0; t < NUM_TILES; t++) begin : tile_compute
            matrix_mult_tile #(
                .M(FFN_DIM / TILE_SIZE),
                .K(TILE_SIZE),
                .N(1),
                .DATA_WIDTH(DATA_WIDTH)
            ) gate_tile (
                .clk(clk),
                .a_matrix(w1_stream),  // 权重块
                .b_vector(input_vector[t*TILE_SIZE +: TILE_SIZE]),
                .c_partial(gate_partial[t])
            );

            // Up投影的相同结构
            matrix_mult_tile up_tile (
                .clk(clk),
                .a_matrix(w2_stream),
                .b_vector(input_vector[t*TILE_SIZE +: TILE_SIZE]),
                .c_partial(up_partial[t])
            );
        end
    endgenerate

    // 激活函数单元
    swiglu_activation #(
        .VECTOR_DIM(FFN_DIM),
        .DATA_WIDTH(DATA_WIDTH)
    ) activation_unit (
        .clk(clk),
        .gate_input(gate_output),
        .up_input(up_output),
        .activated_output(activated)
    );

    // 第二阶段:Down投影
    matrix_mult_streaming #(
        .M(MODEL_DIM),
        .K(FFN_DIM),
        .DATA_WIDTH(DATA_WIDTH)
    ) down_projection (
        .clk(clk),
        .input_vector(activated),
        .weight_stream(w3_stream),
        .output_vector(output_vector),
        .valid_out(output_valid)
    );
endmodule

12.4.2 激活函数硬件实现

不同激活函数的高效硬件实现:

// 统一激活函数模块
module activation_unit #(
    parameter DATA_WIDTH = 16,
    parameter FUNCTION_TYPE = "SWIGLU"  // RELU, GELU, SWISH, SWIGLU
) (
    input  logic signed [DATA_WIDTH-1:0] x,
    output logic signed [DATA_WIDTH-1:0] y
);

    generate
        case (FUNCTION_TYPE)
            "RELU": begin
                // ReLU: max(0, x)
                assign y = (x[DATA_WIDTH-1]) ? '0 : x;
            end

            "GELU": begin
                // GELU ≈ 0.5x(1 + tanh(√(2/π)(x + 0.044715x³)))
                // 使用分段线性近似
                logic signed [DATA_WIDTH-1:0] x_abs, gelu_approx;
                assign x_abs = x[DATA_WIDTH-1] ? -x : x;

                always_comb begin
                    if (x_abs < 16'h0666) begin      // |x| < 0.4
                        gelu_approx = (x >>> 1) + (x >>> 3);  // 0.625x
                    end else if (x_abs < 16'h1333) begin  // |x| < 1.2
                        gelu_approx = (x >>> 1) + (x >>> 2);  // 0.75x
                    end else if (x_abs < 16'h2000) begin  // |x| < 2.0
                        gelu_approx = x - (x >>> 3);          // 0.875x
                    end else begin
                        gelu_approx = x;                      // ≈x for large |x|
                    end

                    y = x[DATA_WIDTH-1] ? 
                        (x_abs > 16'h3000 ? '0 : -gelu_approx) : gelu_approx;
                end
            end

            "SWISH": begin
                // Swish: x * sigmoid(x)
                // 使用查找表实现sigmoid
                logic [7:0] lut_addr;
                logic [15:0] sigmoid_val;

                // 8-bit地址的sigmoid LUT
                sigmoid_lut_256 sig_lut (
                    .addr(x[DATA_WIDTH-1:DATA_WIDTH-8]),
                    .sigmoid_out(sigmoid_val)
                );

                // 定点乘法
                mult_fixed #(.WIDTH(DATA_WIDTH)) mult (
                    .a(x),
                    .b(sigmoid_val),
                    .product(y)
                );
            end

            "SWIGLU": begin
                // SwiGLU需要两个输入,这里只实现Swish部分
                // 完整SwiGLU在上层模块实现
                activation_unit #(.FUNCTION_TYPE("SWISH")) swish_inst (
                    .x(x),
                    .y(y)
                );
            end
        endcase
    endgenerate
endmodule

12.4.3 矩阵乘法优化技术

针对FFN的大规模矩阵乘法优化:

// 脉动阵列矩阵乘法器
module systolic_array_gemm #(
    parameter ARRAY_SIZE = 32,      // 32x32脉动阵列
    parameter DATA_WIDTH = 8,       // INT8量化
    parameter ACCUM_WIDTH = 32      // 累加器位宽
) (
    input  logic                    clk,
    input  logic                    rst_n,

    // 输入数据流
    input  logic [DATA_WIDTH-1:0]   a_data[ARRAY_SIZE],    // 从左侧输入
    input  logic [DATA_WIDTH-1:0]   b_data[ARRAY_SIZE],    // 从顶部输入
    input  logic                    data_valid,

    // 输出累加结果
    output logic [ACCUM_WIDTH-1:0]  c_data[ARRAY_SIZE][ARRAY_SIZE],
    output logic                    result_valid
);

    // 处理单元(PE)定义
    typedef struct {
        logic signed [DATA_WIDTH-1:0]  a_reg;
        logic signed [DATA_WIDTH-1:0]  b_reg;
        logic signed [ACCUM_WIDTH-1:0] c_accum;
    } pe_state_t;

    pe_state_t pe_array[ARRAY_SIZE][ARRAY_SIZE];

    // 脉动阵列数据流
    genvar row, col;
    generate
        for (row = 0; row < ARRAY_SIZE; row++) begin : row_gen
            for (col = 0; col < ARRAY_SIZE; col++) begin : col_gen

                always_ff @(posedge clk) begin
                    if (rst_n) begin
                        // 计算MAC
                        pe_array[row][col].c_accum <= 
                            pe_array[row][col].c_accum + 
                            pe_array[row][col].a_reg * pe_array[row][col].b_reg;

                        // 数据传播
                        if (col == 0) begin
                            // 第一列从外部输入
                            pe_array[row][col].a_reg <= a_data[row];
                        end else begin
                            // 向右传播A数据
                            pe_array[row][col].a_reg <= pe_array[row][col-1].a_reg;
                        end

                        if (row == 0) begin
                            // 第一行从外部输入
                            pe_array[row][col].b_reg <= b_data[col];
                        end else begin
                            // 向下传播B数据
                            pe_array[row][col].b_reg <= pe_array[row-1][col].b_reg;
                        end
                    end
                end

                // 输出连接
                assign c_data[row][col] = pe_array[row][col].c_accum;
            end
        end
    endgenerate

    // 控制逻辑
    logic [15:0] cycle_counter;
    always_ff @(posedge clk) begin
        if (data_valid) begin
            cycle_counter <= cycle_counter + 1;
        end

        // 2N-1个周期后结果准备好
        result_valid <= (cycle_counter >= 2*ARRAY_SIZE-1);
    end
endmodule

矩阵乘法优化策略:

  1. 分块计算:将大矩阵分解为适合片上缓存的小块
  2. 数据重用:最大化权重和激活值的重用率
  3. 混合精度:INT8计算,INT32累加,FP16输出
  4. 流水线重叠:计算、数据传输、累加并行执行---

results matching ""

    No results matching ""