[Bugfix][Platform] Stop re-initializing NVML on every device-capability check (fixes #50381) (#50393)

Signed-off-by: seewoo <seewoo@ucsc.edu>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Sebastian Woo
2026-08-07 08:30:00 +00:00
committed by GitHub
co-authored by Claude
parent b8db7f4abd
commit 4f76c8ad9d
2 changed files with 82 additions and 1 deletions
+77
View File
@@ -115,5 +115,82 @@ def test_get_device_capability_uses_visible_device_ordinal(monkeypatch):
assert seen_indices == [1]
def _stub_nvml(monkeypatch) -> dict[str, int]:
"""Stub NVML to report SM 9.0 and count init/shutdown pairs.
Pins `NvmlCudaPlatform` rather than the `CudaPlatform` alias: that alias is
`NonNvmlCudaPlatform` where NVML is unavailable, and that class reads
`torch.cuda` instead, which needs a real device.
"""
from vllm.platforms.cuda import NvmlCudaPlatform, pynvml
calls = {"init": 0, "shutdown": 0}
monkeypatch.setattr(
pynvml, "nvmlInit", lambda: calls.__setitem__("init", calls["init"] + 1)
)
monkeypatch.setattr(
pynvml,
"nvmlShutdown",
lambda: calls.__setitem__("shutdown", calls["shutdown"] + 1),
)
# Pin the visible-device mapping so the test does not depend on whatever
# CUDA_VISIBLE_DEVICES happens to be set to in the environment.
monkeypatch.setenv(NvmlCudaPlatform.device_control_env_var, "0")
monkeypatch.setattr(
NvmlCudaPlatform,
"device_control_id_to_physical_device_id",
classmethod(lambda _cls, device_id: int(device_id)),
)
monkeypatch.setattr(
pynvml, "nvmlDeviceGetHandleByIndex", lambda index: f"handle-{index}"
)
monkeypatch.setattr(
pynvml, "nvmlDeviceGetCudaComputeCapability", lambda _handle: (9, 0)
)
NvmlCudaPlatform.get_device_capability.cache_clear()
return calls
def test_has_device_capability_does_not_reinit_nvml(monkeypatch):
"""Repeated capability checks must not re-enter an NVML context.
`has_device_capability` only reads the cached `get_device_capability`, which
carries its own NVML context. Wrapping it in `with_nvml_context` as well
cost an nvmlInit()/nvmlShutdown() pair per call, and
`triton_reshape_and_cache_flash` calls it per attention layer per step for
fp8 and bfloat16 KV caches (issue #50381).
"""
from vllm.platforms.cuda import NvmlCudaPlatform
calls = _stub_nvml(monkeypatch)
try:
assert NvmlCudaPlatform.has_device_capability(80)
for _ in range(20):
NvmlCudaPlatform.has_device_capability(80)
NvmlCudaPlatform.has_device_capability(89)
NvmlCudaPlatform.has_device_capability((9, 0))
assert calls["init"] == 1
assert calls["shutdown"] == 1
finally:
NvmlCudaPlatform.get_device_capability.cache_clear()
def test_has_device_capability_comparisons(monkeypatch):
"""Dropping the redundant NVML context must not change the answers."""
from vllm.platforms.cuda import NvmlCudaPlatform
_stub_nvml(monkeypatch)
try:
assert NvmlCudaPlatform.has_device_capability(80)
assert NvmlCudaPlatform.has_device_capability(90)
assert NvmlCudaPlatform.has_device_capability((9, 0))
assert not NvmlCudaPlatform.has_device_capability(100)
assert not NvmlCudaPlatform.has_device_capability((10, 0))
finally:
NvmlCudaPlatform.get_device_capability.cache_clear()
if __name__ == "__main__":
pytest.main([__file__, "-v"])
+5 -1
View File
@@ -741,12 +741,16 @@ class NvmlCudaPlatform(CudaPlatformBase):
return None
@classmethod
@with_nvml_context
def has_device_capability(
cls,
capability: tuple[int, int] | int,
device_id: int = 0,
) -> bool:
# No @with_nvml_context here: the base implementation only reads
# get_device_capability(), which is cached and brings its own NVML
# context. Wrapping this method as well cost an nvmlInit()/
# nvmlShutdown() pair on every call, including calls made per attention
# layer per step from the Triton reshape-and-cache path.
try:
return super().has_device_capability(capability, device_id)
except RuntimeError: