Merge pull request '更新XPU-OJ教程并修复合并冲突' (#25) from xiao-ke/op_optimization:master into master

This commit is contained in:
Beckylu 2026-06-17 08:57:07 +08:00
commit 59a4fb8f8c
97 changed files with 1856 additions and 10 deletions

View File

@ -11,6 +11,8 @@
3. 跑通 BatchDecode、BatchPrefill、MLA 等典型算子的 API 调用示例
4. 完成各算子在不同参数配置下的 Benchmark 测试
5. 输出各算子的 Baseline 性能结果记录表为后续算子优化提供对比基准。
6. 理解 XPU-OJ 评测`run_kernel`接口与精度要求。
7. 理解 Baseline 与 XPU-OJ 评测题包之间的关系,能够根据题包接口实现一个最小正确版 run_kernel。
## 三、适用对象
@ -39,9 +41,9 @@
* 步骤1获取算力券[https://developer.metax-tech.com/activities/6](https://developer.metax-tech.com/activities/6)
* 首次登录需要先进行注册(使用邮箱或者手机号进行注册)
* 登录成功后进行第二步-邮箱验证,填入自己的邮箱。
* 第三步,提交申请。
* 获得兑换码
@ -124,7 +126,9 @@ prefill = 并行处理用户输入decode = 逐个生成回答 token
## 六、项目实践--FlashInfer-Baseline
### Step 1检查运行环境
## 本地项目运行
#### Step 1检查运行环境
**目标:** 进入terminal确认当前环境满足本模块运行要求。
@ -140,7 +144,7 @@ prefill = 并行处理用户输入decode = 逐个生成回答 token
| `No GPUs are available` | 检查 MXMACA 驱动是否正确安装 |
| `ModuleNotFoundError: No module named 'flashinfer'` | 执行 `pip install flashinfer` 安装 |
### Step 2进入项目目录
#### Step 2进入项目目录
**目标:** 进入本模块所需的源码目录[flashinfer_baseline](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%2Fbaselines%2Fflashinfer_baseline)。
@ -154,12 +158,12 @@ prefill = 并行处理用户输入decode = 逐个生成回答 token
在仓库目录 `op_optimization/基于AI Agent开发范式的国产GPU大模型推理算子库优化` 下,找到 `flashinfer_baseline` 文件夹。可以将 `flashinfer_baseline` 整个目录复制到工作目录 `data/` 下。
**下一步操作:** 切换到 FlashInfer Baseline 项目目录。
**下一步操作:** 切换到 FlashInfer_Baseline 项目目录。
**命令示例:**
```bash
cd /data/flashinfer_baseline
cd baselines\flashinfer_baseline\FlashInfer_Baseline
ls -la
```
@ -176,7 +180,7 @@ README.md
```
### Step 3验证项目脚本
#### Step 3验证项目脚本
**目标:** 确认所有基准测试脚本可正常执行。
@ -205,7 +209,7 @@ python -c "from bench_common import setup_workspace, get_csv_path; print('脚本
```
### Step 4运行单算子 Benchmark
#### Step 4运行单算子 Benchmark
**目标:** 执行基准测试获取 Baseline 性能数据。
@ -237,7 +241,7 @@ Results saved to BatchDecodeWithPagedKVCacheWrapper_20260525_xxxxxx.csv
| `out of memory` | 减小 batch\_size  seq\_len 参数 |
| 运行时间过长 | 脚本会自动调整重复次数,耐心等待 |
### Step 5查看测试结果
#### Step 5查看测试结果
**目标:** 查看并分析 Benchmark 输出结果。
@ -272,7 +276,226 @@ if csv_files:
```
### **XPU-OJ 评测与提交**
#### Step 1访问XPUOJ平台(https://xpuoj.com/)进行登录
- 完成 baseline benchmark 后要到统一评测工具XPU-OJ完成提交进行评测。
- 等待组委会统一发放 XPU-OJ 账号
- 使用分配的用户名和初始密码登录平台
![image 20260616152804368](https://origin.picgo.net/2026/06/16/image-20260616152804368aa0d44b72c4f9572.png)
#### Step 2进入比赛页面
- 登录后进入对应赛题页面
- 选择对应的赛题
![image 20260616152953345](https://origin.picgo.net/2026/06/16/image-20260616152953345986ce39fda69da55.png)
#### Step 3选择对应的算子题目进行评测
![image 20260616153142383](https://origin.picgo.net/2026/06/16/image-202606161531423833b0f0428edf2e35e.png)
#### Step 4阅读题目要求接口约定编写符合`run_kernel`接口规范的CUDA算子
以下代码仅用于说明接口结构,不代表最优实现,也不作为评分参考:
```python
FlashInfer Ragged Prefill示例代码
#include <stdint.h>
#include <cuda_bf16.h>
#include <cuda_runtime.h>
#include <math.h>
#ifndef max
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif
template <int D_QK, int D_VO, int Br, int Bc>
__global__ void ragged_prefill_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 seq_len,
int64_t num_qo_heads,
int64_t num_kv_heads,
float scale,
int64_t causal
) {
int b = blockIdx.x;
int hq = blockIdx.y;
int hkv = hq / (num_qo_heads / num_kv_heads);
int ty = threadIdx.y; // 0~31, Block 内的 query 索引 (对应 Br)
int tx = threadIdx.x; // 0~31, Warp 内的线程 (lane)
// 使用 int64_t 防止长序列+大Batch组合下的整数溢出
int64_t q_idx = (int64_t)blockIdx.z * Br + ty;
bool is_valid = q_idx < seq_len;
int64_t cur_qo_start = qo_indptr[b];
int64_t cur_kv_start = kv_indptr[b];
float r_Q[D_QK / 32];
if (is_valid) {
// 显式使用 int64_t 计算全局偏移量,防止超过 2GB 显存边界
int64_t q_offset_base = (cur_qo_start + q_idx) * num_qo_heads * D_QK + hq * D_QK;
#pragma unroll
for (int d = 0; d < D_QK / 32; ++d) {
r_Q[d] = __bfloat162float(q[q_offset_base + tx + d * 32]);
}
}
float r_O[D_VO / 32];
#pragma unroll
for (int d = 0; d < D_VO / 32; ++d) r_O[d] = 0.0f;
float m = -1e20f;
float l = 0.0f;
extern __shared__ __nv_bfloat16 smem[];
__nv_bfloat16* shared_K = smem;
__nv_bfloat16* shared_V = smem + Bc * D_QK;
int tid = ty * 32 + tx;
int total_threads = Br * 32;
int64_t block_max_q = min((int64_t)seq_len - 1, (int64_t)(blockIdx.z * Br + Br - 1));
int total_k_ldg = (Bc * D_QK) / 8;
int total_v_ldg = (Bc * D_VO) / 8;
for (int64_t kv_start = 0; kv_start <= block_max_q; kv_start += Bc) {
// 协同加载 K
for (int i = tid; i < total_k_ldg; i += total_threads) {
int token_idx = i / (D_QK / 8);
int d_idx = i % (D_QK / 8);
int64_t global_kv_idx = kv_start + token_idx;
if (global_kv_idx < seq_len) {
// 同样强制为 int64_t
int64_t k_offset = (cur_kv_start + global_kv_idx) * num_kv_heads * D_QK + hkv * D_QK + d_idx * 8;
((float4*)shared_K)[i] = *(reinterpret_cast<const float4*>(&k[k_offset]));
} else {
((float4*)shared_K)[i] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
}
}
// 协同加载 V
for (int i = tid; i < total_v_ldg; i += total_threads) {
int token_idx = i / (D_VO / 8);
int d_idx = i % (D_VO / 8);
int64_t global_kv_idx = kv_start + token_idx;
if (global_kv_idx < seq_len) {
int64_t v_offset = (cur_kv_start + global_kv_idx) * num_kv_heads * D_VO + hkv * D_VO + d_idx * 8;
((float4*)shared_V)[i] = *(reinterpret_cast<const float4*>(&v[v_offset]));
} else {
((float4*)shared_V)[i] = make_float4(0.0f, 0.0f, 0.0f, 0.0f);
}
}
__syncthreads();
if (is_valid) {
int64_t kv_end = causal ? min(kv_start + Bc, q_idx + 1) : min(kv_start + Bc, seq_len);
for (int j = 0; j < kv_end - kv_start; ++j) {
float score = 0.0f;
#pragma unroll
for (int d = 0; d < D_QK / 32; ++d) {
score += r_Q[d] * __bfloat162float(shared_K[j * D_QK + tx + d * 32]);
}
#pragma unroll
for (int offset = 16; offset > 0; offset /= 2) {
score += __shfl_down_sync(0xffffffff, score, offset);
}
score = __shfl_sync(0xffffffff, score, 0);
score *= scale;
float m_prev = m;
m = max(m, score);
float P = __expf(score - m);
l = l * __expf(m_prev - m) + P;
#pragma unroll
for (int d = 0; d < D_VO / 32; ++d) {
r_O[d] = r_O[d] * __expf(m_prev - m) + P * __bfloat162float(shared_V[j * D_VO + tx + d * 32]);
}
}
}
__syncthreads();
}
if (is_valid) {
int64_t out_offset_base = (cur_qo_start + q_idx) * num_qo_heads * D_VO + hq * D_VO;
#pragma unroll
for (int d = 0; d < D_VO / 32; ++d) {
float out_val = r_O[d] / l;
output[out_offset_base + tx + d * 32] = __float2bfloat16(out_val);
}
}
}
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
) {
float scale = 1.0f / sqrtf((float)head_dim_qk);
const int Br = 32;
dim3 grid(batch_size, num_qo_heads, (seq_len + Br - 1) / Br);
dim3 block(32, Br);
// 根据维度配置绝对安全的共享内存策略
if (head_dim_qk == 128 && head_dim_vo == 128) {
const int Bc = 64;
int smem = Bc * 128 * 2 + Bc * 128 * 2; // 32 KB
cudaFuncSetAttribute(ragged_prefill_kernel<128, 128, Br, Bc>, cudaFuncAttributeMaxDynamicSharedMemorySize, smem);
ragged_prefill_kernel<128, 128, Br, Bc><<<grid, block, smem>>>(
q, k, v, output, qo_indptr, kv_indptr, seq_len, num_qo_heads, num_kv_heads, scale, causal);
} else if (head_dim_qk == 192 && head_dim_vo == 128) {
const int Bc = 64;
int smem = Bc * 192 * 2 + Bc * 128 * 2; // 40 KB
cudaFuncSetAttribute(ragged_prefill_kernel<192, 128, Br, Bc>, cudaFuncAttributeMaxDynamicSharedMemorySize, smem);
ragged_prefill_kernel<192, 128, Br, Bc><<<grid, block, smem>>>(
q, k, v, output, qo_indptr, kv_indptr, seq_len, num_qo_heads, num_kv_heads, scale, causal);
} else if (head_dim_qk == 256 && head_dim_vo == 256) {
// 关键降级256维度下将 Bc 从 64 降为 32。
// 将所需的共享内存从 64KB 压回到绝对安全的 32KB防止在部分环境触发 Launch Failure (OOM/Crash)。
const int Bc = 32;
int smem = Bc * 256 * 2 + Bc * 256 * 2; // 32 KB
cudaFuncSetAttribute(ragged_prefill_kernel<256, 256, Br, Bc>, cudaFuncAttributeMaxDynamicSharedMemorySize, smem);
ragged_prefill_kernel<256, 256, Br, Bc><<<grid, block, smem>>>(
q, k, v, output, qo_indptr, kv_indptr, seq_len, num_qo_heads, num_kv_heads, scale, causal);
}
}
```
#### Step 5提交代码开始评测
![image 20260616155023443](https://origin.picgo.net/2026/06/16/image-202606161550234433e54d703d581f858.png)
## 七、常见问题
@ -353,4 +576,8 @@ mv *.csv results/
| 优化项 | 改动内容 | Baseline | 优化后 | 提升比例 |
| --- | --- | --- | --- | --- |
| 例调整 block\_size | 16  32 | xx ms | xx ms | xx% |
| 完成优化后再次运行本模块的 Benchmark 脚本对比前后性能变化。 | | | | |
| 完成优化后再次运行本模块的 Benchmark 脚本对比前后性能变化。 | | | | |
### 6. 使用多语言完成算子优化加速
可以使用Triton和TileLang语言实现`run_kernel`接口完成算子优化,对比不同的语言对于性能加速的影响

View File

@ -0,0 +1,16 @@
{
"id": 193,
"displayId": 20001,
"type": "Traditional",
"isPublic": false,
"locales": [
"zh_CN"
],
"samples": [
{
"inputData": "1\n",
"outputData": ""
}
],
"problemTagIds": []
}

View File

@ -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

View File

@ -0,0 +1,23 @@
---
sectionTitle: "题目描述"
type: "Text"
---
你需要实现 FlashInfer ragged KV cache prefill 的CUDA C++前向算子。
本题输入采用 FlashInfer `BatchPrefillWithRaggedKVCacheWrapper` 的 ragged `NHD` 布局。每个 batch 中有 `seq_len` 个 query tokenKV 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)。

View File

@ -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 tensorshape `(batch_size * seq_len, num_qo_heads, head_dim_qk)`,连续 `bf16`
* `k`key tensorshape `(batch_size * seq_len, num_kv_heads, head_dim_qk)`,连续 `bf16`
* `v`value tensorshape `(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 indptrshape `(batch_size + 1)`,连续 `int32`
* `kv_indptr`KV ragged indptrshape `(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()` 或显式同步。

View File

@ -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`

View File

@ -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)
```

View File

@ -0,0 +1,16 @@
{
"id": 194,
"displayId": 20002,
"type": "Traditional",
"isPublic": false,
"locales": [
"zh_CN"
],
"samples": [
{
"inputData": "1\n",
"outputData": ""
}
],
"problemTagIds": []
}

View File

@ -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

View File

@ -0,0 +1,22 @@
---
sectionTitle: "题目描述"
type: "Text"
---
你需要实现 FlashInfer paged KV cache prefill 的 CUDA C++ 前向算子。
本题输入采用 FlashInfer `BatchPrefillWithPagedKVCacheWrapper` 的 paged `NHD` 布局。每个 batch 中有 `seq_len` 个 query tokenKV 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)。

