docs: add Chinese README and operator guide

Co-authored-by: wawahejun <hejunlbbc@gmail.com>
This commit is contained in:
yutianyu 2026-05-04 19:16:21 +08:00
parent aec1073394
commit 1368fa2886
3 changed files with 284 additions and 9 deletions

View File

@ -3,9 +3,9 @@
This repository is a training-oriented GPU operator runtime. It is intentionally
small, but its workflow mirrors production operator libraries:
1. Define the operator contract in `ops/<op>/operator.yaml`.
2. Generate build and runtime registries from the manifest.
3. Implement a backend-specific descriptor lifecycle.
1. Create a directory under `ops/<op>/` with backend implementations.
2. The build system auto-discovers sources by directory convention.
3. Implement a backend-specific descriptor lifecycle (create, workspace, execute, destroy).
4. Expose a Python API with out-of-place, out-variant, and prepared execution.
5. Validate correctness against PyTorch and benchmark steady-state execution.
@ -37,23 +37,34 @@ cmake --build . -j$(nproc)
## Validate
```bash
python tools/validate_operator_manifest.py --ops-root ops --tests-root tests
python tests/run_ops.py --op copy --backend nvidia --mode all
CAMP_BUILD_DIR=build pytest tests/ -v --backend nvidia
pytest tests/ -v --backend tilelang
python tests/bench_all.py --backend nvidia --profile tests/perf_profiles/local_gpu.yaml
python tests/run_ops.py --op all --backend nvidia --mode bench
```
The TileLang backend requires the `tilelang` Python package.
The TileLang backend requires the `tilelang` Python package.
## Adding a New Operator
1. Create `ops/<name>/nvidia/<name>_cuda.h` with the C API (4 functions: create, workspace, execute, destroy).
2. Create `ops/<name>/nvidia/<name>_cuda.cu` with the CUDA implementation.
3. Create `python/operator_runtime/ops/<name>.py` using `ctypes_bindings.bind_*` functions.
4. Create `tests/cases/<name>.py` with `correctness_cases()`, `api_error_cases()`, and `benchmark_cases()`.
5. Create `tests/ops/test_<name>.py` and `tests/bench/<name>.py`.
6. Re-run `cmake ..` in the build directory (the glob will pick up the new `.cu` file).
7. Register the public API in `python/operator_runtime/__init__.py`.
No YAML, no code generation, no registration step.
## Production Mapping
| Training concept | Production equivalent |
| --- | --- |
| `operator.yaml` | reviewed operator spec / manifest |
| directory convention `ops/<op>/nvidia/*.cu` | build system auto-discovery / operator registry |
| C header `ops/<op>/nvidia/<op>_cuda.h` | reviewed operator API contract |
| descriptor lifecycle | create, workspace, execute, destroy |
| generated registry | operation table / backend registry |
| `tests/cases/<op>.py` | correctness, layout, and API contract coverage |
| `PerformanceResult` | profiler report row with latency, bytes, flops, bandwidth |
| eager TileLang kernel | puzzle-stage kernel using `T.empty(...)` return values |
| lazy TileLang `out_idx` template | TileOPs-style kernel factory and output-position contract |
| lazy TileLang `out_idx` template | TileOPs-style kernel factory and output-position contract |

56
README.zh.md Normal file
View File

