前言

昇腾NPU插在主板上,PyTorch能直接调用它跑模型,这个过程是怎么实现的?驱动扮演了什么角色?为什么驱动版本不对,NPU就跑不起来?

第一次研究昇腾驱动的时候,也被它的分层架构搞得很懵。从PyTorch调用到NPU执行,中间经过了哪些层?每层负责什么?为什么有时候"驱动装对了,但NPU还是不可用"?

带着这个疑问,深入研究了driver仓库的源码,发现driver不是简单的"硬件驱动",而是一个分层软件栈,包括HDF(Hardware Driver Foundation)、HDK(Hardware Development Kit)、Runtime Library三层,每层各司其职,共同完成"让PyTorch能用NPU"这个目标。

本文是概念拆解——会拆开driver的分层架构、核心模块、常见问题,解释为什么driver是昇腾CANN的基础层,以及为什么它出问题会影响整个栈。

driver在CANN五层架构里的位置

先说清楚driver住在哪。昇腾CANN的架构分五层,driver住在第5层——昇腾计算基础层,是整个栈的最底层。

第1层:昇腾计算语言层 AscendCL
  └─ 算子开发接口 Ascend C

第2层:昇腾计算服务层
  ├─ AOL 算子库
  ├─ AOE 调优引擎
  └─ Framework Adaptor 框架适配器

第3层:昇腾计算编译层
  ├─ Graph Compiler 图编译器
  └─ BiSheng / ATC 编译器

第4层:昇腾计算执行层
  ├─ Runtime 运行时
  ├─ Graph Executor 图执行器
  ├─ HCCL 集合通信库
  └─ AIPP / DVPP

第5层:昇腾计算基础层 ← driver 住在这
  ├─ RMS(Resource Management Service)
  ├─ CMS(Configuration Management Service)
  ├─ DMS(Device Management Service)
  ├─ DRV(DRiVer,驱动核心)
  └─ UTILITY

硬件层:昇腾 AI 硬件(达芬奇架构)

为啥住第5层?因为driver是"硬件抽象层",是连接软件栈和物理硬件的桥梁。没有driver,软件栈就不知道硬件长什么样,更不知道怎么控制硬件。

依赖关系

driver → hardware(NPU)。driver直接控制NPU硬件,是整个栈的最底层。上层的HCCS、Runtime、AscendCL都依赖driver提供的接口。

分层架构:driver的三层结构

driver不是一层代码,而是三层代码:HDF层HDK层Runtime Library层

第1层:HDF(Hardware Driver Foundation)

HDF是驱动框架层,定义了驱动的标准框架和接口规范。所有的昇腾驱动都要遵循HDF规范,这样才能被上层软件统一管理。

核心模块

// HDF核心:设备管理
class HdfDeviceManager {
public:
    // 加载设备驱动
    static int LoadDevice(const char* device_name);
    
    // 卸载设备驱动
    static int UnloadDevice(const char* device_name);
    
    // 获取设备句柄
    static void* GetDevice(const char* device_name);
};

// HDF核心:驱动服务
class HdfDriverService {
public:
    // 初始化驱动服务
    virtual int Initialize() = 0;
    
    // 销毁驱动服务
    virtual int Release() = 0;
    
    // 绑定设备
    virtual int BindDevice(struct Device* device) = 0;
    
    // 解绑设备
    virtual int UnbindDevice(struct Device* device) = 0;
};

// NPU驱动服务
class NpuDriverService : public HdfDriverService {
public:
    int Initialize() override;
    int Release() override;
    int BindDevice(struct Device* device) override;
    int UnbindDevice(struct Device* device) override;
    
    // NPU特有接口
    int SubmitTask(struct Task* task);
    int QueryTask(uint64_t task_id);
    int SyncDevice();
};

关键点

  • HdfDeviceManager:设备管理器,负责加载/卸载设备驱动
  • HdfDriverService:驱动服务基类,定义了驱动的生命周期接口
  • NpuDriverService:NPU驱动服务,实现了NPU特有的接口

踩坑预警:HDF层是内核态代码,普通开发者不要直接修改,看看就好。

第2层:HDK(Hardware Development Kit)

HDK是硬件开发工具包,提供了驱动开发的常用工具和接口。开发者基于HDK,可以快速开发新的驱动或者扩展现有驱动。

核心模块

// HDK核心:内存管理
class HdkMemory {
public:
    // 分配设备内存
    static void* AllocDeviceMem(size_t size);
    
    // 释放设备内存
    static void FreeDeviceMem(void* ptr);
    
    // 分配Host内存(锁页内存,用于DMA)
    static void* AllocHostMem(size_t size);
    
    // 释放Host内存
    static void FreeHostMem(void* ptr);
    
    // 内存拷贝(Device ↔ Host)
    static void Memcpy(void* dst, const void* src, size_t size);
};

// HDK核心:任务调度
class HdkTask {
public:
    // 创建任务
    static uint64_t CreateTask(struct TaskConfig* config);
    
    // 提交任务
    static int SubmitTask(uint64_t task_id);
    
    // 等待任务完成
    static int WaitTask(uint64_t task_id, uint32_t timeout_ms);
    
    // 取消任务
    static int CancelTask(uint64_t task_id);
};

// HDK核心:事件管理
class HdkEvent {
public:
    // 创建事件
    static uint64_t CreateEvent();
    
    // 等待事件
    static int WaitEvent(uint64_t event_id, uint32_t timeout_ms);
    
    // 触发事件
    static int SignalEvent(uint64_t event_id);
    
    // 销毁事件
    static int DestroyEvent(uint64_t event_id);
};

关键点

  • HdkMemory:内存管理,封装了设备内存、Host内存、DMA拷贝的接口
  • HdkTask:任务调度,封装了任务创建、提交、等待、取消的接口
  • HdkEvent:事件管理,封装了事件创建、等待、触发、销毁的接口

踩坑预警:HDK层是内核态代码,普通开发者不要直接修改,看看就好。

第3层:Runtime Library(用户态驱动接口)

Runtime Library是用户态驱动接口,是普通开发者接触driver的唯一途径。PyTorch、AscendCL、Runtime都通过Runtime Library和driver交互。

核心模块

// Runtime Library核心:设备管理
class DeviceManager {
public:
    // 初始化设备管理
    static int Init();
    
    // 销毁设备管理
    static int Destroy();
    
    // 获取设备数量
    static int GetDeviceCount();
    
    // 设置当前设备
    static int SetDevice(int device_id);
    
    // 获取当前设备
    static int GetDevice();
    
    // 同步设备
    static int SyncDevice();
};

// Runtime Library核心:内存管理
class MemoryManager {
public:
    // 分配设备内存
    static void* Alloc(size_t size);
    
    // 释放设备内存
    static void Free(void* ptr);
    
    // 分配锁页内存(用于DMA)
    static void* AllocPinned(size_t size);
    
    // 释放锁页内存
    static void FreePinned(void* ptr);
    
    // 内存拷贝(同步)
    static void Memcpy(void* dst, const void* src, size_t size);
    
    // 内存拷贝(异步)
    static void MemcpyAsync(void* dst, const void* src, size_t size, Stream stream);
};

// Runtime Library核心:流管理
class StreamManager {
public:
    // 创建流
    static Stream CreateStream();
    
    // 销毁流
    static int DestroyStream(Stream stream);
    
    // 同步流
    static int SyncStream(Stream stream);
    
    // 等待流
    static int StreamWait(Stream stream, Event event);
};

关键点

  • DeviceManager:设备管理,初始化driver、设置当前设备、同步设备
  • MemoryManager:内存管理,分配/释放设备内存、锁页内存、DMA拷贝
  • StreamManager:流管理,创建/销毁/同步流

踩坑预警:Runtime Library是用户态代码,可以直接调用,但要确保driver版本匹配。

核心能力:driver到底能干啥?

driver的核心能力分4类:设备管理内存管理任务调度中断处理

1. 设备管理

设备管理是"让软件知道NPU存在"。driver负责枚举NPU设备、加载驱动、初始化设备。

代码讲解

import acl

# 初始化ACL(Ascend Computing Language)
acl.init()

# 获取设备数量
device_count = acl.get_device_count()
print(f"NPU设备数量: {device_count}")

# 分配设备
device_id = 0
acl.rt.set_device(device_id)

# 释放设备
acl.rt.reset_device(device_id)

# 销毁ACL
acl.finalize()

代码讲解

  • acl.init():初始化ACL,加载driver
  • acl.get_device_count():获取NPU设备数量
  • acl.rt.set_device():分配设备
  • acl.rt.reset_device():释放设备

踩坑预警:分配设备后一定要释放,不然会资源泄漏。

2. 内存管理

内存管理是"让软件能往NPU上写数据"。driver负责分配设备内存、Host内存、DMA拷贝。

代码讲解

import numpy as np
import acl

