Skip to content

[Bugfix][Model] Fail fast on Inkling's unsupported GPU architectures - #51560

Open
m4xkushnir wants to merge 4 commits into
vllm-project:mainfrom
m4xkushnir:fix/issue-51405
Open

[Bugfix][Model] Fail fast on Inkling's unsupported GPU architectures#51560
m4xkushnir wants to merge 4 commits into
vllm-project:mainfrom
m4xkushnir:fix/issue-51405

Conversation

@m4xkushnir

@m4xkushnir m4xkushnir commented Aug 9, 2026

Copy link
Copy Markdown

Purpose

Fixes the startup crash reported in #51405.

Serving Inkling on SM120 (RTX PRO 6000 Blackwell, GeForce Blackwell, DGX Spark) died during startup profiling with

AssertionError: Paged KV not supported on SM 12.0 in this PR
RuntimeError: Worker failed with error 'Paged KV not supported on SM 12.0'

raised from the vendored FA4 relative-attention forward, after weights had already been loaded and the KV cache sized. The FA4 kernel has no paged-KV forward on SM12x, and vLLM always attends over the paged KV cache, so there is no configuration that makes the model run there.

Scope note: an earlier revision also rejected SM8x, since FA4 asserts page_table is None there too. @TheVerdman demonstrated Inkling serving end-to-end on 4×A100 through a FlexAttention fallback carrying the relative bias via score_mod, so SM8x is deliberately left open for that work and this PR rejects SM12x only. The capability tables are a deny-list of broken architectures rather than an allow-list of working ones, so adding a fallback means adding a path, not deleting a guard.

This does not add SM120 support. The kernel is fetched at build time from vllm-project/tml-fa4 (pinned in cmake/external_projects/tml_fa4.cmake) and vllm-project/flash-attention, so a paged-KV SM120 forward cannot be implemented from this repo. What this PR does is take the second option #51405 asks for — "a documented supported-hardware note ... would save others the investigation" — and reject those architectures up front instead of asserting inside a worker after a multi-GiB weight load.

Changes

  • vllm/platforms/cuda.pyCudaPlatformBase.verify_model_arch rejects the three Inkling architectures, driven by a new _CAPABILITY_RESTRICTED_MODELS table. Mirrors the existing RocmPlatform.verify_model_arch / _ROCM_UNSUPPORTED_MODELS pattern. The capability query is wrapped in try/except RuntimeError because on non-NVML installs it goes through torch and can initialize CUDA in the resolving process.
  • vllm/config/model.py — calls verify_model_arch from ModelConfig once the architecture is resolved. Without this the hook never runs in the front end: _try_load_model_cls is its only caller, and front-end resolution goes through inspect_model_cls instead, so on SM120 the guard fired inside the worker off the usage-stats path in init_device() (see @Rawsejet's test report). Both TP workers spawned and dumped a traceback before EngineCore shut them down — still before load_model(), but noisier and later than it needs to be. The _try_load_model_cls call stays as a backstop for paths that build a model without a ModelConfig. On ROCm this is a no-op today: _ROCM_UNSUPPORTED_MODELS and _ROCM_PARTIALLY_SUPPORTED_MODELS are both empty.
  • vllm/models/inkling/nvidia/attention.pyInklingAttention.__init__ keeps a check of its own for direct construction, naming the device and the missing kernel. This constructor is the single chokepoint for both backbone (nvidia/model.py) and MTP (nvidia/mtp.py) layers; the vision/audio towers are Triton-only and never reach FA4.
  • vllm/models/inkling/nvidia/ops/fa4_rel_attention.py — adds check_inkling_fa4_support(), and folds the three arch predicates scattered through the file into named constants (_NO_PAGED_KV_MAJORS, _SHEARED_BIAS_MAJORS, _SPLIT_KV_MAJORS) with one comment pointing at the tml-fa4 pin they all track. This also fixes inkling_fa4_num_splits, which keyed off major == 9 and so returned a split count of up to 128 on architectures whose kernel has no split-KV support at all
  • docs/models/supported_models.md — Inkling had no rows in either table. Adds InklingForCausalLM and InklingForConditionalGeneration, plus a note recording that SM120/SM121 are unsupported, why, and that ROCm uses a separate Triton implementation and is unaffected.
  • Tests — coverage for the guard, for the platform hook, a test that InklingAttention.__init__ actually invokes the guard before touching anything else, and tests/test_config.py::test_platform_rejects_unsupported_arch_during_config, which pins the front-end call so the hook cannot silently regress to worker-only again. Adds skip_if_capability_restricted to tests/models/utils.py, used by test_initialization.py and test_registry.py: both resolve every registered architecture, and _try_load_model_cls calls verify_model_arch outside its try/except, so without the skip they would error rather than skip on a GPU that cannot run Inkling.

No behavior change on any architecture that could already run Inkling. On SM8x/SM90/SM100/SM110 the guard is a no-op and inkling_fa4_num_splits returns exactly what it returned before (major == 9 → 1, unchanged; 10/11 → the same computed value). No kernel is added and no numerics are altered on any architecture that could run Inkling previously, so there is no accuracy or serving delta to evaluate. The only behavioral change is on architectures where the model could not run at all

Not duplicating existing work. gh pr list --repo vllm-project/vllm --state open --search "51405 in:body" returns nothing, and #51405 is open with no comments. The open Inkling PRs are unrelated: #51517 and #50528 (parser), #49120 (fp32 head dtype), #49315 (warmup infrastructure), #48768 (multi-depth MTP )

Test Plan

Lint and static checks over every changed file:

pre-commit run --files docs/models/supported_models.md \
  tests/models/inkling/test_fa4_rel_attention.py \
  tests/models/test_initialization.py tests/models/test_registry.py \
  tests/models/utils.py tests/test_config.py vllm/config/model.py \
  vllm/models/inkling/nvidia/attention.py \
  vllm/models/inkling/nvidia/ops/fa4_rel_attention.py vllm/platforms/cuda.py

Unit tests covering the new guard, the platform hook, and the arch constants:

.venv/bin/python -m pytest tests/models/inkling/test_fa4_rel_attention.py -v
.venv/bin/python -m pytest tests/test_config.py -k platform_rejects_unsupported_arch -v

Regression check on the two suites that resolve every registered architecture, since verify_model_arch now runs in that path:

.venv/bin/python -m pytest tests/models/test_registry.py -v
.venv/bin/python -m pytest "tests/models/test_initialization.py::test_can_initialize_large_subset" -v

End-to-end on an unsupported device (SM120) — the failure should now appear during startup, naming the device, rather than as a worker assertion after the weight load:

vllm serve thinkingmachines/Inkling-Small-NVFP4 \
  --tensor-parallel-size 2 \
  --tokenizer-mode inkling \
  --trust-remote-code

And on a supported device (SM90/SM100), to confirm serving is unaffected:

vllm serve thinkingmachines/Inkling-Small-NVFP4 --trust-remote-code

Test Result

Lint — all hooks pass, and the run is idempotent (a second pass modifies nothing):

ruff check.......................................................Passed
ruff format......................................................Passed
typos............................................................Passed
markdownlint-cli2................................................Passed
Run mypy for Python 3.10.........................................Passed
Check SPDX headers...............................................Passed
Check root lazy imports..........................................Passed
Check for spaces in all filenames................................Passed
Update Dockerfile dependency graph...............................Passed
Check for forbidden imports......................................Passed
Prevent new 'torch.cuda' APIs call...............................Passed
Validate configuration has default values and that each field
  has a docstring................................................Passed
Check for boolean ops in with-statements.........................Passed
Suggestion.......................................................Passed

Before, on SM120 (as reported in #51405):weights load, the FP4 MoE backend selects, the KV cache is sized, and the worker then dies in the forward pass with AssertionError: Paged KV not supported on SM 12.0 in this PR.

After, on SM120 — tested by @Rawsejet on the #51405 reporter's hardware (full report): 2× RTX PRO 6000 Blackwell Workstation Edition, compute capability 12.0, driver 595.58.03, CUDA 13.2, vLLM 0.26.1rc1.dev376+g613411a90, torch 2.13.0+cu130, TP=2, runtime hunks applied onto the installed nightly.

Both guards behave as intended on the live device:

check_inkling_fa4_support()                        -> ValueError (names the device, 12.0)
verify_model_arch(InklingForConditionalGeneration) -> ValueError (12.x)
verify_model_arch(InklingForCausalLM)              -> ValueError (12.x)
verify_model_arch(InklingMTPModel)                 -> ValueError (12.x)
verify_model_arch(LlamaForCausalLM)                -> OK (no raise)

vllm serve ... --tensor-parallel-size 2 --tokenizer-mode inkling --trust-remote-code exits in ~25 s with the clean ValueError and no weights loaded, against a full ~152 GiB load before the assertion previously.

That run also showed the guard firing in the worker, not the front end, which the description previously claimed — _try_load_model_cls was the only caller of verify_model_arch, and front-end resolution does not reach it. The vllm/config/model.py change above fixes that; the worker-side check remains as a backstop. Re-test on SM120 welcome.

Everything else — not yet run. I don't have access to SM90/SM100 hardware or a GPU build, so I have not executed the pytest suites or the supported-device vllm serve command above, and I'd rather say so than paste results I can't stand behind. Requesting CI to run the unit and registry suites.


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use /ci run or /ci retry. New commits do not start CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@mergify

mergify Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Documentation preview: https://vllm--51560.org.readthedocs.build/en/51560/

@mergify mergify Bot added documentation Improvements or additions to documentation nvidia labels Aug 9, 2026
@m4xkushnir m4xkushnir changed the title Fix: Fail fast on Inkling's unsupported GPU architectures [Bugfix][Model] Fail fast on Inkling's unsupported GPU architectures Aug 9, 2026
@mergify mergify Bot added the bug Something isn't working label Aug 9, 2026
@m4xkushnir

Copy link
Copy Markdown
Author

First-time contributor, so pre-run-check is gating CI on the 'ready' label. Could a maintainer add it when convenient? Happy for @claude review too. Docs build and DCO are green, and pre-commit passes locally on every changed file

@TheVerdman

Copy link
Copy Markdown

I have end-to-end SM80 evidence that a paged-KV-capable correctness fallback is viable, so I think the blanket SM8x rejection should be reconsidered.

Tested configuration:

  • vLLM 0.26.0+cu129, commit ffd46bfab2128bb84146050e98b51a617c6575ab
  • 4× NVIDIA A100 80GB, TP=4
  • Inkling-Small W8A16
  • SM80-only fallback selecting vLLM FlexAttention, with Inkling’s relative-attention bias supplied through score_mod
  • Existing FA4 path remains selected on SM90/SM100/SM110

End-to-end results:

  • OpenAI Responses serving started successfully
  • strict JSON schema passed in both streaming and non-streaming modes
  • exact early/middle/late retrieval passed at 2K, 8K, 32K, 64K, 128K, and 239,997 actual input tokens
  • zero request retries

The serving image also contained three additional patches (two for W8A16 loading/Marlin and one for structured-output EOS handling) so this demonstrates end-to-end viability, not isolated correctness or performance of the attention fallback alone. I’m happy to rebase the SM80 fallback independently and add focused numerical-parity and paged-KV tests.

Would you and the maintainers prefer that this PR reject only SM12x while leaving SM8x open for the fallback, or that I send a follow-up PR replacing the SM8x guard with the FlexAttention path? I am not proposing SM12x support.

@m4xkushnir

Copy link
Copy Markdown
Author

Thanks. I based the SM8x guard on the FA4 dispatch table without checking whether another backend could serve, which was too broad: FlexAttention supports both score_mod and paged KV, so the fallback is clearly viable

I'll narrow this PR to SM12x only. #51405 is SM120 specific anyway. That leaves SM8x untouched for your follow-up rather than making you remove a guard I'd just added

One note in your favour on scoping: since your test image carried the W8A16/Marlin and EOS patches too, the focused numerical-parity and paged-KV tests you offered are worth having in that PR regardless— happy to review.

@m4xkushnir

Copy link
Copy Markdown
Author

Pushed 693931f. Guard now rejects SM12x only, and I flipped the tables from an allow-list of working architectures to a deny-list of broken ones so your fallback adds a path rather than removing a guard

@TheVerdman

Copy link
Copy Markdown

Thanks, that split makes sense. I’ll keep the SM80 fallback as a separate attention-only follow-up and include focused numerical-parity and paged-KV tests, along with the isolated A100 end-to-end result. I’ll link it here when it’s ready.

@Rawsejet

Copy link
Copy Markdown

Tested the fail-fast on SM120 hardware (the #51405 reporter's box) — it works, with one nuance about where it fires that you may want to reflect in the PR description.

Environment: 2× NVIDIA RTX PRO 6000 Blackwell Workstation Edition, compute capability 12.0, driver 595.58.03, CUDA 13.2, vLLM 0.26.1rc1.dev376+g613411a90, torch 2.13.0+cu130, TP=2. Applied the three runtime hunks (platforms/cuda.py, models/inkling/nvidia/attention.py, models/inkling/nvidia/ops/fa4_rel_attention.py) onto the installed nightly.

Unit-level — both guards behave exactly as intended on the live device:

check_inkling_fa4_support()           -> ValueError: Inkling is not supported on NVIDIA RTX PRO 6000
                                         Blackwell ... (compute capability 12.0): the FA4 relative-
                                         attention kernel has no paged-KV forward on SM12x ...
verify_model_arch(InklingForConditionalGeneration) -> ValueError (12.x)
verify_model_arch(InklingForCausalLM)              -> ValueError (12.x)
verify_model_arch(InklingMTPModel)                 -> ValueError (12.x)
verify_model_arch(LlamaForCausalLM)                -> OK (no raise)

End-to-end vllm serve ... --tensor-parallel-size 2 --tokenizer-mode inkling --trust-remote-code:

Exits in ~25 s with the clean ValueError, and no weights are loaded — a real improvement over the prior behavior (the Paged KV not supported on SM 12.0 assertion only fired after the full ~152 GiB weight load). ✅

Nuance — on this nightly it does not reject in the front end. The PR description says the guard "runs during architecture resolution in the front end, so an unsupported GPU never reaches worker startup or weight loading." What I actually observe is that the API server resolves the architecture without hitting verify_model_arch:

(APIServer pid=…) INFO [model.py:672] Resolved architecture: InklingForConditionalGeneration

and the guard only bites ~11 s later, inside the worker, off the usage-stats path in init_device():

WorkerProc.__init__ -> worker.init_device() -> gpu_worker.py:427 report_usage_stats()
  -> get_architecture_class_name -> get_model_architecture -> resolve_model_cls
  -> _try_load_model_cls -> registry.py:1031 current_platform.verify_model_arch()
  -> cuda.py:315 raise ValueError

So the worker does spawn and init_device() runs — but this is still comfortably before load_model(), so the practical goal (fail fast, name the device, never load weights) is achieved. Two small consequences worth noting:

  1. Because it raises inside report_usage_stats, both TP workers dump a full traceback before EngineCore shuts them down — functionally correct but noisier than a single front-end rejection.
  2. If genuine front-end rejection "before any worker starts" is the goal on this code path, verify_model_arch likely needs to be called from the front-end model-resolution path (model.py) as well, since _try_load_model_cls isn't reached there on this pin.

The InklingAttention.__init__ guard never gets a chance to fire (the arch guard trips first), which is the intended belt-and-suspenders ordering.

Happy to re-test if you wire the front-end call or want the e2e log. — testing on behalf of @Rawsejet (SM120 hardware).

m4xkushnir added a commit to m4xkushnir/vllm that referenced this pull request Aug 11, 2026
verify_model_arch only ran from _try_load_model_cls, which the API server
does not reach: front-end resolution goes through inspect_model_cls. On
SM120 the guard therefore fired inside the worker, off the usage-stats
path in init_device(), so both TP workers spawned and dumped a traceback
before EngineCore shut them down.

Call verify_model_arch from ModelConfig once the architecture is resolved,
so an unsupported device is rejected before any worker starts. The check
in _try_load_model_cls stays as a backstop for paths that build a model
without going through ModelConfig.

Reported-by: Rawsejet <vllm-project#51560>
Signed-off-by: maxim955827848 <maxim955827848@gmail.com>
@m4xkushnir

m4xkushnir commented Aug 11, 2026

Copy link
Copy Markdown
Author

Thanks — that's a precise report, and the front-end claim in the description was wrong. verify_model_arch had exactly one caller, _try_load_model_cls, and front-end resolution goes through inspect_model_cls, so nothing on the API-server path ever reached the hook. Your traceback pins it: the only thing that resolved a model class was report_usage_stats inside init_device(), which is why both TP workers spawned and dumped tracebacks.

Pushed a960df9: ModelConfig now calls current_platform.verify_model_arch(arch) right after Resolved architecture: ..., so rejection happens in the front end and no worker is spawned. The _try_load_model_cls call stays as a backstop for paths that build a model without a ModelConfig. No-op on ROCm today, since _ROCM_UNSUPPORTED_MODELS and _ROCM_PARTIALLY_SUPPORTED_MODELS are both empty. Added tests/test_config.py::test_platform_rejects_unsupported_arch_during_config so this cannot quietly regress to worker-only again.

If you're up for a re-test on the SM120 box, the change is one hunk in vllm/config/model.py on top of what you already applied. What I'd expect now: the ValueError raises immediately after the Resolved architecture line, with no WorkerProc startup at all, so a single traceback rather than one per TP rank, and a faster exit than the ~25 s you measured. Your unit-level results and the "no weights loaded" e2e result are now in the PR description, credited to you.

@mergify

mergify Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @m4xkushnir.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Aug 12, 2026
m4xkushnir added a commit to m4xkushnir/vllm that referenced this pull request Aug 13, 2026
verify_model_arch only ran from _try_load_model_cls, which the API server
does not reach: front-end resolution goes through inspect_model_cls. On
SM120 the guard therefore fired inside the worker, off the usage-stats
path in init_device(), so both TP workers spawned and dumped a traceback
before EngineCore shut them down.

Call verify_model_arch from ModelConfig once the architecture is resolved,
so an unsupported device is rejected before any worker starts. The check
in _try_load_model_cls stays as a backstop for paths that build a model
without going through ModelConfig.

Reported-by: Rawsejet <vllm-project#51560>
Signed-off-by: maxim955827848 <maxim955827848@gmail.com>
@mergify mergify Bot removed the needs-rebase label Aug 13, 2026
Serving Inkling on SM120 (RTX PRO 6000 Blackwell, GeForce Blackwell, DGX
Spark) died during startup profiling with

    AssertionError: Paged KV not supported on SM 12.0 in this PR

raised from the vendored FA4 relative-attention forward, after weights had
already been loaded and the KV cache sized. The FA4 kernel implements paged
KV only on SM90/SM100/SM110, and vLLM always attends over the paged KV
cache, so there is no configuration that makes the model run there.

Reject those architectures up front instead:

- CudaPlatform.verify_model_arch rejects the three Inkling architectures
  during model resolution, before any worker starts.
- InklingAttention.__init__ keeps a check of its own for direct
  construction, naming the device and the missing kernel.
- Document the supported hardware in supported_models.md, where Inkling
  had no rows at all.

Also fold the three arch predicates in fa4_rel_attention.py into named
constants, which fixes inkling_fa4_num_splits returning a split count on
architectures whose kernel has no split-KV support.

This does not add SM120 support; the kernel lives in vllm-project/tml-fa4.
Addresses vllm-project#51405

Signed-off-by: maxim955827848 <maxim955827848@gmail.com>
pre-commit's check-forbidden-imports rejects 'import re'; markdownlint
rewrites a second '*' in a table row as emphasis, which corrupted the
modality legend, so mark only audio as variant-conditional and let the
note cover images.

Signed-off-by: maxim955827848 <maxim955827848@gmail.com>
The SM8x rejection was inferred from the FA4 dispatch table alone. FA4
indeed has no paged-KV forward on SM8x, but that does not mean Inkling
cannot run there: FlexAttention supports both score_mod and paged KV, and
a fallback carrying the relative bias through score_mod has been shown to
serve Inkling end-to-end on A100.

Reject only SM12x, which has no paged-KV path at all, and invert the
tables from an allow-list of working architectures to a deny-list of
broken ones so adding a fallback does not mean deleting a guard.

Signed-off-by: maxim955827848 <maxim955827848@gmail.com>
verify_model_arch only ran from _try_load_model_cls, which the API server
does not reach: front-end resolution goes through inspect_model_cls. On
SM120 the guard therefore fired inside the worker, off the usage-stats
path in init_device(), so both TP workers spawned and dumped a traceback
before EngineCore shut them down.

Call verify_model_arch from ModelConfig once the architecture is resolved,
so an unsupported device is rejected before any worker starts. The check
in _try_load_model_cls stays as a backstop for paths that build a model
without going through ModelConfig.

Reported-by: Rawsejet <vllm-project#51560>
Signed-off-by: maxim955827848 <maxim955827848@gmail.com>
@m4xkushnir

m4xkushnir commented Aug 16, 2026

Copy link
Copy Markdown
Author

Rebased onto current main (70aaec832) and force-pushed — the conflict mergify flagged is gone, and the rebase was clean with no manual resolution. #52289 touched supported_models.md in the meantime but did not add Inkling rows, so the two rows and the SM12x note still apply cleanly.

pre-commit run --files over all ten changed files passes on the rebased tree (ruff check, ruff format, mypy for 3.10, markdownlint, SPDX, forbidden-imports, config-docstring validation — all green). No code changes in this push, only the rebase.

@Rawsejet — the front-end hunk in vllm/config/model.py is unchanged from what you were going to re-test, so the branch is still good for that if you have the SM120 box available

Still gated on the ready label for CI as a first-time contributor — could a maintainer add it when convenient? The suites I'd like CI to cover are tests/models/inkling/test_fa4_rel_attention.py, tests/test_config.py -k platform_rejects_unsupported_arch, tests/models/test_registry.py, and test_initialization.py::test_can_initialize_large_subset, since verify_model_arch now runs in the architecture-resolution path those last two exercise.

@mergify mergify Bot added the inkling label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation inkling nvidia

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants