Skip to content

[Model][Feature] Support D3 decision models on Ascend - #19804

Open
do420 wants to merge 18 commits into
vllm-project:mainfrom
do420:feat/ascend-d3-decision-model
Open

do420 wants to merge 18 commits into
vllm-project:mainfrom
do420:feat/ascend-d3-decision-model

Conversation

@do420

@do420 do420 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it?

Adds an Ascend adapter and model registration for upstream Decision 3.0 (D3) checkpoints. D3 uses a Qwen3.5 multimodal backbone with a separate answer-code readout. The adapter reuses Ascend's Qwen3.5 path and vLLM's sequence-classification/pooling interface, and loads readout.safetensors through the model's secondary-weight source. It also skips the readout tensor in the backbone weight pass so index-less checkpoints such as d3-nano can load it through the sidecar path.

The PR does not implement a parallel decision endpoint. It is intended to use upstream vLLM's native POST /v1/systemone route and D3 model implementation. The upstream implementation landed in vLLM #61565, with additional checkpoint-size support in #61778.

Stacked on #19919. This branch includes the bidirectional attention fix required for D3’s ENCODER_ONLY layers and their numerical comparisons (see #19918).

This branch is synced with vLLM-Ascend main at 219f44ca and inherits its verified vLLM pin, 24420b7a, without a D3-specific pin override.

Does this PR introduce any user-facing change?

Yes. It adds an Ascend model adapter for D3 sequence-classification checkpoints, enabling D3 decisions through upstream System One.

How was this patch tested?

