在国产化替代的大背景下,海光DCU(Deep Computing Unit)已成为国内AI推理和训练场景中重要的GPU替代方案。海光DCU基于AMD GPU架构,使用ROCm作为底层软件栈,并在此基础上封装了DTK(DeepLook Toolkit)开发工具包。对于习惯了CUDA生态的开发者来说,从NVIDIA GPU迁移到海光DCU并非无痛——虽然HIP提供了兼容层,但实际工程中仍有大量细节需要处理。本文将系统梳理从CUDA迁移到海光DCU/ROCm/DTK的完整流程,涵盖环境搭建、代码迁移、PyTorch部署、推理框架适配以及常见踩坑点,帮助开发者少走弯路。

一、海光DCU架构与DTK开发环境概述
海光DCU是海光信息基于AMD CDNA架构设计的通用GPU产品线,典型型号包括DCU Z100、Z100L、K100等。与NVIDIA GPU使用CUDA不同,DCU使用ROCm(Radeon Open Compute)作为基础软件平台,海光在此基础上开发了DTK(DeepLook Toolkit),提供与CUDA类似的开发体验。
DTK的核心组件包括:
- HIP(Heterogeneous-compute Interface for Portability):CUDA-like的GPU编程接口,提供
1hipMalloc
、
1hipMemcpy等与CUDA函数一一对应的API
- hipcc编译器:基于Clang/LLVM的GPU编译器,替代nvcc
- MIOpen:深度学习卷积库,对应cuDNN
- rocBLAS:BLAS线性代数库,对应cuBLAS
- RCCL:多GPU通信库,对应NCCL
- ROCProfiler/ROCtracer:性能分析工具,对应Nsight Systems/Compute
理解这个对应关系是迁移工作的基础。下表列出了CUDA生态与ROCm/DTK生态的组件映射:
| CUDA生态 | ROCm/DTK生态 | 功能说明 |
|---|---|---|
| CUDA Runtime API | HIP Runtime API | GPU内存管理、核函数启动 |
| nvcc编译器 | hipcc编译器 | GPU代码编译 |
| cuDNN | MIOpen | 深度学习算子库 |
| cuBLAS | rocBLAS | 矩阵运算库 |
| NCCL | RCCL | 多卡集合通信 |
| Nsight Systems | ROCProfiler | 系统级性能分析 |
| Nsight Compute | ROCm Compute Profiler | 核函数级性能分析 |
| CUDA Streams | HIP Streams | 异步执行流 |
| cuMemAlloc | hipMalloc | 显存分配 |
二、DTK环境搭建与验证
海光DCU的DTK环境通常预装在海光定制的服务器上,操作系统一般为CentOS 7.9或Ubuntu 20.04/22.04。以下是环境验证和基本配置流程。
首先检查DCU硬件是否被正确识别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 # 查看DCU设备列表
/opt/rocm/bin/rocm-smi
# 预期输出示例(Z100)
# ==================== ROCm System Management Interface ====================
# ========================================================================
# GPU[0] : Z100
# GPU[1] : Z100
# ========================================================================
# 查看详细显存信息
/opt/rocm/bin/rocm-smi --showmeminfo vram
# 查看HIP运行时版本
hipconfig --version
# 预期输出: 5.x.x
配置环境变量,确保DTK工具链在PATH中:
1
2
3
4
5
6
7
8
9 # 添加到 ~/.bashrc 或 /etc/profile.d/dtk.sh
export DTK_HOME=/opt/dtk
export ROCM_PATH=/opt/rocm
export PATH=$ROCM_PATH/bin:$ROCM_PATH/hip/bin:$PATH
export LD_LIBRARY_PATH=$ROCM_PATH/lib:$ROCM_PATH/lib64:$DTK_HOME/lib:$LD_LIBRARY_PATH
# 验证hipcc编译器
hipcc --version
# 预期输出: HIP version 5.x.x, Clang version 15.x
编写一个简单的HIP程序验证环境是否正常:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 // test_hip.cpp - 验证DCU环境
#include <hip/hip_runtime.h>
#include <iostream>
__global__ void vector_add(float* a, float* b, float* c, int n) {
int idx = hipBlockIdx_x * hipBlockDim_x + hipThreadIdx_x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}
int main() {
int n = 1024;
float *a, *b, *c;
hipMalloc(&a, n * sizeof(float));
hipMalloc(&b, n * sizeof(float));
hipMalloc(&c, n * sizeof(float));
// 启动核函数
hipLaunchKernelGGL(vector_add, dim3((n+255)/256), dim3(256), 0, 0, a, b, c, n);
hipDeviceSynchronize();
std::cout << "HIP kernel executed successfully on DCU!" << std::endl;
hipFree(a);
hipFree(b);
hipFree(c);
return 0;
}
1
2
3
4 # 编译并运行
hipcc test_hip.cpp -o test_hip
./test_hip
# 预期输出: HIP kernel executed successfully on DCU!

三、CUDA到HIP的代码迁移:自动化与手动修复
HIP的设计理念是提供与CUDA近乎一一对应的API,使得大部分CUDA代码可以通过自动化工具转换。海光提供了
1 | hipify-perl |
和
1 | hipify-clang |
两个工具来完成自动转换。
3.1 使用hipify工具自动转换
1
2
3
4
5
6
7
8
9
10
11
12 # 使用hipify-perl进行文件级转换
hipify-perl cuda_kernel.cu > hip_kernel.cpp
# 批量转换整个项目
find . -name "*.cu" -o -name "*.cuh" | while read f; do
out_file=$(echo "$f" | sed 's/\.cu$/\.cpp/' | sed 's/\.cuh$/\.hpp/')
hipify-perl "$f" > "$out_file"
echo "Converted: $f -> $out_file"
done
# 使用hipify-clang进行更精确的转换(需要Clang)
hipify-clang cuda_kernel.cu --output=hip_kernel.cpp
hipify工具会进行以下自动替换:
| CUDA API | HIP API | 说明 |
|---|---|---|
| cudaMalloc | hipMalloc | 显存分配 |
| cudaMemcpy | hipMemcpy | 显存拷贝 |
| cudaFree | hipFree | 显存释放 |
| cudaDeviceSynchronize | hipDeviceSynchronize | 设备同步 |
| threadIdx.x | hipThreadIdx_x | 线程索引 |
| blockIdx.x | hipBlockIdx_x | 块索引 |
| blockDim.x | hipBlockDim_x | 块维度 |
| __global__ | __global__ | 核函数声明(保持不变) |
| cudaStreamCreate | hipStreamCreate | 创建流 |
3.2 需要手动处理的迁移问题
自动转换工具不能处理所有情况。以下是实际项目中常见的需要手动修复的问题:
问题1:CUDA核函数启动语法
CUDA的
1 | <<<grid, block, smem, stream>>> |
语法在HIP中不能直接使用,需要改为
1 | hipLaunchKernelGGL |
宏:
1
2
3
4
5
6
7
8
9
10 // CUDA原始代码
my_kernel<<<grid, block, smem_size, stream>>>(arg1, arg2);
// HIP转换后
hipLaunchKernelGGL(my_kernel, grid, block, smem_size, stream, arg1, arg2);
// 或者使用动态并行API
hipModuleLaunchKernel(kernel, gridX, gridY, gridZ,
blockX, blockY, blockZ,
smem, stream, args, NULL);
问题2:cuDNN到MIOpen的迁移
MIOpen的API与cuDNN有差异,不能简单替换头文件。主要区别在于卷积算法的选择和Workspace分配方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 // cuDNN方式
cudnnConvolutionForward(handle, &alpha, input_desc, input_data,
filter_desc, filter_data, conv_desc, algo, workspace, ws_size, &beta,
output_desc, output_data);
// MIOpen方式 - 需要先查找算法
miopenConvolutionForward(workspace, ws_size, &alpha,
input_desc, input_data, filter_desc, filter_data,
conv_desc, algo, &beta, output_desc, output_data);
// MIOpen需要显式查找最优算法
miopenConvolutionAlgoPerf_t perf;
int returnedAlgoCount;
miopenFindConvolutionForwardAlgorithm(handle, input_desc, input_data,
filter_desc, filter_data, conv_desc, output_desc, output_data,
1, &returnedAlgoCount, &perf, workspace, ws_size, false);
问题3:warp级原语差异
CUDA的
1 | __shfl_sync |
、
1 | __ballot_sync |
等warp级原语在HIP中有对应实现,但函数签名略有不同:
1
2
3
4
5
6 // CUDA
int val = __shfl_sync(0xffffffff, val, src_lane);
// HIP
int val = __shfl(val, src_lane, warpSize);
// 注意:HIP的__shfl不需要mask参数,但需要指定warpSize
四、PyTorch在海光DCU上的部署
海光提供了基于ROCm的PyTorch定制版本,安装方式与标准PyTorch略有不同。以下是完整的部署流程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 # 1. 创建conda环境
conda create -n dcu_pytorch python=3.10 -y
conda activate dcu_pytorch
# 2. 安装海光定制版PyTorch(从海光开发者社区下载对应DTK版本的whl包)
# DTK版本与PyTorch版本有严格对应关系,务必匹配
pip install torch-2.1.0+rocm5.7-cp310-cp310-linux_x86_64.whl
pip install torchvision-0.16.0+rocm5.7-cp310-cp310-linux_x86_64.whl
# 3. 验证PyTorch能否识别DCU
python3 -c "
import torch
print('PyTorch version:', torch.__version__)
print('HIP available:', torch.cuda.is_available())
print('DCU device count:', torch.cuda.device_count())
print('Device name:', torch.cuda.get_device_name(0))
# 预期输出:
# PyTorch version: 2.1.0+rocm5.7
# HIP available: True
# DCU device count: 2
# Device name: Z100
"
注意:PyTorch for ROCm保留了
1 | torch.cuda |
接口命名,内部会自动路由到HIP后端。这意味着大部分使用
1 | torch.cuda |
的代码可以不做修改直接运行,但某些CUDA特有功能(如CUDA Graph的某些参数)可能行为不同。
运行一个简单的模型推理测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# 在DCU上加载模型
device = "cuda" # ROCm后端使用cuda接口
model_name = "Qwen/Qwen2-7B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto",
trust_remote_code=True
)
# 推理测试
inputs = tokenizer("你好,请介绍一下海光DCU", return_tensors="pt").to(device)
with torch.no_grad():
outputs = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

五、vLLM推理框架在DCU上的适配
vLLM是目前最流行的大模型推理框架之一,海光已经提供了vLLM的DCU适配版本。以下是部署步骤和关键配置。
1
2
3
4
5
6
7
8
9
10
11
12 # 安装海光定制版vLLM(从海光开发者社区获取对应版本)
pip install vllm-dcu-0.4.2+rocm5.7-cp310-cp310-linux_x86_64.whl
# 启动vLLM推理服务
python3 -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2-7B-Instruct \
--dtype float16 \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.85 \
--max-model-len 32768 \
--port 8000 \
--trust-remote-code
关键参数说明:
| 参数 | 推荐值(Z100 32GB) | 说明 |
|---|---|---|
| tensor-parallel-size | 2 | 双卡张量并行,Z100单卡32GB可跑7B,14B需双卡 |
| gpu-memory-utilization | 0.85 | DCU显存利用率,留15%给系统开销 |
| max-model-len | 32768 | 最大上下文长度,根据显存调整 |
| dtype | float16 | DCU对FP16支持完善,BF16支持视型号而定 |
| enforce-eager | 不建议 | DCU的CUDA Graph支持有限,但vLLM已适配 |
使用curl测试推理服务:
1
2
3
4
5
6
7
8 curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen2-7B-Instruct",
"messages": [{"role": "user", "content": "用Python写一个快速排序"}],
"max_tokens": 500,
"temperature": 0.7
}' | python3 -m json.tool
六、常见踩坑记录与解决方案
在实际迁移过程中,以下问题是高频出现的,整理记录供参考。
踩坑1:BF16精度支持不一致
海光Z100基于CDNA 2架构(类似MI200),支持BF16。但早期型号Z100部分批次和K100仅支持FP16,不支持BF16。如果模型权重是BF16格式,需要转换为FP16:
1
2
3
4
5
6
7
8
9
10
11
12
13 # 检查DCU是否支持BF16
import torch
print('BF16 supported:', torch.cuda.is_bf16_supported())
# 如果不支持,将模型权重转换为FP16
model = model.to(torch.float16)
# 或者在加载时指定dtype
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16, # 而非bfloat16
device_map="auto"
)
踩坑2:RCCL多卡通信超时
多卡推理时,RCCL(对应NCCL)可能出现初始化超时。这通常是因为NCCL环境变量未正确设置。ROCm需要使用
1 | NCCL |
前缀的环境变量(RCCL兼容NCCL变量名):
1
2
3
4
5
6
7
8
9 # 设置RCCL环境变量
export NCCL_SOCKET_IFNAME=eth0 # 指定通信网卡
export NCCL_DEBUG=INFO # 开启调试日志
export NCCL_TIMEOUT=600 # 增加超时时间(秒)
export HSA_ENABLE_SDMA=1 # 启用SDMA加速拷贝
# 如果仍有问题,尝试使用TCP传输
export NCCL_NET_GDR_LEVEL=PHB
export NCCL_IB_DISABLE=1
踩坑3:Flash Attention兼容性
标准Flash Attention依赖CUDA特有指令,在DCU上无法直接运行。海光提供了Flash Attention的ROCm适配版本,但需要单独安装:
1
2
3
4
5
6
7
8
9
10 # 安装DCU版Flash Attention(从海光社区获取)
pip install flash_attn_dcu-2.5.6+rocm5.7-cp310-cp310-linux_x86_64.whl
# 如果没有DCU版Flash Attention,使用PyTorch原生attention
# 在vLLM中禁用Flash Attention
python3 -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2-7B-Instruct \
--dtype float16 \
--tensor-parallel-size 2 \
--use-flash-attn false # 禁用Flash Attention,回退到标准attention
踩坑4:hipcc编译器与nvcc的行为差异
hipcc基于Clang/LLVM,而nvcc基于EDG前端。两者在某些C++特性的处理上存在差异:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 // 问题代码:nvcc可以编译,hipcc报错
__device__ void func() {
// nvcc允许device代码中使用某些host-only的宏
// hipcc更严格,会报错
printf("value = %d\n", SOME_HOST_MACRO);
}
// 解决方案:显式使用HIP的device printf
#include <hip/hip_runtime.h>
__device__ void func() {
printf("value = %d\n", value); // hipcc支持device printf
}
// 编译时添加兼容性标志
hipcc -std=c++17 -fno-gpu-rdc --offload-arch=gfx90a kernel.cpp -o kernel
// gfx90a是Z100/MI200的架构代号,K100可能是gfx908
踩坑5:显存碎片与OOM
DCU的显存管理机制与NVIDIA GPU略有不同,长时间运行后可能出现显存碎片导致OOM。建议定期监控显存使用情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 # 定期监控DCU显存
watch -n 5 /opt/rocm/bin/rocm-smi --showmeminfo vram
# 在Python中主动管理显存碎片
import torch
import gc
def clear_cache():
gc.collect()
torch.cuda.empty_cache()
# DCU特有的显存整理
if hasattr(torch.cuda, 'mem_get_info'):
free, total = torch.cuda.mem_get_info()
print(f"VRAM: {free/1024**3:.1f}GB free / {total/1024**3:.1f}GB total")
七、性能对比与调优建议
以下是在相同模型(Qwen2-7B-Instruct)和相同输入条件下的推理性能对比数据,供参考。测试环境为双卡配置,batch_size=1,输出512 tokens。
| 硬件配置 | 框架 | Prefill延迟(ms) | Decode吞吐(tok/s) | 显存占用(GB) |
|---|---|---|---|---|
| NVIDIA A100 80GB x2 | vLLM 0.4.2 | ~45 | ~2200 | ~28 |
| 海光Z100 32GB x2 | vLLM-DCU 0.4.2 | ~68 | ~1400 | ~26 |
| 海光K100 64GB x2 | vLLM-DCU 0.4.2 | ~58 | ~1600 | ~27 |
从数据可以看出,Z100的性能约为A100的60-65%,K100约为70-75%。虽然绝对性能有差距,但考虑到国产化替代的政策要求和供应链安全,DCU在国产化场景下是可行的选择。
调优建议:
- 优先使用FP16:DCU对FP16的优化最为成熟,BF16和INT8的支持因型号而异
- 合理设置gpu-memory-utilization:建议0.80-0.85,留足余量避免OOM
- 启用PagedAttention:vLLM-DCU已适配PagedAttention,能显著提升显存利用率,更多PagedAttention原理可参考PagedAttention深度解析
- 使用ROCProfiler定位瓶颈:用
1rocprof
工具分析核函数执行时间,类似TFLOPS算力计算中的性能分析方法
- 注意通信开销:多卡场景下RCCL的AllReduce性能不如NCCL,建议在模型较小时优先单卡部署
总结
从CUDA迁移到海光DCU/ROCm/DTK的核心路径是:使用hipify工具完成自动转换,手动修复核函数启动语法和库API差异,安装DCU定制版PyTorch和vLLM,验证BF16/Flash Attention等特性兼容性,调优显存和通信参数。整个迁移过程中最耗时的不是代码转换本身,而是各种库版本匹配和性能调优。建议在项目初期就建立完整的测试基准,逐步验证每个模块的迁移效果,避免到最后才发现某个关键算子不支持。
对于正在进行国产化迁移的团队,海光DCU是目前CUDA生态兼容性最好的国产GPU方案之一,HIP的API设计使得迁移成本相对可控。但随着项目深入,仍需关注华为昇腾等其他国产GPU的适配经验和FP8量化训练等前沿技术在不同硬件平台上的支持情况,为多平台部署做好准备。
汤不热吧