跳到主要内容

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_tmuDNN 设备句柄,绑定到 MUSA 设备与流
mudnnTensorDescriptor_t张量描述符,描述数据地址、类型、格式、维度
mudnnFilterDescriptor_t滤波器/权重描述符
mudnnCreateXXXDescriptor创建各类描述符
mudnnDestroyXXXDescriptor销毁各类描述符

张量算子

算子函数前缀
一元算子mudnnUnary
二元算子mudnnBinary
三元算子mudnnTernary
拼接mudnnConcat
填充mudnnFill
置换mudnnPermute
归约mudnnReduce

mudnn_cnn.h - 神经网络算子

算子类型函数前缀
卷积mudnnConvolution
池化mudnnPooling
归一化mudnnBatchNorm, mudnnLayerNorm, mudnnGroupNorm
激活mudnnActivation, mudnnSoftmax
DropoutmudnnDropout

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 keydispatch table 中找到对应的 kernel 函数指针,最后回调执行。

dispatch key 不仅有硬件后端,还有一些更抽象的概念如 autogradtracing 等。当同一个算子有不同 dispatch key 的实现时,PyTorch 会调用优先级最高的 dispatch key 对应的 kernel 实现。

设备注册流程

要让 PyTorch 支持 MTGPU 设备,需要完成以下步骤:

  1. 添加 backend

    • torch/c10/core/Backend.henum class Backend 中添加 MUSA
    • backendToDispatchKey() 中将 backendDispatchKey(PrivateUse1) 绑定
    • backendToDeviceType() 中将 backendDeviceType(MTGPU) 绑定
  2. 添加 device

    • torch/c10/core/DeviceType.henum class DeviceType 中添加 MTGPU
    • torch/c10/core/Device.hstruct Device 中添加 is_mtgpu()
    • torch/c10/core/DispatchKey.cpptoBackendComponent() 中添加 MTGPU 对应的 BackendComponent
    • torch/c10/core/TensorOptions.hcomputeDispatchKey() 中根据 MTGPU 获得对应的 DispatchKey
    • torch/torch/library.hdispatch 中将 Device_type MTGPUDispatchKey::PrivateUse1 绑定

算子对接流程

relu 算子为例,介绍算子库对接的基本流程:

  1. 查看 muDNN 是否支持该算子

    • relu 属于一元算子,支持 MUDNN_ACTIVATION_RELU 模式
  2. 查找 PyTorch 中的 dispatch 定义

    • torch/include/ATen/native/native_functions.yaml 中搜索 relu
    - func: relu(Tensor self) -> Tensor
    device_check: NoCheck
    variants: function, method
    dispatch:
    CPU, CUDA: relu
    MPS: relu_mps
  3. 获取函数原型

    • torch/include/ATen/ops/relu.h 中找到:Tensor relu(const at::Tensor & self)
  4. 注册 MTGPU 上的算子

    #include <ATen/ATen.h>
    #include <mudnn/mudnn.h>

    at::Tensor musa_relu(const at::Tensor& input) {
    // 步骤 1: 创建输出 tensor
    at::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 文档请参阅: