Delete 基于AI Agent开发范式的国产GPU大模型推理算子库优化/baselines/FlashInfer 迁移 Baseline 实战.md

This commit is contained in:
xxhefei 2026-06-04 17:47:03 +08:00
parent 409d474254
commit daf335c155
1 changed files with 0 additions and 809 deletions

View File

@ -1,809 +0,0 @@
# FlashInfer 迁移 Baseline 实战
## 一、教程定位
本教程是参赛训练课程的 **FlashInfer Baseline 入门**模块主要帮助用户快速跑通 FlashInfer 的最小可运行流程。完成本教程后用户应能够完成源码编译、API 调用、正确性测试和 Benchmark 测试并记录一份 baseline 性能结果为后续算子优化提供对比基准。
## 二、学习目标
1. 理解 FlashInfer Attention Kernel 的基本作用与适用场景
2. 完成 FlashInfer 环境、工具链的准备与源码编译
3. 跑通 BatchDecode、BatchPrefill、MLA 等典型算子的 API 调用示例
4. 完成各算子在不同参数配置下的 Benchmark 测试
5. 输出各算子的 Baseline 性能结果记录表为后续算子优化提供对比基准。
## 三、适用对象
适合人群
* 参赛选手需要完成 Baseline 入门模块为后续算子优化做准备
* 软件开发者和Vibe Coding开发希望从事AI相关行业开发以及用智能体方式来做开发工作
* LLM 推理开发者希望了解 FlashInfer Attention Kernel 的性能表现
* 算子优化工程师希望基于MXMACA软件栈在沐曦国产 GPU 上做算子迁移和优化
前置基础
* Python 基础能够运行和修改 Python 脚本
* PyTorch 基础了解MXMACA软化栈的使用
* Linux 命令行能够使用终端执行命令
* 了解 Attention 机制理解 Q/K/V、KV Cache 等基本概念
## 四、前置准备
开始实战前,请确认你已经完成以下准备:
### GPU准备
* 步骤1获取算力券
[https://developer.metax-tech.com/activities/6](https://developer.metax-tech.com/activities/6)
* 登录平台
![metax developer login 1](https://origin.picgo.net/2026/06/04/metax-developer-login-17ae7b237580ceb5a.png)
* 首次登录需要先进行注册(使用邮箱或者手机号进行注册)
![metax developer login 2](https://origin.picgo.net/2026/06/04/metax-developer-login-26db93f87f79ced1b.png)
* 登录成功后进行第二步-邮箱验证,填入自己的邮箱。
![metax developer login 3](https://origin.picgo.net/2026/06/04/metax-developer-login-34fd23fa8432bdff1.png)
* 第三步,提交申请。
![metax developer login 4](https://origin.picgo.net/2026/06/04/metax-developer-login-4b5200e9f6c00c2bf.png)
* 获得兑换码
![metax developer login 5](https://origin.picgo.net/2026/06/04/metax-developer-login-554e19476b3419540.png)
* 步骤2兑换算力和登陆平台
* 访问模力方舟官网:[https://ai.gitee.com/](https://ai.gitee.com/)
* 进入费用中心 - 算力券 ,点击右上角 “兑换”。
![giteeai 兑换 6](https://origin.picgo.net/2026/06/04/giteeai--6e369bba40d2be65c.png)
* 步骤3租用算力
进入算力容器选择沐曦租用算力建议优先选16G显存/32G显存如下图
![giteeai 租用 7](https://origin.picgo.net/2026/06/04/giteeai--7a35e27970245a913.png)
* 步骤4创建实例
基础镜像maca-pytorch:3.7.1.5-torch2.8-py312-ubuntu24.04-amd64
![giteeai 实例 8](https://origin.picgo.net/2026/06/04/giteeai--8840408058665bced.png)
![giteeai 实例 9](https://origin.picgo.net/2026/06/04/giteeai--958d645e340553967.png)
![giteeai 实例 10](https://origin.picgo.net/2026/06/04/giteeai--10a0ef0e5bcd186ba3.png)
* 步骤5选择工具-lab进入实例环境
![giteeai 实例 11](https://origin.picgo.net/2026/06/04/giteeai--1127a207a2ab9763db.png)
* 步骤6在JupyterLab Terminal中检查运行环境的配置。
![giteeai 实例 12](https://origin.picgo.net/2026/06/04/giteeai--12c1772b12867f6be0.png)
* 确认沐曦 GPU 可见--可以使用`mx-smi`命令查看
![giteeai 实例 13](https://origin.picgo.net/2026/06/04/giteeai--1374ac2ed7c86248b2.png)
### 环境依赖准备
```bash
pip install flashinfer torch pandas numpy
```
### opencode Agent安装
```shell
curl -fsSL https://opencode.ai/install | bash
```
说明:
自动下载最新版本 自动配置环境 适用于 macOS / Linux / WSL 安装完成后还会提示你怎么用OpenCode 包含免费模式使用方式
```shell
cd # 进入项目目录
opencode
```
### 代码准备
* 已进入指定项目目录;
* 准备测试脚本和 Benchmark 脚本。
| 命令 | 说明 |
| --- | --- |
| `python bench_batch_decode.py` | 运行 Batch Decode 基准测试 |
| `python bench_batch_prefill_paged.py` | 运行 Batch Prefill (Paged KV Cache) 基准测试 |
| `python bench_batch_prefill_ragged.py` | 运行 Batch Prefill (Ragged KV Cache) 基准测试 |
| `python bench_batch_mla.py` | 运行 MLA (Multi-head Latent Attention) 基准测试 |
## 五、知识预备
### Flashinfer基础知识
FlashInfer 是一个用于推理的库和内核生成器能够在多种 GPU 架构上实现最先进的性能。它为注意力、GEMM和MoE操作提供统一API支持包括FlashAttention-2/3、cuDNN、CUTLASS和TensorRT-LLM在内的多种后端实现。
https://github.com/flashinfer-ai/flashinfer
#### Attention Kernels
* Paged and Ragged KV-Cache: Efficient memory management for dynamic batch serving
* Decode, Prefill, and Append: Optimized kernels for all attention phases
* MLA Attention: Native support for DeepSeek's Multi-Latent Attention
* Cascade Attention: Memory-efficient hierarchical KV-Cache for shared prefixes
* Sparse Attention: Block-sparse and variable block-sparse patterns
* POD-Attention: Fused prefill+decode for mixed batching
#### LLM推理阶段重要概念
* **Prefill阶段**prefill 阶段是指处理输入 prompt 的阶段
* 输入用户一次性给出的完整 prompt长度为 seq\_len
* 计算 prompt 中的每个 token 并行计算注意力生成第一个输出 token  KV cache
* 特点:这是**计算密集型compute-bound**阶段因为需要做完整的 seq\_len x seq\_len 注意力矩阵乘法
* **decode阶段**
* 每次只生成 1  token利用 prefill 阶段填充好的 KV cache 做自回归生成
* **显存带宽密集型memory-bound**瓶颈在从显存读取 KV cache 而非计算
prefill = 并行处理用户输入decode = 逐个生成回答 token
#### KV Cache原理
标准的多头自注意力Multi-Head Self-Attention
$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d\_k}}\right)V$
其中 $Q$、$K$、$V$ 分别是查询Query、键Key和值Value矩阵$d\_k$是每个注意力头的维度。在训练阶段由于有 causal mask因果掩码整个序列的 $Q$、$K$、$V$可以并行计算。但在推理的自回归生成阶段,当我们生成第 $t$个 token 
位置 1  $t-1$的 $K$、$V$向量在生成第 $t$个 token 时就已经计算过了。如果不做任何缓存每一步都要重新从头计算所有历史 token  $K$和 $V$,这意味着生成第 $t$  token 的复杂度是 $O(t)$,整个序列生成的总复杂度是 $O(n^2)$在长序列下极其低效。KV Cache 的思路非常直观把已经计算过的 Key  Value 向量缓存起来下一步直接拿来用。
#### Ragged KV-Cache非分页缓存
传统的实现中,显存分配要求是**物理连续**的。
* **分配逻辑:** 由于不知道用户最终会生成多少个 Token系统只能“往大了猜”按照模型允许的最大长度例如 2048为每个新请求一次性预留一长条连续的显存。
* **问题:**这样的分配方法容易造成**内部显存碎片化,显存利用率不高**很容易引起“gpu显存利用不足”的问题进而影响模型推理时的吞吐量。
#### Paged KV-Cache分页缓存
借用操作系统虚拟内存的思想,将物理显存切分成大小固定的“页/块”Blocks
* **分配逻辑:** 新请求到来时不再预留连续大空间而是先只分配一个 Block比如 16  Token 的大小。随着模型的逐字解码Decode当这个 Block 填满时再向系统动态申请下一个 Block
* **按需分配:** 生成几个 Token 就用几个位置几乎消灭了内部碎片。
* **物理离散,逻辑连续:** 不同的 Block 在物理显存上完全可以是分散的通过 Block Table 记录映射彻底消灭了外部碎片新请求随时可以插空进入。
![page kvcache](https://origin.picgo.net/2026/06/04/page-kvcachedd5f9b5d0f1f1f4b.png)
flashInfer 将分页 KV Cache 视作一个**块稀疏矩阵**,并巧妙地使用了 **CSR (Compressed Sparse Row)** 格式来建立索引。图 1 中的三个关键数组就是 CSR 格式的体现
* `**kv_page_indices**` **(页索引池):** 把所有请求当前占用的物理页编号,按顺序“平铺”拼接在一起。
* 图中蓝、橙、绿三个请求分别使用了 `[0, 5, 8]`、`[1, 6, 7]`、`[3, 4]`。
* 拼接后得到:`[0, 5, 8, 1, 6, 7, 3, 4]`。
* `**kv_indptr**` **(索引指针):** 用于标记每个请求在 `kv_page_indices` 中的**起始和结束位置**。
* 数组长度固定为 `num_requests + 1`
* 图中数值为 `[0, 3, 6, 8]`。这意味着:
* 请求 0 (蓝) 的页索引在 `kv_page_indices` 的 `0` 到 `3` 之间(即 `[0, 5, 8]` 3 
* 请求 1 (橙) 的页索引在 `3` 到 `6` 之间(即 `[1, 6, 7]` 3 
* 请求 2 (绿) 的页索引在 `6` 到 `8` 之间(即 `[3, 4]` 2 
* `**kv_last_page_lens**` **(尾页有效长度):** 由于一个请求的 Token 总数很少能刚好被 `page_size`每页容量图中为 8整除最后一个页通常是不满的。这个数组记录了每个请求**最后一页实际存储的 Token 数量**。
* 图中分别为 `[6, 4, 7]`
 1 左下角展示了在解码Decode/Append阶段新生成的 Token 是如何追加到 KV Cache 中的。这里的细节非常值得注意
* `**qo_indptr = [0, 4, 6, 9]**`: 这表示当前批次中,各个请求**新追加**的 Token 数量Query/Output
* 请求 0 (蓝) 追加了 `4 - 0 = 4`  Token。
* 请求 1 (橙) 追加了 `6 - 4 = 2`  Token。
* 请求 2 (绿) 追加了 `9 - 6 = 3`  Token。
### Flashinfer API
#### Ragged Tensor不规则张量
![ragged tensor](https://origin.picgo.net/2026/06/04/ragged-tensordb0121cf56ef98f4.png)
假设有 3 个请求长度分别是 5, 3, 4。所以batchsize=3用户请求的seq\_len分别为 5,3,4因为在深度学习中由于底层的矩阵运算要求张量Tensor必须是**规整的矩形**(比如 `[batch_size, seq_len, hidden_dim]`当一个 Batch 中包含**不同长度的句子时**,我们通常会按最长的那句话进行 **Padding补零**。而图中的核心思想是:**“拒绝 Padding把所有 Token 拍扁拼接到一起。”**
这里不使用 `[3(batch_size), 5(seq_len), num_heads, head_dim]` 这样的多维规整张量而是把所有请求的 Token 首尾相连打包成一个长度为 12 5+3+4的连续一维数组。这就是图中的 `data: (12, num_heads, head_dim)` = `data(seq_len(5+3+4)num_headshead_dim)`的由来。
既然数据被拼接到了一起系统怎么知道哪个 Token 属于哪个请求呢这就是 `indptr`Index Pointer索引指针发挥作用的地方。
* **颜色块代表不同的请求Request**
* 🟦 蓝色Request 0长度为 5
* 🟧 橙色Request 1长度为 3
* 🟩 绿色Request 2长度为 4
* `**indptr = [0, 5, 8, 12]**`****
* 这是一个一维数组,用来记录每个请求在 `data` 数组里的**起始和结束位置**。它的长度永远是 `num_requests + 1`且第一个元素必定是 0。
* Request 0 的数据在 `data[0:5]` (对应图中 `indptr[0]` 到 `indptr[1]`
* Request 1 的数据在 `data[5:8]` (对应图中 `indptr[1]` 到 `indptr[2]`
* Request 2 的数据在 `data[8:12]` (对应图中 `indptr[2]` 到 `indptr[3]`
**序列长度计算:** 第 $i$个请求的长度Sequence length可以直接通过 $indptr\[i+1\] - indptr\[i\]$算出来。
**总 Token ** `indptr` 的最后一个元素(即 `indptr[-1]`在这里是 12就是这个 Batch 中所有 Token 的总和。
**数据切片读取:** 当你需要单独提取第 $i$个请求的 $Q/K/V$矩阵时,只需要执行切片操作 `data[indptr[i]:indptr[i+1]]` 即可精准拿取没有任何多余的 Padding 元素。
#### flashinfer.BatchPrefillWithRaggedKVCacheWrapper() 类
参考链接https://docs.flashinfer.ai/api/attention.html#flashinfer.prefill.BatchPrefillWithRaggedKVCacheWrapper
1. 构造函数:
```Python
__init__(float_workspace_buffer: Tensor, kv_layout: str = 'NHD', use_cuda_graph: bool = False, qo_indptr_buf: Tensor | None = None, kv_indptr_buf: Tensor | None = None, custom_mask_buf: Tensor | None = None, mask_indptr_buf: Tensor | None = None, backend: str = 'auto', jit_args: List[Any] | None = None, jit_kwargs: Dict[str, Any] | None = None) → None
```
2. 常用初始化参数理解:
1. `_**float_workspace_buffer**_``(``_torch.Tensor_``)`用户预留的浮点工作空间缓冲区用于在 split-k 算法中存储中间的注意力计算结果。建议大小为 128MB其设备类型需与输入张量所在的设备保持一致。
2. `_**kv_layout**_`_****_输入 K/V 张量的显存布局格式可以是 `NHD` 或 `HND`。默认_**'NHD'**_
* NHD`(seq_len, num_heads, head_dim)`
* HND`(num_heads, seq_len, head_dim)`
3. `_**backend**_`_****_底层实现引擎。可选值包括 `auto`、`fa2`、`fa3`、`cudnn`、`cutlass` 或 `cute-dsl`,默认值为 `auto`。系统会根据显卡架构自动选择最优后端。其中 `cute-dsl` 是专为最新一代 Blackwell 架构 SM100+ 系列)准备的算子。
4. `_**jit_args**_` _**&**_ `_**jit_kwargs**_`_****_用于即时编译JIT, Just-In-Time的参数列表和字典参数。如果提供框架将会在运行时动态编译底层算子手动实现算子否则直接使用预编译好的默认算子。
3. **.Plan()** 方法
  **Plan()** 方法的作用就是“运筹帷幄”的预处理AOT, Ahead-of-Time Setup阶段**任务规划:** 接收当前 Batch 中所有请求的形状、长度和硬件规格计算出最优的 GPU 算力调度方案。**显存分配:** 在底层预先创建并缓存计算所需的辅助数据结构和临时工作空间Workspace。**解耦计算:** 将“准备工作”与真正的“执行工作(`run` 方法)”解耦。调用 `plan` 搭建好“脚手架”后,后续调用 `run`  GPU 就可以直接根据图纸极速开工从而把 **CPU 调度开销降到最低**。`plan()` 方法必须在任何 `run()`之前
```Python
plan(qo_indptr: Tensor, kv_indptr: Tensor, num_qo_heads: int, num_kv_heads: int, head_dim_qk: int, head_dim_vo: int | None = None, custom_mask: Tensor | None = None, packed_custom_mask: Tensor | None = None, causal: bool = False, pos_encoding_mode: str = 'NONE', use_fp16_qk_reduction: bool = False, window_left: int = -1, logits_soft_cap: float | None = None, sm_scale: float | None = None, rope_scale: float | None = None, rope_theta: float | None = None, q_data_type: str | dtype = 'float16', kv_data_type: str | dtype | None = None, o_data_type: str | dtype | None = None, non_blocking: bool = True, prefix_len_ptr: Tensor | None = None, token_pos_in_items_ptr: Tensor | None = None, token_pos_in_items_len: int = 0, max_item_len_ptr: Tensor | None = None, fixed_split_size: int | None = None, disable_split_kv: bool = False, seq_lens: Tensor | None = None, seq_lens_q: Tensor | None = None, max_token_per_sequence: int | None = None, max_sequence_kv: int | None = None, v_indptr: Tensor | None = None, o_indptr: Tensor | None = None) → None
```
* 关键参数
* `_**qo_indptr**_`_****_Query/Output 张量的索引指针数组indptr形状为 `[batch_size + 1]`。用于在 Ragged 连续内存中定位每个请求的 Query 边界。
* `_**kv_indptr**_`_****_Key/Value 张量的索引指针数组形状为 `[batch_size + 1]`
* `_**num_qo_heads**_`_****_ Query  Output 的注意力头Attention Heads数量。
* `_**num_kv_heads**_`_****_Key  Value 的注意力头数量。
* `_**head_dim_qk**_`_****_Query  Key 张量中每个注意力头的维度大小。
* `_**head_dim_vo**_`_****_Value  Output 张量中每个头的维度大小。如果不提供默认与 `head_dim_qk` 相同。
* `_**causal**_`_****_ 是否对注意力矩阵应用因果掩码Causal Mask即屏蔽未来信息。如果在 `plan()` 中已经提供了自定义的 `mask` 参数,此选项将被忽略。
* `_**q_data_type**_`_****_Query 张量的数据类型默认为 `torch.float16`
* `_**kv_data_type**_`_****_Key/Value 张量的数据类型。如果不提供默认与 `q_data_type` 一致。
注意:`plan()` 方法包含复杂的 Python 层逻辑和动态显存分配因此**不能**在 CUDA Graph 捕获环境或 `torch.compile` 环境中被追踪或调用。
1. **.run()** 方法 
```Python
run(q: Tensor, k: Tensor, v: Tensor, *args, out: Tensor | None = None, lse: Tensor | None = None, return_lse: Literal[False] = False, enable_pdl: bool | None = None, kv_cache_sf: torch.Tensor | Tuple[torch.Tensor, torch.Tensor] | None = None) → Tensor
```
* 关键参数
* `_**q**_`_****_Query查询张量。
* **形状:** `[qo_indptr[-1], num_qo_heads, head_dim_qk]`
* **解释:** 这里的 `qo_indptr[-1]` 正是我们之前提到的 Ragged Tensor 中所有 Token 数量的总和。它表示把 Batch 里所有的 Query 拍扁到了一个一维的连续维度上。
* `_**k**_`_****_Key张量。
* **形状:** `[kv_indptr[-1], num_kv_heads, head_dim_qk]`
* `_**v**_`_****_Value张量。
* **形状:** `[kv_indptr[-1], num_kv_heads, head_dim_vo]`
#### flashinfer.BatchPrefillWithPagedKVCacheWrapper() 类
参考链接https://docs.flashinfer.cn/api/attention.html#flashinfer.prefill.BatchPrefillWithPagedKVCacheWrapper
1. 构造函数
```Python
__init__(float_workspace_buffer: Tensor, kv_layout: str = 'NHD', use_cuda_graph: bool = False, qo_indptr_buf: Tensor | None = None, paged_kv_indptr_buf: Tensor | None = None, paged_kv_indices_buf: Tensor | None = None, paged_kv_last_page_len_buf: Tensor | None = None, custom_mask_buf: Tensor | None = None, mask_indptr_buf: Tensor | None = None, backend: str = 'auto', jit_args: List[Any] | None = None, jit_kwargs: Dict[str, Any] | None = None) → None
```
参数含义同**BatchPrefillWithRaggedKVCacheWrapper()** 的构造函数参数相同
2. .Plan() 方法
```Python
plan(qo_indptr: Tensor, paged_kv_indptr: Tensor, paged_kv_indices: Tensor, paged_kv_last_page_len: Tensor, num_qo_heads: int, num_kv_heads: int, head_dim_qk: int, page_size: int, head_dim_vo: int | None = None, custom_mask: Tensor | None = None, packed_custom_mask: Tensor | None = None, causal: bool = False, pos_encoding_mode: str = 'NONE', use_fp16_qk_reduction: bool = False, sm_scale: float | None = None, window_left: int = -1, logits_soft_cap: float | None = None, rope_scale: float | None = None, rope_theta: float | None = None, q_data_type: str | dtype = 'float16', kv_data_type: str | dtype | None = None, o_data_type: str | dtype | None = None, non_blocking: bool = True, prefix_len_ptr: Tensor | None = None, token_pos_in_items_ptr: Tensor | None = None, token_pos_in_items_len: int = 0, max_item_len_ptr: Tensor | None = None, seq_lens: Tensor | None = None, seq_lens_q: Tensor | None = None, block_tables: Tensor | None = None, max_token_per_sequence: int | None = None, max_sequence_kv: int | None = None, fixed_split_size: int | None = None, disable_split_kv: bool = False) → None
```
* 关键参数
* `**o_indptr**``(``_torch.Tensor_``)` – 查询/输出张量的 indptr形状`[batch_size + 1]`。
* `**paged_kv_indptr**``(``_torch.Tensor_``)`  分页 kv-cache  indptr形状`[batch_size + 1]`。
* `**paged_kv_indices**``(``_torch.Tensor_``)`  分页 kv-cache 的页索引形状`[paged_kv_indptr[-1]]`。
* `**paged_kv_last_page_len**``(``_torch.Tensor_``)`  分页 kv-cache 中每个请求的最后一页中的条目数形状`[batch_size]`。
* `**num_qo_heads**``(``_int_``)` – 查询/输出头的数量。
* `**num_kv_heads**``(``_int_``)` – 键/值头的数量。
* `**head_dim_qk**``(``_int_``)` – 查询/键头的维度。
* `**page_size**``(``_int_``)`  分页 kv-cache 中每个页面的大小。
1. run()方法
```Python
run(q: Tensor, paged_kv_cache: Tensor | Tuple[Tensor, Tensor], *args, k_scale: float | None = None, v_scale: float | None = None, out: Tensor | None = None, lse: Tensor | None = None, return_lse: Literal[False] = False, enable_pdl: bool | None = None, window_left: int | None = None) → Tensor
```
* 关键参数
* **q** (_torch.Tensor_) – 查询张量,形状:`[qo_indptr[-1], num_qo_heads, head_dim]`
* **paged\_kv\_cache** (_Union\[torch.Tensor, Tuple\[torch.Tensor, torch.Tensor\]\]_) 存储的分页 KV 缓存作为张量元组或单个张量
* 一个元组 `(k_cache, v_cache)`包含 4D 张量每个张量的形状为`[max_num_pages, page_size, num_kv_heads, head_dim]`,如果 `kv_layout` 是 `NHD`,以及 `[max_num_pages, num_kv_heads, page_size, head_dim]`,如果 `kv_layout` 是 `HND`
* 一个 5D 张量形状为`[max_num_pages, 2, page_size, num_kv_heads, head_dim]`,如果 `kv_layout` 是 `NHD`,以及 `[max_num_pages, 2, num_kv_heads, page_size, head_dim]`,如果 `kv_layout` 是 `HND`。其中 `paged_kv_cache[:, 0]`  key 缓存`paged_kv_cache[:, 1]`  value 缓存
#### flashinfer.mla.BatchMLAPagedAttentionWrapper()类
多头潜在注意力 (MLA) 是一种新的注意力机制,由 [++DeepSeek v2++](https://arxiv.org/abs/2405.04434) 提出并用于后来的 DeepSeek 模型。MLA 将键缓存和值缓存统一到一个张量中因此无需单独存储它们。与多头注意力或分组查询注意力相比MLA  KV-Cache 没有 `num_heads` 维度,因此没有像 `NHD` 和 `HND` 布局这样的区别。
MLA 分离 RoPE旋转位置编码维度和其他头部维度。我们使用 `kpe`(带有位置编码的键)和 `ckv`(压缩的键/值来命名这两个组件。用户可以将它们存储在单个 Paged KV-Cache 
```Python
head_dim_ckv = 512
head_dim_kpe = 64
mla_paged_kv_cache = torch.empty(max_num_pages, page_size, head_dim_ckv + head_dim_kpe, dtype=torch.bfloat16)
ckv = mla_paged_kv_cache[:, :, :head_dim_ckv] # Slicing here does not copy or move data
kpe = mla_paged_kv_cache[:, :, head_dim_ckv:] # Slicing here does not copy or move data
```
**低秩联合压缩 (Joint Compression):** MLA 不再为每个注意力头单独存储巨大的 Key  Value 矩阵。相反它将它们投影并压缩到一个共享的潜在向量Latent Vector即您代码中的 `ckv` (`head_dim_ckv = 512`)。
**解耦旋转位置编码 (Decoupled RoPE):** 位置信息对于注意力机制至关重要但它很难被压缩。MLA 的巧妙之处在于将携带 RoPE 信息的维度单独剥离出来即代码中的 `kpe` (`head_dim_kpe = 64`)。
**消除** `**num_heads**` **维度:** 存储的 Cache 不再区分 NHD序列、头数、头维度 HND 布局。无论模型有多少个注意力头KV-Cache 对于每个 Token 只需要存储 `head_dim_ckv + head_dim_kpe`例如 512 + 64 = 576 个元素
1. 构造函数
```Python
__init__(float_workspace_buffer: Tensor, use_cuda_graph: bool = False, qo_indptr: Tensor | None = None, kv_indptr: Tensor | None = None, kv_indices: Tensor | None = None, kv_len_arr: Tensor | None = None, backend: str = 'auto') → None
```
2. .Plan()方法
```Python
plan(qo_indptr: Tensor, kv_indptr: Tensor, kv_indices: Tensor, kv_len_arr: Tensor, num_heads: int, head_dim_ckv: int, head_dim_kpe: int, page_size: int, causal: bool, sm_scale: float, q_data_type: dtype, kv_data_type: dtype, use_profiler: bool = False) → None
```
* 关键参数
* `**qo_indptr**``(``_torch.IntTensor_``)` – 查询/输出张量的 indptr形状`[batch_size + 1]`。对于解码注意力每个查询的长度为 1张量的内容应为 `[0, 1, 2, ..., batch_size]`
* `**kv_indptr**``(``_torch.IntTensor_``)`  分页 kv-cache  indptr形状`[batch_size + 1]`。
* `**kv_indices**``(``_torch.IntTensor_``)`  分页 kv-cache 的页面索引形状`[kv_indptr[-1]]` 或更大。
* `**kv_len_arr**``(``_torch.IntTensor_``)` – 每个请求的查询长度,形状:`[batch_size]`。
* `**num_heads**``(``_int_``)` – 查询/输出张量中的头数。
* `**head_dim_ckv**``(``_int_``)`  压缩 kv 的头维度。
* `**head_dim_kpe**``(``_int_``)`  rope k-cache 的头维度。
* `**page_size**``(``_int_``)`  分页 kv-cache 的页面大小。
* `**causal**``(``_bool_``)` – 是否使用因果注意力。
* `**sm_scale**``(``_float_``)`  softmax 运算的缩放因子。
* `**q_data_type**``(``_torch.dtype_``)` – 查询张量的数据类型。
* `**kv_data_type**``(``_torch.dtype_``)`  kv-cache 张量的数据类型。
* `**use_profiler**``(``_bool, optional_``)`  是否启用内核内分析器默认值为 False。
1. run()方法
```Python
run(q_nope: Tensor, q_pe: Tensor, ckv_cache: Tensor, kpe_cache: Tensor, out: Tensor | None = None, lse: Tensor | None = None, return_lse: Literal[False] = False, profiler_buffer: Tensor | None = None, kv_len: Tensor | None = None, page_table: Tensor | None = None, return_lse_base_on_e: bool = False) → Tensor
```
* 关键参数
* `**q_nope**``(``_torch.Tensor_``)`  不含 rope 的查询张量形状`[batch_size, num_heads, head_dim_ckv]`。
* `**q_pe**``(``_torch.Tensor_``)`  查询张量的 rope 部分形状`[batch_size, num_heads, head_dim_kpe]`。
* `**ckv_cache**``(``_torch.Tensor_``)`  压缩的 kv-cache 张量不含 rope形状`[num_pages, page_size, head_dim_ckv]`。 `head_dim_ckv`  DeepSeek v2/v3 模型中为 512。
* `**kpe_cache**``(``_torch.Tensor_``)`  kv-cache 张量的 rope 部分形状`[num_pages, page_size, head_dim_kpe]`。 `head_dim_kpe`  DeepSeek v2/v3 模型中为 64。
#### flashinfer.BatchDecodeWithPagedKVCacheWrapper()类
1. 构造函数
```Python
__init__(float_workspace_buffer: Tensor, kv_layout: str = 'NHD', use_cuda_graph: bool = False, use_tensor_cores: bool = False, paged_kv_indptr_buffer: Tensor | None = None, paged_kv_indices_buffer: Tensor | None = None, paged_kv_last_page_len_buffer: Tensor | None = None, backend: str = 'auto', jit_args: List[Any] | None = None) → None
```
2. .Plan()方法
```Python
plan(indptr: Tensor, indices: Tensor, last_page_len: Tensor, num_qo_heads: int, num_kv_heads: int, head_dim: int, page_size: int, pos_encoding_mode: str = 'NONE', window_left: int = -1, logits_soft_cap: float | None = None, q_data_type: str | dtype | None = 'float16', kv_data_type: str | dtype | None = None, o_data_type: str | dtype | None = None, data_type: str | dtype | None = None, sm_scale: float | None = None, rope_scale: float | None = None, rope_theta: float | None = None, non_blocking: bool = True, block_tables: Tensor | None = None, seq_lens: Tensor | None = None, fixed_split_size: int | None = None, disable_split_kv: bool = False) → None
```
* 关键参数
* `**indptr**``(``_torch.Tensor_``)`  分页 kv 缓存的 indptr形状`[batch_size + 1]`dtype`torch.int32`
* `**indices**``(``_torch.Tensor_``)`  分页 kv 缓存的页面索引形状`[kv_indptr[-1]]`dtype`torch.int32`
* `**last_page_len**``(``_torch.Tensor_``)`  分页 kv 缓存中每个请求的最后一页中的条目数形状`[batch_size]`dtype`torch.int32`
* `**num_qo_heads**``(``_int_``)` – 查询/输出头的数量
* `**num_kv_heads**``(``_int_``)`  key/value 头的数量
* `**head_dim**``(``_int_``)` – 头部的维度
* `**page_size**``(``_int_``)`  分页 kv 缓存的页面大小
1. run()方法
```Python
run(q: Tensor, paged_kv_cache: Tensor | Tuple[Tensor, Tensor], *args, q_scale: float | None = None, k_scale: float | None = None, v_scale: float | None = None, out: Tensor | None = None, lse: Tensor | None = None, return_lse: Literal[False] = False, enable_pdl: bool | None = None, window_left: int | None = None) → Tensor
```
* 关键参数
* `**q**``(``_torch.Tensor_``)` – 查询张量,形状:`[batch_size, num_qo_heads, head_dim]`
* `**paged_kv_cache**` (_Union\[torch.Tensor, Tuple\[torch.Tensor, torch.Tensor\]\]_) 存储的分页 KV 缓存作为张量元组或单个张量
* 一个元组 `(k_cache, v_cache)`包含 4D 张量每个张量的形状为`[max_num_pages, page_size, num_kv_heads, head_dim]`,如果 `kv_layout` 是 `NHD`,以及 `[max_num_pages, num_kv_heads, page_size, head_dim]`,如果 `kv_layout` 是 `HND`
* 一个 5D 张量形状为`[max_num_pages, 2, page_size, num_kv_heads, head_dim]`,如果 `kv_layout` 是 `NHD`,以及 `[max_num_pages, 2, num_kv_heads, page_size, head_dim]`,如果 `kv_layout` 是 `HND`。其中 `paged_kv_cache[:, 0]`  key 缓存`paged_kv_cache[:, 1]`  value 缓存
## 六、项目实践--FlashInfer-Baseline
### Step 1检查运行环境
**目标:** 进入terminal确认当前环境满足本模块运行要求。
**操作:** 检查 GPU、Python、编译工具和依赖版本。
![giteeai 实例 12](https://origin.picgo.net/2026/06/04/giteeai--12c1772b12867f6be0.png)
**常见问题:**
| 问题 | 解决方法 |
| --- | --- |
| `mx-smi: command not found` | 使用 `python -c "import torch; print(torch.cuda.get_device_name(0))"` |
| `No GPUs are available` | 检查 MXMACA 驱动是否正确安装 |
| `ModuleNotFoundError: No module named 'flashinfer'` | 执行 `pip install flashinfer` 安装 |
### Step 2进入项目目录
**目标:** 进入本模块所需的源码目录。
**操作:** 切换到 FlashInfer Baseline 项目目录。
**命令示例:**
```bash
cd /data/flashinfer_baseline
ls -la
```
**预期结果:**
```plaintext
bench_common.py
bench_batch_decode.py
bench_batch_prefill_paged.py
bench_batch_prefill_ragged.py
bench_batch_mla.py
README.md
...
```
### Step 3验证项目脚本
**目标:** 确认所有基准测试脚本可正常执行。
**操作:** 检查脚本文件是否存在且可读。
**命令示例:**
```bash
# 检查脚本文件
python -c "import os; scripts = ['bench_common.py', 'bench_batch_decode.py', 'bench_batch_prefill_paged.py', 'bench_batch_prefill_ragged.py', 'bench_batch_mla.py']; [print(f'✓ {s}') if os.path.exists(s) else print(f'✗ {s} missing') for s in scripts]"
# 测试脚本导入
python -c "from bench_common import setup_workspace, get_csv_path; print('脚本导入正常')"
```
**预期结果:**
```plaintext
✓ bench_common.py
✓ bench_batch_decode.py
✓ bench_batch_prefill_paged.py
✓ bench_batch_prefill_ragged.py
✓ bench_batch_mla.py
脚本导入正常
```
### Step 4运行单算子 Benchmark
**目标:** 执行基准测试获取 Baseline 性能数据。
**操作:** 运行 BatchDecode 基准测试脚本。
**命令示例:**
```bash
python bench_batch_decode.py
```
**预期结果:**
```plaintext
[BatchDecodeWithPagedKVCacheWrapper] Starting benchmark, total cases: 144
[1/144] bs=1, kv_len=512, hd=64: 0.020ms, 51.53 GB/s, 0.82 TFLOPs
[2/144] bs=1, kv_len=512, hd=128: 0.023ms, 45.28 GB/s, 0.72 TFLOPs
...
Results saved to BatchDecodeWithPagedKVCacheWrapper_20260525_xxxxxx.csv
```
**常见问题:**
| 问题 | 解决方法 |
| --- | --- |
| `out of memory` | 减小 batch\_size  seq\_len 参数 |
| 运行时间过长 | 脚本会自动调整重复次数,耐心等待 |
### Step 5查看测试结果
**目标:** 查看并分析 Benchmark 输出结果。
**操作:** 读取生成的 CSV 结果文件。
**命令示例:**
```bash
# 查看最新生成的 CSV 文件
ls -lt *.csv | head -1
# 使用 Python 查看结果
python -c "
import pandas as pd
import glob
csv_files = glob.glob('BatchDecodeWithPagedKVCacheWrapper_*.csv')
if csv_files:
df = pd.read_csv(max(csv_files))
print(df[['batch_size', 'seq_len_kv', 'head_dim', 'time_ms', 'bandwidth_GB_s', 'tflops']].head(10))
"
```
**预期结果:**
```plaintext
batch_size seq_len_kv head_dim time_ms bandwidth_GB_s tflops
0 1 512 64 0.020429 51.528822 0.821253
1 1 512 128 0.023340 45.278052 0.718833
2 1 512 256 0.031949 66.153846 1.050256
...
```
## 七、Agent 使用说明
在本模块中Agent 可以帮助你完成以下任务
### 环境检查
```plaintext
请帮我检查当前环境是否满足 FlashInfer 运行要求,包括 GPU、Python、PyTorch 和 flashinfer 依赖。
```
### 运行测试
```plaintext
请帮我运行 bench_batch_decode.py 脚本,执行 BatchDecode 的基准测试。
```
### 分析结果
```plaintext
请帮我读取最新的 CSV 结果文件,分析各参数配置下的性能表现,找出带宽最高和 TFLOPs 最高的配置。
```
### 问题排查
```plaintext
运行时报错 out of memory请帮我分析原因并给出解决方案。
```
### 代码理解
```plaintext
请帮我解释 bench_common.py 中 run_with_profiler 函数的工作原理。
```
## 八、常见问题
### 环境相关问题
| 问题 | 原因 | 解决办法 |
| --- | --- | --- |
| `No GPUs are available` | MXMACA 驱动未安装或 GPU 不可见 | 检查驱动安装,运行 `python -c "import torch; print(torch.cuda.device_count())"` 验证 |
| `ModuleNotFoundError: No module named 'flashinfer'` | flashinfer 未安装 | 执行 `pip install flashinfer` |
| `out of memory` | GPU 显存不足 | 减小 `batch_size` 或 `seq_len` 参数 |
### 运行相关问题
| 问题 | 原因 | 解决办法 |
| --- | --- | --- |
| Benchmark 运行时间过长 | 参数组合过多 workload 较大 | 耐心等待,脚本会自动调整重复次数 |
| `KeyError: 'BatchPrefillWithPagedKVCacheKernel'` | profiler 未捕获目标 kernel | 检查 `target_kernels` 配置是否正确 |
| CSV 文件为空 | 测试未正常完成 | 检查 GPU 显存是否充足重新运行 |
### 代码相关问题
| 问题 | 原因 | 解决办法 |
| --- | --- | --- |
| `ImportError: cannot import name 'xxx' from 'bench_common'` | 函数名拼写错误 | 检查 `bench_common.py` 中的函数名 |
| `RuntimeError: error: device-side assert triggered` | 输入参数超出范围 | 检查 `num_qo_heads`、`num_kv_heads`、`head_dim` 配置 |
### 性能相关问题
| 问题 | 原因 | 解决办法 |
| --- | --- | --- |
| TFLOPs 数值异常低 | 工作负载过小kernel 启动开销占比大 | 增大 `batch_size` 或 `seq_len` |
| 带宽数值异常低 | 数据未正确加载到 GPU | 检查 Tensor 是否在 CUDA 设备上 |
## 九、下一步学习建议
### 1. 保存你的 Baseline 结果
将本次运行生成的 CSV 文件妥善保存后续优化时需要以此作为对比基准。
```bash
# 建议创建 results 目录保存
mkdir -p results
mv *.csv results/
```
### 2. 深入理解 FlashInfer 核心概念
* 阅读 FlashInfer 官方文档理解 Paged KV Cache、Ragged KV Cache 的设计理念
* 学习 MLA (Multi-head Latent Attention) 的原理了解 DeepSeek 的注意力优化方案
* 理解 `plan()` 和 `run()` 两阶段设计的作用
### 3. 进入算子优化模块
参考后续优化模块,学习以下优化技术:
* **Kernel Tuning**调整 Block Size、Thread Count 等参数
* **Memory Optimization**:减少显存占用、优化数据搬运
* **Compute Optimization**:提升计算效率
### 4. 参考资源
* FlashInfer 官方仓库https://github.com/flashinfer-ai/flashinfer
* FlashInfer 文档https://flashinfer.ai
### 5. 记录优化过程
建议维护一份优化日志,记录每次优化的改动和性能变化:
| 优化项 | 改动内容 | Baseline | 优化后 | 提升比例 |
| --- | --- | --- | --- | --- |
| 例调整 block\_size | 16  32 | xx ms | xx ms | xx% |
| 完成优化后再次运行本模块的 Benchmark 脚本对比前后性能变化。 | | | | |