From 205c4b8a1ab25137da3e8a47b3fb8d6be933ed48 Mon Sep 17 00:00:00 2001 From: Shaurya Mishra Date: Fri, 14 Aug 2026 16:30:47 +0530 Subject: [PATCH 1/4] docs: implement automated tag-based related labs system Signed-off-by: Shaurya Mishra --- docs/core-concepts/architecture.md | 8 +- docs/core-concepts/ecosystem-integrations.md | 7 +- docs/core-concepts/gpu-driver.md | 7 +- docs/core-concepts/gpu-stack.md | 8 +- docs/core-concepts/gpu-virtualization.md | 8 ++ docs/core-concepts/hami-architecture.md | 7 ++ docusaurus.config.js | 50 +++++++++ src/components/labs/RelatedLabs.js | 111 +++++++++++++++++++ src/components/labs/RelatedLabs.module.css | 27 +++++ src/theme/DocItem/Content/index.js | 11 +- 10 files changed, 237 insertions(+), 7 deletions(-) create mode 100644 src/components/labs/RelatedLabs.js create mode 100644 src/components/labs/RelatedLabs.module.css diff --git a/docs/core-concepts/architecture.md b/docs/core-concepts/architecture.md index 9a4e832d8..9c1780d63 100644 --- a/docs/core-concepts/architecture.md +++ b/docs/core-concepts/architecture.md @@ -1,5 +1,11 @@ --- -title: Architecture +title: Architecture Overview +sidebar_label: Architecture +tags: + - installation + - nvidia + - hami + - local-setup --- The overall architecture of HAMi is shown as below: diff --git a/docs/core-concepts/ecosystem-integrations.md b/docs/core-concepts/ecosystem-integrations.md index 0633034f9..dcc17116f 100644 --- a/docs/core-concepts/ecosystem-integrations.md +++ b/docs/core-concepts/ecosystem-integrations.md @@ -1,5 +1,10 @@ --- -title: Ecosystem Integrations +title: Ecosystem Integration Partners +sidebar_label: Ecosystem Integrations +tags: + - volcano + - kueue + - kai-scheduler --- HAMi doesn't replace your Kubernetes scheduler. It extends it. HAMi handles GPU virtualization, sharing, and runtime isolation, and it slots into the wider Kubernetes scheduling world so you can pair **GPU sharing** with **batch scheduling, job queuing, and colocation**. diff --git a/docs/core-concepts/gpu-driver.md b/docs/core-concepts/gpu-driver.md index 94dcee00a..da6a290a4 100644 --- a/docs/core-concepts/gpu-driver.md +++ b/docs/core-concepts/gpu-driver.md @@ -1,5 +1,10 @@ --- -title: "Understanding GPU Drivers" +title: "Understanding NVIDIA GPU Drivers in Kubernetes" +sidebar_label: "GPU Driver" +tags: + - nvidia + - simulation + - nvml-mock --- Before using a GPU, you first need to verify that the GPU driver is properly loaded into the kernel. This document explains how to check GPU driver status and understand the architecture of NVIDIA kernel modules. diff --git a/docs/core-concepts/gpu-stack.md b/docs/core-concepts/gpu-stack.md index 6da4b65f9..f0978ae2f 100644 --- a/docs/core-concepts/gpu-stack.md +++ b/docs/core-concepts/gpu-stack.md @@ -1,5 +1,11 @@ --- -title: "GPU Software Stack Overview" +title: "The GPU Software Stack" +sidebar_label: "GPU Stack" +tags: + - installation + - nvidia + - simulation + - nvml-mock --- When you use a GPU on a server, you are not dealing with a single piece of software or hardware. Instead, you are working with an entire **software stack** built around NVIDIA GPUs. From the lowest-level physical hardware to the highest-level Kubernetes scheduling, it can be roughly divided into 5 layers: diff --git a/docs/core-concepts/gpu-virtualization.md b/docs/core-concepts/gpu-virtualization.md index cf1047dd5..1616ed9cf 100644 --- a/docs/core-concepts/gpu-virtualization.md +++ b/docs/core-concepts/gpu-virtualization.md @@ -1,6 +1,14 @@ --- title: GPU Virtualization Principles sidebar_label: GPU Virtualization +tags: + - gpu-partitioning + - isolation + - vllm + - inference + - alibaba-cloud + - resource-sharing + - hami --- In AI inference scenarios, a common dilemma is that GPUs are expensive, but mostly idle. diff --git a/docs/core-concepts/hami-architecture.md b/docs/core-concepts/hami-architecture.md index 0354d04e3..143ce7c27 100644 --- a/docs/core-concepts/hami-architecture.md +++ b/docs/core-concepts/hami-architecture.md @@ -1,5 +1,12 @@ --- title: "HAMi Cluster Architecture After Installation" +sidebar_label: HAMi Architecture +tags: + - simulation + - nvml-mock + - hami + - scheduling + - local-setup --- 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. diff --git a/docusaurus.config.js b/docusaurus.config.js index 11f4414e3..1ecba9dc3 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -25,6 +25,55 @@ function getDocEditUrl(versionDocsDirPath, docPath) { return `${githubEditBaseUrl}${[versionDocsDirPath, docPath].filter(Boolean).join("/")}`; } +/** + * Build-time helper: scans tutorials/labs/*.md and extracts the metadata + * that the RelatedLabs component needs at runtime. This avoids the need + * to cross-reference two separate docs-plugin instances on the client. + */ +function getLabData() { + const fs = require("fs"); + const path = require("path"); + const labsDir = path.join(__dirname, "tutorials", "labs"); + if (!fs.existsSync(labsDir)) return {}; + const files = fs.readdirSync(labsDir).filter((f) => f.endsWith(".md")); + const labs = {}; + for (const file of files) { + const raw = fs.readFileSync(path.join(labsDir, file), "utf8"); + // Normalise CRLF → LF so the regex works on every OS + const content = raw.replace(/\r\n/g, "\n"); + const fmMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (!fmMatch) continue; + const fm = fmMatch[1]; + + const titleMatch = fm.match(/^title:\s*"?(.+?)"?\s*$/m); + const descMatch = fm.match(/^description:\s*"?(.+?)"?\s*$/m); + const levelMatch = fm.match(/level:\s*(.+)/); + const durationMatch = fm.match(/duration:\s*(.+)/); + const tagsMatch = fm.match(/^tags:\s*\n((?:\s+-\s+.*\n?)+)/m); + + const tags = tagsMatch + ? tagsMatch[1] + .split("\n") + .filter((l) => l.trim().startsWith("-")) + .map((l) => l.replace(/^\s*-\s*/, "").trim()) + .filter(Boolean) + : []; + + if (tags.length === 0) continue; + + const docId = `labs/${file.replace(".md", "")}`; + labs[docId] = { + title: titleMatch ? titleMatch[1] : file.replace(".md", ""), + description: descMatch ? descMatch[1] : "", + level: levelMatch ? levelMatch[1].trim() : "", + duration: durationMatch ? durationMatch[1].trim() : "", + tags, + href: `/tutorials/${docId}`, + }; + } + return labs; +} + async function localizedBlogPlugin(context, opts) { const p = await require("@docusaurus/plugin-content-blog").default(context, opts); const orig = p.postBuild?.bind(p); @@ -71,6 +120,7 @@ module.exports = { }, customFields: { defaultOgImage: "/img/hami-graph-color.png", + labData: getLabData(), }, markdown: { mermaid: true, diff --git a/src/components/labs/RelatedLabs.js b/src/components/labs/RelatedLabs.js new file mode 100644 index 000000000..7e6d742ea --- /dev/null +++ b/src/components/labs/RelatedLabs.js @@ -0,0 +1,111 @@ +/** + * RelatedLabs – tag-based, cross-plugin related-labs section. + * + * Concept pages (docs plugin) and lab pages (tutorials plugin) live in + * two separate Docusaurus docs-plugin instances, so we cannot use + * useDocsSidebar/useDocsVersion to reach across the boundary. + * + * Instead, the build-time helper `getLabData()` in docusaurus.config.js + * extracts every lab's metadata (title, description, level, duration, + * tags, href) and injects it into `siteConfig.customFields.labData`. + * This component reads that static map and matches tags at render time. + */ +import React from "react"; +import Link from "@docusaurus/Link"; +import Translate from "@docusaurus/Translate"; +import { useDoc } from "@docusaurus/plugin-content-docs/client"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import LevelBadge from "./LevelBadge"; +import styles from "./RelatedLabs.module.css"; +import gridStyles from "./LabCardGrid.module.css"; + +const DURATIONS = { + "about 30 minutes": ( + + about 30 minutes + + ), + "about 40 minutes": ( + + about 40 minutes + + ), + "about 45 minutes": ( + + about 45 minutes + + ), + "about 60 minutes": ( + + about 60 minutes + + ), + "about 90 minutes": ( + + about 90 minutes + + ), +}; + +export default function RelatedLabs() { + const { frontMatter } = useDoc(); + const pageTags = frontMatter?.tags ?? []; + + // Nothing to match against – render nothing. + if (pageTags.length === 0) { + return null; + } + + const { siteConfig } = useDocusaurusContext(); + const labData = siteConfig.customFields?.labData ?? {}; + + // Build cards for labs whose tags overlap with the current page's tags. + const matchedCards = Object.entries(labData) + .map(([docId, lab]) => { + const labTags = lab.tags ?? []; + const matchCount = labTags.filter((tag) => pageTags.includes(tag)).length; + if (matchCount === 0) return null; + return { + key: docId, + href: lab.href, + title: lab.title, + description: lab.description, + level: lab.level, + duration: lab.duration, + matchCount, + }; + }) + .filter(Boolean) + .sort((a, b) => b.matchCount - a.matchCount); + + if (matchedCards.length === 0) { + return null; + } + + return ( +
+

+ 🔬 + + Related Hands-on Labs + +

+
+ {matchedCards.map((card) => ( + +
+ {card.title} + +
+ {card.description &&

{card.description}

} + {card.duration && ( +
+ {DURATIONS[card.duration] ?? card.duration} +
+ )} + + ))} +
+
+ ); +} diff --git a/src/components/labs/RelatedLabs.module.css b/src/components/labs/RelatedLabs.module.css new file mode 100644 index 000000000..75049598c --- /dev/null +++ b/src/components/labs/RelatedLabs.module.css @@ -0,0 +1,27 @@ +.container { + margin: 3rem 0; + padding: 1.5rem; + background-color: var(--ifm-color-emphasis-100); + border-radius: var(--ifm-global-radius); + border-left: 4px solid var(--ifm-color-primary); +} + +.heading { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0; + margin-bottom: 1rem; + font-size: 1.25rem; + color: var(--ifm-color-emphasis-900); +} + +.icon { + font-size: 1.5rem; +} + +/* Ensure grid overrides its top margin when inside container */ +.container > div { + margin-top: 0; + margin-bottom: 0; +} diff --git a/src/theme/DocItem/Content/index.js b/src/theme/DocItem/Content/index.js index 14bb333a5..4f011cd53 100644 --- a/src/theme/DocItem/Content/index.js +++ b/src/theme/DocItem/Content/index.js @@ -1,8 +1,11 @@ /** * Custom swizzle of DocItem/Content. - * Identical to the original except that the lab metadata row (LabMeta) - * renders directly below the page title for docs that carry a `lab` - * front matter block. Docs without it are unaffected. + * + * Additions on top of the Docusaurus default: + * - LabMeta: renders a metadata row (level, duration, authors) directly + * below the page title for docs that carry a `lab` front matter block. + * - RelatedLabs: renders a tag-matched grid of related hands-on labs + * at the bottom of any doc page that carries `tags` in its front matter. */ import React from "react"; import clsx from "clsx"; @@ -11,6 +14,7 @@ import { useDoc } from "@docusaurus/plugin-content-docs/client"; import Heading from "@theme/Heading"; import MDXContent from "@theme/MDXContent"; import LabMeta from "@site/src/components/labs/LabMeta"; +import RelatedLabs from "@site/src/components/labs/RelatedLabs"; /** Title can be declared inside md content or declared through @@ -42,6 +46,7 @@ export default function DocItemContent({ children }) { )} {children} + ); } From 0105d8cd16c91d37c4741111a653ae089f00e69f Mon Sep 17 00:00:00 2001 From: Shaurya Mishra Date: Wed, 19 Aug 2026 19:34:48 +0530 Subject: [PATCH 2/4] Update RelatedLabs.js Signed-off-by: Shaurya Mishra --- src/components/labs/RelatedLabs.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/labs/RelatedLabs.js b/src/components/labs/RelatedLabs.js index 7e6d742ea..f859cc006 100644 --- a/src/components/labs/RelatedLabs.js +++ b/src/components/labs/RelatedLabs.js @@ -49,6 +49,7 @@ const DURATIONS = { export default function RelatedLabs() { const { frontMatter } = useDoc(); + const { siteConfig } = useDocusaurusContext(); const pageTags = frontMatter?.tags ?? []; // Nothing to match against – render nothing. @@ -56,7 +57,6 @@ export default function RelatedLabs() { return null; } - const { siteConfig } = useDocusaurusContext(); const labData = siteConfig.customFields?.labData ?? {}; // Build cards for labs whose tags overlap with the current page's tags. @@ -76,7 +76,8 @@ export default function RelatedLabs() { }; }) .filter(Boolean) - .sort((a, b) => b.matchCount - a.matchCount); + .sort((a, b) => b.matchCount - a.matchCount) + .slice(0, 4); // Cap to top 4 to avoid long lists of loosely related labs if (matchedCards.length === 0) { return null; From cd6d13fcda4d569dcf68e59ced99d0a2be187ea5 Mon Sep 17 00:00:00 2001 From: Shaurya Mishra Date: Fri, 14 Aug 2026 17:46:59 +0530 Subject: [PATCH 3/4] Update RelatedLabs.js Signed-off-by: Shaurya Mishra --- src/components/labs/RelatedLabs.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/components/labs/RelatedLabs.js b/src/components/labs/RelatedLabs.js index f859cc006..90c383822 100644 --- a/src/components/labs/RelatedLabs.js +++ b/src/components/labs/RelatedLabs.js @@ -19,6 +19,12 @@ import LevelBadge from "./LevelBadge"; import styles from "./RelatedLabs.module.css"; import gridStyles from "./LabCardGrid.module.css"; +/** + * Maps raw duration strings from lab frontmatter to translatable React elements. + * Keeps duration labels consistent with LabCardGridAuto.js and supports i18n. + * + * @type {Object} + */ const DURATIONS = { "about 30 minutes": ( @@ -47,6 +53,16 @@ const DURATIONS = { ), }; +/** + * Renders a "Related Hands-on Labs" card grid at the bottom of doc pages. + * + * Reads the current page's frontmatter `tags` and matches them against + * pre-extracted lab metadata from `siteConfig.customFields.labData`. + * Labs with the most overlapping tags appear first. If the page has no + * tags or no labs match, the component renders nothing. + * + * @returns {React.ReactElement|null} A styled card grid of related labs, or null. + */ export default function RelatedLabs() { const { frontMatter } = useDoc(); const { siteConfig } = useDocusaurusContext(); From 9f38b30aea00c65351ded9be53368fc4f35e0d97 Mon Sep 17 00:00:00 2001 From: Shaurya Mishra Date: Sun, 23 Aug 2026 14:08:26 +0530 Subject: [PATCH 4/4] fix(docs): resolve CodeRabbit nitpicks regarding duration map, sorting tiebreaker, and lab 13 description Signed-off-by: Shaurya Mishra --- .../current/overview.md | 3 +- src/components/labs/LabCardGridAuto.js | 2 +- src/components/labs/RelatedLabs.js | 36 ++----------------- tutorials/overview.md | 3 +- 4 files changed, 7 insertions(+), 37 deletions(-) diff --git a/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md index 427edc0f8..c25ddfbd9 100644 --- a/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md +++ b/i18n/zh/docusaurus-plugin-content-docs-tutorials/current/overview.md @@ -19,4 +19,5 @@ import LabCardGridAuto from '@site/src/components/labs/LabCardGridAuto'; -每个实验都列出了各自的前提条件。实验 3 和 4 直接复用实验 1 搭建的集群,一次开机即可完成全部三个实验;实验 2 可在任意笔记本上运行,无需 GPU。实验 7 在租用的 GPU 虚拟机上自行搭建单节点 k3s 集群,不使用 GPU Operator。实验 8 需要已有的 Volcano GPU 集群,用于验证 Volcano vGPU、Gang 调度和队列级资源限制。实验 9 使用 Kueue 准入控制限制 HAMi vGPU 数量、显存和算力配额。实验 11 将从头搭建完整的 KServe Standard 推理环境,并通过 HAMi 原生 DRA Claim 让两个 vLLM 副本共享一张 GPU。实验 12 在 GKE 1.35/COS/CDI 上部署 KAI Scheduler 与 HAMi-core,并通过 CUDA 分配验证显存上限。实验 13 在昇腾 310P3 ARM 服务器上源码编译 Volcano 与 ascend-device-plugin,验证 hami-vnpu-core 软切分、binpack 共卡与容器级监控指标。 +每个实验都列出了各自的前提条件。实验 3 和 4 直接复用实验 1 搭建的集群,一次开机即可完成全部三个实验;实验 2 可在任意笔记本上运行,无需 GPU。实验 7 在租用的 GPU 虚拟机上自行搭建单节点 k3s 集群,不使用 GPU Operator。实验 8 需要已有的 Volcano GPU 集群,用于验证 Volcano vGPU、Gang 调度和队列级资源限制。实验 9 使用 Kueue 准入控制限制 HAMi vGPU 数量、显存和算力配额。实验 11 将从头搭建完整的 KServe Standard 推理环境,并通过 HAMi 原生 DRA Claim 让两个 vLLM 副本共享一张 GPU。实验 12 在 GKE 1.35/COS/CDI 上部署 KAI Scheduler 与 HAMi-core,并通过 CUDA 分配验证显存上限。实验 13 在昇腾 310P3 ARM 服务器上源码编译 Volcano 并使用官方 v1.4.0 镜像部署 ascend-device-plugin,验证 hami-vnpu-core 软切分、binpack 共卡与容器级监控指标。 + diff --git a/src/components/labs/LabCardGridAuto.js b/src/components/labs/LabCardGridAuto.js index 2d6ca5ed1..c317804f5 100644 --- a/src/components/labs/LabCardGridAuto.js +++ b/src/components/labs/LabCardGridAuto.js @@ -29,7 +29,7 @@ import styles from "./LabCardGrid.module.css"; // Keyed by the English label, like LEVELS in LevelBadge. The ids have to be // static or `write-translations` has nothing to extract; passing the sidebar // value straight to leaves the extractor with a runtime expression. -const DURATIONS = { +export const DURATIONS = { "about 30 minutes": ( about 30 minutes diff --git a/src/components/labs/RelatedLabs.js b/src/components/labs/RelatedLabs.js index 90c383822..be13eb85b 100644 --- a/src/components/labs/RelatedLabs.js +++ b/src/components/labs/RelatedLabs.js @@ -19,39 +19,7 @@ import LevelBadge from "./LevelBadge"; import styles from "./RelatedLabs.module.css"; import gridStyles from "./LabCardGrid.module.css"; -/** - * Maps raw duration strings from lab frontmatter to translatable React elements. - * Keeps duration labels consistent with LabCardGridAuto.js and supports i18n. - * - * @type {Object} - */ -const DURATIONS = { - "about 30 minutes": ( - - about 30 minutes - - ), - "about 40 minutes": ( - - about 40 minutes - - ), - "about 45 minutes": ( - - about 45 minutes - - ), - "about 60 minutes": ( - - about 60 minutes - - ), - "about 90 minutes": ( - - about 90 minutes - - ), -}; +import { DURATIONS } from "./LabCardGridAuto"; /** * Renders a "Related Hands-on Labs" card grid at the bottom of doc pages. @@ -92,7 +60,7 @@ export default function RelatedLabs() { }; }) .filter(Boolean) - .sort((a, b) => b.matchCount - a.matchCount) + .sort((a, b) => b.matchCount - a.matchCount || a.key.localeCompare(b.key)) .slice(0, 4); // Cap to top 4 to avoid long lists of loosely related labs if (matchedCards.length === 0) { diff --git a/tutorials/overview.md b/tutorials/overview.md index 9762308c2..1b69c3ac6 100644 --- a/tutorials/overview.md +++ b/tutorials/overview.md @@ -17,4 +17,5 @@ Background knowledge that the labs build on. ## Labs - Each lab lists its own prerequisites. Labs 3 and 4 continue from the cluster Lab 1 builds, so a single session covers all three; Lab 2 runs on any laptop with no GPU required. Lab 7 brings up its own single-node k3s cluster on a rented GPU VM, without the GPU Operator. Lab 8 requires an existing Volcano GPU cluster and validates Volcano vGPU, Gang scheduling, and queue-level limits. Lab 9 uses Kueue admission control to enforce HAMi vGPU count, memory, and compute quotas. Lab 11 builds a complete KServe Standard inference stack and runs two vLLM replicas on one GPU through native HAMi DRA claims. Lab 12 deploys KAI Scheduler and HAMi-core on GKE 1.35/COS/CDI and proves the memory ceiling with CUDA allocations. Lab 13 builds Volcano and the ascend-device-plugin from source on an Ascend 310P3 ARM server and verifies hami-vnpu-core soft slicing, binpack card sharing, and per-container metrics. + Each lab lists its own prerequisites. Labs 3 and 4 continue from the cluster Lab 1 builds, so a single session covers all three; Lab 2 runs on any laptop with no GPU required. Lab 7 brings up its own single-node k3s cluster on a rented GPU VM, without the GPU Operator. Lab 8 requires an existing Volcano GPU cluster and validates Volcano vGPU, Gang scheduling, and queue-level limits. Lab 9 uses Kueue admission control to enforce HAMi vGPU count, memory, and compute quotas. Lab 11 builds a complete KServe Standard inference stack and runs two vLLM replicas on one GPU through native HAMi DRA claims. Lab 12 deploys KAI Scheduler and HAMi-core on GKE 1.35/COS/CDI and proves the memory ceiling with CUDA allocations. Lab 13 builds Volcano from source and deploys ascend-device-plugin from the official v1.4.0 image on an Ascend 310P3 ARM server, verifying hami-vnpu-core soft slicing, binpack card sharing, and per-container metrics. +