diff --git a/docs/userguide/interactive-manifest-generator.md b/docs/userguide/interactive-manifest-generator.md new file mode 100644 index 00000000..557e7c4d --- /dev/null +++ b/docs/userguide/interactive-manifest-generator.md @@ -0,0 +1,21 @@ +--- +title: Interactive Manifest Generator +sidebar_label: Manifest Generator +--- + +# Interactive Manifest Generator + +Project HAMi supports GPU virtualization across a variety of hardware manufacturers, including NVIDIA, Cambricon, Hygon, Iluvatar, and Huawei. Each device requires specific Kubernetes resource keys in your container `resources.limits` to correctly allocate device memory and cores, and specific `metadata.annotations` to constrain to device types or UUIDs. + +Use the interactive tool below to generate the exact YAML configuration needed for your use case. You can integrate the generated configuration directly into your deployment specifications (such as adding the resources to your `spec.template.spec.containers` section). + +import ManifestGenerator from '@site/src/components/ManifestGenerator'; + + + +## Advanced Options + +- **Specific Device Type**: If you have a heterogeneous cluster (e.g. A100s and V100s), you can specify which device model your pod should be scheduled on. +- **Specific Device UUID**: If you need to bind a pod to a specific physical device for performance profiling or debugging, you can provide its UUID. + +> **Note**: Not all vendors support core percentage or memory percentage scaling. The generator automatically adapts its options based on the selected device vendor's supported capabilities. diff --git a/i18n/zh/docusaurus-plugin-content-docs/current/userguide/interactive-manifest-generator.md b/i18n/zh/docusaurus-plugin-content-docs/current/userguide/interactive-manifest-generator.md new file mode 100644 index 00000000..4d5ef37f --- /dev/null +++ b/i18n/zh/docusaurus-plugin-content-docs/current/userguide/interactive-manifest-generator.md @@ -0,0 +1,21 @@ +--- +title: 交互式 Manifest 生成器 +sidebar_label: Manifest 生成器 +--- + +# 交互式 Manifest 生成器 + +Project HAMi 支持跨多个硬件制造商的 GPU 虚拟化,包括 NVIDIA、寒武纪 (Cambricon)、海光 (Hygon)、天数智芯 (Iluvatar) 和华为升腾 (Huawei Ascend)。每个设备需要在您的容器 `resources.limits` 中使用特定的 Kubernetes 资源键以便正确分配设备内存和核心,并使用特定的 `metadata.annotations` 来限制设备类型或 UUID。 + +使用下方的交互式工具,为您的用例生成准确的 YAML 配置。您可以将生成的配置直接集成到您的部署规范中(例如将资源添加到您的 `spec.template.spec.containers` 部分)。 + +import ManifestGenerator from '@site/src/components/ManifestGenerator'; + + + +## 高级选项 (Advanced Options) + +- **特定设备类型 (Specific Device Type)**: 如果您有一个异构集群(例如 A100 和 V100 混合),您可以指定您的 Pod 应该调度到哪种设备型号上。 +- **特定设备 UUID (Specific Device UUID)**: 如果您出于性能分析或调试目的,需要将 Pod 绑定到特定的物理设备,您可以提供其 UUID。 + +> **注意**: 并非所有供应商都支持核心百分比或内存百分比分配。生成器会根据所选设备供应商支持的功能,自动调整其选项。 diff --git a/sidebars.js b/sidebars.js index 3487c0e9..d5fe2eb4 100644 --- a/sidebars.js +++ b/sidebars.js @@ -73,6 +73,7 @@ module.exports = { items: [ "userguide/configure", "userguide/device-supported", + "userguide/interactive-manifest-generator", "userguide/benchmark", "userguide/hami-webui-user-guide", { diff --git a/src/components/ManifestGenerator/index.js b/src/components/ManifestGenerator/index.js new file mode 100644 index 00000000..fab57ad5 --- /dev/null +++ b/src/components/ManifestGenerator/index.js @@ -0,0 +1,264 @@ +import React, { useState, useMemo } from 'react'; +import CodeBlock from '@theme/CodeBlock'; +import Translate, { translate } from '@docusaurus/Translate'; +import clsx from 'clsx'; +import styles from './styles.module.css'; + +const VENDORS = { + NVIDIA: { + name: 'NVIDIA (GPU)', + resourceKey: 'nvidia.com/gpu', + memKey: 'nvidia.com/gpumem', + memPctKey: 'nvidia.com/gpumem-percentage', + coreKey: 'nvidia.com/gpucores', + corePctKey: 'nvidia.com/gpucores-percentage', + typeKey: 'hami.io/vgpu-type', + uuidKey: 'hami.io/vgpu-uuid', + memUnit: 'MiB', + }, + CAMBRICON: { + name: 'Cambricon (MLU)', + resourceKey: 'cambricon.com/vmlu', + memKey: 'cambricon.com/mlu.smlu.vmemory', + memPctKey: 'cambricon.com/mlu.smlu.vmemory', + coreKey: 'cambricon.com/mlu.smlu.smlu', + corePctKey: 'cambricon.com/mlu.smlu.smlu', + typeKey: 'hami.io/mlu-type', + uuidKey: 'hami.io/mlu-uuid', + memUnit: '%', + }, + HYGON: { + name: 'Hygon (DCU)', + resourceKey: 'hygon.com/dcunum', + memKey: 'hygon.com/dcumem', + memPctKey: null, + coreKey: 'hygon.com/dcucores', + corePctKey: null, + typeKey: 'hami.io/dcu-type', + uuidKey: 'hami.io/dcu-uuid', + memUnit: 'MiB', + }, + ILUVATAR: { + name: 'Iluvatar (GPU)', + resourceKey: 'iluvatar.ai/vgpu', + memKey: 'iluvatar.ai/vcuda-memory', + memPctKey: null, + coreKey: 'iluvatar.ai/vcuda-core', + corePctKey: null, + typeKey: 'hami.io/iluvatar-type', + uuidKey: 'hami.io/iluvatar-uuid', + memUnit: 'MiB', + }, + ASCEND: { + name: 'Huawei Ascend (NPU)', + resourceKey: 'huawei.com/Ascend910', + memKey: 'huawei.com/Ascend910-memory', + memPctKey: null, + coreKey: null, + corePctKey: null, + typeKey: null, + uuidKey: null, + memUnit: 'MiB', + }, +}; + +export default function ManifestGenerator() { + const [vendor, setVendor] = useState('NVIDIA'); + const [deviceCount, setDeviceCount] = useState(1); + const [memMode, setMemMode] = useState('value'); + const [memValue, setMemValue] = useState(3000); + const [coreMode, setCoreMode] = useState('none'); + const [coreValue, setCoreValue] = useState(50); + + const [advanced, setAdvanced] = useState(false); + const [deviceType, setDeviceType] = useState(''); + const [deviceUuid, setDeviceUuid] = useState(''); + + // Compute yamlCode synchronously for bulletproof SSR + const yamlCode = useMemo(() => { + const v = VENDORS[vendor]; + let annotations = []; + let limits = []; + + limits.push(` ${v.resourceKey}: ${deviceCount}`); + + if (v.memKey) { + if (memMode === 'value' && v.memUnit !== '%') { + limits.push(` ${v.memKey}: ${memValue}`); + } else if (memMode === 'percentage' && v.memPctKey) { + limits.push(` ${v.memPctKey}: ${memValue}`); + } else if (v.memUnit === '%') { + // Fallback if forced percentage logic + limits.push(` ${v.memPctKey || v.memKey}: ${memValue}`); + } + } + + if (v.coreKey && coreMode !== 'none') { + if (coreMode === 'value') { + limits.push(` ${v.coreKey}: ${coreValue}`); + } else if (coreMode === 'percentage' && v.corePctKey) { + limits.push(` ${v.corePctKey}: ${coreValue}`); + } + } + + if (advanced) { + if (deviceType && v.typeKey) { + annotations.push(` ${v.typeKey}: ${JSON.stringify(deviceType)}`); + } + if (deviceUuid && v.uuidKey) { + annotations.push(` ${v.uuidKey}: ${JSON.stringify(deviceUuid)}`); + } + } + + let code = `apiVersion: v1 +kind: Pod +metadata: + name: hami-${vendor.toLowerCase()}-pod +`; + + if (annotations.length > 0) { + code += ` annotations:\n${annotations.join('\n')}\n`; + } + + code += `spec: + containers: + - name: hami-container + image: ubuntu:22.04 + command: ["sleep", "infinity"] + resources: + limits: +${limits.join('\n')}`; + + return code; + }, [vendor, deviceCount, memMode, memValue, coreMode, coreValue, advanced, deviceType, deviceUuid]); + + const vInfo = VENDORS[vendor]; + + return ( +
+
+

