runtime/dcmi/ — 超深度逐行分析

三个文件:dcmi.go (360行) + dcmi_api.go (101行) + dcManager.go (294行) = 755行
核心职责:NPU设备管理接口封装,通过CGO调用libdcmi.so


一、模块定位

1.1 业务职责

runtime/dcmi/ 是设备管理抽象层,负责:

  1. 驱动版本自适应 — 自动检测V1/V2驱动,选择匹配的Worker
  2. 设备查询 — 获取卡列表、设备列表、芯片信息、产品类型
  3. 虚拟设备管理 — 创建/销毁vNPU(虚拟NPU切分)
  4. 物理ID↔逻辑ID转换 — 将用户指定的物理设备ID转换为驱动内部逻辑ID
  5. CGO桥接 — 封装所有对libdcmi.so的C语言调用

1.2 在系统中的位置

外部

dcmi模块

调用方

GetMatchingNpuWorker

CreateVDevice

GetChipName/GetProductType

CGO C.DcmiXXX

runtime/process/process.go

dcmi_api.go
顶层API
CreateVDevice

dcmi.go
Worker接口+V1/V2实现
设备查询/虚拟设备

dcManager.go
CGO封装
直接调用libdcmi.so

libdcmi.so
NPU驱动C库

NPU硬件


二、dcmi_api.go 逐行解析(101行)

2.1 数据结构

type VDeviceInfo struct {
    CardID    int32
    DeviceID  int32
    VdeviceID int32
}
字段 含义 示例
CardID NPU卡ID(物理插槽) 0
DeviceID 卡内设备ID 1
VdeviceID 虚拟设备ID(切分后生成) 42

VDeviceInfo

+int32 CardID

+int32 DeviceID

+int32 VdeviceID

«interface»

WorkerInterface

+Initialize() : error

+ShutDown()

+CreateVDevice(uniqueID, coreNum)(VDeviceInfo, error)

+DestroyVDevice(uniqueID, vDevID) : error

+GetProductType()(string, error)

+GetChipName()(string, error)

2.2 WorkerInterface 接口

type WorkerInterface interface {
    Initialize() error
    ShutDown()
    CreateVDevice(uniqueID int32, coreNum string) (VDeviceInfo, error)
    DestroyVDevice(uniqueID int32, vDevID int32) error
    GetProductType() (string, error)
    GetChipName() (string, error)
}

6个方法的职责

方法 输入 输出 用途
Initialize error 初始化dcmi库连接
ShutDown 关闭dcmi库连接
CreateVDevice uniqueID(物理设备ID), coreNum(切分模板) VDeviceInfo, error 创建虚拟NPU
DestroyVDevice uniqueID, vDevID(虚拟设备ID) error 销毁虚拟NPU
GetProductType string, error 获取产品型号
GetChipName string, error 获取芯片名称

设计意图:策略模式接口,V1和V2驱动各自实现此接口,调用方无需关心驱动版本。

2.3 CreateVDevice 顶层API

func CreateVDevice(w WorkerInterface, spec *specs.Spec, devices []int) (VDeviceInfo, error) {
    invalidVDevice := VDeviceInfo{CardID: -1, DeviceID: -1, VdeviceID: -1}
    if spec == nil {
        return invalidVDevice, fmt.Errorf("spec is nil")
    }
    splitDevice, err := extractVpuParam(spec)
    if err != nil {
        return invalidVDevice, err
    }
    if splitDevice == "" {
        return invalidVDevice, nil
    }
    if len(devices) != 1 || devices[0] < 0 || devices[0] >= hiAIMaxCardNum*hiAIMaxDeviceNum {
        hwlog.RunLog.Errorf("invalid devices: %v", devices)
        return invalidVDevice, fmt.Errorf("invalid devices: %v", devices)
    }

    if err := w.Initialize(); err != nil {
        return invalidVDevice, fmt.Errorf("cannot init dcmi : %v", err)
    }
    defer w.ShutDown()

    vDeviceInfo, err := w.CreateVDevice(int32(devices[0]), splitDevice)
    if err != nil || vDeviceInfo.VdeviceID < 0 {
        hwlog.RunLog.Errorf("cannot create vd or vdevice is wrong: %v %v", vDeviceInfo.VdeviceID, err)
        return invalidVDevice, err
    }
    return vDeviceInfo, nil
}

