Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for allocating GPUs in Passthrough-Mode #183

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
60 changes: 60 additions & 0 deletions api/nvidia.com/resource/gpu/v1alpha1/driverconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
*
* 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 v1alpha1

import "fmt"

// GpuDriver encodes the gpu driver as a string.
type GpuDriver string

const (
NvidiaDriver GpuDriver = "nvidia"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there another name for this driver that isn't nvidia?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. The driver is simply called nvidia

VfioPciDriver GpuDriver = "vfio-pci"
)

// GpuDriverConfig holds the set of parameters for configuring a GPU with a driver.
type GpuDriverConfig struct {
Driver GpuDriver `json:"driver"`
}

// DefaultGpuDriverConfig provides the default configuration of a GPU with a driver.
func DefaultGpuDriverConfig() *GpuDriverConfig {
return &GpuDriverConfig{
Driver: NvidiaDriver,
}
}

// Normalize updates a GpuDriverConfig config with implied default values based on other settings.
func (c *GpuDriverConfig) Normalize() error {
if c.Driver == "" {
c.Driver = NvidiaDriver
}
return nil
}

// Validate ensures that GpuDriverConfig has a valid set of values.
func (c *GpuDriverConfig) Validate() error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Is c == nil valid?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. gpuConfig.Normalize() would always ensure GpuDriverConfig is set

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My question should probably have been: "Should we add an explict nil check and retunr an error if this is the case?"

switch c.Driver {
case NvidiaDriver:
fallthrough
case VfioPciDriver:
break
default:
return fmt.Errorf("invalid driver '%s' specified in gpu driver configuration", c.Driver)
}
return nil
}
40 changes: 36 additions & 4 deletions api/nvidia.com/resource/gpu/v1alpha1/gpuconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import (
// GpuConfig holds the set of parameters for configuring a GPU.
type GpuConfig struct {
metav1.TypeMeta `json:",inline"`
Sharing *GpuSharing `json:"sharing,omitempty"`
Sharing *GpuSharing `json:"sharing,omitempty"`
DriverConfig *GpuDriverConfig `json:"driverConfig,omitempty"`
}

// DefaultGpuConfig provides the default GPU configuration.
Expand All @@ -45,11 +46,27 @@ func DefaultGpuConfig() *GpuConfig {
Interval: ptr.To(DefaultTimeSlice),
},
},
DriverConfig: &GpuDriverConfig{
Driver: NvidiaDriver,
},
}
}

// Normalize updates a GpuConfig config with implied default values based on other settings.
func (c *GpuConfig) Normalize() error {
if c.DriverConfig == nil {
c.DriverConfig = DefaultGpuDriverConfig()
}

if err := c.DriverConfig.Normalize(); err != nil {
return err
}

// If sharing is not supported, don't proceed with normalizing its configuration.
if !c.SupportsSharing() {
return nil
}

if c.Sharing == nil {
c.Sharing = &GpuSharing{
Strategy: TimeSlicingStrategy,
Expand All @@ -68,8 +85,23 @@ func (c *GpuConfig) Normalize() error {

// Validate ensures that GpuConfig has a valid set of values.
func (c *GpuConfig) Validate() error {
if c.Sharing == nil {
return fmt.Errorf("no sharing strategy set")
if err := c.DriverConfig.Validate(); err != nil {
return err
}
return c.Sharing.Validate()

if c.SupportsSharing() {
if c.Sharing == nil {
return fmt.Errorf("no sharing strategy set")
}
if err := c.Sharing.Validate(); err != nil {
return err
}
} else if c.Sharing != nil {
return fmt.Errorf("sharing strategy cannot be provided while using non-nvidia driver")
}
return nil
}

func (c *GpuConfig) SupportsSharing() bool {
return c.DriverConfig.Driver == NvidiaDriver
}
21 changes: 21 additions & 0 deletions api/nvidia.com/resource/gpu/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion cmd/nvidia-dra-plugin/allocatable.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -106,3 +106,14 @@ func (d AllocatableDevices) UUIDs() []string {
slices.Sort(uuids)
return uuids
}

func (d AllocatableDevices) PciAddresses() []string {
var pciAddresses []string
for _, device := range d {
if device.Type() == GpuDeviceType {
pciAddresses = append(pciAddresses, device.Gpu.PciAddress)
}
}
slices.Sort(pciAddresses)
return pciAddresses
varunrsekar marked this conversation as resolved.
Show resolved Hide resolved
}
98 changes: 76 additions & 22 deletions cmd/nvidia-dra-plugin/device_state.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -38,17 +38,19 @@ type OpaqueDeviceConfig struct {
}

type DeviceConfigState struct {
MpsControlDaemonID string `json:"mpsControlDaemonID"`
MpsControlDaemonID string `json:"mpsControlDaemonID"`
GpuConfig *configapi.GpuConfig `json:"gpuConfig,omitempty"`
containerEdits *cdiapi.ContainerEdits
}

type DeviceState struct {
sync.Mutex
cdi *CDIHandler
tsManager *TimeSlicingManager
mpsManager *MpsManager
allocatable AllocatableDevices
config *Config
cdi *CDIHandler
tsManager *TimeSlicingManager
mpsManager *MpsManager
vfioPciManager *VfioPciManager
allocatable AllocatableDevices
config *Config

nvdevlib *deviceLib
checkpointManager checkpointmanager.CheckpointManager
Expand Down Expand Up @@ -87,6 +89,8 @@ func NewDeviceState(ctx context.Context, config *Config) (*DeviceState, error) {
tsManager := NewTimeSlicingManager(nvdevlib)
mpsManager := NewMpsManager(config, nvdevlib, MpsRoot, hostDriverRoot, MpsControlDaemonTemplatePath)

vfioPciManager := NewVfioPciManager()

if err := cdi.CreateStandardDeviceSpecFile(allocatable); err != nil {
return nil, fmt.Errorf("unable to create base CDI spec file: %v", err)
}
Expand All @@ -100,12 +104,18 @@ func NewDeviceState(ctx context.Context, config *Config) (*DeviceState, error) {
cdi: cdi,
tsManager: tsManager,
mpsManager: mpsManager,
vfioPciManager: vfioPciManager,
allocatable: allocatable,
config: config,
nvdevlib: nvdevlib,
checkpointManager: checkpointManager,
}

// Initialize the vfio-pci driver manager.
if err := vfioPciManager.Init(); err != nil {
return nil, fmt.Errorf("unable to initialize vfio-pci manager: %v", err)
}

checkpoints, err := state.checkpointManager.ListCheckpoints()
if err != nil {
return nil, fmt.Errorf("unable to list checkpoints: %v", err)
Expand Down Expand Up @@ -349,35 +359,67 @@ func (s *DeviceState) prepareDevices(ctx context.Context, claim *resourceapi.Res

func (s *DeviceState) unprepareDevices(ctx context.Context, claimUID string, devices PreparedDevices) error {
for _, group := range devices {
if group.ConfigState.GpuConfig != nil {
err := s.unprepareGpus(ctx, group.ConfigState.GpuConfig, group.Devices.Gpus())
if err != nil {
return err
}
}
// Stop any MPS control daemons started for each group of prepared devices.
mpsControlDaemon := s.mpsManager.NewMpsControlDaemon(claimUID, group)
varunrsekar marked this conversation as resolved.
Show resolved Hide resolved
if err := mpsControlDaemon.Stop(ctx); err != nil {
return fmt.Errorf("error stopping MPS control daemon: %w", err)
}

// Go back to default time-slicing for all full GPUs.
tsc := configapi.DefaultGpuConfig().Sharing.TimeSlicingConfig
if err := s.tsManager.SetTimeSlice(group.Devices.Gpus(), tsc); err != nil {
if err := s.tsManager.SetTimeSlice(devices, tsc); err != nil {
return fmt.Errorf("error setting timeslice for devices: %w", err)
}
}
return nil
}
func (s *DeviceState) unprepareGpus(ctx context.Context, config *configapi.GpuConfig, devices PreparedDeviceList) error {
elezar marked this conversation as resolved.
Show resolved Hide resolved
if config.DriverConfig.Driver == configapi.VfioPciDriver {
for _, device := range devices {
if err := s.vfioPciManager.Unconfigure(device.Gpu.Info); err != nil {
return fmt.Errorf("error unconfiguring vfio-pci device: %w", err)
}
}
}
return nil
}

func (s *DeviceState) applyConfig(ctx context.Context, config configapi.Interface, claim *resourceapi.ResourceClaim, results []*resourceapi.DeviceRequestAllocationResult) (*DeviceConfigState, error) {
var configState DeviceConfigState
switch castConfig := config.(type) {
case *configapi.GpuConfig:
return s.applySharingConfig(ctx, castConfig.Sharing, claim, results)
configState.GpuConfig = castConfig
return s.applyGpuConfig(ctx, castConfig, claim, results, &configState)
case *configapi.MigDeviceConfig:
return s.applySharingConfig(ctx, castConfig.Sharing, claim, results)
return s.applySharingConfig(ctx, castConfig.Sharing, claim, results, &configState)
case *configapi.ImexChannelConfig:
return s.applyImexChannelConfig(ctx, castConfig, claim, results)
return s.applyImexChannelConfig(ctx, castConfig, claim, results, &configState)
default:
return nil, fmt.Errorf("unknown config type: %T", castConfig)
}
}

func (s *DeviceState) applySharingConfig(ctx context.Context, config configapi.Sharing, claim *resourceapi.ResourceClaim, results []*resourceapi.DeviceRequestAllocationResult) (*DeviceConfigState, error) {
func (s *DeviceState) applyGpuConfig(ctx context.Context, config *configapi.GpuConfig, claim *resourceapi.ResourceClaim, results []*resourceapi.DeviceRequestAllocationResult, configState *DeviceConfigState) (*DeviceConfigState, error) {
var err error
configState, err = s.applyGpuDriverConfig(ctx, config.DriverConfig, results, configState)
if err != nil {
return nil, err
}
if config.SupportsSharing() {
configState, err = s.applySharingConfig(ctx, config.Sharing, claim, results, configState)
if err != nil {
return nil, err
}
}
return configState, nil
}

func (s *DeviceState) applySharingConfig(ctx context.Context, config configapi.Sharing, claim *resourceapi.ResourceClaim, results []*resourceapi.DeviceRequestAllocationResult, configState *DeviceConfigState) (*DeviceConfigState, error) {
// Get the list of claim requests this config is being applied over.
var requests []string
for _, r := range results {
Expand All @@ -390,9 +432,6 @@ func (s *DeviceState) applySharingConfig(ctx context.Context, config configapi.S
allocatableDevices[r.Device] = s.allocatable[r.Device]
}

// Declare a device group state object to populate.
var configState DeviceConfigState

// Apply time-slicing settings (if available).
if config.IsTimeSlicing() {
tsc, err := config.GetTimeSlicingConfig()
Expand Down Expand Up @@ -424,13 +463,10 @@ func (s *DeviceState) applySharingConfig(ctx context.Context, config configapi.S
configState.containerEdits = mpsControlDaemon.GetCDIContainerEdits()
}

return &configState, nil
return configState, nil
}

func (s *DeviceState) applyImexChannelConfig(ctx context.Context, config *configapi.ImexChannelConfig, claim *resourceapi.ResourceClaim, results []*resourceapi.DeviceRequestAllocationResult) (*DeviceConfigState, error) {
// Declare a device group state object to populate.
var configState DeviceConfigState

func (s *DeviceState) applyImexChannelConfig(ctx context.Context, config *configapi.ImexChannelConfig, claim *resourceapi.ResourceClaim, results []*resourceapi.DeviceRequestAllocationResult, configState *DeviceConfigState) (*DeviceConfigState, error) {
// Create any necessary IMEX channels and gather their CDI container edits.
for _, r := range results {
imexChannel := s.allocatable[r.Device].ImexChannel
Expand All @@ -440,7 +476,25 @@ func (s *DeviceState) applyImexChannelConfig(ctx context.Context, config *config
configState.containerEdits = configState.containerEdits.Append(s.cdi.GetImexChannelContainerEdits(imexChannel))
}

return &configState, nil
return configState, nil
}

func (s *DeviceState) applyGpuDriverConfig(ctx context.Context, config *configapi.GpuDriverConfig, results []*resourceapi.DeviceRequestAllocationResult, configState *DeviceConfigState) (*DeviceConfigState, error) {
if config.Driver != configapi.VfioPciDriver {
return configState, nil
}

// Apply vfio-pci driver settings.
for _, r := range results {
info := s.allocatable[r.Device]
err := s.vfioPciManager.Configure(info.Gpu)
if err != nil {
return nil, err
}
configState.containerEdits = configState.containerEdits.Append(s.vfioPciManager.GetCDIContainerEdits(info.Gpu))
}

return configState, nil
}

// GetOpaqueDeviceConfigs returns an ordered list of the configs contained in possibleConfigs for this driver.
Expand Down
4 changes: 4 additions & 0 deletions cmd/nvidia-dra-plugin/deviceinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

type GpuInfo struct {
UUID string `json:"uuid"`
PciAddress string `json:"pciAddress"`
index int
minor int
migEnabled bool
Expand Down Expand Up @@ -130,6 +131,9 @@ func (d *GpuInfo) GetDevice() resourceapi.Device {
"cudaDriverVersion": {
VersionValue: ptr.To(semver.MustParse(d.cudaDriverVersion).String()),
},
"pciAddress": {
StringValue: &d.PciAddress,
},
},
Capacity: map[resourceapi.QualifiedName]resource.Quantity{
"memory": *resource.NewQuantity(int64(d.memoryBytes), resource.BinarySI),
Expand Down
Loading