第十二章: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
);
计算特征分析:
内存带宽瓶颈
- 权重参数量:7B模型约需14GB(FP16)
- KV-cache:每token需要 2 × layers × model_dim × 2 bytes
- 对于2048长度序列,KV-cache可达1.6GB
计算模式差异
- 预填充阶段:计算密集型,可并行处理所有token
- 生成阶段:内存密集型,逐token自回归生成
动态性挑战
- 序列长度可变(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推理中的独特优势:
定制化数据通路
- 可针对特定量化位宽优化(INT4/INT8混合精度)
- 消除不必要的数据移动和格式转换
- 支持非标准数值格式(如BF16、TF32变种)
- 计算与数据流紧密耦合设计
流水线并行
- 层间流水线重叠计算,隐藏内存延迟
- 细粒度任务并行(头级、层级、操作级)
- 自定义缓冲区深度匹配计算吞吐
- 动态流水线深度调整
低批处理延迟
- 适合batch=1的实时服务(首token延迟<10ms)
- 确定性延迟保证(抖动<1ms)
- 无需等待批次聚合的调度开销
- 支持优先级抢占调度
能效比优势
- 典型功耗: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
关键优化技术:
- 分块计算:将大矩阵分解为适合片上存储的小块
- 数值稳定性:使用log-sum-exp技巧避免溢出
- 流水线设计:重叠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
存储层次设计要点:
- L1 Cache (BRAM):存储最热点的KV对,典型容量2-4MB
- L2 Cache (URAM/HBM):存储次热点数据,容量64-256MB
- 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
内存管理策略:
- 预分配池:为常见序列长度预留内存池
- 碎片整理:定期合并空闲块
- 优先级调度:为重要请求预留资源
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
矩阵乘法优化策略:
- 分块计算:将大矩阵分解为适合片上缓存的小块
- 数据重用:最大化权重和激活值的重用率
- 混合精度:INT8计算,INT32累加,FP16输出
- 流水线重叠:计算、数据传输、累加并行执行---