3.2 plugin.go — PluginServer gRPC 接口实现

3.2.1 ListAndWatch — 设备列表上报
reciChan PluginServer Kubelet reciChan PluginServer Kubelet 继续监听 alt [ReRegistryStrategy] [ReStartDevicePluginStrate- gy] [EmptyStrategy] alt [stream.Context().Done()] [ps.stop channel] [ps.reciChan (设备变更通知)] loop [持续监听] ListAndWatch(Empty, stream) isRunning.Store(true) reportDeviceInfo(stream) 首次上报 isRunning.Store(false) SetRestartFlag(true) return nil isRunning.Store(false) return nil reportDeviceInfo(stream) strategyForSendStats() SetRestartFlag(true) return nil exitSelfProcess() SIGTERM

逐行解析:

func (ps *PluginServer) ListAndWatch(empty *v1beta1.Empty,
    stream v1beta1.DevicePlugin_ListAndWatchServer) error {

    serverRestartCount := ps.restartTimes.Load() - 1
    hwlog.RunLog.Infof("receive ListAndWatch from kubelet, deviceType=%s, server restartTimes=%d",
        ps.deviceType, serverRestartCount)

    // 标记 server 正在运行
    ps.isRunning.Store(true)

    // 首次立即上报设备信息
    ps.reportDeviceInfo(stream)

    for {
        select {
        case <-stream.Context().Done():
            // gRPC 流被 kubelet 关闭
            hwlog.RunLog.Warnf("grpc stream closed, deviceType=%s", ps.deviceType)
            if ps.isRunning.Load() {
                // server 应该继续运行,设置重启标志
                ps.isRunning.Store(false)
                ps.SetRestartFlag(true)
            }
            return nil

        case <-ps.stop:
            // 收到停止信号(来自 Stop() 调用)
            ps.isRunning.Store(false)
            return nil

        case <-ps.reciChan:
            // 收到设备变更通知,重新上报
            ps.reportDeviceInfo(stream)

            // 检查发送策略(连续失败时的恢复策略)
            strategy := ps.strategyForSendStats()
            ps.handleConsecutiveErrorStrategy(strategy, serverRestartCount)

            if strategy != common.EmptyStrategy {
                return nil  // 需要重启或重新注册,退出 ListAndWatch
            }
        }
    }
}
3.2.2 responseToKubelet — 构造设备列表响应

false 动态切分

true

Yes

No

responseToKubelet()

cachedLock.RLock()

PresetVDevice?

getUnhealthyAICore()
计算不健康的 AICore

遍历 cachedDevices
设置健康状态
替换设备名
添加到 resp

UseVolcanoType
& !IsVirtualDev?

generateAllDeviceMap()
生成 vol→klt 映射

遍历 cachedDevices
映射到 klt 设备名
处理软共享
添加到 resp

遍历 cachedDevices
替换设备名
添加到 resp

cachedLock.RUnlock()

return resp

三种模式对比:

模式 条件 设备名来源 健康状态来源
动态切分 PresetVDevice=false AICore 设备名 getUnhealthyAICore() 计算
Volcano PresetVDevice=true && UseVolcanoType && !IsVirtualDev klt 设备名(通过 vol→klt 映射) cachedDevices 健康状态
标准预置 PresetVDevice=true(其他) cachedDevices 设备名 cachedDevices 健康状态
3.2.3 Allocate — 设备分配
Manager Volcano PluginServer Kubelet Manager Volcano PluginServer Kubelet alt [Volcano 调度 + 需要注解] loop [每个 ContainerRequest] Allocate(ctx, AllocateRequest) checkAllocateRequest(requests) GetNPUs() 获取所有 NPU 信息 useVolcano(requestDevices) doWithVolcanoSchedule() 过滤 Pod + 找最老 Pod getAICoreFromPodAnnotation() updateAllocMap() getNPUInfoConfigDirFromPod() allocateDevices, npuInfoConfigDir GetDeviceListID() getFinalVisibleDevices() (A5 转 LogicID) mountShareDeviceConfig() setNPUDeviceMount() setHcclTopoFilePathEnv() SetSlowNodeNoticeEnv() ContainerAllocateResponse AllocateResponse

关键代码解析:

func (ps *PluginServer) Allocate(ctx context.Context, requests *v1beta1.AllocateRequest) (
    *v1beta1.AllocateResponse, error) {

    // 1. 校验请求合法性
    if err := ps.checkAllocateRequest(requests); err != nil {
        return nil, err
    }

    // 2. 获取所有 NPU 信息(用于 A5 LogicID 转换)
    allNPUInfo, err := ps.manager.GetNPUs()
    if err != nil {
        return nil, err
    }

    resps := new(v1beta1.AllocateResponse)
    for _, rqt := range requests.ContainerRequests {
        // 3. 设备名转换(公共名→内部名)
        allocateDevices := customname.ReplaceDeviceInnerName(ps.deviceType, rqt.DevicesIDs)

        var npuInfoConfigDir string
        usePodAnnotation := false

        // 4. Volcano 调度场景:如果请求数 != 设备总数 或 动态切分模式
        if (len(allocateDevices) != len(allNPUInfo.AllDevs) || !common.ParamOption.PresetVDevice) &&
            common.ParamOption.UseVolcanoType {
            usePodAnnotation = true
            allocateDevices, npuInfoConfigDir, err = ps.useVolcano(rqt.DevicesIDs)
            if err != nil {
                return nil, err
            }
        }

        // 5. 获取设备 ID 列表(物理 ID)
        _, ascendVisibleDevices, err := common.GetDeviceListID(allocateDevices, ps.ascendRuntimeOptions)
        if err != nil {
            return nil, err
        }

        // 6. A5 场景:物理 ID → 逻辑 ID 转换(除非已通过 Pod 注解获取)
        finalVisibleDevices := getFinalVisibleDevices(ascendVisibleDevices, allNPUInfo, usePodAnnotation)

        // 7. 构造容器分配响应
        resp := new(v1beta1.ContainerAllocateResponse)
        ps.mountShareDeviceConfig(resp, finalVisibleDevices, npuInfoConfigDir)  // 软共享配置
        ps.setNPUDeviceMount(resp, finalVisibleDevices)                         // 设备挂载
        ps.setHcclTopoFilePathEnv(resp, allNPUInfo)                             // HCCL 拓扑路径
        ps.SetSlowNodeNoticeEnv(resp)                                           // 慢节点通知
        resps.ContainerResponses = append(resps.ContainerResponses, resp)
    }
    return resps, nil
}
3.2.4 doWithVolcanoSchedule — Volcano 调度设备分配

前 N-1 次

最后一次

No

Yes

false

true

doWithVolcanoSchedule(requestDevices)

podLock.Lock()

条件函数: checkAnnotationAllocateValid()

重试 GetPodFromInformerTime 次

GetActivePodListCache()
从缓存获取 Pod

GetActivePodList()
从 API Server 获取

FilterPods(allPods, deviceType, conditionFunc)

filteredPods 非空?

getOldestPod(filteredPods)
获取最早调度的 Pod

podLock.Unlock()

PresetVDevice?

getAICoreFromPodAnnotation()
动态创建 vNPU

GetDeviceFromPodAnnotation()
从注解获取设备

updateAllocMap()
更新 klt→real 映射

getNPUInfoConfigDirFromPod()
软共享配置目录

return allocateDevices, npuInfoConfigDir

3.2.5 getUnhealthyAICore — 动态切分不健康 AICore 计算
func (ps *PluginServer) getUnhealthyAICore() sets.String {
    // 1. 收集不健康的物理 ID 和所有 AICore 设备名
    unhealthyPhyID := sets.Int{}
    allAICore := make(sets.String, len(ps.cachedDevices))
    for _, device := range ps.cachedDevices {
        if device.Health == v1beta1.Unhealthy {
            unhealthyPhyID.Insert(int(device.PhyID))
        }
        allAICore.Insert(device.DeviceName)
    }

    // 2. 获取实际被 Pod 使用的 AICore(从 Pod 资源查询)
    realUsedAICore, err := ps.GetRealUsedAICore()
    if err != nil {
        return sets.String{}
    }

    // 3. 标记不健康芯片上已使用的 AICore 为不健康
    unhealthyAICore := sets.String{}
    usedAICore := sets.String{}
    for k, r := range realUsedAICore {
        phyID, _, err := common.GetDeviceID(r, "")
        if err != nil {
            continue
        }
        if unhealthyPhyID.Has(phyID) {
            unhealthyAICore.Insert(k)  // 不健康芯片上的 AICore 标记为不健康
        }
        usedAICore.Insert(k)
    }

    // 4. 计算还需要标记多少个空闲 AICore 为不健康
    // 每个不健康芯片有 chipAICore 个 AICore,已标记一部分,剩余的从空闲 AICore 中选择
    leftUnhealthyAICoreNum := unhealthyPhyID.Len()*int(ps.manager.GetChipAICore()) - unhealthyAICore.Len()
    if leftUnhealthyAICoreNum < 0 {
        return unhealthyAICore
    }

    // 5. 从空闲 AICore 中选取补充不健康数量
    freeAICore := allAICore.Difference(usedAICore)
    freeList := freeAICore.List()
    for count := 0; count < leftUnhealthyAICoreNum; count++ {
        unhealthyAICore.Insert(freeList[count])
    }
    return unhealthyAICore
}
3.2.6 软共享设备配置写入

No

Yes

getNPUInfoConfigDirFromPod(pod, devices)

IsSoftShareDevJob(pod)?

return ''

extractPodAnnotations()
提取 aicoreQuota/hbmQuota/policy

getValidLogicDeviceID(devices)
获取逻辑设备 ID

GetMaxVirtualIDByPhysicalID()
获取最大虚拟 ID

vNPUId = maxVirtualID + 1

GetJobNameOfPod(pod)

buildConfigDirPath()
构造配置目录路径

GetDieID(logicID, VDIE)
获取 Die ID

writeNPUConfigFile()
写入配置文件

return npuInfoConfigDir

// 配置文件内容格式(key=value):
// PhysicalNPUId=0
// VirtualNPUId=1
// AICoreQuota=4
// HBMQuota=8
// ShmId=0
// SchedulingPolicy=1
3.2.7 设备挂载
func (ps *PluginServer) setNPUDeviceMount(resp *v1beta1.ContainerAllocateResponse,
    ascendVisibleDevices []int) {
    if !common.ParamOption.UseAscendDocker {
        // 原始挂载模式:直接挂载 /dev/davinciN 设备文件
        hwlog.RunLog.Info("device-plugin will use origin mount way")
        mountDefaultDevice(resp, ps.defaultDevs)  // 挂载默认设备(hi_ai_manager 等)
        mountDevice(resp, ascendVisibleDevices, ps.ascendRuntimeOptions)  // 挂载 NPU 设备
        return
    }
    // ascend-docker 模式:通过环境变量传递设备信息,由 ascend-docker-runtime 挂载
    common.SetAscendRuntimeEnv(ascendVisibleDevices, ps.ascendRuntimeOptions, resp)
    hwlog.RunLog.Info("device-plugin will use ascend-docker to mount")
}
func mountDevice(resp *v1beta1.ContainerAllocateResponse, devices []int, ascendRuntimeOptions string) {
    for _, deviceID := range devices {
        // /dev/davinciN (物理设备)或 /dev/vdavinciN (虚拟设备)
        containerPath, hostPath := getDevPath(fmt.Sprintf("%d", deviceID), ascendRuntimeOptions)
        resp.Devices = append(resp.Devices, &v1beta1.DeviceSpec{
            HostPath:      hostPath,
            ContainerPath: containerPath,
            Permissions:   "rw",
        })
    }
    // 如果有 NPU 设备,额外挂载 uburma 和 ummu 设备
    if len(devices) != 0 {
        mountUBDevice(resp)
    }
}
3.2.8 策略与错误恢复
func (ps *PluginServer) strategyForSendStats() string {
    if ps.LastSendSuccess() {
        return common.EmptyStrategy  // 上次发送成功,无需特殊策略
    }
    if ps.deviceSyncStat.GetConsecutiveFailures() >= common.FailureCountThresholdForRestart {
        // 连续失败超过重启阈值 → 重启整个 device plugin 进程
        return common.ReStartDevicePluginStrategy
    }
    if ps.deviceSyncStat.GetConsecutiveFailures() >= common.FailureCountThresholdForReRegistry {
        // 连续失败超过重注册阈值 → 重新注册到 kubelet
        return common.ReRegistryStrategy
    }
    return common.EmptyStrategy
}

isRunning = true

reportDeviceInfo success

等待下次通知

reportDeviceInfo fail

重试 < 阈值

连续失败 >= ReRegistry 阈值

SetRestartFlag(true), 重新注册

连续失败 >= Restart 阈值

exitSelfProcess() SIGTERM

stream.Context().Done()

SetRestartFlag(true), 重新启动

ps.stop channel

isRunning = false

Running

SendingOK

SendingFail

ReRegistry

RestartPlugin

StreamClosed

Stopped


3.3 server.go — gRPC Server 启停与注册

3.3.1 Start — 启动流程

Yes

No

success

fail

Start(socketWatcher)

restartTimes.Add(1)

socketWatcher nil?

return error

Stop() 清理旧 server

serve(socketWatcher)
创建 gRPC Server

register()
向 kubelet 注册

日志: register success

Stop() 清理

return error

return nil

3.3.2 serve — gRPC Server 创建
func (ps *PluginServer) serve(socketWatcher *common.FileWatch) error {
    // 1. 创建网络监听器(unix socket)
    netListener, err := createNetListener(socketWatcher, ps.deviceType)
    if err != nil {
        return err
    }

    // 2. 配置 gRPC keepalive 参数
    keepAlive := keepalive.ServerParameters{
        Time:    common.GrpcKeepAliveTime,    // 空闲多久后发 ping
        Timeout: common.GrpcKeepAliveTimeout,  // ping 等待响应超时
    }

    // 3. 创建 gRPC Server,配置最大消息大小、并发流、keepalive
    ps.grpcServer = grpc.NewServer(
        grpc.MaxRecvMsgSize(common.MaxGRPCRecvMsgSize),
        grpc.MaxConcurrentStreams(common.MaxGRPCConcurrentStreams),
        grpc.KeepaliveParams(keepAlive),
    )

    // 4. 注册 DevicePluginServer
    v1beta1.RegisterDevicePluginServer(ps.grpcServer, ps)

    // 5. 异步启动 gRPC Server
    go func() {
        if err := ps.grpcServer.Serve(netListener); err != nil {
            hwlog.RunLog.Errorf("GRPC server for '%s' crashed with error: %v", ps.deviceType, err)
        }
    }()

    // 6. 等待 gRPC Server 就绪
    for len(ps.grpcServer.GetServiceInfo()) <= 0 {
        time.Sleep(time.Second)
    }
    hwlog.RunLog.Infof("device plugin (%s) start serving.", ps.deviceType)
    return nil
}
3.3.3 createNetListener — Socket 创建
func createNetListener(socketWatcher *common.FileWatch, deviceType string) (net.Listener, error) {
    // 1. 验证 device-plugin 目录路径和权限
    realSocketPath, ok := common.VerifyPathAndPermission(v1beta1.DevicePluginPath, waitKubectlSockCreateTime)
    if !ok {
        return nil, fmt.Errorf("socket path verify failed")
    }

    // 2. 添加文件监听
    if err := socketWatcher.WatchFile(realSocketPath); err != nil {
        return nil, err
    }

    // 3. 构造 socket 文件路径:/var/lib/kubelet/device-plugin/{deviceType}.sock
    pluginSocketPath := path.Join(realSocketPath, fmt.Sprintf("%s.sock", deviceType))

    // 4. 如果 socket 文件已存在,先删除
    if _, err := os.Stat(pluginSocketPath); err == nil {
        if err = os.Remove(pluginSocketPath); err != nil {
            return nil, err
        }
    }

    // 5. 监听 unix socket
    netListen, err := net.Listen("unix", pluginSocketPath)
    if err != nil {
        return nil, err
    }

    // 6. 设置 socket 文件权限和属主
    if err = os.Chmod(pluginSocketPath, common.SocketChmod); err != nil {
        return nil, err
    }
    if err = os.Lchown(pluginSocketPath, common.RootUID, common.RootGID); err != nil {
        return nil, err
    }

    // 7. 使用限流器包装 listener(防止单 IP 连接过多)
    return limiter.LimitListener(netListen, common.MaxConcurrentLimit,
        common.MaxIPConnectionLimit, common.CacheSize)
}
3.3.4 register — kubelet 注册
func (ps *PluginServer) register() error {
    // 1. 验证 kubelet socket 路径
    realKubeletSockPath, ok := common.VerifyPathAndPermission(v1beta1.KubeletSocket, 0)
    if !ok {
        return fmt.Errorf("check kubelet socket file path failed")
    }

    // 2. 通过 gRPC 连接 kubelet
    conn, err := grpc.Dial(realKubeletSockPath, grpc.WithInsecure(),
        grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
            if deadline, ok := ctx.Deadline(); ok {
                return net.DialTimeout("unix", addr, time.Until(deadline))
            }
            return net.DialTimeout("unix", addr, 0)
        }))
    if err != nil {
        return fmt.Errorf("connect to kubelet fail: %v", err)
    }
    defer conn.Close()

    // 3. 构造注册请求
    client := v1beta1.NewRegistrationClient(conn)
    resourceName := customname.ReplaceDevicePublicType(ps.deviceType,
        api.ResourceNamePrefix+ps.deviceType)
    reqt := &v1beta1.RegisterRequest{
        Version:      v1beta1.Version,        // Kubernetes Device Plugin API 版本
        Endpoint:     fmt.Sprintf("%s.sock", ps.deviceType),  // socket 文件名
        ResourceName: resourceName,            // 资源名称(如 huawei.com/Ascend910A)
    }

    // 4. 发送注册请求
    if _, err = client.Register(context.Background(), reqt); err != nil {
        return fmt.Errorf("register to kubelet fail: %v", err)
    }
    return nil
}

