Skip to content

Commit befab47

Browse files
authored
[LEP-3454] feat(nvidia/temperature): guard margin threshold API and clarify semantics (#1186)
- guard margin temperature API on pre-570 drivers to avoid symbol lookup crashes - clarify margin semantics (nearest slowdown threshold, core vs HBM undefined) and update fields/metrics/table output - add unit coverage for driver gating and nil GPUd instance, update tests for renamed fields --------- Signed-off-by: Gyuho Lee <gyuhol@nvidia.com>
1 parent 71b3692 commit befab47

15 files changed

Lines changed: 1489 additions & 339 deletions

File tree

cmd/gpud/command/command.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
cmdstatus "github.com/leptonai/gpud/cmd/gpud/status"
2222
cmdup "github.com/leptonai/gpud/cmd/gpud/up"
2323
cmdupdate "github.com/leptonai/gpud/cmd/gpud/update"
24+
componentsnvidiatemperature "github.com/leptonai/gpud/components/accelerator/nvidia/temperature"
2425
componentsxid "github.com/leptonai/gpud/components/accelerator/nvidia/xid"
2526
pkgconfig "github.com/leptonai/gpud/pkg/config"
2627
pkgcustomplugins "github.com/leptonai/gpud/pkg/custom-plugins"
@@ -237,6 +238,11 @@ sudo rm /etc/systemd/system/gpud.service
237238
Usage: fmt.Sprintf("set the allowed reboot attempts for XID errors before escalation (defaults to %d)", componentsxid.DefaultRebootThreshold),
238239
Value: componentsxid.DefaultRebootThreshold,
239240
},
241+
&cli.IntFlag{
242+
Name: "threshold-celsius-slowdown-margin",
243+
Usage: fmt.Sprintf("set the minimum thermal margin (°C) before marking GPUs as degraded (defaults to %d)", componentsnvidiatemperature.ThresholdCelsiusSlowdownMargin),
244+
Value: int(componentsnvidiatemperature.ThresholdCelsiusSlowdownMargin),
245+
},
240246

241247
cli.StringFlag{
242248
Name: "infiniband-exclude-devices",
@@ -559,6 +565,11 @@ sudo rm /etc/systemd/system/gpud.service
559565
Usage: fmt.Sprintf("set the allowed reboot attempts for XID errors before escalation (defaults to %d)", componentsxid.DefaultRebootThreshold),
560566
Value: componentsxid.DefaultRebootThreshold,
561567
},
568+
&cli.IntFlag{
569+
Name: "threshold-celsius-slowdown-margin",
570+
Usage: fmt.Sprintf("set the minimum thermal margin (°C) before marking GPUs as degraded (0 to disable, defaults to %d, e.g., 10 means GPU is degraded at 77°C when slowdown threshold is 87°C)", componentsnvidiatemperature.ThresholdCelsiusSlowdownMargin),
571+
Value: int(componentsnvidiatemperature.ThresholdCelsiusSlowdownMargin),
572+
},
562573
cli.StringFlag{
563574
Name: "infiniband-class-root-dir",
564575
Usage: "sets the infiniband class root directory (leave empty for default)",

cmd/gpud/run/command.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
componentsinfiniband "github.com/leptonai/gpud/components/accelerator/nvidia/infiniband"
2323
componentsnvidiainfinibanditypes "github.com/leptonai/gpud/components/accelerator/nvidia/infiniband/types"
2424
componentsnvlink "github.com/leptonai/gpud/components/accelerator/nvidia/nvlink"
25+
componentstemperature "github.com/leptonai/gpud/components/accelerator/nvidia/temperature"
2526
componentsxid "github.com/leptonai/gpud/components/accelerator/nvidia/xid"
2627
componentsnfs "github.com/leptonai/gpud/components/nfs"
2728
"github.com/leptonai/gpud/pkg/config"
@@ -138,6 +139,7 @@ func Command(cliContext *cli.Context) error {
138139
nvlinkExpectedLinkStates := cliContext.String("nvlink-expected-link-states")
139140
nfsCheckerConfigs := cliContext.String("nfs-checker-configs")
140141
xidRebootThreshold := cliContext.Int("xid-reboot-threshold")
142+
temperatureMarginThresholdCelsius := cliContext.Int("threshold-celsius-slowdown-margin")
141143

142144
if len(infinibandExpectedPortStates) > 0 {
143145
var expectedPortStates componentsnvidiainfinibanditypes.ExpectedPortStates
@@ -180,6 +182,13 @@ func Command(cliContext *cli.Context) error {
180182
}
181183
}
182184

185+
if cliContext.IsSet("threshold-celsius-slowdown-margin") {
186+
componentstemperature.SetDefaultMarginThreshold(componentstemperature.Thresholds{
187+
CelsiusSlowdownMargin: int32(temperatureMarginThresholdCelsius),
188+
})
189+
log.Logger.Infow("set temperature margin threshold", "degraded_celsius", temperatureMarginThresholdCelsius)
190+
}
191+
183192
gpuUUIDsWithRowRemappingPendingRaw := cliContext.String("gpu-uuids-with-row-remapping-pending")
184193
gpuUUIDsWithRowRemappingPending := common.ParseGPUUUIDs(gpuUUIDsWithRowRemappingPendingRaw)
185194

cmd/gpud/scan/command.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
componentsinfiniband "github.com/leptonai/gpud/components/accelerator/nvidia/infiniband"
1515
componentsnvidiainfinibanditypes "github.com/leptonai/gpud/components/accelerator/nvidia/infiniband/types"
1616
componentsnvlink "github.com/leptonai/gpud/components/accelerator/nvidia/nvlink"
17+
componentstemperature "github.com/leptonai/gpud/components/accelerator/nvidia/temperature"
1718
componentsxid "github.com/leptonai/gpud/components/accelerator/nvidia/xid"
1819
componentsnfs "github.com/leptonai/gpud/components/nfs"
1920
"github.com/leptonai/gpud/pkg/log"
@@ -41,6 +42,8 @@ func CreateCommand() func(*cli.Context) error {
4142
cliContext.String("gpu-product-name"),
4243
cliContext.Int("xid-reboot-threshold"),
4344
cliContext.IsSet("xid-reboot-threshold"),
45+
cliContext.Int("threshold-celsius-slowdown-margin"),
46+
cliContext.IsSet("threshold-celsius-slowdown-margin"),
4447
)
4548
}
4649
}
@@ -63,6 +66,8 @@ func cmdScan(
6366
gpuProductNameOverride string,
6467
xidRebootThreshold int,
6568
xidRebootThresholdIsSet bool,
69+
temperatureMarginThresholdCelsius int,
70+
temperatureMarginThresholdIsSet bool,
6671
) error {
6772
zapLvl, err := log.ParseLogLevel(logLevel)
6873
if err != nil {
@@ -121,6 +126,13 @@ func cmdScan(
121126
}
122127
}
123128

129+
if temperatureMarginThresholdIsSet {
130+
componentstemperature.SetDefaultMarginThreshold(componentstemperature.Thresholds{
131+
CelsiusSlowdownMargin: int32(temperatureMarginThresholdCelsius),
132+
})
133+
log.Logger.Infow("set temperature margin threshold", "degraded_celsius", temperatureMarginThresholdCelsius)
134+
}
135+
124136
gpuUUIDsWithRowRemappingPending := common.ParseGPUUUIDs(gpuUUIDsWithRowRemappingPendingRaw)
125137
gpuUUIDsWithRowRemappingFailed := common.ParseGPUUUIDs(gpuUUIDsWithRowRemappingFailedRaw)
126138
gpuUUIDsWithHWSlowdown := common.ParseGPUUUIDs(gpuUUIDsWithHWSlowdownRaw)

components/accelerator/nvidia/temperature/component.go

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ type component struct {
4141
}
4242

4343
func New(gpudInstance *components.GPUdInstance) (components.Component, error) {
44+
if gpudInstance == nil {
45+
return nil, errors.New("gpud instance is nil")
46+
}
4447
cctx, ccancel := context.WithCancel(gpudInstance.RootCtx)
4548
c := &component{
4649
ctx: cctx,
@@ -51,6 +54,7 @@ func New(gpudInstance *components.GPUdInstance) (components.Component, error) {
5154
nvmlInstance: gpudInstance.NVMLInstance,
5255
getTemperatureFunc: GetTemperature,
5356
}
57+
5458
return c, nil
5559
}
5660

@@ -150,7 +154,12 @@ func (c *component) Check() components.CheckResult {
150154
return cr
151155
}
152156

153-
tempThresholdExceeded := make([]string, 0)
157+
marginThreshold := GetDefaultThresholds()
158+
159+
gpuTempThresholdExceeded := make([]string, 0)
160+
hbmTempThresholdExceeded := make([]string, 0)
161+
marginThresholdExceeded := make([]string, 0)
162+
154163
devs := c.nvmlInstance.Devices()
155164
for uuid, dev := range devs {
156165
temp, err := c.getTemperatureFunc(uuid, dev)
@@ -182,22 +191,60 @@ func (c *component) Check() components.CheckResult {
182191
log.Logger.Warnw(cr.reason, "uuid", uuid, "error", cr.err)
183192
return cr
184193
}
185-
cr.Temperatures = append(cr.Temperatures, temp)
186194

187-
// same logic as DCGM "VerifyHBMTemperature" that alerts "DCGM_FR_TEMP_VIOLATION",
188-
// use "DCGM_FI_DEV_MEM_MAX_OP_TEMP" to get the max HBM temperature threshold "NVML_TEMPERATURE_THRESHOLD_MEM_MAX"
189-
if temp.ThresholdCelsiusMemMax > 0 && temp.CurrentCelsiusGPUCore > temp.ThresholdCelsiusMemMax {
190-
tempThresholdExceeded = append(tempThresholdExceeded,
191-
fmt.Sprintf("%s current temperature is %d °C exceeding the HBM temperature threshold %d °C",
195+
// nvmlDeviceGetMarginTemperature reports the thermal margin to the nearest slowdown
196+
// threshold as defined by NVML. NVML does not specify GPU core vs HBM; it is
197+
// whichever slowdown threshold is nearest (driver-defined).
198+
// ref. https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html#group__nvmlDeviceQueries_1g42db93dc04fc99d253eadc2037a5232d
199+
if temp.ThresholdCelsiusSlowdown > 0 && temp.MarginTemperatureSupported {
200+
// margin left less than the threshold, indicating the GPU is approaching the slowdown threshold
201+
// e.g.,
202+
// 5°C margin left means the GPU is approaching the slowdown threshold at 82°C
203+
// when the slowdown threshold is 87°C
204+
if temp.ThresholdCelsiusSlowdownMargin <= marginThreshold.CelsiusSlowdownMargin {
205+
// e.g.,
206+
// 5°C margin left, when we set the threshold to 10°C
207+
// so that we can alert in advance before it's too late,
208+
// before the slowdown already happened
209+
marginThresholdExceeded = append(marginThresholdExceeded,
210+
fmt.Sprintf("%s has only %d °C margin left to slowdown (threshold %d °C)",
211+
uuid,
212+
temp.ThresholdCelsiusSlowdownMargin,
213+
marginThreshold.CelsiusSlowdownMargin,
214+
),
215+
)
216+
}
217+
}
218+
219+
if temp.ThresholdCelsiusGPUMax > 0 && temp.CurrentCelsiusGPUCore > temp.ThresholdCelsiusGPUMax {
220+
gpuTempThresholdExceeded = append(gpuTempThresholdExceeded,
221+
fmt.Sprintf("%s current temperature is %d °C exceeding the threshold %d °C",
192222
uuid,
193223
temp.CurrentCelsiusGPUCore,
224+
temp.ThresholdCelsiusGPUMax,
225+
),
226+
)
227+
}
228+
229+
// same logic as DCGM "VerifyHBMTemperature" that alerts "DCGM_FR_TEMP_VIOLATION",
230+
// use "DCGM_FI_DEV_MEM_MAX_OP_TEMP" to get the max HBM temperature threshold "NVML_TEMPERATURE_THRESHOLD_MEM_MAX"
231+
if temp.ThresholdCelsiusMemMax > 0 && temp.HBMTemperatureSupported && temp.CurrentCelsiusHBM > temp.ThresholdCelsiusMemMax {
232+
hbmTempThresholdExceeded = append(hbmTempThresholdExceeded,
233+
fmt.Sprintf("%s HBM temperature is %d °C exceeding the threshold %d °C",
234+
uuid,
235+
temp.CurrentCelsiusHBM,
194236
temp.ThresholdCelsiusMemMax,
195237
),
196238
)
197239
}
198240

241+
cr.Temperatures = append(cr.Temperatures, temp)
242+
199243
metricCurrentCelsius.With(prometheus.Labels{"uuid": uuid}).Set(float64(temp.CurrentCelsiusGPUCore))
244+
metricCurrentHBMCelsius.With(prometheus.Labels{"uuid": uuid}).Set(float64(temp.CurrentCelsiusHBM))
200245
metricThresholdSlowdownCelsius.With(prometheus.Labels{"uuid": uuid}).Set(float64(temp.ThresholdCelsiusSlowdown))
246+
metricThresholdMemMaxCelsius.With(prometheus.Labels{"uuid": uuid}).Set(float64(temp.ThresholdCelsiusMemMax))
247+
metricMarginCelsius.With(prometheus.Labels{"uuid": uuid}).Set(float64(temp.ThresholdCelsiusSlowdownMargin))
201248

202249
slowdownPct, err := temp.GetUsedPercentSlowdown()
203250
if err != nil {
@@ -208,14 +255,26 @@ func (c *component) Check() components.CheckResult {
208255
return cr
209256
}
210257
metricSlowdownUsedPercent.With(prometheus.Labels{"uuid": uuid}).Set(slowdownPct)
258+
259+
memMaxPct := 0.0
260+
if temp.ThresholdCelsiusMemMax > 0 && temp.HBMTemperatureSupported {
261+
memMaxPct = float64(temp.CurrentCelsiusHBM) / float64(temp.ThresholdCelsiusMemMax) * 100
262+
}
263+
metricMemMaxUsedPercent.With(prometheus.Labels{"uuid": uuid}).Set(memMaxPct)
211264
}
212265

213-
if len(tempThresholdExceeded) == 0 {
266+
if len(marginThresholdExceeded) > 0 {
267+
cr.health = apiv1.HealthStateTypeDegraded
268+
cr.reason = fmt.Sprintf("margin threshold exceeded: %s", strings.Join(marginThresholdExceeded, ", "))
269+
} else if len(gpuTempThresholdExceeded) > 0 {
270+
cr.health = apiv1.HealthStateTypeDegraded
271+
cr.reason = fmt.Sprintf("GPU temperature anomalies detected: %s", strings.Join(gpuTempThresholdExceeded, ", "))
272+
} else if len(hbmTempThresholdExceeded) > 0 {
273+
cr.health = apiv1.HealthStateTypeDegraded
274+
cr.reason = fmt.Sprintf("HBM temperature anomalies detected: %s", strings.Join(hbmTempThresholdExceeded, ", "))
275+
} else {
214276
cr.health = apiv1.HealthStateTypeHealthy
215277
cr.reason = fmt.Sprintf("all %d GPU(s) were checked, no temperature issue found", len(devs))
216-
} else {
217-
cr.health = apiv1.HealthStateTypeUnhealthy
218-
cr.reason = fmt.Sprintf("exceeded HBM temperature thresholds: %s", strings.Join(tempThresholdExceeded, ", "))
219278
}
220279

221280
return cr
@@ -253,14 +312,24 @@ func (cr *checkResult) String() string {
253312

254313
buf := bytes.NewBuffer(nil)
255314
table := tablewriter.NewWriter(buf)
256-
table.SetHeader([]string{"GPU UUID", "GPU Bus ID", "Current temp", "HBM temp threshold", "Used %"})
315+
table.SetHeader([]string{"GPU UUID", "GPU Bus ID", "GPU temp", "HBM temp", "HBM temp threshold", "HBM used %", "Margin to slowdown"})
257316
for _, temp := range cr.Temperatures {
317+
hbmTemp := "n/a"
318+
if temp.HBMTemperatureSupported {
319+
hbmTemp = fmt.Sprintf("%d °C", temp.CurrentCelsiusHBM)
320+
}
321+
marginTemp := "n/a"
322+
if temp.MarginTemperatureSupported {
323+
marginTemp = fmt.Sprintf("%d °C", temp.ThresholdCelsiusSlowdownMargin)
324+
}
258325
table.Append([]string{
259326
temp.UUID,
260327
temp.BusID,
261328
fmt.Sprintf("%d °C", temp.CurrentCelsiusGPUCore),
329+
hbmTemp,
262330
fmt.Sprintf("%d °C", temp.ThresholdCelsiusMemMax),
263331
fmt.Sprintf("%s %%", temp.UsedPercentMemMax),
332+
marginTemp,
264333
})
265334
}
266335
table.Render()

0 commit comments

Comments
 (0)