Repository navigation
fix(device-plugin): resolve the GPU container by its real pod index - #23
Merged
Merged
Conversation
DecodePodDevices dropped container entries carrying no devices, which compacted the slice and broke its positional contract with pod.Spec.Containers. GetNextDeviceRequest then resolved the wrong container whenever the GPU container was not first in the pod, and named the libvgpu shared-cache directory after it (/usr/local/vgpu/containers/<podUID>_<ctrName>). vGPUmonitor derives its ctrname label from that directory name, so every per-container GPU metric was published under the wrong container and never joined with the real workload: dashboards read 0% utilization and 0 GiB memory while the card was fully loaded. The vGPUmonitor MIG collector makes the same positional assumption and was equally affected. Keep empty entries so index i stays aligned with pod.Spec.Containers[i]. The encoder's trailing separator yields one extra empty entry past the container count, so GetNextDeviceRequest bounds-checks the index rather than indexing blindly, and logs which container it picked. This mirrors upstream Project-HAMi#1650 minus its init-container offset: this fork builds the annotation from pod.Spec.Containers alone (k8sutil.Resourcereqs), so no offset applies here. Verified on a single-node cluster with an RTX 5090 24 GB running a llama.cpp pod whose containers are [linkerd-proxy, llamacpp, olares-mesh-in-agent]. Before, the cache directory was <podUID>_linkerd-proxy and vGPU_device_memory_usage_in_bytes was labelled ctrname="linkerd-proxy". After, the directory is <podUID>_llamacpp, the metric carries ctrname="llamacpp" at 22.29 GiB, and Device_utilization_desc_of_container reports 99 under inference load against 98-100% from nvidia-smi. The pkg/util test binary did not compile before this change: positional ContainerDevice literals predate the ShareMode field, so the package's tests had not run in a while and its expectations had gone stale. They are converted to keyed fields and refreshed. Co-authored-by: Cursor <cursoragent@cursor.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.
Problem
Per-container GPU metrics are attributed to the wrong container whenever the GPU container is not the first container in the pod. Dashboards show 0% utilization and 0 GiB memory for a task while the card itself reads 100% / 97% — the device-level numbers come from DCGM and are fine, but the task-level ones never join.
Root cause is a positional contract that gets broken in decode:
pod.Spec.Containersorder. A pod of[linkerd-proxy, llamacpp, olares-mesh-in-agent]with the GPU onllamacppproduces;GPU-…,NVIDIA,0,0,2:;;— the device sits at index 1.DecodePodDevicesskipped segments that decode to no devices, compacting the slice. The GPU entry moved from index 1 to index 0.GetNextDeviceRequestreturnedp.Spec.Containers[ctridx]using that compacted index, so it resolvedlinkerd-proxy.Allocatenames the libvgpu shared-cache directory<podUID>_<ctrName>from that container, producing/usr/local/vgpu/containers/<podUID>_linkerd-proxy.vGPUmonitorderives itsctrnamelabel from the directory name, sovGPU_device_memory_usage_in_bytesandDevice_utilization_desc_of_containerare published underctrname="linkerd-proxy". Consumers query the real container name and match nothing.The values themselves were never wrong — only the label. The
vGPUmonitorMIG collector (collectPodAndContainerMigInfo) comparesctrIdx != ctrDevIdxand so relies on the same alignment; it was silently broken too.Any injected sidecar that lands ahead of the workload triggers this, so in a service-mesh cluster it affects every GPU pod. Multi-GPU-container pods were worse: every
Allocateresolved index 0, so all of them collapsed onto the same wrong container.Fix
Keep empty entries in
DecodePodDevicesso indexistays aligned withpod.Spec.Containers[i]. The encoder terminates every segment with the separator, so decoding yields one extra empty entry past the container count;GetNextDeviceRequestbounds-checks instead of indexing blindly, and logs the container it picked.This mirrors upstream Project-HAMi/HAMi#1650, minus its init-container offset. Upstream encodes init containers into the annotation and therefore subtracts
len(InitContainers); this fork builds the annotation frompod.Spec.Containersalone (k8sutil.Resourcereqs), so applying that offset here would introduce a shift the encoder never produces.Callers audited: the scheduler (
onAddPod→ node usage accounting) and thevGPUmonitorMIG collector only range over the decoded slices, so extra empty entries are inert.EraseNextDeviceTypeFromAnnotationre-encodes what it decodes and keeps index alignment across successiveAllocatecalls.Test plan
Unit tests —
TestGetNextDeviceRequestContainerIndexcovers GPU container first / behind a sidecar / last / none / more device entries than containers. Reverting only the decode change makes it fail with exactly the production symptom:TestDecodePodDevicesPreservesContainerIndexguards the index mapping directly.Real hardware — single-node k3s, RTX 5090 Laptop 24 GB, llama.cpp serving Qwen3.8-27B, containers
[linkerd-proxy, llamacpp, olares-mesh-in-agent]:<podUID>_linkerd-proxy<podUID>_llamacpp[[{GPU}]][[] [{GPU}] [] []]linkerd-proxyllamacppat index 1vGPU_device_memory_usage_in_bytesctrname="linkerd-proxy"ctrname="llamacpp"Device_utilization_desc_of_containerunder load99, against 98-100% fromnvidia-smihami_container_core_util/hami_container_memory_used100/22824 MBNote on the test files
pkg/utildid not compile before this change — positionalContainerDeviceliterals predate theShareModefield, so that package's tests had not been running and its expectations had drifted. They are converted to keyed fields and refreshed, which also brings backTest_DecodePodDevicesandTest_EncodeNodeDevices. The package is green now; no other package changes status, and the set of failing tests elsewhere is byte-identical before and after.Made with Cursor