3.4 types.go — 类型定义

// InterfaceServer 接口:定义了 PluginServer 的生命周期方法
type InterfaceServer interface {
    Start(*common.FileWatch) error  // 启动 gRPC Server 并注册到 kubelet
    Stop()                          // 停止 gRPC Server
    GetRestartFlag() bool           // 获取重启标志
    SetRestartFlag(bool)            // 设置重启标志
    LastSendSuccess() bool          // 上次发送是否成功
}

// PluginServer 实现 Kubernetes Device Plugin v1beta1 接口
type PluginServer struct {
    manager              device.DevManager        // 设备管理器(910/310/310P)
    grpcServer           *grpc.Server             // gRPC Server 实例
    isRunning            *common.AtomicBool       // ListAndWatch 是否在运行
    cachedDevices        []common.NpuDevice       // 缓存的设备列表(用于 ListAndWatch)
    deviceType           string                   // 设备类型(如 Ascend910A、Ascend310P-2c-100)
    ascendRuntimeOptions string                   // 运行时选项(空 或 "virtual")
    defaultDevs          []string                 // 默认挂载设备(hi_ai_manager 等)
    allocMapLock         sync.RWMutex             // klt2RealDevMap 读写锁
    cachedLock           sync.RWMutex             // cachedDevices 读写锁
    reciChan             chan interface{}         // 设备变更通知通道
    stop                 chan interface{}         // 停止通道
    klt2RealDevMap       map[string]string        // kubelet 分配 → 实际设备映射
    restart              bool                     // 是否需要重启
    deviceSyncStat       *common.SendStats        // 发送统计(成功/失败次数)
    restartTimes         atomic.Uint64            // 重启次数计数
    podLock              sync.Mutex               // Pod 操作锁(Volcano 调度时使用)
}

// PodDevice Pod 设备信息
type PodDevice struct {
    ResourceName string    // 资源名称(如 huawei.com/Ascend910A)
    DeviceIds    []string  // 设备 ID 列表
}

// PodResource 实现 kubelet Pod Resources 查询客户端
type PodResource struct {
    conn   *grpc.ClientConn                    // gRPC 连接
    client v1alpha1.PodResourcesListerClient   // Pod Resources 客户端
}

// stepTimeCM 慢节点步时间 ConfigMap 结构
type stepTimeCM struct {
    Data stepTimeData `json:"data"`
}

type stepTimeData struct {
    PerfDumpPath   string `json:"PerfDumpPath"`    // 性能 dump 路径
    PerfDumpConfig string `json:"PerfDumpConfig"`  // 性能 dump 配置
}

3.5 pod_resource.go — Pod 资源查询

3.5.1 GetPodResource — 获取 Pod 设备分配

Yes

No

invalid

valid

No

Yes

GetPodResource()

start()
连接 kubelet pod-resources.sock

assemblePodResource()

client.List()
调用 Pod Resources API

PodResources 数量
> MaxPodLimit?

return error

遍历 PodResources

CheckPodNameAndSpace()
校验 Pod 名称和命名空间

getDeviceFromPod()
提取设备信息

resourceName 非空
& devices 非空?

device[podKey] = PodDevice

stop()
关闭连接

return device map

3.5.2 IsPodMoveComplete — 检查 Pod 迁移完成

Yes

No

IsPodMoveComplete(deviceName, podList, ps)

getValidPodResources(podList)
获取有效 Pod 的资源列表

getKltDev(ps, deviceName)
获取设备对应的 klt 设备名

遍历 PodResource

devID == k8sDev?

return false
设备仍被使用

return true
Pod 迁移完成

在热复位中的作用:
当设备不健康需要热复位时,必须先确认该设备上运行的 Pod 已完成迁移或销毁,否则不能执行复位。IsPodMoveComplete 通过查询 kubelet Pod Resources API,检查该设备是否仍被某个活跃 Pod 使用。


3.6 dpu.go — DPU 网卡健康监测

3.6.1 ListenDpu — DPU 状态周期查询

No

Yes

ticker.C

ctx.Done

No

Yes

ListenDpu(ctx)

RealCardType == A5
&& NpuWithDpuInfos 非空?

return(不启动)

初始化 uniqueDpuMap
和 npuToDpuMap

ticker = 5s

select

handleDpu()

return

遍历 uniqueDpuMap
ethtool.GetInterfaceOperState()

按名称排序 dpuList

dpuList != lastData
|| 超过 6h?

manager.SetDpu()
写入 ConfigMap

更新 lastData 和 lastUpdateTime

3.6.2 updateDpuHealthy — DPU 健康状态更新
func (hdm *HwDevManager) updateDpuHealthy(groupDevice map[string][]*common.NpuDevice) {
    // 1. 构建 NPU→DPU 映射和 DPU→operstate 映射
    uniqueMap := make(map[string]dpucontrol.BaseDpuInfo, api.NpuCountPerNode)
    npuToDpusMap := make(map[string][]string, api.NpuCountPerNode)
    // ... 填充映射 ...

    // 2. 查询每个 DPU 的 operstate
    dpuOperstateMap := make(map[string]string, api.NpuCountPerNode)
    for _, dpu := range uniqueMap {
        state, err := ethtool.GetInterfaceOperState(dpu.DeviceName)
        if err != nil {
            state = api.DpuStatusDown  // 查询失败视为 down
        }
        dpuOperstateMap[dpu.DeviceName] = state
    }

    // 3. 为每个 NPU 计算 DPU 健康状态
    for _, devices := range groupDevice {
        for _, device := range devices {
            device.DpuHealth = getDpuFaultInfoOfNpu(device.DeviceName, npuToDpusMap, dpuOperstateMap)
        }
    }
}
// getDpuFaultInfoOfNpu 判断单个 NPU 的 DPU 健康状态
// - PCIe 场景:一个 NPU 对应一个 DPU,DPU 故障 → NPU 不健康
// - UB 场景:一个 NPU 对应两个 DPU,至少一个 DPU 正常 → NPU 健康;全故障 → 不健康;一个故障 → 子健康
func getDpuFaultInfoOfNpu(npuName string, npuToDpuMap map[string][]string,
    dpuOperstateMap map[string]string) string {
    dpuFaultCount := 0
    for npu, dpus := range npuToDpuMap {
        if !isNpuMatched(npuName, npu) {
            continue
        }
        for _, dpu := range dpus {
            if dpuOperstateMap[dpu] != api.DpuStatusUp {
                dpuFaultCount++
            }
        }
        if dpuFaultCount == len(dpus) {
            return v1beta1.Unhealthy    // 全部 DPU 故障 → 不健康
        } else if dpuFaultCount == common.OneDpuFault {
            return api.DpuSubHealthy     // 一个故障 → 子健康
        }
        return v1beta1.Healthy           // 全部正常 → 健康
    }
    return v1beta1.Healthy
}

3.7 manager_v2.go — A5 扩展管理功能

3.7.1 getCardType — 板卡类型识别
func (hdm *HwDevManager) getCardType() (string, error) {
    // 获取第一块设备的板卡信息
    boardInfo, err := hdm.manager.GetDmgr().GetBoardInfo(hdm.allInfo.AllDevs[common.FirstDevice].LogicID)
    if err != nil {
        return "", err
    }

    // 仅对特定 BoardId 进行识别(A5300I 系列板卡)
    if boardInfo.BoardId != npuCommon.A5300IBoardId &&
        boardInfo.BoardId != npuCommon.A5300IBoardId2 &&
        boardInfo.BoardId != npuCommon.A5300IBoardId3 {
        return "", nil  // 非目标板卡,返回空
    }

    // 通过主板 ID 区分 1P 卡和 4P 卡
    mainBoardId := hdm.manager.GetDmgr().GetMainBoardId()
    if mainBoardId == common.A5300IMainBoardId {
        return common.A5300ICardName, nil    // 1P 卡
    }
    if mainBoardId == common.A5300I4PMainBoardId {
        return common.A54P300ICardName, nil  // 4P 卡
    }
    return "", nil
}
3.7.2 getLevelList — Rank Table 拓扑生成

No

Yes

No

Yes

Yes ~ RoCE

No ~ UB/UBG/UBoE

getLevelList(dev)

RealCardType == A5?

return nil

设置 npuBase.productInfo

SetUrmaDeviceInfoByHdm()
获取 URMA EID 列表

getRankLevelInfoKeyArr()
获取层级类型数组

遍历 4 个层级

infoKey 非空?

getRankAddrList(level, dev)

level == 3?

getROCEAddrList(dev)
从 DPU 获取 IP

getNetTypeAndFeIDListByRankLevel()
获取 netType 和 feIDList

遍历 feIDList

getRandAddrByFuncEntityID()
获取 EID 对应的地址

构造 RankLevel
追加到 levelList

return levelList