# 分配设备内存
size = 1024 * 4  # 1024个float32
ptr = acl.rt.malloc(size, acl.RT_MEM_MALLOC_NORMAL_ONLY)
print(f"设备内存地址: {hex(ptr)}")

# 从numpy数组拷贝数据到设备
host_data = np.random.randn(1024).astype(np.float32)
acl.rt.memcpy(ptr, size, host_data.ctypes.data, size, acl.RT_MEMCPY_HOST_TO_DEVICE)

# 从设备拷贝数据回numpy数组
result = np.empty(1024, dtype=np.float32)
acl.rt.memcpy(result.ctypes.data, size, ptr, size, acl.RT_MEMCPY_DEVICE_TO_HOST)
print(f"结果: {result[:5]}")

# 释放设备内存
acl.rt.free(ptr)

代码讲解

  • acl.rt.malloc():分配设备内存
  • acl.rt.memcpy():DMA拷贝(H2D或D2H)
  • acl.rt.free():释放设备内存

踩坑预警:设备内存要手动释放,不然会内存泄漏。

3. 任务调度

任务调度是"让软件能往NPU上提交计算任务"。driver负责创建任务、提交任务、等待任务完成。

代码讲解

import acl

# 创建流
stream, ret = acl.rt.create_stream()
print(f"流创建成功: {stream}")

# 创建任务(以VectorAdd为例)
task_config = {
    "op_type": "VectorAdd",
    "input_x": ptr_x,
    "input_y": ptr_y,
    "output_z": ptr_z,
    "count": 1024
}
task_id = acl.rt.launch_task(stream, task_config)
print(f"任务提交成功: {task_id}")

# 等待任务完成
acl.rt.stream_synchronize(stream)
print(f"任务执行完成")

# 销毁流
acl.rt.destroy_stream(stream)

代码讲解

  • acl.rt.create_stream():创建流
  • acl.rt.launch_task():提交任务
  • acl.rt.stream_synchronize():等待流完成

踩坑预警:提交任务后一定要等待完成,不然结果还没算出来就读了。

4. 中断处理

中断处理是"让NPU能主动通知CPU"。driver负责注册中断处理函数、处理中断、唤醒等待的CPU核。

代码讲解

import acl

# 注册中断回调函数
def interrupt_callback(interrupt_type, task_id, user_data):
    print(f"中断类型: {interrupt_type}, 任务ID: {task_id}")
    # 处理中断...

# 注册中断回调
acl.rt.register_interrupt_callback(interrupt_callback, None)

# 提交一个需要中断通知的任务
task_id = acl.rt.launch_task_with_interrupt(stream, task_config)

# CPU继续做其他事情,等中断唤醒
print("CPU继续做其他事情...")
# 等NPU发来中断...

代码讲解

  • acl.rt.register_interrupt_callback():注册中断回调函数
  • acl.rt.launch_task_with_interrupt():提交需要中断通知的任务

踩坑预警:中断回调函数要尽快返回,不然会影响其他中断。

常见问题:driver出错了怎么办?

问题1:驱动版本不匹配

现象:运行acl.init(),报错说driver version mismatch

原因:CANN版本和driver版本不匹配。

解决:去昇腾社区下载和CANN版本匹配的driver。

# 查看当前driver版本
cat /usr/local/Ascend/driver/version.info

# 查看CANN版本
atc --version

# 如果版本不匹配,重新安装匹配的driver
bash ascend-installer-8.0.run --full --driver

问题2:设备不可用

现象:运行acl.rt.set_device(0),报错说device not found

原因:driver没加载,或者NPU硬件故障。

解决:检查driver加载状态和NPU硬件状态。

# 检查driver是否加载
lsmod | grep ascend

# 如果没加载,加载driver
modprobe ascend_driver

# 检查NPU硬件状态
npu-smi info

# 如果NPU硬件故障,重启机器或者联系售后

问题3:内存分配失败

现象:运行acl.rt.malloc(),报错说out of memory

原因:设备内存不足,或者内存碎片太多。

解决:释放不必要的内存,或者重启进程。

# 检查设备内存使用情况
mem_info = acl.rt.get_mem_info(acl.RT_MEM_INFO_TOTAL)
print(f"设备总内存: {mem_info['total'] / 1024**3:.2f} GB")

mem_info = acl.rt.get_mem_info(acl.RT_MEM_INFO_USED)
print(f"设备已用内存: {mem_info['used'] / 1024**3:.2f} GB")

# 释放不必要的内存
del large_tensor  # Python层面释放
acl.rt.reset_device(0)  # 重置设备,释放所有设备内存

仓库链接:https://atomgit.com/cann/driver

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