From c9fa898794de441a49cf1600a0e0daf21eec64a0 Mon Sep 17 00:00:00 2001 From: SigureMo Date: Mon, 1 Dec 2025 21:55:32 +0800 Subject: [PATCH 1/9] [Compat] Add docs for cross ecosystem custom ops --- .../cross_ecosystem_custom_op_cn.md | 46 +++++++++++++ .../images/pytorch-op-compatible.drawio.png | Bin 0 -> 178487 bytes .../cross_ecosystem_custom_op/index_cn.rst | 14 ++++ .../user_guide_cn.md | 64 ++++++++++++++++++ docs/guides/custom_op/index_cn.rst | 6 +- 5 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 docs/guides/custom_op/cross_ecosystem_custom_op/cross_ecosystem_custom_op_cn.md create mode 100644 docs/guides/custom_op/cross_ecosystem_custom_op/images/pytorch-op-compatible.drawio.png create mode 100644 docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst create mode 100644 docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/cross_ecosystem_custom_op_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/cross_ecosystem_custom_op_cn.md new file mode 100644 index 00000000000..ff65c0bad30 --- /dev/null +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/cross_ecosystem_custom_op_cn.md @@ -0,0 +1,46 @@ +# 跨生态自定义算子接入 + +## 概述 + +随着大模型的兴起,在深度学习框架之上构建自定义算子(Custom Operator)已成为提升模型性能和功能的关键手段。而目前 PyTorch 作为深度学习领域的主流框架之一,拥有大量的自定义算子实现。为了帮助用户更好地将现有的 PyTorch 自定义算子迁移至 PaddlePaddle 框架,我们提供了自定义算子兼容机制,旨在降低迁移成本,提升开发效率。 + +## 方案介绍 + +为了方便 PyTorch 自定义算子快速接入 PaddlePaddle 框架,我们提供了如下图所示的兼容机制: + +![PyTorch 自定义算子兼容机制示意图](./images/pytorch-op-compatible.drawio.png) + +正如图上所示,我们自底向上提供了如下几层支持: + +- **C++ API 兼容层**:该层实现了常用 PyTorch C++ API 的兼容接口,用户仍然可以通过调用 PyTorch 风格的 `at::*`、`torch::*`、`c10::*` 等命名空间下的函数和类来实现自定义算子逻辑,从而最大限度地复用现有代码,使迁移工作量降至最低。 +- **算子注册兼容层**:对于使用 pybind11 进行算子注册的 PyTorch 自定义算子,PaddlePaddle 无需额外修改注册代码;而对于使用 `TORCH_LIBRARY` 宏进行注册并通过 `torch.ops` 调用的算子,我们提供了同名的注册接口,用户无需修改注册代码即可完成迁移。 +- **Python 接口兼容层**:对于 Python 端自定义算子封装部分,会不可避免地调用一些 PyTorch 内的 Python 组网 API。为此,我们正在致力于提升 Python 端 API 与 PyTorch 的兼容性,力求让用户在迁移过程中无需修改 Python 端代码。 +- **Python API 代理层**:在 Python 端,即便 API 能够完全兼容,用户仍然需要将 `import torch` 替换为 `import paddle`。为此,我们提供了一个轻量级的代理层,用户只需在迁移后的代码开头添加一行 `import paddle.compat.enable_torch_proxy`,后续的 `torch` 下的模块将被重定向至 `paddle` 下的模块,从而实现无缝迁移。 + +通过以上几层兼容机制,用户可以在最大程度上复用现有的 PyTorch 自定义算子代码,从而大幅降低迁移成本。 + +此外,对于 TVM FFI 生态的自定义算子,由于我们已经对 TVM FFI 中所需的 DLPack 协议提供了最佳支持,因此用户可以直接将 TVM FFI 生态的自定义算子迁移至 PaddlePaddle 框架,无需额外修改。当然,如果相关算子库在 Python 端调用了 PyTorch 的组网 API,则仍然需要借助上述的 Python API 代理层来完成迁移。 + +## 迁移步骤 + +下面我们以一个简单的 PyTorch 自定义算子为例,介绍如何将其迁移至 PaddlePaddle 框架。 + +## 已迁移算子库 + +### FlashInfer + +### FlashMLA + +### DeepGEMM + +### TileLang + +### Triton + +### TorchCodec + +### DeepEP + +coming soon... + +## 参考资料 diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/images/pytorch-op-compatible.drawio.png b/docs/guides/custom_op/cross_ecosystem_custom_op/images/pytorch-op-compatible.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..892510718f5f63719b92f94b1c2d60180765f228 GIT binary patch literal 178487 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%0A9X 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 Iok#5d01)!&1poj5 literal 0 HcmV?d00001 diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst b/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst new file mode 100644 index 00000000000..d023071511e --- /dev/null +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst @@ -0,0 +1,14 @@ +############# +跨生态自定义算子接入 +############# + +本章节介绍如何将其他深度学习框架算子生态的自定义算子迁移至飞桨框架,主要分为以下内容: + +- `使用指南 <./user_guide.html>`_ 介绍跨生态自定义算子的使用方法,以及已经成功接入的自定义算子库列表。 +- `原理和迁移 <./design.html>`_ 介绍跨生态自定义算子的设计原理,以及如何将其他深度学习框架的自定义算子迁移至飞桨框架。 + +.. toctree:: + :hidden: + + user_guide_cn.md + design_cn.md diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md new file mode 100644 index 00000000000..8293377113b --- /dev/null +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md @@ -0,0 +1,64 @@ +# 使用指南 + +## 概述 + +随着大模型的兴起,在深度学习框架之上构建自定义算子(Custom Operator)已成为提升模型性能和功能的关键手段。而目前 PyTorch 作为深度学习领域的主流框架之一,拥有大量的自定义算子实现。为了帮助用户更好地将现有的 PyTorch 等生态的自定义算子迁移至 PaddlePaddle 框架,我们提供了自定义算子兼容机制,旨在降低迁移成本,提升开发效率。 + +## 使用步骤 + +### 一般安装方式 + +对于使用基于兼容性方案的跨生态自定义算子库,一般情况下只需要 clone 后通过 pip 安装对应的算子库即可使用。下面以 FlashMLA 为例说明安装方式: + +```bash +git clone https://github.com/PFCCLab/FlashMLA.git +cd FlashMLA +pip install . +``` + +对于部分已经发布到 PyPI 的自定义算子库,也可以直接通过 pip 安装。下面以 TorchCodec 为例: + +```bash +pip install paddlecodec +``` + +个别算子库可能会有特殊的安装方式,请参考对应算子库的说明文档进行安装。 + +### 使用方式 + +安装完成后,即可在代码中直接导入并使用对应的算子库。为了实现跨生态兼容,用户需要在导入算子库之前,先启用 PaddlePaddle 的 PyTorch 代理层,以确保算子库中 `torch` 模块的调用能够正确映射到 `paddle` 模块。下面以 FlashMLA 为例说明使用方式: + +```python +import paddle + +paddle.compat.enable_torch_proxy({"flash_mla"}) + +import flash_mla +# 之后即可使用 flash_mla 下的算子 +``` + +## 已支持的算子库 + +PaddlePaddle 官方协同社区已经对社区中主流的跨生态自定义算子库进行了适配和测试,用户可以直接使用这些算子库而无需进行额外的修改。 + +我们将这些算子库统一放在组织 [PFCCLab](https://github.com/PFCCLab) 下,并列在下方。如果下方列表中没有你需要的算子库,可以移步至[原理和迁移](./design_cn.md),了解自定义算子兼容机制的实现原理,以及如何将你需要的算子库进行迁移。 + +### FlashInfer + +#### 安装方式 + +#### 使用方式 + +### FlashMLA + +### DeepGEMM + +### DeepEP + +### TorchCodec + +## 已支持的 Kernel DSL 库 + +### TileLang + +### Triton diff --git a/docs/guides/custom_op/index_cn.rst b/docs/guides/custom_op/index_cn.rst index ca6480b3c9f..1e979da7ad8 100644 --- a/docs/guides/custom_op/index_cn.rst +++ b/docs/guides/custom_op/index_cn.rst @@ -8,11 +8,12 @@ 2. C++扩展:可将 C++ 类绑定至 Python,支持调用无 Tensor 参数的 C++ 函数。不涉及框架的调度开销 3. Python 算子:使用 Python 编写实现前向(forward)和反向(backward)方法,在模型组网中使用的自定义 API -- `自定义 C++算子 <./new_cpp_op_cn.html>`_ +此外还会介绍如何将其他深度学习框架算子生态的自定义算子迁移至飞桨框架。 +- `自定义 C++算子 <./new_cpp_op_cn.html>`_ - `自定义 C++ 扩展 <./cpp_extension_cn.html>`_ - - `自定义 Python 算子 <./new_python_op_cn.html>`_ +- `跨生态自定义算子接入 <./cross_ecosystem_custom_op/index_cn.html>`_ .. toctree:: @@ -21,3 +22,4 @@ new_cpp_op_cn.md cpp_extension_cn.md new_python_op_cn.md + cross_ecosystem_custom_op/index_cn.rst From f810ee50f629b394b1f91081c0fee1747457315b Mon Sep 17 00:00:00 2001 From: SigureMo Date: Tue, 2 Dec 2025 22:04:45 +0800 Subject: [PATCH 2/9] tmp commit --- ...om_op_cn.md => design_and_migration_cn.md} | 30 +--- ...ecosystem-custom-op-compatible.drawio.png} | Bin .../cross_ecosystem_custom_op/index_cn.rst | 4 +- .../user_guide_cn.md | 157 +++++++++++++++--- 4 files changed, 140 insertions(+), 51 deletions(-) rename docs/guides/custom_op/cross_ecosystem_custom_op/{cross_ecosystem_custom_op_cn.md => design_and_migration_cn.md} (74%) rename docs/guides/custom_op/cross_ecosystem_custom_op/images/{pytorch-op-compatible.drawio.png => cross-ecosystem-custom-op-compatible.drawio.png} (100%) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/cross_ecosystem_custom_op_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md similarity index 74% rename from docs/guides/custom_op/cross_ecosystem_custom_op/cross_ecosystem_custom_op_cn.md rename to docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md index ff65c0bad30..3bd81bf8f33 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/cross_ecosystem_custom_op_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md @@ -1,14 +1,10 @@ -# 跨生态自定义算子接入 +## 原理和迁移方式 -## 概述 - -随着大模型的兴起,在深度学习框架之上构建自定义算子(Custom Operator)已成为提升模型性能和功能的关键手段。而目前 PyTorch 作为深度学习领域的主流框架之一,拥有大量的自定义算子实现。为了帮助用户更好地将现有的 PyTorch 自定义算子迁移至 PaddlePaddle 框架,我们提供了自定义算子兼容机制,旨在降低迁移成本,提升开发效率。 - -## 方案介绍 +### 实现原理 为了方便 PyTorch 自定义算子快速接入 PaddlePaddle 框架,我们提供了如下图所示的兼容机制: -![PyTorch 自定义算子兼容机制示意图](./images/pytorch-op-compatible.drawio.png) +![跨生态自定义算子兼容机制示意图](./images/cross-ecosystem-custom-op-compatible.drawio.png) 正如图上所示,我们自底向上提供了如下几层支持: @@ -24,23 +20,3 @@ ## 迁移步骤 下面我们以一个简单的 PyTorch 自定义算子为例,介绍如何将其迁移至 PaddlePaddle 框架。 - -## 已迁移算子库 - -### FlashInfer - -### FlashMLA - -### DeepGEMM - -### TileLang - -### Triton - -### TorchCodec - -### DeepEP - -coming soon... - -## 参考资料 diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/images/pytorch-op-compatible.drawio.png b/docs/guides/custom_op/cross_ecosystem_custom_op/images/cross-ecosystem-custom-op-compatible.drawio.png similarity index 100% rename from docs/guides/custom_op/cross_ecosystem_custom_op/images/pytorch-op-compatible.drawio.png rename to docs/guides/custom_op/cross_ecosystem_custom_op/images/cross-ecosystem-custom-op-compatible.drawio.png diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst b/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst index d023071511e..390c1f7ecc1 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst @@ -5,10 +5,10 @@ 本章节介绍如何将其他深度学习框架算子生态的自定义算子迁移至飞桨框架,主要分为以下内容: - `使用指南 <./user_guide.html>`_ 介绍跨生态自定义算子的使用方法,以及已经成功接入的自定义算子库列表。 -- `原理和迁移 <./design.html>`_ 介绍跨生态自定义算子的设计原理,以及如何将其他深度学习框架的自定义算子迁移至飞桨框架。 +- `原理和迁移方式 <./design_and_migration_cn.html>`_ 介绍跨生态自定义算子的设计原理,以及如何将其他深度学习框架的自定义算子迁移至飞桨框架。 .. toctree:: :hidden: user_guide_cn.md - design_cn.md + design_and_migration_cn.md diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md index 8293377113b..bc45b3f84b1 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md @@ -6,59 +6,172 @@ ## 使用步骤 -### 一般安装方式 +### 安装方式 -对于使用基于兼容性方案的跨生态自定义算子库,一般情况下只需要 clone 后通过 pip 安装对应的算子库即可使用。下面以 FlashMLA 为例说明安装方式: +对于使用基于兼容性方案的跨生态自定义算子库,一般情况下只需要 clone 后通过 pip 安装对应的算子库即可使用。下面以 `FlashInfer` 为例说明安装方式: ```bash -git clone https://github.com/PFCCLab/FlashMLA.git -cd FlashMLA -pip install . +pip install paddlepaddle_gpu # Install PaddlePaddle with GPU support, refer to https://www.paddlepaddle.org.cn/install/quick for more details +git clone https://github.com/PFCCLab/flashinfer.git +cd flashinfer +git submodule update --init +pip install apache-tvm-ffi>=0.1.2 # Use TVM FFI 0.1.2 or above +pip install filelock jinja2 # Install tools for jit compilation +# Install FlashInfer +pip install --no-build-isolation . -v ``` -对于部分已经发布到 PyPI 的自定义算子库,也可以直接通过 pip 安装。下面以 TorchCodec 为例: +对于部分已经发布到 PyPI 的自定义算子库,也可以直接通过 pip 安装。下面以 `TorchCodec` 为例: ```bash pip install paddlecodec ``` -个别算子库可能会有特殊的安装方式,请参考对应算子库的说明文档进行安装。 +个别算子库可能会有特殊的安装方式,请参考对应算子库 repo 中的 `README.md` 进行安装。 ### 使用方式 安装完成后,即可在代码中直接导入并使用对应的算子库。为了实现跨生态兼容,用户需要在导入算子库之前,先启用 PaddlePaddle 的 PyTorch 代理层,以确保算子库中 `torch` 模块的调用能够正确映射到 `paddle` 模块。下面以 FlashMLA 为例说明使用方式: ```python +# 注意,在导入跨生态自定义算子库之前,需先启用 PaddlePaddle 的 PyTorch 代理层 +# 即添加下面的两行 import paddle -paddle.compat.enable_torch_proxy({"flash_mla"}) +# scope 为限定代理层生效的模块名称空间,避免影响其他模块的使用 +paddle.compat.enable_torch_proxy(scope={"flashinfer"}) -import flash_mla -# 之后即可使用 flash_mla 下的算子 +# 之后即可导入并使用 flashinfer 库 +import flashinfer +# 之后即可使用 flashinfer 下的算子,和 PyTorch 生态下的使用方式一致 + +# 下面以 flashinfer 中的 RMSNorm 算子为例 +import numpy as np + +def rms_norm(x, w, eps=1e-6): + orig_dtype = x.dtype + x = x.float() + variance = x.pow(2).mean(dim=-1, keepdim=True) + x = x * paddle.rsqrt(variance + eps) + x = x * w.float() + x = x.to(orig_dtype) + return x + +batch_size = 99 +hidden_size = 1024 +dtype = paddle.float16 + +x = paddle.randn(batch_size, hidden_size).cuda().to(dtype) +w = paddle.randn(hidden_size).cuda().to(dtype) + +y_ref = rms_norm(x, w) + +y = flashinfer.norm.rmsnorm(x, w, enable_pdl=False) + +# flashinfer 算子输出结果与参考实现保持一致 +np.testing.assert_allclose(y_ref, y, rtol=1e-3, atol=1e-3) ``` ## 已支持的算子库 PaddlePaddle 官方协同社区已经对社区中主流的跨生态自定义算子库进行了适配和测试,用户可以直接使用这些算子库而无需进行额外的修改。 -我们将这些算子库统一放在组织 [PFCCLab](https://github.com/PFCCLab) 下,并列在下方。如果下方列表中没有你需要的算子库,可以移步至[原理和迁移](./design_cn.md),了解自定义算子兼容机制的实现原理,以及如何将你需要的算子库进行迁移。 +我们将这些算子库统一放在组织 [PFCCLab](https://github.com/PFCCLab) 下,并列在下方。如果下方列表中没有你需要的算子库,可以移步至[原理和迁移方式](./design_and_migration_cn.md),了解自定义算子兼容机制的实现原理,以及如何将你需要的算子库进行迁移。 -### FlashInfer +以下是已经支持的跨生态自定义算子库列表: -#### 安装方式 +| 算子库名称 | GitHub repo | PyPI 链接 | +| - | - | - | +| FlashInfer | [PFCCLab/flashinfer](https://github.com/PFCCLab/flashinfer) | - | +| FlashMLA | [PFCCLab/FlashMLA](https://github.com/PFCCLab/FlashMLA) | - | +| DeepGEMM | [PFCCLab/DeepGEMM](https://github.com/PFCCLab/DeepGEMM) | - | +| DeepEP | [PFCCLab/DeepGEMM](https://github.com/PFCCLab/DeepEP) | - | +| TorchCodec | [PFCCLab/paddlecodec](https://github.com/PFCCLab/paddlecodec) | [paddlecodec](https://pypi.org/project/paddlecodec/) | -#### 使用方式 +## Kernel DSL 生态支持 -### FlashMLA +除去自定义算子外,编写自定义算子的方式也在不断演进,涌现出了诸如 Kernel DSL(如 Triton、TileLang)等新兴的编写方式。这些新兴的编写方式在实现中往往或多或少依赖于特定深度学习框架的状态管理接口,从而导致跨生态迁移的难度加大。为此,我们也致力于提升这些新兴编写方式的跨生态兼容性,帮助用户更好地将其迁移至 PaddlePaddle 框架。 -### DeepGEMM +我们目前已经支持的 Kernel DSL 生态包括 Triton 和 TileLang。安装方式分别如下: -### DeepEP - -### TorchCodec +```bash +# Triton 直接安装官方包即可 +pip install triton +# TileLang 目前仍需要安装我们适配后的版本 +pip install tilelang-paddle +``` -## 已支持的 Kernel DSL 库 +与其他自定义算子库相同,用户同样需要在导入对应的 Kernel DSL 库之前,先启用 PaddlePaddle 的 PyTorch 代理层。下面以 TileLang 为例说明使用方式: -### TileLang +```python +# 同样,在导入跨生态 Kernel DSL 库之前,需先启用 PaddlePaddle 的 PyTorch 代理层 +import paddle -### Triton +# 限定生效范围在 TileLang 模块 +paddle.compat.enable_torch_proxy(scope={"tilelang"}) + +# 之后使用方式与官方 PyTorch 生态下保持一致 +@tilelang.jit +def matmul(M, N, K, block_M, block_N, block_K, dtype="float16", accum_dtype="float"): + @T.prim_func + def matmul_relu_kernel( + A: T.Tensor((M, K), dtype), + B: T.Tensor((K, N), dtype), + C: T.Tensor((M, N), dtype), + ): + # Initialize Kernel Context + with T.Kernel(T.ceildiv(N, block_N), T.ceildiv(M, block_M), threads=128) as (bx, by): + A_shared = T.alloc_shared((block_M, block_K), dtype) + B_shared = T.alloc_shared((block_K, block_N), dtype) + C_local = T.alloc_fragment((block_M, block_N), accum_dtype) + + # Enable rasterization for better L2 cache locality (Optional) + # T.use_swizzle(panel_size=10, enable=True) + + # Clear local accumulation + T.clear(C_local) + + for ko in T.Pipelined(T.ceildiv(K, block_K), num_stages=3): + # Copy tile of A + # This is a sugar syntax for parallelized copy + T.copy(A[by * block_M, ko * block_K], A_shared) + + # Copy tile of B + T.copy(B[ko * block_K, bx * block_N], B_shared) + + # Perform a tile-level GEMM on the shared buffers + # Currently we dispatch to the cute/hip on Nvidia/AMD GPUs + T.gemm(A_shared, B_shared, C_local) + + # relu + for i, j in T.Parallel(block_M, block_N): + C_local[i, j] = T.max(C_local[i, j], 0) + + # Copy result back to global memory + T.copy(C_local, C[by * block_M, bx * block_N]) + + return matmul_relu_kernel + +M = 1024 +N = 1024 +K = 1024 +block_M = 128 +block_N = 128 +block_K = 32 + +# 定义并编译 Kernel 函数 +matmul_relu_kernel = matmul(M, N, K, block_M, block_N, block_K) + +# 创建随机输入张量 +a = paddle.randn(M, K, device="cuda", dtype=paddle.float16) +b = paddle.randn(K, N, device="cuda", dtype=paddle.float16) +c = paddle.empty(M, N, device="cuda", dtype=paddle.float16) + +# 运行 kernel +matmul_relu_kernel(a, b, c) + +ref_c = paddle.nn.functional.relu(a @ b) + +# 结果对齐 +np.testing.assert_allclose(c.numpy(), ref_c.numpy(), rtol=1e-2, atol=1e-2) +``` From 85a0c134c1a37d4bcad6c9e9c128bc6760c1b778 Mon Sep 17 00:00:00 2001 From: SigureMo Date: Wed, 3 Dec 2025 03:36:11 +0800 Subject: [PATCH 3/9] update design part --- .../design_and_migration_cn.md | 296 +++++++++++++++++- 1 file changed, 295 insertions(+), 1 deletion(-) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md index 3bd81bf8f33..dc236738a55 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md @@ -19,4 +19,298 @@ ## 迁移步骤 -下面我们以一个简单的 PyTorch 自定义算子为例,介绍如何将其迁移至 PaddlePaddle 框架。 +下面我们以一个简单的 PyTorch 自定义算子为例,介绍如何将其迁移至 PaddlePaddle 框架。相关代码见 [PFCCLab/cross-ecosystem-custom-op-example](https://github.com/PFCCLab/cross-ecosystem-custom-op-example)。 + +### 搭建 PyTorch 运行环境 + +在迁移之前,需要先确保 PyTorch 算子能够在本地正确编译和运行。这可以参考具体的仓库说明文档来完成。 + +本示例展示的自定义算子可以通过如下方式来成功编译和运行: + +```bash +# 首先安装 PyTorch,具体命令可以参考 https://pytorch.org/get-started/locally/ +pip install torch +# 克隆示例代码仓库 +git clone https://github.com/PFCCLab/cross-ecosystem-custom-op-example.git +cd cross-ecosystem-custom-op-example +# 编译自定义算子 +pip install . --no-build-isolation +# 运行测试脚本 +python test.py +``` + +很好,我们已经确保该算子能够在 PyTorch 框架下正确运行,接下来将会介绍如何将其迁移至 PaddlePaddle 框架。 + +### 清理 PyTorch 环境 + +在迁移之前,建议先卸载 PyTorch 相关的包,或者者新建一个干净的虚拟环境来进行迁移工作,以避免潜在的包冲突问题,并安装 PaddlePaddle 框架,具体命令可参考 [PaddlePaddle 安装指南](https://www.paddlepaddle.org.cn/install/quick)。 + +### 理解源码结构 + +当前示例代码的目录结构很简单,如下所示: + +```text +. +├── csrc +│ └── muladd.cc # 自定义算子实现 +├── extension # 自定义算子 Python package +│ └── __init__.py # 自定义算子 Python 封装 +├── pyproject.toml # Python package 配置文件,主要描述 build backend +├── README.md +├── setup.py # Python package 构建脚本,用于 build backend setuptools 的调用 +└── test.py # 测试脚本 +``` + +从构建流程来看,我们主要关注的是: + +- `pyproject.toml` 作为 PEP 517/518 标准的配置文件,描述了该 Python package 的构建后端为 `setuptools`,以及相关的元信息。 + + ```toml + [build-system] + requires = [ + "setuptools", + "torch", + ] + build-backend = "setuptools.build_meta" + ``` + +- `setup.py` 作为 `setuptools` 的构建脚本,主要负责调用 `torch.utils.cpp_extension` 模块来编译 C++ 源码并生成可供 Python 调用的扩展模块。 + + ```python + from setuptools import setup, find_packages + from torch.utils import cpp_extension + + setup( + name="extension", + packages=find_packages(include=['extension']), + ext_modules=[ + cpp_extension.CUDAExtension( + name="extension_cpp", + sources=["csrc/muladd.cc"], + ) + ], + cmdclass={'build_ext': cpp_extension.BuildExtension}, + ) + ``` + +- `csrc/muladd.cc` 作为自定义算子的核心实现文件,包含了算子的具体逻辑和注册代码,我们往往可以分为三部分: + - 框架无关的算子逻辑实现部分,这部分逻辑并不使用 PyTorch 的 API,仅仅使用 C++/CUDA 标准库来实现。 + + ```cpp + template + void muladd_cpu_impl(const T* a_ptr, const T* b_ptr, T c, T* result_ptr, int64_t numel) { + for (int64_t i = 0; i < numel; i++) { + result_ptr[i] = a_ptr[i] * b_ptr[i] + c; + } + } + ``` + - PyTorch C++ API 相关的部分,这部分代码会使用 `at::Tensor` 等 PyTorch C++ API 来进行张量操作和内存管理。 + ```cpp + at::Tensor muladd_cpu(at::Tensor a, const at::Tensor& b, double c) { + TORCH_CHECK(a.sizes() == b.sizes()); + TORCH_CHECK(a.dtype() == at::kFloat); + TORCH_CHECK(b.dtype() == at::kFloat); + TORCH_INTERNAL_ASSERT(a.device().type() == at::DeviceType::CPU); + TORCH_INTERNAL_ASSERT(b.device().type() == at::DeviceType::CPU); + at::Tensor a_contig = a.contiguous(); + at::Tensor b_contig = b.contiguous(); + at::Tensor result = torch::empty(a_contig.sizes(), a_contig.options()); + const float* a_ptr = a_contig.data_ptr(); + const float* b_ptr = b_contig.data_ptr(); + float* result_ptr = result.data_ptr(); + muladd_cpu_impl(a_ptr, b_ptr, static_cast(c), result_ptr, result.numel()); + return result; + } + ``` + - 算子注册部分,这部分代码会使用 PyTorch 提供的注册宏(如 `TORCH_LIBRARY`)来完成算子的注册工作。 + + ```cpp + extern "C" { + /* Creates a dummy empty _C module that can be imported from Python. + The import from Python will load the .so consisting of this file + in this extension, so that the TORCH_LIBRARY static initializers + below are run. */ + PyObject* PyInit_extension_cpp(void) { + static struct PyModuleDef module_def = { + PyModuleDef_HEAD_INIT, + "extension_cpp", /* name of module */ + NULL, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + NULL, /* methods */ + }; + return PyModule_Create(&module_def); + } + } + + TORCH_LIBRARY(extension_cpp, m) { + m.def("muladd_cpp(Tensor a, Tensor b, float c) -> Tensor"); + } + + TORCH_LIBRARY_IMPL(extension_cpp, CPU, m) { + m.impl("muladd_cpp", &muladd_cpu); + } + ``` + + +从执行流程来看,在 Python 端调用该自定义算子时,主要经历了如下几个步骤: + +- 在 `test.py` 中导入自定义算子 Python package `extension`。 +- 在 `extension/__init__.py` 中通过 `torch.ops.extension_cpp.muladd_cpp` 来调用 C++ 扩展模块中的自定义算子,从而调用到上面注册的 `muladd_cpp` 算子。 + + ```python + def muladd(a: torch.Tensor, b: torch.Tensor, c: float) -> torch.Tensor: + return torch.ops.extension_cpp.muladd_cpp(a, b, c) + ``` + +### 调整构建脚本,使用 PaddlePaddle 编译自定义算子 + +由于原本的构建脚本是基于 PyTorch 的 `torch.utils.cpp_extension` 模块来完成编译的,因此我们需要将其替换为 PaddlePaddle 提供的自定义算子编译方式。 + +由于我们提供了 `paddle.compat.enable_torch_proxy()` 代理层来兼容 PyTorch 的 C++ API,因此我们可以使用该 API 实现 torch API 的一键兼容调用。 + +```diff ++import paddle ++paddle.compat.enable_torch_proxy() # Enable torch proxy globally + +from setuptools import setup, find_packages +# 如下的 torch extension 已经被 PaddlePaddle 的同等功能替代(即 paddle.utils.cpp_extension) +# 下面的代码完全不需要修改即可运行 +from torch.utils import cpp_extension + +setup( + name="extension", + packages=find_packages(include=['extension']), + ext_modules=[ + cpp_extension.CUDAExtension( + name="extension_cpp", + sources=["csrc/muladd.cc"], + ) + ], + cmdclass={'build_ext': cpp_extension.BuildExtension}, +) +``` + +对于本示例来说,仅仅需要在 `setup.py` 中添加上述两行代码即可完成迁移工作,其他代码均无需修改。但是自定义算子代码库一般各式各样,可能还需要根据实际情况进行一些调整,关于更多细节请参考 [`paddle.utils.cpp_extension.setup` 文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/utils/cpp_extension/setup_cn.html)。 + +### 尝试编译并修复 + +完成构建脚本的修改后,即可尝试编译自定义算子: + +```bash +pip install . --no-build-isolation +``` + +由于我们提供了 PyTorch C++ API 兼容层,因此理想情况下大多数用户的自定义算子代码都可以直接通过编译而无需修改。 + +PyTorch C++ API 兼容层本质上是以 PyTorch C++ API 作为用户调用接口,并在底层映射至 PaddlePaddle C++ API 来实现的。以 `at::Tensor` 为例,你所调用的 `at::Tensor` 实际上是一个代理类,该类内部持有一个 `paddle::Tensor` 对象,并将所有对 `at::Tensor` 的操作映射为对 `paddle::Tensor` 的操作。 + +```cpp +// paddle/phi/api/include/compat/ATen/core/TensorBody.h +namespace at { +using PaddleTensor = paddle::Tensor; + +class Tensor : public TensorBase { + public: + Tensor() = default; + Tensor(const PaddleTensor& tensor) : TensorBase(tensor){}; // NOLINT + Tensor(const Tensor& tensor) = default; + Tensor(Tensor&& tensor) = default; + + void* data_ptr() const { return const_cast(tensor_.data()); } + template + T* data_ptr() const { + return const_cast(tensor_.data()); + } + + c10::IntArrayRef sizes() const { + return compat::_PD_PhiDDimToIntArrayRef(tensor_.dims()); + } + + int64_t numel() const { return tensor_.numel(); } + + c10::ScalarType dtype() const { // Should we use `TypeMeta` here? + return compat::_PD_PhiDataTypeToAtenScalarType(tensor_.dtype()); + } + + c10::Device device() const { return c10::Device(tensor_.place()); } + + int64_t dim() const { return tensor_.dims().size(); } + int64_t ndimension() const { return dim(); } + + Tensor& fill_(const at::Scalar& value) const { + paddle::experimental::fill_(const_cast(tensor_), value); + return const_cast(*this); + } + + Tensor& zero_() const { + paddle::experimental::fill_(const_cast(tensor_), 0.0); + return const_cast(*this); + } + + PaddleTensor _PD_GetInner() const { return tensor_; } + PaddleTensor& _PD_GetInner() { return tensor_; } +}; + +} // namespace at +namespace torch { +using at::Tensor; +} // namespace torch +``` + +完整的兼容层代码见 [`paddle/phi/api/include/compat`](https://github.com/PaddlePaddle/Paddle/tree/develop/paddle/phi/api/include/compat),我们提供了与 PyTorch C++ API 相同的头文件结构和命名空间,只需按原有方式调用即可。 + +不过目前兼容层还在持续完善中,部分常见 API 尚未覆盖到,此时就会出现编译错误,你可以根据编译错误提示来定位并修复相关代码。 + +以 `Tensor.reshape` 为例,假设用户在自定义算子中使用了该 API,但 Paddle 没有提供该 API 的兼容实现,就会出现编译错误,此时我们可以选择临时取出 `at::Tensor` 内部的 `paddle::Tensor`,并使用 PaddlePaddle 提供的等效 API 来实现该功能: + +```cpp +// PyTorch 原代码 +at::IntArrayRef sizes = {2, 3, 4}; +at::Tensor reshaped_tensor = x.reshape(sizes); +``` + +我们可以将其替换为: + +```cpp +// 替换为 PaddlePaddle 等效实现 +at::IntArrayRef sizes = {2, 3, 4}; +auto paddle_tensor = x._PD_GetInner(); // 获取内部 paddle::Tensor +auto paddle_sizes = shape._PD_ToPaddleIntArray(); // 转换为 paddle::IntArray +auto paddle_reshaped_tensor = paddle::experimental::reshape(paddle_tensor, sizes); // 使用 PaddlePaddle reshape API +at::Tensor reshaped_tensor(paddle_reshaped_tensor); // 包装回 at::Tensor +``` + +更多 PaddlePaddle C++ API 的使用方式可参考 [PaddlePaddle C++ 自定义算子文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/custom_op/new_cpp_op_cn.html)。通过这种方式,你可以逐步修复编译错误,直至自定义算子能够成功编译通过。 + +### 运行测试并修复 + +完成编译后,即可运行测试脚本来验证自定义算子的正确性,由于原本的测试脚本是基于 PyTorch 框架来实现的,因此我们需要改写测试脚本以适配 PaddlePaddle 框架。 + +```python +import paddle +paddle.compat.enable_torch_proxy(scope={"extension"}) # 仅启用 extension 包的 torch 代理 +import extension + +x = paddle.tensor([1.0, 2.0, 3.0]) +y = paddle.tensor([4.0, 5.0, 6.0]) +z = 2.0 +result = extension.muladd(x, y, z) +print(result) # Expected output: tensor([ 6., 12., 20.]) +``` + +由于 `extension` 包中仍然使用了 `torch` 模块下的 Python API,因此我们需要启用 `torch` 代理来确保这些 API 能够正确映射至 PaddlePaddle 框架。为了避免对其他代码产生影响,我们可以通过 `scope` 参数来限定代理的作用范围。 + +当然,与 C++ 端类似,Python 端的兼容层也还在持续完善中,部分常见 API 尚未覆盖到,如果遇到此类错误,你可以尝试参考 [PaddlePaddle Python API 文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/index_cn.html) 和 [PyTorch 最新 release 与 Paddle develop API 映射表](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/model_convert/convert_from_pytorch/pytorch_api_mapping_cn.html)来寻找等效的 PaddlePaddle API 并进行替换,直到运行时不再报错且结果正确为止。 + +至此,一个 PyTorch 自定义算子就成功迁移至 PaddlePaddle 框架了! + +### 总结 + +通过上述步骤,我们介绍了如何将一个简单的 PyTorch 自定义算子迁移至 PaddlePaddle 框架。总体来说,迁移工作主要包括以下几个方面: + +- 调整构建脚本,使用 PaddlePaddle 提供的自定义算子编译方式来替换原有的 PyTorch 构建方式。 +- 修复 C++ 端的编译错误,主要是由于部分 PyTorch C++ API 尚未覆盖到,需要借助 PaddlePaddle C++ API 来实现等效功能。 +- 改写 Python 端的测试脚本,借助 torch proxy 代理层一键替换 PyTorch Python API,并根据实际情况进行部分 API 替换。 + +目前无论是 C++ 端还是 Python 端的兼容层都还在持续完善中,未来我们会不断补充更多常用 API 的兼容实现,从而进一步降低用户的迁移成本。同时我们也非常欢迎社区用户参与到兼容层的建设中来,共同推动跨生态自定义算子的互通与发展! From e30f0ee96397c2e08904b0da9f446b30349d3f23 Mon Sep 17 00:00:00 2001 From: SigureMo Date: Wed, 3 Dec 2025 11:09:41 +0800 Subject: [PATCH 4/9] fix title h1/h2 --- .../cross_ecosystem_custom_op/design_and_migration_cn.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md index dc236738a55..34c3f94659b 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md @@ -1,6 +1,6 @@ -## 原理和迁移方式 +# 原理和迁移方式 -### 实现原理 +## 实现原理 为了方便 PyTorch 自定义算子快速接入 PaddlePaddle 框架,我们提供了如下图所示的兼容机制: From 14b83f669fd7df152a3ae8bd3d2755f0f6553b3d Mon Sep 17 00:00:00 2001 From: Nyakku Shigure Date: Wed, 3 Dec 2025 11:13:35 +0800 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../cross_ecosystem_custom_op/design_and_migration_cn.md | 6 +++--- .../custom_op/cross_ecosystem_custom_op/user_guide_cn.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md index 34c3f94659b..878aed9a572 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md @@ -43,7 +43,7 @@ python test.py ### 清理 PyTorch 环境 -在迁移之前,建议先卸载 PyTorch 相关的包,或者者新建一个干净的虚拟环境来进行迁移工作,以避免潜在的包冲突问题,并安装 PaddlePaddle 框架,具体命令可参考 [PaddlePaddle 安装指南](https://www.paddlepaddle.org.cn/install/quick)。 +在迁移之前,建议先卸载 PyTorch 相关的包,或者新建一个干净的虚拟环境来进行迁移工作,以避免潜在的包冲突问题,并安装 PaddlePaddle 框架,具体命令可参考 [PaddlePaddle 安装指南](https://www.paddlepaddle.org.cn/install/quick)。 ### 理解源码结构 @@ -276,8 +276,8 @@ at::Tensor reshaped_tensor = x.reshape(sizes); // 替换为 PaddlePaddle 等效实现 at::IntArrayRef sizes = {2, 3, 4}; auto paddle_tensor = x._PD_GetInner(); // 获取内部 paddle::Tensor -auto paddle_sizes = shape._PD_ToPaddleIntArray(); // 转换为 paddle::IntArray -auto paddle_reshaped_tensor = paddle::experimental::reshape(paddle_tensor, sizes); // 使用 PaddlePaddle reshape API +auto paddle_sizes = sizes._PD_ToPaddleIntArray(); // 转换为 paddle::IntArray +auto paddle_reshaped_tensor = paddle::experimental::reshape(paddle_tensor, paddle_sizes); // 使用 PaddlePaddle reshape API at::Tensor reshaped_tensor(paddle_reshaped_tensor); // 包装回 at::Tensor ``` diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md index bc45b3f84b1..09d0945a169 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md @@ -85,7 +85,7 @@ PaddlePaddle 官方协同社区已经对社区中主流的跨生态自定义算 | FlashInfer | [PFCCLab/flashinfer](https://github.com/PFCCLab/flashinfer) | - | | FlashMLA | [PFCCLab/FlashMLA](https://github.com/PFCCLab/FlashMLA) | - | | DeepGEMM | [PFCCLab/DeepGEMM](https://github.com/PFCCLab/DeepGEMM) | - | -| DeepEP | [PFCCLab/DeepGEMM](https://github.com/PFCCLab/DeepEP) | - | +| DeepEP | [PFCCLab/DeepEP](https://github.com/PFCCLab/DeepEP) | - | | TorchCodec | [PFCCLab/paddlecodec](https://github.com/PFCCLab/paddlecodec) | [paddlecodec](https://pypi.org/project/paddlecodec/) | ## Kernel DSL 生态支持 From 772c8fc15f23b4b31dfabc22cb49b1632f81e5e1 Mon Sep 17 00:00:00 2001 From: SigureMo Date: Wed, 3 Dec 2025 14:46:53 +0800 Subject: [PATCH 6/9] refine user guide --- .../cross_ecosystem_custom_op/user_guide_cn.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md index 09d0945a169..931c35c41c9 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md @@ -2,9 +2,11 @@ ## 概述 -随着大模型的兴起,在深度学习框架之上构建自定义算子(Custom Operator)已成为提升模型性能和功能的关键手段。而目前 PyTorch 作为深度学习领域的主流框架之一,拥有大量的自定义算子实现。为了帮助用户更好地将现有的 PyTorch 等生态的自定义算子迁移至 PaddlePaddle 框架,我们提供了自定义算子兼容机制,旨在降低迁移成本,提升开发效率。 +随着大模型技术的快速发展,自定义算子(Custom Operator)已成为优化模型性能、扩展框架功能的关键手段。目前,PyTorch 生态中积累了大量高质量的自定义算子库和基于 Kernel DSL(如 Triton、TileLang)的算子实现。为了打破生态壁垒,帮助用户低成本地将这些优质算子资源迁移至 PaddlePaddle 框架,我们推出了一套跨生态自定义算子兼容机制。该机制支持用户在 PaddlePaddle 中直接使用 PyTorch 生态的自定义算子库和 Kernel DSL,从而大幅降低迁移成本,提升开发效率。 -## 使用步骤 +## 自定义算子库 + +目前 PyTorch 生态中存在大量高质量的自定义算子库(如 FlashInfer、FlashMLA 等),这些算子库通常基于 CUDA/C++ 编写并封装为 Python 扩展。为了复用这些现有的算子库,我们提供了兼容性支持,使得用户可以直接在 PaddlePaddle 中安装并使用这些库,而无需进行繁琐的代码移植。 ### 安装方式 @@ -72,7 +74,7 @@ y = flashinfer.norm.rmsnorm(x, w, enable_pdl=False) np.testing.assert_allclose(y_ref, y, rtol=1e-3, atol=1e-3) ``` -## 已支持的算子库 +### 已支持的算子库 PaddlePaddle 官方协同社区已经对社区中主流的跨生态自定义算子库进行了适配和测试,用户可以直接使用这些算子库而无需进行额外的修改。 @@ -88,11 +90,13 @@ PaddlePaddle 官方协同社区已经对社区中主流的跨生态自定义算 | DeepEP | [PFCCLab/DeepEP](https://github.com/PFCCLab/DeepEP) | - | | TorchCodec | [PFCCLab/paddlecodec](https://github.com/PFCCLab/paddlecodec) | [paddlecodec](https://pypi.org/project/paddlecodec/) | -## Kernel DSL 生态支持 +## Kernel DSL + +除去封装好的自定义算子库外,使用 Kernel DSL(Domain Specific Language,如 Triton、TileLang)直接编写算子也是一种常见的开发方式。这些 DSL 通常提供了更高级的抽象,使得用户能够更方便地编写高性能的算子。然而,这些 DSL 在实现中往往依赖于特定深度学习框架的状态管理接口,导致跨生态迁移困难。为此,我们也致力于提升这些新兴编写方式的跨生态兼容性,帮助用户更好地将其迁移至 PaddlePaddle 框架。 -除去自定义算子外,编写自定义算子的方式也在不断演进,涌现出了诸如 Kernel DSL(如 Triton、TileLang)等新兴的编写方式。这些新兴的编写方式在实现中往往或多或少依赖于特定深度学习框架的状态管理接口,从而导致跨生态迁移的难度加大。为此,我们也致力于提升这些新兴编写方式的跨生态兼容性,帮助用户更好地将其迁移至 PaddlePaddle 框架。 +我们目前已经支持的 Kernel DSL 生态包括 [Triton](https://github.com/triton-lang/triton) 和 [TileLang](https://github.com/PFCCLab/tilelang-paddle)。 -我们目前已经支持的 Kernel DSL 生态包括 Triton 和 TileLang。安装方式分别如下: +### 安装方式 ```bash # Triton 直接安装官方包即可 @@ -101,6 +105,8 @@ pip install triton pip install tilelang-paddle ``` +### 使用方式 + 与其他自定义算子库相同,用户同样需要在导入对应的 Kernel DSL 库之前,先启用 PaddlePaddle 的 PyTorch 代理层。下面以 TileLang 为例说明使用方式: ```python From 7224bde56a9eb2acc8c3f258de8d28642861d6ae Mon Sep 17 00:00:00 2001 From: SigureMo Date: Wed, 3 Dec 2025 14:55:38 +0800 Subject: [PATCH 7/9] tmp rename index --- .../cross_ecosystem_custom_op/{index_cn.rst => index_cn_xxx.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/guides/custom_op/cross_ecosystem_custom_op/{index_cn.rst => index_cn_xxx.rst} (100%) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst b/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn_xxx.rst similarity index 100% rename from docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst rename to docs/guides/custom_op/cross_ecosystem_custom_op/index_cn_xxx.rst From c91b49a81ac871258898194c7aa92a83af280a27 Mon Sep 17 00:00:00 2001 From: SigureMo Date: Wed, 3 Dec 2025 15:55:33 +0800 Subject: [PATCH 8/9] Revert "tmp rename index" This reverts commit 7224bde56a9eb2acc8c3f258de8d28642861d6ae. --- .../cross_ecosystem_custom_op/{index_cn_xxx.rst => index_cn.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/guides/custom_op/cross_ecosystem_custom_op/{index_cn_xxx.rst => index_cn.rst} (100%) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn_xxx.rst b/docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst similarity index 100% rename from docs/guides/custom_op/cross_ecosystem_custom_op/index_cn_xxx.rst rename to docs/guides/custom_op/cross_ecosystem_custom_op/index_cn.rst From 09b453bf21bd402400d3267470d5f84a990368b1 Mon Sep 17 00:00:00 2001 From: SigureMo Date: Wed, 3 Dec 2025 22:02:35 +0800 Subject: [PATCH 9/9] refine design and migration --- .../design_and_migration_cn.md | 31 ++++++++++----- .../user_guide_cn.md | 38 +++++++++++-------- 2 files changed, 44 insertions(+), 25 deletions(-) diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md index 878aed9a572..1a7ad93d9c5 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/design_and_migration_cn.md @@ -4,7 +4,10 @@ 为了方便 PyTorch 自定义算子快速接入 PaddlePaddle 框架,我们提供了如下图所示的兼容机制: -![跨生态自定义算子兼容机制示意图](./images/cross-ecosystem-custom-op-compatible.drawio.png) +