@ -0,0 +1,56 @@
# Intro-ops训练营
这个仓库是一个面向训练的 GPU 算子运行时。它体积很小,但工作流尽量贴近真实的算子库开发流程:
1. 在 `ops/<op>/` 下创建后端实现目录。
2. 构建系统通过目录约定自动发现源码。
3. 实现后端专属的 descriptor 生命周期create、workspace、execute、destroy
4. 提供 Python API支持 out-of-place、out-variant 和 prepared 执行。
5. 通过 PyTorch 做正确性验证,并做稳定态性能测试。
## 算子
| 算子 | NVIDIA C++ | TileLang | MetaX |
| --- | --- | --- | --- |
| `copy` | 可运行 | 安装 TileLang 后可运行 | stub |
| `vector_add` | 可运行 | 安装 TileLang 后可运行 | stub |
| `reduce_sum` | 可运行row-wise fp32 | 安装 TileLang 后可运行 | stub |
| `softmax` | 可运行row-wise fp32 | 安装 TileLang 后可运行 | stub |
## 安装
```bash
pip install -r requirements.txt
```
## 构建
```bash
mkdir -p build
cd build
cmake .. -DCAMP_ENABLE_NVIDIA=ON -DCAMP_ENABLE_METAX=OFF
cmake --build . -j$(nproc)
```
## 验证
```bash
python tests/run_ops.py --op copy --backend nvidia --mode all
CAMP_BUILD_DIR=build pytest tests/ -v --backend nvidia
pytest tests/ -v --backend tilelang
python tests/run_ops.py --op all --backend nvidia --mode bench
```
TileLang 后端需要安装 `tilelang` Python 包。
## 生产映射
| 训练概念 | 生产等价物 |
| --- | --- |
| `ops/<op>/nvidia/*.cu` 目录约定 | 构建系统自动发现 / 算子注册 |
| `ops/<op>/nvidia/<op>_cuda.h` 头文件 | 经过评审的算子 API 契约 |
| descriptor 生命周期 | create、workspace、execute、destroy |
| `tests/cases/<op>.py` | 正确性、布局和 API 契约覆盖 |
| `PerformanceResult` | 包含延迟、字节数、FLOPs、带宽的 profiler 报表行 |
| eager TileLang kernel | 使用 `T.empty(...)` 返回值的 puzzle-stage kernel |
| lazy TileLang `out_idx` template | TileOPs 风格的 kernel factory 和输出位置契约 |

View File

