feat: Fabric Manager partition-aware GetPreferredAllocation for Shared NVSwitch multi-tenancy#84
Closed
inerplat wants to merge 1 commit into
Closed
Conversation
…d NVSwitch multi-tenancy Signed-off-by: inerplat <inerplat@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #83. Related to #24, which is about the Service-VM flavor and exposing NVSwitch devices; this PR is about GPU allocation in the host-managed FM flavor.
Background
On NVSwitch-based HGX systems running Fabric Manager in the Shared NVSwitch model, the host keeps the NVSwitches and tenant VMs get GPUs by VFIO passthrough. NVLink between a VM's GPUs only works if those GPUs are exactly the members of an activated FM fabric partition. FM programs the switch routing so traffic stays within a partition. The set of supported partitions is fixed per baseboard and differs across platforms, so it has to be read from FM at runtime rather than assumed. On the HGX H100 8-GPU board we tested, FM reports 15 partitions: the full 8, two 4-GPU halves, four 2-GPU pairs, and eight singles. There the 2-GPU pairs happen to be diagonal by physical id, {1,3} {2,4} {5,7} {6,8}, which is exactly the kind of layout a count-based or ordinal allocator gets wrong.
Today the plugin advertises
nvidia.com/pgpuas a plain counted resource andGetPreferredAllocationis a stub, so it never tells kubelet which GPU sets form a partition. Kubelet's devicemanager then picks devices without regard to the partition topology, and only a few of the possible combinations are valid partitions (on H100, 4 of the 28 possible 2-GPU sets). Two things go wrong. A multi-GPU pod often gets a set that matches no supported partition, so there is no NVLink between its GPUs. And if the partition covering the assigned GPUs was not activated, the guest driver blocks on NVLink fabric registration until nvidia-persistenced's 120s timeout and the VM fails to boot.No fixed device-ordering rule fixes this in general. The plugin's device ids follow vfio/BDF enumeration, and the BDF-to-module-id mapping is board-specific and not derivable from that order. On our test box
0000:19:00.0is physical GPU 2 and0000:4c:00.0is physical GPU 1, so which device sets happen to be valid partitions is a property of the board. The allocator has to consult FM and a real BDF-to-module mapping.Design
The device plugin already knows each GPU by a device id derived from its vfio group / iommufd number. Fabric Manager identifies GPUs by physical (module) id. Partitions are expressed in physical ids. So the allocator needs to translate a device id to a physical id, ask FM which partitions exist, and translate a chosen partition back to device ids. Three pieces:
FM client. A thin wrapper over libnvfm exposing one call,
GetSupportedPartitions() []Partition, wherePartition{ID, IsActive, GPUPhysicalIDs}. It connects lazily and drops the handle on any API error so the next call reconnects, so FM bouncing does not wedge the plugin. Built behind thenvfmtag, with a stub compiled otherwise (see Build).BDF-to-module-id mapping. FM speaks module ids, the plugin speaks device ids that map to a BDF, and the two orderings differ, so a mapping is required. Loaded from a JSON file,
{ "<pci-bdf>": <module-id> }, path/run/nvidia-fabricmanager/gpu-pci-module-mapping.jsonby default. Both integer and string module-id values are accepted and BDFs are normalized to the sysfs0000:bb:dd.fform. This is the same file convention the kubevirt plugin uses.Selection. For each container request of size N,
SelectPreferred(available, mustInclude, N):availabledevice ids to physical ids through the mapping, dropping any that don't map (logged);numGpus == N, whose physical ids are all in the available set, and that contain everymustIncludedevice;If no partition fits, it returns nothing. Active-first is the important bit: partition activation is the operator's steering wheel, so pre-activating the partitions of the layout you intend to run makes the plugin route tenants onto exactly those GPUs, with no extra scheduler component.
Failure handling
GetPreferredAllocationis advisory in the kubelet contract. A preferred set is a hint, and returning none just lets the devicemanager fall back to its normal allocation. Every failure mode here takes that path: FM unreachable, a device missing from the mapping, or no partition of the requested size available all resolve to "no preference". The feature can log a problem but it can never fail a pod's allocation, so enabling it cannot regress the plain counted-resource behavior.GetDevicePluginOptionsonly advertisesGetPreferredAllocationAvailablewhen the manager initialized, and only for the GPU resource, not NVSwitch.What this PR does
pkg/fabric_manager: FM client (nvfm_client_linux.go, tagnvfm) + stub (nvfm_client_stub.go), mapping loader (mapping.go), and the selection logic (partition_manager.go);GetPreferredAllocationand the capability advertisement inpkg/device_plugin, and wires the mapping/manager incmd/main.go;ENABLE_FABRIC_MANAGER=true(off by default; with it unset the plugin is unchanged). A binary built without-tags=nvfmrefuses to enable FM even when the env var is set, rather than advertising a capability that would always fail.FM_ADDRESS,FM_ADDRESS_TYPE, andGPU_PCI_MODULE_MAPPING_PATHcover the deployment knobs;Tests:
partition_manager_test.godrivesSelectPreferredagainst the real H100 partition table with a fake FM client: size-2 requests pick a diagonal pair and never an adjacent {1,2}-style pair; must-include steers the choice; a partly-consumed node still finds a fully available partition; active partitions win and ties break by id; unmappable devices and FM errors both degrade to no preference.mapping_test.gocovers integer/string values, domain normalization, and malformed input. Device-plugin tests cover the capability gating and the fall-back-on-error path.The approach follows the same direction as the kubevirt-gpu-device-plugin work, adapted to this plugin's vfio/iommufd device model:
This PR is scoped to the allocation logic. Container image build, deployment manifests, and partition-activation lifecycle are out of scope; see "Out of scope" below and the README To Do.
Build
The libnvfm client is behind the
nvfmbuild tag and needs thenvidia-fabric-manager-develpackage:The default build compiles a stub instead, so
go build ./...,go vet ./...andgo test ./...work with no FM SDK present. The CGO connection parameters follow the FM SDK sample (inet or unix address, non-zero timeout, versioned request structs).How it was tested
Built with
-tags=nvfmand run on an HGX H100 8-GPU box: driver 580.126.20, kernel 6.17, Kata 3.32 with thekata-qemu-nvidia-gpuruntime classes, GPU Operator v26 sandbox workloads, k8s 1.35, host-managed FM inFABRIC_MODE=1.Four 2-GPU kata pods at once, with the four 2-GPU partitions pre-activated: each landed on a proper diagonal pair and all four guests showed NV18. The plugin log from that run, where the available pool shrinks as pairs get taken and the device ids are non-ordinal:
A mixed 1+1+2+4 layout in one shot also worked: a 4-GPU pod with a full NV18 mesh (devices
[2 0 3 1], the mapping resolving a non-ordinal partition), a 2-GPU pod with an NV18 pair, and two 1-GPU pods, all correct at the same time.For interference, a 4-GPU tenant and a 2-GPU tenant ran sustained
cudaMemcpyPeersweeps through the same physical switches simultaneously. Both held a flat ~370 GB/s for every sample across the whole overlap window, so partitions don't take bandwidth from each other.With
ENABLE_FABRIC_MANAGERunset the plugin behaves as before, and killing FM while pods schedule just loses the preference without failing allocations.Out of scope
Module IDfromnvidia-smi -qin the guest, matched to the QEMUhost=<BDF>argument. Having the driver container emit the file would be a good follow-up.FMClientinterface is the seam where an NMX-backed implementation could slot in.