@@ -24,21 +24,85 @@ import (
2424 "sync"
2525
2626 "github.com/NVIDIA/go-nvml/pkg/nvml"
27+ resourceapi "k8s.io/api/resource/v1"
2728 "k8s.io/klog/v2"
2829)
2930
3031const (
3132 FullGPUInstanceID uint32 = 0xFFFFFFFF
3233)
3334
35+ const (
36+ TaintKeyXID = DriverName + "/xid"
37+ TaintKeyGPULost = DriverName + "/gpu-lost"
38+ TaintKeyUnmonitored = DriverName + "/unmonitored"
39+ )
40+
41+ // TODO: remove later, as its not in vendored api
42+ // DeviceTaintEffectNone is an informational effect that does not affect
43+ // scheduling or eviction. Available in k8s 1.36+ (KEP-5055 beta).
44+ const DeviceTaintEffectNone resourceapi.DeviceTaintEffect = "None"
45+
46+ // DeviceHealthEventType classifies the category of health event detected by
47+ // the NVML health monitor.
48+ type DeviceHealthEventType string
49+
50+ const (
51+ HealthEventXID DeviceHealthEventType = "xid"
52+ HealthEventGPULost DeviceHealthEventType = "gpu-lost"
53+ HealthEventUnmonitored DeviceHealthEventType = "unmonitored"
54+ )
55+
56+ // DeviceHealthEvent carries a typed health notification from the NVML health
57+ // monitor to the driver's event handler, enabling the driver to set the
58+ // appropriate DRA device taint per the Option A schema (KEP-5055).
59+ type DeviceHealthEvent struct {
60+ Device * AllocatableDevice
61+ EventType DeviceHealthEventType
62+ // inspired by NVML Event type and only meaningful for xid errors.
63+ // may have to create a custom type based on future device-api
64+ EventData uint64
65+ }
66+
67+ // healthEventToTaint maps a DeviceHealthEvent to the corresponding DRA
68+ // DeviceTaint using the Option A taint key schema: one key per health
69+ // dimension under the gpu.nvidia.com domain.
70+ // TODO: revisit this to double check on the desired effects
71+ func healthEventToTaint (event * DeviceHealthEvent ) resourceapi.DeviceTaint {
72+ switch event .EventType {
73+ case HealthEventXID :
74+ return resourceapi.DeviceTaint {
75+ Key : TaintKeyXID ,
76+ Value : strconv .FormatUint (event .EventData , 10 ),
77+ Effect : DeviceTaintEffectNone ,
78+ }
79+ case HealthEventGPULost :
80+ return resourceapi.DeviceTaint {
81+ Key : TaintKeyGPULost ,
82+ Effect : resourceapi .DeviceTaintEffectNoExecute ,
83+ }
84+ case HealthEventUnmonitored :
85+ return resourceapi.DeviceTaint {
86+ Key : TaintKeyUnmonitored ,
87+ Effect : DeviceTaintEffectNone ,
88+ }
89+ default :
90+ klog .Errorf ("Unknown health event type %q, defaulting to unmonitored taint" , event .EventType )
91+ return resourceapi.DeviceTaint {
92+ Key : TaintKeyUnmonitored ,
93+ Effect : DeviceTaintEffectNone ,
94+ }
95+ }
96+ }
97+
3498// For a MIG device the placement is defined by the 3-tuple <parent UUID, GI, CI>.
3599// For a full device the returned 3-tuple is the device's uuid and (FullGPUInstanceID) 0xFFFFFFFF for the other two elements.
36100type devicePlacementMap map [string ]map [uint32 ]map [uint32 ]* AllocatableDevice
37101
38102type nvmlDeviceHealthMonitor struct {
39103 nvmllib nvml.Interface
40104 eventSet nvml.EventSet
41- unhealthy chan * AllocatableDevice
105+ unhealthy chan * DeviceHealthEvent
42106 deviceByPlacement devicePlacementMap
43107 skippedXids map [uint64 ]bool
44108 wg sync.WaitGroup
@@ -57,7 +121,7 @@ func newNvmlDeviceHealthMonitor(config *Config, allocatable AllocatableDevices,
57121
58122 m := & nvmlDeviceHealthMonitor {
59123 nvmllib : nvdevlib .nvmllib ,
60- unhealthy : make (chan * AllocatableDevice , len (allocatable )),
124+ unhealthy : make (chan * DeviceHealthEvent , len (allocatable )),
61125 deviceByPlacement : getDevicePlacementMap (allocatable ),
62126 skippedXids : xidsToSkip (config .flags .additionalXidsToIgnore ),
63127 }
@@ -102,15 +166,15 @@ func (m *nvmlDeviceHealthMonitor) registerEventsForDevices() {
102166 for parentUUID , giMap := range m .deviceByPlacement {
103167 gpu , ret := m .nvmllib .DeviceGetHandleByUUID (parentUUID )
104168 if ret != nvml .SUCCESS {
105- klog .Warningf ("Unable to get device handle from UUID[%s]: %v; marking it as unhealthy " , parentUUID , ret )
106- m .markAllMigDevicesUnhealthy (giMap )
169+ klog .Warningf ("Unable to get device handle from UUID[%s]: %v; marking devices as unmonitored " , parentUUID , ret )
170+ m .sendHealthEventsForDevices (giMap , HealthEventUnmonitored )
107171 continue
108172 }
109173
110174 supportedEvents , ret := gpu .GetSupportedEventTypes ()
111175 if ret != nvml .SUCCESS {
112- klog .Warningf ("unable to determine the supported events for %s: %v; marking it as unhealthy " , parentUUID , ret )
113- m .markAllMigDevicesUnhealthy (giMap )
176+ klog .Warningf ("unable to determine the supported events for %s: %v; marking devices as unmonitored " , parentUUID , ret )
177+ m .sendHealthEventsForDevices (giMap , HealthEventUnmonitored )
114178 continue
115179 }
116180
@@ -119,8 +183,8 @@ func (m *nvmlDeviceHealthMonitor) registerEventsForDevices() {
119183 klog .Warningf ("Device %v is too old to support healthchecking." , parentUUID )
120184 }
121185 if ret != nvml .SUCCESS {
122- klog .Warningf ("unable to register events for %s: %v; marking it as unhealthy " , parentUUID , ret )
123- m .markAllMigDevicesUnhealthy (giMap )
186+ klog .Warningf ("unable to register events for %s: %v; marking devices as unmonitored " , parentUUID , ret )
187+ m .sendHealthEventsForDevices (giMap , HealthEventUnmonitored )
124188 }
125189 }
126190}
@@ -158,8 +222,8 @@ func (m *nvmlDeviceHealthMonitor) run(ctx context.Context) {
158222 // Ref doc: [https://docs.nvidia.com/deploy/nvml-api/group__nvmlEvents.html#group__nvmlEvents_1g9714b0ca9a34c7a7780f87fee16b205c].
159223 if ret != nvml .SUCCESS {
160224 if ret == nvml .ERROR_GPU_IS_LOST {
161- klog .Warningf ("GPU is lost error: %v; Marking all devices as unhealthy " , ret )
162- m .markAllDevicesUnhealthy ( )
225+ klog .Warningf ("GPU is lost error: %v; Tainting all devices with %s " , ret , TaintKeyGPULost )
226+ m .sendHealthEventsForAllDevices ( HealthEventGPULost )
163227 continue
164228 }
165229 klog .V (6 ).Infof ("Error waiting for NVML event: %v. Retrying..." , ret )
@@ -187,8 +251,8 @@ func (m *nvmlDeviceHealthMonitor) run(ctx context.Context) {
187251 // TODO: look into how to properly handle this error.
188252 eventUUID , ret := event .Device .GetUUID ()
189253 if ret != nvml .SUCCESS {
190- klog .Warningf ("Failed to determine uuid for event %v: %v; Marking all devices as unhealthy. " , event , ret )
191- m .markAllDevicesUnhealthy ( )
254+ klog .Warningf ("Failed to determine uuid for event %v: %v; Tainting all devices with %s " , event , ret , TaintKeyGPULost )
255+ m .sendHealthEventsForAllDevices ( HealthEventGPULost )
192256 continue
193257 }
194258 affectedDevice := m .deviceByPlacement .get (eventUUID , gi , ci )
@@ -197,38 +261,41 @@ func (m *nvmlDeviceHealthMonitor) run(ctx context.Context) {
197261 continue
198262 }
199263
200- klog .V (4 ).Infof ("Sending unhealthy notification for device %s due to event type:%v and event data:%d" , affectedDevice .UUID (), eType , xid )
201- m .unhealthy <- affectedDevice
264+ klog .V (4 ).Infof ("Sending XID=%d health event for device %s" , xid , affectedDevice .UUID ())
265+ m .unhealthy <- & DeviceHealthEvent {
266+ Device : affectedDevice ,
267+ EventType : HealthEventXID ,
268+ EventData : xid ,
269+ }
202270 }
203271 }
204272}
205273
206- func (m * nvmlDeviceHealthMonitor ) Unhealthy () <- chan * AllocatableDevice {
274+ func (m * nvmlDeviceHealthMonitor ) Unhealthy () <- chan * DeviceHealthEvent {
207275 return m .unhealthy
208276}
209277
210- func (m * nvmlDeviceHealthMonitor ) markAllDevicesUnhealthy ( ) {
278+ func (m * nvmlDeviceHealthMonitor ) sendHealthEventsForAllDevices ( eventType DeviceHealthEventType ) {
211279 for _ , giMap := range m .deviceByPlacement {
212- m .markAllMigDevicesUnhealthy (giMap )
280+ m .sendHealthEventsForDevices (giMap , eventType )
213281 }
214282}
215283
216- // markAllMigDevicesUnhealthy is a helper function to mark every mig device under a parent as unhealthy.
217- func (m * nvmlDeviceHealthMonitor ) markAllMigDevicesUnhealthy (giMap map [uint32 ]map [uint32 ]* AllocatableDevice ) {
284+ // sendHealthEventsForDevices sends a DeviceHealthEvent for every device under
285+ // the given parent-level map. Uses non-blocking sends to protect the monitor
286+ // goroutine from deadlocks when the channel is full.
287+ func (m * nvmlDeviceHealthMonitor ) sendHealthEventsForDevices (giMap map [uint32 ]map [uint32 ]* AllocatableDevice , eventType DeviceHealthEventType ) {
218288 for _ , ciMap := range giMap {
219289 for _ , dev := range ciMap {
220- // Non-blocking send to avoid deadlocks if channel is full.
290+ event := & DeviceHealthEvent {
291+ Device : dev ,
292+ EventType : eventType ,
293+ }
221294 select {
222- case m .unhealthy <- dev :
223- klog .V (6 ).Infof ("Marked device %s as unhealthy" , dev .UUID ())
224- // TODO: The non-blocking send protects the health-monitor goroutine from deadlocks,
225- // but dropping an unhealthy notification means the device's health transition may
226- // never reach the consumer. Consider follow-up improvements:
227- // - increase the channel buffer beyond len(allocatable) to reduce backpressure;
228- // - introduce a special "all devices unhealthy" message when bulk updates occur;
229- // - or revisit whether blocking briefly here is acceptable.
295+ case m .unhealthy <- event :
296+ klog .V (6 ).Infof ("Sent %s health event for device %s" , eventType , dev .UUID ())
230297 default :
231- klog .Errorf ("Unhealthy channel full. Dropping unhealthy notification for device %s" , dev .UUID ())
298+ klog .Errorf ("Health event channel full; dropping %s event for device %s" , eventType , dev .UUID ())
232299 }
233300 }
234301 }
@@ -326,6 +393,7 @@ func getAdditionalXids(input string) []uint64 {
326393 return additionalXids
327394}
328395
396+ // TODO: this can be simplified now with "EffectNone"
329397func xidsToSkip (additionalXids string ) map [uint64 ]bool {
330398 // Add the list of hardcoded disabled (ignored) XIDs:
331399 // http://docs.nvidia.com/deploy/xid-errors/index.html#topic_4
0 commit comments