Skip to content

Commit a7548e5

Browse files
committed
Keep vGPU placement available when an allocated type is unknown
Sort GPUs with unaccountable load last instead of rejecting placement, and stop reporting passthrough capacity when vGPU discovery fails.
1 parent 562ea39 commit a7548e5

4 files changed

Lines changed: 45 additions & 4 deletions

File tree

lib/devices/GPU.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,10 @@ Before downgrading Hypeman or the host to a version that does not support the ac
239239

240240
1. Stop or delete all vGPU instances while the current Hypeman version can release their assignments.
241241
2. Confirm `/resources` reports `used_slots: 0`.
242-
3. Confirm no mdev assignments remain:
242+
3. Confirm no assignments remain in either framework:
243243
```bash
244244
test -z "$(find /sys/bus/mdev/devices -mindepth 1 -maxdepth 1 2>/dev/null)"
245+
find /sys/bus/pci/devices -path '*/nvidia/current_vgpu_type' -exec grep -H -v '^0$' {} +
245246
```
246247
4. Downgrade only after both checks are clean.
247248

lib/devices/vendor_vfio_linux.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,12 +279,19 @@ func (s vendorVFIOSysfs) reconcile(ctx context.Context, protectedDevicePaths map
279279

280280
func (s vendorVFIOSysfs) selectLeastLoadedVF(vfs []VirtualFunction, profileType string) (string, error) {
281281
usageByGPU := make(map[string]int)
282+
unknownUsageByGPU := make(map[string]bool)
282283
freeByGPU := make(map[string][]VirtualFunction)
283284
for _, vf := range vfs {
284285
if vf.Allocated {
286+
// framebufferByType only covers currently creatable profiles, so
287+
// after a restart an allocated type can be missing when its
288+
// capacity is exhausted. Prefer GPUs whose load is fully known
289+
// instead of rejecting placement outright; the kernel driver
290+
// still enforces real capacity through creatable_vgpu_types.
285291
framebuffer, ok := s.framebufferByType[vf.ProfileType]
286292
if !ok {
287-
return "", fmt.Errorf("framebuffer size for allocated vGPU type %s is unknown", vf.ProfileType)
293+
unknownUsageByGPU[vf.ParentGPU] = true
294+
continue
288295
}
289296
usageByGPU[vf.ParentGPU] += framebuffer
290297
continue
@@ -306,6 +313,9 @@ func (s vendorVFIOSysfs) selectLeastLoadedVF(vfs []VirtualFunction, profileType
306313
gpus = append(gpus, gpu)
307314
}
308315
sort.Slice(gpus, func(i, j int) bool {
316+
if unknownUsageByGPU[gpus[i]] != unknownUsageByGPU[gpus[j]] {
317+
return !unknownUsageByGPU[gpus[i]]
318+
}
309319
if usageByGPU[gpus[i]] == usageByGPU[gpus[j]] {
310320
return gpus[i] < gpus[j]
311321
}

lib/devices/vendor_vfio_linux_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,33 @@ func TestVendorVFIOSelectsLeastLoadedGPUWithConsumedType(t *testing.T) {
252252
assert.Equal(t, "0000:e3:00.4", device.VFAddress)
253253
}
254254

255+
func TestVendorVFIOPlacementPrefersKnownLoadWhenAllocatedTypeIsUnknown(t *testing.T) {
256+
t.Parallel()
257+
258+
// Simulates a restart: type 1159 is allocated but no longer creatable
259+
// anywhere, so its framebuffer size is unknown.
260+
sysfs := newTestVendorVFIOSysfs(t)
261+
sysfs.addVF(t, "0000:82:00.0", "0000:82:00.4", "42", "1159", "")
262+
sysfs.addVF(t, "0000:82:00.0", "0000:82:00.5", "43", "0", "1147 : NVIDIA L40S-1Q\n")
263+
sysfs.addVF(t, "0000:e3:00.0", "0000:e3:00.4", "44", "0", "1147 : NVIDIA L40S-1Q\n")
264+
265+
device, err := sysfs.create(context.Background(), "NVIDIA L40S-1Q", "instance-1")
266+
require.NoError(t, err)
267+
assert.Equal(t, "0000:e3:00.4", device.VFAddress, "the GPU with unknown load should be picked last")
268+
}
269+
270+
func TestVendorVFIOPlacesOnGPUWithUnknownLoadWhenItHasTheOnlyCapacity(t *testing.T) {
271+
t.Parallel()
272+
273+
sysfs := newTestVendorVFIOSysfs(t)
274+
sysfs.addVF(t, "0000:82:00.0", "0000:82:00.4", "42", "1159", "")
275+
sysfs.addVF(t, "0000:82:00.0", "0000:82:00.5", "43", "0", "1147 : NVIDIA L40S-1Q\n")
276+
277+
device, err := sysfs.create(context.Background(), "NVIDIA L40S-1Q", "instance-1")
278+
require.NoError(t, err)
279+
assert.Equal(t, "0000:82:00.5", device.VFAddress)
280+
}
281+
255282
func TestVendorVFIOReconcile(t *testing.T) {
256283
t.Parallel()
257284

lib/resources/gpu.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ type GPUResourceStatus struct {
2222
func GetGPUStatus(ctx context.Context) *GPUResourceStatus {
2323
framework, vfs, err := devices.DiscoverVGPU()
2424
if err != nil {
25-
// A failed vGPU probe must not hide passthrough GPUs from status reporting.
25+
// Only report passthrough once vGPU discovery confirms no vGPU
26+
// framework. On a vGPU host a transient probe failure would otherwise
27+
// expose the PFs/VFs as available passthrough slots while active vGPU
28+
// assignments exist.
2629
logger.FromContext(ctx).WarnContext(ctx, "failed to discover vGPU state", "error", err)
27-
return getPassthroughStatus()
30+
return nil
2831
}
2932
if framework != devices.VGPUFrameworkNone {
3033
return getVGPUStatus(ctx, framework, vfs)

0 commit comments

Comments
 (0)