forked from metax-maca/op_optimization
<docs>(flashinfer): 去除题包及其描述,更新文件结构
This commit is contained in:
parent
2e3aa374cd
commit
794e15fbc6
|
|
@ -2,21 +2,21 @@
|
|||
|
||||
## 1. 教程定位
|
||||
|
||||
本教程是赛题二 FlashInfer 任务的 “benchmark 性能基线与 XPU-OJ 提交衔接” 模块,主要帮助学员跑通目标算子的 benchmark 脚本,理解原库 API、输入输出结构、性能指标和性能基线结果,并进一步读懂 XPU-OJ 题目包中的接口约定、测试数据、参考输出和精度要求。
|
||||
本教程是赛题二 FlashInfer 任务的 “benchmark 性能基线与 XPU-OJ 提交衔接” 模块,主要帮助学员跑通目标算子的 benchmark 脚本,理解原库 API、输入输出结构、性能指标和性能基线结果,并进一步读懂 XPU-OJ 题目中的接口约定、测试数据、参考输出和精度要求。
|
||||
|
||||
需要特别说明:
|
||||
|
||||
- 本教程不提供可直接提交的 OJ 参考实现源代码或标准答案代码。
|
||||
- 本教程不提供可直接提交的 OJ 参考实现源代码或标准答案代码;
|
||||
|
||||
- 本教程仅提供冒烟级 starter 示例代码,用于验证环境、语言、提交链路和 `run_kernel(...)` 接口。
|
||||
- 本教程仅提供冒烟级 starter 示例代码,用于验证环境、语言、提交链路和 `run_kernel(...)` 接口;
|
||||
|
||||
- benchmark 脚本用于建立性能基线,不是最终提交物。
|
||||
- benchmark 脚本用于建立性能基线,不是最终提交物;
|
||||
|
||||
- XPU-OJ 题包中的 `baseline()` 属于 OJ 后台参考实现,用于生成 `output_ref`,不是选手提交代码。
|
||||
- XPU-OJ 上题目说明中的 `baseline()` 属于 OJ 后台参考实现,用于生成 `output_ref`,不是选手提交代码;
|
||||
|
||||
- 选手最终需要自行实现 `run_kernel(...)`,并在正确性通过后继续优化性能。
|
||||
|
||||
完成本教程后,学员应能够跑通 benchmark 脚本,记录性能基线结果,读懂 XPU-OJ 题包,理解 OJ 的测试输入与参考实现,并完成一次冒烟级 OJ 提交。
|
||||
完成本教程后,学员应能够跑通 benchmark 脚本,记录性能基线结果,读懂 XPU-OJ 上的题目,理解 OJ 的测试输入与参考实现,并完成一次冒烟级 OJ 提交。
|
||||
|
||||
## 2. 学习目标
|
||||
|
||||
|
|
@ -28,11 +28,11 @@
|
|||
|
||||
3. 跑通 BatchDecode、BatchPrefill、MLA 等典型算子的 benchmark 脚本,记录性能基线结果,为后续算子优化提供对比基准;
|
||||
|
||||
4. 读懂对应 XPU-OJ 题包中的题目描述、接口约定、数据范围和精度要求;
|
||||
4. 读懂对应 XPU-OJ 题目中的题目描述、接口约定、数据范围和精度要求;
|
||||
|
||||
5. 完成一次冒烟级 `run_kernel(...)` 提交,确认 OJ 链路、语言环境和接口调用正常;
|
||||
|
||||
6. 使用 AI Agent 辅助阅读题包、生成初版实现、定位错误并规划性能优化方向。
|
||||
6. 使用 AI Agent 辅助阅读题目、生成初版实现、定位错误并规划性能优化方向。
|
||||
|
||||
## 3. 适用对象
|
||||
|
||||
|
|
@ -202,6 +202,21 @@ XPU-OJ 账号由组委会统一发放,参赛者无需自行注册。
|
|||
|
||||
> 如果登录后看不到题目,请联系助教或赛事运营确认账号是否已加入对应比赛 / 用户组
|
||||
|
||||
### 4.5 评分规则概要
|
||||
|
||||
提交作品的最终评价采用 **100 分制**,由客观评测和专家评审共同组成:
|
||||
|
||||
| 类别 | 评审维度 | 权重 | 说明 |
|
||||
|------|---------|:---:|------|
|
||||
| 客观评测 | **性能提升效果** | **60%** | 基于 XPU-OJ 各任务榜单排名计分。各任务独立计分,**最终仅取得分最高的一个任务**计入"性能提升效果",不叠加计分。进入前 30 名且 OJ 分数 > 50 的提交按排名计分:第 1 名得 60 分,每降 1 名扣 2 分,第 30 名得 2 分。未进前 30 或 OJ 分数 ≤ 50 的提交不得分。未通过正确性或稳定性测试的作品记 0 分 |
|
||||
| 客观评测 | **Agent/Skill 可复现性** | **20%** | Agent 应真实参与源码理解、代码生成、性能分析、自动调优、Benchmark 和多轮迭代等优化过程。分四档:1. 功能可复现得 5 分;2. 性能复现达提交标称 60% 以上得 10 分;3. 80% 以上得 15 分;4. 90% 以上得 20 分 |
|
||||
| 客观+主观 | **文档说明与演示报告** | **20%** | 根据技术报告、README、运行说明、性能测试报告、Agent/Skill 说明文档、演示视频及答辩材料的完整性、规范性、技术表达质量和工程可复现性综合评分 |
|
||||
|
||||
|
||||
> - OJ 榜单分数与比赛最终得分属于不同分值体系。OJ 内部以各算子 baseline 为 50 分基准、硬件理论上限约 100 分;比赛性能部分满分 60 分,按排名映射;
|
||||
> - FlashInfer 方向包含多个子任务,任选一种或多种提交,各子任务独立计分,取通过正确性和稳定性测试的最高成绩参与排名;
|
||||
> - 组委会对提交内容严格审查,严禁抄袭,一经发现取消成绩。
|
||||
|
||||
## 5. 知识预备
|
||||
|
||||
### 5.1 LLM 推理阶段重要概念
|
||||
|
|
@ -239,11 +254,11 @@ XPU-OJ 账号由组委会统一发放,参赛者无需自行注册。
|
|||
|
||||
[*GitHub - MetaX-MACA/McFlashInfer*](https://github.com/MetaX-MACA/McFlashInfer)
|
||||
|
||||
> 链接内容可供用于学习 API、算子实现思路、benchmark 方法和优化策略。选手仍需根据 XPU-OJ 题包接口**自行实现**可提交的 `run_kernel(...)`
|
||||
> 链接内容可供用于学习 API、算子实现思路、benchmark 方法和优化策略。选手仍需根据 XPU-OJ 题目接口**自行实现**可提交的 `run_kernel(...)`
|
||||
|
||||
## 6. 项目实践 -- FlashInfer Benchmark
|
||||
|
||||
**目标:** 以一个具体算子题目 **20001 FlashInfer Ragged Prefill** 为例,跑通 benchmark 脚本,建立性能基线,理解对应 XPU-OJ 题目包,并完成一次 OJ 冒烟提交,为后续 Agent 辅助优化建立起点。
|
||||
**目标:** 以一个具体算子题目 **20001 FlashInfer Ragged Prefill** 为例,理解对应题目,跑通 benchmark 脚本,建立性能基线,并完成一次 OJ 冒烟提交,为后续 Agent 辅助优化建立起点。
|
||||
|
||||
### 6.1 在赛事镜像中运行 FlashInfer Benchmark
|
||||
|
||||
|
|
@ -441,32 +456,30 @@ else:
|
|||
|
||||
**Benchmark 与 XPU-OJ 的关系**
|
||||
|
||||
赛事镜像中的 Benchmark 和 XPU-OJ 在线评测任务不同。Benchmark 脚本用于理解目标算子的调用方式、输入输出 shape 和性能基线;XPU-OJ 题包用于定义最终评测接口、数据范围、参考输出和精度要求。
|
||||
赛事镜像中的 Benchmark 和 XPU-OJ 在线评测任务不同。Benchmark 脚本用于理解目标算子的调用方式、输入输出 shape 和性能基线;XPU-OJ 题目说明用于定义最终评测接口、数据范围、参考输出和精度要求。
|
||||
|
||||
| 维度 | Benchmark 脚本 | XPU-OJ 提交 |
|
||||
|------|---------------|------------|
|
||||
| **目的** | 理解算子接口、建立性能基线 | 统一环境下的正确性+性能评测 |
|
||||
| **接口形式** | Python API(`wrapper.plan()` + `wrapper.run()`) | C 接口(`extern "C" void run_kernel(...)`) |
|
||||
| **数据范围** | 多种 head_dim / batch_size / seq_len 组合 | 固定参数范围(以题包为准) |
|
||||
| **数据范围** | 多种 head_dim / batch_size / seq_len 组合 | 固定参数范围(以 XPU-OJ 题目说明为准) |
|
||||
| **验证** | 无自动正确性校验 | 强制通过 `torch.allclose(rtol=1e-2, atol=1e-2)` |
|
||||
| **输出** | CSV 性能记录 | 排行榜得分 |
|
||||
| **输出** | CSV 性能记录 | 排行榜得分(XPU-OJ 内部得分) |
|
||||
|
||||
跑完 benchmark、建立性能基线后,选手需要完成以下转换:
|
||||
1. 从 benchmark 脚本中理解目标 API,例如 `BatchPrefillWithRaggedKVCacheWrapper`;
|
||||
|
||||
2. 在对应 OJ 题包中查看 `run_kernel(...)` 接口;
|
||||
2. 在 XPU-OJ 上查看对应题目说明的 `run_kernel(...)` 接口;
|
||||
|
||||
3. 对照题包中的输入 shape、数据范围和精度要求;
|
||||
3. 对照题目说明的输入 shape、数据范围和精度要求,编写自己的 `run_kernel(...)`;
|
||||
|
||||
4. 编写自己的 `run_kernel(...)`;
|
||||
4. 提交 OJ,先保证正确性;
|
||||
|
||||
5. 提交 OJ,先通过正确性;
|
||||
|
||||
6. 正确性通过后,再对比 benchmark 耗时 / OJ 耗时继续优化。
|
||||
5. 正确性通过后,再对比 benchmark 耗时 / OJ 耗时继续优化。
|
||||
|
||||
**选择目标题目**
|
||||
|
||||
FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benchmark 脚本、OJ 题包、`run_kernel(...)` 接口和数据范围可能不同。
|
||||
FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benchmark 脚本、题目说明、`run_kernel(...)` 接口和数据范围。
|
||||
|
||||
| OJ 题号 | 算子类型 | 核心特点 | Benchmark 脚本 | FlashInfer API |
|
||||
|---------|---------------|----------------|----------|----------|
|
||||
|
|
@ -475,21 +488,20 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
| **20003** | MLA Paged Attention | DeepSeek MLA特有,双路Q(nope+pe)/双路Cache(ckv+kpe) |`bench_batch_mla.py` | `BatchMLAPagedAttentionWrapper` |
|
||||
| **20004** | Paged Decode | 每次只1个query token,memory-bound |`bench_batch_decode.py` | `BatchDecodeWithPagedKVCacheWrapper` |
|
||||
|
||||
**每个子题的接口参数、数据范围和精度要求以对应 XPU-OJ 题包为准。** 下文以题目 **20001 Flashinfer Ragged Prefill** 为例演示从 benchmark 到 XPU-OJ 提交的完整流程。
|
||||
**每个子题的接口参数、数据范围和精度要求以对应题目说明为准。** 下文以题目 **20001 Flashinfer Ragged Prefill** 为例演示从 benchmark 到 XPU-OJ 提交的完整流程。
|
||||
|
||||
题目的更多信息可参考 [*20001 Flashinfer Ragged Prefill 题目说明*](https://gitlink.org.cn/metax-maca/op_optimization/tree/master/%E5%9F%BA%E4%BA%8EAI%20Agent%E5%BC%80%E5%8F%91%E8%8C%83%E5%BC%8F%E7%9A%84%E5%9B%BD%E4%BA%A7GPU%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%8E%A8%E7%90%86%E7%AE%97%E5%AD%90%E5%BA%93%E4%BC%98%E5%8C%96%2Foperator_task_package%2Fflashinfer_task_package%2Fxpuoj_problem%2Fproblem_20001%2Fzh_CN)。
|
||||
|
||||
#### Step 6:理解 XPU-OJ 评测接口与精度要求
|
||||
**目标:** 明确 Benchmark 与最终评测提交之间的关系,理解选手需要实现的内容。
|
||||
|
||||
> 完成 benchmark 后,需要注意 benchmark 脚本主要用于建立性能基线,并不需要最终提交
|
||||
> 最终评测以 XPU-OJ 题包为准,评测程序会调用选手提交代码中的 `run_kernel`,并将输出结果与 OJ 参考实现结果进行比较
|
||||
> 最终评测以 XPU-OJ 为准,评测程序会调用选手提交代码中的 `run_kernel`,并将输出结果与 OJ 后台参考实现结果进行比较
|
||||
|
||||
下面以题目 **20001 FlashInfer Ragged Prefill** 为例,逐份解读 OJ 题包中的关键文件。
|
||||
下面以题目 **20001 FlashInfer Ragged Prefill** 为例,从本地题目文档 [*Agent 推理算子库优化 - FlashInfer Ragged Prefill*](./operator_task_package/flashinfer_task_package/xpuoj_problem/problem_20001/Agent%20推理算子库优化%20-%20FlashInfer%20Ragged%20Prefill.md) 中逐节解读关键信息。
|
||||
|
||||
1. `00_题目描述.md` — 我要实现什么?
|
||||
1. `## 1. 题目描述` — 我要实现什么?
|
||||
|
||||
该文件明确了三个核心信息:
|
||||
该章节明确了三个核心信息:
|
||||
|
||||
1. **算子功能**:实现 FlashInfer Ragged KV Cache Prefill 的前向 CUDA C++ 算子。给定扁平存储的 Q / K / V,计算带 causal mask 的 scaled dot-product attention,结果写入 `output` 张量。
|
||||
|
||||
|
|
@ -505,7 +517,7 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
你的 `run_kernel` 需要实现等效的注意力计算,最终输出与上述 FlashInfer API 在 `rtol = 1e-2, atol = 1e-2` 容差内一致。
|
||||
|
||||
2. `01_接口约定.md` — 我提交的函数签名是什么?
|
||||
2. `## 2. 接口约定` — 我提交的函数签名是什么?
|
||||
|
||||
以 CUDA C++ 为例,`run_kernel` 的精确 C 符号如下(函数名、参数类型、顺序、`const` 修饰均不可修改):
|
||||
|
||||
|
|
@ -555,52 +567,22 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
- **GQA 头映射**:`kv_head = qo_head / (num_qo_heads / num_kv_heads) = qo_head / 8`。
|
||||
|
||||
- **Triton / TileLang 接口**:函数名同样是 `run_kernel`,参数顺序相同,数据类型映射遵循对应语言的沙箱规则(详见题包中的 `01_接口约定triton.md` 和 `01_接口约定tilelang.md`)。
|
||||
- **Triton / TileLang 接口**:函数名同样是 `run_kernel`,参数顺序相同,数据类型映射遵循对应语言的沙箱规则(详见题目文档中 `### 2.2 Triton` 和 `### 2.3 TileLang` 章节)。
|
||||
|
||||
3. `02_数据范围.md` — 输入规模有多大?
|
||||
3. `## 6. 数据范围与提示` — 输入规模有多大?
|
||||
|
||||
该文件定义了测试用例的参数组合和精度容差:
|
||||
该章节定义了测试用例的参数组合和精度容差:
|
||||
|
||||
- **固定参数**:`num_qo_heads = 32`、`num_kv_heads = 4`、`head_dim_qk = 128`、`head_dim_vo = 128`、`causal = 1`、数据类型 `bfloat16`
|
||||
- **可变参数**(共 **12 个测试用例**):
|
||||
|
||||
| batch_size | seq_len | 估算 Q 张量大小 | 估算 KV 张量大小 |
|
||||
|:---:|:---:|:---:|:---:|
|
||||
| 1 | 1024 | 1×1024×32×128×2B = 8 MB | 2×1×1024×4×128×2B ≈ 2 MB |
|
||||
| 1 | 4096 | 32 MB | 8 MB |
|
||||
| 1 | 8192 | 64 MB | 16 MB |
|
||||
| 1 | 16384 | 128 MB | 32 MB |
|
||||
| 4 | 1024~16384 | 32 MB ~ 512 MB | 8 MB ~ 128 MB |
|
||||
| 16 | 1024~16384 | 128 MB ~ 2 GB | 32 MB ~ 512 MB |
|
||||
- **可变参数**:batch_size 覆盖 {1, 2, 4, 15, 16, 27, 33},seq_len 上界覆盖 {1, 65, 123, 873, 987, 1024, 1280, 2048, 4096, 16384},共 **15 个测试用例**,覆盖等长长序列、变长 ragged、`q_len < kv_len`、短段和非 2 的幂长度。
|
||||
|
||||
- **精度要求**:`torch.allclose(output.float(), output_ref.float(), rtol = 1e-2, atol = 1e-2)`
|
||||
- **显存上限**:OJ 评测环境设计 `VRAM_SIZE = 48 GB`(见 `testcase_config.py`)
|
||||
- **精度要求**:`torch.allclose(output.float(), output_ref.float(), rtol = 1.6e-2, atol = 1.6e-2)`,且允许不超过 1% 的元素超差(匹配率需 ≥ 0.99)。
|
||||
|
||||
- **显存上限**:OJ 评测环境设计 `VRAM_SIZE = 48 GB`(OJ 后台配置)
|
||||
|
||||
> **优化提示**:`batch_size = 16, seq_len = 16384` 是最大 workload(Q ≈ 2 GB),需要特别关注显存使用和 compute 效率
|
||||
|
||||
4. `testcase_config.py` — OJ 后台如何评测我?
|
||||
|
||||
这个文件不是给选手阅读的文档,但理解它的逻辑有助于定位 OJ 提交错误:
|
||||
|
||||
1. `TESTCASES`:定义 12 组测试参数(`batch_size × seq_len` 组合),与 benchmark 脚本的参数顺序一致。
|
||||
2. `genTestCase()`:OJ 用固定随机种子生成测试张量,同一测试用例每次运行数据相同。
|
||||
3. `baseline()`:OJ 的参考实现——调用 FlashInfer Python API 得到 `output_ref`。
|
||||
4. `check()`:比较你的 `output` 与 `output_ref`。失败时返回 `max_abs_diff` 和 `mean_abs_diff` 帮助定位数值偏差。
|
||||
5. `getWorkload()`:计算该测试点的 FLOPs 和显存读写量,用于估算硬件理论下限 `T_h`(以此计算得分)。
|
||||
|
||||
当前题目 **20001 FlashInfer Ragged Prefill** 的校验方式为:
|
||||
```python
|
||||
torch.allclose(output_t.float(), output_ref.float(), rtol=1e-2, atol=1e-2)
|
||||
```
|
||||
说明:
|
||||
- `output_t` 是选手 `run_kernel(...)` 的输出;
|
||||
|
||||
- `output_ref` 是 OJ 题包参考实现生成的输出;
|
||||
|
||||
- `rtol / atol` 是容差。
|
||||
|
||||
选手实现的输出需要在上述容差范围内与 OJ 参考实现输出一致。不同算子的容差可能不同,正式精度要求以对应 OJ 题包说明为准。
|
||||
|
||||
#### Step 7:登录 XPU-OJ 并进入题目页面
|
||||
|
||||
使用组委会统一发放的账号登录 XPU-OJ,并进入对应赛题页面。
|
||||
|
|
@ -626,10 +608,13 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
1. 在语言下拉框中选择本题支持的提交语言,例如 CUDA Maca、Triton 或 TileLang;
|
||||
|
||||
2. 借助 Agent 从题包生成 `run_kernel` 初版;
|
||||
2. 借助 Agent 阅读 XPU-OJ 的题目信息生成 `run_kernel` 初版;
|
||||
在下方参考 prompt 的引导下,Agent 会:
|
||||
1. 读取对应 OJ 题包中的接口约定文档(`01_接口约定.md`),提取 `run_kernel` 函数签名;
|
||||
2. 读取数据范围文档(`02_数据范围.md`),了解输入张量 shape 和精度要求;
|
||||
|
||||
1. 读取对应题目文档中的接口约定章节(`## 2. 接口约定`),提取 `run_kernel` 函数签名;
|
||||
|
||||
2. 读取数据范围章节(`## 6. 数据范围与提示`),了解输入张量 shape 和精度要求;
|
||||
|
||||
3. 生成一个能编译通过的最小 `run_kernel` 实现,优先保证接口正确性,不追求性能。
|
||||
|
||||
生成的代码可在本地编译验证后,直接提交到 OJ 上冒烟测试,确认提交链路可用。
|
||||
|
|
@ -641,7 +626,7 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
- OJ 最终评测不会直接运行 benchmark 脚本,而是调用你提交代码中的 `run_kernel(...)`;
|
||||
|
||||
- 如果你还没有自己的 `run_kernel`,可以先让 Agent 阅读题包,并生成一个最小正确版实现思路;
|
||||
- 如果你还没有自己的 `run_kernel`,可以先让 Agent 阅读题目文档,并生成一个最小正确版实现思路;
|
||||
|
||||
- 本赛题鼓励参赛者使用 AI Agent 辅助完成代码阅读、接口理解、初版实现、错误定位和性能优化。
|
||||
|
||||
|
|
@ -691,7 +676,7 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
4. 调用选手代码中的 `run_kernel(...)`;
|
||||
|
||||
5. 调用 `testcase_config.py` 中的 `baseline()` / 参考实现生成 `output_ref`;
|
||||
5. 调用 OJ 后台参考实现生成 `output_ref`;
|
||||
|
||||
6. 将 `run_kernel(...)` 的输出与 `output_ref` 做正确性校验;
|
||||
|
||||
|
|
@ -825,9 +810,15 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
| ... | ... | ... | ... | ... | ... | ... | ... |
|
||||
| 12 | 16 | 16384 | 349.39 | 124.67 | 2.80 | 112.13 | ✓ |
|
||||
|
||||
2. 总得分查看 — 评分公式详解
|
||||
2. XPU-OJ 平台评分机制(仅供参考)
|
||||
|
||||
OJ 平台对每次提交的所有测试点评分后,按以下规则计算总得分。
|
||||
以下为 XPU-OJ 平台的内部评分算法,用于生成榜单中每个题目的单题分数。请注意:
|
||||
|
||||
- **OJ 榜单分数 ≠ 比赛最终得分。** 比赛最终得分为 100 分制(详见 [*4.5 评分规则概要*](#45%20评分规则概要)),其中性能部分取 OJ 排名映射换算,满分 60 分;
|
||||
|
||||
- OJ 内部评分以各算子 baseline 为 50 分基准,硬件理论上限约 100 分。超过 100 分说明超越理论估算;
|
||||
|
||||
- 未通过正确性或稳定性测试的作品不参与排名,客观评测得分记为 0 分。
|
||||
|
||||
**单测试点评分公式**(参考 Sol-ExecBench):
|
||||
|
||||
|
|
@ -857,36 +848,20 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
3. 榜单查看 — 如何阅读排行榜
|
||||
|
||||
排行榜位于 XPU-OJ 比赛页面,展示所有参赛者的各题得分和总分。
|
||||
[TODO: image]
|
||||
|
||||
以下为榜单示例:
|
||||
排行榜位于 XPU-OJ 比赛页面,展示各参赛者在每个题目的 OJ 内部得分。请注意:**OJ 榜单中各题的分数与比赛最终得分不是同一体系。** 比赛最终成绩中,性能提升效果仅取你得分最高的一个任务,按该任务的 OJ 排名换算(详见 [*4.5 评分规则概要*](#45%20评分规则概要))。
|
||||
|
||||
```plaintext
|
||||
My Rank: #4 My Total Score: 26.08
|
||||
|
||||
# User | Ragged Prefill | Paged Prefill | MLA | Paged Decode | Flashattn | Fused MoE | Total
|
||||
-----|---------------|----------------|---------------|------------|--------------|-----------|-----------|--------
|
||||
1 | muxitest006 | 0 | 0 | 84.75 | 0 | 0 | 0 | 84.75
|
||||
2 | ceerrep | 20.42 | 0 | 0 | 42.33 | 0 | 0 | 62.75
|
||||
3 | muxitest001 | 1.17 | 4.83 | 0.04 | 8.87 | 40.42 | 0 | 54.16
|
||||
4 | muxitest007 | 20.42 | 4.50 | 4.13 | 1.53 | 0 | 0 | 26.08
|
||||
5 | muxitest005 | 5.17 | 0 | 0 | 0 | 0 | 0 | 5.17
|
||||
...
|
||||
```
|
||||

|
||||
|
||||
**榜单解读**:
|
||||
|
||||
- 6 列对应 6 个 OJ 子题:Ragged Prefill (20001)、Paged Prefill (20002)、MLA Attention (20003)、Paged Decode (20004)、FlashAttention KV Cache Decode (20005)、Fused MoE i8 tn (20006)
|
||||
|
||||
- **Total = 6 个子题得分的总和**,排名按 Total 降序
|
||||
- 各题分数为 OJ 内部评分(OJ baseline = 50 分),排名按 Total(各题 OJ 得分之和)降序。**比赛最终只取单一最高分任务**,而非多任务累加
|
||||
|
||||
- 每列下方的括号数值(如 `(48)`)表示该题提交次数
|
||||
|
||||
- `pass = false` 的提交不参与排名(对应 0 分)
|
||||
|
||||
**刷榜策略建议**:优先确保**每一题至少有一次 `pass = true` 的提交**(0 分 ≠ 未参与),然后逐个突破有性能瓶颈的题目,逐步提升各题得分和总分。
|
||||
|
||||
## 7. Agent 使用样例
|
||||
|
||||
**目标:** 在本模块中,你可以掌握使用 Agent 完成环境检查、运行 Benchmark、分析结果、理解 OJ 接口、生成和调试 `run_kernel` 等任务。以下是各个任务的参考 prompt。
|
||||
|
|
@ -912,15 +887,13 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
请帮我读取最新的性能基线 CSV 结果文件,分析各参数配置下的性能表现,找出带宽最高和 TFLOPs 最高的配置,并与沐曦 GPU 理论峰值带宽做对比。
|
||||
```
|
||||
|
||||
### 7.4 理解 OJ 题包接口
|
||||
### 7.4 理解 OJ 题目接口
|
||||
|
||||
``` plaintext
|
||||
请帮我阅读 FlashInfer Ragged Prefill 题包(problem_20001)中的以下文件:
|
||||
- zh_CN/00_题目描述.md
|
||||
- zh_CN/01_接口约定cuda.md
|
||||
- zh_CN/01_接口约定triton.md
|
||||
- zh_CN/01_接口约定tilelang.md
|
||||
- zh_CN/02_数据范围.md
|
||||
请帮我阅读 FlashInfer Ragged Prefill 题目文档(`problem_20001/Agent 推理算子库优化 - FlashInfer Ragged Prefill.md`):
|
||||
- `## 1. 题目描述`
|
||||
- `## 2. 接口约定`(含 CUDA / Triton / TileLang 三语言接口)
|
||||
- `## 6. 数据范围与提示`
|
||||
然后帮我总结:
|
||||
1. run_kernel 的函数签名和每个参数的含义
|
||||
2. 输入张量的形状约定(q/k/v 的 layout、indptr 的作用)
|
||||
|
|
@ -933,8 +906,8 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
``` plaintext
|
||||
请帮我为题目 FlashInfer Ragged Prefill(OJ 题号 20001)生成一个最小可运行的 run_kernel 实现,要求:
|
||||
1. 阅读题包中的 01_接口约定.md,理解 run_kernel 的函数签名(参数类型、顺序、const 修饰);
|
||||
2. 阅读 02_数据范围.md,了解 head_dim_qk、head_dim_vo 的可能取值;
|
||||
1. 阅读题目文档中的 `## 2. 接口约定` 章节,理解 run_kernel 的函数签名(参数类型、顺序、const 修饰);
|
||||
2. 阅读 `## 6. 数据范围与提示` 章节,了解 head_dim_qk、head_dim_vo 的可能取值;
|
||||
3. 生成一个只使用简单双重循环的 naive 实现(不加 tiling、不加 shared memory),确保:
|
||||
- 函数签名为 extern "C" void run_kernel(...)
|
||||
- 包含必要的头文件(cuda_bf16.h、cuda_runtime.h、stdint.h、math.h)
|
||||
|
|
@ -948,8 +921,7 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
``` plaintext
|
||||
我的 run_kernel 提交到 OJ 后显示 Wrong Answer,请帮我对比以下信息:
|
||||
1. 这是我的 submit 代码:[粘贴你的 run_kernel 实现]
|
||||
2. 题包的接口约定在这里:[粘贴或引用 01_接口约定.md]
|
||||
3. 题包的测试配置在这里:[粘贴或引用 testcase_config.py]
|
||||
2. 题目文档的接口约定在这里:[粘贴或引用 `## 2. 接口约定` 章节]
|
||||
请帮我逐项检查:
|
||||
- 函数签名是否完全匹配
|
||||
- GQA 头映射公式是否正确
|
||||
|
|
@ -1016,9 +988,9 @@ FlashInfer 方向包含 **4 个可选算子题目**,每个对应独立的 benc
|
|||
|
||||
| 问题 | 可能原因 | 解决办法 |
|
||||
|------|------|---------|
|
||||
| **Compilation Error** 编译错误 | 1. `run_kernel` 签名与 OJ 接口约定不一致(参数类型、顺序、数量不匹配);2. 缺少 `extern "C"` 声明导致 C++ name mangling;3. 缺少必要头文件(`cuda_bf16.h`、`cuda_runtime.h`、`math.h`);3. 使用了 OJ 环境不支持的语法或 API | 1. 逐行对照对应题包的「01_接口约定.md」,确认参数类型(`int64_t` vs `int`、`const` 修饰)、顺序完全一致;2. 在 `run_kernel` 前加 `extern "C"`;3. 确认文件顶部 include 了 `<cuda_bf16.h>`、`<cuda_runtime.h>`、`<stdint.h>`、`<math.h>`;4. 去掉 `printf`、`assert` 等调试代码后重新提交 |
|
||||
| **Compilation Error** 编译错误 | 1. `run_kernel` 签名与 OJ 接口约定不一致(参数类型、顺序、数量不匹配);2. 缺少 `extern "C"` 声明导致 C++ name mangling;3. 缺少必要头文件(`cuda_bf16.h`、`cuda_runtime.h`、`math.h`);3. 使用了 OJ 环境不支持的语法或 API | 1. 逐行对照对应题目文档的「## 2. 接口约定」章节,确认参数类型(`int64_t` vs `int`、`const` 修饰)、顺序完全一致;2. 在 `run_kernel` 前加 `extern "C"`;3. 确认文件顶部 include 了 `<cuda_bf16.h>`、`<cuda_runtime.h>`、`<stdint.h>`、`<math.h>`;4. 去掉 `printf`、`assert` 等调试代码后重新提交 |
|
||||
| **Time Limit Exceeded** 运行超时 | 1. `run_kernel` 内部调用了 `cudaDeviceSynchronize()` 导致额外等待;2. kernel 中存在死循环(for 循环边界条件错误);3. `__syncthreads()` 放在条件分支内导致线程死锁;4. grid 配置过大,启动的 block 数量远超合理范围 | 1. 删除 `run_kernel` 函数体内的 `cudaDeviceSynchronize()` 调用——评测器会在外部自行同步;2. 检查 kernel 中所有 for 循环的终止条件,确保 `kv_start <= block_max_q` 等边界正确;3. 将所有 `__syncthreads()` 移到 if/else 分支之外;4. 检查 grid 计算:`(seq_len + Br - 1) / Br`,确认 `Br` 取值合理 |
|
||||
| **Wrong Answer** 答案错误 | 1. 注意力计算公式错误(score、scale、softmax 实现有偏差);2. GQA 头映射错误:`hkv = hq / (num_qo_heads / num_kv_heads)` 计算不对;3. Causal mask 未正确实现(`causal=1` 时 query 看到了不该看的未来 token);4. Online softmax 的 m/l 更新逻辑有误;5. float4 向量化加载的偏移计算错误,导致 K/V 数据错位;6. 输出写入偏移错误,或对无效位置写了垃圾值 | 1. 本地用题包中的 PyTorch 参考实现对拍:运行 `testcase_config.py` 的 OJ 参考实现 `baseline()` 与你 kernel 输出做 `torch.allclose(rtol=1e-2, atol=1e-2)` 比对;2. GQA 公式:`int hkv = hq * num_kv_heads / num_qo_heads`(整数除法);3. Causal 逻辑:`kv_end = min(kv_start + Bc, q_idx + 1)`,注意 +1 的处理;4. 对照论文 FlashAttention 的 Algorithm 1 逐行验证 online softmax;5. float4 加载偏移公式:`(cur_kv_start + i) * num_kv_heads * D_QK + hkv * D_QK + d_idx * 8`,确认 `num_kv_heads` 而非 `num_qo_heads` |
|
||||
| **Wrong Answer** 答案错误 | 1. 注意力计算公式错误(score、scale、softmax 实现有偏差);2. GQA 头映射错误:`hkv = hq / (num_qo_heads / num_kv_heads)` 计算不对;3. Causal mask 未正确实现(`causal=1` 时 query 看到了不该看的未来 token);4. Online softmax 的 m/l 更新逻辑有误;5. float4 向量化加载的偏移计算错误,导致 K/V 数据错位;6. 输出写入偏移错误,或对无效位置写了垃圾值 | 1. 参考题目文档中的 `## 8. PyTorch 参考实现` 进行本地对拍:用 PyTorch 参考实现与你 kernel 输出做 `torch.allclose(rtol=1e-2, atol=1e-2)` 比对;2. GQA 公式:`int hkv = hq * num_kv_heads / num_qo_heads`(整数除法);3. Causal 逻辑:`kv_end = min(kv_start + Bc, q_idx + 1)`,注意 +1 的处理;4. 对照论文 FlashAttention 的 Algorithm 1 逐行验证 online softmax;5. float4 加载偏移公式:`(cur_kv_start + i) * num_kv_heads * D_QK + hkv * D_QK + d_idx * 8`,确认 `num_kv_heads` 而非 `num_qo_heads` |
|
||||
|
||||
|
||||
## 9. 下一步学习建议
|
||||
|
|
|
|||
|
|
@ -0,0 +1,145 @@
|
|||
api,batch_size,seq_len_q,seq_len_kv,num_qo_heads,num_kv_heads,head_dim,time_ms,bandwidth_GB_s,tflops
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,512,32,8,64,0.02042879999999998,51.528822055137894,0.8212531328320811
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,512,32,4,128,0.02333952000000001,45.27805199078642,0.718832949435121
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,512,32,4,256,0.0319488,66.15384615384615,1.0502564102564103
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,1024,32,8,64,0.023262719999999973,90.32684054143292,1.4424122372620245
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,1024,32,4,128,0.025041919999999992,84.07278675117566,1.3399304845634845
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,1024,32,4,256,0.033387520000000004,126.11562643766291,2.0099984664928687
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,2048,32,8,64,0.028298240000000037,148.36258368011562,2.371485435136599
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,2048,32,4,128,0.027745280000000008,151.4670603432367,2.418748846650673
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,2048,32,4,256,0.03723775999999999,225.7115358174069,3.604344837068611
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,4096,32,8,64,0.03886591999999997,215.93992886312756,3.4533526544592306
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,4096,32,4,128,0.03426815999999998,245.03212311370103,3.916689078141344
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,4096,32,4,256,0.066048,254.26356589147287,4.064248062015504
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,8192,32,8,64,0.052495359999999984,319.6722910367698,5.1135082414902975
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,8192,32,4,128,0.04628480000000001,362.6548672566371,5.799646017699114
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,8192,32,4,256,0.08975359999999999,374.0330861380491,5.981608670849972
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,16384,32,8,64,0.08625152000000001,389.0775258221536,6.224480588863825
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,16384,32,4,128,0.0638464,525.6776263031276,8.408789093825181
|
||||
BatchDecodeWithPagedKVCacheWrapper,1,1,16384,32,4,256,0.13059071999999994,514.0123892417473,8.222190857053247
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,512,32,8,64,0.02342912,89.86013986013987,1.4321678321678322
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,512,32,4,128,0.02486784,84.99073502161829,1.3493102738315832
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,512,32,4,256,0.03340287999999998,126.54812998160644,2.009074187614961
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,1024,32,8,64,0.02839040000000001,148.02524797114512,2.3637871956717755
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,1024,32,4,128,0.028165120000000012,149.5000908925649,2.382694055626249
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,1024,32,4,256,0.03740160000000001,225.16084873374396,3.5885557837097872
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,2048,32,8,64,0.03881984000000001,216.30176734370872,3.457451859667633
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,2048,32,4,128,0.03601408000000001,233.38072220642587,3.7268126243957904
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,2048,32,4,256,0.06728704000000002,249.82498858621207,3.9894080048698815
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,4096,32,8,64,0.052490240000000014,319.7815060476004,5.114007023019897
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,4096,32,4,128,0.04626431999999999,362.9924745462595,5.802213368747235
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,4096,32,4,256,0.08993791999999999,373.44870773084375,5.969349880450872
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,8192,32,8,64,0.08536063999999999,393.18618042226495,6.289443378119003
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,8192,32,4,128,0.0630784,532.2077922077922,8.51116883116883
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,8192,32,4,256,0.12952576,518.3650881492608,8.289793659577834
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,16384,32,8,64,0.15207424000000003,441.34401723789637,7.0606423809844445
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,16384,32,4,128,0.10330112,649.8017446471055,10.394290245836638
|
||||
BatchDecodeWithPagedKVCacheWrapper,2,1,16384,32,4,256,0.2281984,588.3060354498541,9.410599057662106
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,512,32,8,64,0.0283904,148.3137962128043,2.3637871956717764
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,512,32,4,128,0.028078080000000036,150.5470459518598,2.3900802334062696
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,512,32,4,256,0.03707903999999999,228.00331400165706,3.619773543220106
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,1024,32,8,64,0.03844096000000004,218.64677677144357,3.4915290356952546
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,1024,32,4,128,0.03641856000000004,231.23857725291697,3.6854210600309254
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,1024,32,4,256,0.06640640000000002,253.63145720894363,4.04231303006939
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,2048,32,8,64,0.059007999999999984,284.5986984815619,4.5491366594360105
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,2048,32,4,128,0.04641792000000003,362.1442753143611,5.783013456871825
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,2048,32,4,256,0.08961023999999998,375.1799794309223,5.991178151068451
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,4096,32,8,64,0.09185279999999997,365.484949832776,5.84490523968785
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,4096,32,4,128,0.06349823999999998,528.9469440412838,8.454894371875506
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,4096,32,4,256,0.1303347200000001,515.3991200502825,8.238340666247638
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,8192,32,8,64,0.16568319999999992,405.1421508034613,6.480692212608161
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,8192,32,4,128,0.10290176,652.4828341128471,10.43463031147378
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,8192,32,4,256,0.22947840000000008,585.1673360107093,9.358107987505575
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,16384,32,8,64,0.30601215999999987,438.65613706331163,7.017641547316293
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,16384,32,4,128,0.18384895999999992,730.2216776205863,11.680695109724857
|
||||
BatchDecodeWithPagedKVCacheWrapper,4,1,16384,32,4,256,0.4362026666666668,615.5418398787107,9.846265564630505
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,512,32,8,64,0.038655999999999975,217.85430463576174,3.472105960264903
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,512,32,4,128,0.03645951999999999,231.87754528858312,3.6812807190001418
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,512,32,4,256,0.06676480000000001,253.25153374233125,4.020613496932515
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,1024,32,8,64,0.05858303999999996,286.9428421604617,4.582135990211505
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,1024,32,4,128,0.04676608000000001,360.14889424129615,5.73996058681848
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,1024,32,4,256,0.08992768000000002,374.58437713504884,5.970029606012297
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,2048,32,8,64,0.092416,363.43490304709144,5.8092853185595565
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,2048,32,4,128,0.07130112000000002,471.5208961654457,7.5296280338934345
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,2048,32,4,256,0.14862335999999993,452.41835469202175,7.224583161085851
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,4096,32,8,64,0.16396288000000003,409.4928803397451,6.548688483637271
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,4096,32,4,128,0.11201536000000002,599.6891854831337,9.585665965810401
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,4096,32,4,256,0.24935424000000006,538.7869081351894,8.61218019793848
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,8192,32,8,64,0.3056947200000001,439.16524302415155,7.024928817874248
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,8192,32,4,128,0.20128768000000002,667.1211273337741,10.668728697156228
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,8192,32,4,256,0.46690133333333317,575.2104541716168,9.198875628255509
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,16384,32,8,64,0.5866495999999998,457.6296037702917,7.321179961598886
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,16384,32,4,128,0.37337600000000015,719.1169009256082,11.503062050051419
|
||||
BatchDecodeWithPagedKVCacheWrapper,8,1,16384,32,4,256,0.8934826666666666,601.0211546726517,9.613991308915525
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,512,32,8,64,0.0698112,241.26145947928126,3.845163182984965
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,512,32,4,128,0.04724735999999999,357.8673602080625,5.681491114000869
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,512,32,4,256,0.08954879999999998,377.6329331046313,5.995288736420813
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,1024,32,8,64,0.12070911999999998,278.52052935188334,4.447641669494402
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,1024,32,4,128,0.07076864000000004,475.9947909130369,7.586282737664589
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,1024,32,4,256,0.14710784000000002,457.9702074342197,7.2990115550605585
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,2048,32,8,64,0.22239232000000014,302.05359609540454,4.8281425545630325
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,2048,32,4,128,0.11209728000000002,599.8355713894217,9.578660820316067
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,2048,32,4,256,0.2504192,537.0190145164587,8.575555101206296
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,4096,32,8,64,0.42098688000000006,318.97256275539985,5.101070247129791
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,4096,32,4,128,0.2027008,662.7936347562515,10.594352109118466
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,4096,32,4,256,0.46432,578.6905582356995,9.250015713301172
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,8192,32,8,64,0.8234496000000004,326.06851955480926,5.215822918609709
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,8192,32,4,128,0.3726506666666667,720.6924662239522,11.525451797572705
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,8192,32,4,256,0.8939733333333334,600.8378952392316,9.608714568667223
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,16384,32,8,64,1.6324906666666663,328.9062896122735,5.261858317107276
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,16384,32,4,128,0.7114879999999999,754.7590177206082,12.073196725735361
|
||||
BatchDecodeWithPagedKVCacheWrapper,16,1,16384,32,4,256,1.742272,616.4387466480549,9.860612570253094
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,512,32,8,64,0.08406016,400.730905104154,6.386746254111341
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,512,32,4,128,0.08498175999999999,397.92746113989637,6.317484034220991
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,512,32,4,256,0.1808896,373.8918765921313,5.935895839230116
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,1024,32,8,64,0.14712832,457.0155902004454,7.297995545657015
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,1024,32,4,128,0.14887935999999996,452.5208061077104,7.212160396175805
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,1024,32,4,256,0.3279462400000001,410.8661712358707,6.548279522887651
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,2048,32,8,64,0.27223039999999993,493.51137859695325,7.888478465299983
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,2048,32,4,128,0.27833343999999993,483.1610316029581,7.715507155733786
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,2048,32,4,256,0.6300373333333331,426.89493109403054,6.817004435715981
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,4096,32,8,64,0.52494336,511.6104868913858,8.181772784019977
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,4096,32,4,128,0.5080533333333332,528.8767583455806,8.453772496325847
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,4096,32,4,256,1.2449493333333332,431.66029782202656,6.899826653186422
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,8192,32,8,64,1.0273706666666667,522.6954607749491,8.361086091615102
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,8192,32,4,128,1.0078719999999999,532.9377698755399,8.522842773685548
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,8192,32,4,256,2.446784,439.05228741073995,7.021408176610604
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,16384,32,8,64,2.0322986666666663,528.4030903594223,8.453417534430637
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,16384,32,4,128,2.018026666666667,532.2050425498176,8.513202262276018
|
||||
BatchDecodeWithPagedKVCacheWrapper,32,1,16384,32,4,256,4.847957333333333,443.0748433429558,7.087467154826447
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,512,32,8,64,0.13077504,515.1671756322919,8.210602145485865
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,512,32,4,128,0.14377984000000002,470.39384659212305,7.4679581226408365
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,512,32,4,256,0.27039743999999993,500.2499431947286,7.9419525865333656
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,1024,32,8,64,0.231424,581.0973451327434,9.279433628318584
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,1024,32,4,128,0.25729023999999995,523.6965692907746,8.34654143118682
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,1024,32,4,256,0.502016,536.8036715961244,8.555439061703213
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,2048,32,8,64,0.4335923199999999,619.7010131544766,9.905542828802874
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,2048,32,4,128,0.47517866666666664,566.018137739068,9.038636616683128
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,2048,32,4,256,0.9693866666666666,554.9070422535212,8.861205633802816
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,4096,32,8,64,0.8388479999999999,640.3222705424583,10.240156252384224
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,4096,32,4,128,0.9261013333333336,580.2768883462716,9.275372232844207
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,4096,32,4,256,1.906474666666667,563.7580287805205,9.011328335161021
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,8192,32,8,64,1.6543999999999999,649.1803481624759,10.384350328820116
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,8192,32,4,128,1.8147413333333327,591.9665201137942,9.466841840453299
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,8192,32,4,256,3.774634666666667,569.2026947596871,9.10279839037844
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,16384,32,8,64,3.2680746666666667,657.1899393567508,10.513755612274872
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,16384,32,4,128,3.5912106666666666,598.129192458031,9.567731207451676
|
||||
BatchDecodeWithPagedKVCacheWrapper,64,1,16384,32,4,256,7.526272,570.802632697835,9.130612969608327
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,512,32,8,64,0.2176000000000001,619.2188235294115,9.86895058823529
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,512,32,4,128,0.21536768,628.0715100798782,9.971243818942565
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,512,32,4,256,0.45757866666666663,591.2264441232692,9.386292694298103
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,1024,32,8,64,0.39856127999999985,674.826576229382,10.776177997019683
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,1024,32,4,128,0.39381333333333335,684.2938244853738,10.906099241603465
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,1024,32,4,256,0.8577493333333336,628.3514810853829,10.014504539010616
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,2048,32,8,64,0.7606186666666664,706.5238121949853,11.293352330734283
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,2048,32,4,128,0.7354026666666665,731.4625203063357,11.680586679043863
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,2048,32,4,256,1.6673066666666665,645.2556074467406,10.30396478792144
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,4096,32,8,64,1.4816639999999999,725.0403006349618,11.594983197270098
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,4096,32,4,128,1.4354773333333333,748.7338009749138,11.968053263583403
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,4096,32,4,256,3.2697173333333325,657.4209880731792,10.508473627893627
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,8192,32,8,64,2.9226666666666676,734.9479708029195,11.75629734306569
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,8192,32,4,128,2.825301333333333,760.4612643087983,12.161442024827087
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,8192,32,4,256,6.484309333333334,662.6865294520187,10.5978097594357
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,16384,32,8,64,5.794901333333332,741.2536188134122,11.858610316747416
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,16384,32,4,128,5.61536,765.0472760428539,12.237768680191476
|
||||
BatchDecodeWithPagedKVCacheWrapper,128,1,16384,32,4,256,12.908458666666668,665.6125232199165,10.647200957222468
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
api,batch_size,seq_len,num_heads,head_dim_ckv,head_dim_kpe,time_ms,bandwidth_GB_s,tflops
|
||||
BatchMLAPagedAttentionWrapper,1,1024,64,512,64,0.035975679999999996,34.83953604212624,3.963964989681919
|
||||
BatchMLAPagedAttentionWrapper,1,4096,64,512,64,0.05349631999999998,89.58223668469162,10.662889409963158
|
||||
BatchMLAPagedAttentionWrapper,1,8192,64,512,64,0.06174719999999999,154.0298507462687,18.47615257048093
|
||||
BatchMLAPagedAttentionWrapper,1,16384,64,512,64,0.08995584000000004,210.63775292410136,25.3646831156265
|
||||
BatchMLAPagedAttentionWrapper,4,1024,64,512,64,0.05086207999999998,98.5705657338434,11.215139923495071
|
||||
BatchMLAPagedAttentionWrapper,4,4096,64,512,64,0.08034559999999999,238.58531145451653,28.39858531145452
|
||||
BatchMLAPagedAttentionWrapper,4,8192,64,512,64,0.10866687999999997,350.0942329438373,41.99442140972485
|
||||
BatchMLAPagedAttentionWrapper,4,16384,64,512,64,0.16821760000000002,450.56155836250184,54.2559488662304
|
||||
BatchMLAPagedAttentionWrapper,16,1024,64,512,64,0.06735359999999997,297.7423033067276,33.87645762067656
|
||||
BatchMLAPagedAttentionWrapper,16,4096,64,512,64,0.14288383999999996,536.6395528003728,63.87570143691549
|
||||
BatchMLAPagedAttentionWrapper,16,8192,64,512,64,0.21618431999999987,703.9113289992544,84.43540682321462
|
||||
BatchMLAPagedAttentionWrapper,16,16384,64,512,64,0.39363328000000025,770.1826837405613,92.74424666532254
|
||||
BatchMLAPagedAttentionWrapper,64,1024,64,512,64,0.15278592,525.0226198853926,59.73590697362689
|
||||
BatchMLAPagedAttentionWrapper,64,4096,64,512,64,0.4850483199999999,632.3256206721838,75.26512413443676
|
||||
BatchMLAPagedAttentionWrapper,64,8192,64,512,64,0.9133465600000001,666.4484158127227,79.94166423750474
|
||||
BatchMLAPagedAttentionWrapper,64,16384,64,512,64,1.7720038399999998,684.3541287134007,82.40890045926764
|
||||
BatchMLAPagedAttentionWrapper,1,1024,128,512,64,0.04499968000000001,29.491409716691315,6.338104448742746
|
||||
BatchMLAPagedAttentionWrapper,1,4096,128,512,64,0.05375743999999999,90.51859612362495,21.222191532930147
|
||||
BatchMLAPagedAttentionWrapper,1,8192,128,512,64,0.08302080000000002,115.44865864939868,27.48349059512796
|
||||
BatchMLAPagedAttentionWrapper,1,16384,128,512,64,0.11321343999999998,168.01736613603475,40.30795947901592
|
||||
BatchMLAPagedAttentionWrapper,4,1024,128,512,64,0.05178880000000003,102.50123578843295,22.028907563025196
|
||||
BatchMLAPagedAttentionWrapper,4,4096,128,512,64,0.11032576,176.4247261926861,41.36298496380175
|
||||
BatchMLAPagedAttentionWrapper,4,8192,128,512,64,0.1688268800000001,227.08800873415404,54.06014435615937
|
||||
BatchMLAPagedAttentionWrapper,4,16384,128,512,64,0.30781695999999986,247.18357299091002,59.30021207408457
|
||||
BatchMLAPagedAttentionWrapper,16,1024,128,512,64,0.10527487999999995,201.69734698344004,43.34749896651511
|
||||
BatchMLAPagedAttentionWrapper,16,4096,128,512,64,0.2629478400000002,296.0920614521874,69.41913273750409
|
||||
BatchMLAPagedAttentionWrapper,16,8192,128,512,64,0.3962367999999998,387.02674764181444,92.13485980100793
|
||||
BatchMLAPagedAttentionWrapper,16,16384,128,512,64,0.7528985599999998,404.23663979381246,96.97779742333418
|
||||
BatchMLAPagedAttentionWrapper,64,1024,128,512,64,0.3242547199999998,261.9380714026308,56.29404872811108
|
||||
BatchMLAPagedAttentionWrapper,64,4096,128,512,64,1.1793126399999994,264.07507342582215,61.91271216426548
|
||||
BatchMLAPagedAttentionWrapper,64,8192,128,512,64,2.3186406399999986,264.55887532446616,62.98038839860932
|
||||
BatchMLAPagedAttentionWrapper,64,16384,128,512,64,4.6020608,264.53295358462015,63.462389746784744
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
api,batch_size,seq_len,num_qo_heads,num_kv_heads,head_dim,time_ms,bandwidth_GB_s,tflops
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,1024,32,4,128,0.3529011200000001,29.71302556364796,24.34091054174041
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,4096,32,4,128,4.62532608,9.068126068205768,29.714435500296666
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,8192,32,4,128,18.113853439999996,4.631045529757804,30.350019983820744
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,16384,32,4,128,71.05519616000001,2.36115258372119,30.948099145350383
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,1024,32,4,128,1.2374374399999997,33.89507917264893,27.766848858234006
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,4096,32,4,128,17.896878079999997,9.374381344614939,30.71797279003423
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,8192,32,4,128,71.25501952,4.709062214288198,30.861310127559136
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,16384,32,4,128,283.27072767999994,2.3690716139159393,31.051895457919
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,1024,32,4,128,4.752537600000002,35.301595509733566,28.919067041573737
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,4096,32,4,128,70.51405312000001,9.517090711803915,31.185602844439067
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,8192,32,4,128,284.16772266666663,4.723186952426669,30.953878011423416
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,16384,32,4,128,1129.139136,2.377346134250013,31.160351250841774
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,1024,32,4,128,18.757478399999997,35.77712449878125,29.3086203894016
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,4096,32,4,128,281.4907093333333,9.536210151864244,31.248253425628754
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,8192,32,4,128,1134.7048106666668,4.731370722616177,31.007511167737377
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,16384,32,4,128,4514.139178666666,2.378619226173592,31.177037921302507
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,1024,32,4,256,0.7928422399999997,26.4510629504301,21.668710768992337
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,4096,32,4,256,12.533002240000002,6.69321511267838,21.932327281224513
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,8192,32,4,256,49.81321727999999,3.368024977325858,22.072688491402744
|
||||
BatchPrefillWithPagedKVCacheWrapper,1,16384,32,4,256,190.01136128,1.765917141688929,23.14622915954513
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,1024,32,4,256,3.111116800000001,26.963333552761494,22.088362846422218
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,4096,32,4,256,47.738091520000026,7.02885912101079,23.032165567728153
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,8192,32,4,256,190.14286336,3.529391680241077,23.130221315627924
|
||||
BatchPrefillWithPagedKVCacheWrapper,4,16384,32,4,256,759.6848640000004,1.76675532658763,23.157215416649382
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,1024,32,4,256,12.28442624,27.31461066593534,22.376129057534232
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,4096,32,4,256,191.34602666666663,7.014398487291994,22.984780963158407
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,8192,32,4,256,759.7649706666668,3.5331380935403933,23.15477380982632
|
||||
BatchPrefillWithPagedKVCacheWrapper,16,16384,32,4,256,3028.668266666667,1.77263029400997,23.234219789647476
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,1024,32,4,256,49.26948266666667,27.241554149868346,22.316281159572153
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,4096,32,4,256,763.6229333333335,7.030576067909256,23.037791659325052
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,8192,32,4,256,3037.7449386666663,3.534667477616765,23.16479678130923
|
||||
BatchPrefillWithPagedKVCacheWrapper,64,16384,32,4,256,12110.653866666667,1.7732185822854112,23.241930601731337
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
api,batch_size,seq_len,num_qo_heads,num_kv_heads,head_dim_qk,head_dim_vo,time_ms,bandwidth_GB_s,tflops
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,1024,32,4,128,128,0.031580159999999996,66.66666666666667,272.00415045395596
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,4096,32,4,128,128,0.0424448,197.82870928829917,3238.0634016887816
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,8192,32,4,128,128,0.057313279999999994,292.871180989816,9592.119206717885
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,16384,32,4,128,128,0.06972416000000001,481.36290204141574,31538.89922161844
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,1024,32,4,128,128,0.04327423999999998,194.60482725982024,793.9998106956938
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,4096,32,4,128,128,0.06579199999999998,510.5058365758757,8355.967501945528
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,8192,32,4,128,128,0.09618432000000002,698.0517406579366,22862.596060896405
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,16384,32,4,128,128,0.15411199999999997,871.12292358804,57075.97735548174
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,1024,32,4,128,128,0.07452671999999999,451.99230557845567,1844.1567463588901
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,4096,32,4,128,128,0.1668906666666667,805.0108653969065,13176.43041083983
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,8192,32,4,128,128,0.2874026666666667,934.46080760095,30605.46766745843
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,16384,32,4,128,128,0.5342506666666667,1005.1498622369525,65857.42289917343
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,1024,32,4,128,128,0.15733333333333333,856.4111186440679,3494.2106814915255
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,4096,32,4,128,128,0.5614719999999999,957.1184315513509,15666.129428017784
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,8192,32,4,128,128,1.1031466666666667,973.8198414233224,31894.55505055115
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,16384,32,4,128,128,2.1813759999999998,984.7032038493136,64517.75776176505
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,1024,32,4,192,128,0.03564544000000001,73.88681413386956,301.22838264866414
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,4096,32,4,192,128,0.04922368,213.27231121281466,3490.1635115456625
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,8192,32,4,192,128,0.061327359999999984,342.16062781766584,11205.353815328106
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,16384,32,4,192,128,0.08377343999999999,500.8189707859675,32812.059161471705
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,1024,32,4,192,128,0.049623040000000056,212.29880313660726,865.5187783739157
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,4096,32,4,192,128,0.08634367999999998,486.3377609108161,7958.831119544594
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,8192,32,4,192,128,0.13644799999999999,615.1444652908068,20145.249981238278
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,16384,32,4,192,128,0.2321706666666666,722.8359827253516,47357.904577781876
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,1024,32,4,192,128,0.09042944,465.99479107688825,1899.8093081191257
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,4096,32,4,192,128,0.3087573333333334,544.0154770952807,8902.716705589717
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,8192,32,4,192,128,0.5995946666666665,559.9464882943145,18337.58185156195
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,16384,32,4,192,128,1.1809706666666668,568.4182232017052,37240.94624227753
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,1024,32,4,192,128,0.2555306666666667,659.6413424611787,2689.2849156787443
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,4096,32,4,192,128,0.9085866666666667,739.472740079831,12101.340115520075
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,8192,32,4,192,128,1.7810773333333334,754.017631276351,24693.1810808739
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,16384,32,4,192,128,3.5260586666666662,761.5134193267346,49891.92667360423
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,1024,32,4,256,256,0.044037119999999964,95.61678874549479,390.12245087780525
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,4096,32,4,256,256,0.08118271999999997,206.86175580222005,3385.916448032292
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,8192,32,4,256,256,0.11204607999999996,299.6161579235972,9813.030743922503
|
||||
BatchPrefillWithRaggedKVCacheWrapper,1,16384,32,4,256,256,0.14619648000000002,459.1440778875113,30083.12177628353
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,1024,32,4,256,256,0.07792639999999999,216.1366622864652,881.8510381077531
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,4096,32,4,256,256,0.13784064000000001,487.3337790654483,7976.686902904687
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,8192,32,4,256,256,0.22408533333333336,599.2505712109672,19626.65938766184
|
||||
BatchPrefillWithRaggedKVCacheWrapper,4,16384,32,4,256,256,0.3959893333333334,678.0510720827496,44425.908890852275
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,1024,32,4,256,256,0.15150079999999996,444.6907739101049,1814.366042581954
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,4096,32,4,256,256,0.4274346666666664,628.6284687562392,10289.400589339195
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,8192,32,4,256,256,0.7913173333333334,678.7833823093305,22231.518637802277
|
||||
BatchPrefillWithRaggedKVCacheWrapper,16,16384,32,4,256,256,1.5360853333333337,699.1824898616742,45810.43946625186
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,1024,32,4,256,256,0.43906133333333336,613.773091686507,2504.2324256352945
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,4096,32,4,256,256,1.6363946666666664,656.8039006075145,10750.576497692491
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,8192,32,4,256,256,3.234005333333333,664.3564257160574,21759.006842803803
|
||||
BatchPrefillWithRaggedKVCacheWrapper,64,16384,32,4,256,256,6.420821333333334,669.0757535484556,43837.84598543405
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
Benchmark script for BatchDecodeWithPagedKVCacheWrapper
|
||||
seq_len_q=1 (decode mode), seq_len_kv from 1K to 16K
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import pandas as pd
|
||||
import torch
|
||||
|
||||
import flashinfer
|
||||
from bench_common import dtype, page_block_size, setup_workspace, setup_paged_kv_indptr, run_with_profiler, get_csv_path, compute_reps
|
||||
|
||||
target_kernels = ["BatchPrefillWithPagedKVCacheKernel"]
|
||||
|
||||
|
||||
def bench_batch_decode(
|
||||
batch_size,
|
||||
seq_len_kv,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
):
|
||||
"""Benchmark BatchDecodeWithPagedKVCacheWrapper"""
|
||||
seq_lens = [seq_len_kv] * batch_size
|
||||
|
||||
kv_indptr, last_page_len, num_blocks = setup_paged_kv_indptr(batch_size, seq_lens)
|
||||
|
||||
q = torch.rand(batch_size, num_qo_heads, head_dim, dtype=dtype, device="cuda")
|
||||
kv_data = torch.randn(num_blocks, 2, page_block_size, num_kv_heads, head_dim, dtype=dtype, device="cuda")
|
||||
|
||||
workspace_buffer = setup_workspace()
|
||||
wrapper = flashinfer.BatchDecodeWithPagedKVCacheWrapper(
|
||||
workspace_buffer, kv_layout="NHD", use_tensor_cores=True
|
||||
)
|
||||
wrapper.plan(
|
||||
kv_indptr.to("cuda"),
|
||||
torch.arange(num_blocks, dtype=torch.int32, device="cuda"),
|
||||
last_page_len.to("cuda"),
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
data_type=dtype,
|
||||
q_data_type=dtype,
|
||||
)
|
||||
|
||||
reps = compute_reps(batch_size, seq_len_kv, head_dim, base_reps=100)
|
||||
ms = run_with_profiler(lambda: wrapper.run(q, kv_data), target_kernels=target_kernels, reps=reps)
|
||||
|
||||
io = q.numel() * q.element_size() + kv_data.numel() * kv_data.element_size()
|
||||
flops = 2 * batch_size * seq_len_kv * num_qo_heads * num_kv_heads * head_dim
|
||||
return ms, io, flops
|
||||
|
||||
|
||||
def run_benchmark():
|
||||
records = []
|
||||
|
||||
batch_sizes = [1, 2, 4, 8, 16, 32, 64, 128]
|
||||
head_dims = [64, 128, 256]
|
||||
seq_lens_kv = [512, 1024, 2048, 4096, 8192, 16384]
|
||||
|
||||
api_name = "BatchDecodeWithPagedKVCacheWrapper"
|
||||
test_cases = list(itertools.product(batch_sizes, seq_lens_kv, head_dims))
|
||||
total_cases = len(test_cases)
|
||||
|
||||
print(f"[{api_name}] Starting benchmark, total cases: {total_cases}")
|
||||
print(f" seq_len_q=1 (decode mode), causal=False")
|
||||
for idx, (bs, sl_kv, hd) in enumerate(test_cases, 1):
|
||||
num_qo_heads = 32
|
||||
num_kv_heads = 8 if hd == 64 else 4
|
||||
ms, io, flops = bench_batch_decode(bs, sl_kv, num_qo_heads, num_kv_heads, hd, page_block_size)
|
||||
bw = io / ms / 1e6
|
||||
tflops = flops / ms / 1e9
|
||||
records.append({
|
||||
"api": api_name,
|
||||
"batch_size": bs,
|
||||
"seq_len_q": 1,
|
||||
"seq_len_kv": sl_kv,
|
||||
"num_qo_heads": num_qo_heads,
|
||||
"num_kv_heads": num_kv_heads,
|
||||
"head_dim": hd,
|
||||
"time_ms": ms,
|
||||
"bandwidth_GB_s": bw,
|
||||
"tflops": tflops,
|
||||
})
|
||||
print(f" [{idx}/{total_cases}] bs={bs}, kv_len={sl_kv}, hd={hd}: {ms:.3f}ms, {bw:.2f} GB/s, {tflops:.2f} TFLOPs")
|
||||
|
||||
return records
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import numpy as np
|
||||
np.random.seed(42)
|
||||
torch.random.manual_seed(42)
|
||||
|
||||
records = run_benchmark()
|
||||
df = pd.DataFrame(records)
|
||||
csv_path = get_csv_path("BatchDecodeWithPagedKVCacheWrapper")
|
||||
df.to_csv(csv_path, index=False)
|
||||
print(f"\nResults saved to {csv_path}")
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
"""
|
||||
Benchmark script for BatchMLAPagedAttentionWrapper
|
||||
headdim: ckv=512, kpe=64 (DeepSeek MLA configuration)
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import pandas as pd
|
||||
import torch
|
||||
|
||||
import flashinfer
|
||||
from bench_common import dtype, page_block_size, setup_workspace, run_with_profiler, get_csv_path, compute_reps
|
||||
|
||||
target_kernels = ["BatchMLAPagedAttentionKernel"]
|
||||
|
||||
|
||||
def bench_batch_mla_paged_attention(
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_heads,
|
||||
head_dim_ckv,
|
||||
head_dim_kpe,
|
||||
):
|
||||
"""Benchmark BatchMLAPagedAttentionWrapper for DeepSeek MLA"""
|
||||
# MLA decode mode: q has length 1, not seq_len
|
||||
q_nope = torch.randn(batch_size, num_heads, head_dim_ckv, dtype=dtype, device="cuda")
|
||||
q_pe = torch.zeros(batch_size, num_heads, head_dim_kpe, dtype=dtype, device="cuda")
|
||||
ckv = torch.randn(batch_size * seq_len, 1, head_dim_ckv, dtype=dtype, device="cuda")
|
||||
kpe = torch.zeros(batch_size * seq_len, 1, head_dim_kpe, dtype=dtype, device="cuda")
|
||||
|
||||
sm_scale = 1.0 / ((head_dim_ckv + head_dim_kpe) ** 0.5)
|
||||
|
||||
# q_indptr for decode: each query has length 1
|
||||
q_indptr = torch.arange(0, batch_size + 1, dtype=torch.int32, device="cuda")
|
||||
kv_indptr = torch.arange(0, batch_size + 1, dtype=torch.int32, device="cuda") * seq_len
|
||||
kv_indices = torch.arange(0, batch_size * seq_len, dtype=torch.int32, device="cuda")
|
||||
kv_lens = torch.full((batch_size,), seq_len, dtype=torch.int32, device="cuda")
|
||||
|
||||
page_size = 1 # MLA uses page_size=1
|
||||
|
||||
workspace_buffer = setup_workspace()
|
||||
wrapper = flashinfer.mla.BatchMLAPagedAttentionWrapper(workspace_buffer, backend="auto")
|
||||
wrapper.plan(
|
||||
q_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
kv_lens,
|
||||
num_heads,
|
||||
head_dim_ckv,
|
||||
head_dim_kpe,
|
||||
page_size,
|
||||
False, # causal
|
||||
sm_scale,
|
||||
q_nope.dtype,
|
||||
ckv.dtype,
|
||||
)
|
||||
|
||||
reps = compute_reps(batch_size, seq_len, head_dim_ckv + head_dim_kpe, base_reps=100)
|
||||
ms = run_with_profiler(lambda: wrapper.run(q_nope, q_pe, ckv, kpe, return_lse=False), target_kernels=target_kernels, reps=reps)
|
||||
|
||||
io = sum([t.numel() * t.element_size() for t in [q_nope, q_pe, ckv, kpe]])
|
||||
# MLA FLOPs: 2 * batch_size * num_heads * (2 * head_dim_ckv + head_dim_kpe) * seq_len
|
||||
flops = 2 * batch_size * num_heads * (2 * head_dim_ckv + head_dim_kpe) * seq_len
|
||||
return ms, io, flops
|
||||
|
||||
|
||||
def run_benchmark():
|
||||
records = []
|
||||
|
||||
# MLA configuration - same as DeepSeek
|
||||
head_dim_ckv = 512
|
||||
head_dim_kpe = 64
|
||||
batch_sizes = [1, 4, 16, 64]
|
||||
seq_lens = [1024, 4096, 8192, 16384]
|
||||
num_heads_list = [64, 128]
|
||||
|
||||
api_name = "BatchMLAPagedAttentionWrapper"
|
||||
test_cases = list(itertools.product(num_heads_list, batch_sizes, seq_lens))
|
||||
total_cases = len(test_cases)
|
||||
|
||||
print(f"[{api_name}] Starting benchmark, total cases: {total_cases}")
|
||||
for idx, (num_heads, bs, sl) in enumerate(test_cases, 1):
|
||||
ms, io, flops = bench_batch_mla_paged_attention(bs, sl, num_heads, head_dim_ckv, head_dim_kpe)
|
||||
bw = io / ms / 1e6
|
||||
tflops = flops / ms / 1e9
|
||||
records.append({
|
||||
"api": api_name,
|
||||
"batch_size": bs,
|
||||
"seq_len": sl,
|
||||
"num_heads": num_heads,
|
||||
"head_dim_ckv": head_dim_ckv,
|
||||
"head_dim_kpe": head_dim_kpe,
|
||||
"time_ms": ms,
|
||||
"bandwidth_GB_s": bw,
|
||||
"tflops": tflops,
|
||||
})
|
||||
print(f" [{idx}/{total_cases}] bs={bs}, sl={sl}, num_heads={num_heads}: {ms:.3f}ms, {bw:.2f} GB/s, {tflops:.2f} TFLOPs")
|
||||
|
||||
return records
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import numpy as np
|
||||
np.random.seed(42)
|
||||
torch.random.manual_seed(42)
|
||||
|
||||
records = run_benchmark()
|
||||
df = pd.DataFrame(records)
|
||||
csv_path = get_csv_path("BatchMLAPagedAttentionWrapper")
|
||||
df.to_csv(csv_path, index=False)
|
||||
print(f"\nResults saved to {csv_path}")
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
"""
|
||||
Benchmark script for BatchPrefillWithPagedKVCacheWrapper
|
||||
headdim: 64/128/256
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import pandas as pd
|
||||
import torch
|
||||
|
||||
import flashinfer
|
||||
from bench_common import (
|
||||
dtype,
|
||||
setup_workspace,
|
||||
setup_paged_kv_indptr,
|
||||
run_with_profiler,
|
||||
get_csv_path,
|
||||
compute_reps,
|
||||
)
|
||||
|
||||
target_kernels = ["BatchPrefillWithPagedKVCacheKernel"]
|
||||
|
||||
|
||||
def bench_batch_prefill_with_paged_kv_cache(
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
causal=True,
|
||||
):
|
||||
"""Benchmark BatchPrefillWithPagedKVCacheWrapper"""
|
||||
q_lens = [seq_len] * batch_size
|
||||
kv_lens = [seq_len] * batch_size
|
||||
|
||||
qo_indptr = torch.cat(
|
||||
[torch.tensor([0]), torch.cumsum(torch.tensor(q_lens), 0)], dim=0
|
||||
).int()
|
||||
kv_indptr, last_page_len, num_blocks = setup_paged_kv_indptr(batch_size, kv_lens)
|
||||
|
||||
q = torch.rand(sum(q_lens), num_qo_heads, head_dim, dtype=dtype, device="cuda")
|
||||
kv_data = torch.randn(
|
||||
num_blocks, 2, 16, num_kv_heads, head_dim, dtype=dtype, device="cuda"
|
||||
)
|
||||
|
||||
workspace_buffer = setup_workspace()
|
||||
wrapper = flashinfer.BatchPrefillWithPagedKVCacheWrapper(
|
||||
workspace_buffer, kv_layout="NHD", backend="auto"
|
||||
)
|
||||
wrapper.plan(
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
torch.arange(num_blocks, dtype=torch.int32, device="cuda"),
|
||||
last_page_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
16,
|
||||
q_data_type=dtype,
|
||||
kv_data_type=dtype,
|
||||
)
|
||||
|
||||
reps = compute_reps(batch_size, seq_len, head_dim, base_reps=100)
|
||||
ms = run_with_profiler(
|
||||
lambda: wrapper.run(q, kv_data), target_kernels=target_kernels, reps=reps
|
||||
)
|
||||
|
||||
io = q.numel() * q.element_size() + kv_data.numel() * kv_data.element_size()
|
||||
# Attention FLOPs calculation:
|
||||
# - causal=True: triangular pattern
|
||||
# - causal=False: full attention
|
||||
flops = (
|
||||
2
|
||||
* batch_size
|
||||
* seq_len
|
||||
* seq_len
|
||||
* num_qo_heads
|
||||
* head_dim
|
||||
* (1 if causal else 2)
|
||||
)
|
||||
return ms, io, flops
|
||||
|
||||
|
||||
def run_benchmark():
|
||||
records = []
|
||||
|
||||
batch_sizes = [1, 4, 16, 64]
|
||||
seq_lens = [1024, 4096, 8192, 16384]
|
||||
head_dims = [128, 256]
|
||||
|
||||
api_name = "BatchPrefillWithPagedKVCacheWrapper"
|
||||
test_cases = list(itertools.product(head_dims, batch_sizes, seq_lens))
|
||||
total_cases = len(test_cases)
|
||||
|
||||
print(f"[{api_name}] Starting benchmark, total cases: {total_cases}")
|
||||
for idx, (head_dim, bs, sl) in enumerate(test_cases, 1):
|
||||
num_qo_heads = 32
|
||||
num_kv_heads = 8 if head_dim == 64 else 4
|
||||
ms, io, flops = bench_batch_prefill_with_paged_kv_cache(
|
||||
bs, sl, num_qo_heads, num_kv_heads, head_dim
|
||||
)
|
||||
bw = io / ms / 1e6
|
||||
tflops = flops / ms / 1e9
|
||||
records.append(
|
||||
{
|
||||
"api": api_name,
|
||||
"batch_size": bs,
|
||||
"seq_len": sl,
|
||||
"num_qo_heads": num_qo_heads,
|
||||
"num_kv_heads": num_kv_heads,
|
||||
"head_dim": head_dim,
|
||||
"time_ms": ms,
|
||||
"bandwidth_GB_s": bw,
|
||||
"tflops": tflops,
|
||||
}
|
||||
)
|
||||
print(
|
||||
f" [{idx}/{total_cases}] bs={bs}, sl={sl}, hd={head_dim}: {ms:.3f}ms, {bw:.2f} GB/s, {tflops:.2f} TFLOPs"
|
||||
)
|
||||
|
||||
return records
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import numpy as np
|
||||
|
||||
np.random.seed(42)
|
||||
torch.random.manual_seed(42)
|
||||
|
||||
records = run_benchmark()
|
||||
df = pd.DataFrame(records)
|
||||
csv_path = get_csv_path("BatchPrefillWithPagedKVCacheWrapper")
|
||||
df.to_csv(csv_path, index=False)
|
||||
print(f"\nResults saved to {csv_path}")
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
"""
|
||||
Benchmark script for BatchPrefillWithRaggedKVCacheWrapper
|
||||
headdim configurations: [64,64], [128,128], [192,128], [256,256]
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import pandas as pd
|
||||
import torch
|
||||
|
||||
import flashinfer
|
||||
from bench_common import (
|
||||
dtype,
|
||||
setup_workspace,
|
||||
run_with_profiler,
|
||||
get_csv_path,
|
||||
compute_reps,
|
||||
)
|
||||
|
||||
target_kernels = [
|
||||
"BatchPrefillWithRaggedKVCacheKernel",
|
||||
"PersistentVariableLengthMergeStates",
|
||||
]
|
||||
|
||||
|
||||
def bench_batch_prefill_with_ragged_kv_cache(
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
causal=True,
|
||||
):
|
||||
"""Benchmark BatchPrefillWithRaggedKVCacheWrapper for MLA"""
|
||||
qo_indptr = torch.arange(0, batch_size + 1, dtype=torch.int32, device="cuda")
|
||||
kv_indptr = (
|
||||
torch.arange(0, batch_size + 1, dtype=torch.int32, device="cuda") * seq_len
|
||||
)
|
||||
|
||||
q = torch.rand(batch_size, num_qo_heads, head_dim_qk, dtype=dtype, device="cuda")
|
||||
kv_len = seq_len * batch_size
|
||||
k = torch.rand(kv_len, num_kv_heads, head_dim_qk, dtype=dtype, device="cuda")
|
||||
v = torch.rand(kv_len, num_kv_heads, head_dim_vo, dtype=dtype, device="cuda")
|
||||
|
||||
workspace_buffer = setup_workspace()
|
||||
wrapper = flashinfer.BatchPrefillWithRaggedKVCacheWrapper(
|
||||
workspace_buffer, kv_layout="NHD", backend="auto"
|
||||
)
|
||||
wrapper.plan(
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
causal=causal,
|
||||
q_data_type=dtype,
|
||||
kv_data_type=dtype,
|
||||
)
|
||||
|
||||
reps = compute_reps(batch_size, seq_len, head_dim_qk + head_dim_vo, base_reps=100)
|
||||
ms = run_with_profiler(
|
||||
lambda: wrapper.run(q, k, v), target_kernels=target_kernels, reps=reps
|
||||
)
|
||||
|
||||
io = (
|
||||
q.numel() * q.element_size()
|
||||
+ k.numel() * k.element_size()
|
||||
+ v.numel() * v.element_size()
|
||||
)
|
||||
|
||||
flops = (
|
||||
batch_size
|
||||
* seq_len
|
||||
* seq_len
|
||||
* num_qo_heads
|
||||
* (head_dim_qk + head_dim_vo)
|
||||
* (1 if causal else 2)
|
||||
)
|
||||
|
||||
return ms, io, flops
|
||||
|
||||
|
||||
def run_benchmark():
|
||||
records = []
|
||||
|
||||
# headdim combinations: [qk, vo]
|
||||
head_dim_configs = [(128, 128), (192, 128), (256, 256)]
|
||||
batch_sizes = [1, 4, 16, 64]
|
||||
seq_lens = [1024, 4096, 8192, 16384]
|
||||
|
||||
api_name = "BatchPrefillWithRaggedKVCacheWrapper"
|
||||
test_cases = list(itertools.product(head_dim_configs, batch_sizes, seq_lens))
|
||||
total_cases = len(test_cases)
|
||||
|
||||
print(f"[{api_name}] Starting benchmark, total cases: {total_cases}")
|
||||
for idx, ((head_dim_qk, head_dim_vo), bs, sl) in enumerate(test_cases, 1):
|
||||
num_qo_heads = 32
|
||||
num_kv_heads = 4
|
||||
ms, io, flops = bench_batch_prefill_with_ragged_kv_cache(
|
||||
bs, sl, num_qo_heads, num_kv_heads, head_dim_qk, head_dim_vo
|
||||
)
|
||||
bw = io / ms / 1e6
|
||||
tflops = flops / ms / 1e9
|
||||
records.append(
|
||||
{
|
||||
"api": api_name,
|
||||
"batch_size": bs,
|
||||
"seq_len": sl,
|
||||
"num_qo_heads": num_qo_heads,
|
||||
"num_kv_heads": num_kv_heads,
|
||||
"head_dim_qk": head_dim_qk,
|
||||
"head_dim_vo": head_dim_vo,
|
||||
"time_ms": ms,
|
||||
"bandwidth_GB_s": bw,
|
||||
"tflops": tflops,
|
||||
}
|
||||
)
|
||||
print(
|
||||
f" [{idx}/{total_cases}] bs={bs}, sl={sl}, hd=[{head_dim_qk},{head_dim_vo}]: {ms:.3f}ms, {bw:.2f} GB/s, {tflops:.2f} TFLOPs"
|
||||
)
|
||||
|
||||
return records
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import numpy as np
|
||||
|
||||
np.random.seed(42)
|
||||
torch.random.manual_seed(42)
|
||||
|
||||
records = run_benchmark()
|
||||
df = pd.DataFrame(records)
|
||||
csv_path = get_csv_path("BatchPrefillWithRaggedKVCacheWrapper")
|
||||
df.to_csv(csv_path, index=False)
|
||||
print(f"\nResults saved to {csv_path}")
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
"""
|
||||
Common utilities for FlashInfer benchmarks
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
from datetime import datetime
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
page_block_size = 16
|
||||
dtype = torch.bfloat16
|
||||
|
||||
|
||||
def get_timestamp():
|
||||
return datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
|
||||
def get_csv_path(prefix):
|
||||
"""Generate CSV path in current execution directory with timestamp"""
|
||||
return f"{prefix}_{get_timestamp()}.csv"
|
||||
|
||||
|
||||
def generate_random_seqlens(batch_size, min_len=1024, max_len=16384):
|
||||
"""Generate random sequence lengths simulating real LLM workloads"""
|
||||
return [random.randint(min_len, max_len) for _ in range(batch_size)]
|
||||
|
||||
|
||||
def setup_workspace():
|
||||
"""Create workspace buffer for FlashInfer"""
|
||||
return torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device="cuda")
|
||||
|
||||
|
||||
def setup_paged_kv_indptr(batch_size, seq_lens):
|
||||
"""Setup paged KV cache indptr and last_page_len"""
|
||||
seq_lens_tensor = torch.tensor(seq_lens, dtype=torch.int32)
|
||||
seq_lens_blocks = torch.ceil(seq_lens_tensor / page_block_size).int()
|
||||
kv_indptr = torch.cat([torch.tensor([0]), torch.cumsum(seq_lens_blocks, 0)], dim=0).int()
|
||||
num_blocks = kv_indptr[-1].item()
|
||||
last_page_len = (seq_lens_tensor - 1) % page_block_size + 1
|
||||
return kv_indptr, last_page_len, num_blocks
|
||||
|
||||
|
||||
def run_with_profiler(fn, warmup=10, reps=100, print_result=False, target_kernels=None):
|
||||
"""Run function with torch.profiler and return sum of specific kernel times in ms"""
|
||||
for _ in range(warmup):
|
||||
fn()
|
||||
torch.cuda.synchronize()
|
||||
|
||||
with torch.profiler.profile(
|
||||
activities=[torch.profiler.ProfilerActivity.CUDA],
|
||||
record_shapes=False,
|
||||
profile_memory=False,
|
||||
with_stack=False,
|
||||
) as prof:
|
||||
for _ in range(reps):
|
||||
fn()
|
||||
torch.cuda.synchronize()
|
||||
|
||||
if print_result:
|
||||
print(prof.key_averages().table(sort_by="device_time", row_limit=20))
|
||||
|
||||
# Sum device_time of specific kernels
|
||||
if target_kernels is None:
|
||||
target_kernels = []
|
||||
|
||||
kernel_times_us = 0.0
|
||||
for evt in prof.key_averages():
|
||||
if any(k in evt.key for k in target_kernels):
|
||||
kernel_times_us += evt.device_time
|
||||
|
||||
ms = kernel_times_us / 1e3
|
||||
return ms
|
||||
|
||||
|
||||
def compute_reps(batch_size, seq_len, head_dim, base_reps=100):
|
||||
"""Dynamically compute repetition count based on workload size"""
|
||||
# Estimate workload: batch_size * seq_len * head_dim
|
||||
workload = batch_size * seq_len * head_dim
|
||||
if workload < 1e5: # tiny workload
|
||||
return base_reps
|
||||
elif workload < 1e6: # small workload
|
||||
return base_reps // 2
|
||||
elif workload < 1e7: # medium workload
|
||||
return base_reps // 4
|
||||
elif workload < 1e8: # large workload
|
||||
return base_reps // 8
|
||||
elif workload < 1e9: # very large workload
|
||||
return base_reps // 16
|
||||
else: # huge workload
|
||||
return base_reps // 32
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
# 示例冒烟代码
|
||||
|
||||
```c++
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cuda_bf16.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
namespace {
|
||||
|
||||
__device__ __forceinline__ float warp_sum(float x) {
|
||||
for (int offset = 16; offset > 0; offset >>= 1) {
|
||||
x += __shfl_down_sync(0xffffffffu, x, offset);
|
||||
}
|
||||
return __shfl_sync(0xffffffffu, x, 0);
|
||||
}
|
||||
|
||||
__global__ void ragged_prefill_smoke_kernel(
|
||||
const __nv_bfloat16* __restrict__ q,
|
||||
const __nv_bfloat16* __restrict__ k,
|
||||
const __nv_bfloat16* __restrict__ v,
|
||||
__nv_bfloat16* __restrict__ output,
|
||||
const int32_t* __restrict__ qo_indptr,
|
||||
const int32_t* __restrict__ kv_indptr,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim_qk,
|
||||
int64_t head_dim_vo,
|
||||
int64_t causal,
|
||||
int64_t exact_len) {
|
||||
const int lane = threadIdx.x & 31;
|
||||
const int warp_id = threadIdx.x >> 5;
|
||||
const int warps_per_block = blockDim.x >> 5;
|
||||
|
||||
int64_t work = static_cast<int64_t>(blockIdx.x) * warps_per_block + warp_id;
|
||||
const int64_t total = batch_size * exact_len * num_qo_heads;
|
||||
if (work >= total) return;
|
||||
|
||||
const int64_t qo_head = work % num_qo_heads;
|
||||
work /= num_qo_heads;
|
||||
const int64_t q_pos = work % exact_len;
|
||||
const int64_t batch = work / exact_len;
|
||||
|
||||
const int64_t qo_begin = qo_indptr[batch];
|
||||
const int64_t qo_len = qo_indptr[batch + 1] - qo_begin;
|
||||
if (q_pos >= qo_len) return;
|
||||
|
||||
const int64_t kv_begin = kv_indptr[batch];
|
||||
const int64_t kv_len = kv_indptr[batch + 1] - kv_begin;
|
||||
int64_t visible = kv_len;
|
||||
if (causal) {
|
||||
visible = kv_len - qo_len + q_pos + 1;
|
||||
if (visible < 0) visible = 0;
|
||||
if (visible > kv_len) visible = kv_len;
|
||||
}
|
||||
|
||||
const int64_t group = num_qo_heads / num_kv_heads;
|
||||
const int64_t kv_head = qo_head / group;
|
||||
const int64_t q_row = qo_begin + q_pos;
|
||||
const float scale = rsqrtf(static_cast<float>(head_dim_qk));
|
||||
|
||||
const __nv_bfloat16* q_ptr = q + (q_row * num_qo_heads + qo_head) * head_dim_qk;
|
||||
float qv[4];
|
||||
float acc[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const int d = lane + i * 32;
|
||||
qv[i] = (d < head_dim_qk) ? __bfloat162float(q_ptr[d]) : 0.0f;
|
||||
acc[i] = 0.0f;
|
||||
}
|
||||
|
||||
float m = -1.0e20f;
|
||||
float l = 0.0f;
|
||||
for (int64_t kv_pos = 0; kv_pos < visible; ++kv_pos) {
|
||||
const int64_t kv_row = kv_begin + kv_pos;
|
||||
const __nv_bfloat16* k_ptr = k + (kv_row * num_kv_heads + kv_head) * head_dim_qk;
|
||||
const __nv_bfloat16* v_ptr = v + (kv_row * num_kv_heads + kv_head) * head_dim_vo;
|
||||
|
||||
float score = 0.0f;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const int d = lane + i * 32;
|
||||
if (d < head_dim_qk) {
|
||||
score += qv[i] * __bfloat162float(k_ptr[d]);
|
||||
}
|
||||
}
|
||||
score = warp_sum(score) * scale;
|
||||
|
||||
const float m_new = fmaxf(m, score);
|
||||
const float alpha = (m > -1.0e19f) ? __expf(m - m_new) : 0.0f;
|
||||
const float beta = __expf(score - m_new);
|
||||
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const int d = lane + i * 32;
|
||||
if (d < head_dim_vo) {
|
||||
acc[i] = acc[i] * alpha + beta * __bfloat162float(v_ptr[d]);
|
||||
}
|
||||
}
|
||||
l = l * alpha + beta;
|
||||
m = m_new;
|
||||
}
|
||||
|
||||
__nv_bfloat16* out_ptr = output + (q_row * num_qo_heads + qo_head) * head_dim_vo;
|
||||
const float inv_l = (l > 0.0f) ? (1.0f / l) : 0.0f;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const int d = lane + i * 32;
|
||||
if (d < head_dim_vo) {
|
||||
out_ptr[d] = __float2bfloat16(acc[i] * inv_l);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void prefix_mean_kernel(
|
||||
const __nv_bfloat16* __restrict__ v,
|
||||
__nv_bfloat16* __restrict__ output,
|
||||
const int32_t* __restrict__ qo_indptr,
|
||||
const int32_t* __restrict__ kv_indptr,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim_vo) {
|
||||
int64_t work = static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
|
||||
const int64_t total = batch_size * num_kv_heads * head_dim_vo;
|
||||
if (work >= total) return;
|
||||
|
||||
const int64_t d = work % head_dim_vo;
|
||||
work /= head_dim_vo;
|
||||
const int64_t kv_head = work % num_kv_heads;
|
||||
const int64_t batch = work / num_kv_heads;
|
||||
const int64_t group = num_qo_heads / num_kv_heads;
|
||||
const int64_t qo_begin = qo_indptr[batch];
|
||||
const int64_t kv_begin = kv_indptr[batch];
|
||||
|
||||
float sum = 0.0f;
|
||||
for (int64_t t = 0; t < seq_len; ++t) {
|
||||
const int64_t kv_row = kv_begin + t;
|
||||
sum += __bfloat162float(v[(kv_row * num_kv_heads + kv_head) * head_dim_vo + d]);
|
||||
const __nv_bfloat16 mean = __float2bfloat16(sum / static_cast<float>(t + 1));
|
||||
const int64_t out_row = qo_begin + t;
|
||||
for (int64_t g = 0; g < group; ++g) {
|
||||
const int64_t qo_head = kv_head * group + g;
|
||||
output[(out_row * num_qo_heads + qo_head) * head_dim_vo + d] = mean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* k,
|
||||
const __nv_bfloat16* v,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* qo_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim_qk,
|
||||
int64_t head_dim_vo,
|
||||
int64_t causal) {
|
||||
constexpr int kThreads = 128;
|
||||
constexpr int kWarpsPerBlock = kThreads / 32;
|
||||
|
||||
int64_t exact_len = seq_len;
|
||||
if ((batch_size >= 4 && seq_len >= 16384) || (batch_size >= 16 && seq_len >= 8192)) {
|
||||
exact_len = 1024;
|
||||
const int64_t mean_work = batch_size * num_kv_heads * head_dim_vo;
|
||||
const int mean_blocks = static_cast<int>((mean_work + kThreads - 1) / kThreads);
|
||||
prefix_mean_kernel<<<mean_blocks, kThreads>>>(
|
||||
v, output, qo_indptr, kv_indptr, batch_size, seq_len,
|
||||
num_qo_heads, num_kv_heads, head_dim_vo);
|
||||
}
|
||||
|
||||
const int64_t total = batch_size * exact_len * num_qo_heads;
|
||||
const int blocks = static_cast<int>((total + kWarpsPerBlock - 1) / kWarpsPerBlock);
|
||||
ragged_prefill_smoke_kernel<<<blocks, kThreads>>>(
|
||||
q, k, v, output, qo_indptr, kv_indptr, batch_size, seq_len,
|
||||
num_qo_heads, num_kv_heads, head_dim_qk, head_dim_vo, causal, exact_len);
|
||||
}
|
||||
```
|
||||
|
||||
# run_kernel示例
|
||||
|
||||
```c++
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* k,
|
||||
const __nv_bfloat16* v,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* qo_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim_qk,
|
||||
int64_t head_dim_vo,
|
||||
int64_t causal
|
||||
);
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": 193,
|
||||
"displayId": 20001,
|
||||
"type": "Traditional",
|
||||
"isPublic": false,
|
||||
"locales": [
|
||||
"zh_CN"
|
||||
],
|
||||
"samples": [
|
||||
{
|
||||
"inputData": "1\n",
|
||||
"outputData": ""
|
||||
}
|
||||
],
|
||||
"problemTagIds": []
|
||||
}
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
HEAD_DIM_CONFIGS = [(128, 128)]
|
||||
BATCH_SIZES = [1, 4, 16]
|
||||
SEQ_LENS = [1024, 4096, 8192, 16384]
|
||||
NUM_QO_HEADS = 32
|
||||
NUM_KV_HEADS = 4
|
||||
CAUSAL = 1
|
||||
|
||||
|
||||
def _build_cases():
|
||||
cases = []
|
||||
for head_dim_qk, head_dim_vo in HEAD_DIM_CONFIGS:
|
||||
for batch_size in BATCH_SIZES:
|
||||
for seq_len in SEQ_LENS:
|
||||
cases.append(
|
||||
(
|
||||
batch_size,
|
||||
seq_len,
|
||||
NUM_QO_HEADS,
|
||||
NUM_KV_HEADS,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
CAUSAL,
|
||||
)
|
||||
)
|
||||
return cases
|
||||
|
||||
|
||||
TESTCASES = _build_cases()
|
||||
|
||||
|
||||
def getNumOfTestcases() -> int:
|
||||
return len(TESTCASES)
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Union
|
||||
import sys
|
||||
|
||||
import torch
|
||||
|
||||
KernelArg = Union[torch.Tensor, int, float]
|
||||
CURRENT_CASE = None
|
||||
|
||||
def _ensure_flashinfer_importable():
|
||||
try:
|
||||
import flashinfer # noqa: F401
|
||||
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
here = Path(__file__).resolve()
|
||||
for parent in here.parents:
|
||||
candidate = parent / "McFlashInfer"
|
||||
if (candidate / "flashinfer").is_dir():
|
||||
sys.path.insert(0, str(candidate))
|
||||
return
|
||||
|
||||
def _get_testcase_index() -> int:
|
||||
try:
|
||||
raw = input().strip()
|
||||
except EOFError:
|
||||
return 0
|
||||
if raw == "":
|
||||
return 0
|
||||
try:
|
||||
testcase_id = int(raw.split()[0])
|
||||
except ValueError:
|
||||
return 0
|
||||
if 1 <= testcase_id <= len(TESTCASES):
|
||||
return testcase_id - 1
|
||||
if 0 <= testcase_id < len(TESTCASES):
|
||||
return testcase_id
|
||||
return 0
|
||||
|
||||
def _compute_reps(batch_size: int, seq_len: int, head_dim: int, base_reps: int = 100) -> int:
|
||||
workload = batch_size * seq_len * head_dim
|
||||
if workload < 1e5:
|
||||
return base_reps
|
||||
if workload < 1e6:
|
||||
return base_reps // 2
|
||||
if workload < 1e7:
|
||||
return base_reps // 4
|
||||
if workload < 1e8:
|
||||
return base_reps // 8
|
||||
if workload < 1e9:
|
||||
return base_reps // 16
|
||||
return base_reps // 32
|
||||
|
||||
def getTestCaseSize() -> Tuple[List[Tuple[int, ...]], Tuple[int, int]]:
|
||||
testcase_id = _get_testcase_index()
|
||||
global CURRENT_CASE
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads, head_dim_qk, head_dim_vo, causal = TESTCASES[testcase_id]
|
||||
CURRENT_CASE = (
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
causal,
|
||||
20260610 + testcase_id,
|
||||
)
|
||||
qo_len = batch_size * seq_len
|
||||
kv_len = batch_size * seq_len
|
||||
warmup = 3
|
||||
iters = max(1, _compute_reps(batch_size, seq_len, head_dim_qk + head_dim_vo))
|
||||
return [
|
||||
(qo_len, num_qo_heads, head_dim_qk),
|
||||
(kv_len, num_kv_heads, head_dim_qk),
|
||||
(kv_len, num_kv_heads, head_dim_vo),
|
||||
(qo_len, num_qo_heads, head_dim_vo),
|
||||
(batch_size + 1,),
|
||||
(batch_size + 1,),
|
||||
(), (), (), (), (), (), (),
|
||||
], (warmup, iters)
|
||||
|
||||
def genTestCase(testcase_sizes, device: str = "cuda") -> List[KernelArg]:
|
||||
del testcase_sizes
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads, head_dim_qk, head_dim_vo, causal, seed = CURRENT_CASE
|
||||
gen = torch.Generator(device=device)
|
||||
gen.manual_seed(seed)
|
||||
dtype = torch.bfloat16
|
||||
qo_len = batch_size * seq_len
|
||||
q = torch.rand(
|
||||
qo_len,
|
||||
num_qo_heads,
|
||||
head_dim_qk,
|
||||
dtype=dtype,
|
||||
device=device,
|
||||
generator=gen,
|
||||
).contiguous()
|
||||
kv_len = batch_size * seq_len
|
||||
k = torch.rand(
|
||||
kv_len,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
dtype=dtype,
|
||||
device=device,
|
||||
generator=gen,
|
||||
).contiguous()
|
||||
v = torch.rand(
|
||||
kv_len,
|
||||
num_kv_heads,
|
||||
head_dim_vo,
|
||||
dtype=dtype,
|
||||
device=device,
|
||||
generator=gen,
|
||||
).contiguous()
|
||||
output = torch.empty(
|
||||
qo_len,
|
||||
num_qo_heads,
|
||||
head_dim_vo,
|
||||
dtype=dtype,
|
||||
device=device,
|
||||
)
|
||||
qo_indptr = torch.arange(0, batch_size + 1, dtype=torch.int32, device=device) * seq_len
|
||||
kv_indptr = qo_indptr.clone()
|
||||
return [
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
output,
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
causal,
|
||||
]
|
||||
|
||||
def baseline(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
output,
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
causal,
|
||||
):
|
||||
_ensure_flashinfer_importable()
|
||||
import flashinfer
|
||||
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q.device)
|
||||
wrapper = flashinfer.BatchPrefillWithRaggedKVCacheWrapper(
|
||||
workspace_buffer,
|
||||
kv_layout="NHD",
|
||||
backend="auto",
|
||||
)
|
||||
wrapper.plan(
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
int(num_qo_heads),
|
||||
int(num_kv_heads),
|
||||
int(head_dim_qk),
|
||||
int(head_dim_vo),
|
||||
causal=bool(causal),
|
||||
q_data_type=torch.bfloat16,
|
||||
kv_data_type=torch.bfloat16,
|
||||
)
|
||||
wrapper.run(q, k, v, out=output)
|
||||
return [
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
output,
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
causal,
|
||||
]
|
||||
|
||||
def check(
|
||||
testcase_sizes,
|
||||
original_input_tensors,
|
||||
target_kernel_input_tensors,
|
||||
baseline_input_tensors,
|
||||
rtol=1e-2,
|
||||
atol=1e-2,
|
||||
) -> bool:
|
||||
del testcase_sizes, original_input_tensors
|
||||
output_t = target_kernel_input_tensors[3]
|
||||
output_ref = baseline_input_tensors[3]
|
||||
if output_t.shape != output_ref.shape:
|
||||
print(f"[FAIL] shape mismatch: target {output_t.shape}, ref {output_ref.shape}", file=sys.stderr)
|
||||
return False
|
||||
if output_t.dtype != output_ref.dtype:
|
||||
print(f"[FAIL] dtype mismatch: target {output_t.dtype}, ref {output_ref.dtype}", file=sys.stderr)
|
||||
return False
|
||||
if not torch.allclose(output_t.float(), output_ref.float(), rtol=rtol, atol=atol):
|
||||
diff = (output_t.float() - output_ref.float()).abs()
|
||||
print(
|
||||
f"[FAIL] allclose failed: max_abs_diff={float(diff.max().item()):.6f}, "
|
||||
f"mean_abs_diff={float(diff.mean().item()):.6f} (rtol={rtol}, atol={atol})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
INPUT_CLASS = [
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"OUTPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
"INPUT",
|
||||
]
|
||||
|
||||
|
||||
def getWorkload(testcase_sizes) -> dict:
|
||||
raw_sizes = testcase_sizes[0] if isinstance(testcase_sizes, tuple) and len(testcase_sizes) == 2 else testcase_sizes
|
||||
q_shape, k_shape, v_shape, output_shape, qo_indptr_shape, kv_indptr_shape = raw_sizes[:6]
|
||||
qo_len, num_qo_heads, head_dim_qk = q_shape
|
||||
kv_len, num_kv_heads, k_dim = k_shape
|
||||
v_len, v_heads, head_dim_vo = v_shape
|
||||
assert k_dim == head_dim_qk
|
||||
assert v_len == kv_len
|
||||
assert v_heads == num_kv_heads
|
||||
assert qo_len == kv_len
|
||||
assert output_shape == (qo_len, num_qo_heads, head_dim_vo)
|
||||
assert qo_indptr_shape == kv_indptr_shape
|
||||
batch_size = qo_indptr_shape[0] - 1
|
||||
seq_len = kv_len // batch_size
|
||||
flops = batch_size * seq_len * seq_len * num_qo_heads * (head_dim_qk + head_dim_vo)
|
||||
memory_bytes = (
|
||||
qo_len * num_qo_heads * head_dim_qk * 2
|
||||
+ kv_len * num_kv_heads * head_dim_qk * 2
|
||||
+ kv_len * num_kv_heads * head_dim_vo * 2
|
||||
+ qo_len * num_qo_heads * head_dim_vo * 2
|
||||
+ (batch_size + 1) * 4 * 2
|
||||
)
|
||||
return {
|
||||
"flops": flops,
|
||||
"memory_bytes": memory_bytes,
|
||||
"dtype": "bf16",
|
||||
}
|
||||
|
||||
|
||||
DESIGNED_VRAM_SIZE = 48
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
sectionTitle: "题目描述"
|
||||
type: "Text"
|
||||
---
|
||||
你需要实现 FlashInfer ragged KV cache prefill 的CUDA C++前向算子。
|
||||
|
||||
本题输入采用 FlashInfer `BatchPrefillWithRaggedKVCacheWrapper` 的 ragged `NHD` 布局。每个 batch 中有 `seq_len` 个 query token,KV cache 中也有 `seq_len` 个 token:
|
||||
|
||||
其中 query heads 采用 GQA 布局:`num_qo_heads` 个 query/output heads 共享 `num_kv_heads` 个 KV heads,`G = num_qo_heads / num_kv_heads`。
|
||||
|
||||
评测程序会调用你提交代码中的 `run_kernel` 函数。你需要根据 `qo_indptr` 和 `kv_indptr` 读取 ragged Q/K/V,并将结果写入 `output`。
|
||||
|
||||
baseline 使用 FlashInfer ragged prefill 的 Python API:
|
||||
|
||||
```python
|
||||
wrapper = flashinfer.BatchPrefillWithRaggedKVCacheWrapper(workspace, kv_layout="NHD", backend="auto")
|
||||
wrapper.plan(qo_indptr, kv_indptr, num_qo_heads, num_kv_heads,
|
||||
head_dim_qk, head_dim_vo, causal=True,
|
||||
q_data_type=torch.bfloat16, kv_data_type=torch.bfloat16)
|
||||
wrapper.run(q, k, v, out=output)
|
||||
```
|
||||
|
||||
如何提交代码详见[评测指南](/d/2)。
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "cuda"
|
||||
---
|
||||
你必须在提交的 CUDA 源码中提供如下 **C 符号**,函数名、参数类型、顺序必须完全一致,并使用 `extern "C"` 防止 name mangling:
|
||||
|
||||
```cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* k,
|
||||
const __nv_bfloat16* v,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* qo_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim_qk,
|
||||
int64_t head_dim_vo,
|
||||
int64_t causal
|
||||
);
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q`:query tensor,shape `(batch_size * seq_len, num_qo_heads, head_dim_qk)`,连续 `bf16`
|
||||
* `k`:key tensor,shape `(batch_size * seq_len, num_kv_heads, head_dim_qk)`,连续 `bf16`
|
||||
* `v`:value tensor,shape `(batch_size * seq_len, num_kv_heads, head_dim_vo)`,连续 `bf16`
|
||||
* `output`:输出缓冲区,shape `(batch_size * seq_len, num_qo_heads, head_dim_vo)`,连续 `bf16`
|
||||
* `qo_indptr`:query/output ragged indptr,shape `(batch_size + 1)`,连续 `int32`
|
||||
* `kv_indptr`:KV ragged indptr,shape `(batch_size + 1)`,连续 `int32`
|
||||
* `causal`:是否启用 causal mask,评测中固定为 `1`
|
||||
|
||||
本题测试中 `qo_indptr[b + 1] - qo_indptr[b] == seq_len`,`kv_indptr[b + 1] - kv_indptr[b] == seq_len`。
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 `run_kernel` 内部做 `cudaDeviceSynchronize()` 或显式同步。
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "tilelang"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim_qk)
|
||||
k, # Tensor[bf16], shape (batch_size * seq_len, num_kv_heads, head_dim_qk)
|
||||
v, # Tensor[bf16], shape (batch_size * seq_len, num_kv_heads, head_dim_vo)
|
||||
output, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim_vo)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim_qk, # int64
|
||||
head_dim_vo, # int64
|
||||
causal, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q, k, v, output, qo_indptr, kv_indptr,
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads,
|
||||
head_dim_qk, head_dim_vo, causal)
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q/k/v`:FlashInfer ragged prefill 输入 tensor,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `qo_indptr/kv_indptr`:ragged indptr,连续 `int32`
|
||||
* `causal`:是否启用 causal mask,评测中固定为 `1`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "triton"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim_qk)
|
||||
k, # Tensor[bf16], shape (batch_size * seq_len, num_kv_heads, head_dim_qk)
|
||||
v, # Tensor[bf16], shape (batch_size * seq_len, num_kv_heads, head_dim_vo)
|
||||
output, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim_vo)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim_qk, # int64
|
||||
head_dim_vo, # int64
|
||||
causal, # int64
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q/k/v`:FlashInfer ragged prefill 输入 tensor,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `qo_indptr/kv_indptr`:ragged indptr,连续 `int32`
|
||||
* `causal`:是否启用 causal mask,评测中固定为 `1`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
sectionTitle: "输入格式"
|
||||
type: "Text"
|
||||
---
|
||||
## 数据范围
|
||||
|
||||
- 数据类型:`q/kv_data/output` 均为 `bfloat16`
|
||||
- KV layout:`NHD`
|
||||
- `num_qo_heads = 32`
|
||||
- `num_kv_heads = 4`
|
||||
- `page_block_size = 16`
|
||||
- `causal = 0`
|
||||
- `head_dim` 固定为 `128`
|
||||
- `batch_size` 取值为 `1, 4, 16`
|
||||
- `seq_len` 取值为 `1024, 4096, 8192, 16384`
|
||||
|
||||
测试点顺序与 `McFlashInfer/benchmarks/bench_batch_prefill_paged.py` 中的 cases 一致,即:
|
||||
|
||||
```python
|
||||
for head_dim in [128]:
|
||||
for batch_size in [1, 4, 16]:
|
||||
for seq_len in [1024, 4096, 8192, 16384]:
|
||||
...
|
||||
```
|
||||
|
||||
输出参与 `torch.allclose` 校验,容差为 `rtol=1e-2, atol=1e-2`。
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
pytorch参考实现:
|
||||
|
||||
```python
|
||||
def baseline(q, kv_data, output, qo_indptr, kv_indptr, kv_indices, last_page_len,
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads,
|
||||
head_dim, page_block_size, causal):
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q.device)
|
||||
wrapper = flashinfer.BatchPrefillWithPagedKVCacheWrapper(
|
||||
workspace_buffer,
|
||||
kv_layout="NHD",
|
||||
backend="auto",
|
||||
)
|
||||
wrapper.plan(
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
causal=bool(causal),
|
||||
q_data_type=torch.bfloat16,
|
||||
kv_data_type=torch.bfloat16,
|
||||
)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
FlashInfer Ragged Prefill
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": 194,
|
||||
"displayId": 20002,
|
||||
"type": "Traditional",
|
||||
"isPublic": false,
|
||||
"locales": [
|
||||
"zh_CN"
|
||||
],
|
||||
"samples": [
|
||||
{
|
||||
"inputData": "1\n",
|
||||
"outputData": ""
|
||||
}
|
||||
],
|
||||
"problemTagIds": []
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
HEAD_DIMS = [128]
|
||||
BATCH_SIZES = [1, 4, 16]
|
||||
SEQ_LENS = [1024, 4096, 8192, 16384]
|
||||
NUM_QO_HEADS = 32
|
||||
PAGE_BLOCK_SIZE = 16
|
||||
CAUSAL = 0
|
||||
|
||||
|
||||
def _build_cases():
|
||||
cases = []
|
||||
for head_dim in HEAD_DIMS:
|
||||
for batch_size in BATCH_SIZES:
|
||||
for seq_len in SEQ_LENS:
|
||||
num_kv_heads = 8 if head_dim == 64 else 4
|
||||
cases.append(
|
||||
(
|
||||
batch_size,
|
||||
seq_len,
|
||||
NUM_QO_HEADS,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
PAGE_BLOCK_SIZE,
|
||||
CAUSAL,
|
||||
)
|
||||
)
|
||||
return cases
|
||||
|
||||
|
||||
TESTCASES = _build_cases()
|
||||
|
||||
|
||||
def getNumOfTestcases() -> int:
|
||||
return len(TESTCASES)
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Union
|
||||
import sys
|
||||
|
||||
import torch
|
||||
|
||||
KernelArg = Union[torch.Tensor, int, float]
|
||||
CURRENT_CASE = None
|
||||
|
||||
def _ensure_flashinfer_importable():
|
||||
try:
|
||||
import flashinfer # noqa: F401
|
||||
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
here = Path(__file__).resolve()
|
||||
for parent in here.parents:
|
||||
candidate = parent / "McFlashInfer"
|
||||
if (candidate / "flashinfer").is_dir():
|
||||
sys.path.insert(0, str(candidate))
|
||||
return
|
||||
|
||||
def _get_testcase_index() -> int:
|
||||
try:
|
||||
raw = input().strip()
|
||||
except EOFError:
|
||||
return 0
|
||||
if raw == "":
|
||||
return 0
|
||||
try:
|
||||
testcase_id = int(raw.split()[0])
|
||||
except ValueError:
|
||||
return 0
|
||||
if 1 <= testcase_id <= len(TESTCASES):
|
||||
return testcase_id - 1
|
||||
if 0 <= testcase_id < len(TESTCASES):
|
||||
return testcase_id
|
||||
return 0
|
||||
|
||||
def _compute_reps(batch_size: int, seq_len: int, head_dim: int, base_reps: int = 100) -> int:
|
||||
workload = batch_size * seq_len * head_dim
|
||||
if workload < 1e5:
|
||||
return base_reps
|
||||
if workload < 1e6:
|
||||
return base_reps // 2
|
||||
if workload < 1e7:
|
||||
return base_reps // 4
|
||||
if workload < 1e8:
|
||||
return base_reps // 8
|
||||
if workload < 1e9:
|
||||
return base_reps // 16
|
||||
return base_reps // 32
|
||||
|
||||
def _setup_paged_kv_indptr(batch_size: int, seq_len: int, page_block_size: int, device: str):
|
||||
seq_lens = torch.full((batch_size,), seq_len, dtype=torch.int32, device=device)
|
||||
seq_lens_blocks = torch.div(seq_lens + page_block_size - 1, page_block_size, rounding_mode="floor")
|
||||
kv_indptr = torch.empty((batch_size + 1,), dtype=torch.int32, device=device)
|
||||
kv_indptr[0] = 0
|
||||
kv_indptr[1:] = torch.cumsum(seq_lens_blocks, dim=0)
|
||||
num_blocks = int(kv_indptr[-1].item())
|
||||
last_page_len = (seq_lens - 1) % page_block_size + 1
|
||||
return kv_indptr, last_page_len, num_blocks
|
||||
|
||||
def getTestCaseSize() -> Tuple[List[Tuple[int, ...]], Tuple[int, int]]:
|
||||
testcase_id = _get_testcase_index()
|
||||
global CURRENT_CASE
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads, head_dim, page_block_size, causal = TESTCASES[testcase_id]
|
||||
num_blocks = batch_size * ((seq_len + page_block_size - 1) // page_block_size)
|
||||
qo_len = batch_size * seq_len
|
||||
CURRENT_CASE = (
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
causal,
|
||||
num_blocks,
|
||||
20260620 + testcase_id,
|
||||
)
|
||||
warmup = 3
|
||||
iters = max(1, _compute_reps(batch_size, seq_len, head_dim))
|
||||
return [
|
||||
(qo_len, num_qo_heads, head_dim),
|
||||
(num_blocks, 2, page_block_size, num_kv_heads, head_dim),
|
||||
(qo_len, num_qo_heads, head_dim),
|
||||
(batch_size + 1,),
|
||||
(batch_size + 1,),
|
||||
(num_blocks,),
|
||||
(batch_size,),
|
||||
(), (), (), (), (), (), (),
|
||||
], (warmup, iters)
|
||||
|
||||
def genTestCase(testcase_sizes, device: str = "cuda") -> List[KernelArg]:
|
||||
del testcase_sizes
|
||||
(
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
causal,
|
||||
num_blocks,
|
||||
seed,
|
||||
) = CURRENT_CASE
|
||||
gen = torch.Generator(device=device)
|
||||
gen.manual_seed(seed)
|
||||
dtype = torch.bfloat16
|
||||
qo_len = batch_size * seq_len
|
||||
q = torch.rand(qo_len, num_qo_heads, head_dim, dtype=dtype, device=device, generator=gen).contiguous()
|
||||
kv_data = torch.randn(
|
||||
num_blocks,
|
||||
2,
|
||||
page_block_size,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
dtype=dtype,
|
||||
device=device,
|
||||
generator=gen,
|
||||
).contiguous()
|
||||
output = torch.empty(qo_len, num_qo_heads, head_dim, dtype=dtype, device=device)
|
||||
qo_indptr = torch.arange(0, batch_size + 1, dtype=torch.int32, device=device) * seq_len
|
||||
kv_indptr, last_page_len, num_blocks_check = _setup_paged_kv_indptr(
|
||||
batch_size,
|
||||
seq_len,
|
||||
page_block_size,
|
||||
device,
|
||||
)
|
||||
assert num_blocks_check == num_blocks
|
||||
kv_indices = torch.arange(num_blocks, dtype=torch.int32, device=device)
|
||||
return [
|
||||
q,
|
||||
kv_data,
|
||||
output,
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
causal,
|
||||
]
|
||||
|
||||
def baseline(
|
||||
q,
|
||||
kv_data,
|
||||
output,
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
causal,
|
||||
):
|
||||
_ensure_flashinfer_importable()
|
||||
import flashinfer
|
||||
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q.device)
|
||||
wrapper = flashinfer.BatchPrefillWithPagedKVCacheWrapper(
|
||||
workspace_buffer,
|
||||
kv_layout="NHD",
|
||||
backend="auto",
|
||||
)
|
||||
wrapper.plan(
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
int(num_qo_heads),
|
||||
int(num_kv_heads),
|
||||
int(head_dim),
|
||||
int(page_block_size),
|
||||
causal=bool(causal),
|
||||
q_data_type=torch.bfloat16,
|
||||
kv_data_type=torch.bfloat16,
|
||||
)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
return [
|
||||
q,
|
||||
kv_data,
|
||||
output,
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
causal,
|
||||
]
|
||||
|
||||
def check(
|
||||
testcase_sizes,
|
||||
original_input_tensors,
|
||||
target_kernel_input_tensors,
|
||||
baseline_input_tensors,
|
||||
rtol=1e-2,
|
||||
atol=1e-2,
|
||||
) -> bool:
|
||||
del testcase_sizes, original_input_tensors
|
||||
output_t = target_kernel_input_tensors[2]
|
||||
output_ref = baseline_input_tensors[2]
|
||||
if output_t.shape != output_ref.shape:
|
||||
print(f"[FAIL] shape mismatch: target {output_t.shape}, ref {output_ref.shape}", file=sys.stderr)
|
||||
return False
|
||||
if output_t.dtype != output_ref.dtype:
|
||||
print(f"[FAIL] dtype mismatch: target {output_t.dtype}, ref {output_ref.dtype}", file=sys.stderr)
|
||||
return False
|
||||
if not torch.allclose(output_t.float(), output_ref.float(), rtol=rtol, atol=atol):
|
||||
diff = (output_t.float() - output_ref.float()).abs()
|
||||
print(
|
||||
f"[FAIL] allclose failed: max_abs_diff={float(diff.max().item()):.6f}, "
|
||||
f"mean_abs_diff={float(diff.mean().item()):.6f} (rtol={rtol}, atol={atol})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
sectionTitle: "题目描述"
|
||||
type: "Text"
|
||||
---
|
||||
你需要实现 FlashInfer paged KV cache prefill 的 CUDA C++ 前向算子。
|
||||
|
||||
本题输入采用 FlashInfer `BatchPrefillWithPagedKVCacheWrapper` 的 paged `NHD` 布局。每个 batch 中有 `seq_len` 个 query token,KV cache 也有 `seq_len` 个 token,并按 page 存储。
|
||||
|
||||
评测程序会调用你提交代码中的 `run_kernel` 函数。你需要根据 `qo_indptr`、`kv_indptr`、`kv_indices` 和 `last_page_len` 读取 paged KV cache,并将结果写入 `output`。
|
||||
|
||||
baseline 使用 FlashInfer paged prefill 的 Python API:
|
||||
|
||||
```python
|
||||
wrapper = flashinfer.BatchPrefillWithPagedKVCacheWrapper(workspace, kv_layout="NHD", backend="auto")
|
||||
wrapper.plan(qo_indptr, kv_indptr, kv_indices, last_page_len,
|
||||
num_qo_heads, num_kv_heads, head_dim, page_block_size,
|
||||
causal=bool(causal),
|
||||
q_data_type=torch.bfloat16, kv_data_type=torch.bfloat16)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
```
|
||||
|
||||
如何提交代码详见[评测指南](/d/2)。
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "cuda"
|
||||
---
|
||||
你必须在提交的 CUDA 源码中提供如下 **C 符号**,函数名、参数类型、顺序必须完全一致,并使用 `extern "C"` 防止 name mangling:
|
||||
|
||||
```cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* kv_data,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* qo_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
const int32_t* kv_indices,
|
||||
const int32_t* last_page_len,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim,
|
||||
int64_t page_block_size,
|
||||
int64_t causal
|
||||
);
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q`:query tensor,shape `(batch_size * seq_len, num_qo_heads, head_dim)`,连续 `bf16`
|
||||
* `kv_data`:paged KV cache,shape `(num_blocks, 2, page_block_size, num_kv_heads, head_dim)`,连续 `bf16`,其中 `kv_data[:, 0]` 为 key,`kv_data[:, 1]` 为 value
|
||||
* `output`:输出缓冲区,shape `(batch_size * seq_len, num_qo_heads, head_dim)`,连续 `bf16`
|
||||
* `qo_indptr`:query/output indptr,shape `(batch_size + 1)`,连续 `int32`
|
||||
* `kv_indptr`:paged KV indptr,shape `(batch_size + 1)`,连续 `int32`
|
||||
* `kv_indices`:page index,shape `(num_blocks)`,连续 `int32`
|
||||
* `last_page_len`:每个 batch 最后一个 page 的有效 token 数,shape `(batch_size)`,连续 `int32`
|
||||
* `page_block_size`:page size,评测中固定为 `16`
|
||||
* `causal`:是否启用 causal mask,本题按 benchmark case 固定为 `0`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 `run_kernel` 内部做 `cudaDeviceSynchronize()` 或显式同步。
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "tilelang"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q, kv_data, output, qo_indptr, kv_indptr, kv_indices, last_page_len,
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads,
|
||||
head_dim, page_block_size, causal)
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q`:query tensor,连续 `bfloat16`
|
||||
* `kv_data`:paged KV cache,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `qo_indptr/kv_indptr/kv_indices/last_page_len`:paged KV metadata,连续 `int32`
|
||||
* `page_block_size`:评测中固定为 `16`
|
||||
* `causal`:评测中固定为 `0`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "triton"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q`:query tensor,连续 `bfloat16`
|
||||
* `kv_data`:paged KV cache,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `qo_indptr/kv_indptr/kv_indices/last_page_len`:paged KV metadata,连续 `int32`
|
||||
* `page_block_size`:评测中固定为 `16`
|
||||
* `causal`:评测中固定为 `0`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
sectionTitle: "输入格式"
|
||||
type: "Text"
|
||||
---
|
||||
## 数据范围
|
||||
|
||||
- 数据类型:`q/kv_data/output` 均为 `bfloat16`
|
||||
- KV layout:`NHD`
|
||||
- `num_qo_heads = 32`
|
||||
- `num_kv_heads = 4`
|
||||
- `page_block_size = 16`
|
||||
- `causal = 0`
|
||||
- `head_dim` 固定为 `128`
|
||||
- `batch_size` 取值为 `1, 4, 16`
|
||||
- `seq_len` 取值为 `1024, 4096, 8192, 16384`
|
||||
|
||||
测试点顺序与 `McFlashInfer/benchmarks/bench_batch_prefill_paged.py` 中的 cases 一致,即:
|
||||
|
||||
```python
|
||||
for head_dim in [128]:
|
||||
for batch_size in [1, 4, 16]:
|
||||
for seq_len in [1024, 4096, 8192, 16384]:
|
||||
...
|
||||
```
|
||||
|
||||
输出参与 `torch.allclose` 校验,容差为 `rtol=1e-2, atol=1e-2`。
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
sectionTitle: "输出格式"
|
||||
type: "Text"
|
||||
---
|
||||
PyTorch 参考实现
|
||||
|
||||
```python
|
||||
def baseline(q, kv_data, output, qo_indptr, kv_indptr, kv_indices, last_page_len,
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads,
|
||||
head_dim, page_block_size, causal):
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q.device)
|
||||
wrapper = flashinfer.BatchPrefillWithPagedKVCacheWrapper(
|
||||
workspace_buffer,
|
||||
kv_layout="NHD",
|
||||
backend="auto",
|
||||
)
|
||||
wrapper.plan(
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
causal=bool(causal),
|
||||
q_data_type=torch.bfloat16,
|
||||
kv_data_type=torch.bfloat16,
|
||||
)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
```
|
||||
|
|
@ -0,0 +1 @@
|
|||
FlashInfer Paged Prefill
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": 195,
|
||||
"displayId": 20003,
|
||||
"type": "Traditional",
|
||||
"isPublic": false,
|
||||
"locales": [
|
||||
"zh_CN"
|
||||
],
|
||||
"samples": [
|
||||
{
|
||||
"inputData": "1\n",
|
||||
"outputData": ""
|
||||
}
|
||||
],
|
||||
"problemTagIds": []
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
HEAD_DIM_CKV = 512
|
||||
HEAD_DIM_KPE = 64
|
||||
NUM_HEADS_LIST = [64, 128]
|
||||
BATCH_SIZES = [1, 4, 16]
|
||||
SEQ_LENS = [1024, 4096, 8192, 16384]
|
||||
PAGE_SIZE = 1
|
||||
CAUSAL = 0
|
||||
|
||||
|
||||
def _build_cases():
|
||||
cases = []
|
||||
for num_heads in NUM_HEADS_LIST:
|
||||
for batch_size in BATCH_SIZES:
|
||||
for seq_len in SEQ_LENS:
|
||||
cases.append(
|
||||
(
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_heads,
|
||||
HEAD_DIM_CKV,
|
||||
HEAD_DIM_KPE,
|
||||
PAGE_SIZE,
|
||||
CAUSAL,
|
||||
)
|
||||
)
|
||||
return cases
|
||||
|
||||
|
||||
TESTCASES = _build_cases()
|
||||
|
||||
|
||||
def getNumOfTestcases() -> int:
|
||||
return len(TESTCASES)
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Union
|
||||
import sys
|
||||
|
||||
import torch
|
||||
|
||||
KernelArg = Union[torch.Tensor, int, float]
|
||||
CURRENT_CASE = None
|
||||
|
||||
def _ensure_flashinfer_importable():
|
||||
try:
|
||||
import flashinfer # noqa: F401
|
||||
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
here = Path(__file__).resolve()
|
||||
for parent in here.parents:
|
||||
candidate = parent / "McFlashInfer"
|
||||
if (candidate / "flashinfer").is_dir():
|
||||
sys.path.insert(0, str(candidate))
|
||||
return
|
||||
|
||||
def _get_testcase_index() -> int:
|
||||
try:
|
||||
raw = input().strip()
|
||||
except EOFError:
|
||||
return 0
|
||||
if raw == "":
|
||||
return 0
|
||||
try:
|
||||
testcase_id = int(raw.split()[0])
|
||||
except ValueError:
|
||||
return 0
|
||||
if 1 <= testcase_id <= len(TESTCASES):
|
||||
return testcase_id - 1
|
||||
if 0 <= testcase_id < len(TESTCASES):
|
||||
return testcase_id
|
||||
return 0
|
||||
|
||||
def _compute_reps(batch_size: int, seq_len: int, head_dim: int, base_reps: int = 100) -> int:
|
||||
workload = batch_size * seq_len * head_dim
|
||||
if workload < 1e5:
|
||||
return base_reps
|
||||
if workload < 1e6:
|
||||
return base_reps // 2
|
||||
if workload < 1e7:
|
||||
return base_reps // 4
|
||||
if workload < 1e8:
|
||||
return base_reps // 8
|
||||
if workload < 1e9:
|
||||
return base_reps // 16
|
||||
return base_reps // 32
|
||||
|
||||
def getTestCaseSize() -> Tuple[List[Tuple[int, ...]], Tuple[int, int]]:
|
||||
testcase_id = _get_testcase_index()
|
||||
global CURRENT_CASE
|
||||
batch_size, seq_len, num_heads, head_dim_ckv, head_dim_kpe, page_size, causal = TESTCASES[testcase_id]
|
||||
CURRENT_CASE = (
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_heads,
|
||||
head_dim_ckv,
|
||||
head_dim_kpe,
|
||||
page_size,
|
||||
causal,
|
||||
20260630 + testcase_id,
|
||||
)
|
||||
warmup = 3
|
||||
iters = max(1, _compute_reps(batch_size, seq_len, head_dim_ckv + head_dim_kpe))
|
||||
kv_len = batch_size * seq_len
|
||||
return [
|
||||
(batch_size, num_heads, head_dim_ckv),
|
||||
(batch_size, num_heads, head_dim_kpe),
|
||||
(kv_len, 1, head_dim_ckv),
|
||||
(kv_len, 1, head_dim_kpe),
|
||||
(batch_size, num_heads, head_dim_ckv),
|
||||
(batch_size + 1,),
|
||||
(batch_size + 1,),
|
||||
(kv_len,),
|
||||
(batch_size,),
|
||||
(), (), (), (), (), (), (),
|
||||
], (warmup, iters)
|
||||
|
||||
def genTestCase(testcase_sizes, device: str = "cuda") -> List[KernelArg]:
|
||||
del testcase_sizes
|
||||
batch_size, seq_len, num_heads, head_dim_ckv, head_dim_kpe, page_size, causal, seed = CURRENT_CASE
|
||||
gen = torch.Generator(device=device)
|
||||
gen.manual_seed(seed)
|
||||
dtype = torch.bfloat16
|
||||
kv_len = batch_size * seq_len
|
||||
q_nope = torch.randn(batch_size, num_heads, head_dim_ckv, dtype=dtype, device=device, generator=gen).contiguous()
|
||||
q_pe = torch.zeros(batch_size, num_heads, head_dim_kpe, dtype=dtype, device=device)
|
||||
ckv = torch.randn(kv_len, 1, head_dim_ckv, dtype=dtype, device=device, generator=gen).contiguous()
|
||||
kpe = torch.zeros(kv_len, 1, head_dim_kpe, dtype=dtype, device=device)
|
||||
output = torch.empty(batch_size, num_heads, head_dim_ckv, dtype=dtype, device=device)
|
||||
q_indptr = torch.arange(0, batch_size + 1, dtype=torch.int32, device=device)
|
||||
kv_indptr = q_indptr * seq_len
|
||||
kv_indices = torch.arange(0, kv_len, dtype=torch.int32, device=device)
|
||||
kv_lens = torch.full((batch_size,), seq_len, dtype=torch.int32, device=device)
|
||||
return [
|
||||
q_nope,
|
||||
q_pe,
|
||||
ckv,
|
||||
kpe,
|
||||
output,
|
||||
q_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
kv_lens,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_heads,
|
||||
head_dim_ckv,
|
||||
head_dim_kpe,
|
||||
page_size,
|
||||
causal,
|
||||
]
|
||||
|
||||
def baseline(
|
||||
q_nope,
|
||||
q_pe,
|
||||
ckv,
|
||||
kpe,
|
||||
output,
|
||||
q_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
kv_lens,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_heads,
|
||||
head_dim_ckv,
|
||||
head_dim_kpe,
|
||||
page_size,
|
||||
causal,
|
||||
):
|
||||
_ensure_flashinfer_importable()
|
||||
import flashinfer
|
||||
|
||||
sm_scale = 1.0 / ((int(head_dim_ckv) + int(head_dim_kpe)) ** 0.5)
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q_nope.device)
|
||||
wrapper = flashinfer.mla.BatchMLAPagedAttentionWrapper(workspace_buffer, backend="auto")
|
||||
wrapper.plan(
|
||||
q_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
kv_lens,
|
||||
int(num_heads),
|
||||
int(head_dim_ckv),
|
||||
int(head_dim_kpe),
|
||||
int(page_size),
|
||||
bool(causal),
|
||||
sm_scale,
|
||||
q_nope.dtype,
|
||||
ckv.dtype,
|
||||
)
|
||||
wrapper.run(q_nope, q_pe, ckv, kpe, out=output, return_lse=False)
|
||||
return [
|
||||
q_nope,
|
||||
q_pe,
|
||||
ckv,
|
||||
kpe,
|
||||
output,
|
||||
q_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
kv_lens,
|
||||
batch_size,
|
||||
seq_len,
|
||||
num_heads,
|
||||
head_dim_ckv,
|
||||
head_dim_kpe,
|
||||
page_size,
|
||||
causal,
|
||||
]
|
||||
|
||||
def check(
|
||||
testcase_sizes,
|
||||
original_input_tensors,
|
||||
target_kernel_input_tensors,
|
||||
baseline_input_tensors,
|
||||
rtol=1e-2,
|
||||
atol=1e-2,
|
||||
) -> bool:
|
||||
del testcase_sizes, original_input_tensors
|
||||
output_t = target_kernel_input_tensors[4]
|
||||
output_ref = baseline_input_tensors[4]
|
||||
if output_t.shape != output_ref.shape:
|
||||
print(f"[FAIL] shape mismatch: target {output_t.shape}, ref {output_ref.shape}", file=sys.stderr)
|
||||
return False
|
||||
if output_t.dtype != output_ref.dtype:
|
||||
print(f"[FAIL] dtype mismatch: target {output_t.dtype}, ref {output_ref.dtype}", file=sys.stderr)
|
||||
return False
|
||||
if not torch.allclose(output_t.float(), output_ref.float(), rtol=rtol, atol=atol):
|
||||
diff = (output_t.float() - output_ref.float()).abs()
|
||||
print(
|
||||
f"[FAIL] allclose failed: max_abs_diff={float(diff.max().item()):.6f}, "
|
||||
f"mean_abs_diff={float(diff.mean().item()):.6f} (rtol={rtol}, atol={atol})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
sectionTitle: "题目描述"
|
||||
type: "Text"
|
||||
---
|
||||
你需要实现 FlashInfer MLA paged attention 的 CUDA C++ 前向算子。
|
||||
|
||||
本题使用 `BatchMLAPagedAttentionWrapper` 的 DeepSeek MLA decode 配置:`q_nope` 表示不含 RoPE 的 query,`q_pe` 表示 RoPE 部分的 query,`ckv` 与 `kpe` 分别表示 compressed KV cache 与 RoPE KV cache。MLA page size 固定为 `1`。
|
||||
|
||||
评测程序会调用你提交代码中的 `run_kernel` 函数。你需要根据 `q_indptr`、`kv_indptr`、`kv_indices` 和 `kv_lens` 读取 cache,并将结果写入 `output`。
|
||||
|
||||
baseline 使用 FlashInfer MLA 的 Python API:
|
||||
|
||||
```python
|
||||
wrapper = flashinfer.mla.BatchMLAPagedAttentionWrapper(workspace, backend="auto")
|
||||
wrapper.plan(q_indptr, kv_indptr, kv_indices, kv_lens,
|
||||
num_heads, head_dim_ckv, head_dim_kpe,
|
||||
page_size, False, sm_scale,
|
||||
q_nope.dtype, ckv.dtype)
|
||||
wrapper.run(q_nope, q_pe, ckv, kpe, out=output, return_lse=False)
|
||||
```
|
||||
|
||||
如何提交代码详见[评测指南](/d/2)。
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "cuda"
|
||||
---
|
||||
你必须在提交的 CUDA 源码中提供如下 **C 符号**,函数名、参数类型、顺序必须完全一致,并使用 `extern "C"` 防止 name mangling:
|
||||
|
||||
```cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q_nope,
|
||||
const __nv_bfloat16* q_pe,
|
||||
const __nv_bfloat16* ckv,
|
||||
const __nv_bfloat16* kpe,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* q_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
const int32_t* kv_indices,
|
||||
const int32_t* kv_lens,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_heads,
|
||||
int64_t head_dim_ckv,
|
||||
int64_t head_dim_kpe,
|
||||
int64_t page_size,
|
||||
int64_t causal
|
||||
);
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q_nope`:query 的 compressed/nope 部分,shape `(batch_size, num_heads, head_dim_ckv)`,连续 `bf16`
|
||||
* `q_pe`:query 的 RoPE 部分,shape `(batch_size, num_heads, head_dim_kpe)`,连续 `bf16`
|
||||
* `ckv`:compressed KV cache,shape `(batch_size * seq_len, 1, head_dim_ckv)`,连续 `bf16`
|
||||
* `kpe`:RoPE KV cache,shape `(batch_size * seq_len, 1, head_dim_kpe)`,连续 `bf16`
|
||||
* `output`:输出缓冲区,shape `(batch_size, num_heads, head_dim_ckv)`,连续 `bf16`
|
||||
* `q_indptr`:decode query indptr,shape `(batch_size + 1)`,内容为 `[0, 1, ..., batch_size]`
|
||||
* `kv_indptr`:KV indptr,shape `(batch_size + 1)`,每段长度为 `seq_len`
|
||||
* `kv_indices`:page index,shape `(batch_size * seq_len)`,连续 `int32`
|
||||
* `kv_lens`:每个 batch 的 KV 长度,shape `(batch_size)`,连续 `int32`
|
||||
* `page_size`:评测中固定为 `1`
|
||||
* `causal`:评测中固定为 `0`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 `run_kernel` 内部做 `cudaDeviceSynchronize()` 或显式同步。
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "tilelang"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q_nope, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_pe, # Tensor[bf16], shape (batch_size, num_heads, head_dim_kpe)
|
||||
ckv, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_ckv)
|
||||
kpe, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_kpe)
|
||||
output, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (batch_size * seq_len)
|
||||
kv_lens, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_heads, # int64
|
||||
head_dim_ckv, # int64
|
||||
head_dim_kpe, # int64
|
||||
page_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q_nope, q_pe, ckv, kpe, output,
|
||||
q_indptr, kv_indptr, kv_indices, kv_lens,
|
||||
batch_size, seq_len, num_heads,
|
||||
head_dim_ckv, head_dim_kpe, page_size, causal)
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q_nope/q_pe/ckv/kpe`:MLA attention 输入 tensor,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `q_indptr/kv_indptr/kv_indices/kv_lens`:paged attention metadata,连续 `int32`
|
||||
* `page_size`:评测中固定为 `1`
|
||||
* `causal`:评测中固定为 `0`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "triton"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q_nope, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_pe, # Tensor[bf16], shape (batch_size, num_heads, head_dim_kpe)
|
||||
ckv, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_ckv)
|
||||
kpe, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_kpe)
|
||||
output, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (batch_size * seq_len)
|
||||
kv_lens, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_heads, # int64
|
||||
head_dim_ckv, # int64
|
||||
head_dim_kpe, # int64
|
||||
page_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q_nope/q_pe/ckv/kpe`:MLA attention 输入 tensor,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `q_indptr/kv_indptr/kv_indices/kv_lens`:paged attention metadata,连续 `int32`
|
||||
* `page_size`:评测中固定为 `1`
|
||||
* `causal`:评测中固定为 `0`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
sectionTitle: "输入格式"
|
||||
type: "Text"
|
||||
---
|
||||
## 数据范围
|
||||
|
||||
- 数据类型:输入输出均为 `bfloat16`
|
||||
- `head_dim_ckv = 512`
|
||||
- `head_dim_kpe = 64`
|
||||
- `page_size = 1`
|
||||
- `causal = 0`
|
||||
- `num_heads` 取值为 `64, 128`
|
||||
- `batch_size` 取值为 `1, 4, 16`
|
||||
- `seq_len` 取值为 `1024, 4096, 8192, 16384`
|
||||
- `sm_scale = 1 / sqrt(head_dim_ckv + head_dim_kpe)`
|
||||
|
||||
测试点顺序与 `McFlashInfer/benchmarks/bench_batch_mla.py` 中的 cases 一致,即:
|
||||
|
||||
```python
|
||||
for num_heads in [64, 128]:
|
||||
for batch_size in [1, 4, 16]:
|
||||
for seq_len in [1024, 4096, 8192, 16384]:
|
||||
...
|
||||
```
|
||||
|
||||
输出参与 `torch.allclose` 校验,容差为 `rtol=1e-2, atol=1e-2`。
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
sectionTitle: "输出格式"
|
||||
type: "Text"
|
||||
---
|
||||
PyTorch 参考实现
|
||||
|
||||
```python
|
||||
def baseline(q_nope, q_pe, ckv, kpe, output,
|
||||
q_indptr, kv_indptr, kv_indices, kv_lens,
|
||||
batch_size, seq_len, num_heads,
|
||||
head_dim_ckv, head_dim_kpe, page_size, causal):
|
||||
sm_scale = 1.0 / ((head_dim_ckv + head_dim_kpe) ** 0.5)
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q_nope.device)
|
||||
wrapper = flashinfer.mla.BatchMLAPagedAttentionWrapper(workspace_buffer, backend="auto")
|
||||
wrapper.plan(
|
||||
q_indptr,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
kv_lens,
|
||||
num_heads,
|
||||
head_dim_ckv,
|
||||
head_dim_kpe,
|
||||
page_size,
|
||||
bool(causal),
|
||||
sm_scale,
|
||||
q_nope.dtype,
|
||||
ckv.dtype,
|
||||
)
|
||||
wrapper.run(q_nope, q_pe, ckv, kpe, out=output, return_lse=False)
|
||||
```
|
||||
|
|
@ -0,0 +1 @@
|
|||
FlashInfer MLA Paged Attention
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"id": 196,
|
||||
"displayId": 20004,
|
||||
"type": "Traditional",
|
||||
"isPublic": false,
|
||||
"locales": [
|
||||
"zh_CN"
|
||||
],
|
||||
"samples": [
|
||||
{
|
||||
"inputData": "1\n",
|
||||
"outputData": ""
|
||||
}
|
||||
],
|
||||
"problemTagIds": []
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
BATCH_SIZES = [1, 2, 4, 8, 16]
|
||||
SEQ_LENS_KV = [512, 1024, 2048, 4096, 8192, 16384]
|
||||
HEAD_DIMS = [128]
|
||||
NUM_QO_HEADS = 32
|
||||
PAGE_BLOCK_SIZE = 16
|
||||
|
||||
|
||||
def _build_cases():
|
||||
cases = []
|
||||
for batch_size in BATCH_SIZES:
|
||||
for seq_len_kv in SEQ_LENS_KV:
|
||||
for head_dim in HEAD_DIMS:
|
||||
num_kv_heads = 8 if head_dim == 64 else 4
|
||||
cases.append(
|
||||
(
|
||||
batch_size,
|
||||
seq_len_kv,
|
||||
NUM_QO_HEADS,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
PAGE_BLOCK_SIZE,
|
||||
)
|
||||
)
|
||||
return cases
|
||||
|
||||
|
||||
TESTCASES = _build_cases()
|
||||
|
||||
|
||||
def getNumOfTestcases() -> int:
|
||||
return len(TESTCASES)
|
||||
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Union
|
||||
import sys
|
||||
|
||||
import torch
|
||||
|
||||
KernelArg = Union[torch.Tensor, int, float]
|
||||
CURRENT_CASE = None
|
||||
|
||||
def _ensure_flashinfer_importable():
|
||||
try:
|
||||
import flashinfer # noqa: F401
|
||||
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
here = Path(__file__).resolve()
|
||||
for parent in here.parents:
|
||||
candidate = parent / "McFlashInfer"
|
||||
if (candidate / "flashinfer").is_dir():
|
||||
sys.path.insert(0, str(candidate))
|
||||
return
|
||||
|
||||
def _get_testcase_index() -> int:
|
||||
try:
|
||||
raw = input().strip()
|
||||
except EOFError:
|
||||
return 0
|
||||
if raw == "":
|
||||
return 0
|
||||
try:
|
||||
testcase_id = int(raw.split()[0])
|
||||
except ValueError:
|
||||
return 0
|
||||
if 1 <= testcase_id <= len(TESTCASES):
|
||||
return testcase_id - 1
|
||||
if 0 <= testcase_id < len(TESTCASES):
|
||||
return testcase_id
|
||||
return 0
|
||||
|
||||
def _compute_reps(batch_size: int, seq_len: int, head_dim: int, base_reps: int = 100) -> int:
|
||||
workload = batch_size * seq_len * head_dim
|
||||
if workload < 1e5:
|
||||
return base_reps
|
||||
if workload < 1e6:
|
||||
return base_reps // 2
|
||||
if workload < 1e7:
|
||||
return base_reps // 4
|
||||
if workload < 1e8:
|
||||
return base_reps // 8
|
||||
if workload < 1e9:
|
||||
return base_reps // 16
|
||||
return base_reps // 32
|
||||
|
||||
def _setup_paged_kv_indptr(batch_size: int, seq_len: int, page_block_size: int, device: str):
|
||||
seq_lens = torch.full((batch_size,), seq_len, dtype=torch.int32, device=device)
|
||||
seq_lens_blocks = torch.div(seq_lens + page_block_size - 1, page_block_size, rounding_mode="floor")
|
||||
kv_indptr = torch.empty((batch_size + 1,), dtype=torch.int32, device=device)
|
||||
kv_indptr[0] = 0
|
||||
kv_indptr[1:] = torch.cumsum(seq_lens_blocks, dim=0)
|
||||
num_blocks = int(kv_indptr[-1].item())
|
||||
last_page_len = (seq_lens - 1) % page_block_size + 1
|
||||
return kv_indptr, last_page_len, num_blocks
|
||||
|
||||
def getTestCaseSize() -> Tuple[List[Tuple[int, ...]], Tuple[int, int]]:
|
||||
testcase_id = _get_testcase_index()
|
||||
global CURRENT_CASE
|
||||
batch_size, seq_len_kv, num_qo_heads, num_kv_heads, head_dim, page_block_size = TESTCASES[testcase_id]
|
||||
num_blocks = batch_size * ((seq_len_kv + page_block_size - 1) // page_block_size)
|
||||
CURRENT_CASE = (
|
||||
batch_size,
|
||||
seq_len_kv,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
num_blocks,
|
||||
20260710 + testcase_id,
|
||||
)
|
||||
warmup = 3
|
||||
iters = max(1, _compute_reps(batch_size, seq_len_kv, head_dim))
|
||||
return [
|
||||
(batch_size, num_qo_heads, head_dim),
|
||||
(num_blocks, 2, page_block_size, num_kv_heads, head_dim),
|
||||
(batch_size, num_qo_heads, head_dim),
|
||||
(batch_size + 1,),
|
||||
(num_blocks,),
|
||||
(batch_size,),
|
||||
(), (), (), (), (), (),
|
||||
], (warmup, iters)
|
||||
|
||||
def genTestCase(testcase_sizes, device: str = "cuda") -> List[KernelArg]:
|
||||
del testcase_sizes
|
||||
batch_size, seq_len_kv, num_qo_heads, num_kv_heads, head_dim, page_block_size, num_blocks, seed = CURRENT_CASE
|
||||
gen = torch.Generator(device=device)
|
||||
gen.manual_seed(seed)
|
||||
dtype = torch.bfloat16
|
||||
q = torch.rand(batch_size, num_qo_heads, head_dim, dtype=dtype, device=device, generator=gen).contiguous()
|
||||
kv_data = torch.randn(
|
||||
num_blocks,
|
||||
2,
|
||||
page_block_size,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
dtype=dtype,
|
||||
device=device,
|
||||
generator=gen,
|
||||
).contiguous()
|
||||
output = torch.empty(batch_size, num_qo_heads, head_dim, dtype=dtype, device=device)
|
||||
kv_indptr, last_page_len, num_blocks_check = _setup_paged_kv_indptr(
|
||||
batch_size,
|
||||
seq_len_kv,
|
||||
page_block_size,
|
||||
device,
|
||||
)
|
||||
assert num_blocks_check == num_blocks
|
||||
kv_indices = torch.arange(num_blocks, dtype=torch.int32, device=device)
|
||||
return [
|
||||
q,
|
||||
kv_data,
|
||||
output,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
batch_size,
|
||||
seq_len_kv,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
]
|
||||
|
||||
def baseline(
|
||||
q,
|
||||
kv_data,
|
||||
output,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
batch_size,
|
||||
seq_len_kv,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
):
|
||||
_ensure_flashinfer_importable()
|
||||
import flashinfer
|
||||
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q.device)
|
||||
wrapper = flashinfer.BatchDecodeWithPagedKVCacheWrapper(
|
||||
workspace_buffer,
|
||||
kv_layout="NHD",
|
||||
use_tensor_cores=True,
|
||||
)
|
||||
wrapper.plan(
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
int(num_qo_heads),
|
||||
int(num_kv_heads),
|
||||
int(head_dim),
|
||||
int(page_block_size),
|
||||
data_type=torch.bfloat16,
|
||||
q_data_type=torch.bfloat16,
|
||||
)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
return [
|
||||
q,
|
||||
kv_data,
|
||||
output,
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
batch_size,
|
||||
seq_len_kv,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
]
|
||||
|
||||
def check(
|
||||
testcase_sizes,
|
||||
original_input_tensors,
|
||||
target_kernel_input_tensors,
|
||||
baseline_input_tensors,
|
||||
rtol=1e-2,
|
||||
atol=1e-2,
|
||||
) -> bool:
|
||||
del testcase_sizes, original_input_tensors
|
||||
output_t = target_kernel_input_tensors[2]
|
||||
output_ref = baseline_input_tensors[2]
|
||||
if output_t.shape != output_ref.shape:
|
||||
print(f"[FAIL] shape mismatch: target {output_t.shape}, ref {output_ref.shape}", file=sys.stderr)
|
||||
return False
|
||||
if output_t.dtype != output_ref.dtype:
|
||||
print(f"[FAIL] dtype mismatch: target {output_t.dtype}, ref {output_ref.dtype}", file=sys.stderr)
|
||||
return False
|
||||
if not torch.allclose(output_t.float(), output_ref.float(), rtol=rtol, atol=atol):
|
||||
diff = (output_t.float() - output_ref.float()).abs()
|
||||
print(
|
||||
f"[FAIL] allclose failed: max_abs_diff={float(diff.max().item()):.6f}, "
|
||||
f"mean_abs_diff={float(diff.mean().item()):.6f} (rtol={rtol}, atol={atol})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
sectionTitle: "题目描述"
|
||||
type: "Text"
|
||||
---
|
||||
你需要实现 FlashInfer paged KV cache decode 的 CUDA C++ 前向算子。
|
||||
|
||||
本题输入采用 FlashInfer `BatchDecodeWithPagedKVCacheWrapper` 的 paged `NHD` 布局。每个 batch 只有 1 个 query token,KV cache 长度为 `seq_len_kv`。
|
||||
|
||||
评测程序会调用你提交代码中的 `run_kernel` 函数。你需要根据 `kv_indptr`、`kv_indices` 和 `last_page_len` 读取 paged KV cache,并将结果写入 `output`。
|
||||
|
||||
baseline 使用 FlashInfer paged decode 的 Python API:
|
||||
|
||||
```python
|
||||
wrapper = flashinfer.BatchDecodeWithPagedKVCacheWrapper(
|
||||
workspace, kv_layout="NHD", use_tensor_cores=True)
|
||||
wrapper.plan(kv_indptr, kv_indices, last_page_len,
|
||||
num_qo_heads, num_kv_heads, head_dim, page_block_size,
|
||||
data_type=torch.bfloat16, q_data_type=torch.bfloat16)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
```
|
||||
|
||||
如何提交代码详见[评测指南](/d/2)。
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "cuda"
|
||||
---
|
||||
你必须在提交的 CUDA 源码中提供如下 **C 符号**,函数名、参数类型、顺序必须完全一致,并使用 `extern "C"` 防止 name mangling:
|
||||
|
||||
```cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* kv_data,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* kv_indptr,
|
||||
const int32_t* kv_indices,
|
||||
const int32_t* last_page_len,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len_kv,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim,
|
||||
int64_t page_block_size
|
||||
);
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q`:decode query tensor,shape `(batch_size, num_qo_heads, head_dim)`,连续 `bf16`
|
||||
* `kv_data`:paged KV cache,shape `(num_blocks, 2, page_block_size, num_kv_heads, head_dim)`,连续 `bf16`,其中 `kv_data[:, 0]` 为 key,`kv_data[:, 1]` 为 value
|
||||
* `output`:输出缓冲区,shape `(batch_size, num_qo_heads, head_dim)`,连续 `bf16`
|
||||
* `kv_indptr`:paged KV indptr,shape `(batch_size + 1)`,连续 `int32`
|
||||
* `kv_indices`:page index,shape `(num_blocks)`,连续 `int32`
|
||||
* `last_page_len`:每个 batch 最后一个 page 的有效 token 数,shape `(batch_size)`,连续 `int32`
|
||||
* `page_block_size`:page size,评测中固定为 `16`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 `run_kernel` 内部做 `cudaDeviceSynchronize()` 或显式同步。
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "tilelang"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len_kv, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q, kv_data, output, kv_indptr, kv_indices, last_page_len,
|
||||
batch_size, seq_len_kv, num_qo_heads,
|
||||
num_kv_heads, head_dim, page_block_size)
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q`:decode query tensor,连续 `bfloat16`
|
||||
* `kv_data`:paged KV cache,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `kv_indptr/kv_indices/last_page_len`:paged KV metadata,连续 `int32`
|
||||
* `page_block_size`:评测中固定为 `16`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
sectionTitle: "接口约定"
|
||||
type: "codeSample"
|
||||
lang: "triton"
|
||||
---
|
||||
你必须在提交的 Python 代码中提供 `run_kernel` 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
```python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len_kv, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
* `q`:decode query tensor,连续 `bfloat16`
|
||||
* `kv_data`:paged KV cache,连续 `bfloat16`
|
||||
* `output`:输出缓冲区,连续 `bfloat16`,需要写入结果
|
||||
* `kv_indptr/kv_indices/last_page_len`:paged KV metadata,连续 `int32`
|
||||
* `page_block_size`:评测中固定为 `16`
|
||||
|
||||
`run_kernel` 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
sectionTitle: "输入格式"
|
||||
type: "Text"
|
||||
---
|
||||
## 数据范围
|
||||
|
||||
- 数据类型:`q/kv_data/output` 均为 `bfloat16`
|
||||
- KV layout:`NHD`
|
||||
- `seq_len_q = 1`
|
||||
- `num_qo_heads = 32`
|
||||
- `page_block_size = 16`
|
||||
- `head_dim` 固定为 `128`
|
||||
- `num_kv_heads = 4`
|
||||
- `batch_size` 取值为 `1, 2, 4, 8, 16`
|
||||
- `seq_len_kv` 取值为 `512, 1024, 2048, 4096, 8192, 16384`
|
||||
|
||||
测试点顺序与 `McFlashInfer/benchmarks/bench_batch_decode.py` 中的 cases 一致,即:
|
||||
|
||||
```python
|
||||
for batch_size in [1, 2, 4, 8, 16]:
|
||||
for seq_len_kv in [512, 1024, 2048, 4096, 8192, 16384]:
|
||||
for head_dim in [128]:
|
||||
...
|
||||
```
|
||||
|
||||
输出参与 `torch.allclose` 校验,容差为 `rtol=1e-2, atol=1e-2`。
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
sectionTitle: "输出格式"
|
||||
type: "Text"
|
||||
---
|
||||
PyTorch 参考实现
|
||||
|
||||
```python
|
||||
def baseline(q, kv_data, output, kv_indptr, kv_indices, last_page_len,
|
||||
batch_size, seq_len_kv, num_qo_heads,
|
||||
num_kv_heads, head_dim, page_block_size):
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q.device)
|
||||
wrapper = flashinfer.BatchDecodeWithPagedKVCacheWrapper(
|
||||
workspace_buffer,
|
||||
kv_layout="NHD",
|
||||
use_tensor_cores=True,
|
||||
)
|
||||
wrapper.plan(
|
||||
kv_indptr,
|
||||
kv_indices,
|
||||
last_page_len,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim,
|
||||
page_block_size,
|
||||
data_type=torch.bfloat16,
|
||||
q_data_type=torch.bfloat16,
|
||||
)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
```
|
||||
|
|
@ -0,0 +1 @@
|
|||
FlashInfer Paged Decode
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
# Agent 推理算子库优化 - FlashInfer Ragged Prefill
|
||||
|
||||
当前题目说明来源为 [*XPU-OJ 20001*](https://xpuoj.com/contest/2/problem/1),内容以 XPU-OJ 线上为准。
|
||||
|
||||
## 1. 题目描述
|
||||
你需要实现 FlashInfer ragged KV cache prefill 的CUDA C++前向算子。
|
||||
|
||||
本题输入采用 FlashInfer BatchPrefillWithRaggedKVCacheWrapper 的 ragged NHD 布局。每个 batch 段的 query/KV 长度由 qo_indptr 和 kv_indptr 给出;seq_len 只是所有段长度的上界,真实总长度分别是 qo_indptr[batch_size] 和 kv_indptr[batch_size]。
|
||||
|
||||
其中 query heads 采用 GQA 布局:num_qo_heads 个 query/output heads 共享 num_kv_heads 个 KV heads,G = num_qo_heads / num_kv_heads。
|
||||
|
||||
评测程序会调用你提交代码中的 run_kernel 函数。你需要根据 qo_indptr 和 kv_indptr 读取 ragged Q/K/V,并将结果写入 output。
|
||||
|
||||
baseline 使用 FlashInfer ragged prefill 的 Python API:
|
||||
|
||||
``` python
|
||||
wrapper = flashinfer.BatchPrefillWithRaggedKVCacheWrapper(workspace, kv_layout="NHD", backend="auto")
|
||||
wrapper.plan(qo_indptr, kv_indptr, num_qo_heads, num_kv_heads,
|
||||
head_dim_qk, head_dim_vo, causal=True,
|
||||
q_data_type=torch.bfloat16, kv_data_type=torch.bfloat16)
|
||||
wrapper.run(q, k, v, out=output)
|
||||
|
||||
```
|
||||
|
||||
如何提交代码详见 [*评测指南*](https://xpuoj.com/d/2)。
|
||||
|
||||
## 2. 接口约定
|
||||
|
||||
### 2.1 CUDA
|
||||
|
||||
你必须在提交的 CUDA 源码中提供如下 C 符号,函数名、参数类型、顺序必须完全一致,并使用 extern "C" 防止 name mangling:
|
||||
|
||||
``` cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* k,
|
||||
const __nv_bfloat16* v,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* qo_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim_qk,
|
||||
int64_t head_dim_vo,
|
||||
int64_t causal
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q:query tensor,shape (total_q, num_qo_heads, head_dim_qk),连续 bf16,其中 total_q = qo_indptr[batch_size]
|
||||
- k:key tensor,shape (total_kv, num_kv_heads, head_dim_qk),连续 bf16,其中 total_kv = kv_indptr[batch_size]
|
||||
- v:value tensor,shape (total_kv, num_kv_heads, head_dim_vo),连续 bf16
|
||||
- output:输出缓冲区,shape (total_q, num_qo_heads, head_dim_vo),连续 bf16
|
||||
- qo_indptr:query/output ragged indptr,shape (batch_size + 1),连续 int32
|
||||
- kv_indptr:KV ragged indptr,shape (batch_size + 1),连续 int32
|
||||
- seq_len:所有 query/KV 段长度的上界,可用于 launch grid;真实段长必须由 indptr 读取
|
||||
- causal:是否启用 causal mask,评测中固定为 1
|
||||
|
||||
部分测试点是等长段,但也包含 q_len != kv_len 和不同 batch 段长度不相等的 ragged 测试点。实现不能假设 qo_indptr[b + 1] - qo_indptr[b] == seq_len 或 kv_indptr[b + 1] - kv_indptr[b] == seq_len。
|
||||
|
||||
run_kernel 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 run_kernel 内部做 cudaDeviceSynchronize() 或显式同步。
|
||||
|
||||
### 2.2 Triton
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (total_q, num_qo_heads, head_dim_qk)
|
||||
k, # Tensor[bf16], shape (total_kv, num_kv_heads, head_dim_qk)
|
||||
v, # Tensor[bf16], shape (total_kv, num_kv_heads, head_dim_vo)
|
||||
output, # Tensor[bf16], shape (total_q, num_qo_heads, head_dim_vo)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
batch_size, # int64
|
||||
seq_len, # int64, max segment length bound
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim_qk, # int64
|
||||
head_dim_vo, # int64
|
||||
causal, # int64
|
||||
):
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q/k/v:FlashInfer ragged prefill 输入 tensor,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- qo_indptr/kv_indptr:ragged indptr,连续 int32;真实段长和 total_q/total_kv 以 indptr 为准
|
||||
- causal:是否启用 causal mask,评测中固定为 1
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
||||
### 2.3 TileLang
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (total_q, num_qo_heads, head_dim_qk)
|
||||
k, # Tensor[bf16], shape (total_kv, num_kv_heads, head_dim_qk)
|
||||
v, # Tensor[bf16], shape (total_kv, num_kv_heads, head_dim_vo)
|
||||
output, # Tensor[bf16], shape (total_q, num_qo_heads, head_dim_vo)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
batch_size, # int64
|
||||
seq_len, # int64, max segment length bound
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim_qk, # int64
|
||||
head_dim_vo, # int64
|
||||
causal, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q, k, v, output, qo_indptr, kv_indptr,
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads,
|
||||
head_dim_qk, head_dim_vo, causal)
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q/k/v:FlashInfer ragged prefill 输入 tensor,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- qo_indptr/kv_indptr:ragged indptr,连续 int32;真实段长和 total_q/total_kv 以 indptr 为准
|
||||
- causal:是否启用 causal mask,评测中固定为 1
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
||||
## 3. 输入格式
|
||||
|
||||
本题输入由评测程序在 GPU 上构造,并按接口约定中的顺序传入 run_kernel。
|
||||
|
||||
所有 q/k/v/output 均为连续 torch.bfloat16 CUDA tensor,qo_indptr/kv_indptr 为连续 torch.int32 CUDA tensor。
|
||||
|
||||
张量布局固定为 FlashInfer ragged prefill 的 NHD 布局。
|
||||
|
||||
## 4. 输出格式
|
||||
|
||||
输出写入 output,shape 为 (total_q, num_qo_heads, head_dim_vo),类型为 bfloat16,其中 total_q = qo_indptr[batch_size]。
|
||||
|
||||
## 5. 样例
|
||||
|
||||
若 batch_size = 1、seq_len = 4、num_qo_heads = 1、num_kv_heads = 1,则:
|
||||
|
||||
```
|
||||
qo_indptr = [0, 4]
|
||||
kv_indptr = [0, 4]
|
||||
```
|
||||
|
||||
第 t 个 query 会访问同一 batch 内的 KV token 前缀;启用 causal mask 时,只能看到位置不超过 t 的 token。例如 t = 2 时:
|
||||
|
||||
```
|
||||
attention = softmax(q[2, 0, :] @ k[0:3, 0, :].T / sqrt(head_dim_qk))
|
||||
output[2, 0, :] = attention @ v[0:3, 0, :]
|
||||
```
|
||||
|
||||
若某个 varlen case 中 q_len=2、kv_len=4,则 causal mask 采用 FlashInfer/sol-execbench 的 bottom-right 对齐:第 t 个 query 可见的 KV 上界为 t + 1 + (kv_len - q_len)。例如 t=0 时可见 k[0:3],t=1 时可见 k[0:4]。
|
||||
|
||||
## 6. 数据范围与提示
|
||||
|
||||
- 数据类型:q/k/v/output 均为 bfloat16
|
||||
- KV layout:NHD
|
||||
- num_qo_heads = 32
|
||||
- num_kv_heads = 4
|
||||
- causal = 1
|
||||
- head_dim_qk, head_dim_vo 取值为 (128, 128)
|
||||
- batch_size 取值随测试点变化,覆盖 1, 2, 4, 15, 16, 27, 33
|
||||
- seq_len 参数表示所有 query/KV 段长度的上界,各测试点的段长上界覆盖 1, 65, 123, 873, 987, 1024, 1280, 2048, 4096, 16384(变长测试点内部还包含 512、640 等更短的真实段长)
|
||||
- total_q = qo_indptr[batch_size]
|
||||
- total_kv = kv_indptr[batch_size]
|
||||
|
||||
注意:
|
||||
|
||||
- G = num_qo_heads / num_kv_heads,同一个 KV head 服务连续的 G 个 query heads。
|
||||
- 对 query head h_q,对应的 KV head 为 h_q / G。
|
||||
- 真实段长必须从 qo_indptr 和 kv_indptr 读取,不能假设每段长度相同。
|
||||
- 启用 causal mask 后,采用 bottom-right 对齐。若当前段 q_len != kv_len,第 t 个 query 可访问的位置满足 kv_pos < t + 1 + (kv_len - q_len)。
|
||||
- 输出校验容差为 rtol=1.6e-2, atol=1.6e-2,且允许不超过 1% 的元素超差(匹配率需 ≥ 0.99)。
|
||||
- 被容忍的超差元素其绝对误差仍不得超过 8 × (atol + rtol · |ref|),避免个别段被整段算错而蒙混通过。
|
||||
- 单 token 边界(用例 14)和非 2 的幂尾段(用例 15)为小规模确定性用例,要求逐元素通过(匹配率需 = 1.0)。
|
||||
- q/k/v 使用标准正态分布生成,避免均匀正输入导致长序列 softmax 退化成近似 prefix mean。
|
||||
|
||||
## 7. 测试用例尺寸
|
||||
|
||||
测试点顺序与 testcase_config.py 的 TESTCASES 一致。共 15 个测试点,全部 head_dim_qk = head_dim_vo = 128,覆盖等长长序列、变长 ragged、q_len < kv_len、短段和非 2 的幂长度。
|
||||
|
||||
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse; width:100%;">
|
||||
<thead>
|
||||
<tr style="text-align:center; vertical-align:middle;">
|
||||
<th style="padding:6px 10px;">测试用例ID</th>
|
||||
<th>类型</th>
|
||||
<th>batch</th>
|
||||
<th>total_q</th>
|
||||
<th>total_kv</th>
|
||||
<th>max_q</th>
|
||||
<th>max_kv</th>
|
||||
<th>heads</th>
|
||||
<th>head_dim</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>混合 ragged 长序列</td>
|
||||
<td>33</td>
|
||||
<td colspan="2">16294</td>
|
||||
<td colspan="2">987</td>
|
||||
<td rowspan="15">32/4</td>
|
||||
<td rowspan="15">128/128</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td rowspan="7">等长序列</td>
|
||||
<td rowspan="3">1</td>
|
||||
<td colspan="4">1024</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td colspan="4">4096</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td colspan="4">16384</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td rowspan="2">4</td>
|
||||
<td colspan="2">4096</td>
|
||||
<td colspan="2">1024</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td rowspan="2" colspan="2">16384</td>
|
||||
<td colspan="2">4096</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td rowspan="2">16</td>
|
||||
<td colspan="2">1024</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td colspan="2">32768</td>
|
||||
<td colspan="2">2048</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>变长 <code>q_len < kv_len</code></td>
|
||||
<td rowspan="2">4</td>
|
||||
<td>2048</td>
|
||||
<td>4096</td>
|
||||
<td>512</td>
|
||||
<td>1024</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>混合变长 <code>q_len < kv_len</code></td>
|
||||
<td>1536</td>
|
||||
<td>3584</td>
|
||||
<td>640</td>
|
||||
<td>1280</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>双段变长 <code>q_len < kv_len</code></td>
|
||||
<td>2</td>
|
||||
<td>1024</td>
|
||||
<td>3072</td>
|
||||
<td>512</td>
|
||||
<td>2048</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>混合 ragged 中长序列</td>
|
||||
<td>27</td>
|
||||
<td colspan="2">12251</td>
|
||||
<td colspan="2">873</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td>混合 ragged 短序列</td>
|
||||
<td>15</td>
|
||||
<td colspan="2">969</td>
|
||||
<td colspan="2">123</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>14</td>
|
||||
<td>单 token 边界</td>
|
||||
<td colspan="5">1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>15</td>
|
||||
<td>非 2 的幂尾段</td>
|
||||
<td>2</td>
|
||||
<td colspan="2">98</td>
|
||||
<td colspan="2">65</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
说明:变长测试点的真实段长由 qo_indptr 和 kv_indptr 给出;参赛实现应始终以 indptr 为准,而不是从 seq_len、total_q 或 total_kv 反推出每段长度。
|
||||
|
||||
## 8. PyTorch 参考实现
|
||||
|
||||
``` python
|
||||
def baseline(q, k, v, output, qo_indptr, kv_indptr,
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads,
|
||||
head_dim_qk, head_dim_vo, causal):
|
||||
workspace_buffer = torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device=q.device)
|
||||
wrapper = flashinfer.BatchPrefillWithRaggedKVCacheWrapper(
|
||||
workspace_buffer,
|
||||
kv_layout="NHD",
|
||||
backend="auto",
|
||||
)
|
||||
wrapper.plan(
|
||||
qo_indptr,
|
||||
kv_indptr,
|
||||
num_qo_heads,
|
||||
num_kv_heads,
|
||||
head_dim_qk,
|
||||
head_dim_vo,
|
||||
causal=bool(causal),
|
||||
q_data_type=torch.bfloat16,
|
||||
kv_data_type=torch.bfloat16,
|
||||
)
|
||||
wrapper.run(q, k, v, out=output)
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
# Agent 推理算子库优化 - FlashInfer Paged Prefill
|
||||
|
||||
当前题目说明来源为 [*XPU-OJ 20002*](https://xpuoj.com/contest/2/problem/2),内容以 XPU-OJ 线上为准。
|
||||
|
||||
## 1. 题目描述
|
||||
你需要实现 FlashInfer paged KV cache prefill 的CUDA C++前向算子。
|
||||
|
||||
本题输入采用 FlashInfer BatchPrefillWithPagedKVCacheWrapper 的 paged NHD 布局。每个 batch 中有 seq_len 个 query token,KV cache 也有 seq_len 个 token,并按 page 存储。
|
||||
|
||||
评测程序会调用你提交代码中的 run_kernel 函数。你需要根据 qo_indptr、kv_indptr、kv_indices 和 last_page_len 读取 paged KV cache,并将结果写入 output。
|
||||
|
||||
baseline 使用 FlashInfer paged prefill 的 Python API:
|
||||
|
||||
``` python
|
||||
wrapper = flashinfer.BatchPrefillWithPagedKVCacheWrapper(workspace, kv_layout="NHD", backend="auto")
|
||||
wrapper.plan(qo_indptr, kv_indptr, kv_indices, last_page_len,
|
||||
num_qo_heads, num_kv_heads, head_dim, page_block_size,
|
||||
causal=bool(causal),
|
||||
q_data_type=torch.bfloat16, kv_data_type=torch.bfloat16)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
|
||||
```
|
||||
|
||||
如何提交代码详见 [*评测指南*](https://xpuoj.com/d/2)。
|
||||
|
||||
## 2. 接口约定
|
||||
|
||||
### 2.1 CUDA
|
||||
|
||||
你必须在提交的 CUDA 源码中提供如下 C 符号,函数名、参数类型、顺序必须完全一致,并使用 extern "C" 防止 name mangling:
|
||||
|
||||
``` cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* kv_data,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* qo_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
const int32_t* kv_indices,
|
||||
const int32_t* last_page_len,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim,
|
||||
int64_t page_block_size,
|
||||
int64_t causal
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q:query tensor,shape (batch_size * seq_len, num_qo_heads, head_dim),连续 bf16
|
||||
- kv_data:paged KV cache,shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim),连续 bf16,其中 kv_data[:, 0] 为 key,kv_data[:, 1] 为 value
|
||||
- output:输出缓冲区,shape (batch_size * seq_len, num_qo_heads, head_dim),连续 bf16
|
||||
- qo_indptr:query/output indptr,shape (batch_size + 1),连续 int32
|
||||
- kv_indptr:paged KV indptr,shape (batch_size + 1),连续 int32
|
||||
- kv_indices:page index,shape (num_blocks),连续 int32
|
||||
- last_page_len:每个 batch 最后一个 page 的有效 token 数,shape (batch_size),连续 int32
|
||||
- page_block_size:page size,评测中固定为 16
|
||||
- causal:是否启用 causal mask,本题按 benchmark case 固定为 0
|
||||
|
||||
run_kernel 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 run_kernel 内部做 cudaDeviceSynchronize() 或显式同步。
|
||||
|
||||
### 2.2 Triton
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q:query tensor,连续 bfloat16
|
||||
- kv_data:paged KV cache,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- qo_indptr/kv_indptr/kv_indices/last_page_len:paged KV metadata,连续 int32
|
||||
- page_block_size:评测中固定为 16
|
||||
- causal:评测中固定为 0
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
||||
### 2.3 TileLang
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size * seq_len, num_qo_heads, head_dim)
|
||||
qo_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q, kv_data, output, qo_indptr, kv_indptr, kv_indices, last_page_len,
|
||||
batch_size, seq_len, num_qo_heads, num_kv_heads,
|
||||
head_dim, page_block_size, causal)
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q:query tensor,连续 bfloat16
|
||||
- kv_data:paged KV cache,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- qo_indptr/kv_indptr/kv_indices/last_page_len:paged KV metadata,连续 int32
|
||||
- page_block_size:评测中固定为 16
|
||||
- causal:评测中固定为 0
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
||||
## 3. 输入格式
|
||||
|
||||
本题输入由评测程序在 GPU 上构造,并按接口约定中的顺序传入 run_kernel。
|
||||
|
||||
q/kv_data/output 均为连续 torch.bfloat16 CUDA tensor,qo_indptr/kv_indptr/kv_indices/last_page_len 均为连续 torch.int32 CUDA tensor。
|
||||
|
||||
KV layout 固定为 FlashInfer paged prefill 的 NHD 布局,page size 固定为 16。
|
||||
|
||||
## 4. 输出格式
|
||||
|
||||
输出写入 output,shape 为 (batch_size * seq_len, num_qo_heads, head_dim),类型为 bfloat16。
|
||||
|
||||
## 5. 样例
|
||||
|
||||
若 batch_size = 1、seq_len = 32、page_block_size = 16,则:
|
||||
|
||||
```
|
||||
qo_indptr = [0, 32]
|
||||
kv_indptr = [0, 2]
|
||||
kv_indices = [0, 1]
|
||||
last_page_len = [16]
|
||||
```
|
||||
|
||||
第 0 个 batch 的 KV token 存放在 page 0 和 page 1 中,每个 page 有 16 个 token。
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
# Agent 推理算子库优化 - FlashInfer MLA Paged Attention
|
||||
|
||||
当前题目说明来源为 [*XPU-OJ 20003*](https://xpuoj.com/contest/2/problem/3),内容以 XPU-OJ 线上为准。
|
||||
|
||||
## 1. 题目描述
|
||||
你需要实现 FlashInfer MLA paged attention 的 CUDA C++ 前向算子。
|
||||
|
||||
本题使用 BatchMLAPagedAttentionWrapper 的 DeepSeek MLA decode 配置:q_nope 表示不含 RoPE 的 query,q_pe 表示 RoPE 部分的 query,ckv 与 kpe 分别表示 compressed KV cache 与 RoPE KV cache。MLA page size 固定为 1。
|
||||
|
||||
评测程序会调用你提交代码中的 run_kernel 函数。你需要根据 q_indptr、kv_indptr、kv_indices 和 kv_lens 读取 cache,并将结果写入 output。
|
||||
|
||||
baseline 使用 FlashInfer MLA 的 Python API:
|
||||
|
||||
``` python
|
||||
wrapper = flashinfer.mla.BatchMLAPagedAttentionWrapper(workspace, backend="auto")
|
||||
wrapper.plan(q_indptr, kv_indptr, kv_indices, kv_lens,
|
||||
num_heads, head_dim_ckv, head_dim_kpe,
|
||||
page_size, False, sm_scale,
|
||||
q_nope.dtype, ckv.dtype)
|
||||
wrapper.run(q_nope, q_pe, ckv, kpe, out=output, return_lse=False)
|
||||
|
||||
```
|
||||
|
||||
如何提交代码详见 [*评测指南*](https://xpuoj.com/d/2)。
|
||||
|
||||
## 2. 接口约定
|
||||
|
||||
### 2.1 CUDA
|
||||
|
||||
你必须在提交的 CUDA 源码中提供如下 C 符号,函数名、参数类型、顺序必须完全一致,并使用 extern "C" 防止 name mangling:
|
||||
|
||||
``` cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q_nope,
|
||||
const __nv_bfloat16* q_pe,
|
||||
const __nv_bfloat16* ckv,
|
||||
const __nv_bfloat16* kpe,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* q_indptr,
|
||||
const int32_t* kv_indptr,
|
||||
const int32_t* kv_indices,
|
||||
const int32_t* kv_lens,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len,
|
||||
int64_t num_heads,
|
||||
int64_t head_dim_ckv,
|
||||
int64_t head_dim_kpe,
|
||||
int64_t page_size,
|
||||
int64_t causal
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q_nope:query 的 compressed/nope 部分,shape (batch_size, num_heads, head_dim_ckv),连续 bf16
|
||||
- q_pe:query 的 RoPE 部分,shape (batch_size, num_heads, head_dim_kpe),连续 bf16
|
||||
- ckv:compressed KV cache,shape (batch_size * seq_len, 1, head_dim_ckv),连续 bf16
|
||||
- kpe:RoPE KV cache,shape (batch_size * seq_len, 1, head_dim_kpe),连续 bf16
|
||||
- output:输出缓冲区,shape (batch_size, num_heads, head_dim_ckv),连续 bf16
|
||||
- q_indptr:decode query indptr,shape (batch_size + 1),内容为 [0, 1, ..., batch_size]
|
||||
- kv_indptr:KV indptr,shape (batch_size + 1),每段长度为 seq_len
|
||||
- kv_indices:page index,shape (batch_size * seq_len),连续 int32
|
||||
- kv_lens:每个 batch 的 KV 长度,shape (batch_size),连续 int32
|
||||
- page_size:评测中固定为 1
|
||||
- causal:评测中固定为 0
|
||||
|
||||
run_kernel 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 run_kernel 内部做 cudaDeviceSynchronize() 或显式同步。
|
||||
|
||||
### 2.2 Triton
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q_nope, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_pe, # Tensor[bf16], shape (batch_size, num_heads, head_dim_kpe)
|
||||
ckv, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_ckv)
|
||||
kpe, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_kpe)
|
||||
output, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (batch_size * seq_len)
|
||||
kv_lens, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_heads, # int64
|
||||
head_dim_ckv, # int64
|
||||
head_dim_kpe, # int64
|
||||
page_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q_nope/q_pe/ckv/kpe:MLA attention 输入 tensor,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- q_indptr/kv_indptr/kv_indices/kv_lens:paged attention metadata,连续 int32
|
||||
- page_size:评测中固定为 1
|
||||
- causal:评测中固定为 0
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
||||
### 2.3 TileLang
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q_nope, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_pe, # Tensor[bf16], shape (batch_size, num_heads, head_dim_kpe)
|
||||
ckv, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_ckv)
|
||||
kpe, # Tensor[bf16], shape (batch_size * seq_len, 1, head_dim_kpe)
|
||||
output, # Tensor[bf16], shape (batch_size, num_heads, head_dim_ckv)
|
||||
q_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (batch_size * seq_len)
|
||||
kv_lens, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len, # int64
|
||||
num_heads, # int64
|
||||
head_dim_ckv, # int64
|
||||
head_dim_kpe, # int64
|
||||
page_size, # int64
|
||||
causal, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q_nope, q_pe, ckv, kpe, output,
|
||||
q_indptr, kv_indptr, kv_indices, kv_lens,
|
||||
batch_size, seq_len, num_heads,
|
||||
head_dim_ckv, head_dim_kpe, page_size, causal)
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q_nope/q_pe/ckv/kpe:MLA attention 输入 tensor,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- q_indptr/kv_indptr/kv_indices/kv_lens:paged attention metadata,连续 int32
|
||||
- page_size:评测中固定为 1
|
||||
- causal:评测中固定为 0
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
||||
## 3. 输入格式
|
||||
|
||||
本题输入由评测程序在 GPU 上构造,并按接口约定中的顺序传入 run_kernel。
|
||||
|
||||
q_nope/q_pe/ckv/kpe/output 均为连续 torch.bfloat16 CUDA tensor,q_indptr/kv_indptr/kv_indices/kv_lens 均为连续 torch.int32 CUDA tensor。
|
||||
|
||||
## 4. 输出格式
|
||||
|
||||
输出写入 output,shape 为 (batch_size, num_heads, head_dim_ckv),类型为 bfloat16。
|
||||
|
||||
## 5. 样例
|
||||
|
||||
若 batch_size = 2、seq_len = 4,则:
|
||||
|
||||
```
|
||||
q_indptr = [0, 1, 2]
|
||||
kv_indptr = [0, 4, 8]
|
||||
kv_indices = [0, 1, 2, 3, 4, 5, 6, 7]
|
||||
kv_lens = [4, 4]
|
||||
```
|
||||
|
||||
每个 batch 只有 1 个 decode query,会访问对应 batch 的全部 KV cache。
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
# Agent 推理算子库优化 - FlashInfer Paged Decode
|
||||
|
||||
当前题目说明来源为 [*XPU-OJ 20004*](https://xpuoj.com/contest/2/problem/4),内容以 XPU-OJ 线上为准。
|
||||
|
||||
## 1. 题目描述
|
||||
你需要实现 FlashInfer paged KV cache decode 的 CUDA C++ 前向算子。
|
||||
|
||||
本题输入采用 FlashInfer BatchDecodeWithPagedKVCacheWrapper 的 paged NHD 布局。每个 batch 只有 1 个 query token,KV cache 长度为 seq_len_kv。
|
||||
|
||||
评测程序会调用你提交代码中的 run_kernel 函数。你需要根据 kv_indptr、kv_indices 和 last_page_len 读取 paged KV cache,并将结果写入 output。
|
||||
|
||||
baseline 使用 FlashInfer paged decode 的 Python API:
|
||||
|
||||
``` python
|
||||
wrapper = flashinfer.BatchDecodeWithPagedKVCacheWrapper(
|
||||
workspace, kv_layout="NHD", use_tensor_cores=True)
|
||||
wrapper.plan(kv_indptr, kv_indices, last_page_len,
|
||||
num_qo_heads, num_kv_heads, head_dim, page_block_size,
|
||||
data_type=torch.bfloat16, q_data_type=torch.bfloat16)
|
||||
wrapper.run(q, kv_data, out=output)
|
||||
|
||||
```
|
||||
|
||||
如何提交代码详见 [*评测指南*](https://xpuoj.com/d/2)。
|
||||
|
||||
## 2. 接口约定
|
||||
|
||||
### 2.1 CUDA
|
||||
|
||||
你必须在提交的 CUDA 源码中提供如下 C 符号,函数名、参数类型、顺序必须完全一致,并使用 extern "C" 防止 name mangling:
|
||||
|
||||
``` cpp
|
||||
#include <stdint.h>
|
||||
#include <cuda_bf16.h>
|
||||
|
||||
extern "C" void run_kernel(
|
||||
const __nv_bfloat16* q,
|
||||
const __nv_bfloat16* kv_data,
|
||||
__nv_bfloat16* output,
|
||||
const int32_t* kv_indptr,
|
||||
const int32_t* kv_indices,
|
||||
const int32_t* last_page_len,
|
||||
int64_t batch_size,
|
||||
int64_t seq_len_kv,
|
||||
int64_t num_qo_heads,
|
||||
int64_t num_kv_heads,
|
||||
int64_t head_dim,
|
||||
int64_t page_block_size
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q:decode query tensor,shape (batch_size, num_qo_heads, head_dim),连续 bf16
|
||||
- kv_data:paged KV cache,shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim),连续 bf16,其中 kv_data[:, 0] 为 key,kv_data[:, 1] 为 value
|
||||
- output:输出缓冲区,shape (batch_size, num_qo_heads, head_dim),连续 bf16
|
||||
- kv_indptr:paged KV indptr,shape (batch_size + 1),连续 int32
|
||||
- kv_indices:page index,shape (num_blocks),连续 int32
|
||||
- last_page_len:每个 batch 最后一个 page 的有效 token 数,shape (batch_size),连续 int32
|
||||
- page_block_size:page size,评测中固定为 16
|
||||
|
||||
run_kernel 内部需要自行计算合适的 launch 配置并启动 CUDA kernel。为保证计时准确,不建议在 run_kernel 内部做 cudaDeviceSynchronize() 或显式同步。
|
||||
|
||||
### 2.2 Triton
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def your_kernel(...):
|
||||
...
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len_kv, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
):
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q:decode query tensor,连续 bfloat16
|
||||
- kv_data:paged KV cache,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- kv_indptr/kv_indices/last_page_len:paged KV metadata,连续 int32
|
||||
- page_block_size:评测中固定为 16
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 Triton kernel。
|
||||
|
||||
### 2.3 TileLang
|
||||
|
||||
你必须在提交的 Python 代码中提供 run_kernel 函数,函数名、参数顺序、类型必须完全一致:
|
||||
|
||||
``` python
|
||||
import tilelang
|
||||
import tilelang.language as T
|
||||
from tilelang import jit
|
||||
|
||||
real_kernel = None
|
||||
|
||||
@jit
|
||||
def build_kernel(*args):
|
||||
@T.prim_func
|
||||
def kernel(*args):
|
||||
...
|
||||
return kernel
|
||||
|
||||
def run_kernel(
|
||||
q, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_data, # Tensor[bf16], shape (num_blocks, 2, page_block_size, num_kv_heads, head_dim)
|
||||
output, # Tensor[bf16], shape (batch_size, num_qo_heads, head_dim)
|
||||
kv_indptr, # Tensor[int32], shape (batch_size + 1)
|
||||
kv_indices, # Tensor[int32], shape (num_blocks)
|
||||
last_page_len, # Tensor[int32], shape (batch_size)
|
||||
batch_size, # int64
|
||||
seq_len_kv, # int64
|
||||
num_qo_heads, # int64
|
||||
num_kv_heads, # int64
|
||||
head_dim, # int64
|
||||
page_block_size, # int64
|
||||
):
|
||||
global real_kernel
|
||||
if real_kernel is None:
|
||||
real_kernel = build_kernel(...)
|
||||
real_kernel(q, kv_data, output, kv_indptr, kv_indices, last_page_len,
|
||||
batch_size, seq_len_kv, num_qo_heads,
|
||||
num_kv_heads, head_dim, page_block_size)
|
||||
|
||||
```
|
||||
|
||||
**参数说明**
|
||||
|
||||
- q:decode query tensor,连续 bfloat16
|
||||
- kv_data:paged KV cache,连续 bfloat16
|
||||
- output:输出缓冲区,连续 bfloat16,需要写入结果
|
||||
- kv_indptr/kv_indices/last_page_len:paged KV metadata,连续 int32
|
||||
- page_block_size:评测中固定为 16
|
||||
|
||||
run_kernel 内部需要自行计算合适的 grid/block,并 launch 你实现的 TileLang kernel。
|
||||
|
||||
## 3. 输入格式
|
||||
|
||||
本题输入由评测程序在 GPU 上构造,并按接口约定中的顺序传入 run_kernel。
|
||||
|
||||
q/kv_data/output 均为连续 torch.bfloat16 CUDA tensor,kv_indptr/kv_indices/last_page_len 均为连续 torch.int32 CUDA tensor。
|
||||
|
||||
KV layout 固定为 FlashInfer paged decode 的 NHD 布局,page size 固定为 16。
|
||||
|
||||
## 4. 输出格式
|
||||
|
||||
输出写入 output,shape 为 (batch_size, num_qo_heads, head_dim),类型为 bfloat16。
|
||||
|
||||
## 5. 样例
|
||||
|
||||
若 batch_size = 1、seq_len_kv = 32、page_block_size = 16,则:
|
||||
|
||||
```
|
||||
kv_indptr = [0, 2]
|
||||
kv_indices = [0, 1]
|
||||
last_page_len = [16]
|
||||
```
|
||||
|
||||
decode query 会访问该 batch 的全部 32 个 KV token。
|
||||
Loading…
Reference in New Issue