+ missing +
跨生态自定义算子兼容机制示意图
+
正如图上所示,我们自底向上提供了如下几层支持: @@ -262,23 +265,33 @@ using at::Tensor; 不过目前兼容层还在持续完善中,部分常见 API 尚未覆盖到,此时就会出现编译错误,你可以根据编译错误提示来定位并修复相关代码。 -以 `Tensor.reshape` 为例,假设用户在自定义算子中使用了该 API,但 Paddle 没有提供该 API 的兼容实现,就会出现编译错误,此时我们可以选择临时取出 `at::Tensor` 内部的 `paddle::Tensor`,并使用 PaddlePaddle 提供的等效 API 来实现该功能: +以 `torch::empty` 为例,假设算子库中使用了该 API,但 Paddle 没有提供该 API 的兼容实现,就会出现编译错误: + +```text +/workspace/cross-ecosystem-custom-op-example/csrc/muladd.cc: In function ‘at::Tensor muladd_cpu(at::Tensor, const at::Tensor&, double)’: +/workspace/cross-ecosystem-custom-op-example/csrc/muladd.cc:54:30: error: ‘empty’ is not a member of ‘torch’ + 54 | at::Tensor result = torch::empty(a_contig.sizes(), a_contig.options()); + | ^~~~~ +``` + +此时我们可以选择将 PyTorch 的 structs 转换为 Paddle 的 structs,并用 PaddlePaddle 提供的等效 API 来实现该功能: + +即将下面的代码: ```cpp // PyTorch 原代码 -at::IntArrayRef sizes = {2, 3, 4}; -at::Tensor reshaped_tensor = x.reshape(sizes); +at::Tensor result = torch::empty(a_contig.sizes(), a_contig.options()); ``` 我们可以将其替换为: ```cpp // 替换为 PaddlePaddle 等效实现 -at::IntArrayRef sizes = {2, 3, 4}; -auto paddle_tensor = x._PD_GetInner(); // 获取内部 paddle::Tensor -auto paddle_sizes = sizes._PD_ToPaddleIntArray(); // 转换为 paddle::IntArray -auto paddle_reshaped_tensor = paddle::experimental::reshape(paddle_tensor, paddle_sizes); // 使用 PaddlePaddle reshape API -at::Tensor reshaped_tensor(paddle_reshaped_tensor); // 包装回 at::Tensor +auto paddle_size = a_contig.sizes()._PD_ToPaddleIntArray(); // 将 PyTorch IntArrayRef 转为 Paddle IntArray +auto paddle_dtype = compat::_PD_AtenScalarTypeToPhiDataType(a_contig.dtype()); // 将 PyTorch ScalarType 转为 Paddle DataType +auto paddle_place = a_contig.options()._PD_GetPlace(); // 将 PyTorch Device 转为 Paddle Place +auto paddle_result = paddle::experimental::empty(paddle_size, paddle_dtype, paddle_place); // 调用 PaddlePaddle 的 empty API +at::Tensor result(paddle_result); // 将 Paddle Tensor 包装为 PyTorch Tensor ``` 更多 PaddlePaddle C++ API 的使用方式可参考 [PaddlePaddle C++ 自定义算子文档](https://www.paddlepaddle.org.cn/documentation/docs/zh/guides/custom_op/new_cpp_op_cn.html)。通过这种方式,你可以逐步修复编译错误,直至自定义算子能够成功编译通过。 diff --git a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md index 931c35c41c9..a831a65ad26 100644 --- a/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md +++ b/docs/guides/custom_op/cross_ecosystem_custom_op/user_guide_cn.md @@ -4,30 +4,32 @@ 随着大模型技术的快速发展,自定义算子(Custom Operator)已成为优化模型性能、扩展框架功能的关键手段。目前,PyTorch 生态中积累了大量高质量的自定义算子库和基于 Kernel DSL(如 Triton、TileLang)的算子实现。为了打破生态壁垒,帮助用户低成本地将这些优质算子资源迁移至 PaddlePaddle 框架,我们推出了一套跨生态自定义算子兼容机制。该机制支持用户在 PaddlePaddle 中直接使用 PyTorch 生态的自定义算子库和 Kernel DSL,从而大幅降低迁移成本,提升开发效率。 -## 自定义算子库 +## 外部算子库 目前 PyTorch 生态中存在大量高质量的自定义算子库(如 FlashInfer、FlashMLA 等),这些算子库通常基于 CUDA/C++ 编写并封装为 Python 扩展。为了复用这些现有的算子库,我们提供了兼容性支持,使得用户可以直接在 PaddlePaddle 中安装并使用这些库,而无需进行繁琐的代码移植。 ### 安装方式 -对于使用基于兼容性方案的跨生态自定义算子库,一般情况下只需要 clone 后通过 pip 安装对应的算子库即可使用。下面以 `FlashInfer` 为例说明安装方式: +对于使用基于兼容性方案的跨生态自定义算子库,一般情况分为两种安装方式:源码安装和 PyPI 安装。大部分算子库都托管在 GitHub 上,用户可以根据具体算子库的安装说明进行安装。下面以两个典型的算子库为例,介绍安装方式: -```bash -pip install paddlepaddle_gpu # Install PaddlePaddle with GPU support, refer to https://www.paddlepaddle.org.cn/install/quick for more details -git clone https://github.com/PFCCLab/flashinfer.git -cd flashinfer -git submodule update --init -pip install apache-tvm-ffi>=0.1.2 # Use TVM FFI 0.1.2 or above -pip install filelock jinja2 # Install tools for jit compilation -# Install FlashInfer -pip install --no-build-isolation . -v -``` +- 源码安装(以 `FlashInfer` 为例): -对于部分已经发布到 PyPI 的自定义算子库,也可以直接通过 pip 安装。下面以 `TorchCodec` 为例: + ```bash + pip install paddlepaddle_gpu # Install PaddlePaddle with GPU support, refer to https://www.paddlepaddle.org.cn/install/quick for more details + git clone https://github.com/PFCCLab/flashinfer.git + cd flashinfer + git submodule update --init + pip install apache-tvm-ffi>=0.1.2 # Use TVM FFI 0.1.2 or above + pip install filelock jinja2 # Install tools for jit compilation + # Install FlashInfer + pip install --no-build-isolation . -v + ``` -```bash -pip install paddlecodec -``` +- PyPI 安装(以 `TorchCodec` 为例): + + ```bash + pip install paddlecodec + ``` 个别算子库可能会有特殊的安装方式,请参考对应算子库 repo 中的 `README.md` 进行安装。 @@ -116,6 +118,10 @@ import paddle # 限定生效范围在 TileLang 模块 paddle.compat.enable_torch_proxy(scope={"tilelang"}) +import tilelang +import tilelang.language as T +import numpy as np + # 之后使用方式与官方 PyTorch 生态下保持一致 @tilelang.jit def matmul(M, N, K, block_M, block_N, block_K, dtype="float16", accum_dtype="float"):