Add a paired end-to-end benchmark and record the measurements A single run of each backend cannot answer whether the candidate beats the baseline on this box, and on this box it actively misleads. The torch baseline came out bimodal across six rounds – 76.5, 102.2, 104.8, 103.9, 75.3, 103.2 tok/s – so comparing one torch run against one candidate run picked up a low torch sample and reported 0.85x. e2e_paired.py alternates the backends across rounds and flips the order each round, then reports the paired ratio distribution. That puts the ninetoothed end-to-end speedup at 1.21x (paired median, range 0.95-1.50) with identical token IDs, not the 0.85x a single pair of runs suggested. benchmarks/results/ holds the raw numbers: op_level_ninetoothed_maca.json: 23 operator cases read from the model config, all above 1, worst 1.04x, median 1.13x, best 3.56x op_level_torch_selfcheck_maca.json: torch against itself, which measures the noise floor at 0.96-1.19x and is the reason the operator table reports a median rather than a minimum e2e_paired_ninetoothed_maca.json: the paired end-to-end rounds Operator-level and end-to-end numbers do not compose by addition. The two operators measure 2742 us per token against a 11.4 ms step, about 24%, but most of that cost is CPU-side Python dispatch, which overlaps GPU execution, so the share cannot be subtracted from the wall clock to predict a ceiling.
Add a paired end-to-end benchmark and record the measurements
A single run of each backend cannot answer whether the candidate beats the baseline on this box, and on this box it actively misleads. The torch baseline came out bimodal across six rounds – 76.5, 102.2, 104.8, 103.9, 75.3, 103.2 tok/s – so comparing one torch run against one candidate run picked up a low torch sample and reported 0.85x.
e2e_paired.py alternates the backends across rounds and flips the order each round, then reports the paired ratio distribution. That puts the ninetoothed end-to-end speedup at 1.21x (paired median, range 0.95-1.50) with identical token IDs, not the 0.85x a single pair of runs suggested.
benchmarks/results/ holds the raw numbers:
Operator-level and end-to-end numbers do not compose by addition. The two operators measure 2742 us per token against a 11.4 ms step, about 24%, but most of that cost is CPU-side Python dispatch, which overlaps GPU execution, so the share cannot be subtracted from the wall clock to predict a ceiling.
模力方舟使用算力券租用沐曦C500的镜像选择:TileLang / 0.1.9 / Python 3.12 / maca 3.3.0.4
TileLang / 0.1.9 / Python 3.12 / maca 3.3.0.4
开始阶段先完成四个 Assignment,再进入下面的 Llama 算子练习项目。
mx-smi
assignment/task1
assignment/task2
assignment/task3
assignment/task4
cd assignment/task2 python -m pytest -q test_add.py test_ninetoothed_add.py python benchmark_add.py python benchmark_ninetoothed_add.py cd ../task3 python -m pytest -q test_softmax.py test_ninetoothed_gemm.py python benchmark_softmax.py python benchmark_ninetoothed_gemm.py
完成四个 Assignment 后,继续 Llama 阶段:算子接入 → 正确性验证 → 性能优化 → 端到端评测。
这是一个小型 Llama 推理项目,用来练习 PyTorch、NineToothed、TileLang 和 MXMACA 算子。
安装依赖:
python -m pip install -r requirements.txt
使用 NineToothed 时单独安装可选依赖:
python -m pip install ninetoothed
通过 ModelScope 下载模型:
python -m pip install modelscope modelscope download --model LLM-Research/Llama-3.2-1B \ --local_dir models/Llama-3.2-1B
--backend 决定使用哪份算子代码:
--backend
torch
tilelang
maca_cpp
.maca
ninetoothed
--target 可选 auto、cuda、maca。MACA 版 PyTorch 仍使用 --device cuda。 如果后端、target、扩展或某个算子不可用,或者 kernel 运行出错,框架会打印警告并自动使用 PyTorch。 推理最终输出的 registered_operators 会列出当前后端已接入的算子;值为 torch_fallback 表示该算子暂时仍调用 PyTorch reference。
--target
auto
cuda
maca
--device cuda
registered_operators
torch_fallback
NineToothed 测试:
python infer.py --model models/Llama-3.2-1B --prompts "Hello" \ --max-new-tokens 1 --backend ninetoothed --target maca --device cuda
本仓库只保留 Llama 接入所需的薄包装和一个 RMSNorm kernel。安装和入门见 NineToothed 文档、 Add/Matmul 基础; 更多 RMSNorm、RoPE、SDPA、MM 和 SwiGLU 示例见 ninetoothed-examples。
TileLang测试:
先加载预装的 TileLang 开发环境。脚本会自动发现 /app/tilelang-metax:
/app/tilelang-metax
source ./setup_env.sh # 自定义源码位置:TILELANG_ROOT=/path/to/tilelang-metax source ./setup_env.sh
python infer.py --model models/Llama-3.2-1B --prompts "Hello" \ --max-new-tokens 1 --backend tilelang --target maca --device cuda
MXMACA测试:
python operators/maca_cpp/setup.py build_ext --inplace python infer.py --model models/Llama-3.2-1B --prompts "Hello" \ --max-new-tokens 1 --backend maca_cpp --target maca --device cuda
框架已经预置 rms_norm 和 rope 的调用、注册和测试入口。当前 TileLang rms_norm 仅用于演示算子接入流程,并非性能最优实现;学员可以在此基础上继续优化 线程布局、访存和归约方式。
rms_norm
rope
优化范围不限于现有示例或文档推荐的某一个算子。学员可以根据自己的能力和目标,自行 选择更多适合的模型算子进行实现和优化。RoPE 的接口是:
rope(input, sin_table, cos_table) -> output
学员只需要实现对应的 NineToothed、TileLang 或 MXMACA kernel,还没有实现的槽位会暂时使用 PyTorch reference 并打印警告,所以示例可以直接 跑通;这种状态不能用于性能结论。完整说明见 operators/INTEGRATION.md。
operators/INTEGRATION.md
本文记录本次提交对两个算子的实现与优化,覆盖 NineToothed 和 TileLang 两个后端。
以下章节记录本次提交对两个算子的实现与优化。
优化前 registered_operators 中 rope 在两个后端均为 torch_fallback,不计入后端性能。
签名与 operators/torch_ops.py 一致:
operators/torch_ops.py
input 形状为 [batch, sequence, heads, head_dim],两个 table 为 [sequence, head_dim // 2]。
input
[batch, sequence, heads, head_dim]
[sequence, head_dim // 2]
两个后端都把 head 切成两半,在 float32 寄存器里完成旋转后一次写回, 因此 reference 里的 torch.cat 不再需要额外的 kernel 启动和数据往返。 累积全程使用 float32,只在写回时舍入一次。
torch.cat
位置索引放在 Python wrapper 完成:把 [sequence, head_dim // 2] 展开为 每行对应一个 (batch, sequence, head)。若放进 kernel 内部,需要 % sequence 形式的索引表达式,NineToothed 的 autotune 求解器无法为其 求出上下界。
(batch, sequence, head)
% sequence
实现时踩到的框架行为,记录下来供后续算子参考:
application
arrangement
tl.split
tl.join
split
原实现分两遍扫描:先把每个 chunk 拷到 shared memory 累加平方和, 再在归一化阶段从 global memory 重新读取同一批元素。对一个纯带宽受限的 算子,这等于把读流量翻倍。
优化后整行只入 shared memory 一次并复用,kernel 每行只做一次全局读和一次 全局写。同时把累加与缩放保持在 float32,写回时才舍入一次——原实现是在乘 weight 之前就舍入,白白损失 bfloat16 精度。
staging 数量限制为每线程 8 个元素。 把 2048 列全部放进 fragment 需要 5 KiB/thread,而 C500 驱动将私有内存上限设为 4 KiB/thread,行数较大时会以 mcErrorMemoryValueTooLarge 启动失败。这是实现过程中实际触发过的错误。
mcErrorMemoryValueTooLarge
python -m pytest -q tests/
三个测试文件分别对比 operators.torch_ops 的 reference,覆盖真实 Llama-3.2-1B 前向过程中出现的形状,并断言算子以原生方式注册:
operators.torch_ops
tests/test_rope_ninetoothed.py
tests/test_rope_tilelang.py
tests/test_rms_norm_tilelang.py
在 MetaX C500 上结果为 56 passed、3 skipped,三个后端输出的 token IDs 逐个一致。
注意:这些测试每个都会重新设置 backend。backends.configure_backend 设置的是进程级全局状态,而算子 wrapper 会读取它——不这样做的话,先运行的 测试文件会导致后运行的文件报 “operator called without an active backend”。
backends.configure_backend
bench_ops.py 用批量 CUDA event 计时测单个算子:
bench_ops.py
python bench_ops.py --backend tilelang --repeats 20
单次 launch 加一次 host 同步的开销(约 0.1 ms)与这些算子本身的耗时同量级, 直接测一次 launch 测到的是测量框架而不是 kernel,所以脚本每次采样连续发射 多次再取平均。
参考量级(bf16,rows=4096、columns=2048):
infer.py 的端到端吞吐在这台共享机器上不稳定:同一份 torch 基线连跑三次 分别为 101.90、83.57、74.60 tok/s,波动约 ±16%,来源是同机其他负载。 仓库 README 也提示该配置「结果波动较大,不作为正式性能结论」。
infer.py
若需要引用具体数字,请在独占环境重跑。
参考数据见 benchmarks/results/,由仓库自带的 compare_results.py 生成, 其中 output_tokens_match 为 true。
benchmarks/results/
compare_results.py
output_tokens_match
true
attention
repeat_interleave
下面提供轻量性能对比,统一生成 16 个 token,使用 1 次 warmup、3 次测量,并保持模型、 prompt、seed、精度和设备完全一致。该配置用于快速反馈,结果波动较大,不作为正式性能结论。 下面命令假设在 MACA 机器上运行,模型目录是 models/Llama-3.2-1B。
models/Llama-3.2-1B
先测 PyTorch 基线:
python infer.py \ --model models/Llama-3.2-1B \ --prompts "Hello" \ --max-new-tokens 16 \ --backend torch --target maca --device cuda \ --num-warmup-iterations 1 \ --num-profiling-iterations 3 \ --seed 0 \ --output-json benchmarks/results/torch_maca.json
再测 TileLang:
python infer.py \ --model models/Llama-3.2-1B \ --prompts "Hello" \ --max-new-tokens 16 \ --backend tilelang --target maca --device cuda \ --num-warmup-iterations 1 \ --num-profiling-iterations 3 \ --seed 0 \ --output-json benchmarks/results/tilelang_maca.json
NineToothed 使用同一组参数,运行以下命令并保存结果:
python -m pip install ninetoothed python infer.py \ --model models/Llama-3.2-1B \ --prompts "Hello" \ --max-new-tokens 16 \ --backend ninetoothed --target maca --device cuda \ --num-warmup-iterations 1 \ --num-profiling-iterations 3 \ --seed 0 \ --output-json benchmarks/results/ninetoothed_maca.json
MXMACA 原生算子需要先构建扩展:
python operators/maca_cpp/setup.py build_ext --inplace python infer.py \ --model models/Llama-3.2-1B \ --prompts "Hello" \ --max-new-tokens 16 \ --backend maca_cpp --target maca --device cuda \ --num-warmup-iterations 1 \ --num-profiling-iterations 3 \ --seed 0 \ --output-json benchmarks/results/maca_cpp_maca.json
比较 TileLang 和 PyTorch:
python benchmarks/compare_results.py \ benchmarks/results/torch_maca.json \ benchmarks/results/tilelang_maca.json \ --output-json benchmarks/results/torch_vs_tilelang_maca.json
比较 NineToothed 和 PyTorch:
python benchmarks/compare_results.py \ benchmarks/results/torch_maca.json \ benchmarks/results/ninetoothed_maca.json \ --output-json benchmarks/results/torch_vs_ninetoothed_maca.json
比较 MXMACA 原生实现和 PyTorch:
python benchmarks/compare_results.py \ benchmarks/results/torch_maca.json \ benchmarks/results/maca_cpp_maca.json \ --output-json benchmarks/results/torch_vs_maca_cpp_maca.json
比较器会校验测试条件和生成的 token IDs,并输出吞吐 speedup、性能变化比例及实际替换 的算子。 TileLang 调用包含 JIT 编译,不要直接 拿第一次运行的时间评价性能。如果 RoPE 还在使用临时 PyTorch reference,也不能把该 结果当作完整后端性能;应先实现对应 kernel。
. ├── assignment/ # 四个入门实战 │ ├── README.md # Assignment 总览与通用要求 │ ├── task1/ # 沐曦 GPU 与 MXMACA 环境 │ │ └── README.md │ ├── task2/ # TileLang Add 与 NineToothed Vector Add │ │ ├── README.md │ │ ├── solution.py # TileLang kernel 作业入口 │ │ ├── ninetoothed_add.py # NineToothed kernel 作业入口 │ │ ├── test_add.py # TileLang 正确性测试 │ │ ├── test_ninetoothed_add.py # NineToothed 正确性测试 │ │ ├── benchmark_add.py # TileLang 综合性能测试 │ │ └── benchmark_ninetoothed_add.py # NineToothed 性能测试 │ ├── task3/ # TileLang Softmax 与 NineToothed GEMM │ │ ├── README.md │ │ ├── solution.py # TileLang kernel 作业入口 │ │ ├── ninetoothed_gemm.py # NineToothed kernel 作业入口 │ │ ├── test_softmax.py # TileLang 正确性测试 │ │ ├── test_ninetoothed_gemm.py # NineToothed 正确性测试 │ │ ├── benchmark_softmax.py # TileLang 综合性能测试 │ │ └── benchmark_ninetoothed_gemm.py # NineToothed 性能测试 │ └── task4/ # AI Agent 辅助开发 │ ├── README.md │ ├── prompts.md # Prompt 记录模板 │ └── reflection.md # 实践总结模板 │ ├── infer.py # Llama 推理入口 ├── llama.py # 模型与算子调用点 ├── backends.py # 后端与 target 配置 ├── setup_env.sh # TileLang/MXMACA 环境变量配置 ├── requirements.txt # Python 依赖 │ ├── operators/ # 算子实现 │ ├── registry.py # 算子注册与分发 │ ├── torch_ops.py # PyTorch 参考实现 │ ├── ninetoothed_ops.py # NineToothed 薄包装 │ ├── ninetoothed_kernels/ # NineToothed 示例 kernel │ ├── tilelang_ops.py # TileLang 实现槽位 │ ├── maca_cpp/ # MXMACA 原生扩展 │ └── INTEGRATION.md # 算子接入教程 │ ├── benchmarks/ # 性能评测 │ ├── compare_results.py # 结果比较工具 │ └── results/ # 性能结果 JSON │ └── tests/ # 单元测试与后端集成测试
这是九源软件栈与沐曦GPU联合推出的算子开发实战营的课程仓库。课程中设置了基于国产异构计算平台的全链路实践,以"多DSL对比学习,建立算子开发本质认知"为核心。
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802047560号
多范式算子开发实战营
模力方舟使用算力券租用沐曦C500的镜像选择:
TileLang / 0.1.9 / Python 3.12 / maca 3.3.0.4开始阶段先完成四个 Assignment,再进入下面的 Llama 算子练习项目。
mx-smiassignment/task1assignment/task2assignment/task3assignment/task4完成四个 Assignment 后,继续 Llama 阶段:算子接入 → 正确性验证 → 性能优化 → 端到端评测。
Llama 算子练习
这是一个小型 Llama 推理项目,用来练习 PyTorch、NineToothed、TileLang 和 MXMACA 算子。
快速开始
安装依赖:
使用 NineToothed 时单独安装可选依赖:
通过 ModelScope 下载模型:
选择后端
--backend决定使用哪份算子代码:torchtilelangmaca_cpp.macakernelninetoothed--target可选auto、cuda、maca。MACA 版 PyTorch 仍使用--device cuda。 如果后端、target、扩展或某个算子不可用,或者 kernel 运行出错,框架会打印警告并自动使用 PyTorch。 推理最终输出的registered_operators会列出当前后端已接入的算子;值为torch_fallback表示该算子暂时仍调用 PyTorch reference。NineToothed 测试:
本仓库只保留 Llama 接入所需的薄包装和一个 RMSNorm kernel。安装和入门见 NineToothed 文档、 Add/Matmul 基础; 更多 RMSNorm、RoPE、SDPA、MM 和 SwiGLU 示例见 ninetoothed-examples。
TileLang测试:
先加载预装的 TileLang 开发环境。脚本会自动发现
/app/tilelang-metax:MXMACA测试:
学员要改什么
框架已经预置
rms_norm和rope的调用、注册和测试入口。当前 TileLangrms_norm仅用于演示算子接入流程,并非性能最优实现;学员可以在此基础上继续优化 线程布局、访存和归约方式。优化范围不限于现有示例或文档推荐的某一个算子。学员可以根据自己的能力和目标,自行 选择更多适合的模型算子进行实现和优化。RoPE 的接口是:
学员只需要实现对应的 NineToothed、TileLang 或 MXMACA kernel,还没有实现的槽位会暂时使用 PyTorch reference 并打印警告,所以示例可以直接 跑通;这种状态不能用于性能结论。完整说明见
operators/INTEGRATION.md。RoPE 与 RMSNorm 实现说明
本文记录本次提交对两个算子的实现与优化,覆盖 NineToothed 和 TileLang 两个后端。
本次提交:RoPE 与 RMSNorm
以下章节记录本次提交对两个算子的实现与优化。
变更概览
roperms_norm优化前
registered_operators中rope在两个后端均为torch_fallback,不计入后端性能。RoPE
签名与
operators/torch_ops.py一致:input形状为[batch, sequence, heads, head_dim],两个 table 为[sequence, head_dim // 2]。实现要点
两个后端都把 head 切成两半,在 float32 寄存器里完成旋转后一次写回, 因此 reference 里的
torch.cat不再需要额外的 kernel 启动和数据往返。 累积全程使用 float32,只在写回时舍入一次。位置索引放在 Python wrapper 完成:把
[sequence, head_dim // 2]展开为 每行对应一个(batch, sequence, head)。若放进 kernel 内部,需要% sequence形式的索引表达式,NineToothed 的 autotune 求解器无法为其 求出上下界。NineToothed 的三个约束
实现时踩到的框架行为,记录下来供后续算子参考:
application的签名里不能出现 constexpr symbol。 NineToothed 从该签名 生成 kernel 函数体,constexpr 会漏成未定义名。arrangement中引用。 在application中引用 不会被重写。tl.split/tl.join的 tile 形状与表格对不齐。 这里的 ninetoothed/triton 组合下,split后的形状无法与 sin/cos 广播对齐。 最终把 head 的两半作为独立张量传入,split上移到 wrapper, 代价只是两个 strided view。RMSNorm 优化(TileLang)
原实现分两遍扫描:先把每个 chunk 拷到 shared memory 累加平方和, 再在归一化阶段从 global memory 重新读取同一批元素。对一个纯带宽受限的 算子,这等于把读流量翻倍。
优化后整行只入 shared memory 一次并复用,kernel 每行只做一次全局读和一次 全局写。同时把累加与缩放保持在 float32,写回时才舍入一次——原实现是在乘 weight 之前就舍入,白白损失 bfloat16 精度。
staging 数量限制为每线程 8 个元素。 把 2048 列全部放进 fragment 需要 5 KiB/thread,而 C500 驱动将私有内存上限设为 4 KiB/thread,行数较大时会以
mcErrorMemoryValueTooLarge启动失败。这是实现过程中实际触发过的错误。正确性验证
三个测试文件分别对比
operators.torch_ops的 reference,覆盖真实 Llama-3.2-1B 前向过程中出现的形状,并断言算子以原生方式注册:tests/test_rope_ninetoothed.pytests/test_rope_tilelang.pytests/test_rms_norm_tilelang.py在 MetaX C500 上结果为 56 passed、3 skipped,三个后端输出的 token IDs 逐个一致。
注意:这些测试每个都会重新设置 backend。
backends.configure_backend设置的是进程级全局状态,而算子 wrapper 会读取它——不这样做的话,先运行的 测试文件会导致后运行的文件报 “operator called without an active backend”。性能测量
bench_ops.py用批量 CUDA event 计时测单个算子:单次 launch 加一次 host 同步的开销(约 0.1 ms)与这些算子本身的耗时同量级, 直接测一次 launch 测到的是测量框架而不是 kernel,所以脚本每次采样连续发射 多次再取平均。
参考量级(bf16,rows=4096、columns=2048):
关于端到端数字
infer.py的端到端吞吐在这台共享机器上不稳定:同一份 torch 基线连跑三次 分别为 101.90、83.57、74.60 tok/s,波动约 ±16%,来源是同机其他负载。 仓库 README 也提示该配置「结果波动较大,不作为正式性能结论」。若需要引用具体数字,请在独占环境重跑。
参考数据见
benchmarks/results/,由仓库自带的compare_results.py生成, 其中output_tokens_match为true。未处理
.maca)后端未实现rope,仍走 PyTorch reference。attention中的repeat_interleave和 SDPA 未做融合,仍是 PyTorch 路径。测试和性能
下面提供轻量性能对比,统一生成 16 个 token,使用 1 次 warmup、3 次测量,并保持模型、 prompt、seed、精度和设备完全一致。该配置用于快速反馈,结果波动较大,不作为正式性能结论。 下面命令假设在 MACA 机器上运行,模型目录是
models/Llama-3.2-1B。先测 PyTorch 基线:
再测 TileLang:
NineToothed 使用同一组参数,运行以下命令并保存结果:
MXMACA 原生算子需要先构建扩展:
比较 TileLang 和 PyTorch:
比较 NineToothed 和 PyTorch:
比较 MXMACA 原生实现和 PyTorch:
比较器会校验测试条件和生成的 token IDs,并输出吞吐 speedup、性能变化比例及实际替换 的算子。 TileLang 调用包含 JIT 编译,不要直接 拿第一次运行的时间评价性能。如果 RoPE 还在使用临时 PyTorch reference,也不能把该 结果当作完整后端性能;应先实现对应 kernel。
目录