View File

@ -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 tensorshape `(batch_size * seq_len, num_qo_heads, head_dim)`,连续 `bf16`
* `kv_data`paged KV cacheshape `(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 indptrshape `(batch_size + 1)`,连续 `int32`
* `kv_indptr`paged KV indptrshape `(batch_size + 1)`,连续 `int32`
* `kv_indices`page indexshape `(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()` 或显式同步。

View File

@ -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`

View File

@ -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)
```

View File

@ -0,0 +1,16 @@
{
"id": 195,
"displayId": 20003,
"type": "Traditional",
"isPublic": false,
"locales": [
"zh_CN"
],
"samples": [
{
"inputData": "1\n",
"outputData": ""
}
],
"problemTagIds": []
}

View File

@ -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

View File

@ -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)。

View File

@ -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 cacheshape `(batch_size * seq_len, 1, head_dim_ckv)`,连续 `bf16`
* `kpe`RoPE KV cacheshape `(batch_size * seq_len, 1, head_dim_kpe)`,连续 `bf16`
* `output`输出缓冲区shape `(batch_size, num_heads, head_dim_ckv)`,连续 `bf16`
* `q_indptr`decode query indptrshape `(batch_size + 1)`,内容为 `[0, 1, ..., batch_size]`
* `kv_indptr`KV indptrshape `(batch_size + 1)`,每段长度为 `seq_len`
* `kv_indices`page indexshape `(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()` 或显式同步。

View File

@ -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`

View File

@ -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)
```

View File

@ -0,0 +1,16 @@
{
"id": 196,
"displayId": 20004,
"type": "Traditional",
"isPublic": false,
"locales": [
"zh_CN"
],
"samples": [
{
"inputData": "1\n",
"outputData": ""
}
],
"problemTagIds": []
}

View File

@ -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

View File

@ -0,0 +1,22 @@
---
sectionTitle: "题目描述"
type: "Text"
---
你需要实现 FlashInfer paged KV cache decode 的 CUDA C++ 前向算子。
本题输入采用 FlashInfer `BatchDecodeWithPagedKVCacheWrapper` 的 paged `NHD` 布局。每个 batch 只有 1 个 query tokenKV 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)。

View File

@ -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 tensorshape `(batch_size, num_qo_heads, head_dim)`,连续 `bf16`
* `kv_data`paged KV cacheshape `(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 indptrshape `(batch_size + 1)`,连续 `int32`
* `kv_indices`page indexshape `(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()` 或显式同步。

View File

@ -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`

View File

@ -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)
```