3.7.3 getROCEAddrList — RoCE 地址获取
func (hdm *HwDevManager) getROCEAddrList(dev *common.NpuDevice) []api.RankAddrItem {
    // 通过 DPU 管理器获取 NPU 对应的 DPU IP 列表
    dpuIPList, err := hdm.getNpuCorrespDpuInfo(dev)
    if err != nil {
        return []api.RankAddrItem{}
    }

    // 将每个 DPU IP 转换为 RankAddrItem
    rankAddrList := make([]api.RankAddrItem, 0)
    for _, ip := range dpuIPList {
        rankAddrList = append(rankAddrList, api.RankAddrItem{
            AddrType: "IPV4",
            Addr:     ip,
            Ports:    []string{},
            PlaneId:  api.DefaultRandAddrPlaneID,
        })
    }
    return rankAddrList
}

3.8 plugin_v2.go — HCCL 拓扑环境变量

// setHcclTopoFilePathEnv 为容器设置 HCCL 拓扑文件路径环境变量
func (ps *PluginServer) setHcclTopoFilePathEnv(resp *v1beta1.ContainerAllocateResponse,
    allNPUInfo common.NpuAllInfo) {
    // 仅 A5 设备需要设置
    if common.ParamOption.RealCardType != api.Ascend910A5 {
        return
    }
    if len(allNPUInfo.AllDevs) == 0 {
        return
    }

    // 根据板卡类型或 SuperPod 类型获取拓扑文件路径 key
    productTypeKey := ps.getProductTypeKey(common.ParamOption.CardType, allNPUInfo)
    if productTypeKey == -1 {
        return
    }

    if resp.Envs == nil {
        resp.Envs = make(map[string]string)
    }

    // 从映射表中查找拓扑文件路径
    if path, exist := hcclTopoFilePathMap[productTypeKey]; exist {
        resp.Envs[common.HcclTopoFilePathKey] = path
        return
    }
}

拓扑文件路径映射表:

var hcclTopoFilePathMap = map[int8]string{
    common.ProductTypeServer:    common.Server8PTopoPath,    // 8P 服务器
    common.ProductType1D:        common.Pod1DTopoPath,       // 1D Pod
    common.ProductType2D:        common.Pod2DTopoPath,       // 2D Pod
    common.ProductType16PServer: common.Server16PTopoPath,   // 16P 服务器
    common.ProductType32PServer: common.Server32PTopoPath,   // 32P 服务器
    common.ProductType1PCard:    common.Card1PTopoPath,      // 1P 卡
    common.ProductType4PCard:    common.Card4PTopoPath,      // 4P 卡
}

3.9 npu_base_v2.go — NPU 基础拓扑信息

3.9.1 ProductBase — 产品场景判断

getID(level) 逻辑

标准卡

Pod 场景

超级服务器

普通服务器

普通服务器

其他

Level 0

nodeInternalIP

superPodID_chassisID

superPodID_serverIndex

nodeInternalIP

Level 1

空(无 Level1)

superPodID

Level 2

DefaultClusterName

Level 3

DefaultClusterName

ProductBase 场景判断

isPodScene()?
superPodType == 1D || 2D

isServer()?
!isPodScene()

isSuperServer()?
isServer() && superPodSize != invalid
&& superPodType != 16P/32P

isStandCard()?
cardType == A5300I || A54P300I

3.9.2 getRankLevelInfoKeyArr — 层级网络类型
func (n *NpuBase) getRankLevelInfoKeyArr() []string {
    if n.productInfo == nil {
        return []string{}
    }
    // 1. 优先从 cardType 映射表查找(标准卡)
    if arr, ok := rankLevelInfoKeyArrMap[n.productInfo.cardType]; ok {
        return arr
    }
    // 2. Pod 场景:4 层全部使用 UB/UBG/RoCE
    if n.productInfo.isPodScene() {
        return []string{
            api.LevelInfoTypeUB,    // Level 0: UB(Die-to-Die)
            api.LevelInfoTypeUB,    // Level 1: UB(Server内)
            api.LevelInfoTypeUBG,   // Level 2: UBG(Pod内跨服务器)
            api.LevelInfoTypeRoCE,  // Level 3: RoCE(跨 Pod)
        }
    }
    // 3. 超级服务器场景
    if n.productInfo.isSuperServer() {
        return []string{
            api.LevelInfoTypeUB,    // Level 0: UB
            api.LevelInfoTypeUB,    // Level 1: UB
            api.LevelInfoTypeUBoE,  // Level 2: UBoE(UB over Ethernet)
            api.LevelInfoTypeRoCE,  // Level 3: RoCE
        }
    }
    // 4. 普通服务器场景(无 Level 1)
    if n.productInfo.isServer() {
        return []string{
            api.LevelInfoTypeUB,    // Level 0: UB
            api.LevelInfoTypeIgnore, // Level 1: 忽略
            api.LevelInfoTypeUBoE,  // Level 2: UBoE
            api.LevelInfoTypeRoCE,  // Level 3: RoCE
        }
    }
    return []string{}
}
3.9.3 EID 解析与端口计算

getDieIdAndPortId

dieIdStr = eid[len-4:len-2]
ParseInt(hex) & 0x04

portIdStr = eid[len-2:]
ParseInt(hex) & 0x7F >> 3

checkEidIsUsedForD2D

true

false

uint16(Raw[14:16]) >> 11 & 0x01 == 0

D2D 使用

非 D2D

getFeIDByEid

uint64(Raw[0:8]) >> FeIdIndexBit & FeIdMask

EID 结构(16 字节)

EID.Raw[16]

Raw[0:8] → BigEndian.Uint64
>> FeIdIndexBit & FeIdMask = FeID

Raw[14] bit 11 = usage
0 = D2D 使用

Raw[13] & 0x04 → dieId
(第 3 位)

Raw[14:16] → ParseInt(hex)
& 0x7F >> 3 = portId

3.9.4 getRandAddrByFuncEntityID — Rank 地址生成
func (n *NpuBase) getRandAddrByFuncEntityID(phyID int32, feID uint, netType string,
    rankLevel int) []api.RankAddrItem {
    // 1. 从缓存获取 URMA 设备信息
    urmaDevInfoAll, exist := n.urmaDevInfoMap[phyID]
    if !exist {
        return nil
    }

    rankAddrList := make([]api.RankAddrItem, 0)
    for _, devInfo := range urmaDevInfoAll {
        // 2. 根据 feID 和 rankLevel 筛选 EID 列表
        eidList := n.getEidListByFeIDAndRankLevel(feID, &devInfo, rankLevel)
        for i := 0; i < len(eidList); i++ {
            eid := eidList[i].Eid
            eidStr := hex.EncodeToString(eid.Raw[:])

            // 3. 获取 EID 对应的端口列表(含缓存)
            portList, err := n.GetPortListByEid(phyID, eidStr, rankLevel)
            if err != nil {
                continue
            }

            // 4. 根据网络类型构造 RankAddrItem
            item := n.createRankAddrItem(netType, eid, portList)
            rankAddrList = append(rankAddrList, item)
        }
    }
    return rankAddrList
}
3.9.5 createRankAddrItem — 地址项构造
func (n *NpuBase) createRankAddrItem(netType string, eid apiCommon.Eid,
    ports []string) api.RankAddrItem {
    planeId := api.DefaultRandAddrPlaneID

    if netType == api.LevelInfoTypeUB {
        // UB 类型:从 EID 最后一个字节计算 dieId 作为 planeId
        val := int(eid.Raw[len(eid.Raw)-1])
        dieId := 0
        if val <= common.PhyLimit {
            dieId = ((val - 1) / common.PhyPortNumPerDie) % common.DieNumPerDev
        } else if common.LogicLowerLimit <= val && val <= common.LogicUpperLimit {
            dieId = ((val - common.LogicLowerLimit) / common.LogicPortNumPerDie) % common.DieNumPerDev
        }
        planeId = strconv.Itoa(dieId)
    }

    if netType == api.LevelInfoTypeUBG || netType == api.LevelInfoTypeUB {
        // UB/UBG:地址类型为 EID(十六进制字符串)
        return api.RankAddrItem{
            AddrType: addrTypeEID,   // "EID"
            Addr:     hex.EncodeToString(eid.Raw[:]),
            Ports:    ports,
            PlaneId:  planeId,
        }
    }

    if netType == api.LevelInfoTypeUBoE {
        // UBoE:从 EID 后 4 字节提取 IPv4 地址
        ipv4Bytes := eid.Raw[len(eid.Raw)-apiCommon.MaxUBCNAByteLen : len(eid.Raw)]
        ipv4Str := fmt.Sprintf("%d.%d.%d.%d", ipv4Bytes[0], ipv4Bytes[1], ipv4Bytes[2], ipv4Bytes[3])
        return api.RankAddrItem{
            AddrType: addrTypeIPV4,  // "IPV4"
            Addr:     ipv4Str,
            Ports:    ports,
            PlaneId:  planeId,
        }
    }
    return api.RankAddrItem{}
}
3.9.6 getPortsList — 端口列表获取

Yes

No

Yes

No 0 或 1

Yes

Yes

No

No

GetPortListByEid(phyId, eid, rLevel)

portMapMutex.Lock()

eidPortMap 命中缓存?

return cached ports

getPortsList()

rLevel == 2?

getTopoFileInfo()
读取拓扑文件

遍历 EdgeList
匹配 NetLayer==2 && LocalA==phyId

savePortsResult()
收集 LocalAPorts

eidPortMap[key] = ports

getDieIdAndPortId(eid)

isStandCard() && rLevel==0?

portId > PortIdLimit?

return empty

ports.append(dieId/portId)

getTopoFileInfo()

遍历 EdgeList
匹配 NetLayer==rLevel
&& LocalA==phyId
&& LinkType==Peer2Net

savePortsResultByDieId()
按 dieId 过滤 LocalAPorts

eidPortMap[key] = ports

return ports


3.10 types_v2.go — V2 类型定义

// Peer 拓扑对端信息
type Peer struct {
    LocalId int `json:"local_id"`  // 本地设备 ID
}

// TopoInfo 拓扑文件信息(从 JSON 文件解析)
type TopoInfo struct {
    Version      string `json:"version"`        // 拓扑文件版本
    HardwareType string `json:"hardware_type"`  // 硬件类型
    PeerCount    int    `json:"peer_count"`     // 对端数量
    PeerList     []Peer `json:"peer_list"`      // 对端列表
    EdgeList     []Edge `json:"edge_list"`      // 边列表(连接信息)
}

// Edge 拓扑边信息(设备间连接)
type Edge struct {
    NetLayer       int      `json:"net_layer"`        // 网络层级(0-3)
    LinkType       string   `json:"link_type"`        // 链路类型(peer2net 等)
    TopoType       string   `json:"topo_type"`        // 拓扑类型
    TopoInstanceId int      `json:"topo_instance_id"` // 拓扑实例 ID
    TopoAttr       string   `json:"topo_attr"`        // 拓扑属性
    LocalA         int      `json:"local_a"`          // 本地设备 A
    LocalAPorts    []string `json:"local_a_ports"`    // 本地设备 A 端口列表
    LocalB         int      `json:"local_b"`          // 本地设备 B
    LocalBPorts    []string `json:"local_b_ports"`    // 本地设备 B 端口列表
    Protocols      []string `json:"protocols"`        // 协议列表
    Position       string   `json:"position"`         // 位置信息
}

四、核心流程总览

4.1 设备插件完整生命周期

停止阶段

主循环

运行阶段

初始化阶段

每秒

每N秒

ctx.Done

NewHwDevManager()

setAscendManager()
选择设备管理器

setAllDeviceAndType()
发现所有 NPU

initPluginServer()
创建 PluginServer

UpdateNode()
更新节点标签/注解

ListenDevice(ctx)

subscribeFaultEvent()
订阅故障

loadFaultCodeAndDeviceInfoCm()
加载故障码

go Serve(ctx)
管理 gRPC Server

startAllServer()
启动 PluginServer

register()
注册到 kubelet

ListAndWatch
持续上报设备

主循环

parseTriggers()
检查触发器

handleDeviceInfoUpdate()
设备信息更新

notifyToK8s()
通知 kubelet

PluginServer.Notify()
→ reciChan

reportDeviceInfo()
发送到 kubelet

SignCatch()
捕获 OS 信号

stopAllSever()
停止所有 PluginServer

dmgr.ShutDown()
关闭设备管理器

4.2 设备分配完整流程

DCMI DevManager Volcano PluginServer Kubelet K8s Pod DCMI DevManager Volcano PluginServer Kubelet K8s Pod alt [动态切分模式] alt [Volcano 调度场景] alt [ascend-docker 模式] [原始模式] 创建 Pod(请求 NPU 资源) Allocate(ctx, AllocateRequest) checkAllocateRequest(requests) GetNPUs() 获取设备信息 useVolcano(requestDevices) 过滤匹配 Pod 找最早调度的 Pod CreateVirtualDevice() 创建 vNPU updateAllocMap(klt→real) 写软共享配置文件 allocateDevices GetDeviceListID(allocateDevices) getFinalVisibleDevices() (A5: phyID→logicID) mountShareDeviceConfig(resp) setNPUDeviceMount(resp) SetAscendRuntimeEnv() mountDefaultDevice() + mountDevice() mountUBDevice() (uburma/ummu) setHcclTopoFilePathEnv() (A5) SetSlowNodeNoticeEnv() AllocateResponse 挂载设备文件,启动容器

4.3 故障处理完整流程

故障处理

故障检测

热复位(训练场景)

graceTolerance()
训练容错

AnnotationReset()
写入复位注解

训练任务复位流程
(由 device 包处理)

热复位(推理场景)

No

Yes

No

Yes

超时

成功

chipHotReset()

isPodRemove()
检查 Pod 是否已迁移

等待下次检查

checkNoProc()
检查设备无进程

hotReset()

SetDeviceReset()
执行复位

轮询 GetDeviceBootStatus()
等待启动完成

复位失败计数+1

清除复位计数
标记设备已初始化

subscribeFaultEvent()
订阅 NPU 故障事件

SaveDevFaultInfo()
故障回调保存

周期性 handleDeviceInfoUpdate()

mendSubscribeFaultEvents()
补充订阅未覆盖的故障

UpdateHealth()
根据故障码更新健康状态

graceTolerance()
训练容错(正在复位的设备标记为 healthy)

notifyToK8s()
通知 kubelet 不健康设备

ListAndWatch 上报
kubelet 标记设备为 Unhealthy

kubelet 驱逐 Pod
(或训练容错不驱逐)


五、架构总结

5.1 设计模式

模式 应用
策略模式 responseToKubelet 三种模式(动态切分/Volcano/标准)
观察者模式 PluginServer.Notify()reciChanListAndWatch
工厂模式 NewHwDevManager 根据设备类型创建不同 manager
状态机 strategyForSendStats 发送状态机(OK→ReRegistry→RestartPlugin)
代理模式 PodResource 代理 kubelet Pod Resources API
缓存模式 NpuBase.eidPortMap EID→Port 缓存、ProductBase.topoInfo 拓扑文件缓存

5.2 并发控制

通道

PluginServer.reciChan
设备变更通知(buffer=1)

PluginServer.stop
停止信号

common.GetUpdateChan()
更新触发通道

原子操作

PluginServer.isRunning
AtomicBool

PluginServer.restartTimes
atomic.Uint64

锁层级

common.LockAllDeviceInfo()
全局设备信息锁

HwDevManager.ManagerLock
管理器锁

PluginServer.podLock
Pod 操作锁

PluginServer.allocMapLock
klt→real 映射锁

PluginServer.cachedLock
缓存设备列表锁

NpuBase.portMapMutex
EID→Port 缓存锁

5.3 关键常量

常量 含义
memoryRadix 1024 内存单位进制(字节→GB)
nodeAnnotationUpdateInterval 60 节点注解更新间隔(秒)
serverIndexKey “serverIndex” 节点注解 key
serverTypeKey “serverType” 节点注解 key
cardTypeKey “cardType” 节点注解 key
listenDpuInterval 5s DPU 查询间隔
maxUpdateInterval 6h DPU ConfigMap 最大更新间隔
socketPath /var/lib/kubelet/pod-resources/kubelet.sock Pod Resources socket
callTimeout 2s Pod Resources API 超时
waitKubectlSockCreateTime 5min 等待 kubelet socket 创建超时

本文档严格基于源码分析,涵盖 10 个源文件共约 4750 行代码的逐行级深度解析。

Logo

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

更多推荐