+ Resource Request Configuration +

+ +
+ + +
+ +
+ + setDeviceCount(Math.max(1, parseInt(e.target.value) || 1))} className={styles.input} /> +
+ + {vInfo.memKey && ( +
+
+ + +
+
+ + setMemValue(Math.max(0, parseInt(e.target.value) || 0))} className={styles.input} /> +
+
+ )} + + {vInfo.coreKey && ( +
+
+ + +
+ {coreMode !== 'none' && ( +
+ + setCoreValue(Math.max(0, parseInt(e.target.value) || 0))} className={styles.input} /> +
+ )} +
+ )} + + + + {advanced && vInfo.typeKey && ( +
+ + setDeviceType(e.target.value)} placeholder={translate({ id: 'manifest.generator.emptyPlaceholder', message: 'Leave empty for any' })} className={styles.input} /> +
+ )} + + {advanced && vInfo.uuidKey && ( +
+ + setDeviceUuid(e.target.value)} placeholder={translate({ id: 'manifest.generator.emptyPlaceholder', message: 'Leave empty for any' })} className={styles.input} /> +
+ )} + +
+ +
+

+ Generated YAML Manifest +

+

+ Integrate this into your Kubernetes Pod or Deployment spec. +

+ + {yamlCode} + +
+
+ ); +} diff --git a/src/components/ManifestGenerator/styles.module.css b/src/components/ManifestGenerator/styles.module.css new file mode 100644 index 00000000..00fd304c --- /dev/null +++ b/src/components/ManifestGenerator/styles.module.css @@ -0,0 +1,92 @@ +.generatorContainer { + display: flex; + flex-direction: column; + gap: 2rem; + margin: 2rem 0; + border-radius: var(--ifm-global-radius); + border: 1px solid var(--ifm-color-emphasis-200); + background-color: var(--ifm-background-surface-color); + padding: 1.5rem; +} + +@media (min-width: 768px) { + .generatorContainer { + flex-direction: row; + align-items: flex-start; + } +} + +.controlsPanel { + flex: 1; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.previewPanel { + flex: 1; + width: 100%; +} + +.inputGroup { + display: flex; + flex-direction: column; + gap: 0.5rem; + flex: 1; +} + +.inputGroup label { + font-weight: 600; + font-size: 0.9rem; + color: var(--ifm-color-emphasis-800); +} + +.select, .input { + padding: 0.6rem; + border-radius: var(--ifm-global-radius); + border: 1px solid var(--ifm-color-emphasis-400); + background-color: var(--ifm-background-color); + color: var(--ifm-font-color-base); + font-size: 1rem; + transition: border-color 0.2s ease; +} + +.select:focus, .input:focus { + outline: none; + border-color: var(--ifm-color-primary); +} + +.select:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.flexRow { + display: flex; + gap: 1rem; + flex-direction: column; +} + +@media (min-width: 480px) { + .flexRow { + flex-direction: row; + } +} + +.advancedToggle { + margin-top: 1rem; + padding: 0.5rem 0; + color: var(--ifm-color-primary); + font-size: 0.9rem; + display: inline-block; +} + +.advancedToggle:hover { + color: var(--ifm-color-primary-dark); +} + +.interactiveText { + cursor: pointer; + user-select: none; + font-weight: bold; +}