muDNN 开发者指南
本文档介绍 muDNN 的架构、核心概念和使用方法。
C++ API 已标记为弃用。新应用请使用 C API,C++ API 将在未来版本中移除。
安装与环境准备
muDNN 随 MUSA SDK 一起发布。安装步骤可参考 MUSA SDK 安装指南。
在使用本文档中的示例前,请先完成 Linux Driver 和 MUSA Toolkit 安装,并确保相关环境变量已生效。
概述
什么是 muDNN
muDNN(Moore Threads Deep Neural Network Library,深度神经网络库)是面向 MUSA 平台的深度神经网络加速库,提供张量、卷积、归一化、激活、注意力等常用神经网络算子接口。
关键特性
- 提供 C API 形式的神经网络算子接口,新应用推荐优先使用 C API。
- 支持张量算子、卷积神经网络算子和高级算子等多类能力。
- 通过 handle、descriptor 和 workspace 模型管理执行上下文与算子参数。
- 针对 MTGPU 架构优化, 适用于深度学习训练和推理场景。
muDNN 架构
API 层次结构
muDNN C API 由以下头文件组成:
mudnn.h - 主头文件
聚合所有 C API 导出接口,包含:
mudnn_version.h: 版本信息查询mudnn_ops.h: 张量算子和神经网络算子mudnn_cnn.h: 卷积神经网络算子mudnn_adv.h: 高级算子mudnn_graph.h: 图API
mudnn_ops.h - 基础算子
| 类型/函数 | 说明 |
|---|---|
mudnnHandle_t | muDNN 设备句柄,绑定到 MUSA 设备与流 |
mudnnTensorDescriptor_t | 张量描述符,描述数据地址、类型、格式、维度 |
mudnnFilterDescriptor_t | 滤波器/权重描述符 |
mudnnCreateXXXDescriptor | 创建各类描述符 |
mudnnDestroyXXXDescriptor | 销毁各类描述符 |
张量算子:
| 算子 | 函数前缀 |
|---|---|
| 一元算子 | mudnnUnary |
| 二元算子 | mudnnBinary |
| 三元算子 | mudnnTernary |
| 拼接 | mudnnConcat |
| 填充 | mudnnFill |
| 置换 | mudnnPermute |
| 归约 | mudnnReduce |
mudnn_cnn.h - 神经网络算子
| 算子类型 | 函数前缀 |
|---|---|
| 卷积 | mudnnConvolution |
| 池化 | mudnnPooling |
| 归一化 | mudnnBatchNorm, mudnnLayerNorm, mudnnGroupNorm |
| 激活 | mudnnActivation, mudnnSoftmax |
| Dropout | mudnnDropout |
mudnn_adv.h - 高级算子
| 算子类型 | 函数前缀 |
|---|---|
| 注意力 | mudnnMultiHeadAttention, mudnnScaledDotProductAttention |
| 损失 | mudnnCTCLoss |
| 矩阵乘法 | mudnnGemm |
快速开始
示例 1:LayerNorm(C API,使用非deprecated接口)
以下示例展示如何使用 muDNN C API 执行 LayerNorm 操作:
#include <stdio.h>
#include <stdlib.h>
#include <musa_runtime_api.h>
#include <mudnn/mudnn.h>
#define MUDNN_CHECK(cmd) \
do { \
mudnnStatus_t status = cmd; \
if (status != MUDNN_STATUS_SUCCESS) { \
fprintf(stderr, "muDNN error %d at %s:%d\n", status, \
__FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
#define MUSACHECK(cmd) \
do { \
musaError_t err = cmd; \
if (err != musaSuccess) { \
fprintf(stderr, "MUSA error %d at %s:%d\n", err, \
__FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
int main() {
mudnnHandle_t handle;
mudnnTensorDescriptor_t input_desc, output_desc;
mudnnTensorDescriptor_t mean_desc, variance_desc, gamma_desc, beta_desc;
mudnnLayerNormDescriptor_t layernorm_desc;
// 1. 创建 muDNN 句柄
MUDNN_CHECK(mudnnCreate(&handle));
// 2. 创建描述符
MUDNN_CHECK(mudnnCreateTensorDescriptor(&input_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&output_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&mean_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&variance_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&gamma_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&beta_desc));
MUDNN_CHECK(mudnnCreateLayerNormDescriptor(&layernorm_desc));
// 3. 设置张量维度 (N, C, H, W) - NCHW 格式
const int N = 4, C = 64, H = 32, W = 32;
// 设置输入张量描述符
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
input_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
// 输出/../gamma/beta 使用相同维度
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
output_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
mean_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
variance_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
gamma_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
beta_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
// 设置 LayerNorm 描述符
int axes[] = {1, 2, 3}; // 对 C, H, W 维度归一化
MUDNN_CHECK(mudnnSetLayerNormDescriptor(
layernorm_desc,
1e-5, // epsilon
MUDNN_LAYERNORM_MODE_ELEMENTWISE, // 模式
axes, // 归一化轴
3 // 轴数量
));
// 4. 分配设备内存
float *input_data, *output_data, *mean_data, *variance_data;
float *gamma_data, *beta_data;
size_t size = N * C * H * W * sizeof(float);
size_t channel_size = C * sizeof(float);
MUSACHECK(musaMalloc((void**)&input_data, size));
MUSACHECK(musaMalloc((void**)&output_data, size));
MUSACHECK(musaMalloc((void**)&mean_data, size));
MUSACHECK(musaMalloc((void**)&variance_data, size));
MUSACHECK(musaMalloc((void**)&gamma_data, channel_size));
MUSACHECK(musaMalloc((void**)&beta_data, channel_size));
// 5. 查询工作区大小
size_t workspace_size;
MUDNN_CHECK(mudnnGetLayerNormForwardWorkspaceSize(
handle, layernorm_desc, output_desc, mean_desc, variance_desc,
input_desc, gamma_desc, beta_desc, &workspace_size
));
void* workspace = NULL;
if (workspace_size > 0) {
MUSACHECK(musaMalloc(&workspace, workspace_size));
}
// 6. 执行 LayerNorm
MUDNN_CHECK(mudnnLayerNormForward(
handle, layernorm_desc,
output_desc, output_data,
mean_desc, mean_data,
variance_desc, variance_data,
input_desc, input_data,
gamma_desc, gamma_data,
beta_desc, beta_data,
workspace, workspace_size
));
// 7. 同步流
MUSACHECK(musaStreamSynchronize(musaStreamPerThread));
printf("LayerNorm executed successfully!\n");
// 8. 清理资源
MUSACHECK(musaFree(input_data));
MUSACHECK(musaFree(output_data));
MUSACHECK(musaFree(mean_data));
MUSACHECK(musaFree(variance_data));
MUSACHECK(musaFree(gamma_data));
MUSACHECK(musaFree(beta_data));
if (workspace) MUSACHECK(musaFree(workspace));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(input_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(output_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(mean_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(variance_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(gamma_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(beta_desc));
MUDNN_CHECK(mudnnDestroyLayerNormDescriptor(layernorm_desc));
MUDNN_CHECK(mudnnDestroy(handle));
return 0;
}
编译命令:
编译依赖于 MUSA SDK 的 musa_asm 组件,请确保已正确安装。
mcc layernorm_example.c \
-I${MUSA_SDK_PATH}/include \
-L${MUSA_SDK_PATH}/lib \
-lmudnn -lmusa_runtime \
-Wl,-rpath,${MUSA_SDK_PATH}/lib \
-o layernorm_example
运行:
./layernorm_example
示例 2:GEMM(使用 muDNN)
以下示例展示如何使用 muDNN 执行矩阵乘法(GEMM)操作:
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <vector>
#include <musa_runtime_api.h>
#include "mudnn.h"
#define MUDNN_CHECK(cmd) \
do { \
mudnnStatus_t status = cmd; \
if (status != MUDNN_STATUS_SUCCESS) { \
fprintf(stderr, "muDNN error %d at %s:%d\n", status, \
__FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
#define MUSACHECK(cmd) \
do { \
musaError_t err = cmd; \
if (err != musaSuccess) { \
fprintf(stderr, "MUSA error %d at %s:%d\n", err, \
__FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
namespace {
std::vector<int> ContiguousStrides(const std::vector<int>& shape) {
std::vector<int> stride(shape.size(), 1);
for (int i = static_cast<int>(shape.size()) - 2; i >= 0; --i) {
stride[static_cast<size_t>(i)] =
stride[static_cast<size_t>(i + 1)] * shape[static_cast<size_t>(i + 1)];
}
return stride;
}
void SetTensorDesc3D(mudnnTensorDescriptor_t desc, mudnnDataType_t dtype,
const std::vector<int>& shape) {
const std::vector<int> stride = ContiguousStrides(shape);
MUDNN_CHECK(mudnnSetTensorNdDescriptor(desc, dtype, static_cast<int>(shape.size()),
shape.data(), stride.data()));
}
} // namespace
int main() {
std::cout << "muDNN version: " << mudnnGetVersion() << std::endl;
MUSACHECK(musaSetDevice(0));
constexpr int batch = 1;
constexpr int m = 2;
constexpr int k = 3;
constexpr int n = 4;
const std::vector<float> h_a_f32 = {
1.0f, 2.0f, 3.0f,
4.0f, 5.0f, 6.0f,
};
const std::vector<float> h_b_f32 = {
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f,
9.0f, 10.0f, 11.0f, 12.0f,
};
std::vector<float> h_d(static_cast<size_t>(batch * m * n), 0.0f);
void* d_a = nullptr;
void* d_b = nullptr;
void* d_d = nullptr;
MUSACHECK(musaMalloc(&d_a, h_a_f32.size() * sizeof(float)));
MUSACHECK(musaMalloc(&d_b, h_b_f32.size() * sizeof(float)));
MUSACHECK(musaMalloc(&d_d, h_d.size() * sizeof(float)));
MUSACHECK(musaMemcpy(d_a, h_a_f32.data(), h_a_f32.size() * sizeof(float),
musaMemcpyHostToDevice));
MUSACHECK(musaMemcpy(d_b, h_b_f32.data(), h_b_f32.size() * sizeof(float),
musaMemcpyHostToDevice));
MUSACHECK(musaMemset(d_d, 0, h_d.size() * sizeof(float)));
mudnnHandle_t handle = nullptr;
mudnnTensorDescriptor_t a_desc = nullptr;
mudnnTensorDescriptor_t b_desc = nullptr;
mudnnTensorDescriptor_t d_desc = nullptr;
mudnnBatchMatMulDescriptor_t bmm_desc = nullptr;
MUDNN_CHECK(mudnnCreate(&handle));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&a_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&b_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&d_desc));
MUDNN_CHECK(mudnnCreateBatchMatMulDescriptor(&bmm_desc));
SetTensorDesc3D(a_desc, MUDNN_DATA_FLOAT, {batch, m, k});
SetTensorDesc3D(b_desc, MUDNN_DATA_FLOAT, {batch, k, n});
SetTensorDesc3D(d_desc, MUDNN_DATA_FLOAT, {batch, m, n});
MUDNN_CHECK(mudnnSetBatchMatMulDescriptor(
bmm_desc, MUDNN_TENSOR_OP_MATH, false, false));
size_t workspace_size = 0;
MUDNN_CHECK(mudnnBatchMatmulGetWorkspaceSize(handle, bmm_desc, a_desc, b_desc,
nullptr, d_desc,
&workspace_size));
void* workspace = nullptr;
if (workspace_size > 0) {
MUSACHECK(musaMalloc(&workspace, workspace_size));
}
MUDNN_CHECK(mudnnBatchMatMulBias(handle, bmm_desc, a_desc, d_a, b_desc, d_b,
nullptr, nullptr, d_desc, d_d, nullptr,
nullptr, workspace, workspace_size));
MUSACHECK(musaMemcpy(h_d.data(), d_d, h_d.size() * sizeof(float),
musaMemcpyDeviceToHost));
MUSACHECK(musaDeviceSynchronize());
if (workspace != nullptr) {
MUSACHECK(musaFree(workspace));
}
MUDNN_CHECK(mudnnDestroyBatchMatMulDescriptor(bmm_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(a_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(b_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(d_desc));
MUDNN_CHECK(mudnnDestroy(handle));
MUSACHECK(musaFree(d_a));
MUSACHECK(musaFree(d_b));
MUSACHECK(musaFree(d_d));
return 0;
}
示例 3:BatchNorm(C API)
#include <stdio.h>
#include <stdlib.h>
#include <musa_runtime_api.h>
#include <mudnn/mudnn.h>
#define MUDNN_CHECK(cmd) \
do { \
mudnnStatus_t status = cmd; \
if (status != MUDNN_STATUS_SUCCESS) { \
fprintf(stderr, "muDNN error %d at %s:%d\n", status, \
__FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
#define MUSACHECK(cmd) \
do { \
musaError_t err = cmd; \
if (err != musaSuccess) { \
fprintf(stderr, "MUSA error %d at %s:%d\n", err, \
__FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
int main() {
mudnnHandle_t handle;
mudnnTensorDescriptor_t input_desc, output_desc;
mudnnTensorDescriptor_t scale_desc, bias_desc;
mudnnTensorDescriptor_t mean_desc, variance_desc;
mudnnBatchNormDescriptor_t bn_desc;
// 1. 创建句柄和描述符
MUDNN_CHECK(mudnnCreate(&handle));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&input_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&output_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&scale_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&bias_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&mean_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&variance_desc));
MUDNN_CHECK(mudnnCreateBatchNormDescriptor(&bn_desc));
// 2. 设置张量维度 (N, C, H, W) NCHW 格式
const int N = 4, C = 64, H = 32, W = 32;
// 输入/输出张量描述符
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
input_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
output_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, N, C, H, W));
// scale/bias 和 mean/var 描述符(每通道)
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
scale_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, 1, C, 1, 1));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
bias_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, 1, C, 1, 1));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
mean_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, 1, C, 1, 1));
MUDNN_CHECK(mudnnSetTensor4dDescriptor(
variance_desc, MUDNN_TENSOR_NCHW, MUDNN_DATA_FLOAT, 1, C, 1, 1));
// 3. 分配设备内存
float *input_data, *output_data, *scale_data, *bias_data, *mean_data, *variance_data;
size_t input_size = N * C * H * W * sizeof(float);
size_t channel_size = C * sizeof(float);
MUSACHECK(musaMalloc((void**)&input_data, input_size));
MUSACHECK(musaMalloc((void**)&output_data, input_size));
MUSACHECK(musaMalloc((void**)&scale_data, channel_size));
MUSACHECK(musaMalloc((void**)&bias_data, channel_size));
MUSACHECK(musaMalloc((void**)&mean_data, channel_size));
MUSACHECK(musaMalloc((void**)&variance_data, channel_size));
// 4. 设置 BatchNorm 参数(使用 v5.1.0 API)
MUDNN_CHECK(mudnnSetBatchNormDescriptor(
bn_desc,
MUDNN_BATCHNORM_PER_ACTIVATION,
1e-5, // epsilon
true // isTraining = true for training mode
));
// 5. 查询工作区大小
size_t workspace_size;
MUDNN_CHECK(mudnnGetBatchNormForwardTrainingWorkspaceSize(
handle, bn_desc, input_desc, &workspace_size));
void* workspace = NULL;
if (workspace_size > 0) {
MUSACHECK(musaMalloc(&workspace, workspace_size));
}
// 6. 执行 BatchNorm(训练模式)- 使用 v5.1.0 API
// 注意:参数顺序为 outputDesc, output, inputDesc, input, ...
MUDNN_CHECK(mudnnBatchNormForwardTraining(
handle, bn_desc,
output_desc, output_data,
input_desc, input_data,
mean_desc, mean_data, // accumulated mean
variance_desc, variance_data, // accumulated variance
mean_desc, mean_data, // fresh mean (same buffer for simplicity)
variance_desc, variance_data, // fresh variance (same buffer for simplicity)
scale_desc, scale_data,
bias_desc, bias_data,
0.9, // momentum
workspace, workspace_size
));
// 7. 同步流
MUSACHECK(musaStreamSynchronize(musaStreamPerThread));
printf("BatchNorm executed successfully!\n");
// 8. 清理资源
MUSACHECK(musaFree(input_data));
MUSACHECK(musaFree(output_data));
MUSACHECK(musaFree(scale_data));
MUSACHECK(musaFree(bias_data));
MUSACHECK(musaFree(mean_data));
MUSACHECK(musaFree(variance_data));
if (workspace) MUSACHECK(musaFree(workspace));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(input_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(output_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(scale_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(bias_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(mean_desc));
MUDNN_CHECK(mudnnDestroyTensorDescriptor(variance_desc));
MUDNN_CHECK(mudnnDestroyBatchNormDescriptor(bn_desc));
MUDNN_CHECK(mudnnDestroy(handle));
return 0;
}
核心概念
Handle(设备句柄)
mudnnHandle_t 是 muDNN 与 MUSA 设备交互的核心接口:
mudnnHandle_t handle;
mudnnCreate(&handle);
// 设置流
musaStream_t stream;
musaStreamCreate(&stream);
mudnnSetStream(handle, stream);
// 清理
mudnnDestroy(handle);
musaStreamDestroy(stream);
Tensor Descriptor(张量描述符)
张量描述符描述多维数组的元数据:
mudnnTensorDescriptor_t tensor_desc;
mudnnCreateTensorDescriptor(&tensor_desc);
// 4D 张量 (NCHW)
mudnnSetTensor4dDescriptor(
tensor_desc,
MUDNN_TENSOR_NCHW, // 格式
MUDNN_DATA_FLOAT, // 数据类型
N, C, H, W // 维度
);
// 2D 张量
mudnnSetTensor2dDescriptor(
tensor_desc,
MUDNN_DATA_FLOAT, // 数据类型
rows, cols // 维度
);
// 清理
mudnnDestroyTensorDescriptor(tensor_desc);
Workspace(工作区)
muDNN 算子需要临时工作区内存:
// 以 LayerNorm 为例:查询工作区大小
size_t workspace_size;
mudnnGetLayerNormForwardWorkspaceSize(
handle, layernorm_desc,
output_desc, mean_desc, variance_desc,
input_desc, gamma_desc, beta_desc,
&workspace_size
);
// 2. 分配工作区
void* workspace;
if (workspace_size > 0) {
musaMalloc(&workspace, workspace_size);
}
// 3. 使用工作区执行算子
mudnnLayerNormForward(
handle, layernorm_desc,
output_desc, output_data,
mean_desc, mean_data,
variance_desc, variance_data,
input_desc, input_data,
gamma_desc, gamma_data,
beta_desc, beta_data,
workspace, workspace_size
);
最佳实践
错误处理
始终检查 muDNN 函数返回状态:
#define MUDNN_CHECK(cmd) \
do { \
mudnnStatus_t status = cmd; \
if (status != MUDNN_STATUS_SUCCESS) { \
fprintf(stderr, "muDNN error %d at %s:%d\n", status, \
__FILE__, __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
资源管理
使用宏简化错误检查和资源清理:
// 在函数开始处创建所有资源
mudnnHandle_t handle;
mudnnTensorDescriptor_t input_desc, output_desc;
void* workspace = NULL;
MUDNN_CHECK(mudnnCreate(&handle));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&input_desc));
MUDNN_CHECK(mudnnCreateTensorDescriptor(&output_desc));
// 使用 goto 统一清理
cleanup:
if (input_desc) mudnnDestroyTensorDescriptor(input_desc);
if (output_desc) mudnnDestroyTensorDescriptor(output_desc);
if (workspace) musaFree(workspace);
if (handle) mudnnDestroy(handle);
性能优化
- 选择合适的卷积算法(使用
mudnnFindConvolutionForwardAlgorithm) - 重用工作区内存,避免重复分配
- 批处理操作以减少内核启动开销
- 使用
mudnnSetStream绑定 MUSA 流以实现并发执行
与 AI 框架对接扩展
muDNN 可以与主流 AI 框架(PyTorch、TensorFlow)对接,使框架能够使用 muDNN 算子库在 MTGPU 上运行 AI 模型。
torch_musa - muDNN 对接扩展
本节介绍如何将 MTGPU 设备通过 muDNN 库和 torch_musa 集成到 PyTorch 框架中。
算子派发机制(Dispatch)
PyTorch 中的 dispatcher 是一个分发器,当执行一个 operator 时,dispatcher 会根据 tensor 的输入信息和其他信息(参数个数、返回值类型等)计算得到一个 dispatch key,然后根据 dispatch key 从 dispatch table 中找到对应的 kernel 函数指针,最后回调执行。
dispatch key 不仅有硬件后端,还有一些更抽象的概念如 autograd、tracing 等。当同一个算子有不同 dispatch key 的实现时,PyTorch 会调用优先级最高的 dispatch key 对应的 kernel 实现。
设备注册流程
要让 PyTorch 支持 MTGPU 设备,需要完成以下步骤:
-
添加 backend
- 在
torch/c10/core/Backend.h的enum class Backend中添加MUSA - 在
backendToDispatchKey()中将backend与DispatchKey(PrivateUse1)绑定 - 在
backendToDeviceType()中将backend与DeviceType(MTGPU)绑定
- 在
-
添加 device
- 在
torch/c10/core/DeviceType.h的enum class DeviceType中添加MTGPU - 在
torch/c10/core/Device.h的struct Device中添加is_mtgpu() - 在
torch/c10/core/DispatchKey.cpp的toBackendComponent()中添加 MTGPU 对应的BackendComponent - 在
torch/c10/core/TensorOptions.h的computeDispatchKey()中根据 MTGPU 获得对应的DispatchKey - 在
torch/torch/library.h的dispatch中将Device_type MTGPU与DispatchKey::PrivateUse1绑定
- 在
算子对接流程
以 relu 算子为例,介绍算子库对接的基本流程:
-
查看 muDNN 是否支持该算子
relu属于一元算子,支持MUDNN_ACTIVATION_RELU模式
-
查找 PyTorch 中的 dispatch 定义
- 在
torch/include/ATen/native/native_functions.yaml中搜索relu:
- func: relu(Tensor self) -> Tensordevice_check: NoCheckvariants: function, methoddispatch:CPU, CUDA: reluMPS: relu_mps - 在
-
获取函数原型
- 在
torch/include/ATen/ops/relu.h中找到:Tensor relu(const at::Tensor & self)
- 在
-
注册 MTGPU 上的算子
#include <ATen/ATen.h>#include <mudnn/mudnn.h>at::Tensor musa_relu(const at::Tensor& input) {// 步骤 1: 创建输出 tensorat::Tensor result = at::native::empty_mtgpu(input.sizes(), c10::DeviceType::MTGPU, ...);// 步骤 2: 创建 muDNN 描述符mudnnHandle_t handle;mudnnTensorDescriptor_t input_desc, output_desc;mudnnActivationDescriptor_t activ_desc;mudnnCreate(&handle);mudnnCreateTensorDescriptor(&input_desc);mudnnCreateTensorDescriptor(&output_desc);mudnnCreateActivationDescriptor(&activ_desc);// 设置张量描述符mudnnSetTensorDescriptor(input_desc, MUDNN_DATA_FLOAT, input.dim(), input.sizes().data());mudnnSetTensorDescriptor(output_desc, MUDNN_DATA_FLOAT, result.dim(), result.sizes().data());// 设置激活函数参数mudnnSetActivationDescriptor(activ_desc, MUDNN_ACTIVATION_RELU, 0.0, 0.0);// 步骤 3: 获取数据指针void* input_ptr = input.data_ptr();void* output_ptr = result.data_ptr();// 步骤 4: 执行算子float alpha = 1.0f;mudnnActivationForward(handle, activ_desc, &alpha, input_desc, input_ptr, &alpha, output_desc, output_ptr);// 清理mudnnDestroyActivationDescriptor(activ_desc);mudnnDestroyTensorDescriptor(input_desc);mudnnDestroyTensorDescriptor(output_desc);mudnnDestroy(handle);return result;}// 注册算子TORCH_LIBRARY_IMPL(aten, MTGPU, m) {m.impl("relu", &musa_relu);}
由于 PyTorch 的 tensor 的 storage 具有 offset 属性,当多个 tensor 共用一块 storage 时,tensor 的 offset 可能不为零。此外,muDNN 目前不支持不连续的 tensor,因此在自定义函数中需要进行连续性检测和处理。
算子测试
算子注册完成后,需要进行正确性测试:
单算子测试:
import torch
import torch_musa
data = torch.randn(3, 4)
out = torch.relu(data)
with torch.autograd.inference_mode(mode=True):
data_mt = data.to("musa")
out_mt = torch.relu(data_mt)
out_c = out_mt.to("cpu")
# 比较 out 和 out_c 是否一致
模型测试:
import torch
import torch_musa
from torchvision.models import alexnet
model = alexnet(pretrained=True).eval()
model_mt = model.to("musa")
x = torch.rand((1, 3, 224, 224))
x_mt = x.to("musa")
with torch.autograd.inference_mode(mode=True):
y = model(x)
y_mt = model_mt(x_mt)
y_c = y_mt.to("cpu")
# 比较 y 和 y_c 是否一致
muTensorFlow - muDNN 对接扩展
Kernel 注册
在 TensorFlow 中,Op 类似于函数声明,Kernel 类似于函数实现。自定义 Kernel 需要继承从 OpKernel 类并重写 Compute 成员函数:
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
#include <mudnn/mudnn.h>
class VecAddOp : public tensorflow::OpKernel {
public:
explicit VecAddOp(tensorflow::OpKernelConstruction* context) : OpKernel(context) {}
void Compute(tensorflow::OpKernelContext* context) override {
const tensorflow::Tensor& input_0 = context->input(0);
const tensorflow::Tensor& input_1 = context->input(1);
// 检查输入数据类型是否相同
OP_REQUIRES(context, input_0.dtype() == input_1.dtype(),
tensorflow::errors::InvalidArgument("input data type should be the same!"));
tensorflow::Tensor* output = nullptr;
// 步骤 1: 给 output 分配内存空间
OP_REQUIRES_OK(context,
context->allocate_output(0, input_0.shape(), &output));
// 步骤 2: 创建 muDNN 描述符
mudnnHandle_t handle;
mudnnTensorDescriptor_t in1_desc, in2_desc, out_desc;
mudnnBinaryDescriptor_t binary_desc;
mudnnCreate(&handle);
mudnnCreateTensorDescriptor(&in1_desc);
mudnnCreateTensorDescriptor(&in2_desc);
mudnnCreateTensorDescriptor(&out_desc);
mudnnCreateBinaryDescriptor(&binary_desc);
// 设置张量描述符
mudnnSetTensorDescriptor(in1_desc, MUDNN_DATA_FLOAT, input_0.dims(), input_0.shape().data());
mudnnSetTensorDescriptor(in2_desc, MUDNN_DATA_FLOAT, input_1.dims(), input_1.shape().data());
mudnnSetTensorDescriptor(out_desc, MUDNN_DATA_FLOAT, output->dims(), output->shape().data());
// 设置二元算子参数
mudnnSetBinaryDescriptor(binary_desc, MUDNN_BINARY_ADD, MUDNN_DATA_FLOAT);
// 步骤 3: 获取数据指针
void* in1_ptr = input_0.flat<float>().data();
void* in2_ptr = input_1.flat<float>().data();
void* out_ptr = output->flat<float>().data();
// 步骤 4: 执行二元加法
float alpha = 1.0f;
mudnnBinary(handle, binary_desc, &alpha, in1_desc, in1_ptr, in2_desc, in2_ptr, &alpha, out_desc, out_ptr);
// 清理
mudnnDestroyBinaryDescriptor(binary_desc);
mudnnDestroyTensorDescriptor(in1_desc);
mudnnDestroyTensorDescriptor(in2_desc);
mudnnDestroyTensorDescriptor(out_desc);
mudnnDestroy(handle);
}
};
// 注册 Kernel
MUSA_KERNEL_REGISTER(VecAdd) {
REGISTER_KERNEL_BUILDER(tensorflow::Name("VecAdd").Device(tensorflow::DEVICE_MTGPU), VecAddOp);
}
对于自定义 kernel 的代码,需要将其放在 muTensorFlow/musa-plugin/src/kernels 目录下,随后在 muTensorFlow/musa-plugin 目录下调用 python setup.py install 完成整个 musa-plugin 的安装。
Op 注册
自定义 Op 和设备没有关系,参考 TensorFlow 的官方教程即可:
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/shape_inference.h"
using namespace tensorflow;
REGISTER_OP("VecAdd")
.Input("in1: int32") // 输入 0,类型为 int32
.Input("in2: int32") // 输入 1,类型为 int32
.Output("out: int32") // 输出 0,类型为 int32
.SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
c->set_output(0, c->input(0)); // 设置输出 0 的 shape
return tensorflow::Status::OK();
});
编译为动态库:
TF_CFLAGS=$(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_compile_flags()))')
TF_LFLAGS=$(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_link_flags()))')
g++ -std=c++14 -shared vec_add_ops.cc -o _vec_add_ops.so -fPIC ${TF_CFLAGS[@]} ${TF_LFLAGS[@]} -O2
加载动态库并使用:
import tensorflow as tf
vec_add_module = tf.load_op_library('./_vec_add_ops.so')
result = vec_add_module.vec_add([[1, 2], [3, 4]], [[1, 2], [3, 4]])
print(result)
图像预处理算子
muDNN 提供图像预处理算子,支持以下操作:
池化
- 最大池化:取邻域内的最大值
- 平均池化:取邻域内的平均值
插值
- 最邻近插值:赋予离目标点最近的原像素值
- 双线性插值:根据相邻的四个点进行插值
- 双三次插值:用 16 个相邻点做插值,效果更平滑
坐标变换
- 平移:将输入图像坐标平移
- 旋转:旋转图像坐标
- 缩放:缩放图像坐标
muDNN 图像预处理算子仅支持池化、插值、坐标变换,不支持其他操作。
API 参考
详细的 API 文档请参阅:
- muDNN C API 参考 - 推荐,长期支持
- muDNN C++ API 参考 - 已弃用,仅供参考

