欢迎光临

海光DCU开发实战全攻略:从CUDA代码迁移到ROCm/DTK适配的完整踩坑记录

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

海光DCU GPU硬件与开发环境

一、海光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编程接口,提供
    1
    hipMalloc

    、

    1
    hipMemcpy

    等与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!

DTK开发环境配置与代码编译

三、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
&lt;&lt;&lt;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))

PyTorch模型在海光DCU上的推理部署

五、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在国产化场景下是可行的选择。

调优建议:

  1. 优先使用FP16:DCU对FP16的优化最为成熟,BF16和INT8的支持因型号而异
  2. 合理设置gpu-memory-utilization:建议0.80-0.85,留足余量避免OOM
  3. 启用PagedAttention:vLLM-DCU已适配PagedAttention,能显著提升显存利用率,更多PagedAttention原理可参考PagedAttention深度解析
  4. 使用ROCProfiler定位瓶颈:用
    1
    rocprof

    工具分析核函数执行时间,类似TFLOPS算力计算中的性能分析方法

  5. 注意通信开销:多卡场景下RCCL的AllReduce性能不如NCCL,建议在模型较小时优先单卡部署

总结

从CUDA迁移到海光DCU/ROCm/DTK的核心路径是:使用hipify工具完成自动转换,手动修复核函数启动语法和库API差异,安装DCU定制版PyTorch和vLLM,验证BF16/Flash Attention等特性兼容性,调优显存和通信参数。整个迁移过程中最耗时的不是代码转换本身,而是各种库版本匹配和性能调优。建议在项目初期就建立完整的测试基准,逐步验证每个模块的迁移效果,避免到最后才发现某个关键算子不支持。

对于正在进行国产化迁移的团队,海光DCU是目前CUDA生态兼容性最好的国产GPU方案之一,HIP的API设计使得迁移成本相对可控。但随着项目深入,仍需关注华为昇腾等其他国产GPU的适配经验和FP8量化训练等前沿技术在不同硬件平台上的支持情况,为多平台部署做好准备。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 海光DCU开发实战全攻略:从CUDA代码迁移到ROCm/DTK适配的完整踩坑记录
分享到: 更多 (0)