Repository navigation
Conversation
c41de68 to
5d22add
Compare
sfeng33
left a comment
There was a problem hiding this comment.
Thanks for the PR! The fix is correct for a single frontend but silently incomplete for the default multi-API-server / data-parallel topology.
I think the single-backend check belongs in StructuredOutputManager.grammar_init() inside the shared EngineCore, immediately before backend creation/grammar compilation, conceptually:
if self.backend is None:
self.backend_name = request_backend
self.backend = create_backend(request_backend)
elif request_backend != self.backend_name:
reject_request(...)
Wdyt?
5d22add to
c99312f
Compare
|
Good catch, agreed. The frontend config state is process-local, so it cannot be the only guard. I updated the PR to record the selected backend in I also added a regression test that models two independent frontends resolving requests to xgrammar and guidance before sending them to one manager. It verifies that the guidance backend is not constructed and the second grammar is not compiled through the cached xgrammar backend. The focused suite now passes 6 tests. Thanks for pointing this out. |
|
This pull request has merge conflicts that must be resolved before it can be |
c99312f to
9d0fade
Compare
|
Hi @QwertyJack, thank you for your PR! I see you implemented approach with rejection of requests that needs to use different grammar backends. |
|
This pull request has merge conflicts that must be resolved before it can be |
|
@arpera Thanks for the suggestion! I agree that supporting multiple backends would be preferable for This PR preserves the existing single-backend constraint and prevents requests from reaching an incompatible compiler. Removing that constraint would also require validating mixed-backend batches, bitmask compatibility, and async/speculative decoding paths. I expect that work to extend beyond this PR’s scope and be better addressed separately. |
|
/ci run |
|
✅ Triggered Buildkite CI #96098 for commit |
|
/ci retry |
|
✅ Triggered Buildkite CI #96121 for commit |
|
@QwertyJack, please, look at this failed ci job first |
Signed-off-by: QwertyJack <7554089+QwertyJack@users.noreply.github.com>
|
/ci run |
|
✅ Triggered Buildkite CI #96225 for commit |
### What this PR does / why we need it?
Port `vllm-ascend` to vLLM main `544f182d` (bumped from `c0b6f74`) while
keeping the
`v0.31.0` release lane green. The upstream window drops
`replace_parameter(..., prefer_copy=...)`,
fuses the Kimi-K3 output gate into the QKV-A projection, adds the ROCm
Kimi-K3
specialized-decode hooks, widens the EPLB expert-weight and
rejection-sample contracts, and
adds the DP-drafter `num_reqs_override`, the MRv2 `skip_sample` drafting
skip, the HiSparse
`topk_buffer_width`, the SimpleCPUOffload CPU-block RPC and the abstract
`AsyncModelRunnerOutput.get_kv_connector_output`. Every gate below is
either a
`vllm_version_is("0.31.0")` split or an attribute/signature probe.
| Files | Upstream vLLM change (why it breaks vllm-ascend) | vllm-ascend
adaptation |
|-------|--------------------------------------------------|------------------------|
| `.github/vllm-main-verified.commit` | — (main-lane pin only) |
Verified main pin bumped `c0b6f74` -> `544f182d` |
| `tests/ut/attention/test_mla_prolog_v3_route.py` |
[vllm#60805](vllm-project/vllm#60805) makes
`MLAAttention.get_kv_cache_spec` read `topk_buffer_width` (set in
`__init__`);
[vllm#59634](vllm-project/vllm#59634) makes
`_mla_preprocess` return a 3-tuple | Set `owner.topk_buffer_width =
None` on the stub; mock `_mla_preprocess` as `(None, None, None)` |
| `tests/ut/attention/test_mla_v1.py` |
[vllm#59634](vllm-project/vllm#59634) fuses the
Kimi-K3 output gate as a third QKV-A shard | Add tests for
`qkv_a_gate_fused`/`use_output_gate` and `_split_fused_qkv_lora` gate
peeling; unpack the 3-tuple |
| `tests/ut/models/minimax_m3/test_minimax_m3_vit.py` |
[vllm#60238](vllm-project/vllm#60238) makes
`_mark_tower_model` read `use_v2_model_runner` | Add
`use_v2_model_runner=False` to the constructed runner stub |
| `tests/ut/models/test_kimi_k3_adapter.py` |
[vllm#60683](vllm-project/vllm#60683) adds the
ROCm specialized-decode hooks (`init_specialized_decode`,
`get_model_state_cls`, the `step` forward argument, and `layer.kv_cache`
reassignment via `clear_layer_kv_caches`) | Add main-only tests (`skipif
vllm_version_is("0.31.0")`) covering the inherited hooks |
| `tests/ut/ops/test_routed_experts.py` |
[vllm#61786](vllm-project/vllm#61786) registers
expert weights via `w.view(torch.uint8) if w.element_size() == 1 else w`
| New UT asserting `EplbExpertTensorList.element_size()/view()` keep the
container and its aliasing semantics |
| `tests/ut/patch/platform/test_patch_structured_output.py` |
[vllm#49625](vllm-project/vllm#49625) makes
`StructuredOutputManager.grammar_init` read `backend_name` | Set
`manager.backend_name = None` on the stub |
| `tests/ut/runtime_guard/test_unified_wiring.py` |
[vllm#56430](vllm-project/vllm#56430) adds
`AsyncModelRunnerOutput.get_kv_connector_output` | Add a stub
`get_kv_connector_output` to the fake async output |
| `tests/ut/simple_kv_offload/test_simple_kv_offload.py` |
[vllm#52921](vllm-project/vllm#52921) adds the
`get_simple_cpu_offload_num_cpu_blocks` worker RPC | New main-only UTs
for the aligned / no-group / no-connector paths |
| `tests/ut/spec_decode/test_dflash2_v2.py` |
[vllm#58984](vllm-project/vllm#58984) adds
`num_reqs_override` to `prepare_dflash_inputs_triton` | Update the
expected kwarg; new test for positional forwarding |
| `tests/ut/worker/test_model_runner_v1.py` |
[vllm#60805](vllm-project/vllm#60805)
`topk_buffer_width` | Set `layer.topk_buffer_width = None` on the layer
stub |
| `tests/ut/worker/test_model_runner_v2.py` | Same
[vllm#60805](vllm-project/vllm#60805) | Same
stub attribute |
| `tests/ut/worker/v2/test_speculator.py` |
[vllm#62784](vllm-project/vllm#62784) adds
`skip_sample` to the drafter `_prefill` | Expect/forward `skip_sample`
on main (7 -> 8 positional args) |
| `tests/ut/worker/v2/test_v2_block_table.py` |
[vllm#60767](vllm-project/vllm#60767) makes
`init_block_table_layout_tensors` read `_sliding_window_reach` | Set
`tables._sliding_window_reach = [0, 0]` |
| `vllm_ascend/attention/mla_v1.py` |
[vllm#59634](vllm-project/vllm#59634) fuses the
Kimi-K3 output gate into the QKV-A projection (three shards, no
standalone `g_proj`);
[vllm#62326](vllm-project/vllm#62326) drops
`replace_parameter(..., prefer_copy=)` | Detect the gate-fused layout
from `fused_qkv_a_proj.output_sizes`, disable MLAPO/PROLOG_V3 for it,
bound the KV-A slice with `_fused_qkv_a_kv_end`, peel the gate in
`_split_fused_qkv_lora`; version-split `replace_parameter` |
| `vllm_ascend/attention/sfa_v1.py` |
[vllm#62326](vllm-project/vllm#62326) drops
`replace_parameter(..., prefer_copy=)` | Version-split
`replace_parameter` (keep `prefer_copy=True` on v0.31.0) |
| `vllm_ascend/models/deepseek_v4/indexer.py` |
[vllm#52670](vllm-project/vllm#52670) keeps
`STR_DTYPE_TO_TORCH_DTYPE["fp8"]` as the uint8 storage dtype, so
`kv_cache_dtype_str_to_dtype` cannot validate the indexer's fp8 compute
dtype | Validate `indexer_kv_dtype` against the platform default string
and return the compute dtype directly |
| `vllm_ascend/models/kimi_k3.py` |
[vllm#60683](vllm-project/vllm#60683) adds the
ROCm specialized-decode hooks and reassigns `layer.kv_cache` on teardown
| Add a `kv_cache` setter, call `init_specialized_decode` + override
`get_model_state_cls` to the Ascend hybrid state on main, forward `step`
|
| `vllm_ascend/observability/runtime_guard/hooks.py` |
[vllm#56430](vllm-project/vllm#56430) makes
`AsyncModelRunnerOutput.get_kv_connector_output` required | Implement it
on `AscendAsyncOutput` (main only) |
| `vllm_ascend/ops/fused_moe/routed_experts.py` |
[vllm#61786](vllm-project/vllm#61786) moves
1-byte expert tensors as uint8 views | Add `element_size()`/`view()` to
`EplbExpertTensorList`; widen the `get_expert_weights` return type |
| `vllm_ascend/ops/gdn.py` |
[vllm#62326](vllm-project/vllm#62326) drops
`replace_parameter(..., prefer_copy=)` | Version-split
`replace_parameter` for the packed conv weight |
| `vllm_ascend/ops/kimi_kda.py` | Same
[vllm#62326](vllm-project/vllm#62326) | Same
version-split for the packed conv weight |
| `vllm_ascend/ops/linear.py` | Same
[vllm#62326](vllm-project/vllm#62326) | Same
version-split for `weight_fp32` |
| `vllm_ascend/ops/triton/v2/spec_decode/prepare_dflash_inputs.py` |
[vllm#58984](vllm-project/vllm#58984) forwards
the DP-synced request count during dummy runs | Accept
`num_reqs_override`, cap `num_reqs` and slice `num_scheduled_tokens` |
| `vllm_ascend/worker/v2/spec_decode/autoregressive/speculator.py` |
[vllm#62784](vllm-project/vllm#62784) adds
`skip_sample` to `_prefill` | Version-split `_prefill`, forward
`skip_sample` on main |
| `vllm_ascend/worker/v2/spec_decode/dflash/speculator.py` |
[vllm#58984](vllm-project/vllm#58984) adds
`num_reqs_override` | Forward `num_reqs_override` through both
`prepare_dflash_inputs` wrappers |
| `vllm_ascend/worker/v2/spec_decode/gemma4/speculator.py` |
[vllm#62784](vllm-project/vllm#62784) adds
`skip_sample` | Accept and forward `skip_sample` |
| `vllm_ascend/worker/v2/spec_decode/rejection_sampler_utils.py` |
[vllm#53987](vllm-project/vllm#53987) adds the
FLy entropy-gated verification mode; upstream also threads the
watermarking kwargs | Accept both kwarg groups, but raise
`NotImplementedError` for FLy and watermarking on NPU |
| `vllm_ascend/worker/worker.py` |
[vllm#52921](vllm-project/vllm#52921) adds the
`get_simple_cpu_offload_num_cpu_blocks` RPC to the worker | Implement it
(main only) via
`_find_simple_cpu_offload_connector().get_aligned_num_cpu_blocks()` |
### Does this PR introduce _any_ user-facing change?
### How was this patch tested?
---
### Checklist
#### Testing
- [ ] **Boundary / edge-case coverage**: I reviewed whether this change
involves
boundary conditions (e.g. empty input, single/zero element, min/max
values,
off-by-one, sequence-length limits, integer overflow). Where applicable,
I
added unit tests covering these edge cases in `tests/ut/` or
`tests/e2e/`.
- [ ] **Regression test**: If this PR fixes a bug, I added a regression
test
that would fail without the fix.
- [ ] **Error / failure path coverage**: I covered abnormal paths
(exceptions,
timeouts, out-of-range, division by zero) in addition to the happy path.
- [ ] **Meaningful & reliable tests**: New tests assert real behavior
and are
not written merely to satisfy CI.
- [ ] **Performance impact**: If this PR touches a performance-sensitive
path,
I attached benchmark results and added performance regression tests.
- vLLM main:
vllm-project/vllm@c0b6f74
---------
Signed-off-by: zhangxinyuehfad <starmoon_zhang@163.com>
Signed-off-by: hfadzxy <starmoon_zhang@163.com>
…llm-project#49625) Signed-off-by: QwertyJack <7554089+QwertyJack@users.noreply.github.com> Signed-off-by: Artem Perevedentsev <aperevedents@nvidia.com> Co-authored-by: QwertyJack <7554089+QwertyJack@users.noreply.github.com> Co-authored-by: Artem Perevedentsev <aperevedents@nvidia.com>
Purpose
Fixes #43920.
The V1 structured-output manager initializes one engine-level backend from the first structured-output request. However,
backend=autostill resolves every request independently. A valid xgrammar-compatible schema followed by a valid schema usingmultipleOfresolves to xgrammar and guidance respectively, while EngineCore continues compiling both with the first backend. After #47312, this no longer kills EngineCore, but the second request still reaches grammar compilation through the wrong backend and returns HTTP 500.This change adds two consistency checks:
VLLMValidationErrorfor a later mismatch, preserving an admission-time HTTP 400 for the common single-frontend path.StructuredOutputManagerrecords the backend name after successful backend creation and rejects any later mismatch before backend construction or grammar compilation. This is the authoritative guard for multi-API-server and data-parallel topologies where frontend config state is process-local.Requests without structured outputs do not execute either check.
This is not a duplicate of existing work:
FinishReason.ERROR, which becomes HTTP 500.Test Plan
PYTHONPATH=$PWD .venv/bin/python -m pytest tests/v1/structured_output/ -q .venv/bin/pre-commit run --files vllm/sampling_params.py vllm/v1/structured_output/__init__.py tests/v1/structured_output/test_validation.pyThe frontend regression test uses real xgrammar and guidance validation. The first JSON schema resolves to xgrammar; a second valid schema containing
multipleOfresolves to guidance and must be rejected because the engine already selected xgrammar.The manager regression test models two independent frontends sending requests resolved to xgrammar and guidance into one EngineCore. It verifies that the second backend is not constructed and the guidance grammar is not compiled through the cached xgrammar backend.
Test Result
Pre-fix, order-dependent repro on unmodified
main(one engine config withbackend=auto, real xgrammar 0.2.2):The same request succeeds on a fresh engine (guidance accepts the EBNF) and fails only after an xgrammar request latched the backend, which is why this shows up as intermittent 500s rather than as a deterministic failure of a single request.
Regression tests:
tests/v1/structured_output/test_validation.py:20 passed(2 new tests; all pre-existing validation tests still pass).tests/v1/structured_output/:74 passed.pre-commit run --files ...for the three touched files: all hooks passed, including ruff, mypy, typos, config validation, and SPDX checks.Essential Elements of an Effective PR Description Checklist