面向 XC7Z020 + PS DDR3 + QSPI Flash 的“DDR 流式权重 + PL 专用数据通路”方案。本文重点回答:数据放在哪里、AXI 总线怎么走、PL 内部怎么搭、性能大约多少、瓶颈在哪里、工程上怎么分阶段推进。
推荐目标不是追求 32K 上下文、也不是追求高吞吐服务器式推理,而是做一个 batch=1、上下文 512–2048 token、INT4/W4A16 权重量化、1–5 token/s 级别 的嵌入式 LLM 原型。若 AXI HP 与 DDR 读带宽优化得很好,短上下文下可以冲击 5 token/s 以上;若只用单 HP 口或 DDR 访问不连续,可能只有 1 token/s 左右。
| 问题 | 建议 | 原因 |
|---|---|---|
| 权重放哪里? | DDR3 | INT4 后仍约 240–270 MiB,远超 Zynq-7020 BRAM 和 32MB QSPI。 |
| QSPI 做什么? | 只做启动介质 | 放 FSBL、bitstream、U-Boot/裸机 app、配置表;不适合存完整模型,也不适合运行时流权重。 |
| PL 做什么? | 重计算全部进 PL | GEMV、RMSNorm、RoPE、Attention、SwiGLU、LM Head Top-K 都应在 PL 完成,避免 PS 往返。 |
| PS 做什么? | 系统与语言侧控制 | 启动加载、tokenizer、chat template、采样、UI/串口/网络、错误处理。 |
| 上下文长度? | 先限制 512–2048 | Qwen 支持 32K,但 KV cache 和 attention 读取会让 Zynq-7020 明显降速。 |
| 采样方式? | PL streaming Top-K,PS 采样 | 词表 151,936,不能在 BRAM 保存完整 logits;Top-K 流式维护最省资源。 |
本文按 Qwen2.5-0.5B-Instruct 配置规划。该模型是 decoder-only Transformer,使用 RoPE、SwiGLU、RMSNorm、GQA、QKV bias 与 tied word embeddings。关键配置如下:
| 项目 | 数值 | 对硬件的影响 |
|---|---|---|
| 层数 | 24 | 单 token 需要顺序执行 24 个 block。 |
| hidden size | 896 = 7 × 128 | 非常适合 64/128 维 tile。 |
| intermediate size | 4864 = 38 × 128 | MLP 最大矩阵尺寸,DDR 流量主体。 |
| attention heads | 14 | Q 维度为 14 × 64 = 896。 |
| KV heads | 2 | GQA 显著减少 KV cache:每层每 token 只存 K/V 各 128 维。 |
| head dim | 64 | attention dot product 较小,适合流式处理。 |
| vocab size | 151,936 = 1187 × 128 | LM head 非常大,是隐藏瓶颈之一。 |
| max position | 32,768 | 软件上支持,硬件演示不建议直接开满。 |
| tie_word_embeddings | true | embedding 与 LM head 共享一份权重,省存储但不省 LM head 计算。 |
按配置估算,单层主要矩阵参数如下:
| 模块 | 矩阵尺寸 | 参数量/层 | 说明 |
|---|---|---|---|
| Q projection | 896 × 896 | 802,816 | 输出 14 个 Q head。 |
| K projection | 896 × 128 | 114,688 | 2 个 KV head。 |
| V projection | 896 × 128 | 114,688 | 2 个 KV head。 |
| O projection | 896 × 896 | 802,816 | attention 输出回 hidden。 |
| MLP gate/up/down | 3 × 896 × 4864 | 13,074,432 | 最大权重流量来源。 |
| 每层合计 | — | 14,909,440 | 不含小量 norm/bias。 |
| 24 层 block | — | 357,826,560 | 约 0.36B non-embedding 参数。 |
| Embedding / tied LM head | 151,936 × 896 | 136,134,656 | 存一份,但 decode 每 token 要读完整矩阵做输出投影。 |
| 总计 | — | 493,961,216 | 与官方 0.49B 量级一致。 |
| 格式 | 全模型权重 | 24 层 block | Embedding/LM head | 结论 |
|---|---|---|---|---|
| BF16/FP16 | ≈942 MiB | ≈682.5 MiB | ≈259.7 MiB | 几乎吃满 1GB DDR,不现实。 |
| INT8 | ≈471 MiB | ≈341.3 MiB | ≈129.8 MiB | 能放 DDR,但带宽压力较大。 |
| INT4 | ≈235.5 MiB | ≈170.6 MiB | ≈64.9 MiB | 推荐主方案。 |
| INT4 + group scale | ≈243–270 MiB | ≈176–190 MiB | ≈67–75 MiB | 考虑 scale、padding、descriptor 后的工程体积。 |
按 ALINX AX7020 类开发板估算:Zynq-7020 片上 PL 资源约为 85K logic cells、4.9Mb BRAM、220 DSP slices;ALINX AX7020 资料显示常见配置为 XC7Z020-2CLG400I、两片 4Gbit DDR3 合计 8Gbit/1GB、256Mbit/32MB QSPI。
| 资源 | 量级 | 对本项目的含义 |
|---|---|---|
| PS DDR3 | 通常 1GB,32-bit DDR3 | 模型权重、KV cache、scratch、tokenizer、运行时都放这里。 |
| QSPI Flash | 256Mbit = 32MB | 只能放启动镜像、bitstream、配置;不能放 INT4 全模型。 |
| BRAM | 4.9Mb ≈ 612.5KB | 只能放 tile buffer、activation、norm/bias、DMA FIFO、少量表。 |
| DSP | 220 | 可做 32–128 lane 级 GEMV/attention MAC 阵列;资源很紧。 |
| PS | 双核 Cortex-A9 | 适合控制与 tokenizer,不适合承担大矩阵计算。 |
具体物理地址要看你使用 Linux、裸机、PetaLinux 还是 Vitis bare-metal。下面是推荐的逻辑布局;重点是给 PL 一段连续、物理地址稳定、cache 不干扰的 DDR 区域。
| 区域 | 建议容量 | 访问方 | 说明 |
|---|---|---|---|
| Boot/runtime/OS | 裸机:16–64 MiB;Linux:128–256 MiB+ | PS | Linux 方便文件加载和网络,裸机更确定且省 DDR。 |
| Quantized model region | 280–320 MiB 预留 | PL 读为主,PS 启动写入 | W4 权重、scale、bias、descriptor,按 layer 和矩阵顺序线性排列。 |
| KV cache region | 16 MiB/1K token 预留;64 MiB 可覆盖 4K FP16 | PL 读写 | 理论 FP16 KV:12 MiB/1K token;预留额外对齐与管理空间。 |
| Activation/scratch DDR | 8–32 MiB | PL/PS | 主要用于调试、batched prefill、错误回读;正常 decode activation 在 BRAM。 |
| Tokenizer / vocab / prompt | 8–32 MiB | PS | Qwen tokenizer 与 chat template 放 PS 侧处理。 |
| Top-K / command ring | <1 MiB | PS/PL | PL 输出 top-k token id + logit,PS 采样后写下一个 token。 |
Zynq-7020 BRAM 只有约 612KB。它的角色是缓存当前计算 tile,不是缓存模型层权重。
| BRAM 用途 | 建议量级 | 说明 |
|---|---|---|
| Activation/residual 双缓冲 | 16–32KB | hidden=896,int16/int32 都很小;多留给 norm 与 residual。 |
| MLP 中间向量 | 32–64KB | gate/up/down 的 4864 维向量可完整放下,也可 tile。 |
| Weight stream FIFO | 128–256KB | 多 HP 口时需要足够 FIFO 吸收 DDR 抖动。 |
| KV attention tile | 64–128KB | 例如一次读 128 token 的 K/V tile。 |
| Norm/bias/constants | 96–140KB | 所有 RMSNorm 权重约 88KB FP16;QKV bias 约 55KB FP16,可按需压缩/分批。 |
| Top-K、softmax LUT、microcode | 32–64KB | 在线 softmax 和 streaming top-k 不需要完整 logits 缓冲。 |
| 内容 | 是否建议放 QSPI | 理由 |
|---|---|---|
| FSBL | 建议 | 标准启动流程。 |
| PL bitstream | 建议 | XC7Z020 bitstream 量级为数 MB,适合 QSPI。 |
| U-Boot / 裸机推理 app | 可行 | 裸机更容易放下;Linux kernel + rootfs 可能需要 SD/网络。 |
| 模型 manifest / layer descriptor | 建议 | 很小,便于版本校验。 |
| Tokenizer 文件 | 视空间而定 | 可放 QSPI 或 SD;PS 读入 DDR 后使用。 |
| 完整 INT4 Qwen2.5-0.5B | 不可能 | 约 243–270 MiB,远超 32MB。 |
Zynq-7000 的关键优势是 PS 和 PL 共享 DDR。PL 侧自定义 AXI master 通过 PS 的 S_AXI_HP 端口读取 DDR 权重/KV。PS 通过 AXI-Lite/GP 端口写控制寄存器,PL 完成一个 token 或一段 prefill 后中断 PS。
| 接口 | 方向 | 用途 | 建议 |
|---|---|---|---|
| M_AXI_GP0/GP1 或 AXI-Lite | PS → PL | 控制寄存器、模型基地址、token 位置、启动命令 | 只做低速控制,不传大数据。 |
| PL → PS IRQ | PL → PS | 单 token 完成、prefill block 完成、错误状态 | 避免每个矩阵中断;一次 token 最多一次中断。 |
| S_AXI_HP0 | PL master → DDR | 权重流 A | 64-bit、长 burst、连续地址。 |
| S_AXI_HP1 | PL master → DDR | 权重流 B / LM head | 用于双路预取,隐藏 DDR latency。 |
| S_AXI_HP2 | PL master ↔ DDR | KV cache 读写 | attention tile 顺序读,当前 token K/V 顺序写。 |
| S_AXI_HP3 | 可选 | scratch、batched prefill、Top-K 写回 | 初版可不用;优化版可加入。 |
| ACP | PL ↔ PS cache coherent | 低容量共享数据 | 不建议用于权重流。HP 更适合大吞吐。 |
每个 decode token 的主流量是完整扫描模型权重:
INT4 权重本体 ≈ 235.5 MiB Group scale / padding / descriptor ≈ 7–35 MiB KV cache 读取:FP16 时 ≈ 12 MiB × context/1024 KV cache 写入:≈ 12 KiB/token,可忽略 因此 1K 上下文时:≈ 255 MiB/token 4K 上下文时:≈ 291 MiB/token 32K 上下文时:≈ 627 MiB/token
这意味着设计成败主要取决于 DDR 顺序读带宽,而不是 DSP 峰值算力。AXI master 必须发长 burst,权重文件必须按实际计算顺序预重排,避免随机读取。
不要为每个矩阵单独做硬件。整个模型 90% 以上计算都可以复用一个高效的 tiled GEMV engine:
| 模型操作 | 是否复用 GEMV | 特殊处理 |
|---|---|---|
| Embedding lookup | 否,直接读一行 | 从 tied embedding 矩阵读 token row,dequant 成 hidden 向量。 |
| QKV projection | 是 | 推荐融合为 1152×896,一次线性扫描。 |
| O projection | 是 | 896×896。 |
| MLP gate/up | 是 | 推荐融合为 9728×896,然后做 SwiGLU。 |
| MLP down | 是 | 896×4864。 |
| LM head | 是 | 151,936×896,输出不保存完整 logits,直接 streaming top-k。 |
| Attention QK/AV | 部分复用 | 可用较小 dot-product 阵列,不需要大 GEMV。 |
| 参数 | 保守初版 | 推荐版 | 激进版 |
|---|---|---|---|
| MAC lanes | 32 | 64–96 | 128+ |
| PL clock | 100 MHz | 125–150 MHz | 150 MHz+ |
| AXI HP | 1 个 HP 口 | 2 个 HP 口读权重 + 1 个 KV | 3–4 个 HP 口并行 |
| 权重量化 | W4A16 | W4A16 / W4A8 混合 | INT4 packed 多 MAC/DSP |
| 预计难度 | 可快速验证 | 建议目标 | 时序与仲裁风险高 |
如果每个矩阵都由 PS 发命令,一次 token 至少要触发数百次寄存器写和同步,调度开销会很明显。建议 PL 内置 microsequencer:PS 只发一次“generate one token / prefill N tokens”命令,PL 根据 descriptor 自动遍历 24 层。
Instruction example: LOAD_EMBED token_id -> X FOR layer = 0..23: RMSNORM X -> XN GEMV QKV[layer], XN -> Q,K,V ROPE Q,K,position KV_WRITE layer, position, K,V ATTEND layer, Q, KV[0:position] -> A GEMV O[layer], A -> Y ADD X,Y -> X RMSNORM X -> XN GEMV GATE_UP[layer], XN -> G,U SWIGLU G,U -> M GEMV DOWN[layer], M -> Y ADD X,Y -> X FINAL_RMSNORM X -> XN GEMV_TOPK EMBED, XN -> top_k IRQ PS
Decode 是生成阶段的主循环。每生成一个 token,模型权重基本都要扫一遍,因此 DDR 带宽成为主导。
| 步骤 | 数据来源 | 数据去向 | 关键硬件 | 备注 |
|---|---|---|---|---|
| 1. token embedding | DDR embedding row | BRAM X[896] | DMA + dequant | 只读一行,流量很小。 |
| 2. RMSNorm | BRAM X + norm weight | BRAM XN | Vector Unit | norm weight 可常驻 BRAM。 |
| 3. QKV GEMV | DDR QKV weights | BRAM Q/K/V | GEMV | QKV 融合读,K/V 输出写 KV cache。 |
| 4. RoPE + KV write | BRAM Q/K/V | DDR KV cache | Vector + DMA write | 只写当前 token 的 K/V。 |
| 5. Attention | DDR history KV | BRAM attention output | Attention Engine | 按 tile 读历史 KV,online softmax。 |
| 6. O GEMV + residual | DDR O weights + BRAM A | BRAM X | GEMV + Vector | 输出回 hidden。 |
| 7. RMSNorm + MLP gate/up | DDR MLP weights | BRAM G/U/M | GEMV + Vector | gate/up 融合,再做 SwiGLU。 |
| 8. Down GEMV + residual | DDR down weights | BRAM X | GEMV + Vector | 完成一层。 |
| 9. 重复 24 层 | DDR layer blobs | BRAM final X | Microsequencer | PS 不介入。 |
| 10. Final RMSNorm | BRAM X | BRAM XN | Vector Unit | 准备 LM head。 |
| 11. LM head Top-K | DDR tied embedding matrix | Top-K ring | GEMV + Top-K | 不保存 151,936 个 logits。 |
| 12. Sampling | Top-K ring | next token id | PS | PS 做 temperature/top-k 采样。 |
如果 prompt 有 256 或 512 token,逐 token prefill 会非常慢,因为每个 token 都要重新扫权重。建议实现一个小批量 prefill 模式,batch size 取 4 或 8,把同一层同一矩阵的权重只读一次,同时处理多个 prompt token。
| 方案 | 实现复杂度 | 效果 | 建议 |
|---|---|---|---|
| 逐 token prefill | 低 | 最慢;512-token prompt 可能数分钟级 | 只用于 bring-up。 |
| B=4 batched prefill | 中 | 权重流量/每 token 约降到 1/4,compute 利用率提高 | 推荐第一版优化。 |
| B=8 或 B=16 | 高 | 更好复用权重,但 BRAM、attention causal mask、控制复杂度上升 | 等 decode 跑稳后再做。 |
| 部分 | 推荐格式 | 原因 |
|---|---|---|
| Dense weights | INT4 group-wise,group=128 | 尺寸压到 240–270 MiB;维度天然 128 对齐。 |
| Activation | INT16 或 block-float 16-bit | 小模型对激进 W4A8 更敏感,初版先保证质量。 |
| Accumulator | INT32 | GEMV 累加必须留足动态范围。 |
| RMSNorm/Softmax | 16/24-bit fixed 或 FP-like | norm、softmax 对输出质量敏感,不建议过早做极低精度。 |
| KV cache | FP16 初版;INT8 优化版 | FP16 简单可靠;INT8 可把 KV 空间和读取减半。 |
| LM head | INT4 初版;可混合 INT8 | LM head 影响 token 选择,若质量差可对 embedding/LM head 升 INT8。 |
model.bin
header
magic, version, quant_type, group_size
hidden=896, inter=4864, layers=24, vocab=151936
base offsets for each region
global constants
final_norm, layer_norms, qkv_bias, rope constants
embedding_tied_lm_head
packed int4 rows [vocab][hidden]
row/group scales
layer[0]
qkv_fused: [1152][896] packed by out_tile/in_tile
o_proj: [896][896]
gate_up: [9728][896]
down_proj: [896][4864]
scales / descriptor
...
layer[23]
...
推荐以 out_tile × in_tile 为基本单元,in_tile 取 128,out_tile 取 16/32/64。每个 tile 内 INT4 权重连续打包,scale 紧随或独立连续存放。
for out_tile in output_dim / OUT_TILE:
zero accum[OUT_TILE]
for in_tile in input_dim / 128:
burst read packed_weight[OUT_TILE][128]
burst read scale[OUT_TILE]
broadcast activation[128]
MAC + scale accumulate
requantize accum -> output vector / top-k
忽略 attention 随上下文增长的部分,线性层每个 decode token 约需要扫描全部参数并做一次乘加:
| 部分 | MAC/token | 占比 |
|---|---|---|
| 24 层 Transformer block | ≈357.8M MAC | ≈72.4% |
| LM head / tied embedding projection | ≈136.1M MAC | ≈27.6% |
| 线性层合计 | ≈494.0M MAC/token | 100% |
Attention 额外计算随上下文长度增长。GQA 降低了 KV 存储,但 Q head 仍是 14 个:
| 上下文长度 | Attention 额外 MAC/token | 相对线性层 | KV 读取 FP16 |
|---|---|---|---|
| 512 | ≈22M | ≈4.5% | ≈6 MiB |
| 1024 | ≈44M | ≈8.9% | ≈12 MiB |
| 2048 | ≈88M | ≈17.8% | ≈24 MiB |
| 4096 | ≈176M | ≈35.7% | ≈48 MiB |
| 32768 | ≈1.41B | ≈285% | ≈384 MiB |
按 INT4 权重 + scale + FP16 KV 读取估算,1K 上下文下约 255 MiB/token,4K 上下文下约 291 MiB/token。实际 token/s 可以粗略按下式估计:
token/s ≈ sustained_DDR_read_bandwidth / bytes_per_token
| 场景 | 持续 DDR 读带宽假设 | 1K ctx 估算 | 4K ctx 估算 | 工程判断 |
|---|---|---|---|---|
| 保守:单 HP / 初版 DMA | 0.35–0.55 GB/s | ≈1.3–2.1 tok/s | ≈1.2–1.9 tok/s | 可 bring-up,但用户体验一般。 |
| 推荐:2 HP 权重 + 1 HP KV | 0.9–1.4 GB/s | ≈3.3–5.2 tok/s | ≈3.0–4.8 tok/s | 比较合理的目标。 |
| 激进:4 HP 调优 | 1.8–2.5 GB/s | ≈6.7–9.3 tok/s | ≈6.2–8.6 tok/s | 有机会,但时序和 DDR 仲裁风险高。 |
| 32K ctx | 1.2 GB/s | ≈1.8 tok/s,且 attention compute 很重 | 不建议作为 Zynq-7020 目标。 | |
494M MAC/token 看起来很大,但在 INT4 权重流式推理里,Zynq-7020 更可能先被 DDR 带宽限制。
| MAC 阵列 | 理论 MAC/s | 494M MAC 计算时间 | 结论 |
|---|---|---|---|
| 32 lanes @100MHz | 3.2 GMAC/s | ≈154 ms | 计算上限约 6.5 tok/s;与 DDR 初版相近。 |
| 64 lanes @125MHz | 8.0 GMAC/s | ≈62 ms | 计算上限约 16 tok/s;通常 DDR 更慢。 |
| 96 lanes @150MHz | 14.4 GMAC/s | ≈34 ms | 明显带宽受限。 |
| 128 lanes @150MHz | 19.2 GMAC/s | ≈26 ms | 需要更高 DDR 吞吐才有意义。 |
| 阶段 | 目标 | 验收指标 |
|---|---|---|
| MVP | 完整生成闭环 | 上下文 ≤512,≥0.5 tok/s,输出可读。 |
| 可演示版 | 优化 DDR 和 Top-K | 上下文 1K,1–3 tok/s,短问答可用。 |
| 优化版 | 多 HP + batched prefill | 上下文 1–2K,3–5 tok/s,prefill 不明显拖慢。 |
| 挑战版 | 极致 AXI/INT4 packing | 短上下文 5 tok/s+,但开发成本高。 |
| 优先级 | 瓶颈 | 表现 | 缓解策略 |
|---|---|---|---|
| 1 | DDR 权重流带宽 | 每 token 约 250–300 MiB 读取,直接决定 token/s | INT4、矩阵重排、长 burst、多 HP、双缓冲、减少随机访问。 |
| 2 | LM head 大词表 | 单 LM head 就要读约 65 MiB INT4 权重,占总流量约 27% | streaming top-k;可选 hot vocab cache/词表裁剪/混合精度,但裁剪会改变模型。 |
| 3 | Prefill 延迟 | 长 prompt 逐 token 处理会非常慢 | batched prefill B=4/8,限制 prompt 长度。 |
| 4 | Attention 随上下文增长 | 4K 后 attention compute/KV 读明显上升;32K 不适合 | 限制上下文、KV INT8、窗口化或摘要,但会改变行为。 |
| 5 | 量化质量 | 0.5B 模型容量小,W4 可能让回答质量下降 | W4A16 起步;敏感矩阵 INT8;离线逐层误差验证。 |
| 6 | PL 资源与时序 | 多 HP + 128 lanes + softmax/top-k 可能难收敛 | 先 32/64 lanes,模块化验证,再扩。 |
| 7 | PS/PL cache coherency | PS 写命令/读 top-k 与 PL DMA 结果不一致 | non-cacheable buffer、cache flush/invalidate、ring buffer 协议。 |
由于 vocab=151,936,最终输出投影矩阵为 151,936×896,即使 tied embedding 让它不额外占一份存储,生成每个 token 仍必须扫描这 136M 个权重来找下一个 token。INT4 下约 64.9 MiB;加 scale 和 padding 后可能接近 70 MiB。
FP16 KV cache 的空间公式:
KV_bytes = layers × context × 2(K,V) × kv_heads × head_dim × 2 bytes
| 上下文 | KV cache FP16 | KV cache INT8 | 建议 |
|---|---|---|---|
| 512 | ≈6 MiB | ≈3 MiB | bring-up 目标。 |
| 1024 | ≈12 MiB | ≈6 MiB | 推荐默认。 |
| 2048 | ≈24 MiB | ≈12 MiB | 推荐上限。 |
| 4096 | ≈48 MiB | ≈24 MiB | 可做优化版。 |
| 32768 | ≈384 MiB | ≈192 MiB | Zynq-7020 不建议。 |
| 任务 | 产物 | 验收 |
|---|---|---|
| 下载 Qwen2.5-0.5B-Instruct,固定 transformers 版本 | PyTorch reference | 能在 PC 上生成固定测试样例。 |
| 实现 W4A16 离线量化 | quantized model.bin | PC 模拟量化输出与 FP16 输出差异可接受。 |
| 按硬件 tile 重排权重 | tiled binary + descriptors | PC 上用同一格式跑出正确 logits。 |
| 制作测试向量 | embedding/layer/logits golden data | 每个 PL 模块都有 golden reference。 |
| 优化项 | 收益 | 风险 |
|---|---|---|
| QKV/gate-up 融合矩阵 | 减少调度和 activation 处理 | 需要修改权重转换器。 |
| 多 HP 并行读 | 提升 DDR 吞吐 | DDR 仲裁、时序、FIFO 复杂。 |
| B=4/8 batched prefill | 显著降低 prompt 首 token 延迟 | 控制复杂度增加。 |
| KV INT8 | KV 空间/读取减半 | attention 质量需验证。 |
| LM head hot-vocab cache | 部分减少常见 token 延迟 | BRAM 很紧,收益有限。 |
| 混合精度敏感矩阵 | 提升输出质量 | DDR 流量增加。 |
Qwen2.5-0.5B 在 ALINX Zynq-7020 上可以做,但必须接受“慢速、强约束、DDR 流式”的实现边界。
比较务实的目标是:用 Qwen2.5-0.5B-Instruct,权重 INT4 放 DDR,PL 做硬化推理核,PS 做系统控制;上下文限制在 1K–2K;最终达到 1–5 token/s 的嵌入式演示效果。这个方案的技术亮点在于完整跑通现代 dense Transformer,而不是高吞吐。
如果项目目标是论文/展示/工程验证,它值得做;如果目标是实用聊天速度,Zynq-7020 会明显吃力,应考虑更高端 Zynq UltraScale+、带 PL DDR 的板卡,或换 100M 级模型。