@ -0,0 +1,208 @@
# 如何开发一个新算子
本文只描述当前仓库里新增算子的实际流程,不展开代码细节。
## 目标
在当前项目里,新增一个可运行算子的最小闭环包括四部分:
1. 在 `ops/<算子名>/` 下补后端实现。
2. 在 `python/operator_runtime/ops/` 下补 Python API。
3. 在 `tests/` 下补正确性测试和 benchmark 入口。
4. 重新构建并验证。
## 当前仓库的真实约束
- 现在没有 `operator.yaml`
- 现在没有代码生成步骤。
- 现在没有单独的 operator registry 文件。
- NVIDIA 的 `.cu` 文件通过目录约定自动发现。
- 新增或重命名 `.cu` 后,需要重新执行一次 `cmake ..`
也就是说,新增算子主要依赖目录结构和命名约定,而不是额外的注册配置。
## Step 1明确算子接口
开始前先确认:
1. 这个算子有几个输入、几个输出。
2. 输出 shape 是否和输入一致。
3. 是否要求输入输出 dtype 一致。
4. 是否只支持 contiguous tensor。
5. 是否需要额外参数,例如 `dim`、`scalar`。
6. 是否需要 workspace。
这一步的目的,是确定后面 C API 和 Python 绑定该按哪种模式实现。
## Step 2创建算子目录
先在 `ops/<算子名>/` 下建立对应目录。
当前建议至少补齐:
- `ops/<算子名>/nvidia/`
- `python/operator_runtime/ops/<算子名>.py`
- `tests/cases/<算子名>.py`
- `tests/ops/test_<算子名>.py`
- `tests/bench/<算子名>.py`
如果后续要支持 TileLang 或 MetaX再分别补 `tilelang/``metax/`
但对当前流程来说NVIDIA 版本是最小必需项。
## Step 3实现 NVIDIA 后端
`ops/<算子名>/nvidia/` 这一层负责 C++/CUDA 实现。
通常需要这几部分:
1. kernel 文件
2. C API 头文件
3. C API 实现文件
这里最关键的是保持现有命名约定一致,因为 Python 侧会按固定符号名去找函数。
一个 NVIDIA 算子需要完整提供四个生命周期接口:
1. create
2. workspace
3. execute
4. destroy
整体流程可以理解为:
1. `create`:检查输入是否合法,并保存运行所需信息。
2. `workspace`:返回执行需要的临时内存大小。
3. `execute`:按 dtype 和参数启动 kernel。
4. `destroy`:释放 descriptor。
## Step 4补 Python 绑定
Python 入口放在 `python/operator_runtime/ops/<算子名>.py`
当前项目对外通常暴露三类接口:
1. `prepare_<算子名>`
2. `<算子名>_`
3. `<算子名>`
职责分工一般是:
1. `prepare_<算子名>`:完成参数检查、绑定底层函数、创建 descriptor 和 workspace。
2. `<算子名>_`:接收调用方提供的输出 tensor执行一次。
3. `<算子名>`:自动分配输出 tensor再调用 `<算子名>_`
如果你的算子签名和现有 unary、binary、reduce-like 模式一致,就沿用现有 helper。
如果签名比较特殊,例如带额外标量参数,就单独写一层绑定。
## Step 5导出到公共 API
新增 Python 文件后,还要把公开接口补到:
1. `python/operator_runtime/ops/__init__.py`
2. `python/operator_runtime/__init__.py`
至少需要把这三个名字暴露出去:
1. `<算子名>`
2. `<算子名>_`
3. `prepare_<算子名>`
这样测试和用户代码才能直接导入。
## Step 6补测试用例数据
`tests/cases/<算子名>.py` 只负责组织测试数据。
当前仓库一般分成三类:
1. `correctness_cases()`
2. `api_error_cases()`
3. `benchmark_cases()`
建议在这里把不同 shape、dtype、容差、错误场景、性能场景分开整理避免把 case 直接写死在测试函数里。
## Step 7补正确性测试
`tests/ops/test_<算子名>.py` 主要负责三件事:
1. 正确性对比
2. API contract 检查
3. prepared 执行复用检查
正确性测试通常是拿 PyTorch 结果做对照。
API contract 测试主要覆盖 shape 不匹配、dtype 不匹配、非 contiguous 等场景。
如果底层走 descriptor lifecycle建议补一个 prepared 多次执行的测试,确认 descriptor 可以复用。
## Step 8补 benchmark
`tests/bench/<算子名>.py` 负责性能入口。
当前流程里,一般会:
1. 从 `benchmark_cases()` 取输入规模。
2. 构造 CUDA tensor。
3. 测量自定义算子耗时。
4. 测量对应 PyTorch 实现耗时。
5. 汇总成性能结果。
这一步的目标不是做复杂分析,而是保证新算子已经接入仓库现有 benchmark 流程。
## Step 9重新配置和编译
因为 `ops/CMakeLists.txt` 是通过 glob 自动发现 `ops/*/nvidia/*.cu`,所以新增 `.cu` 之后要重新配置。
顺序是:
1. 进入 `build/` 目录。
2. 重新执行 `cmake ..`
3. 再执行编译。
如果你只改了 Python不新增 `.cu`,通常不需要重新配置。
但只要新加了 NVIDIA 源文件,就必须重新跑一次 CMake。
## Step 10验证
建议按下面顺序验证:
1. 先跑单算子的 pytest。
2. 再跑更大范围的算子测试。
3. 最后跑 benchmark 或统一入口。
优先确认:
1. 输出结果和 PyTorch 一致。
2. 错误输入能稳定报错。
3. descriptor/workspace 流程正常。
4. 没有破坏已有算子。
## 推荐开发顺序
如果你要新增的是一个普通算子,建议按这个顺序推进:
1. 先参考最接近的现有算子选模板。
2. 先打通 NVIDIA 后端最小可运行版本。
3. 再补 Python API。
4. 再补 cases、pytest、benchmark。
5. 最后补其他后端或做性能优化。
## 现有模板怎么选
- `copy`:适合最简单的单输入单输出流程。
- `vector_add`:适合标准 elementwise 双输入算子。
- `reduce_sum`:适合带 reduce 维度的算子。
- `softmax`:适合带更明确 shape 约束和归一化逻辑的算子。
如果新算子本质上是普通 elementwise优先参考 `vector_add`
## 最终检查清单
提交前至少确认以下内容都已完成:
1. `ops/<算子名>/nvidia/` 已补齐实现。
2. `python/operator_runtime/ops/<算子名>.py` 已补齐。
3. 两个 `__init__.py` 已导出新接口。
4. `tests/cases/<算子名>.py` 已补数据。
5. `tests/ops/test_<算子名>.py` 已补测试。
6. `tests/bench/<算子名>.py` 已补 benchmark。
7. 新增 `.cu` 后已经重新执行过 `cmake ..`
8. 至少完成一次单算子验证。