技术博客

CUDA迁移到海光DCU实战:HIP编程模型、hipify工具与常见坑

系统讲解从NVIDIA CUDA迁移到海光DCU(ROCm/HIP)的完整实战流程:hipify-perl自动转换工具使用、HIP与CUDA API对照表、PyTorch/DeepSpeed DCU适配方法、分布式训练NCCL→RCCL迁移、迁移后性能调优(rocprof/rocblas调参),以及迁移难度评估方法论。

海光DCUCUDAHIPROCm迁移AI基础设施国产GPU

CUDA 迁移到 DCU 是国产算力替换的核心挑战。好消息是:海光 DCU 基于 AMD ROCm 架构,有成熟的 hipify 迁移工具;大多数 PyTorch/TensorFlow 上层代码不需要修改。本文聚焦迁移实战,尤其是那些工具无法自动处理的部分。

迁移难度快速评估

可以零改动直接跑(约 40% 的场景):
  - 使用标准 PyTorch API(不涉及自定义 CUDA Kernel)
  - DeepSpeed / Megatron 框架(ROCm 版本已适配)
  - vLLM / llama.cpp 推理(有 ROCm 分支)

需要少量改动(约 40% 的场景):
  - 有自定义 CUDA Kernel,但使用标准 CUDA Runtime API
  - 使用 NCCL 通信(改为 RCCL 或兼容层)
  - 有 PTX 相关代码(需要改为 GCN ISA 或 HIP 实现)

需要大量改动(约 20% 的场景):
  - 使用 NVIDIA 专有库(cuML、cuGraph、TensorRT)
  - 深度依赖 Tensor Core 特定内置函数
  - PTX 汇编级优化

一、hipify 工具使用

hipify-perl(快速转换)

# hipify-perl 基于正则替换,速度快,适合快速评估和批量转换
which hipify-perl  # 在 DTK 安装后可用:/opt/dtk/bin/hipify-perl

# 转换单个文件(生成新文件)
hipify-perl matrix_mul.cu > matrix_mul.hip.cpp

# 原地修改(修改原文件)
hipify-perl --inplace matrix_mul.cu

# 批量转换整个项目
find ./src -name "*.cu" -o -name "*.cuh" | while read f; do
  hipify-perl --inplace "$f"
  # .cu 文件重命名为 .cpp(HIP 代码不需要 .cu 扩展名)
  mv "$f" "${f%.cu}.cpp" 2>/dev/null || true
done

# 查看转换统计(有哪些 API 未能自动转换)
hipify-perl --print-stats my_kernel.cu 2>&1
# ...
# hipSuccess                -> hipSuccess               [ CUDA Runtime ]
# cudaMalloc                -> hipMalloc                [ CUDA Runtime ]
# [UNRESOLVED] cusolverDnCreate  ← 未支持的 API

hipify-clang(精确转换)

# hipify-clang 基于 Clang AST,更精确,需要编译配置
# 适合有复杂模板、宏展开的代码

hipify-clang --cuda-path=/usr/local/cuda \
             -o output_dir/ \
             input_file.cu \
             -- -I./include -std=c++17

# 生成 compile_commands.json 辅助 hipify-clang
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .
hipify-clang --compilation-database=compile_commands.json *.cu

二、CUDA → HIP API 核心对照

// ====== 内存管理 ======
cudaMalloc()           → hipMalloc()
cudaFree()             → hipFree()
cudaMemcpy()           → hipMemcpy()
cudaMemset()           → hipMemset()
cudaMallocHost()       → hipHostMalloc()    // 锁页内存
cudaFreeHost()         → hipHostFree()

// ====== 设备管理 ======
cudaGetDeviceCount()   → hipGetDeviceCount()
cudaSetDevice()        → hipSetDevice()
cudaDeviceSynchronize() → hipDeviceSynchronize()
cudaGetDeviceProperties() → hipGetDeviceProperties()

// ====== Stream ======
cudaStream_thipStream_t
cudaStreamCreate()     → hipStreamCreate()
cudaStreamDestroy()    → hipStreamDestroy()
cudaStreamSynchronize() → hipStreamSynchronize()

// ====== 事件(用于计时)======
cudaEvent_thipEvent_t
cudaEventCreate()      → hipEventCreate()
cudaEventRecord()      → hipEventRecord()
cudaEventElapsedTime() → hipEventElapsedTime()

// ====== 错误处理 ======
cudaError_thipError_t
cudaSuccess            → hipSuccess
cudaGetErrorString()   → hipGetErrorString()
cudaGetLastError()     → hipGetLastError()

// ====== Kernel 启动(语法不变!)======
myKernel<<<grid, block, sharedMem, stream>>>(args);  // 完全相同

// ====== 内置变量(不变)======
threadIdx.x / blockIdx.x / blockDim.x / gridDim.x  // 完全相同
__shared__ / __device__ / __global__                  // 完全相同

三、PyTorch 代码迁移

大多数情况下,PyTorch 代码几乎不需要修改

# 原 CUDA 代码
import torch

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = MyModel().to(device)
x = torch.randn(32, 512).to(device)
output = model(x)

# 迁移到 DCU:代码完全不变!
# DCU 通过 HIP 透明支持 torch.cuda API
# 只需确保使用的是 DCU 版 PyTorch
# 需要修改的情况:自定义 CUDA Extension
# 原来:
import torch.utils.cpp_extension as ext
ext.load_inline(
    name='my_op',
    cuda_sources=['my_kernel.cu'],    # CUDA 代码
    ...
)

# 迁移后:
ext.load_inline(
    name='my_op',
    cuda_sources=['my_kernel.hip.cpp'],  # 转换后的 HIP 代码
    extra_compile_args={'cxx': ['-D__HIP_PLATFORM_HCC__']},
    ...
)

四、分布式训练迁移(NCCL → RCCL)

# PyTorch 分布式训练(DDP)迁移

# 原 CUDA 代码(使用 NCCL 后端):
import torch.distributed as dist
dist.init_process_group(backend='nccl')

# DCU 代码:使用 rccl 后端(或 gloo 也可)
# 方法1:直接换后端(需要 RCCL 安装)
dist.init_process_group(backend='rccl')

# 方法2:设置环境变量(RCCL 自动接管 NCCL 调用,代码不变)
# export NCCL_SOCKET_IFNAME=eth0
# 部分 DTK 版本支持 NCCL-RCCL 透明兼容
# 多卡训练启动命令(不变)
torchrun \
    --nproc_per_node=8 \
    --master_addr=127.0.0.1 \
    --master_port=29500 \
    train.py

# 检查 RCCL 通信
export RCCL_DEBUG=INFO
export RCCL_DEBUG_SUBSYS=ALL
torchrun ... train.py 2>&1 | grep "RCCL\|INFO\|WARN" | head -30

五、DeepSpeed DCU 适配

# 安装 DeepSpeed ROCm 版本
pip install deepspeed  # 新版已原生支持 ROCm,自动检测

# 如果自动检测失败,强制指定
export DS_BUILD_SPARSE_ATTN=0
export DS_BUILD_CCL_COMM=0
pip install deepspeed --global-option="build_ext" --global-option="--use-rocm"

# 验证 DeepSpeed DCU 支持
python3 -c "
import deepspeed
print(deepspeed.__version__)
print(deepspeed.ops.op_builder.AsyncIOBuilder().is_compatible())
"
# DeepSpeed ZeRO 训练配置(DCU 版本)
ds_config = {
    "train_micro_batch_size_per_gpu": 4,
    "gradient_accumulation_steps": 8,
    "bf16": {"enabled": True},        # 昇腾/DCU 都支持 BF16
    "zero_optimization": {
        "stage": 3,
        "offload_optimizer": {
            "device": "cpu"
        }
    },
    "communication_data_type": "bf16",
    # DCU 注意:去掉 NVIDIA 特有的 autotuning 配置
}

六、迁移后性能调优

# 使用 rocprof 分析性能(类似 Nsight)
rocprof --stats -o profile_output.csv python3 train.py

# 查看 Kernel 执行时间排行
sort -t',' -k4 -nr profile_output.csv | head -20
# 找出耗时最多的 Kernel

# 检查 VRAM 带宽利用率
rocprof --hsa-trace python3 train.py
# 分析 memory_copy / memory_bandwidth 指标

# hipBLAS 性能调优(矩阵乘法)
# 环境变量调整 GEMM 算法
export ROCBLAS_TENSILE_LIBRARY_PATH=/opt/dtk/lib/
# 使用 rocblas-bench 对比不同矩阵规格下的性能
rocblas-bench -f gemm --transposeA N --transposeB N \
    -m 4096 -n 4096 -k 4096 \
    --alpha 1 --beta 0 \
    -i 100   # 测试 GEMM 性能

# MIOpen 算子调优(卷积/注意力)
# 自动调优(首次运行慢,之后使用缓存)
export MIOPEN_ENABLE_LOGGING=1
export MIOPEN_FIND_ENFORCE=3   # 枚举所有算法找最优
python3 train_epoch_1.py   # 生成 MIOpen 调优缓存

# 查看调优缓存位置
ls ~/.config/miopen/

七、迁移验证清单

# 迁移后必须验证:

# 1. 数值精度(最重要!)
python3 validate_precision.py
# 对比 CUDA vs DCU 输出的最大绝对误差
# FP32: 误差 < 1e-5 为正常
# BF16/FP16: 误差 < 1e-2 为正常

# 2. 训练收敛性
# 运行 100 步对比 loss 曲线(应该趋势一致)

# 3. 多卡通信正确性
# 运行梯度同步验证脚本

# 4. 内存泄漏检查
# 运行 3-5 个 epoch,观察 VRAM 使用是否持续增长

# 5. 性能基线
# MFU(模型利用率)= 实际 TFLOPS / 峰值 TFLOPS
# 目标:DCU MFU 应 > 30%(相对 NVIDIA 差距在 2 倍以内属于正常)

小结

CUDA → DCU 迁移的最大优势是 HIP 透明兼容:PyTorch 层面的代码通常零改动,torch.cuda.* API 直接可用。真正需要工作的是:自定义 CUDA Kernel(用 hipify 转换)和多卡通信(NCCL 换 RCCL)。迁移的核心风险不在于能不能跑,而在于精度是否一致性能是否可接受——建议每次迁移后都做精度对比测试,并测量 MFU,确保相对 NVIDIA 的性能差距在业务可接受范围内。