Default to jit-cdi mode in the nvidia runtime

Signed-off-by: Evan Lezar <elezar@nvidia.com>
This commit is contained in:
Evan Lezar 2025-02-07 22:46:48 +01:00
parent b16bd71f7e
commit 787e779318
No known key found for this signature in database
7 changed files with 65 additions and 45 deletions

View File

@ -122,11 +122,10 @@ func TestGoodInput(t *testing.T) {
err = cmdCreate.Run() err = cmdCreate.Run()
require.NoError(t, err, "runtime should not return an error") require.NoError(t, err, "runtime should not return an error")
// Check config.json for NVIDIA prestart hook // Check config.json to ensure that the NVIDIA prestart was not inserted.
spec, err = cfg.getRuntimeSpec() spec, err = cfg.getRuntimeSpec()
require.NoError(t, err, "should be no errors when reading and parsing spec from config.json") require.NoError(t, err, "should be no errors when reading and parsing spec from config.json")
require.NotEmpty(t, spec.Hooks, "there should be hooks in config.json") require.Empty(t, spec.Hooks, "there should be no hooks in config.json")
require.Equal(t, 1, nvidiaHookCount(spec.Hooks), "exactly one nvidia prestart hook should be inserted correctly into config.json")
} }
// NVIDIA prestart hook already present in config file // NVIDIA prestart hook already present in config file
@ -168,11 +167,10 @@ func TestDuplicateHook(t *testing.T) {
output, err := cmdCreate.CombinedOutput() output, err := cmdCreate.CombinedOutput()
require.NoErrorf(t, err, "runtime should not return an error", "output=%v", string(output)) require.NoErrorf(t, err, "runtime should not return an error", "output=%v", string(output))
// Check config.json for NVIDIA prestart hook // Check config.json to ensure that the NVIDIA prestart hook was removed.
spec, err = cfg.getRuntimeSpec() spec, err = cfg.getRuntimeSpec()
require.NoError(t, err, "should be no errors when reading and parsing spec from config.json") require.NoError(t, err, "should be no errors when reading and parsing spec from config.json")
require.NotEmpty(t, spec.Hooks, "there should be hooks in config.json") require.Empty(t, spec.Hooks, "there should be no hooks in config.json")
require.Equal(t, 1, nvidiaHookCount(spec.Hooks), "exactly one nvidia prestart hook should be inserted correctly into config.json")
} }
// addNVIDIAHook is a basic wrapper for an addHookModifier that is used for // addNVIDIAHook is a basic wrapper for an addHookModifier that is used for

View File

@ -30,6 +30,7 @@ const (
RuntimeModeLegacy = RuntimeMode("legacy") RuntimeModeLegacy = RuntimeMode("legacy")
RuntimeModeCSV = RuntimeMode("csv") RuntimeModeCSV = RuntimeMode("csv")
RuntimeModeCDI = RuntimeMode("cdi") RuntimeModeCDI = RuntimeMode("cdi")
RuntimeModeJitCDI = RuntimeMode("jit-cdi")
) )
// ResolveAutoMode determines the correct mode for the platform if set to "auto" // ResolveAutoMode determines the correct mode for the platform if set to "auto"
@ -57,9 +58,9 @@ func resolveMode(logger logger.Interface, mode string, image image.CUDA, propert
switch nvinfo.ResolvePlatform() { switch nvinfo.ResolvePlatform() {
case info.PlatformNVML, info.PlatformWSL: case info.PlatformNVML, info.PlatformWSL:
return RuntimeModeLegacy return RuntimeModeJitCDI
case info.PlatformTegra: case info.PlatformTegra:
return RuntimeModeCSV return RuntimeModeCSV
} }
return RuntimeModeLegacy return RuntimeModeJitCDI
} }

View File

@ -43,11 +43,16 @@ func TestResolveAutoMode(t *testing.T) {
mode: "not-auto", mode: "not-auto",
expectedMode: "not-auto", expectedMode: "not-auto",
}, },
{
description: "legacy resolves to legacy",
mode: "legacy",
expectedMode: "legacy",
},
{ {
description: "no info defaults to legacy", description: "no info defaults to legacy",
mode: "auto", mode: "auto",
info: map[string]bool{}, info: map[string]bool{},
expectedMode: "legacy", expectedMode: "jit-cdi",
}, },
{ {
description: "non-nvml, non-tegra, nvgpu resolves to csv", description: "non-nvml, non-tegra, nvgpu resolves to csv",
@ -80,14 +85,14 @@ func TestResolveAutoMode(t *testing.T) {
expectedMode: "csv", expectedMode: "csv",
}, },
{ {
description: "nvml, non-tegra, non-nvgpu resolves to legacy", description: "nvml, non-tegra, non-nvgpu resolves to jit-cdi",
mode: "auto", mode: "auto",
info: map[string]bool{ info: map[string]bool{
"nvml": true, "nvml": true,
"tegra": false, "tegra": false,
"nvgpu": false, "nvgpu": false,
}, },
expectedMode: "legacy", expectedMode: "jit-cdi",
}, },
{ {
description: "nvml, non-tegra, nvgpu resolves to csv", description: "nvml, non-tegra, nvgpu resolves to csv",
@ -100,14 +105,14 @@ func TestResolveAutoMode(t *testing.T) {
expectedMode: "csv", expectedMode: "csv",
}, },
{ {
description: "nvml, tegra, non-nvgpu resolves to legacy", description: "nvml, tegra, non-nvgpu resolves to jit-cdi",
mode: "auto", mode: "auto",
info: map[string]bool{ info: map[string]bool{
"nvml": true, "nvml": true,
"tegra": true, "tegra": true,
"nvgpu": false, "nvgpu": false,
}, },
expectedMode: "legacy", expectedMode: "jit-cdi",
}, },
{ {
description: "nvml, tegra, nvgpu resolves to csv", description: "nvml, tegra, nvgpu resolves to csv",
@ -136,7 +141,7 @@ func TestResolveAutoMode(t *testing.T) {
}, },
}, },
{ {
description: "at least one non-cdi device resolves to legacy", description: "at least one non-cdi device resolves to jit-cdi",
mode: "auto", mode: "auto",
envmap: map[string]string{ envmap: map[string]string{
"NVIDIA_VISIBLE_DEVICES": "nvidia.com/gpu=0,0", "NVIDIA_VISIBLE_DEVICES": "nvidia.com/gpu=0,0",
@ -146,7 +151,7 @@ func TestResolveAutoMode(t *testing.T) {
"tegra": false, "tegra": false,
"nvgpu": false, "nvgpu": false,
}, },
expectedMode: "legacy", expectedMode: "jit-cdi",
}, },
{ {
description: "at least one non-cdi device resolves to csv", description: "at least one non-cdi device resolves to csv",
@ -170,7 +175,7 @@ func TestResolveAutoMode(t *testing.T) {
expectedMode: "cdi", expectedMode: "cdi",
}, },
{ {
description: "cdi mount and non-CDI devices resolves to legacy", description: "cdi mount and non-CDI devices resolves to jit-cdi",
mode: "auto", mode: "auto",
mounts: []string{ mounts: []string{
"/var/run/nvidia-container-devices/cdi/nvidia.com/gpu/0", "/var/run/nvidia-container-devices/cdi/nvidia.com/gpu/0",
@ -181,7 +186,7 @@ func TestResolveAutoMode(t *testing.T) {
"tegra": false, "tegra": false,
"nvgpu": false, "nvgpu": false,
}, },
expectedMode: "legacy", expectedMode: "jit-cdi",
}, },
{ {
description: "cdi mount and non-CDI envvar resolves to cdi", description: "cdi mount and non-CDI envvar resolves to cdi",
@ -199,22 +204,6 @@ func TestResolveAutoMode(t *testing.T) {
}, },
expectedMode: "cdi", expectedMode: "cdi",
}, },
{
description: "non-cdi mount and CDI envvar resolves to legacy",
mode: "auto",
envmap: map[string]string{
"NVIDIA_VISIBLE_DEVICES": "nvidia.com/gpu=0",
},
mounts: []string{
"/var/run/nvidia-container-devices/0",
},
info: map[string]bool{
"nvml": true,
"tegra": false,
"nvgpu": false,
},
expectedMode: "legacy",
},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -31,11 +31,22 @@ import (
"github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/spec" "github.com/NVIDIA/nvidia-container-toolkit/pkg/nvcdi/spec"
) )
const (
automaticDeviceVendor = "runtime.nvidia.com"
automaticDeviceClass = "gpu"
automaticDeviceKind = automaticDeviceVendor + "/" + automaticDeviceClass
)
// NewCDIModifier creates an OCI spec modifier that determines the modifications to make based on the // NewCDIModifier creates an OCI spec modifier that determines the modifications to make based on the
// CDI specifications available on the system. The NVIDIA_VISIBLE_DEVICES environment variable is // CDI specifications available on the system. The NVIDIA_VISIBLE_DEVICES environment variable is
// used to select the devices to include. // used to select the devices to include.
func NewCDIModifier(logger logger.Interface, cfg *config.Config, ociSpec oci.Spec) (oci.SpecModifier, error) { func NewCDIModifier(logger logger.Interface, cfg *config.Config, ociSpec oci.Spec, isJitCDI bool) (oci.SpecModifier, error) {
devices, err := getDevicesFromSpec(logger, ociSpec, cfg) defaultKind := cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.DefaultKind
if isJitCDI {
defaultKind = automaticDeviceKind
}
devices, err := getDevicesFromSpec(logger, ociSpec, cfg, defaultKind)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get required devices from OCI specification: %v", err) return nil, fmt.Errorf("failed to get required devices from OCI specification: %v", err)
} }
@ -65,13 +76,13 @@ func NewCDIModifier(logger logger.Interface, cfg *config.Config, ociSpec oci.Spe
) )
} }
func getDevicesFromSpec(logger logger.Interface, ociSpec oci.Spec, cfg *config.Config) ([]string, error) { func getDevicesFromSpec(logger logger.Interface, ociSpec oci.Spec, cfg *config.Config, defaultKind string) ([]string, error) {
cdiModifier := &cdiModifier{ cdiModifier := &cdiModifier{
logger: logger, logger: logger,
acceptDeviceListAsVolumeMounts: cfg.AcceptDeviceListAsVolumeMounts, acceptDeviceListAsVolumeMounts: cfg.AcceptDeviceListAsVolumeMounts,
acceptEnvvarUnprivileged: cfg.AcceptEnvvarUnprivileged, acceptEnvvarUnprivileged: cfg.AcceptEnvvarUnprivileged,
annotationPrefixes: cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.AnnotationPrefixes, annotationPrefixes: cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.AnnotationPrefixes,
defaultKind: cfg.NVIDIAContainerRuntimeConfig.Modes.CDI.DefaultKind, defaultKind: defaultKind,
} }
return cdiModifier.getDevicesFromSpec(ociSpec) return cdiModifier.getDevicesFromSpec(ociSpec)
} }
@ -114,9 +125,7 @@ func (c *cdiModifier) getDevicesFromSpec(ociSpec oci.Spec) ([]string, error) {
var devices []string var devices []string
seen := make(map[string]bool) seen := make(map[string]bool)
for _, name := range container.VisibleDevices() { for _, name := range container.VisibleDevices() {
if !parser.IsQualifiedName(name) { name = c.normalizeDevice(name)
name = fmt.Sprintf("%s=%s", c.defaultKind, name)
}
if seen[name] { if seen[name] {
c.logger.Debugf("Ignoring duplicate device %q", name) c.logger.Debugf("Ignoring duplicate device %q", name)
continue continue
@ -128,6 +137,13 @@ func (c *cdiModifier) getDevicesFromSpec(ociSpec oci.Spec) ([]string, error) {
return devices, nil return devices, nil
} }
func (c *cdiModifier) normalizeDevice(device string) string {
if !parser.IsQualifiedName(device) {
return fmt.Sprintf("%s=%s", c.defaultKind, device)
}
return device
}
// getAnnotationDevices returns a list of devices specified in the annotations. // getAnnotationDevices returns a list of devices specified in the annotations.
// Keys starting with the specified prefixes are considered and expected to contain a comma-separated list of // Keys starting with the specified prefixes are considered and expected to contain a comma-separated list of
// fully-qualified CDI devices names. If any device name is not fully-quality an error is returned. // fully-qualified CDI devices names. If any device name is not fully-quality an error is returned.
@ -168,7 +184,7 @@ func filterAutomaticDevices(devices []string) []string {
var automatic []string var automatic []string
for _, device := range devices { for _, device := range devices {
vendor, class, _ := parser.ParseDevice(device) vendor, class, _ := parser.ParseDevice(device)
if vendor == "runtime.nvidia.com" && class == "gpu" { if vendor == automaticDeviceVendor && class == automaticDeviceClass {
automatic = append(automatic, device) automatic = append(automatic, device)
} }
} }
@ -177,6 +193,8 @@ func filterAutomaticDevices(devices []string) []string {
func newAutomaticCDISpecModifier(logger logger.Interface, cfg *config.Config, devices []string) (oci.SpecModifier, error) { func newAutomaticCDISpecModifier(logger logger.Interface, cfg *config.Config, devices []string) (oci.SpecModifier, error) {
logger.Debugf("Generating in-memory CDI specs for devices %v", devices) logger.Debugf("Generating in-memory CDI specs for devices %v", devices)
// TODO: We should try to load the kernel modules and create the device nodes here.
// Failures should raise a warning and not error out.
spec, err := generateAutomaticCDISpec(logger, cfg, devices) spec, err := generateAutomaticCDISpec(logger, cfg, devices)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to generate CDI spec: %w", err) return nil, fmt.Errorf("failed to generate CDI spec: %w", err)

View File

@ -142,6 +142,20 @@ func TestGetDevicesFromSpec(t *testing.T) {
}, },
expectedDevices: []string{"nvidia.com/gpu=0", "example.com/class=device"}, expectedDevices: []string{"nvidia.com/gpu=0", "example.com/class=device"},
}, },
{
description: "cdi devices from envvar with default kind",
input: cdiModifier{
defaultKind: "runtime.nvidia.com/gpu",
acceptEnvvarUnprivileged: true,
acceptDeviceListAsVolumeMounts: true,
},
spec: &specs.Spec{
Process: &specs.Process{
Env: []string{"NVIDIA_VISIBLE_DEVICES=all"},
},
},
expectedDevices: []string{"runtime.nvidia.com/gpu=all"},
},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -119,8 +119,8 @@ func newModeModifier(logger logger.Interface, mode info.RuntimeMode, cfg *config
return modifier.NewStableRuntimeModifier(logger, cfg.NVIDIAContainerRuntimeHookConfig.Path), nil return modifier.NewStableRuntimeModifier(logger, cfg.NVIDIAContainerRuntimeHookConfig.Path), nil
case info.RuntimeModeCSV: case info.RuntimeModeCSV:
return modifier.NewCSVModifier(logger, cfg, image) return modifier.NewCSVModifier(logger, cfg, image)
case info.RuntimeModeCDI: case info.RuntimeModeCDI, info.RuntimeModeJitCDI:
return modifier.NewCDIModifier(logger, cfg, ociSpec) return modifier.NewCDIModifier(logger, cfg, ociSpec, mode == info.RuntimeModeJitCDI)
} }
return nil, fmt.Errorf("invalid runtime mode: %v", cfg.NVIDIAContainerRuntimeConfig.Mode) return nil, fmt.Errorf("invalid runtime mode: %v", cfg.NVIDIAContainerRuntimeConfig.Mode)
@ -129,7 +129,7 @@ func newModeModifier(logger logger.Interface, mode info.RuntimeMode, cfg *config
// supportedModifierTypes returns the modifiers supported for a specific runtime mode. // supportedModifierTypes returns the modifiers supported for a specific runtime mode.
func supportedModifierTypes(mode info.RuntimeMode) []string { func supportedModifierTypes(mode info.RuntimeMode) []string {
switch mode { switch mode {
case info.RuntimeModeCDI: case info.RuntimeModeCDI, info.RuntimeModeJitCDI:
// For CDI mode we make no additional modifications. // For CDI mode we make no additional modifications.
return []string{"nvidia-hook-remover", "mode"} return []string{"nvidia-hook-remover", "mode"}
case info.RuntimeModeCSV: case info.RuntimeModeCSV:

View File

@ -1 +1 @@
{"ociVersion":"1.0.1-dev","process":{"terminal":true,"user":{"uid":0,"gid":0},"args":["sh"],"env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","TERM=xterm"],"cwd":"/","capabilities":{"bounding":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"effective":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"inheritable":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"permitted":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"ambient":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"]},"rlimits":[{"type":"RLIMIT_NOFILE","hard":1024,"soft":1024}],"noNewPrivileges":true},"root":{"path":"rootfs","readonly":true},"hostname":"runc","mounts":[{"destination":"/proc","type":"proc","source":"proc"},{"destination":"/dev","type":"tmpfs","source":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","type":"devpts","source":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/shm","type":"tmpfs","source":"shm","options":["nosuid","noexec","nodev","mode=1777","size=65536k"]},{"destination":"/dev/mqueue","type":"mqueue","source":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","type":"sysfs","source":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/sys/fs/cgroup","type":"cgroup","source":"cgroup","options":["nosuid","noexec","nodev","relatime","ro"]}],"hooks":{"prestart":[{"path":"nvidia-container-runtime-hook","args":["nvidia-container-runtime-hook","prestart"]}]},"linux":{"resources":{"devices":[{"allow":false,"access":"rwm"}]},"namespaces":[{"type":"pid"},{"type":"network"},{"type":"ipc"},{"type":"uts"},{"type":"mount"}],"maskedPaths":["/proc/kcore","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/sys/firmware","/proc/scsi"],"readonlyPaths":["/proc/asound","/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"]}} {"ociVersion":"1.0.1-dev","process":{"terminal":true,"user":{"uid":0,"gid":0},"args":["sh"],"env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","TERM=xterm"],"cwd":"/","capabilities":{"bounding":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"effective":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"inheritable":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"permitted":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"],"ambient":["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE"]},"rlimits":[{"type":"RLIMIT_NOFILE","hard":1024,"soft":1024}],"noNewPrivileges":true},"root":{"path":"rootfs","readonly":true},"hostname":"runc","mounts":[{"destination":"/proc","type":"proc","source":"proc"},{"destination":"/dev","type":"tmpfs","source":"tmpfs","options":["nosuid","strictatime","mode=755","size=65536k"]},{"destination":"/dev/pts","type":"devpts","source":"devpts","options":["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620","gid=5"]},{"destination":"/dev/shm","type":"tmpfs","source":"shm","options":["nosuid","noexec","nodev","mode=1777","size=65536k"]},{"destination":"/dev/mqueue","type":"mqueue","source":"mqueue","options":["nosuid","noexec","nodev"]},{"destination":"/sys","type":"sysfs","source":"sysfs","options":["nosuid","noexec","nodev","ro"]},{"destination":"/sys/fs/cgroup","type":"cgroup","source":"cgroup","options":["nosuid","noexec","nodev","relatime","ro"]}],"hooks":{},"linux":{"resources":{"devices":[{"allow":false,"access":"rwm"}]},"namespaces":[{"type":"pid"},{"type":"network"},{"type":"ipc"},{"type":"uts"},{"type":"mount"}],"maskedPaths":["/proc/kcore","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/sys/firmware","/proc/scsi"],"readonlyPaths":["/proc/asound","/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"]}}