diff --git a/docs/core-concepts/architecture.md b/docs/core-concepts/architecture.md index 9a4e832d8..e9ee1fd02 100644 --- a/docs/core-concepts/architecture.md +++ b/docs/core-concepts/architecture.md @@ -2,29 +2,84 @@ title: Architecture --- -The overall architecture of HAMi is shown as below: +HAMi extends Kubernetes with device-aware scheduling and runtime resource controls for heterogeneous AI accelerators. Its architecture separates cluster-wide placement decisions from node-level device allocation and, where supported, in-container enforcement. -![Architecture](/img/docs/common/core-concepts/architect.jpg) +![HAMi components and the GPU workload scheduling sequence](/img/docs/common/core-concepts/hami-architecture-en.svg) -HAMi consists of the following components: +## What are HAMi's core components? -- HAMi MutatingWebhook -- HAMi scheduler-extender -- Device-plugin (HAMi-device-plugin) -- In-container resource control (HAMi-Core) +HAMi coordinates four components during the lifecycle of an accelerator workload: + +| Component | Runs as | Primary responsibility | +| --- | --- | --- | +| HAMi MutatingWebhook | Part of the `hami-scheduler` deployment | Directs Pods that request HAMi-managed resources to `hami-scheduler` | +| HAMi scheduler extender | Part of the `hami-scheduler` deployment | Selects a node and a physical device from a cluster-wide view of available resources | +| HAMi device plugin | DaemonSet on accelerator nodes | Registers devices with kubelet and prepares the selected device for the container | +| HAMi-Core | Library injected into supported containers | Enforces the assigned memory and compute limits at runtime | + +The exact resources and enforcement mechanism depend on the device vendor. For example, NVIDIA workloads can request `nvidia.com/gpumem` in MiB and `nvidia.com/gpucores` as a percentage. Other devices expose vendor-specific resources and may support different allocation granularities. See the [FAQ](../faq/faq.md) for the current support matrix. + +## How does a workload move through HAMi? + +1. **Admission:** When a Pod requests a HAMi-managed device, the MutatingWebhook sets `spec.schedulerName` to `hami-scheduler` unless the Pod already names a scheduler. +2. **Placement:** The HAMi scheduler extender combines the Pod request with device information reported by each node. During scheduling, it filters nodes that cannot satisfy the request and selects a suitable physical device. +3. **Allocation:** The scheduler records the selected device and quota in Pod annotations. On the chosen node, kubelet calls the HAMi device plugin, which reads that result and makes the device available to the container. +4. **Runtime control:** For devices that support in-container control, the device plugin injects the required runtime library and configuration. For NVIDIA virtual GPUs, HAMi-Core intercepts relevant CUDA and NVML calls to apply the assigned memory and compute limits. + +This division keeps cluster policy in the control plane, hardware discovery and allocation on each node, and workload-level enforcement close to the application. ## HAMi MutatingWebhook {#hami-mutatingwebhook} -HAMi MutatingWebhook checks if this task can be handled by HAMi. It scans the resource field of each pod submitted. If each resource the pod requires is either 'CPU', 'Memory' or a HAMi-resource, then it will set the schedulerName field of this pod to 'hami-scheduler'. +The MutatingWebhook is the admission entry point. It examines a newly created Pod's resource requests to determine whether HAMi should handle it. For eligible Pods, it sets: + +```yaml +spec: + schedulerName: hami-scheduler +``` + +Pods that do not request HAMi-managed resources continue through the normal Kubernetes scheduling path. Pods that explicitly choose another scheduler are not silently reassigned. ## HAMi Scheduler {#hami-scheduler} -The HAMi scheduler is responsible for assigning tasks to the appropriate nodes and devices. At the same time, the scheduler needs to maintain a global view of heterogeneous computing devices for monitoring. +The scheduler is responsible for choosing both a node and a device. Kubernetes device plugins normally advertise integer resource counts, which are not enough to describe properties such as device model, memory capacity, compute capacity, health, or topology. HAMi device plugins therefore report detailed device information through node annotations, allowing the scheduler to maintain a cluster-wide view. + +HAMi participates in the standard scheduling flow as a [scheduler extender](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md); it does not replace Kubernetes scheduling. It filters candidates according to the requested resources, applies the configured binpack or spread policy, binds the Pod, and writes the allocation result to annotations such as `hami.io/vgpu-devices-allocated`. ## Device Plugin {#device-plugin} -The device-plugin layer obtains the scheduling result from the annotations field of the task and maps the corresponding device to the container. +The HAMi device plugin runs on each supported accelerator node and implements the Kubernetes [device plugin API](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/). It discovers local devices, registers allocatable resources with kubelet, reports device details for scheduling, and handles kubelet's `Allocate` request after a Pod is bound. + +During allocation, the plugin reads the scheduler's result from the Pod annotations and exposes the selected device to the container. Depending on the vendor integration, it may mount device files and runtime libraries or inject environment variables that describe the assigned quota. ## HAMi-Core {#hami-core} -The in-container resource control is responsible for monitoring the resource usage within the container and providing hard isolation capabilities. +HAMi-Core provides runtime control for NVIDIA virtual GPUs through `libvgpu.so`. The device plugin loads the library into the container through `/etc/ld.so.preload`. HAMi-Core then intercepts CUDA memory allocation and kernel launch calls: allocations beyond the assigned memory budget return an out-of-memory error, while compute usage is throttled toward the requested limit. It also adjusts NVML results so applications see their assigned memory rather than the full physical device. + +This is user-space enforcement, not a hardware security boundary. Applications that bypass the intercepted libraries—for example, by using direct driver calls or Docker-in-Docker—may bypass these controls. Use [NVIDIA MIG](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) when a supported GPU and hardware-enforced isolation are required. See [GPU Virtualization Principles](./gpu-virtualization.md) for the full interception and allocation flow. + +## How does HAMi compare with time-slicing and MIG? + +| Approach | Sharing model | Memory and compute boundary | Best fit | +| --- | --- | --- | --- | +| Time-slicing | Workloads take turns on one GPU | No per-workload GPU memory isolation | Simple concurrency where strict quotas are unnecessary | +| HAMi virtual GPU | Workloads share a GPU with flexible requested quotas | User-space memory enforcement and compute throttling | Fine-grained, dynamic sharing across a broad range of GPUs | +| NVIDIA MIG | A supported GPU is divided into fixed hardware partitions | Hardware-enforced memory and compute isolation | Strong isolation on MIG-capable GPUs | + +HAMi also supports dynamic MIG allocation, so these approaches are not always mutually exclusive. The right choice depends on the accelerator, workload, isolation requirement, and desired partition granularity. + +## Product architecture and reference deployment + +This page describes HAMi's product components and the request lifecycle common to its integrations. A complete cluster can also include a CNI, vendor drivers and runtimes, monitoring, and an optional dashboard. For one NVIDIA-oriented deployment topology and its dependencies, see [HAMi Cluster Architecture After Installation](./hami-architecture.md). + +## References + +- [Kubernetes device plugins](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/) +- [Kubernetes scheduler extender design](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md) +- [HAMi-Core source code](https://github.com/Project-HAMi/HAMi-core) +- [NVIDIA Multi-Instance GPU user guide](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) + +## What's next + +- Explore the detailed [GPU virtualization principles](./gpu-virtualization.md) +- Review the [reference cluster architecture](./hami-architecture.md) +- Check the [installation prerequisites](../installation/prerequisites.md) diff --git a/docs/core-concepts/hami-architecture.md b/docs/core-concepts/hami-architecture.md index 0354d04e3..41ac3cf08 100644 --- a/docs/core-concepts/hami-architecture.md +++ b/docs/core-concepts/hami-architecture.md @@ -4,6 +4,12 @@ title: "HAMi Cluster Architecture After Installation" After completing the HAMi installation, the cluster is no longer an ordinary Kubernetes cluster, it becomes an AI infrastructure platform with GPU virtualization capabilities. This document breaks down the responsibilities and dependencies of every layer and every component in the cluster after installation. +:::note + +This page describes one NVIDIA-oriented reference deployment after installation, including optional components such as GPU Operator, Prometheus, and WebUI. For HAMi's product components and the request lifecycle, see [Architecture](./architecture.md). + +::: + ## 5-Layer Architecture Overview The cluster after installation consists of 5 layers, each providing services to the layer above: diff --git a/docusaurus.config.js b/docusaurus.config.js index 11f4414e3..6dbb8c734 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -409,6 +409,23 @@ module.exports = { }, ], }, + { + title: "Legal", + items: [ + { + label: "About", + to: "/about", + }, + { + label: "Privacy", + to: "/privacy", + }, + { + label: "Terms", + to: "/terms", + }, + ], + }, ], copyright: `
diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/architecture.md b/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/architecture.md index 1d22b59d9..1152a057b 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/architecture.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/architecture.md @@ -3,29 +3,84 @@ title: 架构设计 translated: true --- -HAMi 的整体架构如下所示: +HAMi 通过设备感知调度和运行时资源控制,扩展 Kubernetes 对异构 AI 加速器的管理能力。其架构将集群级调度决策、节点级设备分配以及支持场景下的容器内资源控制相互分离。 -![HAMi 系统架构图,显示各组件之间的关系和交互](/img/docs/common/core-concepts/architect.jpg) +![HAMi 组件及 GPU 工作负载调度流程](/img/docs/common/core-concepts/hami-architecture.svg) -HAMi 由以下组件组成: +## HAMi 有哪些核心组件? -- HAMi MutatingWebhook -- HAMi scheduler-extender -- 设备插件 (HAMi-device-plugin) -- 容器内资源控制 (HAMi-Core) +在加速器工作负载的整个生命周期中,HAMi 会协调以下四个组件: + +| 组件 | 运行方式 | 主要职责 | +| --- | --- | --- | +| HAMi MutatingWebhook | `hami-scheduler` Deployment 的一部分 | 将请求 HAMi 管理资源的 Pod 交给 `hami-scheduler` | +| HAMi 调度器扩展程序 | `hami-scheduler` Deployment 的一部分 | 根据集群范围内的可用资源视图选择节点和物理设备 | +| HAMi 设备插件 | 加速器节点上的 DaemonSet | 向 kubelet 注册设备,并为容器准备选定的设备 | +| HAMi-Core | 注入受支持容器的动态库 | 在运行时执行已分配的显存和算力限制 | + +具体资源名称和控制机制取决于设备厂商。例如,NVIDIA 工作负载可以使用 `nvidia.com/gpumem` 请求以 MiB 为单位的显存,并使用 `nvidia.com/gpucores` 请求百分比形式的算力。其他设备使用厂商特定的资源名称,支持的分配粒度也可能不同。当前支持情况请参阅[常见问题](../faq/faq.md)。 + +## 工作负载如何通过 HAMi 运行? + +1. **准入:** 当 Pod 请求 HAMi 管理的设备时,MutatingWebhook 会将 `spec.schedulerName` 设置为 `hami-scheduler`;如果 Pod 已明确指定调度器,则不会改写。 +2. **调度:** HAMi 调度器扩展程序将 Pod 请求与各节点上报的设备信息结合起来,在调度过程中排除无法满足请求的节点,并选择合适的物理设备。 +3. **分配:** 调度器把选定的设备和配额写入 Pod 注解。在目标节点上,kubelet 调用 HAMi 设备插件;设备插件读取调度结果,并将设备提供给容器。 +4. **运行时控制:** 对于支持容器内控制的设备,设备插件会注入所需的运行时库和配置。对于 NVIDIA 虚拟 GPU,HAMi-Core 会拦截相关 CUDA 和 NVML 调用,以执行已分配的显存和算力限制。 + +这种职责划分让集群策略留在控制平面、硬件发现和分配发生在各节点,并让工作负载级资源控制靠近应用程序。 ## HAMi MutatingWebhook {#hami-mutatingwebhook} -HAMi MutatingWebhook 检查该任务是否可以由 HAMi 处理,它扫描每个提交的 Pod 的资源字段,如果这些 Pod 所需的每个资源是 'cpu'、'memory' 或 HAMi 资源,则会将该 Pod 的 schedulerName 字段设置为 'hami-scheduler'。 +MutatingWebhook 是准入入口。它检查新建 Pod 的资源请求,以确定是否应由 HAMi 处理。对于符合条件的 Pod,它会设置: + +```yaml +spec: + schedulerName: hami-scheduler +``` + +没有请求 HAMi 管理资源的 Pod 会继续使用常规 Kubernetes 调度流程。明确选择其他调度器的 Pod 不会被静默改写。 ## HAMi 调度器 {#hami-scheduler} -HAMi 调度器负责将任务分配给适当的节点和设备。同时,调度器需要维护异构计算设备的全局视图以进行监控。 +HAMi 调度器负责同时选择节点和设备。Kubernetes 设备插件通常只能通告整数形式的资源数量,无法完整描述设备型号、显存容量、算力、健康状态或拓扑等属性。因此,HAMi 设备插件通过节点注解上报详细设备信息,使调度器能够维护集群范围的设备视图。 + +HAMi 以[调度器扩展程序](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md)的方式参与标准调度流程,并不会取代 Kubernetes 调度机制。它根据资源请求筛选候选节点,应用配置的 binpack 或 spread 策略,绑定 Pod,并将分配结果写入 `hami.io/vgpu-devices-allocated` 等注解。 ## 设备插件 {#device-plugin} -设备插件层从任务的注释字段获取调度结果,并将相应的设备映射到容器。 +HAMi 设备插件运行在每个受支持的加速器节点上,并实现 Kubernetes [设备插件 API](https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/)。它发现本地设备、向 kubelet 注册可分配资源、上报调度所需的设备详情,并在 Pod 绑定后处理 kubelet 的 `Allocate` 请求。 + +分配期间,设备插件从 Pod 注解中读取调度结果,并将选定的设备提供给容器。根据厂商集成方式的不同,它可能会挂载设备文件和运行时库,或注入描述分配配额的环境变量。 ## HAMi-Core {#hami-core} -容器内资源控制负责监控容器内的资源使用情况,并提供硬隔离能力。 +HAMi-Core 通过 `libvgpu.so` 为 NVIDIA 虚拟 GPU 提供运行时控制。设备插件借助 `/etc/ld.so.preload` 将该库加载到容器中。随后,HAMi-Core 会拦截 CUDA 显存分配和内核启动调用:超出已分配显存配额的申请会收到显存不足错误,算力使用则会被节流至请求的限制。它还会调整 NVML 返回结果,使应用看到已分配的显存,而不是物理设备的全部显存。 + +这种机制属于用户态控制,并非硬件安全边界。绕过被拦截库的应用(例如使用直接驱动调用或 Docker-in-Docker)可能绕过这些限制。当 GPU 支持 MIG 且需要硬件级隔离时,请使用 [NVIDIA MIG](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/)。完整的拦截和分配流程请参阅 [GPU 虚拟化原理](./gpu-virtualization.md)。 + +## HAMi 与时间切片和 MIG 有何区别? + +| 方案 | 共享方式 | 显存和算力边界 | 适用场景 | +| --- | --- | --- | --- | +| 时间切片 | 多个工作负载轮流使用同一 GPU | 不提供工作负载级 GPU 显存隔离 | 不需要严格配额的简单并发场景 | +| HAMi 虚拟 GPU | 多个工作负载按灵活请求的配额共享 GPU | 用户态显存控制和算力节流 | 在多种 GPU 上进行细粒度、动态共享 | +| NVIDIA MIG | 将受支持的 GPU 划分为固定硬件分区 | 硬件级显存和算力隔离 | 在支持 MIG 的 GPU 上实现强隔离 | + +HAMi 也支持动态 MIG 分配,因此这些方案并不总是互斥。具体选择取决于加速器型号、工作负载、隔离要求和期望的分区粒度。 + +## 产品架构与参考部署 + +本文介绍 HAMi 的产品组件,以及各类集成共有的请求处理流程。完整集群还可以包括 CNI、厂商驱动和运行时、监控系统及可选的仪表盘。有关一种面向 NVIDIA 的部署拓扑及其依赖关系,请参阅 [HAMi 安装后的集群架构](./hami-architecture.md)。 + +## 参考资料 + +- [Kubernetes 设备插件](https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/) +- [Kubernetes 调度器扩展程序设计](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md) +- [HAMi-Core 源代码](https://github.com/Project-HAMi/HAMi-core) +- [NVIDIA Multi-Instance GPU 用户指南](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) + +## 下一步 + +- 深入了解 [GPU 虚拟化原理](./gpu-virtualization.md) +- 查看[参考集群架构](./hami-architecture.md) +- 检查[安装前提条件](../installation/prerequisites.md) diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/hami-architecture.md b/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/hami-architecture.md index e664b6309..c31cb1c6a 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/hami-architecture.md +++ b/i18n/zh/docusaurus-plugin-content-docs/current/core-concepts/hami-architecture.md @@ -4,6 +4,12 @@ title: "HAMi 安装后的集群架构" 完成 HAMi 安装后,集群不再是一个普通的 Kubernetes 集群,它变成了一个具备 GPU 虚拟化能力的 AI 基础设施平台。本文将拆解安装完成后集群中每一层、每一个组件的职责和依赖关系。 +:::note + +本文描述一种面向 NVIDIA 的参考部署拓扑(安装完成后),其中可能包含 GPU Operator、Prometheus 和 WebUI 等可选组件。HAMi 的产品组件与请求处理流程见[架构设计](./architecture.md)。 + +::: + ## 5 层架构总览 安装完成后的集群由 5 层组成,每一层为上层提供服务: diff --git a/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/architecture.md b/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/architecture.md index 66b139669..1152a057b 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/architecture.md +++ b/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/architecture.md @@ -3,21 +3,84 @@ title: 架构设计 translated: true --- -HAMi 的整体架构如下所示: +HAMi 通过设备感知调度和运行时资源控制,扩展 Kubernetes 对异构 AI 加速器的管理能力。其架构将集群级调度决策、节点级设备分配以及支持场景下的容器内资源控制相互分离。 -![HAMi 系统架构图,显示各组件之间的关系和交互](/img/docs/common/core-concepts/architect.jpg) +![HAMi 组件及 GPU 工作负载调度流程](/img/docs/common/core-concepts/hami-architecture.svg) -HAMi 由以下组件组成: +## HAMi 有哪些核心组件? -- HAMi MutatingWebhook -- HAMi scheduler-extender -- 设备插件 (HAMi-device-plugin) -- 容器内资源控制 (HAMi-Core) +在加速器工作负载的整个生命周期中,HAMi 会协调以下四个组件: -HAMi MutatingWebhook 检查该任务是否可以由 HAMi 处理,它扫描每个提交的 Pod 的资源字段,如果这些 Pod 所需的每个资源是 'cpu'、'memory' 或 HAMi 资源,则会将该 Pod 的 schedulerName 字段设置为 'HAMi-scheduler'。 +| 组件 | 运行方式 | 主要职责 | +| --- | --- | --- | +| HAMi MutatingWebhook | `hami-scheduler` Deployment 的一部分 | 将请求 HAMi 管理资源的 Pod 交给 `hami-scheduler` | +| HAMi 调度器扩展程序 | `hami-scheduler` Deployment 的一部分 | 根据集群范围内的可用资源视图选择节点和物理设备 | +| HAMi 设备插件 | 加速器节点上的 DaemonSet | 向 kubelet 注册设备,并为容器准备选定的设备 | +| HAMi-Core | 注入受支持容器的动态库 | 在运行时执行已分配的显存和算力限制 | -HAMi 调度器负责将任务分配给适当的节点和设备。同时,调度器需要维护异构计算设备的全局视图以进行监控。 +具体资源名称和控制机制取决于设备厂商。例如,NVIDIA 工作负载可以使用 `nvidia.com/gpumem` 请求以 MiB 为单位的显存,并使用 `nvidia.com/gpucores` 请求百分比形式的算力。其他设备使用厂商特定的资源名称,支持的分配粒度也可能不同。当前支持情况请参阅[常见问题](../faq/faq.md)。 -设备插件层从任务的注释字段获取调度结果,并将相应的设备映射到容器。 +## 工作负载如何通过 HAMi 运行? -容器内资源控制负责监控容器内的资源使用情况,并提供硬隔离能力。 +1. **准入:** 当 Pod 请求 HAMi 管理的设备时,MutatingWebhook 会将 `spec.schedulerName` 设置为 `hami-scheduler`;如果 Pod 已明确指定调度器,则不会改写。 +2. **调度:** HAMi 调度器扩展程序将 Pod 请求与各节点上报的设备信息结合起来,在调度过程中排除无法满足请求的节点,并选择合适的物理设备。 +3. **分配:** 调度器把选定的设备和配额写入 Pod 注解。在目标节点上,kubelet 调用 HAMi 设备插件;设备插件读取调度结果,并将设备提供给容器。 +4. **运行时控制:** 对于支持容器内控制的设备,设备插件会注入所需的运行时库和配置。对于 NVIDIA 虚拟 GPU,HAMi-Core 会拦截相关 CUDA 和 NVML 调用,以执行已分配的显存和算力限制。 + +这种职责划分让集群策略留在控制平面、硬件发现和分配发生在各节点,并让工作负载级资源控制靠近应用程序。 + +## HAMi MutatingWebhook {#hami-mutatingwebhook} + +MutatingWebhook 是准入入口。它检查新建 Pod 的资源请求,以确定是否应由 HAMi 处理。对于符合条件的 Pod,它会设置: + +```yaml +spec: + schedulerName: hami-scheduler +``` + +没有请求 HAMi 管理资源的 Pod 会继续使用常规 Kubernetes 调度流程。明确选择其他调度器的 Pod 不会被静默改写。 + +## HAMi 调度器 {#hami-scheduler} + +HAMi 调度器负责同时选择节点和设备。Kubernetes 设备插件通常只能通告整数形式的资源数量,无法完整描述设备型号、显存容量、算力、健康状态或拓扑等属性。因此,HAMi 设备插件通过节点注解上报详细设备信息,使调度器能够维护集群范围的设备视图。 + +HAMi 以[调度器扩展程序](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md)的方式参与标准调度流程,并不会取代 Kubernetes 调度机制。它根据资源请求筛选候选节点,应用配置的 binpack 或 spread 策略,绑定 Pod,并将分配结果写入 `hami.io/vgpu-devices-allocated` 等注解。 + +## 设备插件 {#device-plugin} + +HAMi 设备插件运行在每个受支持的加速器节点上,并实现 Kubernetes [设备插件 API](https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/)。它发现本地设备、向 kubelet 注册可分配资源、上报调度所需的设备详情,并在 Pod 绑定后处理 kubelet 的 `Allocate` 请求。 + +分配期间,设备插件从 Pod 注解中读取调度结果,并将选定的设备提供给容器。根据厂商集成方式的不同,它可能会挂载设备文件和运行时库,或注入描述分配配额的环境变量。 + +## HAMi-Core {#hami-core} + +HAMi-Core 通过 `libvgpu.so` 为 NVIDIA 虚拟 GPU 提供运行时控制。设备插件借助 `/etc/ld.so.preload` 将该库加载到容器中。随后,HAMi-Core 会拦截 CUDA 显存分配和内核启动调用:超出已分配显存配额的申请会收到显存不足错误,算力使用则会被节流至请求的限制。它还会调整 NVML 返回结果,使应用看到已分配的显存,而不是物理设备的全部显存。 + +这种机制属于用户态控制,并非硬件安全边界。绕过被拦截库的应用(例如使用直接驱动调用或 Docker-in-Docker)可能绕过这些限制。当 GPU 支持 MIG 且需要硬件级隔离时,请使用 [NVIDIA MIG](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/)。完整的拦截和分配流程请参阅 [GPU 虚拟化原理](./gpu-virtualization.md)。 + +## HAMi 与时间切片和 MIG 有何区别? + +| 方案 | 共享方式 | 显存和算力边界 | 适用场景 | +| --- | --- | --- | --- | +| 时间切片 | 多个工作负载轮流使用同一 GPU | 不提供工作负载级 GPU 显存隔离 | 不需要严格配额的简单并发场景 | +| HAMi 虚拟 GPU | 多个工作负载按灵活请求的配额共享 GPU | 用户态显存控制和算力节流 | 在多种 GPU 上进行细粒度、动态共享 | +| NVIDIA MIG | 将受支持的 GPU 划分为固定硬件分区 | 硬件级显存和算力隔离 | 在支持 MIG 的 GPU 上实现强隔离 | + +HAMi 也支持动态 MIG 分配,因此这些方案并不总是互斥。具体选择取决于加速器型号、工作负载、隔离要求和期望的分区粒度。 + +## 产品架构与参考部署 + +本文介绍 HAMi 的产品组件,以及各类集成共有的请求处理流程。完整集群还可以包括 CNI、厂商驱动和运行时、监控系统及可选的仪表盘。有关一种面向 NVIDIA 的部署拓扑及其依赖关系,请参阅 [HAMi 安装后的集群架构](./hami-architecture.md)。 + +## 参考资料 + +- [Kubernetes 设备插件](https://kubernetes.io/zh-cn/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/) +- [Kubernetes 调度器扩展程序设计](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md) +- [HAMi-Core 源代码](https://github.com/Project-HAMi/HAMi-core) +- [NVIDIA Multi-Instance GPU 用户指南](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) + +## 下一步 + +- 深入了解 [GPU 虚拟化原理](./gpu-virtualization.md) +- 查看[参考集群架构](./hami-architecture.md) +- 检查[安装前提条件](../installation/prerequisites.md) diff --git a/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/hami-architecture.md b/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/hami-architecture.md index e664b6309..c31cb1c6a 100644 --- a/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/hami-architecture.md +++ b/i18n/zh/docusaurus-plugin-content-docs/version-v2.9.0/core-concepts/hami-architecture.md @@ -4,6 +4,12 @@ title: "HAMi 安装后的集群架构" 完成 HAMi 安装后,集群不再是一个普通的 Kubernetes 集群,它变成了一个具备 GPU 虚拟化能力的 AI 基础设施平台。本文将拆解安装完成后集群中每一层、每一个组件的职责和依赖关系。 +:::note + +本文描述一种面向 NVIDIA 的参考部署拓扑(安装完成后),其中可能包含 GPU Operator、Prometheus 和 WebUI 等可选组件。HAMi 的产品组件与请求处理流程见[架构设计](./architecture.md)。 + +::: + ## 5 层架构总览 安装完成后的集群由 5 层组成,每一层为上层提供服务: diff --git a/i18n/zh/docusaurus-theme-classic/footer.json b/i18n/zh/docusaurus-theme-classic/footer.json index ccd55c83a..1d872ce42 100644 --- a/i18n/zh/docusaurus-theme-classic/footer.json +++ b/i18n/zh/docusaurus-theme-classic/footer.json @@ -91,6 +91,22 @@ "message": "Slack ", "description": "The label of footer link with label=Slack linking to https://cloud-native.slack.com/archives/C07T10BU4R2/" }, + "link.title.Legal": { + "message": "法律信息", + "description": "The title of the footer links column with title=Legal in the footer" + }, + "link.item.label.About": { + "message": "关于", + "description": "The label of footer link with label=About linking to /about" + }, + "link.item.label.Privacy": { + "message": "隐私", + "description": "The label of footer link with label=Privacy linking to /privacy" + }, + "link.item.label.Terms": { + "message": "使用条款", + "description": "The label of footer link with label=Terms linking to /terms" + }, "copyright": { "message": "\n
\n © HAMi Authors 2026 | Documentation Distributed under CC-BY-4.0 | Powered by Netlify\n
\n
\n Copyright © HAMi a Series of LF Projects, LLC\n
\n For website terms of use, trademark policy and other project policies please see lfprojects.org/policies/.\n ", "description": "The footer copyright" diff --git a/src/components/JsonLd.js b/src/components/JsonLd.js new file mode 100644 index 000000000..05abf466c --- /dev/null +++ b/src/components/JsonLd.js @@ -0,0 +1,17 @@ +import React from "react"; +import Head from "@docusaurus/Head"; +import { serializeJsonLd } from "../utils/jsonLd"; + +export default function JsonLd({ data }) { + if (!data) { + return null; + } + + return ( + + + + ); +} diff --git a/src/pages/about.js b/src/pages/about.js new file mode 100644 index 000000000..4968e462e --- /dev/null +++ b/src/pages/about.js @@ -0,0 +1,183 @@ +import React from "react"; +import Link from "@docusaurus/Link"; +import { useLocation } from "@docusaurus/router"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import Layout from "@theme/Layout"; +import JsonLd from "../components/JsonLd"; +import { buildWebPageJsonLd } from "../utils/jsonLd"; +import styles from "./trust.module.css"; + +const externalProps = { + target: "_blank", + rel: "noopener noreferrer", +}; + +export default function AboutPage() { + const { i18n, siteConfig } = useDocusaurusContext(); + const { pathname } = useLocation(); + const isZh = i18n.currentLocale.startsWith("zh"); + const title = isZh ? "关于 HAMi" : "About HAMi"; + const description = isZh + ? "了解 HAMi 开源项目、治理模式、许可证和官方社区资源。" + : "Learn about the HAMi open-source project, its governance, licenses, and official community resources."; + + return ( + + +
+
+
+