Passed

  • D3 E2E setup (b41f8c8a): added accelerate to the test dependencies and used one loopback port for the System One server, health check, and request. bash format.sh ci passed, and a local HTTP harness verified that the health check and request use the launched port. The model and NPU assertions await CI.

  • CI compatibility sync (3e7f6367): merged upstream main to align the Ascend Qwen4 indexer imports with the verified vLLM 24420b7a pin. The preceding CPU job used 544f182d, which lacks qsa_indexer.py, and failed during conftest import before any tests ran. Confirmed the new pin contains QSAIndexerMetadataBuilder; bash format.sh ci passes locally on the synced tree. Both CPU jobs passed in the CI run.

  • CPU-only validation of current head b41f8c8a against vLLM 24420b7a: python -m pytest -q tests/ut/models/test_d3_registration.py tests/ut/attention/test_attention_v1.py tests/ut/test_platform.py — 208 passed, 1 skipped. The container had no NPU devices, 2 CPUs and a 12 GiB memory limit.

  • Checked the pinned vLLM 24420b7a loader API on CPU: DefaultModelLoader.Source accepts allow_patterns_overrides; the bot-suggested allowed_patterns raises TypeError. The existing readout source argument is correct.

  • bash format.sh ci: all hooks pass.

  • TORCH_DEVICE_BACKEND_AUTOLOAD=0 pytest -q tests/ut/models/test_d3_registration.py: 2 passed on the rebased branch, against vLLM 544f182d (CPU only).

  • The e2e tests are skipped unless vllm.model_executor.models.d3 is importable (native D3 is in neither v0.31.0 nor v0.32.0).

  • Ruff check and format check on the D3 implementation and test files: passed.

  • Ascend 950DT, CANN 9.2.0.b080, torch_npu 2.10.0.post4, vLLM c0b6f74 (the pin on main when this comparison was run), this branch plus the fix from [Attention][BugFix] Ensure ENCODER_ONLY layers in pooling models bypass causal path #19919: d3-edge and d3-nano loaded through the sidecar path and classified with runner="pooling", max_model_len=4096, media inputs disabled.
    Pooling output compared with the checkpoint runtime (AutoModel.from_pretrained(..., trust_remote_code=True)), softmax over each question's options, three questions (choice, noul, score):

    model mode max softmax difference
    d3-edge eager and graph 0.0065
    d3-nano graph 0.0193

    Without the fix from [Attention][BugFix] Ensure ENCODER_ONLY layers in pooling models bypass causal path #19919 the same comparison gives 0.33 on d3-edge.

  • POST /v1/systemone on d3-edge (Choice, Noul and Score in one request) returned HTTP 200 with probabilities matching the offline run.

  • Re-ran the same comparison after rebasing onto a newer vLLM-Ascend main (one commit later than the 544f182d pin above), against vLLM 766c837b (the merge commit of upstream #61778, so it carries both #61565 and #61778): same hardware, same fix from [Attention][BugFix] Ensure ENCODER_ONLY layers in pooling models bypass causal path #19919. Softmax differences matched exactly (d3-edge eager and graph 0.0065, d3-nano graph 0.0193), so the result is not tied to one specific vLLM/vLLM-Ascend commit pair.

  • System One Bench accuracy (benchmark results).

    Model Ascend 950DT accuracy CPU baseline accuracy (llama.cpp, Q8 GGUF) Difference (Ascend − baseline)
    d3-edge 45.68% 45.73% −0.05 pp
    d3-nano 64.86% 64.95% −0.09 pp

    Accuracy is the share of correct decisions across 2,200 labeled examples per model. The benchmark contains 2,000 Typed Decisions examples and 200 AG News topic-classification examples. The CPU baseline totals are calculated from the published subset scores, weighted by their example counts. Both Ascend runs completed 600 requests with zero failures.

  • Jev Decision Index, BANKING77 (evaluation kit, leaderboard results). Each model completed all 3,080 requests, with zero failures. Macro-F1 is the leaderboard metric.

    Model Ascend accuracy Ascend macro-F1
    d3-edge 89.94% 89.85%
    d3-nano 91.36% 91.27%

    The published macro-F1 scores are 89.97% for d3-edge and 91.33% for d3-nano. The Ascend results are 0.12 and 0.06 percentage points lower, respectively.

    Both benchmarks used one Ascend 950DT per model in eager mode via POST /v1/systemone, with media disabled and max_model_len=4096. The scratch stack used the D3 adapter, the [Attention][BugFix] Ensure ENCODER_ONLY layers in pooling models bypass causal path #19919 attention fix, and vLLM 544f182d. The Jev results cover BANKING77 only. The current 24420b7a pin is outside the scope of both runs.

  • A first, lightweight latency pass. Measurement environment caveat: this ran in a scratch setup, not the configuration used for the accuracy numbers above: a generic vllm-ascend:nightly-main-a5-openeuler image with this branch and a standalone vLLM source tree (766c837b) overlaid on top of the image's own installed packages via PYTHONPATH, rather than a normal pip install, one card, gpu_memory_utilization deliberately low (0.2-0.25) to assume a shared card, one engine per run, 3-question batch, 20 repeats after a warmup call. Treat the numbers below as a rough first signal, not a benchmark from a representative deployment:

    model mode mean latency (3-question batch) throughput
    d3-edge eager 26.7 ms 112.2 req/s
    d3-edge graph 31.7 ms 94.7 req/s
    d3-nano graph 76.1 ms 39.4 req/s

    Note: graph mode was slightly slower than eager for d3-edge here. The batch is tiny (3 requests) and the model is small (12 layers), so graph capture overhead may outweigh its benefit at this scale; this is not a claim that graph mode regresses D3 in general, and it should be re-measured under a larger batch/concurrent load before drawing that conclusion.

  • System One serving performance and replica scaling. Measured successful requests per second and end-to-end p99 HTTP latency through POST /v1/systemone. Each request contained a synthetic 330-token support ticket and one choice question (436 input tokens after formatting). Closed-loop clients kept one request in flight each. Each point was measured once for 15 seconds after a 3-second warm-up.

    The scratch stack used vLLM 544f182d, the D3 adapter, and the [Attention][BugFix] Ensure ENCODER_ONLY layers in pooling models bypass causal path #19919 attention fix. Each replica ran in eager mode on its own Ascend 950DT, with prefix caching and media disabled, max_model_len=4096, and max_num_seqs=64. In the two-replica run, clients were evenly split between the two endpoints. The one-replica rows were remeasured in the same sweep.

    How to read the tables. Throughput counts successful responses per second across all cards. The p99 response time is the time within which about 99% of successful requests completed. For example, at 32 concurrent clients, d3-edge on two cards served 779.2 requests per second and about 99% of successful requests finished within 56 ms.

    Initial single-card sweep

    This separate run measured each model on one card at 1–64 concurrent clients. The tables below it use the later replica-scaling run, which remeasured the overlapping one-card settings.

    Concurrent clients Edge requests/sec Edge p99 response time Nano requests/sec Nano p99 response time
    1 66.9 16 ms 33.0 33 ms
    2 84.4 24 ms 37.3 57 ms
    4 163.8 26 ms 73.7 57 ms
    8 312.8 37 ms 126.5 66 ms
    16 451.1 44 ms 200.0 83 ms
    32 499.0 145 ms 202.0 180 ms
    64 159.1 1,812 ms 181.5 1,438 ms

    The Edge client recorded two read errors at 64 clients. All other measured requests in this sweep succeeded.

    Replica scaling sweep

    d3-edge

    Concurrent clients (total) NPU cards Successful requests/sec p99 response time
    32 1 452.6 111 ms
    32 2 779.2 56 ms
    64 1 156.5 1,904 ms
    64 2 472.4 709 ms
    128 1 80.5 5,341 ms
    128 2 250.6 2,618 ms
    256 1 132.4 7,286 ms
    256 2 131.8 5,928 ms

    d3-nano

    Concurrent clients (total) NPU cards Successful requests/sec p99 response time
    32 1 204.4 194 ms
    32 2 380.0 150 ms
    64 1 185.3 1,391 ms
    64 2 409.0 242 ms
    128 1 153.5 4,828 ms
    128 2 243.4 3,076 ms
    256 1 68.2 8,380 ms
    256 2 138.9 7,162 ms

    Peak measured throughput increased by 1.72× for d3-edge and 2.00× for d3-nano. The client logged one read error each for d3-edge at 64 and 256 clients on one card and at 256 clients on two cards. For d3-nano it logged three at 256 clients on one card, one at 128 clients on two cards, and three at 256 clients on two cards. Other points had zero request errors.

Validation limitations

  • Ran tests/e2e/pull_request/one_card/pooling/test_d3.py as pytest on Ascend, unmodified. Both numeric-comparison parametrizations passed: test_d3_pooling_matches_checkpoint_runtime[vllm-sr/d3-edge] and [vllm-sr/d3-nano] (1 passed each). The environment has no outbound access to huggingface.co, so the two model directories were made available under the working directory as vllm-sr/d3-edge and vllm-sr/d3-nano (both resolve locally before any Hub lookup is attempted, in transformers, vLLM and the D3 runtime's own resolve_dir); no test code was changed. d3-edge's checkpoint ships without a MODEL_MANIFEST.json-matching model.safetensors.index.json, so one was generated from the safetensors header and the checkpoint's MODEL_MANIFEST.json was left out of the local copy, which skips the (otherwise inapplicable) manifest hash check.
    test_d3_systemone_endpoint did not reach a verdict in this run: the server started and answered /health normally, but the request from the test itself did not return within 10 minutes and the run was stopped. This looks environment-specific (unrelated runs on this same card show an occasional long one-time delay around the first full decode-capable graph build, logged as a Triton causal_conv1d_update fallback that "stalls ACL graph capture at decode-FULL"); the System One endpoint itself was separately confirmed working over HTTP, with matching probabilities, in the manual run above.
  • Graph mode: a stale vLLM torch.compile cache can crash startup with ValueError: not enough values to unpack (expected 6, got 5) when it was written by a run with a different limit_mm_per_prompt setting (for example media limited to zero, then enabled, or the reverse). The cache key (MultiModalConfig.compute_hash) does not include limit_per_prompt, so both settings share one cache directory. With a clean cache, d3-edge in graph mode with media enabled works (max softmax difference 0.0065), and re-running the same configuration from its own cache works. This is a known upstream vLLM issue (vllm-project/vllm#50891, fix proposed in vllm-project/vllm#50908) and is not caused by the D3 adapter or by the attention fix in [Attention][BugFix] Ensure ENCODER_ONLY layers in pooling models bypass causal path #19919. The e2e tests keep media inputs disabled because they only send text.
  • The serving benchmark covers short text requests with one decision question. Image and video requests, long prompts, and tensor parallelism remain outside its scope.

Checklist

  • Added D3 registration and model adapter.
  • Added unit coverage for registration and readout filtering.
  • Added pooling differential coverage for d3-edge and d3-nano.
  • Added a System One smoke test covering Choice, Noul, and Score.
  • Pooling output agrees with the checkpoint runtime on Ascend (manual runs above; needs [Attention][BugFix] Ensure ENCODER_ONLY layers in pooling models bypass causal path #19919).
  • System One endpoint works on Ascend (manual run above).
  • Complete D3 E2E validation. Both pooling parametrizations passed as pytest in the manual Ascend run; the System One pytest remained inconclusive. Native D3 is unavailable in the v0.32.0 release pin, so release-job skips do not establish model coverage.
  • Run bash format.sh ci with pre-commit available (all hooks pass, gitleaks and shellcheck installed locally).

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces support for Decision 3.0 (D3) models on Ascend hardware by implementing a new model adapter. It leverages the existing Ascend Qwen3.5 multimodal backbone and integrates vLLM's sequence-classification capabilities. The changes include custom processing info for D3, weight mapping adjustments, and the ability to load secondary readout weights, ensuring compatibility with index-less checkpoints.

Highlights

  • Ascend Model Adapter: Added the AscendD3ForSequenceClassification adapter to support D3 decision models on Ascend hardware.
  • Model Registration: Registered the new D3 sequence classification model in the model registry.
  • Weight Loading: Implemented secondary weight loading for the D3 readout.safetensors sidecar and added logic to skip unused backbone weights.
  • Testing: Added unit tests for model registration and end-to-end tests for pooling and system-one endpoint compatibility.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions

Copy link
Copy Markdown
Contributor

👋 Hi! Thank you for contributing to the vLLM Ascend project. The following points will speed up your PR merge:‌‌

  • A PR should do only one thing, smaller PRs enable faster reviews.
  • Every PR should include unit tests and end-to-end tests ‌to ensure it works and is not broken by other future PRs.
  • Write the commit message by fulfilling the PR description to help reviewer and future developers understand.

If CI fails, you can run linting and testing checks locally according Contributing and Testing.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

Suggested PR Title:

[Model][Feature] Support D3ForSequenceClassification model on Ascend

Suggested PR Summary:

### What this PR does / why we need it?
This pull request registers and implements the `D3ForSequenceClassification` model (based on `AscendQwen3_5ForConditionalGeneration`) for the Ascend backend. It adds processing info (`D3ProcessingInfo`) to handle image and video token sizes and configures secondary weights for the readout.

However, there is a critical issue in `vllm_ascend/models/d3.py` where `allow_patterns_overrides` is used instead of `allowed_patterns` in `DefaultModelLoader.Source`, which will cause a `TypeError` at runtime.

### Does this PR introduce _any_ user-facing change?
Yes, it adds support for the `D3ForSequenceClassification` model.

### How was this patch tested?
New tests have been added:
- `tests/e2e/pull_request/one_card/pooling/test_d3.py` for end-to-end pooling and endpoint tests.
- `tests/ut/models/test_d3_registration.py` for unit testing model registration and unused weight checks.

Comment thread vllm_ascend/models/d3.py Resolved
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

@do420 do420 changed the title [ModelRunner] Add Ascend support for D3 decision models [Model][Feature] Support D3 decision models on Ascend Oct 26, 2026
The pooling branch of AscendAttentionBackendImpl.forward chose the bidirectional path from attn_metadata.causal alone. ENCODER_ONLY layers of hybrid models (Gated DeltaNet + full attention, e.g. Qwen3.5-based D3) reach the backend with causal=True because they do not go through the EncoderOnlyAttention wrapper, so they silently ran causal attention. Also route layers whose attn_type is ENCODER_ONLY to _forward_encoder_attention, as upstream backends do. DECODER layers are unchanged. Add unit tests; the ENCODER_ONLY one fails without the change.

Signed-off-by: do420 <denizozcanmc@gmail.com>
@do420
do420 force-pushed the feat/ascend-d3-decision-model branch from 545b0e4 to 6cbafff Compare October 27, 2026 09:54
do420 added 11 commits October 28, 2026 10:50
The encoder attention path attends over rank-local K/V only; unlike the KV-cache write it never gathers the other PCP ranks' tokens, so bidirectional attention would silently be wrong. Pooling models default to the V1 model runner, which rejects PCP, but VLLM_USE_V2_MODEL_RUNNER=1 can force V2 and bypass that check. Fail at model construction instead, add unit tests for the guard and for the V1 rejection, and document the limitation.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Adapt the upstream D3 scoring model to the Ascend Qwen3.5 path and add model registration and System One regression coverage.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Skip the readout tensor during the backbone weight pass so index-less D3 checkpoints such as d3-nano still load their decision head through secondary weights. Cover d3-edge and d3-nano in the checkpoint differential test.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Exercise the index-less D3 nano checkpoint in the pooling differential and System One endpoint tests.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Match upstream differential coverage for both the edge baseline and nano sidecar-loading case; retain the endpoint smoke test on edge.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Record the user-visible Ascend D3 adapter and its dependency on upstream native D3 and System One support.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Disable media inputs in the D3 pooling and System One tests: graph mode with multimodal inputs enabled currently fails during startup profiling for D3's ENCODER_ONLY attention layers, and the tests only send text. Raise the softmax tolerance from 0.02 to 0.03: d3-nano reaches about 0.019 against the checkpoint runtime, which left almost no headroom, while the causal-attention regression this test guards against shows differences above 0.2.

Signed-off-by: do420 <denizozcanmc@gmail.com>
The graph-mode startup failure seen with media inputs enabled came from a stale torch.compile cache written by a run with a different limit_mm_per_prompt setting (the cache key does not include it), not from D3's ENCODER_ONLY layers. With a clean cache, graph mode with media enabled works. Keep media disabled since the tests only send text, and document the cache-key caveat.

Signed-off-by: do420 <denizozcanmc@gmail.com>
QUESTIONS mixed str, dict and list values, so mypy inferred object and rejected indexing it in the System One test on every supported Python version.

Signed-off-by: do420 <denizozcanmc@gmail.com>
The test coverage config check requires every e2e test file under tests/e2e/pull_request to have an estimated time; test_d3.py was missing, which fails the select-tests job.

Signed-off-by: do420 <denizozcanmc@gmail.com>
The release tag matrix (main2main label) runs the e2e suite on v0.31.0, which has no D3 model. Use the existing vllm_version_is gate so the test is skipped there.

Signed-off-by: do420 <denizozcanmc@gmail.com>
@do420
do420 force-pushed the feat/ascend-d3-decision-model branch from 6cbafff to d185e6f Compare October 28, 2026 09:53
…rsion

Native D3 (vLLM #61565, #61778) is in neither v0.31.0 nor v0.32.0, so the exact-version skip let the tests run and fail on v0.32.0. Probe for vllm.model_executor.models.d3 instead.

Signed-off-by: do420 <denizozcanmc@gmail.com>
@do420
do420 force-pushed the feat/ascend-d3-decision-model branch from d185e6f to 2ead1cb Compare October 28, 2026 09:55
Signed-off-by: Deniz Özcan <66875687+do420@users.noreply.github.com>
@do420
do420 marked this pull request as ready for review November 3, 2026 06:18
@czydyy czydyy added the ready-precise run selected e2e test for pr label Nov 3, 2026
do420 and others added 3 commits November 3, 2026 15:47
Bring in the verified vLLM 24420b7a pin alongside the Qwen4 indexer imports. The previous CI run combined current Ascend sources with the older 544f182d pin and failed while importing the unit-test conftest.

Validation: bash format.sh ci; verified that the new vLLM pin defines QSAIndexerMetadataBuilder in qsa_indexer.py. Runtime CPU and NPU tests require CI hardware and dependencies.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Stack the changes from vllm-project#19919 so the D3 numerical tests exercise the required ENCODER_ONLY attention behavior.

Signed-off-by: do420 <denizozcanmc@gmail.com>
Install accelerate for the checkpoint runtime in E2E CI. Bind the D3 server to the same loopback host and port used by its health check and System One request.

Signed-off-by: do420 <denizozcanmc@gmail.com>
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Nov 4, 2026
@do420

do420 commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor Author

/rerun
[Bot]: rerun completed.

Rerun (failed jobs only):

  • E2E

Keep pooling parity and System One coverage on the smaller checkpoint. Allow the test environment to resolve accelerate without a version floor.

Signed-off-by: do420 <denizozcanmc@gmail.com>
@do420

do420 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor Author

/rerun
[Bot]: rerun completed.

Rerun (failed jobs only):

  • E2E

1 similar comment
@do420

do420 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor Author

/rerun
[Bot]: rerun completed.

Rerun (failed jobs only):

  • E2E

@Angazenn

Angazenn commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Thanks for your contribution! Can you explain why we can't inherit vLLM model strucutre in the PR description?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation module:tests ready-precise run selected e2e test for pr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants