-
-
-
-
-
-
-
-
- {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.
-
+
-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: