Conversation
…wn device Every capability query defaulted to device_id=0, index 0 of the visibility list, so on a node that mixes card generations every worker except the first gated its kernels on another card (1CatAI#412; 1CatAI#514 and 1CatAI#576 fixed two call sites, main carries 263 more). device_id now defaults to None: the CUDA platform resolves it to torch.cuda.current_device() once the process holds a CUDA context and to 0 before that, so the engine core, the API server and a worker before set_device behave as before. After initialization the capability is read from torch instead of NVML, whose PCI-bus order differs from the runtime's default ordering; an unusable index still answers None. Caches are keyed by the resolved index. ROCm and XPU signatures are widened, their behaviour unchanged. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Peuqui <peuqui@github.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.
Purpose
Generalizes #412. That issue named three device-0 capability gates; they
were fixed one by one (#514, #403, #576) and the issue closed with the
second item left as is. This change fixes the class instead of the
instances.
Every capability query on
current_platform—get_device_capability,has_device_capability,is_device_capability,is_device_capability_family— defaults todevice_id=0, whichNvmlCudaPlatformmaps to index 0 ofCUDA_VISIBLE_DEVICES. On a node thatmixes card generations, every worker except the one on the first listed card
therefore gates its kernels on somebody else's capability. #514 and #576 fixed
two instances by passing the worker's device explicitly; a count on today's
mainfinds 263 call sites that still query without a device index, two ofthem inside
_flashinfer_topkand_use_sm70_bf16_emulation(fixed in thesibling PR), the rest spread over quantization, MoE, GDN, attention and the
model runners. Patching them one by one is not a plan.
This change moves the decision to the one place all four queries share:
device_idnow defaults toNone, meaning "the device this process runson".
Platform.resolve_device_id()turns it into a concrete index. The baseplatform keeps the historical answer (0). The CUDA platform answers with
torch.cuda.current_device()once the process holds a CUDA context and 0before that, so the engine core, the API server and a worker before
set_devicebehave exactly as today; a worker afterinit_deviceanswersfor its own card.
torch.cuda.is_initialized()only reports and nevercreates a context.
has_device_capability(80, device_id=i)still asks device
i.NvmlCudaPlatformreads the capability fromtorch.cuda.get_device_capability(ordinal)instead of NVML. NVML counts inPCI bus order, the CUDA runtime defaults to
FASTEST_FIRST; on a mixed nodethe two orders differ unless
CUDA_DEVICE_ORDER=PCI_BUS_IDis set, which isexactly the situation
log_warningswarns about. torch is authoritative forthe ordinal, and reading it after initialization cannot trigger the
initialization the NVML path exists to avoid. NVML remains the pre-init
path. (Same approach as [Platform] Read device capability from torch, not NVML, once CUDA is initialized vllm-project/vllm#53834, which did not land.)
_torch_device_capability,_nvml_device_capability, ROCm_device_capability), never byNone, so anunspecified device cannot freeze one process-wide answer.
Signatures of the ROCm and XPU overrides are widened to match (
int | None),their behaviour is unchanged (base resolution, index 0). The torch path keeps
the NVML contract for an unusable index: it answers
None(torch raisesAssertionErrorthere), sohas_device_capability(80, device_id=7)on afive-card node is
False, not an exception.Cost: resolving the current device adds about half a microsecond per query
(Tesla V100, after
set_device, 20,000 calls:is_device_capability(70)0.36 → 0.84 µs,
has_device_capability(80)2.84 → 3.41 µs). The queriesthat sit in forward paths run on the order of a hundred times per decode
step, i.e. well under 0.1 % of a 30 ms step.
Not covered, on purpose:
get_device_name,get_device_uuid,get_device_total_memory,is_integrated_gpuandnum_compute_unitskeeptheir
device_id=0default; only the capability queries select kernels.Module-level constants evaluated at import time in a worker before
set_devicestill see index 0; they need a different fix (lazy evaluation)and are out of scope here.
Test Plan
pre-commit run --files vllm/platforms/{interface,cuda,rocm,xpu}.py tests/platforms/test_capability_default_device.pyand the same with
mypy-3.10 --hook-stage manual, plus mypy overvllm/platforms/*.py(override compatibility).tests/platforms/test_capability_default_device.py: a mockednode (Turing at 0, Volta at 1, Ampere at 2); resolution with and without a
CUDA context, explicit index wins, all three predicate queries follow the
current device, torch answers after init and NVML is left alone, the
caches see the resolved index, an unusable index answers
None, the baseplatform keeps index 0.
tests/config/test_sm70_gates_any_visible_device.py([Bugfix][SM70] Gate SM70 config defaults on any visible device (#412) #514) andtests/cuda/.CUDA_VISIBLE_DEVICES=0,1(RTX first), one process doestorch.cuda.set_device(1)like a worker with local rank 1 and then asksget_device_capability()without an index — onmainand with thischange, once with
CUDA_DEVICE_ORDER=PCI_BUS_IDand once without.Test Result
All applicable hooks passed; mypy-3.10 passed on the five files and on
vllm/platforms/*.py.11 passed.
48 passed, 6 failed — the six fail identically without this change:
three parametrizations of
test_sm70_baseline_defaults_follow_any_visible_deviceneed
facebook/opt-125mfrom the Hub (run offline), threeTestSetCudaContextcases need a visible GPU (run with none).Probe on the mixed node,
CUDA_VISIBLE_DEVICES=0,1(Quadro RTX 8000first, Tesla V100 second), one process,
torch.cuda.set_device(1):set_deviceset_device(1), a V100main(fe67339 + [Bugfix][Perf][SM70] Make Turing (sm75) boot, compute correctly and keep its Inductor fusions #572)(7, 5)(7, 5),has(75)=True,is(70)=False(7, 5)(7, 0),has(75)=False,is(70)=TrueIdentical with
CUDA_DEVICE_ORDER=PCI_BUS_IDand with the defaultordering.
mainkeeps answering for the first listed card no matter whichdevice the process selected; with this change the process answers for its
own card, and before device selection nothing changes.
Per-call cost, Tesla V100 after
set_device, 20,000 calls each:is_device_capability(70)0.36 → 0.84 µs,has_device_capability(80)2.84 → 3.41 µs.
Not a duplicate
Checked on 2026-09-11 against
1CatAI/1Cat-vLLM(gh issue view 412 --comments;gh pr list --state open --search "412 in:body"→ #572 and#594, neither touches the platform queries;
gh pr list --state open --searchfor "device_id default", "current device capability","resolve_device_id", "device 0 capability", "worker device capability";
issues for "device 0 capability";
gh pr diff --name-onlyover every openPR for
vllm/platforms/{interface,cuda}.py). Open PRs touchingcuda.pyare#431 (custom all-reduce on Volta) and #435 (build fixes); both add a new
has_device_capability(70)call and neither changes the query itself. #514(merged) and #576 (open) are the per-site fixes this change generalizes;
their explicit
device_idarguments keep working unchanged. Upstream:vllm-project/vllm#53834 (closed, not merged) covers the torch-after-init part
only.
AI assistance (Claude) was used to trace the failure class, count the call
sites and prepare the change; I reviewed every line and ran the tests above.
Essential Elements of an Effective PR Description Checklist
supported_models.mdandexamplesfor a new model.