逐行解析

  1. 定义无效返回值 invalidVDevice(所有ID为-1)
  2. nil检查保护
  3. extractVpuParam — 从OCI Spec环境变量中提取vNPU切分模板
  4. 如果splitDevice为空 → 不是虚拟模式,返回无效值但无错误
  5. 设备数校验:vNPU切分只允许1个物理设备(len(devices) != 1),且ID在有效范围内
  6. 初始化Worker → 创建虚拟设备 → defer关闭
  7. 如果创建失败或VdeviceID<0 → 返回错误

设计意图:此函数是对Worker.CreateVDevice的包装,增加了Spec解析和参数校验。

2.4 extractVpuParam — vNPU切分模板解析

func extractVpuParam(spec *specs.Spec) (string, error) {
    allowSplit := map[string]string{
        "vir01": "vir01", "vir02": "vir02", "vir04": "vir04", "vir08": "vir08", "vir16": "vir16",
        "vir02_1c": "vir02_1c", "vir03_1c_8g": "vir03_1c_8g", "vir04_3c": "vir04_3c",
        "vir04_4c_dvpp": "vir04_4c_dvpp", "vir04_3c_ndvpp": "vir04_3c_ndvpp",
        "vir05_1c_8g": "vir05_1c_8g", "vir05_1c_16g": "vir05_1c_16g",
        "vir06_1c_16g": "vir06_1c_16g", "vir10_3c_16g": "vir10_3c_16g",
        "vir10_3c_16g_nm": "vir10_3c_16g_nm", "vir10_3c_32g": "vir10_3c_32g",
        "vir10_4c_16g_m": "vir10_4c_16g_m", "vir12_3c_32g": "vir12_3c_32g",
    }

    for _, line := range spec.Process.Env {
        words := strings.Split(line, "=")
        const LENGTH int = 2
        if len(words) != LENGTH {
            continue
        }
        if strings.TrimSpace(words[0]) == api.AscendVnpuSpescEnv {
            if split, ok := allowSplit[words[1]]; ok && split != "" {
                return split, nil
            }
            return "", fmt.Errorf("cannot parse param : %v", words[1])
        }
    }
    return "", nil
}

vNPU切分模板说明

模板名 含义
vir01 1/16切分(最小)
vir02 1/8切分
vir04 1/4切分
vir08 1/2切分
vir16 不切分(全卡)
vir02_1c 1/8切分+1核
vir04_3c 1/4切分+3核
vir04_4c_dvpp 1/4切分+4核+DVPP
vir04_3c_ndvpp 1/4切分+3核+无DVPP
vir05_1c_8g 1/3.2切分+1核+8GB
vir10_3c_16g 1/1.6切分+3核+16GB
vir10_4c_16g_m 1/1.6切分+4核+16GB+内存增强
vir12_3c_32g 1/1.3切分+3核+32GB

逐行解析

  1. 定义允许的切分模板白名单(map)
  2. 遍历容器环境变量
  3. 找到 ASCEND_VNPU_SPECS 环境变量
  4. 如果值在白名单中 → 返回切分模板
  5. 如果值不在白名单 → 返回错误(防止非法切分参数)
  6. 如果环境变量不存在 → 返回空字符串(非虚拟模式)

设计意图:严格的白名单校验,防止用户传入非法切分参数导致驱动异常。


三、dcmi.go 逐行解析(360行)

3.1 常量定义

const (
    retError           = -1
    hiAIMaxCardNum     = 64           // 最大卡数
    hiAIMaxCardID      = math.MaxInt32 // 卡ID上限
    hiAIMaxDeviceID    = math.MaxInt32 // 设备ID上限
    hiAIMaxDeviceNum   = 4             // 每卡最大设备数
    maxChipNameLen     = 32            // 芯片名最大长度
    productTypeLen     = 64            // 产品类型最大长度
    notSupportCode     = -8255         // "不支持"错误码
    coreNumLen         = 32            // 切分模板名最大长度
    vfgID              = 4294967295    // 虚拟组ID默认值(0xFFFFFFFF)
    notSupportString   = "[not support]"
)
常量 用途
hiAIMaxCardNum 64 系统最多支持64张NPU卡
hiAIMaxDeviceNum 4 每张卡最多4个芯片
maxChipNameLen 32 C结构体中芯片名字段长度
vfgID 0xFFFFFFFF 虚拟功能组ID,默认自动分配
notSupportCode -8255 DCMI库返回的"不支持"错误码

