HAMi 整体工作原理与指标产生原理
一、HAMi 整体工作原理
HAMi 由三个核心组件构成,形成一个完整的 GPU 虚拟化链路。
组件概览
阶段 1:Device Plugin — GPU 设备注册
Device Plugin 作为 DaemonSet 运行于每个 GPU 节点,通过 WatchAndRegister 每 30 秒循环调用 RegistrInAnnotation,将节点 GPU 信息(UUID、显存、算力等)编码后写入 Node Annotation hami.io/node-nvidia-register。 register.go:186-222
设备信息由 getAPIDevices() 通过 NVML 获取,包括 GPU 显存大小(支持超分配系数 DeviceMemoryScaling)、算力上限、NUMA 拓扑等。 register.go:114-184
阶段 2:Admission Webhook — 资源请求改写
Scheduler 内嵌一个 Webhook,拦截 Pod 创建请求,对每个容器调用各设备的 MutateAdmission 方法改写资源请求(如将 nvidia.com/gpu 等原生资源转换为 HAMi 内部资源),并在必要时将 pod.Spec.SchedulerName 修改为 HAMi 调度器。 webhook.go:52-104
阶段 3:Scheduler Extender — 调度与设备分配决策
Scheduler 以 Kubernetes Extender 形式运行,对外暴露 /filter 和 /bind 接口。
Filter 阶段:getNodesUsage 读取调度器内存中维护的节点设备状态与已调度 Pod 信息,通过 calcScore 打分选出最优节点与 GPU,并将分配结果(具体哪块 GPU、分配多少显存和算力)写入 Pod Annotation hami.io/vgpu-devices-to-allocate 和 hami.io/vgpu-node。 scheduler.go:481-553
节点信息采集: Scheduler 通过 RegisterFromNodeAnnotations 定期从 Node Annotation 读取设备信息,结合 Pod informer 维护 overviewstatus 内存状态。 scheduler.go:185-281
Bind 阶段: 设置 Node Lock 防止并发分配冲突,调用 Kubernetes Bind API 完成 Pod 绑定。 scheduler.go:421-479
阶段 4:Device Plugin Allocate — 关键注入机制
Kubelet 调用 Device Plugin 的 Allocate() gRPC 方法。这是 GPU 虚拟化的核心环节:
从 Pod Annotation 读取调度器已决定的设备分配信息(
GetNextDeviceRequest)设置关键环境变量:
CUDA_DEVICE_MEMORY_LIMIT_x(显存限制)、CUDA_DEVICE_SM_LIMIT(算力限制)将
libvgpu.so和ld.so.preload以 Volume Mount 方式注入容器,实现对 CUDA API 的透明拦截在宿主机创建专属缓存目录
{HOOK_PATH}/vgpu/containers/{podUID}_{containerName},挂载进容器,用于共享内存通信 server.go:455-586
阶段 5:容器内 libvgpu.so 运行时拦截
libvgpu.so 通过 ld.so.preload 在容器进程启动时自动加载,拦截所有 CUDA 调用,对显存分配和 SM 利用率进行限制,并将实时使用数据写入共享内存 .cache 文件(sharedRegionT 结构体)。
二、HAMi 指标系统
HAMi 有两套独立的 Prometheus 指标服务,服务于不同层级。
Scheduler 指标(:9395)
Scheduler 的 initMetrics 启动一个独立 HTTP 服务,注册 ClusterManagerCollector,每次 Prometheus 抓取时触发 Collect()。 metrics.go:298-310
设备级指标(来自 InspectAllNodesUsage(),即调度器内存状态):
用户(Pod/容器)级分配指标(来自 GetScheduledPods(),即已调度 Pod 的分配记录):
vGPUmonitor 指标(:9394)— 用户级实时指标的产生原理
这是最核心的部分。vGPUmonitor 的 initMetrics 启动 HTTP 服务并注册 ClusterManagerCollector。 main.go:136-170
指标定义 metrics.go:83-127
第一步:共享内存的数据结构
libvgpu.so 和 monitor 共享同一个内存映射文件,其核心结构 sharedRegionT 包含:
uuids[16]:容器使用的 GPU UUID 列表limit[16]:每个 GPU 的显存限额procs[1024]:每个进程的使用数据槽位(shrregProcSlotT),每个槽位含used[16]deviceMemory和deviceUtil[16]deviceUtilizationutilizationSwitch、recentKernel、priority:反馈控制字段lastKernelTime:最近一次 kernel 执行时间戳 spec.go:57-81
第二步:ContainerLister 扫描与映射
ContainerLister.Update() 扫描 {HOOK_PATH}/containers/ 目录,对每个子目录(命名格式为 {podUID}_{containerName})调用 loadCache(),通过 syscall.Mmap 将 .cache 文件以读写共享模式映射到内存,并根据文件大小/版本号区分 v0/v1 格式,调用 CastSpec() 将字节切片直接转换为结构体指针(零拷贝)。 cudevshr.go:164-221 cudevshr.go:223-288
第三步:指标采集与聚合
collectPodAndContainerInfo() 的工作流程:
通过 Pod Lister 获取当前节点(
hami.io/vgpu-node= nodeName)所有 GPU Pod将
ContainerLister.ListContainers()的结果按podUID建立containerMap对每个 Pod 的每个容器调用
collectContainerMetrics()metrics.go:305-354
collectContainerMetrics() 遍历容器使用的每块 GPU,调用 UsageInfo 接口聚合所有进程(procs[0..procnum])的显存(total/contextSize/moduleSize/bufferSize/offset)和 SM 利用率(smUtil),并带上 {podnamespace, podname, ctrname, vdeviceid, deviceuuid} 标签发送到 Prometheus channel。 metrics.go:363-426
数据聚合实现(以显存 Total 为例,累加容器所有进程的用量): spec.go:123-137
第四步:反馈控制(闭环)
watchAndFeedback 每 5 秒触发一次 Observe(),读取共享内存中各容器的 recentKernel 和 priority,向共享内存写回 utilizationSwitch(控制是否限速)和 recentKernel(控制是否阻塞),libvgpu.so 读取这些字段来调整容器的 GPU 使用行为,实现多容器间的优先级调度。 feedback.go:186-214 feedback.go:128-184
总结
关键设计思路:
用户级实时指标(
vGPU_device_memory_usage_in_bytes,Device_utilization_desc_of_container等)通过libvgpu.so↔mmap共享内存↔vGPUmonitor三层联动产生,数据完全来自容器内部运行时,精度高且无侵入用户级分配指标(
vGPUMemoryAllocated,vGPUCoreAllocated)由 Scheduler 从调度决策记录中产生,反映的是调度时承诺的资源量,而非实际使用量两套指标互补:前者反映实际运行时状态,后者反映资源配额分配情况
Notes
两套指标端口不同:vGPUmonitor 暴露
:9394,Scheduler 暴露:9395,部署时需要分别配置 Prometheus scrape job。共享内存版本兼容:v0 格式(文件大小恰好为 1197897 字节)和 v1 格式(
majorVersion == 1)共存,loadCache()会自动区分。v0 的LastKernelTime()返回 0(不支持),v1 支持该字段。 cudevshr.go:272-287MIG 指标:
MigInfo指标由collectPodAndContainerMigInfo()从 Pod Annotation 解码产生,而非共享内存,因为 MIG 切分的是物理设备,其 UUID 格式为{GPU-UUID}[templateIdx-pos]。 metrics.go:428-473vGPUPodsDeviceAllocated、vGPUMemoryPercentage、vGPUCorePercentage已标记为在 v2.8.0 弃用,应使用vGPUMemoryAllocated和vGPUCoreAllocated替代。 metrics.go:172-196