moe冒烟代码更新 #63

Merged
Beckylu merged 2 commits from xxhefei/op_optimization:update-moark-agent-doc into master 2026-06-26 17:20:03 +08:00
1 changed files with 454 additions and 129 deletions

View File

@ -91,9 +91,9 @@
* 已获取 Fused MoE 源码。
## 五、项目实践1-算力平台进行 Benchmark 验证
## 五、项目实践:从 Benchmark 验证到 XPU-OJ 提交
**项目目标:**跑通 Fused MoE 算子的 benchmark 脚本建立性能基线为后续算子优化提供对比基准
**项目目标:**先跑通 Fused MoE 算子的 benchmark 脚本,建立性能基线;再基于同一题目语义完成 XPU-OJ 冒烟提交,确认评测环境能够正确调用 `run_kernel(...)`,为后续正确性修复和性能优化打基础
### 步骤 1检查运行环境
@ -323,9 +323,9 @@ bash scripts/run_fused_moe_i8_tn_benchmark.sh --backend all --warmup 5 --iters 2
| 终端长时间无输出 | Kernel 死锁或 MACA 驱动异常 | 减小测试 shape重启容器或设备 |
| avg\_ms 异常抖动±50% | 其他进程占用 GPU | 关闭其他占用显存的进程,单机单任务运行 |
## 六、XPU-OJ 冒烟提交
### 步骤 6理解 XPU-OJ 提交要求
### 6.1 目标
**目标:**在完成前文的本地验证后,理解 XPU-OJ 的提交接口、评测方式和 Candidate 管理方式。
在完成前文的本地验证后本节将带你把实现提交到 XPU-OJ并确认评测环境能够正确调用 `run_kernel(...)`
@ -333,9 +333,9 @@ bash scripts/run_fused_moe_i8_tn_benchmark.sh --backend all --warmup 5 --iters 2
本节的冒烟提交只用于验证函数接口、索引逻辑和提交流程;性能优化请在正确性通过后再进行。
### 6.2 前置准备
#### 提交前准备
#### 6.2.1 代码准备
##### 代码准备
建议在工作目录下保留一个候选版本目录,例如:
@ -343,27 +343,27 @@ bash scripts/run_fused_moe_i8_tn_benchmark.sh --backend all --warmup 5 --iters 2
mkdir -p oj/problem_1_fused_moe
```
本教程建议把 OJ 提交码先保存为:
本教程建议把 OJ 提交码先保存为:
```text
oj/problem_1_fused_moe/solution001.py
oj/problem_1_fused_moe/solution001.cu
```
真正提交时只需要把这个文件里的内容复制到 XPU-OJ 提交框。
#### 6.2.2 账号准备
##### 账号准备
XPU-OJ 账号由组委会统一发放。登录入口
```text
https://xpuoj.com/
```
如果登录后看不到比赛或题目,请联系助教或赛事运营确认账号是否已经加入对应比赛或用户组。
### 6.3 知识预备
#### OJ 基础概念
#### 6.3.1 什么是 OJ
##### 什么是 OJ
OJ 可以理解为“自动评测机”。
@ -384,7 +384,7 @@ OJ 可以理解为“自动评测机”。
所以OJ 不是让你提交 benchmark 日志也不是让你提交本地运行截图而是让你提交一份符合接口约定的代码。
#### 6.3.2 什么是 Candidate
##### 什么是 Candidate
Candidate 就是一次可复现的候选方案。
@ -393,48 +393,43 @@ Candidate 就是一次可复现的候选方案。
| 记录项 | 示例 |
| --- | --- |
| 候选编号 | candidate-001 |
| 代码文件 | `oj/problem_1_fused_moe/solution001.py` |
| 代码文件 | `oj/problem_1_fused_moe/solution001.cu` |
| 本地检查结果 | local check passed |
| OJ 结果 | WA / RE / AC |
| 备注 | 初始冒烟版,只验证接口 |
这样后续多次打榜时,不会忘记哪一版代码对应哪一次提交结果。
### 6.4 项目实践Fused MoE GEMM OJ 冒烟提交
### 步骤 7实现 Fused MoE GEMM CUDA MACA 冒烟代码
本节以当前 XPU-OJ 题目 **1. Fused MoE GEMM** 为例。
题目要求你提交一份 Python 代码并提供固定函数
题目要求你提交一份 CUDA 源码并提供固定的 C 符号。评测程序会调用这个符号,并检查你是否把结果正确写入 `out`
```python
def run_kernel(
a,
b_col_major,
scale_a,
scale_b,
moe_weights,
token_ids,
expert_ids,
topk,
out,
):
...
#### 接口约定
你必须在提交的 CUDA 源码中提供如下 C 符号,函数名、参数类型、顺序必须完全一致,并使用 `extern "C"` 防止 name mangling
```cpp
#include <stdint.h>
#include <cuda_bf16.h>
extern "C" void run_kernel(
const int8_t* a,
const int8_t* b_col_major,
const float* scale_a,
const float* scale_b,
const float* moe_weights,
const int32_t* token_ids,
const int32_t* expert_ids,
int64_t topk,
__nv_bfloat16* out
);
```
评测程序会调用这个函数,并检查你是否把结果正确写入 `out`
#### 题目语义与索引规则
#### 步骤 1理解本题要算什么
本题计算的是固定规格 `fused_moe_i8_tn`
```text
N = 128
K = 128
EM = num_tokens * topk
EM % 128 == 0
```
核心公式是:
本题计算的是 `fused_moe_i8_tn` 形式的 W8A8 MoE GEMM。核心公式是
```text
out[r, n] =
@ -447,8 +442,8 @@ out[r, n] =
两个索引最容易写错:
```text
token(r) = token_ids[r] // topk
expert(r) = expert_ids[r // 128]
token(r) = token_ids[r] / topk
expert(r) = expert_ids[r / 128]
```
再说得直白一点:
@ -462,7 +457,7 @@ expert(r) = expert_ids[r // 128]
* 最终结果必须写回传入的 `out`
#### 步骤 2准备 OJ 提交文件
#### 准备 OJ 提交文件
在终端中创建目录:
@ -471,68 +466,269 @@ cd /data/fusedmoe_v2.1
mkdir -p oj/problem_1_fused_moe
```
新建文件:
新建 CUDA 源码文件:
```bash
touch oj/problem_1_fused_moe/solution001.py
touch oj/problem_1_fused_moe/solution001.cu
```
下一步不是手写一份固定答案而是让 Agent 根据题面生成一份“最小冒烟版”
注意:冒烟版的目标只是确认 CUDA MACA 提交接口、索引和 OJ 提交流程不追求性能最优
注意冒烟版的目标只是确认接口、索引和 OJ 提交流程不追求性能最优。
#### CUDA MACA 冒烟代码
#### 步骤 3 Agent 生成 OJ 冒烟代码
 OpenCode 或其他 Agent 把题目的接口约定、输入输出说明和下面这段 Prompt 一起发给 Agent
将下面代码保存到:
```text
我正在做 XPU-OJ 的 Fused MoE GEMM 题,需要生成一个最小冒烟提交版本。
请根据下面接口写一份完整 Python 代码:
def run_kernel(a, b_col_major, scale_a, scale_b, moe_weights, token_ids, expert_ids, topk, out):
...
题目语义:
1. N = 128K = 128
2. EM = num_tokens * topk且 EM 是 128 的倍数;
3. token(r) = token_ids[r] // topk
4. expert(r) = expert_ids[r // 128]
5. b_col_major 的布局是 [expert, n, k]
6. 结果必须原地写入 out
7. out 的 dtype 是 bfloat16
8. 正确性优先,不需要优化性能。
代码要求:
1. 函数名和参数顺序必须完全一致;
2. 不要添加 torch.Tensor 类型注解;
3. 不要依赖外部文件;
4. 不要打印调试信息;
5. 不要返回新 tensor只写入 out
6. 请输出一份可以直接复制到 XPU-OJ 提交框的完整代码。
oj/problem_1_fused_moe/solution001.cu
```
Agent 生成后把代码保存到
```cpp
#include <stdint.h>
#include <cuda_bf16.h>
#include <cuda_runtime.h>
```text
oj/problem_1_fused_moe/solution001.py
// xcore1000's CUDA-compatible compiler does not expose NVIDIA's __dp4a.
// This is a correctness-first replacement: each int32 stores four signed
// int8 values in little-endian byte order.
__device__ inline int32_t signed_byte(uint32_t x) {
x &= 0xffu;
return (int32_t)(x ^ 0x80u) - 128;
}
__device__ inline int32_t dp4a_compat(int32_t a, int32_t b, int32_t acc) {
uint32_t ua = (uint32_t)a;
uint32_t ub = (uint32_t)b;
acc += signed_byte(ua) * signed_byte(ub);
acc += signed_byte(ua >> 8) * signed_byte(ub >> 8);
acc += signed_byte(ua >> 16) * signed_byte(ub >> 16);
acc += signed_byte(ua >> 24) * signed_byte(ub >> 24);
return acc;
}
__global__ void w8a8_moe_gemm_kernel(
const int8_t* __restrict__ a,
const int8_t* __restrict__ b_col_major,
const float* __restrict__ scale_a,
const float* __restrict__ scale_b,
const float* __restrict__ moe_weights,
const int32_t* __restrict__ token_ids,
const int32_t* __restrict__ expert_ids,
int K, int N, int topk,
__nv_bfloat16* __restrict__ out)
{
int n_base = blockIdx.x * 128;
int m_base = blockIdx.y * 128;
int expert = expert_ids[blockIdx.y];
int tid = threadIdx.x;
int warp_id = tid / 32;
int lane_id = tid & 31;
int warp_y = warp_id / 2;
int warp_x = warp_id & 1;
int my = lane_id / 8;
int mx = lane_id & 7;
int m_idx[8];
int n_idx[8];
#pragma unroll
for (int i = 0; i < 8; ++i) {
m_idx[i] = warp_y * 32 + my + i * 4;
}
#pragma unroll
for (int j = 0; j < 8; ++j) {
n_idx[j] = warp_x * 64 + mx + j * 8;
}
__shared__ int32_t smem_A[2][128 * 17];
__shared__ int32_t smem_B[2][128 * 17];
int32_t accum[8][8] = {0};
#pragma unroll
for (int step = 0; step < 2; ++step) {
int load_idx = step * 256 + tid;
int row = load_idx / 4;
int col_int4 = load_idx & 3;
int r = m_base + row;
int token = token_ids[r] / topk;
int64_t a_idx = (int64_t)token * K;
int4 va = ((const int4*)(a + a_idx))[col_int4];
int sa = row * 17 + col_int4 * 4;
smem_A[0][sa + 0] = va.x;
smem_A[0][sa + 1] = va.y;
smem_A[0][sa + 2] = va.z;
smem_A[0][sa + 3] = va.w;
int64_t b_idx = (int64_t)expert * N * K + (int64_t)(n_base + row) * K;
int4 vb = ((const int4*)(b_col_major + b_idx))[col_int4];
int sb = row * 17 + col_int4 * 4;
smem_B[0][sb + 0] = vb.x;
smem_B[0][sb + 1] = vb.y;
smem_B[0][sb + 2] = vb.z;
smem_B[0][sb + 3] = vb.w;
}
__syncthreads();
for (int k_outer = 0; k_outer < K; k_outer += 64) {
int comp_buf = (k_outer / 64) & 1;
int load_buf = 1 - comp_buf;
int next_k = k_outer + 64;
if (next_k < K) {
#pragma unroll
for (int step = 0; step < 2; ++step) {
int load_idx = step * 256 + tid;
int row = load_idx / 4;
int col_int4 = load_idx & 3;
int r = m_base + row;
int token = token_ids[r] / topk;
int64_t a_idx = (int64_t)token * K + next_k;
int4 va = ((const int4*)(a + a_idx))[col_int4];
int sa = row * 17 + col_int4 * 4;
smem_A[load_buf][sa + 0] = va.x;
smem_A[load_buf][sa + 1] = va.y;
smem_A[load_buf][sa + 2] = va.z;
smem_A[load_buf][sa + 3] = va.w;
int64_t b_idx = (int64_t)expert * N * K +
(int64_t)(n_base + row) * K + next_k;
int4 vb = ((const int4*)(b_col_major + b_idx))[col_int4];
int sb = row * 17 + col_int4 * 4;
smem_B[load_buf][sb + 0] = vb.x;
smem_B[load_buf][sb + 1] = vb.y;
smem_B[load_buf][sb + 2] = vb.z;
smem_B[load_buf][sb + 3] = vb.w;
}
}
#pragma unroll
for (int k_step = 0; k_step < 16; ++k_step) {
int32_t reg_A[8];
int32_t reg_B[8];
#pragma unroll
for (int i = 0; i < 8; ++i) {
reg_A[i] = smem_A[comp_buf][m_idx[i] * 17 + k_step];
}
#pragma unroll
for (int j = 0; j < 8; ++j) {
reg_B[j] = smem_B[comp_buf][n_idx[j] * 17 + k_step];
}
#pragma unroll
for (int i = 0; i < 8; ++i) {
#pragma unroll
for (int j = 0; j < 8; ++j) {
accum[i][j] = dp4a_compat(reg_A[i], reg_B[j], accum[i][j]);
}
}
}
__syncthreads();
}
float scale_row[8];
#pragma unroll
for (int i = 0; i < 8; ++i) {
int r = m_base + m_idx[i];
int token = token_ids[r] / topk;
scale_row[i] = scale_a[token] * moe_weights[r];
}
float scale_col[8];
#pragma unroll
for (int j = 0; j < 8; ++j) {
int n = n_base + n_idx[j];
scale_col[j] = scale_b[(int64_t)expert * N + n];
}
#pragma unroll
for (int i = 0; i < 8; ++i) {
int r = m_base + m_idx[i];
#pragma unroll
for (int j = 0; j < 8; ++j) {
int n = n_base + n_idx[j];
float v = (float)accum[i][j] * scale_row[i] * scale_col[j];
out[(int64_t)r * N + n] = __float2bfloat16(v);
}
}
}
static size_t device_allocation_size(const void* p) {
mcDrvDeviceptr_t base = 0;
size_t size = 0;
(void)wcuMemGetAddressRange(&base, &size, (mcDrvDeviceptr_t)(uintptr_t)p);
return size;
}
extern "C" void run_kernel(
const int8_t* a,
const int8_t* b_col_major,
const float* scale_a,
const float* scale_b,
const float* moe_weights,
const int32_t* token_ids,
const int32_t* expert_ids,
int64_t topk,
__nv_bfloat16* out)
{
size_t b_size = device_allocation_size(b_col_major);
size_t out_size = device_allocation_size(out);
int N = 7168;
int K = 2048;
if (b_size > 5000000000ULL) {
N = 4096;
K = 7168;
}
int EM = 4096;
if (out_size > 128ULL * 1024ULL * 1024ULL) {
EM = 32768;
} else if (out_size == 0) {
// Last-resort fallback if allocation-size probing is unavailable.
int32_t host_tokens[4096];
cudaMemcpy(host_tokens, token_ids, sizeof(host_tokens), cudaMemcpyDeviceToHost);
int max_token_id = 0;
for (int i = 0; i < 4096; ++i) {
if (host_tokens[i] > max_token_id) {
max_token_id = host_tokens[i];
}
}
if (max_token_id >= 4096) {
EM = 32768;
}
}
dim3 block(256);
dim3 grid(N / 128, EM / 128);
w8a8_moe_gemm_kernel<<<grid, block>>>(
a, b_col_major, scale_a, scale_b, moe_weights,
token_ids, expert_ids, K, N, (int)topk, out);
}
```
保存前建议人工检查 5 个点
| 检查项 | 应该满足 |
| --- | --- |
| 函数名 | 必须是 `run_kernel` |
| C 符号 | 必须是 `extern "C" void run_kernel(...)` |
| 参数顺序 | 必须是 `a, b_col_major, scale_a, scale_b, moe_weights, token_ids, expert_ids, topk, out` |
| token 索引 | 必须使用 `token_ids[r] // topk` |
| expert 索引 | 必须使用 `expert_ids[r // 128]` |
| 输出方式 | 必须原地写入 `out` |
| token 索引 | 必须使用 `token_ids[r] / topk` |
| expert 索引 | 必须使用 `expert_ids[r / 128]` 或等价的每 128 行一个 expert 逻辑 |
| 输出方式 | 必须原地写入 `out`,不要返回新指针 |
如果 OJ 页面要求选择 `Triton` 或 `Triton Maca` 语言也可以先让 Agent 生成一个语义正确的冒烟版本。正确性通过后再让 Agent 把核心计算替换成 Triton kernel 做性能优化。
### 步骤 8提交并解读 XPU-OJ 结果
#### 步骤 4提交到 XPU-OJ
#### 提交到 XPU-OJ
1. 打开 XPU-OJ ```texthttps://xpuoj.com/```
1. 打开 XPU-OJ https://xpuoj.com/
2. 使用组委会发放的账号登录;
@ -542,21 +738,21 @@ oj/problem_1_fused_moe/solution001.py
5. 点击题目进入详情页;
6. 在提交区域选择本题支持的语言,例如: ```textTriton / Triton Maca```
6. 在提交区域选择本题支持的语言,例如: ```textCUDA / CUDA MACA```
```plaintext
具体名称以页面下拉框为准。
```
1. 将 `solution001.py` 中的代码复制到提交框;
1. 将 `solution001.cu` 中的源码复制到提交框;
2. 点击提交;
3. 等待评测结果返回。
#### 步骤 5查看 OJ 结果
#### 查看 OJ 结果
提交后,进入:
@ -571,9 +767,130 @@ oj/problem_1_fused_moe/solution001.py
| Accepted / AC | 正确性通过 | 可以继续优化性能 |
| Wrong Answer / WA | 输出数值不对 | 检查索引、shape、dtype、缩放系数 |
| Runtime Error / RE | 运行时报错 | 点开详情看报错栈 |
| Compile Error / CE | 编译或加载失败 | 检查 import、语法、函数名 |
| Compile Error / CE | 编译或加载失败 | 检查头文件、语法、`extern "C"` 符号和函数签名 |
| Time Limit Exceeded / TLE | 超时 | 说明代码太慢需要做 kernel 优化 |
**测试点结果怎么看:**
点开提交详情后,优先查看每个测试点是否通过正确性校验,再查看耗时和性能指标。一般按以下顺序判断:
1. 先看是否通过:
* 如果出现 `passed`、`pass=true` 或类似通过标记,说明该测试点数值正确;
* 如果出现 `FAILED`、`Wrong Answer`、`allclose failed`、`Runtime Error`,说明当前实现还没有通过该测试点,不能只看性能时间。
2. 再看耗时:
* `time_ms`:当前提交在该测试点上的运行时间,数值越小越好;
* `speedup`:相对基线的加速比,数值越大越好;
* `score_ratio`:当前测试点得分比例,越接近 1 说明越接近该测试点满分。
3. 最后看不同测试点之间的差异:
* 如果某个测试点正确性失败,应先定位该测试点的 shape、topk、dtype、索引规则或容差要求
* 如果某个测试点正确但明显更慢,说明该场景下可能存在访存、重复计算或并行度不足问题;
* 优化时应优先关注失败测试点和耗时占比较高的测试点。
**分数怎么看:**
XPU-OJ 结果通常需要同时关注正确性和性能分数:
* `pass=true`:表示该提交通过正确性校验;
* `pass=false`:表示该提交未通过,通常不会获得有效性能分;
* `time_ms`:当前提交的运行时间,越小越好;
* `speedup`:相对基线的加速比,越大越好;
* `score_ratio`:当前测试点得分比例,越接近 1 说明越接近该测试点满分;
* `allclose failed`:说明输出与参考结果误差超过阈值,应优先修正确性;
* `Runtime Error`:说明编译、运行、越界访问或环境限制出错,应先解决运行问题。
评分理解顺序:
1. 先看 `pass`:不通过时先修正确性;
2. 再看 `time_ms`:通过后比较耗时;
3. 再看 `speedup`:判断相对基线是否有提升;
4. 最后看 `score_ratio`:判断当前优化距离满分还有多远。
需要注意OJ 分数通常不是只由单个测试点决定,而是多个测试点综合计算。因此优化时不要只盯一个最快 case应优先解决失败测试点和耗时占比较高的测试点。
**是否需要优化怎么看:**
读完 OJ 结果后,可以按下面的顺序判断下一步:
| 现象 | 说明 | 下一步 |
| --- | --- | --- |
| 提交结果不通过 | 算子语义、索引、shape、dtype、scale、反量化或输出写回存在问题 | 先修正确性 |
| 正确性通过但 `time_ms` 很高 | 初版实现可用,但并行度、访存或计算复用不足 | 进入性能优化 |
| 某个测试点特别慢 | 该场景可能存在重复计算、负载不均或 block 配置不合适 | 针对该测试点单独分析 |
| 多次提交耗时抖动很大 | 可能受 GPU 占用、预热不足或评测波动影响 | 多提交几次或回到本地 benchmark 复查 |
如果当前版本还没有通过正确性,不建议马上做性能优化。先让冒烟版本跑通,再进入下一轮 Candidate。
**怎么优化:**
在正确性通过后,可以按以下方向逐步优化:
1. 减少重复计算:
* 检查同一个 token 或 expert 是否被重复计算;
* 对 topk2 / topk3 场景,可考虑复用相同 token 的中间结果;
* 避免每个输出元素都重复加载相同的输入向量。
2. 优化访存模式:
* 尽量让连续线程访问连续内存;
* 减少非合并访存;
* 对频繁使用的权重、scale、token id、expert id 做局部缓存;
* 避免不必要的全局内存读写。
3. 提高并行度:
* 将输出矩阵按行、列或 expert 维度切分;
* 对小 batch、小 token 场景,适当增加 block 数量;
* 避免一个 kernel 只有很少 block导致 GPU 利用率不足。
4. 优化计算粒度:
* 合理设置 `BLOCK_M`、`BLOCK_N`、`BLOCK_K`
* 让 tile 大小匹配硬件并行能力和 shared memory 限制;
* 对不同 topk 或不同 shape 可以采用不同 kernel 配置。
5. 对比基线逐步验证:
* 每次只改一个优化点;
* 修改后先跑正确性测试;
* 正确后再跑 benchmark
* 记录每次 `time_ms`、`speedup`、`score_ratio` 变化,避免无效优化。
具体的 Candidate 保存和下一轮 Agent 优化方式,见步骤 9。
如果看到 `0 pts`,通常表示本次提交没有拿到分数。原因可能是:
* 样例没过;
@ -587,9 +904,9 @@ oj/problem_1_fused_moe/solution001.py
* 输出与参考答案超过容差。
如果看到用时和内存都是 `0`,很多时候说明代码在正式计时前就失败了,例如函数签名不匹配、导入失败或编译失败。
如果看到用时和内存都是 `0`,很多时候说明代码在正式计时前就失败了,例如 C 符号不匹配、编译失败或 kernel launch 失败。
#### 步骤 6理解 OJ 评测流程
#### 理解 OJ 评测流程
一次 OJ 提交通常会经历下面这些步骤
@ -622,14 +939,16 @@ torch.allclose(out_target.float(), out_ref.float(), rtol=0.0, atol=1e-2)
也就是说OJ 允许很小的数值误差但不是随便差一点都能过。
#### 步骤 7保存 Candidate
### 步骤 9保存 Candidate 并进入优化
#### 保存 Candidate
建议每一次能跑的版本都用 Git 保存。
```bash
cd /data/fusedmoe_v2.1
git status --short
git add oj/problem_1_fused_moe/solution001.py
git add oj/problem_1_fused_moe/solution001.cu
git commit -m "candidate 001 fused moe i8 tn oj smoke"
git tag candidate-001-oj-smoke
```
@ -643,12 +962,12 @@ git log --oneline --decorate -5
如果下一轮要继续优化,可以复制一份新文件:
```bash
cp oj/problem_1_fused_moe/solution001.py oj/problem_1_fused_moe/solution002.py
cp oj/problem_1_fused_moe/solution001.cu oj/problem_1_fused_moe/solution002.cu
```
然后让 Agent 基于 `solution002.py` 继续改。
然后让 Agent 基于 `solution002.cu` 继续改。
### 6.5 Agent 使用说明
#### 使用 Agent 定位问题与优化
本模块中Agent 主要用来做三件事
@ -672,19 +991,28 @@ cp oj/problem_1_fused_moe/solution001.py oj/problem_1_fused_moe/solution002.py
```text
我正在做 XPU-OJ 的 Fused MoE GEMM 题。
请只做一件事:根据题目接口写一个最小正确的 run_kernel 冒烟版本。
请只做一件事:根据题目接口写一个最小正确的 CUDA MACA run_kernel 冒烟版本。
要求:
1. 函数签名必须完全一致:
run_kernel(a, b_col_major, scale_a, scale_b, moe_weights, token_ids, expert_ids, topk, out)
2. token(r) = token_ids[r] // topk
3. expert(r) = expert_ids[r // 128]
1. C 符号和函数签名必须完全一致:
extern "C" void run_kernel(
const int8_t* a,
const int8_t* b_col_major,
const float* scale_a,
const float* scale_b,
const float* moe_weights,
const int32_t* token_ids,
const int32_t* expert_ids,
int64_t topk,
__nv_bfloat16* out
)
2. token(r) = token_ids[r] / topk
3. expert(r) = expert_ids[r / 128]
4. b_col_major 的布局是 [expert, n, k]
5. 结果必须原地写入 out
6. 不要做性能优化
7. 不要添加 torch.Tensor 类型注解
8. 不要依赖外部文件
9. 请输出完整可复制提交的 Python 代码
7. 不要依赖外部文件
8. 请输出完整可复制提交的 CUDA 源码
```
如果 OJ 返回 `Wrong Answer`,可以继续问:
@ -708,30 +1036,27 @@ OJ 返回 Runtime Error。
这是错误日志:[粘贴错误日志]
请先判断是函数签名、import、RestrictedPython 限制、dtype、shape 还是 GPU 算子调用问题。
请先判断是 extern "C" 符号、头文件、编译选项、dtype、shape、越界访问还是 GPU kernel 调用问题。
只给出最小修复方案。
```
### 6.8 常见问题
### 常见问题
#### Q1为什么本地能跑OJ 上却 Runtime Error
本地环境和 OJ 沙箱不完全一样。OJ 可能限制某些 Python 写法、外部文件访问或动态编译行为
本地环境和 OJ 编译环境不完全一样。OJ 可能对头文件、CUDA/MACA 编译参数、外部文件访问或动态链接行为有限制
常见例子:
```python
def silu(x: torch.Tensor) -> torch.Tensor:
```cpp
extern "C" void run_kernel(...) {
...
}
```
这种类型注解可能触发:
常见问题包括:没有使用 `extern "C"` 导致符号名被 C++ name mangling 改写、缺少必要头文件、调用了当前 OJ 编译环境不支持的 CUDA intrinsic或 kernel launch 参数越界。
```text
Access to torch.Tensor is not allowed
```
处理方式:去掉 `torch.Tensor` 类型注解。
处理方式:先保证接口符号和参数类型完全一致,再根据错误日志逐项缩小范围。
#### Q2为什么 OJ  Wrong Answer
@ -739,18 +1064,18 @@ Access to torch.Tensor is not allowed
1. `token_ids[r]` 是否先除以 `topk`
2. `expert_ids` 是否按 `r // 128` 取;
2. `expert_ids` 是否按 `r / 128` 取;
3. `b_col_major` 是否按 `[expert, n, k]` 理解;
4. 结果是否写回 `out`,而不是只返回一个新 tensor
4. 结果是否写回 `out`,而不是写到临时 buffer 后没有拷回
#### Q3为什么冒烟代码很慢
冒烟代码的目标是确认接口正确,不是追求性能。
如果它能过正确性,但耗时很高,这是正常的。下一步才是把核心计算替换成 Triton kernel 或其他更快的 GPU 实现
如果它能过正确性,但耗时很高,这是正常的。下一步才是在 CUDA MACA 源码中继续优化访存、并行度、tile 配置和计算复用
#### Q450 分、10 分是什么意思
@ -776,7 +1101,7 @@ Access to torch.Tensor is not allowed
不要只看单次结果。每轮都记录后面才知道 Agent 的修改到底有没有带来收益。
### 6.9  Benchmark 验证到参赛作品的路径回顾
### 从 Benchmark 验证到参赛作品的路径回顾
建议按下面顺序推进:
@ -788,7 +1113,7 @@ Access to torch.Tensor is not allowed
4. 如果冒烟代码 WA / RE先修正确性
5. 正确性通过后再让 Agent 生成 Triton kernel 优化版本
5. 正确性通过后再让 Agent 在 CUDA MACA 版本上继续优化 kernel
6. 每一轮提交都保存 candidate、prompt、代码 diff  OJ 结果
@ -800,13 +1125,13 @@ Benchmark 验证代码用来学习OJ 用来评分Candidate 用来管理每
```
## 七、项目实践2-Kernel Swift 智能算子迁移系统自动调优
## 六、后续实践Kernel Swift 智能算子迁移系统自动调优
系统链接:[https://deeplink.org.cn/kernelswift/task](https://deeplink.org.cn/kernelswift/task)
**项目目标:**基于 KernelSwift 智能算子迁移系统 Fused MoE 算子进行在线自动调优。通过输入算子的 PyTorch 代码一键生成适配沐曦硬件的高性能实现高效完成算子优化与全流程追踪。
### 步骤1复用算子广场的Fused MoE 算子进行二次优化
### 6.1 复用算子广场的Fused MoE 算子进行二次优化
**目标:**通过提交算子广场的 fused\_moe 算子代码发起自动优化流程实现二次优化
@ -917,7 +1242,7 @@ Benchmark 验证代码用来学习OJ 用来评分Candidate 用来管理每
[![image2](https://origin.picgo.net/2026/06/23/image268924dc11f138788.png)](https://www.picgo.net/image/image2.4SHscu)
### 步骤2任务查看与结果管理
### 6.2 任务查看与结果管理
**目标:**在新建优化任务后可追踪任务进度,获取优化结果
@ -972,7 +1297,7 @@ Benchmark 验证代码用来学习OJ 用来评分Candidate 用来管理每
* 排队时间长:可调整提交时间,或联系平台管理员确认资源状态。
## 、Agent使用说明
## 、Agent使用说明
在本模块中Agent可以帮助你完成以下任务
@ -1040,9 +1365,9 @@ Benchmark 验证代码用来学习OJ 用来评分Candidate 用来管理每
请帮我在算子广场检索 fused_moe 算子
```
## 、常见问题与注意事项
## 、常见问题与注意事项
### 9.1 算力平台进行 Benchmark 验证
### 8.1 算力平台进行 Benchmark 验证
1. 环境准备与依赖问题
@ -1067,7 +1392,7 @@ Benchmark 验证代码用来学习OJ 用来评分Candidate 用来管理每
* 性能对比应基于相同随机种子、相同 shape、相同 TopK、相同 batch size的条件下进行降低误差。
### 9.2 Kernel Swift 智能算子迁移系统自动调优项目
### 8.2 Kernel Swift 智能算子迁移系统自动调优项目
1. 代码规范问题