3.2 managerList — Worker注册表

var managerList = []WorkerInterface{
    &NpuV1Worker{DcMgr: &DcV1Manager{}},
    &NpuV2Worker{DcMgr: &DcV2Manager{}},
}

设计意图:全局Worker列表,V1在前V2在后。GetMatchingNpuWorker 按顺序尝试初始化,第一个成功的即为匹配的驱动版本。

3.3 ChipInfo 结构体

type ChipInfo struct {
    Type    string `json:"chip_type"`
    Name    string `json:"chip_name"`
    Version string `json:"chip_version"`
}
  • 从C结构体 DcmiChipInfo 转换而来
  • Name 用于判断芯片类型(如"Ascend910A"→Ascend910)

3.4 驱动接口定义

type DcDriverV1Interface interface {
    DcInitialize() error
    DcShutDown()
    DcGetCardList() (int32, []int32, error)
    DcGetDeviceNumInCard(cardID int32) (int32, error)
    DcGetDeviceLogicID(cardID, deviceID int32) (int32, error)
    DcGetProductType(cardID, deviceID int32) (string, error)
    DcCreateVDevice(cardID, deviceID int32, coreNum string) (int32, error)
    DcDestroyVDevice(cardID, deviceID int32, vDevID int32) error
    DcGetChipInfo(cardID, deviceID int32) (*ChipInfo, error)
    DcGetDeviceLogicidFromPhyid(phyID int32) (int32, error)
}

type DcDriverV2Interface interface {
    DcInitialize() error
    DcShutDown()
    DcCreateVDevice(deviceID int32, coreNum string) (int32, error)
    DcDestroyVDevice(deviceID int32, vDevID int32) error
    DcGetChipInfo(deviceID int32) (*ChipInfo, error)
    DcGetDeviceList() (int32, []int32, error)
}

V1 vs V2 接口差异

差异点 V1 V2
设备寻址 cardID + deviceID 二级寻址 deviceID 一级寻址
卡列表 DcGetCardList → 每卡DcGetDeviceNumInCard DcGetDeviceList 直接获取所有设备
产品类型 支持 DcGetProductType 不支持(返回"[not support]")
物理ID转换 DcGetDeviceLogicidFromPhyid 不需要(直接用deviceID)
虚拟设备 支持 不支持(返回错误)

3.5 GetMatchingNpuWorker — 工厂函数

func GetMatchingNpuWorker() (WorkerInterface, error) {
    for _, manager := range managerList {
        err := manager.Initialize()
        if err == nil {
            manager.ShutDown()
            hwlog.RunLog.Infof("worker type: %T", manager)
            return manager, nil
        }
    }
    return nil, fmt.Errorf("failed to find a valid manager")
}

逐行解析

  1. 遍历managerList:先尝试V1,再尝试V2
  2. 调用 Initialize() 尝试初始化dcmi库
  3. 如果成功 → 立即 ShutDown()(因为只是探测,实际使用时会重新初始化)
  4. 返回匹配的Worker
  5. 如果都失败 → 返回错误

设计意图:V1调用 DcmiInit(),V2调用 DcmiV2Init()。在新版硬件上调用V1初始化会失败,反之亦然。利用这个特性来自动检测驱动版本。

GetMatchingNpuWorker

尝试NpuV1Worker.Initialize

DcmiInit成功?

ShutDown
关闭探测连接

返回NpuV1Worker

尝试NpuV2Worker.Initialize

DcmiV2Init成功?

ShutDown

返回NpuV2Worker

返回错误
failed to find valid manager

3.6 NpuV1Worker.FindDevice — 物理ID到卡/设备映射

func (w *NpuV1Worker) FindDevice(visibleDevice int32) (int32, int32, error) {
    targetLogicID, err := w.DcMgr.DcGetDeviceLogicidFromPhyid(visibleDevice)
    if err != nil {
        return 0, 0, fmt.Errorf("cannot convert phy id to logic id, err: %v", err)
    }
    _, cardList, err := w.DcMgr.DcGetCardList()
    if err != nil {
        return 0, 0, fmt.Errorf("get card list err : %v", err)
    }
    targetDeviceID, targetCardID := int32(math.MaxInt32), int32(math.MaxInt32)
    for _, cardID := range cardList {
        deviceCount, err := w.DcMgr.DcGetDeviceNumInCard(cardID)
        if err != nil {
            return 0, 0, fmt.Errorf("cannot get device num in card : %v", err)
        }
        for deviceID := int32(0); deviceID < deviceCount; deviceID++ {
            logicID, err := w.DcMgr.DcGetDeviceLogicID(cardID, deviceID)
            if err != nil {
                return 0, 0, fmt.Errorf("cannot get logic id : %v", err)
            }
            if logicID == targetLogicID {
                targetCardID, targetDeviceID = cardID, deviceID
            }
        }
    }
    return targetDeviceID, targetCardID, nil
}

逐行解析

  1. 将物理ID(用户指定的visibleDevice)转换为逻辑ID
  2. 获取所有卡的列表
  3. 双层遍历:遍历每张卡,遍历卡内每个设备
  4. 对每个设备获取其逻辑ID
  5. 如果逻辑ID匹配目标 → 记录cardID和deviceID
  6. 返回找到的deviceID和cardID

设计意图:V1驱动使用"物理ID"作为用户接口(如ASCEND_VISIBLE_DEVICES=0),但内部操作需要cardID+deviceID二维坐标。此函数完成这个映射。

FindDevice
visibleDevice=0

DcGetDeviceLogicidFromPhyid
物理ID→逻辑ID

DcGetCardList
获取所有卡ID

遍历每张卡

DcGetDeviceNumInCard
获取卡内设备数

遍历卡内每个设备

DcGetDeviceLogicID
获取设备的逻辑ID

逻辑ID == 目标?

记录cardID和deviceID

还有更多设备?

还有更多卡?

返回deviceID, cardID

3.7 NpuV1Worker.GetChipName

func (w *NpuV1Worker) GetChipName() (string, error) {
    invalidName := ""

    if err := w.Initialize(); err != nil {
        return invalidName, fmt.Errorf("cannot init dcmi : %v", err)
    }
    defer w.ShutDown()

    cardNum, cardList, err := w.DcMgr.DcGetCardList()
    if err != nil {
        return invalidName, err
    }
    if cardNum == 0 {
        return invalidName, fmt.Errorf("get chip info failed, no card found")
    }

    for _, cardID := range cardList {
        devNum, err := w.DcMgr.DcGetDeviceNumInCard(cardID)
        if err != nil || devNum == 0 {
            continue
        }
        for devID := int32(0); devID < devNum; devID++ {
            chipInfo, err := w.DcMgr.DcGetChipInfo(cardID, devID)
            if err != nil {
                continue
            }
            if !isValidChipInfo(chipInfo) {
                continue
            }
            return (*chipInfo).Name, nil
        }
    }

    return invalidName, fmt.Errorf("cannot get valid chip info")
}

执行流程

  1. 初始化dcmi → 获取卡列表 → 遍历卡 → 遍历设备 → 获取芯片信息 → 返回第一个有效芯片名
  2. 容错策略:任何单步失败都continue跳过,继续尝试下一个设备
  3. defer确保关闭dcmi连接

设计意图:只需要获取任意一个有效芯片的名称即可确定整个系统的芯片类型(假设同一机器上芯片类型一致)。

3.8 NpuV2Worker.GetChipName

func (a *NpuV2Worker) GetChipName() (string, error) {
    invalidName := ""

    if err := a.Initialize(); err != nil {
        return invalidName, fmt.Errorf("cannot init dcmi : %v", err)
    }
    defer a.ShutDown()

    deviceNum, deviceList, err := a.DcMgr.DcGetDeviceList()
    // ...
    for _, devID := range deviceList {
        chipInfo, err := a.DcMgr.DcGetChipInfo(devID)
        // ...
        return (*chipInfo).Name, nil
    }
    return invalidName, fmt.Errorf("cannot get valid chip info")
}

与V1的区别

  • V1:卡列表→每卡设备数→遍历cardID+deviceID
  • V2:直接获取设备列表→遍历deviceID

3.9 NpuV1Worker.CreateVDevice

func (w *NpuV1Worker) CreateVDevice(uniqueID int32, coreNum string) (VDeviceInfo, error) {
    deviceID, cardID, err := w.FindDevice(uniqueID)
    if err != nil {
        return VDeviceInfo{CardID: -1, DeviceID: -1, VdeviceID: -1}, err
    }
    vdevId, err := w.DcMgr.DcCreateVDevice(cardID, deviceID, coreNum)
    if err != nil {
        return VDeviceInfo{CardID: -1, DeviceID: -1, VdeviceID: math.MaxInt32}, err
    }
    if vdevId > math.MaxInt32 {
        return VDeviceInfo{CardID: cardID, DeviceID: deviceID, VdeviceID: math.MaxInt32},
            fmt.Errorf("create virtual device failed, vdeviceId too large")
    }
    return VDeviceInfo{CardID: cardID, DeviceID: deviceID, VdeviceID: int32(vdevId)}, nil
}

逐行解析

  1. FindDevice — 将物理ID转换为cardID+deviceID
  2. DcCreateVDevice — 调用CGO创建虚拟设备
  3. 溢出检查:vdevId > math.MaxInt32
  4. 返回完整的VDeviceInfo

3.10 NpuV2Worker.CreateVDevice

func (a *NpuV2Worker) CreateVDevice(uniqueID int32, coreNum string) (VDeviceInfo, error) {
    vdevId, err := a.DcMgr.DcCreateVDevice(uniqueID, coreNum)
    if err != nil {
        return VDeviceInfo{
            CardID:    -1,
            DeviceID:  -1,
            VdeviceID: -1,
        }, err
    }
    return VDeviceInfo{VdeviceID: vdevId}, nil
}

注意:V2的 DcCreateVDevice 实际返回错误(“ascend 950 not support virtual device”),所以这段代码在当前版本实际上不会成功执行。


四、dcManager.go 逐行解析(294行)

4.1 CGO导入

// #cgo LDFLAGS: -ldl
// #include "dcmi_interface_api.h"
import "C"
  • LDFLAGS: -ldl — 链接动态加载库(dlopen)
  • #include "dcmi_interface_api.h" — DCMI C接口头文件
  • import "C" — Go的CGO机制,允许调用C函数

4.2 DcV1Manager.DcInitialize

func (d *DcV1Manager) DcInitialize() error {
    cDlPath := C.CString(string(make([]byte, int32(C.PATH_MAX))))
    defer C.free(unsafe.Pointer(cDlPath))
    if err := C.DcmiInitDl(cDlPath); err != C.SUCCESS {
        errInfo := fmt.Errorf("dcmi lib load failed, error code: %d", int32(err))
        return errInfo
    }
    dlPath := C.GoString(cDlPath)
    if _, err := mindxcheckutils.RealFileChecker(dlPath, true, false, mindxcheckutils.DefaultSize); err != nil {
        return err
    }
    if err := C.DcmiInit(); err != C.SUCCESS {
        errInfo := fmt.Errorf("dcmi init failed, error code: %d", int32(err))
        return errInfo
    }
    return nil
}

逐行解析

  1. make([]byte, C.PATH_MAX) — 创建PATH_MAX长度的空字节切片
  2. C.CString — 转换为C字符串(用于接收dcmi库路径输出)
  3. C.DcmiInitDl(cDlPath)动态加载libdcmi.so(dlopen方式),路径通过cDlPath返回
  4. defer C.free — 释放C字符串内存(防内存泄漏)
  5. C.GoString(cDlPath) — 将C字符串转回Go字符串
  6. RealFileChecker — 安全校验加载的dcmi库文件
  7. C.DcmiInit() — 初始化dcmi设备管理连接

设计意图:两步初始化——先加载动态库(dlopen),再初始化设备连接。安全校验确保加载的是合法dcmi库。

失败

成功

不通过

通过

失败

成功

DcInitialize

分配C字符串buffer
PATH_MAX长度

DcmiInitDl
dlopen加载libdcmi.so

返回错误
dcmi lib load failed

获取库路径
C.GoString

RealFileChecker
安全校验库文件

返回错误

DcmiInit
初始化设备连接

返回错误
dcmi init failed

返回nil

4.3 DcV1Manager.DcGetCardList

func (d *DcV1Manager) DcGetCardList() (int32, []int32, error) {
    var ids [hiAIMaxCardNum]C.int
    var cNum C.int
    if err := C.DcmiGetCardNumList(&cNum, &ids[0], hiAIMaxCardNum); err != 0 {
        return retError, nil, fmt.Errorf("get card list failed, error code: %d", int32(err))
    }
    if cNum <= 0 || cNum > hiAIMaxCardNum {
        return retError, nil, fmt.Errorf("get error card quantity: %d", int32(cNum))
    }
    var cardNum = int32(cNum)
    var cardIDList []int32
    for i := int32(0); i < cardNum; i++ {
        cardID := int32(ids[i])
        if cardID < 0 {
            continue
        }
        cardIDList = append(cardIDList, cardID)
    }
    return cardNum, cardIDList, nil
}

逐行解析

  1. 声明C数组 ids[64] 和数量 cNum
  2. C.DcmiGetCardNumList — C函数填充卡数量和卡ID列表
  3. 校验数量:1 ≤ cNum ≤ 64
  4. 遍历C数组,过滤负值,转为Go的[]int32

4.4 DcV1Manager.DcCreateVDevice

func (d *DcV1Manager) DcCreateVDevice(cardID, deviceID int32, coreNum string) (int32, error) {
    var createInfo C.struct_DcmiCreateVdevOut
    createInfo.vdevId = C.uint(math.MaxUint32)
    var deviceCreateStr C.struct_DcmiCreateVdevResStru
    deviceCreateStr = C.struct_DcmiCreateVdevResStru{
        vdevId: C.uint(vfgID),
        vfgId:  C.uint(vfgID),
    }
    deviceCreateStrArr := [coreNumLen]C.char{0}
    for i := 0; i < len(coreNum); i++ {
        if i >= coreNumLen {
            return math.MaxInt32, fmt.Errorf("wrong template")
        }
        deviceCreateStrArr[i] = C.char(coreNum[i])
    }
    deviceCreateStr.templateName = deviceCreateStrArr
    ret := C.DcmiCreateVdevice(C.int(cardID), C.int(deviceID), &deviceCreateStr, &createInfo)
    if ret != 0 {
        return math.MaxInt32, fmt.Errorf("create virtual device failed, error code: %d", int32(ret))
    }
    return int32(createInfo.vdevId), nil
}

逐行解析

  1. 创建输出结构体 DcmiCreateVdevOut,初始vdevId设为MaxUint32(表示未创建)
  2. 创建输入结构体 DcmiCreateVdevResStru,设置vdevId和vfgId为默认值(0xFFFFFFFF)
  3. 将切分模板名(如"vir04")逐字节复制到C字符数组
  4. 模板名长度校验(≤32)
  5. 调用 C.DcmiCreateVdevice — CGO调用创建虚拟设备
  6. 返回创建的vdevId

关键数据结构

输入→输出

DcmiCreateVdevResStru

+uint vdevId

+uint vfgId

+char[32] templateName "切分模板名如vir04"

DcmiCreateVdevOut

+uint vdevId "创建后的虚拟设备ID"

4.5 DcV1Manager.DcGetChipInfo

func (d *DcV1Manager) DcGetChipInfo(cardID, deviceID int32) (*ChipInfo, error) {
    if !isValidCardIDAndDeviceID(cardID, deviceID) {
        return nil, fmt.Errorf("cardID(%d) or deviceID(%d) is invalid", cardID, deviceID)
    }
    var chipInfo C.struct_DcmiChipInfo
    if rCode := C.DcmiGetDeviceChipInfo(C.int(cardID), C.int(deviceID), &chipInfo); int32(rCode) != 0 {
        return nil, fmt.Errorf("get device ChipInfo information failed, ...")
    }

    name := convertUCharToCharArr(chipInfo.chipName)
    cType := convertUCharToCharArr(chipInfo.chipType)
    ver := convertUCharToCharArr(chipInfo.chipVer)

    chip := &ChipInfo{
        Name:    string(name),
        Type:    string(cType),
        Version: string(ver),
    }
    if !isValidChipInfo(chip) {
        return nil, fmt.Errorf("get device ChipInfo information failed, chip info is empty, ...")
    }

    return chip, nil
}

逐行解析

  1. 参数校验(cardID和deviceID范围检查)
  2. 声明C结构体 DcmiChipInfo
  3. 调用 C.DcmiGetDeviceChipInfo 获取芯片信息
  4. convertUCharToCharArr — 将C的uchar数组转为Go byte切片(遇0截断)
  5. 构造Go的ChipInfo结构体
  6. 有效性校验

4.6 convertUCharToCharArr — C数组转Go

func convertUCharToCharArr(cgoArr [maxChipNameLen]C.uchar) []byte {
    var charArr []byte
    for _, v := range cgoArr {
        if v == 0 {
            break
        }
        charArr = append(charArr, byte(v))
    }
    return charArr
}
  • C字符串以null(0)结尾,遍历到0即停止
  • 将C的[32]uchar转为Go的[]byte,再转为string

4.7 DcV2Manager — V2 CGO实现

func (d *DcV2Manager) DcInitialize() error {
    // ...同V1的DcmiInitDl...
    if err := C.DcmiV2Init(); err != C.SUCCESS {
        return fmt.Errorf("dcmi init failed, error code: %d", int32(err))
    }
    return nil
}

关键差异:V2调用 C.DcmiV2Init() 而非 C.DcmiInit()

func (d *DcV2Manager) DcCreateVDevice(deviceID int32, coreNum string) (int32, error) {
    return -1, fmt.Errorf("create error, ascend 950 not support virtual device")
}

func (d *DcV2Manager) DcDestroyVDevice(deviceID int32, vDevID int32) error {
    return fmt.Errorf("destroy error, ascend 950 not support virtual device")
}

V2不支持虚拟设备:Ascend 950(910A5)不支持vNPU切分,直接返回错误。

func (d *DcV2Manager) DcGetDeviceList() (int32, []int32, error) {
    var ids [hiAIMaxCardNum]C.int
    var cNum C.int
    if err := C.DcmiV2GetDeviceList(&ids[0], &cNum, hiAIMaxCardNum); err != 0 {
        return retError, nil, fmt.Errorf("get device list failed, error code: %d", int32(err))
    }
    // ...校验+转换...
    return deviceNum, deviceIDList, nil
}

V2直接获取设备列表,无需先获取卡列表再遍历卡内设备。


五、V1 vs V2 完整对比

V2 新版驱动 Ascend 910B/A5

DcmiV2Init

DcmiV2GetDeviceList
直接获取设备列表

DcmiV2GetDeviceChipInfo
仅deviceID

不支持vNPU
返回错误

V1 旧版驱动 Ascend 910/310

DcmiInit

DcmiGetCardNumList
获取卡列表

DcmiGetDeviceNumInCard
每卡设备数

DcmiGetDeviceChipInfo
cardID+deviceID

DcmiCreateVdevice
支持vNPU切分

DcmiSetDestroyVdevice
销毁vNPU

DcmiGetDeviceLogicidFromPhyid
物理ID→逻辑ID

对比维度 V1 (DcV1Manager) V2 (DcV2Manager)
初始化 DcmiInitDl + DcmiInit DcmiInitDl + DcmiV2Init
设备寻址 cardID + deviceID 二级 deviceID 一级
获取设备列表 DcmiGetCardNumList → 遍历卡 → DcmiGetDeviceNumInCard DcmiV2GetDeviceList 一步到位
芯片信息 DcmiGetDeviceChipInfo(cardID, deviceID) DcmiV2GetDeviceChipInfo(deviceID)
产品类型 支持 DcmiGetProductType 不支持(返回"[not support]")
vNPU切分 支持 DcmiCreateVdevice 不支持(返回错误)
物理ID转换 需要 DcmiGetDeviceLogicidFromPhyid 不需要
适用芯片 Ascend 910, 310, 310P, 310B Ascend 910B, A5 (950)
Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