Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions charts/hami/templates/device-plugin/daemonsetnvidia.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ spec:
value: all
- name: DEVICE_LIST_STRATEGY
value: {{ .Values.devicePlugin.deviceListStrategy }}
{{- if .Values.devicePlugin.deviceDiscoveryStrategy }}
- name: DEVICE_DISCOVERY_STRATEGY
value: {{ .Values.devicePlugin.deviceDiscoveryStrategy }}
{{- end }}
- name: HOOK_PATH
value: {{ .Values.global.gpuHookPath }}
{{- if typeIs "bool" .Values.devicePlugin.passDeviceSpecsEnabled }}
Expand Down
3 changes: 3 additions & 0 deletions charts/hami/templates/scheduler/device-configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ data:
defaultCores: 0
defaultGPUNum: 1
preConfiguredDeviceMemory: {{ .Values.devicePlugin.preConfiguredDeviceMemory | default 0 }}
{{- if .Values.devicePlugin.preConfiguredDeviceType }}
preConfiguredDeviceType: "{{ .Values.devicePlugin.preConfiguredDeviceType }}"
{{- end }}
memoryFactor: 1
deviceSplitCount: {{ .Values.devicePlugin.deviceSplitCount }}
deviceMemoryScaling: {{ .Values.devicePlugin.deviceMemoryScaling }}
Expand Down
9 changes: 9 additions & 0 deletions charts/hami/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ devicePlugin:
# Set to 0 to use auto-detection (default). For unified memory GPUs, set to the total GPU memory (e.g., 131072 for 128GB).
# Can be overridden per-node via nodeConfiguration.config.
preConfiguredDeviceMemory: 0
# Device type reported for GPUs discovered via CDI that cannot be enumerated by NVML (e.g., NVIDIA GB10).
# Only used with deviceDiscoveryStrategy=cdi. Empty uses the built-in default ("NVIDIA-GB10").
preConfiguredDeviceType: ""
# Node configuration for device plugin, Priority: externalConfigName > config > default config
nodeConfiguration:
# If you want to use a custom config.json, you can set the content here.
Expand Down Expand Up @@ -384,6 +387,12 @@ devicePlugin:
disablecorelimit: "false"
passDeviceSpecsEnabled: false
deviceListStrategy: "envvar"
# Strategy used by the NVIDIA device plugin to discover devices:
# "" / "auto" (default) auto-detects the platform; "nvml", "tegra", or "cdi".
# Use "cdi" (together with deviceListStrategy: "cdi-annotations,cdi-cri") for
# CDI-only accelerators such as the GB10 (Grace-Blackwell iGPU), which are not
# enumerable via NVML and are described by an on-node CDI spec instead.
deviceDiscoveryStrategy: ""
nvidiaHookPath: null
nvidiaDriverRoot: null
gdrcopyEnabled: null
Expand Down
14 changes: 11 additions & 3 deletions cmd/device-plugin/nvidia/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func main() {
&cli.StringFlag{
Name: "device-discovery-strategy",
Value: "auto",
Usage: "the strategy to use to discover devices: 'auto', 'nvml', or 'tegra'",
Usage: "the strategy to use to discover devices: 'auto', 'nvml', 'tegra', or 'cdi'",
EnvVars: []string{"DEVICE_DISCOVERY_STRATEGY"},
},
&cli.IntSliceFlag{
Expand Down Expand Up @@ -209,9 +209,16 @@ func validateFlags(infolib nvinfo.Interface, config *spec.Config) error {
return fmt.Errorf("invalid --device-list-strategy option: %v", err)
}

// CDI device-list strategies normally require NVML to generate the CDI spec.
// The exception is the "cdi" discovery strategy (or "auto", which falls back
// to CDI discovery), where devices are described by externally-managed CDI
// specs on the node — this is how CDI-only accelerators such as the GB10
// (Grace-Blackwell iGPU) are supported without NVML.
hasNvml, _ := infolib.HasNvml()
if deviceListStrategies.AnyCDIEnabled() && !hasNvml {
return fmt.Errorf("CDI --device-list-strategy options are only supported on NVML-based systems")
discoveryStrategy := *config.Flags.DeviceDiscoveryStrategy
cdiDiscovery := discoveryStrategy == "cdi" || discoveryStrategy == "auto"
if deviceListStrategies.AnyCDIEnabled() && !hasNvml && !cdiDiscovery {
return fmt.Errorf("CDI --device-list-strategy options are only supported on NVML-based systems or with --device-discovery-strategy=cdi")
}

if *config.Flags.Plugin.DeviceIDStrategy != spec.DeviceIDStrategyUUID && *config.Flags.Plugin.DeviceIDStrategy != spec.DeviceIDStrategyIndex {
Expand All @@ -231,6 +238,7 @@ func validateFlags(infolib nvinfo.Interface, config *spec.Config) error {
case "auto":
case "nvml":
case "tegra":
case "cdi":
default:
return fmt.Errorf("invalid --device-discovery-strategy option %v", *config.Flags.DeviceDiscoveryStrategy)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/device-plugin/nvidia/plugin-manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func GetPlugins(ctx context.Context, infolib info.Interface, nvmllib nvml.Interf
cdi.WithTargetDevRoot(*config.Flags.NvidiaDevRoot),
cdi.WithNvidiaCTKPath(*config.Flags.Plugin.NvidiaCTKPath),
cdi.WithDeviceIDStrategy(*config.Flags.Plugin.DeviceIDStrategy),
cdi.WithVendor("k8s.device-plugin.nvidia.com"),
cdi.WithVendor(cdi.DefaultVendor),
cdi.WithGdrcopyEnabled(*config.Flags.GDRCopyEnabled),
cdi.WithGdsEnabled(*config.Flags.GDSEnabled),
cdi.WithMofedEnabled(*config.Flags.MOFEDEnabled),
Expand Down
127 changes: 127 additions & 0 deletions docs/nvidia-gb10-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# NVIDIA GB10 (Grace-Blackwell iGPU) Support

## Introduction

The NVIDIA GB10 (Grace-Blackwell "superchip", as shipped in the DGX Spark) is an
**integrated GPU** that shares unified LPDDR5X memory with the Grace CPU over a
coherent NVLink-C2C link. It differs from a discrete GPU in two ways that affect
HAMi's device plugin:

1. **No dedicated framebuffer.** `nvmlDeviceGetMemoryInfo()` returns
`ERROR_NOT_SUPPORTED` because there is no fixed device-local memory pool to
report — the "GPU memory" is system memory allocated dynamically.
2. **CDI-only exposure.** The device is provisioned through the NVIDIA Container
Toolkit's [CDI](https://github.com/cncf-tags/container-device-interface)
mechanism. It is described by an on-node CDI spec
(`/var/run/cdi/k8s.device-plugin.nvidia.com-gpu.json`) and is **not**
enumerable via NVML inside the device-plugin container.

Because HAMi's device plugin historically discovered devices only through NVML,
on a GB10 node `ResolvePlatform()` returns `unknown` and the plugin exits with:

```text
factory.go] Incompatible strategy detected auto
main.go] error starting plugins: ... failed to construct resource managers:
invalid device discovery strategy
```

To support these accelerators, the NVIDIA device plugin can discover GPUs
directly from the node's CDI specs (no NVML required) using the `cdi`
device-discovery strategy.

## Prerequisites

- The **NVIDIA GPU Operator** (or NVIDIA Container Toolkit) is installed and has
generated the CDI spec at `/var/run/cdi/k8s.device-plugin.nvidia.com-gpu.json`.
You can verify the node's own device plugin advertises `nvidia.com/gpu` and
that GPU Feature Discovery has labelled the node, e.g.
`nvidia.com/gpu.product=NVIDIA-GB10`.
- The node carries the label HAMi's device-plugin DaemonSet selects on
(`gpu=on` by default):

```bash
kubectl label node <gb10-node> gpu=on
```

- The kernel's inotify limits are high enough for the plugin's filesystem
watcher. Busy nodes can exhaust the default `fs.inotify.max_user_instances`
(128), which makes the plugin fail at start-up with
`couldn't initialize inotify: too many open files`. Raise it persistently:

```bash
# /etc/sysctl.d/99-hami-inotify.conf
fs.inotify.max_user_instances = 8192
fs.inotify.max_user_watches = 524288
```

```bash
sudo sysctl --system
```

## Configuration

Set the following on the NVIDIA device plugin (Helm `values.yaml`):

```yaml
devicePlugin:
# Discover devices from the on-node CDI specs instead of via NVML.
deviceDiscoveryStrategy: "cdi"
# Inject the device through CDI (matches the NVIDIA GPU Operator plugin).
deviceListStrategy: "cdi-annotations,cdi-cri"
# Unified memory (in MiB) HAMi should treat as schedulable per GPU. NVML
# cannot report it on a unified-memory GPU, so it is a policy value — set it
# at or below the node's total unified memory, leaving headroom for the OS.
# See the note below for deriving it from the node (the example is not a
# direct GiB conversion).
preConfiguredDeviceMemory: 122566
# Optional. Device type recorded for scheduling/`use-gputype`.
# Defaults to "NVIDIA-GB10" when empty.
preConfiguredDeviceType: "NVIDIA-GB10"
```

Notes:

- `deviceDiscoveryStrategy` also accepts `auto` (the default), which falls back
to `cdi` automatically when no NVML/Tegra platform is detected **and** a CDI
device-list strategy is active and CDI specs are present. Setting it to `cdi`
explicitly is recommended for clarity on GB10 nodes.
- `preConfiguredDeviceMemory` is a scheduling **policy** value — how much unified
memory HAMi treats as schedulable per GPU — interpreted as MiB (HAMi multiplies
it by 1024×1024 to get bytes). It is **not** a hardware GPU-memory readout: on a
unified-memory device the memory is shared with the CPU/OS. The example
`122566` is **this DGX Spark's total system memory as reported by Kubernetes**,
not a direct GiB conversion (120 GiB would be 122880 MiB):

```bash
kubectl get node <gb10-node> -o jsonpath='{.status.capacity.memory}'
# 125506464Ki -> 125506464 / 1024 = 122566 MiB (~119.7 GiB)
Comment thread
tittuvarghese marked this conversation as resolved.
```

Choose a value at or below this, leaving headroom for the OS.
It can be overridden per node via `nodeConfiguration.config` (`preconfigureddevicememory`).
- `preConfiguredDeviceType` can be overridden per node via `preconfigureddevicetype`.
Comment thread
tittuvarghese marked this conversation as resolved.

## How it works

1. **Discovery** — the plugin reads the CDI specs under `/etc/cdi` and
`/var/run/cdi`, enumerating GPU devices for vendor
`k8s.device-plugin.nvidia.com`, class `gpu` (the `all` meta-device is
excluded). Each CDI device becomes a schedulable GPU keyed by its CDI device
name (a GPU UUID).
2. **Registration** — because NVML is unavailable, per-GPU memory and type come
from `preConfiguredDeviceMemory` / `preConfiguredDeviceType` instead of being
queried from the driver.
3. **Allocation** — on `Allocate`, the plugin emits the CDI device reference
(`k8s.device-plugin.nvidia.com/gpu=<uuid>`) via the `cdi-annotations` /
`cdi-cri` device-list strategy, so containerd injects the device. HAMi's
memory/core limiting (libvgpu) is applied on top as usual.

## Limitations

- Because there is no per-device NVML data, **all** GPUs discovered in `cdi`
mode share the same `preConfiguredDeviceMemory` and `preConfiguredDeviceType`.
This targets homogeneous CDI-only nodes (e.g. a single-GB10 DGX Spark).
- Health checks and MIG are not available in `cdi` mode (both require NVML).
- GPU topology scoring (`ENABLE_TOPOLOGY_SCORE`) is skipped in `cdi` mode: it
computes pairwise P2P/NVLink scores via NVML, which is unavailable and
meaningless for a single CDI-only iGPU.
9 changes: 7 additions & 2 deletions pkg/device-plugin/nvidiadevice/nvinternal/cdi/cdi.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import (
cdiparser "tags.cncf.io/container-device-interface/pkg/parser"

spec "github.com/NVIDIA/k8s-device-plugin/api/config/v1"

"github.com/Project-HAMi/HAMi/pkg/device-plugin/nvidiadevice/nvinternal/imex"
)

Expand Down Expand Up @@ -101,8 +102,12 @@ func New(infolib info.Interface, nvmllib nvml.Interface, devicelib device.Interf
}
hasNVML, _ := infolib.HasNvml()
if !hasNVML {
klog.Warning("No valid resources detected, creating a null CDI handler")
return &null{}, nil
// NVML is unavailable but a CDI device-list strategy is requested. This
// is the case for CDI-only accelerators such as the GB10 (Grace-Blackwell
// iGPU), whose device is described by an externally-managed CDI spec on
// the node. Return a handler that can reference those specs for injection.
klog.Warning("NVML not detected; using externally-managed CDI specs for device injection")
return newExternalHandler(c.vendor), nil
}

if c.logger == nil {
Expand Down
75 changes: 75 additions & 0 deletions pkg/device-plugin/nvidiadevice/nvinternal/cdi/cdi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright 2024 The HAMi Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cdi

import (
"testing"

"github.com/NVIDIA/go-nvlib/pkg/nvlib/info"
spec "github.com/NVIDIA/k8s-device-plugin/api/config/v1"
"github.com/stretchr/testify/require"
)

// fakeInfo is a minimal info.Interface used to drive the New() branches.
type fakeInfo struct {
platform info.Platform
hasNVML bool
}

func (f fakeInfo) ResolvePlatform() info.Platform { return f.platform }
func (f fakeInfo) HasDXCore() (bool, string) { return false, "" }
func (f fakeInfo) HasNvml() (bool, string) { return f.hasNVML, "" }
func (f fakeInfo) HasTegraFiles() (bool, string) { return false, "" }
func (f fakeInfo) HasAnIntegratedGPU() (bool, string) { return false, "" }

func mustStrategies(t *testing.T, s ...string) spec.DeviceListStrategies {
t.Helper()
ds, err := spec.NewDeviceListStrategies(s)
require.NoError(t, err)
return ds
}

// When no CDI device-list strategy is enabled, New returns the null handler,
// whose QualifiedName yields an empty string.
func TestNew_NoCDIEnabled_ReturnsNull(t *testing.T) {
h, err := New(fakeInfo{hasNVML: false}, nil, nil,
WithDeviceListStrategies(mustStrategies(t, "envvar")))
require.NoError(t, err)
require.Empty(t, h.QualifiedName("gpu", "0"))
require.NoError(t, h.CreateSpecFile())
}

// When a CDI device-list strategy is requested but NVML is unavailable (the
// GB10 / CDI-only case), New returns the external handler that can still build
// qualified device names for injection.
func TestNew_NoNVML_CDIEnabled_ReturnsExternal(t *testing.T) {
h, err := New(fakeInfo{hasNVML: false}, nil, nil,
WithDeviceListStrategies(mustStrategies(t, "cdi-annotations", "cdi-cri")),
WithVendor(DefaultVendor))
require.NoError(t, err)
require.Equal(t, DefaultVendor+"/gpu=GPU-abc", h.QualifiedName("gpu", "GPU-abc"))
require.NoError(t, h.CreateSpecFile())
require.Empty(t, h.AdditionalDevices())
}

// The external handler falls back to DefaultVendor when no vendor is set.
func TestNew_NoNVML_DefaultVendor(t *testing.T) {
h, err := New(fakeInfo{hasNVML: false}, nil, nil,
WithDeviceListStrategies(mustStrategies(t, "cdi-cri")))
require.NoError(t, err)
require.Equal(t, DefaultVendor+"/gpu=0", h.QualifiedName("gpu", "0"))
}
66 changes: 66 additions & 0 deletions pkg/device-plugin/nvidiadevice/nvinternal/cdi/external.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright 2024 The HAMi Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package cdi

import (
cdiparser "tags.cncf.io/container-device-interface/pkg/parser"
)

// DefaultVendor is the CDI vendor used by the NVIDIA device plugin. It matches
// the vendor of the CDI specs generated by the NVIDIA Container Toolkit /
// GPU Operator (e.g. /var/run/cdi/k8s.device-plugin.nvidia.com-gpu.json).
const DefaultVendor = "k8s.device-plugin.nvidia.com"

// externalHandler is a CDI handler for nodes where the accelerator is exposed
// only through externally-managed CDI specs and NVML is not available to the
// device plugin. This is the case for CDI-only accelerators such as the GB10
// (Grace-Blackwell iGPU), whose device is described by a CDI spec generated by
// the NVIDIA Container Toolkit rather than being enumerable via NVML.
//
// It can construct qualified CDI device names for injection but does not
// generate CDI spec files itself (the spec is provided by the node).
type externalHandler struct {
vendor string
}

var _ Interface = &externalHandler{}

// newExternalHandler returns a CDI handler that references externally-managed
// CDI specs. An empty vendor defaults to DefaultVendor.
func newExternalHandler(vendor string) Interface {
if vendor == "" {
vendor = DefaultVendor
}
return &externalHandler{vendor: vendor}
}

// CreateSpecFile is a no-op: the CDI spec is managed externally on the node.
func (h *externalHandler) CreateSpecFile() error {
return nil
}

// QualifiedName constructs a CDI qualified device name for the specified
// resources. It assumes the id matches a device name present in the
// externally-managed CDI spec.
func (h *externalHandler) QualifiedName(class string, id string) string {
return cdiparser.QualifiedName(h.vendor, class, id)
}

// AdditionalDevices returns no additional devices for externally-managed specs.
func (h *externalHandler) AdditionalDevices() []string {
return nil
}
Loading
Loading