{isZh ? "关于 HAMi" : "About HAMi"}

+

+ {isZh + ? "HAMi 帮助 Kubernetes 集群共享和调度异构 AI 计算设备。" + : "HAMi helps Kubernetes clusters share and schedule heterogeneous AI computing devices."} +

+
+
+ +
+
+
+
+

{isZh ? "什么是 HAMi?" : "What is HAMi?"}

+

+ {isZh + ? "HAMi(异构 AI 计算虚拟化中间件)是一个开源项目,为 Kubernetes 中的 GPU、NPU 和其他加速器提供设备共享、细粒度资源分配和设备感知调度能力。项目的前身是 k8s-vGPU-scheduler。" + : "HAMi (Heterogeneous AI Computing Virtualization Middleware) is an open-source project that provides device sharing, fine-grained resource allocation, and device-aware scheduling for GPUs, NPUs, and other accelerators in Kubernetes. It was formerly known as k8s-vGPU-scheduler."} +

+

+ {isZh ? ( + <> + HAMi 是{" "} + + CNCF 孵化项目 + + 。有关技术能力和使用方式,请从 HAMi 文档开始。 + + ) : ( + <> + HAMi is a{" "} + + CNCF Incubating project + + . For technical capabilities and usage, start with the{" "} + HAMi documentation. + + )} +

+
+ +
+

{isZh ? "开放治理" : "Open governance"}

+

+ {isZh ? ( + <> + HAMi 由开放社区共同开发。项目角色、决策流程和维护者职责记录在 + 治理文档中,社区参与遵循{" "} + + CNCF 行为准则 + + 。你可以通过社区页面加入讨论、会议和贡献。 + + ) : ( + <> + HAMi is developed by an open community. Project roles, decision-making, and + maintainer responsibilities are documented in the{" "} + governance guide, and community + participation follows the{" "} + + CNCF Code of Conduct + + . Visit the community page to join discussions, + meetings, and contributions. + + )} +

+
+ +
+

{isZh ? "参与贡献" : "Contribute"}

+

+ {isZh ? ( + <> + HAMi 欢迎社区贡献。请从 + 贡献指南 + 开始,了解如何报告问题、提交变更,以及遵循项目的 GitHub 工作流。 + + ) : ( + <> + HAMi welcomes community contributions. Start with the{" "} + contributor guide to report + issues, propose changes, and follow the project's GitHub workflow. + + )} +

+
+ +
+

{isZh ? "许可证" : "Licenses"}

+

+ {isZh + ? "HAMi 代码和本网站内容使用不同的许可证。HAMi 软件代码采用 Apache License 2.0;本网站及其文档采用 Creative Commons Attribution 4.0 International(CC BY 4.0)。" + : "HAMi code and this website's content use different licenses. The HAMi software is licensed under Apache License 2.0; this website and its documentation are licensed under Creative Commons Attribution 4.0 International (CC BY 4.0)."} +

+
+
+ + +
+
+
+
+ ); +} diff --git a/src/pages/index.js b/src/pages/index.js index be811bd14..d8ff02939 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useCallback, useState } from "react"; +import React, { useEffect, useMemo, useRef, useCallback, useState } from "react"; import clsx from "clsx"; import Layout from "@theme/Layout"; import Link from "@docusaurus/Link"; @@ -29,6 +29,8 @@ import adoptersData from "../data/adopters.json"; import ecosystemData from "../data/ecosystem.json"; import heroStats from "../data/home/heroStats"; import valueCards from "../data/home/valueCards"; +import JsonLd from "../components/JsonLd"; +import { buildSiteJsonLd, serializeJsonLd } from "../utils/jsonLd"; const cardIcons = { "network-wired": faNetworkWired, @@ -448,7 +450,7 @@ function RuntimeLaneCard({ lane, locale }) { } export default function Home() { - const { i18n } = useDocusaurusContext(); + const { i18n, siteConfig } = useDocusaurusContext(); const { withBaseUrl } = useBaseUrlUtils(); const isZh = i18n.currentLocale === "zh"; const [starsCount, setStarsCount] = useState(3100); @@ -515,6 +517,19 @@ export default function Home() { return () => controller.abort(); }, []); + const siteJsonLd = useMemo( + () => + serializeJsonLd( + buildSiteJsonLd({ + siteUrl: siteConfig.url, + name: siteConfig.title, + description: siteConfig.tagline, + logoPath: siteConfig.customFields.defaultOgImage, + }), + ), + [siteConfig.url, siteConfig.title, siteConfig.tagline, siteConfig.customFields.defaultOgImage], + ); + return ( +
diff --git a/src/pages/privacy.js b/src/pages/privacy.js new file mode 100644 index 000000000..13a6fc361 --- /dev/null +++ b/src/pages/privacy.js @@ -0,0 +1,134 @@ +import React from "react"; +import { useLocation } from "@docusaurus/router"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import Layout from "@theme/Layout"; +import JsonLd from "../components/JsonLd"; +import { buildWebPageJsonLd } from "../utils/jsonLd"; +import styles from "./trust.module.css"; + +const externalProps = { + target: "_blank", + rel: "noopener noreferrer", +}; + +export default function PrivacyPage() { + const { i18n, siteConfig } = useDocusaurusContext(); + const { pathname } = useLocation(); + const isZh = i18n.currentLocale.startsWith("zh"); + const title = isZh ? "隐私" : "Privacy"; + const description = isZh + ? "了解适用于 HAMi 网站的隐私政策和第三方服务。" + : "Learn about the privacy policies and third-party services relevant to the HAMi website."; + + return ( + + +
+
+
+

{isZh ? "隐私" : "Privacy"}

+

+ {isZh + ? "本页说明 HAMi 网站使用的服务,并指向适用的权威隐私政策。" + : "This page identifies services used by the HAMi website and points to the authoritative privacy policies."} +

+
+
+ +
+
+
+
+

{isZh ? "LF Projects 隐私政策" : "LF Projects privacy policies"}

+

+ {isZh + ? "本页是便于查阅的网站说明,不替代 LF Projects 发布的政策。请以链接中的政策原文为准。" + : "This page is a convenient website summary, not a replacement for policies published by LF Projects. The linked policies are authoritative."} +

+

+ {isZh + ? "HAMi 是 LF Projects, LLC 旗下系列项目之一。有关个人信息和遥测数据处理方式的说明,请查阅 LF Projects 的隐私政策与遥测数据政策。" + : "HAMi is a Series of LF Projects, LLC. For information about the handling of personal information and telemetry data, consult the LF Projects Privacy Policy and Telemetry Data Policy."} +

+
+ +
+

{isZh ? "本网站使用的服务" : "Services used by this website"}

+

+ {isZh + ? "project-hami.io 托管在 Netlify,并使用启用了 IP 匿名化的 Google Analytics 来了解网站的总体使用情况。这些服务可能会根据各自的隐私政策处理浏览器和网络提供的技术信息。" + : "project-hami.io is hosted on Netlify and uses Google Analytics with IP anonymization enabled to understand aggregate site usage. These services may process technical information supplied by browsers and networks under their respective privacy policies."} +

+

+ {isZh + ? "网站中的 GitHub Star 按钮会从 GitHub API 获取 HAMi 仓库的公开 Star 数量;如果请求失败,可能会改用 Shields.io。显示的数量会缓存在浏览器本地存储中。" + : "The GitHub star button fetches the HAMi repository's public star count from the GitHub API and may fall back to Shields.io if that request fails. The displayed count is cached in browser local storage."} +

+

+ {isZh + ? "本站的文档搜索在浏览器中运行,并使用浏览器本地存储记住语言偏好。HAMi 不要求创建账户即可阅读网站或使用文档搜索。" + : "Documentation search runs in the browser, and browser local storage remembers the selected language. HAMi does not require an account to read the website or use documentation search."} +

+
+
+ + +
+
+
+
+ ); +} diff --git a/src/pages/terms.js b/src/pages/terms.js new file mode 100644 index 000000000..19ea3f5a4 --- /dev/null +++ b/src/pages/terms.js @@ -0,0 +1,120 @@ +import React from "react"; +import { useLocation } from "@docusaurus/router"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import Layout from "@theme/Layout"; +import JsonLd from "../components/JsonLd"; +import { buildWebPageJsonLd } from "../utils/jsonLd"; +import styles from "./trust.module.css"; + +const externalProps = { + target: "_blank", + rel: "noopener noreferrer", +}; + +export default function TermsPage() { + const { i18n, siteConfig } = useDocusaurusContext(); + const { pathname } = useLocation(); + const isZh = i18n.currentLocale.startsWith("zh"); + const title = isZh ? "使用条款" : "Terms"; + const description = isZh + ? "查看适用于 HAMi 网站的 LF Projects 使用条款、商标和其他项目政策。" + : "Find the LF Projects terms of use, trademark policy, and other project policies relevant to the HAMi website."; + + return ( + + +
+
+
+

{isZh ? "使用条款" : "Terms"}

+

+ {isZh + ? "HAMi 网站的使用条款和商标规则由 LF Projects 发布。" + : "Terms for using the HAMi website and rules for project trademarks are published by LF Projects."} +

+
+
+ +
+
+
+
+

{isZh ? "网站使用条款" : "Website terms of use"}

+

+ {isZh + ? "本页不复制或修改法律文本。请以 LF Projects 发布的政策原文为准。" + : "This page does not reproduce or modify legal text. The policies published by LF Projects are authoritative."} +

+

+ {isZh + ? "HAMi 是 LF Projects, LLC 旗下系列项目之一。访问或使用 project-hami.io 时,请查阅 LF Projects 使用条款以及政策索引中列出的其他适用政策。" + : "HAMi is a Series of LF Projects, LLC. When accessing or using project-hami.io, consult the LF Projects Terms of Use and any other applicable policies listed in the policy index."} +

+
+ +
+

{isZh ? "名称与商标" : "Names and trademarks"}

+

+ {isZh + ? "HAMi 及相关名称、标志的使用应遵循 LF Projects 商标政策。开源许可证授予的代码或文档使用权并不自动授予商标使用权。" + : "Use of HAMi and related names or logos is subject to the LF Projects Trademark Policy. Rights granted by open-source code or documentation licenses do not automatically grant trademark rights."} +

+
+ +
+

{isZh ? "项目许可证" : "Project licenses"}

+

+ {isZh + ? "HAMi 软件代码采用 Apache License 2.0。本网站及其文档采用 CC BY 4.0。许可证文本规定了各自材料的复制、修改和分发条件。" + : "HAMi software is licensed under Apache License 2.0. This website and its documentation are licensed under CC BY 4.0. The license texts define the conditions for copying, modifying, and distributing their respective materials."} +

+
+
+ + +
+
+
+
+ ); +} diff --git a/src/pages/trust.module.css b/src/pages/trust.module.css new file mode 100644 index 000000000..f22bdb88f --- /dev/null +++ b/src/pages/trust.module.css @@ -0,0 +1,90 @@ +.page { + padding-bottom: var(--hami-space-64); +} + +.hero { + padding: var(--hami-space-64) 0 var(--hami-space-32); + background: + radial-gradient(circle at 0% 0%, rgba(17, 208, 93, 0.18), transparent 34%), + linear-gradient(180deg, rgba(17, 208, 93, 0.08), transparent 70%); +} + +.title { + margin: 0 0 var(--hami-space-16); + max-width: 880px; +} + +.subtitle { + margin: 0; + max-width: 760px; + font-size: 1.08rem; +} + +.content { + padding: var(--hami-space-32) 0; +} + +.contentGrid { + display: grid; + grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr); + gap: var(--hami-space-24); + align-items: start; +} + +.main { + min-width: 0; +} + +.main section + section { + margin-top: var(--hami-space-32); +} + +.main h2 { + margin-bottom: var(--hami-space-12); +} + +.main p:last-child { + margin-bottom: 0; +} + +.card { + padding: var(--hami-space-24); + border: 1px solid var(--hami-color-border); + border-radius: var(--hami-radius-lg); + background: var(--hami-color-surface); + box-shadow: var(--hami-shadow-sm); +} + +.card h2 { + margin-top: 0; + font-size: 1.2rem; +} + +.linkList { + display: grid; + gap: 0.75rem; + margin: 0; + padding: 0; + list-style: none; +} + +.linkList a { + font-weight: 600; +} + +.notice { + padding: var(--hami-space-16); + border-left: 4px solid var(--hami-color-primary); + border-radius: 0 var(--hami-radius-md) var(--hami-radius-md) 0; + background: rgba(17, 208, 93, 0.08); +} + +@media (max-width: 996px) { + .hero { + padding: var(--hami-space-48) 0 var(--hami-space-24); + } + + .contentGrid { + grid-template-columns: 1fr; + } +} diff --git a/src/theme/DocItem/Layout/index.js b/src/theme/DocItem/Layout/index.js index fad616e8c..ee6f0996c 100644 --- a/src/theme/DocItem/Layout/index.js +++ b/src/theme/DocItem/Layout/index.js @@ -3,8 +3,10 @@ * Moves DocVersionBadge inline with DocBreadcrumbs on desktop so the version * badge doesn't waste a full row by itself. */ -import React from "react"; +import React, { useMemo } from "react"; import clsx from "clsx"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import useRouteContext from "@docusaurus/useRouteContext"; import { useWindowSize } from "@docusaurus/theme-common"; import { useDoc } from "@docusaurus/plugin-content-docs/client"; import DocItemPaginator from "@theme/DocItem/Paginator"; @@ -18,6 +20,8 @@ import DocBreadcrumbs from "@theme/DocBreadcrumbs"; import ContentVisibility from "@theme/ContentVisibility"; import styles from "./styles.module.css"; import useImageLightbox from "../../utils/useImageLightbox"; +import JsonLd from "../../../components/JsonLd"; +import { buildTechArticleJsonLd, serializeJsonLd } from "../../../utils/jsonLd"; function useDocTOC() { const { frontMatter, toc } = useDoc(); @@ -35,26 +39,68 @@ function useDocTOC() { export default function DocItemLayout({ children }) { useImageLightbox(); const docTOC = useDocTOC(); - const { metadata } = useDoc(); + const { metadata, frontMatter } = useDoc(); + const { i18n, siteConfig } = useDocusaurusContext(); + const { plugin } = useRouteContext(); + const skipJsonLd = Boolean(frontMatter.unlisted || frontMatter.draft); + const techArticleJsonLd = useMemo(() => { + if (skipJsonLd) { + return null; + } + return serializeJsonLd( + buildTechArticleJsonLd({ + siteUrl: siteConfig.url, + title: metadata.title, + description: metadata.description, + permalink: metadata.permalink, + image: frontMatter.image, + locale: i18n.currentLocale, + lastUpdatedAt: metadata.lastUpdatedAt, + version: + plugin?.id === "tutorials" + ? undefined + : metadata.version === "current" + ? "next" + : metadata.version, + organizationLogo: siteConfig.customFields?.defaultOgImage, + }), + ); + }, [ + skipJsonLd, + siteConfig.url, + siteConfig.customFields?.defaultOgImage, + metadata.title, + metadata.description, + metadata.permalink, + metadata.lastUpdatedAt, + metadata.version, + frontMatter.image, + i18n.currentLocale, + plugin?.id, + ]); + return ( -
-
- - -
-
-
- - -
- {docTOC.mobile} - {children} - -
- + <> + +
+
+ + +
+
+
+ + +
+ {docTOC.mobile} + {children} + +
+ +
+ {docTOC.desktop &&
{docTOC.desktop}
}
- {docTOC.desktop &&
{docTOC.desktop}
} -
+ ); } diff --git a/src/utils/jsonLd.js b/src/utils/jsonLd.js new file mode 100644 index 000000000..353e11038 --- /dev/null +++ b/src/utils/jsonLd.js @@ -0,0 +1,177 @@ +const OFFICIAL_PROFILES = [ + "https://github.com/Project-HAMi", + "https://www.linkedin.com/company/project-hami-io/", + "https://x.com/HAMiProject", +]; + +function normalizeSiteUrl(siteUrl) { + return String(siteUrl ?? "").replace(/\/+$/, ""); +} + +function absoluteUrl(siteUrl, path) { + if (!path) { + return undefined; + } + if (/^https?:\/\//i.test(path)) { + return path; + } + return `${normalizeSiteUrl(siteUrl)}/${String(path).replace(/^\/+/, "")}`; +} + +function schemaLanguage(locale) { + if (typeof locale === "string" && locale.toLowerCase().startsWith("zh")) { + return "zh-CN"; + } + return locale || "en"; +} + +function canonicalPageUrl(siteUrl, permalink) { + const absolutePermalink = absoluteUrl(siteUrl, permalink); + if (!absolutePermalink) { + return undefined; + } + const home = `${normalizeSiteUrl(siteUrl)}/`; + return absolutePermalink === home ? absolutePermalink : absolutePermalink.replace(/\/$/, ""); +} + +function organizationReference(siteUrl, name = "HAMi", logoPath) { + return { + "@type": "Organization", + "@id": `${normalizeSiteUrl(siteUrl)}/#organization`, + name, + url: `${normalizeSiteUrl(siteUrl)}/`, + ...(logoPath && { + logo: { + "@type": "ImageObject", + url: absoluteUrl(siteUrl, logoPath), + }, + }), + }; +} + +export function buildSiteJsonLd({ + siteUrl, + name, + description, + logoPath, + profiles = OFFICIAL_PROFILES, +}) { + const rootUrl = `${normalizeSiteUrl(siteUrl)}/`; + const organizationId = `${rootUrl}#organization`; + const websiteId = `${rootUrl}#website`; + const organization = { + ...organizationReference(siteUrl, name, logoPath), + ...(description && { description }), + alternateName: ["Heterogeneous AI Computing Virtualization Middleware", "k8s-vGPU-scheduler"], + sameAs: profiles, + parentOrganization: { + "@type": "Organization", + name: "LF Projects, LLC", + url: "https://lfprojects.org/", + }, + memberOf: { + "@type": "Organization", + name: "Cloud Native Computing Foundation", + url: "https://www.cncf.io/", + }, + }; + + return { + "@context": "https://schema.org", + "@graph": [ + organization, + { + "@type": "WebSite", + "@id": websiteId, + name, + url: rootUrl, + description, + inLanguage: ["en", "zh-CN"], + publisher: { + "@id": organizationId, + }, + }, + ], + }; +} + +export function buildWebPageJsonLd({ + siteUrl, + type = "WebPage", + name, + description, + permalink, + locale, +}) { + const pageUrl = canonicalPageUrl(siteUrl, permalink); + + return { + "@context": "https://schema.org", + "@type": type, + name, + ...(description && { description }), + ...(pageUrl && { "@id": pageUrl, url: pageUrl }), + inLanguage: schemaLanguage(locale), + isPartOf: { + "@type": "WebSite", + "@id": `${normalizeSiteUrl(siteUrl)}/#website`, + }, + about: { + "@id": `${normalizeSiteUrl(siteUrl)}/#organization`, + }, + publisher: { + "@id": `${normalizeSiteUrl(siteUrl)}/#organization`, + }, + }; +} + +export function buildTechArticleJsonLd({ + siteUrl, + title, + description, + permalink, + image, + locale, + lastUpdatedAt, + version, + organizationName = "HAMi", + organizationLogo = "/img/hami-graph-color.png", +}) { + const pageUrl = canonicalPageUrl(siteUrl, permalink); + const imageUrl = absoluteUrl(siteUrl, image || organizationLogo); + const lastUpdatedDate = Number.isFinite(lastUpdatedAt) ? new Date(lastUpdatedAt) : undefined; + const modifiedDate = + lastUpdatedDate && !Number.isNaN(lastUpdatedDate.getTime()) + ? lastUpdatedDate.toISOString() + : undefined; + const organization = organizationReference(siteUrl, organizationName, organizationLogo); + + return { + "@context": "https://schema.org", + "@type": "TechArticle", + headline: title, + ...(description && { description }), + ...(pageUrl && { + "@id": `${pageUrl}#article`, + url: pageUrl, + mainEntityOfPage: { + "@type": "WebPage", + "@id": pageUrl, + }, + }), + ...(imageUrl && { image: imageUrl }), + inLanguage: schemaLanguage(locale), + ...(modifiedDate && { dateModified: modifiedDate }), + ...(version && { version }), + author: organization, + publisher: organization, + isPartOf: { + "@type": "WebSite", + "@id": `${normalizeSiteUrl(siteUrl)}/#website`, + }, + }; +} + +export function serializeJsonLd(value) { + return JSON.stringify(value).replace(/ { + const schema = buildSiteJsonLd({ + siteUrl: `${siteUrl}/`, + name: "HAMi", + description: "Heterogeneous AI Computing Virtualization Middleware", + logoPath: "/img/hami-graph-color.png", + }); + + assert.equal(schema["@graph"][0]["@id"], `${siteUrl}/#organization`); + assert.equal(schema["@graph"][0].logo.url, `${siteUrl}/img/hami-graph-color.png`); + assert.equal( + schema["@graph"][0].description, + "Heterogeneous AI Computing Virtualization Middleware", + ); + assert.deepEqual(schema["@graph"][0].alternateName, [ + "Heterogeneous AI Computing Virtualization Middleware", + "k8s-vGPU-scheduler", + ]); + assert.equal(schema["@graph"][0].parentOrganization.name, "LF Projects, LLC"); + assert.equal(schema["@graph"][0].memberOf.url, "https://www.cncf.io/"); + assert.deepEqual(schema["@graph"][1].publisher, { + "@id": `${siteUrl}/#organization`, + }); + assert.equal(JSON.stringify(schema).includes("SearchAction"), false); + assert.equal("potentialAction" in schema["@graph"][1], false); +}); + +test("TechArticle uses canonical metadata and a millisecond modification date", () => { + const schema = buildTechArticleJsonLd({ + siteUrl, + title: "Architecture", + description: "How HAMi schedules and controls accelerator workloads.", + permalink: "/docs/core-concepts/architecture", + image: "/img/hami-graph-color.png", + locale: "en", + lastUpdatedAt: Date.UTC(2026, 6, 29, 12, 0, 0), + version: "v2.9.0", + }); + + assert.equal(schema.url, `${siteUrl}/docs/core-concepts/architecture`); + assert.equal(schema["@id"], `${schema.url}#article`); + assert.equal(schema.mainEntityOfPage["@id"], schema.url); + assert.equal(schema.image, `${siteUrl}/img/hami-graph-color.png`); + assert.equal("datePublished" in schema, false); + assert.equal(schema.dateModified, "2026-07-29T12:00:00.000Z"); + assert.equal(schema.author.name, "HAMi"); + assert.equal(schema.publisher["@id"], `${siteUrl}/#organization`); + assert.equal(schema.publisher.logo.url, `${siteUrl}/img/hami-graph-color.png`); + assert.equal(schema.version, "v2.9.0"); +}); + +test("TechArticle localizes Chinese and omits unavailable optional metadata", () => { + const schema = buildTechArticleJsonLd({ + siteUrl, + title: "教程", + permalink: "/zh/tutorials/", + locale: "zh", + }); + + assert.equal(schema.inLanguage, "zh-CN"); + assert.equal(schema.url, `${siteUrl}/zh/tutorials`); + assert.equal(schema.image, `${siteUrl}/img/hami-graph-color.png`); + assert.equal("description" in schema, false); + assert.equal("datePublished" in schema, false); + assert.equal("dateModified" in schema, false); + assert.equal("version" in schema, false); +}); + +test("TechArticle uses a site-root image and does not require locale or permalink", () => { + const withDefaultImage = buildTechArticleJsonLd({ + siteUrl, + title: "Architecture", + permalink: "/zh/docs/core-concepts/architecture", + locale: "zh-Hans", + }); + const withoutPermalink = buildTechArticleJsonLd({ + siteUrl, + title: "Architecture", + }); + + assert.equal(withDefaultImage.inLanguage, "zh-CN"); + assert.equal(withDefaultImage.image, `${siteUrl}/img/hami-graph-color.png`); + assert.equal("url" in withoutPermalink, false); + assert.equal("mainEntityOfPage" in withoutPermalink, false); + assert.equal(withoutPermalink.inLanguage, "en"); +}); + +test("JSON-LD serialization prevents script-tag breakout", () => { + const serialized = serializeJsonLd({ title: "" }); + + assert.equal(serialized.includes(""), false); + assert.equal(serialized.includes("\\u003c/script>"), true); +}); + +test("AboutPage schema points at the site Organization without copying legal text", () => { + const schema = buildWebPageJsonLd({ + siteUrl, + type: "AboutPage", + name: "About HAMi", + description: "Learn about the HAMi open-source project.", + permalink: "/about", + locale: "en", + }); + + assert.equal(schema["@type"], "AboutPage"); + assert.equal(schema.url, `${siteUrl}/about`); + assert.equal(schema.about["@id"], `${siteUrl}/#organization`); + assert.equal(schema.isPartOf["@id"], `${siteUrl}/#website`); +}); diff --git a/versioned_docs/version-v2.9.0/core-concepts/architecture.md b/versioned_docs/version-v2.9.0/core-concepts/architecture.md index 99e2bdf4b..e9ee1fd02 100644 --- a/versioned_docs/version-v2.9.0/core-concepts/architecture.md +++ b/versioned_docs/version-v2.9.0/core-concepts/architecture.md @@ -2,29 +2,84 @@ title: Architecture --- -The overall architecture of HAMi is shown as below: +HAMi extends Kubernetes with device-aware scheduling and runtime resource controls for heterogeneous AI accelerators. Its architecture separates cluster-wide placement decisions from node-level device allocation and, where supported, in-container enforcement. -![Architecture](/img/docs/common/core-concepts/architect.jpg) +![HAMi components and the GPU workload scheduling sequence](/img/docs/common/core-concepts/hami-architecture-en.svg) -HAMi consists of the following components: +## What are HAMi's core components? -- HAMi MutatingWebhook -- HAMi scheduler-extender -- Device-plugin (HAMi-device-plugin) -- In-container resource control (HAMi-Core) +HAMi coordinates four components during the lifecycle of an accelerator workload: + +| Component | Runs as | Primary responsibility | +| --- | --- | --- | +| HAMi MutatingWebhook | Part of the `hami-scheduler` deployment | Directs Pods that request HAMi-managed resources to `hami-scheduler` | +| HAMi scheduler extender | Part of the `hami-scheduler` deployment | Selects a node and a physical device from a cluster-wide view of available resources | +| HAMi device plugin | DaemonSet on accelerator nodes | Registers devices with kubelet and prepares the selected device for the container | +| HAMi-Core | Library injected into supported containers | Enforces the assigned memory and compute limits at runtime | + +The exact resources and enforcement mechanism depend on the device vendor. For example, NVIDIA workloads can request `nvidia.com/gpumem` in MiB and `nvidia.com/gpucores` as a percentage. Other devices expose vendor-specific resources and may support different allocation granularities. See the [FAQ](../faq/faq.md) for the current support matrix. + +## How does a workload move through HAMi? + +1. **Admission:** When a Pod requests a HAMi-managed device, the MutatingWebhook sets `spec.schedulerName` to `hami-scheduler` unless the Pod already names a scheduler. +2. **Placement:** The HAMi scheduler extender combines the Pod request with device information reported by each node. During scheduling, it filters nodes that cannot satisfy the request and selects a suitable physical device. +3. **Allocation:** The scheduler records the selected device and quota in Pod annotations. On the chosen node, kubelet calls the HAMi device plugin, which reads that result and makes the device available to the container. +4. **Runtime control:** For devices that support in-container control, the device plugin injects the required runtime library and configuration. For NVIDIA virtual GPUs, HAMi-Core intercepts relevant CUDA and NVML calls to apply the assigned memory and compute limits. + +This division keeps cluster policy in the control plane, hardware discovery and allocation on each node, and workload-level enforcement close to the application. ## HAMi MutatingWebhook {#hami-mutatingwebhook} -HAMi MutatingWebhook checks if this task can be handled by HAMi, It scans the resource field of each pod submitted, If each resource the pod requires is either 'CPU', 'Memory' or a HAMi-resource, Then it will set the schedulerName field of this pod to 'HAMi-scheduler'. +The MutatingWebhook is the admission entry point. It examines a newly created Pod's resource requests to determine whether HAMi should handle it. For eligible Pods, it sets: -## HAMi scheduler {#hami-scheduler} +```yaml +spec: + schedulerName: hami-scheduler +``` -The HAMi scheduler is responsible for assigning tasks to the appropriate nodes and devices. At the same time, the scheduler needs to maintain a global view of heterogeneous computing devices for monitoring. +Pods that do not request HAMi-managed resources continue through the normal Kubernetes scheduling path. Pods that explicitly choose another scheduler are not silently reassigned. -## Device-plugin {#device-plugin} +## HAMi Scheduler {#hami-scheduler} -The device-plugin layer obtains the scheduling result from the annotations field of the task and maps the corresponding device to the container. +The scheduler is responsible for choosing both a node and a device. Kubernetes device plugins normally advertise integer resource counts, which are not enough to describe properties such as device model, memory capacity, compute capacity, health, or topology. HAMi device plugins therefore report detailed device information through node annotations, allowing the scheduler to maintain a cluster-wide view. + +HAMi participates in the standard scheduling flow as a [scheduler extender](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md); it does not replace Kubernetes scheduling. It filters candidates according to the requested resources, applies the configured binpack or spread policy, binds the Pod, and writes the allocation result to annotations such as `hami.io/vgpu-devices-allocated`. + +## Device Plugin {#device-plugin} + +The HAMi device plugin runs on each supported accelerator node and implements the Kubernetes [device plugin API](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/). It discovers local devices, registers allocatable resources with kubelet, reports device details for scheduling, and handles kubelet's `Allocate` request after a Pod is bound. + +During allocation, the plugin reads the scheduler's result from the Pod annotations and exposes the selected device to the container. Depending on the vendor integration, it may mount device files and runtime libraries or inject environment variables that describe the assigned quota. ## HAMi-Core {#hami-core} -The in-container resource control is responsible for monitoring the resource usage within the container and providing hard isolation capabilities. +HAMi-Core provides runtime control for NVIDIA virtual GPUs through `libvgpu.so`. The device plugin loads the library into the container through `/etc/ld.so.preload`. HAMi-Core then intercepts CUDA memory allocation and kernel launch calls: allocations beyond the assigned memory budget return an out-of-memory error, while compute usage is throttled toward the requested limit. It also adjusts NVML results so applications see their assigned memory rather than the full physical device. + +This is user-space enforcement, not a hardware security boundary. Applications that bypass the intercepted libraries—for example, by using direct driver calls or Docker-in-Docker—may bypass these controls. Use [NVIDIA MIG](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) when a supported GPU and hardware-enforced isolation are required. See [GPU Virtualization Principles](./gpu-virtualization.md) for the full interception and allocation flow. + +## How does HAMi compare with time-slicing and MIG? + +| Approach | Sharing model | Memory and compute boundary | Best fit | +| --- | --- | --- | --- | +| Time-slicing | Workloads take turns on one GPU | No per-workload GPU memory isolation | Simple concurrency where strict quotas are unnecessary | +| HAMi virtual GPU | Workloads share a GPU with flexible requested quotas | User-space memory enforcement and compute throttling | Fine-grained, dynamic sharing across a broad range of GPUs | +| NVIDIA MIG | A supported GPU is divided into fixed hardware partitions | Hardware-enforced memory and compute isolation | Strong isolation on MIG-capable GPUs | + +HAMi also supports dynamic MIG allocation, so these approaches are not always mutually exclusive. The right choice depends on the accelerator, workload, isolation requirement, and desired partition granularity. + +## Product architecture and reference deployment + +This page describes HAMi's product components and the request lifecycle common to its integrations. A complete cluster can also include a CNI, vendor drivers and runtimes, monitoring, and an optional dashboard. For one NVIDIA-oriented deployment topology and its dependencies, see [HAMi Cluster Architecture After Installation](./hami-architecture.md). + +## References + +- [Kubernetes device plugins](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/) +- [Kubernetes scheduler extender design](https://github.com/kubernetes/design-proposals-archive/blob/main/scheduling/scheduler_extender.md) +- [HAMi-Core source code](https://github.com/Project-HAMi/HAMi-core) +- [NVIDIA Multi-Instance GPU user guide](https://docs.nvidia.com/datacenter/tesla/mig-user-guide/) + +## What's next + +- Explore the detailed [GPU virtualization principles](./gpu-virtualization.md) +- Review the [reference cluster architecture](./hami-architecture.md) +- Check the [installation prerequisites](../installation/prerequisites.md) diff --git a/versioned_docs/version-v2.9.0/core-concepts/hami-architecture.md b/versioned_docs/version-v2.9.0/core-concepts/hami-architecture.md index 8573571ce..df881e928 100644 --- a/versioned_docs/version-v2.9.0/core-concepts/hami-architecture.md +++ b/versioned_docs/version-v2.9.0/core-concepts/hami-architecture.md @@ -4,6 +4,12 @@ title: "HAMi Cluster Architecture After Installation" After completing the HAMi installation, the cluster is no longer an ordinary Kubernetes cluster, it becomes an AI infrastructure platform with GPU virtualization capabilities. This document breaks down the responsibilities and dependencies of every layer and every component in the cluster after installation. +:::note + +This page describes one NVIDIA-oriented reference deployment after installation, including optional components such as GPU Operator, Prometheus, and WebUI. For HAMi's product components and the request lifecycle, see [Architecture](./architecture.md). + +::: + ## 5-Layer Architecture Overview The cluster after installation consists of 5 layers, each providing services to the layer above: