diff --git a/.github/TEST_README.md b/.github/TEST_README.md index 1cb9a310f..91685dddd 100644 --- a/.github/TEST_README.md +++ b/.github/TEST_README.md @@ -195,16 +195,6 @@ python3 .github/workflows/scripts/select_tests.py --diff-base origin/main # Route based on explicit changed files python3 .github/workflows/scripts/select_tests.py --changed-files vllm_ascend/ops/foo.py - -# Run a specific subset of e2e tests (mirrors the /e2e slash command) -python3 .github/workflows/scripts/select_tests.py \ - --explicit-e2e-tests tests/e2e/pull_request/one_card/test_foo.py \ - tests/e2e/pull_request/two_card/test_bar.py - -# Run a single test method (supports the same ::nodeid syntax as pytest) -python3 .github/workflows/scripts/select_tests.py \ - --explicit-e2e-tests \ - tests/e2e/pull_request/one_card/test_foo.py::TestClass::test_method ``` ## Testing Changes to `select_tests.py` diff --git a/.github/actions/read-vllm-release-tag/action.yml b/.github/actions/read-vllm-release-tag/action.yml deleted file mode 100644 index 48530c943..000000000 --- a/.github/actions/read-vllm-release-tag/action.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Read vLLM release tag -description: Read and validate the verified vLLM release tag. -outputs: - release_tag: - description: Verified vLLM release tag. - value: ${{ steps.read.outputs.release_tag }} -runs: - using: composite - steps: - - name: Read release tag - id: read - shell: bash - run: | - release_tag="$(tr -d '[:space:]' < .github/vllm-release-tag.commit)" - [[ "${release_tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-].*)?$ ]] || { - echo "::error file=.github/vllm-release-tag.commit::invalid vLLM release tag: ${release_tag}" - exit 1 - } - echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT" diff --git a/.github/vllm-main-verified.commit b/.github/vllm-main-verified.commit index fa06c663f..70c8a07f9 100644 --- a/.github/vllm-main-verified.commit +++ b/.github/vllm-main-verified.commit @@ -1 +1 @@ -967c5c3bc38891f4465d3f4e99917ed837bb3833 +9090368b650896bf5fc990c921df7eb4c20355a5 diff --git a/.github/vllm-release-tag.commit b/.github/vllm-release-tag.commit index a9fedb0df..759e855fb 100644 --- a/.github/vllm-release-tag.commit +++ b/.github/vllm-release-tag.commit @@ -1 +1 @@ -v0.22.1 +v0.21.0 diff --git a/.github/workflows/_e2e_periodic_ops.yaml b/.github/workflows/_e2e_periodic_ops.yaml new file mode 100644 index 000000000..baaede2f3 --- /dev/null +++ b/.github/workflows/_e2e_periodic_ops.yaml @@ -0,0 +1,112 @@ +# +# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# This file is a part of the vllm-ascend project. +# + +# Lightweight reusable workflow for periodic ops tests. +# Runs pytest against the specified test target (file or directory). + +name: 'e2e periodic ops test' + +on: + workflow_call: + inputs: + runner: + required: true + type: string + description: GitHub Actions runner label + image: + required: true + type: string + description: Docker image to use for the test container + tests: + required: true + type: string + description: pytest target(s) — a .py file, a directory, or a space-separated list of files (one per-chip group) + name: + required: true + type: string + description: human-readable test name used in job labels and logs + should_run: + required: true + type: boolean + description: set to false to skip this job (matrix-level gate) + vllm_ascend_branch: + required: true + type: string + description: branch ref used for checkout + request_id: + required: false + type: string + default: '' + description: non-empty when triggered by PR /nightly command + vllm_ascend_ref: + required: false + type: string + default: '' + description: PR commit SHA for PR-triggered checkout + dry_run: + required: false + type: boolean + default: false + description: print resolved inputs and skip ops execution + +defaults: + run: + shell: bash -el {0} + +jobs: + dry-run: + name: dry-run / ${{ inputs.name }} + runs-on: ${{ inputs.runner }} + if: ${{ inputs.should_run && inputs.dry_run }} + steps: + - name: Print dry-run inputs + run: | + echo "workflow=e2e_periodic_ops" + echo "runner=${{ inputs.runner }}" + echo "image=${{ inputs.image }}" + echo "tests=${{ inputs.tests }}" + echo "name=${{ inputs.name }}" + echo "vllm_ascend_branch=${{ inputs.vllm_ascend_branch }}" + + ops-test: + name: ${{ inputs.name }} + runs-on: ${{ inputs.runner }} + if: ${{ inputs.should_run && !inputs.dry_run }} + container: + image: ${{ inputs.image }} + options: --network host --shm-size 1g --device /dev/davinci_manager --device /dev/hisi_hdc --device /dev/devmm_svm + volumes: + - /usr/local/dcmi:/usr/local/dcmi + - /usr/local/bin/npu-smi:/usr/local/bin/npu-smi + - /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ + - /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info + - /etc/ascend_install.info:/etc/ascend_install.info + + steps: + - name: Checkout vllm-ascend + uses: actions/checkout@v6 + with: + ref: ${{ inputs.request_id != '' && inputs.vllm_ascend_ref || inputs.vllm_ascend_branch }} + + - name: Install vllm-ascend + run: pip install -e . --no-build-isolation + + - name: Run ops tests + # 'tests' may be a single path or a space-separated list of files + # (a per-chip group expanded from a directory entry); leave it unquoted + # so the shell splits it into separate pytest arguments. + run: pytest -s ${{ inputs.tests }} diff --git a/.github/workflows/_schedule_image_build.yaml b/.github/workflows/_schedule_image_build.yaml index e96f20922..bce25876b 100644 --- a/.github/workflows/_schedule_image_build.yaml +++ b/.github/workflows/_schedule_image_build.yaml @@ -19,10 +19,6 @@ on: description: 'Quay username for pushing images' required: false type: string - quay_temp_username: - description: 'Quay username for pushing temp tag digests' - required: false - type: string workflow_dispatch_tag: description: 'The tag to use for workflow dispatch' required: false @@ -42,32 +38,13 @@ on: required: false type: string default: 'digests' - vllm_commit: - description: 'vLLM commit hash to checkout (used when set; mutually exclusive with tag/branch).' - required: false - type: string - default: '' - vllm_ascend_commit: - description: 'vllm-ascend commit hash to checkout (used when set; mutually exclusive with tag/branch).' - required: false - type: string - default: '' - temp_only: - description: 'If true, push only to QUAY_TEMP_REPO and skip the merge-image job (no push to QUAY_REPO).' - required: false - type: boolean - default: false secrets: QUAY_PASSWORD: description: 'Quay password for pushing images' required: false - QUAY_TEMP_PASSWORD: - description: 'Quay password for pushing temp tag digests' - required: false env: QUAY_REPO: quay.io/ascend/vllm-ascend - QUAY_TEMP_REPO: quay.io/atlas-ci/vllm-atlas-temp CACHE_REPO: ghcr.io/vllm-project/vllm-ascend jobs: @@ -94,47 +71,15 @@ jobs: run: | tag="${{ inputs.workflow_dispatch_tag }}" branch="${{ inputs.branch_ref }}" - vllm_commit="${{ inputs.vllm_commit }}" - vllm_ascend_commit="${{ inputs.vllm_ascend_commit }}" - - commit_mode="false" - if [[ -n "$vllm_commit" || -n "$vllm_ascend_commit" ]]; then - if [[ -z "$vllm_commit" || -z "$vllm_ascend_commit" ]]; then - echo "Error: 'vllm_commit' and 'vllm_ascend_commit' must be specified together." - exit 1 - fi - for c in "$vllm_commit" "$vllm_ascend_commit"; do - if ! echo "$c" | grep -Eq '^[0-9a-f]{7,40}$'; then - echo "Error: invalid commit format: $c (expected 7-40 hex chars)." - exit 1 - fi - done - commit_mode="true" + if [[ -n "$tag" && -n "$branch" ]]; then + echo "Error: 'tag' and 'branch' are mutually exclusive. Please specify only one." + exit 1 fi - - if [[ "$commit_mode" == "true" ]]; then - if [[ -n "$tag" || -n "$branch" ]]; then - echo "Error: commit mode is mutually exclusive with 'tag'/'branch'." - exit 1 - fi - else - if [[ -n "$tag" && -n "$branch" ]]; then - echo "Error: 'tag' and 'branch' are mutually exclusive. Please specify only one." - exit 1 - fi - if [[ -z "$tag" && -z "$branch" ]]; then - echo "Error: Either 'tag', 'branch', or (vllm_commit + vllm_ascend_commit) must be specified." - exit 1 - fi + if [[ -z "$tag" && -z "$branch" ]]; then + echo "Error: Either 'tag' or 'branch' must be specified." + exit 1 fi - - uses: actions/checkout@v6 - if: ${{ github.event_name == 'workflow_dispatch' && inputs.vllm_ascend_commit != '' }} - with: - fetch-depth: 0 - persist-credentials: false - ref: ${{ inputs.vllm_ascend_commit }} - - uses: actions/checkout@v6 if: ${{ github.event_name != 'workflow_dispatch' }} with: @@ -150,7 +95,7 @@ jobs: ref: ${{ inputs.workflow_dispatch_tag }} - uses: actions/checkout@v6 - if: ${{ github.event_name == 'workflow_dispatch' && inputs.workflow_dispatch_tag == '' && inputs.vllm_ascend_commit == '' }} + if: ${{ github.event_name == 'workflow_dispatch' && inputs.workflow_dispatch_tag == '' }} with: fetch-depth: 0 persist-credentials: false @@ -162,13 +107,13 @@ jobs: tool-cache: true docker-images: false - - name: Publish - Login to Quay Temp (for pushing temp tag digest) + - name: Publish - Login to Quay Container Registry if: ${{ inputs.should_push }} uses: docker/login-action@v4 with: registry: quay.io - username: ${{ inputs.quay_temp_username }} - password: ${{ secrets.QUAY_TEMP_PASSWORD }} + username: ${{ inputs.quay_username }} + password: ${{ secrets.QUAY_PASSWORD }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 @@ -229,11 +174,10 @@ jobs: file: ${{ inputs.dockerfile || 'Dockerfile' }} # only trigger when tag, branch/main push push: ${{ inputs.should_push }} - outputs: type=image,name=${{ env.QUAY_TEMP_REPO }},push-by-digest=true,name-canonical=true,push=${{ inputs.should_push }} + outputs: type=image,name=${{ env.QUAY_REPO }},push-by-digest=true,name-canonical=true,push=${{ inputs.should_push }} build-args: | PIP_INDEX_URL=https://pypi.org/simple COMPILE_CUSTOM_KERNELS=${{ steps.cache-csrc.outputs.cache-hit == 'true' && '0' || '1' }} - VLLM_COMMIT=${{ inputs.vllm_commit }} provenance: false # To speed up the build, we use registry cache. The cache tag is determined by the suffix and architecture, and shared across different workflow runs. # For example, the cache tag for arm64 image with suffix "openeuler" will be "buildcache-openeuler-arm64". @@ -241,28 +185,6 @@ jobs: cache-from: type=registry,ref=${{ env.CACHE_REPO }}:${{ steps.cache-tag.outputs.value }} cache-to: ${{ inputs.should_push && format('type=registry,ref={0}:{1},mode=max', env.CACHE_REPO, steps.cache-tag.outputs.value) || '' }} - # Quay.io automatically cleans up untagged digests on a regular basis, with a cycle of approximately one hour. - # Add temporary tags to digests to prevent them from being removed. - - name: Tag digest to prevent GC - if: ${{ inputs.should_push }} - run: | - SUFFIX="" - if [ -n "${{ inputs.suffix }}" ]; then - SUFFIX="-${{ inputs.suffix }}" - fi - if [ "${{ inputs.temp_only }}" = "true" ]; then - VLLM_SHORT=$(echo "${{ inputs.vllm_commit }}" | cut -c1-7) - ASCEND_SHORT=$(echo "${{ inputs.vllm_ascend_commit }}" | cut -c1-7) - TAG="vllm-ascend-${VLLM_SHORT}-${ASCEND_SHORT}${SUFFIX}-${{ matrix.tag }}-temp" - else - TAG="nightly-${{ inputs.schedule_tag_pattern }}${SUFFIX}-${{ matrix.tag }}-temp" - fi - echo "Creating temp tag: ${{ env.QUAY_TEMP_REPO }}:$TAG" - docker buildx imagetools create \ - -t "${{ env.QUAY_TEMP_REPO }}:$TAG" \ - "${{ env.QUAY_TEMP_REPO }}@${{ steps.build.outputs.digest }}" - - - name: Export digest run: | mkdir -p ${{ runner.temp }}/digests @@ -281,7 +203,7 @@ jobs: merge-image: runs-on: ubuntu-latest needs: build-push-digest - if: ${{ inputs.should_push && !inputs.temp_only }} + if: ${{ inputs.should_push }} steps: - name: Checkout branch uses: actions/checkout@v6 @@ -365,7 +287,7 @@ jobs: - name: Merge and push multi-arch image env: - IMAGE: ${{ env.QUAY_TEMP_REPO }} + IMAGE: ${{ env.QUAY_REPO }} TAGS: ${{ steps.meta.outputs.tags }} run: | DIGESTS=$(printf "$IMAGE@sha256:%s " $(ls ${{ runner.temp }}/digests)) @@ -380,52 +302,3 @@ jobs: -t "$tag" \ $DIGESTS done - - merge-image-temp: - name: merge-image-temp - runs-on: ubuntu-latest - needs: build-push-digest - if: ${{ inputs.should_push && inputs.temp_only }} - steps: - - name: Download arm64 digests - uses: actions/download-artifact@v8 - with: - path: ${{ runner.temp }}/digests - pattern: ${{ inputs.artifact_prefix }}-${{ inputs.suffix }}-arm64 - merge-multiple: true - - - name: Download amd64 digests - uses: actions/download-artifact@v8 - with: - path: ${{ runner.temp }}/digests - pattern: ${{ inputs.artifact_prefix }}-${{ inputs.suffix }}-amd64 - merge-multiple: true - - - name: Login to Quay Temp - uses: docker/login-action@v4 - with: - registry: quay.io - username: ${{ inputs.quay_temp_username }} - password: ${{ secrets.QUAY_TEMP_PASSWORD }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Merge and push multi-arch manifest to QUAY_TEMP_REPO - env: - IMAGE: ${{ env.QUAY_TEMP_REPO }} - run: | - set -euo pipefail - SUFFIX="" - if [ -n "${{ inputs.suffix }}" ]; then - SUFFIX="-${{ inputs.suffix }}" - fi - VLLM_SHORT=$(echo "${{ inputs.vllm_commit }}" | cut -c1-7) - ASCEND_SHORT=$(echo "${{ inputs.vllm_ascend_commit }}" | cut -c1-7) - TAG="${IMAGE}:vllm-ascend-${VLLM_SHORT}-${ASCEND_SHORT}${SUFFIX}" - DIGESTS=$(printf "$IMAGE@sha256:%s " $(ls ${{ runner.temp }}/digests)) - echo "Digests: $DIGESTS" - echo "Creating multi-arch manifest tag: $TAG" - docker buildx imagetools create \ - -t "$TAG" \ - $DIGESTS diff --git a/.github/workflows/_selected_tests.yaml b/.github/workflows/_selected_tests.yaml index dc1c59d4d..1b7c61108 100644 --- a/.github/workflows/_selected_tests.yaml +++ b/.github/workflows/_selected_tests.yaml @@ -32,16 +32,6 @@ on: required: false default: '' description: 'The vllm-ascend ref to test.' - upload_timing: - type: boolean - required: false - default: false - description: 'Upload test_timing_data.json as artifact' - continue_on_error: - type: boolean - required: false - default: false - description: 'Continue running the job even if tests fail' # Bash shells do not use ~/.profile or ~/.bashrc so these shells need to be explicitly # declared as "shell: bash -el {0}" on steps that need to be properly activated. @@ -53,10 +43,12 @@ defaults: permissions: contents: read pull-requests: write + issues: write + jobs: selected-tests: - name: ${{ matrix.group.npu_type }}-${{ matrix.group.num_npus }} card-${{ matrix.group.partition && format('(part {0})', matrix.group.partition) || '' }} + name: ${{ matrix.group.npu_type }}-${{ matrix.group.num_npus }}card strategy: fail-fast: false matrix: @@ -89,7 +81,7 @@ jobs: pip config set global.trusted-host cache-service.nginx-pypi-cache.svc.cluster.local apt-get update -y apt-get install -y python3-pip git vim wget net-tools gcc g++ cmake libnuma-dev curl gnupg2 zstd - git config --global --add safe.directory /__w/vllm-ascend/vllm-ascend + git config --global --add safe.directory "$GITHUB_WORKSPACE" pip install uv - name: Checkout vllm-project/vllm repo @@ -111,12 +103,12 @@ jobs: ref: ${{ inputs.ref || github.ref }} fetch-depth: 0 - - name: Rebase on latest main - run: | - git config user.name "vllm-ascend-ci" - git config user.email "vllm-ascend-ci@users.noreply.github.com" - git fetch origin main - git rebase origin/main + # - name: Rebase on latest main + # run: | + # git config user.name "vllm-ascend-ci" + # git config user.email "vllm-ascend-ci@users.noreply.github.com" + # git fetch origin main + # git rebase origin/main - name: Get csrc hash id: get_csrc_hash @@ -157,6 +149,19 @@ jobs: vllm_ascend/lib vllm_ascend/include key: vllm-ascend-build-v1-${{ steps.get_arch.outputs.arch }}-${{ matrix.group.image_tag }}-${{ steps.get_csrc_hash.outputs.CSRC_HASH }} + restore-keys: | + vllm-ascend-build-v1-${{ steps.get_arch.outputs.arch }}-${{ matrix.group.image_tag }}- + + - name: Save vllm-ascend csrc cache + if: ${{ matrix.group.npu_type != 'cpu' && steps.csrc-filter.outputs.csrc == 'true' && steps.cache-csrc.outputs.cache-hit != 'true' }} + uses: runs-on/cache/save@v5 + with: + path: | + vllm_ascend/_cann_ops_custom + vllm_ascend/*.so + vllm_ascend/lib + vllm_ascend/include + key: vllm-ascend-build-v1-${{ steps.get_arch.outputs.arch }}-${{ matrix.group.image_tag }}-${{ steps.get_csrc_hash.outputs.CSRC_HASH }} - name: Install Mooncake wheel if: ${{ matrix.group.npu_type != 'cpu' }} @@ -171,7 +176,7 @@ jobs: libcurl4 ldconfig - MOONCAKE_WHEEL="mooncake_transfer_engine_ascend-0.3.9-cp312-cp312-manylinux_2_35_aarch64.whl" + MOONCAKE_WHEEL="mooncake_transfer_engine_ascend-0.3.8.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux_2_35_aarch64.whl" pip install --no-cache-dir --no-deps \ "https://vllm-ascend.obs.cn-north-4.myhuaweicloud.com/vllm-ascend/${MOONCAKE_WHEEL}" @@ -180,6 +185,12 @@ jobs: - name: Install vllm-project/vllm-ascend with device if: ${{ matrix.group.npu_type != 'cpu' }} run: | + CACHE_HIT="${{ steps.cache-csrc.outputs.cache-hit }}" + if [ -n "$CACHE_HIT" ]; then + echo "CSRC cache restored from cache: $CACHE_HIT" + else + echo "CSRC cache miss: no cache entry found" + fi export MAX_JOBS=$(( ${{ matrix.group.num_npus }} * 23 )) pip install uc-manager uv pip install -r requirements-dev.txt @@ -189,20 +200,9 @@ jobs: COMPILE_CUSTOM_KERNELS=0 uv pip install -e . else echo "CSRC cache miss: no .so files found, compile kernels" - uv pip install -e . --no-build-isolation + uv pip install -e . fi - - name: Save vllm-ascend csrc cache - if: ${{ matrix.group.npu_type != 'cpu' && steps.csrc-filter.outputs.csrc == 'true' && steps.cache-csrc.outputs.cache-hit != 'true' }} - uses: runs-on/cache/save@v5 - with: - path: | - vllm_ascend/_cann_ops_custom - vllm_ascend/*.so - vllm_ascend/lib - vllm_ascend/include - key: vllm-ascend-build-v1-${{ steps.get_arch.outputs.arch }}-${{ matrix.group.image_tag }}-${{ steps.get_csrc_hash.outputs.CSRC_HASH }} - - name: Install vllm-project/vllm-ascend no device if: ${{ matrix.group.npu_type == 'cpu' }} env: @@ -220,25 +220,18 @@ jobs: - name: Run selected tests with device if: ${{ matrix.group.npu_type != 'cpu' }} - continue-on-error: ${{ inputs.continue_on_error }} env: VLLM_WORKER_MULTIPROC_METHOD: spawn run: | . /usr/local/Ascend/ascend-toolkit/set_env.sh - TIMING_FLAG="" - if [ "${{ inputs.upload_timing }}" = "true" ]; then - TIMING_FLAG="--timing" - fi .github/workflows/scripts/run_selected_tests.sh \ "${{ matrix.group.npu_type }}" \ "${{ matrix.group.num_npus }}" \ "with-device" \ - ${TIMING_FLAG} \ ${{ matrix.group.tests }} - name: Run selected tests without device if: ${{ matrix.group.npu_type == 'cpu' }} - continue-on-error: ${{ inputs.continue_on_error }} env: VLLM_WORKER_MULTIPROC_METHOD: spawn TORCH_DEVICE_BACKEND_AUTOLOAD: 0 @@ -249,16 +242,6 @@ jobs: "without-device" \ ${{ matrix.group.tests }} - - name: Upload timing data - if: ${{ inputs.upload_timing && matrix.group.npu_type != 'cpu' }} - continue-on-error: true - uses: actions/upload-artifact@v7 - with: - name: timing-data-${{ matrix.group.npu_type }}-${{ matrix.group.num_npus }}card-${{ matrix.group.partition }} - path: ${{ runner.temp }}/selected-tests-*/test_timing_data.json - if-no-files-found: ignore - retention-days: 7 - - name: Upload selected test logs if: always() continue-on-error: true diff --git a/.github/workflows/bot_issue_manage.yaml b/.github/workflows/bot_issue_manage.yaml index a027d5568..6083f52fa 100644 --- a/.github/workflows/bot_issue_manage.yaml +++ b/.github/workflows/bot_issue_manage.yaml @@ -9,7 +9,7 @@ permissions: jobs: triage: - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest if: | startsWith(github.event.issue.title, '[Bug]:') || startsWith(github.event.issue.title, '[Installation]:') || @@ -23,4 +23,4 @@ jobs: enable-versioned-regex: 0 repo-token: ${{ secrets.GITHUB_TOKEN }} include-title: 1 - include-body: 0 + include-body: 0 \ No newline at end of file diff --git a/.github/workflows/bot_merge_conflict.yaml b/.github/workflows/bot_merge_conflict.yaml index e1778a007..8515a1232 100644 --- a/.github/workflows/bot_merge_conflict.yaml +++ b/.github/workflows/bot_merge_conflict.yaml @@ -1,12 +1,16 @@ name: Merge Conflict Labeler on: + # So that PRs touching the same files as the push are updated push: + # So that the `dirtyLabel` is removed if conflicts are resolve + # We recommend `pull_request_target` so that github secrets are available. + # In `pull_request` we wouldn't be able to change labels of fork PRs pull_request_target: types: [synchronize] jobs: main: - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest steps: - name: check if prs are dirty uses: eps1lon/actions-label-merge-conflict@v3 diff --git a/.github/workflows/bot_pr_create.yaml b/.github/workflows/bot_pr_create.yaml index 51afe3bf6..a630b4b27 100644 --- a/.github/workflows/bot_pr_create.yaml +++ b/.github/workflows/bot_pr_create.yaml @@ -18,7 +18,11 @@ name: PR Create on: + # The PR updated when PR opened and push new commits pull_request_target: + types: [opened, synchronize] + branches: + - 'main' permissions: pull-requests: write @@ -29,43 +33,29 @@ jobs: contents: read pull-requests: write name: PR create action - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest steps: - - name: Install system dependencies - run: | - sudo apt-get update -y && sudo apt-get install -y jq curl - sudo mkdir -p -m 755 /etc/apt/keyrings - curl -sL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install gh -y + - name: Checkout repository + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v4.2.2 - - name: Fetch required files - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + + - name: Install docs dependencies run: | - REPO="${{ github.repository }}" - REF="${{ github.event.pull_request.base.ref }}" - mkdir -p .github - for file in vllm-main-verified.commit vllm-release-tag.commit labeler.yml; do - curl -sSf -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$REPO/contents/.github/$file?ref=$REF" \ - | jq -r '.content' | base64 -d > ".github/$file" - done + pip install docutils sphinx sphinx-book-theme myst-parser sphinx-argparse sphinx-design - name: Get vLLM version run: | - VLLM_COMMIT=$(tr -d '[:space:]' < .github/vllm-main-verified.commit) + VLLM_COMMIT=$(python3 docs/source/conf.py | jq .main_vllm_commit | tr -d '"') echo "VLLM_COMMIT=https://github.com/vllm-project/vllm/commit/$VLLM_COMMIT" >> "$GITHUB_ENV" - VLLM_VERSION=$(tr -d '[:space:]' < .github/vllm-release-tag.commit) + VLLM_VERSION=$(python3 docs/source/conf.py | jq .main_vllm_tag | tr -d '"') echo "VLLM_VERSION=$VLLM_VERSION" >> "$GITHUB_ENV" - name: Update PR description env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} run: | PR_NUMBER=${{ github.event.number }} VLLM_VERSION=${{ env.VLLM_VERSION }} @@ -109,7 +99,7 @@ jobs: - name: Remind to run full CI on PR if: github.event.action == 'opened' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | github.rest.issues.createComment({ diff --git a/.github/workflows/configs/nightly_config.yaml b/.github/workflows/configs/nightly_config.yaml new file mode 100644 index 000000000..3566ccf41 --- /dev/null +++ b/.github/workflows/configs/nightly_config.yaml @@ -0,0 +1,191 @@ +# Single source of truth for the Nightly-A2 / Nightly-A3 test matrix. +# SOC differentiation: A2 runs on a2b3 runners and -a2 images, A3 runs on +# a3 runners and -a3 images. Adding a new test case is just appending a +# `- name: ...` entry under the matching section. PRs that add entries +# here can be exercised via `/nightly ` without merging to main. + +a2: + single_node: + test_config: + # pytest-driven tests + - name: test_custom_op_multi_card + os: linux-aarch64-a2b3-4 + tests: tests/e2e/nightly/single_node/ops/multicard_ops_a2/ + # YAML-driven tests + - name: qwen3-vl-32b-instruct-w8a8 + os: linux-aarch64-a2b3-4 + config_file_path: Qwen3-VL-32B-Instruct-W8A8.yaml + - name: qwen3-32b-int8 + os: linux-aarch64-a2b3-4 + config_file_path: Qwen3-32B-Int8-A2.yaml + - name: Qwen3.5-27B-w8a8-A2 + os: linux-aarch64-a2b3-2 + config_file_path: Qwen3.5-27B-w8a8-A2.yaml + - name: Qwen3.5-27B-w8a8-A2-bak + os: linux-aarch64-a2b3-2 + config_file_path: Qwen3.5-27B-w8a8-A2-bak.yaml + - name: Qwen3.5-397B-A17B-w4a8-mtp + os: linux-aarch64-a2b3-8 + config_file_path: Qwen3.5-397B-A17B-w4a8-mtp-A2.yaml + multi_node: + test_config: + - name: multi-node-qwen3-235b-dp + config_file_path: Qwen3-235B-A22B-A2.yaml + size: 2 + - name: multi-node-GLM-5.1-w8a8-A2 + config_file_path: GLM5_1-W8A8-A2-dual-nodes.yaml + size: 2 + - name: multi-node-Kimi-K2.5-W4A8-A2 + config_file_path: Kimi-K2_5-W4A8-A2-dual-nodes.yaml + size: 2 + accuracy: + nightly: + - name: accuracy-group-1 + os: linux-aarch64-a2b3-1 + model_list: + - Qwen3-VL-8B-Instruct-W8A8 + - Qwen3-8B + - Qwen2-Audio-7B-Instruct + - Qwen3-8B-W8A8 + - Qwen3-VL-8B-Instruct + - Minitron-8B-Base + - name: accuracy-group-2 + os: linux-aarch64-a2b3-1 + model_list: + - ERNIE-4.5-21B-A3B-PT + - Molmo-7B-D-0924 + - Llama-3.2-3B-Instruct + - name: accuracy-group-3 + os: linux-aarch64-a2b3-2 + model_list: + - Qwen3-30B-A3B + - Qwen3-VL-30B-A3B-Instruct + - Qwen3-30B-A3B-W8A8 + - name: accuracy-group-4 + os: linux-aarch64-a2b3-4 + model_list: + - Qwen3-Next-80B-A3B-Instruct + - Qwen3-Omni-30B-A3B-Instruct + - Mixtral-8x7B-Instruct-v0.1 + pr_only: + - name: pr-accuracy-group-1 + os: linux-aarch64-a2b3-1 + model_list: + - gemma-3-4b-it + - internlm3-8b-instruct + - Qwen3-ASR-1.7B + - InternVL3_5-8B-hf + - llava-onevision-qwen2-0.5b-ov-hf + - name: pr-accuracy-group-2 + os: linux-aarch64-a2b3-4 + model_list: + - Qwen2.5-Math-RM-72B + - Hunyuan-A13B-Instruct + +a3: + multi_node: + test_config: + - name: multi-node-deepseek-v3.2-W8A8-EP + config_file_path: DeepSeek-V3_2-W8A8-EP.yaml + size: 4 + double_node: + test_config: + - name: multi-node-deepseek-r1-w8a8-longseq + config_file_path: DeepSeek-R1-W8A8-longseq.yaml + size: 2 + - name: multi-node-qwen3-dp + config_file_path: Qwen3-235B-A22B.yaml + size: 2 + - name: multi-node-qwenw8a8-2node-eplb + config_file_path: Qwen3-235B-W8A8-EPLB.yaml + size: 2 + - name: multi-node-dpsk3.2-2node + config_file_path: DeepSeek-V3_2-W8A8-A3-dual-nodes.yaml + size: 2 + - name: multi-node-qwen3-dp-mooncake-layerwise + config_file_path: Qwen3-235B-A22B-Mooncake-Layerwise.yaml + size: 2 + - name: multi-node-qwenw8a8-2node-longseq + config_file_path: Qwen3-235B-W8A8-longseq.yaml + size: 2 + - name: multi-node-qwen-disagg-pd + config_file_path: Qwen3-235B-disagg-pd.yaml + size: 2 + - name: multi-node-qwen-vl-disagg-pd + config_file_path: Qwen3-VL-235B-disagg-pd.yaml + size: 2 + - name: multi-node-deepseek-v3.1 + config_file_path: DeepSeek-V3.1-BF16.yaml + size: 2 + - name: multi-node-GLM-5.1-w8a8-A3 + config_file_path: GLM5_1-W8A8-A3-dual-nodes.yaml + size: 2 + single_node: + test_config: + - name: mtpx-deepseek-r1-0528-w8a8 + os: linux-aarch64-a3-16 + config_file_path: MTPX-DeepSeek-R1-0528-W8A8.yaml + - name: deepseek-r1-0528-w8a8 + os: linux-aarch64-a3-16 + config_file_path: DeepSeek-R1-0528-W8A8.yaml + - name: kimi-k2-thinking + os: linux-aarch64-a3-16 + config_file_path: Kimi-K2-Thinking.yaml + - name: qwen3-vl-235b-a22b-instruct-w8a8 + os: linux-aarch64-a3-16 + config_file_path: Qwen3-VL-235B-A22B-Instruct-W8A8.yaml + - name: deepseek-r1-0528-w8a8-prefix-cache + os: linux-aarch64-a3-16 + config_file_path: Prefix-Cache-DeepSeek-R1-0528-W8A8.yaml + - name: deepseek-v3-2-w8a8 + os: linux-aarch64-a3-16 + config_file_path: DeepSeek-V3.2-W8A8.yaml + - name: glm-4.7-w8a8 + os: linux-aarch64-a3-16 + config_file_path: GLM-4.7.yaml + - name: kimi-k2.5 + os: linux-aarch64-a3-16 + config_file_path: Kimi-K2.5.yaml + - name: qwen3-235b-a22b-w8a8 + os: linux-aarch64-a3-16 + config_file_path: Qwen3-235B-A22B-W8A8.yaml + - name: Qwen3.5-397B-A17B-w8a8-mtp + os: linux-aarch64-a3-16 + config_file_path: Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml + - name: MiniMax-M2.5-w8a8-QuaRot-A3 + os: linux-aarch64-a3-16 + config_file_path: MiniMax-M2.5-w8a8-QuaRot-A3.yaml + - name: Qwen3.5-27B-w8a8-A3 + os: linux-aarch64-a3-2 + config_file_path: Qwen3.5-27B-w8a8-A3.yaml + - name: Qwen3.5-122B-A10B-W8A8-A3 + os: linux-aarch64-a3-16 + config_file_path: Qwen3.5-122B-A10B-W8A8-A3.yaml + - name: DeepSeek-V4-Flash-W8A8-A3 + os: linux-aarch64-a3-16 + config_file_path: DeepSeek-V4-Flash-W8A8-A3.yaml + multi_card: + test_config: + # pytest-driven tests + - name: qwen3-30b-acc + os: linux-aarch64-a3-4 + tests: tests/e2e/weekly/single_node/models/test_qwen3_30b_acc.py + # YAML-driven tests + - name: qwen3-30b-a3b-w8a8 + os: linux-aarch64-a3-4 + config_file_path: Qwen3-30B-A3B-W8A8.yaml + - name: qwen3-32b-int8 + os: linux-aarch64-a3-4 + config_file_path: Qwen3-32B-Int8.yaml + - name: qwen3-32b-int8-prefix-cache + os: linux-aarch64-a3-4 + config_file_path: Prefix-Cache-Qwen3-32B-Int8.yaml + - name: Qwen3-30B-A3B-W4A8-llm-compressor + os: linux-aarch64-a3-2 + config_file_path: Qwen3-30B-A3B-W4A8-llm-compressor.yaml + - name: Qwen3-30B-QuaRot + os: linux-aarch64-a3-2 + config_file_path: Qwen3-30B-QuaRot-eagle3.yaml + - name: Qwen3-32B-QuaRot + os: linux-aarch64-a3-2 + config_file_path: Qwen3-32B-QuaRot-eagle3.yaml diff --git a/.github/workflows/dockerfiles/Dockerfile.buildwheel.310p b/.github/workflows/dockerfiles/Dockerfile.buildwheel.310p index 427d50a8b..6ca4382f7 100644 --- a/.github/workflows/dockerfiles/Dockerfile.buildwheel.310p +++ b/.github/workflows/dockerfiles/Dockerfile.buildwheel.310p @@ -15,7 +15,7 @@ # This file is a part of the vllm-ascend project. # ARG PY_VERSION=3.12 -FROM quay.io/ascend/manylinux:9.1.0-beta.1-310p-manylinux_2_34-py${PY_VERSION} +FROM quay.io/ascend/manylinux:9.1.0-beta.1-310p-manylinux_2_28-py${PY_VERSION} ARG SOC_VERSION="ascend310p1" diff --git a/.github/workflows/dockerfiles/Dockerfile.lint b/.github/workflows/dockerfiles/Dockerfile.lint index 6a1e59e54..30b807336 100644 --- a/.github/workflows/dockerfiles/Dockerfile.lint +++ b/.github/workflows/dockerfiles/Dockerfile.lint @@ -15,7 +15,7 @@ # This file is a part of the vllm-ascend project. # -FROM ascendai/python:3.12-ubuntu22.04 +FROM ascendai/python:3.11-ubuntu22.04 ARG TARGETARCH @@ -27,9 +27,8 @@ RUN apt-get update -y && \ ARG VLLM_REPO=https://github.com/vllm-project/vllm.git # For lint purpose, actually we need make a main2main matching. -ARG VLLM_COMMIT -RUN [ -n "$VLLM_COMMIT" ] || { echo "VLLM_COMMIT is empty"; exit 1; } && \ - git init /vllm-workspace/vllm && \ +ARG VLLM_COMMIT=9090368b650896bf5fc990c921df7eb4c20355a5 +RUN git init /vllm-workspace/vllm && \ git -C /vllm-workspace/vllm fetch --depth 1 $VLLM_REPO $VLLM_COMMIT && \ git -C /vllm-workspace/vllm checkout FETCH_HEAD diff --git a/.github/workflows/dockerfiles/Dockerfile.nightly.a2 b/.github/workflows/dockerfiles/Dockerfile.nightly.a2 index 23b8ea2e2..c860856e7 100644 --- a/.github/workflows/dockerfiles/Dockerfile.nightly.a2 +++ b/.github/workflows/dockerfiles/Dockerfile.nightly.a2 @@ -19,7 +19,7 @@ ARG VLLM_ASCEND_BRANCH="" FROM quay.io/ascend/vllm-ascend:nightly-${VLLM_ASCEND_BRANCH} ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG AIS_BENCH_TAG="v3.1-20260609-master" +ARG AIS_BENCH_TAG="v3.1-20260429-master" ARG AIS_BENCH_URL="https://github.com/AISBench/benchmark.git" ARG GITEE_USERNAME="" ARG GITEE_TOKEN="" diff --git a/.github/workflows/dockerfiles/Dockerfile.nightly.a3 b/.github/workflows/dockerfiles/Dockerfile.nightly.a3 index 2bfeafeed..03a475b51 100644 --- a/.github/workflows/dockerfiles/Dockerfile.nightly.a3 +++ b/.github/workflows/dockerfiles/Dockerfile.nightly.a3 @@ -19,7 +19,7 @@ ARG VLLM_ASCEND_BRANCH="" FROM quay.io/ascend/vllm-ascend:nightly-${VLLM_ASCEND_BRANCH}-a3 ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG AIS_BENCH_TAG="v3.1-20260609-master" +ARG AIS_BENCH_TAG="v3.1-20260429-master" ARG AIS_BENCH_URL="https://github.com/AISBench/benchmark.git" ARG GITEE_USERNAME="" ARG GITEE_TOKEN="" diff --git a/.github/workflows/labeled_doctest.yaml b/.github/workflows/labeled_doctest.yaml index 553fc2d4b..b9d28553f 100644 --- a/.github/workflows/labeled_doctest.yaml +++ b/.github/workflows/labeled_doctest.yaml @@ -19,19 +19,7 @@ name: Doc Test on: workflow_dispatch: - inputs: - doc_versions: - description: 'JSON array of Read the Docs versions to test, e.g. ["v0.18.0","latest"]' - required: true - type: string - default: '["v0.18.0","latest"]' workflow_call: - inputs: - doc_versions: - description: 'JSON array of Read the Docs versions to test' - required: false - type: string - default: '["v0.18.0","latest"]' pull_request: branches: - 'main' @@ -40,12 +28,9 @@ on: paths: # If we are changing the doctest we should do a PR test - '.github/workflows/labeled_doctest.yaml' - - 'docs/source/conf.py' - 'tests/e2e/doctests/**' - 'tests/e2e/common.sh' - 'tests/e2e/run_doctests.sh' -permissions: - contents: read # Bash shells do not use ~/.profile or ~/.bashrc so these shells need to be explicitly # declared as "shell: bash -el {0}" on steps that need to be properly activated. @@ -59,223 +44,49 @@ concurrency: cancel-in-progress: true jobs: - setup-matrix: - name: Setup test matrix - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.set_matrix_pr.outputs.matrix || steps.set_matrix_doc.outputs.matrix }} - steps: - # PR runs should validate the PR content itself. The matrix is generated - # from this checkout instead of resolving a Read the Docs version. - - name: Checkout current source - if: github.event_name == 'pull_request' - uses: actions/checkout@v6 - with: - path: matrix-src - - - name: Set matrix (PR-driven) - if: github.event_name == 'pull_request' - id: set_matrix_pr - run: | - set -euo pipefail - - matrix_file="$(mktemp)" - trap 'rm -f "${matrix_file}"' EXIT - echo '{"include":[]}' > "${matrix_file}" - - doc_repository="${GITHUB_REPOSITORY}" - doc_version="pull_request" - doc_ref="${GITHUB_REF}" - doc_verbose_name="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME}}" - documentation_url="" - repo_dir="${GITHUB_WORKSPACE}/matrix-src" - - vllm_ascend_version="$(python3 "${repo_dir}/docs/source/conf.py" \ - | jq -r '.vllm_ascend_version // empty')" - if [ -z "${vllm_ascend_version}" ]; then - echo "Failed to resolve vllm_ascend_version from ${doc_ref}." >&2 - exit 1 - fi - - for os_variant in ubuntu openeuler; do - if [ "${os_variant}" = "ubuntu" ]; then - image="quay.io/ascend/vllm-ascend:${vllm_ascend_version}" - else - image="quay.io/ascend/vllm-ascend:${vllm_ascend_version}-${os_variant}" - fi - - next_matrix_file="$(mktemp)" - jq \ - --arg doc_repository "${doc_repository}" \ - --arg doc_version "${doc_version}" \ - --arg doc_ref "${doc_ref}" \ - --arg doc_verbose_name "${doc_verbose_name}" \ - --arg documentation_url "${documentation_url}" \ - --arg vllm_ascend_version "${vllm_ascend_version}" \ - --arg os_variant "${os_variant}" \ - --arg image "${image}" \ - '.include += [{ - doc_repository: $doc_repository, - doc_version: $doc_version, - doc_ref: $doc_ref, - doc_verbose_name: $doc_verbose_name, - documentation_url: $documentation_url, - vllm_ascend_version: $vllm_ascend_version, - os_variant: $os_variant, - image: $image - }]' "${matrix_file}" > "${next_matrix_file}" - mv "${next_matrix_file}" "${matrix_file}" - done - - echo "Resolved PR-driven doctest matrix:" - jq . "${matrix_file}" - - { - echo "matrix<> "${GITHUB_OUTPUT}" - - - name: Set matrix (doc-driven) - if: github.event_name != 'pull_request' - id: set_matrix_doc - env: - INPUT_DOC_VERSIONS: ${{ inputs.doc_versions || '["v0.18.0","latest"]' }} - run: | - set -euo pipefail - - matrix_file="$(mktemp)" - workdir="$(mktemp -d)" - trap 'rm -rf "${matrix_file}" "${workdir}"' EXIT - echo '{"include":[]}' > "${matrix_file}" - - # Manual and scheduled runs are version-driven. Resolve each RTD - # version to the Git ref that owns the published documentation. - echo "${INPUT_DOC_VERSIONS}" | jq -e 'type == "array" and all(.[]; type == "string")' >/dev/null - - while IFS= read -r doc_version; do - version_url="https://readthedocs.org/api/v3/projects/vllm-ascend/versions/${doc_version}/" - metadata_file="${workdir}/${doc_version}.json" - repo_dir="${workdir}/repo-${doc_version//[^A-Za-z0-9._-]/-}" - - echo "Resolving Read the Docs version: ${doc_version}" - curl --fail --show-error --location \ - --retry 3 \ - --retry-delay 5 \ - "${version_url}" \ - -o "${metadata_file}" - - doc_ref="$(jq -r '.identifier // empty' "${metadata_file}")" - doc_verbose_name="$(jq -r '.verbose_name // empty' "${metadata_file}")" - documentation_url="$(jq -r '.urls.documentation // empty' "${metadata_file}")" - - if [ -z "${doc_ref}" ]; then - echo "Read the Docs version ${doc_version} does not have an identifier." >&2 - exit 1 - fi - - git clone --depth 1 --branch "${doc_ref}" \ - https://github.com/vllm-project/vllm-ascend.git "${repo_dir}" - - doc_repository="vllm-project/vllm-ascend" - vllm_ascend_version="$(python3 "${repo_dir}/docs/source/conf.py" \ - | jq -r '.vllm_ascend_version // empty')" - if [ -z "${vllm_ascend_version}" ]; then - echo "Failed to resolve vllm_ascend_version from ${doc_ref}." >&2 - exit 1 - fi - - for os_variant in ubuntu openeuler; do - if [ "${os_variant}" = "ubuntu" ]; then - image="quay.io/ascend/vllm-ascend:${vllm_ascend_version}" - else - image="quay.io/ascend/vllm-ascend:${vllm_ascend_version}-${os_variant}" - fi - - next_matrix_file="$(mktemp)" - jq \ - --arg doc_repository "${doc_repository}" \ - --arg doc_version "${doc_version}" \ - --arg doc_ref "${doc_ref}" \ - --arg doc_verbose_name "${doc_verbose_name}" \ - --arg documentation_url "${documentation_url}" \ - --arg vllm_ascend_version "${vllm_ascend_version}" \ - --arg os_variant "${os_variant}" \ - --arg image "${image}" \ - '.include += [{ - doc_repository: $doc_repository, - doc_version: $doc_version, - doc_ref: $doc_ref, - doc_verbose_name: $doc_verbose_name, - documentation_url: $documentation_url, - vllm_ascend_version: $vllm_ascend_version, - os_variant: $os_variant, - image: $image - }]' "${matrix_file}" > "${next_matrix_file}" - mv "${next_matrix_file}" "${matrix_file}" - done - done < <(echo "${INPUT_DOC_VERSIONS}" | jq -r '.[]') - - echo "Resolved doc-driven doctest matrix:" - jq . "${matrix_file}" - - { - echo "matrix<> "${GITHUB_OUTPUT}" - test: - needs: setup-matrix strategy: # Each version should be tested fail-fast: false - matrix: ${{ fromJSON(needs.setup-matrix.outputs.matrix) }} - name: vLLM Ascend test (${{ matrix.doc_version }}, ${{ matrix.os_variant }}) + matrix: + vllm_version: [nightly-releases-v0.18.0, nightly-releases-v0.18.0-openeuler, nightly-main, nightly-main-openeuler] + name: vLLM Ascend test runs-on: linux-aarch64-a2b3-1 container: - image: ${{ matrix.image }} + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/vllm-ascend:${{ matrix.vllm_version }} steps: - # setup-matrix decides whether this is a PR ref or an RTD identifier, so - # the test job can use the same checkout step for both trigger types. - - name: Checkout doctest source - uses: actions/checkout@v6 - with: - repository: ${{ matrix.doc_repository }} - ref: ${{ matrix.doc_ref }} - path: doctest-src - - name: Check NPU/CANN and git info run: | - echo "====> Print doctest source info" - echo "Doc repository: ${{ matrix.doc_repository }}" - echo "Doc version: ${{ matrix.doc_version }}" - echo "Doc ref: ${{ matrix.doc_ref }}" - echo "Doc verbose name: ${{ matrix.doc_verbose_name }}" - echo "Documentation URL: ${{ matrix.documentation_url }}" - echo "vllm-ascend version: ${{ matrix.vllm_ascend_version }}" - echo "Container image: ${{ matrix.image }}" - echo "====> Print NPU/CANN info" npu-smi info cat /usr/local/Ascend/ascend-toolkit/latest/"$(uname -i)"-linux/ascend_toolkit_install.info - echo "====> Print doctest source git info" - git -C "${GITHUB_WORKSPACE}/doctest-src" --no-pager log -1 - echo "====> Print doctest source version info" - python3 "${GITHUB_WORKSPACE}/doctest-src/docs/source/conf.py" | jq . + echo "====> Print vllm-ascend git info" + cd /vllm-workspace/vllm-ascend + git --no-pager log -1 || true + echo "====> Print vllm git info" + cd /vllm-workspace/vllm + git --no-pager log -1 || true - echo "====> Print container vllm-ascend git info" - git -C /vllm-workspace/vllm-ascend --no-pager log -1 || true - echo "====> Print container vllm git info" - git -C /vllm-workspace/vllm --no-pager log -1 || true + - name: Checkout vllm-project/vllm-ascend repo + uses: actions/checkout@v6 - - name: Run doctest source tests/e2e/run_doctests.sh + - name: Run vllm-ascend/tests/e2e/run_doctests.sh run: | - # Avoid putting the checkout root on Python's import path. The test - # should use doctest scripts from the checkout and packages installed - # in the container image. + # PWD: /__w/vllm-ascend/vllm-ascend + # Make sure e2e tests are latest + echo "Replacing /vllm-workspace/vllm-ascend/tests/e2e ..." + rm -rf /vllm-workspace/vllm-ascend/tests/e2e + mkdir -p /vllm-workspace/vllm-ascend/tests + # Overwrite e2e and examples + cp -r tests/e2e /vllm-workspace/vllm-ascend/tests/ + cp -r examples /vllm-workspace/vllm-ascend/ + # We are now maintain version policy in the main, so we need to copy docs to make sure the doctest can be run successfully. + cp -r docs /vllm-workspace/vllm-ascend/ + + # Simulate container to enter directory cd /workspace + # Run real test echo "Test:" - bash "${GITHUB_WORKSPACE}/doctest-src/tests/e2e/run_doctests.sh" + /vllm-workspace/vllm-ascend/tests/e2e/run_doctests.sh diff --git a/.github/workflows/pr_close_cancel_job.yaml b/.github/workflows/pr_close_cancel_job.yaml index 946e4e0cc..667312906 100644 --- a/.github/workflows/pr_close_cancel_job.yaml +++ b/.github/workflows/pr_close_cancel_job.yaml @@ -9,9 +9,9 @@ permissions: jobs: cancel: - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest steps: - - uses: actions/github-script@v9 + - uses: actions/github-script@v8 with: github-token: ${{ github.token }} script: | diff --git a/.github/workflows/pr_e2e_command.yml b/.github/workflows/pr_e2e_command.yml index 18d574a2e..f2ac9daee 100644 --- a/.github/workflows/pr_e2e_command.yml +++ b/.github/workflows/pr_e2e_command.yml @@ -34,34 +34,18 @@ permissions: jobs: e2e-test: name: Run /e2e tests - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest outputs: pr_sha: ${{ steps.resolve.outputs.pr_sha }} test_groups: ${{ steps.groups.outputs.test_groups }} has_tests: ${{ steps.groups.outputs.has_tests }} + vllm_versions: ${{ steps.resolve.outputs.vllm_versions }} notify_comment_id: ${{ steps.notify.outputs.comment-id }} - main_commit: ${{ steps.vllm.outputs.main_commit }} - release_tag: ${{ steps.vllm.outputs.release_tag }} steps: - - name: Install system dependencies - run: | - sudo apt-get update -y && sudo apt-get install -y curl - sudo mkdir -p -m 755 /etc/apt/keyrings - curl -sL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install gh -y - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - name: Check user authorization id: auth env: - GH_TOKEN: ${{ secrets.PAT_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | ACTOR='${{ github.event.client_payload.github.payload.comment.user.login }}' PR_AUTHOR='${{ github.event.client_payload.pull_request.user.login }}' @@ -71,10 +55,10 @@ jobs: echo "User $ACTOR is the PR author, authorized." echo "authorized=true" >> "$GITHUB_OUTPUT" else - ROLE_NAME=$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq '.role_name' 2>/dev/null || true) - case "$ROLE_NAME" in + PERMISSION=$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq '.permission') + case "$PERMISSION" in triage|write|maintain|admin) - echo "User $ACTOR has $ROLE_NAME role, authorized." + echo "User $ACTOR has $PERMISSION permission, authorized." echo "authorized=true" >> "$GITHUB_OUTPUT" ;; *) @@ -112,12 +96,15 @@ jobs: run: exit 1 - name: Checkout repo + if: steps.auth.outputs.authorized == 'true' uses: actions/checkout@v6 with: + ref: ${{ github.event.client_payload.pull_request.head.sha }} fetch-depth: 1 - name: Read verified vLLM refs id: vllm + if: steps.auth.outputs.authorized == 'true' run: | main_commit="$(tr -d '[:space:]' < .github/vllm-main-verified.commit)" release_tag="$(tr -d '[:space:]' < .github/vllm-release-tag.commit)" @@ -136,19 +123,50 @@ jobs: - name: Resolve PR info and test names id: resolve + if: steps.auth.outputs.authorized == 'true' run: | PR_SHA='${{ github.event.client_payload.pull_request.head.sha }}' echo "pr_sha=$PR_SHA" >> "$GITHUB_OUTPUT" ALL_ARGS="${{ github.event.client_payload.slash_command.args.all }}" - echo "all_args=$ALL_ARGS" >> "$GITHUB_OUTPUT" + + DEFAULT_VLLM_VERSIONS="[\"${{ steps.vllm.outputs.main_commit }}\",\"${{ steps.vllm.outputs.release_tag }}\"]" + VLLM_VERSIONS="$DEFAULT_VLLM_VERSIONS" + CLEAN_ARGS="" + NEXT_IS_VLLM=0 + VLLM_FLAG_COUNT=0 + for WORD in $(echo "$ALL_ARGS" | tr '\n' ' ' | xargs); do + if [ "$NEXT_IS_VLLM" = "1" ]; then + VLLM_VERSIONS=$(printf '%s' "$WORD" | python3 -c "import sys,json; print(json.dumps([v for v in sys.stdin.read().strip().split(',') if v]))") + NEXT_IS_VLLM=0 + elif [ "$WORD" = "--vllm" ]; then + NEXT_IS_VLLM=1 + VLLM_FLAG_COUNT=$((VLLM_FLAG_COUNT + 1)) + if [ "$VLLM_FLAG_COUNT" -gt 1 ]; then + echo "::error::--vllm can only be specified once." + exit 1 + fi + else + CLEAN_ARGS="$CLEAN_ARGS $WORD" + fi + done + + if [ "$NEXT_IS_VLLM" = "1" ]; then + echo "::error::--vllm requires a value." + exit 1 + fi + + CLEAN_ARGS=$(echo "$CLEAN_ARGS" | xargs) + echo "all_args=$CLEAN_ARGS" >> "$GITHUB_OUTPUT" + echo "vllm_versions=$VLLM_VERSIONS" >> "$GITHUB_OUTPUT" - name: Validate test paths id: validate + if: steps.auth.outputs.authorized == 'true' env: ALL_ARGS: ${{ steps.resolve.outputs.all_args }} run: | - TESTS=$(echo "$ALL_ARGS" | tr '\n' ' ') + TESTS=$(echo "$ALL_ARGS" | tr '\n' ' ' | xargs) if [ -z "$TESTS" ]; then echo "::error::No test paths provided. Usage: /e2e [test-path-2] ..." exit 1 @@ -165,27 +183,20 @@ jobs: - name: Categorize tests by runner id: groups + if: steps.auth.outputs.authorized == 'true' run: | - pip install regex pyyaml + pip install regex TESTS=$(echo "${{ steps.validate.outputs.tests }}" | tr '\n' ' ') read -ra TEST_ARRAY <<< "$TESTS" python3 .github/workflows/scripts/select_tests.py \ - --explicit-e2e-tests "${TEST_ARRAY[@]}" - - - name: Fail if no valid tests - if: steps.groups.outputs.has_tests != 'true' - run: | - echo "::error::No valid test paths matched. Please verify the paths exist and try again." - exit 1 + --changed-files "${TEST_ARRAY[@]}" trigger-selected-tests: name: e2e-comment (${{ matrix.vllm_version }}) strategy: fail-fast: false matrix: - vllm_version: - - ${{ needs.e2e-test.outputs.main_commit }} - - ${{ needs.e2e-test.outputs.release_tag }} + vllm_version: ${{ fromJSON(needs.e2e-test.outputs.vllm_versions) }} needs: [e2e-test] if: ${{ needs.e2e-test.outputs.has_tests == 'true' }} uses: ./.github/workflows/_selected_tests.yaml @@ -198,7 +209,7 @@ jobs: name: Report result needs: [e2e-test, trigger-selected-tests] if: always() && needs.e2e-test.result == 'success' && needs.e2e-test.outputs.notify_comment_id != '' - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest steps: - name: Update comment with result uses: peter-evans/create-or-update-comment@v5 @@ -207,5 +218,5 @@ jobs: repository: ${{ github.event.client_payload.github.payload.repository.full_name }} comment-id: ${{ needs.e2e-test.outputs.notify_comment_id }} body: | - [Bot]: e2e command ${{ (needs.trigger-selected-tests.result == 'success' || needs.trigger-selected-tests.result == 'skipped') && 'completed successfully' || 'failed' }}. - reactions: ${{ (needs.trigger-selected-tests.result == 'success' || needs.trigger-selected-tests.result == 'skipped') && 'hooray' || 'confused' }} + [Bot]: e2e command ${{ (needs.trigger-selected-tests.result == 'success' && 'completed successfully') || (needs.trigger-selected-tests.result == 'skipped' && 'found no tests to run (please check the test paths)') || (needs.trigger-selected-tests.result == 'cancelled' && 'was cancelled') || 'failed' }}. + reactions: ${{ (needs.trigger-selected-tests.result == 'success' && 'hooray') || 'confused' }} diff --git a/.github/workflows/pr_nightly_command.yml b/.github/workflows/pr_nightly_command.yml index 8c7dfa9ea..84c5eca9b 100644 --- a/.github/workflows/pr_nightly_command.yml +++ b/.github/workflows/pr_nightly_command.yml @@ -30,7 +30,7 @@ permissions: jobs: authorize: name: Check user authorization - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest outputs: test_cases: ${{ steps.resolve.outputs.test_cases }} branch: ${{ steps.resolve.outputs.branch }} @@ -39,23 +39,8 @@ jobs: dispatch_a3: ${{ steps.resolve.outputs.dispatch_a3 }} vllm_ascend_ref: ${{ steps.resolve.outputs.vllm_ascend_ref }} steps: - - name: Install system dependencies - run: | - sudo apt-get update -y && sudo apt-get install -y curl - sudo mkdir -p -m 755 /etc/apt/keyrings - curl -sL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install gh -y - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.12' - - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: token: ${{ secrets.PAT_TOKEN }} sparse-checkout: | @@ -153,15 +138,11 @@ jobs: exit 0 fi - # Parse test matrices from the PR branch's workflow YAML files + # Parse test matrices from the PR branch's nightly config pip install pyyaml -q 2>/dev/null - A2_RAW=$(gh api "repos/$HEAD_REPO/contents/.github/workflows/schedule_nightly_test_a2.yaml?ref=$PR_BRANCH" --jq '.content' 2>/dev/null || echo "") - export A2_RAW - A3_RAW=$(gh api "repos/$HEAD_REPO/contents/.github/workflows/schedule_nightly_test_a3.yaml?ref=$PR_BRANCH" --jq '.content' 2>/dev/null || echo "") - export A3_RAW - A2_ACC_GROUPS=$(gh api "repos/$HEAD_REPO/contents/tests/e2e/models/configs/accuracy_groups_a2.json?ref=$PR_BRANCH" --jq '.content' 2>/dev/null || echo "") - export A2_ACC_GROUPS + NIGHTLY_MATRIX=$(gh api "repos/$HEAD_REPO/contents/.github/workflows/configs/nightly_config.yaml?ref=$PR_BRANCH" --jq '.content' 2>/dev/null || echo "") + export NIGHTLY_MATRIX export TEST_CASES python3 .github/workflows/scripts/resolve_nightly_tests.py @@ -170,20 +151,10 @@ jobs: name: Trigger Nightly-A2 needs: [authorize] if: needs.authorize.outputs.is_authorized == 'true' && needs.authorize.outputs.dispatch_a2 == 'true' - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest outputs: a2_run_id: ${{ steps.dispatch_a2.outputs.a2_run_id }} steps: - - name: Install system dependencies - run: | - sudo apt-get update -y && sudo apt-get install -y curl - sudo mkdir -p -m 755 /etc/apt/keyrings - curl -sL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install gh -y - - name: Dispatch nightly-a2 workflow id: dispatch_a2 env: @@ -209,20 +180,10 @@ jobs: name: Trigger Nightly-A3 needs: [authorize] if: needs.authorize.outputs.is_authorized == 'true' && needs.authorize.outputs.dispatch_a3 == 'true' - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest outputs: a3_run_id: ${{ steps.dispatch_a3.outputs.a3_run_id }} steps: - - name: Install system dependencies - run: | - sudo apt-get update -y && sudo apt-get install -y curl - sudo mkdir -p -m 755 /etc/apt/keyrings - curl -sL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install gh -y - - name: Dispatch nightly-a3 workflow id: dispatch_a3 env: @@ -248,7 +209,7 @@ jobs: name: Post nightly workflow links needs: [authorize, dispatch-a2, dispatch-a3] if: always() && needs.authorize.outputs.is_authorized == 'true' - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest steps: - name: Build comment body id: body diff --git a/.github/workflows/pr_rerun_command.yml b/.github/workflows/pr_rerun_command.yml index 2f147f24f..610b0ed1c 100644 --- a/.github/workflows/pr_rerun_command.yml +++ b/.github/workflows/pr_rerun_command.yml @@ -30,36 +30,24 @@ permissions: jobs: rerun: name: Re-run failed CI jobs - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest outputs: rerun_count: ${{ steps.rerun.outputs.rerun_count }} steps: - - name: Install system dependencies - run: | - sudo apt-get update -y && sudo apt-get install -y jq curl - sudo mkdir -p -m 755 /etc/apt/keyrings - curl -sL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install gh -y - - name: Check user authorization id: auth env: - GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | ACTOR='${{ github.event.client_payload.github.payload.comment.user.login }}' PR_AUTHOR='${{ github.event.client_payload.pull_request.user.login }}' REPO='${{ github.event.client_payload.github.payload.repository.full_name }}' if [ "$ACTOR" = "$PR_AUTHOR" ]; then - echo "User $ACTOR is the PR author, authorized." echo "authorized=true" >> "$GITHUB_OUTPUT" else - ROLE_NAME=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$REPO/collaborators/$ACTOR/permission" | jq -r '.role_name // empty') - case "$ROLE_NAME" in + PERMISSION=$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq '.permission') + case "$PERMISSION" in triage|write|maintain|admin) echo "authorized=true" >> "$GITHUB_OUTPUT" ;; @@ -71,116 +59,56 @@ jobs: fi - name: Post unauthorized comment - if: steps.auth.outputs.authorized == 'false' + if: steps.auth.outputs.authorized != 'true' uses: peter-evans/create-or-update-comment@v5 with: token: ${{ secrets.PAT_TOKEN }} - edit-mode: replace repository: ${{ github.event.client_payload.github.payload.repository.full_name }} - comment-id: ${{ github.event.client_payload.github.payload.comment.id }} + issue-number: ${{ github.event.client_payload.github.payload.issue.number }} body: | [Bot]: rerun command failed: you do not have permission. Only the PR author or users with triage+ permission can trigger /rerun. reactions: confused - - name: Fail if unauthorized - if: steps.auth.outputs.authorized == 'false' - run: exit 1 - - name: Re-run failed jobs id: rerun + if: steps.auth.outputs.authorized == 'true' env: - GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} + GH_TOKEN: ${{ secrets.PAT_TOKEN }} run: | PR_SHA='${{ github.event.client_payload.pull_request.head.sha }}' REPO='${{ github.event.client_payload.github.payload.repository.full_name }}' - # Get all workflow runs for this SHA, sorted by newest first. - RUNS=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$REPO/actions/runs?head_sha=$PR_SHA&per_page=100&sort=created&direction=desc") - TOTAL=$(echo "$RUNS" | jq '.total_count // 0') - if [ "$TOTAL" = "0" ]; then - { - echo "rerun_count=0" - echo "comment_body<> "$GITHUB_OUTPUT" - exit 0 - fi - - # Dedup: keep only the latest run per workflow name (by run ID) - CANDIDATE_RUNS=$(echo "$RUNS" | jq -r ' - [.workflow_runs[] | {id: .id, name: .name, status: .status, conclusion: .conclusion}] | - group_by(.name) | - map(max_by(.id)) | - .[] | "\(.id)|\(.name)|\(.status)|\(.conclusion // "null")"') + FAILED_RUNS=$(gh api "repos/$REPO/actions/runs?head_sha=$PR_SHA&status=completed&conclusion=failure&per_page=50" \ + --jq '.workflow_runs[] | "\(.id)|\(.name)"') - RERUNNED="" - FAILED="" - RERUN_COUNT=0 + if [ -z "$FAILED_RUNS" ]; then + echo "No failed workflow runs found for SHA $PR_SHA." + echo "rerun_count=0" >> "$GITHUB_OUTPUT" + fi + COUNT=0 while IFS= read -r RUN; do - [ -z "$RUN" ] && continue RUN_ID=$(echo "$RUN" | cut -d'|' -f1) RUN_NAME=$(echo "$RUN" | cut -d'|' -f2) - STATUS=$(echo "$RUN" | cut -d'|' -f3) - CONCLUSION=$(echo "$RUN" | cut -d'|' -f4) - - if [ "$STATUS" != "completed" ]; then - echo "Skipping $RUN_NAME (ID: $RUN_ID) - still in progress." - FAILED="${FAILED}"$'\n'"- $RUN_NAME: still in progress, retry /rerun after completion" - continue - fi - - if [ "$CONCLUSION" != "failure" ]; then - echo "Skipping $RUN_NAME (ID: $RUN_ID) - conclusion is '$CONCLUSION'." - continue - fi - echo "Re-running failed jobs in $RUN_NAME (ID: $RUN_ID)..." - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - "https://api.github.com/repos/$REPO/actions/runs/$RUN_ID/rerun-failed-jobs") - if [ "$HTTP_CODE" = "201" ]; then - echo " -> Success." - RERUNNED="${RERUNNED}"$'\n'"- $RUN_NAME" - RERUN_COUNT=$((RERUN_COUNT + 1)) - else - echo " -> Failed (HTTP $HTTP_CODE)." - FAILED="${FAILED}"$'\n'"- $RUN_NAME: API returned HTTP $HTTP_CODE" - fi - done <<< "$CANDIDATE_RUNS" + gh api "repos/$REPO/actions/runs/$RUN_ID/rerun-failed-jobs" -X POST --silent + COUNT=$((COUNT + 1)) + done <<< "$FAILED_RUNS" - # Build comment body - { - echo "comment_body<> "$GITHUB_OUTPUT" - echo "rerun_count=$RERUN_COUNT" >> "$GITHUB_OUTPUT" + echo "Successfully re-ran failed jobs in $COUNT workflow run(s)." + echo "rerun_count=$COUNT" >> "$GITHUB_OUTPUT" - name: Comment result on PR + if: steps.auth.outputs.authorized == 'true' uses: peter-evans/create-or-update-comment@v5 with: token: ${{ secrets.PAT_TOKEN }} - edit-mode: replace repository: ${{ github.event.client_payload.github.payload.repository.full_name }} comment-id: ${{ github.event.client_payload.github.payload.comment.id }} - body: ${{ steps.rerun.outputs.comment_body }} + body: | + [Bot]: rerun completed. + + - **Re-ran failed jobs in**: ${{ steps.rerun.outputs.rerun_count }} workflow run(s) + - **Ref**: `${{ github.event.client_payload.pull_request.head.sha }}` + - **Workflow run**: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} reactions: hooray diff --git a/.github/workflows/push_build_csrc_cache.yaml b/.github/workflows/push_build_csrc_cache.yaml index 959fe2479..b43362e74 100644 --- a/.github/workflows/push_build_csrc_cache.yaml +++ b/.github/workflows/push_build_csrc_cache.yaml @@ -15,8 +15,7 @@ # This file is a part of the vllm-ascend project. # -# 3 chip_type variants (a2 / a3 / 310p) — all ARM64 + Ubuntu. -# Only ARM64 is needed since NPU test runners are all linux-aarch64-*. +# Cartesian product: 2 arch × 2 os × 3 chip_type = 12 jobs name: 'Cache csrc Build Artifacts' on: push: @@ -49,35 +48,65 @@ env: jobs: build-cache: - name: build-${{ matrix.chip_type }}-cache - runs-on: linux-arm64-cpu-16 + name: build-${{ matrix.arch }}-${{ matrix.chip_type }}-${{ matrix.os }}-cache + runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: + arch: + - X64 + - ARM64 + os: + - ubuntu + - openeuler chip_type: - a2 - a3 - 310p include: - # chip_type → image + soc_version + # arch → runner + - arch: X64 + runner: linux-amd64-cpu-16-hk + - arch: ARM64 + runner: linux-arm64-cpu-16 + + # chip_type → soc_version - chip_type: a2 - image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-ubuntu22.04-py3.12 soc_version: ascend910b1 - chip_type: a3 - image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-a3-ubuntu22.04-py3.12 soc_version: ascend910_9391 - chip_type: 310p - image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.1.0-beta.1-310p-ubuntu22.04-py3.12 soc_version: ascend310p1 + # os × chip_type → image + - os: ubuntu + chip_type: a2 + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-ubuntu22.04-py3.12 + - os: ubuntu + chip_type: a3 + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-a3-ubuntu22.04-py3.12 + - os: ubuntu + chip_type: 310p + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.1.0-beta.1-310p-ubuntu22.04-py3.12 + - os: openeuler + chip_type: a2 + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-openeuler24.03-py3.12 + - os: openeuler + chip_type: a3 + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-a3-openeuler24.03-py3.12 + - os: openeuler + chip_type: 310p + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.1.0-beta.1-310p-openeuler24.03-py3.12 + container: image: ${{ matrix.image }} steps: - name: Checkout repo uses: actions/checkout@v6 - - name: Config mirrors + - name: Config mirrors (ubuntu) + if: matrix.os == 'ubuntu' run: | sed -Ei 's@(ports|archive).ubuntu.com@cache-service.nginx-pypi-cache.svc.cluster.local:8081@g' /etc/apt/sources.list pip config set global.index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple @@ -86,7 +115,17 @@ jobs: apt-get install -y git zstd git config --global --add safe.directory /__w/vllm-ascend/vllm-ascend - - name: Install system dependencies + - name: Config mirrors (openeuler) + if: matrix.os == 'openeuler' + run: | + pip config set global.index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple + pip config set global.trusted-host cache-service.nginx-pypi-cache.svc.cluster.local + sed -Ei 's@https?://[^/]+/(openeuler|centos|fedora)@http://cache-service.nginx-pypi-cache.svc.cluster.local:8081/\1@g' /etc/yum.repos.d/*.repo + yum install -y git zstd + git config --global --add safe.directory /__w/vllm-ascend/vllm-ascend + + - name: Install system dependencies (ubuntu) + if: matrix.os == 'ubuntu' run: | apt-get -y install `cat packages.txt` apt-get -y install gcc g++ cmake libnuma-dev zstd clang-15 @@ -94,6 +133,12 @@ jobs: update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 20 pip install uv + - name: Install system dependencies (openeuler) + if: matrix.os == 'openeuler' + run: | + yum install -y gcc gcc-c++ cmake numactl-devel clang patch zstd + pip install uv + - name: Get image tag id: get_image_tag run: | @@ -117,7 +162,7 @@ jobs: vllm_ascend/*.so vllm_ascend/lib vllm_ascend/include - key: vllm-ascend-build-v1-ARM64-${{ steps.get_image_tag.outputs.IMAGE_TAG }}-${{ steps.get_csrc_hash.outputs.CSRC_HASH }} + key: vllm-ascend-build-v1-${{ matrix.arch }}-${{ steps.get_image_tag.outputs.IMAGE_TAG }}-${{ steps.get_csrc_hash.outputs.CSRC_HASH }} - name: Install vllm-ascend if: steps.cache-csrc.outputs.cache-hit != 'true' diff --git a/.github/workflows/schedule_doc_linkcheck.yaml b/.github/workflows/schedule_doc_linkcheck.yaml index 97d823c70..be3b3cd13 100644 --- a/.github/workflows/schedule_doc_linkcheck.yaml +++ b/.github/workflows/schedule_doc_linkcheck.yaml @@ -1,9 +1,9 @@ name: Docs link check on: - schedule: - # GitHub cron uses UTC. This runs every Sunday at 20:00 UTC+8. - - cron: '0 12 * * 0' +# schedule: +# # GitHub cron uses UTC. This runs every Sunday at 20:00 UTC+8. +# - cron: '0 12 * * 0' pull_request: branches: - 'main' diff --git a/.github/workflows/schedule_doc_translate.yaml b/.github/workflows/schedule_doc_translate.yaml index f24df24d6..0b6863653 100644 --- a/.github/workflows/schedule_doc_translate.yaml +++ b/.github/workflows/schedule_doc_translate.yaml @@ -18,8 +18,8 @@ name: Auto Doc Translate on: - schedule: - - cron: '0 0 */3 * *' +# schedule: +# - cron: '0 0 */3 * *' workflow_dispatch: inputs: target_branch: @@ -152,7 +152,7 @@ jobs: - name: Create PR in upstream if: steps.detect.outputs.has_changes == 'true' - uses: actions/github-script@v9 + uses: actions/github-script@v8 env: FILE_LIST: ${{ steps.results.outputs.file_list }} FILE_COUNT: ${{ steps.results.outputs.file_count }} diff --git a/.github/workflows/schedule_image_build_and_push.yaml b/.github/workflows/schedule_image_build_and_push.yaml index 204b13874..633a20f2c 100644 --- a/.github/workflows/schedule_image_build_and_push.yaml +++ b/.github/workflows/schedule_image_build_and_push.yaml @@ -12,6 +12,9 @@ # - Publish when tag with v* (pep440 version) ===> vllm-ascend:v1.2.3 / vllm-ascend:v1.2.3rc1 name: Image Build and Push on: +# schedule: +# # UTC+8: 12pm, 21pm +# - cron: '0 4,13 * * *' push: tags: - 'v*' @@ -29,8 +32,6 @@ on: type: choice options: - main - - v0.23.0rc1 - - v0.22.1rc1 - v0.21.0rc1 - v0.20.2rc1 - none @@ -41,20 +42,9 @@ on: type: choice options: - main - - releases/v0.21.0rc - releases/v0.20.2rc - releases/v0.18.0 - none - vllm_commit: - description: 'vLLM commit hash (must be paired with vllm_ascend_commit; mutually exclusive with tag/branch). Image will be pushed only to QUAY_TEMP_USERNAME.' - required: false - default: '' - type: string - vllm_ascend_commit: - description: 'vllm-ascend commit hash (must be paired with vllm_commit; mutually exclusive with tag/branch). Image will be pushed only to QUAY_TEMP_USERNAME.' - required: false - default: '' - type: string permissions: packages: write @@ -63,8 +53,7 @@ permissions: jobs: image_build: name: Image Build and Push - if: ${{ !((github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'image-build'))) - && !(github.event_name == 'workflow_dispatch' && inputs.vllm_commit != '' && inputs.vllm_ascend_commit != '') }} + if: ${{ (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'image-build')) && github.event_name != 'schedule' }} strategy: matrix: build_meta: @@ -91,23 +80,22 @@ jobs: dockerfile: ${{ matrix.build_meta.dockerfile }} suffix: ${{ matrix.build_meta.suffix }} quay_username: ${{ vars.QUAY_USERNAME }} - quay_temp_username: ${{ vars.QUAY_TEMP_USERNAME }} should_push: ${{ github.repository_owner == 'vllm-project' && (github.event_name != 'pull_request') }} workflow_dispatch_tag: ${{ inputs.tag != 'none' && inputs.tag || '' }} branch_ref: ${{ inputs.branch != 'none' && inputs.branch || '' }} secrets: QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} - QUAY_TEMP_PASSWORD: ${{ secrets.QUAY_TEMP_PASSWORD }} - image_build_by_commit: - name: Image Build (commit mode) and Push to QUAY_TEMP - if: ${{ github.event_name == 'workflow_dispatch' - && inputs.vllm_commit != '' - && inputs.vllm_ascend_commit != '' - && inputs.tag == 'none' - && inputs.branch == 'none' }} + schedule_image_build: + name: Image Build and Push + if: ${{ github.event_name == 'schedule' }} strategy: + max-parallel: 6 matrix: + branch_meta: + - branch_ref: main + schedule_tag_pattern: main + artifact_prefix: digests build_meta: - name: A2 Ubuntu dockerfile: Dockerfile @@ -131,14 +119,11 @@ jobs: with: dockerfile: ${{ matrix.build_meta.dockerfile }} suffix: ${{ matrix.build_meta.suffix }} - quay_temp_username: ${{ vars.QUAY_TEMP_USERNAME }} - # Commit mode always pushes (to QUAY_TEMP_REPO only) and never promotes - # the image to QUAY_REPO via merge-image, so quay_username is intentionally omitted. - should_push: true - temp_only: true - vllm_commit: ${{ inputs.vllm_commit }} - vllm_ascend_commit: ${{ inputs.vllm_ascend_commit }} - workflow_dispatch_tag: '' - branch_ref: '' + quay_username: ${{ vars.QUAY_USERNAME }} + should_push: ${{ github.repository_owner == 'vllm-project' && (github.event_name != 'pull_request') }} + workflow_dispatch_tag: ${{ inputs.tag != 'none' && inputs.tag || '' }} + branch_ref: ${{ matrix.branch_meta.branch_ref }} + schedule_tag_pattern: ${{ matrix.branch_meta.schedule_tag_pattern }} + artifact_prefix: ${{ matrix.branch_meta.artifact_prefix }} secrets: - QUAY_TEMP_PASSWORD: ${{ secrets.QUAY_TEMP_PASSWORD }} + QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} diff --git a/.github/workflows/schedule_lint_image_build.yaml b/.github/workflows/schedule_lint_image_build.yaml index ac2207974..6a6bb69ce 100644 --- a/.github/workflows/schedule_lint_image_build.yaml +++ b/.github/workflows/schedule_lint_image_build.yaml @@ -1,12 +1,11 @@ name: 'Image build lint' on: - schedule: - # Runs at 00:00 UTC+8 every day - - cron: '0 20 * * *' +# schedule: +# # Runs at 00:00 UTC+8 every day +# - cron: '0 20 * * *' workflow_dispatch: push: paths: - - '.github/vllm-main-verified.commit' - '.github/workflows/dockerfiles/Dockerfile.lint' - 'requirements-lint.txt' - 'requirements-dev.txt' @@ -41,13 +40,6 @@ jobs: flavor: latest=false - - name: Read verified vLLM commit - id: vllm - run: | - vllm_commit="$(cat .github/vllm-main-verified.commit)" - [ -n "$vllm_commit" ] || { echo "::error::vLLM commit is empty"; exit 1; } - echo "main_commit=$vllm_commit" >> "$GITHUB_OUTPUT" - - name: Build - Set up QEMU uses: docker/setup-qemu-action@v4 @@ -73,8 +65,6 @@ jobs: push: true labels: ${{ steps.meta.outputs.labels }} tags: ${{ steps.meta.outputs.tags }} - build-args: | - VLLM_COMMIT=${{ steps.vllm.outputs.main_commit }} provenance: false - name: Build and push @@ -88,6 +78,4 @@ jobs: push: true labels: ${{ steps.meta.outputs.labels }} tags: ${{ steps.meta.outputs.tags }} - build-args: | - VLLM_COMMIT=${{ steps.vllm.outputs.main_commit }} provenance: false diff --git a/.github/workflows/schedule_main2main.yaml b/.github/workflows/schedule_main2main.yaml new file mode 100644 index 000000000..9806874ba --- /dev/null +++ b/.github/workflows/schedule_main2main.yaml @@ -0,0 +1,417 @@ +# +# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +name: Main2Main + +on: +# schedule: +# - cron: '0 14 * * *' + workflow_dispatch: + inputs: + target_commit: + description: 'Target vLLM commit hash (default: latest vLLM main HEAD)' + required: false + default: '' + +permissions: + contents: write + pull-requests: write + issues: write + actions: write + +defaults: + run: + shell: bash -el {0} + +env: + UPSTREAM_REPO: vllm-project/vllm-ascend + WORK_REPO_DIR: ${{ github.workspace }} + VLLM_DIR: vllm-upstream + MAIN2MAIN_MODEL: deepseek/deepseek-v4-pro + MAIN2MAIN_IMAGE: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-ubuntu22.04-py3.11 + UV_INDEX_URL: http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple + UV_EXTRA_INDEX_URL: "https://repo.huaweicloud.com/ascend/repos/pypi http://cache-service.nginx-pypi-cache.svc.cluster.local/whl/cpu/" + UV_INDEX_STRATEGY: unsafe-best-match + UV_INSECURE_HOST: cache-service.nginx-pypi-cache.svc.cluster.local + UV_HTTP_TIMEOUT: 120 + UV_NO_CACHE: 1 + UV_SYSTEM_PYTHON: 1 + +jobs: + main2main: + runs-on: linux-aarch64-a2-4 + timeout-minutes: 2880 + concurrency: + group: main2main + cancel-in-progress: false + container: + image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-ubuntu22.04-py3.11 + env: + VLLM_LOGGING_LEVEL: ERROR + VLLM_USE_MODELSCOPE: True + HCCL_BUFFSIZE: 1024 + HF_HUB_OFFLINE: 1 + defaults: + run: + shell: bash -el {0} + working-directory: ${{ github.workspace }} + env: + GH_TOKEN: ${{ secrets.PAT_TOKEN }} + TARGET_COMMIT: ${{ github.event_name == 'workflow_dispatch' && inputs.target_commit || '' }} + PUSH_TO_GITHUB: 'true' + GITHUB_REPO: vllm-project/vllm-ascend + HEAD_FORK: wjunlu/vllm-ascend + MAIN2MAIN_KEEP_BRANCH: 'true' + MAIN2MAIN_WORKSPACE: /tmp/main2main_flow/workspace + MAIN2MAIN_TEST_CASES: >- + tests/e2e/pull_request/one_card/test_qwen3_5_0_8b.py::test_mamba_ssm_multimodal_reasoning_mtp_full_decode_only + tests/e2e/pull_request/one_card/test_qwen3_8b_w8a8.py::test_dense_w8a8_eagle3_full_graph + tests/e2e/pull_request/two_card/test_qwen3_30b_a3b.py::test_moe_tp_ep_eplb_full_decode_only + tests/e2e/pull_request/two_card/test_qwen3_vl_30b_a3b_instruct.py::test_multimodal_reasoning_pp_full_decode_only + MAIN2MAIN_LOG_HELPERS: | + print_group() { + local title="$1" + local path="$2" + echo "::group::${title}" + cat "${path}" || true + echo "::endgroup::" + } + print_group_if_nonempty() { + local title="$1" + local path="$2" + [ -s "${path}" ] || return 0 + print_group "${title}" "${path}" + } + steps: + - name: Checkout workflow repo + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.PAT_TOKEN }} + + - name: Exclude vllm dir from ascend git + run: echo "vllm-upstream/" >> .git/info/exclude + + - name: Checkout vllm-project/vllm repo + uses: actions/checkout@v6 + with: + repository: vllm-project/vllm + ref: ${{ env.TARGET_COMMIT != '' && env.TARGET_COMMIT || 'main' }} + path: ${{ env.VLLM_DIR }} + fetch-depth: 0 + + - name: Validate target commit + run: | + TARGET="${TARGET_COMMIT}" + if [ -n "${TARGET}" ]; then + if ! echo "${TARGET}" | grep -Eq '^[0-9a-f]{40}$'; then + echo "::error::Invalid target_commit format: ${TARGET}" + exit 1 + fi + fi + + - name: Config mirrors + if: always() + run: | + sed -Ei 's@(ports|archive).ubuntu.com@cache-service.nginx-pypi-cache.svc.cluster.local:8081@g' /etc/apt/sources.list + pip config set global.index-url http://cache-service.nginx-pypi-cache.svc.cluster.local/pypi/simple + pip config set global.trusted-host cache-service.nginx-pypi-cache.svc.cluster.local + apt-get update -y + apt-get -y install git + git config --global --add safe.directory "${WORK_REPO_DIR}" + git config --global --add safe.directory "$GITHUB_WORKSPACE/$GITHUB_WORKSPACE/${VLLM_DIR}" + + - name: Install system dependencies + run: | + if [ -s "${WORK_REPO_DIR}/packages.txt" ]; then + xargs -r apt-get -y install < "${WORK_REPO_DIR}/packages.txt" + fi + apt-get -y install curl ca-certificates gcc g++ cmake libnuma-dev clang-15 jq + # gh 2.4 is too old for 'gh auth git-credential', install latest + curl -fsSL https://github.com/cli/cli/releases/download/v2.68.1/gh_2.68.1_linux_arm64.deb -o /tmp/gh.deb && dpkg -i /tmp/gh.deb && rm /tmp/gh.deb || apt-get -y install gh + update-alternatives --install /usr/bin/clang clang /usr/bin/clang-15 20 + update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-15 20 + pip install uv pytest + + - name: Configure git identity + if: always() + working-directory: ${{ env.WORK_REPO_DIR }} + run: | + set -eu + git config user.name "main2main-bot" + git config user.email "main2main-bot@users.noreply.github.com" + gh --version + + - name: Check npu and CANN info + if: always() + run: | + npu-smi info || true + if [ -d /usr/local/Ascend/ascend-toolkit/latest ]; then + cat /usr/local/Ascend/ascend-toolkit/latest/"$(uname -i)"-linux/ascend_toolkit_install.info || true + fi + + - name: Install opencode + main2main-flow + run: | + set -eu + + # install opencode + curl -fsSL https://opencode.ai/install | bash + # configure opencode auth + mkdir -p ~/.local/share/opencode + cat > ~/.local/share/opencode/auth.json < /tmp/opencode_verify.log 2>&1 + OCODE_EXIT=$? + set -e + cat /tmp/opencode_verify.log + if [ "${OCODE_EXIT}" -ne 0 ]; then + echo "::error::opencode verification failed (exit ${OCODE_EXIT})" + exit 1 + fi + echo "opencode verification passed" + + # ensure git push uses gh token + git config --global credential.helper "!gh auth git-credential" + + # install main2main_flow + M2M_FLOW_DIR="/tmp/main2main_flow" + if [ ! -d "${M2M_FLOW_DIR}/.git" ]; then + git clone https://github.com/wjunLu/main2main_flow.git "${M2M_FLOW_DIR}" + fi + pip install -e "${M2M_FLOW_DIR}" + + - name: Get csrc hash + id: get_csrc_hash + working-directory: ${{ env.WORK_REPO_DIR }} + run: | + CSRC_HASH=$(find ./csrc ./setup.py ./CMakeLists.txt ./cmake \ + -type f -not -path '*/.*' | sort | xargs -r sha256sum | sha256sum | awk '{print $1}') + echo "CSRC_HASH=$CSRC_HASH" >> $GITHUB_OUTPUT + + - name: Cache vllm-ascend csrc + uses: runs-on/cache@v4 + with: + path: | + ${{ env.WORK_REPO_DIR }}/vllm_ascend/_cann_ops_custom + ${{ env.WORK_REPO_DIR }}/vllm_ascend/*.so + ${{ env.WORK_REPO_DIR }}/vllm_ascend/lib + ${{ env.WORK_REPO_DIR }}/vllm_ascend/include + key: vllm-ascend-build-v1-${{ runner.os }}-${{ env.MAIN2MAIN_IMAGE }}-${{ steps.get_csrc_hash.outputs.CSRC_HASH }} + restore-keys: | + vllm-ascend-build-v1-${{ runner.os }}-${{ env.MAIN2MAIN_IMAGE }}- + + - name: Create working branch + id: branch + working-directory: ${{ env.WORK_REPO_DIR }} + run: | + set -eu + BRANCH="main2main_auto_$(date +%Y-%m-%d_%H-%M)" + git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" || \ + git remote set-url upstream "https://github.com/${UPSTREAM_REPO}.git" + git fetch upstream main + git checkout -B "${BRANCH}" upstream/main + BASE_SHA=$(git rev-parse HEAD) + echo "name=${BRANCH}" >> "$GITHUB_OUTPUT" + echo "base_sha=${BASE_SHA}" >> "$GITHUB_OUTPUT" + + - name: Install Mooncake wheel + shell: bash + run: | + set -euxo pipefail + apt-get update -y + apt-get install -y --no-install-recommends \ + libibverbs1 \ + ibverbs-providers \ + librdmacm1 \ + libnuma1 \ + libcurl4 + ldconfig + MOONCAKE_WHEEL="mooncake_transfer_engine_ascend-0.3.8.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux_2_35_aarch64.whl" + pip install --no-cache-dir --no-deps \ + "https://vllm-ascend.obs.cn-north-4.myhuaweicloud.com/vllm-ascend/${MOONCAKE_WHEEL}" + pip show mooncake-transfer-engine-ascend || true + + - name: Install vllm-ascend + working-directory: ${{ env.WORK_REPO_DIR }} + run: | + pip install uc-manager + uv pip install -r requirements-dev.txt + uv pip install --force-reinstall --no-deps triton-ascend==3.2.1 + if find vllm_ascend -maxdepth 1 -name '*.so' -type f 2>/dev/null | grep -q .; then + COMPILE_CUSTOM_KERNELS=0 uv pip install -e . + else + uv pip install -e . + fi + + - name: Run main2main flow + id: run-main2main + working-directory: ${{ github.workspace }} + env: + RAY_EXPERIMENTAL_NOSET_ASCEND_RT_VISIBLE_DEVICES: True + run: | + set -euo pipefail + export PATH="/root/.opencode/bin:$PATH" + eval "${MAIN2MAIN_LOG_HELPERS}" + + rm -rf /tmp/main2main + mkdir -p /tmp/main2main + + cat > /tmp/main2main/heartbeat.sh <<'HEARTBEAT' + trap "exit 0" TERM INT + echo "::notice::main2main heartbeat $(date -Is)" + while sleep 300; do + echo "::notice::main2main heartbeat $(date -Is)" + done + HEARTBEAT + setsid bash /tmp/main2main/heartbeat.sh & + HEARTBEAT_PID="$!" + cleanup_heartbeat() { + if [ -n "${HEARTBEAT_PID:-}" ]; then + kill -TERM -- "-${HEARTBEAT_PID}" 2>/dev/null || true + wait "${HEARTBEAT_PID}" 2>/dev/null || true + fi + } + trap cleanup_heartbeat EXIT + + set +e + kickoff \ + --vllm-path "$GITHUB_WORKSPACE/${VLLM_DIR}" \ + --vllm-ascend-path "${WORK_REPO_DIR}" \ + --target-commit "${TARGET_COMMIT}" \ + 2> /tmp/main2main/kickoff.err \ + | tee /tmp/main2main/kickoff.log + KICKOFF_STATUS="${PIPESTATUS[0]}" + set -e + cleanup_heartbeat + trap - EXIT + + echo "::notice::after kickoff: $(date -Is)" + echo "KICKOFF_STATUS=${KICKOFF_STATUS}" + ls -la /tmp/main2main || true + find /tmp/main2main -maxdepth 3 -type f -print || true + + print_group "main2main kickoff output" /tmp/main2main/kickoff.log + print_group_if_nonempty "main2main kickoff stderr" /tmp/main2main/kickoff.err + + if [ "${KICKOFF_STATUS}" -ne 0 ]; then + echo "::error::kickoff exited with status ${KICKOFF_STATUS}" + exit "${KICKOFF_STATUS}" + fi + + # Copy final summary from workspace + FINAL_SUMMARY="${MAIN2MAIN_WORKSPACE}/final_summary.md" + if [ -f "${FINAL_SUMMARY}" ]; then + cp "${FINAL_SUMMARY}" /tmp/main2main/final-summary.md + fi + + if [ -s /tmp/main2main/final-summary.md ]; then + print_group "main2main final summary" /tmp/main2main/final-summary.md + else + echo "::warning::final-summary.md not found, generating stub" + echo "Status: completed" > /tmp/main2main/final-summary.md + echo "Steps: 0/0" >> /tmp/main2main/final-summary.md + fi + + - name: Upload workspace + if: always() + continue-on-error: true + uses: actions/upload-artifact@v7 + with: + name: main2main-workspace + path: /tmp/main2main_flow/workspace/ + retention-days: 7 + + - name: Summarize final status + id: final-status + working-directory: ${{ env.WORK_REPO_DIR }} + run: | + set -eu + eval "${MAIN2MAIN_LOG_HELPERS}" + + # Read structured status from main2main_flow workspace + STATUS_JSON="${MAIN2MAIN_WORKSPACE}/final_status.json" + OLD_COMMIT=$(python3 -c "import json; print(json.load(open('${STATUS_JSON}'))['old_commit'])") + NEW_COMMIT=$(python3 -c "import json; print(json.load(open('${STATUS_JSON}'))['new_commit'])") + FINAL_STATUS=$(python3 -c "import json; print(json.load(open('${STATUS_JSON}'))['status'])") + REACHED_COMMIT=$(python3 -c "import json; print(json.load(open('${STATUS_JSON}'))['reached_commit'])") + STEPS_COMPLETED=$(python3 -c "import json; print(json.load(open('${STATUS_JSON}'))['steps_completed'])") + STEPS_TOTAL=$(python3 -c "import json; print(json.load(open('${STATUS_JSON}'))['steps_total'])") + + MANUAL_REVIEW_REQUIRED=false + if [ "${FINAL_STATUS}" != "completed" ]; then + MANUAL_REVIEW_REQUIRED=true + fi + + git log --reverse --format="- \`%H\` %s" "${{ steps.branch.outputs.base_sha }}..HEAD" \ + > /tmp/main2main/created-commits.md + + COMMIT_COUNT=$(git rev-list --count "${{ steps.branch.outputs.base_sha }}..HEAD") + + { + echo "status=${FINAL_STATUS}" + echo "commit_count=${COMMIT_COUNT}" + echo "manual_review_required=${MANUAL_REVIEW_REQUIRED}" + echo "old_commit=${OLD_COMMIT}" + echo "new_commit=${NEW_COMMIT}" + } >> "$GITHUB_OUTPUT" + echo "::notice::final_status=${FINAL_STATUS}, reached_commit=${REACHED_COMMIT}, steps=${STEPS_COMPLETED}/${STEPS_TOTAL}, commit_count=${COMMIT_COUNT}" + print_group_if_nonempty "main2main created commits" /tmp/main2main/created-commits.md + + - name: Create manual review issue + if: steps.final-status.outputs.manual_review_required == 'true' + working-directory: ${{ env.WORK_REPO_DIR }} + run: | + eval "${MAIN2MAIN_LOG_HELPERS}" + + PR_URL=$(cat /tmp/main2main/pr_url.txt 2>/dev/null || echo "") + OLD="${{ steps.final-status.outputs.old_commit }}" + NEW="${{ steps.final-status.outputs.new_commit }}" + SHORT_NEW=$(printf '%.8s' "${NEW}") + + cat > /tmp/main2main-manual-review.md <> $GITHUB_OUTPUT + - name: Checkout PR code + if: github.event_name == 'workflow_dispatch' && inputs.vllm_ascend_ref != '' + uses: actions/checkout@v6 + with: + ref: ${{ inputs.vllm_ascend_ref }} + sparse-checkout: .github/workflows/configs/ + path: ./pr + - name: Checkout repository + uses: actions/checkout@v4 + - name: Read A2 test matrix config + id: set-matrix + env: + MATRIX_FILE: ${{ github.event_name == 'workflow_dispatch' && inputs.vllm_ascend_ref != '' && './pr/.github/workflows/configs/nightly_config.yaml' || '.github/workflows/configs/nightly_config.yaml' }} + MATRIX_OUTPUTS: '{"single_node":"a2.single_node.test_config","multi_node":"a2.multi_node.test_config"}' + run: python3 .github/workflows/scripts/resolve_nightly_tests.py --mode=matrix build-image: name: Build nightly-a2 image @@ -153,24 +170,7 @@ jobs: fail-fast: false matrix: vllm_ascend_branch: ${{ fromJSON(needs.setup-vars.outputs.vllm_ascend_branches) }} - test_config: - # pytest-driven tests - - name: test_custom_op_multi_card - os: linux-aarch64-a2b3-4 - tests: tests/e2e/nightly/single_node/ops/multicard_ops_a2/ - # YAML-driven tests - - name: qwen3-vl-32b-instruct-w8a8 - os: linux-aarch64-a2b3-4 - config_file_path: Qwen3-VL-32B-Instruct-W8A8.yaml - - name: qwen3-32b-int8 - os: linux-aarch64-a2b3-4 - config_file_path: Qwen3-32B-Int8-A2.yaml - - name: Qwen3.5-27B-w8a8-A2 - os: linux-aarch64-a2b3-2 - config_file_path: Qwen3.5-27B-w8a8-A2.yaml - - name: Qwen3.5-397B-A17B-w4a8-mtp - os: linux-aarch64-a2b3-8 - config_file_path: Qwen3.5-397B-A17B-w4a8-mtp-A2.yaml + test_config: ${{ fromJson(needs.setup-vars.outputs.single_node) }} uses: ./.github/workflows/_e2e_nightly_single_node.yaml with: runner: ${{ matrix.test_config.os }} @@ -189,8 +189,8 @@ jobs: ) }} secrets: - OBS_ACCESS_KEY: ${{ secrets.OBS_ACCESS_KEY }} - OBS_SECRET_KEY: ${{ secrets.OBS_SECRET_KEY }} + OBS_ACCESS_KEY_ID: ${{ secrets.OBS_ACCESS_KEY_ID }} + OBS_SECRET_ACCESS_KEY: ${{ secrets.OBS_SECRET_ACCESS_KEY }} multi-node-tests: name: multi-node @@ -204,16 +204,7 @@ jobs: max-parallel: 5 matrix: vllm_ascend_branch: ${{ fromJSON(needs.setup-vars.outputs.vllm_ascend_branches) }} - test_config: - - name: multi-node-qwen3-235b-dp - config_file_path: Qwen3-235B-A22B-A2.yaml - size: 2 - - name: multi-node-GLM-5.1-w8a8-A2 - config_file_path: GLM5_1-W8A8-A2-dual-nodes.yaml - size: 2 - - name: multi-node-Kimi-K2.5-W4A8-A2 - config_file_path: Kimi-K2_5-W4A8-A2-dual-nodes.yaml - size: 2 + test_config: ${{ fromJson(needs.setup-vars.outputs.multi_node) }} uses: ./.github/workflows/_e2e_nightly_multi_node.yaml with: soc_version: a2 @@ -236,8 +227,8 @@ jobs: }} secrets: KUBECONFIG_B64: ${{ secrets.KUBECONFIG_HK_001_INTERNAL_B64 }} - OBS_ACCESS_KEY: ${{ secrets.OBS_ACCESS_KEY }} - OBS_SECRET_KEY: ${{ secrets.OBS_SECRET_KEY }} + OBS_ACCESS_KEY_ID: ${{ secrets.OBS_ACCESS_KEY_ID }} + OBS_SECRET_ACCESS_KEY: ${{ secrets.OBS_SECRET_ACCESS_KEY }} generate-accuracy-matrix: name: Generate accuracy test matrix @@ -253,17 +244,22 @@ jobs: nightly_matrix: ${{ steps.set-matrix.outputs.nightly_matrix }} pr_only_matrix: ${{ steps.set-matrix.outputs.pr_only_matrix }} steps: - - name: Checkout repository + - name: Checkout PR code + if: github.event_name == 'workflow_dispatch' && inputs.vllm_ascend_ref != '' uses: actions/checkout@v6 + with: + ref: ${{ inputs.vllm_ascend_ref }} + sparse-checkout: .github/workflows/configs/ + path: ./pr + - name: Checkout repository + uses: actions/checkout@v7 - name: Read accuracy group config id: set-matrix - run: | - CONFIG_FILE="tests/e2e/models/configs/accuracy_groups_a2.json" - NIGHTLY=$(jq -c '.nightly' "$CONFIG_FILE") - PR_ONLY=$(jq -c '.pr_only' "$CONFIG_FILE") - echo "nightly_matrix=${NIGHTLY}" >> "$GITHUB_OUTPUT" - echo "pr_only_matrix=${PR_ONLY}" >> "$GITHUB_OUTPUT" + env: + MATRIX_FILE: ${{ github.event_name == 'workflow_dispatch' && inputs.vllm_ascend_ref != '' && './pr/.github/workflows/configs/nightly_config.yaml' || '.github/workflows/configs/nightly_config.yaml' }} + MATRIX_OUTPUTS: '{"nightly_matrix":"a2.accuracy.nightly","pr_only_matrix":"a2.accuracy.pr_only"}' + run: python3 .github/workflows/scripts/resolve_nightly_tests.py --mode=matrix single-node-accuracy-tests: needs: [setup-vars, parse-trigger, build-image, generate-accuracy-matrix] diff --git a/.github/workflows/schedule_nightly_test_a3.yaml b/.github/workflows/schedule_nightly_test_a3.yaml index 1bb7c2250..737e9652a 100644 --- a/.github/workflows/schedule_nightly_test_a3.yaml +++ b/.github/workflows/schedule_nightly_test_a3.yaml @@ -26,7 +26,7 @@ on: vllm_ascend_branch: description: 'Branch to test' required: true - default: 'main' + default: 'releases/v0.22.1rc' type: string test_cases: description: 'Test cases to run (comma-separated, e.g., "test_custom_op,qwen3-32b,accuracy-group" or "all" for all tests)' @@ -112,6 +112,10 @@ jobs: outputs: ascend_log_prefix: ${{ steps.export.outputs.ascend_log_prefix }} vllm_ascend_branches: ${{ steps.export.outputs.vllm_ascend_branches }} + multi_node: ${{ steps.set-matrix.outputs.multi_node }} + double_node: ${{ steps.set-matrix.outputs.double_node }} + single_node: ${{ steps.set-matrix.outputs.single_node }} + multi_card: ${{ steps.set-matrix.outputs.multi_card }} steps: - id: export run: | @@ -121,6 +125,21 @@ jobs: normalized=$(echo "$input_branch" | tr '/' '-') echo "vllm_ascend_branches=[\"${normalized}\"]" } >> $GITHUB_OUTPUT + - name: Checkout PR code + if: github.event_name == 'workflow_dispatch' && inputs.vllm_ascend_ref != '' + uses: actions/checkout@v6 + with: + ref: ${{ inputs.vllm_ascend_ref }} + sparse-checkout: .github/workflows/configs/ + path: ./pr + - name: Checkout repository + uses: actions/checkout@v4 + - name: Read A3 test matrix config + id: set-matrix + env: + MATRIX_FILE: ${{ github.event_name == 'workflow_dispatch' && inputs.vllm_ascend_ref != '' && './pr/.github/workflows/configs/nightly_config.yaml' || '.github/workflows/configs/nightly_config.yaml' }} + MATRIX_OUTPUTS: '{"multi_node":"a3.multi_node.test_config","double_node":"a3.double_node.test_config","single_node":"a3.single_node.test_config","multi_card":"a3.multi_card.test_config"}' + run: python3 .github/workflows/scripts/resolve_nightly_tests.py --mode=matrix build-image: name: Build nightly-a3 image @@ -151,10 +170,7 @@ jobs: max-parallel: 1 matrix: vllm_ascend_branch: ${{ fromJSON(needs.setup-vars.outputs.vllm_ascend_branches) }} - test_config: - - name: multi-node-deepseek-v3.2-W8A8-EP - config_file_path: DeepSeek-V3_2-W8A8-EP.yaml - size: 4 + test_config: ${{ fromJson(needs.setup-vars.outputs.multi_node) }} uses: ./.github/workflows/_e2e_nightly_multi_node.yaml with: soc_version: a3 @@ -177,8 +193,8 @@ jobs: }} secrets: KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} - OBS_ACCESS_KEY: ${{ secrets.OBS_ACCESS_KEY }} - OBS_SECRET_KEY: ${{ secrets.OBS_SECRET_KEY }} + OBS_ACCESS_KEY_ID: ${{ secrets.OBS_ACCESS_KEY_ID }} + OBS_SECRET_ACCESS_KEY: ${{ secrets.OBS_SECRET_ACCESS_KEY }} double-node-tests: name: double-node @@ -192,37 +208,7 @@ jobs: max-parallel: 3 matrix: vllm_ascend_branch: ${{ fromJSON(needs.setup-vars.outputs.vllm_ascend_branches) }} - test_config: - - name: multi-node-deepseek-r1-w8a8-longseq - config_file_path: DeepSeek-R1-W8A8-longseq.yaml - size: 2 - - name: multi-node-qwen3-dp - config_file_path: Qwen3-235B-A22B.yaml - size: 2 - - name: multi-node-qwenw8a8-2node-eplb - config_file_path: Qwen3-235B-W8A8-EPLB.yaml - size: 2 - - name: multi-node-dpsk3.2-2node - config_file_path: DeepSeek-V3_2-W8A8-A3-dual-nodes.yaml - size: 2 - - name: multi-node-qwen3-dp-mooncake-layerwise - config_file_path: Qwen3-235B-A22B-Mooncake-Layerwise.yaml - size: 2 - - name: multi-node-qwenw8a8-2node-longseq - config_file_path: Qwen3-235B-W8A8-longseq.yaml - size: 2 - - name: multi-node-qwen-disagg-pd - config_file_path: Qwen3-235B-disagg-pd.yaml - size: 2 - - name: multi-node-qwen-vl-disagg-pd - config_file_path: Qwen3-VL-235B-disagg-pd.yaml - size: 2 - - name: multi-node-deepseek-v3.1 - config_file_path: DeepSeek-V3.1-BF16.yaml - size: 2 - - name: multi-node-GLM-5.1-w8a8-A3 - config_file_path: GLM5_1-W8A8-A3-dual-nodes.yaml - size: 2 + test_config: ${{ fromJson(needs.setup-vars.outputs.double_node) }} uses: ./.github/workflows/_e2e_nightly_multi_node.yaml with: soc_version: a3 @@ -245,8 +231,8 @@ jobs: }} secrets: KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} - OBS_ACCESS_KEY: ${{ secrets.OBS_ACCESS_KEY }} - OBS_SECRET_KEY: ${{ secrets.OBS_SECRET_KEY }} + OBS_ACCESS_KEY_ID: ${{ secrets.OBS_ACCESS_KEY_ID }} + OBS_SECRET_ACCESS_KEY: ${{ secrets.OBS_SECRET_ACCESS_KEY }} single-node-tests: name: single-node @@ -260,50 +246,7 @@ jobs: max-parallel: 7 matrix: vllm_ascend_branch: ${{ fromJSON(needs.setup-vars.outputs.vllm_ascend_branches) }} - test_config: - # YAML-driven tests - - name: mtpx-deepseek-r1-0528-w8a8 - os: linux-aarch64-a3-16 - config_file_path: MTPX-DeepSeek-R1-0528-W8A8.yaml - - name: deepseek-r1-0528-w8a8 - os: linux-aarch64-a3-16 - config_file_path: DeepSeek-R1-0528-W8A8.yaml - - name: kimi-k2-thinking - os: linux-aarch64-a3-16 - config_file_path: Kimi-K2-Thinking.yaml - - name: qwen3-vl-235b-a22b-instruct-w8a8 - os: linux-aarch64-a3-16 - config_file_path: Qwen3-VL-235B-A22B-Instruct-W8A8.yaml - - name: deepseek-r1-0528-w8a8-prefix-cache - os: linux-aarch64-a3-16 - config_file_path: Prefix-Cache-DeepSeek-R1-0528-W8A8.yaml - - name: deepseek-v3-2-w8a8 - os: linux-aarch64-a3-16 - config_file_path: DeepSeek-V3.2-W8A8.yaml - - name: glm-4.7-w8a8 - os: linux-aarch64-a3-16 - config_file_path: GLM-4.7.yaml - - name: kimi-k2.5 - os: linux-aarch64-a3-16 - config_file_path: Kimi-K2.5.yaml - - name: qwen3-235b-a22b-w8a8 - os: linux-aarch64-a3-16 - config_file_path: Qwen3-235B-A22B-W8A8.yaml - - name: Qwen3.5-397B-A17B-w8a8-mtp - os: linux-aarch64-a3-16 - config_file_path: Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml - - name: MiniMax-M2.5-w8a8-QuaRot-A3 - os: linux-aarch64-a3-16 - config_file_path: MiniMax-M2.5-w8a8-QuaRot-A3.yaml - - name: Qwen3.5-27B-w8a8-A3 - os: linux-aarch64-a3-2 - config_file_path: Qwen3.5-27B-w8a8-A3.yaml - - name: Qwen3.5-122B-A10B-W8A8-A3 - os: linux-aarch64-a3-16 - config_file_path: Qwen3.5-122B-A10B-W8A8-A3.yaml - - name: DeepSeek-V4-Flash-W8A8-A3 - os: linux-aarch64-a3-16 - config_file_path: DeepSeek-V4-Flash-W8A8-A3.yaml + test_config: ${{ fromJson(needs.setup-vars.outputs.single_node) }} uses: ./.github/workflows/_e2e_nightly_single_node.yaml with: runner: ${{ matrix.test_config.os }} @@ -331,30 +274,7 @@ jobs: fail-fast: false matrix: vllm_ascend_branch: ${{ fromJSON(needs.setup-vars.outputs.vllm_ascend_branches) }} - test_config: - # pytest-driven tests - - name: qwen3-30b-acc - os: linux-aarch64-a3-4 - tests: tests/e2e/weekly/single_node/models/test_qwen3_30b_acc.py - # YAML-driven tests - - name: qwen3-30b-a3b-w8a8 - os: linux-aarch64-a3-4 - config_file_path: Qwen3-30B-A3B-W8A8.yaml - - name: qwen3-32b-int8 - os: linux-aarch64-a3-4 - config_file_path: Qwen3-32B-Int8.yaml - - name: qwen3-32b-int8-prefix-cache - os: linux-aarch64-a3-4 - config_file_path: Prefix-Cache-Qwen3-32B-Int8.yaml - - name: Qwen3-30B-A3B-W4A8-llm-compressor - os: linux-aarch64-a3-2 - config_file_path: Qwen3-30B-A3B-W4A8-llm-compressor.yaml - - name: Qwen3-30B-QuaRot - os: linux-aarch64-a3-2 - config_file_path: Qwen3-30B-QuaRot-eagle3.yaml - - name: Qwen3-32B-QuaRot - os: linux-aarch64-a3-2 - config_file_path: Qwen3-32B-QuaRot-eagle3.yaml + test_config: ${{ fromJson(needs.setup-vars.outputs.multi_card) }} uses: ./.github/workflows/_e2e_nightly_single_node.yaml with: runner: ${{ matrix.test_config.os }} @@ -372,8 +292,8 @@ jobs: request_id: ${{ inputs.request_id || '' }} vllm_ascend_ref: ${{ inputs.vllm_ascend_ref || '' }} secrets: - OBS_ACCESS_KEY: ${{ secrets.OBS_ACCESS_KEY }} - OBS_SECRET_KEY: ${{ secrets.OBS_SECRET_KEY }} + OBS_ACCESS_KEY_ID: ${{ secrets.OBS_ACCESS_KEY_ID }} + OBS_SECRET_ACCESS_KEY: ${{ secrets.OBS_SECRET_ACCESS_KEY }} clear-pre-logs: runs-on: linux-aarch64-a3-0 diff --git a/.github/workflows/schedule_release_code_and_wheel.yml b/.github/workflows/schedule_release_code_and_wheel.yml index aa39da540..2b322b96a 100644 --- a/.github/workflows/schedule_release_code_and_wheel.yml +++ b/.github/workflows/schedule_release_code_and_wheel.yml @@ -18,9 +18,9 @@ name: Release Code and Wheel on: - schedule: - # UTC+8: 10am, 16pm - - cron: '0 2,8 * * *' +# schedule: +# # UTC+8: 10am, 16pm +# - cron: '0 2,8 * * *' push: tags: - 'v*' @@ -33,10 +33,14 @@ on: type: choice options: - main - - v0.23.0rc1 - - v0.22.1rc1 - - v0.21.0rc1 - - v0.20.2rc1 + - v0.19.1rc1 + - v0.18.0 + - v0.18.0rc1 + - v0.17.0rc1 + - v0.16.0rc1 + - v0.15.0rc1 + - v0.14.0rc1 + - v0.13.0 jobs: build_and_release_code: @@ -154,7 +158,7 @@ jobs: mkdir -p dist/variants python3 .github/workflows/scripts/wheel/make_variant.py \ -c .github/workflows/scripts/wheel/config.json \ - -l 910b + -l a2 echo "Generated variant wheels:" ls dist/variants/ diff --git a/.github/workflows/schedule_stale_manage.yaml b/.github/workflows/schedule_stale_manage.yaml index 8a24bd0ad..0bbff1551 100644 --- a/.github/workflows/schedule_stale_manage.yaml +++ b/.github/workflows/schedule_stale_manage.yaml @@ -1,33 +1,24 @@ name: "Close stale resolved/wait-feedback issues" on: - schedule: - - cron: '0 2 * * *' +# schedule: +# - cron: '0 2 * * *' issue_comment: types: [created] jobs: remove-wait-feedback-on-comment: if: ${{ github.event_name == 'issue_comment' && contains(github.event.issue.labels.*.name, 'wait-feedback') }} - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest permissions: issues: write steps: - - name: Install system dependencies - run: | - sudo apt-get update -y && sudo apt-get install -y curl - sudo mkdir -p -m 755 /etc/apt/keyrings - curl -sL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null - sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt-get update -y - sudo apt-get install gh -y # When an issue tagged with "wait-feedback" receives a new response, the "wait-feedback" tag will be removed. - run: gh issue edit "${{ github.event.issue.number }}" --remove-label "wait-feedback" --repo "${{ github.repository }}" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} stale: if: ${{ github.event_name == 'schedule' }} - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest permissions: actions: write issues: write diff --git a/.github/workflows/schedule_update_estimated_times.yaml b/.github/workflows/schedule_update_estimated_times.yaml deleted file mode 100644 index 67913ce97..000000000 --- a/.github/workflows/schedule_update_estimated_times.yaml +++ /dev/null @@ -1,217 +0,0 @@ -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# - -name: Update estimated test times - -on: - schedule: - - cron: '0 8 */3 * *' # Every 3 days at 16:00 Beijing (UTC+8) - workflow_dispatch: - inputs: - filter_npu_type: - description: 'Only run tests for this NPU type (e.g. a2, a3, 310p). Leave empty to run all.' - required: false - type: string - default: '' - filter_num_npus: - description: 'Only run tests with this NPU count (e.g. 1, 2, 4, 8). Leave empty to run all.' - required: false - type: string - default: '' - filter_partition: - description: 'Only run this partition (e.g. 1-3). Leave empty to run all.' - required: false - type: string - default: '' - -permissions: - contents: read - pull-requests: write - -concurrency: - group: update-estimated-times-${{ github.ref }} - cancel-in-progress: true - -jobs: - select-tests: - runs-on: ubuntu-latest - outputs: - test_groups: ${{ steps.scope.outputs.test_groups }} - has_tests: ${{ steps.scope.outputs.has_tests }} - main_commit: ${{ steps.vllm.outputs.main_commit }} - release_tag: ${{ steps.vllm.outputs.release_tag }} - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: pip install regex pyyaml - - - name: Read verified vLLM refs - id: vllm - run: | - main_commit="$(tr -d '[:space:]' < .github/vllm-main-verified.commit)" - release_tag="$(tr -d '[:space:]' < .github/vllm-release-tag.commit)" - echo "main_commit=${main_commit}" >> "$GITHUB_OUTPUT" - echo "release_tag=${release_tag}" >> "$GITHUB_OUTPUT" - - - name: Select all tests - id: scope - run: | - python3 .github/workflows/scripts/select_tests.py \ - --run-all-modules \ - --changed-files vllm_ascend/__init__.py - # Re-read outputs and strip cpu-only groups (timing data from CPU UTs - # is batch-level and skipped by update_estimated_times.py anyway). - groups_json="$(grep '^test_groups=' "$GITHUB_OUTPUT" | cut -d= -f2-)" - filter=".npu_type != \"cpu\"" - if [ -n "${{ inputs.filter_npu_type }}" ]; then - filter="${filter} and .npu_type == \"${{ inputs.filter_npu_type }}\"" - fi - if [ -n "${{ inputs.filter_num_npus }}" ]; then - filter="${filter} and .num_npus == ${{ inputs.filter_num_npus }}" - fi - if [ -n "${{ inputs.filter_partition }}" ]; then - filter="${filter} and .partition == \"${{ inputs.filter_partition }}\"" - fi - filtered="$(echo "$groups_json" | jq -c "[.[] | select(${filter})]")" - has_tests="false" - if [ "$(echo "$filtered" | jq 'length')" -gt 0 ]; then has_tests="true"; fi - sed -i "s|^test_groups=.*|test_groups=${filtered}|" "$GITHUB_OUTPUT" - sed -i "s|^has_tests=.*|has_tests=${has_tests}|" "$GITHUB_OUTPUT" - - run-tests-main: - needs: select-tests - if: ${{ needs.select-tests.outputs.has_tests == 'true' }} - uses: ./.github/workflows/_selected_tests.yaml - with: - vllm: ${{ needs.select-tests.outputs.main_commit }} - test_groups: ${{ needs.select-tests.outputs.test_groups }} - upload_timing: true - continue_on_error: true - - run-tests-release: - needs: select-tests - if: ${{ needs.select-tests.outputs.has_tests == 'true' }} - uses: ./.github/workflows/_selected_tests.yaml - with: - vllm: ${{ needs.select-tests.outputs.release_tag }} - test_groups: ${{ needs.select-tests.outputs.test_groups }} - upload_timing: true - continue_on_error: true - - update-estimated-times: - needs: [run-tests-main, run-tests-release] - if: always() - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - token: ${{ secrets.PAT_TOKEN }} - - - uses: actions/download-artifact@v8 - with: - pattern: timing-data-* - path: timing-artifacts/ - merge-multiple: false - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install dependencies - run: pip install pyyaml - - - name: Update estimated_times from timing data - run: | - python3 .github/workflows/scripts/update_estimated_times.py \ - --timing-dir timing-artifacts/ \ - --config .github/workflows/scripts/test_config.yaml - - - name: Check for changes - id: check - run: | - if git diff --quiet .github/workflows/scripts/test_config.yaml; then - echo "changed=false" >> "$GITHUB_OUTPUT" - echo "No changes to test_config.yaml." - else - echo "changed=true" >> "$GITHUB_OUTPUT" - echo "test_config.yaml has been updated:" - git diff .github/workflows/scripts/test_config.yaml - fi - - - name: Commit and push - if: steps.check.outputs.changed == 'true' - env: - GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} - run: | - BRANCH="auto/update-estimated-times-${{ github.run_id }}" - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git checkout -b "$BRANCH" - git add .github/workflows/scripts/test_config.yaml - git commit -s -m "[CI] Auto-update estimated test times in test_config.yaml" - git push origin "$BRANCH" - echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" - echo "Pushed branch: $BRANCH" - - - name: Create pull request - if: steps.check.outputs.changed == 'true' - uses: actions/github-script@v9 - env: - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - BRANCH: auto/update-estimated-times-${{ github.run_id }} - with: - github-token: ${{ secrets.PAT_TOKEN }} - script: | - try { - const pr = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - head: process.env.BRANCH, - base: 'main', - title: '[CI] Auto-update estimated test times in test_config.yaml', - body: '## Summary\n\n' + - 'This PR was auto-generated by the **Update estimated test times** ' + - '[workflow](' + process.env.RUN_URL + ').\n\n' + - 'It updates the `estimated_times` values in ' + - '`.github/workflows/scripts/test_config.yaml` based on actual elapsed ' + - 'times collected from CI workflow runs.\n\n' + - '### Methodology\n\n' + - '- Each test job uploads its elapsed time as a `timing-data-*` ' + - 'artifact upon completion.\n' + - '- The workflow aggregates all collected timing artifacts across jobs.\n' + - '- For each test, the **median** elapsed time is computed to reduce ' + - 'outlier impact.\n' + - '- A **10% safety buffer** is applied and the result is rounded to the ' + - 'nearest 10 seconds.\n\n' + - '### Review Checklist\n\n' + - '- [ ] Verify that updated `estimated_time` values are within a ' + - 'reasonable range.\n' + - '- [ ] Confirm no test entries are missing or unexpectedly removed.\n', - }); - core.info('Created PR #' + pr.data.number); - } catch (error) { - if (error.message.includes('A pull request already exists')) { - core.warning('PR already exists'); - } else { throw error; } - } diff --git a/.github/workflows/schedule_vllm_e2e_test.yaml b/.github/workflows/schedule_vllm_e2e_test.yaml index f32c62d38..05e279bc6 100644 --- a/.github/workflows/schedule_vllm_e2e_test.yaml +++ b/.github/workflows/schedule_vllm_e2e_test.yaml @@ -18,8 +18,9 @@ name: E2E-upstream on: - schedule: - - cron: '0 8 * * 0' # every Sunday at 8:00 + workflow_dispatch: +# schedule: +# - cron: '0 8 * * 0' # every Sunday at 8:00 # Bash shells do not use ~/.profile or ~/.bashrc so these shells need to be explicitly # declared as "shell: bash -el {0}" on steps that need to be properly activated. # It's used to activate ascend-toolkit environment variables. @@ -49,6 +50,7 @@ jobs: fail-fast: false matrix: part: [0, 1, 2, 3] + vllm: [v0.20.2] container: image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-ubuntu22.04-py3.12 env: @@ -57,9 +59,6 @@ jobs: steps: - name: Checkout vllm-project/vllm-ascend repo uses: actions/checkout@v6 - - name: Read vLLM release tag - id: vllm - uses: ./.github/actions/read-vllm-release-tag - name: Check npu and CANN info run: | npu-smi info @@ -86,7 +85,7 @@ jobs: uses: actions/checkout@v6 with: repository: vllm-project/vllm - ref: ${{ steps.vllm.outputs.release_tag }} + ref: ${{ matrix.vllm }} path: ./vllm-empty fetch-depth: 1 @@ -134,6 +133,7 @@ jobs: fail-fast: false matrix: part: [0] + vllm: [v0.20.2] container: image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-ubuntu22.04-py3.12 env: @@ -142,9 +142,6 @@ jobs: steps: - name: Checkout vllm-project/vllm-ascend repo uses: actions/checkout@v6 - - name: Read vLLM release tag - id: vllm - uses: ./.github/actions/read-vllm-release-tag - name: Check npu and CANN info run: | npu-smi info @@ -171,7 +168,7 @@ jobs: uses: actions/checkout@v6 with: repository: vllm-project/vllm - ref: ${{ steps.vllm.outputs.release_tag }} + ref: ${{ matrix.vllm }} path: ./vllm-empty fetch-depth: 1 @@ -210,6 +207,7 @@ jobs: fail-fast: false matrix: part: [0] + vllm: [v0.20.2] container: image: swr.cn-southwest-2.myhuaweicloud.com/base_image/ascend-ci/cann:9.0.0-910b-ubuntu22.04-py3.12 env: @@ -218,9 +216,6 @@ jobs: steps: - name: Checkout vllm-project/vllm-ascend repo uses: actions/checkout@v6 - - name: Read vLLM release tag - id: vllm - uses: ./.github/actions/read-vllm-release-tag - name: Check npu and CANN info run: | npu-smi info @@ -247,7 +242,7 @@ jobs: uses: actions/checkout@v6 with: repository: vllm-project/vllm - ref: ${{ steps.vllm.outputs.release_tag }} + ref: ${{ matrix.vllm }} path: ./vllm-empty fetch-depth: 1 diff --git a/.github/workflows/schedule_weekly_test_a2.yaml b/.github/workflows/schedule_weekly_test_a2.yaml deleted file mode 100644 index 4c5815727..000000000 --- a/.github/workflows/schedule_weekly_test_a2.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# - -name: Weekly-A2 - -on: - schedule: - # Run doctest at 00:00 Beijing time (UTC+8) every Sunday - - cron: "0 16 * * 6" - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ascend-weekly-${{ github.ref }}-a2 - cancel-in-progress: true - -jobs: - doc-test: - name: doc-test - uses: ./.github/workflows/labeled_doctest.yaml - with: - doc_versions: '["v0.18.0","latest"]' diff --git a/.github/workflows/scripts/coverage.py b/.github/workflows/scripts/coverage.py index 59a99fd55..f5b4e067a 100644 --- a/.github/workflows/scripts/coverage.py +++ b/.github/workflows/scripts/coverage.py @@ -1,16 +1,12 @@ # How to use this script: in vllm-ascend directory # python .github/workflows/scripts/coverage.py -import contextlib import sys from pathlib import Path -import regex as re import yaml with open(".github/workflows/scripts/test_config.yaml") as f: - docs = list(yaml.safe_load_all(f)) - config = docs[0] - meta = docs[1] if len(docs) >= 2 else {} + config = yaml.safe_load(f) def pytest_node_file_path(path: str) -> str: @@ -65,7 +61,7 @@ def pytest_node_file_path(path: str) -> str: uncovered_ut = sorted(actual_ut - resolved_ut) # ============================================================ -# 4. source code coverage +# 4. 源码覆盖 # ============================================================ source_deps = {d.rstrip("/") for module in config for d in module.get("source_file_dependencies", [])} covered_source = set() @@ -83,98 +79,6 @@ def pytest_node_file_path(path: str) -> str: break uncovered_source = sorted({str(f) for f in Path("vllm_ascend").rglob("*.py") if str(f) not in covered_source}) -# ============================================================ -# 5. estimated_times coverage -# ============================================================ -_et = dict(meta.get("estimated_times", {}) or {}) -_rm = dict(meta.get("runner_mapping", {}) or {}) -_part = dict(meta.get("partition", {}) or {}) - -# Build NPU UT regex patterns from runner_mapping -npu_ut_patterns = [] -for pattern_str in _rm: - with contextlib.suppress(re.error): - npu_ut_patterns.append(re.compile(pattern_str)) - -# Expand all test paths -all_expanded = set() -for p in all_yaml_paths: - pp = Path(pytest_node_file_path(p)) - if pp.exists() and pp.is_dir(): - for f in sorted(pp.rglob("test_*.py")): - all_expanded.add(str(f)) - else: - all_expanded.add(p) - -# Strip ::nodeid suffix so counting is at file level (same as step 2) -all_expanded_files = {pytest_node_file_path(p) for p in all_expanded} - -# Separate E2E / NPU UT / CPU UT -e2e_files = {p for p in all_expanded_files if "tests/e2e/" in p} -ut_files = {p for p in all_expanded_files if "tests/ut/" in p} -npu_ut_files = set() -cpu_ut_files = set() -for p in ut_files: - if any(pat.search(p) for pat in npu_ut_patterns): - npu_ut_files.add(p) - else: - cpu_ut_files.add(p) - -# Need estimated_times: E2E + NPU UT (file-level) -need_et_files = e2e_files | npu_ut_files -existing_et_keys = set(_et.keys()) -missing_et = sorted(need_et_files - existing_et_keys) -# CPU UT should NOT have estimated_times -cpu_ut_leaked = sorted(cpu_ut_files & existing_et_keys) - -# ============================================================ -# 6. Correctness of runner_mapping -# ============================================================ -rm_errors: list[str] = [] -for pattern_str, runner_config in sorted(_rm.items()): - try: - pat = re.compile(pattern_str) - except re.error as e: - rm_errors.append(f"Pattern {pattern_str!r}: invalid regex — {e}") - continue - if "default" not in runner_config: - rm_errors.append(f"Pattern {pattern_str!r}: missing 'default' key") - continue - matched = [p for p in all_expanded if pat.search(p)] - if not matched: - rm_errors.append(f"Pattern {pattern_str!r}: matches 0 tests (unused)") - -rm_broken = len(rm_errors) > 0 - -# ============================================================ -# 7. partition validity -# ============================================================ -part_errors: list[str] = [] -# Collect actual runner keys used in routing -actual_runner_keys: set[str] = set() -for p in all_expanded: - for pat_str, rc in _rm.items(): - if re.compile(pat_str).search(p): - for rk in rc.values(): - actual_runner_keys.add(rk) - break - -for key, val in sorted(_part.items()): - if "_x" not in key: - part_errors.append(f"Key {key!r}: missing '_x' separator") - continue - parts = key.rsplit("_x", 1) - if not parts[1].isdigit(): - part_errors.append(f"Key {key!r}: num_npus '{parts[1]}' is not a number") - continue - if key == "cpu_x0": - # CPU is the default fallback runner, always valid - continue - if key not in actual_runner_keys: - part_errors.append(f"Key {key!r}: no tests route to this runner (unused)") - -part_broken = len(part_errors) > 0 - # ============================================================ # REPORT # ============================================================ @@ -196,10 +100,7 @@ def pytest_node_file_path(path: str) -> str: else: print(f" ✓ ALL {len(actual_e2e)} E2E test files covered") -print( - f"\n[3] UT coverage: {len(actual_ut)} total files, {len(uncovered_ut)} uncovered" - f" (CPU: {len(cpu_ut_files)}, NPU: {len(npu_ut_files)})" -) +print(f"\n[3] UT coverage: {len(actual_ut)} total files, {len(uncovered_ut)} uncovered") if uncovered_ut: for p in uncovered_ut: print(f" ✗ {p}") @@ -214,6 +115,7 @@ def pytest_node_file_path(path: str) -> str: else: print(" ✓ ALL source .py files covered (including __init__.py)") +# Also check: which __init__.py files are NOT covered init_uncovered = sorted({str(f) for f in Path("vllm_ascend").rglob("__init__.py") if str(f) not in covered_source}) if init_uncovered: print(f"\n Note: __init__.py files NOT in source_file_dependencies ({len(init_uncovered)}):") @@ -221,46 +123,7 @@ def pytest_node_file_path(path: str) -> str: print(f" - {p}") print(" (These are trivial files; their parent dirs are covered by prefix match)") -print("\n[5] estimated_times coverage (file-level):") -print(f" E2E: {len([p for p in e2e_files if p in existing_et_keys])}/{len(e2e_files)} covered") -print(f" NPU UT: {len([p for p in npu_ut_files if p in existing_et_keys])}/{len(npu_ut_files)} covered") -print(f" CPU UT (should be 0): {len(cpu_ut_leaked)} leaked") -if missing_et: - for p in missing_et: - print(f" ✗ MISSING: {p}") -else: - print(" ✓ All E2E + NPU UT tests have estimated_times") -if cpu_ut_leaked: - for p in cpu_ut_leaked: - print(f" ✗ LEAKED (CPU UT should not have et): {p}") -else: - print(" ✓ No CPU UT entries in estimated_times") - -print("\n[6] runner_mapping validation:") -if rm_errors: - for err in rm_errors: - print(f" ✗ {err}") -else: - print(" ✓ All patterns valid and match at least one test") - -print("\n[7] partition validation:") -if part_errors: - for err in part_errors: - print(f" ✗ {err}") -else: - print(" ✓ All partition keys valid and map to active runners") - print("\n" + "=" * 70) -has_errors = bool( - broken - or uncovered_e2e - or uncovered_ut - or uncovered_source - or missing_et - or cpu_ut_leaked - or rm_errors - or part_errors -) -if has_errors: +if broken or uncovered_e2e or uncovered_ut or uncovered_source: sys.exit(1) diff --git a/.github/workflows/scripts/parse_schedule_config.py b/.github/workflows/scripts/parse_schedule_config.py new file mode 100644 index 000000000..2ebe30a52 --- /dev/null +++ b/.github/workflows/scripts/parse_schedule_config.py @@ -0,0 +1,774 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# This file is a part of the vllm-ascend project. +# +"""Parse schedule_config.yaml and output test matrices for GitHub Actions. + +Architecture: + raw file paths from schedule_config.yaml + -> framework classes (ModelFramework, AccuracyFramework, OpsFramework) + -> PeriodicCase objects + -> framework-specific matrices + -> GITHUB_OUTPUT + +Directory conventions (routing is inferred from path, not config): + tests/e2e/schedule/model/...//*.yaml -> model framework + tests/e2e/schedule/accuracy//*.yaml -> accuracy framework + tests/e2e/schedule/ops//*.py -> ops framework + tests/e2e/schedule/ops// -> ops framework (directory) + +Supported resource directories: + one_card, two_card, four_card, eight_card -> card resources + one_node, two_node, four_node -> node resources + +Chip detection (separator-bounded token in path or filename): + contains 310/310p/v310 -> 310p + contains a2/A2 -> a2 + contains a3/A3 -> a3 + default -> a3 + +Route rules: + model + card or one_node -> single_node + model + two_node/four_node -> multi_node + accuracy + card -> accuracy (node resources not supported) + ops + any -> ops + +Multi-node type (model multi-node only): + filename stem contains external_dp -> external_dp + otherwise -> internal_dp + +Usage: + python parse_schedule_config.py \\ + --config .github/workflows/scripts/schedule_config.yaml \\ + --runner-label .github/workflows/scripts/runner_label.json \\ + --event-name schedule \\ + --cron "45 15 * * *" \\ + --test-filter all +""" + +from __future__ import annotations + +import argparse +import json +import os +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import regex as re +import yaml + +RESOURCE_DIRS: dict[str, tuple[str, int]] = { + "one_card": ("card", 1), + "two_card": ("card", 2), + "four_card": ("card", 4), + "eight_card": ("card", 8), + "one_node": ("node", 1), + "two_node": ("node", 2), + "four_node": ("node", 4), +} + +# (resource_type, chip, resource_num) -> npu_num for runner_label.json lookup. +# npu_num=0 means LWS orchestration runner (no NPUs on the control node). +_NPU_NUM: dict[tuple[str, str, int], int] = { + ("card", "a3", 1): 2, + ("card", "a3", 2): 2, + ("card", "a3", 4): 4, + ("card", "a3", 8): 8, + ("node", "a3", 1): 16, + ("node", "a3", 2): 0, + ("node", "a3", 4): 0, + ("card", "a2", 1): 1, + ("card", "a2", 2): 2, + ("card", "a2", 4): 4, + ("card", "a2", 8): 8, + ("node", "a2", 1): 8, + ("node", "a2", 2): 0, + ("node", "a2", 4): 0, + ("card", "310p", 1): 1, + ("card", "310p", 2): 2, + ("card", "310p", 4): 4, +} + +# Separator-bounded chip tokens: A2/a2 must not be part of A22B, etc. +# 310p matches the 310 / 310p / v310 token forms used in filenames and dir names. +_CHIP_310P = re.compile(r"(? single YAML path + # accuracy -> YAML path list passed as config_paths + # ops -> pytest target path or grouped path list + case_path: list[str] | str = "" + size: int | None = None # node count for multi-node + + +# --------------------------------------------------------------------------- +# Parsing helpers +# --------------------------------------------------------------------------- + + +def _load_runner_map(runner_label_path: Path) -> dict[tuple[str, int], str]: + """Build (chip, npu_num) -> runner_label reverse map from runner_label.json.""" + with open(runner_label_path, encoding="utf-8") as f: + data: dict[str, dict[str, Any]] = json.load(f) + return {(info["chip"], info["npu_num"]): label for label, info in data.items()} + + +def _resolve_runner( + chip: str, + resource_type: str, + resource_num: int, + runner_map: dict[tuple[str, int], str], +) -> str: + """Resolve a runner label from chip/resource metadata. + + The schedule config intentionally does not carry runner labels. It only + encodes topology through the path, then this helper maps that topology to + runner_label.json's (chip, npu_num) space. + """ + npu_num = _NPU_NUM.get((resource_type, chip, resource_num), 0) + key = (chip, npu_num) + runner = runner_map.get(key) + if runner is None: + raise ValueError( + f"No runner for chip={chip!r}, {resource_type}x{resource_num} " + f"(npu_num={npu_num}). Add an entry to runner_label.json." + ) + return runner + + +def _detect_resource(path: str) -> tuple[str, str, int]: + """Return (resource_dir, resource_type, resource_num). + + Every routable path must include exactly one supported resource directory. + This keeps ambiguous paths from silently choosing the wrong matrix. + """ + parts = Path(path.replace("\\", "/")).parts + matches = [p for p in parts if p in RESOURCE_DIRS] + if not matches: + raise ValueError(f"No resource directory in path {path!r}. Expected one of: {', '.join(RESOURCE_DIRS)}.") + if len(matches) > 1: + raise ValueError( + f"Multiple resource directories in path {path!r}: {matches}. Each path must contain exactly one." + ) + resource_dir = matches[0] + resource_type, resource_num = RESOURCE_DIRS[resource_dir] + return resource_dir, resource_type, resource_num + + +def _detect_chip(path: str) -> str: + """Infer chip from separator-bounded path tokens, defaulting to a3.""" + norm = path.replace("\\", "/") + if _CHIP_310P.search(norm): + return "310p" + if _CHIP_A2.search(norm): + return "a2" + if _CHIP_A3.search(norm): + return "a3" + return "a3" + + +def _detect_multi_node_type(path: str) -> str: + """Infer the multi-node launch mode from the model config filename.""" + stem = Path(path.replace("\\", "/")).stem + return "external_dp" if "external_dp" in stem else "internal_dp" + + +def _derive_name(path: str) -> str: + """Use the path stem as the stable case name for dedupe and summaries.""" + return Path(path.replace("\\", "/").rstrip("/")).stem + + +# --------------------------------------------------------------------------- +# Directory helpers +# --------------------------------------------------------------------------- + + +def _normalize_path(raw: Any) -> str: + """Normalize schedule entries before matching or expanding frameworks.""" + return str(raw).strip().replace("\\", "/").rstrip("/") + + +def _is_directory_entry(raw: Any, path: str) -> bool: + """Heuristic: an entry is a directory if it ends with '/', exists as a dir, + or carries no file extension.""" + if str(raw).rstrip().endswith("/"): + return True + if Path(path).is_dir(): + return True + return Path(path).suffix == "" + + +def _list_dir_files(dir_path: str, patterns: list[str]) -> list[str]: + """Recursively list files matching framework-provided patterns.""" + p = Path(dir_path) + files: set[str] = set() + for pat in patterns: + files.update(str(f).replace("\\", "/") for f in p.rglob(pat)) + return sorted(files) + + +def _group_by_chip(files: list[str]) -> dict[str, list[str]]: + """Group files by their detected chip (deterministic ordering preserved).""" + groups: dict[str, list[str]] = {} + for f in files: + groups.setdefault(_detect_chip(f), []).append(f) + return groups + + +# --------------------------------------------------------------------------- +# Frameworks +# --------------------------------------------------------------------------- + + +class BaseFramework: + """Interface for framework-specific path parsing and matrix conversion.""" + + name: str + output_names: tuple[str, ...] + + def __init__(self, runner_map: dict[tuple[str, int], str]): + self.runner_map = runner_map + + def match(self, path: str) -> bool: + """Return True when this framework owns the raw schedule path.""" + raise NotImplementedError + + def expand(self, raw: Any) -> list[PeriodicCase]: + """Convert a raw file or directory entry into normalized cases.""" + raise NotImplementedError + + def group(self, cases: list[PeriodicCase]) -> dict[str, list[dict]]: + """Convert this framework's cases into GitHub Actions matrix items.""" + raise NotImplementedError + + +class ModelFramework(BaseFramework): + """Handle model YAML configs and split them into single/multi-node matrices.""" + + name = "model" + output_names = ("single_node_matrix", "multi_node_matrix") + + def match(self, path: str) -> bool: + return _normalize_path(path).startswith(f"{_SCHEDULE_ROOT}/model/") + + def expand(self, raw: Any) -> list[PeriodicCase]: + path = _normalize_path(raw) + if _is_directory_entry(raw, path): + # Directory entries still need an explicit resource segment. Without + # it, nested YAMLs from different topologies could be mixed together. + _detect_resource(path) + files = _list_dir_files(path, ["*.yaml", "*.yml"]) + if not files: + raise ValueError(f"Directory entry {path!r} contains no routable files.") + return [self._case_from_file(f) for f in files] + return [self._case_from_file(path)] + + def _case_from_file(self, path: str) -> PeriodicCase: + """Parse one model YAML into a case and decide its route.""" + resource_dir, resource_type, resource_num = _detect_resource(path) + chip = _detect_chip(path) + + if resource_type == "card" or resource_num == 1: + route = "single_node" + runner = _resolve_runner(chip, resource_type, resource_num, self.runner_map) + multi_node_type = None + size = None + else: + route = "multi_node" + runner = "" + multi_node_type = _detect_multi_node_type(path) + size = resource_num + + return PeriodicCase( + name=_derive_name(path), + path=path, + framework=self.name, + route=route, + chip=chip, + resource_type=resource_type, + resource_num=resource_num, + resource_dir=resource_dir, + runner=runner, + multi_node_type=multi_node_type, + case_path=path, + size=size, + ) + + def group(self, cases: list[PeriodicCase]) -> dict[str, list[dict]]: + """Build model matrices while preserving workflow output shape.""" + single_node = [] + multi_node = [] + for case in cases: + if case.route == "single_node": + single_node.append( + { + "name": case.name, + "chip": case.chip, + "runner": case.runner, + "config_path": case.case_path, + "tests": "", + "extra_components": False, + } + ) + elif case.route == "multi_node": + multi_node.append( + { + "name": case.name, + "chip": case.chip, + "config_path": case.case_path, + "multi_node_type": case.multi_node_type or "internal_dp", + "extra_components": False, + "size": case.size or case.resource_num, + } + ) + else: + raise ValueError(f"Unknown model route: {case.route}") + + multi_node.sort(key=lambda e: -e.get("size", 0)) + return { + "single_node_matrix": single_node, + "multi_node_matrix": multi_node, + } + + +class AccuracyFramework(BaseFramework): + """Handle accuracy YAML configs, grouping directory entries by chip.""" + + name = "accuracy" + output_names = ("accuracy_matrix",) + + def match(self, path: str) -> bool: + return _normalize_path(path).startswith(f"{_SCHEDULE_ROOT}/accuracy/") + + def expand(self, raw: Any) -> list[PeriodicCase]: + path = _normalize_path(raw) + if _is_directory_entry(raw, path): + # Accuracy directories become one job per chip, so the directory + # itself must identify a single resource size. + _detect_resource(path) + files = _list_dir_files(path, ["*.yaml", "*.yml"]) + if not files: + raise ValueError(f"Directory entry {path!r} contains no routable files.") + return self._cases_from_directory(path, files) + if not path.endswith((".yaml", ".yml")): + raise ValueError(f"Accuracy entries must be YAML configs: {path}") + return [self._case_from_files(path, [path])] + + def _case_from_files(self, path: str, files: list[str]) -> PeriodicCase: + """Create one accuracy case from one or more YAML config paths.""" + resource_dir, resource_type, resource_num = _detect_resource(path) + if resource_type != "card": + raise ValueError("Accuracy framework only supports card resources.") + + chip = _detect_chip(path) + runner = _resolve_runner(chip, resource_type, resource_num, self.runner_map) + return PeriodicCase( + name=_derive_name(path), + path=path, + framework=self.name, + route="accuracy", + chip=chip, + resource_type=resource_type, + resource_num=resource_num, + resource_dir=resource_dir, + runner=runner, + case_path=files, + ) + + def _cases_from_directory(self, dir_path: str, files: list[str]) -> list[PeriodicCase]: + """Bundle directory YAMLs by chip so each group uses the right runner.""" + resource_dir, resource_type, resource_num = _detect_resource(dir_path) + if resource_type != "card": + raise ValueError("Accuracy framework only supports card resources.") + + cases = [] + groups = _group_by_chip(files) + for chip in sorted(groups): + group_files = sorted(groups[chip]) + cases.append( + PeriodicCase( + name=f"{resource_dir}-{chip}", + path=dir_path, + framework=self.name, + route="accuracy", + chip=chip, + resource_type=resource_type, + resource_num=resource_num, + resource_dir=resource_dir, + runner=_resolve_runner(chip, resource_type, resource_num, self.runner_map), + case_path=group_files, + ) + ) + return cases + + def group(self, cases: list[PeriodicCase]) -> dict[str, list[dict]]: + """Expose accuracy payloads as config_paths for the workflow.""" + return { + "accuracy_matrix": [ + { + "name": case.name, + "chip": case.chip, + "runner": case.runner, + "config_paths": case.case_path, + } + for case in cases + ] + } + + +class OpsFramework(BaseFramework): + """Handle pytest ops targets, grouping directory entries by chip.""" + + name = "ops" + output_names = ("ops_matrix",) + + def match(self, path: str) -> bool: + return _normalize_path(path).startswith(f"{_SCHEDULE_ROOT}/ops/") + + def expand(self, raw: Any) -> list[PeriodicCase]: + path = _normalize_path(raw) + if _is_directory_entry(raw, path): + # Ops directories can contain chip-specific files. Group after + # discovery so 310p/a2/a3 tests land on matching runners. + _detect_resource(path) + files = _list_dir_files(path, ["test_*.py"]) + if not files: + raise ValueError(f"Directory entry {path!r} contains no routable files.") + return self._cases_from_directory(path, files) + return [self._case_from_file(path)] + + def _case_from_file(self, path: str) -> PeriodicCase: + """Create one ops case from a direct pytest target path.""" + resource_dir, resource_type, resource_num = _detect_resource(path) + chip = _detect_chip(path) + runner = _resolve_runner(chip, resource_type, resource_num, self.runner_map) + return PeriodicCase( + name=_derive_name(path), + path=path, + framework=self.name, + route="ops", + chip=chip, + resource_type=resource_type, + resource_num=resource_num, + resource_dir=resource_dir, + runner=runner, + case_path=path, + ) + + def _cases_from_directory(self, dir_path: str, files: list[str]) -> list[PeriodicCase]: + """Bundle discovered pytest files by chip for per-runner jobs.""" + resource_dir, resource_type, resource_num = _detect_resource(dir_path) + base_name = _derive_name(dir_path) + cases = [] + groups = _group_by_chip(files) + for chip in sorted(groups): + group_files = sorted(groups[chip]) + cases.append( + PeriodicCase( + name=f"{base_name}-{chip}", + path=dir_path, + framework=self.name, + route="ops", + chip=chip, + resource_type=resource_type, + resource_num=resource_num, + resource_dir=resource_dir, + runner=_resolve_runner(chip, resource_type, resource_num, self.runner_map), + case_path=group_files, + ) + ) + return cases + + def group(self, cases: list[PeriodicCase]) -> dict[str, list[dict]]: + """Expose ops payloads as space-separated pytest targets.""" + return { + "ops_matrix": [ + { + "name": case.name, + "chip": case.chip, + "runner": case.runner, + "tests": " ".join(case.case_path) if isinstance(case.case_path, list) else case.case_path, + } + for case in cases + ] + } + + +def _build_frameworks(runner_map: dict[tuple[str, int], str]) -> list[BaseFramework]: + """Register supported frameworks in matching order.""" + return [ + ModelFramework(runner_map), + AccuracyFramework(runner_map), + OpsFramework(runner_map), + ] + + +def _find_framework(path: str, frameworks: list[BaseFramework]) -> BaseFramework: + """Find the single framework responsible for a raw schedule path.""" + matched = [fw for fw in frameworks if fw.match(path)] + if not matched: + raise ValueError(f"No framework matched path {path!r}.") + if len(matched) > 1: + names = [fw.name for fw in matched] + raise ValueError(f"Multiple frameworks matched path {path!r}: {names}") + return matched[0] + + +# --------------------------------------------------------------------------- +# Schedule selection, filtering, deduplication +# --------------------------------------------------------------------------- + + +def _select_schedules(config: dict, event_name: str, cron: str, schedule_name: str) -> list[dict]: + """Return schedule sections matching the current GitHub event.""" + selected = [] + for schedule in config.get("periodic_tests", []): + sched_cron = schedule.get("cron", "") + sched_name = schedule.get("name", "") + if event_name == "schedule" and cron: + if sched_cron == cron: + selected.append(schedule) + elif event_name == "workflow_dispatch": + if schedule_name and sched_name == schedule_name: + selected.append(schedule) + elif not schedule_name or schedule_name == "manual": + if schedule.get("files"): + selected.append(schedule) + return selected + + +def _dedupe_cases(cases: list[PeriodicCase]) -> tuple[list[PeriodicCase], dict[str, list[PeriodicCase]]]: + """Remove duplicates by name and record duplicate groups for reporting.""" + seen: dict[str, PeriodicCase] = {} + duplicates: dict[str, list[PeriodicCase]] = {} + result = [] + for c in cases: + first_case = seen.get(c.name) + if first_case is None: + seen[c.name] = c + result.append(c) + else: + duplicates.setdefault(c.name, [first_case]).append(c) + return result, duplicates + + +def _split_test_filters(test_filter: str) -> list[str]: + """Normalize comma-separated --test-filter input into individual filters.""" + filters = [item.strip() for item in test_filter.split(",") if item.strip()] + return filters or ["all"] + + +def _matches_filter(case: PeriodicCase, test_filter: str) -> bool: + """Match one or more filters against path, filename, stem, segment, or name.""" + filters = _split_test_filters(test_filter) + if len(filters) > 1: + return any(_matches_filter(case, item) for item in filters) + + test_filter = filters[0] + if test_filter == "all": + return True + paths = case.case_path if isinstance(case.case_path, list) else [case.case_path or case.path] + name = case.name + # Priority: full path > filename > stem > path segment > name/path substring + for path in paths: + filename = Path(path).name if path else "" + stem = Path(path).stem if path else "" + for target in [path, filename, stem]: + if target and target == test_filter: + return True + if any(part == test_filter for part in Path(path.replace("\\", "/")).parts): + return True + return test_filter in name or any(path and test_filter in path for path in paths) + + +# --------------------------------------------------------------------------- +# Output helpers +# --------------------------------------------------------------------------- + + +def _build_summary( + all_cases: list[PeriodicCase], + outputs: dict[str, list[dict]], + duplicate_cases: dict[str, list[PeriodicCase]], +) -> str: + """Build the human-readable summary emitted to stderr and GITHUB_OUTPUT.""" + summary_lines = ["=== Selected test cases ==="] + for c in all_cases: + loc = ", ".join(c.case_path) if isinstance(c.case_path, list) else c.case_path + runner = c.runner or "workflow-default" + summary_lines.append(f" [{c.framework:8s}] [{c.route:11s}] [{c.chip}] [{runner:30s}] {c.name} ({loc})") + summary_lines.append( + f"\nTotals: " + f"{len(outputs.get('single_node_matrix', []))} single-node, " + f"{len(outputs.get('multi_node_matrix', []))} multi-node, " + f"{len(outputs.get('accuracy_matrix', []))} accuracy, " + f"{len(outputs.get('ops_matrix', []))} ops" + ) + if duplicate_cases: + summary_lines.append("\nWARNING: duplicate test case names detected; kept the first occurrence:") + for name in sorted(duplicate_cases): + cases = duplicate_cases[name] + summary_lines.append(f" {name}:") + summary_lines.append(f" kept: {cases[0].path}") + for case in cases[1:]: + summary_lines.append(f" duplicate: {case.path}") + return "\n".join(summary_lines) + + +def _write_outputs( + outputs: dict[str, list[dict]], + image_targets: list[str], + summary: str, +) -> None: + """Write GitHub Actions outputs, or print debug output for local runs.""" + print(summary, file=sys.stderr) + + lines = [ + f"single_node_matrix={json.dumps(outputs.get('single_node_matrix', []))}", + f"multi_node_matrix={json.dumps(outputs.get('multi_node_matrix', []))}", + f"accuracy_matrix={json.dumps(outputs.get('accuracy_matrix', []))}", + f"ops_matrix={json.dumps(outputs.get('ops_matrix', []))}", + f"image_build_targets={json.dumps(image_targets)}", + ] + + output_path = os.environ.get("GITHUB_OUTPUT", "") + if output_path: + with open(output_path, "a", encoding="utf-8") as f: + f.write("\n".join(lines) + "\n") + f.write("selected_cases_summary< None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--config", required=True, help="Path to schedule_config.yaml") + parser.add_argument("--runner-label", help="Path to runner_label.json (default: same dir as config)") + parser.add_argument("--event-name", default="workflow_dispatch") + parser.add_argument("--cron", default="") + parser.add_argument("--schedule-name", default="") + parser.add_argument("--test-filter", default="all") + args = parser.parse_args() + + with open(args.config, encoding="utf-8") as f: + config = yaml.safe_load(f) + + runner_label_path = Path(args.runner_label) if args.runner_label else Path(args.config).parent / "runner_label.json" + runner_map = _load_runner_map(runner_label_path) + frameworks = _build_frameworks(runner_map) + + schedules = _select_schedules(config, args.event_name, args.cron, args.schedule_name) + if not schedules: + print( + f"No schedules matched event={args.event_name!r} cron={args.cron!r} schedule_name={args.schedule_name!r}", + file=sys.stderr, + ) + + all_cases: list[PeriodicCase] = [] + errors: list[str] = [] + for schedule in schedules: + for raw in schedule.get("files", []): + path = _normalize_path(raw) + try: + # Dispatch is path-prefix based. Frameworks own all parsing and + # expansion after this point, including directory semantics. + framework = _find_framework(path, frameworks) + all_cases.extend(framework.expand(raw)) + except Exception as exc: + errors.append(f" {raw!r}: {exc}") + + if errors: + print("Errors parsing schedule entries:", file=sys.stderr) + for e in errors: + print(e, file=sys.stderr) + sys.exit(1) + + all_cases, duplicate_cases = _dedupe_cases(all_cases) + + test_filter = args.test_filter.strip() + if test_filter: + all_cases = [c for c in all_cases if _matches_filter(c, test_filter)] + + # Keep main framework-agnostic: split by framework name, then let each + # framework produce its own workflow-compatible matrix payloads. + cases_by_framework: dict[str, list[PeriodicCase]] = {fw.name: [] for fw in frameworks} + for case in all_cases: + cases_by_framework[case.framework].append(case) + + outputs: dict[str, list[dict]] = {name: [] for name in _MATRIX_OUTPUT_NAMES} + for fw in frameworks: + grouped = fw.group(cases_by_framework[fw.name]) + for output_name, items in grouped.items(): + # Guard the public output contract so a framework cannot introduce a + # new output name without an explicit workflow update. + if output_name not in outputs: + raise ValueError(f"Framework {fw.name!r} returned unknown output {output_name!r}.") + outputs[output_name].extend(items) + + image_targets = sorted({c.chip for c in all_cases}) + summary = _build_summary(all_cases, outputs, duplicate_cases) + _write_outputs(outputs, image_targets, summary) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/scripts/resolve_nightly_tests.py b/.github/workflows/scripts/resolve_nightly_tests.py index 01c6369c3..b5de416a9 100644 --- a/.github/workflows/scripts/resolve_nightly_tests.py +++ b/.github/workflows/scripts/resolve_nightly_tests.py @@ -1,3 +1,4 @@ +import argparse import base64 import json import os @@ -10,57 +11,75 @@ import yaml -def parse_names(b64_content): - if not b64_content: - return set() - try: - content = base64.b64decode(b64_content).decode() - parsed = yaml.safe_load(content) - names = set() - for job in parsed.get("jobs", {}).values(): - tc = job.get("strategy", {}).get("matrix", {}).get("test_config", []) - if isinstance(tc, list): - for entry in tc: - if isinstance(entry, dict) and "name" in entry: - names.add(entry["name"]) - return names - except Exception: - return set() +# ----- shared helpers ----- + +def _collect_names(node, names): + """Recursively collect `name` fields from list-of-dict entries.""" + if isinstance(node, dict): + for v in node.values(): + _collect_names(v, names) + elif isinstance(node, list): + for item in node: + if isinstance(item, dict) and isinstance(item.get("name"), str): + names.add(item["name"]) + elif isinstance(item, (dict, list)): + _collect_names(item, names) -def parse_accuracy_names(b64_content): + +def parse_nightly_matrix(b64_content, soc): + """Read base64-encoded matrix YAML and return a set of all `name` strings under `soc`.""" if not b64_content: return set() try: - parsed = json.loads(base64.b64decode(b64_content).decode()) + parsed = yaml.safe_load(base64.b64decode(b64_content).decode()) names = set() - for group_list in parsed.values(): - if isinstance(group_list, list): - for group in group_list: - if isinstance(group, dict) and "name" in group: - names.add(group["name"]) + soc_block = parsed.get(soc, {}) + if isinstance(soc_block, dict): + _collect_names(soc_block, names) return names except Exception: return set() -def main(): - a2_names = parse_names(os.environ.get("A2_RAW", "")) - a2_names |= parse_accuracy_names(os.environ.get("A2_ACC_GROUPS", "")) - a3_names = parse_names(os.environ.get("A3_RAW", "")) +def _walk(matrix, path): + """Walk a dot-separated path in the matrix; return the node at the end, or {} if missing.""" + node = matrix + for key in path.split("."): + if isinstance(node, dict): + node = node.get(key, {}) + else: + return {} + return node + + +# ----- mode: dispatch ----- + + +def cmd_dispatch(_args): + """Resolve /nightly tokens and emit dispatch flags. - raw_test_cases = os.environ.get("TEST_CASES", "") - test_cases = [tc.strip() for tc in raw_test_cases.split(",") if tc.strip()] + Reads NIGHTLY_MATRIX (base64-encoded nightly_config.yaml) and TEST_CASES + (comma-separated tokens from the /nightly comment). Writes to GITHUB_OUTPUT: + - dispatch_a2=true|false + - dispatch_a3=true|false + - test_cases= (only when a / token matched an accuracy group) + """ + matrix_b64 = os.environ.get("NIGHTLY_MATRIX", "") + a2_names = parse_nightly_matrix(matrix_b64, "a2") + a3_names = parse_nightly_matrix(matrix_b64, "a3") + + raw = os.environ.get("TEST_CASES", "") + test_cases = [tc.strip() for tc in raw.split(",") if tc.strip()] da2, da3 = False, False - transformed_tc = None + transformed = None for tc in test_cases: if "/" in tc: - parts = tc.split("/", 1) - group_name, model_name = parts[0], parts[1] - if group_name in a2_names: + g, m = tc.split("/", 1) + if g in a2_names: da2 = True - transformed_tc = f"{group_name},{model_name}" + transformed = f"{g},{m}" break elif tc == "accuracy-group": da2 = True @@ -71,11 +90,60 @@ def main(): da3 = True with open(os.environ["GITHUB_OUTPUT"], "a") as f: - if transformed_tc: - f.write(f"test_cases={transformed_tc}\n") + if transformed: + f.write(f"test_cases={transformed}\n") f.write(f"dispatch_a2={str(da2).lower()}\n") f.write(f"dispatch_a3={str(da3).lower()}\n") +# ----- mode: matrix ----- + + +def cmd_matrix(_args): + """Extract matrix sections per MATRIX_OUTPUTS spec. + + Reads MATRIX_FILE (path to nightly_config.yaml) and MATRIX_OUTPUTS + (JSON object {output_name: "dot.path.in.yaml"}). Writes to GITHUB_OUTPUT: + - = (one entry per spec item) + """ + matrix_file = os.environ.get("MATRIX_FILE", "") + if not matrix_file: + raise SystemExit("MATRIX_FILE env var is required for --mode=matrix") + spec = json.loads(os.environ.get("MATRIX_OUTPUTS", "{}")) + + with open(matrix_file) as f: + matrix = yaml.safe_load(f) or {} + + with open(os.environ["GITHUB_OUTPUT"], "a") as f: + for name, path in spec.items(): + node = _walk(matrix, path) + cfg = node if isinstance(node, list) else [] + f.write(f"{name}={json.dumps(cfg)}\n") + + +# ----- entry point ----- + +_MODES = { + "dispatch": cmd_dispatch, + "matrix": cmd_matrix, +} + + +def main(): + parser = argparse.ArgumentParser( + description="Nightly matrix config helper (used by /nightly dispatcher and A2/A3 workflows).", + ) + parser.add_argument( + "--mode", + choices=sorted(_MODES.keys()), + default="dispatch", + help="Operation mode. 'dispatch' resolves /nightly names into A2/A3 dispatch flags " + "(used by pr_nightly_command.yml). 'matrix' extracts matrix sections per a JSON " + "spec (used by A2/A3 generate-* jobs). Default: dispatch.", + ) + args = parser.parse_args() + _MODES[args.mode](args) + + if __name__ == "__main__": main() diff --git a/.github/workflows/scripts/run_selected_tests.sh b/.github/workflows/scripts/run_selected_tests.sh index 55c30f1bd..cee614fc8 100755 --- a/.github/workflows/scripts/run_selected_tests.sh +++ b/.github/workflows/scripts/run_selected_tests.sh @@ -2,7 +2,7 @@ set -euo pipefail if [ "$#" -lt 4 ]; then - echo "Usage: $0 [--timing] [test ...]" + echo "Usage: $0 [test ...]" exit 1 fi @@ -10,13 +10,6 @@ npu_type="$1" num_npus="$2" mode="$3" shift 3 - -record_timing=false -if [ "$1" = "--timing" ]; then - record_timing=true - shift -fi - targets=("$@") if [ "${mode}" != "with-device" ] && [ "${mode}" != "without-device" ]; then @@ -26,7 +19,6 @@ fi test_results=() failed_logs=() -timing_entries=() test_index=0 pytest_log_dir="${RUNNER_TEMP:-/tmp}/selected-tests-${npu_type}-${num_npus}card" @@ -73,29 +65,18 @@ run_pytest_target() { local log_file="${pytest_log_dir}/${test_index}-${log_name}.log" echo "::group::${target}" echo -e "\033[1;34m=== Running target: ${target} ===\033[0m" - local start_time=0 - if [ "${record_timing}" = true ]; then - start_time=$(date +%s%N) - fi set +e pytest -sv --color=yes "${target}" 2>&1 | tee "${log_file}" local status=${PIPESTATUS[0]} set -e - if [ "${record_timing}" = true ]; then - local elapsed_ns=$(( $(date +%s%N) - start_time )) - local elapsed=$(( elapsed_ns / 1000000000 )).$(( (elapsed_ns % 1000000000) / 100000000 )) - timing_entries+=("{\"name\":\"${target}\",\"passed\":$([ ${status} -eq 0 ] && echo true || echo false),\"elapsed\":${elapsed}}") - fi echo "::endgroup::" if [ "${status}" -eq 0 ]; then test_results+=("${target}|PASSED|${log_file}") else test_results+=("${target}|FAILED|${log_file}") failed_logs+=("${target}|${log_file}") - if [ "${record_timing}" != true ]; then - print_summary - exit "${status}" - fi + print_summary + exit "${status}" fi } @@ -108,50 +89,21 @@ run_pytest_batch() { echo "::group::${target}" echo -e "\033[1;34m=== Running target: ${target} ===\033[0m" - local start_time=0 - if [ "${record_timing}" = true ]; then - start_time=$(date +%s%N) - fi set +e pytest -sv --color=yes "${batch_targets[@]}" 2>&1 | tee "${log_file}" local status=${PIPESTATUS[0]} set -e - if [ "${record_timing}" = true ]; then - local elapsed_ns=$(( $(date +%s%N) - start_time )) - local elapsed=$(( elapsed_ns / 1000000000 )).$(( (elapsed_ns % 1000000000) / 100000000 )) - timing_entries+=("{\"name\":\"${target}\",\"passed\":$([ ${status} -eq 0 ] && echo true || echo false),\"elapsed\":${elapsed}}") - fi echo "::endgroup::" if [ "${status}" -eq 0 ]; then test_results+=("${target}|PASSED|${log_file}") else test_results+=("${target}|FAILED|${log_file}") failed_logs+=("${target}|${log_file}") - if [ "${record_timing}" != true ]; then - print_summary - exit "${status}" - fi + print_summary + exit "${status}" fi } -print_timing_json() { - if [ "${#timing_entries[@]}" -eq 0 ]; then - return - fi - local json="[" - local i=0 - for entry in "${timing_entries[@]}"; do - if [ "${i}" -gt 0 ]; then - json+="," - fi - json+="${entry}" - i=$((i + 1)) - done - json+="]" - echo "${json}" > "${pytest_log_dir}/test_timing_data.json" - echo -e "\033[1;34m=== Timing data written to ${pytest_log_dir}/test_timing_data.json ===\033[0m" -} - print_test_info if [ "${npu_type}" = "cpu" ]; then @@ -176,5 +128,4 @@ else done fi -print_timing_json print_summary diff --git a/.github/workflows/scripts/schedule_config.yaml b/.github/workflows/scripts/schedule_config.yaml new file mode 100644 index 000000000..9271f5172 --- /dev/null +++ b/.github/workflows/scripts/schedule_config.yaml @@ -0,0 +1,191 @@ +# +# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# This file is a part of the vllm-ascend project. +# +# Central scheduling configuration for periodic CI tests. +# +# To add a new test: +# 1. Place the YAML config (or .py file / directory) under tests/e2e/schedule/. +# 2. Register its path below in the relevant schedule section. +# +# All entries must be plain path strings. Do not add chip, npu_num, runner, +# framework, route, multi_node_type, or extra_components — these are inferred +# from the path by parse_schedule_config.py. +# +# Directory layout rules (framework is always the 4th path segment): +# tests/e2e/schedule/model///*.yaml -> model tests +# tests/e2e/schedule/accuracy///*.yaml -> accuracy tests (direct configs) +# tests/e2e/schedule/ops//*.py -> ops tests (file) +# tests/e2e/schedule/ops// -> ops tests (directory) +# +# Supported resource directories (English form only): +# one_card, two_card, four_card, eight_card, one_node, two_node, four_node +# +# Chip is auto-detected from filename (a2/A2 token -> a2, else a3), or from the +# explicit a2/a3/310p directory for accuracy configs. +# Accuracy configs are real executable YAML files (not model-list groups); a +# directory entry is grouped into one job per chip (config_paths list). +# External DP: filename stem must contain external_dp -> external_dp routing. + +periodic_tests: + + # ========================================================================= + # nightly-main (runs every day at 23:45 Beijing time = 15:45 UTC) + # ========================================================================= + - name: nightly-main + cron: "45 15 * * *" + files: + # Temporary path-check targets for nightly workflow refactor validation. + - tests/e2e/schedule/model/PathCheck/two_node/path_check_external_dp.yaml + - tests/e2e/schedule/model/PathCheck/one_card/path_check_single_node.yaml + - tests/e2e/schedule/accuracy/one_card/a3/path_check_accuracy.yaml + - tests/e2e/schedule/ops/one_card/test_path_check_ops.py + disabled_files_for_path_check: + # ------------------------------------------------------------------- + # A3 four-node (64 NPUs, runs first to claim largest resource window) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/DeepSeek/four_node/DeepSeek-V3_2-W8A8-EP.yaml + + # ------------------------------------------------------------------- + # A3 two-node internal DP + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/DeepSeek/two_node/DeepSeek-R1-W8A8-longseq.yaml + - tests/e2e/schedule/model/DeepSeek/two_node/DeepSeek-V3_2-W8A8-A3-dual-nodes.yaml + - tests/e2e/schedule/model/DeepSeek/two_node/DeepSeek-V3.1-BF16.yaml + - tests/e2e/schedule/model/Qwen/two_node/Qwen3-235B-A22B.yaml + - tests/e2e/schedule/model/Qwen/two_node/Qwen3-235B-W8A8-EPLB.yaml + - tests/e2e/schedule/model/Qwen/two_node/Qwen3-235B-A22B-Mooncake-Layerwise.yaml + - tests/e2e/schedule/model/Qwen/two_node/Qwen3-235B-W8A8-longseq.yaml + - tests/e2e/schedule/model/Qwen/two_node/Qwen3-235B-disagg-pd.yaml + - tests/e2e/schedule/model/Qwen/two_node/Qwen3-VL-235B-disagg-pd.yaml + - tests/e2e/schedule/model/GLM/two_node/GLM5_1-W8A8-A3-dual-nodes.yaml + + # A3 two-node external DP (filename contains external_dp -> external_dp routing) + - tests/e2e/schedule/model/GLM/two_node/GLM5_1-W8A8-EP-external_dp.yaml + + # ------------------------------------------------------------------- + # A3 one_node (full 16-NPU node) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/DeepSeek/one_node/DeepSeek-R1-0528-W8A8.yaml + - tests/e2e/schedule/model/DeepSeek/one_node/MTPX-DeepSeek-R1-0528-W8A8.yaml + - tests/e2e/schedule/model/DeepSeek/one_node/Prefix-Cache-DeepSeek-R1-0528-W8A8.yaml + - tests/e2e/schedule/model/DeepSeek/one_node/DeepSeek-V3.2-W8A8.yaml + - tests/e2e/schedule/model/DeepSeek/one_node/DeepSeek-V4-Flash-W8A8-A3.yaml + - tests/e2e/schedule/model/GLM/one_node/GLM-4.7.yaml + - tests/e2e/schedule/model/Kimi/one_node/Kimi-K2-Thinking.yaml + - tests/e2e/schedule/model/Kimi/one_node/Kimi-K2.5.yaml + - tests/e2e/schedule/model/Qwen/one_node/Qwen3-235B-A22B-W8A8.yaml + - tests/e2e/schedule/model/Qwen/one_node/Qwen3-VL-235B-A22B-Instruct-W8A8.yaml + - tests/e2e/schedule/model/Qwen/one_node/Qwen3.5-122B-A10B-W8A8-A3.yaml + - tests/e2e/schedule/model/Qwen/one_node/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml + - tests/e2e/schedule/model/MiniMax/one_node/MiniMax-M2.5-w8a8-QuaRot-A3.yaml + - tests/e2e/schedule/model/Hy/one_node/Hy3-preview.yaml + + # ------------------------------------------------------------------- + # A3 four_card (4-NPU partial node) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/Qwen/four_card/Qwen3-30B-A3B-W8A8.yaml + - tests/e2e/schedule/model/Qwen/four_card/Qwen3-32B-Int8.yaml + - tests/e2e/schedule/model/Qwen/four_card/Prefix-Cache-Qwen3-32B-Int8.yaml + + # ------------------------------------------------------------------- + # A3 two_card (2-NPU partial node) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/Qwen/two_card/Qwen3.5-27B-w8a8-A3.yaml + - tests/e2e/schedule/model/Qwen/two_card/Qwen3-30B-A3B-W4A8-llm-compressor.yaml + - tests/e2e/schedule/model/Qwen/two_card/Qwen3-30B-QuaRot-eagle3.yaml + - tests/e2e/schedule/model/Qwen/two_card/Qwen3-32B-QuaRot-eagle3.yaml + + # ------------------------------------------------------------------- + # A2 two-node + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/Qwen/two_node/Qwen3-235B-A22B-A2.yaml + - tests/e2e/schedule/model/GLM/two_node/GLM5_1-W8A8-A2-dual-nodes.yaml + - tests/e2e/schedule/model/Kimi/two_node/Kimi-K2_5-W4A8-A2-dual-nodes.yaml + + # ------------------------------------------------------------------- + # A2 one_node (full 8-NPU node; chip auto-detected from filename) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/MiniMax/one_node/MiniMax-M2.5-w8a8-QuaRot-A2.yaml + - tests/e2e/schedule/model/Qwen/one_node/Qwen3.5-397B-A17B-w4a8-mtp-A2.yaml + + # ------------------------------------------------------------------- + # A2 four_card (4-NPU; chip auto-detected from filename) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/Qwen/four_card/Qwen3-VL-32B-Instruct-W8A8-A2.yaml + - tests/e2e/schedule/model/Qwen/four_card/Qwen3-32B-Int8-A2.yaml + + # ------------------------------------------------------------------- + # A2 two_card (2-NPU; chip auto-detected from filename) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/Qwen/two_card/Qwen3.5-27B-w8a8-A2.yaml + + # ------------------------------------------------------------------- + # Ops tests (chip and runner derived from path/filename) + # ------------------------------------------------------------------- + - tests/e2e/schedule/ops/one_card/ + - tests/e2e/schedule/ops/four_card/test_matmul_allreduce_add_rmsnorm_a2.py + - tests/e2e/schedule/ops/one_node/ + + # ------------------------------------------------------------------- + # Accuracy tests (direct config YAMLs; chip from the a2/a3/310p dir, + # parser groups each directory's configs into one job per chip) + # ------------------------------------------------------------------- + - tests/e2e/schedule/accuracy/one_card/a2/ + - tests/e2e/schedule/accuracy/two_card/a2/ + - tests/e2e/schedule/accuracy/four_card/a2/ + + # ========================================================================= + # weekly-main (runs every Monday at 23:45 Beijing time = 15:45 UTC) + # ========================================================================= + - name: weekly-main + cron: "45 15 * * 1" + files: + # Temporary path-check targets for nightly workflow refactor validation. + - tests/e2e/schedule/model/PathCheck/two_node/path_check_external_dp.yaml + - tests/e2e/schedule/model/PathCheck/one_card/path_check_single_node.yaml + - tests/e2e/schedule/accuracy/one_card/a3/path_check_accuracy.yaml + - tests/e2e/schedule/ops/one_card/test_path_check_ops.py + disabled_files_for_path_check: + # ------------------------------------------------------------------- + # A3 two-node (weekly-only configs) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/DeepSeek/two_node/DeepSeek-V3_2-W8A8-EP_weekly.yaml + - tests/e2e/schedule/model/DeepSeek/two_node/DeepSeek-V3.yaml + - tests/e2e/schedule/model/GLM/two_node/GLM-4.7-W8A8C8-Mooncake-Layerwise.yaml + + # ------------------------------------------------------------------- + # A3 one_node (weekly-unique configs) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/DeepSeek/one_node/DeepSeek-V3.2-W8A8_A3_weekly.yaml + - tests/e2e/schedule/model/GLM/one_node/GLM-5_1-W8A8_A3_weekly.yaml + - tests/e2e/schedule/model/GLM/one_node/GLM-5.yaml + - tests/e2e/schedule/model/Kimi/one_node/Kimi-K2.5-32k-512.yaml + - tests/e2e/schedule/model/MiniMax/one_node/MiniMax-M2.5-W8A8-A3.yaml + - tests/e2e/schedule/model/Qwen/one_node/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml + + # ------------------------------------------------------------------- + # A3 four_card (weekly-unique) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/Qwen/four_card/Qwen2.5-VL-7B-Instruct-EPD.yaml + - tests/e2e/schedule/model/Qwen/four_card/Qwen3-30B-A3B-W8A8-eagle3-mooncake.yaml + + # ------------------------------------------------------------------- + # Shared with nightly (same file runs on both schedules) + # ------------------------------------------------------------------- + - tests/e2e/schedule/model/Kimi/one_node/Kimi-K2.5.yaml + - tests/e2e/schedule/model/MiniMax/one_node/MiniMax-M2.5-w8a8-QuaRot-A3.yaml + - tests/e2e/schedule/model/Qwen/one_node/Qwen3.5-122B-A10B-W8A8-A3.yaml + - tests/e2e/schedule/model/Qwen/two_card/Qwen3.5-27B-w8a8-A3.yaml diff --git a/.github/workflows/scripts/select_tests.py b/.github/workflows/scripts/select_tests.py index 8c00a3e45..dba52c82d 100644 --- a/.github/workflows/scripts/select_tests.py +++ b/.github/workflows/scripts/select_tests.py @@ -16,29 +16,37 @@ # """Determine which tests to run based on changed files in a PR. -Two input modes are supported (mutually exclusive): - -- ``--changed-files`` / ``--diff-base``: PR-driven. The input is a list of - source files changed in a PR. Modules are matched by their - ``source_file_dependencies``, and their configured tests are collected - and routed to runners. - -- ``--explicit-e2e-tests``: Slash-command driven. The input is a list of - e2e test paths (files or directories) supplied via the ``/e2e`` PR - comment. Module matching is bypassed entirely; each path is routed - directly to the appropriate runner. - -Pipeline (PR-driven mode): +Pipeline: 1. Diff -- get changed files from git. 2. Match -- identify affected modules via test_config.yaml. 3. Collect -- gather test paths (always resolved to individual files). - 4. Route -- determine runner via config-driven runner_mapping. - 5. Partition -- split test groups across parallel runners by estimated time. - 6. Output -- write test_groups / has_tests / matched_modules. - -Routing is driven by ``test_config.yaml`` ``runner_mapping:`` (regex patterns). -Partition sizing by ``partition:`` config block. -See ``test_config.yaml`` for details. + 4. Route -- determine runner for each test file by convention: + UT: directory path pattern (a2/, a3_2/, 310p/, etc.) + E2E: directory path pattern (one_card, two_card, four_card, 310p) + 5. Output -- write test_groups / has_tests / matched_modules. + +Directory conventions for UT runner routing: + tests/ut// -> CPU runner (default) + tests/ut//a2/ -> A2 NPU x1 + tests/ut//a2_2/ -> A2 NPU x2 + tests/ut//a3_2/ -> A3 NPU x2 + tests/ut//a3_4/ -> A3 NPU x4 + tests/ut//310p/ -> 310P NPU x1 + +Directory conventions for E2E runner routing: + tests/e2e/pull_request/one_card/ -> A2 NPU x1 + tests/e2e/pull_request/two_card/ -> A3 NPU x2 + tests/e2e/pull_request/four_card/ -> A3 NPU x4 + *_310p.py under one/two-card paths -> 310P NPU x1 + *_310p.py under four-card paths -> 310P NPU x4 + +Usage: + python select_tests.py --diff-base origin/main + python select_tests.py --changed-files file1.py file2.py + +Flags: + --run-all-modules Run tests for all configured modules regardless of + changed files """ from __future__ import annotations @@ -79,77 +87,23 @@ class RunnerInfo: RunnerKey = tuple[int, NpuType] _DEFAULT_KEY: RunnerKey = (0, NpuType.CPU) -# Populated by _load_runner_mapping(). Ordered list of (regex, {key: RunnerKey}). -_RUNNER_MAPPING: list[tuple[re.Pattern, dict[str, RunnerKey]]] = [] - - -def _parse_runner_key(runner_key: str) -> RunnerKey: - """Parse ``a2_x1`` → ``(1, NpuType.A2)``, ``310p_x4`` → ``(4, NpuType._310P)``.""" - parts = runner_key.rsplit("_x", 1) - if len(parts) != 2: - raise ValueError(f"Invalid runner key: {runner_key!r}") - raw_type, raw_npus = parts - npu_type = NpuType(raw_type) - num_npus = int(raw_npus) - return (num_npus, npu_type) - - -def _load_runner_mapping(config_path: Path) -> None: - """Load runner mapping from the second YAML document into ``_RUNNER_MAPPING``. - - Config format:: - - runner_mapping: - : - default: - "310p": # optional override for 310P files - - Patterns are sorted longest first so more specific patterns match first. - """ - global _RUNNER_MAPPING - _RUNNER_MAPPING = [] - try: - docs = list(yaml.safe_load_all(config_path.read_text())) - if len(docs) >= 2: - meta = docs[1] or {} - raw = list((meta.get("runner_mapping", {}) or {}).items()) - raw.sort(key=lambda x: -len(x[0])) - for pattern_str, runner_config in raw: - runners: dict[str, RunnerKey] = {} - for key, val in runner_config.items(): - runners[key] = _parse_runner_key(val) - _RUNNER_MAPPING.append((re.compile(pattern_str), runners)) - except Exception: - pass - - -def _resolve_runner(file_path: str) -> RunnerKey | None: - """Match *file_path* against ``_RUNNER_MAPPING``. - - Returns the ``default`` runner for the first matching pattern. - If the filename contains ``_310p`` and the matched pattern has - a ``"310p"`` entry, that entry is returned instead. - """ - route_path = _as_posix_path(_pytest_node_file_path(file_path)) - for pattern, runners in _RUNNER_MAPPING: - if pattern.search(route_path): - if "_310p" in Path(route_path).name and "310p" in runners: - return runners["310p"] - return runners.get("default") - return None - - -def _route_ut_dir(dir_path: str) -> RunnerKey: - result = _resolve_runner(dir_path) - return result if result is not None else _DEFAULT_KEY - - -def _route_e2e_dir(dir_path: str) -> RunnerKey | None: - return _resolve_runner(dir_path) - - -def _route_e2e_file(file_path: str) -> RunnerKey | None: - return _resolve_runner(file_path) +_UT_DIR_PATTERNS: list[tuple[re.Pattern, NpuType, int]] = [ + # Order matters: longer/more-specific patterns first (e.g. /a2_2/ before /a2/). + (re.compile(r"/a2_2/"), NpuType.A2, 2), + (re.compile(r"/a2/"), NpuType.A2, 1), + (re.compile(r"/a3_4/"), NpuType.A3, 4), + (re.compile(r"/a3_2/"), NpuType.A3, 2), + # /310p/ matches the convention subdir (e.g. tests/ut//310p/). + # Note: tests/ut/_310p/ (top-level module, underscore prefix) is NOT matched + # by this pattern — those tests run on CPU in mock mode, which is intentional. + (re.compile(r"/310p/"), NpuType._310P, 1), +] + +_E2E_DIR_PATTERNS: list[tuple[re.Pattern, NpuType, int]] = [ + (re.compile(r"/four_card/"), NpuType.A3, 4), + (re.compile(r"/two_card/"), NpuType.A3, 2), + (re.compile(r"/one_card/"), NpuType.A2, 1), +] def _as_posix_path(path: str) -> str: @@ -161,6 +115,15 @@ def _pytest_node_file_path(path: str) -> str: return path.split("::", 1)[0] +def _route_e2e_file(file_path: str) -> RunnerKey | None: + route_path = _as_posix_path(_pytest_node_file_path(file_path)) + if "_310p" in Path(route_path).name: + if "/four_card/" in route_path: + return (4, NpuType._310P) + return (1, NpuType._310P) + return _route_e2e_dir(route_path) + + def _load_runners() -> list[RunnerInfo]: with open(_RUNNER_LABEL_PATH) as f: raw = json.load(f) @@ -321,6 +284,24 @@ def _is_skipped_test_target(target: str, skip_tests: set[str]) -> bool: return target in skip_tests or _pytest_node_file_path(target) in skip_tests +def _route_ut_dir(dir_path: str) -> RunnerKey: + dir_path = _pytest_node_file_path(dir_path) + normalized = dir_path if dir_path.endswith("/") else dir_path + "/" + normalized = _as_posix_path(normalized) + for pattern, npu_type, num_npus in _UT_DIR_PATTERNS: + if pattern.search(normalized): + return (num_npus, npu_type) + return _DEFAULT_KEY + + +def _route_e2e_dir(dir_path: str) -> RunnerKey | None: + dir_path = _as_posix_path(dir_path) + for pattern, npu_type, num_npus in _E2E_DIR_PATTERNS: + if pattern.search(dir_path): + return (num_npus, npu_type) + return None + + def _is_ut_path(path: str) -> bool: return path == "tests/ut" or path.startswith("tests/ut/") @@ -377,10 +358,6 @@ def _scan_e2e_test_dir( """ path = Path(_pytest_node_file_path(dir_path)) if not path.exists(): - print( - f"Warning: Path does not exist: {dir_path}", - file=sys.stderr, - ) return if path.is_file(): @@ -441,117 +418,12 @@ def _find_runner( return candidates[0] if candidates else None -def _load_estimated_times( - config_path: Path, -) -> dict[str, float]: - """Load per-test estimated times from the second YAML document. - - Tests not listed default to 600s when used by _partition_tests. - """ - estimated_times: dict[str, float] = {} - try: - docs = list(yaml.safe_load_all(config_path.read_text())) - if len(docs) >= 2: - meta = docs[1] or {} - for k, v in meta.get("estimated_times", {}).items(): - estimated_times[k] = float(v) - except Exception: - pass - return estimated_times - - -def _load_partition_config( - config_path: Path, -) -> dict[str, int]: - """Load partition configuration from the second YAML document. - - Returns a dict mapping runner keys (e.g. ``a2_x1``) to partition - counts. Runner keys not listed default to 1. - """ - partition: dict[str, int] = {} - try: - docs = list(yaml.safe_load_all(config_path.read_text())) - if len(docs) >= 2: - meta = docs[1] or {} - partition = {k: int(v) for k, v in meta.get("partition", {}).items()} - except Exception: - pass - return partition - - -def _lookup_estimated_time( - test_name: str, - estimated_times: dict[str, float], - default: float = 600.0, -) -> float: - """Look up the estimated time for *test_name*, falling back to defaults. - - 1. Try exact match (handles both file-level and ``::nodeid`` keys). - 2. Strip any ``::nodeid`` suffix and try again. - 3. Otherwise use *default*. - - Note: when both a file-level path and a ``::nodeid`` path for the same - file exist in module ``tests:`` lists, that method executes twice. - Avoid mixing levels for the same file in ``tests:``. - """ - val = estimated_times.get(test_name) - if val is not None: - return val - base = _pytest_node_file_path(test_name) - if base != test_name: - val = estimated_times.get(base) - if val is not None: - return val - return default - - -def _partition_tests( - tests: list[str], - partition_size: int, - estimated_times: dict[str, float], -) -> list[list[str]]: - """Split *tests* into *partition_size* groups of roughly equal total time. - - Uses a greedy algorithm: sort tests descending by estimated time, then - place each test into the currently lightest bucket. - """ - if not tests or partition_size <= 1: - return [tests] - - indexed = sorted( - enumerate(tests), - key=lambda x: (-_lookup_estimated_time(x[1], estimated_times), x[0]), - ) - - buckets: list[list[int]] = [[] for _ in range(partition_size)] - sums = [0.0] * partition_size - - for idx, test in indexed: - lightest = sums.index(min(sums)) - buckets[lightest].append(idx) - sums[lightest] += _lookup_estimated_time(test, estimated_times) - - result = [] - for bucket in buckets: - result.append( - sorted( - (tests[i] for i in bucket), - key=lambda t: -_lookup_estimated_time(t, estimated_times), - ) - ) - return result - - def _resolve_to_runners( all_groups: dict[RunnerKey, list[str]], runners: list[RunnerInfo], - partition_config: dict[str, int] | None = None, - estimated_times: dict[str, float] | None = None, ) -> list[dict]: result: list[dict] = [] errors: list[str] = [] - partition_config = partition_config or {} - estimated_times = estimated_times or {} for (num_npus, npu_type), tests in sorted(all_groups.items()): if not tests: @@ -569,35 +441,15 @@ def _resolve_to_runners( errors.append(header + runners_line + tests_line) continue - partition_key = f"{npu_type.value}_x{num_npus}" - psize = partition_config.get(partition_key, 1) - - if psize > 1: - buckets = _partition_tests(sorted(tests), psize, estimated_times) - for i, bucket in enumerate(buckets): - if not bucket: - continue - group: dict = { - "num_npus": num_npus, - "npu_type": npu_type.value, - "runner": runner.label, - "tests": " ".join(sorted(bucket)), - "partition": f"{i + 1}-{psize}", - } - if runner.image_tag: - group["image_tag"] = runner.image_tag - result.append(group) - else: - group = { - "num_npus": num_npus, - "npu_type": npu_type.value, - "runner": runner.label, - "tests": " ".join(sorted(tests)), - "partition": "1-1", - } - if runner.image_tag: - group["image_tag"] = runner.image_tag - result.append(group) + group: dict = { + "num_npus": num_npus, + "npu_type": npu_type.value, + "runner": runner.label, + "tests": " ".join(sorted(tests)), + } + if runner.image_tag: + group["image_tag"] = runner.image_tag + result.append(group) if errors: print( @@ -653,11 +505,10 @@ def _print_summary( num_npus = group["num_npus"] runner = group["runner"] tests = group["tests"].split() - partition_info = group.get("partition", "full") if npu_type == "cpu": - header = f"### CPU ({len(tests)} tests) part {partition_info} -> `{runner}`" + header = f"### CPU ({len(tests)} tests) -> `{runner}`" else: - header = f"### {npu_type.upper()} x{num_npus} ({len(tests)} tests) part {partition_info} -> `{runner}`" + header = f"### {npu_type.upper()} x{num_npus} ({len(tests)} tests) -> `{runner}`" print(f"\n {header}", file=sys.stderr) for t in tests: print(f" - {t}", file=sys.stderr) @@ -667,7 +518,7 @@ def _print_summary( def main(): parser = argparse.ArgumentParser( - description="Determine test scope from changed files or explicit e2e test paths", + description="Determine test scope based on changed files", ) input_group = parser.add_mutually_exclusive_group(required=True) input_group.add_argument( @@ -680,15 +531,6 @@ def main(): type=str, help="Git ref to diff against (e.g. origin/main)", ) - input_group.add_argument( - "--explicit-e2e-tests", - nargs="+", - help="List of explicit e2e test paths (files or directories) to run. " - "Bypasses module matching and routes each path to the appropriate runner. " - "Use this for the /e2e slash command to run a specific subset of tests. " - "Supports ``::nodeid`` suffix (e.g. ``test_foo.py::TestClass::test_method``) " - "to run a single test method.", - ) parser.add_argument( "--config", type=Path, @@ -702,82 +544,69 @@ def main(): ) args = parser.parse_args() - _load_runner_mapping(args.config) - config = _resolve_config_inheritance(next(yaml.safe_load_all(args.config.read_text()))) + config = _resolve_config_inheritance(yaml.safe_load(args.config.read_text())) + + changed_files = _get_changed_files(args.diff_base) if args.diff_base else args.changed_files + matched_modules = ( + [module["name"] for module in config] if args.run_all_modules else _match_modules(changed_files, config) + ) + test_dirs, cpu_only_dirs = _collect_test_dirs(matched_modules, config) skip_tests: set[str] = set() for module in config: for s in module.get("skip_tests", []): skip_tests.add(s.rstrip("/")) - if args.explicit_e2e_tests: - matched_modules: list[str] = [] - all_groups: dict[RunnerKey, list[str]] = defaultdict(list) - for path in args.explicit_e2e_tests: - if not _is_e2e_path(path): - print( - f"Warning: Skipping non-e2e path: {path}", - file=sys.stderr, - ) - continue - _scan_e2e_test_dir(path, all_groups) - else: - changed_files = _get_changed_files(args.diff_base) if args.diff_base else args.changed_files - matched_modules = ( - [module["name"] for module in config] if args.run_all_modules else _match_modules(changed_files, config) - ) - test_dirs, cpu_only_dirs = _collect_test_dirs(matched_modules, config) - - changed_test_files = [ - f - for f in changed_files - if (_is_ut_path(f) or _is_e2e_path(f)) - and Path(_pytest_node_file_path(f)).name.startswith("test_") - and Path(_pytest_node_file_path(f)).exists() - ] + changed_test_files = [ + f + for f in changed_files + if (_is_ut_path(f) or _is_e2e_path(f)) + and Path(_pytest_node_file_path(f)).name.startswith("test_") + and Path(_pytest_node_file_path(f)).exists() + ] - ut_dirs = [d for d in test_dirs if _is_ut_path(d)] - cpu_only_ut_dirs = [d for d in cpu_only_dirs if _is_ut_path(d)] - e2e_dirs = [d for d in test_dirs if _is_e2e_path(d)] + ut_dirs = [d for d in test_dirs if _is_ut_path(d)] + cpu_only_ut_dirs = [d for d in cpu_only_dirs if _is_ut_path(d)] + e2e_dirs = [d for d in test_dirs if _is_e2e_path(d)] - all_groups: dict[RunnerKey, list[str]] = defaultdict(list) + all_groups: dict[RunnerKey, list[str]] = defaultdict(list) - for dir_path in ut_dirs: - p = Path(_pytest_node_file_path(dir_path)) - if p.is_file(): - key = _route_ut_dir(dir_path) + for dir_path in ut_dirs: + p = Path(_pytest_node_file_path(dir_path)) + if p.is_file(): + key = _route_ut_dir(dir_path) + all_groups[key].append(dir_path) + else: + _scan_ut_test_dir(dir_path, all_groups) + for dir_path in cpu_only_ut_dirs: + p = Path(_pytest_node_file_path(dir_path)) + if p.is_file(): + key = _route_ut_dir(dir_path) + if key == _DEFAULT_KEY: all_groups[key].append(dir_path) - else: - _scan_ut_test_dir(dir_path, all_groups) - for dir_path in cpu_only_ut_dirs: - p = Path(_pytest_node_file_path(dir_path)) - if p.is_file(): - key = _route_ut_dir(dir_path) - if key == _DEFAULT_KEY: - all_groups[key].append(dir_path) - else: - _scan_ut_test_dir(dir_path, all_groups, cpu_only=True) + else: + _scan_ut_test_dir(dir_path, all_groups, cpu_only=True) - for dir_path in e2e_dirs: - _scan_e2e_test_dir(dir_path, all_groups) + for dir_path in e2e_dirs: + _scan_e2e_test_dir(dir_path, all_groups) - for changed_test_file in changed_test_files: - if "::" in changed_test_file: - changed_targets = [changed_test_file] - else: - changed_targets = _configured_nodeid_targets_for_file(changed_test_file, config) or [changed_test_file] - for f in changed_targets: - if _is_skipped_test_target(f, skip_tests): - continue - if _is_ut_path(f): - key = _route_ut_dir(f) + for changed_test_file in changed_test_files: + if "::" in changed_test_file: + changed_targets = [changed_test_file] + else: + changed_targets = _configured_nodeid_targets_for_file(changed_test_file, config) or [changed_test_file] + for f in changed_targets: + if _is_skipped_test_target(f, skip_tests): + continue + if _is_ut_path(f): + key = _route_ut_dir(f) + all_groups[key].append(f) + elif _is_e2e_path(f): + key = _route_e2e_file(f) + if key is not None: all_groups[key].append(f) - elif _is_e2e_path(f): - key = _route_e2e_file(f) - if key is not None: - all_groups[key].append(f) - _dedup_groups(all_groups) + _dedup_groups(all_groups) if skip_tests: for key in list(all_groups.keys()): @@ -798,9 +627,7 @@ def main(): _dedup_groups(all_groups) runners = _load_runners() - estimated_times = _load_estimated_times(args.config) - partition_config = _load_partition_config(args.config) - test_groups = _resolve_to_runners(all_groups, runners, partition_config, estimated_times) + test_groups = _resolve_to_runners(all_groups, runners) _write_output(test_groups, matched_modules) diff --git a/.github/workflows/scripts/test_config.yaml b/.github/workflows/scripts/test_config.yaml index 51a66d735..28c4c81c6 100644 --- a/.github/workflows/scripts/test_config.yaml +++ b/.github/workflows/scripts/test_config.yaml @@ -5,12 +5,7 @@ # corresponding test paths are selected to run. # All test directories are resolved to individual files before routing. # -# Runner routing is driven by ``runner_mapping:`` in the second YAML -# document of this file (regex patterns mapping paths to runner types). -# Partition sizing by ``partition:`` config block. -# Estimated times for load-balanced partitioning in ``estimated_times:``. -# -# Below are the conventional routing rules for reference (now config-driven): +# Runner routing conventions: # UT (tests/ut/): # / -> CPU runner (default) # /a2/ -> A2 NPU x1 @@ -19,9 +14,10 @@ # /a3_4/ -> A3 NPU x4 # /310p/ -> 310P NPU # E2E (tests/e2e/): -# pull_request/{one_card,two_card,four_card}/ -> matched by card count -# *_310p.py files under one/two-card dirs -> 310P NPU x1 -# *_310p.py files under four-card dirs -> 310P NPU x4 +# All E2E tests run on NPU. Routing is by directory structure: +# pull_request/{one_card,two_card,four_card}/ -> matched by card count +# *_310p.py files under one/two-card dirs -> 310P NPU x1 +# *_310p.py files under four-card dirs -> 310P NPU x4 # # Fields: # name: Module identifier @@ -32,10 +28,7 @@ # exclude_source_file_dependencies: Optional list of source/test directories # to exclude from source_file_dependencies # tests: List of test directories/files to run (directories are expanded -# to individual test files before routing). Supports ``::nodeid`` -# syntax (e.g. ``test_foo.py::test_bar``) to run a single method. -# WARNING: mixing a bare file path and a ``::nodeid`` path for the -# same file causes that method to execute twice. +# to individual test files before routing) # skip_tests: List of test files to skip (matched against individual test # paths after directory scanning) @@ -49,7 +42,7 @@ # 310P - name: 310p - optional: false + optional: true source_file_dependencies: - vllm_ascend/_310p tests: @@ -59,7 +52,7 @@ # Attention - name: attention_common - optional: false + optional: true source_file_dependencies: - vllm_ascend/attention/__init__.py - vllm_ascend/attention/abstract.py @@ -72,13 +65,13 @@ - tests/e2e/pull_request/one_card/test_qwen3_5_0_8b.py - name: attention_gqa - optional: false + optional: true base: attention_common source_file_dependencies: - vllm_ascend/attention/attention_v1.py - name: attention_fa3 - optional: false + optional: true base: attention_common source_file_dependencies: - vllm_ascend/attention/fa3_v1.py @@ -86,7 +79,7 @@ - tests/e2e/pull_request/one_card/test_attention_fa3.py - name: attention_mla - optional: false + optional: true base: attention_common source_file_dependencies: - vllm_ascend/attention/mla_v1.py @@ -94,7 +87,7 @@ - tests/e2e/pull_request/two_card/test_deepseek_multistream_moe.py - name: attention_sfa - optional: false + optional: true base: attention_common source_file_dependencies: - vllm_ascend/attention/sfa_v1.py @@ -104,7 +97,7 @@ - tests/e2e/pull_request/four_card/test_deepseek_v3_2_w8a8_pruning.py - name: attention_dsa - optional: false + optional: true base: attention_common source_file_dependencies: - vllm_ascend/attention/dsa_v1.py @@ -112,7 +105,7 @@ - tests/e2e/pull_request/four_card/test_deepseek_v4.py - name: attention_cp - optional: false + optional: true base: attention_common source_file_dependencies: - vllm_ascend/attention/context_parallel @@ -121,7 +114,7 @@ # Compilation - name: compilation_aclgraph - optional: false + optional: true source_file_dependencies: - vllm_ascend/compilation/__init__.py - vllm_ascend/compilation/acl_graph.py @@ -135,7 +128,7 @@ - tests/e2e/pull_request/two_card/test_qwen3_vl_30b_a3b_instruct.py - name: compilation_passes_base - optional: false + optional: true source_file_dependencies: - vllm_ascend/compilation/passes/__init__.py - vllm_ascend/compilation/compiler_interface.py @@ -147,7 +140,7 @@ - tests/e2e/pull_request/one_card/compile/ - name: compilation_norm_quant_fusion_pass - optional: false + optional: true source_file_dependencies: - vllm_ascend/compilation/passes/norm_quant_fusion_pass.py tests: @@ -156,7 +149,7 @@ - tests/e2e/pull_request/one_card/compile/test_graphex_norm_quant_fusion.py - name: compilation_graphex_qknorm_rope_fusion_pass - optional: false + optional: true source_file_dependencies: - vllm_ascend/compilation/passes/qknorm_rope_fusion_pass.py tests: @@ -164,7 +157,7 @@ - tests/e2e/pull_request/one_card/compile/test_graphex_qknorm_rope_fusion.py - name: compilation_sp_pass_pass - optional: false + optional: true source_file_dependencies: - vllm_ascend/compilation/passes/sequence_parallelism_moe.py - vllm_ascend/compilation/passes/sequence_parallelism.py @@ -172,7 +165,7 @@ - tests/e2e/pull_request/two_card/test_sp_pass.py - name: compilation_no_test - optional: false + optional: true source_file_dependencies: - vllm_ascend/compilation/passes/allgather_chunk_noop_pass.py - vllm_ascend/compilation/passes/allreduce_rmsnorm_fusion_pass.py @@ -182,16 +175,15 @@ # Core - name: core - optional: false + optional: true source_file_dependencies: - vllm_ascend/core tests: - tests/ut/core - - tests/ut/test_compressed_prefix_cache.py # Device - name: device - optional: false + optional: true source_file_dependencies: - vllm_ascend/device tests: @@ -199,7 +191,7 @@ # Device Allocator - name: device_allocator - optional: false + optional: true source_file_dependencies: - vllm_ascend/device_allocator tests: @@ -208,7 +200,7 @@ # Distributed - name: distributed - optional: false + optional: true source_file_dependencies: - vllm_ascend/distributed tests: @@ -221,7 +213,7 @@ # EPLB - name: eplb - optional: false + optional: true source_file_dependencies: - vllm_ascend/eplb tests: @@ -229,17 +221,9 @@ - tests/e2e/pull_request/two_card/test_qwen3_30b_a3b.py - tests/e2e/pull_request/two_card/test_qwen3_moe_eplb.py -# weight transfer -- name: weight_transfer - optional: true - source_file_dependencies: - - vllm_ascend/distributed/weight_transfer - tests: - - tests/e2e/pull_request/two_card/test_hccl_weight_transfer.py - # KV Offload - name: kv_offload - optional: false + optional: true source_file_dependencies: - vllm_ascend/kv_offload tests: @@ -248,7 +232,7 @@ # Lora - name: lora - optional: false + optional: true source_file_dependencies: - vllm_ascend/lora tests: @@ -256,19 +240,9 @@ - tests/e2e/pull_request/one_card/lora - tests/e2e/pull_request/two_card/lora -# CPU Weight Offloading -- name: cpu_weight_offload - optional: false - source_file_dependencies: - - vllm_ascend/model_executor - - vllm_ascend/worker/model_runner_v1.py - - vllm_ascend/compilation/acl_graph.py - tests: - - tests/e2e/pull_request/one_card/test_cpu_weight_offload.py - # Model Loader - name: model_loader - optional: false + optional: true source_file_dependencies: - vllm_ascend/model_loader tests: @@ -276,7 +250,7 @@ # Models - name: models - optional: false + optional: true source_file_dependencies: - vllm_ascend/models tests: @@ -284,7 +258,7 @@ # Ops - name: ops_basic - optional: false + optional: true source_file_dependencies: - vllm_ascend/ops/__init__.py - vllm_ascend/ops/activation.py @@ -300,7 +274,7 @@ - tests/e2e/pull_request/one_card/test_minicpm.py - name: ops_moe - optional: false + optional: true base: ops_basic source_file_dependencies: - vllm_ascend/ops/fused_moe @@ -314,7 +288,7 @@ - tests/e2e/pull_request/two_card/test_gpt_oss_distributed.py - name: ops_vl - optional: false + optional: true base: - ops_basic - ops_moe @@ -323,10 +297,9 @@ - vllm_ascend/ops/mm_encoder_attention.py tests: - tests/e2e/pull_request/one_card/test_vlm.py - - tests/e2e/pull_request/two_card/test_qwen3_6_27b_fia.py - name: ops_deepseek_v4 - optional: false + optional: true base: - ops_basic - ops_moe @@ -339,7 +312,7 @@ - tests/e2e/pull_request/four_card/test_deepseek_v4.py - name: ops_distributed_inference - optional: false + optional: true source_file_dependencies: - vllm_ascend/ops/flashcomm2_oshard_manager.py - vllm_ascend/ops/layer_shard_linear.py @@ -352,10 +325,9 @@ - tests/e2e/pull_request/two_card/test_flashcomm_distributed.py::test_qwen3_dense_prefetch_mlp_weight_tp2 - name: ops_gdn - optional: false + optional: true source_file_dependencies: - vllm_ascend/ops/gdn.py - - vllm_ascend/ops/gdn_attn_builder.py - vllm_ascend/ops/triton/gdn_chunk_meta.py tests: - tests/ut/ops @@ -363,7 +335,7 @@ - tests/e2e/pull_request/four_card/test_qwen3_next.py - name: ops_triton - optional: false + optional: true source_file_dependencies: - vllm_ascend/ops/triton exclude_source_file_dependencies: @@ -373,7 +345,7 @@ - tests/e2e/pull_request/four_card/test_qwen3_5.py - name: ops_no_test - optional: false + optional: true source_file_dependencies: - vllm_ascend/ops/bailing_moe_linear_attn.py # deepseek ocr @@ -383,25 +355,17 @@ # Patch - name: patch - optional: false + optional: true source_file_dependencies: - vllm_ascend/patch tests: - tests/ut/patch - - tests/ut/test_compressed_prefix_cache.py - tests/e2e/pull_request/one_card/test_qwen3_0_6b.py - tests/e2e/pull_request/one_card/test_qwen3_5_0_8b.py -- name: patch_mamba_utils_310p - optional: false - source_file_dependencies: - - vllm_ascend/patch/worker/patch_mamba_utils.py - tests: - - tests/e2e/pull_request/one_card/_310p/test_dense_model_310p.py::test_qwen3_5_dense_prefix_mamba_cache_tp1_fp16 - # Profiler - name: profiler - optional: false + optional: true source_file_dependencies: - vllm_ascend/profiler tests: @@ -409,7 +373,7 @@ # Quantization - name: quantization - optional: false + optional: true source_file_dependencies: - vllm_ascend/quantization tests: @@ -419,16 +383,15 @@ # Sample - name: sample - optional: false + optional: true source_file_dependencies: - vllm_ascend/sample - - vllm_ascend/worker/v2/sample tests: - tests/ut/sample - tests/e2e/pull_request/one_card/test_sampler.py - name: simple_kv_offload - optional: false + optional: true source_file_dependencies: - vllm_ascend/simple_kv_offload - vllm_ascend/distributed/kv_transfer/kv_pool/simple_cpu_offload @@ -437,7 +400,7 @@ # Spec Decode - name: spec_decode_base - optional: false + optional: true source_file_dependencies: - vllm_ascend/spec_decode/__init__.py - vllm_ascend/spec_decode/utils.py @@ -445,7 +408,7 @@ - tests/ut/spec_decode - name: spec_decode_eagle - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/llm_base_proposer.py @@ -457,7 +420,7 @@ - tests/e2e/pull_request/four_card/spec_decode/test_mtp_qwen3_next.py - name: spec_decode_ngram - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/ngram_proposer.py @@ -465,7 +428,7 @@ - tests/e2e/pull_request/one_card/spec_decode/test_ngram.py - name: spec_decode_ngram_npu - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/ngram_proposer_npu.py @@ -473,7 +436,7 @@ - tests/e2e/pull_request/one_card/spec_decode/test_ngram_npu.py - name: spec_decode_suffix - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/suffix_proposer.py @@ -481,7 +444,7 @@ - tests/e2e/pull_request/one_card/spec_decode/test_suffix.py - name: spec_decode_draft - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/draft_proposer.py @@ -489,7 +452,7 @@ - tests/e2e/pull_request/one_card/spec_decode/test_draft_parallel.py - name: spec_decode_dflash - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/dflash_proposer.py @@ -497,7 +460,7 @@ - tests/e2e/pull_request/one_card/spec_decode/test_dflash.py - name: spec_decode_extract_hidden_states - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/extract_hidden_states_proposer.py @@ -505,7 +468,7 @@ - tests/e2e/pull_request/one_card/spec_decode/test_extract_hidden_states.py - name: spec_decode_medusa - optional: false + optional: true base: spec_decode_base source_file_dependencies: - vllm_ascend/spec_decode/medusa_proposer.py @@ -513,7 +476,7 @@ # Worker - name: worker_v1 - optional: false + optional: true source_file_dependencies: - vllm_ascend/worker exclude_source_file_dependencies: @@ -539,7 +502,7 @@ - name: worker_v2 - optional: false + optional: true source_file_dependencies: - vllm_ascend/worker/worker.py - vllm_ascend/worker/v2 @@ -549,7 +512,7 @@ # XLite - name: xlite - optional: false + optional: true source_file_dependencies: - vllm_ascend/xlite tests: @@ -558,7 +521,7 @@ # Batch Invariant - name: batch_invariant - optional: false + optional: true source_file_dependencies: - vllm_ascend/batch_invariant.py - vllm_ascend/ops/triton/batch_invariant @@ -569,7 +532,7 @@ # csrc - name: csrc - optional: false + optional: true source_file_dependencies: - csrc/torch_binding.cpp - csrc/torch_binding_meta.cpp @@ -579,7 +542,7 @@ - tests/ut/ # Single File - name: misc - optional: false + optional: true source_file_dependencies: - vllm_ascend/__init__.py - vllm_ascend/_build_info.py @@ -589,7 +552,6 @@ - vllm_ascend/cpu_binding.py - vllm_ascend/envs.py - vllm_ascend/flash_common3_context.py - - vllm_ascend/logger.py - vllm_ascend/meta_registration.py - vllm_ascend/platform.py - vllm_ascend/profiling_config.py @@ -600,7 +562,6 @@ - tests/ut/test_ascend_forward_context.py - tests/ut/test_cpu_binding.py - tests/ut/test_envs.py - - tests/ut/test_logger.py - tests/ut/test_flash_common3_context.py - tests/ut/test_meta_registration.py - tests/ut/test_profiling_config.py @@ -611,7 +572,7 @@ # === Features === - name: quantization_gqa_mla_c8 - optional: false + optional: true source_file_dependencies: - vllm_ascend/attention/attention_v1.py - vllm_ascend/attention/mla_v1.py @@ -619,13 +580,13 @@ - tests/ut/quantization/methods/test_kv_c8.py - name: quantization_sfa_c8 - optional: false + optional: true source_file_dependencies: - vllm_ascend/attention/sfa_v1.py tests: [] - name: quantization_moe - optional: false + optional: true source_file_dependencies: - vllm_ascend/ops/fused_moe/fused_moe.py - vllm_ascend/ops/fused_moe/moe_mlp.py @@ -637,179 +598,8 @@ # === Others === - name: _tools - optional: false + optional: true source_file_dependencies: - tools/ tests: - tests/ut/_tools - ---- -# === Estimated Times === -# Per-test estimated execution time in seconds. -# Updated periodically by schedule_update_estimated_times workflow. -# Unlisted tests default to 600s (10 minutes). -# Supports ``::nodeid`` suffix for method-level granularity. -# When both file-level and ``::nodeid`` entries exist, lookup uses -# exact match first, then file-level fallback. -estimated_times: - tests/e2e/pull_request/one_card/_310p/test_classification_310p.py: 210 - tests/e2e/pull_request/one_card/_310p/test_dense_model_310p.py: 1410 - tests/e2e/pull_request/one_card/_310p/test_embedding_310p.py: 400 - tests/e2e/pull_request/one_card/_310p/test_scoring_310p.py: 290 - tests/e2e/pull_request/one_card/_310p/test_spec_decode_mtp_310p.py: 320 - tests/e2e/pull_request/one_card/_310p/test_vl_model_310p.py: 380 - tests/e2e/pull_request/one_card/aclgraph/test_aclgraph_accuracy.py: 1810 - tests/e2e/pull_request/one_card/aclgraph/test_aclgraph_batch_invariant.py: 670 - tests/e2e/pull_request/one_card/aclgraph/test_aclgraph_mem.py: 300 - tests/e2e/pull_request/one_card/compile/test_graphex_norm_quant_fusion.py: 90 - tests/e2e/pull_request/one_card/compile/test_graphex_qknorm_rope_fusion.py: 140 - tests/e2e/pull_request/one_card/compile/test_norm_quant_fusion.py: 120 - tests/e2e/pull_request/one_card/lora/test_ilama_lora.py: 170 - tests/e2e/pull_request/one_card/lora/test_llama32_lora.py: 160 - tests/e2e/pull_request/one_card/lora/test_lora_with_spec_decode.py: 520 - tests/e2e/pull_request/one_card/lora/test_qwen35_densemodel_lora.py: 330 - tests/e2e/pull_request/one_card/lora/test_qwen3_multi_loras.py: 130 - tests/e2e/pull_request/one_card/lora/test_qwen3_reranker_lora.py: 190 - tests/e2e/pull_request/one_card/model_runner_v2/test_basic.py: 780 - tests/e2e/pull_request/one_card/pooling/test_classification.py: 220 - tests/e2e/pull_request/one_card/pooling/test_embedding.py: 430 - tests/e2e/pull_request/one_card/pooling/test_scoring.py: 570 - tests/e2e/pull_request/one_card/spec_decode/test_dflash.py: 250 - tests/e2e/pull_request/one_card/spec_decode/test_draft_parallel.py: 260 - tests/e2e/pull_request/one_card/spec_decode/test_eagle.py: 400 - tests/e2e/pull_request/one_card/spec_decode/test_extract_hidden_states.py: 220 - tests/e2e/pull_request/one_card/spec_decode/test_mtp_eagle_correctness.py: 150 - tests/e2e/pull_request/one_card/spec_decode/test_ngram.py: 150 - tests/e2e/pull_request/one_card/spec_decode/test_ngram_npu.py: 230 - tests/e2e/pull_request/one_card/spec_decode/test_suffix.py: 140 - tests/e2e/pull_request/one_card/test_attention_fa3.py: 30 - tests/e2e/pull_request/one_card/test_batch_invariant.py: 680 - tests/e2e/pull_request/one_card/test_camem.py: 150 - tests/e2e/pull_request/one_card/test_completion_with_prompt_embeds.py: 130 - tests/e2e/pull_request/one_card/test_cpu_offloading.py: 30 - tests/e2e/pull_request/one_card/test_cpu_weight_offload.py: 790 - tests/e2e/pull_request/one_card/test_guided_decoding.py: 790 - tests/e2e/pull_request/one_card/test_minicpm.py: 280 - tests/e2e/pull_request/one_card/test_multi_instance.py: 180 - tests/e2e/pull_request/one_card/test_multistream_overlap_shared_expert.py: 270 - tests/e2e/pull_request/one_card/test_qwen3_0_6b.py: 190 - tests/e2e/pull_request/one_card/test_qwen3_5_0_8b.py: 360 - tests/e2e/pull_request/one_card/test_qwen3_8b_w8a8.py: 330 - tests/e2e/pull_request/one_card/test_qwen3_embedding_0_6b.py: 170 - tests/e2e/pull_request/one_card/test_sampler.py: 260 - tests/e2e/pull_request/one_card/test_simple_cpu_offload.py: 260 - tests/e2e/pull_request/one_card/test_vlm.py: 770 - tests/e2e/pull_request/one_card/test_xlite.py: 170 - tests/e2e/pull_request/two_card/aclgraph/test_aclgraph_capture_replay.py: 20 - tests/e2e/pull_request/two_card/aclgraph/test_full_graph_mode.py: 750 - tests/e2e/pull_request/two_card/aclgraph/test_single_request_aclgraph.py: 950 - tests/e2e/pull_request/two_card/lora/test_ilama_lora_tp2.py: 110 - tests/e2e/pull_request/two_card/lora/test_llama32_lora_tp2.py: 430 - tests/e2e/pull_request/two_card/spec_decode/test_spec_decode.py: 910 - tests/e2e/pull_request/two_card/test_data_parallel.py: 790 - tests/e2e/pull_request/two_card/test_deepseek_multistream_moe.py: 180 - tests/e2e/pull_request/two_card/test_disaggregated_encoder.py: 160 - tests/e2e/pull_request/two_card/test_external_launcher.py: 1010 - tests/e2e/pull_request/two_card/test_flashcomm_distributed.py: 20 - tests/e2e/pull_request/two_card/test_gpt_oss_distributed.py: 170 - tests/e2e/pull_request/two_card/test_moe_routing_replay.py: 550 - tests/e2e/pull_request/two_card/test_offline_weight_load.py: 20 - tests/e2e/pull_request/two_card/test_prefix_caching.py: 410 - tests/e2e/pull_request/two_card/test_qwen3_30b_a3b.py: 290 - tests/e2e/pull_request/two_card/test_qwen3_5_35b_a3b_w8a8.py: 370 - tests/e2e/pull_request/two_card/test_qwen3_6_27b_fia.py: 360 - tests/e2e/pull_request/two_card/test_qwen3_moe_eplb.py: 420 - tests/e2e/pull_request/two_card/test_qwen3_performance.py: 30 - tests/e2e/pull_request/two_card/test_qwen3_vl_30b_a3b_instruct.py: 270 - tests/e2e/pull_request/two_card/test_sequence_parallelism_moe.py: 50 - tests/e2e/pull_request/two_card/test_shared_expert_dp.py: 370 - tests/e2e/pull_request/two_card/test_sp_pass.py: 240 - tests/e2e/pull_request/two_card/test_hccl_weight_transfer.py: 110 - tests/e2e/pull_request/four_card/_310p/test_dense_model_310p.py: 480 - tests/e2e/pull_request/four_card/_310p/test_moe_model_310p.py: 1010 - tests/e2e/pull_request/four_card/_310p/test_vl_model_310p.py: 350 - tests/e2e/pull_request/four_card/long_sequence/test_accuracy.py: 3140 - tests/e2e/pull_request/four_card/long_sequence/test_basic.py: 3960 - tests/e2e/pull_request/four_card/long_sequence/test_chunked_prefill_cp.py: 1570 - tests/e2e/pull_request/four_card/long_sequence/test_mtp.py: 730 - tests/e2e/pull_request/four_card/long_sequence/test_prefix_caching_cp.py: 1760 - tests/e2e/pull_request/four_card/spec_decode/test_mtp_qwen3_next.py: 410 - tests/e2e/pull_request/four_card/test_data_parallel_tp2.py: 30 - tests/e2e/pull_request/four_card/test_deepseek_v3_2_w8a8_pruning.py: 400 - tests/e2e/pull_request/four_card/test_deepseek_v4.py: 730 - tests/e2e/pull_request/four_card/test_pipeline_parallel.py: 480 - tests/e2e/pull_request/four_card/test_profiling_chunk_performance.py: 200 - tests/e2e/pull_request/four_card/test_qwen3_5.py: 1090 - tests/e2e/pull_request/four_card/test_qwen3_next.py: 4980 - tests/ut/attention/a2/test_attention_cp.py: 40 - tests/ut/attention/a2/test_attention_cp_precision.py: 30 - tests/ut/attention/a2/test_attention_v1.py: 30 - tests/ut/attention/a2/test_attention_v1_precision.py: 480 - tests/ut/attention/a2/test_common_cp.py: 30 - tests/ut/attention/a2/test_mla_cp.py: 40 - tests/ut/attention/a2/test_mla_cp_precision.py: 30 - tests/ut/attention/a2/test_mla_precision.py: 190 - tests/ut/attention/a2/test_mla_v1.py: 30 - tests/ut/attention/a2/test_sfa_cp_precision.py: 170 - tests/ut/attention/a2/test_sfa_v1.py: 30 - tests/ut/attention/a2/test_sfa_v1_precision.py: 150 - tests/ut/compilation/a2/test_acl_graph.py: 40 - tests/ut/device_allocator/a2/test_find_loaded_library.py: 30 - tests/ut/eplb/core/a2/test_eplb_utils.py: 30 - tests/ut/kv_offload/a2/test_remote_decode_lifecycle.py: 30 - tests/ut/kv_offload/a2/test_remote_prefill_lifecycle.py: 30 - tests/ut/ops/a2/test_gdn_chunk_meta.py: 30 - tests/ut/ops/a2/test_token_dispatcher.py: 30 - tests/ut/ops/a2/test_weight_prefetch.py: 30 - tests/ut/ops/a3_2/test_activation.py: 30 - tests/ut/ops/a3_2/test_select_experts.py: 20 - tests/ut/quantization/methods/a2/test_w4a16.py: 30 - tests/ut/quantization/methods/a2/test_w4a4_flatquant.py: 40 - tests/ut/quantization/methods/a2/test_w4a4_laos_dynamic.py: 40 - tests/ut/quantization/methods/a2/test_w4a8.py: 40 - tests/ut/quantization/methods/a2/test_w8a16.py: 40 - tests/ut/quantization/methods/a2/test_w8a8_dynamic.py: 40 - tests/ut/quantization/methods/a2/test_w8a8_static.py: 40 - tests/ut/sample/a2/test_gumbel_sampling.py: 80 - tests/ut/spec_decode/a2/test_eagle_proposer.py: 50 - tests/ut/worker/a2/test_block_table.py: 30 - tests/ut/worker/a2/test_kvcomp_utils.py: 50 - tests/ut/worker/a2/test_model_runner_v1.py: 30 - tests/ut/worker/a2/test_model_runner_v1_with_device.py: 40 - tests/ut/worker/a2/test_worker_multi_instance.py: 30 - tests/ut/worker/a2/test_worker_v1.py: 40 - -# === Runner Mapping === -# Maps test paths to runner types using regex patterns. -# Each entry has a ``default`` runner and an optional ``310p`` override -# for files whose name contains ``_310p``. -# Patterns are evaluated longest first (most specific wins). -runner_mapping: - tests/e2e/pull_request/one_card: - default: a2_x1 - 310p: 310p_x1 - tests/e2e/pull_request/two_card: - default: a3_x2 - tests/e2e/pull_request/four_card: - default: a3_x4 - 310p: 310p_x4 - tests/ut/.+/a3_2: - default: a3_x2 - tests/ut/.+/a2: - default: a2_x1 - -# === Partition Configuration === -# Controls how tests are split across parallel runners of the same type. -# Key format: _x -# Only runner types that can appear in test routing need entries. -# Unlisted types default to 1 (no partition). -partition: - 310p_x1: 1 - 310p_x4: 1 - a2_x1: 5 - a3_x2: 3 - a3_x4: 3 - cpu_x0: 1 - - - diff --git a/.github/workflows/scripts/test_select_tests.py b/.github/workflows/scripts/test_select_tests.py index 81863a3b1..a4c1b183c 100644 --- a/.github/workflows/scripts/test_select_tests.py +++ b/.github/workflows/scripts/test_select_tests.py @@ -516,206 +516,3 @@ def test_default_cpu_ut_always_runs(tmp_path, monkeypatch, capsys): assert any("test_cpu.py" in t for t in cpu_tests) a2_tests = {g["tests"] for g in test_groups if g["npu_type"] == "a2"} assert any("test_a2.py" in t for t in a2_tests) - - -def _write_two_doc_config(path, modules, meta): - """Write a two-document YAML config (modules + meta) for select_tests.py.""" - path.write_text(yaml.safe_dump(modules) + "---\n" + yaml.safe_dump(meta)) - - -def test_explicit_e2e_tests_runs_only_specified_paths(tmp_path, monkeypatch, capsys): - """--explicit-e2e-tests must bypass module matching and run only the - user-specified paths, regardless of ``optional: false`` modules that - would otherwise pull in the full suite.""" - test_root = tmp_path / "tests" - e2e_one_card = test_root / "e2e" / "pull_request" / "one_card" - e2e_two_card = test_root / "e2e" / "pull_request" / "two_card" - e2e_four_card = test_root / "e2e" / "pull_request" / "four_card" - for path in (e2e_one_card, e2e_two_card, e2e_four_card): - path.mkdir(parents=True) - one_a = e2e_one_card / "test_one_a.py" - one_b = e2e_one_card / "test_one_b.py" - one_310p = e2e_one_card / "test_one_310p.py" - two_a = e2e_two_card / "test_two_a.py" - two_b = e2e_two_card / "test_two_b.py" - four_a = e2e_four_card / "test_four_a.py" - for path in (one_a, one_b, one_310p, two_a, two_b, four_a): - path.write_text("") - - # Module with ``optional: false`` would normally pull in the entire e2e - # suite via _match_modules; explicit mode must skip it. - config_modules = [ - { - "name": "always_run_e2e", - "optional": False, - "source_file_dependencies": ["src/any.py"], - "tests": [ - "tests/e2e/pull_request/one_card", - "tests/e2e/pull_request/two_card", - "tests/e2e/pull_request/four_card", - ], - }, - ] - runner_mapping = { - "tests/e2e/pull_request/one_card": {"default": "a2_x1", "310p": "310p_x1"}, - "tests/e2e/pull_request/two_card": {"default": "a3_x2"}, - "tests/e2e/pull_request/four_card": {"default": "a3_x4", "310p": "310p_x4"}, - } - config_path = tmp_path / "config.yaml" - _write_two_doc_config(config_path, config_modules, {"runner_mapping": runner_mapping}) - runner_file = tmp_path / "runner_label.json" - runner_file.write_text( - json.dumps( - { - "a2-runner": {"chip": "a2", "npu_num": 1}, - "a3-runner-2": {"chip": "a3", "npu_num": 2}, - "a3-runner-4": {"chip": "a3", "npu_num": 4}, - "310p-runner": {"chip": "310p", "npu_num": 1}, - } - ) - ) - monkeypatch.setattr(select_tests, "_RUNNER_LABEL_PATH", runner_file) - monkeypatch.chdir(tmp_path) - - # Use repo-relative paths because the script's _is_e2e_path / routing - # patterns expect paths starting with "tests/". - rel_one_a = "tests/e2e/pull_request/one_card/test_one_a.py" - rel_one_b = "tests/e2e/pull_request/one_card/test_one_b.py" - rel_one_310p = "tests/e2e/pull_request/one_card/test_one_310p.py" - rel_two_a = "tests/e2e/pull_request/two_card/test_two_a.py" - rel_two_b = "tests/e2e/pull_request/two_card/test_two_b.py" - rel_four_a = "tests/e2e/pull_request/four_card/test_four_a.py" - rel_e2e_one = "tests/e2e/pull_request/one_card" - rel_ut_file = "tests/ut/test_ut.py" - rel_missing = "tests/e2e/pull_request/one_card/does_not_exist.py" - - def run_explicit(*paths): - capsys.readouterr() - monkeypatch.setattr( - sys, - "argv", - ["select_tests.py", "--config", str(config_path), "--explicit-e2e-tests", *paths], - ) - select_tests.main() - captured = capsys.readouterr() - out, err = captured.out, captured.err - groups_line = next((line for line in out.splitlines() if line.startswith("test_groups=")), None) - assert groups_line is not None - test_groups = json.loads(groups_line.removeprefix("test_groups=")) - return test_groups, out, err - - # 1. Single file routes to the correct runner. - test_groups, out, _ = run_explicit(rel_one_a) - matched = out.split("matched_modules=")[1].strip() - assert matched == "" - assert len(test_groups) == 1 - assert test_groups[0]["npu_type"] == "a2" - assert test_groups[0]["num_npus"] == 1 - assert test_groups[0]["tests"].split() == [rel_one_a] - - # 2. Multiple files spanning different runners. - test_groups, _, _ = run_explicit(rel_one_a, rel_two_a, rel_four_a) - npu_keys = {(g["npu_type"], g["num_npus"]) for g in test_groups} - assert npu_keys == {("a2", 1), ("a3", 2), ("a3", 4)} - selected = {t for g in test_groups for t in g["tests"].split()} - assert selected == {rel_one_a, rel_two_a, rel_four_a} - - # 3. _310p suffix overrides the default runner. - test_groups, _, _ = run_explicit(rel_one_310p) - assert test_groups[0]["npu_type"] == "310p" - assert test_groups[0]["num_npus"] == 1 - - # 4. Directory input rglobs all test_*.py under it. - test_groups, _, _ = run_explicit(rel_e2e_one) - selected = {t for g in test_groups for t in g["tests"].split()} - assert selected == {rel_one_a, rel_one_b, rel_one_310p} - npu_types = {g["npu_type"] for g in test_groups} - assert npu_types == {"a2", "310p"} - - # 5. ::nodeid suffix is preserved and routed by file path. - nodeid = f"{rel_one_a}::TestClass::test_method" - test_groups, _, _ = run_explicit(nodeid) - assert test_groups[0]["tests"].split() == [nodeid] - assert test_groups[0]["npu_type"] == "a2" - - # 6. Non-e2e path is skipped with a warning. - test_groups, _, err = run_explicit(rel_ut_file) - assert test_groups == [] - assert "Skipping non-e2e path" in err - - # 7. Non-existent path is dropped with a warning; no test groups emitted. - test_groups, out, err = run_explicit(rel_missing) - assert test_groups == [] - assert "has_tests=false" in out - assert "Path does not exist" in err - assert rel_missing in err - - # 8. Mix of valid and invalid paths: only valid ones are routed. - test_groups, _, err = run_explicit(rel_two_a, rel_ut_file, rel_missing) - selected = {t for g in test_groups for t in g["tests"].split()} - assert selected == {rel_two_a} - assert "Skipping non-e2e path" in err - - # 9. Optional modules are NOT triggered in explicit mode even if their - # source_file_dependencies happen to match the explicit path. - config_with_match = [ - { - "name": "always_run_e2e", - "optional": False, - "source_file_dependencies": ["src/any.py"], - "tests": [ - "tests/e2e/pull_request/one_card", - "tests/e2e/pull_request/two_card", - "tests/e2e/pull_request/four_card", - ], - }, - { - "name": "would_match", - "optional": True, - "source_file_dependencies": [rel_e2e_one], - "tests": [rel_e2e_one], - }, - ] - _write_two_doc_config(config_path, config_with_match, {"runner_mapping": runner_mapping}) - test_groups, _, _ = run_explicit(rel_two_b) - selected = {t for g in test_groups for t in g["tests"].split()} - assert selected == {rel_two_b} - assert "test_two_a.py" not in selected - - # 10. ::nodeid is filtered out when the underlying file is in skip_tests. - config_with_skip = [ - { - "name": "with_skip", - "optional": True, - "source_file_dependencies": ["src/any.py"], - "tests": [rel_e2e_one, rel_two_a, rel_two_b], - "skip_tests": [rel_one_a], - }, - ] - _write_two_doc_config(config_path, config_with_skip, {"runner_mapping": runner_mapping}) - test_groups, out, _ = run_explicit(f"{rel_one_a}::TestClass::test_method") - selected = {t for g in test_groups for t in g["tests"].split()} - assert selected == set() - assert "has_tests=false" in out - - # 11. Partition splits multiple same-runner tests; single test into a - # psize=5 runner yields 1 non-empty partition (others are dropped). - config_with_partition = [ - { - "name": "with_partition", - "optional": True, - "source_file_dependencies": ["src/any.py"], - "tests": [rel_e2e_one], - }, - ] - _write_two_doc_config( - config_path, - config_with_partition, - {"runner_mapping": runner_mapping, "partition": {"a2_x1": 5}}, - ) - test_groups, _, _ = run_explicit(rel_one_a, rel_one_b) - a2_groups = [g for g in test_groups if g["npu_type"] == "a2"] - assert len(a2_groups) >= 1 - a2_tests = {t for g in a2_groups for t in g["tests"].split()} - assert a2_tests == {rel_one_a, rel_one_b} - assert all(g["partition"].endswith("-5") for g in a2_groups) diff --git a/.github/workflows/scripts/update_estimated_times.py b/.github/workflows/scripts/update_estimated_times.py deleted file mode 100644 index a5db76f13..000000000 --- a/.github/workflows/scripts/update_estimated_times.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -""" -Update estimated_times in test_config.yaml from CI timing data. - -Usage: - python3 update_estimated_times.py \ - --timing-dir ./timing-artifacts \ - --config .github/workflows/scripts/test_config.yaml - -Methodology: - 1. Collect all elapsed times per test from timing JSON files - 2. Take median per test - 3. Apply 10 % safety buffer, round to nearest 10 s - 4. Overwrite estimated_times section in test_config.yaml -""" - -import argparse -import json -from pathlib import Path - - -def collect_timings(timing_dir: Path) -> dict[str, list[int]]: - """Scan *timing_dir* recursively for timing JSON files. - - Returns ``{test_name: [elapsed_seconds, ...]}`` for all passed tests. - """ - json_files = list(timing_dir.rglob("*.json")) - print(f"Found {len(json_files)} timing file(s) in {timing_dir}") - - timings: dict[str, list[int]] = {} - for path in json_files: - try: - data = json.loads(path.read_text()) - except (json.JSONDecodeError, OSError) as e: - print(f" Warning: skipping {path}: {e}") - continue - - if isinstance(data, dict): - tests = data.get("tests", []) - elif isinstance(data, list): - tests = data - else: - continue - - for test in tests: - name: str = test.get("name", "") - passed: bool = test.get("passed", False) - elapsed: float = test.get("elapsed", 0.0) - if not name or not passed or elapsed <= 0: - continue - timings.setdefault(name, []).append(int(elapsed)) - - return timings - - -def compute_median(values: list[int]) -> int: - """Compute the median of a list of integers.""" - sorted_vals = sorted(values) - n = len(sorted_vals) - if n % 2 == 0: - return (sorted_vals[n // 2 - 1] + sorted_vals[n // 2]) // 2 - return sorted_vals[n // 2] - - -def update_config(config_path: Path, timings: dict[str, list[int]]) -> int: - """Overwrite the ``estimated_times`` section in *config_path*. - - For each test: median -> x1.1 -> round to nearest 10 s. - - Returns the number of entries whose values changed. - """ - text = config_path.read_text() - - # --- parse existing estimated_times --- - import yaml - - docs = list(yaml.safe_load_all(text)) - existing: dict[str, int] = {} - if len(docs) >= 2 and isinstance(docs[1], dict): - existing = docs[1].get("estimated_times", {}) or {} - - # --- compute new entries (preserve existing, update from timing data) --- - new_entries = dict(existing) - changed = 0 - for name in sorted(timings.keys()): - elapsed_list = timings[name] - if not elapsed_list: - continue - median = compute_median(elapsed_list) - new_val = int(round(median * 1.1 / 10.0) * 10.0) - if new_val <= 0: - new_val = 10 - # Preserve existing ``::nodeid`` entries if configured, else fall back to file-level - if "::" in name and name in existing: - key = name - else: - key = name.split("::", 1)[0] - # Skip non-test entries (e.g. ``cpu-ut (115 targets)`` batch label) - if not key.startswith("tests/"): - continue - if new_entries.get(key) != new_val: - new_entries[key] = new_val - changed += 1 - - if not changed: - print("No estimated_time values changed.") - return 0 - - # --- find section boundaries in raw text --- - lines = text.split("\n") - et_start = None - section_end = None - - for i, line in enumerate(lines): - stripped = line.strip() - if stripped == "estimated_times:": - et_start = i - elif et_start is not None and section_end is None: - # Next top-level key (no leading spaces) after estimated_times marks the end - if line and not line.startswith(" ") and not line.startswith("#") and not line.startswith("-"): - if ":" in line and line.split(":")[0].strip(): - section_end = i - # Backtrack through preceding blank/comment lines so they - # are preserved in ``after`` rather than being dropped. - while section_end > 0 and ( - lines[section_end - 1].strip() == "" or lines[section_end - 1].strip().startswith("#") - ): - section_end -= 1 - break - - if et_start is None: - print("Error: 'estimated_times:' section not found in config file.") - return 0 - - # Build new estimated_times lines - new_section_lines = ["estimated_times:"] - for name, val in new_entries.items(): - new_section_lines.append(f" {name}: {val}") - - # Reconstruct file - before = lines[:et_start] - after = lines[section_end:] if section_end is not None else [] - - new_text = "\n".join(before) + "\n" + "\n".join(new_section_lines) + "\n" - if after: - # Ensure a blank line separates estimated_times from the next section - if after[0].strip(): - new_text += "\n" - new_text += "\n".join(after) + "\n" - - config_path.write_text(new_text) - print(f"\nDone. {changed} estimated_time value(s) changed.") - return changed - - -def main() -> None: - parser = argparse.ArgumentParser( - description="Update estimated_times in test_config.yaml from CI timing data", - ) - parser.add_argument( - "--timing-dir", - required=True, - type=Path, - help="Directory containing timing JSON files (searched recursively)", - ) - parser.add_argument( - "--config", - default=".github/workflows/scripts/test_config.yaml", - type=Path, - help="Path to test_config.yaml", - ) - args = parser.parse_args() - - timings = collect_timings(args.timing_dir) - if not timings: - print("No timing data collected. Exiting without changes.") - return - - print(f"\nCollected timing data for {len(timings)} test(s).") - print(f"Updating {args.config}...") - update_config(args.config, timings) - - -if __name__ == "__main__": - main() diff --git a/.github/workflows/scripts/upstream_config.yaml b/.github/workflows/scripts/upstream_config.yaml index 300908a81..ea1e58596 100644 --- a/.github/workflows/scripts/upstream_config.yaml +++ b/.github/workflows/scripts/upstream_config.yaml @@ -371,6 +371,10 @@ e2e-upstream_singlecard: estimated_time: 20 - name: tests/entrypoints/openai/responses/test_sampling_params.py estimated_time: 20 + - name: tests/kernels/moe/test_unquantized_backend_selection.py + estimated_time: 20 + - name: tests/model_executor/model_loader/test_reload.py + estimated_time: 20 - name: tests/model_executor/test_oink_integration.py estimated_time: 20 - name: tests/model_executor/test_qwen3_vl_mrope.py @@ -561,6 +565,8 @@ e2e-upstream_singlecard: estimated_time: 20 - name: tests/models/language/pooling/test_pooler_config_init_behaviour.py estimated_time: 20 + - name: tests/models/language/pooling/test_reward.py + estimated_time: 20 - name: tests/models/language/pooling_mteb_test/test_baai.py estimated_time: 20 - name: tests/models/language/pooling_mteb_test/test_cross_encoder.py @@ -833,112 +839,6 @@ e2e-upstream_singlecard: estimated_time: 20 - name: tests/models/multimodal/pooling/test_phi3v.py estimated_time: 20 - - name: tests/models/multimodal/pooling/test_colmodernvbert.py - estimated_time: 20 - - name: tests/models/multimodal/pooling/test_prithvi_mae.py - estimated_time: 20 - - name: tests/models/test_registry.py - estimated_time: 20 - - name: tests/kernels/helion/test_case_key.py - estimated_time: 20 - - name: tests/utils_/test_spawn_decorator.py - estimated_time: 20 - - name: tests/ir/test_inplace_op.py - estimated_time: 20 - - name: tests/v1/attention/test_kv_head_stride_canonicalization.py - estimated_time: 20 - - name: tests/v1/attention/test_mla_prefill_selector.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/offloading_connector/test_worker_metadata.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_mooncake_store_connector.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_mooncake_stats.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_tp_mapping.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_mooncake_store_worker.py - estimated_time: 20 - - name: tests/v1/kv_offload/cpu/test_manager.py - estimated_time: 20 - - name: tests/renderers/test_chat_utils_prompt_embeds.py - estimated_time: 20 - - name: tests/entrypoints/openai/chat_completion/test_chat_completion_with_mixed_image_embeds.py - estimated_time: 20 - - name: tests/entrypoints/openai/chat_completion/test_chat_completion_with_prompt_embeds.py - estimated_time: 20 - - name: tests/entrypoints/openai/chat_completion/test_chat_completion_with_mixed_audio_embeds.py - estimated_time: 20 - - name: tests/entrypoints/openai/test_fingerprint.py - estimated_time: 20 - - name: tests/entrypoints/openai/speech_to_text/test_speech_to_text_cancellation.py - estimated_time: 20 - - name: tests/entrypoints/openai/test_tool_choice_content_none.py - estimated_time: 20 - - name: tests/entrypoints/pooling/test_utils.py - estimated_time: 20 - - name: tests/evals/mrcr/test_mrcr_correctness.py - estimated_time: 20 - - name: tests/benchmarks/test_custom_dataset_seed.py - estimated_time: 20 - - name: tests/model_executor/test_gemma_hidden_act.py - estimated_time: 20 - - name: tests/tool_parsers/test_lfm2_tool_parser.py - estimated_time: 20 - - name: tests/compile/passes/ir/test_clone_cleanup.py - estimated_time: 20 - - name: tests/compile/test_codegen.py - estimated_time: 20 - - name: tests/tools/test_docker_build_metadata_args.py - estimated_time: 20 - - name: tests/models/multimodal/processing/test_moondream3.py - estimated_time: 20 - - name: tests/models/multimodal/processing/test_molmo2.py - estimated_time: 20 - - name: tests/models/multimodal/test_nano_nemotron_vl.py - estimated_time: 20 - - name: tests/parser/test_streaming.py - estimated_time: 20 - - name: tests/entrypoints/pooling/token_classify/test_offline.py - estimated_time: 20 - - name: tests/entrypoints/pooling/token_embed/test_offline.py - estimated_time: 20 - - name: tests/entrypoints/pooling/embed/test_offline.py - estimated_time: 20 - - name: tests/entrypoints/pooling/classify/test_offline.py - estimated_time: 20 - - name: tests/config/test_config_generation.py - estimated_time: 20 - - name: tests/entrypoints/openai/test_return_tokens_as_ids.py - estimated_time: 20 - - name: tests/lora/test_add_lora.py - estimated_time: 20 - - name: tests/lora/test_qwenvl.py - estimated_time: 20 - - name: tests/lora/test_chatglm3_tp.py - estimated_time: 20 - - name: tests/model_executor/test_enabled_custom_ops.py - estimated_time: 20 - - name: tests/models/multimodal/pooling/test_siglip.py - estimated_time: 20 - - name: tests/v1/engine/test_engine_args.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_cache_pollution_prevention.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_config.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_decode_bench_connector.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_error_propagation.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_invalid_blocks_correctness.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_kv_load_failure_recovery.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_remote_decode_lifecycle.py - estimated_time: 20 - - name: tests/v1/kv_connector/unit/test_remote_prefill_lifecycle.py - estimated_time: 20 e2e-upstream_a2_2: - name: tests/v1/distributed/test_async_llm_dp.py estimated_time: 20 diff --git a/.github/workflows/scripts/wheel/config.json b/.github/workflows/scripts/wheel/config.json index 67db7b403..374147682 100644 --- a/.github/workflows/scripts/wheel/config.json +++ b/.github/workflows/scripts/wheel/config.json @@ -13,9 +13,9 @@ "skip_plugin_validation": true }, { - "variant_label": "910b", + "variant_label": "a2", "properties": [ - "ascend :: npu_type :: 910b" + "ascend :: npu_type :: a2" ], "skip_plugin_validation": true }, diff --git a/.github/workflows/scripts/wheel/pyproject.toml b/.github/workflows/scripts/wheel/pyproject.toml index 8d9d70fda..854660538 100644 --- a/.github/workflows/scripts/wheel/pyproject.toml +++ b/.github/workflows/scripts/wheel/pyproject.toml @@ -4,4 +4,4 @@ namespace = ["ascend"] [variant.providers.ascend] enable-if = "platform_system == 'Linux'" plugin-api = "huawei_ascend_variant_provider.plugin:AscendVariantPlugin" -requires = ["huawei-ascend-variant-provider>=0.0.2,<0.0.3"] +requires = ["huawei-ascend-variant-provider>=0.0.2,<1.0.0"] diff --git a/.github/workflows/slash_command_dispatch.yml b/.github/workflows/slash_command_dispatch.yml index b3ff59b9d..1aa0528bb 100644 --- a/.github/workflows/slash_command_dispatch.yml +++ b/.github/workflows/slash_command_dispatch.yml @@ -23,8 +23,7 @@ on: jobs: slashCommandDispatch: - if: github.event.issue.pull_request != null && github.event.comment.user.type == 'User' - runs-on: linux-amd64-cpu-8-hk + runs-on: ubuntu-latest steps: - name: Slash Command Dispatch uses: peter-evans/slash-command-dispatch@v5 @@ -53,6 +52,6 @@ jobs: { "command": "nightly", "permission": "none", - "issue_type": "pull-request" + "issue_type": "both" } ] diff --git a/.gitignore b/.gitignore index 20af3005c..fdbbad5b1 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,6 @@ __pycache__/ # Distribution / packaging .Python build/ -build_out/ !csrc/cmake/third_party/build/ !csrc/cmake/third_party/build/modules/ !csrc/cmake/third_party/build/modules/patch/ diff --git a/Dockerfile b/Dockerfile index 46ba2eb4c..8ed477024 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ FROM quay.io/ascend/cann:9.0.0-910b-ubuntu22.04-py3.12 ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG MOONCAKE_TAG="v0.3.9" +ARG MOONCAKE_TAG="v0.3.8.post1" WORKDIR /workspace @@ -47,15 +47,8 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 -ARG VLLM_COMMIT="" -RUN if [ -n "$VLLM_COMMIT" ]; then \ - git init /vllm-workspace/vllm && \ - git -C /vllm-workspace/vllm fetch --depth 1 $VLLM_REPO "$VLLM_COMMIT" && \ - git -C /vllm-workspace/vllm checkout FETCH_HEAD; \ - else \ - git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm; \ - fi +ARG VLLM_TAG=v0.20.2 +RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ python3 -m pip uninstall -y triton && \ @@ -71,7 +64,6 @@ ENV SOC_VERSION=$SOC_VERSION \ COPY . /vllm-workspace/vllm-ascend/ RUN export PIP_EXTRA_INDEX_URL="https://mirrors.huaweicloud.com/ascend/repos/pypi" && \ - export VLLM_BATCH_INVARIANT=1 && \ source /usr/local/Ascend/ascend-toolkit/set_env.sh && \ source /usr/local/Ascend/nnal/atb/set_env.sh && \ python3 -m pip install -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \ diff --git a/Dockerfile.310p b/Dockerfile.310p index 3460a5948..c21ecbc2c 100644 --- a/Dockerfile.310p +++ b/Dockerfile.310p @@ -33,15 +33,8 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 -ARG VLLM_COMMIT="" -RUN if [ -n "$VLLM_COMMIT" ]; then \ - git init /vllm-workspace/vllm && \ - git -C /vllm-workspace/vllm fetch --depth 1 $VLLM_REPO "$VLLM_COMMIT" && \ - git -C /vllm-workspace/vllm checkout FETCH_HEAD; \ - else \ - git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm; \ - fi +ARG VLLM_TAG=v0.20.2 +RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ python3 -m pip uninstall -y triton && \ diff --git a/Dockerfile.310p.openEuler b/Dockerfile.310p.openEuler index 62903c673..09a4c3db0 100644 --- a/Dockerfile.310p.openEuler +++ b/Dockerfile.310p.openEuler @@ -32,15 +32,8 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 -ARG VLLM_COMMIT="" -RUN if [ -n "$VLLM_COMMIT" ]; then \ - git init /vllm-workspace/vllm && \ - git -C /vllm-workspace/vllm fetch --depth 1 $VLLM_REPO "$VLLM_COMMIT" && \ - git -C /vllm-workspace/vllm checkout FETCH_HEAD; \ - else \ - git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm; \ - fi +ARG VLLM_TAG=v0.20.2 +RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ python3 -m pip uninstall -y triton && \ diff --git a/Dockerfile.a3 b/Dockerfile.a3 index 514bfc7af..e873cd728 100644 --- a/Dockerfile.a3 +++ b/Dockerfile.a3 @@ -18,7 +18,7 @@ FROM quay.io/ascend/cann:9.0.0-a3-ubuntu22.04-py3.12 ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG MOONCAKE_TAG=v0.3.9 +ARG MOONCAKE_TAG=v0.3.8.post1 COPY ./tools/mooncake_installer.sh /vllm-workspace/ @@ -49,15 +49,8 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 -ARG VLLM_COMMIT="" -RUN if [ -n "$VLLM_COMMIT" ]; then \ - git init /vllm-workspace/vllm && \ - git -C /vllm-workspace/vllm fetch --depth 1 $VLLM_REPO "$VLLM_COMMIT" && \ - git -C /vllm-workspace/vllm checkout FETCH_HEAD; \ - else \ - git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm; \ - fi +ARG VLLM_TAG=v0.20.2 +RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ python3 -m pip uninstall -y triton && \ @@ -73,7 +66,6 @@ ENV SOC_VERSION=$SOC_VERSION \ COPY . /vllm-workspace/vllm-ascend/ RUN export PIP_EXTRA_INDEX_URL="https://mirrors.huaweicloud.com/ascend/repos/pypi" && \ - export VLLM_BATCH_INVARIANT=1 && \ source /usr/local/Ascend/ascend-toolkit/set_env.sh && \ source /usr/local/Ascend/nnal/atb/set_env.sh && \ python3 -m pip install -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \ diff --git a/Dockerfile.a3.openEuler b/Dockerfile.a3.openEuler index f007e4762..f354e2828 100644 --- a/Dockerfile.a3.openEuler +++ b/Dockerfile.a3.openEuler @@ -18,7 +18,7 @@ FROM quay.io/ascend/cann:9.0.0-a3-openeuler24.03-py3.12 ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG MOONCAKE_TAG="v0.3.9" +ARG MOONCAKE_TAG="v0.3.8.post1" WORKDIR /workspace @@ -47,15 +47,8 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 -ARG VLLM_COMMIT="" -RUN if [ -n "$VLLM_COMMIT" ]; then \ - git init /vllm-workspace/vllm && \ - git -C /vllm-workspace/vllm fetch --depth 1 $VLLM_REPO "$VLLM_COMMIT" && \ - git -C /vllm-workspace/vllm checkout FETCH_HEAD; \ - else \ - git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm; \ - fi +ARG VLLM_TAG=v0.20.2 +RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ python3 -m pip uninstall -y triton && \ @@ -70,7 +63,6 @@ ENV SOC_VERSION=$SOC_VERSION \ COPY . /vllm-workspace/vllm-ascend/ RUN export PIP_EXTRA_INDEX_URL="https://mirrors.huaweicloud.com/ascend/repos/pypi" && \ - export VLLM_BATCH_INVARIANT=1 && \ source /usr/local/Ascend/ascend-toolkit/set_env.sh && \ source /usr/local/Ascend/nnal/atb/set_env.sh && \ python3 -m pip install -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \ diff --git a/Dockerfile.a5 b/Dockerfile.a5 index da01f7681..6c8cb890d 100644 --- a/Dockerfile.a5 +++ b/Dockerfile.a5 @@ -18,7 +18,7 @@ FROM quay.io/ascend/cann:9.0.0-950-ubuntu22.04-py3.12 ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG MOONCAKE_TAG=v0.3.9 +ARG MOONCAKE_TAG=v0.3.8.post1 COPY ./tools/mooncake_installer.sh /vllm-workspace/ @@ -49,7 +49,7 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 +ARG VLLM_TAG=v0.20.2 RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ diff --git a/Dockerfile.a5.openEuler b/Dockerfile.a5.openEuler index 13b5d7d29..5a97dd267 100644 --- a/Dockerfile.a5.openEuler +++ b/Dockerfile.a5.openEuler @@ -18,7 +18,7 @@ FROM quay.io/ascend/cann:9.0.0-950-openeuler24.03-py3.12 ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG MOONCAKE_TAG="v0.3.9" +ARG MOONCAKE_TAG="v0.3.8.post1" WORKDIR /workspace @@ -47,7 +47,7 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 +ARG VLLM_TAG=v0.20.2 RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ diff --git a/Dockerfile.openEuler b/Dockerfile.openEuler index 4a73636d6..015e2a3ba 100644 --- a/Dockerfile.openEuler +++ b/Dockerfile.openEuler @@ -18,7 +18,7 @@ FROM quay.io/ascend/cann:9.0.0-910b-openeuler24.03-py3.12 ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple" -ARG MOONCAKE_TAG="v0.3.9" +ARG MOONCAKE_TAG="v0.3.8.post1" WORKDIR /workspace @@ -47,15 +47,8 @@ RUN pip config set global.index-url ${PIP_INDEX_URL} && \ # Install vLLM ARG VLLM_REPO=https://github.com/vllm-project/vllm.git -ARG VLLM_TAG=v0.22.1 -ARG VLLM_COMMIT="" -RUN if [ -n "$VLLM_COMMIT" ]; then \ - git init /vllm-workspace/vllm && \ - git -C /vllm-workspace/vllm fetch --depth 1 $VLLM_REPO "$VLLM_COMMIT" && \ - git -C /vllm-workspace/vllm checkout FETCH_HEAD; \ - else \ - git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm; \ - fi +ARG VLLM_TAG=v0.20.2 +RUN git clone --depth 1 -b $VLLM_TAG $VLLM_REPO /vllm-workspace/vllm # In x86, triton will be installed by vllm. But in Ascend, triton doesn't work correctly. we need to uninstall it. RUN VLLM_TARGET_DEVICE="empty" python3 -m pip install -e /vllm-workspace/vllm/[audio] --extra-index https://download.pytorch.org/whl/cpu/ && \ python3 -m pip uninstall -y triton && \ @@ -70,7 +63,6 @@ ENV SOC_VERSION=$SOC_VERSION \ COPY . /vllm-workspace/vllm-ascend/ RUN export PIP_EXTRA_INDEX_URL="https://mirrors.huaweicloud.com/ascend/repos/pypi" && \ - export VLLM_BATCH_INVARIANT=1 && \ source /usr/local/Ascend/ascend-toolkit/set_env.sh && \ source /usr/local/Ascend/nnal/atb/set_env.sh && \ python3 -m pip install -e /vllm-workspace/vllm-ascend/ --extra-index https://download.pytorch.org/whl/cpu/ && \ diff --git a/README.md b/README.md index ab8403938..409711070 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Please use the following recommended versions to get started quickly: | Version | Release type | Doc | |------------|--------------|--------------------------------------| -| v0.21.0rc1 | Latest release candidate | See [QuickStart](https://docs.vllm.ai/projects/ascend/en/latest/quick_start.html) and [Installation](https://docs.vllm.ai/projects/ascend/en/latest/installation.html) for more details | +| v0.20.2rc1 | Latest release candidate | See [QuickStart](https://docs.vllm.ai/projects/ascend/en/latest/quick_start.html) and [Installation](https://docs.vllm.ai/projects/ascend/en/latest/installation.html) for more details | | v0.18.0 | Latest stable version | See [QuickStart](https://docs.vllm.ai/projects/ascend/en/v0.18.0/quick_start.html) and [Installation](https://docs.vllm.ai/projects/ascend/en/v0.18.0/installation.html) for more details | ## Branch @@ -86,7 +86,7 @@ Below are the maintained branches: | Branch | Status | Note | |------------------|--------------|--------------------------------------| -| main | Maintained | CI commitment for vLLM main branch and vLLM v0.22.1 tag | +| main | Maintained | CI commitment for vLLM main branch and vLLM v0.20.2 tag | | v0.7.1-dev | Unmaintained | Outdated, no longer maintained. | | v0.7.3-dev | Unmaintained | Only bug fixes are allowed, and no new release tags anymore. | | v0.9.1-dev | Unmaintained | Only bug fixes are allowed, and no new release tags anymore. | diff --git a/README.zh.md b/README.zh.md index e6017dc5a..0a679278e 100644 --- a/README.zh.md +++ b/README.zh.md @@ -66,7 +66,7 @@ vLLM 昇腾插件 (`vllm-ascend`) 是一个由社区维护的让vLLM在Ascend NP | Version | Release type | Doc | |------------|--------------|--------------------------------------| -| v0.21.0rc1 | 最新RC版本 | 请查看[快速开始](https://docs.vllm.ai/projects/ascend/en/latest/quick_start.html)和[安装指南](https://docs.vllm.ai/projects/ascend/en/latest/installation.html)了解更多 | +| v0.20.2rc1 | 最新RC版本 | 请查看[快速开始](https://docs.vllm.ai/projects/ascend/en/latest/quick_start.html)和[安装指南](https://docs.vllm.ai/projects/ascend/en/latest/installation.html)了解更多 | | v0.18.0 | 最新正式/稳定版本 | 请查看[快速开始](https://docs.vllm.ai/projects/ascend/en/v0.18.0/quick_start.html)和[安装指南](https://docs.vllm.ai/projects/ascend/en/v0.18.0/installation.html)了解更多 | ## 分支策略 @@ -80,7 +80,7 @@ vllm-ascend有主干分支和开发分支。 | 分支 | 状态 | 备注 | |------------------|--------------|----------------------| -| main | Maintained | 基于vLLM main分支和vLLM最新版本(v0.22.1)CI看护 | +| main | Maintained | 基于vLLM main分支和vLLM最新版本(v0.20.2)CI看护 | | v0.7.1-dev | Unmaintained | 不再维护 | | v0.7.3-dev | Unmaintained | 只允许Bug修复,不会再发布新版本 | | v0.9.1-dev | Unmaintained | 只允许Bug修复,不会再发布新版本 | diff --git a/csrc/aclnn_torch_adapter/op_api_common.h b/csrc/aclnn_torch_adapter/op_api_common.h index ca3028974..6694b5664 100644 --- a/csrc/aclnn_torch_adapter/op_api_common.h +++ b/csrc/aclnn_torch_adapter/op_api_common.h @@ -16,7 +16,6 @@ #ifndef OP_API_COMMON_ADAPTER #define OP_API_COMMON_ADAPTER -#include #include #include #include @@ -156,106 +155,6 @@ bool IsOpInputBaseFormat(const at::Tensor &tensor) (format == ACL_FORMAT_NCDHW); } -static std::vector split_str(std::string s, const std::string &del) -{ - int end = s.find(del); - std::vector path_list; - while (end != -1) { - path_list.push_back(s.substr(0, end)); - s.erase(s.begin(), s.begin() + end + 1); - end = s.find(del); - } - path_list.push_back(s); - return path_list; -} - -static bool is_file_exist(const std::string &path) -{ - if (path.empty() || path.size() > PATH_MAX) { - return false; - } - return (access(path.c_str(), F_OK) == 0) ? true : false; -} - -inline std::string real_path(const std::string &path) -{ - if (path.empty() || path.size() > PATH_MAX) { - return ""; - } - char realPath[PATH_MAX] = {0}; - if (realpath(path.c_str(), realPath) == nullptr) { - return ""; - } - return std::string(realPath); -} - -inline std::vector get_custom_lib_path() -{ - char *ascend_custom_opppath = std::getenv("ASCEND_CUSTOM_OPP_PATH"); - std::vector custom_lib_path_list; - - if (ascend_custom_opppath == nullptr) { - return std::vector(); - } - - std::string ascend_custom_opppath_str(ascend_custom_opppath); - // split string with ":" - custom_lib_path_list = split_str(ascend_custom_opppath_str, ":"); - if (custom_lib_path_list.empty()) { - return std::vector(); - } - for (auto &it : custom_lib_path_list) { - it = it + "/op_api/lib/"; - } - - return custom_lib_path_list; -} - -inline std::vector get_default_custom_lib_path() -{ - char *ascend_opp_path = std::getenv("ASCEND_OPP_PATH"); - std::vector default_vendors_list; - - if (ascend_opp_path == nullptr) { - return std::vector(); - } - - std::string vendors_path(ascend_opp_path); - vendors_path = vendors_path + "/vendors"; - std::string vendors_config_file = real_path(vendors_path + "/config.ini"); - if (vendors_config_file.empty()) { - return std::vector(); - } - - if (!is_file_exist(vendors_config_file)) { - return std::vector(); - } - - std::ifstream ifs(vendors_config_file); - std::string line; - while (std::getline(ifs, line)) { - if (line.find("load_priority=") == 0) { - break; - } - } - std::string head = "load_priority="; - line.erase(0, head.length()); - - // split string with "," - default_vendors_list = split_str(line, ","); - if (default_vendors_list.empty()) { - return std::vector(); - } - for (auto &it : default_vendors_list) { - it = real_path(vendors_path + "/" + it + "/op_api/lib/"); - } - - return default_vendors_list; -} - -const std::vector g_custom_lib_path = get_custom_lib_path(); -const std::vector g_default_custom_lib_path = get_default_custom_lib_path(); - inline const char *GetOpApiLibName(void) { return "libopapi.so"; } inline const char *GetCustOpApiLibName(void) { return "libcust_opapi.so"; } @@ -271,47 +170,21 @@ inline void *GetOpApiLibHandler(const char *libName) { return handler; } -inline void *GetOpApiFuncAddr(const char *apiName) -{ - if (!g_custom_lib_path.empty()) { - for (auto &it : g_custom_lib_path) { - auto cust_opapi_lib = real_path(it + "/" + GetCustOpApiLibName()); - if (cust_opapi_lib.empty()) { - continue; - } - auto custOpApiHandler = GetOpApiLibHandler(cust_opapi_lib.c_str()); - if (custOpApiHandler != nullptr) { - auto funcAddr = - GetOpApiFuncAddrInLib(custOpApiHandler, GetCustOpApiLibName(), apiName); - if (funcAddr != nullptr) { - return funcAddr; - } - } - } - } - - if (!g_default_custom_lib_path.empty()) { - for (auto &it : g_default_custom_lib_path) { - auto default_cust_opapi_lib = real_path(it + "/" + GetCustOpApiLibName()); - if (default_cust_opapi_lib.empty()) { - continue; - } - auto custOpApiHandler = GetOpApiLibHandler(default_cust_opapi_lib.c_str()); - if (custOpApiHandler != nullptr) { - auto funcAddr = - GetOpApiFuncAddrInLib(custOpApiHandler, GetCustOpApiLibName(), apiName); - if (funcAddr != nullptr) { - return funcAddr; - } - } - } +inline void *GetOpApiFuncAddr(const char *apiName) { + static auto custOpApiHandler = GetOpApiLibHandler(GetCustOpApiLibName()); + if (custOpApiHandler != nullptr) { + auto funcAddr = + GetOpApiFuncAddrInLib(custOpApiHandler, GetCustOpApiLibName(), apiName); + if (funcAddr != nullptr) { + return funcAddr; } + } - static auto opApiHandler = GetOpApiLibHandler(GetOpApiLibName()); - if (opApiHandler == nullptr) { - return nullptr; - } - return GetOpApiFuncAddrInLib(opApiHandler, GetOpApiLibName(), apiName); + static auto opApiHandler = GetOpApiLibHandler(GetOpApiLibName()); + if (opApiHandler == nullptr) { + return nullptr; + } + return GetOpApiFuncAddrInLib(opApiHandler, GetOpApiLibName(), apiName); } inline c10::Scalar ConvertTensorToScalar(const at::Tensor &tensor) { diff --git a/csrc/attention/fused_gdn_gating/fused_gdn_gating_torch_adpt.h b/csrc/attention/fused_gdn_gating/fused_gdn_gating_torch_adpt.h index f23bc7379..d2cd39319 100644 --- a/csrc/attention/fused_gdn_gating/fused_gdn_gating_torch_adpt.h +++ b/csrc/attention/fused_gdn_gating/fused_gdn_gating_torch_adpt.h @@ -16,8 +16,7 @@ std::tuple npu_fused_gdn_gating( const at::Tensor& a, const at::Tensor& b, const at::Tensor& dt_bias, - double beta = 1.0, - double threshold = 20.0) + double beta = 1.0) { TORCH_CHECK(A_log.dim() == 1, "A_log should be 1-D [num_heads], got ", A_log.dim(), "D"); TORCH_CHECK(dt_bias.dim() == 1, "dt_bias should be 1-D [num_heads], got ", dt_bias.dim(), "D"); @@ -25,12 +24,6 @@ std::tuple npu_fused_gdn_gating( TORCH_CHECK(b.dim() == 2, "b should be 2-D [batch, num_heads], got ", b.dim(), "D"); TORCH_CHECK(b.size(0) == a.size(0) && b.size(1) == a.size(1), "a and b must have the same shape, got a=", a.sizes(), " b=", b.sizes()); - TORCH_CHECK(a.scalar_type() == b.scalar_type(), - "a and b must have the same dtype, got a=", a.scalar_type(), - " b=", b.scalar_type()); - TORCH_CHECK(A_log.scalar_type() == dt_bias.scalar_type(), - "A_log and dt_bias must have the same dtype, got A_log=", - A_log.scalar_type(), " dt_bias=", dt_bias.scalar_type()); TORCH_CHECK(a.size(1) == A_log.size(0), "a second dim (num_heads) must equal A_log first dim, got a.size(1)=", a.size(1), " A_log.size(0)=", A_log.size(0)); @@ -43,12 +36,10 @@ std::tuple npu_fused_gdn_gating( at::Tensor beta_output = at::empty({1, batch, num_heads}, b.options()); float beta_val = static_cast(beta); - float threshold_val = static_cast(threshold); EXEC_NPU_CMD(aclnnFusedGdnGating, A_log, a, b, dt_bias, beta_val, - threshold_val, g, beta_output); return std::make_tuple(g, beta_output); diff --git a/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_def.cpp b/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_def.cpp index e1748abe5..ff8d4b924 100644 --- a/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_def.cpp +++ b/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_def.cpp @@ -19,37 +19,36 @@ class FusedGdnGating : public OpDef { { this->Input("a_log") .ParamType(REQUIRED) - .DataType({ge::DT_FLOAT, ge::DT_FLOAT, ge::DT_BF16, ge::DT_BF16, ge::DT_FLOAT16, ge::DT_FLOAT16}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); + .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) + .Format({ge::FORMAT_ND, ge::FORMAT_ND}) + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND}); this->Input("a") .ParamType(REQUIRED) - .DataType({ge::DT_BF16, ge::DT_FLOAT16, ge::DT_BF16, ge::DT_FLOAT16, ge::DT_BF16, ge::DT_FLOAT16}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); + .DataType({ge::DT_BF16, ge::DT_FLOAT16}) + .Format({ge::FORMAT_ND, ge::FORMAT_ND}) + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND}); this->Input("b") .ParamType(REQUIRED) - .DataType({ge::DT_BF16, ge::DT_FLOAT16, ge::DT_BF16, ge::DT_FLOAT16, ge::DT_BF16, ge::DT_FLOAT16}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); + .DataType({ge::DT_BF16, ge::DT_FLOAT16}) + .Format({ge::FORMAT_ND, ge::FORMAT_ND}) + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND}); this->Input("dt_bias") .ParamType(REQUIRED) - .DataType({ge::DT_FLOAT, ge::DT_FLOAT, ge::DT_BF16, ge::DT_BF16, ge::DT_FLOAT16, ge::DT_FLOAT16}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); + .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) + .Format({ge::FORMAT_ND, ge::FORMAT_ND}) + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND}); this->Output("g") .ParamType(REQUIRED) - .DataType({ge::DT_FLOAT, ge::DT_FLOAT, ge::DT_FLOAT, ge::DT_FLOAT, ge::DT_FLOAT, ge::DT_FLOAT}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); + .DataType({ge::DT_FLOAT, ge::DT_FLOAT}) + .Format({ge::FORMAT_ND, ge::FORMAT_ND}) + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND}); this->Output("beta_output") .ParamType(REQUIRED) - .DataType({ge::DT_BF16, ge::DT_FLOAT16, ge::DT_BF16, ge::DT_FLOAT16, ge::DT_BF16, ge::DT_FLOAT16}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}); + .DataType({ge::DT_BF16, ge::DT_FLOAT16}) + .Format({ge::FORMAT_ND, ge::FORMAT_ND}) + .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND}); this->Attr("beta").AttrType(OPTIONAL).Float(1.0f); - this->Attr("threshold").AttrType(OPTIONAL).Float(20.0f); OpAICoreConfig aicConfig; aicConfig.DynamicCompileStaticFlag(true) diff --git a/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling.cpp b/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling.cpp index 8f90c44bc..b7e764e9c 100644 --- a/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling.cpp +++ b/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling.cpp @@ -27,11 +27,7 @@ namespace { constexpr uint64_t TILING_KEY_BF16 = 1; constexpr uint64_t TILING_KEY_FP16 = 2; -constexpr uint64_t TILING_KEY_PARAM_BF16_OFFSET = 2; -constexpr uint64_t TILING_KEY_PARAM_FP16_OFFSET = 4; -constexpr size_t INPUT_INDEX_A_LOG = 0; constexpr size_t INPUT_INDEX_A = 1; -constexpr size_t INPUT_INDEX_DT_BIAS = 3; } // namespace @@ -68,36 +64,21 @@ ge::graphStatus FusedGdnGatingTilingFunc(gert::TilingContext *context) } float beta = 1.0f; - float threshold = 20.0f; auto *attrs = context->GetAttrs(); if (attrs != nullptr) { const float *betaAttr = attrs->GetAttrPointer(0); if (betaAttr != nullptr) { beta = *betaAttr; } - const float *thresholdAttr = attrs->GetAttrPointer(1); - if (thresholdAttr != nullptr) { threshold = *thresholdAttr; } } auto *aDesc = context->GetInputDesc(INPUT_INDEX_A); - auto *aLogDesc = context->GetInputDesc(INPUT_INDEX_A_LOG); - auto *dtBiasDesc = context->GetInputDesc(INPUT_INDEX_DT_BIAS); - if (aDesc == nullptr || aLogDesc == nullptr || dtBiasDesc == nullptr) { + if (aDesc == nullptr) { return ge::GRAPH_FAILED; } ge::DataType aDtype = aDesc->GetDataType(); - ge::DataType aLogDtype = aLogDesc->GetDataType(); - ge::DataType dtBiasDtype = dtBiasDesc->GetDataType(); - if (aLogDtype != dtBiasDtype) { - return ge::GRAPH_FAILED; - } uint64_t tilingKey = TILING_KEY_BF16; if (aDtype == ge::DT_FLOAT16) { tilingKey = TILING_KEY_FP16; } - if (aLogDtype == ge::DT_BF16) { - tilingKey += TILING_KEY_PARAM_BF16_OFFSET; - } else if (aLogDtype == ge::DT_FLOAT16) { - tilingKey += TILING_KEY_PARAM_FP16_OFFSET; - } uint32_t blockDim = static_cast(numBatches); if (blockDim > aivNum) { @@ -136,7 +117,6 @@ ge::graphStatus FusedGdnGatingTilingFunc(gert::TilingContext *context) td.rowsPerIter = rowsPerIter; td.useBulkDma = useBulkDma ? 1u : 0u; td.beta = beta; - td.threshold = threshold; const size_t tilingSize = sizeof(FusedGdnGatingTilingData); auto *rawTilingData = context->GetRawTilingData(); diff --git a/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling_utils.h b/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling_utils.h index bb76eb4fd..de34e9092 100644 --- a/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling_utils.h +++ b/csrc/attention/fused_gdn_gating/op_host/fused_gdn_gating_tiling_utils.h @@ -20,7 +20,6 @@ namespace FusedGdnGating { constexpr uint32_t VECTOR_BYTES_PER_ITER = 256; constexpr uint32_t DATACOPY_MIN_BYTES = 32; constexpr uint32_t BF16_PER_BLOCK = DATACOPY_MIN_BYTES / 2; // 16 -constexpr uint32_t MASK_ALIGN_ELEMS = 64; /// Align count to vector unit width (256 bytes) for given dtype size. inline uint32_t AlignCountToVectorBytes(uint32_t count, uint32_t dtypeSize) @@ -65,23 +64,22 @@ inline uint32_t ComputeRowsPerIter(uint32_t numHeads, uint64_t ubBudget, uint32_t ubDim = 0) { if (ubDim == 0) { - // Match the kernel's fp32 compute/mask alignment. - ubDim = ((numHeads + MASK_ALIGN_ELEMS - 1) / MASK_ALIGN_ELEMS) * MASK_ALIGN_ELEMS; + // Use DMA-friendly alignment matching kernel's DMA_ALIGN_ELEMS=16. + // 16 bf16/fp16 elements = 32 bytes = 1 DMA block (min alignment). + // 16 fp32 elements = 64 bytes = 2 DMA blocks. + ubDim = ((numHeads + BF16_PER_BLOCK - 1) / BF16_PER_BLOCK) * BF16_PER_BLOCK; } - uint32_t maskUbDim = ubDim; - // 2 parameter input queues + 2 fp32 constant buffers, each 1 row. - // Use fp32 for the parameter queues as a conservative upper bound. - uint32_t sharedBytes = 4 * ubDim * static_cast(sizeof(float)); + // 3 fp32 constant buffers, each 1 row. + uint32_t sharedBytes = 3 * ubDim * static_cast(sizeof(float)); // Multi-row constant buffers (precomputed once, scaled by R): // dtBiasMultiBuf_ + negExpMultiBuf_: 2 fp32 buffers. uint32_t constPerRowBytes = 2 * ubDim * static_cast(sizeof(float)); - // Per-row (per-chunk): 3 bf16/fp16 buffers + 5 fp32 buffers + 1 uint8 mask buffer. + // Per-row (per-chunk): 3 bf16/fp16 buffers + 6 fp32 buffers. uint32_t perRowBytes = 3 * ubDim * static_cast(sizeof(int16_t)) // a, b, betaOut - + 5 * ubDim * static_cast(sizeof(float)) // g, x, betaX, tmp, betaFp32 - + 1 * maskUbDim * static_cast(sizeof(uint8_t)); // threshold mask + + 6 * ubDim * static_cast(sizeof(float)); // g, x, betaX, abs, tmp, betaFp32 if (perRowBytes == 0) { return 1; diff --git a/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.cpp b/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.cpp index 307fb5ce0..bb15c1c84 100644 --- a/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.cpp +++ b/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.cpp @@ -37,7 +37,6 @@ struct FusedGdnGatingParams { const aclTensor *b{nullptr}; const aclTensor *dtBias{nullptr}; float beta{1.0f}; - float threshold{20.0f}; aclTensor *g{nullptr}; aclTensor *betaOutput{nullptr}; }; @@ -46,8 +45,6 @@ static const std::initializer_list AB_TYPE_SUPPORT_LIST = {op::DataType::DT_BF16, op::DataType::DT_FLOAT16}; static const std::initializer_list FP32_TYPE_SUPPORT_LIST = {op::DataType::DT_FLOAT}; -static const std::initializer_list PARAM_TYPE_SUPPORT_LIST = - {op::DataType::DT_FLOAT, op::DataType::DT_BF16, op::DataType::DT_FLOAT16}; static inline bool CheckNotNull(const FusedGdnGatingParams ¶ms) { @@ -62,21 +59,12 @@ static inline bool CheckNotNull(const FusedGdnGatingParams ¶ms) static inline bool CheckDtype(const FusedGdnGatingParams ¶ms) { - OP_CHECK_DTYPE_NOT_SUPPORT(params.aLog, PARAM_TYPE_SUPPORT_LIST, return false); - OP_CHECK_DTYPE_NOT_SUPPORT(params.dtBias, PARAM_TYPE_SUPPORT_LIST, return false); + OP_CHECK_DTYPE_NOT_SUPPORT(params.aLog, FP32_TYPE_SUPPORT_LIST, return false); + OP_CHECK_DTYPE_NOT_SUPPORT(params.dtBias, FP32_TYPE_SUPPORT_LIST, return false); OP_CHECK_DTYPE_NOT_SUPPORT(params.a, AB_TYPE_SUPPORT_LIST, return false); OP_CHECK_DTYPE_NOT_SUPPORT(params.b, AB_TYPE_SUPPORT_LIST, return false); OP_CHECK_DTYPE_NOT_SUPPORT(params.g, FP32_TYPE_SUPPORT_LIST, return false); OP_CHECK_DTYPE_NOT_SUPPORT(params.betaOutput, AB_TYPE_SUPPORT_LIST, return false); - OP_CHECK(params.a->GetDataType() == params.b->GetDataType(), - OP_LOGE(ACLNN_ERR_PARAM_INVALID, "a and b must have the same dtype."), - return false); - OP_CHECK(params.aLog->GetDataType() == params.dtBias->GetDataType(), - OP_LOGE(ACLNN_ERR_PARAM_INVALID, "aLog and dtBias must have the same dtype."), - return false); - OP_CHECK(params.betaOutput->GetDataType() == params.b->GetDataType(), - OP_LOGE(ACLNN_ERR_PARAM_INVALID, "betaOutput and b must have the same dtype."), - return false); return true; } @@ -91,18 +79,18 @@ static aclnnStatus CheckParams(const FusedGdnGatingParams ¶ms) aclnnStatus aclnnFusedGdnGatingGetWorkspaceSize( const aclTensor *aLog, const aclTensor *a, const aclTensor *b, - const aclTensor *dtBias, float beta, float threshold, + const aclTensor *dtBias, float beta, aclTensor *g, aclTensor *betaOutput, uint64_t *workspaceSize, aclOpExecutor **executor) { L2_DFX_PHASE_1(aclnnFusedGdnGating, - DFX_IN(aLog, a, b, dtBias, beta, threshold), + DFX_IN(aLog, a, b, dtBias, beta), DFX_OUT(g, betaOutput)); auto uniqueExecutor = CREATE_EXECUTOR(); CHECK_RET(uniqueExecutor.get() != nullptr, ACLNN_ERR_INNER_CREATE_EXECUTOR); - FusedGdnGatingParams params{aLog, a, b, dtBias, beta, threshold, g, betaOutput}; + FusedGdnGatingParams params{aLog, a, b, dtBias, beta, g, betaOutput}; CHECK_RET(CheckParams(params) == ACLNN_SUCCESS, ACLNN_ERR_PARAM_INVALID); // Bring inputs to a contiguous form that the kernel expects. @@ -116,7 +104,7 @@ aclnnStatus aclnnFusedGdnGatingGetWorkspaceSize( CHECK_RET(dtBiasContig != nullptr, ACLNN_ERR_INNER_NULLPTR); auto result = l0op::FusedGdnGating(aLogContig, aContig, bContig, dtBiasContig, - beta, threshold, uniqueExecutor.get()); + beta, uniqueExecutor.get()); CHECK_RET(result.g != nullptr && result.beta_output != nullptr, ACLNN_ERR_INNER_NULLPTR); diff --git a/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.h b/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.h index 74d935066..19dbaea96 100644 --- a/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.h +++ b/csrc/attention/fused_gdn_gating/op_host/op_api/aclnn_fused_gdn_gating.h @@ -20,12 +20,11 @@ extern "C" { /** * @brief FusedGdnGating phase-1: compute required workspace size. - * @param [in] aLog : A_log, [num_heads], dtype fp32/bf16/fp16. + * @param [in] aLog : A_log, [num_heads], dtype fp32. * @param [in] a : a, [batch, num_heads], dtype bf16/fp16. * @param [in] b : b, [batch, num_heads], dtype bf16/fp16. - * @param [in] dtBias : dt_bias, [num_heads], same dtype as aLog. + * @param [in] dtBias : dt_bias, [num_heads], dtype fp32. * @param [in] beta : softplus beta (default 1.0). - * @param [in] threshold : softplus threshold (default 20.0). * @param [out] g : output gate, [1, batch, num_heads], dtype fp32. * @param [out] betaOutput : sigmoid(b), [1, batch, num_heads], same dtype as a/b. * @param [out] workspaceSize: required workspace bytes on device. @@ -33,7 +32,7 @@ extern "C" { */ __attribute__((visibility("default"))) aclnnStatus aclnnFusedGdnGatingGetWorkspaceSize( const aclTensor *aLog, const aclTensor *a, const aclTensor *b, - const aclTensor *dtBias, float beta, float threshold, + const aclTensor *dtBias, float beta, aclTensor *g, aclTensor *betaOutput, uint64_t *workspaceSize, aclOpExecutor **executor); diff --git a/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.cpp b/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.cpp index e38373f05..d6a6aef41 100644 --- a/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.cpp +++ b/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.cpp @@ -28,10 +28,10 @@ static constexpr FusedGdnGatingOutput kNullOutput{nullptr, nullptr}; FusedGdnGatingOutput FusedGdnGating(const aclTensor *aLog, const aclTensor *a, const aclTensor *b, const aclTensor *dtBias, - float beta, float threshold, + float beta, aclOpExecutor *executor) { - L0_DFX(FusedGdnGating, aLog, a, b, dtBias, beta, threshold); + L0_DFX(FusedGdnGating, aLog, a, b, dtBias, beta); const DataType betaDtype = b->GetDataType(); const Format format = Format::FORMAT_ND; @@ -48,14 +48,14 @@ FusedGdnGatingOutput FusedGdnGating(const aclTensor *aLog, const aclTensor *a, auto ret = INFER_SHAPE(FusedGdnGating, OP_INPUT(aLog, a, b, dtBias), OP_OUTPUT(g, betaOutput), - OP_ATTR(beta, threshold)); + OP_ATTR(beta)); OP_CHECK_INFERSHAPE(ret != ACLNN_SUCCESS, return kNullOutput, "FusedGdnGating InferShape failed."); ret = ADD_TO_LAUNCHER_LIST_AICORE(FusedGdnGating, OP_INPUT(aLog, a, b, dtBias), OP_OUTPUT(g, betaOutput), - OP_ATTR(beta, threshold)); + OP_ATTR(beta)); OP_CHECK_ADD_TO_LAUNCHER_LIST_AICORE(ret != ACLNN_SUCCESS, return kNullOutput, "FusedGdnGating ADD_TO_LAUNCHER_LIST_AICORE failed."); diff --git a/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.h b/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.h index 2032151bf..e504fbb6a 100644 --- a/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.h +++ b/csrc/attention/fused_gdn_gating/op_host/op_api/fused_gdn_gating.h @@ -19,7 +19,7 @@ struct FusedGdnGatingOutput { FusedGdnGatingOutput FusedGdnGating(const aclTensor *aLog, const aclTensor *a, const aclTensor *b, const aclTensor *dtBias, - float beta, float threshold, + float beta, aclOpExecutor *executor); } // namespace l0op diff --git a/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.cpp b/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.cpp index e9d483c5a..39de4aa6c 100644 --- a/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.cpp +++ b/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.cpp @@ -27,27 +27,11 @@ fused_gdn_gating(GM_ADDR a_log, GM_ADDR a, GM_ADDR b, GM_ADDR dt_bias, TPipe pipe; if (TILING_KEY_IS(1)) { - KernelFusedGdnGating op; + KernelFusedGdnGating op; op.Init(a_log, a, b, dt_bias, g, beta_output, &tilingData, &pipe); op.Process(); } else if (TILING_KEY_IS(2)) { - KernelFusedGdnGating op; - op.Init(a_log, a, b, dt_bias, g, beta_output, &tilingData, &pipe); - op.Process(); - } else if (TILING_KEY_IS(3)) { - KernelFusedGdnGating op; - op.Init(a_log, a, b, dt_bias, g, beta_output, &tilingData, &pipe); - op.Process(); - } else if (TILING_KEY_IS(4)) { - KernelFusedGdnGating op; - op.Init(a_log, a, b, dt_bias, g, beta_output, &tilingData, &pipe); - op.Process(); - } else if (TILING_KEY_IS(5)) { - KernelFusedGdnGating op; - op.Init(a_log, a, b, dt_bias, g, beta_output, &tilingData, &pipe); - op.Process(); - } else if (TILING_KEY_IS(6)) { - KernelFusedGdnGating op; + KernelFusedGdnGating op; op.Init(a_log, a, b, dt_bias, g, beta_output, &tilingData, &pipe); op.Process(); } diff --git a/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.h b/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.h index 108d035a1..893e19828 100644 --- a/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.h +++ b/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating.h @@ -9,15 +9,13 @@ * \brief AscendC kernel for fused GDN gating. * * Per-row math: - * g = -exp(A_log) * softplus(cast(a,fp32) + dt_bias, beta, threshold) + * g = -exp(A_log) * softplus(cast(a,fp32) + dt_bias) * beta_output = sigmoid(cast(b, fp32)) -> cast back to InDtype */ #ifndef FUSED_GDN_GATING_KERNEL_H #define FUSED_GDN_GATING_KERNEL_H -#include - #include "kernel_operator.h" #include "fused_gdn_gating_tiling_data.h" @@ -29,7 +27,6 @@ using namespace AscendC; constexpr uint32_t BYTES_PER_BLOCK = 32; constexpr uint32_t BF16_PER_BLOCK = BYTES_PER_BLOCK / sizeof(int16_t); // 16 constexpr uint32_t FP32_PER_BLOCK = BYTES_PER_BLOCK / sizeof(float); // 8 -constexpr uint32_t MASK_ALIGN_ELEMS = 64; // DMA-friendly alignment: 16 elements = 32 bytes = 1 DMA block. // Vector ops use count=numHeads_ with partial-iteration masking, @@ -42,7 +39,7 @@ __aicore__ inline T CeilDiv(T a, T b) { return (a + b - 1) / b; } template __aicore__ inline T AlignUp(T a, T b) { return CeilDiv(a, b) * b; } -template +template class KernelFusedGdnGating { public: __aicore__ inline KernelFusedGdnGating() {} @@ -62,17 +59,13 @@ class KernelFusedGdnGating { rowsPerIter_ = tiling->rowsPerIter; useBulkDma_ = (tiling->useBulkDma != 0); beta_ = tiling->beta; - threshold_ = tiling->threshold; // Aligned dimensions for UB tensors. - alignedHeadsHalf_ = AlignUp(numHeads_, MASK_ALIGN_ELEMS); - alignedHeadsFloat_ = AlignUp(numHeads_, MASK_ALIGN_ELEMS); - alignedHeadsMask_ = alignedHeadsFloat_; - constexpr uint32_t paramAlignElems = BYTES_PER_BLOCK / sizeof(ParamDtype); - alignedHeadsParam_ = AlignUp(numHeads_, paramAlignElems); - - aLogGm_.SetGlobalBuffer(reinterpret_cast<__gm__ ParamDtype *>(aLogGm), numHeads_); - dtBiasGm_.SetGlobalBuffer(reinterpret_cast<__gm__ ParamDtype *>(dtBiasGm), numHeads_); + alignedHeadsHalf_ = AlignUp(numHeads_, DMA_ALIGN_ELEMS); + alignedHeadsFloat_ = AlignUp(numHeads_, DMA_ALIGN_ELEMS); + + aLogGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float *>(aLogGm), numHeads_); + dtBiasGm_.SetGlobalBuffer(reinterpret_cast<__gm__ float *>(dtBiasGm), numHeads_); aGm_.SetGlobalBuffer(reinterpret_cast<__gm__ InDtype *>(aGm), static_cast(numBatches_) * numHeads_); bGm_.SetGlobalBuffer(reinterpret_cast<__gm__ InDtype *>(bGm), @@ -89,9 +82,8 @@ class KernelFusedGdnGating { pipe_->InitBuffer(betaOutQue_, 1, rowsPerIter_ * alignedHeadsHalf_ * sizeof(InDtype)); // Constant queues (single-row). - pipe_->InitBuffer(aLogInQue_, 1, 1 * alignedHeadsParam_ * sizeof(ParamDtype)); - pipe_->InitBuffer(dtBiasInQue_, 1, 1 * alignedHeadsParam_ * sizeof(ParamDtype)); - pipe_->InitBuffer(negExpInQue_, 1, 1 * alignedHeadsFloat_ * sizeof(float)); + pipe_->InitBuffer(aLogInQue_, 1, 1 * alignedHeadsFloat_ * sizeof(float)); + pipe_->InitBuffer(dtBiasInQue_, 1, 1 * alignedHeadsFloat_ * sizeof(float)); pipe_->InitBuffer(dtBiasPreloadQue_, 1, 1 * alignedHeadsFloat_ * sizeof(float)); // Multi-row constants: dt_bias and neg_exp(A_log) replicated R times. @@ -104,8 +96,8 @@ class KernelFusedGdnGating { // Scratch buffers (V-only access). pipe_->InitBuffer(xBuf_, rowsPerIter_ * alignedHeadsFloat_ * sizeof(float)); pipe_->InitBuffer(betaXBuf_, rowsPerIter_ * alignedHeadsFloat_ * sizeof(float)); + pipe_->InitBuffer(softplusAbsBuf_, rowsPerIter_ * alignedHeadsFloat_ * sizeof(float)); pipe_->InitBuffer(softplusTmpBuf_, rowsPerIter_ * alignedHeadsFloat_ * sizeof(float)); - pipe_->InitBuffer(thresholdMaskBuf_, rowsPerIter_ * alignedHeadsMask_ * sizeof(uint8_t)); pipe_->InitBuffer(betaFp32Buf_, rowsPerIter_ * alignedHeadsFloat_ * sizeof(float)); } @@ -135,49 +127,34 @@ class KernelFusedGdnGating { */ __aicore__ inline void PreloadConstants() { - LocalTensor tmpALog = aLogInQue_.template AllocTensor(); - dtBiasTensor_ = negExpInQue_.template AllocTensor(); + LocalTensor tmpALog = aLogInQue_.template AllocTensor(); + dtBiasTensor_ = dtBiasInQue_.template AllocTensor(); - DataCopyExtParams paramCopyParams{1, static_cast(numHeads_ * sizeof(ParamDtype)), + DataCopyExtParams fp32CopyParams{1, static_cast(numHeads_ * sizeof(float)), 0, 0, 0}; - DataCopyPadExtParams paramPadParams{false, 0, 0, static_cast(0)}; + DataCopyPadExtParams fp32PadParams{false, 0, 0, 0.0f}; // Load A_log. - DataCopyPad(tmpALog, aLogGm_, paramCopyParams, paramPadParams); - aLogInQue_.template EnQue(tmpALog); - tmpALog = aLogInQue_.template DeQue(); - - if constexpr (std::is_same()) { - Adds(dtBiasTensor_, tmpALog, 0.0f, numHeads_); - } else { - Cast(dtBiasTensor_, tmpALog, RoundMode::CAST_NONE, numHeads_); - } - PipeBarrier(); + DataCopyPad(tmpALog, aLogGm_, fp32CopyParams, fp32PadParams); + aLogInQue_.template EnQue(tmpALog); + tmpALog = aLogInQue_.template DeQue(); // neg_exp(A_log). - Exp(dtBiasTensor_, dtBiasTensor_, numHeads_); + Exp(dtBiasTensor_, tmpALog, numHeads_); PipeBarrier(); Muls(dtBiasTensor_, dtBiasTensor_, -1.0f, numHeads_); PipeBarrier(); aLogInQue_.FreeTensor(tmpALog); - negExpInQue_.template EnQue(dtBiasTensor_); - dtBiasTensor_ = negExpInQue_.template DeQue(); + dtBiasInQue_.template EnQue(dtBiasTensor_); + dtBiasTensor_ = dtBiasInQue_.template DeQue(); // Load dt_bias. - LocalTensor tmpDtBias = dtBiasInQue_.template AllocTensor(); dtBiasPreloaded_ = dtBiasPreloadQue_.template AllocTensor(); - DataCopyPad(tmpDtBias, dtBiasGm_, paramCopyParams, paramPadParams); - dtBiasInQue_.template EnQue(tmpDtBias); - tmpDtBias = dtBiasInQue_.template DeQue(); - if constexpr (std::is_same()) { - Adds(dtBiasPreloaded_, tmpDtBias, 0.0f, numHeads_); - } else { - Cast(dtBiasPreloaded_, tmpDtBias, RoundMode::CAST_NONE, numHeads_); - } - PipeBarrier(); - dtBiasInQue_.FreeTensor(tmpDtBias); + DataCopyExtParams dtBiasCopyParams{1, static_cast(numHeads_ * sizeof(float)), 0, 0, 0}; + DataCopyPadExtParams dtBiasPadParams{false, 0, 0, 0.0f}; + DataCopyPad(dtBiasPreloaded_, dtBiasGm_, dtBiasCopyParams, dtBiasPadParams); dtBiasPreloadQue_.template EnQue(dtBiasPreloaded_); dtBiasPreloaded_ = dtBiasPreloadQue_.template DeQue(); @@ -237,12 +214,11 @@ class KernelFusedGdnGating { LocalTensor x = xBuf_.Get(); LocalTensor betaX = betaXBuf_.Get(); + LocalTensor softplusAbs = softplusAbsBuf_.Get(); LocalTensor softplusTmp = softplusTmpBuf_.Get(); - LocalTensor thresholdMask = thresholdMaskBuf_.Get(); LocalTensor betaFp32 = betaFp32Buf_.Get(); const uint32_t multiCount = validRows * alignedHeadsFloat_; - const uint32_t maskCount = validRows * alignedHeadsMask_; // Batch Cast a→fp32, b→fp32. Cast(x, aLocal, RoundMode::CAST_NONE, multiCount); @@ -257,7 +233,9 @@ class KernelFusedGdnGating { PipeBarrier(); Muls(betaX, x, beta_, multiCount); PipeBarrier(); - Mins(softplusTmp, betaX, threshold_, multiCount); + Abs(softplusAbs, betaX, multiCount); + PipeBarrier(); + Muls(softplusTmp, softplusAbs, -1.0f, multiCount); PipeBarrier(); Exp(softplusTmp, softplusTmp, multiCount); PipeBarrier(); @@ -267,9 +245,9 @@ class KernelFusedGdnGating { PipeBarrier(); Muls(softplusTmp, softplusTmp, 1.0f / beta_, multiCount); PipeBarrier(); - CompareScalar(thresholdMask, betaX, threshold_, CMPMODE::LE, maskCount); + Maxs(gLocal, x, 0.0f, multiCount); PipeBarrier(); - Select(gLocal, thresholdMask, softplusTmp, x, SELMODE::VSEL_TENSOR_TENSOR_MODE, multiCount); + Add(gLocal, gLocal, softplusTmp, multiCount); PipeBarrier(); Mul(gLocal, gLocal, negExpMulti, multiCount); PipeBarrier(); @@ -279,7 +257,11 @@ class KernelFusedGdnGating { PipeBarrier(); Muls(betaX, x, beta_, multiCount); PipeBarrier(); - Mins(softplusTmp, betaX, threshold_, multiCount); + Abs(softplusAbs, betaX, multiCount); + PipeBarrier(); + Adds(betaX, dtBiasTensor_, 0.0f, numHeads_); + PipeBarrier(); + Muls(softplusTmp, softplusAbs, -1.0f, multiCount); PipeBarrier(); Exp(softplusTmp, softplusTmp, multiCount); PipeBarrier(); @@ -289,11 +271,11 @@ class KernelFusedGdnGating { PipeBarrier(); Muls(softplusTmp, softplusTmp, 1.0f / beta_, multiCount); PipeBarrier(); - CompareScalar(thresholdMask, betaX, threshold_, CMPMODE::LE, maskCount); + Maxs(gLocal, x, 0.0f, multiCount); PipeBarrier(); - Select(gLocal, thresholdMask, softplusTmp, x, SELMODE::VSEL_TENSOR_TENSOR_MODE, multiCount); + Add(gLocal, gLocal, softplusTmp, multiCount); PipeBarrier(); - Mul(gLocal, gLocal, dtBiasTensor_, multiCount); + Mul(gLocal, gLocal, betaX, multiCount); PipeBarrier(); } @@ -352,8 +334,8 @@ class KernelFusedGdnGating { private: TPipe *pipe_{nullptr}; - GlobalTensor aLogGm_; - GlobalTensor dtBiasGm_; + GlobalTensor aLogGm_; + GlobalTensor dtBiasGm_; GlobalTensor aGm_; GlobalTensor bGm_; GlobalTensor gGm_; @@ -363,7 +345,6 @@ class KernelFusedGdnGating { TQue bInQue_; TQue aLogInQue_; TQue dtBiasInQue_; - TQue negExpInQue_; TQue dtBiasPreloadQue_; TQue gOutQue_; TQue betaOutQue_; @@ -372,8 +353,8 @@ class KernelFusedGdnGating { TBuf negExpMultiBuf_; TBuf xBuf_; TBuf betaXBuf_; + TBuf softplusAbsBuf_; TBuf softplusTmpBuf_; - TBuf thresholdMaskBuf_; TBuf betaFp32Buf_; LocalTensor dtBiasTensor_; // neg_exp(A_log), 1 row @@ -385,10 +366,7 @@ class KernelFusedGdnGating { bool useBulkDma_{false}; uint32_t alignedHeadsHalf_{0}; uint32_t alignedHeadsFloat_{0}; - uint32_t alignedHeadsMask_{0}; - uint32_t alignedHeadsParam_{0}; float beta_{1.0f}; - float threshold_{20.0f}; }; } // namespace FusedGdnGating diff --git a/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating_tiling_data.h b/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating_tiling_data.h index c8faed96b..46e0cbc00 100644 --- a/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating_tiling_data.h +++ b/csrc/attention/fused_gdn_gating/op_kernel/fused_gdn_gating_tiling_data.h @@ -23,7 +23,6 @@ struct alignas(8) FusedGdnGatingTilingData { uint32_t rowsPerIter; uint32_t useBulkDma; float beta; - float threshold; }; #pragma pack(pop) diff --git a/csrc/attention/quant_lightning_indexer/op_host/quant_lightning_indexer_tiling.cpp b/csrc/attention/quant_lightning_indexer/op_host/quant_lightning_indexer_tiling.cpp index 6496a7904..32862f6bb 100644 --- a/csrc/attention/quant_lightning_indexer/op_host/quant_lightning_indexer_tiling.cpp +++ b/csrc/attention/quant_lightning_indexer/op_host/quant_lightning_indexer_tiling.cpp @@ -871,24 +871,15 @@ ge::graphStatus QuantLightningIndexerTiling::DoTiling(QLITilingInfo *tilingInfo) constexpr uint32_t V1_DECODE_DATA_NUM = 2; // Decode每个核需要存储头和尾部两块数据 constexpr uint32_t S1_BASE_SIZE = 8; // S1轴基本块的大小 constexpr uint32_t TOPK_MAX_SIZE = 2048; // TopK选取个数 - constexpr uint32_t ASCEND950_S1_BASE_SIZE = 4; // Ascend 950 S1轴基本块的大小 - constexpr uint32_t ASCEND950_S2_BASE_SIZE = 128; // Ascend 950 S2轴基本块的大小 uint32_t workspaceSize = ascendcPlatform.GetLibApiWorkSpaceSize(); // 主流程需Workspace大小 - platform_ascendc::SocVersion socVersion_ = ascendcPlatform.GetSocVersion(); - if (socVersion_ == platform_ascendc::SocVersion::ASCEND950) { - constexpr uint32_t s1Base = ASCEND950_S1_BASE_SIZE; - constexpr uint32_t s2Base = ASCEND950_S2_BASE_SIZE; - workspaceSize += s1Base * ((tilingInfo->s2Size + s2Base - 1) / s2Base) * s2Base * sizeof(uint32_t) * aicNum; - } else { - uint32_t mm1ResSize = M_BASE_SIZE * S2_BASE_SIZE; - workspaceSize += mm1ResSize * MM1_RES_ELEM_SIZE * DOUBLE_BUFFER * aicNum; - // Decode流程(LD)需要Workspace大小 - // 临时存储Decode中间结果大小: 2(头/尾)*8(s1Base)*2(idx/value)*2048(K)*sizeof(int32)*24=6M - workspaceSize += V1_DECODE_DATA_NUM * S1_BASE_SIZE * V1_RES_ELEM_TYPE * TOPK_MAX_SIZE * V1_RES_ELEM_SIZE * aicNum; - // 临时存储Decode中间参数信息大小: 2(头/尾)*8(s1Base)*16(paramNum)*sizeof(int64_t)*24=48k - workspaceSize += V1_DECODE_DATA_NUM * S1_BASE_SIZE * V1_DECODE_PARAM_NUM * V1_DECODE_PARAM_ELEM_SIZE * aicNum; - } + uint32_t mm1ResSize = M_BASE_SIZE * S2_BASE_SIZE; + workspaceSize += mm1ResSize * MM1_RES_ELEM_SIZE * DOUBLE_BUFFER * aicNum; + // Decode流程(LD)需要Workspace大小 + // 临时存储Decode中间结果大小: 2(头/尾)*8(s1Base)*2(idx/value)*2048(K)*sizeof(int32)*24=6M + workspaceSize += V1_DECODE_DATA_NUM * S1_BASE_SIZE * V1_RES_ELEM_TYPE * TOPK_MAX_SIZE * V1_RES_ELEM_SIZE * aicNum; + // 临时存储Decode中间参数信息大小: 2(头/尾)*8(s1Base)*16(paramNum)*sizeof(int64_t)*24=48k + workspaceSize += V1_DECODE_DATA_NUM * S1_BASE_SIZE * V1_DECODE_PARAM_NUM * V1_DECODE_PARAM_ELEM_SIZE * aicNum; size_t *workSpaces = context_->GetWorkspaceSizes(1); workSpaces[0] = workspaceSize; diff --git a/csrc/attention/quant_lightning_indexer/op_kernel/arch35/quant_lightning_indexer_kernel.h b/csrc/attention/quant_lightning_indexer/op_kernel/arch35/quant_lightning_indexer_kernel.h index 63f05b77d..96bee1117 100644 --- a/csrc/attention/quant_lightning_indexer/op_kernel/arch35/quant_lightning_indexer_kernel.h +++ b/csrc/attention/quant_lightning_indexer/op_kernel/arch35/quant_lightning_indexer_kernel.h @@ -398,11 +398,9 @@ __aicore__ inline void QLIPreload::Init(__gm__ uint8_t *query, __gm__ uint uint64_t offset = 0; //vec 把整个s2的score存储在GM,大小为s1BaseSize * 16K * 4 GlobalTensor scoreGm; //存放vec核写出的score - if ASCEND_IS_AIV { - uint64_t singleCoreScoreSize = constInfo.s1BaseSize * QLICommon::Align((uint64_t)constInfo.kSeqSize, (uint64_t)constInfo.s2BaseSize) * sizeof(SCORE_T); - scoreGm.SetGlobalBuffer((__gm__ SCORE_T *)(workspace + aiCoreIdx * singleCoreScoreSize)); - offset += GetBlockNum() * singleCoreScoreSize; - } + uint64_t singleCoreScoreSize = constInfo.s1BaseSize * QLICommon::Align((uint64_t)constInfo.kSeqSize, (uint64_t)constInfo.s2BaseSize) * sizeof(SCORE_T); + scoreGm.SetGlobalBuffer((__gm__ SCORE_T *)(workspace + aiCoreIdx * singleCoreScoreSize)); + offset += GetBlockNum() * singleCoreScoreSize; if ASCEND_IS_AIV { vectorService.InitParams(constInfo, tiling); diff --git a/csrc/attention/store_kv_block/CMakeLists.txt b/csrc/attention/store_kv_block/CMakeLists.txt deleted file mode 100644 index 86b308249..000000000 --- a/csrc/attention/store_kv_block/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# ----------------------------------------------------------------------------------------------------------- -# Copyright (c) 2025 Huawei Technologies Co., Ltd. -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of -# CANN Open Software License Agreement Version 2.0 (the "License"). -# Please refer to the License for details. You may not use this file except in compliance with the License. -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, -# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. -# See LICENSE in the root of the software repository for the full text of the License. -# ----------------------------------------------------------------------------------------------------------- - -file(GLOB CURRENT_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*) -if(NOT ENABLE_TEST AND NOT BENCHMARK) - list(REMOVE_ITEM CURRENT_DIRS tests) -endif() -foreach(SUB_DIR ${CURRENT_DIRS}) - if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUB_DIR}/CMakeLists.txt") - add_subdirectory(${SUB_DIR}) - endif() -endforeach() \ No newline at end of file diff --git a/csrc/attention/store_kv_block/op_host/CMakeLists.txt b/csrc/attention/store_kv_block/op_host/CMakeLists.txt deleted file mode 100644 index e40581fd9..000000000 --- a/csrc/attention/store_kv_block/op_host/CMakeLists.txt +++ /dev/null @@ -1,67 +0,0 @@ -# This program is free software, you can redistribute it and/or modify it. -# Copyright (c) 2025 Huawei Technologies Co., Ltd. -# This file is a part of the CANN Open Software. -# Licensed under CANN Open Software License Agreement Version 2.0 (the "License"). -# Please refer to the License for details. You may not use this file except in compliance with the License. -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. -# See LICENSE in the root of the software repository for the full text of the License. -# ====================================================================================================================== -#add_definitions(-DMAX_BLOCK_NUM_=3) -# add_ops_compile_options( -# OP_NAME StoreKVBlock -# OPTIONS --cce-auto-sync=on -# -Wno-deprecated-declarations -# -Werror -# ) - -# # -o0 -# # -g -# # --cce-ignore-always-inline=true -# target_sources(op_host_aclnn PRIVATE -# store_kv_block_def.cpp -# ) - -# target_sources(optiling PRIVATE -# store_kv_block_tiling.cpp -# store_kv_block_common.cpp -# ) - -# if (NOT BUILD_OPEN_PROJECT) -# target_sources(opmaster_ct PRIVATE -# store_kv_block_tiling.cpp -# ) -# endif () - -# target_include_directories(optiling PRIVATE -# ${CMAKE_CURRENT_SOURCE_DIR} -# ) - -# target_sources(opsproto PRIVATE -# store_kv_block_infershape.cpp -# ) - -# target_link_libraries(optiling -# PRIVATE -# ) -add_op_to_compiled_list() - -if (BUILD_OPEN_PROJECT) - target_sources(op_host_aclnn PRIVATE - store_kv_block_def.cpp - ) -endif() - -add_ops_compile_options( - OP_NAME StoreKVBlock - OPTIONS - --cce-auto-sync=off - -Wno-deprecated-declarations - -Werror -) - -if (NOT BUILD_OPS_RTY_KERNEL) - add_modules_sources(OPTYPE store_kv_block ACLNNTYPE aclnn) - target_include_directories(${OPHOST_NAME}_tiling_obj PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ) -endif() diff --git a/csrc/attention/store_kv_block/op_host/store_kv_block_def.cpp b/csrc/attention/store_kv_block/op_host/store_kv_block_def.cpp deleted file mode 100644 index 1b81e0971..000000000 --- a/csrc/attention/store_kv_block/op_host/store_kv_block_def.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/*! - * \file store_kv_block_def.cpp - * \brief Operator definition for StoreKVBlock - */ -#include "register/op_def_registry.h" - -namespace ops { -class StoreKVBlock : public OpDef { - public: - explicit StoreKVBlock(const char* name) : OpDef(name) { - this->Input("keyIn") - .ParamType(REQUIRED) - .DataType({ge::DT_INT8, ge::DT_FLOAT16, ge::DT_BF16}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .AutoContiguous(); - this->Input("keyCacheIn") - .ParamType(REQUIRED) - .DataType({ge::DT_INT8, ge::DT_FLOAT16, ge::DT_BF16}) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .AutoContiguous(); - this->Input("groupLen") - .ParamType(REQUIRED) - .DataType({ge::DT_INT32 , ge::DT_INT32 , ge::DT_INT32 }) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .AutoContiguous(); - this->Input("groupKeyIdx") - .ParamType(REQUIRED) - .DataType({ge::DT_INT32 , ge::DT_INT32 , ge::DT_INT32 }) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .AutoContiguous(); - this->Input("groupKeyCacheIdx") - .ParamType(REQUIRED) - .DataType({ge::DT_INT32 , ge::DT_INT32 , ge::DT_INT32 }) - .Format({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .UnknownShapeFormat({ge::FORMAT_ND, ge::FORMAT_ND, ge::FORMAT_ND}) - .AutoContiguous(); - this->Attr("blockSize").Int(); - this->AICore().AddConfig("ascend910b"); - this->AICore().AddConfig("ascend910_93"); - } -}; - -OP_ADD(StoreKVBlock); -} // namespace ops \ No newline at end of file diff --git a/csrc/attention/store_kv_block/op_host/store_kv_block_infershape.cpp b/csrc/attention/store_kv_block/op_host/store_kv_block_infershape.cpp deleted file mode 100644 index 097935304..000000000 --- a/csrc/attention/store_kv_block/op_host/store_kv_block_infershape.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/** - * This program is free software, you can redistribute it and/or modify. - * Copyright (c) 2025 Huawei Technologies Co., Ltd. - * This file is a part of the CANN Open Software. - * Licensed under CANN Open Software License Agreement Version 2.0 (the "License"). - * Please refer to the License for details. You may not use this file except in compliance with the License. - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. - * See LICENSE in the root of the software repository for the full text of the License. - */ - -/*! - * \file store_kv_block_infershape.cpp - * \brief InferShape implementation for StoreKVBlock - */ -#include -#include - -#include "error/ops_error.h" - -static constexpr int IDX_0 = 0; -static constexpr int IDX_1 = 1; -static constexpr int IDX_2 = 2; - -using namespace ge; -// using namespace Ops::Base; - -namespace ops { - -static ge::graphStatus InferShape4StoreKVBlock(gert::InferShapeContext* context) -{ - return GRAPH_SUCCESS; -} - -static graphStatus InferDataType4StoreKVBlock(gert::InferDataTypeContext* context) -{ - return GRAPH_SUCCESS; -} - -IMPL_OP_INFERSHAPE(StoreKVBlock).InferShape(InferShape4StoreKVBlock).InferDataType(InferDataType4StoreKVBlock); -} // namespace ops diff --git a/csrc/attention/store_kv_block/op_host/store_kv_block_tiling.cpp b/csrc/attention/store_kv_block/op_host/store_kv_block_tiling.cpp deleted file mode 100644 index b76ec2c1b..000000000 --- a/csrc/attention/store_kv_block/op_host/store_kv_block_tiling.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "store_kv_block_tiling.h" -#include "register/op_def_registry.h" -#include "tiling/platform/platform_ascendc.h" -#include "tiling_base/error_log.h" - -namespace optiling { - -constexpr uint32_t DIM_0 = 0; -constexpr uint32_t DIM_1 = 1; -constexpr uint32_t DIM_2 = 2; -constexpr int32_t MAX_UB_USE_SIZE = 180 * 1024; - -struct StoreKVBlockParams { - uint32_t numTokens{0}; - uint32_t numCache{0}; - uint32_t numHeads{1}; - uint32_t headSize[5]{1, 1, 1, 1, 1}; - uint32_t blockTableSize{0}; - uint32_t typeByte{0}; - uint32_t tokenSize{1}; - uint32_t tilingKey{0}; - uint64_t workspaceSize{0}; - uint64_t groupInfoLen{0}; - uint32_t corepernum{0}; - uint32_t coretail{0}; - uint64_t sysWorkspaceSize{0}; - uint32_t coreNum{0}; -}; - -static ge::graphStatus DoCommonTiling(gert::TilingContext* context, StoreKVBlockParams& params) { - auto kShape = context->GetInputShape(DIM_0); - auto kDimNum = kShape->GetStorageShape().GetDimNum(); - if (kDimNum < 2 || kDimNum > 7) { - OP_LOGE(context->GetNodeName(), "StoreKVBlock Input kDimNum dim < 2 || kDimNum>7"); - return ge::GRAPH_FAILED; - } - - for (int i = 0; i < kDimNum; i++) { - if (i == 0) params.numTokens = static_cast(kShape->GetStorageShape().GetDim(i)); - else if (i == 1) params.numHeads = static_cast(kShape->GetStorageShape().GetDim(i)); - else if (static_cast(kShape->GetStorageShape().GetDim(i)) != 0) - params.headSize[i - 2] = static_cast(kShape->GetStorageShape().GetDim(i)); - } - - auto kCacheShape = context->GetInputShape(DIM_1); - auto kCacheDimNum = kCacheShape->GetStorageShape().GetDimNum(); - if (kCacheDimNum < 2 || kCacheDimNum > 7) { - OP_LOGE(context->GetNodeName(), "StoreKVBlock Input kCacheDimNum < 2"); - return ge::GRAPH_FAILED; - } - params.numCache = kCacheShape->GetStorageShape().GetDim(0) * kCacheShape->GetStorageShape().GetDim(1); - - const int64_t* blockSizePtr = context->GetAttrs()->GetInt(0); - uint32_t blockSize = static_cast(*blockSizePtr); - params.tokenSize = params.numHeads * params.headSize[0] * params.headSize[1] * params.headSize[2] * params.headSize[3] * params.headSize[4]; - params.blockTableSize = blockSize; - - uint32_t typeByte = 0; - auto xDataType = context->GetInputDesc(DIM_0)->GetDataType(); - if (xDataType == ge::DataType::DT_INT8) { - typeByte = sizeof(int8_t); - params.tilingKey = 1; - } else if (xDataType == ge::DataType::DT_FLOAT16 || xDataType == ge::DataType::DT_BF16) { - typeByte = sizeof(uint16_t); - params.tilingKey = 2; - } else if (xDataType == ge::DataType::DT_INT32 || xDataType == ge::DataType::DT_UINT32) { - typeByte = sizeof(uint32_t); - params.tilingKey = 4; - } else { - OP_LOGE(context->GetNodeName(), "Unsupported type."); - return ge::GRAPH_FAILED; - } - - params.typeByte = typeByte; - - auto groupInfoShape = context->GetInputShape(DIM_2); - params.groupInfoLen = static_cast(groupInfoShape->GetStorageShape().GetDim(0)); - params.corepernum = params.groupInfoLen / params.coreNum; - params.coretail = params.groupInfoLen % params.coreNum; - - uint32_t pageBlockEleSize = params.blockTableSize * params.tokenSize; - if (pageBlockEleSize > MAX_UB_USE_SIZE) { - OP_LOGE(context->GetNodeName(), "pageBlockEleSize > MaxUBSize"); - return ge::GRAPH_FAILED; - } - return ge::GRAPH_SUCCESS; -} - -static ge::graphStatus StoreKVBlockTilingFunc(gert::TilingContext* context) { - StoreKVBlockParams params; - - auto platformInfo = context->GetPlatformInfo(); - OP_CHECK_NULL_WITH_CONTEXT(context, platformInfo); - auto ascendcPlatform = platform_ascendc::PlatformAscendC(platformInfo); - params.coreNum = ascendcPlatform.GetCoreNum(); - if (params.coreNum == 0) { - OP_LOGE(context->GetNodeName(), "Failed to get core num."); - return ge::GRAPH_FAILED; - } - params.sysWorkspaceSize = ascendcPlatform.GetLibApiWorkSpaceSize(); - - auto ret = DoCommonTiling(context, params); - if (ret != ge::GRAPH_SUCCESS) { - return ret; - } - - StoreKVBlockTilingData tilingData; - if (params.blockTableSize > 0) tilingData.set_blockTableSize(params.blockTableSize); - if (params.typeByte > 0) tilingData.set_typeByte(params.typeByte); - if (params.tokenSize > 0) tilingData.set_tokenSize(params.tokenSize); - if (params.corepernum > 0 || params.coretail != 0) tilingData.set_corePerNum(params.corepernum); - if (params.coretail < 48) tilingData.set_coreTail(params.coretail); - if (params.numTokens > 0) tilingData.set_numTokens(params.numTokens); - if (params.numCache > 0) tilingData.set_numCache(params.numCache); - if (params.groupInfoLen > 0) tilingData.set_groupInfoLen(params.groupInfoLen); - - size_t* workspaceSize = context->GetWorkspaceSizes(1); - *workspaceSize = params.workspaceSize + params.sysWorkspaceSize; - context->SetTilingKey(params.tilingKey); - if (params.coreNum > 0) context->SetBlockDim(params.coreNum); - - tilingData.SaveToBuffer(context->GetRawTilingData()->GetData(), context->GetRawTilingData()->GetCapacity()); - context->GetRawTilingData()->SetDataSize(tilingData.GetDataSize()); - - return ge::GRAPH_SUCCESS; -} - -static ge::graphStatus TilingParseForStoreKVBlock(gert::TilingParseContext* context) { - return ge::GRAPH_SUCCESS; -} - -IMPL_OP_OPTILING(StoreKVBlock) - .Tiling(StoreKVBlockTilingFunc) - .TilingParse(TilingParseForStoreKVBlock); - -} // namespace optiling diff --git a/csrc/attention/store_kv_block/op_host/store_kv_block_tiling.h b/csrc/attention/store_kv_block/op_host/store_kv_block_tiling.h deleted file mode 100644 index ff43c6055..000000000 --- a/csrc/attention/store_kv_block/op_host/store_kv_block_tiling.h +++ /dev/null @@ -1,23 +0,0 @@ -#include "register/tilingdata_base.h" - -namespace optiling { -BEGIN_TILING_DATA_DEF(StoreKVBlockTilingData) - TILING_DATA_FIELD_DEF(uint32_t, blockTableSize); - TILING_DATA_FIELD_DEF(uint32_t, typeByte); - TILING_DATA_FIELD_DEF(uint32_t, tokenSize); - TILING_DATA_FIELD_DEF(uint32_t, corePerNum); - TILING_DATA_FIELD_DEF(uint32_t, coreTail); - TILING_DATA_FIELD_DEF(uint32_t, numTokens); - TILING_DATA_FIELD_DEF(uint32_t, numCache); - TILING_DATA_FIELD_DEF(uint32_t, groupInfoLen); -END_TILING_DATA_DEF; - -REGISTER_TILING_DATA_CLASS(StoreKVBlock, StoreKVBlockTilingData) - -struct StoreKVBlockCompileInfo { - uint32_t coreNum; - uint64_t ubSizePlatForm; - uint32_t sysWorkspaceSize; -}; - -} // namespace optiling diff --git a/csrc/attention/store_kv_block/op_kernel/store_kv_block.cpp b/csrc/attention/store_kv_block/op_kernel/store_kv_block.cpp deleted file mode 100644 index cb6c89112..000000000 --- a/csrc/attention/store_kv_block/op_kernel/store_kv_block.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/*! - * \file store_kv_block.cpp - * \brief Kernel entry for StoreKVBlock operator - */ -#include "store_kv_block.h" - -extern "C" __global__ __aicore__ void store_kv_block( - GM_ADDR keyIn, GM_ADDR keyCacheIn, GM_ADDR groupLen, GM_ADDR groupKeyIdx, GM_ADDR groupKeyCacheIdx, GM_ADDR workspace, GM_ADDR tiling) -{ - AscendC::TPipe pipe; - REGISTER_TILING_DEFAULT(StoreKVBlock::StoreKVBlockTilingData); - GET_TILING_DATA(tilingData, tiling); - - if (TILING_KEY_IS(1)) { - StoreKVBlock::StoreKVBlockBase op; - op.Init( &pipe, &tilingData); - op.Process(keyIn,keyCacheIn, groupLen, groupKeyIdx, groupKeyCacheIdx); - } else if (TILING_KEY_IS(2)) { - StoreKVBlock::StoreKVBlockBase op; - op.Init( &pipe, &tilingData); - op.Process(keyIn,keyCacheIn, groupLen, groupKeyIdx, groupKeyCacheIdx); - } else if (TILING_KEY_IS(4)) { - StoreKVBlock::StoreKVBlockBase op; - op.Init( &pipe, &tilingData); - op.Process(keyIn,keyCacheIn, groupLen, groupKeyIdx, groupKeyCacheIdx); - } -} diff --git a/csrc/attention/store_kv_block/op_kernel/store_kv_block.h b/csrc/attention/store_kv_block/op_kernel/store_kv_block.h deleted file mode 100644 index 69450226a..000000000 --- a/csrc/attention/store_kv_block/op_kernel/store_kv_block.h +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/*! - * \file store_kv_block.h - * \brief StoreKVBlock kernel operator - */ - -#ifndef ASCEND_STORE_KV_BLOCK_H -#define ASCEND_STORE_KV_BLOCK_H - -#include "kernel_operator.h" - -namespace StoreKVBlock { -using namespace AscendC; - - -#ifndef STORE_KV_BLOCK_TILING_DATA_H_ -#define STORE_KV_BLOCK_TILING_DATA_H_ -struct StoreKVBlockTilingData{ - uint32_t blockTableSize; - uint32_t typeByte; - uint32_t tokenSize; - uint32_t corePerNum; - uint32_t coreTail; - uint32_t numTokens; - uint32_t numCache; - uint32_t groupInfoLen; - -}; -#endif -template -class StoreKVBlockBase { -public: - - uint32_t tokenSize = 0; - uint32_t tokenByteSize = 0; - uint32_t blockTableSize = 0; - uint32_t typeByte = 0; - uint32_t numTokens = 0; - uint32_t numCache = 0; - uint32_t groupInfoLen = 0; - - uint32_t coreId = 0; - uint32_t coreTail = 0; - uint32_t corePerNum = 0; - uint32_t blockNum = 0; - AscendC::TPipe* pipeThis; - AscendC::LocalTensor tokenLocal; - AscendC::GlobalTensor keyInputGt; - AscendC::GlobalTensor keyCacheInputGt; - AscendC::GlobalTensor groupLenGt; - AscendC::GlobalTensor groupKeyIdxGt; - AscendC::GlobalTensor groupKeyCacheIdxGt; - AscendC::TBuf tokenBuf; - __aicore__ inline StoreKVBlockBase() {} - - __aicore__ inline uint32_t RoundUp(uint32_t x, uint32_t y = 16) - { - return y == 0 ? 0 : (x + y - 1) / y * y; - } - - __aicore__ inline void Init( AscendC::TPipe *pipe, StoreKVBlockTilingData *tilingData) - { - pipeThis = pipe; - typeByte = tilingData->typeByte; - tokenSize = tilingData->tokenSize; - tokenByteSize = tokenSize*typeByte; - blockTableSize = tilingData->blockTableSize; - numTokens = tilingData->numTokens; - numCache = tilingData->numCache; - groupInfoLen = tilingData->groupInfoLen; - - - coreId = AscendC::GetBlockIdx(); - coreTail = tilingData->coreTail; - blockNum = AscendC::GetBlockNum(); - if (coreId < coreTail){ - // Not all cores have corePerNum+1 items; only coreTail cores get one extra. - // If corePerNum is 0, cores beyond coreTail have no work and will not access any address. - corePerNum = tilingData->corePerNum+1; - }else { - corePerNum = tilingData->corePerNum; - } - } - __aicore__ inline void Process(GM_ADDR keyIn, GM_ADDR keyCacheIn, GM_ADDR groupLen, GM_ADDR groupKeyIdx, GM_ADDR groupKeyCacheIdx) - { - - keyInputGt.SetGlobalBuffer(reinterpret_cast<__gm__ T*>(keyIn)); - keyCacheInputGt.SetGlobalBuffer(reinterpret_cast<__gm__ T*>(keyCacheIn)); - groupLenGt.SetGlobalBuffer(reinterpret_cast<__gm__ uint32_t*>(groupLen)); - groupKeyIdxGt.SetGlobalBuffer(reinterpret_cast<__gm__ uint32_t*>(groupKeyIdx)); - groupKeyCacheIdxGt.SetGlobalBuffer(reinterpret_cast<__gm__ uint32_t*>(groupKeyCacheIdx)); - - pipeThis->InitBuffer(tokenBuf, blockTableSize*tokenByteSize); - tokenLocal = tokenBuf.Get(); - - AscendC::DataCopyExtParams copyParams{1, 0, 0, 0, 0}; // todo: full block length - AscendC::DataCopyPadExtParams padParams{false, 0, 0, 0}; - for (uint32_t i = 0; i < corePerNum; i++) { - uint32_t idx = (coreId+i*blockNum); - - // if( groupLenGt.GetValue(idx)<= 0 || groupKeyIdxGt.GetValue(idx)<0 || groupKeyCacheIdxGt.GetValue(idx)<0){ - // continue; - // } - - copyParams.blockLen = groupLenGt.GetValue(idx)*tokenByteSize; // in bytes - DataCopyPad(tokenLocal, keyInputGt[ groupKeyIdxGt.GetValue(idx)*tokenSize], copyParams, padParams); // note: offset order - AscendC::SetFlag(EVENT_ID1); - AscendC::WaitFlag(EVENT_ID1); - DataCopyPad(keyCacheInputGt[groupKeyCacheIdxGt.GetValue(idx)*tokenSize], tokenLocal, copyParams); - AscendC::SetFlag(EVENT_ID1); - AscendC::WaitFlag(EVENT_ID1); - } - - } - -}; -} - -#endif diff --git a/csrc/attention/store_kv_block/store_kv_block_torch_adpt.h b/csrc/attention/store_kv_block/store_kv_block_torch_adpt.h deleted file mode 100644 index 3dcedc661..000000000 --- a/csrc/attention/store_kv_block/store_kv_block_torch_adpt.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// #include "../aclnn_torch_adapter/op_api_common.h" - -#ifndef STORE_KV_BLOCK_TORCH_ADPT_H -#define STORE_KV_BLOCK_TORCH_ADPT_H -#include -namespace vllm_ascend { - -std::tuple store_kv_block_pre( - const at::Tensor &slot_mapping_npu, - at::IntArrayRef slot_mapping_list, - int64_t block_size) -{ - - int64_t slot_mapping_len = slot_mapping_list.size(); - - std::vector length(16, 0); - std::vector key_idx(16, 0); - std::vector key_cache_idx(16, 0); - int32_t idx_slotmap = 0; - int32_t idx_groups = 0; - - while (idx_slotmap < slot_mapping_len) { - - int32_t current_idx = slot_mapping_list[idx_slotmap]; - if(current_idx <0){ - idx_slotmap++; - continue; - } - - int32_t block_id = current_idx / block_size; - int32_t y= current_idx % block_size; - - key_idx[idx_groups] = idx_slotmap; - key_cache_idx[idx_groups] = current_idx; - - int32_t j = idx_slotmap; - - if(j+1 < slot_mapping_len &&slot_mapping_list[j+1]!=slot_mapping_list[j]+1 ) { - j++; - - }else{ - int32_t idx_stride = std::min(block_size-y,slot_mapping_len-idx_slotmap)-1; - int32_t expected_last = current_idx + idx_stride; - int32_t expected_last_idx = idx_slotmap + (expected_last-current_idx); - - if (expected_last == slot_mapping_list[expected_last_idx]){ - j = expected_last_idx+1; - }else{ - - while(j+1 < slot_mapping_len && slot_mapping_list[j] / block_size == block_id && slot_mapping_list[j+1] ==slot_mapping_list[j]+1) { - j++; - } - } - } - - length[idx_groups] = (j - idx_slotmap); - idx_slotmap = j; - idx_groups++; - - if(idx_groups>=length.capacity()){ - int32_t new_capacity = length.capacity() * 2; - length.reserve(new_capacity); - key_idx.reserve(new_capacity); - key_cache_idx.reserve(new_capacity); - - for (int32_t k = idx_groups; k < new_capacity; ++k){ - length.emplace_back(0); - key_idx.emplace_back(0); - key_cache_idx.emplace_back(0); - } - } - } - - at::Tensor group_len = at::empty({idx_groups}, - at::TensorOptions(slot_mapping_npu.options().device()).dtype(torch::kInt32) - ); - void* group_len_addr = group_len.data_ptr(); - - at::Tensor group_key_idx = at::empty({idx_groups}, - at::TensorOptions(slot_mapping_npu.options().device()).dtype(torch::kInt32) - ); - void* group_key_idx_addr = group_key_idx.data_ptr(); - - at::Tensor group_key_cache_idx = at::empty({idx_groups}, - at::TensorOptions(slot_mapping_npu.options().device()).dtype(torch::kInt32) - ); - void* group_key_cache_idx_addr = group_key_cache_idx.data_ptr(); - - uint32_t device_size=idx_groups*sizeof(length[0]); - aclrtStream stream = c10_npu::getCurrentNPUStream().stream(); - aclrtMemcpyKind memcpy_type=ACL_MEMCPY_HOST_TO_DEVICE; - aclrtMemcpyAsync(group_len_addr, device_size, &length[0], device_size, ACL_MEMCPY_HOST_TO_DEVICE, stream); - aclrtMemcpyAsync(group_key_idx_addr, device_size, &key_idx[0], device_size, ACL_MEMCPY_HOST_TO_DEVICE, stream); - aclrtMemcpyAsync(group_key_cache_idx_addr, device_size, &key_cache_idx[0], device_size, ACL_MEMCPY_HOST_TO_DEVICE, stream); - - return std::tuple(group_len, group_key_idx, group_key_cache_idx); - -} - -void store_kv_block( - const at::Tensor &key_in, - const at::Tensor &key_cache_in, - const at::Tensor &group_len, - const at::Tensor &group_key_idx, - const at::Tensor &group_key_cache_idx, - int64_t block_size) -{ - - EXEC_NPU_CMD(aclnnStoreKVBlock, key_in, key_cache_in,group_len, group_key_idx, group_key_cache_idx, block_size); - -} - -} -#endif \ No newline at end of file diff --git a/csrc/build.sh b/csrc/build.sh index 4f20cbd98..a643fd374 100755 --- a/csrc/build.sh +++ b/csrc/build.sh @@ -32,8 +32,6 @@ VERBOSE="false" OOM="false" THREAD_NUM=$(grep -c ^processor /proc/cpuinfo) MAX_JOBS=${MAX_JOBS:-} -USE_NINJA=${USE_NINJA:-${VLLM_ASCEND_USE_NINJA:-auto}} -CMAKE_GENERATOR_ARGS=() ENABLE_VALGRIND=FALSE ENABLE_CREATE_LIB=FALSE ENABLE_OPKERNEL=FALSE @@ -342,6 +340,7 @@ function set_env() exit 1 fi } + function clean() { if [ -n "${BUILD_DIR}" ];then @@ -356,17 +355,6 @@ function clean() mkdir -p ${BUILD_DIR} ${OUTPUT_DIR} } -function clean_output() -{ - - if [ -z "${TEST}" ] && [ -z "${EXAMPLE}" ];then - if [ -n "${OUTPUT_DIR}" ];then - rm -rf ${OUTPUT_DIR} - fi - fi - - mkdir -p ${BUILD_DIR} ${OUTPUT_DIR} -} function clean_build_out() { @@ -389,8 +377,8 @@ function clean_third_party() function cmake_config() { local extra_option="$1" - log "Info: cmake config generator=${CMAKE_GENERATOR_ARGS[*]:-} ${CUSTOM_OPTION} ${extra_option} ." - cmake "${CMAKE_GENERATOR_ARGS[@]}" .. ${CUSTOM_OPTION} ${extra_option} + log "Info: cmake config ${CUSTOM_OPTION} ${extra_option} ." + cmake .. ${CUSTOM_OPTION} ${extra_option} } function build() @@ -919,14 +907,6 @@ while [[ $# -gt 0 ]]; do CCACHE_PROGRAM="$2" shift 2 ;; - --ninja) - USE_NINJA="true" - shift - ;; - --no-ninja) - USE_NINJA="false" - shift - ;; -p|--package-path) ascend_package_path="$2" shift 2 @@ -1403,21 +1383,7 @@ CUSTOM_OPTION="${CUSTOM_OPTION} -DCUSTOM_ASCEND_CANN_PACKAGE_PATH=${ASCEND_CANN_ set_env -use_ninja_lower=$(echo "${USE_NINJA:-auto}" | tr '[:upper:]' '[:lower:]') -if [[ "${use_ninja_lower}" == "0" || "${use_ninja_lower}" == "false" || "${use_ninja_lower}" == "off" || "${use_ninja_lower}" == "no" ]]; then - log "Info: use default CMake generator because USE_NINJA=${USE_NINJA}" -elif command -v ninja >/dev/null 2>&1; then - CMAKE_GENERATOR_ARGS=(-G Ninja) - log "Info: use CMake generator Ninja" -elif [[ "${use_ninja_lower}" == "1" || "${use_ninja_lower}" == "true" || "${use_ninja_lower}" == "on" || "${use_ninja_lower}" == "yes" ]]; then - log "Error: USE_NINJA=${USE_NINJA}, but ninja is not found." - exit 1 -else - log "Info: ninja is not found; use default CMake generator" -fi - -clean_build_out -clean_output +clean if [ -n "${CCACHE_PROGRAM}" ]; then if [ "${CCACHE_PROGRAM}" == "false" ] || [ "${CCACHE_PROGRAM}" == "off" ]; then diff --git a/csrc/build_aclnn.sh b/csrc/build_aclnn.sh index c6b3761b5..206204727 100755 --- a/csrc/build_aclnn.sh +++ b/csrc/build_aclnn.sh @@ -2,7 +2,6 @@ ROOT_DIR=$1 SOC_VERSION=$2 -: "${ROOT_DIR:?ROOT_DIR is not set}" log() { echo "[build_aclnn] $*" @@ -80,7 +79,7 @@ elif [[ "$SOC_VERSION" =~ ^ascend910b ]]; then cd - || exit 1 fi ABSOLUTE_CATLASS_PATH=$(cd "${CATLASS_PATH}" && pwd) - export CPATH="${ABSOLUTE_CATLASS_PATH}${CPATH:+:${CPATH}}" + export CPATH=${ABSOLUTE_CATLASS_PATH}:${CPATH} log "catlass include=${ABSOLUTE_CATLASS_PATH}" CUSTOM_OPS_ARRAY=( @@ -120,7 +119,6 @@ elif [[ "$SOC_VERSION" =~ ^ascend910b ]]; then "ngram_spec_decode" "chunk_fwd_o" "chunk_gated_delta_rule_fwd_h" - "store_kv_block" ) CUSTOM_OPS=$(IFS=';'; echo "${CUSTOM_OPS_ARRAY[*]}") @@ -184,7 +182,6 @@ elif [[ "$SOC_VERSION" =~ ^ascend910_93 ]]; then "ngram_spec_decode" "chunk_fwd_o" "chunk_gated_delta_rule_fwd_h" - "store_kv_block" ) CUSTOM_OPS=$(IFS=';'; echo "${CUSTOM_OPS_ARRAY[*]}") SOC_ARG="ascend910_93" @@ -208,7 +205,7 @@ elif [[ "$SOC_VERSION" =~ ^ascend950 ]]; then cd - || exit 1 fi ABSOLUTE_CATLASS_PATH=$(cd "${CATLASS_PATH}" && pwd) - export CPATH="${ABSOLUTE_CATLASS_PATH}${CPATH:+:${CPATH}}" + export CPATH=${ABSOLUTE_CATLASS_PATH}:${CPATH} log "catlass include=${ABSOLUTE_CATLASS_PATH}" CUSTOM_OPS_ARRAY=( @@ -259,14 +256,13 @@ log_selected_ops ( set -euo pipefail - : "${ROOT_DIR:?ROOT_DIR is not set}" - log "subshell cwd before cd=$(pwd)" - cd "${ROOT_DIR}/csrc" + cd csrc log "subshell cwd after cd=$(pwd)" - log "preserving csrc/build and cleaning output dirs" - rm -rf -- output build_out + log "cleaning csrc build dirs" + rm -rf -- build output build_out + : "${ROOT_DIR:?ROOT_DIR is not set}" : "${CUSTOM_OPS:?CUSTOM_OPS is not set}" : "${SOC_VERSION:?SOC_VERSION is not set}" : "${SOC_ARG:?SOC_ARG is not set}" @@ -308,20 +304,4 @@ log_selected_ops log "installer finished" log "installed files under ${custom_ops_install_dir} (maxdepth=4, first 120 entries):" { find "${custom_ops_install_dir}" -mindepth 1 -maxdepth 4 -print | sort | head -n 120 | sed 's#^#[build_aclnn] install: #'; } || true - - # install batch_invariant run package and whl package - if [[ "${VLLM_BATCH_INVARIANT:-0}" == "1" ]]; then - log "VLLM_BATCH_INVARIANT=1, installing batch_invariant run package and whl package..." - - # call separate installation script - batch_invariant_script="${ROOT_DIR}/csrc/build_batch_invariant_ops.sh" - if [[ -f "${batch_invariant_script}" ]]; then - log "Calling batch_invariant_ops build script: ${batch_invariant_script}" - bash "${batch_invariant_script}" "${SOC_ARG}" - else - log "Warning: batch_invariant_ops build script not found at ${batch_invariant_script}" - fi - else - log "VLLM_BATCH_INVARIANT is not set to 1, skipping batch_invariant ops build" - fi ) diff --git a/csrc/build_batch_invariant_ops.sh b/csrc/build_batch_invariant_ops.sh deleted file mode 100644 index 988bb0a0f..000000000 --- a/csrc/build_batch_invariant_ops.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -# arguments: -# $1: SOC_ARG (ascend910b, ascend910_93, ascend950) - -SOC_ARG="${1:-}" - -log() { - echo "[install_batch_invariant] $*" -} - -# validate arguments -if [[ -z "${SOC_ARG}" ]]; then - log "ERROR: SOC_ARG is required as first argument" - exit 1 -fi - -log "Starting batch_invariant installation..." -log "SOC_ARG=${SOC_ARG}" - -# determine device type from SOC_ARG -case "${SOC_ARG}" in - ascend910b) - BATCH_INVARIANT_DEVICE="910b" - ;; - ascend910_93) - BATCH_INVARIANT_DEVICE="A3" - ;; - *) - log "Warning: batch_invariant not available for SOC_ARG=${SOC_ARG}; skipping" - exit 0 - ;; -esac - -# detect system architecture -ARCH_INFO=$(uname -m) -case "${ARCH_INFO}" in - aarch64) - ARCH_SUFFIX="aarch64" - ;; - x86_64) - ARCH_SUFFIX="x86_64" - ;; - *) - log "Warning: unknown architecture ${ARCH_INFO}; cannot determine batch_invariant package" - exit 0 - ;; -esac - -# download and install run package -BATCH_INVARIANT_RUN_URL="https://vllm-ascend.obs.cn-north-4.myhuaweicloud.com/vllm-ascend/cann-ops-batch_invariant-${BATCH_INVARIANT_DEVICE}-1.0.0-linux.${ARCH_SUFFIX}.run" -BATCH_INVARIANT_RUN_FILE="cann-ops-batch_invariant-${BATCH_INVARIANT_DEVICE}-1.0.0-linux.${ARCH_SUFFIX}.run" - -log "Downloading batch_invariant run package..." -unset ASCEND_CUSTOM_OPP_PATH -if curl --max-time 60 -sS -k -O "${BATCH_INVARIANT_RUN_URL}" && [[ -f "${BATCH_INVARIANT_RUN_FILE}" ]]; then - chmod +x "${BATCH_INVARIANT_RUN_FILE}" - log "Running installer: ${BATCH_INVARIANT_RUN_FILE}" - if "./${BATCH_INVARIANT_RUN_FILE}"; then - log "batch_invariant run package installed successfully" - else - log "Failed to install batch_invariant run package" - fi -else - log "Failed to download batch_invariant run package: ${BATCH_INVARIANT_RUN_URL}" -fi -# clean up downloaded run file (always clean, regardless of success/failure) -rm -f "${BATCH_INVARIANT_RUN_FILE}" - -# download and install whl package -BATCH_INVARIANT_WHL_URL="https://vllm-ascend.obs.cn-north-4.myhuaweicloud.com/vllm-ascend/batch_invariant-torch_ops_extension-1.0.0.zip" -BATCH_INVARIANT_WHL_FILE="batch_invariant-torch_ops_extension-1.0.0.zip" - -log "Downloading batch_invariant whl package..." -if curl --max-time 3 -sS -k -O "${BATCH_INVARIANT_WHL_URL}" >/dev/null 2>&1 && [[ -f "${BATCH_INVARIANT_WHL_FILE}" ]]; then - if unzip -o "${BATCH_INVARIANT_WHL_FILE}" >/dev/null 2>&1; then - if [[ -d "torch_ops_extension/batch_invariant_ops" ]]; then - cd torch_ops_extension/batch_invariant_ops - log "Building and installing batch_invariant whl package..." - if bash build_and_install.sh; then - log "batch_invariant whl package installed successfully" - else - log "Failed to build and install batch_invariant whl package" - fi - cd - - else - log "batch_invariant_ops directory not found in zip" - fi - else - log "Failed to unzip batch_invariant whl package" - fi -else - log "Failed to download batch_invariant whl package: ${BATCH_INVARIANT_WHL_URL}" -fi -# clean up downloaded files (always clean, regardless of success/failure) -rm -rf "${BATCH_INVARIANT_WHL_FILE}" torch_ops_extension - -log "batch_invariant_ops build completed" diff --git a/csrc/cmake/custom_build.cmake b/csrc/cmake/custom_build.cmake index e0bdb391f..39ea93e6b 100644 --- a/csrc/cmake/custom_build.cmake +++ b/csrc/cmake/custom_build.cmake @@ -870,7 +870,7 @@ if (NOT ENABLE_BUILT_IN AND BUILD_OPEN_PROJECT) set(CPACK_PACKAGE_DIRECTORY ${CMAKE_BINARY_DIR}) set(CPACK_PACKAGE_FILE_NAME "cann-ops-transformer-${VENDOR_NAME}_linux-${ARCH}.run") set(CPACK_GENERATOR External) - set(CPACK_CMAKE_GENERATOR "${CMAKE_GENERATOR}") + set(CPACK_CMAKE_GENERATOR "Unix Makefiles") set(CPACK_EXTERNAL_ENABLE_STAGING TRUE) if (ENABLE_BUILD_PKG) if (EXISTS ${ASCEND_CMAKE_DIR}/makeself.cmake) diff --git a/csrc/cmake/third_party/ascend_protobuf.cmake b/csrc/cmake/third_party/ascend_protobuf.cmake index 374223592..130106b92 100644 --- a/csrc/cmake/third_party/ascend_protobuf.cmake +++ b/csrc/cmake/third_party/ascend_protobuf.cmake @@ -1,89 +1,89 @@ -# ---------------------------------------------------------------------------------------------------------- -# Copyright (c) 2025 Huawei Technologies Co., Ltd. -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of -# CANN Open Software License Agreement Version 2.0 (the "License"). -# Please refer to the License for details. You may not use this file except in compliance with the License. -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, -# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. -# See LICENSE in the root of the software repository for the full text of the License. -# ---------------------------------------------------------------------------------------------------------- -include(ExternalProject) -set(PROTOBUF_VERSION_PKG protobuf-25.1.tar.gz) -set(ASCEND_PROTOBUF_DIR ${CANN_3RD_LIB_PATH}/ascend_protobuf) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ascend_protobuf_build_transformer - FOUND_VAR - ascend_protobuf_build_transformer_FOUND - REQUIRED_VARS - ASCEND_PROTOBUF_SHARED_INCLUDE -) - -set(ASCEND_PROTOBUF_SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/ascend_protobuf) -if(ascend_protobuf_build_transformer_FOUND AND NOT FORCE_REBUILD_CANN_3RD) - message(STATUS "[ThirdPartyLib][ascend protobuf] ascend_protobuf_shared found, skip compile.") - cmake_print_variables(ASCEND_PROTOBUF_SHARED_INCLUDE) - cmake_print_variables(ASCEND_PROTOC) - set(Protobuf_INCLUDE ${ASCEND_PROTOBUF_SHARED_INCLUDE}) - set(Protobuf_PATH ${ASCEND_PROTOC}) - set(Protobuf_PROTOC_EXECUTABLE ${Protobuf_PATH}/protoc) - add_library(ascend_protobuf_build_transformer INTERFACE) -else() - message(STATUS "[ThirdPartyLib][ascend protobuf] ascend protobuf shared not found, finding binary file.") - if(EXISTS "${CANN_3RD_LIB_PATH}/protobuf/protobuf-all-25.1.tar.gz") - set(REQ_URL "file://${CANN_3RD_LIB_PATH}/protobuf/protobuf-all-25.1.tar.gz") - message(STATUS "[ThirdPartyLib][ascend protobuf] found in ${REQ_URL}.") - elseif(EXISTS "${CANN_3RD_LIB_PATH}/pkg/${PROTOBUF_VERSION_PKG}") - set(REQ_URL "file://${CANN_3RD_LIB_PATH}/pkg/${PROTOBUF_VERSION_PKG}") - message(STATUS "[ThirdPartyLib][ascend protobuf] found in ${REQ_URL}.") - else() - set(REQ_URL "https://gitcode.com/cann-src-third-party/protobuf/releases/download/v25.1/protobuf-25.1.tar.gz") - message(STATUS "[ThirdPartyLib][ascend protobuf] ${REQ_URL} not found, need download.") - endif() - - set(protobuf_CXXFLAGS "-Wno-maybe-uninitialized -Wno-unused-parameter -fPIC -fstack-protector-all -D_FORTIFY_SOURCE=2 -D_GLIBCXX_USE_CXX11_ABI=0 -O2 -Dgoogle=ascend_private") - set(protobuf_LDFLAGS "-Wl,-z,relro,-z,now,-z,noexecstack") - - ExternalProject_Add(ascend_protobuf_build_transformer - URL ${REQ_URL} - DOWNLOAD_DIR ${CANN_3RD_LIB_PATH}/pkg - PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_LIST_DIR}/build/modules/patch/protobuf_25.1_change_version.patch - CONFIGURE_COMMAND ${CMAKE_COMMAND} - -DCMAKE_MESSAGE_LOG_LEVEL=ERROR - -DCMAKE_INSTALL_LIBDIR=lib - -Dprotobuf_WITH_ZLIB=OFF - -DLIB_PREFIX=ascend_ - -DCMAKE_SKIP_RPATH=TRUE - -Dprotobuf_BUILD_TESTS=OFF - -DBUILD_SHARED_LIBS=OFF - -DCMAKE_CXX_STANDARD=14 - -DCMAKE_CXX_FLAGS=${protobuf_CXXFLAGS} - -DCMAKE_CXX_LDFLAGS=${protobuf_LDFLAGS} - -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} - -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER} - -DCMAKE_INSTALL_PREFIX=${ASCEND_PROTOBUF_DIR} - -Dprotobuf_BUILD_PROTOC_BINARIES=ON - -Dprotobuf_ABSL_PROVIDER=module - -DABSL_ROOT_DIR=${ABSL_SOURCE_DIR} - - SOURCE_DIR ${ASCEND_PROTOBUF_SOURCE_DIR} - BUILD_COMMAND ${CMAKE_COMMAND} --build . - INSTALL_COMMAND "" - EXCLUDE_FROM_ALL TRUE - ) - if(TARGET abseil_build_transformer) - add_dependencies(ascend_protobuf_build_transformer abseil_build_transformer) - endif() - - ExternalProject_Get_Property(ascend_protobuf_build_transformer SOURCE_DIR) - ExternalProject_Get_Property(ascend_protobuf_build_transformer BINARY_DIR) - - set(Protobuf_INCLUDE ${SOURCE_DIR}/src) - set(Protobuf_PATH ${BINARY_DIR}) - set(Protobuf_PROTOC_EXECUTABLE ${Protobuf_PATH}/protoc) - - add_custom_command( - OUTPUT ${Protobuf_PROTOC_EXECUTABLE} - DEPENDS ascend_protobuf_build_transformer - ) +# ---------------------------------------------------------------------------------------------------------- +# Copyright (c) 2025 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. +# ---------------------------------------------------------------------------------------------------------- +include(ExternalProject) +set(PROTOBUF_VERSION_PKG protobuf-25.1.tar.gz) +set(ASCEND_PROTOBUF_DIR ${CANN_3RD_LIB_PATH}/ascend_protobuf) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ascend_protobuf_build_transformer + FOUND_VAR + ascend_protobuf_build_transformer_FOUND + REQUIRED_VARS + ASCEND_PROTOBUF_SHARED_INCLUDE +) + +set(ASCEND_PROTOBUF_SOURCE_DIR ${PROJECT_SOURCE_DIR}/third_party/ascend_protobuf) +if(ascend_protobuf_build_transformer_FOUND AND NOT FORCE_REBUILD_CANN_3RD) + message(STATUS "[ThirdPartyLib][ascend protobuf] ascend_protobuf_shared found, skip compile.") + cmake_print_variables(ASCEND_PROTOBUF_SHARED_INCLUDE) + cmake_print_variables(ASCEND_PROTOC) + set(Protobuf_INCLUDE ${ASCEND_PROTOBUF_SHARED_INCLUDE}) + set(Protobuf_PATH ${ASCEND_PROTOC}) + set(Protobuf_PROTOC_EXECUTABLE ${Protobuf_PATH}/protoc) + add_library(ascend_protobuf_build_transformer INTERFACE) +else() + message(STATUS "[ThirdPartyLib][ascend protobuf] ascend protobuf shared not found, finding binary file.") + if(EXISTS "${CANN_3RD_LIB_PATH}/protobuf/protobuf-all-25.1.tar.gz") + set(REQ_URL "file://${CANN_3RD_LIB_PATH}/protobuf/protobuf-all-25.1.tar.gz") + message(STATUS "[ThirdPartyLib][ascend protobuf] found in ${REQ_URL}.") + elseif(EXISTS "${CANN_3RD_LIB_PATH}/pkg/${PROTOBUF_VERSION_PKG}") + set(REQ_URL "file://${CANN_3RD_LIB_PATH}/pkg/${PROTOBUF_VERSION_PKG}") + message(STATUS "[ThirdPartyLib][ascend protobuf] found in ${REQ_URL}.") + else() + set(REQ_URL "https://gitcode.com/cann-src-third-party/protobuf/releases/download/v25.1/protobuf-25.1.tar.gz") + message(STATUS "[ThirdPartyLib][ascend protobuf] ${REQ_URL} not found, need download.") + endif() + + set(protobuf_CXXFLAGS "-Wno-maybe-uninitialized -Wno-unused-parameter -fPIC -fstack-protector-all -D_FORTIFY_SOURCE=2 -D_GLIBCXX_USE_CXX11_ABI=0 -O2 -Dgoogle=ascend_private") + set(protobuf_LDFLAGS "-Wl,-z,relro,-z,now,-z,noexecstack") + + ExternalProject_Add(ascend_protobuf_build_transformer + URL ${REQ_URL} + DOWNLOAD_DIR ${CANN_3RD_LIB_PATH}/pkg + PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_LIST_DIR}/build/modules/patch/protobuf_25.1_change_version.patch + CONFIGURE_COMMAND ${CMAKE_COMMAND} + -DCMAKE_MESSAGE_LOG_LEVEL=ERROR + -DCMAKE_INSTALL_LIBDIR=lib + -Dprotobuf_WITH_ZLIB=OFF + -DLIB_PREFIX=ascend_ + -DCMAKE_SKIP_RPATH=TRUE + -Dprotobuf_BUILD_TESTS=OFF + -DBUILD_SHARED_LIBS=OFF + -DCMAKE_CXX_STANDARD=14 + -DCMAKE_CXX_FLAGS=${protobuf_CXXFLAGS} + -DCMAKE_CXX_LDFLAGS=${protobuf_LDFLAGS} + -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} + -DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER} + -DCMAKE_INSTALL_PREFIX=${ASCEND_PROTOBUF_DIR} + -Dprotobuf_BUILD_PROTOC_BINARIES=ON + -Dprotobuf_ABSL_PROVIDER=module + -DABSL_ROOT_DIR=${ABSL_SOURCE_DIR} + + SOURCE_DIR ${ASCEND_PROTOBUF_SOURCE_DIR} + BUILD_COMMAND $(MAKE) + INSTALL_COMMAND "" + EXCLUDE_FROM_ALL TRUE + ) + if(TARGET abseil_build_transformer) + add_dependencies(ascend_protobuf_build_transformer abseil_build_transformer) + endif() + + ExternalProject_Get_Property(ascend_protobuf_build_transformer SOURCE_DIR) + ExternalProject_Get_Property(ascend_protobuf_build_transformer BINARY_DIR) + + set(Protobuf_INCLUDE ${SOURCE_DIR}/src) + set(Protobuf_PATH ${BINARY_DIR}) + set(Protobuf_PROTOC_EXECUTABLE ${Protobuf_PATH}/protoc) + + add_custom_command( + OUTPUT ${Protobuf_PROTOC_EXECUTABLE} + DEPENDS ascend_protobuf_build_transformer + ) endif() \ No newline at end of file diff --git a/csrc/moe/dequant_swiglu_quant/op_host/dequant_swiglu_quant_tiling.cpp b/csrc/moe/dequant_swiglu_quant/op_host/dequant_swiglu_quant_tiling.cpp index 1da6069c8..f3086e72e 100644 --- a/csrc/moe/dequant_swiglu_quant/op_host/dequant_swiglu_quant_tiling.cpp +++ b/csrc/moe/dequant_swiglu_quant/op_host/dequant_swiglu_quant_tiling.cpp @@ -521,7 +521,7 @@ ge::graphStatus DequantSwigluQuantDskTiling::GetShapeAttrsInfoInner() { // set the relevant param of group, hasGroupIndex_, groupNum_ and speGroupType_ auto shapeGroupIndex = context_->GetOptionalInputShape(INPUT_GROUP_INDEX); hasGroupIndex_ = shapeGroupIndex != nullptr; - groupNum_ = 1; + groupNum_ = 0; speGroupType_ = false; if (hasGroupIndex_) { const gert::Shape& inputShapeGroupIndex = shapeGroupIndex->GetStorageShape(); @@ -557,14 +557,7 @@ bool DequantSwigluQuantDskTiling::IsPerformanceAndGroupIndexBrach() { if (shapeGroupIndex != nullptr) { return true; } - - auto xPtr = context_->GetInputDesc(X_INDEX); - auto attrs = context_->GetAttrs(); - if (xPtr == nullptr || attrs == nullptr) { - return false; - } - auto* swigluMode = attrs->GetAttrPointer(SWIGLU_MODE_INDEX); - return xPtr->GetDataType() == ge::DT_INT32 && swigluMode != nullptr && *swigluMode == 1; + return false; } bool DequantSwigluQuantDskTiling::IsCapable() { diff --git a/csrc/torch_binding.cpp b/csrc/torch_binding.cpp index 627bc4e64..27f2451b5 100644 --- a/csrc/torch_binding.cpp +++ b/csrc/torch_binding.cpp @@ -49,7 +49,6 @@ #include "moe/causal_conv1d_v310/causal_conv1d_310_torch_adpt.h" #include "attention/recurrent_gated_delta_rule/recurrent_gated_delta_rule_torch_adpt.h" #include "attention/recurrent_gated_delta_rule_v310/recurrent_gated_delta_rule_310_torch_adpt.h" -#include "attention/store_kv_block/store_kv_block_torch_adpt.h" #include "attention/fused_gdn_gating/fused_gdn_gating_torch_adpt.h" #include #include @@ -1981,7 +1980,9 @@ std::tuple npu_dequant_swiglu_quant( const at::Tensor& bias_opt = c10::value_or_else(bias, [] { return at::Tensor(); }); const at::Tensor& quant_scale_opt = c10::value_or_else(quant_scale, [] { return at::Tensor(); }); const at::Tensor& quant_offset_opt = c10::value_or_else(quant_offset, [] { return at::Tensor(); }); - const at::Tensor& group_index_opt = c10::value_or_else(group_index, [] { return at::Tensor(); }); + const at::Tensor& group_index_value = c10::value_or_else(group_index, [&x] { + return at::empty({1}, x.options().dtype(c10::ScalarType::Long)).fill_(x.size(0)); + }); static const bool is_v2_available = GetOpApiFuncAddr("aclnnDequantSwigluQuantV2") != nullptr && @@ -1989,13 +1990,13 @@ std::tuple npu_dequant_swiglu_quant( if (swiglu_mode == 0 && !is_v2_available) { EXEC_NPU_CMD(aclnnDequantSwigluQuant, x, weight_scale_value, activation_scale_opt, bias_opt, quant_scale_opt, - quant_offset_opt, group_index_opt, activate_left, quant_mode_ptr, y, scale); + quant_offset_opt, group_index_value, activate_left, quant_mode_ptr, y, scale); } else { int64_t dst_type = 2; char* round_mode = const_cast("rint"); int64_t activate_dim = -1; EXEC_NPU_CMD(aclnnDequantSwigluQuantV2, x, weight_scale_value, activation_scale_opt, bias_opt, quant_scale_opt, - quant_offset_opt, group_index_opt, activate_left, quant_mode_ptr, dst_type, round_mode, + quant_offset_opt, group_index_value, activate_left, quant_mode_ptr, dst_type, round_mode, activate_dim, swiglu_mode, clamp_limit, glu_alpha, glu_bias, y, scale); } @@ -2797,25 +2798,13 @@ TORCH_LIBRARY_EXPAND(CONCAT(_C, _ascend), ops) ); ops.impl("chunk_fwd_o", torch::kPrivateUse1, &vllm_ascend::chunk_fwd_o); - //store_kv_block - ops.def( - "store_kv_block_pre(Tensor slot_mapping_npu, int[2] slot_mapping_list =[], int block_size=0)" - "-> (Tensor group_len ,Tensor group_key_idx, Tensor group_key_cache_idx)" - ); - ops.impl("store_kv_block_pre", torch::kPrivateUse1, &vllm_ascend::store_kv_block_pre); - - ops.def( - "store_kv_block(Tensor key_in, Tensor key_cache_in, Tensor group_len, Tensor group_key_idx,Tensor group_key_cache_idx, int block_size=0) -> ()" - ); - ops.impl("store_kv_block", torch::kPrivateUse1, &vllm_ascend::store_kv_block); // Fused GDN gating. ops.def( "npu_fused_gdn_gating(Tensor A_log, " " Tensor a, " " Tensor b, " " Tensor dt_bias, " - " float beta=1.0, " - " float threshold=20.0) -> (Tensor g, Tensor beta_output)"); + " float beta=1.0) -> (Tensor g, Tensor beta_output)"); ops.impl("npu_fused_gdn_gating", torch::kPrivateUse1, &vllm_ascend::npu_fused_gdn_gating); } #endif diff --git a/csrc/torch_binding_meta.cpp b/csrc/torch_binding_meta.cpp index faf18de49..18f033c56 100644 --- a/csrc/torch_binding_meta.cpp +++ b/csrc/torch_binding_meta.cpp @@ -703,11 +703,8 @@ std::tuple npu_fused_gdn_gating_meta( const at::Tensor& a, const at::Tensor& b, const at::Tensor& dt_bias, - double beta, - double threshold) + double beta) { - (void)beta; - (void)threshold; int64_t batch = a.size(0); int64_t num_heads = a.size(1); @@ -1604,31 +1601,6 @@ at::Tensor chunk_fwd_o_meta( return o; } -std::tuple store_kv_block_pre( - const at::Tensor &slot_mapping_npu, - at::IntArrayRef slot_mapping_list, - int64_t block_size) -{ - auto s_size = slot_mapping_npu.sizes(); - at::Tensor group_len = at::empty({s_size[0]}, slot_mapping_npu.options()); - at::Tensor group_key_idx = at::empty({s_size[0]}, slot_mapping_npu.options()); - at::Tensor group_key_cache_idx = at::empty({s_size[0]}, slot_mapping_npu.options()); - return std::tuple(group_len, group_key_idx, group_key_cache_idx); - -} - -void store_kv_block( - const at::Tensor &key_in, - const at::Tensor &key_cache_in, - const at::Tensor &group_len, - const at::Tensor &group_key_idx, - const at::Tensor &group_key_cache_idx, - int64_t block_size) -{ - return; - -} - } // namespace meta } // namespace vllm_ascend @@ -1737,9 +1709,6 @@ TORCH_LIBRARY_IMPL_EXPAND(CONCAT(_C, _ascend), Meta, ops) { ops.impl("chunk_gated_delta_rule_fwd_h", &vllm_ascend::meta::chunk_gated_delta_rule_fwd_h_meta); // chunk_fwd_o ops.impl("chunk_fwd_o", &vllm_ascend::meta::chunk_fwd_o_meta); - // store_kv_block - ops.impl("store_kv_block_pre", &vllm_ascend::meta::store_kv_block_pre); - ops.impl("store_kv_block", &vllm_ascend::meta::store_kv_block); // npu_fused_gdn_gating ops.impl("npu_fused_gdn_gating", &vllm_ascend::meta::npu_fused_gdn_gating_meta); } diff --git a/docs/source/_templates/Model-Deployment-Tutorial-Template.md b/docs/source/_templates/Model-Deployment-Tutorial-Template.md index 70db5edab..54f24b778 100644 --- a/docs/source/_templates/Model-Deployment-Tutorial-Template.md +++ b/docs/source/_templates/Model-Deployment-Tutorial-Template.md @@ -182,7 +182,7 @@ lm_eval \ --output_path ./ ``` -## 8 Performance Evaluation +## 8 Performance Omitted. Requirements are the same as for Accuracy Evaluation. @@ -210,10 +210,10 @@ Provide recommended configurations for three typical scenarios (long context, lo #### Table 2: Detailed Node Configuration -| Scenario | Configuration | NPUs | TP | DP | Max Num Seqs | Max Num Batched Tokens | Max Model Len | MTP Speculation Num | FUSED_MC2 | EP Switch | FC+CP Switch | Async Scheduling | +| Scenario | Configuration | #NPUs | TP | DP | BS | Concurrency | Max Context Length | MTP Speculation Num | FUSED_MC2 | EP Switch | FC+CP Switch | Async Scheduling | |----------|---------------|-------|----|----|----|-------------|--------------------|---------------------|-----------|-----------|--------------|------------------| -| High Throughput (32K→1K) | Server-P Node / Single Machine | 8 | 8 | 2 | 32 | 4096 | 30k | 3 | Off | On | On | On | -| High Throughput (32K→1K) | Server-D Node | 8 | 2 | 8 | 8 | 4096 | 30k | 12 | Off | On | Off | On | +| High Throughput (32K→1K) | Server-P Node / Single Machine | 8 | 8 | 2 | 32 | 64 | 30k | 3 | Off | On | On | On | +| High Throughput (32K→1K) | Server-D Node | 8 | 2 | 8 | 8 | 64 | 30k | 12 | Off | On | Off | On | | Long Context | Server-P Node / Single Machine | | | | | | | | | | | | | Long Context | Server-D Node | | | | | | | | | | | | | Low Latency | Server-P Node / Single Machine | | | | | | | | | | | | diff --git a/docs/source/_templates/Model-Deployment-Tutorial-Template.zh.md b/docs/source/_templates/Model-Deployment-Tutorial-Template.zh.md index e28d7f202..e5b12aba3 100644 --- a/docs/source/_templates/Model-Deployment-Tutorial-Template.zh.md +++ b/docs/source/_templates/Model-Deployment-Tutorial-Template.zh.md @@ -182,7 +182,7 @@ lm_eval \ --output_path ./ ``` -## 8 性能评估 +## 8 性能 略,要求同精度评估 @@ -210,10 +210,10 @@ lm_eval \ #### 表2:节点详细配置 -| 场景 | 配置 | 卡数 | TP | DP | 最大序列数 | 最大批量Token数 | 最大上下文 | MTP投机数 | FUSED_MC2 | EP开关 | FC+CP开关 | 异步调度 | +| 场景 | 配置 | 卡数 | TP | DP | BS | 并发 | 最大上下文 | MTP投机数 | FUSED_MC2 | EP开关 | FC+CP开关 | 异步调度 | |------|------|------|----|----|----|------|----------|---------|---------------|--------|-------|------| -| 高吞吐(32K推1K) | 服务端-P节点/单机 | 8 | 8 | 2 | 32 | 4096 | 30k | 3 | 关 | 开 | 开 | 开 | -| 高吞吐(32K推1K) | 服务端-D节点 | 8 | 2 | 8 | 8 | 4096 | 30k | 12 | 关 | 开 | 关 | 开 | +| 高吞吐(32K推1K) | 服务端-P节点/单机 | 8 | 8 | 2 | 32 | 64 | 30k | 3 | 关 | 开 | 开 | 开 | +| 高吞吐(32K推1K) | 服务端-D节点 | 8 | 2 | 8 | 8 | 64 | 30k | 12 | 关 | 开 | 关 | 开 | | 长序列 | 服务端-P节点/单机 | | | | | | | | | | | | | 长序列 | 服务端-D节点 | | | | | | | | | | | | | 低时延 | 服务端-P节点/单机 | | | | | | | | | | | | diff --git a/docs/source/community/slash-commands.md b/docs/source/community/slash-commands.md index 3c213bc83..9d2f4a897 100644 --- a/docs/source/community/slash-commands.md +++ b/docs/source/community/slash-commands.md @@ -65,7 +65,7 @@ Use `--branch ` to specify a target branch. Without `--branch`, all argume /nightly qwen3-vl-32b-instruct-w8a8 # Run on a specific release branch -/nightly qwen3-vl-32b-instruct-w8a8 --branch releases/v0.22.1 +/nightly qwen3-vl-32b-instruct-w8a8 --branch releases/v0.21.0 # Run all tests on a specific branch /nightly all --branch my-feature-branch diff --git a/docs/source/community/versioning_policy.md b/docs/source/community/versioning_policy.md index da16cde6a..1e0ce8cc9 100644 --- a/docs/source/community/versioning_policy.md +++ b/docs/source/community/versioning_policy.md @@ -21,41 +21,40 @@ For example: The table below is the release compatibility matrix for vLLM Ascend release. -| vLLM Ascend | vLLM | Python | Stable CANN | PyTorch/torch_npu | Triton Ascend | Mooncake | -|-------------|-------------------|-----------------|-------------|---------------------------------|-------------------|--------------| -| v0.21.0rc1 | v0.21.0 | >= 3.10, < 3.13 | 9.0.0 | 2.10.0 / 2.10.0 | 3.2.1 | v0.3.9 | -| v0.20.2rc1 | v0.20.2 | >= 3.10, < 3.12 | 9.0.0 | 2.10.0 / 2.10.0 | 3.2.1 | v0.3.8.post1 | -| v0.19.1rc1 | v0.19.1 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | v0.3.8.post1 | -| v0.18.0 | v0.18.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0.post1+git4c901a4 | 3.2.0.dev20260322 | v0.3.9 | -| v0.18.0rc1 | v0.18.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | | -| v0.17.0rc1 | v0.17.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | | -| v0.16.0rc1 | v0.16.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | | -| v0.15.0rc1 | v0.15.0 | >= 3.10, < 3.12 | 8.5.0 | 2.9.0 / 2.9.0 | 3.2.0 | | -| v0.14.0rc1 | v0.14.1 | >= 3.10, < 3.12 | 8.5.0 | 2.9.0 / 2.9.0 | 3.2.0 | | -| v0.13.0rc3 | v0.13.0 | >= 3.10, < 3.12 | 8.5.1 | 2.8.0 / 2.8.0.post2 | 3.2.0 | | -| v0.13.0 | v0.13.0 | >= 3.10, < 3.12 | 8.5.0 | 2.8.0 / 2.8.0.post2 | 3.2.0 | | -| v0.13.0rc2 | v0.13.0 | >= 3.10, < 3.12 | 8.5.0 | 2.8.0 / 2.8.0.post1 | 3.2.0 | | -| v0.13.0rc1 | v0.13.0 | >= 3.10, < 3.12 | 8.3.RC2 | 2.8.0 / 2.8.0 | | | -| v0.12.0rc1 | v0.12.0 | >= 3.10, < 3.12 | 8.3.RC2 | 2.8.0 / 2.8.0 | | | -| v0.11.0 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC2 | 2.7.1 / 2.7.1.post1 | | | -| v0.11.0rc3 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC2 | 2.7.1 / 2.7.1.post1 | | | -| v0.11.0rc2 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC2 | 2.7.1 / 2.7.1 | | | -| v0.11.0rc1 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC1 | 2.7.1 / 2.7.1 | | | -| v0.11.0rc0 | v0.11.0rc3 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | -| v0.10.2rc1 | v0.10.2 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | -| v0.10.1rc1 | v0.10.1/v0.10.1.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | -| v0.10.0rc1 | v0.10.0 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | -| v0.9.2rc1 | v0.9.2 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1.post1.dev20250619 | | | -| v0.9.1 | v0.9.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.5.1 / 2.5.1.post1 | | | -| v0.9.1rc3 | v0.9.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.5.1 / 2.5.1.post1 | | | -| v0.9.1rc2 | v0.9.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.5.1 / 2.5.1.post1 | | | -| v0.9.1rc1 | v0.9.1 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1.post1.dev20250528 | | | -| v0.9.0rc2 | v0.9.0 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | -| v0.9.0rc1 | v0.9.0 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | -| v0.8.5rc1 | v0.8.5.post1 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | -| v0.8.4rc2 | v0.8.4 | >= 3.9, < 3.12 | 8.0.0 | 2.5.1 / 2.5.1 | | | -| v0.7.3.post1| v0.7.3 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | -| v0.7.3 | v0.7.3 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | +| vLLM Ascend | vLLM | Python | Stable CANN | PyTorch/torch_npu | Triton Ascend | Mooncake | +|-------------|-------------------|-----------------|-------------|---------------------------------|-------------------|----------| +| v0.20.2rc1 | v0.20.2 | >= 3.10, < 3.12 | 9.0.0 | 2.10.0 / 2.10.0 | 3.2.1 | | +| v0.19.1rc1 | v0.19.1 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | | +| v0.18.0 | v0.18.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0.post1+git4c901a4 | 3.2.0.dev20260322 | 3.9.0 | +| v0.18.0rc1 | v0.18.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | | +| v0.17.0rc1 | v0.17.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | | +| v0.16.0rc1 | v0.16.0 | >= 3.10, < 3.12 | 8.5.1 | 2.9.0 / 2.9.0 | 3.2.0 | | +| v0.15.0rc1 | v0.15.0 | >= 3.10, < 3.12 | 8.5.0 | 2.9.0 / 2.9.0 | 3.2.0 | | +| v0.14.0rc1 | v0.14.1 | >= 3.10, < 3.12 | 8.5.0 | 2.9.0 / 2.9.0 | 3.2.0 | | +| v0.13.0rc3 | v0.13.0 | >= 3.10, < 3.12 | 8.5.1 | 2.8.0 / 2.8.0.post2 | 3.2.0 | | +| v0.13.0 | v0.13.0 | >= 3.10, < 3.12 | 8.5.0 | 2.8.0 / 2.8.0.post2 | 3.2.0 | | +| v0.13.0rc2 | v0.13.0 | >= 3.10, < 3.12 | 8.5.0 | 2.8.0 / 2.8.0.post1 | 3.2.0 | | +| v0.13.0rc1 | v0.13.0 | >= 3.10, < 3.12 | 8.3.RC2 | 2.8.0 / 2.8.0 | | | +| v0.12.0rc1 | v0.12.0 | >= 3.10, < 3.12 | 8.3.RC2 | 2.8.0 / 2.8.0 | | | +| v0.11.0 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC2 | 2.7.1 / 2.7.1.post1 | | | +| v0.11.0rc3 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC2 | 2.7.1 / 2.7.1.post1 | | | +| v0.11.0rc2 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC2 | 2.7.1 / 2.7.1 | | | +| v0.11.0rc1 | v0.11.0 | >= 3.9, < 3.12 | 8.3.RC1 | 2.7.1 / 2.7.1 | | | +| v0.11.0rc0 | v0.11.0rc3 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | +| v0.10.2rc1 | v0.10.2 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | +| v0.10.1rc1 | v0.10.1/v0.10.1.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | +| v0.10.0rc1 | v0.10.0 | >= 3.9, < 3.12 | 8.2.RC1 | 2.7.1 / 2.7.1.dev20250724 | | | +| v0.9.2rc1 | v0.9.2 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1.post1.dev20250619 | | | +| v0.9.1 | v0.9.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.5.1 / 2.5.1.post1 | | | +| v0.9.1rc3 | v0.9.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.5.1 / 2.5.1.post1 | | | +| v0.9.1rc2 | v0.9.1 | >= 3.9, < 3.12 | 8.2.RC1 | 2.5.1 / 2.5.1.post1 | | | +| v0.9.1rc1 | v0.9.1 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1.post1.dev20250528 | | | +| v0.9.0rc2 | v0.9.0 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | +| v0.9.0rc1 | v0.9.0 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | +| v0.8.5rc1 | v0.8.5.post1 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | +| v0.8.4rc2 | v0.8.4 | >= 3.9, < 3.12 | 8.0.0 | 2.5.1 / 2.5.1 | | | +| v0.7.3.post1| v0.7.3 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | +| v0.7.3 | v0.7.3 | >= 3.9, < 3.12 | 8.1.RC1 | 2.5.1 / 2.5.1 | | | :::{note} If you're using v0.7.3, don't forget to install [mindie-turbo](https://pypi.org/project/mindie-turbo) as well. @@ -73,7 +72,6 @@ For main branch of vLLM Ascend, we usually make it compatible with the latest vL | Date | Event | |------------|-------------------------------------------| -| 2026.06.16 | Release candidates, v0.21.0rc1 | | 2026.06.03 | Release candidates, v0.20.2rc1 | | 2026.04.30 | Release candidates, v0.19.1rc1 | | 2026.04.24 | Release candidates, v0.13.0rc3 | diff --git a/docs/source/conf.py b/docs/source/conf.py index 5229e6c22..382ff9407 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -37,7 +37,7 @@ author = "the vllm-ascend team" # The full version, including alpha/beta/rc tags -release = "0.21.0rc1" +release = "0.20.2rc1" # -- General configuration --------------------------------------------------- @@ -74,16 +74,16 @@ # the branch of vllm, used in vllm clone # - main branch: 'main' # - vX.Y.Z branch: 'vX.Y.Z' - "vllm_version": "v0.21.0", + "vllm_version": "v0.20.2", # the branch of vllm-ascend, used in vllm-ascend clone and image tag # - main branch: 'main' # - vX.Y.Z branch: latest vllm-ascend release tag - "vllm_ascend_version": "v0.21.0rc1", + "vllm_ascend_version": "v0.20.2rc1", # the newest release version of vllm-ascend and matched vLLM, used in pip install. # This value should be updated when cut down release. - "pip_vllm_ascend_version": "0.21.0rc1", - "pip_vllm_version": "0.21.0", - # CANN image tag paired with the vllm_ascend_version above + "pip_vllm_ascend_version": "0.20.2rc1", + "pip_vllm_version": "0.20.2", + # CANN image tag "cann_image_tag": "9.0.0-910b-ubuntu22.04-py3.12", # vLLM commit hash for main branch "main_vllm_commit": _VLLM_MAIN_VERIFIED_COMMIT, diff --git a/docs/source/developer_guide/Design_Documents/ACL_Graph.md b/docs/source/developer_guide/Design_Documents/ACL_Graph.md index a2a86d033..b7043e6c3 100644 --- a/docs/source/developer_guide/Design_Documents/ACL_Graph.md +++ b/docs/source/developer_guide/Design_Documents/ACL_Graph.md @@ -49,18 +49,18 @@ The smaller step at small batch sizes reduces padding overhead where latency is On Ascend, this generic upstream bucketing strategy is still the starting point, but the final capture sizes may be reduced further by platform-specific constraints: - sequence-parallel filtering may remove unsupported sizes, -- runtime resource limits may still prevent some configured sizes from being captured, +- stream-budget trimming may reduce the number of sizes that can be captured, - some runtime modes may be normalized before capture begins. ## Ascend-Specific Design Constraints -### Capture breadth is still constrained by runtime resources +### Stream budget constrains capture breadth -Unlike CUDA Graph on CUDA devices, ACL graph capture on Ascend can still fail when the selected graph sizes consume more runtime resources than the current backend can supply. Piecewise mode is the most sensitive case because it captures many subgraphs and the total capture cost scales with model depth and configured size coverage. +Unlike CUDA Graph, ACL graph capture is limited by stream resources. The current implementation treats graph count as a stream budget problem and trims capture sizes accordingly in `vllm_ascend.utils.update_aclgraph_sizes()`. The trimming logic starts from the configured capture sizes, estimates per-graph resource cost from model depth and communication structure, and samples a smaller representative size set when the requested range would exceed the supported budget. -Older versions of vLLM Ascend applied a local `update_aclgraph_sizes()` heuristic to shrink the PIECEWISE capture-size set before final capture. That heuristic has been removed. The current implementation keeps upstream sizing and dispatch behavior intact, then intercepts the confirmed capture-time stream-resource signature in `vllm_ascend/compilation/acl_graph.py` and re-raises it with clearer mitigation guidance. +The current implementation uses a practical maximum graph count budget of about 1800, below the device stream limit, and further reduces the budget for communication-heavy cases such as context parallel execution. Piecewise mode is more constrained because each captured segment consumes resources independently, roughly one graph per layer. -In practice, this means users should treat `cudagraph_capture_sizes` and `max_cudagraph_capture_size` as the primary tuning levers when capture fails. Newer HDK/CANN combinations can materially improve ACL graph capacity, while communication-heavy configurations may still require a smaller configured size set. +The communication execution mode also matters. `update_aclgraph_sizes()` uses different formulas depending on `HCCL_OP_EXPANSION_MODE`. In practice, `HCCL_OP_EXPANSION_MODE=AIV` can increase the number of supported capture sizes, while the default communication unfolding path is more restrictive and reduces the supported runtime shape range. ### Platform mode normalization is stricter than generic upstream behavior @@ -117,7 +117,7 @@ Full graph mode is the more performance-oriented path when the attention backend - The simplest way to confirm that graph mode is active is to enable cudagraph metrics and keep log stats enabled. In CLI usage, use `--cudagraph-metrics` and do not pass `--disable-log-stats`. In Python usage, set `cudagraph_metrics=True` and `disable_log_stats=False`. Then inspect the emitted metrics and logs. - Profiling can also confirm whether replay is happening, and developers can add temporary prints before replay when debugging locally, but those are secondary methods and are not expanded here. -- Capture-size selection primarily follows upstream configuration and dispatch behavior; only the confirmed stream-resource capture failure is rewritten with user-facing guidance at runtime. +- `update_aclgraph_sizes()` is the main implementation point for stream-budget-driven capture-size trimming. - In debug mode, `ACLGraphWrapper` asserts that replay uses the same tensor addresses recorded during capture. - `ASCEND_LAUNCH_BLOCKING=1` is incompatible with ACL graph enablement in the current implementation. - For debugging inside graph execution, the repo also provides graph-aware print helpers in `vllm_ascend.utils`, but those are developer diagnostics rather than part of the execution design. @@ -126,7 +126,7 @@ Full graph mode is the more performance-oriented path when the attention backend - `vllm_ascend/platform.py`, mode normalization, platform hooks, and static graph wrapper selection. - `vllm_ascend/compilation/acl_graph.py`, ACL graph wrapper, capture and replay cache, graph parameter containers, and full graph update dispatch. -- `vllm_ascend/compilation/acl_graph.py`, runtime ACL graph capture, replay, and capture-failure guidance. +- `vllm_ascend/utils.py`, capture size adjustment through `update_aclgraph_sizes()`. - `vllm_ascend/attention/attention_v1.py`, full graph attention parameter capture and update logic. - `vllm_ascend/attention/mla_v1.py`, MLA (Multi-Head Latent Attention) specific full graph parameter capture and update logic. - `vllm_ascend/attention/context_parallel/attention_cp.py`, context parallel attention update path. diff --git a/docs/source/developer_guide/Design_Documents/context_parallel.md b/docs/source/developer_guide/Design_Documents/context_parallel.md index eee33e862..ccd4c5a0a 100644 --- a/docs/source/developer_guide/Design_Documents/context_parallel.md +++ b/docs/source/developer_guide/Design_Documents/context_parallel.md @@ -125,8 +125,5 @@ By predefining the maximum amount of KV cache processed per round, we sequential - slot_mapping computation: `vllm_ascend/worker/block_table.py` - sequences splitting and metadata prepare: `vllm_ascend/worker/model_runner_v1.py` -- PCP token splitting and metadata generation: `vllm_ascend/worker/pcp_utils.py` -- GQA backend: `vllm_ascend/attention/context_parallel/attention_cp.py` -- MLA backend: `vllm_ascend/attention/context_parallel/mla_cp.py` -- DSA backend: `vllm_ascend/attention/context_parallel/dsa_cp.py` -- SFA backend: `vllm_ascend/attention/context_parallel/sfa_cp.py` +- GQA backend: `vllm_ascend/attention/attention_cp.py` +- MLA backend: `vllm_ascend/attention/mla_cp.py` diff --git a/docs/source/developer_guide/Design_Documents/cpu_binding.md b/docs/source/developer_guide/Design_Documents/cpu_binding.md index d685b8d39..3ae091604 100644 --- a/docs/source/developer_guide/Design_Documents/cpu_binding.md +++ b/docs/source/developer_guide/Design_Documents/cpu_binding.md @@ -33,8 +33,8 @@ The allocator derives its plan from runtime host state: | Input | Source | Purpose | | --- | --- | --- | | Allowed CPUs | `/proc/self/status` `Cpus_allowed_list` | The only CPUs eligible for binding. Container cpusets are respected. | -| Logical NPU map | `npu-smi info -m` | Maps card/chip IDs to global logical NPU IDs and gives `total_logic_npus`. On Ascend 950, `Chip Logic ID` is not reported, so `NPU ID` is used as the logical ID. | -| Running NPUs | `npu-smi info` process table, filtered by `ASCEND_RT_VISIBLE_DEVICES` | Identifies the logical NPUs used by this worker process. A2/A3 process rows use `NPU Chip`; Ascend 950 process rows use `NPU ID`. | +| Logical NPU map | `npu-smi info -m` | Maps card/chip IDs to global logical NPU IDs and gives `total_logic_npus`. | +| Running NPUs | `npu-smi info` process table, filtered by `ASCEND_RT_VISIBLE_DEVICES` | Identifies the logical NPUs used by this worker process. | | Topology affinity | `npu-smi info -t topo` | Provides NPU-to-CPU affinity for `topo_affinity` mode. | | CPU NUMA map | `lscpu -e=CPU,NODE` | Used to extend single-NUMA affinity pools to the next NUMA node. | @@ -45,8 +45,7 @@ The binding strategy is selected by Ascend device type: | Device type | Strategy | Reason | | --- | --- | --- | | A3 | `global_slice` | A3 uses HCCS card-to-card interconnect. Each NPU is nearly equidistant from all NUMA nodes, so there is no strong NPU-to-NUMA affinity signal. Global logical NPU ID based slicing gives deterministic, non-overlapping CPU pools and CPU/NUMA isolation between workers. | -| Ascend 950 | `global_slice` | Ascend 950 reports NPU-to-NPU/NIC topology but does not report NPU-to-CPU affinity in `npu-smi info -t topo`. It also reports process rows by `NPU ID` instead of `NPU Chip`. Global logical NPU ID based slicing keeps CPU pools deterministic without relying on missing affinity data. | -| A2 and Atlas 300 inference products | `topo_affinity` | A2 and Atlas 300 inference products provide NPU-to-CPU affinity information through `npu-smi info -t topo`, so they use this topology signal when available. | +| A2, Atlas 300 inference products, and other non-A3 device types | `topo_affinity` | A2 and Atlas 300 inference products provide NPU-to-CPU affinity information through `npu-smi info -t topo`. Non-A3 device types use this topology signal when available. | If `topo_affinity` is selected but topo affinity is unavailable, the allocator falls back to `global_slice`. @@ -54,12 +53,10 @@ If `topo_affinity` is selected but topo affinity is unavailable, the allocator f #### global_slice -`global_slice` is designed for devices without a useful NPU-to-CPU affinity -signal, including A3 and Ascend 950. Because A3's **HCCS interconnect makes the distance -from each NPU to each NUMA node nearly the same**, topology affinity is not a -useful placement signal. Ascend 950 similarly exposes UB/NIC topology but not CPU -affinity. The allocator therefore partitions the sorted `allowed_cpus` list by -global logical NPU ID. +`global_slice` is designed for A3. Because A3's **HCCS interconnect makes the +distance from each NPU to each NUMA node nearly the same**, topology affinity is +not a useful placement signal. The allocator therefore partitions the sorted +`allowed_cpus` list by global logical NPU ID. 1. Determine `total_npus` in this order: - `total_logic_npus` from `npu-smi info -m` @@ -79,14 +76,12 @@ both processes slice against the same global NPU ID space. With a NUMA-aligned cpuset, this also provides **CPU/NUMA isolation between workers**, so one worker does not share the same CPU or NUMA slice with another worker. -`global_slice` requires enough CPUs for the selected device's role split: +`global_slice` requires `base >= 5`, because every NPU pool reserves: -- Devices with IRQ binding require `base >= 5`: - 2 CPUs for SQ/CQ IRQ binding, at least 1 CPU for the main worker, 1 CPU for - ACL thread, and 1 CPU for release thread. -- Ascend 950 skips IRQ binding and does not reserve SQ/CQ IRQ CPUs, so it requires - `base >= 3`: at least 1 CPU for the main worker, 1 CPU for ACL thread, and - 1 CPU for release thread. +- 2 CPUs for SQ/CQ IRQ binding +- at least 1 CPU for the main worker +- 1 CPU for ACL thread +- 1 CPU for release thread #### topo_affinity @@ -113,8 +108,6 @@ share the same topology affinity. After a CPU pool is built, the allocator splits it by role: -For devices with IRQ binding: - | Role | CPUs | | --- | --- | | SQ/CQ IRQ | `pool[0]`, `pool[1]` | @@ -122,17 +115,7 @@ For devices with IRQ binding: | ACL thread | `pool[-2]` | | Release thread | `pool[-1]` | -For Ascend 950: - -| Role | CPUs | -| --- | --- | -| Main worker process and subthreads | `pool[:-2]` | -| ACL thread | `pool[-2]` | -| Release thread | `pool[-1]` | - -If a final pool has fewer CPUs than the selected role split requires, binding -fails for this rank and the worker logs a warning from the caller. The minimum -is 5 CPUs per NPU for devices with IRQ binding, and 3 CPUs per NPU for Ascend 950. +If a final pool has fewer than 5 CPUs, binding fails for this rank and the worker logs a warning from the caller. ## Conditional Host Tuning @@ -144,7 +127,6 @@ steps when the environment supports them: reads and reduces remote-NUMA memory read latency. - IRQ binding places NPU IRQ handling on the CPUs reserved for the corresponding NPU when `/proc/irq` is writable and IRQ files can be resolved. - Ascend 950 skips this step and gives those CPUs to the main worker instead. These are conditional parts of CPU binding, not separate feature switches. If a host prerequisite is missing, that step is skipped while CPU thread binding @@ -233,15 +215,12 @@ NPU0: main=[...] acl=[...] release=[...] ## Limitations - CPU binding runs only on ARM. It is skipped on x86_64. -- Each final NPU pool must have enough CPUs for its role split: at least 5 CPUs - for devices with IRQ binding, and at least 3 CPUs for Ascend 950. +- Each final NPU pool must have at least 5 CPUs. - `global_slice` is deterministic and provides CPU/NUMA isolation when the cpuset is NUMA-aligned, but it cannot guarantee NUMA-local pools when CPU numbering or cpuset layout crosses NUMA boundaries. - `topo_affinity` depends on usable output from `npu-smi info -t topo`. - IRQ binding requires writable `/proc/irq` and resolvable PCI/IRQ information. - Ascend 950 skips IRQ binding even when `/proc/irq` is writable, and does not reserve - IRQ CPUs in its role split. - Memory migration requires `migratepages`; otherwise only memory migration is skipped. CPU affinity still applies, but performance may degrade because existing pages are not moved to the target NUMA node and may be read through diff --git a/docs/source/developer_guide/Design_Documents/eplb_swift_balancer.md b/docs/source/developer_guide/Design_Documents/eplb_swift_balancer.md index 7aeede0c0..7fadd2657 100644 --- a/docs/source/developer_guide/Design_Documents/eplb_swift_balancer.md +++ b/docs/source/developer_guide/Design_Documents/eplb_swift_balancer.md @@ -20,26 +20,28 @@ Please refer to the EPLB section of the user guide for detailed information: [Ho vllm_ascend ├── eplb │ ├── adaptor -│ │ └── vllm_adaptor.py +│ │ ├── abstract_adaptor.py +│ │ ├── vllm_adaptor.py │ ├── core │ │ ├── policy │ │ │ ├── policy_abstract.py │ │ │ ├── policy_default_eplb.py +│ │ │ ├── policy_swift_balancer.py │ │ │ ├── policy_factory.py │ │ │ ├── policy_flashlb.py -│ │ │ ├── policy_random.py -│ │ │ └── policy_swift_balancer.py │ │ ├── eplb_device_transfer_loader.py │ │ ├── eplb_utils.py -│ │ └── eplb_worker.py +│ │ ├── eplb_worker.py │ ├── eplb_updator.py -│ └── utils.py +│ ├── utils.py └─────────── ``` **1. Adaptor Module** *Handles registration and adaptation for different MoE model types* +- `abstract_adaptor.py` + Abstract base class defining unified registration interfaces for EPLB adapters - `vllm_adaptor.py` Implementation supporting Qwen3-MoE and DeepSeek models, standardizing parameter handling for policy algorithms @@ -56,8 +58,6 @@ vllm_ascend Enhanced version optimizing expert swaps for low-bandwidth devices (e.g., A2) - `policy_flashlb.py` Threshold-based adjustment reducing operational costs through layer-wise fluctuation detection - - `policy_random.py` - Random policy for basic testing - `policy_factory.py` Strategy factory for automatic algorithm instantiation diff --git a/docs/source/developer_guide/evaluation/using_ais_bench.md b/docs/source/developer_guide/evaluation/using_ais_bench.md index 479bdce05..9b513d200 100644 --- a/docs/source/developer_guide/evaluation/using_ais_bench.md +++ b/docs/source/developer_guide/evaluation/using_ais_bench.md @@ -1,6 +1,6 @@ # Using AISBench -This document guides you to conduct accuracy testing using [AISBench](https://github.com/AISBench/benchmark/tree/master). AISBench provides accuracy and performance evaluation for many datasets. +This document guides you to conduct accuracy testing using [AISBench](https://gitee.com/aisbench/benchmark/tree/master). AISBench provides accuracy and performance evaluation for many datasets. ## Online Server @@ -57,7 +57,7 @@ INFO: Application startup complete. #### Install AISBench -Refer to [AISBench](https://github.com/AISBench/benchmark/tree/master) for details. +Refer to [AISBench](https://gitee.com/aisbench/benchmark/tree/master) for details. Install AISBench from source. ```shell @@ -81,7 +81,7 @@ You can choose one or multiple datasets to execute accuracy evaluation. 1. `C-Eval` dataset. - Take `C-Eval` dataset as an example. You can refer to [Datasets](https://github.com/AISBench/benchmark/tree/master/ais_bench/benchmark/configs/datasets) for more datasets. Each dataset has a `README.md` with detailed download and installation instructions. + Take `C-Eval` dataset as an example. You can refer to [Datasets](https://gitee.com/aisbench/benchmark/tree/master/ais_bench/benchmark/configs/datasets) for more datasets. Each dataset has a `README.md` with detailed download and installation instructions. Download dataset and install it to specific path. diff --git a/docs/source/developer_guide/performance_and_debug/optimization_and_tuning.md b/docs/source/developer_guide/performance_and_debug/optimization_and_tuning.md index b80e83bb6..382672a4c 100644 --- a/docs/source/developer_guide/performance_and_debug/optimization_and_tuning.md +++ b/docs/source/developer_guide/performance_and_debug/optimization_and_tuning.md @@ -66,9 +66,42 @@ Make sure your vLLM and vLLM Ascend are installed after your Python configuratio ## Optimizations -### 1. OS Optimization +### 1. Compilation Optimization -#### 1.1. jemalloc +#### 1.1. Install optimized `python` (OUT OF DATE) + +Python supports **LTO** and **PGO** optimization starting from version `3.6` and above, which can be enabled at compile time. And we have offered optimized `python` packages directly to users for the sake of convenience. You can also reproduce the `python` build following this [tutorial](https://www.hiascend.com/document/detail/zh/Pytorch/600/ptmoddevg/trainingmigrguide/performance_tuning_0063.html) according to your specific scenarios. + +```{code-block} bash + :substitutions: +mkdir -p /workspace/tmp +cd /workspace/tmp + +# Download prebuilt lib and packages +wget https://repo.oepkgs.net/ascend/pytorch/vllm/lib/libcrypto.so.1.1 +wget https://repo.oepkgs.net/ascend/pytorch/vllm/lib/libomp.so +wget https://repo.oepkgs.net/ascend/pytorch/vllm/lib/libssl.so.1.1 +wget https://repo.oepkgs.net/ascend/pytorch/vllm/python/py311_bisheng.tar.gz + +# Configure python and pip + +cp ./*.so* /usr/local/lib +tar -zxvf ./py311_bisheng.tar.gz -C /usr/local/ +mv /usr/local/py311_bisheng/ /usr/local/python +sed -i "1c#\!/usr/local/python/bin/python3.11" /usr/local/python/bin/pip3 +sed -i "1c#\!/usr/local/python/bin/python3.11" /usr/local/python/bin/pip3.11 +ln -sf /usr/local/python/bin/python3 /usr/bin/python +ln -sf /usr/local/python/bin/python3 /usr/bin/python3 +ln -sf /usr/local/python/bin/python3.11 /usr/bin/python3.11 +ln -sf /usr/local/python/bin/pip3 /usr/bin/pip3 +ln -sf /usr/local/python/bin/pip3 /usr/bin/pip + +export PATH=/usr/bin:/usr/local/python/bin:$PATH +``` + +### 2. OS Optimization + +#### 2.1. jemalloc **jemalloc** is a memory allocator that improves performance for multi-threaded scenarios and can reduce memory fragmentation. jemalloc uses a local thread memory manager to allocate variables, which can avoid lock competition between threads and can hugely optimize performance. @@ -82,7 +115,7 @@ sudo apt install libjemalloc2 export LD_PRELOAD=/usr/lib/"$(uname -i)"-linux-gnu/libjemalloc.so.2:$LD_PRELOAD ``` -#### 1.2. Tcmalloc +#### 2.2. Tcmalloc **TCMalloc (Thread Caching Malloc)** is a universal memory allocator that improves overall performance while ensuring low latency by introducing a multi-level cache structure, reducing mutex contention and optimizing large object processing flow. Find more [details](https://www.hiascend.com/document/detail/zh/Pytorch/700/ptmoddevg/trainingmigrguide/performance_tuning_0068.html). @@ -105,7 +138,7 @@ export LD_PRELOAD="$LD_PRELOAD:" ldd `which python` ``` -### 2. `torch_npu` Optimization +### 3. `torch_npu` Optimization Some performance tuning features in `torch_npu` are controlled by environment variables. Some features and their related environment variables are shown below. @@ -136,9 +169,9 @@ export TASK_QUEUE_ENABLE=2 export CPU_AFFINITY_CONF=1 ``` -### 3. CANN Optimization +### 4. CANN Optimization -#### 3.1. HCCL Optimization +#### 4.1. HCCL Optimization There are some performance tuning features in HCCL, which are controlled by environment variables. @@ -156,7 +189,7 @@ Plus, there are more features for performance optimization in specific scenarios - `HCCL_RDMA_SL`: Use this var to configure service level of RDMA NIC. Find more [details](https://www.hiascend.com/document/detail/zh/Pytorch/600/ptmoddevg/trainingmigrguide/performance_tuning_0046.html). - `HCCL_BUFFSIZE`: Use this var to control the cache size for sharing data between two NPUs. Find more [details](https://www.hiascend.com/document/detail/zh/Pytorch/600/ptmoddevg/trainingmigrguide/performance_tuning_0047.html). -### 4. OS Optimization +### 5. OS Optimization This section describes operating system–level optimizations applied on the host machine (bare metal or Kubernetes node) to improve performance stability, latency, and throughput for inference workloads. @@ -164,7 +197,7 @@ This section describes operating system–level optimizations applied on the hos These settings must be applied on the host OS and with root privileges. Not inside containers. ::: -#### 4.1 +#### 5.1 Set CPU Frequency Governor to `performance` @@ -183,7 +216,7 @@ Benefits - Reduces latency jitter - Improves predictability for inference workloads -#### 4.2 Disable Swap Usage +#### 5.2 Disable Swap Usage ```shell sysctl -w vm.swappiness=0 @@ -203,7 +236,7 @@ Notes - For inference workloads, swap can introduce second-level latency - Recommended values are `0` or `1` -#### 4.3 Disable Automatic NUMA Balancing +#### 5.3 Disable Automatic NUMA Balancing ```shell sysctl -w kernel.numa_balancing=0 @@ -225,7 +258,7 @@ Recommended For - Ascend / NPU deployments with explicit NUMA binding - Systems with manually managed CPU and memory affinity -#### 4.4 Increase Scheduler Migration Cost +#### 5.4 Increase Scheduler Migration Cost ```shell sysctl -w kernel.sched_migration_cost_ns=50000 diff --git a/docs/source/faqs.md b/docs/source/faqs.md index dc3000646..73eda19d5 100644 --- a/docs/source/faqs.md +++ b/docs/source/faqs.md @@ -2,7 +2,6 @@ ## Version Specific FAQs -- [[v0.21.0rc1] FAQ & Feedback](https://github.com/vllm-project/vllm-ascend/issues/9970) - [[v0.20.2rc1] FAQ & Feedback](https://github.com/vllm-project/vllm-ascend/issues/9586) - [[v0.19.1rc1] FAQ & Feedback](https://github.com/vllm-project/vllm-ascend/issues/8819) - [[v0.18.0] FAQ & Feedback](https://github.com/vllm-project/vllm-ascend/issues/8238) @@ -108,9 +107,9 @@ If all above steps are not working, feel free to submit a GitHub issue. `vllm-ascend` is a hardware plugin for vLLM. Stable releases usually align with the same vLLM version, while RC releases may use the corresponding vLLM final release version. For example, `vllm-ascend` `v0.18.0rc1` matches vLLM `v0.18.0`. For the main branch, we ensure that `vllm-ascend` and `vllm` are compatible at every commit. -### 8. Does vllm-ascend support Prefill-Decode (PD) Disaggregation feature? +### 8. Does vllm-ascend support Prefill Disaggregation feature? -Yes, vllm-ascend supports Prefill-Decode Disaggregation feature with Mooncake backend. See the [official tutorial](https://docs.vllm.ai/projects/ascend/en/latest/tutorials/features/pd_disaggregation_mooncake_multi_node.html) for example. +Yes, vllm-ascend supports Prefill Disaggregation feature with Mooncake backend. See the [official tutorial](https://docs.vllm.ai/projects/ascend/en/latest/tutorials/features/pd_disaggregation_mooncake_multi_node.html) for example. ### 9. Does vllm-ascend support quantization method? @@ -206,24 +205,20 @@ This package will install `librosa` and its related dependencies, resolving the ### 17. How to troubleshoot and resolve size capture failures resulting from stream resource exhaustion, and what are the underlying causes? -```text -capture_begin:../torch_npu/csrc/core/npu/NPUGraph.cpp:230 NPU function error: c10_npu::acl::AclmdlRICaptureBegin(capture_stream_, capture_mode), error code is 207008 -[Error]: Stream resources are insufficient. -[PID: ...] Insufficient_Stream_Resources(EL0009): The stream resources are insufficient. +```shell +error example in detail: +ERROR 09-26 10:48:07 [model_runner_v1.py:3029] ACLgraph sizes capture fail: RuntimeError: +ERROR 09-26 10:48:07 [model_runner_v1.py:3029] ACLgraph has insufficient available streams to capture the configured number of sizes.Please verify both the availability of adequate streams and the appropriateness of the configured size count. ``` -When vLLM Ascend recognizes this capture-time stream-resource signature in the error text, it re-raises the error with targeted guidance for ACL graph sizing and mitigation. - Recommended mitigation strategies: -1. Upgrade to a newer HDK/CANN stack if one is available for your environment. Recent releases improve ACL graph capacity, so older workarounds may no longer be necessary. -2. Manually reduce the configured graph sizes, for example: '{"cudagraph_capture_sizes":[size1, size2, size3, ...]}', or lower `max_cudagraph_capture_size`. -3. If your workload is mostly uniform decode, try ACLGraph's `FULL` or `FULL_DECODE_ONLY` mode instead of the `PIECEWISE`. -4. If you use `PIECEWISE` or `FULL_AND_PIECEWISE` and still hit this failure after upgrading, set `cudagraph_capture_sizes` manually according to your real workload and reduce the configured coverage. -5. If you are debugging a startup failure, temporarily disable graph mode (`cudagraph_mode="NONE"` / `enforce_eager=True`) to confirm the issue is capture-related. +1. Manually configure the compilation_config parameter with a reduced size set: '{"cudagraph_capture_sizes":[size1, size2, size3, ...]}'. +2. If your workload is mostly uniform decode, employ ACLGraph's `FULL` or `FULL_DECODE_ONLY` mode as an alternative to the piecewise approach. +3. If you use `PIECEWISE` or `FULL_AND_PIECEWISE`, it is recommended to set `cudagraph_capture_sizes` manually according to your workload. Root cause analysis: -ACL graph capture can still fail when the runtime resources required by the selected graph sizes exceed what the current software/hardware stack can provide. This is most visible in `PIECEWISE` scenarios because the number of captured graphs scales with model depth and capture-size coverage. vLLM Ascend no longer auto-shrinks the PIECEWISE capture-size set locally, so the practical mitigations are to upgrade the HDK/CANN stack or reduce the configured graph sizes explicitly. The runtime guidance is intentionally narrow: it is only added when capture fails with the confirmed stream-resource signature above. +The current stream requirement calculation for size captures only accounts for measurable factors including: data parallel size, tensor parallel size, expert parallel configuration, piece graph count, multistream-overlap shared expert settings, and HCCL communication mode (AIV/AICPU). However, numerous unquantifiable elements, such as operator characteristics and specific hardware features, consume additional streams outside of this calculation framework, resulting in stream resource exhaustion during size capture operations. ### 18. How to install custom version of torch_npu? @@ -276,7 +271,7 @@ export SOC_VERSION="ascend910_9391" # Atlas 300I export SOC_VERSION="ascend310p1" -# Ascend 950 Products +# Atlas A5 (Ascend 950 series) export SOC_VERSION="" ``` diff --git a/docs/source/installation.md b/docs/source/installation.md index 27a3cdf21..d06f16a7e 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -156,7 +156,7 @@ pip install vllm==|pip_vllm_version| # Install vllm-project/vllm-ascend. pip install \ ---extra-index-url https://mirrors.huaweicloud.com/ascend/repos/pypi/variant https://mirrors.huaweicloud.com/ascend/repos/pypi \ +--extra-index-url https://mirrors.huaweicloud.com/repository/pypi/simple \ vllm-ascend==|pip_vllm_ascend_version| ``` @@ -183,18 +183,10 @@ pip install vllm==|pip_vllm_version| # Install vllm-project/vllm-ascend from wheelnext index. uv pip install --system \ --extra-index-url https://mirrors.huaweicloud.com/ascend/repos/pypi/variant \ ---index-url https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple \ vllm-ascend==|pip_vllm_ascend_version| ``` -```{note} -If you encounter errors during `uv pip install` (e.g., corrupted cache or stale package data), try clearing the uv cache first and then re-run the install command: - - uv cache clean - -``` - :::: ::::: @@ -238,12 +230,7 @@ If you are building in a CPU-only environment where `npu-smi` is unavailable, yo - Atlas A2: `export SOC_VERSION=ascend910b1` - Atlas A3: `export SOC_VERSION=ascend910_9391` - Atlas 300I: `export SOC_VERSION=ascend310p1` -- Ascend 950 Products: `export SOC_VERSION=` -``` - -```{note} -To enable the batch invariance feature, set `VLLM_BATCH_INVARIANT=1` before building vllm-ascend to install the batch invariance custom operator library during the installation process. -For usage guidance on the batch invariance feature, see +- Atlas A5: `export SOC_VERSION=` ``` ## Set up using Docker diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/Design_Documents/ACL_Graph.po b/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/Design_Documents/ACL_Graph.po index 984cbf771..ca9831eca 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/Design_Documents/ACL_Graph.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/Design_Documents/ACL_Graph.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2026-06-10 04:47+0000\n" +"POT-Creation-Date: 2026-06-04 05:04+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -196,9 +196,9 @@ msgstr "序列并行过滤可能移除不支持的大小," #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:52 msgid "" -"runtime resource limits may still prevent some configured sizes from " -"being captured," -msgstr "运行时资源限制仍可能阻止某些配置大小被捕获," +"stream-budget trimming may reduce the number of sizes that can be " +"captured," +msgstr "流预算裁剪可能减少可捕获的大小数量," #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:53 msgid "some runtime modes may be normalized before capture begins." @@ -209,40 +209,46 @@ msgid "Ascend-Specific Design Constraints" msgstr "Ascend 特定设计约束" #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:57 -msgid "Capture breadth is still constrained by runtime resources" -msgstr "捕获广度仍受运行时资源约束" +msgid "Stream budget constrains capture breadth" +msgstr "流预算限制捕获广度" #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:59 msgid "" -"Unlike CUDA Graph on CUDA devices, ACL graph capture on Ascend can still " -"fail when the selected graph sizes consume more runtime resources than " -"the current backend can supply. Piecewise mode is the most sensitive case" -" because it captures many subgraphs and the total capture cost scales " -"with model depth and configured size coverage." +"Unlike CUDA Graph, ACL graph capture is limited by stream resources. The " +"current implementation treats graph count as a stream budget problem and " +"trims capture sizes accordingly in " +"`vllm_ascend.utils.update_aclgraph_sizes()`. The trimming logic starts " +"from the configured capture sizes, estimates per-graph resource cost from" +" model depth and communication structure, and samples a smaller " +"representative size set when the requested range would exceed the " +"supported budget." msgstr "" -"与 CUDA 设备上的 CUDA Graph 不同,Ascend 上的 ACL 图捕获在所选图大小消耗的运行时资源超过当前后端供应能力时仍可能失败。分段模式是最敏感的情况,因为它捕获许多子图,且总捕获成本随模型深度和配置大小覆盖范围而扩展。" +"与 CUDA Graph 不同,ACL 图捕获受流资源限制。当前实现将图计数视为流预算问题,并在 " +"`vllm_ascend.utils.update_aclgraph_sizes()` " +"中相应裁剪捕获大小。裁剪逻辑从配置的捕获大小开始,根据模型深度和通信结构估算每图资源成本,并在请求范围超出支持预算时采样更小的代表性大小集。" #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:61 msgid "" -"Older versions of vLLM Ascend applied a local `update_aclgraph_sizes()` " -"heuristic to shrink the PIECEWISE capture-size set before final capture. " -"That heuristic has been removed. The current implementation keeps " -"upstream sizing and dispatch behavior intact, then intercepts the " -"confirmed capture-time stream-resource signature in " -"`vllm_ascend/compilation/acl_graph.py` and re-raises it with clearer " -"mitigation guidance." +"The current implementation uses a practical maximum graph count budget of" +" about 1800, below the device stream limit, and further reduces the " +"budget for communication-heavy cases such as context parallel execution. " +"Piecewise mode is more constrained because each captured segment consumes" +" resources independently, roughly one graph per layer." msgstr "" -"旧版 vLLM Ascend 应用了本地 `update_aclgraph_sizes()` 启发式方法,在最终捕获前缩小 PIECEWISE 捕获大小集。该启发式方法已被移除。当前实现保持上游大小调整和调度行为不变,然后在 `vllm_ascend/compilation/acl_graph.py` 中拦截确认的捕获时流资源签名,并以更清晰的缓解指导重新抛出。" +"当前实现使用约 1800 " +"的实际最大图计数预算,低于设备流限制,并进一步减少通信密集型情况(如上下文并行执行)的预算。分段模式更受限制,因为每个捕获的段独立消耗资源,大约每层一个图。" #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:63 msgid "" -"In practice, this means users should treat `cudagraph_capture_sizes` and " -"`max_cudagraph_capture_size` as the primary tuning levers when capture " -"fails. Newer HDK/CANN combinations can materially improve ACL graph " -"capacity, while communication-heavy configurations may still require a " -"smaller configured size set." +"The communication execution mode also matters. `update_aclgraph_sizes()` " +"uses different formulas depending on `HCCL_OP_EXPANSION_MODE`. In " +"practice, `HCCL_OP_EXPANSION_MODE=AIV` can increase the number of " +"supported capture sizes, while the default communication unfolding path " +"is more restrictive and reduces the supported runtime shape range." msgstr "" -"在实践中,这意味着当捕获失败时,用户应将 `cudagraph_capture_sizes` 和 `max_cudagraph_capture_size` 视为主要调优杠杆。较新的 HDK/CANN 组合可以显著改善 ACL 图容量,而通信密集型配置可能仍需要较小的配置大小集。" +"通信执行模式也很重要。`update_aclgraph_sizes()` 根据 `HCCL_OP_EXPANSION_MODE` " +"使用不同的公式。实践中,`HCCL_OP_EXPANSION_MODE=AIV` " +"可以增加支持的捕获大小数量,而默认的通信展开路径更严格,减少了支持的运行时形状范围。" #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:65 msgid "Platform mode normalization is stricter than generic upstream behavior" @@ -459,10 +465,9 @@ msgstr "性能分析也可以确认重放是否发生,开发者可以在本地 #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:120 msgid "" -"Capture-size selection primarily follows upstream configuration and " -"dispatch behavior; only the confirmed stream-resource capture failure is " -"rewritten with user-facing guidance at runtime." -msgstr "捕获大小选择主要遵循上游配置和分发行为;仅在确认流资源捕获失败时,才会在运行时重写为用户可见的指导信息。" +"`update_aclgraph_sizes()` is the main implementation point for stream-" +"budget-driven capture-size trimming." +msgstr "`update_aclgraph_sizes()` 是基于流预算驱动的捕获大小调整的主要实现点。" #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:121 msgid "" @@ -503,9 +508,9 @@ msgstr "`vllm_ascend/compilation/acl_graph.py`,ACL 图包装器、捕获与重 #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:129 msgid "" -"`vllm_ascend/compilation/acl_graph.py`, runtime ACL graph capture, " -"replay, and capture-failure guidance." -msgstr "`vllm_ascend/compilation/acl_graph.py`,运行时 ACL 图捕获、重放以及捕获失败指导。" +"`vllm_ascend/utils.py`, capture size adjustment through " +"`update_aclgraph_sizes()`." +msgstr "`vllm_ascend/utils.py`,通过 `update_aclgraph_sizes()` 进行捕获大小调整。" #: ../../source/developer_guide/Design_Documents/ACL_Graph.md:130 msgid "" @@ -845,74 +850,3 @@ msgstr "`vllm_ascend/attention/context_parallel/mla_cp.py`,上下文并行 MLA #~ msgid "`FULL_AND_PIECEWISE` is normalized to `PIECEWISE`." #~ msgstr "`FULL_AND_PIECEWISE` 被规范化为 `PIECEWISE`。" - -#~ msgid "" -#~ "stream-budget trimming may reduce the" -#~ " number of sizes that can be " -#~ "captured," -#~ msgstr "流预算裁剪可能减少可捕获的大小数量," - -#~ msgid "Stream budget constrains capture breadth" -#~ msgstr "流预算限制捕获广度" - -#~ msgid "" -#~ "Unlike CUDA Graph, ACL graph capture " -#~ "is limited by stream resources. The " -#~ "current implementation treats graph count " -#~ "as a stream budget problem and " -#~ "trims capture sizes accordingly in " -#~ "`vllm_ascend.utils.update_aclgraph_sizes()`. The trimming" -#~ " logic starts from the configured " -#~ "capture sizes, estimates per-graph " -#~ "resource cost from model depth and " -#~ "communication structure, and samples a " -#~ "smaller representative size set when the" -#~ " requested range would exceed the " -#~ "supported budget." -#~ msgstr "" -#~ "与 CUDA Graph 不同,ACL " -#~ "图捕获受流资源限制。当前实现将图计数视为流预算问题,并在 " -#~ "`vllm_ascend.utils.update_aclgraph_sizes()` " -#~ "中相应裁剪捕获大小。裁剪逻辑从配置的捕获大小开始,根据模型深度和通信结构估算每图资源成本,并在请求范围超出支持预算时采样更小的代表性大小集。" - -#~ msgid "" -#~ "The current implementation uses a " -#~ "practical maximum graph count budget of" -#~ " about 1800, below the device stream" -#~ " limit, and further reduces the " -#~ "budget for communication-heavy cases " -#~ "such as context parallel execution. " -#~ "Piecewise mode is more constrained " -#~ "because each captured segment consumes " -#~ "resources independently, roughly one graph " -#~ "per layer." -#~ msgstr "" -#~ "当前实现使用约 1800 " -#~ "的实际最大图计数预算,低于设备流限制,并进一步减少通信密集型情况(如上下文并行执行)的预算。分段模式更受限制,因为每个捕获的段独立消耗资源,大约每层一个图。" - -#~ msgid "" -#~ "The communication execution mode also " -#~ "matters. `update_aclgraph_sizes()` uses different" -#~ " formulas depending on `HCCL_OP_EXPANSION_MODE`." -#~ " In practice, `HCCL_OP_EXPANSION_MODE=AIV` can" -#~ " increase the number of supported " -#~ "capture sizes, while the default " -#~ "communication unfolding path is more " -#~ "restrictive and reduces the supported " -#~ "runtime shape range." -#~ msgstr "" -#~ "通信执行模式也很重要。`update_aclgraph_sizes()` 根据 " -#~ "`HCCL_OP_EXPANSION_MODE` " -#~ "使用不同的公式。实践中,`HCCL_OP_EXPANSION_MODE=AIV` " -#~ "可以增加支持的捕获大小数量,而默认的通信展开路径更严格,减少了支持的运行时形状范围。" - -#~ msgid "" -#~ "`update_aclgraph_sizes()` is the main " -#~ "implementation point for stream-budget-" -#~ "driven capture-size trimming." -#~ msgstr "`update_aclgraph_sizes()` 是基于流预算驱动的捕获大小调整的主要实现点。" - -#~ msgid "" -#~ "`vllm_ascend/utils.py`, capture size adjustment " -#~ "through `update_aclgraph_sizes()`." -#~ msgstr "`vllm_ascend/utils.py`,通过 `update_aclgraph_sizes()` 进行捕获大小调整。" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/evaluation/using_ais_bench.po b/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/evaluation/using_ais_bench.po index 6f5ba0aac..07c5941d4 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/evaluation/using_ais_bench.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/developer_guide/evaluation/using_ais_bench.po @@ -26,10 +26,10 @@ msgstr "使用 AISBench" #: ../../source/developer_guide/evaluation/using_ais_bench.md:3 msgid "" "This document guides you to conduct accuracy testing using " -"[AISBench](https://github.com/AISBench/benchmark/tree/master). AISBench " +"[AISBench](https://gitee.com/aisbench/benchmark/tree/master). AISBench " "provides accuracy and performance evaluation for many datasets." msgstr "" -"本文档指导您如何使用 [AISBench](https://github.com/AISBench/benchmark/tree/master) " +"本文档指导您如何使用 [AISBench](https://gitee.com/aisbench/benchmark/tree/master) " "进行准确率测试。AISBench 为多种数据集提供准确率和性能评估。" #: ../../source/developer_guide/evaluation/using_ais_bench.md:5 @@ -69,10 +69,10 @@ msgstr "安装 AISBench" #: ../../source/developer_guide/evaluation/using_ais_bench.md:60 msgid "" -"Refer to [AISBench](https://github.com/AISBench/benchmark/tree/master) for" +"Refer to [AISBench](https://gitee.com/aisbench/benchmark/tree/master) for" " details. Install AISBench from source." msgstr "" -"详细安装说明请参考 [AISBench](https://github.com/AISBench/benchmark/tree/master) " +"详细安装说明请参考 [AISBench](https://gitee.com/aisbench/benchmark/tree/master) " "文档。从源码安装 AISBench。" #: ../../source/developer_guide/evaluation/using_ais_bench.md:69 @@ -98,12 +98,12 @@ msgstr "`C-Eval` 数据集。" #: ../../source/developer_guide/evaluation/using_ais_bench.md:84 msgid "" "Take `C-Eval` dataset as an example. You can refer to " -"[Datasets](https://github.com/AISBench/benchmark/tree/master/ais_bench/benchmark/configs/datasets)" +"[Datasets](https://gitee.com/aisbench/benchmark/tree/master/ais_bench/benchmark/configs/datasets)" " for more datasets. Each dataset has a `README.md` with detailed download" " and installation instructions." msgstr "" "以 `C-Eval` 数据集为例。您可以参考 " -"[数据集列表](https://github.com/AISBench/benchmark/tree/master/ais_bench/benchmark/configs/datasets)" +"[数据集列表](https://gitee.com/aisbench/benchmark/tree/master/ais_bench/benchmark/configs/datasets)" " 查看更多数据集信息。每个数据集都有一个 `README.md` 文件,提供了详细的下载和安装说明。" #: ../../source/developer_guide/evaluation/using_ais_bench.md:86 diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/faqs.po b/docs/source/locale/zh_CN/LC_MESSAGES/faqs.po index a933feb88..9d6735d4d 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/faqs.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/faqs.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: vllm-ascend\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-10 04:47+0000\n" +"POT-Creation-Date: 2026-06-04 05:04+0000\n" "PO-Revision-Date: 2026-01-22 11:40+0800\n" "Last-Translator: Gemini \n" "Language: zh_CN\n" @@ -507,80 +507,50 @@ msgid "" msgstr "17.如何排查并解决因流资源耗尽导致的尺寸捕获失败,其根本原因是什么?" #: ../../source/faqs.md:214 -msgid "" -"When vLLM Ascend recognizes this capture-time stream-resource signature " -"in the error text, it re-raises the error with targeted guidance for ACL " -"graph sizing and mitigation." -msgstr "" -"当 vLLM Ascend 在错误文本中识别出这种捕获时的流资源特征时,它会重新抛出错误,并提供针对 ACL 图尺寸和缓解措施的具体指导。" - -#: ../../source/faqs.md:216 msgid "Recommended mitigation strategies:" msgstr "建议的缓解策略:" -#: ../../source/faqs.md:218 -msgid "" -"Upgrade to a newer HDK/CANN stack if one is available for your " -"environment. Recent releases improve ACL graph capacity, so older " -"workarounds may no longer be necessary." -msgstr "" -"如果您的环境有可用的较新 HDK/CANN 栈,请升级。最近的版本改进了 ACL 图容量,因此旧的变通方法可能不再需要。" - -#: ../../source/faqs.md:219 +#: ../../source/faqs.md:216 #, python-brace-format msgid "" -"Manually reduce the configured graph sizes, for example: " -"'{\"cudagraph_capture_sizes\":[size1, size2, size3, ...]}', or lower " -"`max_cudagraph_capture_size`." +"Manually configure the compilation_config parameter with a reduced size " +"set: '{\"cudagraph_capture_sizes\":[size1, size2, size3, ...]}'." msgstr "" -"手动减少配置的图尺寸,例如:'{\"cudagraph_capture_sizes\":[size1, size2, size3, ...]}',或降低 " -"`max_cudagraph_capture_size`。" +"手动配置 `compilation_config` 参数以减少尺寸集:'{\"cudagraph_capture_sizes\":[size1, " +"size2, size3, ...]}'。" -#: ../../source/faqs.md:220 +#: ../../source/faqs.md:217 msgid "" -"If your workload is mostly uniform decode, try ACLGraph's `FULL` or " -"`FULL_DECODE_ONLY` mode instead of the `PIECEWISE`." -msgstr "如果您的负载主要是均匀解码,请尝试使用 ACLGraph 的 `FULL` 或 `FULL_DECODE_ONLY` 模式,而不是 `PIECEWISE`。" +"If your workload is mostly uniform decode, employ ACLGraph's `FULL` or " +"`FULL_DECODE_ONLY` mode as an alternative to the piecewise approach." +msgstr "如果您的负载主要是均匀解码,请使用 ACLGraph 的 `FULL` 或 `FULL_DECODE_ONLY` 模式作为分段方式的替代方案。" -#: ../../source/faqs.md:221 +#: ../../source/faqs.md:218 msgid "" -"If you use `PIECEWISE` or `FULL_AND_PIECEWISE` and still hit this failure" -" after upgrading, set `cudagraph_capture_sizes` manually according to " -"your real workload and reduce the configured coverage." -msgstr "" -"如果您使用 `PIECEWISE` 或 `FULL_AND_PIECEWISE`,并且在升级后仍然遇到此失败,请根据您的实际负载手动设置 " -"`cudagraph_capture_sizes` 并减少配置的覆盖范围。" +"If you use `PIECEWISE` or `FULL_AND_PIECEWISE`, it is recommended to set " +"`cudagraph_capture_sizes` manually according to your workload." +msgstr "如果您使用 `PIECEWISE` 或 `FULL_AND_PIECEWISE`,建议根据您的负载手动设置 `cudagraph_capture_sizes`。" -#: ../../source/faqs.md:222 -msgid "" -"If you are debugging a startup failure, temporarily disable graph mode " -"(`cudagraph_mode=\"NONE\"` / `enforce_eager=True`) to confirm the issue " -"is capture-related." -msgstr "" -"如果您正在调试启动失败,请临时禁用图模式(`cudagraph_mode=\"NONE\"` / `enforce_eager=True`),以确认问题是否与捕获相关。" - -#: ../../source/faqs.md:224 +#: ../../source/faqs.md:220 msgid "" -"Root cause analysis: ACL graph capture can still fail when the runtime " -"resources required by the selected graph sizes exceed what the current " -"software/hardware stack can provide. This is most visible in `PIECEWISE` " -"scenarios because the number of captured graphs scales with model depth " -"and capture-size coverage. vLLM Ascend no longer auto-shrinks the " -"PIECEWISE capture-size set locally, so the practical mitigations are to " -"upgrade the HDK/CANN stack or reduce the configured graph sizes " -"explicitly. The runtime guidance is intentionally narrow: it is only " -"added when capture fails with the confirmed stream-resource signature " -"above." +"Root cause analysis: The current stream requirement calculation for size " +"captures only accounts for measurable factors including: data parallel " +"size, tensor parallel size, expert parallel configuration, piece graph " +"count, multistream-overlap shared expert settings, and HCCL communication" +" mode (AIV/AICPU). However, numerous unquantifiable elements, such as " +"operator characteristics and specific hardware features, consume " +"additional streams outside of this calculation framework, resulting in " +"stream resource exhaustion during size capture operations." msgstr "" -"根本原因分析:当所选图尺寸所需的运行时资源超过当前软件/硬件栈所能提供的资源时,ACL 图捕获仍可能失败。这在 `PIECEWISE` " -"场景中最为明显,因为捕获的图数量随模型深度和捕获尺寸覆盖范围而扩展。vLLM Ascend 不再在本地自动缩小 PIECEWISE " -"捕获尺寸集,因此实际的缓解措施是升级 HDK/CANN 栈或显式减少配置的图尺寸。运行时指导特意保持狭窄:仅在捕获失败并带有上述确认的流资源特征时才会添加。" +"根本原因分析:当前的尺寸捕获流需求计算仅考虑了可测量因素,包括:数据并行大小、张量并行大小、专家并行配置、分段图数量、多流重叠共享专家设置以及 " +"HCCL " +"通信模式(AIV/AICPU)。然而,许多不可量化的因素(如算子特性和特定硬件功能)在该计算框架之外消耗了额外的流,导致在尺寸捕获操作期间流资源耗尽。" -#: ../../source/faqs.md:227 +#: ../../source/faqs.md:223 msgid "18. How to install custom version of torch_npu?" msgstr "18.如何安装自定义版本的 torch_npu?" -#: ../../source/faqs.md:229 +#: ../../source/faqs.md:225 msgid "" "torch-npu will be overridden when installing vllm-ascend. If you need to" " install a specific version of torch-npu, you can manually install the " @@ -589,48 +559,48 @@ msgstr "" "安装 vllm-ascend 时会覆盖 torch-npu。如果您需要安装特定版本的 torch-npu,请在安装完 vllm-ascend " "之后再手动安装指定版本的 torch-npu。" -#: ../../source/faqs.md:231 +#: ../../source/faqs.md:227 msgid "" "19. On certain systems (e.g., Kylin OS), `docker pull` may fail with an " "`invalid tar header` error" msgstr "19.在某些系统(如麒麟 OS)上,执行 `docker pull` 可能会报错 `invalid tar header`" -#: ../../source/faqs.md:233 +#: ../../source/faqs.md:229 msgid "" "On certain operating systems, such as Kylin OS, you may encounter an " "`invalid tar header` error during the `docker pull` process:" msgstr "在某些操作系统(如麒麟 OS)上,您可能会在 `docker pull` 过程中遇到 `invalid tar header` 错误:" -#: ../../source/faqs.md:239 +#: ../../source/faqs.md:235 msgid "" "This is often due to system compatibility issues. You can resolve this by" " using an offline loading method with a second machine." msgstr "这通常是由于系统兼容性问题导致的。您可以通过另一台机器使用离线加载的方法来解决。" -#: ../../source/faqs.md:241 +#: ../../source/faqs.md:237 msgid "" "On a separate host machine (e.g., a standard Ubuntu server), pull the " "image for the target ARM64 architecture and package it into a `.tar` " "file." msgstr "在另一台宿主机(如标准的 Ubuntu 服务器)上,拉取目标 ARM64 架构的镜像并打包为 `.tar` 文件。" -#: ../../source/faqs.md:254 +#: ../../source/faqs.md:250 msgid "Transfer the image archive" msgstr "传输镜像归档文件" -#: ../../source/faqs.md:256 +#: ../../source/faqs.md:252 msgid "" "Copy the `vllm_ascend_.tar` file (where `` is the image tag you" " used) to your target machine" msgstr "将 `vllm_ascend_.tar` 文件(其中 `` 是您使用的镜像标签)拷贝到目标机器。" -#: ../../source/faqs.md:258 +#: ../../source/faqs.md:254 msgid "" "20. Why am I getting an error when executing the script to start a Docker" " container? The error message is: \"operation not permitted\"" msgstr "20.为什么执行启动 Docker 容器的脚本时会报错 \"operation not permitted\"?" -#: ../../source/faqs.md:260 +#: ../../source/faqs.md:256 msgid "" "When using `--shm-size`, you may need to add the `--privileged=true` flag" " to your `docker run` command to grant the container necessary " @@ -643,13 +613,13 @@ msgstr "" "标志以授予容器必要权限。请注意,使用 `--privileged=true` " "会授予容器在宿主机系统上的极高权限,这可能存在安全风险。请仅在您了解后果并信任镜像来源的情况下使用此选项。" -#: ../../source/faqs.md:262 +#: ../../source/faqs.md:258 msgid "" "21. How to set `SOC_VERSION` when building from source on a CPU-only " "machine?" msgstr "21.在仅有 CPU 的机器上从源码构建时,如何设置 `SOC_VERSION`?" -#: ../../source/faqs.md:264 +#: ../../source/faqs.md:260 msgid "" "When building from source (e.g. `pip install -e .`), the build may try to" " infer the target chip via `npu-smi`. If `npu-smi` is not available " @@ -659,15 +629,15 @@ msgstr "" "从源码构建时(例如执行 `pip install -e .`),构建过程可能会尝试通过 `npu-smi` 推断目标芯片。如果 `npu-smi`" " 不可用(这在仅有 CPU 的构建环境中很常见),则必须在安装前手动设置 `SOC_VERSION`。" -#: ../../source/faqs.md:266 +#: ../../source/faqs.md:262 msgid "You can use the defaults from `Dockerfile*` as a reference. For example:" msgstr "你可以参考 `Dockerfile*` 中的默认值。例如:" -#: ../../source/faqs.md:282 +#: ../../source/faqs.md:278 msgid "22. Why TPOT increases drastically as concurrency grows?" msgstr "22.为什么 TPOT 会随着并发数增加而急剧上升?" -#: ../../source/faqs.md:284 +#: ../../source/faqs.md:280 msgid "" "When testing a vLLM server, one may find that TPOT increases as " "concurrency increases (for example, TPOT increases by 0.5 ~ 1ms when " @@ -689,7 +659,7 @@ msgstr "" " 引起的。通常,当你的服务器达到 KV 缓存限制时,vLLM 会尝试释放某些请求的 KV 缓存,以确保为其他请求提供足够的空间,这在 vLLM " "中称为抢占。当一个请求被抢占时,默认行为是在未来重新计算该请求的 KV 缓存,这就是性能可能显著下降的原因。有几种方法可以验证这一点:" -#: ../../source/faqs.md:287 +#: ../../source/faqs.md:283 msgid "" "vLLM usually logs stats on your server. You might see metrics like `GPU " "KV cache usage: 99.0%,`. When reaching 100%, it triggers preemption." @@ -697,7 +667,7 @@ msgstr "" "vLLM 通常会在服务器上记录统计信息。你可能会看到类似 `GPU KV cache usage: 99.0%,` 的指标。当达到 100% " "时,会触发抢占。" -#: ../../source/faqs.md:288 +#: ../../source/faqs.md:284 msgid "" "When launching a vLLM server, you will see logs like `GPU KV cache size: " "66340 tokens` and `Maximum concurrency for 16,384 tokens per request: " @@ -708,7 +678,7 @@ msgstr "" "concurrency for 16,384 tokens per request: 4.05` 的日志。这些是针对单个 DP 组的估计 KV " "缓存容量。你可以据此调整总体请求流量。" -#: ../../source/faqs.md:290 +#: ../../source/faqs.md:286 msgid "" "Preemption cannot be avoided completely since KV cache usage always has a" " limit. But there are methods to reduce the chances of preemption. As is " @@ -792,23 +762,3 @@ msgstr "" #~ "`tools/install_flash_infer_attention_score_ops_a3.sh` 脚本中的 " #~ "`/vllm-workspace` 目录更改为您自己的目录,或创建一个。如果您不是 root " #~ "用户,运行此脚本需要 `sudo` **权限**。" - -#~ msgid "" -#~ "Root cause analysis: The current stream" -#~ " requirement calculation for size captures" -#~ " only accounts for measurable factors " -#~ "including: data parallel size, tensor " -#~ "parallel size, expert parallel configuration," -#~ " piece graph count, multistream-overlap " -#~ "shared expert settings, and HCCL " -#~ "communication mode (AIV/AICPU). However, " -#~ "numerous unquantifiable elements, such as " -#~ "operator characteristics and specific hardware" -#~ " features, consume additional streams " -#~ "outside of this calculation framework, " -#~ "resulting in stream resource exhaustion " -#~ "during size capture operations." -#~ msgstr "" -#~ "根本原因分析:当前的尺寸捕获流需求计算仅考虑了可测量因素,包括:数据并行大小、张量并行大小、专家并行配置、分段图数量、多流重叠共享专家设置以及" -#~ " HCCL " -#~ "通信模式(AIV/AICPU)。然而,许多不可量化的因素(如算子特性和特定硬件功能)在该计算框架之外消耗了额外的流,导致在尺寸捕获操作期间流资源耗尽。" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/installation.po b/docs/source/locale/zh_CN/LC_MESSAGES/installation.po index 6d60d9359..b209e6633 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/installation.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/installation.po @@ -325,8 +325,8 @@ msgid "Atlas 300I: `export SOC_VERSION=ascend310p1`" msgstr "Atlas 300I:`export SOC_VERSION=ascend310p1`" #: ../../source/installation.md:233 -msgid "Ascend 950 Products: `export SOC_VERSION=`" -msgstr "Ascend 950 系列产品:`export SOC_VERSION=<以 \"ascend950\" 开头的值>`" +msgid "Atlas A5: `export SOC_VERSION=`" +msgstr "Atlas A5:`export SOC_VERSION=<以 \"ascend950\" 开头的值>`" #: ../../source/installation.md:236 msgid "Set up using Docker" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/features/pd_disaggregation_mooncake_multi_node.po b/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/features/pd_disaggregation_mooncake_multi_node.po index 0780ab846..ef46abe09 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/features/pd_disaggregation_mooncake_multi_node.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/features/pd_disaggregation_mooncake_multi_node.po @@ -411,10 +411,10 @@ msgstr "基准测试" #: ../../source/tutorials/features/pd_disaggregation_mooncake_multi_node.md:881 msgid "" "We recommend use aisbench tool to assess performance. " -"[aisbench](https://github.com/AISBench/benchmark) Execute the following " +"[aisbench](https://gitee.com/aisbench/benchmark) Execute the following " "commands to install aisbench" msgstr "" -"我们推荐使用 aisbench 工具进行性能评估。[aisbench](https://github.com/AISBench/benchmark) 执行以下命令安装 aisbench" +"我们推荐使用 aisbench 工具进行性能评估。[aisbench](https://gitee.com/aisbench/benchmark) 执行以下命令安装 aisbench" #: ../../source/tutorials/features/pd_disaggregation_mooncake_multi_node.md:889 msgid "" @@ -444,9 +444,9 @@ msgstr "以 gsm8k 数据集为例,执行以下命令以评估性能。" #: ../../source/tutorials/features/pd_disaggregation_mooncake_multi_node.md:930 msgid "" "For more details for commands and parameters for aisbench, refer to " -"[aisbench](https://github.com/AISBench/benchmark)" +"[aisbench](https://gitee.com/aisbench/benchmark)" msgstr "" -"有关 aisbench 命令和参数的更多详细信息,请参阅 [aisbench](https://github.com/AISBench/benchmark)" +"有关 aisbench 命令和参数的更多详细信息,请参阅 [aisbench](https://gitee.com/aisbench/benchmark)" #: ../../source/tutorials/features/pd_disaggregation_mooncake_multi_node.md:932 msgid "FAQ" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/models/MiniMax-M2.5.po b/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/models/MiniMax-M2.5.po index 688568c03..dca99d388 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/models/MiniMax-M2.5.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/tutorials/models/MiniMax-M2.5.po @@ -1,330 +1,75 @@ -# Translations template for PROJECT. -# Copyright (C) 2026 ORGANIZATION -# This file is distributed under the same license as the PROJECT project. -# FIRST AUTHOR , 2026. -# -msgid "" msgstr "" -"Project-Id-Version: PROJECT VERSION\n" -"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2026-06-10 04:47+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.18.0\n" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:1 -msgid "MiniMax-M2.5" -msgstr "MiniMax-M2.5" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:3 -msgid "Introduction" -msgstr "简介" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:5 -msgid "" -"MiniMax‑M2.5 is MiniMax’s flagship large language model, reinforced for " -"high‑value scenarios such as code generation, agentic tool " -"calling/search, and complex office workflows, with an emphasis on " -"reasoning efficiency and end‑to‑end speed on challenging tasks." +"`--max-num-seqs` 参数可根据实际请求情况调整。" msgstr "" -"MiniMax‑M2.5 是 MiniMax 的旗舰大语言模型,针对代码生成、智能体工具调用/搜索以及复杂办公工作流等高价值场景进行了强化,重点优化了推理效率和具有挑战性任务上的端到端速度。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:7 -msgid "" -"This document provides a unified deployment guide for `MiniMax-M2.5` on " -"vLLM Ascend, covering both:" -msgstr "" -"本文档提供了在 vLLM Ascend 上部署 `MiniMax-M2.5` 的统一指南,涵盖以下两种场景:" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:9 -msgid "**A3 single-node** deployment (Atlas 800 A3)" -msgstr "**A3 单节点**部署(Atlas 800 A3)" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:10 -msgid "**A2 single-node** deployment (Atlas 800I A2)" -msgstr "**A2 单节点**部署(Atlas 800I A2)" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:12 -msgid "Supported Features" -msgstr "支持的特性" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:14 -msgid "" -"Refer to [supported " -"features](../../user_guide/support_matrix/supported_models.md) to get the" -" model's supported feature matrix." -msgstr "请参考[支持的特性](../../user_guide/support_matrix/supported_models.md)获取模型支持的特性矩阵。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:16 -msgid "" -"Refer to [feature guide](../../user_guide/feature_guide/index.md) to get " -"the feature's configuration." -msgstr "请参考[特性指南](../../user_guide/feature_guide/index.md)获取特性的配置方法。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:18 -msgid "Environment Preparation" -msgstr "环境准备" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:20 -msgid "Model Weights" -msgstr "模型权重" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:22 -msgid "" -"`MiniMax-M2.5` (fp8 checkpoint): recommended to use **1× Atlas 800 A3** " -"or **1× Atlas 800I A2** nodes. Download the model weights from " -"[MiniMax/MiniMax-M2.5](https://modelscope.cn/models/MiniMax/MiniMax-M2.5)." -msgstr "" -"`MiniMax-M2.5`(fp8 检查点):建议使用 **1× Atlas 800 A3** 或 **1× Atlas 800I A2** 节点。从 [MiniMax/MiniMax-M2.5](https://modelscope.cn/models/MiniMax/MiniMax-M2.5) 下载模型权重。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:23 -msgid "" -"`MiniMax-M2.5-w8a8-QuaRot` : Download the model weights from [Eco-" -"Tech/MiniMax-M2.5-w8a8-QuaRot](https://modelscope.cn/models/Eco-" -"Tech/MiniMax-M2.5-w8a8-QuaRot)." -msgstr "" -"`MiniMax-M2.5-w8a8-QuaRot`:从 [Eco-Tech/MiniMax-M2.5-w8a8-QuaRot](https://modelscope.cn/models/Eco-Tech/MiniMax-M2.5-w8a8-QuaRot) 下载模型权重。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:24 -msgid "" -"`Eagle3` : Download the model weights from [vllm-ascend/MiniMax-M2.5" -"-eagel-model](https://modelscope.cn/models/vllm-ascend/MiniMax-M2.5" -"-eagel-model-0318)." +"`--max-num-batched-tokens 32768` 适用于输入序列长度为 32k 或更长的场景。" msgstr "" -"`Eagle3`:从 [vllm-ascend/MiniMax-M2.5-eagel-model](https://modelscope.cn/models/vllm-ascend/MiniMax-M2.5-eagel-model-0318) 下载模型权重。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:26 -msgid "" -"It is recommended to download the model weights to a shared directory, " -"such as `/mnt/sfs_turbo/.cache/`. The current release automatically " -"detects the MiniMax-M2 fp8 checkpoint, disables fp8 quantization kernels " -"on NPU, and loads the weights by dequantizing to bf16. This behavior may " -"be removed once public bf16 weights are available." +"`--max-num-batched-tokens 16384` 适用于输入序列长度为 16k 的场景。" msgstr "" -"建议将模型权重下载到共享目录,例如 `/mnt/sfs_turbo/.cache/`。当前版本会自动检测 MiniMax-M2 fp8 检查点,禁用 NPU 上的 fp8 量化内核,并通过反量化至 bf16 来加载权重。一旦公开的 bf16 权重可用,此行为可能会被移除。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:28 -msgid "Installation" -msgstr "安装" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:30 -msgid "You can use the official docker image to run `MiniMax-M2.5` directly." -msgstr "您可以直接使用官方 Docker 镜像来运行 `MiniMax-M2.5`。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:32 -msgid "" -"Select an image based on your machine type and start the container on " -"your node. See [using docker](../../installation.md#set-up-using-docker)." -msgstr "根据您的机器类型选择镜像,并在节点上启动容器。请参阅[使用 Docker](../../installation.md#set-up-using-docker)。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:34 -msgid "Run with Docker" -msgstr "使用 Docker 运行" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:36 -#: ../../source/tutorials/models/MiniMax-M2.5.md:123 -#: ../../source/tutorials/models/MiniMax-M2.5.md:241 -msgid "A3 (single node)" +"`--max-num-batched-tokens 6144` 适用于短序列输入场景,例如 2k 和 3.5k。" +msgstr "验证服务" msgstr "A3(单节点)" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:79 -#: ../../source/tutorials/models/MiniMax-M2.5.md:193 -#: ../../source/tutorials/models/MiniMax-M2.5.md:287 -msgid "A2 (single node)" +msgstr "使用 OpenAI 兼容客户端进行测试:" +msgstr "或使用 curl 发送请求:" msgstr "A2(单节点)" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:81 -msgid "Create and run `minimax25-docker-run.sh`." -msgstr "创建并运行 `minimax25-docker-run.sh`。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:83 -#: ../../source/tutorials/models/MiniMax-M2.5.md:127 -msgid "Notes:" -msgstr "注意:" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:85 -msgid "" -"The default configuration assumes an **Atlas 800I A2 8-NPU** node and " -"sets `ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7`. Update it based on your" -" hardware." -msgstr "默认配置假设使用 **Atlas 800I A2 8-NPU** 节点,并设置 `ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7`。请根据您的硬件进行更新。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:86 -msgid "" -"Map your model weight directory into the container (the example maps it " -"to `/opt/data/verification/`)." -msgstr "将您的模型权重目录映射到容器中(示例中映射到 `/opt/data/verification/`)。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:121 -msgid "Online Inference on Multi-NPU" -msgstr "多 NPU 在线推理" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:125 -msgid "" -"Below is a recommended startup configuration for short-context condition " -"like 3.5k/1.5k to reach a good performance." -msgstr "以下是针对 3.5k/1.5k 等短上下文场景推荐的启动配置,以获得良好性能。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:129 -msgid "" -"If you only care about short-context low latency, you can explicitly set " -"`--max-model-len 32768`. You may also set `tensor-parallel-size` to 16 " -"and set `data-parallel-size` to 1." -msgstr "如果您只关心短上下文低延迟,可以显式设置 `--max-model-len 32768`。您也可以将 `tensor-parallel-size` 设置为 16,并将 `data-parallel-size` 设置为 1。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:130 -msgid "" -"`export VLLM_ASCEND_BALANCE_SCHEDULING=1` is used to enhance scheduling " -"capacity between prefill and decode. This will work remarkably with a " -"larger `data-parallel-size`. This can increace performance when " -"concurrency gets closer to values equals to `data-parallel-size` times " -"`max-num-seqs`." -msgstr "" -"`export VLLM_ASCEND_BALANCE_SCHEDULING=1` 用于增强 prefill 和 decode 之间的调度能力。这在 `data-parallel-size` 较大时效果显著。当并发数接近 `data-parallel-size` 乘以 `max-num-seqs` 的值时,可以提升性能。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:166 -#: ../../source/tutorials/models/MiniMax-M2.5.md:232 -msgid "Remarks:" -msgstr "备注:" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:168 -msgid "`minimax_m2_append_think` keeps `...` inside `content`." -msgstr "`minimax_m2_append_think` 将 `...` 保留在 `content` 中。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:169 -msgid "" -"If you mainly rely on the reasoning semantics of `/v1/responses`, it is " -"recommended to use `--reasoning-parser minimax_m2` instead." -msgstr "如果您主要依赖 `/v1/responses` 的推理语义,建议改用 `--reasoning-parser minimax_m2`。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:170 -msgid "" -"To receive a better performance on long-context like 128k or 64k, we " -"recommend to do changes as shown below, and you can remove `export " -"VLLM_ASCEND_BALANCE_SCHEDULING=1`." -msgstr "为了在 128k 或 64k 等长上下文场景中获得更好的性能,我们建议进行如下更改,并且可以移除 `export VLLM_ASCEND_BALANCE_SCHEDULING=1`。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:185 -msgid "" -"If you will to test with `curl` command, you can add following commands " -"addition to start up command above." -msgstr "如果您想使用 `curl` 命令进行测试,可以在上述启动命令中添加以下命令。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:234 -msgid "" -"`--max-num-seqs` parameter can be adjusted according to actual request " -"conditions." -msgstr "`--max-num-seqs` 参数可根据实际请求情况调整。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:235 -msgid "" -"`--max-num-batched-tokens 32768` is applicable to the input sequence " -"length of 32k or longer." -msgstr "`--max-num-batched-tokens 32768` 适用于 32k 或更长的输入序列长度。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:236 -msgid "" -"`--max-num-batched-tokens 16384` is applicable to the input sequence " -"length of 16k." -msgstr "`--max-num-batched-tokens 16384` 适用于 16k 的输入序列长度。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:237 -msgid "" -"`--max-num-batched-tokens 6144` is applicable to short sequence input " -"scenarios such as 2k and 3.5k." -msgstr "`--max-num-batched-tokens 6144` 适用于 2k 和 3.5k 等短序列输入场景。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:239 -msgid "Verify the Service" -msgstr "验证服务" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:243 -msgid "Test with an OpenAI-compatible client:" -msgstr "使用兼容 OpenAI 的客户端进行测试:" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:258 -msgid "Or send a request using curl:" -msgstr "或者使用 curl 发送请求:" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:289 -#, python-brace-format -msgid "" -"Run the following from any machine that can reach the service node " -"(replace `{NodeIP}` with the real IP):" -msgstr "在任意能访问服务节点的机器上运行以下命令(将 `{NodeIP}` 替换为实际 IP):" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:305 -msgid "FAQ" +msgstr "在任意可以访问服务节点的机器上运行以下命令(将 `{NodeIP}` 替换为实际 IP):" msgstr "常见问题" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:307 -msgid "**Q: What should I do if the output is garbled in EP mode?**" msgstr "**问:在 EP 模式下输出乱码怎么办?**" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:309 -msgid "" -"A: It is recommended to keep `--enable-expert-parallel` and " -"`VLLM_ASCEND_ENABLE_FLASHCOMM1=1`." msgstr "答:建议保持 `--enable-expert-parallel` 和 `VLLM_ASCEND_ENABLE_FLASHCOMM1=1`。" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:311 -msgid "" -"**Q: Why is the `reasoning` field often empty after using " -"`minimax_m2_append_think`?**" -msgstr "**问:使用 `minimax_m2_append_think` 后,`reasoning` 字段为什么经常为空?**" - -#: ../../source/tutorials/models/MiniMax-M2.5.md:313 -msgid "" -"A: This is expected. The parser keeps `...` inside " -"`content`. If you mainly rely on the reasoning semantics of " -"`/v1/responses`, use `--reasoning-parser minimax_m2` instead." +msgstr "**问:为什么使用 `minimax_m2_append_think` 后 `reasoning` 字段经常为空?**" msgstr "答:这是预期行为。解析器将 `...` 保留在 `content` 中。如果您主要依赖 `/v1/responses` 的推理语义,请改用 `--reasoning-parser minimax_m2`。" +msgstr "**问:启动失败,出现 HCCL 端口冲突(地址已被占用)。该怎么办?**" +msgstr "答:清理旧进程并重启:`pkill -f \"vllm serve /models/MiniMax-M2.5\"`。" +msgstr "**问:如何处理 OOM 或启动不稳定的问题?**" +msgstr "答:首先减小 `--max-num-seqs` 和 `--max-num-batched-tokens`。如有必要,降低并发和负载测试压力(例如 `max-concurrency` / `num-prompts`)。" +msgstr "**问:如何选择 `--reasoning-parser`?**" +msgstr "答:本指南使用 `minimax_m2_append_think`,以便将 `...` 保留在 `content` 中。如果您主要依赖 `/v1/responses` 的推理语义,请考虑使用 `--reasoning-parser minimax_m2`。" +msgstr "**问:哪些端口必须可访问?**" -#: ../../source/tutorials/models/MiniMax-M2.5.md:315 -msgid "" -"**Q: Startup fails with HCCL port conflicts (address already bound). What" -" should I do?**" -msgstr "**问:启动失败,提示 HCCL 端口冲突(地址已绑定)。该怎么办?**" +#: ../../source/tutorials/models/MiniMax-M2.5.md:329 +msgid "A: At minimum, expose the serving port (e.g., `8000`)" +msgstr "答:至少需要暴露服务端口(例如 `8000`)" -#: ../../source/tutorials/models/MiniMax-M2.5.md:317 -msgid "" -"A: Clean up old processes and restart: `pkill -f \"vllm serve " -"/models/MiniMax-M2.5\"`." -msgstr "答:清理旧进程并重启:`pkill -f \"vllm serve /models/MiniMax-M2.5\"`。" +#~ msgid "**A2 dual-node** deployment (2× Atlas 800I A2)" +#~ msgstr "**A2 双节点**部署(2× Atlas 800I A2)" -#: ../../source/tutorials/models/MiniMax-M2.5.md:319 -msgid "**Q: How to handle OOM or unstable startup?**" -msgstr "**问:如何处理 OOM 或不稳定的启动?**" +#~ msgid "A2 (dual node, run on both nodes)" +#~ msgstr "A2(双节点,在两个节点上运行)" -#: ../../source/tutorials/models/MiniMax-M2.5.md:321 -msgid "" -"A: Reduce `--max-num-seqs` and `--max-num-batched-tokens` first. If " -"needed, reduce concurrency and load-testing pressure (e.g., `max-" -"concurrency` / `num-prompts`)." -msgstr "答:首先减少 `--max-num-seqs` 和 `--max-num-batched-tokens`。如有必要,降低并发和负载测试压力(例如 `max-concurrency` / `num-prompts`)。" +#~ msgid "A2 (dual node, tp=8 + dp=2)" +#~ msgstr "A2(双节点,tp=8 + dp=2)" -#: ../../source/tutorials/models/MiniMax-M2.5.md:323 -msgid "**Q: How should I choose `--reasoning-parser`?**" -msgstr "**问:如何选择 `--reasoning-parser`?**" +#~ msgid "" +#~ "Since cross-node tensor parallelism (TP)" +#~ " can be unstable, the dual-node " +#~ "guide uses a **tp=8 + dp=2** setup" +#~ " (8 NPUs per node, 16 NPUs " +#~ "total)." +#~ msgstr "由于跨节点张量并行(TP)可能不稳定,双节点指南使用 **tp=8 + dp=2** 的设置(每节点 8 个 NPU,共 16 个 NPU)。" -#: ../../source/tutorials/models/MiniMax-M2.5.md:325 -msgid "" -"A: This guide uses `minimax_m2_append_think` so that `...`" -" is kept in `content`. If you mainly rely on the reasoning semantics of " -"`/v1/responses`, consider using `--reasoning-parser minimax_m2`." -msgstr "答:本指南使用 `minimax_m2_append_think` 以便将 `...` 保留在 `content` 中。如果您主要依赖 `/v1/responses` 的推理语义,请考虑使用 `--reasoning-parser minimax_m2`。" +#~ msgid "Node0 (primary) startup script" +#~ msgstr "Node0(主节点)启动脚本" -#: ../../source/tutorials/models/MiniMax-M2.5.md:327 -msgid "**Q: Which ports must be accessible?**" -msgstr "**问:哪些端口必须可访问?**" +#~ msgid "" +#~ "Edit `minimax25_service_node0.sh` inside the " +#~ "node0 container, and replace the " +#~ "placeholders with your actual values:" +#~ msgstr "在 node0 容器中编辑 `minimax25_service_node0.sh`,并将占位符替换为实际值:" -#: ../../source/tutorials/models/MiniMax-M2.5.md:329 -msgid "A: At minimum, expose the serving port (e.g., `8000`)" -msgstr "答:至少需要暴露服务端口(例如 `8000`)" +#~ msgid "" +#~ "`{PrimaryNodeIP}`: the primary node's IP " +#~ "address (public/cluster network)" +#~ msgstr "`{PrimaryNodeIP}`:主节点的 IP 地址(公共/集群网络)" + +#~ msgid "" +#~ "`{NIC}`: the NIC name for the " +#~ "public/cluster network (check via `ifconfig`," +#~ " e.g., `enp67s0f0np0`)" +#~ msgstr "`{NIC}`:公共/集群网络的网卡名称(通过 `ifconfig` 查看,例如 `enp67s0f0np0`)" + +#~ msgid "" +#~ "`VLLM_TORCH_PROFILER_DIR`: optional, directory to" +#~ " store profiling outputs" +#~ msgstr "`VLLM_TORCH_PROFILER_DIR`:可选,用于存储性能分析输出的目录" #~ msgid "Node1 (secondary) startup script" #~ msgstr "Node1(从节点)启动脚本" @@ -402,7 +147,7 @@ msgstr "答:至少需要暴露服务端口(例如 `8000`)" #~ msgstr "`16096.59`" #~ msgid "**Long-context reference** (`190k/1k@bs=4`)" -#~ msgstr "**长上下文参考**(`190k/1k@bs=4`)" +#~ msgstr "**长上下文参考** (`190k/1k@bs=4`)" #~ msgid "`37.12`" #~ msgstr "`37.12`" @@ -423,7 +168,7 @@ msgstr "答:至少需要暴露服务端口(例如 `8000`)" #~ msgstr "基准测试方法" #~ msgid "Use vLLM bench for the **190k/1k, concurrency=4, 16 prompts** scenario:" -#~ msgstr "对 **190k/1k,并发=4,16 个提示** 场景使用 vLLM bench:" +#~ msgstr "对 **190k/1k,并发=4,16 个提示** 的场景使用 vLLM bench:" #~ msgid "**190k/1k, concurrency=4, 16 prompts**" #~ msgstr "**190k/1k,并发=4,16 个提示**" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/configuration/additional_config.po b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/configuration/additional_config.po index 476a831f5..5d08db773 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/configuration/additional_config.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/configuration/additional_config.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: vllm-ascend \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-10 04:47+0000\n" +"POT-Creation-Date: 2026-06-04 05:04+0000\n" "PO-Revision-Date: 2026-01-22 16:45+0800\n" "Last-Translator: Gemini\n" "Language: zh_CN\n" @@ -28,7 +28,7 @@ msgid "" "Additional configuration is a mechanism provided by vLLM to allow plugins" " to control internal behavior by themselves. VLLM Ascend uses this " "mechanism to make the project more flexible." -msgstr "附加配置是 vLLM 提供的一种机制,允许插件自行控制内部行为。vLLM Ascend 利用该机制使项目更加灵活。" +msgstr "附加配置是 vLLM 提供的一种机制,允许插件自行控制内部行为。vLLM Ascend 利用这种机制来增强项目的灵活性。" #: ../../source/user_guide/configuration/additional_config.md:5 msgid "Migration Guide" @@ -464,8 +464,8 @@ msgstr "`enable_sparse_c8`" #: ../../source/user_guide/configuration/additional_config.md msgid "" "Whether to enable KV cache C8 in DSA models (e.g., DeepSeekV3.2 and " -"GLM5). Not supported on Ascend 950 devices now" -msgstr "是否在 DSA 模型中启用 KV 缓存 C8(例如 DeepSeekV3.2 和 GLM5)。目前 Ascend 950 设备不支持。" +"GLM5). Not supported on A5 devices now" +msgstr "是否在 DSA 模型中启用 KV 缓存 C8(例如 DeepSeekV3.2 和 GLM5)。目前 A5 设备不支持。" #: ../../source/user_guide/configuration/additional_config.md msgid "`enable_mc2_hierarchy_comm`" @@ -491,20 +491,23 @@ msgstr "动态分块流水线并行的配置选项。详情请参见[动态分 msgid "" "Whether to enable balance scheduling. Can also be configured via " "`VLLM_ASCEND_BALANCE_SCHEDULING` environment variable (deprecated)." -msgstr "是否启用均衡调度。也可以通过 `VLLM_ASCEND_BALANCE_SCHEDULING` 环境变量配置(已弃用)。" +msgstr "" +"是否启用均衡调度。也可以通过 `VLLM_ASCEND_BALANCE_SCHEDULING` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "" "Whether to enable FlashComm1 optimization. Can also be configured via " "`VLLM_ASCEND_ENABLE_FLASHCOMM1` environment variable (deprecated)." -msgstr "是否启用 FlashComm1 优化。也可以通过 `VLLM_ASCEND_ENABLE_FLASHCOMM1` 环境变量配置(已弃用)。" +msgstr "" +"是否启用 FlashComm1 优化。也可以通过 `VLLM_ASCEND_ENABLE_FLASHCOMM1` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "" "Whether to enable matmul allreduce optimization. Can also be configured " "via `VLLM_ASCEND_ENABLE_MATMUL_ALLREDUCE` environment variable " "(deprecated)." -msgstr "是否启用矩阵乘法全规约优化。也可以通过 `VLLM_ASCEND_ENABLE_MATMUL_ALLREDUCE` 环境变量配置(已弃用)。" +msgstr "" +"是否启用矩阵乘法全规约优化。也可以通过 `VLLM_ASCEND_ENABLE_MATMUL_ALLREDUCE` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "`flashcomm2_parallel_size`" @@ -518,20 +521,23 @@ msgstr "`0`" msgid "" "FlashComm2 parallel size. Can also be configured via " "`VLLM_ASCEND_FLASHCOMM2_PARALLEL_SIZE` environment variable (deprecated)." -msgstr "FlashComm2 并行大小。也可以通过 `VLLM_ASCEND_FLASHCOMM2_PARALLEL_SIZE` 环境变量配置(已弃用)。" +msgstr "" +"FlashComm2 并行大小。也可以通过 `VLLM_ASCEND_FLASHCOMM2_PARALLEL_SIZE` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "" "Whether to use daemon mode for msmonitor. Can also be configured via " "`MSMONITOR_USE_DAEMON` environment variable (deprecated)." -msgstr "是否对 msmonitor 使用守护进程模式。也可以通过 `MSMONITOR_USE_DAEMON` 环境变量配置(已弃用)。" +msgstr "" +"是否对 msmonitor 使用守护进程模式。也可以通过 `MSMONITOR_USE_DAEMON` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "" "Whether to enable MLAPO (Model Layer-wise Adaptive Parallel " "Optimization). Can also be configured via `VLLM_ASCEND_ENABLE_MLAPO` " "environment variable (deprecated)." -msgstr "是否启用 MLAPO(模型逐层自适应并行优化)。也可以通过 `VLLM_ASCEND_ENABLE_MLAPO` 环境变量配置(已弃用)。" +msgstr "" +"是否启用 MLAPO(模型逐层自适应并行优化)。也可以通过 `VLLM_ASCEND_ENABLE_MLAPO` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "`1`" @@ -541,19 +547,22 @@ msgstr "`1`" msgid "" "Weight NZ mode. Can also be configured via `VLLM_ASCEND_ENABLE_NZ` " "environment variable (deprecated)." -msgstr "权重 NZ 模式。也可以通过 `VLLM_ASCEND_ENABLE_NZ` 环境变量配置(已弃用)。" +msgstr "" +"权重 NZ 模式。也可以通过 `VLLM_ASCEND_ENABLE_NZ` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "" "Whether to enable context parallelism. Can also be configured via " "`VLLM_ASCEND_ENABLE_CONTEXT_PARALLEL` environment variable (deprecated)." -msgstr "是否启用上下文并行。也可以通过 `VLLM_ASCEND_ENABLE_CONTEXT_PARALLEL` 环境变量配置(已弃用)。" +msgstr "" +"是否启用上下文并行。也可以通过 `VLLM_ASCEND_ENABLE_CONTEXT_PARALLEL` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "" "Fused MC2 configuration. Can also be configured via " "`VLLM_ASCEND_ENABLE_FUSED_MC2` environment variable (deprecated)." -msgstr "融合 MC2 配置。也可以通过 `VLLM_ASCEND_ENABLE_FUSED_MC2` 环境变量配置(已弃用)。" +msgstr "" +"融合 MC2 配置。也可以通过 `VLLM_ASCEND_ENABLE_FUSED_MC2` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "" @@ -561,8 +570,7 @@ msgid "" " `VLLM_ASCEND_FUSION_OP_TRANSPOSE_KV_CACHE_BY_BLOCK` environment variable" " (deprecated)." msgstr "" -"是否启用按块转置 KV 缓存。也可以通过 `VLLM_ASCEND_FUSION_OP_TRANSPOSE_KV_CACHE_BY_BLOCK` " -"环境变量配置(已弃用)。" +"是否启用按块转置 KV 缓存。也可以通过 `VLLM_ASCEND_FUSION_OP_TRANSPOSE_KV_CACHE_BY_BLOCK` 环境变量配置(已弃用)。" #: ../../source/user_guide/configuration/additional_config.md msgid "`enable_dsa_cp`" @@ -574,24 +582,13 @@ msgid "" " with the same architecture. This feature depends on FLASHCOMM1. Please " "ensure that FLASHCOMM1 is enabled before enabling this feature." msgstr "" -"是否为 DeepSeek V3.2、DeepSeek V4 及其他相同架构的模型启用 dsa_cp。此功能依赖于 " -"FLASHCOMM1。启用此功能前请确保已启用 FLASHCOMM1。" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "`rejection_sampler_config`" -msgstr "`rejection_sampler_config`" +"是否为 DeepSeek V3.2、DeepSeek V4 及其他相同架构的模型启用 dsa_cp。此功能依赖于 FLASHCOMM1。启用此功能前请确保已启用 FLASHCOMM1。" -#: ../../source/user_guide/configuration/additional_config.md -msgid "" -"Configuration options for rejection sampler (block verify and entropy " -"verify)." -msgstr "拒绝采样器(块验证和熵验证)的配置选项。" - -#: ../../source/user_guide/configuration/additional_config.md:104 +#: ../../source/user_guide/configuration/additional_config.md:103 msgid "The details of each configuration option are as follows:" msgstr "每个配置选项的详细说明如下:" -#: ../../source/user_guide/configuration/additional_config.md:106 +#: ../../source/user_guide/configuration/additional_config.md:105 msgid "**xlite_graph_config**" msgstr "**xlite_graph_config**" @@ -615,7 +612,7 @@ msgid "" "default, Xlite is only enabled for the decode stage." msgstr "是否在预填充和解码阶段同时启用 Xlite。默认情况下,Xlite 仅在解码阶段启用。" -#: ../../source/user_guide/configuration/additional_config.md:113 +#: ../../source/user_guide/configuration/additional_config.md:112 msgid "**weight_prefetch_config**" msgstr "**weight_prefetch_config**" @@ -640,7 +637,7 @@ msgstr "" msgid "Prefetch ratio of each weight." msgstr "各项权重的预取比例。" -#: ../../source/user_guide/configuration/additional_config.md:120 +#: ../../source/user_guide/configuration/additional_config.md:119 msgid "**finegrained_tp_config**" msgstr "**finegrained_tp_config**" @@ -676,7 +673,7 @@ msgstr "`mlp_tensor_parallel_size`" msgid "The custom tensor parallel size of mlp." msgstr "MLP 层的自定义张量并行大小。" -#: ../../source/user_guide/configuration/additional_config.md:129 +#: ../../source/user_guide/configuration/additional_config.md:128 msgid "**ascend_compilation_config**" msgstr "**ascend_compilation_config**" @@ -731,7 +728,7 @@ msgstr "`fuse_muls_add`" msgid "Whether to enable fuse_muls_add pass." msgstr "是否启用 fuse_muls_add 优化通道。" -#: ../../source/user_guide/configuration/additional_config.md:140 +#: ../../source/user_guide/configuration/additional_config.md:139 msgid "**eplb_config**" msgstr "**eplb_config**" @@ -795,7 +792,7 @@ msgstr "`num_redundant_experts`" msgid "Specify redundant experts during initialization." msgstr "在初始化阶段指定冗余专家的数量。" -#: ../../source/user_guide/configuration/additional_config.md:151 +#: ../../source/user_guide/configuration/additional_config.md:150 msgid "**profiling_chunk_config**" msgstr "**profiling_chunk_config**" @@ -849,82 +846,11 @@ msgstr "True" msgid "Enable/disable Online Calibration" msgstr "启用/禁用在线校准" -#: ../../source/user_guide/configuration/additional_config.md:160 -msgid "**rejection_sampler_config**" -msgstr "**rejection_sampler_config**" - -#: ../../source/user_guide/configuration/additional_config.md:162 -msgid "" -"**Note**: Both block verify and entropy verify improve speculative " -"decoding performance (higher acceptance rate, lower latency) at the cost " -"of reduced sampling precision. A larger `posterior_alpha` makes the " -"adjustment more aggressive — it further lowers the acceptance threshold " -"for high-entropy tokens, improving throughput but degrading output " -"quality. Users should tune these parameters based on their specific model" -" weights and application scenario to find the right trade-off between " -"performance and precision." -msgstr "" -"**注意**:块验证和熵验证都能提升推测解码性能(更高的接受率、更低的延迟),但会降低采样精度。`posterior_alpha` 值越大,调整越激进——它会进一步降低高熵 token 的接受阈值,从而提高吞吐量但降低输出质量。用户应根据具体的模型权重和应用场景调整这些参数,以找到性能与精度之间的最佳平衡。" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "`enable_block_verify`" -msgstr "`enable_block_verify`" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "" -"Whether to enable block verify mode. Block verify evaluates all draft " -"tokens as a block using cumulative probability products, which can " -"improve acceptance rate." -msgstr "是否启用块验证模式。块验证使用累积概率乘积将草稿 token 作为一个整体进行评估,可以提高接受率。" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "`enable_entropy_verify`" -msgstr "`enable_entropy_verify`" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "" -"Whether to enable entropy verify mode. Entropy verify adjusts the " -"acceptance threshold based on the entropy of the target distribution — " -"higher entropy (uncertain) tokens get a lower threshold (easier to " -"accept), while lower entropy (confident) tokens get a stricter threshold." -msgstr "是否启用熵验证模式。熵验证根据目标分布的熵调整接受阈值——高熵(不确定)token 获得更低的阈值(更容易接受),而低熵(确定)token 获得更严格的阈值。" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "`posterior_threshold`" -msgstr "`posterior_threshold`" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "`0.95`" -msgstr "`0.95`" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "" -"Upper bound for the entropy-adjusted acceptance threshold. Must be in (0," -" 1]. The effective threshold is `min(exp(-entropy * posterior_alpha), " -"posterior_threshold)`." -msgstr "熵调整后接受阈值的上限。必须在 (0, 1] 范围内。有效阈值为 `min(exp(-entropy * posterior_alpha), posterior_threshold)`。" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "`posterior_alpha`" -msgstr "`posterior_alpha`" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "`0.4`" -msgstr "`0.4`" - -#: ../../source/user_guide/configuration/additional_config.md -msgid "" -"Scaling factor for entropy in the threshold computation. Must be >= 0. " -"Higher values make the threshold more sensitive to entropy — high-entropy" -" tokens become much easier to accept, improving performance but reducing " -"precision." -msgstr "阈值计算中熵的缩放因子。必须 >= 0。值越大,阈值对熵越敏感——高熵 token 更容易被接受,从而提高性能但降低精度。" - -#: ../../source/user_guide/configuration/additional_config.md:171 +#: ../../source/user_guide/configuration/additional_config.md:159 msgid "Example" msgstr "示例" -#: ../../source/user_guide/configuration/additional_config.md:173 +#: ../../source/user_guide/configuration/additional_config.md:161 msgid "An example of additional configuration is as follows:" msgstr "附加配置的一个示例如下:" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/batch_invariance.po b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/batch_invariance.po index 1e0884be5..c6d542d86 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/batch_invariance.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/batch_invariance.po @@ -1,8 +1,14 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2025, vllm-ascend team +# This file is distributed under the same license as the vllm-ascend +# package. +# FIRST AUTHOR , 2026. +# msgid "" msgstr "" "Project-Id-Version: vllm-ascend \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-10 04:47+0000\n" +"POT-Creation-Date: 2026-06-07 05:04+0000\n" "PO-Revision-Date: 2026-04-10 18:37+0800\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -82,15 +88,12 @@ msgstr "" "通信的批次不变性。未来我们将支持其他 NPU。" #: ../../source/user_guide/feature_guide/batch_invariance.md:24 -msgid "Software Requirements" -msgstr "软件要求" - -#: ../../source/user_guide/feature_guide/batch_invariance.md:26 msgid "" "Batch invariance requires a custom operator library for Atlas A2 " "inference products. We will release the customized operator library in " "future versions." -msgstr "批次不变性需要 Atlas A2 推理产品的自定义算子库。我们将在未来版本中发布该自定义算子库。" +msgstr "" +"批次不变性需要 Atlas A2 推理产品的自定义算子库。我们将在未来版本中发布该自定义算子库。" #: ../../source/user_guide/feature_guide/batch_invariance.md:29 msgid "Enabling Batch Invariance" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/graph_mode.po b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/graph_mode.po index 50a00f806..2aa54279c 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/graph_mode.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/graph_mode.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2026-06-10 04:47+0000\n" +"POT-Creation-Date: 2026-06-04 05:04+0000\n" "PO-Revision-Date: 2026-01-22 16:30+0800\n" "Last-Translator: Gemini\n" "Language: zh_CN\n" @@ -37,12 +37,12 @@ msgid "" msgstr "vLLM 已提供了通用的图模式架构、模式定义和编译集成。关于这些上游概念,请参阅:" #: ../../source/user_guide/feature_guide/graph_mode.md:9 -#: ../../source/user_guide/feature_guide/graph_mode.md:293 +#: ../../source/user_guide/feature_guide/graph_mode.md:280 msgid "[CUDA Graphs](https://docs.vllm.ai/en/latest/design/cuda_graphs/)" msgstr "[CUDA 图](https://docs.vllm.ai/en/latest/design/cuda_graphs/)" #: ../../source/user_guide/feature_guide/graph_mode.md:10 -#: ../../source/user_guide/feature_guide/graph_mode.md:294 +#: ../../source/user_guide/feature_guide/graph_mode.md:281 msgid "[torch.compile](https://docs.vllm.ai/en/latest/design/torch_compile/)" msgstr "[torch.compile](https://docs.vllm.ai/en/latest/design/torch_compile/)" @@ -168,8 +168,7 @@ msgid "" " The compile-time path follows PIECEWISE compilation, while the runtime " "may still use full-graph behavior for uniform decode batches." msgstr "" -"**FULL_AND_PIECEWISE**:默认模式,与上游 vLLM 策略相同。编译时路径遵循 PIECEWISE " -"编译,而运行时对于均匀解码批次仍可能使用全图行为。" +"**FULL_AND_PIECEWISE**:默认模式,与上游 vLLM 策略相同。编译时路径遵循 PIECEWISE 编译,而运行时对于均匀解码批次仍可能使用全图行为。" #: ../../source/user_guide/feature_guide/graph_mode.md:36 msgid "" @@ -288,16 +287,16 @@ msgid "Basic usage" msgstr "基本用法" #: ../../source/user_guide/feature_guide/graph_mode.md:55 -#: ../../source/user_guide/feature_guide/graph_mode.md:144 -#: ../../source/user_guide/feature_guide/graph_mode.md:182 -#: ../../source/user_guide/feature_guide/graph_mode.md:234 +#: ../../source/user_guide/feature_guide/graph_mode.md:133 +#: ../../source/user_guide/feature_guide/graph_mode.md:171 +#: ../../source/user_guide/feature_guide/graph_mode.md:223 msgid "Offline example:" msgstr "离线推理示例:" #: ../../source/user_guide/feature_guide/graph_mode.md:64 -#: ../../source/user_guide/feature_guide/graph_mode.md:160 -#: ../../source/user_guide/feature_guide/graph_mode.md:199 -#: ../../source/user_guide/feature_guide/graph_mode.md:254 +#: ../../source/user_guide/feature_guide/graph_mode.md:149 +#: ../../source/user_guide/feature_guide/graph_mode.md:188 +#: ../../source/user_guide/feature_guide/graph_mode.md:243 msgid "Online example:" msgstr "在线服务示例:" @@ -366,48 +365,59 @@ msgstr "声明的支持" msgid "Practical meaning" msgstr "实际含义" -#: ../../source/user_guide/feature_guide/graph_mode.md:111 -msgid "Troubleshooting capture resource exhaustion" -msgstr "捕获资源耗尽问题排查" +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`attention_v1`" +msgstr "`attention_v1`" -#: ../../source/user_guide/feature_guide/graph_mode.md:113 -msgid "" -"If ACLGraph capture fails because the configured graph sizes exceed the " -"runtime resources available on the current stack, vLLM Ascend now raises " -"a dedicated error with mitigation guidance. In practice, the most useful " -"actions are:" -msgstr "如果 ACLGraph 捕获失败,因为配置的图大小超过了当前栈上可用的运行时资源,vLLM Ascend 现在会引发一个带有缓解指导的专用错误。实际上,最有用的操作是:" +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`ALWAYS`" +msgstr "`ALWAYS`" -#: ../../source/user_guide/feature_guide/graph_mode.md:115 -msgid "upgrade to a newer HDK/CANN stack if one is available;" -msgstr "1.升级到可用的较新 HDK/CANN 栈;" +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "Supports graph execution for mixed prefill/decode batches" +msgstr "支持混合预填充/解码批次的图执行" -#: ../../source/user_guide/feature_guide/graph_mode.md:116 -msgid "reduce `cudagraph_capture_sizes` or `max_cudagraph_capture_size`;" -msgstr "2.减少 `cudagraph_capture_sizes` 或 `max_cudagraph_capture_size`;" +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`context_parallel/attention_cp`" +msgstr "`context_parallel/attention_cp`" -#: ../../source/user_guide/feature_guide/graph_mode.md:117 +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`mla_v1`" +msgstr "`mla_v1`" + +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`UNIFORM_BATCH`" +msgstr "`UNIFORM_BATCH`" + +#: ../../source/user_guide/feature_guide/graph_mode.md msgid "" -"prefer `FULL` or `FULL_DECODE_ONLY` when the workload is mostly uniform " -"decode;" -msgstr "3.当工作负载主要是均匀解码时,优先使用 `FULL` 或 `FULL_DECODE_ONLY`;" +"Graph execution is limited to uniform batches; full graph is more " +"restricted" +msgstr "图执行仅限于均匀批次;全图模式限制更多" + +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`context_parallel/mla_cp`" +msgstr "`context_parallel/mla_cp`" -#: ../../source/user_guide/feature_guide/graph_mode.md:118 -msgid "temporarily disable graph mode to confirm the issue is capture-related." -msgstr "4.临时禁用图模式以确认问题与捕获相关。" +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`sfa_v1`" +msgstr "`sfa_v1`" + +#: ../../source/user_guide/feature_guide/graph_mode.md +msgid "`context_parallel/sfa_cp`" +msgstr "`context_parallel/sfa_cp`" -#: ../../source/user_guide/feature_guide/graph_mode.md:120 +#: ../../source/user_guide/feature_guide/graph_mode.md:109 msgid "" -"This is most likely to appear in `PIECEWISE` or `FULL_AND_PIECEWISE` " -"configurations because those paths tend to capture more graphs than " -"uniform full-graph decode." -msgstr "这最可能出现在 `PIECEWISE` 或 `FULL_AND_PIECEWISE` 配置中,因为这些路径往往比均匀的全图解码捕获更多的图。" +"This is why the effective graph mode on Ascend may differ from the mode " +"requested in configuration." +msgstr "这就是为什么 Ascend 上生效的图模式可能与配置中请求的模式不同。" -#: ../../source/user_guide/feature_guide/graph_mode.md:122 +#: ../../source/user_guide/feature_guide/graph_mode.md:111 msgid "Using Npugraph_ex" msgstr "使用 Npugraph_ex" -#: ../../source/user_guide/feature_guide/graph_mode.md:124 +#: ../../source/user_guide/feature_guide/graph_mode.md:113 msgid "" "As introduced in the [RFC](https://github.com/vllm-project/vllm-" "ascend/issues/4715), Npugraph_ex is a compile-time FX graph optimization " @@ -421,11 +431,11 @@ msgstr "" "图之前对其进行优化。其性能优势主要来自于将多个算子融合为单个内核(例如,add + rms_norm → " "npu_add_rms_norm),以减少内核启动开销。" -#: ../../source/user_guide/feature_guide/graph_mode.md:126 +#: ../../source/user_guide/feature_guide/graph_mode.md:115 msgid "Default behavior" msgstr "默认行为" -#: ../../source/user_guide/feature_guide/graph_mode.md:128 +#: ../../source/user_guide/feature_guide/graph_mode.md:117 msgid "" "Npugraph_ex is **enabled by default** when `cudagraph_mode` is `FULL` or " "`FULL_DECODE_ONLY`. It is automatically disabled in `PIECEWISE` or `NONE`" @@ -434,29 +444,29 @@ msgstr "" "当 `cudagraph_mode` 为 `FULL` 或 `FULL_DECODE_ONLY` 时,Npugraph_ex **默认启用**。在" " `PIECEWISE` 或 `NONE` 模式下会自动禁用。" -#: ../../source/user_guide/feature_guide/graph_mode.md:130 +#: ../../source/user_guide/feature_guide/graph_mode.md:119 msgid "" "This means for most users, Npugraph_ex is active without any explicit " "configuration:" msgstr "这意味着对于大多数用户,Npugraph_ex 无需任何显式配置即可激活:" -#: ../../source/user_guide/feature_guide/graph_mode.md:140 +#: ../../source/user_guide/feature_guide/graph_mode.md:129 msgid "Explicit configuration" msgstr "显式配置" -#: ../../source/user_guide/feature_guide/graph_mode.md:142 +#: ../../source/user_guide/feature_guide/graph_mode.md:131 msgid "To explicitly control Npugraph_ex:" msgstr "要显式控制 Npugraph_ex:" -#: ../../source/user_guide/feature_guide/graph_mode.md:167 +#: ../../source/user_guide/feature_guide/graph_mode.md:156 msgid "To disable Npugraph_ex explicitly:" msgstr "要显式禁用 Npugraph_ex:" -#: ../../source/user_guide/feature_guide/graph_mode.md:174 +#: ../../source/user_guide/feature_guide/graph_mode.md:163 msgid "Static kernel compilation" msgstr "静态内核编译" -#: ../../source/user_guide/feature_guide/graph_mode.md:176 +#: ../../source/user_guide/feature_guide/graph_mode.md:165 msgid "" "Static kernel compilation is an **optional** feature that pre-compiles " "operator binaries with fixed shapes at compile time, reducing runtime " @@ -464,7 +474,7 @@ msgid "" " by default** and must be explicitly enabled." msgstr "静态内核编译是一个**可选**功能,它在编译时预编译具有固定形状的算子二进制文件,从而减少静态或接近静态形状网络的运行时开销。它**默认禁用**,需要显式启用。" -#: ../../source/user_guide/feature_guide/graph_mode.md:179 +#: ../../source/user_guide/feature_guide/graph_mode.md:168 msgid "" "Enabling static kernel triggers a compilation pass during the graph " "capture phase at service startup. This may add **several minutes to tens " @@ -473,17 +483,17 @@ msgid "" "processing is not affected." msgstr "启用静态内核会在服务启动时的图捕获阶段触发编译过程。根据待编译算子的数量和模型复杂度,这可能会增加**几分钟到几十分钟**的启动时间。一旦完成,后续的请求处理不会受到影响。" -#: ../../source/user_guide/feature_guide/graph_mode.md:206 +#: ../../source/user_guide/feature_guide/graph_mode.md:195 msgid "Verifying static kernel is active" msgstr "验证静态内核是否生效" -#: ../../source/user_guide/feature_guide/graph_mode.md:208 +#: ../../source/user_guide/feature_guide/graph_mode.md:197 msgid "" "The recommended way to verify static kernel is in effect is through " "**Ascend Profiling**:" msgstr "推荐通过 **Ascend Profiling** 来验证静态内核是否生效:" -#: ../../source/user_guide/feature_guide/graph_mode.md:210 +#: ../../source/user_guide/feature_guide/graph_mode.md:199 msgid "" "Collect a profiling trace of your running model using [Ascend PyTorch " "Profiler](https://www.hiascend.com/document/detail/zh/Pytorch/2600/apiref/torchnpuCustomsapi/docs/zh/custom_APIs" @@ -495,11 +505,11 @@ msgstr "" "/torch_npu-profiler/torch_npu-profiler-profile.md) (`torch_npu.profiler`)" " 收集运行模型的 profiling 跟踪数据。" -#: ../../source/user_guide/feature_guide/graph_mode.md:211 +#: ../../source/user_guide/feature_guide/graph_mode.md:200 msgid "Open the generated `op_statistic.csv` file." msgstr "打开生成的 `op_statistic.csv` 文件。" -#: ../../source/user_guide/feature_guide/graph_mode.md:212 +#: ../../source/user_guide/feature_guide/graph_mode.md:201 msgid "" "Look for operators whose `op_type` or `name` column contains the keyword " "**`static_kernel`**. If such entries exist, static kernel compilation has" @@ -508,20 +518,20 @@ msgstr "" "查找 `op_type` 或 `name` 列中包含关键字 **`static_kernel`** " "的算子。如果存在此类条目,则说明静态内核编译已对这些算子生效。" -#: ../../source/user_guide/feature_guide/graph_mode.md:214 +#: ../../source/user_guide/feature_guide/graph_mode.md:203 msgid "" "During the compilation phase, you will see a Python warning (visible by " "default):" msgstr "在编译阶段,您会看到一个 Python 警告(默认可见):" -#: ../../source/user_guide/feature_guide/graph_mode.md:220 +#: ../../source/user_guide/feature_guide/graph_mode.md:209 msgid "" "This confirms that compilation has been triggered. The absence of this " "message means static kernel was not enabled or the cached result was " "reused directly." msgstr "这确认了编译已被触发。如果没有此消息,则表示静态内核未启用或直接复用了缓存结果。" -#: ../../source/user_guide/feature_guide/graph_mode.md:222 +#: ../../source/user_guide/feature_guide/graph_mode.md:211 msgid "" "For more details about Npugraph_ex, see the [npugraph_ex " "guide](https://www.hiascend.com/document/detail/zh/Pytorch/2600/modthirdparty/torchairuseguide/docs/zh/overview.md)." @@ -529,11 +539,11 @@ msgstr "" "有关 Npugraph_ex 的更多详细信息,请参阅 [npugraph_ex " "指南](https://www.hiascend.com/document/detail/zh/Pytorch/2600/modthirdparty/torchairuseguide/docs/zh/overview.md)。" -#: ../../source/user_guide/feature_guide/graph_mode.md:224 +#: ../../source/user_guide/feature_guide/graph_mode.md:213 msgid "Using XliteGraph" msgstr "使用 XliteGraph" -#: ../../source/user_guide/feature_guide/graph_mode.md:226 +#: ../../source/user_guide/feature_guide/graph_mode.md:215 msgid "" "XliteGraph is an optional path for Llama, Qwen dense series models, Qwen " "MoE series models, and Qwen3-VL. It requires Xlite to be installed and " @@ -542,11 +552,11 @@ msgstr "" "XliteGraph 是 Llama、Qwen 密集系列模型、Qwen MoE 系列模型和 Qwen3-VL 的可选路径。它需要安装 Xlite " "并通过 `xlite_graph_config` 进行配置。" -#: ../../source/user_guide/feature_guide/graph_mode.md:228 +#: ../../source/user_guide/feature_guide/graph_mode.md:217 msgid "Install Xlite first:" msgstr "首先安装 Xlite:" -#: ../../source/user_guide/feature_guide/graph_mode.md:262 +#: ../../source/user_guide/feature_guide/graph_mode.md:251 msgid "" "For more details about Xlite, see the [Xlite " "README](https://atomgit.com/openeuler/GVirt/blob/master/xlite/README.md)." @@ -554,70 +564,53 @@ msgstr "" "有关 Xlite 的更多详细信息,请参阅 [Xlite " "README](https://atomgit.com/openeuler/GVirt/blob/master/xlite/README.md)。" -#: ../../source/user_guide/feature_guide/graph_mode.md:264 +#: ../../source/user_guide/feature_guide/graph_mode.md:253 msgid "Common Limitations and Caveats" msgstr "常见限制与注意事项" -#: ../../source/user_guide/feature_guide/graph_mode.md:266 +#: ../../source/user_guide/feature_guide/graph_mode.md:255 msgid "" "XliteGraph should be treated as an alternative graph path, not as a drop-" "in replacement for ACLGraph in all scenarios." msgstr "XliteGraph 应被视为一种替代的图路径,而非在所有场景下都能直接替代 ACLGraph。" -#: ../../source/user_guide/feature_guide/graph_mode.md:267 +#: ../../source/user_guide/feature_guide/graph_mode.md:256 msgid "" "Model and backend coverage is still evolving, so a configuration that " "works for one model family may not yet be recommended for another." msgstr "模型和后端的覆盖范围仍在不断发展,因此适用于一个模型系列的配置可能尚不推荐用于另一个模型系列。" -#: ../../source/user_guide/feature_guide/graph_mode.md:268 +#: ../../source/user_guide/feature_guide/graph_mode.md:257 msgid "" "Encoder-decoder models currently do not keep `FULL_AND_PIECEWISE`; on " "Ascend they fall back to `PIECEWISE` or `NONE` depending on compilation " "support." msgstr "" -"编码器-解码器模型当前不保持 `FULL_AND_PIECEWISE`;在 Ascend 上,它们会根据编译支持情况回退到 `PIECEWISE`" -" 或 `NONE`。" +"编码器-解码器模型当前不保持 `FULL_AND_PIECEWISE`;在 Ascend 上,它们会根据编译支持情况回退到 `PIECEWISE` 或 `NONE`。" -#: ../../source/user_guide/feature_guide/graph_mode.md:270 +#: ../../source/user_guide/feature_guide/graph_mode.md:259 msgid "Fallback to Eager Mode" msgstr "回退到 Eager 模式" -#: ../../source/user_guide/feature_guide/graph_mode.md:272 +#: ../../source/user_guide/feature_guide/graph_mode.md:261 msgid "" "If you encounter issues with graph mode, you can temporarily fall back to" " eager mode by setting `enforce_eager=True`." msgstr "如果您遇到图模式的问题,可以通过设置 `enforce_eager=True` 临时回退到 eager 模式。" -#: ../../source/user_guide/feature_guide/graph_mode.md:274 -msgid "" -"If ACL graph capture fails with the confirmed stream-resource signature " -"in the error text, such as `207008` together with `Stream resources are " -"insufficient` or `Insufficient_Stream_Resources`, vLLM Ascend will re-" -"raise that capture failure with targeted mitigation guidance. In " -"practice, the main levers are: upgrading to a newer HDK/CANN stack, " -"reducing `cudagraph_capture_sizes`, lowering " -"`max_cudagraph_capture_size`, or preferring `FULL` / `FULL_DECODE_ONLY` " -"when the workload is mostly uniform decode." -msgstr "" -"如果 ACL 图捕获失败,错误文本中包含确认的流资源签名,例如 `207008` 与 `Stream resources are " -"insufficient` 或 `Insufficient_Stream_Resources`,vLLM Ascend 将重新引发该捕获失败,并附带针对性的缓解指导。" -"实际上,主要手段是:升级到较新的 HDK/CANN 栈,减少 `cudagraph_capture_sizes`,降低 " -"`max_cudagraph_capture_size`,或者在工作负载主要是均匀解码时优先使用 `FULL` / `FULL_DECODE_ONLY`。" - -#: ../../source/user_guide/feature_guide/graph_mode.md:276 +#: ../../source/user_guide/feature_guide/graph_mode.md:263 msgid "**Offline example:**" msgstr "**离线推理示例:**" -#: ../../source/user_guide/feature_guide/graph_mode.md:285 +#: ../../source/user_guide/feature_guide/graph_mode.md:272 msgid "**Online example:**" msgstr "**在线服务示例:**" -#: ../../source/user_guide/feature_guide/graph_mode.md:291 +#: ../../source/user_guide/feature_guide/graph_mode.md:278 msgid "References" msgstr "参考资料" -#: ../../source/user_guide/feature_guide/graph_mode.md:295 +#: ../../source/user_guide/feature_guide/graph_mode.md:282 msgid "" "[Xlite " "README](https://atomgit.com/openeuler/GVirt/blob/master/xlite/README.md)" @@ -625,7 +618,7 @@ msgstr "" "[Xlite " "README](https://atomgit.com/openeuler/GVirt/blob/master/xlite/README.md)" -#: ../../source/user_guide/feature_guide/graph_mode.md:296 +#: ../../source/user_guide/feature_guide/graph_mode.md:283 msgid "" "[Npugraph_ex " "guide](https://www.hiascend.com/document/detail/zh/Pytorch/2600/modthirdparty/torchairuseguide/docs/zh/overview.md)" @@ -633,11 +626,11 @@ msgstr "" "[Npugraph_ex " "指南](https://www.hiascend.com/document/detail/zh/Pytorch/2600/modthirdparty/torchairuseguide/docs/zh/overview.md)" -#: ../../source/user_guide/feature_guide/graph_mode.md:297 +#: ../../source/user_guide/feature_guide/graph_mode.md:284 msgid "[Npugraph_ex RFC](https://github.com/vllm-project/vllm-ascend/issues/4715)" msgstr "[Npugraph_ex RFC](https://github.com/vllm-project/vllm-ascend/issues/4715)" -#: ../../source/user_guide/feature_guide/graph_mode.md:298 +#: ../../source/user_guide/feature_guide/graph_mode.md:285 msgid "" "[ACL Graph Developer " "Guide](../../developer_guide/Design_Documents/ACL_Graph.md)" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/large_scale_ep.po b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/large_scale_ep.po index 2d38b840a..d9a163040 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/large_scale_ep.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/large_scale_ep.po @@ -270,11 +270,11 @@ msgstr "基准测试" #: ../../source/user_guide/feature_guide/large_scale_ep.md:366 msgid "" "We recommend using aisbench tool to assess performance. " -"[aisbench](https://github.com/AISBench/benchmark). Execute the following " +"[aisbench](https://gitee.com/aisbench/benchmark). Execute the following " "commands to install aisbench" msgstr "" "我们建议使用 aisbench " -"工具来评估性能。[aisbench](https://github.com/AISBench/benchmark)。执行以下命令安装 " +"工具来评估性能。[aisbench](https://gitee.com/aisbench/benchmark)。执行以下命令安装 " "aisbench" #: ../../source/user_guide/feature_guide/large_scale_ep.md:374 @@ -307,10 +307,10 @@ msgstr "以 gsm8k 数据集为例,执行以下命令评估性能。" #: ../../source/user_guide/feature_guide/large_scale_ep.md:415 msgid "" "For more details on commands and parameters for aisbench, refer to " -"[aisbench](https://github.com/AISBench/benchmark)" +"[aisbench](https://gitee.com/aisbench/benchmark)" msgstr "" "有关 aisbench 命令和参数的更多详细信息,请参阅 " -"[aisbench](https://github.com/AISBench/benchmark)" +"[aisbench](https://gitee.com/aisbench/benchmark)" #: ../../source/user_guide/feature_guide/large_scale_ep.md:417 msgid "Prefill & Decode Configuration Details" diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/speculative_decoding.po b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/speculative_decoding.po index 1ad9104a1..7e8005cce 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/speculative_decoding.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/feature_guide/speculative_decoding.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: vllm-ascend \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-06-10 04:47+0000\n" +"POT-Creation-Date: 2026-05-07 02:15+0000\n" "PO-Revision-Date: 2026-01-22 16:35+0800\n" "Last-Translator: Gemini\n" "Language: zh_CN\n" @@ -44,7 +44,6 @@ msgstr "以下代码配置 vLLM Ascend 使用推测解码,其中候选 token #: ../../source/user_guide/feature_guide/speculative_decoding.md:42 #: ../../source/user_guide/feature_guide/speculative_decoding.md:127 #: ../../source/user_guide/feature_guide/speculative_decoding.md:162 -#: ../../source/user_guide/feature_guide/speculative_decoding.md:263 msgid "Offline inference" msgstr "离线推理" @@ -147,7 +146,6 @@ msgstr "" "[Multi_Token_Prediction](https://docs.vllm.ai/projects/ascend/en/latest/user_guide/feature_guide/Multi_Token_Prediction.html)" #: ../../source/user_guide/feature_guide/speculative_decoding.md:96 -#: ../../source/user_guide/feature_guide/speculative_decoding.md:254 msgid "Online inference" msgstr "在线推理" @@ -203,10 +201,7 @@ msgid "" "states from specified layers of the target model and saves them to disk. " "This is primarily used for collecting training data for EAGLE-style draft" " models." -msgstr "" -"`extract_hidden_states` " -"方法是一种特殊的推测解码模式,不执行实际的推测。相反,它从目标模型的指定层提取隐藏状态并将其保存到磁盘。这主要用于收集 EAGLE " -"风格草稿模型的训练数据。" +msgstr "`extract_hidden_states` 方法是一种特殊的推测解码模式,不执行实际的推测。相反,它从目标模型的指定层提取隐藏状态并将其保存到磁盘。这主要用于收集 EAGLE 风格草稿模型的训练数据。" #: ../../source/user_guide/feature_guide/speculative_decoding.md:159 msgid "" @@ -229,9 +224,7 @@ msgid "" "**`eagle_aux_hidden_state_layer_ids`**: List of layer indices from which " "to extract hidden states. For example, `[2, 18, 34]` extracts from layers" " 2, 18, and 34." -msgstr "" -"**`eagle_aux_hidden_state_layer_ids`**:要从中提取隐藏状态的层索引列表。例如,`[2, 18, 34]` " -"从第 2、18 和 34 层提取。" +msgstr "**`eagle_aux_hidden_state_layer_ids`**:要从中提取隐藏状态的层索引列表。例如,`[2, 18, 34]` 从第 2、18 和 34 层提取。" #: ../../source/user_guide/feature_guide/speculative_decoding.md:223 msgid "" @@ -247,90 +240,4 @@ msgstr "**`kv_role`**:对于提取模式,必须设置为 `\"kv_producer\"` msgid "" "**`shared_storage_path`**: Directory where hidden states will be saved as" " `.safetensors` files (one per request)." -msgstr "**`shared_storage_path`**:隐藏状态将保存为 `.safetensors` 文件的目录(每个请求一个文件)。" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:229 -msgid "Block Verify and Entropy Verify" -msgstr "块验证与熵验证" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:231 -msgid "" -"vLLM Ascend provides two optional optimizations for the rejection sampler" -" in speculative decoding: **Block Verify** and **Entropy Verify**. These " -"features trade a small amount of output precision for improved inference " -"throughput." -msgstr "vLLM Ascend 为推测解码中的拒绝采样器提供了两种可选优化:**块验证**和**熵验证**。这些功能以少量输出精度为代价,换取推理吞吐量的提升。" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:233 -msgid "" -"[!WARNING] Both Block Verify and Entropy Verify modify the token " -"acceptance criteria and may cause minor precision degradation (e.g., " -"slightly different output tokens compared to the standard rejection " -"sampler). Evaluate the quality impact on your specific workload before " -"enabling them in production." -msgstr "[!警告] 块验证和熵验证都会修改 token 接受标准,可能导致轻微的精度下降(例如,与标准拒绝采样器相比,输出 token 略有不同)。在生产环境中启用它们之前,请评估对您特定工作负载的质量影响。" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:236 -msgid "Block Verify" -msgstr "块验证" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:238 -msgid "" -"Block Verify evaluates all draft tokens as a block using cumulative " -"probability products, rather than checking each token independently. This" -" can improve the acceptance rate and reduce the overhead of rejection " -"sampling, especially when `num_speculative_tokens >= 3`." -msgstr "块验证使用累积概率乘积将所有草稿 token 作为一个整体进行评估,而不是独立检查每个 token。这可以提高接受率并减少拒绝采样的开销,尤其是在 `num_speculative_tokens >= 3` 时。" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:240 -msgid "Entropy Verify" -msgstr "熵验证" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:242 -msgid "" -"Entropy Verify adjusts the acceptance threshold based on the entropy of " -"the target distribution:" -msgstr "熵验证根据目标分布的熵调整接受阈值:" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:244 -msgid "" -"**High entropy** (uncertain distribution) → lower effective threshold → " -"more tokens accepted" -msgstr "**高熵**(不确定分布)→ 较低的有效阈值 → 接受更多 token" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:245 -msgid "" -"**Low entropy** (confident distribution) → higher effective threshold → " -"stricter rejection" -msgstr "**低熵**(确定分布)→ 较高的有效阈值 → 更严格的拒绝" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:247 -msgid "This entropy-aware threshold is controlled by two parameters:" -msgstr "这种熵感知阈值由两个参数控制:" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:249 -msgid "" -"**`posterior_threshold`** (default: `0.95`): The upper bound of the " -"modified threshold. Even when entropy is very low, the effective " -"threshold will not exceed this value." -msgstr "**`posterior_threshold`**(默认值:`0.95`):修改后阈值的上限。即使熵非常低,有效阈值也不会超过此值。" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:250 -msgid "" -"**`posterior_alpha`** (default: `0.4`): Controls how strongly entropy " -"influences the threshold. A higher alpha makes the threshold more " -"sensitive to entropy changes, resulting in a higher acceptance rate for " -"speculative tokens but also greater precision loss. You need to tune this" -" value based on your specific model and dataset." -msgstr "**`posterior_alpha`**(默认值:`0.4`):控制熵影响阈值的强度。较高的 alpha 使阈值对熵变化更敏感,导致推测 token 的接受率更高,但精度损失也更大。您需要根据具体的模型和数据集调整此值。" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:252 -msgid "Usage" -msgstr "用法" - -#: ../../source/user_guide/feature_guide/speculative_decoding.md:279 -msgid "" -"Both features can be enabled independently or together. When used " -"together, the cumulative acceptance from Block Verify is combined with " -"the entropy-adjusted threshold from Entropy Verify." -msgstr "这两个功能可以独立启用,也可以同时启用。同时使用时,块验证的累积接受率将与熵验证的熵调整阈值相结合。" \ No newline at end of file +msgstr "**`shared_storage_path`**:隐藏状态将保存为 `.safetensors` 文件的目录(每个请求一个文件)。" \ No newline at end of file diff --git a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/release_notes.po b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/release_notes.po index c611d8a0c..b6ee78c89 100644 --- a/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/release_notes.po +++ b/docs/source/locale/zh_CN/LC_MESSAGES/user_guide/release_notes.po @@ -91,13 +91,13 @@ msgstr "" #: ../../source/user_guide/release_notes.md:10 msgid "" -"**Ascend 950 Products and XLite Quantization Expansion**: Added MXFP4 flatquant with row " -"parallelism for Ascend 950 Products and expanded XLite support to GLM-4.7 W8A8 " +"**A5 and XLite Quantization Expansion**: Added MXFP4 flatquant with row " +"parallelism for Ascend A5 and expanded XLite support to GLM-4.7 W8A8 " "quantization. [#9391](https://github.com/vllm-project/vllm-" "ascend/pull/9391) [#9415](https://github.com/vllm-project/vllm-" "ascend/pull/9415)" msgstr "" -"**Ascend 950 系列产品 与 XLite 量化扩展**:为 Ascend 950 系列产品添加了支持行并行的 MXFP4 flatquant,并将 XLite 支持扩展到 " +"**A5 与 XLite 量化扩展**:为 Ascend A5 添加了支持行并行的 MXFP4 flatquant,并将 XLite 支持扩展到 " "GLM-4.7 W8A8 量化。[#9391](https://github.com/vllm-project/vllm-" "ascend/pull/9391) [#9415](https://github.com/vllm-project/vllm-" "ascend/pull/9415)" @@ -199,19 +199,19 @@ msgstr "" #: ../../source/user_guide/release_notes.md:24 msgid "" -"Enabled MXFP4 flatquant and row parallel support on Ascend 950 Products. " +"Enabled MXFP4 flatquant and row parallel support on Ascend A5. " "[#9391](https://github.com/vllm-project/vllm-ascend/pull/9391)" msgstr "" -"在 Ascend 950 系列产品上启用了 MXFP4 flatquant 和行并行支持。[#9391](https://github.com/vllm-" +"在 Ascend A5 上启用了 MXFP4 flatquant 和行并行支持。[#9391](https://github.com/vllm-" "project/vllm-ascend/pull/9391)" #: ../../source/user_guide/release_notes.md:25 msgid "" "Enabled MC2 dispatch and combine support for MXFP4/MXFP8 quantization on " -"Ascend 950 Products. [#9365](https://github.com/vllm-project/vllm-ascend/pull/9365)" +"Ascend A5. [#9365](https://github.com/vllm-project/vllm-ascend/pull/9365)" " [#9328](https://github.com/vllm-project/vllm-ascend/pull/9328)" msgstr "" -"在 Ascend 950 系列产品上启用了 MXFP4/MXFP8 量化的 MC2 分发与合并支持。[#9365](https://github.com" +"在 Ascend A5 上启用了 MXFP4/MXFP8 量化的 MC2 分发与合并支持。[#9365](https://github.com" "/vllm-project/vllm-ascend/pull/9365) [#9328](https://github.com/vllm-" "project/vllm-ascend/pull/9328)" @@ -2840,10 +2840,10 @@ msgstr "" #: ../../source/user_guide/release_notes.md:397 msgid "" -"DeepSeek models are now supported on Ascend 950 Products through new MLA operators. " +"DeepSeek models are now supported on A5 through new MLA operators. " "[#7232](https://github.com/vllm-project/vllm-ascend/pull/7232)" msgstr "" -"通过新的 MLA 算子,DeepSeek 模型现已在 Ascend 950 系列产品上得到支持。[#7232](https://github.com/vllm-" +"通过新的 MLA 算子,DeepSeek 模型现已在 A5 上得到支持。[#7232](https://github.com/vllm-" "project/vllm-ascend/pull/7232)" #: ../../source/user_guide/release_notes.md:402 diff --git a/docs/source/tutorials/features/dynamic_chunked_pipeline_parallel.md b/docs/source/tutorials/features/dynamic_chunked_pipeline_parallel.md index 9855f09ab..25955184a 100644 --- a/docs/source/tutorials/features/dynamic_chunked_pipeline_parallel.md +++ b/docs/source/tutorials/features/dynamic_chunked_pipeline_parallel.md @@ -4,9 +4,7 @@ vLLM-Ascend supports Dynamic Chunked Pipeline Parallel (CPP) for optimizing prefill performance in Pipeline Parallelism scenarios. This guide demonstrates deployment with DeepSeek-V3.1 on 1 Atlas 800T A3 server (64G × 16). -For configuration details, see the [Feature Guide](../../user_guide/feature_guide/dynamic_chunk_pipeline_parallel.md). - -For design details, see the [Design Document](../../developer_guide/Design_Documents/dynamic_chunked_pipeline_parallel.md). +For configuration details, see the [Feature Guide](../../user_guide/feature_guide/dynamic_chunk_pipeline_parallel.md). For design details, see the [Design Document](../../developer_guide/Design_Documents/dynamic_chunked_pipeline_parallel.md). ## Environment Preparation diff --git a/docs/source/tutorials/features/index.md b/docs/source/tutorials/features/index.md index fb1193f9e..95cc6577b 100644 --- a/docs/source/tutorials/features/index.md +++ b/docs/source/tutorials/features/index.md @@ -5,7 +5,6 @@ This section provides tutorials for different features of vLLM Ascend. :::{toctree} :caption: Feature Tutorials :maxdepth: 1 - pd_colocated_mooncake_multi_instance pd_disaggregation_mooncake_single_node pd_disaggregation_mooncake_multi_node diff --git a/docs/source/tutorials/features/pd_disaggregation_mooncake_multi_node.md b/docs/source/tutorials/features/pd_disaggregation_mooncake_multi_node.md index b43d77676..d8be18987 100644 --- a/docs/source/tutorials/features/pd_disaggregation_mooncake_multi_node.md +++ b/docs/source/tutorials/features/pd_disaggregation_mooncake_multi_node.md @@ -870,7 +870,7 @@ You can get the proxy program in the repository's examples, [load\_balance\_prox ## Benchmark -We recommend use aisbench tool to assess performance. [aisbench](https://github.com/AISBench/benchmark) Execute the following commands to install aisbench +We recommend use aisbench tool to assess performance. [aisbench](https://gitee.com/aisbench/benchmark) Execute the following commands to install aisbench ```shell git clone https://github.com/AISBench/benchmark.git @@ -919,7 +919,7 @@ models = [ ais_bench --models vllm_api_stream_chat --datasets gsm8k_gen_0_shot_cot_str_perf --debug --mode perf ``` -- For more details for commands and parameters for aisbench, refer to [aisbench](https://github.com/AISBench/benchmark) +- For more details for commands and parameters for aisbench, refer to [aisbench](https://gitee.com/aisbench/benchmark) ## FAQ diff --git a/docs/source/tutorials/features/suffix_speculative_decoding.md b/docs/source/tutorials/features/suffix_speculative_decoding.md index 09c50cf19..646b510ad 100644 --- a/docs/source/tutorials/features/suffix_speculative_decoding.md +++ b/docs/source/tutorials/features/suffix_speculative_decoding.md @@ -21,21 +21,17 @@ The benchmarking tool used in this tutorial is AISBench, which supports performa This tutorial uses the official image, version v0.13.0rc1. Use the following command to download: -```{code-block} bash -:substitutions: - -docker pull quay.io/ascend/vllm-ascend:|vllm_ascend_version| +```bash +docker pull quay.io/ascend/vllm-ascend:v0.13.0rc1 ``` ## **Run with Docker** Container startup command: -```{code-block} bash -:substitutions: - +```bash # Update the vllm-ascend image -export IMAGE=quay.io/ascend/vllm-ascend:|vllm_ascend_version| +export IMAGE=quay.io/ascend/vllm-ascend:v0.13.0rc1 export NAME=vllm-ascend # Run the container using the defined variables diff --git a/docs/source/tutorials/hardwares/310p.md b/docs/source/tutorials/hardwares/310p.md index 6ea97f623..4b8f751be 100644 --- a/docs/source/tutorials/hardwares/310p.md +++ b/docs/source/tutorials/hardwares/310p.md @@ -33,7 +33,7 @@ Run docker container: :substitutions: # Use the vllm-ascend image -export IMAGE=quay.io/ascend/vllm-ascend:|vllm_ascend_version|-310p +export IMAGE=quay.io/ascend/vllm-ascend:v0.18.0-310p docker run --rm \ --name vllm-ascend \ diff --git a/docs/source/tutorials/models/DeepSeek-V3.2.md b/docs/source/tutorials/models/DeepSeek-V3.2.md index e85e88a8b..5004fb33d 100644 --- a/docs/source/tutorials/models/DeepSeek-V3.2.md +++ b/docs/source/tutorials/models/DeepSeek-V3.2.md @@ -16,8 +16,8 @@ Refer to [feature guide](../../user_guide/feature_guide/index.md) to get the fea ### Model Weight -- `DeepSeek-V3.2-Exp-W8A8` (Quantized version): requires **1 Atlas 800 A3 (64G × 16) node** or **2 Atlas 800 A2 (64G × 8) nodes**. [Download model weight](https://www.modelscope.cn/models/vllm-ascend/DeepSeek-V3.2-Exp-W8A8) -- `DeepSeek-V3.2-w8a8` (Quantized version): requires **1 Atlas 800 A3 (64G × 16) node** or **2 Atlas 800 A2 (64G × 8) nodes**. [Download model weight](https://www.modelscope.cn/models/vllm-ascend/DeepSeek-V3.2-W8A8/) +- `DeepSeek-V3.2-Exp-W8A8`(Quantized version): require 1 Atlas 800 A3 (64G × 16) node or 2 Atlas 800 A2 (64G × 8) nodes. [Download model weight](https://www.modelscope.cn/models/vllm-ascend/DeepSeek-V3.2-Exp-W8A8) +- `DeepSeek-V3.2-w8a8`(Quantized version): require 1 Atlas 800 A3 (64G × 16) node or 2 Atlas 800 A2 (64G × 8) nodes. [Download model weight](https://www.modelscope.cn/models/vllm-ascend/DeepSeek-V3.2-W8A8/) It is recommended to download the model weight to the shared directory of multiple nodes, such as `/root/.cache/`. @@ -860,8 +860,8 @@ Once your server is started, you can query the model with input prompts: **Note**: -- ``: The IP address of the node where the server is running (e.g., localhost). For PD-separated deployment, use the host IP of the node where the proxy script resides. -- ``: The port number specified in the server startup command (e.g., 8000). For PD-separated deployment, use the port configured in the proxy script. +- ``: The IP address of the node where the server is running (e.g., localhost). +- ``: The port number specified in the server startup command (e.g., 8000). ```shell curl http://:/v1/completions \ @@ -874,12 +874,6 @@ curl http://:/v1/completions \ }' ``` -**Expected Result**: - -```json -{"id":"019eab54ead036b23e53f3a709e09289","object":"chat.completion","created":1780990929,"model":"deepseek_v3.2","choices":[{"index":0,"message":{"role":"assistant","content":"The future of AI is **not a single destination, but a complex, multi-faceted trajectory** that will reshape nearly every aspect of human society, technology, and our understanding of intelligence itself. It can be understood through several interconnected lenses:\n\n### "},"finish_reason":"length"}],"usage":{"prompt_tokens":9,"completion_tokens":50,"total_tokens":59,"completion_tokens_details":{"reasoning_tokens":0},"prompt_tokens_details":{"cached_tokens":0},"prompt_cache_hit_tokens":0,"prompt_cache_miss_tokens":9},"system_fingerprint":""} -``` - ## Accuracy Evaluation Here are two accuracy evaluation methods. diff --git a/docs/source/tutorials/models/DeepSeek-V4-Flash.md b/docs/source/tutorials/models/DeepSeek-V4-Flash.md index 5cabf1618..55ebe1053 100644 --- a/docs/source/tutorials/models/DeepSeek-V4-Flash.md +++ b/docs/source/tutorials/models/DeepSeek-V4-Flash.md @@ -140,6 +140,7 @@ export OMP_NUM_THREADS=10 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD export HCCL_BUFFSIZE=1024 +export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 export TASK_QUEUE_ENABLE=1 export HCCL_OP_EXPANSION_MODE="AIV" @@ -189,6 +190,7 @@ export OMP_NUM_THREADS=10 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD export HCCL_BUFFSIZE=1024 +export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 export TASK_QUEUE_ENABLE=1 export HCCL_OP_EXPANSION_MODE="AIV" @@ -357,6 +359,7 @@ Before you start, please export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export HCCL_BUFFSIZE=2560 export TASK_QUEUE_ENABLE=1 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 export HCCL_OP_EXPANSION_MODE="AIV" export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD @@ -430,6 +433,7 @@ Before you start, please export OMP_NUM_THREADS=10 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export HCCL_BUFFSIZE=1024 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export ASCEND_RT_VISIBLE_DEVICES=$1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Flash-w8a8-mtp \ @@ -612,6 +616,7 @@ Before you start, please export ASCEND_RT_VISIBLE_DEVICES=$1 export TASK_QUEUE_ENABLE=1 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Flash-w8a8-mtp \ --host 0.0.0.0 \ @@ -679,6 +684,7 @@ For each P instance, only these two configuration values need to be modified: export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD export HCCL_OP_EXPANSION_MODE="AIV" export TASK_QUEUE_ENABLE=1 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_RPC_TIMEOUT=3600000 export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000 export HCCL_EXEC_TIMEOUT=204 diff --git a/docs/source/tutorials/models/DeepSeek-V4-Pro.md b/docs/source/tutorials/models/DeepSeek-V4-Pro.md index 8e71a4d69..86668d1f3 100644 --- a/docs/source/tutorials/models/DeepSeek-V4-Pro.md +++ b/docs/source/tutorials/models/DeepSeek-V4-Pro.md @@ -164,6 +164,7 @@ export HCCL_CONNECT_TIMEOUT=7200 export ASCEND_CONNECT_TIMEOUT=10000 export ASCEND_TRANSFER_TIMEOUT=10000 export VLLM_RPC_TIMEOUT=1800000 +export VLLM_ASCEND_APPLY_DSV4_PATCH=1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Pro-w4a8-mtp \ --host 0.0.0.0 \ @@ -242,6 +243,7 @@ export HCCL_CONNECT_TIMEOUT=7200 export ASCEND_CONNECT_TIMEOUT=10000 export ASCEND_TRANSFER_TIMEOUT=10000 export VLLM_RPC_TIMEOUT=1800000 +export VLLM_ASCEND_APPLY_DSV4_PATCH=1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Pro-w4a8-mtp \ --host 0.0.0.0 \ @@ -318,6 +320,7 @@ export OMP_PROC_BIND=false export OMP_NUM_THREADS=10 export TASK_QUEUE_ENABLE=1 export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD +export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Pro-w4a8-mtp \ @@ -377,6 +380,7 @@ export OMP_PROC_BIND=false export OMP_NUM_THREADS=10 export TASK_QUEUE_ENABLE=1 export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD +export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Pro-w4a8-mtp \ @@ -550,6 +554,7 @@ Before you start, please export TASK_QUEUE_ENABLE=1 export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD export ASCEND_RT_VISIBLE_DEVICES=$1 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_ASCEND_ENABLE_FUSED_MC2=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 @@ -621,6 +626,7 @@ Before you start, please export TASK_QUEUE_ENABLE=1 export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD export ASCEND_RT_VISIBLE_DEVICES=$1 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export VLLM_ASCEND_ENABLE_FUSED_MC2=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 @@ -691,6 +697,7 @@ Before you start, please export OMP_NUM_THREADS=10 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export HCCL_BUFFSIZE=1024 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 export ASCEND_RT_VISIBLE_DEVICES=$1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Pro-w4a8-mtp \ @@ -920,6 +927,7 @@ Before you start, please export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 export ASCEND_RT_VISIBLE_DEVICES=$1 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Pro-w4a8-mtp \ --host 0.0.0.0 \ @@ -996,6 +1004,7 @@ Before you start, please sysctl kernel.sched_migration_cost_ns=50000 export ASCEND_RT_VISIBLE_DEVICES=$1 + export VLLM_ASCEND_APPLY_DSV4_PATCH=1 vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/DeepSeek-V4-Pro-w4a8-mtp \ --host 0.0.0.0 \ diff --git a/docs/source/tutorials/models/GLM4.x.md b/docs/source/tutorials/models/GLM4.x.md index ec7fdc344..4379f0bdd 100644 --- a/docs/source/tutorials/models/GLM4.x.md +++ b/docs/source/tutorials/models/GLM4.x.md @@ -165,7 +165,7 @@ The parameters are explained as follows: ### Multi-node Deployment -While the previous documentation advises against multi-node deployment on the Atlas 800 A2 (64G × 8) platform, this configuration can still be implemented for the GLM-4.x model if required. To proceed with a dual-node setup, execute the following scripts on each respective node. +Although the former tutorial said "Not recommended to deploy multi-node on Atlas 800 A2 (64G × 8)", but if you insist to deploy GLM-4.x model on multi-node like 2 × Atlas 800 A2 (64G × 8), run the following scripts on two nodes respectively. **Node 0** diff --git a/docs/source/tutorials/models/GLM5.2.md b/docs/source/tutorials/models/GLM5.2.md deleted file mode 100644 index b8e59e32c..000000000 --- a/docs/source/tutorials/models/GLM5.2.md +++ /dev/null @@ -1,1265 +0,0 @@ -# GLM-5.2 - -## Introduction - -[GLM-5.2](https://huggingface.co/zai-org/GLM-5.2) use a Mixture-of-Experts (MoE) architecture and targets complex systems engineering and long-horizon agentic tasks. - -This document will show the main verification steps of the model, including supported features, feature configuration, environment preparation, single-node and multi-node deployment, accuracy and performance evaluation. - -## Supported Features - -Refer to [supported features](../../user_guide/support_matrix/supported_models.md) to get the model's supported feature matrix. - -Refer to [feature guide](../../user_guide/feature_guide/index.md) to get the feature's configuration. - -## Environment Preparation - -### Model Weight - -- `GLM-5.2`(BF16 version)require 2 Atlas 800 A3 (128G × 8) node or 4 Atlas 800 A2 (64G × 8) node.: [Download model weight](https://www.modelscope.cn/models/ZhipuAI/GLM-5.2). -- `GLM-5.2-w8a8`: require 1 Atlas 800 A3 (128G × 8) node or 2 Atlas 800 A2 (64G × 8) node.[Download model weight](https://www.modelscope.cn/models/Eco-Tech/GLM-5.2-w8a8). -- You can use [msmodelslim](https://gitcode.com/Ascend/msmodelslim) to quantify the model naively. - -It is recommended to download the model weight to the shared directory of multiple nodes, such as `/root/.cache/` - -### Installation - -You can use our official docker image to run GLM-5 directly. - -:::::{tab-set} -:sync-group: install - -::::{tab-item} A3 series -:sync: A3 - -Start the docker image on your each node. - -```{code-block} bash - :substitutions: - -export IMAGE=quay.io/ascend/vllm-ascend:glm5.2-a3 -export NAME=vllm-ascend - -# Run the container using the defined variables -# Note: If you are running bridge network with docker, please expose available ports for multiple nodes communication in advance -docker run --rm \ ---name $NAME \ ---net=host \ ---shm-size=1g \ ---device /dev/davinci0 \ ---device /dev/davinci1 \ ---device /dev/davinci2 \ ---device /dev/davinci3 \ ---device /dev/davinci4 \ ---device /dev/davinci5 \ ---device /dev/davinci6 \ ---device /dev/davinci7 \ ---device /dev/davinci8 \ ---device /dev/davinci9 \ ---device /dev/davinci10 \ ---device /dev/davinci11 \ ---device /dev/davinci12 \ ---device /dev/davinci13 \ ---device /dev/davinci14 \ ---device /dev/davinci15 \ ---device /dev/davinci_manager \ ---device /dev/devmm_svm \ ---device /dev/hisi_hdc \ --v /usr/local/dcmi:/usr/local/dcmi \ --v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \ --v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ --v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \ --v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \ --v /etc/ascend_install.info:/etc/ascend_install.info \ --v /root/.cache:/root/.cache \ --it $IMAGE bash -``` - -:::: -::::{tab-item} A2 series -:sync: A2 - -Start the docker image on your each node. - -```{code-block} bash - :substitutions: - -export IMAGE=quay.io/ascend/vllm-ascend:glm5.2 -docker run --rm \ - --name vllm-ascend \ - --shm-size=1g \ - --net=host \ - --device /dev/davinci0 \ - --device /dev/davinci1 \ - --device /dev/davinci2 \ - --device /dev/davinci3 \ - --device /dev/davinci4 \ - --device /dev/davinci5 \ - --device /dev/davinci6 \ - --device /dev/davinci7 \ - --device /dev/davinci_manager \ - --device /dev/devmm_svm \ - --device /dev/hisi_hdc \ - -v /usr/local/dcmi:/usr/local/dcmi \ - -v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \ - -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ - -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \ - -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \ - -v /etc/ascend_install.info:/etc/ascend_install.info \ - -v /root/.cache:/root/.cache \ - -it $IMAGE bash -``` - -:::: -::::: - -If you want to deploy multi-node environment, you need to set up environment on each node. - -## Deployment - -### Single-node Deployment - -- Quantized model `glm-5.2-w8a8` can be deployed on 1 Atlas 800 A3 (64G × 16) . - -Run the following script to execute online inference. - -```{code-block} bash - :substitutions: -export HCCL_OP_EXPANSION_MODE="AIV" -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=1 -export HCCL_BUFFSIZE=200 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export VLLM_ASCEND_BALANCE_SCHEDULING=1 -export VLLM_ASCEND_ENABLE_MLAPO=1 -export VLLM_VERSION=0.21.0 -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ ---host 0.0.0.0 \ ---port 8077 \ ---data-parallel-size 2 \ ---tensor-parallel-size 8 \ ---enable-expert-parallel \ ---seed 1024 \ ---served-model-name glm-52 \ ---max-num-seqs 48 \ ---max-model-len 20480 \ ---max-num-batched-tokens 4096 \ ---trust-remote-code \ ---gpu-memory-utilization 0.95 \ ---quantization ascend \ ---async-scheduling \ ---additional-config '{"enable_npugraph_ex": true,"fuse_muls_add":true,"multistream_overlap_shared_expert":true}' \ ---compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ ---speculative-config '{"num_speculative_tokens": 3, "method": "deepseek_mtp"}' - -``` - -**Notice:** -The parameters are explained as follows: - -- For single-node deployment, we recommend using `dp2tp8` and turn off expert parallel in low-latency scenarios. - -### Multi-node Deployment - -If you want to deploy multi-node environment, you need to verify multi-node communication according to [verify multi-node communication environment](../../installation.md#verify-multi-node-communication). - -:::::{tab-set} -:sync-group: install - -::::{tab-item} A3 series -:sync: A3 - -- `glm-5.2-w8a8`: can be deployed on 2 Atlas 800 A3 (64G × 16). - -Run the following scripts on two nodes respectively. - -**node 0** - -```{code-block} bash - :substitutions: -# this obtained through ifconfig -# nic_name is the network interface name corresponding to local_ip of the current node -nic_name="xxx" -local_ip="xxx" - -# The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) -node0_ip="xxxx" - -export VLLM_VERSION=0.21.0 -export HCCL_OP_EXPANSION_MODE="AIV" -export VLLM_ASCEND_BALANCE_SCHEDULING=0 -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=1 -export HCCL_BUFFSIZE=400 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export VLLM_ASCEND_ENABLE_MLAPO=1 -export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -export ASCEND_LAUNCH_BLOCKING=0 - -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ ---host 0.0.0.0 \ ---port 8077 \ ---data-parallel-size 2 \ ---data-parallel-size-local 1 \ ---data-parallel-address $node0_ip \ ---data-parallel-rpc-port 12980 \ ---tensor-parallel-size 16 \ ---seed 1024 \ ---served-model-name glm-5 \ ---max-num-seqs 48 \ ---max-model-len 64000 \ ---max-num-batched-tokens 4096 \ ---trust-remote-code \ ---gpu-memory-utilization 0.93 \ ---quantization ascend \ ---enable-prefix-caching \ ---async-scheduling \ ---compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ ---additional-config '{"enable_npugraph_ex": true,"fuse_muls_add":true,"multistream_overlap_shared_expert":true}' \ ---speculative-config '{"num_speculative_tokens": 5, "method": "deepseek_mtp"}' -``` - -**node 1** - -```{code-block} bash - :substitutions: -# this obtained through ifconfig -# nic_name is the network interface name corresponding to local_ip of the current node -nic_name="xxx" -local_ip="xxx" - -# The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) -node0_ip="xxxx" - -export VLLM_VERSION=0.21.0 -export HCCL_OP_EXPANSION_MODE="AIV" -export VLLM_ASCEND_BALANCE_SCHEDULING=0 -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=1 -export HCCL_BUFFSIZE=400 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export VLLM_ASCEND_ENABLE_MLAPO=1 -export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -export ASCEND_LAUNCH_BLOCKING=0 - -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ ---host 0.0.0.0 \ ---port 8077 \ ---headless \ ---data-parallel-size 2 \ ---data-parallel-size-local 1 \ ---data-parallel-start-rank 1 \ ---data-parallel-rpc-port 12980 \ ---data-parallel-address $node0_ip \ ---tensor-parallel-size 16 \ ---seed 1024 \ ---served-model-name glm-5 \ ---max-num-seqs 48 \ ---max-model-len 64000 \ ---max-num-batched-tokens 4096 \ ---trust-remote-code \ ---gpu-memory-utilization 0.93 \ ---quantization ascend \ ---enable-prefix-caching \ ---async-scheduling \ ---compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ ---additional-config '{"enable_npugraph_ex": true,"fuse_muls_add":true,"multistream_overlap_shared_expert":true}' \ ---speculative-config '{"num_speculative_tokens": 5, "method": "deepseek_mtp"}' -``` - -:::: -::::{tab-item} A2 series -:sync: A2 - -- `glm-5.2-w8a8`: can be deployed on 2 Atlas 800 A2 (64G × 32). - -**node 0** - -```{code-block} bash - :substitutions: -# this obtained through ifconfig -# nic_name is the network interface name corresponding to local_ip of the current node -nic_name="xxx" -local_ip="xxx" - -# The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) -node0_ip="xxx" - -export HCCL_OP_EXPANSION_MODE="AIV" -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name -export VLLM_RPC_TIMEOUT=360000 -export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=3000 -export HCCL_EXEC_TIMEOUT=200 -export HCCL_CONNECT_TIMEOUT=120 -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=10 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export ACL_OP_INIT_MODE=1 -#export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -#export USE_MULTI_GROUPS_KV_CACHE=1 -#export USE_MULTI_BLOCK_POOL=1 -export TASK_QUEUE_ENABLE=1 -export CPU_AFFINITY_CONF=1 -export VLLM_ENGINE_READY_TIMEOUT_S=1200 - -export VLLM_VERSION=0.21.0 -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ ---max_model_len 40000 \ ---max-num-batched-tokens 4096 \ ---served-model-name glm-52 \ ---seed 1024 \ ---gpu-memory-utilization 0.95 \ ---api-server-count 1 \ ---max-num-seqs 16 \ ---data-parallel-size 2 \ ---data-parallel-size-local 1 \ ---data-parallel-address $local_ip \ ---data-parallel-rpc-port 13389 \ ---tensor-parallel-size 8 \ ---enable-expert-parallel \ ---quantization ascend \ ---port 7000 \ ---safetensors-load-strategy 'prefetch' \ ---block-size 128 \ ---async-scheduling \ ---additional-config '{"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "ascend_compilation_config": {"enable_npugraph_ex": true}}' \ ---compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ ---speculative-config '{"num_speculative_tokens": 5, "method": "deepseek_mtp"}' -``` - -**node 1** - -```{code-block} bash - :substitutions: -# this obtained through ifconfig -# nic_name is the network interface name corresponding to local_ip of the current node -nic_name="xxx" -local_ip="xxx" - -# The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) -node0_ip="xxx" - -export HCCL_OP_EXPANSION_MODE="AIV" -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name -export VLLM_RPC_TIMEOUT=360000 -export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=3000 -export HCCL_EXEC_TIMEOUT=200 -export HCCL_CONNECT_TIMEOUT=120 -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=10 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export ACL_OP_INIT_MODE=1 -#export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -#export USE_MULTI_GROUPS_KV_CACHE=1 -#export USE_MULTI_BLOCK_POOL=1 -export TASK_QUEUE_ENABLE=1 -export CPU_AFFINITY_CONF=1 -export VLLM_ENGINE_READY_TIMEOUT_S=1200 - -export VLLM_VERSION=0.21.0 -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ ---max_model_len 40000 \ ---max-num-batched-tokens 4096 \ ---served-model-name glm-52 \ ---seed 1024 \ ---gpu-memory-utilization 0.95 \ ---api-server-count 1 \ ---max-num-seqs 16 \ ---headless \ ---data-parallel-size 2 \ ---data-parallel-size-local 1 \ ---data-parallel-start-rank 1 \ ---data-parallel-address $local_ip \ ---data-parallel-rpc-port 13389 \ ---tensor-parallel-size 8 \ ---enable-expert-parallel \ ---quantization ascend \ ---port 7000 \ ---safetensors-load-strategy 'prefetch' \ ---block-size 128 \ ---async-scheduling \ ---additional-config '{"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "ascend_compilation_config": {"enable_npugraph_ex": true}}' \ ---compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ ---speculative-config '{"num_speculative_tokens": 5, "method": "deepseek_mtp"}' -``` - -:::: -::::: - -### Co-located Deployment on 4 Nodes (200k context) - -In a co-located (mixed) deployment, prefill and decode run together on the same nodes, in contrast to the disaggregated setup below. The following templates deploy `GLM-5.2` across 4 nodes with `DP4 TP8` (`data-parallel-size-local=1` per node), a 200k context window, and MTP (`num_speculative_tokens=5`). Node 0 hosts the API server and is the DP master; Node 1 to Node 3 run with `--headless`. Prefix caching is disabled (`--no-enable-prefix-caching`) in this configuration. All IPs, NIC names, ports and weight paths are placeholders. - -**Node 0** (API server / DP master): - -```bash -#!/usr/bin/bash - -nic_name="" -local_ip=$(hostname -I | awk -F " " '{print $1}') -echo "$local_ip" - -export HCCL_OP_EXPANSION_MODE="AIV" -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name - -export VLLM_RPC_TIMEOUT=360000 -export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=3000 -export HCCL_EXEC_TIMEOUT=200 -export HCCL_CONNECT_TIMEOUT=120 - -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=10 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export ACL_OP_INIT_MODE=1 - -export TASK_QUEUE_ENABLE=1 -export CPU_AFFINITY_CONF=1 -export VLLM_ENGINE_READY_TIMEOUT_S=1200 - -export VLLM_VERSION=0.21.0 - -vllm serve \ - --max_model_len 200000 \ - --max-num-batched-tokens 4096 \ - --served-model-name glm \ - --seed 1024 \ - --api-server-count 1 \ - --gpu-memory-utilization 0.95 \ - --max-num-seqs 32 \ - --data-parallel-size 4 \ - --data-parallel-size-local 1 \ - --data-parallel-address $local_ip \ - --data-parallel-rpc-port 13389 \ - --tensor-parallel-size 8 \ - --enable-expert-parallel \ - --quantization ascend \ - --port 7000 \ - --safetensors-load-strategy 'prefetch' \ - --block-size 128 \ - --enable-chunked-prefill \ - --no-enable-prefix-caching \ - --async-scheduling \ - --additional-config '{"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "ascend_compilation_config": {"enable_npugraph_ex": true}}' \ - --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ - --speculative-config '{"num_speculative_tokens": 5, "method": "deepseek_mtp"}' -``` - -**Node 1** (headless, `--data-parallel-start-rank 1`): - -```bash -#!/usr/bin/bash - -nic_name="" -local_ip=$(hostname -I | awk -F " " '{print $1}') -node0_ip="" -echo "$local_ip" - -export HCCL_OP_EXPANSION_MODE="AIV" -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name - -export VLLM_RPC_TIMEOUT=360000 -export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=3000 -export HCCL_EXEC_TIMEOUT=200 -export HCCL_CONNECT_TIMEOUT=120 - -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=10 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export ACL_OP_INIT_MODE=1 - -export TASK_QUEUE_ENABLE=1 -export CPU_AFFINITY_CONF=1 -export VLLM_ENGINE_READY_TIMEOUT_S=1200 - -export VLLM_VERSION=0.21.0 - -vllm serve \ - --max_model_len 200000 \ - --max-num-batched-tokens 4096 \ - --headless \ - --served-model-name glm \ - --seed 1024 \ - --gpu-memory-utilization 0.95 \ - --max-num-seqs 32 \ - --safetensors-load-strategy 'prefetch' \ - --data-parallel-size 4 \ - --data-parallel-size-local 1 \ - --data-parallel-start-rank 1 \ - --data-parallel-address $node0_ip \ - --data-parallel-rpc-port 13389 \ - --tensor-parallel-size 8 \ - --enable-expert-parallel \ - --quantization ascend \ - --port 7000 \ - --block-size 128 \ - --enable-chunked-prefill \ - --no-enable-prefix-caching \ - --async-scheduling \ - --additional-config '{"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "ascend_compilation_config": {"enable_npugraph_ex": true}}' \ - --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ - --speculative-config '{"num_speculative_tokens": 5, "method": "deepseek_mtp"}' -``` - -Node 2 and Node 3 use the same script as Node 1, with `--data-parallel-start-rank` set to `2` and `3` respectively (and `node0_ip` pointing to Node 0). - -### Prefill-Decode Disaggregation - -We'd like to show the deployment guide of `GLM-5` on multi-node environment with 1P1D for better performance. - -Prefill-Decode disaggregation can be deployed on 4 Atlas 800 A3 (64G × 32). - -Before you start, please - -1. prepare the script `launch_online_dp.py` on each node: - - ```python - import argparse - import multiprocessing - import os - import subprocess - import sys - - def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument( - "--dp-size", - type=int, - required=True, - help="Data parallel size." - ) - parser.add_argument( - "--tp-size", - type=int, - default=1, - help="Tensor parallel size." - ) - parser.add_argument( - "--dp-size-local", - type=int, - default=-1, - help="Local data parallel size." - ) - parser.add_argument( - "--dp-rank-start", - type=int, - default=0, - help="Starting rank for data parallel." - ) - parser.add_argument( - "--dp-address", - type=str, - required=True, - help="IP address for data parallel master node." - ) - parser.add_argument( - "--dp-rpc-port", - type=str, - default=12345, - help="Port for data parallel master node." - ) - parser.add_argument( - "--vllm-start-port", - type=int, - default=9000, - help="Starting port for the engine." - ) - return parser.parse_args() - - args = parse_args() - dp_size = args.dp_size - tp_size = args.tp_size - dp_size_local = args.dp_size_local - if dp_size_local == -1: - dp_size_local = dp_size - dp_rank_start = args.dp_rank_start - dp_address = args.dp_address - dp_rpc_port = args.dp_rpc_port - vllm_start_port = args.vllm_start_port - - def run_command(visible_devices, dp_rank, vllm_engine_port): - command = [ - "bash", - "./run_dp_template.sh", - visible_devices, - str(vllm_engine_port), - str(dp_size), - str(dp_rank), - dp_address, - dp_rpc_port, - str(tp_size), - ] - subprocess.run(command, check=True) - - if __name__ == "__main__": - template_path = "./run_dp_template.sh" - if not os.path.exists(template_path): - print(f"Template file {template_path} does not exist.") - sys.exit(1) - - processes = [] - num_cards = dp_size_local * tp_size - for i in range(dp_size_local): - dp_rank = dp_rank_start + i - vllm_engine_port = vllm_start_port + i - visible_devices = ",".join(str(x) for x in range(i * tp_size, (i + 1) * tp_size)) - process = multiprocessing.Process(target=run_command, - args=(visible_devices, dp_rank, - vllm_engine_port)) - processes.append(process) - process.start() - - for process in processes: - process.join() - - ``` - -2. prepare the script `run_dp_template.sh` on each node. - - To support a 200k context window on the stage of prefill, the parameter `"layer_sharding": ["q_b_proj"]` needs to be added to `--additional_config` on each prefill node. - 1. Prefill node 0 - - ```shell - nic_name="xxxx" # change to your own nic name - local_ip="xxxx" # change to your own ip - - export VLLM_VERSION=0.21.0 - export HCCL_OP_EXPANSION_MODE="AIV" - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export HCCL_BUFFSIZE=400 - export ASCEND_AGGREGATE_ENABLE=1 - export ASCEND_TRANSPORT_PRINT=1 - export ACL_OP_INIT_MODE=1 - export ASCEND_A3_ENABLE=1 - export VLLM_MOONCAKE_ABORT_REQUEST_TIMEOUT=480 - export ASCEND_RT_VISIBLE_DEVICES=$1 - export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 - export VLLM_ASCEND_ENABLE_FUSED_MC2=1 - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --served-model-name glm-52 \ - --max-model-len 135000 \ - --speculative-config '{"num_speculative_tokens": 5, "method":"deepseek_mtp"}' \ - --additional-config '{"enable_sparse_c8":false,"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "recompute_scheduler_enable": true, "ascend_compilation_config": {"enable_npugraph_ex": true},"enable_dsa_cp": true}' \ - --max-num-batched-tokens 4096 \ - --trust-remote-code \ - --max-num-seqs 64 \ - --async-scheduling \ - --quantization ascend \ - --gpu-memory-utilization 0.95 \ - --enforce-eager \ - --enable-auto-tool-choice \ - --tool-call-parser glm47 \ - --reasoning-parser glm45 \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_producer", - "kv_port": "30000", - "engine_id": "0", - "kv_connector_extra_config": { - "use_ascend_direct": true, - "prefill": { - "dp_size": 2, - "tp_size": 16 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - - ``` - - 2. Prefill node 1 - - ```shell - nic_name="xxxx" # change to your own nic name - local_ip="xxxx" # change to your own ip - - export VLLM_VERSION=0.21.0 - export HCCL_OP_EXPANSION_MODE="AIV" - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export HCCL_BUFFSIZE=400 - export ASCEND_AGGREGATE_ENABLE=1 - export ASCEND_TRANSPORT_PRINT=1 - export ACL_OP_INIT_MODE=1 - export ASCEND_A3_ENABLE=1 - export VLLM_MOONCAKE_ABORT_REQUEST_TIMEOUT=480 - export ASCEND_RT_VISIBLE_DEVICES=$1 - export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 - export VLLM_ASCEND_ENABLE_FUSED_MC2=1 - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --served-model-name glm-52 \ - --max-model-len 135000 \ - --speculative-config '{"num_speculative_tokens": 5, "method":"deepseek_mtp"}' \ - --additional-config '{"enable_sparse_c8":false,"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "recompute_scheduler_enable": true, "ascend_compilation_config": {"enable_npugraph_ex": true},"enable_dsa_cp": true}' \ - --max-num-batched-tokens 4096 \ - --trust-remote-code \ - --max-num-seqs 64 \ - --async-scheduling \ - --quantization ascend \ - --gpu-memory-utilization 0.95 \ - --enforce-eager \ - --enable-auto-tool-choice \ - --tool-call-parser glm47 \ - --reasoning-parser glm45 \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_producer", - "kv_port": "30000", - "engine_id": "0", - "kv_connector_extra_config": { - "use_ascend_direct": true, - "prefill": { - "dp_size": 2, - "tp_size": 16 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - ``` - - 3. Decode node 0 - - ```shell - nic_name="xxxx" # change to your own nic name - local_ip="xxxx" # change to your own ip - - export HCCL_OP_EXPANSION_MODE="AIV" - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export HCCL_BUFFSIZE=500 - export ASCEND_AGGREGATE_ENABLE=1 - export ASCEND_TRANSPORT_PRINT=1 - export ACL_OP_INIT_MODE=1 - export ASCEND_A3_ENABLE=1 - export VLLM_VERSION=0.21.0 - export TASK_QUEUE_ENABLE=1 - export ASCEND_RT_VISIBLE_DEVICES=$1 - export DYNAMIC_EPLB=1 - export VLLM_ASCEND_ENABLE_FUSED_MC2=1 - export VLLM_ASCEND_ENABLE_MLAPO=1 - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5.2-w8a8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --served-model-name glm-52 \ - --max-model-len 135000 \ - --max-num-batched-tokens 164 \ - --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \ - --speculative-config '{"num_speculative_tokens": 5, "method":"deepseek_mtp"}' \ - --additional-config '{"enable_sparse_c8":false,"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "recompute_scheduler_enable": true, "ascend_compilation_config": {"enable_npugraph_ex": true}}' \ - --trust-remote-code \ - --max-num-seqs 48 \ - --gpu-memory-utilization 0.92 \ - --async-scheduling \ - --quantization ascend \ - --enable-auto-tool-choice \ - --tool-call-parser glm47 \ - --reasoning-parser glm45 \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_consumer", - "kv_port": "30100", - "engine_id": "1", - "kv_connector_extra_config": { - "use_ascend_direct": true, - "prefill": { - "dp_size": 2, - "tp_size": 16 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - ``` - - 4. Decode node 1 - - ```shell - nic_name="xxxx" # change to your own nic name - local_ip="xxxx" # change to your own ip - - export HCCL_OP_EXPANSION_MODE="AIV" - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export HCCL_BUFFSIZE=500 - export ASCEND_AGGREGATE_ENABLE=1 - export ASCEND_TRANSPORT_PRINT=1 - export ACL_OP_INIT_MODE=1 - export ASCEND_A3_ENABLE=1 - export TASK_QUEUE_ENABLE=1 - export VLLM_VERSION=0.21.0 - export ASCEND_RT_VISIBLE_DEVICES=$1 - export DYNAMIC_EPLB=1 - export VLLM_ASCEND_ENABLE_FUSED_MC2=1 - export VLLM_ASCEND_ENABLE_MLAPO=1 - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5.2-w8a8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --served-model-name glm-52 \ - --max-model-len 135000 \ - --max-num-batched-tokens 164 \ - --speculative-config '{"num_speculative_tokens": 5, "method":"deepseek_mtp"}' \ - --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \ - --additional-config '{"enable_sparse_c8":false,"fuse_muls_add": true, "multistream_overlap_shared_expert": true, "recompute_scheduler_enable": true, "ascend_compilation_config": {"enable_npugraph_ex": true}}' \ - --trust-remote-code \ - --max-num-seqs 48 \ - --gpu-memory-utilization 0.92 \ - --async-scheduling \ - --quantization ascend \ - --enable-auto-tool-choice \ - --tool-call-parser glm47 \ - --reasoning-parser glm45 \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_consumer", - "kv_port": "30100", - "engine_id": "1", - "kv_connector_extra_config": { - "use_ascend_direct": true, - "prefill": { - "dp_size": 2, - "tp_size": 16 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - ``` - -Once the preparation is done, you can start the server with the following command on each node: - -1. Prefill node 0 - - ```shell - # change ip to your own - python launch_online_dp.py --dp-size 2 --tp-size 16 --dp-size-local 2 --dp-rank-start 0 --dp-address $node_p0_ip --dp-rpc-port 16591 --vllm-start-port 9081 - ``` - -2. Prefill node 1 - - ```shell - # change ip to your own - python launch_online_dp.py --dp-size 2 --tp-size 16 --dp-size-local 2 --dp-rank-start 1 --dp-address $node_p0_ip --dp-rpc-port 16591 --vllm-start-port 9081 - ``` - -3. Decode node 0 - - ```shell - # change ip to your own - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 4 --dp-rank-start 0 --dp-address $node_p0_ip --dp-rpc-port 16600 --vllm-start-port 9900 - ``` - -4. Decode node 1 - - ```shell - # change ip to your own - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 4 --dp-rank-start 4 --dp-address $node_p0_ip --dp-rpc-port 16600 --vllm-start-port 9900 - ``` - -To set up request forwarding, run the following script on any machine. You can get the proxy program in the repository's examples: [load_balance_proxy_server_example.py](https://github.com/vllm-project/vllm-ascend/blob/main/examples/disaggregated_prefill_v1/load_balance_proxy_server_example.py) - -```shell -unset http_proxy -unset https_proxy - -python load_balance_proxy_server_example.py \ - --port 8000 \ - --host 0.0.0.0 \ - --prefiller-hosts \ - $node_p0_ip \ - $node_p1_ip \ - --prefiller-ports \ - 9081 9081 \ - --decoder-hosts \ - $node_d0_ip \ - $node_d0_ip \ - $node_d0_ip \ - $node_d0_ip \ - $node_d1_ip \ - $node_d1_ip \ - $node_d1_ip \ - $node_d1_ip \ - --decoder-ports \ - 9900 9901 9902 9903 \ - 9900 9901 9902 9903 \ -``` - -#### Deployment on 8 Atlas 800 A2 - -On Atlas 800 A2, where each node exposes 8 cards, the same global P/D topology (Prefill `DP4 TP8`, Decode `DP8 TP4`) is split across 8 nodes: 4 prefill nodes hosting 1 DP rank each (8 cards per rank), and 4 decode nodes hosting 2 DP ranks each (4 cards per rank). The `launch_online_dp.py` above is reused as-is. The prefill side enables FlashComm1 and DSA CP; the decode side enables MLAPO and `DYNAMIC_EPLB` with a `FULL_DECODE_ONLY` graph. Both sides enable prefix caching and MTP (`num_speculative_tokens=3`). All IPs, NIC names, ports and weight paths below are placeholders. - -`run_dp_template.sh` for the prefill nodes: - -```bash -#!/usr/bin/bash -nic_name="" -local_ip="" - -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name -export VLLM_HOST_IP=$local_ip - -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib -export HCCL_OP_EXPANSION_MODE="AIV" -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=1 -export HCCL_BUFFSIZE=256 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export ASCEND_AGGREGATE_ENABLE=1 -export ASCEND_TRANSPORT_PRINT=1 -export ACL_OP_INIT_MODE=1 -export VLLM_NIXL_ABORT_REQUEST_TIMEOUT=300000 -export VLLM_VERSION=0.21.0 - -export ASCEND_RT_VISIBLE_DEVICES=$1 -export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 - -vllm serve \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --served-model-name glm5.2 \ - --max-model-len 115168 \ - --max-num-batched-tokens 4096 \ - --trust-remote-code \ - --max-num-seqs 64 \ - --gpu-memory-utilization 0.95 \ - --quantization ascend \ - --async-scheduling \ - --enable-chunked-prefill \ - --enable-prefix-caching \ - --enforce-eager \ - --enable-auto-tool-choice \ - --tool-call-parser glm47 \ - --reasoning-parser glm45 \ - --kv-transfer-config \ - '{ - "kv_connector": "MooncakeConnector", - "kv_role": "kv_producer", - "kv_port": "30000", - "engine_id": "0", - "kv_connector_module_path": "vllm_ascend.distributed.kv_transfer.kv_p2p.mooncake_connector", - "kv_connector_extra_config": { - "use_ascend_direct": true, - "prefill": { - "dp_size": 4, - "tp_size": 8 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' \ - --additional-config \ - '{ - "enable_sparse_c8": false, - "fuse_muls_add": true, - "multistream_overlap_shared_expert": true, - "recompute_scheduler_enable": true, - "ascend_compilation_config": { - "enable_npugraph_ex": true - }, - "enable_dsa_cp": true - }' \ - --speculative-config '{"num_speculative_tokens": 3, "method":"deepseek_mtp"}' -``` - -`run_dp_template.sh` for the decode nodes: - -```bash -#!/usr/bin/bash - -nic_name="" -local_ip="" - -export HCCL_IF_IP=$local_ip -export GLOO_SOCKET_IFNAME=$nic_name -export TP_SOCKET_IFNAME=$nic_name -export HCCL_SOCKET_IFNAME=$nic_name -export VLLM_HOST_IP=$local_ip - -export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib -export VLLM_ASCEND_ENABLE_MLAPO=1 -export HCCL_OP_EXPANSION_MODE="AIV" -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=1 -export HCCL_BUFFSIZE=500 -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export TASK_QUEUE_ENABLE=1 -export ASCEND_AGGREGATE_ENABLE=1 -export ASCEND_TRANSPORT_PRINT=1 -export ACL_OP_INIT_MODE=1 -export VLLM_VERSION=0.21.0 -export DYNAMIC_EPLB=1 - -export ASCEND_RT_VISIBLE_DEVICES=$1 - -vllm serve \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --served-model-name glm5.2 \ - --max-model-len 135168 \ - --max-num-batched-tokens 164 \ - --trust-remote-code \ - --max-num-seqs 48 \ - --gpu-memory-utilization 0.92 \ - --async-scheduling \ - --quantization ascend \ - --enable-prefix-caching \ - --enable-auto-tool-choice \ - --tool-call-parser glm47 \ - --reasoning-parser glm45 \ - --kv-transfer-config \ - '{ - "kv_connector": "MooncakeConnector", - "kv_role": "kv_consumer", - "kv_port": "30100", - "engine_id": "1", - "kv_connector_module_path": "vllm_ascend.distributed.kv_transfer.kv_p2p.mooncake_connector", - "kv_connector_extra_config": { - "use_ascend_direct": true, - "prefill": { - "dp_size": 4, - "tp_size": 8 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' \ - --compilation-config \ - '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ - --additional-config \ - '{ - "enable_sparse_c8": false, - "fuse_muls_add": true, - "multistream_overlap_shared_expert": true, - "recompute_scheduler_enable": true, - "ascend_compilation_config": { - "enable_npugraph_ex": true - } - }' \ - --speculative-config '{"num_speculative_tokens": 3, "method":"deepseek_mtp"}' -``` - -Once the preparation is done, start the server with the following commands: - -1. Prefill nodes — run on `$node_p0_ip`, `$node_p1_ip`, `$node_p2_ip`, `$node_p3_ip` with `--dp-rank-start` `0/1/2/3`: - - ```shell - python launch_online_dp.py --dp-size 4 --tp-size 8 --dp-size-local 1 --dp-rank-start 0 --dp-address $node_p0_ip --dp-rpc-port 16591 --vllm-start-port 9081 - python launch_online_dp.py --dp-size 4 --tp-size 8 --dp-size-local 1 --dp-rank-start 1 --dp-address $node_p0_ip --dp-rpc-port 16591 --vllm-start-port 9081 - python launch_online_dp.py --dp-size 4 --tp-size 8 --dp-size-local 1 --dp-rank-start 2 --dp-address $node_p0_ip --dp-rpc-port 16591 --vllm-start-port 9081 - python launch_online_dp.py --dp-size 4 --tp-size 8 --dp-size-local 1 --dp-rank-start 3 --dp-address $node_p0_ip --dp-rpc-port 16591 --vllm-start-port 9081 - ``` - -2. Decode nodes — run on `$node_d0_ip`, `$node_d1_ip`, `$node_d2_ip`, `$node_d3_ip` with `--dp-rank-start` `0/2/4/6`: - - ```shell - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 2 --dp-rank-start 0 --dp-address $node_d0_ip --dp-rpc-port 16600 --vllm-start-port 9900 - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 2 --dp-rank-start 2 --dp-address $node_d0_ip --dp-rpc-port 16600 --vllm-start-port 9900 - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 2 --dp-rank-start 4 --dp-address $node_d0_ip --dp-rpc-port 16600 --vllm-start-port 9900 - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 2 --dp-rank-start 6 --dp-address $node_d0_ip --dp-rpc-port 16600 --vllm-start-port 9900 - ``` - -For request forwarding on this 8-node A2 layout, use 4 prefiller hosts (1 endpoint each) and 4 decoder hosts (2 endpoints each) in the Request Forwarding command below. - -To set up request forwarding, run the following script on any machine. You can get the proxy program in the repository's examples: [load_balance_proxy_server_example.py](https://github.com/vllm-project/vllm-ascend/blob/main/examples/disaggregated_prefill_v1/load_balance_proxy_server_example.py) - -```shell -unset http_proxy -unset https_proxy - -python load_balance_proxy_server_example.py \ - --port 8000 \ - --host 0.0.0.0 \ - --prefiller-hosts \ - $node_p0_ip \ - $node_p1_ip \ - $node_p2_ip \ - $node_p3_ip \ - --prefiller-ports \ - 9081 9081 \ - 9081 9081 \ - --decoder-hosts \ - $node_d0_ip \ - $node_d0_ip \ - $node_d1_ip \ - $node_d1_ip \ - $node_d2_ip \ - $node_d2_ip \ - $node_d3_ip \ - $node_d3_ip \ - --decoder-ports \ - 9900 9901 9900 9901 \ - 9900 9901 9900 9901 \ -``` - -**Notice:** - -Some configurations for optimization are shown below: - -- `VLLM_ASCEND_ENABLE_FLASHCOMM1`: Enable FlashComm optimization to reduce communication and computation overhead on prefill node. With FlashComm enabled, layer_sharding list cannot include o_proj as an element. -- `VLLM_ASCEND_ENABLE_FUSED_MC2`: Enable following fused operators: dispatch_gmm_combine_decode and dispatch_ffn_combine operator. - -Please refer to the following python file for further explanation and restrictions of the environment variables above: [envs.py](https://github.com/vllm-project/vllm-ascend/blob/main/vllm_ascend/envs.py) - -## Functional Verification - -Once your server is started, you can query the model with input prompts: - -```shell -curl http://:/v1/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "glm-52", - "prompt": "The future of AI is", - "max_completion_tokens": 50, - "temperature": 0 - }' -``` - -## Accuracy Evaluation - -Here are two accuracy evaluation methods. - -### Using AISBench - -1. Refer to [Using AISBench](../../developer_guide/evaluation/using_ais_bench.md) for details. - -2. After execution, you can get the result. - -### Using Language Model Evaluation Harness - -Not tested yet. - -## Performance - -### Using AISBench - -Refer to [Using AISBench for performance evaluation](../../developer_guide/evaluation/using_ais_bench.md#execute-performance-evaluation) for details. - -### Using vLLM Benchmark - -Refer to [vllm benchmark](https://docs.vllm.ai/en/latest/contributing/) for more details. - -**Notice:** -`max-model-len` and `max-num-seqs` need to be set according to the actual usage scenario. For other settings, please refer to the **[Deployment](#deployment)** chapter. - -## FAQ - -- **Q: How to enable function calling for GLM-5.2?** - - A: Please add following configurations in vLLM startup command - - ```shell - --tool-call-parser glm47 \ - --reasoning-parser glm45 \ - --enable-auto-tool-choice \ - ``` diff --git a/docs/source/tutorials/models/GLM5.md b/docs/source/tutorials/models/GLM5.md index f639dbfcf..b7b157871 100644 --- a/docs/source/tutorials/models/GLM5.md +++ b/docs/source/tutorials/models/GLM5.md @@ -241,7 +241,7 @@ export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export VLLM_ASCEND_BALANCE_SCHEDULING=1 export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w4a8 \ +vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5-w4a8 \ --host 0.0.0.0 \ --port 8077 \ --data-parallel-size 1 \ @@ -409,7 +409,7 @@ export VLLM_ASCEND_BALANCE_SCHEDULING=1 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w4a8 \ +vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5-w4a8 \ --host 0.0.0.0 \ --port 8077 \ --data-parallel-size 2 \ @@ -456,7 +456,7 @@ export VLLM_ASCEND_BALANCE_SCHEDULING=1 export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w4a8 \ +vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM-5-w4a8 \ --host 0.0.0.0 \ --port 8077 \ --headless \ @@ -483,7 +483,7 @@ vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w4a8 \ :::: ::::: -- For bf16 weight, use this script on each node to enable [Multi Token Prediction (MTP)](../../user_guide/feature_guide/speculative_decoding.md). +- For bf16 weight, use this script on each node to enable [Multi Token Prediction (MTP)](../../user_guide/feature_guide/Multi_Token_Prediction.md). ```shell python adjust_weight.py "path_of_bf16_weight" @@ -658,7 +658,7 @@ vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w8a8 \ ### 5.3 Prefill-Decode Disaggregation -We'd like to show the deployment guide of `GLM-5` on multi-node environment with 1P1D for better performance. *Prefill-Decode Disaggregation* refers to the separation of the prefill stage and the decode stage across different nodes to improve throughput and latency. +We'd like to show the deployment guide of `GLM-5` on multi-node environment with 1P1D for better performance. Before you start, please @@ -799,7 +799,7 @@ Before you start, please export VLLM_ASCEND_ENABLE_FUSED_MC2=1 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w8a8 \ + vllm serve /root/.cache/glm5-w8a8 \ --host 0.0.0.0 \ --port $2 \ --data-parallel-size $3 \ @@ -879,7 +879,7 @@ Before you start, please export VLLM_ASCEND_ENABLE_FUSED_MC2=1 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w8a8 \ + vllm serve /root/.cache/glm5-w8a8 \ --host 0.0.0.0 \ --port $2 \ --data-parallel-size $3 \ @@ -961,7 +961,7 @@ Before you start, please export VLLM_ASCEND_ENABLE_MLAPO=1 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w8a8 \ + vllm serve /root/.cache/glm5-w8a8 \ --host 0.0.0.0 \ --port $2 \ --data-parallel-size $3 \ @@ -1041,7 +1041,7 @@ Before you start, please export VLLM_ASCEND_ENABLE_MLAPO=1 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w8a8 \ + vllm serve /root/.cache/glm5-w8a8 \ --host 0.0.0.0 \ --port $2 \ --data-parallel-size $3 \ @@ -1121,7 +1121,7 @@ Before you start, please export VLLM_ASCEND_ENABLE_MLAPO=1 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w8a8 \ + vllm serve /root/.cache/glm5-w8a8 \ --host 0.0.0.0 \ --port $2 \ --data-parallel-size $3 \ @@ -1201,7 +1201,7 @@ Before you start, please export VLLM_ASCEND_ENABLE_MLAPO=1 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib - vllm serve /root/.cache/modelscope/hub/models/vllm-ascend/GLM5-w8a8 \ + vllm serve /root/.cache/glm5-w8a8 \ --host 0.0.0.0 \ --port $2 \ --data-parallel-size $3 \ diff --git a/docs/source/tutorials/models/Kimi-K2.6.md b/docs/source/tutorials/models/Kimi-K2.6.md deleted file mode 100644 index 4cb923217..000000000 --- a/docs/source/tutorials/models/Kimi-K2.6.md +++ /dev/null @@ -1,850 +0,0 @@ -# Kimi-K2.6 - -## 1 Introduction - -Kimi K2.6 is an open-source, native multimodal agentic model built through continual pretraining on approximately 15 trillion mixed visual and text tokens atop Kimi-K2-Base. It seamlessly integrates vision and language understanding with advanced agentic capabilities, instant and thinking modes, as well as conversational and agentic paradigms. - -This document will show the main verification steps of the model, including supported features, feature configuration, environment preparation, single-node and multi-node deployment, accuracy and performance evaluation. - -This document is validated and written based on **vLLM-Ascend v0.20.0rc1**. The current model (Kimi-K2.6) is first supported in this version. - -## 2 Supported Features - -Refer to [supported features](../../user_guide/support_matrix/supported_models.md) to get the model's supported feature matrix. - -Refer to [feature guide](../../user_guide/feature_guide/index.md) to get the feature's configuration. - -## 3 Prerequisites - -### 3.1 Model Weight - -- `Kimi-K2.6-w4a8` (Quantized version for w4a8): requires 1 Atlas 800 A3 (64G × 16) node or 2 Atlas 800 A2 (64G × 8) nodes. [Download model weight](https://modelscope.cn/models/Eco-Tech/Kimi-K2.6-W4A8). -- `kimi-k2.6-eagle3` (Eagle3 MTP draft model for accelerating inference of Kimi-K2.6): [Download model weight](https://huggingface.co/lightseekorg/kimi-k2.6-eagle3) -- `Kimi-K2.5-DFlash` (a speculative decoding framework that leverages a lightweight block diffusion model for parallel drafting): [Download model weight](https://huggingface.co/z-lab/Kimi-K2.5-DFlash) - -It is recommended to download the model weight to the shared directory of multiple nodes, such as `/root/.cache/`. - -### 3.2 Verify Multi-node Communication (Optional) - -If you want to deploy multi-node environment, you need to verify multi-node communication according to [verify multi-node communication environment](../../installation.md#verify-multi-node-communication). - -## 4 Installation - -### 4.1 Docker Image Installation - -Select an image based on your machine type and start the docker image on your node, refer to [using docker](../../installation.md#set-up-using-docker). - -**A3 series** - -Start the docker image on your each node. - -```bash -export IMAGE=quay.io/ascend/vllm-ascend:|vllm_ascend_version|-a3 -docker run --rm \ - --name vllm-ascend \ - --shm-size=1g \ - --net=host \ - --device /dev/davinci0 \ - --device /dev/davinci1 \ - --device /dev/davinci2 \ - --device /dev/davinci3 \ - --device /dev/davinci4 \ - --device /dev/davinci5 \ - --device /dev/davinci6 \ - --device /dev/davinci7 \ - --device /dev/davinci8 \ - --device /dev/davinci9 \ - --device /dev/davinci10 \ - --device /dev/davinci11 \ - --device /dev/davinci12 \ - --device /dev/davinci13 \ - --device /dev/davinci14 \ - --device /dev/davinci15 \ - --device /dev/davinci_manager \ - --device /dev/devmm_svm \ - --device /dev/hisi_hdc \ - -v /usr/local/dcmi:/usr/local/dcmi \ - -v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \ - -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ - -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \ - -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \ - -v /etc/ascend_install.info:/etc/ascend_install.info \ - -v /root/.cache:/root/.cache \ - -it $IMAGE bash -``` - -**A2 series** - -Start the docker image on your each node. - -```bash -export IMAGE=quay.io/ascend/vllm-ascend:|vllm_ascend_version| -docker run --rm \ - --name vllm-ascend \ - --shm-size=1g \ - --net=host \ - --device /dev/davinci0 \ - --device /dev/davinci1 \ - --device /dev/davinci2 \ - --device /dev/davinci3 \ - --device /dev/davinci4 \ - --device /dev/davinci5 \ - --device /dev/davinci6 \ - --device /dev/davinci7 \ - --device /dev/davinci_manager \ - --device /dev/devmm_svm \ - --device /dev/hisi_hdc \ - -v /usr/local/dcmi:/usr/local/dcmi \ - -v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \ - -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ - -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \ - -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \ - -v /etc/ascend_install.info:/etc/ascend_install.info \ - -v /root/.cache:/root/.cache \ - -it $IMAGE bash -``` - -After a successful docker run, you can verify the running container service by executing the `docker ps` command. - -### 4.2 Source Code Installation - -If you don't want to use the docker image as above, you can also build all from source: - -- Install `vllm-ascend` from source, refer to [installation](../../installation.md). - -If you want to deploy multi-node environment, you need to set up environment on each node. - -To use the tools_call feature, please ensure that your transformers version is 4.57.6 or lower. If vllm-ascend has been upgraded to v0.21 or later, this requirement no longer applies. - -## 5 Online Service Deployment - -### 5.1 Single-Node Online Deployment - -Single-node deployment completes both Prefill and Decode within the same node. The quantized model `Kimi-K2.6-w4a8` can be deployed on 1 Atlas 800 A3 (64G × 16). - -While a single-node setup supports all input/output scenarios, consider deploying multinodes for optimal performance. - -Startup Command: - -```bash -#!/bin/sh -export HCCL_OP_EXPANSION_MODE="AIV" -export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True -export OMP_PROC_BIND=false -export OMP_NUM_THREADS=1 -export TASK_QUEUE_ENABLE=1 -export VLLM_ASCEND_ENABLE_MLAPO=1 - -# [Optional] jemalloc -# jemalloc is for better performance, if `libjemalloc.so` is installed on your machine, you can turn it on. -export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD -echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor -sysctl -w vm.swappiness=0 -sysctl -w kernel.numa_balancing=0 -sysctl -w kernel.sched_migration_cost_ns=50000 - -export HCCL_BUFFSIZE=800 -export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 -export VLLM_ASCEND_BALANCE_SCHEDULING=1 - -vllm serve Eco-Tech/Kimi-K2.6-W4A8 \ - --quantization ascend \ - --served-model-name kimi_k26 \ - --allowed-local-media-path / \ - --trust-remote-code \ - --tensor-parallel-size 4 \ - --data-parallel-size 4 \ - --no-enable-prefix-caching \ - --enable-expert-parallel \ - --port 8088 \ - --max-num-seqs 4 \ - --max-model-len 32768 \ - --max-num-batched-tokens 16384 \ - --gpu-memory-utilization 0.9 \ - --seed 42 \ - --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \ - --mm-processor-cache-gb 0 \ - --mm-encoder-tp-mode data \ - --speculative-config '{"method": "dflash","model": "z-lab/Kimi-K2.6-DFlash", "num_speculative_tokens": 15}' -``` - -Key Parameter Descriptions: - -- Setting the environment variable `VLLM_ASCEND_BALANCE_SCHEDULING=1` enables balance scheduling. This may help increase output throughput and reduce TPOT in v1 scheduler. However, TTFT may degrade in some scenarios. Furthermore, enabling this feature is not recommended in scenarios where PD is separated. -- `--max-model-len` specifies the maximum context length - that is, the sum of input and output tokens for a single request. For performance testing with an input length of 3.5K and output length of 1.5K, a value of `16384` is sufficient, however, for precision testing, please set it at least `35000`. -- `--no-enable-prefix-caching` indicates that prefix caching is disabled. To enable it, remove this option. -- `--mm-encoder-tp-mode` indicates how to optimize multi-modal encoder inference using tensor parallelism (TP). If you want to test the multimodal inputs, we recommend using `data`. -- If you use the w4a8 weight, more memory will be allocated to kvcache, and you can try to increase system throughput to achieve greater throughput. - -Common Issues Tip: If you encounter issues, please refer to the [Public FAQ](https://docs.vllm.ai/projects/ascend/en/latest/faqs.html) for troubleshooting. - -Service Verification: - -```shell -curl http://:8088/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "kimi_k26", - "messages": [{ - "role": "user", - "content": [ - { - "type": "text", - "text": "The future of AI is" - }] - }], - "max_tokens": 1024, - "temperature": 1.0, - "top_p": 0.95 - }' -``` - -Expected Result: - -The service returns HTTP 200 OK with a JSON response containing the `choices` field. Example output (content truncated for brevity): - -```json -{ - "id": "chatcmpl-9df13fd5e539af93", - "object": "chat.completion", - "created": 1780971952, - "model": "kimi_k26", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "The future of AI is not a destination we are passively approaching, but a design problem we are actively solving right now...", - "reasoning": "The user is asking for my thoughts on \"The future of AI is\"...", - "refusal": null, - "annotations": null, - "audio": null, - "function_call": null - }, - "logprobs": null, - "finish_reason": "length", - "stop_reason": null, - "token_ids": null - } - ], - "usage": { - "prompt_tokens": 13, - "total_tokens": 1037, - "completion_tokens": 1024, - "completion_tokens_details": { - "reasoning_tokens": 0, - "audio_tokens": null, - "accepted_prediction_tokens": null, - "rejected_prediction_tokens": null - } - } -} -``` - -### 5.2 Multi-Node PD Separation Deployment - -We recommend using Mooncake for deployment: [Mooncake](../features/pd_disaggregation_mooncake_multi_node.md). - -In the standard single-node deployment mode, Prefill (prompt processing) and Decode (token generation) tasks run on the same set of NPUs. This can lead to two issues: - -1. **Prefill preemption interrupts Decode**: Prefill is a compute-intensive task that processes the entire input context at once, while Decode generates tokens one by one. When a new user request arrives, its Prefill phase can preempt and interrupt ongoing Decode tasks, causing jitter and higher time-per-output-token (TPOT) latency. -2. **Inflexible resource allocation**: Prefill and Decode have fundamentally different computational characteristics — Prefill is compute-bound and memory-bandwidth-intensive, while Decode is memory-bandwidth-bound. Running them on the same hardware forces a compromise that satisfies neither optimally. - -PD (Prefill-Decode) separation addresses these issues by running Prefill and Decode on dedicated node groups, each configured independently: - -- **Prefill nodes** focus on high-throughput prompt processing, optimized for compute and communication (e.g., enabling FlashComm for Allreduce acceleration). - -- **Decode nodes** focus on low-latency token generation, optimized for memory bandwidth (e.g., enabling MLAPO fusion operators). - -This architecture is recommended for production deployments with concurrent multi-user workloads, where stable latency and high throughput are both required. - -Take Atlas 800 A3 (64G × 16) for example, we recommend to deploy 2P1D (4 nodes) rather than 1P1D (2 nodes), because there is not enough NPU memory to serve high concurrency in 1P1D case. - -- `Kimi-K2.6-w4a8 2P1D`: requires 4 Atlas 800 A3 (64G × 16) nodes. - -To run the vllm-ascend `Prefill-Decode Disaggregation` service, you need to deploy a `launch_online_dp.py` script and a `run_dp_template.sh` script on each node and deploy a `proxy.sh` script on prefill master node to forward requests. - -1. `launch_online_dp.py` to launch external dp vllm servers. - [launch_online_dp.py](https://github.com/vllm-project/vllm-ascend/blob/main/examples/external_online_dp/launch_online_dp.py) - - Parameter descriptions: - - |Parameter|Type|Required|Default|Description| - |---------|----|--------|-------|-----------| - |`--dp-size`|int|Yes|-|Data parallel size (total number of DP ranks across all nodes).| - |`--tp-size`|int|No|1|Tensor parallel size within each DP rank.| - |`--dp-size-local`|int|No|(same as `--dp-size`)|Number of DP ranks on the current node. If not set, defaults to `--dp-size`.| - |`--dp-rank-start`|int|No|0|Starting rank offset for data parallel ranks on this node.| - |`--dp-address`|str|Yes|-|IP address of the data parallel master node (node 0).| - |`--dp-rpc-port`|str|No|12345|RPC port for data parallel master communication.| - |`--vllm-start-port`|int|No|9000|Starting port for each vLLM engine instance on this node. Each DP rank's engine port = `vllm_start_port` + local rank index.| - -2. Prefill Node 0 `run_dp_template.sh` script - - ```shell - # this obtained through ifconfig - # nic_name is the network interface name corresponding to local_ip of the current node - nic_name="xxx" - local_ip="141.xx.xx.1" - - # The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) - node0_ip="xxxx" - - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - - # [Optional] jemalloc - # jemalloc is for better performance, if `libjemalloc.so` is installed on your machine, you can turn it on. - export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD - echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - sysctl -w vm.swappiness=0 - sysctl -w kernel.numa_balancing=0 - sysctl kernel.sched_migration_cost_ns=50000 - export VLLM_RPC_TIMEOUT=3600000 - export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000 - - export HCCL_OP_EXPANSION_MODE="AIV" - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export TASK_QUEUE_ENABLE=1 - export ASCEND_BUFFER_POOL=4:8 - export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH - - export HCCL_BUFFSIZE=800 - export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 - export ASCEND_RT_VISIBLE_DEVICES=$1 - - vllm serve Eco-Tech/Kimi-K2.6-W4A8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --quantization ascend \ - --served-model-name kimi_k26 \ - --trust-remote-code \ - --max-num-seqs 4 \ - --max-model-len 32768 \ - --max-num-batched-tokens 16384 \ - --no-enable-prefix-caching \ - --gpu-memory-utilization 0.95 \ - --enforce-eager \ - --speculative-config '{"method": "eagle3", "model":"lightseekorg/kimi-k2.6-eagle3", "num_speculative_tokens": 3}' \ - --additional-config '{"recompute_scheduler_enable":true}' \ - --mm-encoder-tp-mode data \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_producer", - "kv_port": "30000", - "engine_id": "0", - "kv_connector_extra_config": { - "prefill": { - "dp_size": 4, - "tp_size": 4 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - ``` - -3. Prefill Node 1 `run_dp_template.sh` script - - ```shell - # this obtained through ifconfig - # nic_name is the network interface name corresponding to local_ip of the current node - nic_name="xxx" - local_ip="141.xx.xx.2" - - # The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) - node0_ip="xxxx" - - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - - # [Optional] jemalloc - # jemalloc is for better performance, if `libjemalloc.so` is installed on your machine, you can turn it on. - export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD - echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - sysctl -w vm.swappiness=0 - sysctl -w kernel.numa_balancing=0 - sysctl kernel.sched_migration_cost_ns=50000 - export VLLM_RPC_TIMEOUT=3600000 - export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000 - - export HCCL_OP_EXPANSION_MODE="AIV" - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export TASK_QUEUE_ENABLE=1 - export ASCEND_BUFFER_POOL=4:8 - export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH - - export HCCL_BUFFSIZE=800 - export VLLM_ASCEND_ENABLE_FLASHCOMM1=1 - export ASCEND_RT_VISIBLE_DEVICES=$1 - - vllm serve Eco-Tech/Kimi-K2.6-W4A8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --quantization ascend \ - --served-model-name kimi_k26 \ - --trust-remote-code \ - --max-num-seqs 4 \ - --max-model-len 32768 \ - --max-num-batched-tokens 16384 \ - --no-enable-prefix-caching \ - --gpu-memory-utilization 0.95 \ - --enforce-eager \ - --speculative-config '{"method": "eagle3", "model":"lightseekorg/kimi-k2.6-eagle3", "num_speculative_tokens": 3}' \ - --additional-config '{"recompute_scheduler_enable":true}' \ - --mm-encoder-tp-mode data \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_producer", - "kv_port": "30100", - "engine_id": "1", - "kv_connector_extra_config": { - "prefill": { - "dp_size": 4, - "tp_size": 4 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - ``` - -4. Decode Node 0 `run_dp_template.sh` script - - ```shell - # this obtained through ifconfig - # nic_name is the network interface name corresponding to local_ip of the current node - nic_name="xxx" - local_ip="141.xx.xx.3" - - # The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) - node0_ip="xxxx" - - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - - # [Optional] jemalloc - # jemalloc is for better performance, if `libjemalloc.so` is installed on your machine, you can turn it on. - export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD - echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - sysctl -w vm.swappiness=0 - sysctl -w kernel.numa_balancing=0 - sysctl kernel.sched_migration_cost_ns=50000 - export VLLM_RPC_TIMEOUT=3600000 - export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000 - - export HCCL_OP_EXPANSION_MODE="AIV" - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export TASK_QUEUE_ENABLE=1 - export ASCEND_BUFFER_POOL=4:8 - export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH - - export HCCL_BUFFSIZE=800 - export VLLM_ASCEND_ENABLE_MLAPO=1 - export ASCEND_RT_VISIBLE_DEVICES=$1 - - vllm serve Eco-Tech/Kimi-K2.6-W4A8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --quantization ascend \ - --served-model-name kimi_k26 \ - --trust-remote-code \ - --max-num-seqs 8 \ - --max-model-len 32768 \ - --max-num-batched-tokens 32 \ - --no-enable-prefix-caching \ - --gpu-memory-utilization 0.91 \ - --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ - --additional-config '{"recompute_scheduler_enable":true,"multistream_overlap_shared_expert": false}' \ - --speculative-config '{"method": "eagle3", "model":"lightseekorg/kimi-k2.6-eagle3", "num_speculative_tokens": 3}' \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_consumer", - "kv_port": "30200", - "engine_id": "2", - "kv_connector_extra_config": { - "prefill": { - "dp_size": 4, - "tp_size": 4 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - ``` - -5. Decode Node 1 `run_dp_template.sh` script - - ```shell - # this obtained through ifconfig - # nic_name is the network interface name corresponding to local_ip of the current node - nic_name="xxx" - local_ip="141.xx.xx.4" - - # The value of node0_ip must be consistent with the value of local_ip set in node0 (master node) - node0_ip="xxxx" - - export HCCL_IF_IP=$local_ip - export GLOO_SOCKET_IFNAME=$nic_name - export TP_SOCKET_IFNAME=$nic_name - export HCCL_SOCKET_IFNAME=$nic_name - - # [Optional] jemalloc - # jemalloc is for better performance, if `libjemalloc.so` is installed on your machine, you can turn it on. - export LD_PRELOAD=/usr/lib/aarch64-linux-gnu/libjemalloc.so.2:$LD_PRELOAD - echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - sysctl -w vm.swappiness=0 - sysctl -w kernel.numa_balancing=0 - sysctl kernel.sched_migration_cost_ns=50000 - export VLLM_RPC_TIMEOUT=3600000 - export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=30000 - - export HCCL_OP_EXPANSION_MODE="AIV" - export PYTORCH_NPU_ALLOC_CONF=expandable_segments:True - export OMP_PROC_BIND=false - export OMP_NUM_THREADS=1 - export TASK_QUEUE_ENABLE=1 - export ASCEND_BUFFER_POOL=4:8 - export LD_LIBRARY_PATH=/usr/local/Ascend/ascend-toolkit/latest/python/site-packages/mooncake:$LD_LIBRARY_PATH - - export HCCL_BUFFSIZE=1100 - export VLLM_ASCEND_ENABLE_MLAPO=1 - export ASCEND_RT_VISIBLE_DEVICES=$1 - - vllm serve Eco-Tech/Kimi-K2.6-W4A8 \ - --host 0.0.0.0 \ - --port $2 \ - --data-parallel-size $3 \ - --data-parallel-rank $4 \ - --data-parallel-address $5 \ - --data-parallel-rpc-port $6 \ - --tensor-parallel-size $7 \ - --enable-expert-parallel \ - --seed 1024 \ - --quantization ascend \ - --served-model-name kimi_k26 \ - --trust-remote-code \ - --max-num-seqs 8 \ - --max-model-len 32768 \ - --max-num-batched-tokens 4 \ - --no-enable-prefix-caching \ - --gpu-memory-utilization 0.91 \ - --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ - --additional-config '{"recompute_scheduler_enable":true,"multistream_overlap_shared_expert": false}' \ - --speculative-config '{"method": "eagle3", "model":"lightseekorg/kimi-k2.6-eagle3", "num_speculative_tokens": 3}' \ - --kv-transfer-config \ - '{"kv_connector": "MooncakeConnectorV1", - "kv_role": "kv_consumer", - "kv_port": "30200", - "engine_id": "2", - "kv_connector_extra_config": { - "prefill": { - "dp_size": 4, - "tp_size": 4 - }, - "decode": { - "dp_size": 8, - "tp_size": 4 - } - } - }' - ``` - -Key Parameter Descriptions: - -- `VLLM_ASCEND_ENABLE_FLASHCOMM1=1`: enables the communication optimization function on the prefill nodes. -- `VLLM_ASCEND_ENABLE_MLAPO=1`: enables the fusion operator, which can significantly improve performance but consumes more NPU memory. In the Prefill-Decode (PD) separation scenario, enable MLAPO only on decode nodes. -- `recompute_scheduler_enable: true`: enables the recomputation scheduler. When the Key-Value Cache (KV Cache) of the decode node is insufficient, requests will be sent to the prefill node to recompute the KV Cache. In the PD separation scenario, it is recommended to enable this configuration on both prefill and decode nodes simultaneously. -- `multistream_overlap_shared_expert: true`: When the Tensor Parallelism (TP) size is 1 or `enable_shared_expert_dp: true`, an additional stream is enabled to overlap the computation process of shared experts for improved efficiency. - -6. Run server for each node: - - ```shell - # p0 - python launch_online_dp.py --dp-size 4 --tp-size 4 --dp-size-local 4 --dp-rank-start 0 --dp-address 141.xx.xx.1 --dp-rpc-port 12321 --vllm-start-port 7100 - # p1 - python launch_online_dp.py --dp-size 4 --tp-size 4 --dp-size-local 4 --dp-rank-start 0 --dp-address 141.xx.xx.2 --dp-rpc-port 12321 --vllm-start-port 7100 - # d0 - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 8 --dp-rank-start 0 --dp-address 141.xx.xx.3 --dp-rpc-port 12321 --vllm-start-port 7100 - # d1 - python launch_online_dp.py --dp-size 8 --tp-size 4 --dp-size-local 8 --dp-rank-start 8 --dp-address 141.xx.xx.3 --dp-rpc-port 12321 --vllm-start-port 7100 - ``` - -7. Run the `proxy.sh` script on the prefill master node - - Run a proxy server on the same node with the prefiller service instance. You can get the proxy program in the repository's examples: [load_balance_proxy_server_example.py](https://github.com/vllm-project/vllm-ascend/blob/main/examples/disaggregated_prefill_v1/load_balance_proxy_server_example.py) - - ```shell - python load_balance_proxy_server_example.py \ - --port 1999 \ - --host 141.xx.xx.1 \ - --prefiller-hosts \ - 141.xx.xx.1 \ - 141.xx.xx.1 \ - 141.xx.xx.1 \ - 141.xx.xx.1 \ - 141.xx.xx.2 \ - 141.xx.xx.2 \ - 141.xx.xx.2 \ - 141.xx.xx.2 \ - --prefiller-ports \ - 7100 7101 7102 7103 7100 7101 7102 7103 \ - --decoder-hosts \ - 141.xx.xx.3 \ - 141.xx.xx.3 \ - 141.xx.xx.3 \ - 141.xx.xx.3 \ - 141.xx.xx.4 \ - 141.xx.xx.4 \ - 141.xx.xx.4 \ - 141.xx.xx.4 \ - --decoder-ports \ - 7100 7101 7102 7103 \ - 7100 7101 7102 7103 \ - ``` - - ```shell - cd vllm-ascend/examples/disaggregated_prefill_v1/ - bash proxy.sh - ``` - -Deployment Verification: - -After the PD separation service is fully started, send a request through the proxy port on the prefill master node to verify that Prefill and Decode nodes are working correctly together: - -```shell -curl http://141.xx.xx.1:1999/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "kimi_k26", - "messages": [{ - "role": "user", - "content": [ - { - "type": "text", - "text": "The future of AI is" - }] - }], - "max_tokens": 1024, - "temperature": 1.0, - "top_p": 0.95 - }' -``` - -Expected Result: - -The proxy returns HTTP 200 OK. The JSON response contains the `choices` field with the generated text, confirming that Prefill nodes have successfully processed the prompt and Decode nodes have generated the response: - -```json -{ - "id": "chatcmpl-xxxxxxxxxxxxx", - "object": "chat.completion", - "model": "kimi_k26", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "The future of AI is not a destination we are passively approaching...", - "finish_reason": "length" - } - } - ], - "usage": { - "prompt_tokens": 13, - "total_tokens": 1037, - "completion_tokens": 1024 - } -} -``` - -Common Issues Tip: If you encounter issues with PD separation deployment, please refer to the [Public FAQ](https://docs.vllm.ai/projects/ascend/en/latest/faqs.html) for troubleshooting. - -## 6 Functional Verification - -Once your server is started, you can query the model with input prompts: - -```shell -curl http://:/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "kimi_k26", - "messages": [{ - "role": "user", - "content": [ - { - "type": "text", - "text": "The future of AI is" - }] - }], - "max_tokens": 1024, - "temperature": 1.0, - "top_p": 0.95 - }' -``` - -Expected Result: - -The service returns HTTP 200 OK. The JSON response contains the `choices` field with the generated text, along with usage statistics: - -```json -{ - "id": "chatcmpl-9df13fd5e539af93", - "object": "chat.completion", - "created": 1780971952, - "model": "kimi_k26", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "The future of AI is not a destination we are passively approaching, but a design problem we are actively solving right now...", - "reasoning": "The user is asking for my thoughts on...", - "finish_reason": "length" - } - } - ], - "usage": { - "prompt_tokens": 13, - "total_tokens": 1037, - "completion_tokens": 1024 - } -} -``` - -## 7 Accuracy Evaluation - -Here is one accuracy evaluation method. - -### Using AISBench - -1. Refer to [Using AISBench](../../developer_guide/evaluation/using_ais_bench.md) for details. - -2. After execution, you can get the result. Here is the result of `Kimi-K2.6-w4a8` in `vllm-ascend:v0.20.0rc1` for reference only. - -| dataset | version | metric | mode | vllm-api-general-chat | note | -| ----- | ----- | ----- | ----- | ----- | ----- | -| AIME2026 | - | accuracy | gen | 90.00 | 1 Atlas 800 A3 (64G × 16) | -| GPQA | - | accuracy | gen | 89.90 | 1 Atlas 800 A3 (64G × 16) | -| MMMU | - | accuracy | gen | 82.67 | 1 Atlas 800 A3 (64G × 16) | - -## 8 Performance Evaluation - -### Using AISBench - -Refer to [Using AISBench for performance evaluation](../../developer_guide/evaluation/using_ais_bench.md#execute-performance-evaluation) for details. - -### Using vLLM Benchmark - -Run performance evaluation of `Kimi-K2.6-w4a8` as an example. - -Refer to [vllm benchmark](https://docs.vllm.ai/en/latest/benchmarking/) for more details. - -There are three `vllm bench` subcommands: - -- `latency`: Benchmark the latency of a single batch of requests. -- `serve`: Benchmark the online serving throughput. -- `throughput`: Benchmark offline inference throughput. - -Take the `serve` as an example. Run the code as follows. - -```shell -export VLLM_USE_MODELSCOPE=True -vllm bench serve --model Eco-Tech/Kimi-K2.6-w4a8 --dataset-name random --random-input 1024 --num-prompts 200 --request-rate 1 --save-result --result-dir ./ -``` - -After about several minutes, you can get the performance evaluation result. - -## 9 Performance Tuning - -### 9.1 Recommended Configurations - -> **Note**: The following configurations are validated in specific test environments and are for reference only. The optimal configuration depends on factors such as maximum input/output length, prefix cache hit rate, precision requirements, and deployment machine ratios. It is recommended to refer to Section 9.2 for tuning based on actual conditions. - -#### Table 1: Scenario Overview - -> `*Total NPUs` indicates the total number of NPUs used across all nodes. 1 node = 1 Atlas 800 A3 server (64G × 16 NPUs). - -|Scenario|Deployment Mode|*Total NPUs|Weight Version|Key Considerations| -|--------|---------------|-----------|--------------|------------------| -|High Throughput
(16K context)|Single-Node Mixed|16 (A3)|kimi-k2.6-w4a8|Use dp2 tp8 to balance memory capacity and compute efficiency| -|High Throughput
(16K context)|1P1D deployment|32 (A3)|kimi-k2.6-w4a8|dp2 tp8 on both P and D nodes; balanced latency and throughput| -|High Throughput
(16K context)|2P2D deployment|64 (A3)|kimi-k2.6-w4a8|Scale from dp4 tp4 to dp8 tp4 across nodes| -|Long Context
(128K, no prefix cache)|Single-Node Mixed|16 (A3)|kimi-k2.6-w4a8|dp1 tp16 to maximize TP, accommodate extreme context lengths| -|Long Context
(128K, with prefix cache)|Single-Node Mixed|16 (A3)|kimi-k2.6-w4a8|dp2 tp8 to optimize memory bandwidth and improve cache utilization| -|Multimodal
(1080P)|Single-Node Mixed|16 (A3)|kimi-k2.6-w4a8|dp1 tp16 for high-resolution visual inputs| -|Multimodal
(1080P)|1P1D deployment|32 (A3)|kimi-k2.6-w4a8|dp2 tp8 or dp16 tp1, depending on memory and concurrency| -|Multimodal
(1080P)|2P2D deployment|64 (A3)|kimi-k2.6-w4a8|dp8 tp2 to dp32 tp1, maximize throughput for heavy multimodal workloads| - -#### Table 2: Detailed Node Configuration - -|Scenario|Configuration|NPUs|TP|DP|Max Model Len|MTP Speculation Num| -|--------|-------------|-----|--|--|-------------------|--------------------| -|High Throughput / Low Latency (16K)|Server / Single Machine|16|8|2|~16K|15| -|High Throughput / Low Latency (16K)|Server-P Node|16|8|2|~16K|3| -|High Throughput / Low Latency (16K)|Server-D Node|16|8|2|~16K|3| -|Long Context (128K, no cache)|Server / Single Machine|16|16|1|128K|15| -|Long Context (128K, with cache)|Server / Single Machine|16|8|2|128K|15| -|Multimodal (1080P)|Server / Single Machine|16|16|1|~16K|15| -|Multimodal (1080P)|Server-P Node|16|8|2|~16K|3| -|Multimodal (1080P)|Server-D Node|16|1|16|~16K|3| - -> For complete startup commands and parameter descriptions, please refer to the deployment examples in [Chapter 5](#5-online-service-deployment). - -**Notice:** -`max-model-len` and `max-num-seqs` need to be set according to the actual usage scenario. For other settings, please refer to the **[Deployment](#5-online-service-deployment)** chapter. - -### 9.2 Tuning Guidelines - -#### 9.2.1 General Tuning Reference - -Please refer to the [Public Performance Tuning Documentation](../../developer_guide/performance_and_debug/optimization_and_tuning.md) for tuning methods. - -Please refer to the [Feature Guide](../../user_guide/support_matrix/feature_matrix.md) for detailed feature descriptions. - -## 10 FAQ - -For common environment, installation, and general parameter issues, please refer to the [Public FAQ](https://docs.vllm.ai/projects/ascend/en/latest/faqs.html); this chapter only covers model-specific issues. - -- **Q: What transformer version is required for tools_call feature?** - - A: To use the tools_call feature, please ensure that your transformers version is 4.57.6 or lower. If vllm-ascend has been upgraded to v0.21 or later, this requirement no longer applies. diff --git a/docs/source/tutorials/models/Qwen3.5-27B.md b/docs/source/tutorials/models/Qwen3.5-27B.md index 9b2e2144c..c5862037d 100644 --- a/docs/source/tutorials/models/Qwen3.5-27B.md +++ b/docs/source/tutorials/models/Qwen3.5-27B.md @@ -116,7 +116,7 @@ vllm serve Eco-Tech/Qwen3.5-27B-w8a8-mtp \ --trust-remote-code \ --gpu-memory-utilization 0.90 \ --no-enable-prefix-caching \ ---speculative-config '{"method": "qwen3_5_mtp", "num_speculative_tokens": 3, "enforce_eager": true}' \ +--speculative_config '{"method": "qwen3_5_mtp", "num_speculative_tokens": 3, "enforce_eager": true}' \ --compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}' \ --additional-config '{"enable_cpu_binding":true}' \ ``` diff --git a/docs/source/tutorials/models/index.md b/docs/source/tutorials/models/index.md index 4f8d7d7d9..c143284d9 100644 --- a/docs/source/tutorials/models/index.md +++ b/docs/source/tutorials/models/index.md @@ -30,10 +30,8 @@ DeepSeek-R1.md DeepSeekOCR2.md GLM4.x.md GLM5.md -GLM5.2.md Kimi-K2-Thinking.md Kimi-K2.5.md -Kimi-K2.6.md PaddleOCR-VL.md MiniMax-M2.5.md Hunyuan-A13B-Instruct.md diff --git a/docs/source/user_guide/configuration/additional_config.md b/docs/source/user_guide/configuration/additional_config.md index 347f7cb87..9d57cf9c3 100644 --- a/docs/source/user_guide/configuration/additional_config.md +++ b/docs/source/user_guide/configuration/additional_config.md @@ -85,8 +85,7 @@ The following table lists additional configuration options available in vLLM Asc | `pa_shape_list` | list | `[]` | The custom shape list of page attention ops. | | `enable_kv_nz` | bool | `False` | Whether to enable KV cache NZ layout. This option only takes effects on models using MLA (e.g., DeepSeek). | | `layer_sharding` | dict | `{}` | Configuration options for Layer Sharding Linear. Layer Sharding can only be enabled in PD-disaggregated's P node. | -| `enable_sparse_c8` | bool | `False` | Whether to enable KV cache C8 in DSA models (e.g., DeepSeekV3.2 and GLM5). Not supported on Ascend 950 devices now | -| `c8_enable_reshape_optim` | bool | `False` | Whether to enable StoreKVBlock operator achieves acceleration under the C8 feature (this means that enable_sparse_c8 needs to be enabled). In the PD separation scenario, only the P node is enabled. | +| `enable_sparse_c8` | bool | `False` | Whether to enable KV cache C8 in DSA models (e.g., DeepSeekV3.2 and GLM5). Not supported on A5 devices now | | `enable_mc2_hierarchy_comm` | bool | `False` | Enable dispatch/combine op inter-node communication by ROCE. | | `profiling_chunk_config` | dict | `{}` | Configuration options for dynamic chunked pipeline parallel. See [Dynamic Chunked Pipeline Parallel](../feature_guide/dynamic_chunk_pipeline_parallel.md) for details. | | `enable_balance_scheduling` | bool | `False` | Whether to enable balance scheduling. Can also be configured via `VLLM_ASCEND_BALANCE_SCHEDULING` environment variable (deprecated). | @@ -100,8 +99,6 @@ The following table lists additional configuration options available in vLLM Asc | `enable_fused_mc2` | int | `0` | Fused MC2 configuration. Can also be configured via `VLLM_ASCEND_ENABLE_FUSED_MC2` environment variable (deprecated). | | `enable_transpose_kv_cache_by_block`| bool | `True` | Whether to enable transpose KV cache by block. Can also be configured via `VLLM_ASCEND_FUSION_OP_TRANSPOSE_KV_CACHE_BY_BLOCK` environment variable (deprecated). | | `enable_dsa_cp` | bool | `False` | Whether to enable dsa_cp for DeepSeek V3.2, DeepSeek V4, and other models with the same architecture. This feature depends on FLASHCOMM1. Please ensure that FLASHCOMM1 is enabled before enabling this feature.| -| `rejection_sampler_config` | dict | `{}` | Configuration options for rejection sampler (block verify and entropy verify). | -| `multistream_dsv4_dsa_overlap` | bool | `True` | Whether to enable dsa multi-stream overlap for DeepSeek V4. | The details of each configuration option are as follows: @@ -149,7 +146,6 @@ The details of each configuration option are as follows: | `algorithm_execution_interval` | int | `30` | The forward iterations when the EPLB worker will finish CPU tasks. | | `expert_map_record_path` | str | `None` | Save the expert load calculation results to a new expert table in the specified directory.| | `num_redundant_experts` | int | `0` | Specify redundant experts during initialization. | -| `eplb_policy_type` | int | `1` | EPLB balancing policy: `0`=Random, `1`=DefaultEplb (open-source algorithm), `2`=SwiftBalanceEplb (optimized for low-bandwidth), `3`=FlashLB (statistical method with sliding windows). | **profiling_chunk_config** @@ -160,17 +156,6 @@ The details of each configuration option are as follows: | `min_chunk` | int | `4096` | Minimum chunk size for dynamic calculation. Should be smaller than `max-num-batched-tokens`. | | `need_timing` | bool | True | Enable/disable Online Calibration | -**rejection_sampler_config** - -> **Note**: Both block verify and entropy verify improve speculative decoding performance (higher acceptance rate, lower latency) at the cost of reduced sampling precision. A larger `posterior_alpha` makes the adjustment more aggressive — it further lowers the acceptance threshold for high-entropy tokens, improving throughput but degrading output quality. Users should tune these parameters based on their specific model weights and application scenario to find the right trade-off between performance and precision. - -| Name | Type | Default | Description | -| ---- | ---- | ------- | ----------- | -| `enable_block_verify` | bool | `False` | Whether to enable block verify mode. Block verify evaluates all draft tokens as a block using cumulative probability products, which can improve acceptance rate. | -| `enable_entropy_verify` | bool | `False` | Whether to enable entropy verify mode. Entropy verify adjusts the acceptance threshold based on the entropy of the target distribution — higher entropy (uncertain) tokens get a lower threshold (easier to accept), while lower entropy (confident) tokens get a stricter threshold. | -| `posterior_threshold` | float | `0.95` | Upper bound for the entropy-adjusted acceptance threshold. Must be in (0, 1]. The effective threshold is `min(exp(-entropy * posterior_alpha), posterior_threshold)`. | -| `posterior_alpha` | float | `0.4` | Scaling factor for entropy in the threshold computation. Must be >= 0. Higher values make the threshold more sensitive to entropy — high-entropy tokens become much easier to accept, improving performance but reducing precision. | - ### Example An example of additional configuration is as follows: @@ -201,12 +186,6 @@ An example of additional configuration is as follows: }, "enable_kv_nz": False, "multistream_overlap_shared_expert": True, - "rejection_sampler_config": { - "enable_block_verify": True, - "enable_entropy_verify": True, - "posterior_threshold": 0.95, - "posterior_alpha": 0.4, - }, "refresh": False } ``` diff --git a/docs/source/user_guide/feature_guide/Ai_QoS_introduction_en.md b/docs/source/user_guide/feature_guide/Ai_QoS_introduction_en.md index 66c3caeef..5e50e8ee4 100644 --- a/docs/source/user_guide/feature_guide/Ai_QoS_introduction_en.md +++ b/docs/source/user_guide/feature_guide/Ai_QoS_introduction_en.md @@ -2,23 +2,23 @@ ## Background -​In the inference scenario, there are different types of traffic, such as operator delivery, collective communication, and KVCache. Such traffics are transmitted through network and affect each other, increasing the inference latency. +​ In the inference scenario, there are different types of traffic, such as operator delivery, collective communication, and KVCache. Such traffics are transmitted through network and affect each other, increasing the inference latency. -​For example, in the Agentic AI era, as the context length continues to increase, the size of the KVCache also gradually grows. To conserve HBM usage, the approach of offloading KVCache to DDR is adopted to enhance inference TPS. At the same time, to maximize the utilization of computing power, a pipeline orchestration method using computation to mask KVCache is commonly employed. This method involves prefetching the next layer's KVCache during the current layer's computation/communication to reduce overall latency. However, this approach introduces a traffic conflict issue between the KVCache and the operator delivery/collective communication, leading to increased inference latency and impacting the SLO. +​ For example, in the Agentic AI era, as the context length continues to increase, the size of the KVCache also gradually grows. To conserve HBM usage, the approach of offloading KVCache to DDR is adopted to enhance inference TPS. At the same time, to maximize the utilization of computing power, a pipeline orchestration method using computation to mask KVCache is commonly employed. This method involves prefetching the next layer's KVCache during the current layer's computation/communication to reduce overall latency. However, this approach introduces a traffic conflict issue between the KVCache and the operator delivery/collective communication, leading to increased inference latency and impacting the SLO. ![alt text](<./images/ai_qos1.png>) -​As shown in the preceding figure, traffic conflicts occur on the UB switch when intra-node device-to-device (D2D) traffic, intra-node host-to-device (H2D) traffic, and inter-node D2D traffic are transmitted. +​ As shown in the preceding figure, traffic conflicts occur on the UB switch when intra-node device-to-device (D2D) traffic, intra-node host-to-device (H2D) traffic, and inter-node D2D traffic are transmitted. ## Introduction -​When different types of traffic conflict with each other, the Virtual Lane (VL) can be used to isolate the traffic at the UB switch and perform differentiated scheduling between the VLs. This helps to: (1) isolate the VLs of different types of traffic to prevent congestion from spreading; (2) perform differentiated scheduling for different types of traffic. +​ When different types of traffic conflict with each other, the Virtual Lane (VL) can be used to isolate the traffic at the UB switch and perform differentiated scheduling between the VLs. This helps to: (1) isolate the VLs of different types of traffic to prevent congestion from spreading; (2) perform differentiated scheduling for different types of traffic. -​As shown in the following figure, different types of traffic are mapped to different VLs to isolate the traffic. In addition, the priority of each VL is set and the strict priority (SP) scheduling mode is used. When different types of traffic reach the UB switch at the same time, the traffic in the VL with the high priority is scheduled first, and then the traffic in the VL with the middle priority is scheduled. This process repeats until all the traffic is scheduled. In this way, differentiated scheduling is implemented for different types of traffic. +​ As shown in the following figure, different types of traffic are mapped to different VLs to isolate the traffic. In addition, the priority of each VL is set and the strict priority (SP) scheduling mode is used. When different types of traffic reach the UB switch at the same time, the traffic in the VL with the high priority is scheduled first, and then the traffic in the VL with the middle priority is scheduled. This process repeats until all the traffic is scheduled. In this way, differentiated scheduling is implemented for different types of traffic. ![alt text](<./images/ai_qos2.png>) -​Different traffic is transmitted through different channels. Therefore, the AI QoS solution implements isolation and differentiated scheduling of different traffic to meet service requirements by (1) setting priorities for different NPU channels on the host, (2) establishing the mapping between the NPU channel priority and the VL of the UB switch, and (3) performing differentiated scheduling among different VLs of the UB switch based on the priority. +​ Different traffic is transmitted through different channels. Therefore, the AI QoS solution implements isolation and differentiated scheduling of different traffic to meet service requirements by (1) setting priorities for different NPU channels on the host, (2) establishing the mapping between the NPU channel priority and the VL of the UB switch, and (3) performing differentiated scheduling among different VLs of the UB switch based on the priority. ## Build AI QoS Module @@ -41,40 +41,38 @@ cmake --install tools/ai_qos/build ## Usage Instruction -​The AI QoS feature supports two modes: Auto and Manual. Enter the vLLM-Ascend installation directory and run the following command before running the inference job: +​ The AI QoS feature supports two modes: Auto and Manual. Enter the vLLM-Ascend installation directory and run the following command before running the inference job: -​### 1) Auto mode +​ 1)Auto mode: -`python tools/ai_qos.py` +​ python tools/ai_qos.py -​AI QoS auto mode automatically classifies the priorities of different types of traffic and generates QoS tags. It also prints the UB switch configuration. You can copy the outputs and log in to the UB switch to configure the QoS configurations of UB switch. This configuration will overwrite the current QoS configuration on the UB switch. If there is any existing QoS configuration, please back it up in advance. +​ AI QoS auto mode automatically classifies the priorities of different types of traffic and generates QoS tags. It also prints the UB switch configuration. You can copy the outputs and log in to the UB switch to configure the QoS configurations of UB switch. This configuration will overwrite the current QoS configuration on the UB switch. If there is any existing QoS configuration,please back it up in advance. -​### 2) Manual mode +​ 2)Manual mode: -​python tools/ai_qos.py --mode manual --AIV_D2D *{priority}* --AIV_H2D *{priority}* --SDMA_D2D *{priority}* --SDMA_H2D *{priority}* --PCIEDMA_H2D *{priority}* +​ python tools/ai_qos.py --mode manual --AIV_D2D *{priority}* --AIV_H2D *{priority}* --SDMA_D2D *{priority}* --SDMA_H2D *{priority}* --PCIEDMA_H2D *{priority}* -​AI QoS manual mode calculates the QoS tag of traffic based on the priority of different types of traffic set by users, and generates and prints the UB switch configuration.You can copy the outputs and log in to the UB switch to configure the QoS configurations of UB switch. This configuration will overwrite the current QoS configuration on the UB switch. If there is any existing QoS configuration, please back it up in advance. +​ AI QoS manual mode calculates the QoS tag of traffic based on the priority of different types of traffic set by users, and generates and prints the UB switch configuration.You can copy the outputs and log in to the UB switch to configure the QoS configurations of UB switch. This configuration will overwrite the current QoS configuration on the UB switch. If there is any existing QoS configuration,please back it up in advance. -​In manual mode, you can specify the priority of only one type of traffic. The parameters are described as follows: +​ In manual mode, you can specify the priority of only one type of traffic. The parameters are described as follows: | Name | Type | Default | Description | | ----------------- | ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | | mode | str | auto | The mode of AI QoS, default mode is "auto", another mode is "manual",some parameters need to be configured if you choose "manual" mode. | | qos_manual_config | / | AIV_D2D: high,
AIV_H2D: high,
SDMA_D2D: high,
SDMA_H2D: low,
PCIEDMA_H2D: high | Parameters for "manual" mode, determined the QoS priority of different types of traffic.
The default configuration is the same as "auto" mode.
Typical traffic types are as follows for reference: AIV_D2D: AIV-based Device-to-Device communication, such as dispatch and combine.
AIV_H2D: AIV-based Operator Delivery.
SDMA_D2D: SDMA-based Device-to-Device communication, such as Allreduce and Allgather.
SDMA_H2D: SDMA-based Host-to-Device/Device-to-Host communication, such as KVCache offloading and prefetching.
PCIEDMA_H2D: PCIEDMA-based Operator Delivery.
You can change the priority of different types of traffic, with "high/middle/low" options available.Due to hardware restrictions, "PCIEDMA_H2D" only supports "high/low" priority. | -**How to disable AI QoS**: +​ How to disable AI QoS: -```bash -​python tools/ai_qos.py unset -``` +​ python tools/ai_qos.py unset -​The command for disabling the AI QoS feature on the UB Switch will be printed on the screen. Please log in to the UB Switch and execute the command printed on the screen to complete the feature disabling. +​ The command for disabling the AI QoS feature on the UB Switch will be printed on the screen. Please log in to the UB Switch and execute the command printed on the screen to complete the feature disabling. ## Usage Constraints -​Due to underlying driver limitations, the QoS configurations for AIV_H2D and AIV_D2D do not take effect currently. Once the required adaptation capabilities are added in a future driver release, this feature will be delivered through a module upgrade. +​ Due to underlying driver limitations, the QoS configurations for AIV_H2D and AIV_D2D do not take effect currently. Once the required adaptation capabilities are added in a future driver release, this feature will be delivered through a module upgrade. -The AI QoS feature supports the Atlas 800T A3 server and Atlas 900 A3 SuperPoD cluster. It must be used in privileged containers and requires the following software versions: +​ The AI QoS feature supports the Atlas 800T A3 server and Atlas 900 A3 SuperPoD cluster. It must be used in privileged containers and requires the following software versions: | Software | Matched Version | | :----------: | :--------------------------------------: | diff --git a/docs/source/user_guide/feature_guide/Multi_Token_Prediction.md b/docs/source/user_guide/feature_guide/Multi_Token_Prediction.md new file mode 100644 index 000000000..4bf312e43 --- /dev/null +++ b/docs/source/user_guide/feature_guide/Multi_Token_Prediction.md @@ -0,0 +1,114 @@ +# Multi Token Prediction (MTP) + +## Why We Need MTP + +MTP boosts inference performance by parallelizing the prediction of multiple tokens, shifting from single-token to multi-token generation. This approach significantly increases generation throughput and achieves multiplicative acceleration in inference speed—all without compromising output quality. + +## How to Use MTP + +To enable MTP for DeepSeek-V3 models, add the following parameter when starting the service: + +--speculative_config ' {"method": "mtp", "num_speculative_tokens": 1, "disable_padded_drafter_batch": False} ' + +- `num_speculative_tokens`: The number of speculative tokens that enables the model to predict multiple tokens at once, if provided. It will default to the number in the draft model config if present, otherwise, it is required. +- `disable_padded_drafter_batch`: Disable input padding for speculative decoding. If set to True, speculative input batches can contain sequences of different lengths, which may only be supported by certain attention backends. This currently only affects the MTP method of speculation, default is False. + +## How It Works + +### Module Architecture + +```shell +vllm_ascend +├── sample +│ ├── rejection_sample.py +├── spec_decode +│ ├── mtp_proposer.py +└─────────── +``` + +**1. sample** + +- *rejection_sample.py*: During decoding, the main model processes the previous round’s output token and the predicted token together (computing 1+k tokens simultaneously). The first token is always correct, while the second token—referred to as the **bonus token**—is uncertain since it is derived from speculative prediction, thus we employ **Greedy Strategy** and **Rejection Sampling Strategy** to determine whether the bonus token should be accepted. The module structure consists of an `AscendRejectionSampler` class with a forward method that implements the specific sampling logic. + +```shell +rejection_sample.py +├── AscendRejectionSampler +│ ├── forward +``` + +**2. spec_decode** + +This section encompasses the model preprocessing for spec-decode, primarily structured as follows: it includes loading the model, executing a dummy run, and generating token IDs. These steps collectively form the model data construction and forward invocation for a single spec-decode operation. + +- *mtp_proposer.py*: Configure vLLM-Ascend to use speculative decoding where proposals are generated by DeepSeek MTP layer. + +```shell +mtp_proposer.py +├── Proposer +│ ├── load_model +│ ├── dummy_run +│ ├── _prepare_inputs +│ ├── _propose +``` + +### Algorithm + +**1. Rejection Sampling** + +- *Greedy Strategy* + +Verify whether the token generated by the main model matches the speculative token predicted by MTP in the previous round. If they match exactly, accept the bonus token; otherwise, reject it and any subsequent tokens derived from that speculation. + +- *Rejection Sampling Strategy* + +This method introduces stochasticity in rejection sampling. + +For each draft token, acceptance is determined by verifying whether the inequality `P_target / P_draft ≥ U` holds, where `P_target` represents the probability assigned to the current draft token by the target model, `P_draft` denotes the probability assigned by the draft model, and `U` is a random number sampled uniformly from the interval [0, 1). + +The decision logic for each draft token is as follows: if the inequality `P_target / P_draft ≥ U` holds, the draft token is accepted as output; conversely, if `P_target / P_draft < U`, the draft token is rejected. + +When a draft token is rejected, a recovery sampling process is triggered where a "recovered token" is resampled from the adjusted probability distribution defined as `Q = max(P_target - P_draft, 0)`. In the current MTP implementation, since `P_draft` is not provided and defaults to 1, the formulas simplify such that token acceptance occurs when `P_target ≥ U` and the recovery distribution becomes `Q = max(P_target - 1, 0)`. + +**2. Performance** + +If the bonus token is accepted, the MTP model performs inference for (num_speculative + 1) tokens, including original main model output token and bonus token. If rejected, inference is performed for fewer tokens, depending on how many tokens are accepted. + +## DFX + +### Method Validation + +- Currently, the spec_decode scenario only supports methods such as n-gram, EAGLE, EAGLE3, and MTP. If an incorrect parameter is passed for the method, the code will raise an error to alert the user that an incorrect method was provided. + +```python +def get_spec_decode_method(method, + vllm_config, + device, + runner): + if method == "ngram": + return AscendNgramProposer(vllm_config, device, runner) + elif method in ["eagle", "eagle3"]: + return AscendEagleProposer(vllm_config, device, runner) + elif method == 'mtp': + return AscendMtpProposer(vllm_config, device, runner) + else: + raise ValueError("Unknown speculative decoding method: " + f"{method}") +``` + +### Integer Validation + +- The current npu_fused_infer_attention_score operator only supports integers less than 16 per decode round. Therefore, the maximum supported value for MTP is 15. If a value greater than 15 is provided, the code will raise an error and alert the user. + +```python +if self.speculative_config: + spec_token_num = self.speculative_config.num_speculative_tokens + self.decode_threshold += spec_token_num + assert self.decode_threshold <= 16, f"decode_threshold exceeded \ + npu_fused_infer_attention_score TND layout's limit of 16, \ + got {self.decode_threshold}" +``` + +## Limitations + +- Due to the fact that only a single layer of weights is exposed in DeepSeek's MTP, the accuracy and performance are not effectively guaranteed in scenarios where MTP > 1 (especially MTP ≥ 3). Moreover, due to current operator limitations, MTP supports a maximum of 15. +- In the fullgraph mode with MTP > 1, the capture size of each ACLGraph must be an integer multiple of (num_speculative_tokens + 1). diff --git a/docs/source/user_guide/feature_guide/batch_invariance.md b/docs/source/user_guide/feature_guide/batch_invariance.md index 431e584ea..67d4dd501 100644 --- a/docs/source/user_guide/feature_guide/batch_invariance.md +++ b/docs/source/user_guide/feature_guide/batch_invariance.md @@ -5,11 +5,6 @@ Batch invariance is currently in beta. Some features are still under active deve Track progress and planned improvements at ``` -```{note} -To install the batch invariance custom operator library, set `VLLM_BATCH_INVARIANT=1` before building vllm-ascend. -For installation instructions, see -``` - This document shows how to enable batch invariance in vLLM-Ascend. Batch invariance ensures that the output of a model is deterministic and independent of the batch size or the order of requests in a batch. ## Motivation @@ -23,12 +18,13 @@ Batch invariance is crucial for several use cases: ## Hardware Requirements -Batch invariance currently requires Ascend Atlas A2 and A3 inference products NPUs. -We will support Ascend 950 Products and other NPUs in the future. +Batch invariance currently requires Ascend Atlas A2 inference products NPUs, because only the Atlas A2 inference products supports batch invariance with HCCL communication for now. +We will support other NPUs in the future. ## Software Requirements -Batch invariance requires a custom operator library for Atlas A2 and A3 inference products, and users need to set `VLLM_BATCH_INVARIANT=1` before building vllm-ascend to install the batch invariance custom operator library during the installation process. +Batch invariance requires a custom operator library for Atlas A2 inference products. +We will release the customized operator library in future versions. ## Enabling Batch Invariance @@ -111,7 +107,7 @@ for output in outputs: Batch invariance has been tested and verified on the following models: - **Qwen3 (Dense)**: `Qwen/Qwen3-1.7B`, `Qwen/Qwen3-8B` -- **Qwen3 (MoE)**: `Qwen/Qwen3-30B-A3B`, `Qwen/Qwen3-235B-A22B` +- **Qwen3 (MoE)**: `Qwen/Qwen3-30B-A3B` Other models may also work, but these have been explicitly validated. If you encounter issues with a specific model, please report them on the [GitHub issue tracker](https://github.com/vllm-project/vllm-ascend/issues/new/choose). diff --git a/docs/source/user_guide/feature_guide/cpu_binding.md b/docs/source/user_guide/feature_guide/cpu_binding.md index 7d3ce3cab..df786d7cf 100644 --- a/docs/source/user_guide/feature_guide/cpu_binding.md +++ b/docs/source/user_guide/feature_guide/cpu_binding.md @@ -90,23 +90,12 @@ degrade latency or throughput.** For optimal locality, use a cpuset that is evenly distributed across NUMA nodes. Unbalanced cpusets may reduce the locality benefit of CPU binding. -On Ascend 950, CPU binding uses deterministic global CPU slicing because Ascend 950 does not -report NPU-to-CPU affinity in `npu-smi info -t topo`. Ascend 950 still pins worker, -ACL, and release threads and can still migrate memory pages when `migratepages` -is available. Because Ascend 950 skips IRQ binding, it does not reserve the first two -CPUs in each NPU pool for IRQ handling. Those CPUs are assigned to the main -worker instead. - For IRQ binding, the process also needs permission to read `/proc/interrupts` and write `/proc/irq/*/smp_affinity`. If `irqbalance` is running and the process can use `systemctl`, vLLM Ascend stops it before applying IRQ affinity. In containers where `systemctl` is unavailable, stop `irqbalance` on the host when IRQ affinity matters. -Ascend 950 does not apply IRQ binding. When running on Ascend 950, the log contains -`[irq] IRQ binding skipped on Ascend 950.` and no `/proc/irq/*/smp_affinity` files are -written by this feature. - On the host, stop `irqbalance` before starting vLLM when you need stable IRQ affinity: @@ -127,8 +116,7 @@ sudo systemctl start irqbalance | --- | --- | --- | | `CPU binding skipped: non-ARM CPU detected.` | CPU binding only runs on ARM. | No action needed on x86_64. | | `Can not get running npu info.` | No running NPU was found, or `ASCEND_RT_VISIBLE_DEVICES` filtered all NPUs. | Check visible NPU IDs and `npu-smi info`. | -| `Insufficient CPUs for binding...` | Fewer CPUs are available than the role split requires. Devices with IRQ binding need at least 5 CPUs per logical NPU; Ascend 950 needs at least 3. | Expand the cpuset or reduce visible NPUs. | +| `Insufficient CPUs for binding...` | Fewer than 5 CPUs are available per logical NPU. | Expand the cpuset or reduce visible NPUs. | | `NPU topo affinity not found...` | Topology affinity is unavailable. | vLLM Ascend falls back to `global_slice`; check `npu-smi info -t topo` only if topology affinity is expected on this device. | | `The 'migratepages' command is not available...` | Memory migration is skipped, while CPU thread binding still proceeds. | Install `numactl` if NUMA locality or performance is affected. | -| `[irq] IRQ binding skipped on Ascend 950.` | Ascend 950 does not use the IRQ binding step. | No action needed. Worker, ACL, release thread binding and memory migration still proceed. | | `Bind cpus failed in rank...` | A binding step failed and CPU binding was skipped for that rank. | Check `taskset`, `lscpu`, `npu-smi`, cpuset size, and `/proc/irq` permissions. | diff --git a/docs/source/user_guide/feature_guide/epd_disaggregation.md b/docs/source/user_guide/feature_guide/epd_disaggregation.md index 9df1e667a..44cf38a11 100644 --- a/docs/source/user_guide/feature_guide/epd_disaggregation.md +++ b/docs/source/user_guide/feature_guide/epd_disaggregation.md @@ -1,17 +1,5 @@ # Disaggregated-encoder -Disaggregated encoder refers to running the vision (multimodal) encoder stage of a large language model (LLM) in a separate vLLM process/instance from the language model's prefill and decode stages. - -Similarly, disaggregated prefill isolates prompt processing (KV cache computation) from autoregressive token generation (the decode phase) across distinct vLLM instances. - -This separation allows for targeted hardware and resource optimization for each phase, enabling precise tuning of Time-to-First-Token (TTFT) against Inter-Token Latency (ITL). Consequently, it enhances overall throughput and resource utilization during high-load serving. - -Prefill-Decode (PD) disaggregation serves as the overarching architecture for this mechanism. In this setup, dedicated prefill instances compute KV caches and transfer them—via specialized connectors such as MooncakeLayerwise—to decode instances for token generation. - -Within frameworks like vLLM (including the Ascend Hardware Plugin), PD disaggregation often integrates with Encoder-Prefill-Decode (EPD) architectures for multimodal models, while supporting multi-node configurations with distributed load balancing. - -Ultimately, these architectural patterns maximize inference efficiency by addressing the contrasting computational profiles of each stage: encoding and prefilling are compute-bound and bursty, whereas decoding is memory-bound and sustained. - ## Why disaggregated-encoder? A **disaggregated encoder** runs the vision-encoder stage of a multimodal LLM in a process that is separate from the pre-fill / decoder stage. Deploying these two stages in independent vLLM instances brings three practical benefits: diff --git a/docs/source/user_guide/feature_guide/eplb_swift_balancer.md b/docs/source/user_guide/feature_guide/eplb_swift_balancer.md index 5cdc7941a..04878d5e4 100644 --- a/docs/source/user_guide/feature_guide/eplb_swift_balancer.md +++ b/docs/source/user_guide/feature_guide/eplb_swift_balancer.md @@ -7,7 +7,7 @@ Expert balancing for MoE (Mixture of Experts) models in LLM (Large Language) ser ## EPLB Effects - Reduced Latency: Dynamically balances expert loads to minimize TTFT and TPOT by distributing workloads evenly across experts. -- Enhanced Throughput: Optimizes NPU utilization, increasing token generation speed under high-concurrency scenarios. +- Enhanced Throughput: Optimizes GPU utilization, increasing token generation speed under high-concurrency scenarios. - Zero-Overhead Movement: Expert redistribution occurs asynchronously without interrupting ongoing inference requests. - Adaptive Scaling: Automatically adjusts to workload fluctuations while maintaining stable performance. - Fault Tolerance: Redundant expert placement ensures system resilience during hardware failures. @@ -20,26 +20,11 @@ DeepSeekV3/V3.1/R1, Qwen3-MoE ### MOE QuantType -| QuantType | Supported Hardware | -| ------------------------------- | --------------------------- | -| W8A8 / W8A8-Dynamic | A2, A3, Ascend 950 Products | -| W4A8 (with fused MC2 enabled) | A2, A3, Ascend 950 Products | -| MXFP4 | Ascend 950 Products | -| MXFP8 | Ascend 950 Products | +W8A8-Dynamic +W4A8 (with fused MC2 enabled) ## How to Use EPLB -EPLB has three usage modes: - -| Mode | Config in `eplb_config` | Env Variable | -| ---- | ----------------------- | ------------ | -| **Dynamic EPLB** | `dynamic_eplb: true` | `DYNAMIC_EPLB=true` | -| **Recording** (generate expert map) | `expert_map_record_path` | `DYNAMIC_EPLB=true` or `EXPERT_MAP_RECORD=true` | -| **Static EPLB** (load pre-recorded map) | `expert_map_path` | none required | - -> [!IMPORTANT] -> For Dynamic EPLB and Recording modes, the env variable acts as a safety guard: setting `dynamic_eplb: true` in config alone is not enough — the assertion requires `DYNAMIC_EPLB=true` or `EXPERT_MAP_RECORD=true`. Static EPLB (loading a pre-recorded map via `expert_map_path`) does **not** require an env variable. - ### Dynamic EPLB We need to add environment variable `export DYNAMIC_EPLB="true"` to enable vLLM EPLB. Enable dynamic balancing with auto-tuned parameters. Adjust expert_heat_collection_interval and algorithm_execution_interval based on workload patterns. In the current version, we recommend using the following: policy of swift balancer(2). @@ -57,17 +42,6 @@ vllm serve Qwen/Qwen3-235B-A22 \ }}' ``` -#### EPLB Policy Types - -The `eplb_policy_type` parameter selects the balancing algorithm used during dynamic expert redistribution: - -| Value | Policy | Description | -|-------|--------|-------------| -| `0` | Random | Randomly swaps experts between ranks. Suitable for basic testing only. | -| `1` | DefaultEplb | Open-source EPLB algorithm. Adds redundant experts to the hottest, packs via balanced assignment with local constraint exchange. | -| `2` | SwiftBalanceEplb | Optimized for low-bandwidth environments. Supports intra-node and inter-node expert redundancy, joint optimization of expert placement. **(Recommended)** | -| `3` | FlashLB | Statistical method using sliding-window mean/variance/covariance of expert loads. Uses FlashTree layered search for optimal replica allocation and `minimize_redeploy` for incremental adjustment. Best for high-frequency load fluctuations. | - ### Static EPLB #### Initial Setup (Record Expert Map) @@ -104,10 +78,10 @@ vllm serve Qwen/Qwen3-235B-A22 \ 1. Parameter Tuning: - expert_heat_collection_interval: Higher values (e.g., 400+) for stable workloads; lower values (e.g., 100-200) for fluctuating traffic. - algorithm_execution_interval: Should be ≥ 30 to avoid premature balancing during startup. - - num_redundant_experts: Must match tensor-parallel size (e.g., 16 for 16 NPUs) to ensure sufficient redundancy. + - num_redundant_experts: Must match tensor-parallel size (e.g., 16 for 16 GPUs) to ensure sufficient redundancy. 2. Hardware Requirements: - - Ensure that all NPUs have identical memory capacity and compute capabilities. + - Ensure that all GPUs have identical memory capacity and compute capabilities. - Network bandwidth must support expert redistribution traffic (≥ 10 Gbps recommended). 3. Model Compatibility: @@ -115,7 +89,7 @@ vllm serve Qwen/Qwen3-235B-A22 \ - Verify model architecture supports dynamic expert routing through `--enable-expert-parallel`. 4. Monitoring & Validation: - - Track metrics: expert_load_balance_ratio, ttft_p99, tpot_avg, and npu_utilization. + - Track metrics: expert_load_balance_ratio, ttft_p99, tpot_avg, and gpu_utilization. - Use vLLM monitor to detect imbalances during runtime. - Always verify expert map JSON structure before loading (validate with jq or similar tools). @@ -124,6 +98,6 @@ vllm serve Qwen/Qwen3-235B-A22 \ - Avoid sudden traffic spikes during the warm-up phase. 6. Common Pitfalls: - - Incorrect tensor-parallel-size vs. actual NPU count → causes resource underutilization. + - Incorrect tensor-parallel-size vs. actual GPU count → causes resource underutilization. - Using expert_map_path without generating the map first → runtime errors. - - Setting num_redundant_experts > available NPUs → system failure. + - Setting num_redundant_experts > available GPUs → system failure. diff --git a/docs/source/user_guide/feature_guide/graph_mode.md b/docs/source/user_guide/feature_guide/graph_mode.md index ef9dc3f6d..fd8425aff 100644 --- a/docs/source/user_guide/feature_guide/graph_mode.md +++ b/docs/source/user_guide/feature_guide/graph_mode.md @@ -108,17 +108,6 @@ On Ascend, the current attention backend support levels are: This is why the effective graph mode on Ascend may differ from the mode requested in configuration. -### Troubleshooting capture resource exhaustion - -If ACLGraph capture fails because the configured graph sizes exceed the runtime resources available on the current stack, vLLM Ascend now raises a dedicated error with mitigation guidance. In practice, the most useful actions are: - -- upgrade to a newer HDK/CANN stack if one is available; -- reduce `cudagraph_capture_sizes` or `max_cudagraph_capture_size`; -- prefer `FULL` or `FULL_DECODE_ONLY` when the workload is mostly uniform decode; -- temporarily disable graph mode to confirm the issue is capture-related. - -This is most likely to appear in `PIECEWISE` or `FULL_AND_PIECEWISE` configurations because those paths tend to capture more graphs than uniform full-graph decode. - ## Using Npugraph_ex As introduced in the [RFC](https://github.com/vllm-project/vllm-ascend/issues/4715), Npugraph_ex is a compile-time FX graph optimization layer that works together with ACLGraph. It optimizes the model's FX graph before ACLGraph captures it at runtime. Its performance benefits mainly come from fusing multiple operators into single kernels (e.g., add + rms_norm → npu_add_rms_norm) to reduce kernel launch overhead. @@ -271,8 +260,6 @@ For more details about Xlite, see the [Xlite README](https://atomgit.com/openeul If you encounter issues with graph mode, you can temporarily fall back to eager mode by setting `enforce_eager=True`. -If ACL graph capture fails with the confirmed stream-resource signature in the error text, such as `207008` together with `Stream resources are insufficient` or `Insufficient_Stream_Resources`, vLLM Ascend will re-raise that capture failure with targeted mitigation guidance. In practice, the main levers are: upgrading to a newer HDK/CANN stack, reducing `cudagraph_capture_sizes`, lowering `max_cudagraph_capture_size`, or preferring `FULL` / `FULL_DECODE_ONLY` when the workload is mostly uniform decode. - **Offline example:** ```python diff --git a/docs/source/user_guide/feature_guide/index.md b/docs/source/user_guide/feature_guide/index.md index 126be31a5..db10094f6 100644 --- a/docs/source/user_guide/feature_guide/index.md +++ b/docs/source/user_guide/feature_guide/index.md @@ -15,6 +15,7 @@ lora eplb_swift_balancer netloader rfork +Multi_Token_Prediction dynamic_batch epd_disaggregation kv_pool diff --git a/docs/source/user_guide/feature_guide/kv_pool.md b/docs/source/user_guide/feature_guide/kv_pool.md index e6367b38b..b8c5cff77 100644 --- a/docs/source/user_guide/feature_guide/kv_pool.md +++ b/docs/source/user_guide/feature_guide/kv_pool.md @@ -1,12 +1,4 @@ -# KV Cache Pool(Ascend Store)Deployment Guide - -## Contents - -* [Environmental Dependencies](#environmental-dependencies) -* [Example of using Mooncake as a KV Pool backend](#example-of-using-mooncake-as-a-kv-pool-backend) -* [Example of using Memcache as a KV Pool backend](#example-of-using-memcache-as-a-kv-pool-backend) -* [Example of using Yuanrong as a KV Pool backend](#example-of-using-yuanrong-as-a-kv-pool-backend) -* [FAQ](#faq) +# Ascend Store Deployment Guide ## Environmental Dependencies @@ -22,7 +14,7 @@ `kv_load_failure_policy` is a top-level field in `kv-transfer-config`. -* `recompute`: When KV loading fails, vLLM rolls the request back to the last valid prefix and reschedules it to recompute the failed KV blocks. Hybrid attention models (e.g. DeepSeekV4, Qwen 3.5) are not supported yet. +* `recompute`: When KV loading fails, vLLM rolls the request back to the last valid prefix and reschedules it to recompute the failed KV blocks. * `fail`: When KV loading fails, the affected request is terminated directly with an error. The default value in vLLM is `fail`. If you want the request to fall back to recomputation after a KV load failure, set it to `recompute`. @@ -112,11 +104,80 @@ export PYTHONHASHSEED=0 ### Environment Variables Description -| Hardware | Dependencies | Export Command | Description | +| Hardware | HDK & CANN versions | Export Command | Description | | :--- | :--- | :--- | :--- | -| 800 I/T A3 series | HDK >= 26.0
or HDK >= 25.5 with mooncake >= v0.3.11
CANN >= 9.0.0
LingQu Computing Network >= 1.5 | `export ASCEND_ENABLE_USE_FABRIC_MEM=1` | **Recommended**. Enables unified memory address direct transmission scheme. | -| 800 I/T A3 series | If any dependency above is not met | `export ASCEND_BUFFER_POOL=4:8` | Configures the number and size of buffers on the NPU Device for aggregation and KV transfer (e.g., `4:8` means 4 buffers of 8MB). | -| 800 I/T A2 series | HDK >= 25.5 is recommended | `export HCCL_INTRA_ROCE_ENABLE=1` | Required by direct transmission scheme on 800 I/T A2 series| +| 800 I/T A3 series | HDK >= 25.5
CANN >= 9.0.0
LingQu Computing Network >= 1.5 | `export ASCEND_ENABLE_USE_FABRIC_MEM=1` | **Recommended**. Enables unified memory address direct transmission scheme. | +| 800 I/T A3 series | 25.5.0<=HDK<26.0.0 | `export ASCEND_BUFFER_POOL=4:8` | Configures the number and size of buffers on the NPU Device for aggregation and KV transfer (e.g., `4:8` means 4 buffers of 8MB). | +| 800 I/T A2 series | N/A | `export HCCL_INTRA_ROCE_ENABLE=1` | Required by direct transmission scheme on 800 I/T A2 series| + +### Embedded Real Client Mode(Mooncake ssd-offload.md Step 3A) + +* Software: + * mooncake >= v0.3.11 + +#### Start the master + +```bash +mooncake_master --rpc_port=50051 --enable_offload=true +``` + +| Field | Description | +| :--- | :--- | +| `enable_offload` | Set `true` to enable SSD offload. | + +#### Configuration + +Add the following fields to your `mooncake.json`: + +```json +{ + "local_hostname": "xx.xx.xx.xx", + "metadata_server": "P2PHANDSHAKE", + "protocol": "ascend", + "use_ascend_direct": true, + "device_name": "", + "master_server_address": "xx.xx.xx.xx:50088", + "global_segment_size": "1GB", + "enable_ssd_offload": true, + "ssd_offload_path": "/nvme/mooncake_offload" +} +``` + +| Field | Description | +| :--- | :--- | +| `enable_ssd_offload` | Set to `true` to enable SSD offload. Environment variables are not supported. | +| `ssd_offload_path` | **Required when `enable_ssd_offload` is `true`.** Absolute path to a local directory where Mooncake stores offloaded KV data (for example, `/nvme/mooncake_offload`). The directory must exist and be writable by the vLLM process; create it before startup (`mkdir -p `). Relative paths, symbolic links, and paths containing `..` are rejected by Mooncake. Passed to `MooncakeDistributedStore.setup()` as the SSD storage root (equivalent to `MOONCAKE_OFFLOAD_FILE_STORAGE_PATH` in standalone clients). Configure this field in `mooncake.json` only; environment variables are not supported. | + +#### Running the Embedded Real Client + +With Mode A (Embedded Real Client), Mooncake is embedded in vLLM. When the vLLM service starts, `AscendStoreConnector` / `MooncakeBackend` automatically calls `MooncakeDistributedStore.setup()` using the settings in `mooncake.json` (including `enable_ssd_offload` and `ssd_offload_path` when SSD offload is enabled). No separate `mooncake_client` process is required. + +#### SSD Disk Usage Control + +The following environment variables control disk space usage for SSD offload (bucket backend): + +| Environment Variable | Default | Description | +| :--- | :--- | :--- | +| `MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE` | `0` | Eviction threshold in bytes. When set to `0`, the backend uses **90% of the physical disk capacity** as the quota. Set an explicit value to control disk usage precisely. | +| `MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY` | `none` | Eviction policy: `none` (writes fail when full), `fifo`, or `lru`. | +| `MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES` | `2199023255552` (2 TB) | Global maximum disk usage limit. | + +Since each TP rank uses an independent SSD subdirectory (`rank_0/`, `rank_1/`, ...) under `ssd_offload_path`, all ranks share the same physical disk. To prevent a single rank from consuming excessive space, set an explicit per-rank quota. For example, with an 800 GB disk and 8 TP ranks: + +```bash +# 800 GB total disk, 8 ranks, ~100 GB per rank +export MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE=$((100 * 1024 * 1024 * 1024)) +export MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru +``` + +#### Notes + +* This feature requires mooncake >= v0.3.11. + +### FAQ for HIXL (ascend_direct) backend + +For common troubleshooting and issue localization guidance for HIXL (ascend_direct), see: + ### Run Mooncake Master @@ -425,60 +486,6 @@ This is because HCCL one-sided communication connections are created lazily afte **For warm-up, it is recommended to issue requests with an input sequence length of 8K and an output sequence length of 1, with the total number of requests being 2–3× the number of devices (cards/dies).** -### Enable MooncakeStore SSD Offload with Embedded Real Client Mode - -* Requires mooncake >= v0.3.11. - -#### Start the master - -Start Mooncake master as described in [Run Mooncake Master](#run-mooncake-master). To enable SSD offload, add `--enable_offload=true` to the same master startup command. For example: - -```shell -mooncake_master --port 50088 --eviction_high_watermark_ratio 0.9 --eviction_ratio 0.1 --default_kv_lease_ttl 11000 --enable_offload=true -``` - -| Field | Description | -| :--- | :--- | -| `enable_offload` | Set to `true` to enable SSD offload in Mooncake master. Keep the master port aligned with `master_server_address` in `mooncake.json`. | - -#### Configuration - -Starting from the `mooncake.json` configured in [Run Mooncake Master](#run-mooncake-master), add the following SSD offload fields: - -```json -{ - "enable_ssd_offload": true, - "ssd_offload_path": "/nvme/mooncake_offload" -} -``` - -| Field | Description | -| :--- | :--- | -| `enable_ssd_offload` | Set to `true` to enable SSD offload. Environment variables are not supported. | -| `ssd_offload_path` | **Required when `enable_ssd_offload` is `true`.** Absolute path to a local directory where Mooncake stores offloaded KV data (for example, `/nvme/mooncake_offload`). The directory must exist and be writable by the vLLM process; create it before startup (`mkdir -p `). Relative paths, symbolic links, and paths containing `..` are rejected by Mooncake. Passed to `MooncakeDistributedStore.setup()` as the SSD storage root (equivalent to `MOONCAKE_OFFLOAD_FILE_STORAGE_PATH` in standalone clients). Configure this field in `mooncake.json` only; environment variables are not supported. | - -#### Running the Embedded Real Client - -With Mode A (Embedded Real Client), Mooncake is embedded in vLLM. When the vLLM service starts, `AscendStoreConnector` / `MooncakeBackend` automatically calls `MooncakeDistributedStore.setup()` using the settings in `mooncake.json` (including `enable_ssd_offload` and `ssd_offload_path` when SSD offload is enabled). No separate `mooncake_client` process is required. - -#### SSD Disk Usage Control - -The following environment variables control disk space usage for SSD offload (bucket backend): - -| Environment Variable | Default | Description | -| :--- | :--- | :--- | -| `MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE` | `0` | Eviction threshold in bytes. When set to `0`, the backend uses **90% of the physical disk capacity** as the quota. Set an explicit value to control disk usage precisely. | -| `MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY` | `none` | Eviction policy: `none` (writes fail when full), `fifo`, or `lru`. | -| `MOONCAKE_OFFLOAD_TOTAL_SIZE_LIMIT_BYTES` | `2199023255552` (2 TB) | Global maximum disk usage limit. | - -Since each TP rank uses an independent SSD subdirectory (`rank_0/`, `rank_1/`, ...) under `ssd_offload_path`, all ranks share the same physical disk. To prevent a single rank from consuming excessive space, set an explicit per-rank quota. For example, with an 800 GB disk and 8 TP ranks: - -```shell -# 800 GB total disk, 8 ranks, ~100 GB per rank -export MOONCAKE_OFFLOAD_BUCKET_MAX_TOTAL_SIZE=$((100 * 1024 * 1024 * 1024)) -export MOONCAKE_OFFLOAD_BUCKET_EVICTION_POLICY=lru -``` - ## Example of using Memcache as a KV Pool backend ### Installing Memcache @@ -929,27 +936,3 @@ and the worker process. Each instance must use a unique port value. uses device pointers directly when building blob lists. #### [2. Run Inference](#2-run-inference) - -## FAQ - -### 1. Mooncake failed to put/get key - -When vLLM reports failed `put` or `get` operations, first check whether the error is reported by Mooncake itself. - -* If the error is reported by Mooncake: - * For `put` failures, check whether the Mooncake log contains `NO_AVAILABLE_HANDLE` or `BatchPut failed ... due to insufficient space`. This usually means the remaining space after eviction is not enough for one `BatchPut` request. Ensure the space left by the eviction policy (for example, the capacity implied by `1 - eviction_ratio`) can hold one batch put, or consider increasing the available capacity, increasing eviction headroom, or reducing the batch size. - * For `get` failures, check whether the Mooncake log contains `lease_expired_before_data_transfer_completed key=...` or returns `LEASE_EXPIRED`. This means the KV object lease expired before the data transfer completed. Increase `--default_kv_lease_ttl` for `mooncake_master` as needed, and keep it larger than `ASCEND_CONNECT_TIMEOUT` and `ASCEND_TRANSFER_TIMEOUT`. -* If the error is not reported by Mooncake, it is likely an HIXL (ascend_direct) transfer-layer issue. Collect plog files under `/root/ascend/log/debug/plog` and check whether the issue matches a known HIXL problem. - -For common troubleshooting and issue localization guidance for HIXL (ascend_direct), see: - - -### 2. Memcache FAQ - -For Memcache troubleshooting, see: - - -### 3. DSv4 known issue (temporary) - -For the temporary DSv4 known issue, see: - diff --git a/docs/source/user_guide/feature_guide/large_scale_ep.md b/docs/source/user_guide/feature_guide/large_scale_ep.md index 48e6f4bbe..67d370e18 100644 --- a/docs/source/user_guide/feature_guide/large_scale_ep.md +++ b/docs/source/user_guide/feature_guide/large_scale_ep.md @@ -363,7 +363,7 @@ You can get the proxy program in the repository's examples, [load\_balance\_prox ## Benchmark -We recommend using aisbench tool to assess performance. [aisbench](https://github.com/AISBench/benchmark). Execute the following commands to install aisbench +We recommend using aisbench tool to assess performance. [aisbench](https://gitee.com/aisbench/benchmark). Execute the following commands to install aisbench ```shell git clone https://github.com/AISBench/benchmark.git @@ -412,7 +412,7 @@ models = [ ais_bench --models vllm_api_stream_chat --datasets gsm8k_gen_0_shot_cot_str_perf --debug --mode perf ``` -- For more details on commands and parameters for aisbench, refer to [aisbench](https://github.com/AISBench/benchmark) +- For more details on commands and parameters for aisbench, refer to [aisbench](https://gitee.com/aisbench/benchmark) ## Prefill & Decode Configuration Details diff --git a/docs/source/user_guide/feature_guide/quantization.md b/docs/source/user_guide/feature_guide/quantization.md index 022d242ab..382c8cb8b 100644 --- a/docs/source/user_guide/feature_guide/quantization.md +++ b/docs/source/user_guide/feature_guide/quantization.md @@ -16,24 +16,24 @@ vLLM Ascend supports models quantized by two main tools: `ModelSlim` and `LLM-Co ### 1. ModelSlim (Recommended) -[ModelSlim](https://gitcode.com/Ascend/msmodelslim/blob/master/README.md) is an Ascend-friendly compression tool focused on acceleration, using compression techniques, and built for Ascend hardware. It includes a series of inference optimization technologies such as quantization and compression, aiming to accelerate large language dense models, MoE models, multimodal understanding models, multimodal generation models, etc. +[ModelSlim](https://gitcode.com/Ascend/msit/blob/master/msmodelslim/README.md) is an Ascend-friendly compression tool focused on acceleration, using compression techniques, and built for Ascend hardware. It includes a series of inference optimization technologies such as quantization and compression, aiming to accelerate large language dense models, MoE models, multimodal understanding models, multimodal generation models, etc. #### Installation -To use ModelSlim for model quantization, install it from its [Git repository](https://gitcode.com/Ascend/msmodelslim): +To use ModelSlim for model quantization, install it from its [Git repository](https://gitcode.com/Ascend/msit): ```bash -# Install 26.0.0 version, this is currently the latest stable branch -git clone https://gitcode.com/Ascend/msmodelslim.git -b 26.0.0 +# Install br_release_MindStudio_8.3.0_20261231 version +git clone https://gitcode.com/Ascend/msit.git -b br_release_MindStudio_8.3.0_20261231 -cd msmodelslim +cd msit/msmodelslim bash install.sh ``` #### Model Quantization -The following example shows how to generate W8A8 quantized weights for the [Qwen3-MoE model](https://gitcode.com/Ascend/msmodelslim/blob/master/example/Qwen3-MOE/README.md). +The following example shows how to generate W8A8 quantized weights for the [Qwen3-MoE model](https://gitcode.com/Ascend/msit/blob/master/msmodelslim/example/Qwen3-MOE/README.md). **Quantization Script:** @@ -58,7 +58,7 @@ python3 quant_qwen_moe_w8a8.py --model_path $MODEL_PATH \ After quantization completes, the output directory will contain the quantized model files. -For more examples, refer to the [official examples](https://gitcode.com/Ascend/msmodelslim/tree/master/example). +For more examples, refer to the [official examples](https://gitcode.com/Ascend/msit/tree/master/msmodelslim/example). ### 2. LLM-Compressor @@ -100,7 +100,7 @@ python3 w8a8_int8_dynamic_moe.py For more content, refer to the [official examples](https://github.com/vllm-project/llm-compressor/tree/main/examples). -The quantization types currently supported by LLM-Compressor can be viewed in the `vllm_ascend/quantization/compressed_tensors_config.py` file. +Currently supported quantization types by LLM-Compressor: `W8A8` and `W8A8_DYNAMIC`. ## Running Quantized Models @@ -156,6 +156,6 @@ python -m vllm.entrypoints.api_server \ ## References -- [ModelSlim GitCode](https://gitcode.com/Ascend/msmodelslim) +- [ModelSlim Documentation](https://gitcode.com/Ascend/msit/blob/master/msmodelslim/README.md) - [LLM-Compressor GitHub](https://github.com/vllm-project/llm-compressor) - [vLLM Quantization Guide](https://docs.vllm.ai/en/latest/features/quantization/) diff --git a/docs/source/user_guide/feature_guide/speculative_decoding.md b/docs/source/user_guide/feature_guide/speculative_decoding.md index 3bf7c4a98..23f1bdb7d 100644 --- a/docs/source/user_guide/feature_guide/speculative_decoding.md +++ b/docs/source/user_guide/feature_guide/speculative_decoding.md @@ -2,62 +2,6 @@ This guide shows how to use Speculative Decoding with vLLM Ascend. Speculative decoding is a technique which improves inter-token latency in memory-bound LLM inference. -## Overview - -vLLM Ascend implements speculative decoding through a **proposer-verifier** architecture: - -1. **Proposer** (`vllm_ascend/spec_decode/`): Generates draft (speculative) tokens using various methods — from simple n-gram matching to neural-network-based draft models. -2. **Rejection Sampler** (`vllm_ascend/sample/`): Verifies draft tokens against the target model's output, accepting matches and rejecting mismatches, with optional optimizations including [Block Verify and Entropy Verify](#block-verify-and-entropy-verify). - -The following speculative decoding methods are supported: - -| Method | Description | -| ------ | ----------- | -| `ngram` | Match n-grams from the prompt | -| `suffix` | Suffix-based pattern matching (requires Arctic Inference) | -| `medusa` | Medusa heads embedded in the target model | -| `eagle` | EAGLE-based draft model | -| `eagle3` | EAGLE-3 based draft model | -| `mtp` | Multi-Token Prediction with shared embedding head | -| `dflash` | Draft-and-Flash with cross-attention | -| `draft_model` | Generic external draft LLM | -| `extract_hidden_states` | Extract hidden states for EAGLE training | - -## Common Configuration - -All speculative decoding methods are configured through the `speculative_config` parameter when initializing the model or starting the server: - -- **`method`** (str, required): The speculative decoding method. Must be one of the supported method names listed in the table above. -- **`num_speculative_tokens`** (int, required): Number of speculative tokens to generate per forward pass. Auto-filled from the draft model's `n_predict` config (e.g., MTP) or `suffix_decoding_max_tree_depth` (suffix method) when available. -- **`model`** (str, optional): Path or HF repo ID for the draft model. Required for `eagle`, `eagle3`, `dflash`, `medusa`, and `draft_model`. Automatically resolved for `mtp` (reuses target model), `ngram`, `suffix`, and `extract_hidden_states`. -- **`draft_tensor_parallel_size`** (int, optional): Tensor parallelism size for the draft model. Can only be `1` or the same as the target model's tensor parallel size. -- **`disable_padded_drafter_batch`** (bool, default: `False`): Disable input padding for speculative decoding. If set to `True`, speculative input batches can contain sequences of different lengths, which may only be supported by certain attention backends. **Note:** Only effective with `eagle`, `eagle3`, `mtp`, `dflash`, `draft_model`, and `extract_hidden_states` methods. - -**Offline inference** — pass `speculative_config` as a Python dict to `LLM()`: - -```python -from vllm import LLM - -llm = LLM( - model="path/to/target/model", - speculative_config={ - "method": "eagle3", - "model": "path/to/draft/model", - "num_speculative_tokens": 3, - }, -) -``` - -**Online serving** — pass `--speculative-config` (or `-sc`) as a JSON string: - -```shell -vllm serve path/to/target/model \ - --speculative-config '{"method": "eagle3", "model": "path/to/draft/model", "num_speculative_tokens": 3}' -``` - -> [!NOTE] -> On Ascend NPUs, the `npu_fused_infer_attention_score` operator supports a maximum of 16 tokens per decode round. Therefore, `(num_speculative_tokens + 1)` must be ≤ 15. - ## Speculating by matching n-grams in the prompt The following code configures vLLM Ascend to use speculative decoding where proposals are generated by matching n-grams in the prompt. @@ -145,9 +89,9 @@ A few important things to consider when using the EAGLE based draft models: so `cudagraph_capture_sizes` must be a list of capture sizes, where each size is calculated as `n * (K + 1)` for each batch size `n` you want to support. For instance, to support batch sizes from 1 to 4 with `num_speculative_tokens = 4`, `cudagraph_capture_sizes` should be set to `[5, 10, 15, 20]`. -## Speculating using MTP +## Speculating using MTP speculators -MTP (Multi-Token Prediction) boosts inference performance by parallelizing the prediction of multiple tokens, shifting from single-token to multi-token generation. This approach significantly increases generation throughput and achieves multiplicative acceleration in inference speed — all without compromising output quality. +The following code configures vLLM Ascend to use speculative decoding where proposals are generated by MTP (Multi Token Prediction), boosting inference performance by parallelizing the prediction of multiple tokens. For more information about MTP see [Multi_Token_Prediction](https://docs.vllm.ai/projects/ascend/en/latest/user_guide/feature_guide/Multi_Token_Prediction.html) - Online inference @@ -166,14 +110,9 @@ MTP (Multi-Token Prediction) boosts inference performance by parallelizing the p --trust-remote-code \ --gpu-memory-utilization 0.9 \ --compilation-config '{"cudagraph_mode": "FULL_DECODE_ONLY"}' \ - --speculative-config '{"num_speculative_tokens": 2, "method":"mtp", "disable_padded_drafter_batch": false}' + --speculative-config '{"num_speculative_tokens": 2, "method":"deepseek_mtp", "disable_padded_drafter_batch": "False"}' ``` -> [!NOTE] -> Due to the fact that only a single layer of weights is exposed in DeepSeek's MTP, accuracy and performance are not effectively guaranteed in scenarios where `num_speculative_tokens > 1` (especially ≥ 3). -> -> In the fullgraph mode with `num_speculative_tokens > 1`, the capture size of each ACLGraph must be an integer multiple of `(num_speculative_tokens + 1)`. - ## Speculating using Suffix Decoding The following code configures vLLM to use speculative decoding where proposals are generated using Suffix Decoding [(SuffixDecoding: Extreme Speculative Decoding for Emerging AI Applications)](https://arxiv.org/abs/2411.04975). @@ -286,55 +225,3 @@ Key configuration parameters: 4. **`kv_role`**: Must be set to `"kv_producer"` for the extraction mode. 5. **`shared_storage_path`**: Directory where hidden states will be saved as `.safetensors` files (one per request). - -## Block Verify and Entropy Verify - -vLLM Ascend provides two optional optimizations for the rejection sampler in speculative decoding: **Block Verify** and **Entropy Verify**. These features trade a small amount of output precision for improved inference throughput. - -> [!WARNING] -> Both Block Verify and Entropy Verify modify the token acceptance criteria and may cause minor precision degradation (e.g., slightly different output tokens compared to the standard rejection sampler). Evaluate the quality impact on your specific workload before enabling them in production. - -### Block Verify - -Block Verify evaluates all draft tokens as a block using cumulative probability products, rather than checking each token independently. This can improve the acceptance rate and reduce the overhead of rejection sampling, especially when `num_speculative_tokens >= 3`. - -### Entropy Verify - -Entropy Verify adjusts the acceptance threshold based on the entropy of the target distribution: - -- **High entropy** (uncertain distribution) → lower effective threshold → more tokens accepted -- **Low entropy** (confident distribution) → higher effective threshold → stricter rejection - -This entropy-aware threshold is controlled by two parameters: - -- **`posterior_threshold`** (default: `0.95`, range: `(0, 1]`): The upper bound of the modified threshold. Even when entropy is very low, the effective threshold will not exceed this value. -- **`posterior_alpha`** (default: `0.4`, range: `>= 0`): Controls how strongly entropy influences the threshold. A higher alpha makes the threshold more sensitive to entropy changes, resulting in a higher acceptance rate for speculative tokens but also greater precision loss. You need to tune this value based on your specific model and dataset. When alpha is `0`, entropy has no effect and the threshold equals `posterior_threshold`. - -### Usage - -- Online inference - - ```shell - vllm serve --additional-config \ - '{"rejection_sampler_config": {"enable_block_verify": true, \ - "enable_entropy_verify": true, "posterior_threshold": 0.95, \ - "posterior_alpha": 0.4}}' - ``` - -- Offline inference - - ```python - llm = LLM( - model, - additional_config={ - "rejection_sampler_config": { - "enable_block_verify": True, - "enable_entropy_verify": True, - "posterior_threshold": 0.95, - "posterior_alpha": 0.4, - } - }, - ) - ``` - -Both features can be enabled independently or together. When used together, the cumulative acceptance from Block Verify is combined with the entropy-adjusted threshold from Entropy Verify. diff --git a/docs/source/user_guide/image-1.png b/docs/source/user_guide/image-1.png deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/source/user_guide/image.png b/docs/source/user_guide/image.png deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/source/user_guide/release_notes.md b/docs/source/user_guide/release_notes.md index d756fc649..02f0996ec 100644 --- a/docs/source/user_guide/release_notes.md +++ b/docs/source/user_guide/release_notes.md @@ -1,145 +1,5 @@ # Release Notes -## v0.21.0rc1 - 2026.06.16 - -We're excited to announce the release of v0.21.0rc1 for vLLM Ascend. This is the first release candidate for the v0.21.0 release line, building on v0.20.2rc1. Please follow the [official doc](https://docs.vllm.ai/projects/ascend/en/latest) to get started. - -### Highlights - -- **DeepSeek-V4 for Ascend 950**: Full end-to-end support for DeepSeek-V4 on Ascend 950, including piecewise graph mode, DSA attention, KV cache management, and MTP. [#9757](https://github.com/vllm-project/vllm-ascend/pull/9757) [#9935](https://github.com/vllm-project/vllm-ascend/pull/9935) -- **Hybrid & Mamba Align Prefix Cache**: New alignment-based prefix caching mechanism for Hybrid and Mamba architectures, improving cache hit rates across related sequences. [#9533](https://github.com/vllm-project/vllm-ascend/pull/9533) -- **FULL_AND_PIECEWISE Graph Mode**: Introduced a hybrid graph compilation mode combining full-graph and piecewise strategies. **Requires HDK 25.5.1+ / CANN 8.5.0+** to remove the old stream-budget limitation, enabling up to ~32K graphs on A3 and ~64K on Ascend 950. [#9572](https://github.com/vllm-project/vllm-ascend/pull/9572) [#9962](https://github.com/vllm-project/vllm-ascend/pull/9962) -- **Python 3.12 Support**: Dockerfiles and setup.py now officially support Python 3.12, and all base images have been upgraded from `py3.11` to `py3.12`. [#9558](https://github.com/vllm-project/vllm-ascend/pull/9558) - -### Features - -- Added end-to-end support for DeepSeek-V4 on Ascend 950, including piecewise graph mode, DSA attention backend, KV cache management, distributed inference (with PP fixes), and MTP. [#9757](https://github.com/vllm-project/vllm-ascend/pull/9757) [#9473](https://github.com/vllm-project/vllm-ascend/pull/9473) [#9935](https://github.com/vllm-project/vllm-ascend/pull/9935) -- Added Hybrid & Mamba Align Prefix Cache for improved prefix cache reuse in Hybrid and Mamba architectures. [#9533](https://github.com/vllm-project/vllm-ascend/pull/9533) -- Added layerwise KV cache event callbacks for finer per-layer observability and control. [#9468](https://github.com/vllm-project/vllm-ascend/pull/9468) -- Added GLM4.7-Flash model support with Flash Attention backend. [#9560](https://github.com/vllm-project/vllm-ascend/pull/9560) -- Added `FULL_AND_PIECEWISE` graph mode, a hybrid compilation strategy mixing full-graph and piecewise approaches. **Requires HDK 25.5.1+ / CANN 8.5.0+** to remove the old stream-budget limitation, enabling significantly more graph captures — approximately 32K on A3 and 64K on Ascend 950. Legacy capture-size pruning has been cleaned up accordingly. [#9572](https://github.com/vllm-project/vllm-ascend/pull/9572) [#9962](https://github.com/vllm-project/vllm-ascend/pull/9962) -- Added W4A8 MXFP4 quantization support for Ascend 950. [#8265](https://github.com/vllm-project/vllm-ascend/pull/8265) -- Added MXFP8 FlashCommV3 support on Ascend 950. [#9671](https://github.com/vllm-project/vllm-ascend/pull/9671) -- Added NZ layout support for W4A8 MoE compressed tensors and C8 quantization (GQA). [#9625](https://github.com/vllm-project/vllm-ascend/pull/9625) [#9721](https://github.com/vllm-project/vllm-ascend/pull/9721) -- Added Mooncake Connector hybrid PCP/DCP support for QWen3.5. [#9809](https://github.com/vllm-project/vllm-ascend/pull/9809) -- Added D2D NetLoader weight loading for draft models in speculative decoding. [#9893](https://github.com/vllm-project/vllm-ascend/pull/9893) -- Added Mooncake Connector hybrid attention support. [#8850](https://github.com/vllm-project/vllm-ascend/pull/8850) -- Added Mooncake KV pool usage optimization. [#7820](https://github.com/vllm-project/vllm-ascend/pull/7820) -- Added KV Pool support for loading failure block IDs without hybrid recompute. [#9701](https://github.com/vllm-project/vllm-ascend/pull/9701) -- Added NPU storage metadata debug helpers for improved troubleshooting. [#9189](https://github.com/vllm-project/vllm-ascend/pull/9189) -- Added torch reserved/allocated memory profiling in `execute_model()`. [#9765](https://github.com/vllm-project/vllm-ascend/pull/9765) -- Added EPLB experts hotness metrics and EPLB time consumption data exposure. [#9536](https://github.com/vllm-project/vllm-ascend/pull/9536) -- Added `group_name` parameter when creating HCCL config for better group management. [#9667](https://github.com/vllm-project/vllm-ascend/pull/9667) -- Enabled prefix caching with PCP/DCP, allowing KV cache reuse across prefill and decode in disaggregated deployments. [#9638](https://github.com/vllm-project/vllm-ascend/pull/9638) -- Added simple yet general CPU KV Cache Offloading support. [#8743](https://github.com/vllm-project/vllm-ascend/pull/8743) -- Added Mooncake SSD offload with embedded client for large-scale KV cache storage. [#9731](https://github.com/vllm-project/vllm-ascend/pull/9731) -- Re-added code start compilation caching for npugraph_ex (previously reverted), improving warmup time. [#9914](https://github.com/vllm-project/vllm-ascend/pull/9914) -- Added ACL graph memory estimation before KV cache allocation to prevent OOM during graph capture. [#9865](https://github.com/vllm-project/vllm-ascend/pull/9865) -- Added DeepSeek-V4 compressor block size [32,64,128] support to improve automatic prefix cache hit rate. [#10354](https://github.com/vllm-project/vllm-ascend/pull/10354) -- Added batch_invariant_ops setup for reinforcement learning scenarios. [#10034](https://github.com/vllm-project/vllm-ascend/pull/10034) -- Adapted load balance proxy example to shared scheduler workers. [#9645](https://github.com/vllm-project/vllm-ascend/pull/9645) -- [310P] Added Qwen3.5 MTP and graph mode support. [#10309](https://github.com/vllm-project/vllm-ascend/pull/10309) - -### Hardware and Operator Support - -- Added custom GDN operator support for Ascend 950 with a new fused GDN gating AscendC operator (`fused_gdn_gating`). [#9382](https://github.com/vllm-project/vllm-ascend/pull/9382) [#9601](https://github.com/vllm-project/vllm-ascend/pull/9601) -- Added A2/A3 and Ascend 950 compressor operator paths. [#9350](https://github.com/vllm-project/vllm-ascend/pull/9350) -- Adapted GDN and Conv1D operators for the Ascend 950 platform. [#9224](https://github.com/vllm-project/vllm-ascend/pull/9224) -- Added Ascend 950 Dockerfiles and disaggregated PD endpoint configuration documentation. [#9723](https://github.com/vllm-project/vllm-ascend/pull/9723) [#9690](https://github.com/vllm-project/vllm-ascend/pull/9690) -- Removed unused MC2 prefill custom ops to streamline the operator surface. [#9919](https://github.com/vllm-project/vllm-ascend/pull/9919) -- Added Sparse Flash Attention support on Ascend 950 devices. [#9825](https://github.com/vllm-project/vllm-ascend/pull/9825) -- Added LightningIndexer and SparseFlashAttention ACLNN ops for improved sparse attention performance. [#9491](https://github.com/vllm-project/vllm-ascend/pull/9491) -- Added Rehash for AscendStore grouped keys to support DeepSeek V4 and compressed layouts. [#9789](https://github.com/vllm-project/vllm-ascend/pull/9789) - -### Performance - -- Optimized 310P MoE routing path for improved throughput. [#9105](https://github.com/vllm-project/vllm-ascend/pull/9105) -- Added NZ format support for W4A8 MoE compressed tensors, delivering better memory access patterns. [#9625](https://github.com/vllm-project/vllm-ascend/pull/9625) -- Added irregular mask build optimization for PCP/DCP with speculative decoding, improving efficiency. [#9678](https://github.com/vllm-project/vllm-ascend/pull/9678) -- Reconstructed reduce sampling to eliminate patch behaviors and support both DFlash and MTP. [#9735](https://github.com/vllm-project/vllm-ascend/pull/9735) - -### Stability and Bug Fixes - -- Fixed speculative decoding MLA shape mismatch with Eagle3 and added DeepSeek V2 Eagle3 support. [#9703](https://github.com/vllm-project/vllm-ascend/pull/9703) -- Fixed draft `lm_head` preservation for DFlash with reduced (draft-to-target) vocabulary. [#9795](https://github.com/vllm-project/vllm-ascend/pull/9795) -- Fixed a draft model index-out-of-range error caused by `token_indices_to_sample` on Ascend 950. [#9867](https://github.com/vllm-project/vllm-ascend/pull/9867) -- Added validation of DCP for draft models to catch configuration mismatches early. [#9717](https://github.com/vllm-project/vllm-ascend/pull/9717) -- Fixed multiple DeepSeek V4 PP issues. [#9473](https://github.com/vllm-project/vllm-ascend/pull/9473) -- Fixed DSA compressed idle dummy graph out-of-bounds issue. [#9818](https://github.com/vllm-project/vllm-ascend/pull/9818) -- Fixed HMA support in AscendMultiConnector. [#9782](https://github.com/vllm-project/vllm-ascend/pull/9782) -- Patched GLM47 inline zero-argument streaming tool calls. [#9901](https://github.com/vllm-project/vllm-ascend/pull/9901) -- Patched GLM tool-call final chunks for correct streaming termination. [#9787](https://github.com/vllm-project/vllm-ascend/pull/9787) -- Fixed empty `tool_calls` being emitted in OpenAI-format chat responses. [#9791](https://github.com/vllm-project/vllm-ascend/pull/9791) -- Backported MiniMax M2 tool call streaming support. [#9742](https://github.com/vllm-project/vllm-ascend/pull/9742) -- Repaired 310P Qwen3.5 ACLGraph precision. [#9727](https://github.com/vllm-project/vllm-ascend/pull/9727) -- Fixed precision of the `causal_conv1d_v310` operator on 310P. [#9720](https://github.com/vllm-project/vllm-ascend/pull/9720) -- Fixed ACL dtype mapping table for correct dtype conversions. [#9826](https://github.com/vllm-project/vllm-ascend/pull/9826) -- Chunked `wq_b` matmul to work around the NPU 65536 dimension limit. [#9780](https://github.com/vllm-project/vllm-ascend/pull/9780) -- Optimized router experts in eager mode and fixed communication handling. [#9728](https://github.com/vllm-project/vllm-ascend/pull/9728) -- Lazy initialization of KV store on `put` to avoid early resource allocation. [#9771](https://github.com/vllm-project/vllm-ascend/pull/9771) -- Fixed MTP placeholders exceeding max model length in P/D deployments. [#9749](https://github.com/vllm-project/vllm-ascend/pull/9749) -- Added compress ratio and block IDs cutting for Mooncake hybrid connector. [#9808](https://github.com/vllm-project/vllm-ascend/pull/9808) -- Fixed `qwen.png` FileNotFoundError in test assets. [#9907](https://github.com/vllm-project/vllm-ascend/pull/9907) -- Fixed backend unit test regressions. [#9805](https://github.com/vllm-project/vllm-ascend/pull/9805) -- Fixed PCP handshake port collision in Mooncake layerwise KV transfer connector. [#10019](https://github.com/vllm-project/vllm-ascend/pull/10019) -- Reduced Mooncake KV cache register regions for sparse C8 to avoid resource exhaustion. [#10102](https://github.com/vllm-project/vllm-ascend/pull/10102) -- Fixed W4A8 MXFP quantization in shared experts. [#10153](https://github.com/vllm-project/vllm-ascend/pull/10153) -- Fixed MoE hanging in multi-DP scenarios. [#10117](https://github.com/vllm-project/vllm-ascend/pull/10117) -- Fixed reduce sampling where `top_k` and `top_p` could be None. [#10004](https://github.com/vllm-project/vllm-ascend/pull/10004) -- Added environment variable to control DP metadata all_reduce communication. [#10046](https://github.com/vllm-project/vllm-ascend/pull/10046) -- Fixed `token_indices_to_sample` out-of-bounds index error. [#10080](https://github.com/vllm-project/vllm-ascend/pull/10080) -- Fixed `chunk_scaled_dot_kkt_fwd_kernel` accuracy issues. [#10033](https://github.com/vllm-project/vllm-ascend/pull/10033) -- Fixed DeepSeek-V4 compress attention groups prefix caching hit. [#9903](https://github.com/vllm-project/vllm-ascend/pull/9903) -- Fixed DSv4 piecewise graph scenario. [#10003](https://github.com/vllm-project/vllm-ascend/pull/10003) -- Fixed `split_qkv_rmsnorm_rope` Triton kernel accuracy on Ascend 950. [#9849](https://github.com/vllm-project/vllm-ascend/pull/9849) -- Fixed lm_head parallel feature assert and nightly test failures. [#10100](https://github.com/vllm-project/vllm-ascend/pull/10100) -- Fixed NPU MoE quantization methods to correctly support TP-only configurations. [#9908](https://github.com/vllm-project/vllm-ascend/pull/9908) -- Fixed stuck chunked pipeline parallelism by updating `discard_request_mask`. [#9843](https://github.com/vllm-project/vllm-ascend/pull/9843) -- Fixed `cudagraph_config` mode `FULL` corner case. [#9863](https://github.com/vllm-project/vllm-ascend/pull/9863) -- Fixed 310P Qwen3-Embedding and Qwen3-VL-Embedding run failures. [#9854](https://github.com/vllm-project/vllm-ascend/pull/9854) -- Removed legacy capture-size pruning in `update_aclgraph_sizes`. [#9962](https://github.com/vllm-project/vllm-ascend/pull/9962) -- Fixed `fused_gdn_gating` unavailability on Ascend 950 for Qwen3.5. [#10083](https://github.com/vllm-project/vllm-ascend/pull/10083) -- Fixed DSA v1 W8A8 dynamic conflict in attention. [#9476](https://github.com/vllm-project/vllm-ascend/pull/9476) -- Fixed DeepSeek-V4 compressed prefix lookup in prefix cache. [#10297](https://github.com/vllm-project/vllm-ascend/pull/10297) -- Fixed GLM streaming tool call name preservation. [#10361](https://github.com/vllm-project/vllm-ascend/pull/10361) -- Fixed GLM5.1-W8A8 MTP load weight error with vLLM v0.21.0. [#10317](https://github.com/vllm-project/vllm-ascend/pull/10317) -- Moved DeepSeek V4 cache hooks into model, removing legacy patch environment variables. [#10327](https://github.com/vllm-project/vllm-ascend/pull/10327) [#10333](https://github.com/vllm-project/vllm-ascend/pull/10333) -- Fixed FP32 MM encoder attention support. [#10200](https://github.com/vllm-project/vllm-ascend/pull/10200) -- Aligned vllm-ascend with upstream vLLM unit test expectations. [#10146](https://github.com/vllm-project/vllm-ascend/pull/10146) - -### Dependencies - -- **Python**: Python 3.12 is now officially supported and the default for all Docker images. Python 3.10 and 3.11 remain supported. [#9558](https://github.com/vllm-project/vllm-ascend/pull/9558) -- **Upstream vLLM**: Upgraded from v0.20.2 to v0.21.0. [#9835](https://github.com/vllm-project/vllm-ascend/pull/9835) -- **xlite**: Upgraded from `0.1.0rc9.dev210` to `0.1.0rc10.dev210`. -- **CANN**: 9.0.0 for A2/A3/Ascend 950 (unchanged from v0.20.2rc1); **310P uses CANN 9.1.0 beta**. **Note**: `FULL_AND_PIECEWISE` requires HDK 25.5.1+ / CANN 8.5.0+ for the stream-budget fix; older stacks are still limited by the legacy stream budget and may fall back to `PIECEWISE`. -- **PyTorch / torch_npu**: 2.10.0 (unchanged from v0.20.2rc1). -- **triton-ascend**: 3.2.1 (unchanged from v0.20.2rc1). -- **Mooncake**: Upgraded from v0.3.8.post1 to v0.3.9. [#10339](https://github.com/vllm-project/vllm-ascend/pull/10339) - -### Breaking Changes and Migration Notes - -- **`VLLM_ASCEND_ENABLE_CONTEXT_PARALLEL` Removed**: The environment variable `VLLM_ASCEND_ENABLE_CONTEXT_PARALLEL` has been removed as part of the migration to `AscendConfig`. Users should migrate any remaining uses to the equivalent AscendConfig option. [#9668](https://github.com/vllm-project/vllm-ascend/pull/9668) -- **DSA-CP Configuration Decoupling**: DSA-CP is now controlled via `additional_config.enable_dsa_cp`, decoupled from the FlashComm1 switch. Users who previously relied on FC1 implicitly enabling DSA-CP must now explicitly set both `enable_flashcomm1` and `enable_dsa_cp`. [#9697](https://github.com/vllm-project/vllm-ascend/pull/9697) [#9910](https://github.com/vllm-project/vllm-ascend/pull/9910) -- **Python 3.12 in Docker Images**: All Docker base images now use Python 3.12 (`py3.12`). If your deployment or custom images depend on `py3.11`, update your image tags accordingly. [#9558](https://github.com/vllm-project/vllm-ascend/pull/9558) - -### Documentation - -- Refreshed and optimized documentation for the current development branch. [#9606](https://github.com/vllm-project/vllm-ascend/pull/9606) -- Updated model-code converter writing guide. [#9881](https://github.com/vllm-project/vllm-ascend/pull/9881) -- Added DSA-CP configuration documentation for DeepSeek V3.2 and GLM5. [#9910](https://github.com/vllm-project/vllm-ascend/pull/9910) -- Added Ascend 950 disaggregated PD endpoint configuration documentation. [#9690](https://github.com/vllm-project/vllm-ascend/pull/9690) - -### Known Issues - -- **FULL_AND_PIECEWISE on older HDK/CANN**: HDK < 25.5.1 / CANN < 8.5.0 stacks still have the old stream-budget limitation, which may cause graph capture failures or fallback to `PIECEWISE` mode. Upgrade to HDK 25.5.1+ / CANN 8.5.0+ is recommended for full `FULL_AND_PIECEWISE` support. -- GLM5/GLM5.1 W4A8 deployments have known issues in some advanced configurations. CANN 9.0 with MC2 can return inaccurate output, FlashComm can fail during model startup, and MTP weight loading can fail in 1P1D A3 deployments. [#9395](https://github.com/vllm-project/vllm-ascend/issues/9395) [#9658](https://github.com/vllm-project/vllm-ascend/issues/9658) [#9655](https://github.com/vllm-project/vllm-ascend/issues/9655) -- GLM-5.1 deployments can hit `MoeDistributeDispatchV2`/NPU graph failures when Expert Parallel is used together with FULL graph mode. The reported workaround is to disable Expert Parallel for FULL graph mode, or use PIECEWISE/eager mode. [#9503](https://github.com/vllm-project/vllm-ascend/issues/9503) -- Qwen3.6-35B-A3B may shut down when MTP/speculative decoding is enabled, with `numAcceptedTokens[0]=4 exceeds varlen segment length=3` reported during shape/dtype processing. [#9956](https://github.com/vllm-project/vllm-ascend/issues/9956) -- GLM-5.1 can hang on the P node in 200K long-sequence 1P1D agent workloads after long-running service, with `MoeDistributeDispatchV2`/`aclnnMoeDistributeDispatchV4` reporting an AICore timeout. [#9958](https://github.com/vllm-project/vllm-ascend/issues/9958) -- GLM5 W4A8 deployments can see a significantly lower speculative decoding acceptance rate when MTP3 is used together with FlashComm. [#9803](https://github.com/vllm-project/vllm-ascend/issues/9803) -- **DeepSeek-V4 KV Pool**: When enabling KV Pool for DeepSeek-V4, the `--no-disable-hybrid-kv-cache-manager` flag must be added, otherwise the service will OOM at startup. Additionally, KV Pool for DSv4 stores all states for all compression ratio families — storing a sequence of 1M tokens takes approximately 300GB, which is the same behavior as upstream vLLM. [#9975](https://github.com/vllm-project/vllm-ascend/issues/9975) - ## v0.20.2rc1 - 2026.06.03 We're excited to announce the release of v0.20.2rc1 for vLLM Ascend. This is the first release candidate for the v0.20.2 release line. Please follow the [official doc](https://docs.vllm.ai/projects/ascend/en/latest) to get started. @@ -147,7 +7,7 @@ We're excited to announce the release of v0.20.2rc1 for vLLM Ascend. This is the ### Highlights - **DeepSeek V4 Support**: Added end-to-end support for DeepSeek V4, including the model architecture, DSA attention backend, KV cache management, distributed inference, tool-call parser, MTP support, KV Pool adaptation, and custom operator enablement. [#9270](https://github.com/vllm-project/vllm-ascend/pull/9270) [#9385](https://github.com/vllm-project/vllm-ascend/pull/9385) [#9228](https://github.com/vllm-project/vllm-ascend/pull/9228) -- **Ascend 950 Products and XLite Quantization Expansion**: Added MXFP4 flatquant with row parallelism for Ascend 950 Products and expanded XLite support to GLM-4.7 W8A8 quantization. [#9391](https://github.com/vllm-project/vllm-ascend/pull/9391) [#9415](https://github.com/vllm-project/vllm-ascend/pull/9415) +- **A5 and XLite Quantization Expansion**: Added MXFP4 flatquant with row parallelism for Ascend A5 and expanded XLite support to GLM-4.7 W8A8 quantization. [#9391](https://github.com/vllm-project/vllm-ascend/pull/9391) [#9415](https://github.com/vllm-project/vllm-ascend/pull/9415) ### Features @@ -161,8 +21,8 @@ We're excited to announce the release of v0.20.2rc1 for vLLM Ascend. This is the ### Hardware and Operator Support - Added DeepSeek V4 custom operators required for the new model path, registered the operators for Ascend 910B, and switched the DeepSeek V4 `hc_pre` path to a fused operator. [#9228](https://github.com/vllm-project/vllm-ascend/pull/9228) [#9339](https://github.com/vllm-project/vllm-ascend/pull/9339) [#9396](https://github.com/vllm-project/vllm-ascend/pull/9396) -- Enabled MXFP4 flatquant and row parallel support on Ascend 950 Products. [#9391](https://github.com/vllm-project/vllm-ascend/pull/9391) -- Enabled MC2 dispatch and combine support for MXFP4/MXFP8 quantization on Ascend 950 Products. [#9365](https://github.com/vllm-project/vllm-ascend/pull/9365) [#9328](https://github.com/vllm-project/vllm-ascend/pull/9328) +- Enabled MXFP4 flatquant and row parallel support on Ascend A5. [#9391](https://github.com/vllm-project/vllm-ascend/pull/9391) +- Enabled MC2 dispatch and combine support for MXFP4/MXFP8 quantization on Ascend A5. [#9365](https://github.com/vllm-project/vllm-ascend/pull/9365) [#9328](https://github.com/vllm-project/vllm-ascend/pull/9328) - Improved 310P support by optimizing fused operators for Qwen3.5 Dense ACLGraph and simplifying the 310P RMSNormGated path. [#9104](https://github.com/vllm-project/vllm-ascend/pull/9104) [#9489](https://github.com/vllm-project/vllm-ascend/pull/9489) ### Performance @@ -534,7 +394,7 @@ This is the first release candidate of v0.18.0 for vLLM Ascend. Please follow th ### Highlights - C8(INT8 KV cache) is now supported for GQA attention models, and also supported on DeepSeek-V3.1 with PD disaggregation scenario. [#7474](https://github.com/vllm-project/vllm-ascend/pull/7474), [#7222](https://github.com/vllm-project/vllm-ascend/pull/7222) -- DeepSeek models are now supported on Ascend 950 Products through new MLA operators. [#7232](https://github.com/vllm-project/vllm-ascend/pull/7232) +- DeepSeek models are now supported on A5 through new MLA operators. [#7232](https://github.com/vllm-project/vllm-ascend/pull/7232) ### Features diff --git a/docs/source/user_guide/support_matrix/supported_models.md b/docs/source/user_guide/support_matrix/supported_models.md index 4c87127e1..e586a5987 100644 --- a/docs/source/user_guide/support_matrix/supported_models.md +++ b/docs/source/user_guide/support_matrix/supported_models.md @@ -28,7 +28,6 @@ Get the latest info here: None: - logging.basicConfig( - level=logging.WARNING, - format="%(asctime)s %(levelname)s [%(name)s] %(message)s", - force=True, - ) - logger.setLevel(getattr(logging, log_level.upper())) - - -def next_req_id() -> str: - return str(uuid.uuid4()) - - -def calculate_prefill_score(request_length: int) -> float: - length_score = request_length / 4.0 - return length_score * 0.0345 + 120.0745 - - -def calculate_decode_score(request_length: int) -> float: - return request_length - - -def normalize_host(host: str) -> str: - return host.replace("localhost", "0.0.0.0").replace("127.0.0.1", "0.0.0.0") - - -def server_key(host: str, port: int) -> str: - return f"{normalize_host(host)}:{int(port)}" - - -def build_server_url(host: str, port: int) -> str: - url = f"http://{host}:{port}" - try: - ip = ipaddress.ip_address(host) - if isinstance(ip, ipaddress.IPv6Address): - url = f"http://[{host}]:{port}" - except Exception: - pass - return url +class ServerState: + def __init__(self, host, port): + self.host = host + self.port = port + self.url = f"http://{host}:{port}/v1" + try: + ip = ipaddress.ip_address(self.host) + if isinstance(ip, ipaddress.IPv6Address): + self.url = f"http://[{host}]:{port}/v1" + except Exception: + pass + self.client = httpx.AsyncClient( + timeout=None, + base_url=self.url, + limits=httpx.Limits(max_connections=100000, max_keepalive_connections=100000), + ) + self.active_tokens = 0 + self.active_kv_cache = 0 # Only for prefiller + self.active_requests = 0 # Number of active requests + self.aborted_requests = set() # Track aborted requests + # Removed individual server lock - will use global locks instead -def build_base_url(host: str, port: int) -> str: - return f"{build_server_url(host, port)}/v1" + def __eq__(self, other): + self_host = self.host.replace("localhost", "0.0.0.0").replace("127.0.0.1", "0.0.0.0") + other_host = other.host.replace("localhost", "0.0.0.0").replace("127.0.0.1", "0.0.0.0") + return self_host == other_host and str(self.port) == str(other.port) + def __hash__(self): + self_host = self.host.replace("localhost", "0.0.0.0").replace("127.0.0.1", "0.0.0.0") + return hash((self_host, str(self.port))) -class SharedProxyScheduler: - """Centralized mutable scheduling state shared by all uvicorn workers. + def __repr__(self): + return f"{self.host}:{self.port}" - Uses lazy-deletion min-heap: on priority change, push a new entry and - bump the server's ``heap_seq`` counter; stale entries (whose seq does - not match) are skipped on pop. - """ +class ProxyState: def __init__(self, prefiller_instances, decoder_instances): - self._lock = threading.RLock() self.request_num = 0 - self.waiting_nodes: dict[str, tuple[str, tuple[str, int], int]] = {} - self._pools: dict[ServerRole, RolePools] = { - ServerRole.PREFILL: RolePools(), - ServerRole.DECODE: RolePools(), - } - self._ordinal = 0 - - for host, port in prefiller_instances: - self._add_server_no_lock(ServerRole.PREFILL, host, port) - for host, port in decoder_instances: - self._add_server_no_lock(ServerRole.DECODE, host, port) - - def _pool(self, role: ServerRole) -> RolePools: - return self._pools[role] - - @property - def prefillers(self) -> dict[str, BackendServer]: - return self._pool(ServerRole.PREFILL).servers - - @property - def decoders(self) -> dict[str, BackendServer]: - return self._pool(ServerRole.DECODE).servers - - def _next_ordinal(self) -> int: - ordinal = self._ordinal - self._ordinal += 1 - return ordinal - - def _priority(self, role: ServerRole, entry: BackendServer, key: str) -> float: - if key in self._pool(role).tainted: - return TAINT_PRIORITY - if role is ServerRole.PREFILL: - return entry.active_tokens + entry.active_kv_cache * 0.3 - return entry.active_tokens - - def _push_heap(self, role: ServerRole, key: str) -> None: - pool = self._pool(role) - entry = pool.servers[key] - entry.heap_seq += 1 - heapq.heappush(pool.heap, (self._priority(role, entry, key), entry.ordinal, entry.heap_seq, key)) - if len(pool.heap) > 2 * len(pool.servers): - self._reset_heap(role) - - def _pop_valid(self, role: ServerRole) -> str: - pool = self._pool(role) - while pool.heap: - _, _, seq, key = heapq.heappop(pool.heap) - if key not in pool.servers: - continue - entry = pool.servers[key] - if entry.heap_seq == seq: - return key - raise RuntimeError(f"No available {role.value} servers") - - def _reset_heap(self, role: ServerRole, *, bump_seq: bool = False) -> None: - pool = self._pool(role) - heap = [] - for key, entry in pool.servers.items(): - if bump_seq: - entry.heap_seq += 1 - heap.append((self._priority(role, entry, key), entry.ordinal, entry.heap_seq, key)) - heapq.heapify(heap) - pool.heap = heap - - def _add_server_no_lock(self, role: ServerRole, host: str, port: int) -> bool: - key = server_key(host, port) - pool = self._pool(role) - if key in pool.servers: - return False - pool.servers[key] = BackendServer(host, int(port), self._next_ordinal()) - self._push_heap(role, key) - return True - - def get_snapshot(self) -> dict[str, list[dict[str, Any]]]: - with self._lock: - return { - "prefill_instances": [ - {"host": e.host, "port": e.port} - for _, e in sorted(self.prefillers.items(), key=lambda item: item[1].ordinal) - ], - "decode_instances": [ - {"host": e.host, "port": e.port} - for _, e in sorted(self.decoders.items(), key=lambda item: item[1].ordinal) - ], - } - - def log_status(self, msg: str) -> None: - snapshot = self.get_snapshot() - logger.info( - "%s prefill=%s decode=%s", - msg, - [f"{s['host']}:{s['port']}" for s in snapshot["prefill_instances"]], - [f"{s['host']}:{s['port']}" for s in snapshot["decode_instances"]], - ) - - def healthcheck(self) -> dict[str, Any]: - with self._lock: - return { - "status": "ok", - "prefill_instances": len(self.prefillers), - "decode_instances": len(self.decoders), - "request_num": self.request_num, - } - - def _pick_server( - self, - role: ServerRole, - load: float, - *, - active_tokens: bool = False, - kv_cache: bool = False, - ) -> dict[str, Any]: - key = self._pop_valid(role) - entry = self._pool(role).servers[key] - if active_tokens: - entry.active_tokens += load - if kv_cache: - entry.active_kv_cache += load - self._push_heap(role, key) - return {"key": key, "host": entry.host, "port": entry.port} - - def _release_load( - self, - role: ServerRole, - key: str | None, - load: float, - *, - active_tokens: bool = False, - kv_cache: bool = False, - ) -> None: - if not key or key not in self._pool(role).servers: + self.tainted_prefillers: list[ServerState] = [] + self.tainted_decoders: list[ServerState] = [] + self.node_listener = NodeListener(self) + + self.prefillers: list[ServerState] = [ServerState(h, p) for h, p in prefiller_instances] + self.decoders: list[ServerState] = [ServerState(h, p) for h, p in decoder_instances] + self.req_to_prefiller = {} + self.req_id_lock = asyncio.Lock() + # Removed selection locks - no longer needed for synchronous methods + + # Initialize priority queues for efficient server selection + # Each entry is (priority_score, server_index, server_reference) + # Lower priority score = higher priority (less loaded) + self.prefiller_heap = [(0.0, i, server) for i, server in enumerate(self.prefillers)] + self.decoder_heap = [(0.0, i, server) for i, server in enumerate(self.decoders)] + heapq.heapify(self.prefiller_heap) + heapq.heapify(self.decoder_heap) + + def _update_prefiller_priority(self, server_idx: int): + """Update the priority of a prefiller server in the heap.""" + server = self.prefillers[server_idx] + # Priority based on active_tokens and active_kv_cache + priority = server.active_tokens + server.active_kv_cache * 0.3 + # Remove old entry and add new one + self.prefiller_heap = [(p, i, s) for p, i, s in self.prefiller_heap if i != server_idx] + heapq.heappush(self.prefiller_heap, (priority, server_idx, server)) + + def _update_decoder_priority(self, server_idx: int): + """Update the priority of a decoder server in the heap.""" + server = self.decoders[server_idx] + priority = server.active_tokens + # Remove old entry and add new one + self.decoder_heap = [(p, i, s) for p, i, s in self.decoder_heap if i != server_idx] + heapq.heappush(self.decoder_heap, (priority, server_idx, server)) + + def abort_prefiller_request(self, server_idx: int, request_id): # Changed to synchronous + """ + Mark a request as aborted. This will helps to release kv cache in + prefiller node. + """ + # No lock needed - atomic operation + if server_idx >= len(self.prefillers): return - entry = self._pool(role).servers[key] - if active_tokens: - entry.active_tokens -= load - if kv_cache: - entry.active_kv_cache = max(0.0, entry.active_kv_cache - load) - self._push_heap(role, key) - - def begin_request(self, load: float) -> dict[str, Any]: - """Pick a prefiller, reserve KV pressure, and count this as an active request.""" - with self._lock: - picked = self._pick_server(ServerRole.PREFILL, load, kv_cache=True) - self.request_num += 1 - return picked - - def reserve_prefill_kv(self, load: float) -> dict[str, Any]: - """Pick a prefiller for recompute without bumping the active request count.""" - with self._lock: - return self._pick_server(ServerRole.PREFILL, load, kv_cache=True) - - def pick_decoder(self, load: float) -> dict[str, Any]: - with self._lock: - return self._pick_server(ServerRole.DECODE, load, active_tokens=True) - - def release_prefill_kv(self, key: str, load: float) -> None: - with self._lock: - self._release_load(ServerRole.PREFILL, key, load, kv_cache=True) - - def release_decoder(self, key: str, load: float) -> None: - with self._lock: - self._release_load(ServerRole.DECODE, key, load, active_tokens=True) - - def finish_request( - self, - prefiller_key: str | None, - prefiller_load: float, - decoder_key: str | None, - decoder_load: float, - release_prefill_kv: bool, - ) -> None: - with self._lock: - if release_prefill_kv: - self._release_load(ServerRole.PREFILL, prefiller_key, prefiller_load, kv_cache=True) - self._release_load(ServerRole.DECODE, decoder_key, decoder_load, active_tokens=True) - self.request_num = max(0, self.request_num - 1) - - def get_waiting_nodes(self) -> dict[str, tuple[str, tuple[str, int], int]]: - with self._lock: - return dict(self.waiting_nodes) - - def add_instances(self, role: ServerRole, instances: list[tuple[str, int]]) -> list[str]: - waiting_nodes: list[str] = [] - with self._lock: - servers = self._pool(role).servers - for host, port in instances: - key = server_key(host, port) - if key in servers or key in self.waiting_nodes: - continue - self.waiting_nodes[key] = (role.value, (host, int(port)), 0) - waiting_nodes.append(f"{host}:{port}") - return waiting_nodes - - def mark_waiting_retry(self, key: str, retry_count: int) -> None: - with self._lock: - if key not in self.waiting_nodes: - return - instance_type, server, _ = self.waiting_nodes[key] - self.waiting_nodes[key] = (instance_type, server, retry_count) - - def activate_waiting_instance(self, role: ServerRole, host: str, port: int) -> None: - with self._lock: - key = server_key(host, port) - self.waiting_nodes.pop(key, None) - pool = self._pool(role) - if key in pool.tainted: - pool.tainted.discard(key) - self._push_heap(role, key) - return - if self._add_server_no_lock(role, host, port): - self.log_status(f"Add {role.value} instance: {host}:{port}.") + self.prefillers[server_idx].aborted_requests.add(request_id) + + def acquire_aborted_prefiller_requests(self, server_idx: int): # Changed to synchronous + """ + Get the set of aborted requests and clear it. + This is used to release kv cache in prefiller node. + """ + # No lock needed - atomic operation + if server_idx >= len(self.prefillers): + return set() + aborted_requests = self.prefillers[server_idx].aborted_requests.copy() + self.prefillers[server_idx].aborted_requests.clear() + return aborted_requests + + async def next_req_id(self): + async with self.req_id_lock: + return str(uuid.uuid4()) + + def select_prefiller(self, token_count): # Changed to synchronous + # No lock needed - entire function is atomic + if not self.prefiller_heap: + raise RuntimeError("No prefiller servers available") + + priority, chosen, server = heapq.heappop(self.prefiller_heap) + + # Update the chosen server atomically + self.prefillers[chosen].active_tokens += token_count + self.prefillers[chosen].active_kv_cache += token_count + + # Update priority and re-add to heap + self._update_prefiller_priority(chosen) + + return chosen + + def release_prefiller(self, idx, token_count): # Changed to synchronous + # No lock needed - atomic operation + if idx >= len(self.prefillers): + return + self.prefillers[idx].active_tokens -= token_count + # Update priority queue after releasing + self._update_prefiller_priority(idx) - def drop_waiting_instance(self, key: str) -> None: - with self._lock: - self.waiting_nodes.pop(key, None) + def release_prefiller_kv(self, idx, token_count): # Changed to synchronous + # No lock needed - atomic operation + if idx >= len(self.prefillers): + return + if self.prefillers[idx].active_kv_cache > 0: + self.prefillers[idx].active_kv_cache -= token_count + # Update priority queue after releasing + self._update_prefiller_priority(idx) - def remove_instances(self, role: ServerRole, instances: list[tuple[str, int]]) -> bool: - if not instances: - return False - keys = {server_key(host, port) for host, port in instances} - with self._lock: - pool = self._pool(role) - if self.request_num > 0: - pool.tainted.update(keys) - self._reset_heap(role, bump_seq=True) - logger.warning("Start to taint %s instances %s.", role.value, sorted(keys)) - return True - - removed = False - for key in keys: - removed = pool.servers.pop(key, None) is not None or removed - self.waiting_nodes.pop(key, None) - pool.tainted.difference_update(keys) - if removed: - self._reset_heap(role, bump_seq=True) - self.log_status(f"Remove {role.value} instances: {sorted(keys)}.") - return False + def select_decoder(self, token_count): # Changed to synchronous + # No lock needed - entire function is atomic + if not self.decoder_heap: + raise RuntimeError("No decoder servers available") - def finalize_tainted_instances(self) -> None: - with self._lock: - if self.request_num != 0: - return - for role in ServerRole: - pool = self._pool(role) - if not pool.tainted: - continue - keys = list(pool.tainted) - for key in keys: - pool.servers.pop(key, None) - pool.tainted.clear() - self._reset_heap(role, bump_seq=True) - self.log_status(f"Remove {role.value} instances after drain: {keys}.") + priority, chosen, server = heapq.heappop(self.decoder_heap) + # Update the chosen server atomically + self.decoders[chosen].active_tokens += token_count -class SchedulerManager(BaseManager): - """Multiprocessing RPC bridge; body is empty but required by BaseManager.""" + # Update priority and re-add to heap + self._update_decoder_priority(chosen) + return chosen -def _shared_scheduler_proxy() -> "SharedProxyScheduler": - if shared_scheduler is None: - raise RuntimeError("shared scheduler is not initialized") - return shared_scheduler + def release_decoder(self, idx, token_count): # Changed to synchronous + # No lock needed - atomic operation + if idx >= len(self.decoders): + return + self.decoders[idx].active_tokens -= token_count + # Update priority queue after releasing + self._update_decoder_priority(idx) + + # Omni_infer's calculate_input_scores function + def calculate_prefill_scores(self, request_length: int) -> float: + length_score = request_length / 4.0 + input_score = length_score * 0.0345 + 120.0745 + return input_score + + def calculate_decode_scores(self, request_length: int) -> float: + return request_length + + async def add_instances(self, instance_type: str, instances: list[ServerState]) -> tuple[list[str], list[str]]: + added_nodes, waiting_nodes = [], [] + for server in instances: + is_valid = await self.node_listener.check_instance_status(server.client) + if is_valid and instance_type == InstanceType.PREFILL: + self.add_prefillers([server]) + added_nodes.append(str(server)) + elif is_valid and instance_type == InstanceType.DECODE: + self.add_decoders([server]) + added_nodes.append(str(server)) + else: + node = str(server) + self.node_listener.waiting_nodes[node] = (instance_type, server, 0) + waiting_nodes.append(node) + return added_nodes, waiting_nodes + + def add_prefillers(self, instances: list[ServerState]) -> None: + for server in instances: + if server in self.tainted_prefillers: + self.tainted_prefillers.remove(server) + self.prefiller_heap = [ + (0, idx, server) if srv == server else (priority, idx, srv) + for priority, idx, srv in self.prefiller_heap + ] + heapq.heapify(self.prefiller_heap) + elif server not in self.prefillers: + self.prefillers.append(server) + # prefiller_heap: [(priority_0, 0, server_0)] -> [(priority_0, 0, server_0), (0, 1, server_1)] + heapq.heappush(self.prefiller_heap, (0, len(self.prefillers) - 1, server)) + self.print_status(f"Add prefiller instances: {instances}.") + + def add_decoders(self, instances: list[ServerState]) -> None: + for server in instances: + if server in self.tainted_decoders: + self.tainted_decoders.remove(server) + self.decoder_heap = [ + (0, idx, server) if srv == server else (priority, idx, srv) + for priority, idx, srv in self.decoder_heap + ] + heapq.heapify(self.decoder_heap) + elif server not in self.decoders: + self.decoders.append(server) + # decoder_heap: [(priority_0, 0, server_0)] -> [(priority_0, 0, server_0), (0, 1, server_1)] + heapq.heappush(self.decoder_heap, (0, len(self.decoders) - 1, server)) + self.print_status(f"Add decoder instances: {instances}.") + + def remove_prefillers(self, instances: list[ServerState]) -> bool: + if not instances: + return False + if self.request_num > 0: + logger.warning("Start to taint prefill instances %s.", instances) + self._taint_prefillers(instances) + return True + + instances_to_remove = set(instances) + self.prefillers = [server for server in self.prefillers if server not in instances_to_remove] + prefiller_heap_copy = self.prefiller_heap.copy() + prefiller_heap_copy.sort(key=lambda x: x[1]) # sorted by key: prefiller_idx + prefiller_heap = [] + idx = 0 + for priority, _, server in prefiller_heap_copy: + if server not in instances_to_remove: + prefiller_heap.append((priority, idx, server)) + idx += 1 + + # prefiller_heap: [(priority_0, 0, server_0), (priority_1, 1, server_1)] -> [(priority_1, 0, server_1)] + self.prefiller_heap = prefiller_heap + heapq.heapify(self.prefiller_heap) + self.print_status(f"Remove prefiller instances: {instances}.") + return False + + def remove_decoders(self, instances: list[ServerState]) -> bool: + if not instances: + return False -SchedulerManager.register("get_scheduler", callable=_shared_scheduler_proxy) + if self.request_num > 0: + logger.warning("Start to taint decode instances %s.", instances) + self._taint_decoders(instances) + return True + + instances_to_remove = set(instances) + self.decoders = [server for server in self.decoders if server not in instances_to_remove] + decoder_heap_copy = self.decoder_heap.copy() + decoder_heap_copy.sort(key=lambda x: x[1]) # sorted by key: decoder_idx + decoder_heap = [] + idx = 0 + for priority, _, server in decoder_heap_copy: + if server not in instances_to_remove: + decoder_heap.append((priority, idx, server)) + idx += 1 + + # decoder_heap: [(priority_0, 0, server_0), (priority_1, 1, server_1)] -> [(priority_1, 0, server_1)] + self.decoder_heap = decoder_heap + heapq.heapify(self.decoder_heap) + self.print_status(f"Remove decoder instances: {instances}.") + return False + + def _taint_prefillers(self, instances: list[ServerState]) -> None: + instances_to_taint = set(instances) + for server in self.prefillers: + if server in instances_to_taint and server not in self.tainted_prefillers: + self.tainted_prefillers.append(server) + + self.prefiller_heap = [ + (TAINT_PRIORITY, idx, srv) if srv in instances_to_taint else (priority, idx, srv) + for priority, idx, srv in self.prefiller_heap + ] + heapq.heapify(self.prefiller_heap) + + def _taint_decoders(self, instances: list[ServerState]) -> None: + instances_to_taint = set(instances) + for server in self.decoders: + if server in instances_to_taint and server not in self.tainted_decoders: + self.tainted_decoders.append(server) + + self.decoder_heap = [ + (TAINT_PRIORITY, idx, srv) if srv in instances_to_taint else (priority, idx, srv) + for priority, idx, srv in self.decoder_heap + ] + heapq.heapify(self.decoder_heap) + + def print_status(self, msg: str) -> None: + status = { + "prefill_instances": [str(server) for server in self.prefillers], + "decode_instances": [str(server) for server in self.decoders], + } + print(f"{msg} Status: {status}") -class WorkerRuntime: - def __init__(self, scheduler: Any): - self.scheduler = scheduler - self._clients: dict[ServerRole, dict[str, httpx.AsyncClient]] = { - ServerRole.PREFILL: {}, - ServerRole.DECODE: {}, - } - self._async_lock = asyncio.Lock() - - async def schedule(self, method: str, /, *args, **kwargs) -> Any: - async with self._async_lock: - return getattr(self.scheduler, method)(*args, **kwargs) - - async def get_client(self, role: ServerRole, key: str) -> httpx.AsyncClient: - clients = self._clients[role] - if key not in clients: - await self.sync_clients() - return clients[key] - - async def sync_clients(self) -> None: - snapshot = self.scheduler.get_snapshot() - role_targets = { - ServerRole.PREFILL: { - server_key(s["host"], s["port"]): (s["host"], s["port"]) for s in snapshot["prefill_instances"] - }, - ServerRole.DECODE: { - server_key(s["host"], s["port"]): (s["host"], s["port"]) for s in snapshot["decode_instances"] - }, - } - for role, targets in role_targets.items(): - await self._sync_clients(role, targets) - - async def _sync_clients(self, role: ServerRole, targets: dict[str, tuple[str, int]]) -> None: - clients = self._clients[role] - for key in [key for key in clients if key not in targets]: - await clients.pop(key).aclose() - for key, (host, port) in targets.items(): - if key in clients: - continue - clients[key] = httpx.AsyncClient( - timeout=None, - base_url=build_base_url(host, port), - limits=httpx.Limits(max_connections=100000, max_keepalive_connections=100000), - ) - - async def close(self) -> None: - for role in ServerRole: - for client in list(self._clients[role].values()): - await client.aclose() - self._clients[role].clear() - - -def get_runtime() -> WorkerRuntime: - if runtime is None: - raise RuntimeError("worker runtime is not initialized") - return runtime +proxy_state = None class NodeListener: - def __init__(self, scheduler): - self.scheduler = scheduler - self.thread = threading.Thread(target=self._run, daemon=True) - self.thread.start() + def __init__(self, proxy): + self.proxy_state = proxy + self.waiting_nodes: dict[str, tuple[str, Any, int]] = {} + self.listening_thread = threading.Thread(target=self._node_listener, daemon=True) + self.listening_thread.start() - def _run(self) -> None: + def _node_listener(self) -> None: while True: - args = get_global_args() - for key, (instance_type, server, retries) in list(self.scheduler.get_waiting_nodes().items()): - host, port = server - is_valid = asyncio.run(self.check_instance_status(host, port)) - print(f"Checking instance {key}...") - retries += 1 + for node, (instance_type, server, check_times) in list(self.waiting_nodes.items()): + is_valid = asyncio.run(self.check_instance_status(server.client)) + print(f"Checking instance {node}...") + check_times += 1 if is_valid: - self.scheduler.activate_waiting_instance(ServerRole(instance_type), host, port) - elif retries >= args.max_waiting_retries: - print(f"Instance {key} was not added to the proxy.") - self.scheduler.drop_waiting_instance(key) + if instance_type == InstanceType.PREFILL: + self.proxy_state.add_prefillers([server]) + else: + self.proxy_state.add_decoders([server]) + self.waiting_nodes.pop(node) + elif check_times == global_args.max_waiting_retries: + print(f"Instance {node} was not added to the proxy.") + self.waiting_nodes.pop(node) else: - self.scheduler.mark_waiting_retry(key, retries) + self.waiting_nodes[node] = (instance_type, server, check_times) - self.scheduler.finalize_tainted_instances() - time.sleep(args.waiting_retry_interval) + if self.proxy_state.tainted_prefillers and not self.proxy_state.request_num: + need_waiting = self.proxy_state.remove_prefillers(self.proxy_state.tainted_prefillers) + if not need_waiting: + self.proxy_state.tainted_prefillers.clear() + + if self.proxy_state.tainted_decoders and not self.proxy_state.request_num: + need_waiting = self.proxy_state.remove_decoders(self.proxy_state.tainted_decoders) + if not need_waiting: + self.proxy_state.tainted_decoders.clear() + time.sleep(global_args.waiting_retry_interval) @staticmethod - async def check_instance_status(host: str, port: int) -> bool: + async def check_instance_status(client: httpx.AsyncClient) -> bool: endpoint = "/models" headers = {"Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}"} try: - async with httpx.AsyncClient(timeout=5.0, base_url=build_base_url(host, port)) as client: - response = await client.get(endpoint, headers=headers) - response.raise_for_status() - return True + response = await client.get(endpoint, headers=headers) + response.raise_for_status() + return True except (httpx.RequestError, httpx.HTTPStatusError): return False -def manager_config_path(proxy_port: int) -> Path: - return Path(tempfile.gettempdir()) / f"vllm_lb_proxy_manager_{proxy_port}.json" - - -def write_manager_config(proxy_port: int, host: str, manager_port: int, authkey: bytes) -> None: - manager_config_path(proxy_port).write_text( - json.dumps( - { - "host": host, - "port": manager_port, - "authkey": base64.b64encode(authkey).decode("ascii"), - } - ), - encoding="utf-8", - ) - - -def read_manager_config(proxy_port: int) -> dict[str, Any]: - path = manager_config_path(proxy_port) - if not path.is_file(): - raise RuntimeError( - f"Manager config not found at {path}. " - "Start the proxy from __main__ with --workers > 1 before worker processes connect." - ) - return json.loads(path.read_text(encoding="utf-8")) - - -def cleanup_manager_config(proxy_port: int) -> None: - manager_config_path(proxy_port).unlink(missing_ok=True) - - -def parse_args() -> argparse.Namespace: +def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=8000) parser.add_argument("--host", type=str, default="localhost") @@ -660,19 +524,6 @@ def parse_args() -> argparse.Namespace: default=10, help="Check interval (seconds) for waiting nodes to be started", ) - parser.add_argument( - "--workers", - type=int, - default=1, - help="Number of uvicorn worker processes. Scheduling state is shared across workers.", - ) - parser.add_argument( - "--log-level", - type=str, - default="INFO", - choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], - help="Log level for the proxy server.", - ) args = parser.parse_args() if len(args.prefiller_hosts) != len(args.prefiller_ports): raise ValueError("Number of prefiller hosts must match number of prefiller ports") @@ -683,81 +534,20 @@ def parse_args() -> argparse.Namespace: return args -def get_global_args() -> argparse.Namespace: - global global_args - if global_args is None: - global_args = parse_args() - return global_args - - -def connect_shared_scheduler(proxy_port: int): - manager_cfg = read_manager_config(proxy_port) - manager = SchedulerManager( - address=(manager_cfg["host"], manager_cfg["port"]), - authkey=base64.b64decode(manager_cfg["authkey"]), - ) - manager.connect() - return manager.get_scheduler() # type: ignore[attr-defined] - - -def bootstrap_parent_process(args: argparse.Namespace) -> None: - """Initialize cross-worker shared state in the parent process before uvicorn spawns workers.""" - global shared_scheduler - if args.workers <= 1: - return - - shared_scheduler = SharedProxyScheduler(args.prefiller_instances, args.decoder_instances) - NodeListener(shared_scheduler) - - authkey = os.urandom(16) - manager = SchedulerManager(address=("127.0.0.1", 0), authkey=authkey) - server = manager.get_server() - thread = threading.Thread(target=server.serve_forever, daemon=True) - thread.start() - host, port = cast(tuple[str, int], server.address) - write_manager_config(args.port, host, port, authkey) - - -def _ensure_scheduler(args) -> SharedProxyScheduler: - global shared_scheduler - if shared_scheduler is not None: - return shared_scheduler - shared_scheduler = SharedProxyScheduler(args.prefiller_instances, args.decoder_instances) - NodeListener(shared_scheduler) - return shared_scheduler - - @asynccontextmanager -async def lifespan(_app: FastAPI): - global runtime - args = get_global_args() - if args.workers > 1: - scheduler = connect_shared_scheduler(args.port) - else: - scheduler = _ensure_scheduler(args) - runtime = WorkerRuntime(scheduler) - await runtime.sync_clients() - snapshot = scheduler.get_snapshot() - logger.info( - "Initialized %s prefill clients and %s decode clients in worker %s.", - len(snapshot["prefill_instances"]), - len(snapshot["decode_instances"]), - os.getpid(), - ) +async def lifespan(app: FastAPI): + global proxy_state + proxy_state = ProxyState(global_args.prefiller_instances, global_args.decoder_instances) + print(f"Initialized {len(proxy_state.prefillers)} prefill clients and {len(proxy_state.decoders)} decode clients.") yield - await runtime.close() - runtime = None - - -app = FastAPI(lifespan=lifespan) - - -def create_app(): - setup_logging(get_global_args().log_level) - return app + for p in proxy_state.prefillers: + await p.client.aclose() + for d in proxy_state.decoders: + await d.client.aclose() async def listen_for_disconnect(request: Request) -> None: + """Return if a disconnect message is received""" while True: message = await request.receive() if message["type"] == "http.disconnect": @@ -780,51 +570,46 @@ async def wrapper(*args, **kwargs): return wrapper -def auth_headers(request_id: str) -> dict[str, str]: - return { - "Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}", - "X-Request-Id": request_id, - } - - -def build_prefill_request(req_data: dict) -> dict: - payload = req_data.copy() - payload["kv_transfer_params"] = { - "do_remote_decode": True, - "do_remote_prefill": False, - "remote_engine_id": None, - "remote_block_ids": None, - "remote_host": None, - "remote_port": None, - } - payload["stream"] = False - payload["max_tokens"] = 1 - payload["min_tokens"] = 1 - if "max_completion_tokens" in payload: - payload["max_completion_tokens"] = 1 - payload.pop("stream_options", None) - return payload +app = FastAPI(lifespan=lifespan) async def send_request_to_service( client: httpx.AsyncClient, + prefiller_id: int, endpoint: str, req_data: dict, request_id: str, max_retries: int = 3, base_delay: float = 0.2, ): - req_data = build_prefill_request(req_data) - headers = auth_headers(request_id) + aborted_requests = proxy_state.acquire_aborted_prefiller_requests(prefiller_id) + req_data = req_data.copy() + req_data["kv_transfer_params"] = { + "do_remote_decode": True, + "do_remote_prefill": False, + "remote_engine_id": None, + "remote_block_ids": None, + "remote_host": None, + "remote_port": None, + "aborted_request": list(aborted_requests), + } + req_data["stream"] = False + req_data["max_tokens"] = 1 + req_data["min_tokens"] = 1 + if "max_completion_tokens" in req_data: + req_data["max_completion_tokens"] = 1 + if "stream_options" in req_data: + del req_data["stream_options"] + headers = {"Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}", "X-Request-Id": request_id} last_exc = None for attempt in range(1, max_retries + 1): try: response = await client.post(endpoint, json=req_data, headers=headers) response.raise_for_status() return response - except (httpx.RequestError, httpx.HTTPStatusError) as exc: - logger.warning("Attempt %s failed for %s: %s", attempt, endpoint, exc) - last_exc = exc + except (httpx.RequestError, httpx.HTTPStatusError) as e: + logger.warning("Attempt %s failed for %s: %s", attempt, endpoint, e) + last_exc = e if attempt < max_retries: await asyncio.sleep(base_delay * (2 ** (attempt - 1))) else: @@ -840,7 +625,7 @@ async def stream_service_response_with_retry( max_retries: int = 3, base_delay: float = 0.2, ): - headers = auth_headers(request_id) + headers = {"Authorization": f"Bearer {os.environ.get('OPENAI_API_KEY')}", "X-Request-Id": request_id} for attempt in range(1, max_retries + 1): try: async with client.stream("POST", endpoint, json=req_data, headers=headers) as response: @@ -849,124 +634,86 @@ async def stream_service_response_with_retry( async for chunk in response.aiter_bytes(): first_chunk_sent = True yield chunk - return - except (httpx.RequestError, httpx.HTTPStatusError) as exc: + return # Success, exit after streaming + except (httpx.RequestError, httpx.HTTPStatusError) as e: if attempt < max_retries: - logger.warning("Attempt %s failed for streaming %s: %s", attempt, endpoint, exc) + logger.warning("Attempt %s failed for streaming %s: %s", attempt, endpoint, e) await asyncio.sleep(base_delay * (2 ** (attempt - 1))) else: logger.error("All %s attempts failed for streaming %s.", max_retries, endpoint) - raise exc - except Exception as exc: + raise e + except Exception as e: + # If any chunk has been sent, do not retry, just log and drop if "first_chunk_sent" in locals() and first_chunk_sent: - logger.error("Streaming to client interrupted after response started: %s", exc) + logger.error("Streaming to client interrupted after response started: %s", e) return - if attempt < max_retries: - logger.warning("Attempt %s failed for streaming %s: %s", attempt, endpoint, exc) - await asyncio.sleep(base_delay * (2 ** (attempt - 1))) else: - logger.error("All %s attempts failed for streaming %s.", max_retries, endpoint) - raise exc - - -async def _abort_prefill_selection( - runtime: WorkerRuntime, - prefiller_key: str, - prefiller_score: float, - *, - is_initial_request: bool, -) -> None: - if is_initial_request: - await runtime.schedule("finish_request", prefiller_key, prefiller_score, None, 0.0, release_prefill_kv=True) - else: - await runtime.schedule("release_prefill_kv", prefiller_key, prefiller_score) - - -async def _finish_instance(runtime: WorkerRuntime, info: InstanceInfo, *, release_prefill_kv: bool) -> None: - await runtime.schedule( - "finish_request", - info.prefiller_key, - info.prefiller_score, - info.decoder_key, - info.decoder_score, - release_prefill_kv, + if attempt < max_retries: + logger.warning("Attempt %s failed for streaming %s: %s", attempt, endpoint, e) + await asyncio.sleep(base_delay * (2 ** (attempt - 1))) + else: + logger.error("All %s attempts failed for streaming %s.", max_retries, endpoint) + raise e + + +async def _handle_select_instance(api: str, req_data: Any, request_length: int): + prefiller_score = proxy_state.calculate_prefill_scores(request_length) + logger.debug("Request length: %s, Prefiller score: %s", request_length, prefiller_score) + request_id = await proxy_state.next_req_id() + # Select prefiller + prefiller_idx = proxy_state.select_prefiller(prefiller_score) + prefiller = proxy_state.prefillers[prefiller_idx] + # Send request to prefiller + response = await send_request_to_service( + prefiller.client, + prefiller_idx, + api, + req_data, + request_id, + max_retries=global_args.max_retries, + base_delay=global_args.retry_delay, ) - - -async def assign_instances( - api: str, - req_data: Any, - request_length: int, - *, - is_initial_request: bool, -) -> InstanceInfo: - runtime = get_runtime() - args = get_global_args() - prefiller_score = calculate_prefill_score(request_length) - decoder_score = calculate_decode_score(request_length) - request_id = next_req_id() - pick_prefill = "begin_request" if is_initial_request else "reserve_prefill_kv" - prefiller = await runtime.schedule(pick_prefill, prefiller_score) - prefiller_key = prefiller["key"] - - try: - response = await send_request_to_service( - await runtime.get_client(ServerRole.PREFILL, prefiller_key), - api, - req_data, - request_id, - max_retries=args.max_retries, - base_delay=args.retry_delay, - ) - except Exception: - await _abort_prefill_selection(runtime, prefiller_key, prefiller_score, is_initial_request=is_initial_request) - raise - - kv_transfer_params = response.json().get("kv_transfer_params", {}) + proxy_state.release_prefiller(prefiller_idx, prefiller_score) + response_json = response.json() + kv_transfer_params = response_json.get("kv_transfer_params", {}) if kv_transfer_params: req_data["kv_transfer_params"] = kv_transfer_params - - try: - decoder = await runtime.schedule("pick_decoder", decoder_score) - except Exception: - await _abort_prefill_selection(runtime, prefiller_key, prefiller_score, is_initial_request=is_initial_request) - raise - - prefiller_client = await runtime.get_client(ServerRole.PREFILL, prefiller_key) - decoder_client = await runtime.get_client(ServerRole.DECODE, decoder["key"]) - logger.debug("Using %s %s", prefiller_client.base_url, decoder_client.base_url) + # Select decoder + decoder_score = proxy_state.calculate_decode_scores(request_length) + logger.debug("Decoder score: %f", decoder_score) + # Use the prefiller's kv_transfer_params to select decoder + decoder_idx = proxy_state.select_decoder(decoder_score) + decoder = proxy_state.decoders[decoder_idx] + logger.debug("Using %s %s", prefiller.url, decoder.url) return InstanceInfo( request_id=request_id, - prefiller_key=prefiller_key, + prefiller_idx=prefiller_idx, prefiller_score=prefiller_score, - decoder_key=decoder["key"], + prefiller=prefiller, + decoder=decoder, + decoder_idx=decoder_idx, decoder_score=decoder_score, - decoder_host=decoder["host"], - decoder_port=decoder["port"], ) -async def reassign_instances( - api: str, - req_data: Any, - request_length: int, - previous_instance: InstanceInfo, -) -> InstanceInfo: - runtime = get_runtime() - await runtime.schedule("release_prefill_kv", previous_instance.prefiller_key, previous_instance.prefiller_score) - await runtime.schedule("release_decoder", previous_instance.decoder_key, previous_instance.decoder_score) - return await assign_instances(api, req_data, request_length, is_initial_request=False) +@dataclass +class InstanceInfo: + request_id: str + prefiller_idx: int + prefiller_score: float + prefiller: ServerState + decoder_idx: int + decoder_score: float + decoder: ServerState -async def handle_completions_impl(api: str, request: Request): - runtime = get_runtime() - args = get_global_args() - request_released = False +async def _handle_completions(api: str, request: Request): try: + proxy_state.request_num += 1 req_data = await request.json() req_body = await request.body() request_length = len(req_body) - instance_info = await assign_instances(api, req_data, request_length, is_initial_request=True) + instance_info = await _handle_select_instance(api, req_data, request_length) stream_flag = bool(req_data.get("stream", False)) chat_flag = "messages" in req_data @@ -977,39 +724,37 @@ async def handle_completions_impl(api: str, request: Request): origin_prompt = messages[0].get("content", "") else: origin_prompt = "" + # refer to vLLM sampling_params: max_token default value origin_max_tokens = req_data.get("max_tokens", 16) async def generate_stream(): nonlocal instance_info - nonlocal request_released generated_token = "" released_kv = False retry_count = 0 retry = True completion_tokens = 0 - async def release_prefill_kv_once() -> None: + def release_prefiller_kv_once(): nonlocal released_kv if not released_kv: - await runtime.schedule( - "release_prefill_kv", instance_info.prefiller_key, instance_info.prefiller_score - ) + proxy_state.release_prefiller_kv(instance_info.prefiller_idx, instance_info.prefiller_score) released_kv = True + # Only one await per chunk, minimal logic in loop try: while retry: retry = False - decoder_client = await runtime.get_client(ServerRole.DECODE, instance_info.decoder_key) async for chunk in stream_service_response_with_retry( - decoder_client, + instance_info.decoder.client, api, req_data, request_id=instance_info.request_id, - max_retries=args.max_retries, - base_delay=args.retry_delay, + max_retries=global_args.max_retries, + base_delay=global_args.retry_delay, ): if not released_kv and chunk: - await release_prefill_kv_once() + release_prefiller_kv_once() try: chunk_str = chunk.decode("utf-8").strip() except UnicodeDecodeError: @@ -1023,6 +768,7 @@ async def release_prefill_kv_once() -> None: try: chunk_json = json.loads(chunk_str) except json.JSONDecodeError: + # if chunk is [done], skip it. logger.debug("Skipping chunk: %s", chunk_str) yield chunk continue @@ -1042,7 +788,7 @@ async def release_prefill_kv_once() -> None: completion_tokens = ( (completion_tokens + 1) if stream_flag - else (completion_tokens + usage.get("completion_tokens", 0)) + else (completion_tokens + usage.get("completion_tokens")) ) if stop_reason == "recomputed": retry = True @@ -1053,8 +799,7 @@ async def release_prefill_kv_once() -> None: req_data["prompt"] = origin_prompt + generated_token req_data["max_tokens"] = origin_max_tokens - completion_tokens + retry_count tmp_request_length = len(json.dumps(req_data).encode("utf-8")) - instance_info = await reassign_instances(api, req_data, tmp_request_length, instance_info) - released_kv = False + instance_info = await _handle_select_instance(api, req_data, tmp_request_length) break if retry_count > 0 and not stream_flag: if chat_flag: @@ -1064,151 +809,120 @@ async def release_prefill_kv_once() -> None: chunk = json.dumps(chunk_json).encode("utf-8") yield chunk except asyncio.CancelledError: - logger.warning( - "Streaming from decoder %s:%s was cancelled; releasing request %s resources", - instance_info.decoder_host, - instance_info.decoder_port, - instance_info.request_id, - ) raise - except Exception as exc: + except Exception as e: logger.error( - "Error during streaming from decoder %s:%s: %s while handling request %s; releasing prefiller KV", - instance_info.decoder_host, - instance_info.decoder_port, - exc, + "Error during streaming from decoder %s: %s the aborted request %s " + "will be routing to the target prefiller when new request is ready to dispatch to it", + instance_info.decoder.url, + e, instance_info.request_id, ) + proxy_state.abort_prefiller_request(instance_info.prefiller_idx, instance_info.request_id) + release_prefiller_kv_once() finally: - await _finish_instance(runtime, instance_info, release_prefill_kv=not released_kv) - released_kv = True - request_released = True + # After streaming is done or cancelled, release tokens. + release_prefiller_kv_once() + proxy_state.release_decoder(instance_info.decoder_idx, instance_info.decoder_score) + proxy_state.request_num -= 1 + # Determine the correct media type based on stream flag media_type = "text/event-stream; charset=utf-8" if stream_flag else "application/json" return StreamingResponse(generate_stream(), media_type=media_type) - except Exception: + except Exception as e: import traceback exc_info = sys.exc_info() print(f"Error occurred in disagg prefill proxy server - {api} endpoint") + print(e) print("".join(traceback.format_exception(*exc_info))) - if not request_released and "instance_info" in locals(): - await _finish_instance(runtime, instance_info, release_prefill_kv=True) - request_released = True + proxy_state.request_num -= 1 raise -async def adjust_instances_impl(adjust_mode: str, request: Request): - req_data = await request.json() - instance_type = req_data.get("type", "") - instances = req_data.get("instances", []) - if isinstance(instances, str): - instances = [instances] - parsed_instances = parse_server_addresses(instances) - all_msg = f"{adjust_mode} {instance_type} instances: {[f'{host}:{port}' for host, port in parsed_instances]}." - +async def _handle_adjust_instances(adjust_mode: str, request: Request): try: - role = ServerRole(instance_type) - except ValueError: + req_data = await request.json() + instance_type = req_data.get("type", "") + instances = req_data.get("instances", []) + if isinstance(instances, str): + instances = [instances] + instances = trans_instances(instances) + all_msg = f"{adjust_mode} {instance_type} instances: {[str(server) for server in instances]}." + + if instance_type not in [InstanceType.PREFILL, InstanceType.DECODE]: + return { + "error": f"Instance type {instance_type} is not supported. " + f"Only support '{InstanceType.PREFILL}' and '{InstanceType.DECODE}'." + } + + if adjust_mode == "add": + added_nodes, waiting_nodes = await proxy_state.add_instances(instance_type, instances) + if waiting_nodes: + all_msg = ( + f"{adjust_mode} {instance_type} instances: {added_nodes}. " + f"Instances {waiting_nodes} are waiting to be added." + ) + elif adjust_mode == "remove": + if instance_type == InstanceType.PREFILL: + need_waiting = proxy_state.remove_prefillers(instances) + else: + need_waiting = proxy_state.remove_decoders(instances) + + if need_waiting: + all_msg = f"Instances {instances} are isolated and waiting to be removed." return { - "error": ( - f"Instance type {instance_type!r} is not supported. " - f"Only '{ServerRole.PREFILL.value}' and '{ServerRole.DECODE.value}' are allowed." - ) + "message": all_msg, + "current_prefill_instances": [str(prefiller) for prefiller in proxy_state.prefillers], + "current_decode_instances": [str(decoder) for decoder in proxy_state.decoders], } - - scheduler = get_runtime().scheduler - - if adjust_mode == "add": - waiting_nodes = scheduler.add_instances(role, parsed_instances) - if waiting_nodes: - all_msg = f"Instances {waiting_nodes} are waiting to be added." - elif adjust_mode == "remove": - need_waiting = scheduler.remove_instances(role, parsed_instances) - if need_waiting: - all_msg = ( - f"Instances {[f'{host}:{port}' for host, port in parsed_instances]} " - "are isolated and waiting to be removed." - ) - - snapshot = scheduler.get_snapshot() - return { - "message": all_msg, - "current_prefill_instances": [f"{server['host']}:{server['port']}" for server in snapshot["prefill_instances"]], - "current_decode_instances": [f"{server['host']}:{server['port']}" for server in snapshot["decode_instances"]], - } + except Exception as e: + logger.error("Failed to %s instances: %s", adjust_mode, e) + raise e -def parse_server_addresses(instances: list[str]) -> list[tuple[str, int]]: - return [(host, int(port)) for host, port in (instance.split(":") for instance in instances)] +def trans_instances(instances: list[str]) -> list[ServerState]: + server_list = [] + for instance in instances: + h, p = instance.split(":") + server_list.append(ServerState(h, int(p))) + return server_list @app.post("/v1/completions") @with_cancellation async def handle_completions(request: Request): - return await handle_completions_impl("/completions", request) + return await _handle_completions("/completions", request) @app.post("/v1/chat/completions") @with_cancellation async def handle_chat_completions(request: Request): - return await handle_completions_impl("/chat/completions", request) - - -@app.post("/reset_prefix_cache") -async def reset_prefix_cache(request: Request): - params = dict(request.query_params) - runtime = get_runtime() - await runtime.sync_clients() - snapshot = runtime.scheduler.get_snapshot() - backend_instances = [(ServerRole.PREFILL, server) for server in snapshot["prefill_instances"]] + [ - (ServerRole.DECODE, server) for server in snapshot["decode_instances"] - ] - failures: list[str] = [] - for role, server in backend_instances: - base_url = build_server_url(server["host"], server["port"]) - try: - client = await runtime.get_client(role, server_key(server["host"], server["port"])) - resp = await client.post(f"{base_url}/reset_prefix_cache", params=params) - resp.raise_for_status() - except Exception as e: - logger.error("reset_prefix_cache failed for %s: %s", base_url, e) - failures.append(base_url) - if failures: - return JSONResponse(status_code=500, content={"failed": failures}) - return Response(status_code=200) + return await _handle_completions("/chat/completions", request) @app.get("/healthcheck") async def healthcheck(): - return get_runtime().scheduler.healthcheck() + return { + "status": "ok", + "prefill_instances": len(proxy_state.prefillers), + "decode_instances": len(proxy_state.decoders), + } @app.post("/instances/add") async def handle_add_instances(request: Request): - return await adjust_instances_impl("add", request) + return await _handle_adjust_instances("add", request) @app.post("/instances/remove") async def handle_remove_instances(request: Request): - return await adjust_instances_impl("remove", request) + return await _handle_adjust_instances("remove", request) if __name__ == "__main__": + global global_args global_args = parse_args() - setup_logging(global_args.log_level) - bootstrap_parent_process(global_args) import uvicorn - module_name = Path(__file__).stem - try: - uvicorn.run( - f"{module_name}:create_app", - host=global_args.host, - port=global_args.port, - workers=global_args.workers, - factory=True, - app_dir=str(Path(__file__).resolve().parent), - ) - finally: - cleanup_manager_config(global_args.port) + uvicorn.run(app, host=global_args.host, port=global_args.port) diff --git a/examples/rl/rlhf_http_hccl.py b/examples/rl/rlhf_http_hccl.py deleted file mode 100644 index 94740fc3c..000000000 --- a/examples/rl/rlhf_http_hccl.py +++ /dev/null @@ -1,290 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project -""" -Demonstrates reinforcement learning from human feedback (RLHF) using vLLM -via HTTP API, with native weight syncing APIs. - -Unlike rlhf.py which creates a vLLM instance programmatically, this script -assumes you have already started a vLLM server using `vllm serve`. It uses: -- OpenAI-compatible API for inference requests -- HTTP endpoints for weight transfer control plane -- HCCL for actual weight data transfer - -Prerequisites: - Start a vLLM server with weight transfer enabled: - - $ VLLM_SERVER_DEV_MODE=1 vllm serve qwen/qwen3-0.6b \ - --enforce-eager \ - --weight-transfer-config '{"backend": "nccl"}' \ - --load-format dummy - - Then run this script: - - $ python rlhf_http_hccl.py - -The example performs the following steps: - -* Load the training model on NPU 0. -* Generate text using the vLLM server via OpenAI-compatible API. The output - is expected to be nonsense because the server is initialized with dummy weights. -* Initialize weight transfer via HTTP endpoint. -* Broadcast the real weights from the training model to the vLLM server - using HCCL. -* Generate text again to show normal output after the weight update. -""" - -import requests -import torch -from openai import OpenAI -from transformers import AutoModelForCausalLM -from vllm.utils.network_utils import get_ip, get_open_port - -from vllm_ascend.distributed.weight_transfer.hccl_engine import ( - HCCLTrainerSendWeightsArgs, - HCCLWeightTransferEngine, -) - -BASE_URL = "http://localhost:8000" -MODEL_NAME = "qwen/qwen3-0.6b" - - -def generate_completions(client: OpenAI, model: str, prompts: list[str]) -> list[str]: - """Generate completions using the OpenAI-compatible API.""" - results = [] - for prompt in prompts: - response = client.completions.create( - model=model, - prompt=prompt, - max_tokens=32, - temperature=0, - ) - results.append(response.choices[0].text) - return results - - -def init_weight_transfer_engine( - base_url: str, - master_address: str, - master_port: int, - rank_offset: int, - world_size: int, -) -> None: - """Initialize weight transfer via HTTP endpoint.""" - url = f"{base_url}/init_weight_transfer_engine" - payload = { - "init_info": dict( - master_address=master_address, - master_port=master_port, - rank_offset=rank_offset, - world_size=world_size, - ) - } - response = requests.post(url, json=payload, timeout=60) - response.raise_for_status() - - -def update_weights( - base_url: str, - names: list[str], - dtype_names: list[str], - shapes: list[list[int]], - packed: bool = False, - packed_buffer_size_bytes: int | None = None, -) -> None: - """Update weights via HTTP endpoint.""" - url = f"{base_url}/update_weights" - payload = { - "update_info": dict( - names=names, - dtype_names=dtype_names, - shapes=shapes, - packed=packed, - ) - } - if packed and packed_buffer_size_bytes is not None: - payload["update_info"]["packed_buffer_size_bytes"] = packed_buffer_size_bytes - response = requests.post(url, json=payload, timeout=300) - response.raise_for_status() - - -def start_weight_update(base_url: str, is_checkpoint_format: bool = True) -> None: - """Start weight update via HTTP endpoint. - - Prepares the model for layerwise reload on the vLLM server side. - Must be called before update_weights. - """ - url = f"{base_url}/start_weight_update" - payload = {"is_checkpoint_format": is_checkpoint_format} - response = requests.post(url, json=payload, timeout=60) - response.raise_for_status() - - -def finish_weight_update(base_url: str) -> None: - """Finish weight update via HTTP endpoint. - - Finalizes layerwise reload on the vLLM server side. - Must be called after all update_weights calls are complete. - """ - url = f"{base_url}/finish_weight_update" - response = requests.post(url, timeout=60) - response.raise_for_status() - - -def pause_generation(base_url: str) -> None: - """Pause generation via HTTP endpoint.""" - url = f"{base_url}/pause" - response = requests.post(url, timeout=60) - response.raise_for_status() - - -def resume_generation(base_url: str) -> None: - """Resume generation via HTTP endpoint.""" - url = f"{base_url}/resume" - response = requests.post(url, timeout=60) - response.raise_for_status() - - -def get_world_size(base_url: str) -> int: - """Get world size from the vLLM server.""" - url = f"{base_url}/get_world_size" - response = requests.get(url, timeout=10) - response.raise_for_status() - return response.json()["world_size"] - - -def main(): - # Get the inference world size from the vLLM server - inference_world_size = get_world_size(BASE_URL) - world_size = inference_world_size + 1 # +1 for the trainer - device = f"npu:{inference_world_size}" - torch.accelerator.set_device_index(device) - - # Load the training model - print(f"Loading training model: {MODEL_NAME}") - train_model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, dtype=torch.bfloat16) - train_model.to(device) - - # Create OpenAI client pointing to the vLLM server - client = OpenAI( - base_url=f"{BASE_URL}/v1", - api_key="EMPTY", # vLLM doesn't require an API key by default - ) - - # Test prompts - prompts = [ - "Hello, my name is", - "The president of the United States is", - "The capital of France is", - "The future of AI is", - ] - - # Generate text before weight update. The output is expected to be nonsense - # because the server is initialized with dummy weights. - print("-" * 50) - print("Generating text BEFORE weight update (expect nonsense):") - print("-" * 50) - outputs = generate_completions(client, MODEL_NAME, prompts) - for prompt, generated_text in zip(prompts, outputs): - print(f"Prompt: {prompt!r}\nGenerated text: {generated_text!r}") - print("-" * 50) - - # Set up the communication channel between the training process and the - # vLLM server. The trainer is rank 0, vLLM worker(s) start at rank_offset. - master_address = get_ip() - master_port = get_open_port() - rank_offset = 1 - - print(f"Initializing weight transfer: master={master_address}:{master_port}") - - # Initialize weight transfer on vLLM server (this is async, server will - # wait for HCCL connection) - import threading - - init_thread = threading.Thread( - target=init_weight_transfer_engine, - args=(BASE_URL, master_address, master_port, rank_offset, world_size), - ) - init_thread.start() - - # Initialize HCCL process group on trainer side - model_update_group = HCCLWeightTransferEngine.trainer_init( - dict( - master_address=master_address, - master_port=master_port, - world_size=world_size, - ), - ) - - # Wait for init_weight_transfer_engine to complete - init_thread.join() - - # Pause generation before weight sync - pause_generation(BASE_URL) - - # Start weight update (prepares layerwise reload on the vLLM server) - start_weight_update(BASE_URL) - - # Collect weight metadata for the update request. - # Also track the largest tensor to auto-size the packed buffer. - names = [] - dtype_names = [] - shapes = [] - max_tensor_bytes = 0 - for name, p in train_model.named_parameters(): - names.append(name) - dtype_names.append(str(p.dtype).split(".")[-1]) - shapes.append(list(p.shape)) - tensor_bytes = p.numel() * p.element_size() - if tensor_bytes > max_tensor_bytes: - max_tensor_bytes = tensor_bytes - - # Size the packed buffer to fit the largest tensor with 128 MB headroom, - # but keep the default 1 GB when the largest tensor is smaller than that. - packed_buffer_size_bytes = max(max_tensor_bytes + 128 * 2**20, 2**30) - print( - f"Largest tensor: {max_tensor_bytes / 2**30:.2f} GiB, packed buffer: {packed_buffer_size_bytes / 2**30:.2f} GiB" - ) - - # Start the update_weights call in a separate thread since it will block - # waiting for HCCL broadcasts - # packed=True enables efficient batched tensor broadcasting - update_thread = threading.Thread( - target=update_weights, - args=(BASE_URL, names, dtype_names, shapes, True, packed_buffer_size_bytes), - ) - update_thread.start() - - # Broadcast all weights from trainer to vLLM workers - print("Broadcasting weights via HCCL...") - trainer_args = HCCLTrainerSendWeightsArgs( - group=model_update_group, - packed=True, - packed_buffer_size_bytes=packed_buffer_size_bytes, - ) - HCCLWeightTransferEngine.trainer_send_weights( - iterator=train_model.named_parameters(), - trainer_args=trainer_args, - ) - - # Wait for update_weights to complete - update_thread.join() - - # Finish weight update (finalizes layerwise reload on the vLLM server) - finish_weight_update(BASE_URL) - - # Resume generation after weight sync - resume_generation(BASE_URL) - - # Generate text after weight update. The output is expected to be normal - # because the real weights are now loaded. - print("-" * 50) - print("Generating text AFTER weight update:") - print("-" * 50) - outputs_updated = generate_completions(client, MODEL_NAME, prompts) - for prompt, generated_text in zip(prompts, outputs_updated): - print(f"Prompt: {prompt!r}\nGenerated text: {generated_text!r}") - print("-" * 50) - - -if __name__ == "__main__": - main() diff --git a/requirements-dev.txt b/requirements-dev.txt index 657d6251d..26e70ce68 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ openai pytest >= 6.0,<9.0.0 pytest-asyncio pytest-mock -lm-eval==0.4.12 +lm-eval==0.4.11 types-jsonschema xgrammar zmq @@ -21,6 +21,5 @@ scipy soundfile pytest_mock mindstudio-probe>=8.3.0 -xlite==0.1.0rc11.dev210 +xlite==0.1.0rc10.dev210 uc-manager -ninja diff --git a/tests/e2e/conftest.py b/tests/e2e/conftest.py index 98feca71f..68b2cdaa5 100644 --- a/tests/e2e/conftest.py +++ b/tests/e2e/conftest.py @@ -426,14 +426,6 @@ def _terminate_process_tree(self, proc: subprocess.Popen) -> None: return children = parent.children(recursive=True) - - try: - parent.terminate() - parent.wait(timeout=60) - except (psutil.NoSuchProcess, psutil.TimeoutExpired): - with contextlib.suppress(psutil.NoSuchProcess): - parent.kill() - for child in children: with contextlib.suppress(psutil.NoSuchProcess): child.terminate() @@ -444,6 +436,13 @@ def _terminate_process_tree(self, proc: subprocess.Popen) -> None: with contextlib.suppress(psutil.NoSuchProcess): child.kill() + try: + parent.terminate() + parent.wait(timeout=10) + except (psutil.NoSuchProcess, psutil.TimeoutExpired): + with contextlib.suppress(psutil.NoSuchProcess): + parent.kill() + def url_for(self, *parts: str) -> str: return self.url_root + "/" + "/".join(parts) diff --git a/tests/e2e/coverage.md b/tests/e2e/coverage.md index 7c900f8db..2f3231437 100644 --- a/tests/e2e/coverage.md +++ b/tests/e2e/coverage.md @@ -2,426 +2,424 @@ The coverage of e2e is as follows: ## 1-Card Tests -| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| _310p/test_classification_310p.py | test_qwen_pooling_classify_correctness | Howeee/Qwen2.5-1.5B-apeach | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | -| _310p/test_dense_model_310p.py | test_qwen3_5_dense_tp1_fp16 | Qwen/Qwen3.5-4B | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| _310p/test_dense_model_310p.py | test_qwen3_5_dense_tp1_fp16_aclgraph | Qwen/Qwen3.5-4B | ✅ | | | | | | ✅ | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| _310p/test_dense_model_310p.py | test_qwen3_dense_tp1_fp16 | Qwen/Qwen3-8B | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| _310p/test_dense_model_310p.py | test_qwen3_dense_tp1_fp16_aclgraph | Qwen/Qwen3-8B | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| _310p/test_dense_model_310p.py | test_qwen3_dense_tp1_w8a8 | vllm-ascend/Qwen3-8B-W8A8 | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| _310p/test_embedding_310p.py | test_bge_m3_correctness | BAAI/bge-m3 | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | -| _310p/test_embedding_310p.py | test_embed_models_correctness | Qwen/Qwen3-Embedding-0.6B
intfloat/multilingual-e5-small | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | -| _310p/test_scoring_310p.py | test_cross_encoder_score_1_to_1 | BAAI/bge-reranker-v2-m3 | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| _310p/test_scoring_310p.py | test_cross_encoder_score_1_to_N | BAAI/bge-reranker-v2-m3 | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| _310p/test_scoring_310p.py | test_cross_encoder_score_N_to_N | BAAI/bge-reranker-v2-m3 | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| _310p/test_vl_model_310p.py | test_qwen3_vl_8b_tp1_fp16 | Qwen/Qwen3-VL-8B-Instruct | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| aclgraph/test_aclgraph_accuracy.py | test_default_full_and_piecewise_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| aclgraph/test_aclgraph_accuracy.py | test_full_decode_only_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| aclgraph/test_aclgraph_accuracy.py | test_full_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| aclgraph/test_aclgraph_accuracy.py | test_npugraph_ex_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| aclgraph/test_aclgraph_accuracy.py | test_npugraph_ex_with_static_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_logprobs_bitwise_batch_invariance_bs1_vs_bsN | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | -| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_logprobs_without_batch_invariance_should_fail | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | -| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_simple_generation | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | -| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_v1_generation_is_deterministic_across_batch_sizes_with_needle | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | | -| aclgraph/test_aclgraph_mem.py | test_aclgraph_mem_use | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| compile/test_graphex_norm_quant_fusion.py | test_rmsnorm_quant_fusion | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| compile/test_graphex_qknorm_rope_fusion.py | test_rmsnorm_quant_fusion | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| compile/test_norm_quant_fusion.py | test_rmsnorm_quant_fusion | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| lora/test_ilama_lora.py | test_ilama_lora | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| lora/test_llama32_lora.py | test_llama_lora | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| lora/test_lora_with_spec_decode.py | test_batch_inference_correctness | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| lora/test_qwen35_densemodel_lora.py | test_qwen35_text_lora | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| lora/test_qwen3_multi_loras.py | test_multi_loras_with_tp_sync | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | -| lora/test_qwen3_reranker_lora.py | test_reranker_models_lora | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | -| model_runner_v2/test_basic.py | test_egale_spec_decoding | Qwen/Qwen3-0.6B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | -| model_runner_v2/test_basic.py | test_qwen3_dense_eager_mode | Qwen/Qwen3-0.6B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | ✅ | | -| model_runner_v2/test_basic.py | test_qwen3_dense_graph_mode | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | -| pooling/test_classification.py | test_qwen_pooling_classify_correctness | Howeee/Qwen2.5-1.5B-apeach | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | -| pooling/test_embedding.py | test_bge_m3_correctness | BAAI/bge-m3 | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | -| pooling/test_embedding.py | test_causal_embed_models_using_prefix_caching_correctness | Qwen/Qwen3-Embedding-0.6B | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | ✅ | | -| pooling/test_embedding.py | test_embed_models_correctness | Qwen/Qwen3-Embedding-0.6B
intfloat/multilingual-e5-small | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | -| pooling/test_scoring.py | test_cross_encoder_score_1_to_1 | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| pooling/test_scoring.py | test_cross_encoder_score_1_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| pooling/test_scoring.py | test_cross_encoder_score_N_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| pooling/test_scoring.py | test_embedding_score_1_to_1 | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| pooling/test_scoring.py | test_embedding_score_1_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| pooling/test_scoring.py | test_embedding_score_N_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | -| spec_decode/test_dflash.py | test_dflash_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | | -| spec_decode/test_draft_parallel.py | test_parallel_drafting_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | | -| spec_decode/test_eagle.py | test_qwen3_vl_eagle | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| spec_decode/test_eagle.py | test_qwen_eagle3_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | -| spec_decode/test_extract_hidden_states.py | test_extract_hidden_states_aclgraph_mode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | | -| spec_decode/test_extract_hidden_states.py | test_extract_hidden_states_eager_mode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | | -| spec_decode/test_mtp_eagle_correctness.py | test_deepseek_mtp | wemaster/deepseek_mtp_main_random_bf16 | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | -| spec_decode/test_ngram.py | test_ngram | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | -| spec_decode/test_ngram_npu.py | test_ngram_npu_async_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | -| spec_decode/test_suffix.py | test_suffix_acceptance | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | -| test_attention_fa3.py | test_fa3_vs_fia_logprobs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | -| test_attention_fa3.py | test_fa3_vs_fia_mixed_lengths | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | ✅ | -| test_attention_fa3.py | test_fa3_vs_fia_single_prompt | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | -| test_attention_fa3.py | test_fa3_vs_fia_with_chunkprefill | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | -| test_batch_invariant.py | test_logprobs_bitwise_batch_invariance_bs1_vs_bsN | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | -| test_batch_invariant.py | test_logprobs_without_batch_invariance_should_fail | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | -| test_batch_invariant.py | test_simple_generation | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | -| test_batch_invariant.py | test_v1_generation_is_deterministic_across_batch_sizes_with_needle | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | | -| test_camem.py | test_end_to_end | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | -| test_completion_with_prompt_embeds.py | test_mixed_prompt_embeds_and_text | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | -| test_cpu_offloading.py | test_cpu_offloading | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| test_guided_decoding.py | test_guided_json_completion | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_guided_decoding.py | test_guided_regex | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_minicpm.py | test_minicpm | OpenBMB/MiniCPM4-0.5B
openbmb/MiniCPM-2B-sft-bf16 | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_multi_instance.py | test_two_instances_on_single_card | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_multistream_overlap_shared_expert.py | test_models_with_multistream_overlap_shared_expert | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_qwen3_0_6b.py | test_dense_default_full_and_piecewise_graph | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_qwen3_5_0_8b.py | test_mamba_ssm_multimodal_reasoning_mtp_full_decode_only | Qwen/Qwen3.5-0.8B | | | | | | | ✅ | | | | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_qwen3_8b_w8a8.py | test_dense_w8a8_eagle3_full_graph | RedHatAI/Qwen3-8B-speculator.eagle3
vllm-ascend/Qwen3-8B-W8A8 | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | -| test_qwen3_embedding_0_6b.py | test_embedding_full_decode_only | Qwen/Qwen3-Embedding-0.6B | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | -| test_sampler.py | test_qwen3_exponential_overlap | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_sampler.py | test_qwen3_prompt_logprobs | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | -| test_sampler.py | test_qwen3_topk | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_vlm.py | test_multimodal_audio | Qwen/Qwen2-Audio-7B-Instruct | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_vlm.py | test_multimodal_vl | openai-mirror/whisper-large-v3-turbo | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_vlm.py | test_multimodal_vl_language_model_only | Qwen/Qwen3-VL-8B-Instruct | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_vlm.py | test_whisper | openai-mirror/whisper-large-v3-turbo | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_xlite.py | test_models_with_xlite_decode_only | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| test_xlite.py | test_models_with_xlite_full_mode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | DeepSeek V4 patch | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| _310p/test_classification_310p.py | test_qwen_pooling_classify_correctness | Howeee/Qwen2.5-1.5B-apeach | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | ✅ | | +| _310p/test_dense_model_310p.py | test_qwen3_5_dense_tp1_fp16 | Qwen/Qwen3.5-4B | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| _310p/test_dense_model_310p.py | test_qwen3_5_dense_tp1_fp16_aclgraph | Qwen/Qwen3.5-4B | ✅ | | | | | | ✅ | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| _310p/test_dense_model_310p.py | test_qwen3_dense_tp1_fp16 | Qwen/Qwen3-8B | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| _310p/test_dense_model_310p.py | test_qwen3_dense_tp1_fp16_aclgraph | Qwen/Qwen3-8B | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| _310p/test_dense_model_310p.py | test_qwen3_dense_tp1_w8a8 | vllm-ascend/Qwen3-8B-W8A8 | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| _310p/test_embedding_310p.py | test_bge_m3_correctness | BAAI/bge-m3 | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | +| _310p/test_embedding_310p.py | test_embed_models_correctness | Qwen/Qwen3-Embedding-0.6B
intfloat/multilingual-e5-small | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | +| _310p/test_scoring_310p.py | test_cross_encoder_score_1_to_1 | BAAI/bge-reranker-v2-m3 | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| _310p/test_scoring_310p.py | test_cross_encoder_score_1_to_N | BAAI/bge-reranker-v2-m3 | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| _310p/test_scoring_310p.py | test_cross_encoder_score_N_to_N | BAAI/bge-reranker-v2-m3 | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| _310p/test_vl_model_310p.py | test_qwen3_vl_8b_tp1_fp16 | Qwen/Qwen3-VL-8B-Instruct | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| aclgraph/test_aclgraph_accuracy.py | test_default_full_and_piecewise_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| aclgraph/test_aclgraph_accuracy.py | test_full_decode_only_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| aclgraph/test_aclgraph_accuracy.py | test_full_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| aclgraph/test_aclgraph_accuracy.py | test_npugraph_ex_res_consistency | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| aclgraph/test_aclgraph_accuracy.py | test_npugraph_ex_with_static_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_logprobs_bitwise_batch_invariance_bs1_vs_bsN | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | ✅ | | +| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_logprobs_without_batch_invariance_should_fail | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | ✅ | | +| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_simple_generation | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | +| aclgraph/test_aclgraph_batch_invariant.py | test_aclgraph_v1_generation_is_deterministic_across_batch_sizes_with_needle | - | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | ✅ | | +| aclgraph/test_aclgraph_mem.py | test_aclgraph_mem_use | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| compile/test_graphex_norm_quant_fusion.py | test_rmsnorm_quant_fusion | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| compile/test_graphex_qknorm_rope_fusion.py | test_rmsnorm_quant_fusion | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| compile/test_norm_quant_fusion.py | test_rmsnorm_quant_fusion | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| lora/test_ilama_lora.py | test_ilama_lora | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| lora/test_llama32_lora.py | test_llama_lora | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| lora/test_lora_with_spec_decode.py | test_batch_inference_correctness | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| lora/test_qwen35_densemodel_lora.py | test_qwen35_text_lora | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| lora/test_qwen3_multi_loras.py | test_multi_loras_with_tp_sync | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| lora/test_qwen3_reranker_lora.py | test_reranker_models_lora | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | +| model_runner_v2/test_basic.py | test_egale_spec_decoding | Qwen/Qwen3-0.6B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | +| model_runner_v2/test_basic.py | test_qwen3_dense_eager_mode | Qwen/Qwen3-0.6B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | ✅ | | +| model_runner_v2/test_basic.py | test_qwen3_dense_graph_mode | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | +| pooling/test_classification.py | test_qwen_pooling_classify_correctness | Howeee/Qwen2.5-1.5B-apeach | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | ✅ | | +| pooling/test_embedding.py | test_bge_m3_correctness | BAAI/bge-m3 | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | +| pooling/test_embedding.py | test_causal_embed_models_using_prefix_caching_correctness | Qwen/Qwen3-Embedding-0.6B | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | | | | | ✅ | | +| pooling/test_embedding.py | test_embed_models_correctness | Qwen/Qwen3-Embedding-0.6B
intfloat/multilingual-e5-small | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | +| pooling/test_scoring.py | test_cross_encoder_score_1_to_1 | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| pooling/test_scoring.py | test_cross_encoder_score_1_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| pooling/test_scoring.py | test_cross_encoder_score_N_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| pooling/test_scoring.py | test_embedding_score_1_to_1 | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| pooling/test_scoring.py | test_embedding_score_1_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| pooling/test_scoring.py | test_embedding_score_N_to_N | BAAI/bge-reranker-v2-m3
dengcao/ms-marco-MiniLM-L6-v2
sentence-transformers/all-MiniLM-L12-v2 | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | +| spec_decode/test_dflash.py | test_dflash_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | ✅ | | +| spec_decode/test_draft_parallel.py | test_parallel_drafting_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | ✅ | | +| spec_decode/test_eagle.py | test_qwen3_vl_eagle | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| spec_decode/test_eagle.py | test_qwen_eagle3_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | +| spec_decode/test_extract_hidden_states.py | test_extract_hidden_states_aclgraph_mode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | +| spec_decode/test_extract_hidden_states.py | test_extract_hidden_states_eager_mode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | +| spec_decode/test_mtp_eagle_correctness.py | test_deepseek_mtp | wemaster/deepseek_mtp_main_random_bf16 | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | +| spec_decode/test_ngram.py | test_ngram | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | +| spec_decode/test_ngram_npu.py | test_ngram_npu_async_acceptance | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | +| spec_decode/test_suffix.py | test_suffix_acceptance | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_attention_fa3.py | test_fa3_vs_fia_logprobs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | +| test_attention_fa3.py | test_fa3_vs_fia_mixed_lengths | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | ✅ | +| test_attention_fa3.py | test_fa3_vs_fia_single_prompt | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | +| test_attention_fa3.py | test_fa3_vs_fia_with_chunkprefill | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | +| test_batch_invariant.py | test_logprobs_bitwise_batch_invariance_bs1_vs_bsN | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | ✅ | | +| test_batch_invariant.py | test_logprobs_without_batch_invariance_should_fail | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | ✅ | | +| test_batch_invariant.py | test_simple_generation | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | +| test_batch_invariant.py | test_v1_generation_is_deterministic_across_batch_sizes_with_needle | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | ✅ | | +| test_camem.py | test_end_to_end | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | +| test_completion_with_prompt_embeds.py | test_mixed_prompt_embeds_and_text | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | +| test_cpu_offloading.py | test_cpu_offloading | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| test_guided_decoding.py | test_guided_json_completion | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_guided_decoding.py | test_guided_regex | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_minicpm.py | test_minicpm | OpenBMB/MiniCPM4-0.5B
openbmb/MiniCPM-2B-sft-bf16 | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_multi_instance.py | test_two_instances_on_single_card | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_multistream_overlap_shared_expert.py | test_models_with_multistream_overlap_shared_expert | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_qwen3_0_6b.py | test_dense_default_full_and_piecewise_graph | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_qwen3_5_0_8b.py | test_mamba_ssm_multimodal_reasoning_mtp_full_decode_only | Qwen/Qwen3.5-0.8B | | | | | | | ✅ | | | | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_qwen3_8b_w8a8.py | test_dense_w8a8_eagle3_full_graph | RedHatAI/Qwen3-8B-speculator.eagle3
vllm-ascend/Qwen3-8B-W8A8 | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | +| test_qwen3_embedding_0_6b.py | test_embedding_full_decode_only | Qwen/Qwen3-Embedding-0.6B | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | +| test_sampler.py | test_qwen3_exponential_overlap | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_sampler.py | test_qwen3_prompt_logprobs | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | +| test_sampler.py | test_qwen3_topk | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_vlm.py | test_multimodal_audio | Qwen/Qwen2-Audio-7B-Instruct | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_vlm.py | test_multimodal_vl | openai-mirror/whisper-large-v3-turbo | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_vlm.py | test_multimodal_vl_language_model_only | Qwen/Qwen3-VL-8B-Instruct | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_vlm.py | test_whisper | openai-mirror/whisper-large-v3-turbo | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_xlite.py | test_models_with_xlite_decode_only | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| test_xlite.py | test_models_with_xlite_full_mode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ## 2-Card Tests -| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| aclgraph/test_aclgraph_capture_replay.py | test_models_aclgraph_capture_replay_metrics_dp2 | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| aclgraph/test_full_graph_mode.py | test_qwen3_moe_full_decode_only_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| aclgraph/test_full_graph_mode.py | test_qwen3_moe_full_graph_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| lora/test_ilama_lora_tp2.py | test_ilama_lora_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| lora/test_llama32_lora_tp2.py | test_llama_lora_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| spec_decode/test_spec_decode.py | test_eagle3_sp_acceptance | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | ✅ | | | ✅ | | -| spec_decode/test_spec_decode.py | test_p_eagle_acceptance | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | ✅ | | -| spec_decode/test_spec_decode.py | test_qwen3_eagle3_pcp2_tp1 | - | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | -| test_data_parallel.py | test_qwen3_inference_dp2 | Qwen/Qwen3-30B-A3B
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_deepseek_multistream_moe.py | test_deepseek_multistream_moe_tp2 | vllm-ascend/DeepSeek-V3-Pruning | | | ✅ | | | | | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_external_launcher.py | test_qwen3_external_launcher | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_external_launcher.py | test_qwen3_external_launcher_with_matmul_allreduce | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | -| test_external_launcher.py | test_qwen3_external_launcher_with_sleepmode | Qwen/Qwen3-8B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_external_launcher.py | test_qwen3_external_launcher_with_sleepmode_level2 | Qwen/Qwen3-8B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_external_launcher.py | test_qwen3_moe_external_launcher_ep_tp2 | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_flashcomm_distributed.py | test_deepseek_v2_lite_fc1_tp2 | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_flashcomm_distributed.py | test_qwen3_dense_fc1_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_flashcomm_distributed.py | test_qwen3_dense_prefetch_mlp_weight_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_flashcomm_distributed.py | test_qwen3_moe_fc2_oshard_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | | | | | -| test_gpt_oss_distributed.py | test_gpt_oss_distributed_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_moe_routing_replay.py | test_qwen3_moe_routing_replay | Qwen/Qwen3-30B-A3B
Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_offline_weight_load.py | test_qwen3_offline_load_and_sleepmode_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_prefix_caching.py | test_models_prefix_cache_tp2 | Qwen/Qwen3-8B
deepseek-ai/DeepSeek-V2-Lite-Chat | | ✅ | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | -| test_qwen3_30b_a3b.py | test_moe_tp_ep_eplb_full_decode_only | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_qwen3_6_27b_fia.py | test_qwen3_6_27b_multimodel_fia_eager | Qwen/Qwen3.6-27B/ | | ✅ | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_qwen3_6_27b_fia.py | test_qwen3_6_27b_multimodel_fia_acl_graph | Qwen/Qwen3.6-27B/ | | ✅ | | | | | | ✅ | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_qwen3_vl_30b_a3b_instruct.py | test_multimodal_reasoning_pp_full_decode_only | Qwen/Qwen3-VL-30B-A3B-Instruct | | | ✅ | | | | | ✅ | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_sequence_parallelism_moe.py | test_sequence_parallelism_moe_patterns | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_shared_expert_dp.py | test_deepseek_v2_lite_enable_shared_expert_dp_tp2 | deepseek-ai/DeepSeek-V2-Lite | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_sp_pass.py | test_qwen3_vl_sp_tp2 | Qwen/Qwen3-VL-2B-Instruct | | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | DeepSeek V4 patch | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| aclgraph/test_aclgraph_capture_replay.py | test_models_aclgraph_capture_replay_metrics_dp2 | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| aclgraph/test_full_graph_mode.py | test_qwen3_moe_full_decode_only_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| aclgraph/test_full_graph_mode.py | test_qwen3_moe_full_graph_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| lora/test_ilama_lora_tp2.py | test_ilama_lora_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| lora/test_llama32_lora_tp2.py | test_llama_lora_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| spec_decode/test_spec_decode.py | test_eagle3_sp_acceptance | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | ✅ | | +| spec_decode/test_spec_decode.py | test_p_eagle_acceptance | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | ✅ | | +| spec_decode/test_spec_decode.py | test_qwen3_eagle3_pcp2_tp1 | - | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | +| test_data_parallel.py | test_qwen3_inference_dp2 | Qwen/Qwen3-30B-A3B
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_deepseek_multistream_moe.py | test_deepseek_multistream_moe_tp2 | vllm-ascend/DeepSeek-V3-Pruning | | | ✅ | | | | | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_external_launcher.py | test_qwen3_external_launcher | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_external_launcher.py | test_qwen3_external_launcher_with_matmul_allreduce | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | +| test_external_launcher.py | test_qwen3_external_launcher_with_sleepmode | Qwen/Qwen3-8B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_external_launcher.py | test_qwen3_external_launcher_with_sleepmode_level2 | Qwen/Qwen3-8B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_external_launcher.py | test_qwen3_moe_external_launcher_ep_tp2 | Qwen/Qwen3-0.6B | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_flashcomm_distributed.py | test_deepseek_v2_lite_fc1_tp2 | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_flashcomm_distributed.py | test_qwen3_dense_fc1_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_flashcomm_distributed.py | test_qwen3_dense_prefetch_mlp_weight_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_flashcomm_distributed.py | test_qwen3_moe_fc2_oshard_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | +| test_gpt_oss_distributed.py | test_gpt_oss_distributed_tp2 | - | | | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_moe_routing_replay.py | test_qwen3_moe_routing_replay | Qwen/Qwen3-30B-A3B
Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_offline_weight_load.py | test_qwen3_offline_load_and_sleepmode_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_prefix_caching.py | test_models_prefix_cache_tp2 | Qwen/Qwen3-8B
deepseek-ai/DeepSeek-V2-Lite-Chat | | ✅ | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | +| test_qwen3_30b_a3b.py | test_moe_tp_ep_eplb_full_decode_only | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_qwen3_vl_30b_a3b_instruct.py | test_multimodal_reasoning_pp_full_decode_only | Qwen/Qwen3-VL-30B-A3B-Instruct | | | ✅ | | | | | ✅ | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_sequence_parallelism_moe.py | test_sequence_parallelism_moe_patterns | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_shared_expert_dp.py | test_deepseek_v2_lite_enable_shared_expert_dp_tp2 | deepseek-ai/DeepSeek-V2-Lite | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_sp_pass.py | test_qwen3_vl_sp_tp2 | Qwen/Qwen3-VL-2B-Instruct | | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ## 4-Card Tests -| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| _310p/test_dense_model_310p.py | test_qwen3_dense_tp2_fp16 | Qwen/Qwen3-8B | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| _310p/test_dense_model_310p.py | test_qwen3_dense_tp4_w8a8 | vllm-ascend/Qwen3-32B-W8A8 | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| _310p/test_moe_model_310p.py | test_qwen3_5_moe_tp4_fp16 | Qwen/Qwen3.5-35B-A3B | ✅ | | ✅ | | | | ✅ | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| _310p/test_moe_model_310p.py | test_qwen3_moe_tp2_w8a8 | vllm-ascend/Qwen3-30B-A3B-W8A8 | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| _310p/test_moe_model_310p.py | test_qwen3_moe_tp4_fp16 | Qwen/Qwen3-30B-A3B | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| _310p/test_vl_model_310p.py | test_qwen3_vl_8b_tp2_fp16 | Qwen/Qwen3-VL-8B-Instruct | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| long_sequence/test_accuracy.py | test_accuracy_dcp_only_eager | Qwen/Qwen3-8B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_accuracy.py | test_accuracy_dcp_only_graph | Qwen/Qwen3-8B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_accuracy.py | test_accuracy_pcp_only | Qwen/Qwen3-8B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_accuracy.py | test_models_long_sequence_cp_kv_interleave_size_output_between_tp_and_cp | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| long_sequence/test_accuracy.py | test_models_long_sequence_output_between_tp_and_cp | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_dcp_basic | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_dcp_full_graph | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_dcp_piece_wise | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_deepseek_v4_w4a8_dsa_cp_basic_greedy | gdydems/DeepSeek-V4-Flash-w4a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | ✅ | | ✅ | | ✅ | | -| long_sequence/test_basic.py | test_models_pcp_dcp_basic | Qwen/Qwen3-Next-80B-A3B-Instruct
deepseek-ai/DeepSeek-V2-Lite-Chat
vllm-ascend/DeepSeek-V3.2-W8A8-Pruning
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_models_pcp_dcp_full_graph | deepseek-ai/DeepSeek-V2-Lite-Chat
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_models_pcp_dcp_piece_wise | deepseek-ai/DeepSeek-V2-Lite-Chat
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_pcp_basic | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_pcp_full_graph | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_pcp_piece_wise | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | -| long_sequence/test_basic.py | test_qwen3_5_4b_multimodal_single_and_multi_image | Qwen/Qwen3.5-4B | | | | | | | ✅ | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | | | -| long_sequence/test_basic.py | test_qwen3_vl_8b_multimodal_single_and_multi_image | Qwen/Qwen3-VL-8B-Instruct | | | | | | | | ✅ | ✅ | | | ✅ | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | | | -| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_mixed_length_prompts_including_1_token | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | ✅ | -| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_cp_basic | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | | | | | | -| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_cp_default_full_and_piecewise | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | | | | | | -| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_cp_full_graph | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | | | | | | -| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_empty_kvcache | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | ✅ | | | ✅ | | -| long_sequence/test_mtp.py | test_dcp_mtp3_full_graph | - | | | | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| long_sequence/test_mtp.py | test_pcp_dcp_mtp1_eager | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| long_sequence/test_mtp.py | test_pcp_dcp_mtp3_eager | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| long_sequence/test_mtp.py | test_pcp_dcp_mtp3_full_graph | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| long_sequence/test_mtp.py | test_pcp_dcp_mtp3_piecewise_graph | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| long_sequence/test_mtp.py | test_pcp_eagle3_eager | - | | | | | | | | | ✅ | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | -| long_sequence/test_prefix_caching_cp.py | test_models_prefix_cache_with_cp_basic | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| long_sequence/test_prefix_caching_cp.py | test_models_prefix_cache_with_cp_default_full_and_piecewise | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| long_sequence/test_prefix_caching_cp.py | test_models_prefix_cache_with_cp_full_graph | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| spec_decode/test_mtp_qwen3_next.py | test_qwen3_next_mtp_acceptance_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | -| test_data_parallel_tp2.py | test_qwen3_inference_dp2_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_deepseek_v3_2_w8a8_pruning.py | test_moe_w8a8_tp_pp_ep_full_decode_only | vllm-ascend/DeepSeek-V3.2-W8A8-Pruning | | | ✅ | | | | | | ✅ | ✅ | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_deepseek_v3_2_w8a8_pruning.py | test_pd_disaggregation_w8a8_sfa_dsa_full_decode_only | vllm-ascend/DeepSeek-V3.2-W8A8-Pruning | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_deepseek_v4.py | test_deepseek_v4_w4a8_tp4_basic_greedy | gdydems/DeepSeek-V4-Flash-w4a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_deepseek_v4.py | test_deepseek_v4_w4a8_tp4_index_cache_freq4 | gdydems/DeepSeek-V4-Flash-w4a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_pipeline_parallel.py | test_models_pp2_dp2 | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_pipeline_parallel.py | test_models_pp2_tp2 | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| test_profiling_chunk_performance.py | test_profiling_chunk_ttft_performance | - | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | ✅ | | | | | | | -| test_qwen3_5.py | test_qwen3_5_27b_distributed_mp_tp4 | Qwen/Qwen3.5-27B | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_qwen3_5.py | test_qwen3_5_35b_distributed_mp_tp4 | Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_qwen3_5.py | test_qwen3_5_35b_distributed_mp_tp4_full_decode_only_mtp3 | Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | -| test_qwen3_5.py | test_qwen3_5_35b_distributed_mp_tp4_full_decode_only_mtp3_flashcomm | Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| test_qwen3_next.py | test_qwen3_next_distributed_mp_flash_comm_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_qwen3_next.py | test_qwen3_next_distributed_mp_full_decode_only_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_qwen3_next.py | test_qwen3_next_distributed_mp_graph_mode_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_qwen3_next.py | test_qwen3_next_distributed_mp_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | -| test_qwen3_next.py | test_qwen3_next_w8a8dynamic_distributed_tp4_ep | vllm-ascend/Qwen3-Next-80B-A3B-Instruct-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | DeepSeek V4 patch | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| _310p/test_dense_model_310p.py | test_qwen3_dense_tp2_fp16 | Qwen/Qwen3-8B | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| _310p/test_dense_model_310p.py | test_qwen3_dense_tp4_w8a8 | vllm-ascend/Qwen3-32B-W8A8 | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| _310p/test_moe_model_310p.py | test_qwen3_5_moe_tp4_fp16 | Qwen/Qwen3.5-35B-A3B | ✅ | | ✅ | | | | ✅ | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| _310p/test_moe_model_310p.py | test_qwen3_moe_tp2_w8a8 | vllm-ascend/Qwen3-30B-A3B-W8A8 | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| _310p/test_moe_model_310p.py | test_qwen3_moe_tp4_fp16 | Qwen/Qwen3-30B-A3B | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| _310p/test_vl_model_310p.py | test_qwen3_vl_8b_tp2_fp16 | Qwen/Qwen3-VL-8B-Instruct | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| long_sequence/test_accuracy.py | test_accuracy_dcp_only_eager | Qwen/Qwen3-8B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_accuracy.py | test_accuracy_dcp_only_graph | Qwen/Qwen3-8B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_accuracy.py | test_accuracy_pcp_only | Qwen/Qwen3-8B
vllm-ascend/DeepSeek-V2-Lite-W8A8 | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_accuracy.py | test_models_long_sequence_cp_kv_interleave_size_output_between_tp_and_cp | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| long_sequence/test_accuracy.py | test_models_long_sequence_output_between_tp_and_cp | vllm-ascend/DeepSeek-V2-Lite-W8A8 | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_dcp_basic | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_dcp_full_graph | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_dcp_piece_wise | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_deepseek_v4_w4a8_dsa_cp_basic_greedy | gdydems/DeepSeek-V4-Flash-w4a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | | | | | ✅ | | ✅ | | ✅ | | ✅ | | +| long_sequence/test_basic.py | test_models_pcp_dcp_basic | Qwen/Qwen3-Next-80B-A3B-Instruct
deepseek-ai/DeepSeek-V2-Lite-Chat
vllm-ascend/DeepSeek-V3.2-W8A8-Pruning
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_models_pcp_dcp_full_graph | deepseek-ai/DeepSeek-V2-Lite-Chat
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_models_pcp_dcp_piece_wise | deepseek-ai/DeepSeek-V2-Lite-Chat
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_pcp_basic | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_pcp_full_graph | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_pcp_piece_wise | deepseek-ai/DeepSeek-V2-Lite-Chat | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | +| long_sequence/test_basic.py | test_qwen3_5_4b_multimodal_single_and_multi_image | Qwen/Qwen3.5-4B | | | | | | | ✅ | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | +| long_sequence/test_basic.py | test_qwen3_vl_8b_multimodal_single_and_multi_image | Qwen/Qwen3-VL-8B-Instruct | | | | | | | | ✅ | ✅ | | | ✅ | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | +| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_mixed_length_prompts_including_1_token | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | +| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_cp_basic | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | | | | | | | +| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_cp_default_full_and_piecewise | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | | | | | | | +| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_cp_full_graph | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | | | | | | | +| long_sequence/test_chunked_prefill_cp.py | test_models_chunked_prefill_with_empty_kvcache | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | ✅ | | ✅ | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | ✅ | | | ✅ | | +| long_sequence/test_mtp.py | test_dcp_mtp3_full_graph | - | | | | | | | | | ✅ | | ✅ | | ✅ | ✅ | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| long_sequence/test_mtp.py | test_pcp_dcp_mtp1_eager | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| long_sequence/test_mtp.py | test_pcp_dcp_mtp3_eager | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| long_sequence/test_mtp.py | test_pcp_dcp_mtp3_full_graph | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| long_sequence/test_mtp.py | test_pcp_dcp_mtp3_piecewise_graph | - | | | | | | | | | ✅ | | ✅ | ✅ | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| long_sequence/test_mtp.py | test_pcp_eagle3_eager | - | | | | | | | | | ✅ | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| long_sequence/test_prefix_caching_cp.py | test_models_prefix_cache_with_cp_basic | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| long_sequence/test_prefix_caching_cp.py | test_models_prefix_cache_with_cp_default_full_and_piecewise | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| long_sequence/test_prefix_caching_cp.py | test_models_prefix_cache_with_cp_full_graph | vllm-ascend/DeepSeek-V2-Lite-W8A8
vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| spec_decode/test_mtp_qwen3_next.py | test_qwen3_next_mtp_acceptance_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | +| test_data_parallel_tp2.py | test_qwen3_inference_dp2_tp2 | Qwen/Qwen3-30B-A3B | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_deepseek_v3_2_w8a8_pruning.py | test_moe_w8a8_tp_pp_ep_full_decode_only | vllm-ascend/DeepSeek-V3.2-W8A8-Pruning | | | ✅ | | | | | | ✅ | ✅ | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_deepseek_v3_2_w8a8_pruning.py | test_pd_disaggregation_w8a8_sfa_dsa_full_decode_only | vllm-ascend/DeepSeek-V3.2-W8A8-Pruning | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_deepseek_v4.py | test_deepseek_v4_w4a8_tp4_basic_greedy | gdydems/DeepSeek-V4-Flash-w4a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_deepseek_v4.py | test_deepseek_v4_w4a8_tp4_index_cache_freq4 | gdydems/DeepSeek-V4-Flash-w4a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_pipeline_parallel.py | test_models_pp2_dp2 | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_pipeline_parallel.py | test_models_pp2_tp2 | - | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| test_profiling_chunk_performance.py | test_profiling_chunk_ttft_performance | - | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | ✅ | | | | | | | | +| test_qwen3_5.py | test_qwen3_5_27b_distributed_mp_tp4 | Qwen/Qwen3.5-27B | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_qwen3_5.py | test_qwen3_5_35b_distributed_mp_tp4 | Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_qwen3_5.py | test_qwen3_5_35b_distributed_mp_tp4_full_decode_only_mtp3 | Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | ✅ | | | | | | | | | | | | | | | | ✅ | | +| test_qwen3_5.py | test_qwen3_5_35b_distributed_mp_tp4_full_decode_only_mtp3_flashcomm | Qwen/Qwen3.5-35B-A3B | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| test_qwen3_next.py | test_qwen3_next_distributed_mp_flash_comm_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_qwen3_next.py | test_qwen3_next_distributed_mp_full_decode_only_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_qwen3_next.py | test_qwen3_next_distributed_mp_graph_mode_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_qwen3_next.py | test_qwen3_next_distributed_mp_tp4 | Qwen/Qwen3-Next-80B-A3B-Instruct | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| test_qwen3_next.py | test_qwen3_next_w8a8dynamic_distributed_tp4_ep | vllm-ascend/Qwen3-Next-80B-A3B-Instruct-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ## Nightly Tests -| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| 310p/single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule_v310.py | test_recurrent_gated_delta_rule_v310 | - | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| multi_node/external_dp/config/GLM5_1-W8A8-EP-external.yaml | multi-node-glm-5.1-w8a8-ep-external-dp | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | ✅ | | | | | | -| multi_node/external_dp/scripts/test_external_dp.py | test_external_dp | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| multi_node/internal_dp/config/DeepSeek-R1-W8A8-EPLB.yaml | test DeepSeek-R1-W8A8 disaggregated_prefill | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | ✅ | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/DeepSeek-R1-W8A8-longseq.yaml | test DeepSeek-R1-W8A8-longseq disaggregated_prefill | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | ✅ | | | ✅ | ✅ | | | | | | | -| multi_node/internal_dp/config/DeepSeek-R1-W8A8.yaml | test DeepSeek-R1-W8A8 disaggregated_prefill | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/DeepSeek-V3.1-BF16.yaml | test DeepSeek-V3.1-BF16 on A3 | unsloth/DeepSeek-V3.1-BF16 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | -| multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-A3-dual-nodes.yaml | test DeepSeek-V3.2-W8A8 on A3 | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP.yaml | test DeepSeek-V3.2-W8A8-EP disaggregated_prefill | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/GLM5_1-W8A8-A2-dual-nodes.yaml | multi-node-GLM-5.1-w8a8-A2 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/GLM5_1-W8A8-A3-dual-nodes.yaml | multi-node-GLM-5.1-w8a8-A3 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/GLM5_1-W8A8-EP.yaml | multi-node-GLM-5.1-w8a8-EP | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/Kimi-K2_5-W4A8-A2-dual-nodes.yaml | test Kimi-K2.5-W4A8 A2 dual nodes | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/Qwen3-235B-A22B-A2.yaml | test Qwen3-235B-A22B multi-dp on A2 | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/Qwen3-235B-A22B-Mooncake-Layerwise.yaml | test Qwen3-235B-A22B PD separation with mooncake layerwise connector | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | -| multi_node/internal_dp/config/Qwen3-235B-A22B.yaml | test Qwen3-235B-A22B multi-dp | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| multi_node/internal_dp/config/Qwen3-235B-W8A8-EPLB.yaml | test Qwen3-235B-A22B-W8A8 disaggregated_prefill | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | ✅ | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | -| multi_node/internal_dp/config/Qwen3-235B-W8A8-longseq.yaml | test Qwen3-235B-A22B-W8A8-longseq disaggregated_prefill | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | -| multi_node/internal_dp/config/Qwen3-235B-W8A8.yaml | test Qwen3-235B-A22B-W8A8 disaggregated_prefill | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | -| multi_node/internal_dp/config/Qwen3-235B-disagg-pd.yaml | test Qwen3-235B-A22B disaggregated_prefill | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | -| multi_node/internal_dp/config/Qwen3-VL-235B-disagg-pd.yaml | test Qwen3-VL-235B-A22B disaggregated_prefill | Qwen/Qwen3-VL-235B-A22B-Instruct | | | | | | | | ✅ | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | -| single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml | DeepSeek-R1-0528-W8A8-EPLB | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml | DeepSeek-R1-0528-W8A8-aclgraph | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml | DeepSeek-R1-0528-W8A8-single | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/DeepSeek-V3.2-W8A8.yaml | DeepSeek-V3.2-W8A8-TP8-DP2 | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/DeepSeek-V4-Flash-W8A8-A3.yaml | DeepSeek-V4-Flash-W8A8-A3 | Eco-Tech/DeepSeek-V4-Flash-w8a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/GLM-4.7.yaml | GLM-4.7-TP8-DP2-decodegraph | Eco-Tech/GLM-4.7-W8A8-floatmtp | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/models/configs/Hy3-preview.yaml | Hy3-preview-TP16-EP-MTP | Tencent-Hunyuan/Hy3-preview | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Kimi-K2-Thinking.yaml | Kimi-K2-Thinking-TP16-Case | moonshotai/Kimi-K2-Thinking | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/models/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-Case | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml | MTPX-DeepSeek-R1-0528-W8A8-mtp2 | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml | MTPX-DeepSeek-R1-0528-W8A8-mtp3 | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/MiniMax-M2.5-w8a8-QuaRot-A2.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/Prefix-Cache-DeepSeek-R1-0528-W8A8.yaml | prefix-cache-deepseek-r1-0528-w8a8 | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/models/configs/Prefix-Cache-Qwen3-32B-Int8.yaml | prefix-cache-qwen3-32b-w8a8 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/models/configs/Qwen3-235B-A22B-W8A8.yaml | Qwen3-235B-A22B-W8A8-EPLB | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/Qwen3-235B-A22B-W8A8.yaml | Qwen3-235B-A22B-W8A8-full_graph | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/Qwen3-235B-A22B-W8A8.yaml | Qwen3-235B-A22B-W8A8-piecewise | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/Qwen3-30B-A3B-W4A8-llm-compressor.yaml | Qwen3-30B-A3B-W4A8-llm-compressor | vllm-ascend/Qwen3-30B-A3B-Instruct-2507-quantized.w4a8 | | | ✅ | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3-30B-A3B-W8A8.yaml | Qwen3-30B-A3B-W8A8-TP1 | vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/models/configs/Qwen3-30B-QuaRot-eagle3.yaml | Qwen3-30B-QuaRot | vllm-ascend/Qwen3-30B-A3B-W8A8-QuaRot | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | -| single_node/models/configs/Qwen3-32B-Int8-A2.yaml | Qwen3-32B-W8A8-aclgraph-a2 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3-32B-Int8-A2.yaml | Qwen3-32B-W8A8-single-a2 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3-32B-Int8.yaml | Qwen3-32B-W8A8-aclgraph-a3 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3-32B-Int8.yaml | Qwen3-32B-W8A8-single-a3 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3-32B-QuaRot-eagle3.yaml | Qwen3-32B-QuaRot | vllm-ascend/Qwen3-32B-W8A8-QuaRot | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | -| single_node/models/configs/Qwen3-VL-235B-A22B-Instruct-W8A8.yaml | Qwen3-VL-235B-A22B-Instruct-W8A8 | Eco-Tech/Qwen3-VL-235B-A22B-Instruct-w8a8-QuaRot | | | | | | | | ✅ | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/Qwen3-VL-32B-Instruct-W8A8.yaml | Qwen3-VL-32B-Instruct-W8A8 | Eco-Tech/Qwen3-VL-32B-Instruct-w8a8-QuaRot | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-A3 | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/models/configs/Qwen3.5-27B-w8a8-A2.yaml | Qwen3.5-27B-w8a8 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml | Qwen3.5-397B-A17B-w8a8-mtp | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/models/configs/Qwen3.5-397B-A17B-w4a8-mtp-A2.yaml | Qwen3.5-397B-A17B-w4a8-mtp | Eco-Tech/Qwen3.5-397B-A17B-w4a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/ops/multicard_ops_a2/test_matmul_allreduce_add_rmsnorm.py | test_matmul_allreduce_add_rmsnorm_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/multicard_ops_a3/test_dispatch_ffn_combine.py | test_dispatch_ffn_combine_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/multicard_ops_a3/test_dispatch_ffn_combine_bf16.py | test_dispatch_ffn_combine_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/multicard_ops_a3/test_dispatch_ffn_combine_w4a8.py | test_dispatch_ffn_combine_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/multicard_ops_a3/test_dispatch_gmm_combine_decode.py | test_dispatch_gmm_combine_decode_base | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/multicard_ops_a3/test_dispatch_gmm_combine_decode.py | test_dispatch_gmm_combine_decode_dynamic_eplb | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/multicard_ops_a3/test_dispatch_gmm_combine_decode.py | test_dispatch_gmm_combine_decode_with_mc2_mask | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_add_rms_norm_bias.py | test_quant_fpx_linear | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_apply_top_k_top_p_custom.py | test_npu_apply_top_k | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_apply_top_k_top_p_custom.py | test_npu_apply_top_k_top_p | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_apply_top_k_top_p_custom.py | test_npu_apply_top_p | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_batch_matmul_transpose.py | test_boundary_conditions | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_batch_matmul_transpose.py | test_random_shapes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_batch_matmul_transpose.py | test_zero_values | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_bgmv_expand.py | test_bgmv_expand | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_bgmv_shrink.py | test_bgmv_shrink | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_causal_conv1d_310.py | test_ascend_causal_conv1d_310_fn | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | -| single_node/ops/singlecard_ops/test_causal_conv1d_310.py | test_causal_conv1d_310_update | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | -| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_copy_and_expand_eagle_inputs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_large_tokens_per_request | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_large_tokens_shift_true | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_minimal_case | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_no_rejected_tokens | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_dequant_swiglu_quant.py | test_npu_dequant_swiglu_quant_with_limit | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_bulk_dma_alignment | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_extreme_large_batch | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_large_batch_multi_row | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_non_bulk_dma_fallback | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_non_default_params | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_output_shapes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_small_batch_optimization | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_vs_reference | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_fused_moe.py | test_select_experts | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/test_fused_moe.py | test_select_experts_invalid_scoring_func | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/test_fused_moe.py | test_token_dispatcher_with_all_gather | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/test_fused_moe.py | test_token_dispatcher_with_all_gather_quant | - | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/test_gating_top_k_softmax.py | test_quant_fpx_linear | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_gmm_swiglu_quant_weight_nz_tensor_list.py | test_gmm_swiglu_quant_weight_nz_tensor_list | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_grouped_matmul_swiglu_quant.py | test_grouped_matmul_swiglu_quant_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_hamming_dist_top_k.py | test_hamming_dist_top_k | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_hamming_dist_top_k.py | test_hamming_dist_top_k_compare | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_mla_preprocess.py | test_mla_preprocess_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/test_mla_preprocess_nq.py | test_mla_preprocess_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/test_mla_preprocess_qdown.py | test_mla_preprocess_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/test_moe_init_routing_custom.py | test_moe_init_routing_custom | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_attrs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_basic | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_decode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_discard | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_exact_match | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_full_capacity | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_k1 | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_minimal | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_no_valid_sampled | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_padding | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_prefill | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_npu_hc_pre.py | test_npu_hc_pre_v1_v2_bf16_3d_input | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_npu_hc_pre.py | test_npu_hc_pre_v1_v2_bf16_4d_input | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_npu_moe_gating_top_k.py | test_npu_moe_gating_topk_compare | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule.py | test_recurrent_gated_delta_rule | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule.py | test_recurrent_gated_delta_rule_no_accepted | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule_310.py | test_fused_recurrent_gated_delta_rule_310 | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | -| single_node/ops/singlecard_ops/test_reshape_and_cache_bnsd.py | test_reshape_and_cache_bnsd_bf16_shape | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_reshape_and_cache_bnsd.py | test_reshape_and_cache_bnsd_compare | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_reshape_and_cache_bnsd.py | test_reshape_and_cache_bnsd_with_expected_output | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_transpose_kv_cache_by_block.py | test_transpose_kv_cache_by_block | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/test_vocabparallelembedding.py | test_get_masked_input_and_mask | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_apply_penalties_triton.py | test_apply_all_penalties_v1_vs_ascend | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_different_shapes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_edge_cases | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_no_bad_words | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_token_limit | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_batch_memcpy.py | test_batch_memcpy | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| single_node/ops/singlecard_ops/triton/test_bincount.py | test_bincount_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_ascend_causal_conv1d | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_causal_conv1d | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_causal_conv1d_update_qwen3_next_shape | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_causal_conv1d_update_with_batch_gather | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | -| single_node/ops/singlecard_ops/triton/test_chunk_gated_delta_rule.py | test_chunk_gated_delta_rule_310_state_layout_matches_vllm | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_chunk_gated_delta_rule.py | test_triton_fusion_ops | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_clear_ssm_states.py | test_clear_ssm_states_ref_parity | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_compute_slot_mapping.py | test_compute_slot_mapping_npu_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_deterministic | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_dtypes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_edge_cases | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_compute_topk_logprobs.py | test_compute_topk_logprobs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_fused_gdn_gating.py | test_fused_gdn_gating_310p_parity_precision | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_fused_qkvzba_split_reshape_cat.py | test_fused_qkvzba_split_reshape_cat | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_fused_recurrent_gated_delta_rule.py | test_fused_recurrent_gated_delta_rule_310_state_layout_matches_vllm | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_fused_recurrent_gated_delta_rule.py | test_fused_recurrent_gated_delta_rule_310p_parity_precision | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_fused_sigmoid_gating_delta_rule.py | test_triton_fusion_ops | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_gdn_chunk_meta.py | test_build_chunk_meta_device_correctness | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_l2norm.py | test_l2norm | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_log_softmax.py | test_topk_log_softmax_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_min_p.py | test_apply_min_p_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_mrope.py | test_mrotary_embedding_triton_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_muls_add.py | test_muls_add_triton_correctness | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_penality.py | test_apply_penalties | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_post_update.py | test_post_update | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | -| single_node/ops/singlecard_ops/triton/test_prepare_inputs_padded.py | test_prepare_inputs_padded | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_rejection_sample.py | test_rejection_random_sample | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_rejection_sample.py | test_rejection_sampler_block_verify_triton_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | -| single_node/ops/singlecard_ops/triton/test_rope.py | test_rotary_embedding_triton_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_rope.py | test_rotary_embedding_triton_kernel_siso | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_rope.py | test_rotary_embedding_triton_kernel_with_cos_sin_cache | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_split_qkv_rmsnorm_mrope.py | test_split_qkv_rmsnorm_mrope | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_split_qkv_rmsnorm_rope.py | test_split_qkv_rmsnorm_rope | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_split_qkv_rmsnorm_rope.py | test_split_qkv_rmsnorm_rope_with_bias | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_split_qkv_tp_rmsnorm_rope.py | test_split_qkv_tp_rmsnorm_rope | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/ops/singlecard_ops/triton/test_temperature.py | test_temperature_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | DeepSeek V4 patch | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| 310p/single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule_v310.py | test_recurrent_gated_delta_rule_v310 | - | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| multi_node/external_dp/config/GLM5_1-W8A8-EP-external.yaml | multi-node-glm-5.1-w8a8-ep-external-dp | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | ✅ | | | | | | +| multi_node/external_dp/scripts/test_external_dp.py | test_external_dp | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| multi_node/internal_dp/config/DeepSeek-R1-W8A8-EPLB.yaml | test DeepSeek-R1-W8A8 disaggregated_prefill | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | ✅ | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/DeepSeek-R1-W8A8-longseq.yaml | test DeepSeek-R1-W8A8-longseq disaggregated_prefill | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | ✅ | | | ✅ | ✅ | | | | | | | | +| multi_node/internal_dp/config/DeepSeek-R1-W8A8.yaml | test DeepSeek-R1-W8A8 disaggregated_prefill | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/DeepSeek-V3.1-BF16.yaml | test DeepSeek-V3.1-BF16 on A3 | unsloth/DeepSeek-V3.1-BF16 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | +| multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-A3-dual-nodes.yaml | test DeepSeek-V3.2-W8A8 on A3 | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP.yaml | test DeepSeek-V3.2-W8A8-EP disaggregated_prefill | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/GLM5_1-W8A8-A2-dual-nodes.yaml | multi-node-GLM-5.1-w8a8-A2 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/GLM5_1-W8A8-A3-dual-nodes.yaml | multi-node-GLM-5.1-w8a8-A3 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/GLM5_1-W8A8-EP.yaml | multi-node-GLM-5.1-w8a8-EP | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/Kimi-K2_5-W4A8-A2-dual-nodes.yaml | test Kimi-K2.5-W4A8 A2 dual nodes | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/Qwen3-235B-A22B-A2.yaml | test Qwen3-235B-A22B multi-dp on A2 | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/Qwen3-235B-A22B-Mooncake-Layerwise.yaml | test Qwen3-235B-A22B PD separation with mooncake layerwise connector | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | +| multi_node/internal_dp/config/Qwen3-235B-A22B.yaml | test Qwen3-235B-A22B multi-dp | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| multi_node/internal_dp/config/Qwen3-235B-W8A8-EPLB.yaml | test Qwen3-235B-A22B-W8A8 disaggregated_prefill | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | ✅ | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | +| multi_node/internal_dp/config/Qwen3-235B-W8A8-longseq.yaml | test Qwen3-235B-A22B-W8A8-longseq disaggregated_prefill | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | +| multi_node/internal_dp/config/Qwen3-235B-W8A8.yaml | test Qwen3-235B-A22B-W8A8 disaggregated_prefill | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | +| multi_node/internal_dp/config/Qwen3-235B-disagg-pd.yaml | test Qwen3-235B-A22B disaggregated_prefill | Qwen/Qwen3-235B-A22B | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | +| multi_node/internal_dp/config/Qwen3-VL-235B-disagg-pd.yaml | test Qwen3-VL-235B-A22B disaggregated_prefill | Qwen/Qwen3-VL-235B-A22B-Instruct | | | | | | | | ✅ | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | +| single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml | DeepSeek-R1-0528-W8A8-EPLB | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml | DeepSeek-R1-0528-W8A8-aclgraph | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml | DeepSeek-R1-0528-W8A8-single | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/DeepSeek-V3.2-W8A8.yaml | DeepSeek-V3.2-W8A8-TP8-DP2 | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/DeepSeek-V4-Flash-W8A8-A3.yaml | DeepSeek-V4-Flash-W8A8-A3 | Eco-Tech/DeepSeek-V4-Flash-w8a8-mtp | | | ✅ | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | ✅ | | | | | | +| single_node/models/configs/GLM-4.7.yaml | GLM-4.7-TP8-DP2-decodegraph | Eco-Tech/GLM-4.7-W8A8-floatmtp | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/models/configs/Hy3-preview.yaml | Hy3-preview-TP16-EP-MTP | Tencent-Hunyuan/Hy3-preview | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Kimi-K2-Thinking.yaml | Kimi-K2-Thinking-TP16-Case | moonshotai/Kimi-K2-Thinking | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/models/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-Case | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml | MTPX-DeepSeek-R1-0528-W8A8-mtp2 | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml | MTPX-DeepSeek-R1-0528-W8A8-mtp3 | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/MiniMax-M2.5-w8a8-QuaRot-A2.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/Prefix-Cache-DeepSeek-R1-0528-W8A8.yaml | prefix-cache-deepseek-r1-0528-w8a8 | vllm-ascend/DeepSeek-R1-0528-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/models/configs/Prefix-Cache-Qwen3-32B-Int8.yaml | prefix-cache-qwen3-32b-w8a8 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/models/configs/Qwen3-235B-A22B-W8A8.yaml | Qwen3-235B-A22B-W8A8-EPLB | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/Qwen3-235B-A22B-W8A8.yaml | Qwen3-235B-A22B-W8A8-full_graph | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/Qwen3-235B-A22B-W8A8.yaml | Qwen3-235B-A22B-W8A8-piecewise | vllm-ascend/Qwen3-235B-A22B-W8A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/Qwen3-30B-A3B-W4A8-llm-compressor.yaml | Qwen3-30B-A3B-W4A8-llm-compressor | vllm-ascend/Qwen3-30B-A3B-Instruct-2507-quantized.w4a8 | | | ✅ | | | | | | ✅ | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3-30B-A3B-W8A8.yaml | Qwen3-30B-A3B-W8A8-TP1 | vllm-ascend/Qwen3-30B-A3B-W8A8 | | | ✅ | | | | | | | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/models/configs/Qwen3-30B-QuaRot-eagle3.yaml | Qwen3-30B-QuaRot | vllm-ascend/Qwen3-30B-A3B-W8A8-QuaRot | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| single_node/models/configs/Qwen3-32B-Int8-A2.yaml | Qwen3-32B-W8A8-aclgraph-a2 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3-32B-Int8-A2.yaml | Qwen3-32B-W8A8-single-a2 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3-32B-Int8.yaml | Qwen3-32B-W8A8-aclgraph-a3 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3-32B-Int8.yaml | Qwen3-32B-W8A8-single-a3 | vllm-ascend/Qwen3-32B-W8A8 | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3-32B-QuaRot-eagle3.yaml | Qwen3-32B-QuaRot | vllm-ascend/Qwen3-32B-W8A8-QuaRot | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | +| single_node/models/configs/Qwen3-VL-235B-A22B-Instruct-W8A8.yaml | Qwen3-VL-235B-A22B-Instruct-W8A8 | Eco-Tech/Qwen3-VL-235B-A22B-Instruct-w8a8-QuaRot | | | | | | | | ✅ | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/Qwen3-VL-32B-Instruct-W8A8.yaml | Qwen3-VL-32B-Instruct-W8A8 | Eco-Tech/Qwen3-VL-32B-Instruct-w8a8-QuaRot | | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-A3 | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/models/configs/Qwen3.5-27B-w8a8-A2.yaml | Qwen3.5-27B-w8a8 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml | Qwen3.5-397B-A17B-w8a8-mtp | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/models/configs/Qwen3.5-397B-A17B-w4a8-mtp-A2.yaml | Qwen3.5-397B-A17B-w4a8-mtp | Eco-Tech/Qwen3.5-397B-A17B-w4a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/ops/multicard_ops_a2/test_matmul_allreduce_add_rmsnorm.py | test_matmul_allreduce_add_rmsnorm_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/multicard_ops_a3/test_dispatch_ffn_combine.py | test_dispatch_ffn_combine_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/multicard_ops_a3/test_dispatch_ffn_combine_bf16.py | test_dispatch_ffn_combine_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/multicard_ops_a3/test_dispatch_ffn_combine_w4a8.py | test_dispatch_ffn_combine_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/multicard_ops_a3/test_dispatch_gmm_combine_decode.py | test_dispatch_gmm_combine_decode_base | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/multicard_ops_a3/test_dispatch_gmm_combine_decode.py | test_dispatch_gmm_combine_decode_dynamic_eplb | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/multicard_ops_a3/test_dispatch_gmm_combine_decode.py | test_dispatch_gmm_combine_decode_with_mc2_mask | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_add_rms_norm_bias.py | test_quant_fpx_linear | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_apply_top_k_top_p_custom.py | test_npu_apply_top_k | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_apply_top_k_top_p_custom.py | test_npu_apply_top_k_top_p | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_apply_top_k_top_p_custom.py | test_npu_apply_top_p | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_batch_matmul_transpose.py | test_boundary_conditions | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_batch_matmul_transpose.py | test_random_shapes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_batch_matmul_transpose.py | test_zero_values | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_bgmv_expand.py | test_bgmv_expand | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_bgmv_shrink.py | test_bgmv_shrink | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_causal_conv1d_310.py | test_ascend_causal_conv1d_310_fn | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | +| single_node/ops/singlecard_ops/test_causal_conv1d_310.py | test_causal_conv1d_310_update | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | +| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_copy_and_expand_eagle_inputs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_large_tokens_per_request | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_large_tokens_shift_true | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_minimal_case | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_copy_and_expand_eagle_inputs.py | test_no_rejected_tokens | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_dequant_swiglu_quant.py | test_npu_dequant_swiglu_quant_with_limit | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_bulk_dma_alignment | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_extreme_large_batch | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_large_batch_multi_row | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_non_bulk_dma_fallback | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_non_default_params | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_output_shapes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_small_batch_optimization | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| single_node/ops/singlecard_ops/test_fused_gdn_gating.py | test_fused_gdn_gating_vs_reference | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_fused_moe.py | test_select_experts | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/test_fused_moe.py | test_select_experts_invalid_scoring_func | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/test_fused_moe.py | test_token_dispatcher_with_all_gather | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/test_fused_moe.py | test_token_dispatcher_with_all_gather_quant | - | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/test_gating_top_k_softmax.py | test_quant_fpx_linear | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_gmm_swiglu_quant_weight_nz_tensor_list.py | test_gmm_swiglu_quant_weight_nz_tensor_list | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_grouped_matmul_swiglu_quant.py | test_grouped_matmul_swiglu_quant_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_hamming_dist_top_k.py | test_hamming_dist_top_k | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_hamming_dist_top_k.py | test_hamming_dist_top_k_compare | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_mla_preprocess.py | test_mla_preprocess_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/test_mla_preprocess_nq.py | test_mla_preprocess_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/test_mla_preprocess_qdown.py | test_mla_preprocess_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/test_moe_init_routing_custom.py | test_moe_init_routing_custom | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_attrs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_basic | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_decode | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_discard | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_exact_match | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_full_capacity | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_k1 | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_minimal | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_no_valid_sampled | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_padding | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_ngram_spec_decode.py | test_ngram_spec_decode_prefill | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_npu_hc_pre.py | test_npu_hc_pre_v1_v2_bf16_3d_input | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_npu_hc_pre.py | test_npu_hc_pre_v1_v2_bf16_4d_input | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_npu_moe_gating_top_k.py | test_npu_moe_gating_topk_compare | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule.py | test_recurrent_gated_delta_rule | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule.py | test_recurrent_gated_delta_rule_no_accepted | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_recurrent_gated_delta_rule_310.py | test_fused_recurrent_gated_delta_rule_310 | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | +| single_node/ops/singlecard_ops/test_reshape_and_cache_bnsd.py | test_reshape_and_cache_bnsd_bf16_shape | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_reshape_and_cache_bnsd.py | test_reshape_and_cache_bnsd_compare | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_reshape_and_cache_bnsd.py | test_reshape_and_cache_bnsd_with_expected_output | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_transpose_kv_cache_by_block.py | test_transpose_kv_cache_by_block | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/test_vocabparallelembedding.py | test_get_masked_input_and_mask | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_apply_penalties_triton.py | test_apply_all_penalties_v1_vs_ascend | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_different_shapes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_edge_cases | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_no_bad_words | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_bad_words.py | test_apply_bad_words_token_limit | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_batch_memcpy.py | test_batch_memcpy | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| single_node/ops/singlecard_ops/triton/test_bincount.py | test_bincount_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_ascend_causal_conv1d | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_causal_conv1d | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_causal_conv1d_update_qwen3_next_shape | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_causal_conv1d.py | test_causal_conv1d_update_with_batch_gather | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | ✅ | | +| single_node/ops/singlecard_ops/triton/test_chunk_gated_delta_rule.py | test_chunk_gated_delta_rule_310_state_layout_matches_vllm | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_chunk_gated_delta_rule.py | test_triton_fusion_ops | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_clear_ssm_states.py | test_clear_ssm_states_ref_parity | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_compute_slot_mapping.py | test_compute_slot_mapping_npu_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_deterministic | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_dtypes | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_edge_cases | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_compute_token_logprobs.py | test_topk_log_softmax_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_compute_topk_logprobs.py | test_compute_topk_logprobs | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_fused_gdn_gating.py | test_fused_gdn_gating_310p_parity_precision | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_fused_qkvzba_split_reshape_cat.py | test_fused_qkvzba_split_reshape_cat | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_fused_recurrent_gated_delta_rule.py | test_fused_recurrent_gated_delta_rule_310_state_layout_matches_vllm | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_fused_recurrent_gated_delta_rule.py | test_fused_recurrent_gated_delta_rule_310p_parity_precision | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_fused_sigmoid_gating_delta_rule.py | test_triton_fusion_ops | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_gdn_chunk_meta.py | test_build_chunk_meta_device_correctness | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_l2norm.py | test_l2norm | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_log_softmax.py | test_topk_log_softmax_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_min_p.py | test_apply_min_p_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_mrope.py | test_mrotary_embedding_triton_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_muls_add.py | test_muls_add_triton_correctness | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_penality.py | test_apply_penalties | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_post_update.py | test_post_update | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | +| single_node/ops/singlecard_ops/triton/test_prepare_inputs_padded.py | test_prepare_inputs_padded | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_rejection_sample.py | test_rejection_random_sample | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_rejection_sample.py | test_rejection_sampler_block_verify_triton_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | +| single_node/ops/singlecard_ops/triton/test_rope.py | test_rotary_embedding_triton_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_rope.py | test_rotary_embedding_triton_kernel_siso | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_rope.py | test_rotary_embedding_triton_kernel_with_cos_sin_cache | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_split_qkv_rmsnorm_mrope.py | test_split_qkv_rmsnorm_mrope | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_split_qkv_rmsnorm_rope.py | test_split_qkv_rmsnorm_rope | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_split_qkv_rmsnorm_rope.py | test_split_qkv_rmsnorm_rope_with_bias | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_split_qkv_tp_rmsnorm_rope.py | test_split_qkv_tp_rmsnorm_rope | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/ops/singlecard_ops/triton/test_temperature.py | test_temperature_kernel | - | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ## Weekly Tests -| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| multi_node/internal_dp/config/DeepSeek-V3.yaml | test DeepSeek-V3 disaggregated_prefill | vllm-ascend/DeepSeek-V3-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | -| multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP_weekly.yaml | weekly test DeepSeek-V3.2-W8A8-EP disaggregated_prefill | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | -| multi_node/internal_dp/config/GLM-4.7-W8A8C8-Mooncake-Layerwise.yaml | test GLM-4.7-W8A8C8 PD separation with mooncake layerwise connector | vllm-ascend/GLM-4.7-W8A8C8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | -| single_node/configs/DeepSeek-V3.2-W8A8_A3_weekly.yaml | DeepSeek-V3.2-W8A8-weekly | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/GLM-5.yaml | GLM-5-TP16-DP1-decodegraph | Eco-Tech/GLM-5-w4a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | ✅ | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs10 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | -| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs20 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | -| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs32 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | -| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs8 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | -| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT20-32k-0.5k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT20-32k-0.5k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT50-32k-0.5k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT50-32k-0.5k-prefix-cache90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-Case | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT20-128k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT20-16k-1k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT20-64k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT50-128k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT50-16k-1k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT50-64k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-W8A8-A3.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in128k-32-8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in128k-4-1 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in128k-64-16 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in16k-120-30 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in16k-16-4 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-36-9 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-4-1 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-4-1-90 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-80-20 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in64k-4-1 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in64k-72-18 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/Qwen2.5-VL-7B-Instruct-EPD.yaml | Qwen2.5-VL-7B-Instruct-epd | Qwen/Qwen2.5-VL-7B-Instruct | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/configs/Qwen3-32B.yaml | Qwen3-32B-TP4 | Qwen/Qwen3-32B | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A2.yaml | Qwen3.5-122B-A10B-W8A8-single-A2 | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | ✅ | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-A3 | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT20-16k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT20-32k-0.5k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT20-64k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT50-16k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT50-32k-0.5k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT50-64k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in128k-28-7 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in128k-4-1 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in16k-16-4 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in16k-56-14 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in32k-16-4 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in32k-56-14 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in32k-8-2 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-16-4 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-48-12 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-8-2 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-8-2-90 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml | Qwen3.5-397B-A17B-w8a8-mtp | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs136 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs144 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs32_in65536 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs48 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs8 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs80 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs16 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs160 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs16_in65536 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs2 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs32 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | -| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs36 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| Test file | Test method | Model | 310P | Dense | MoE | Embedding | Classification | Reranker | Mamba/SSM | Multimodal Reasoning | TP | PP | EP | PCP | DCP | Context Parallel | EPLB | Dynamic EPLB | Multistream MoE | Full Graph | Full Decode Only Graph | Default FULL_AND_PIECEWISE Graph | Piecewise Graph | Eager Mode | PD disaggregation | W8A8 | W4A8 | FP16 | LoRA | Multi-LoRA | Runtime LoRA updating | Fully sharded LoRA parameterization | Spec Decode | MTP | Eagle-3 | SFA/DSA | DSA CP | Pooling runner | Score API | Classification API | Distributed executor mp | Flash Attention 3 | FIA comparison | Chunked Prefill | Prefix Caching | CPU/KV offloading | KV transfer/events | Sleep/Wake memory | Xlite Graph | CP KV Interleave | Long Sequence | DeepSeek V4 patch | FlashComm1 env | Skipped | Conditional skip | Logprobs | Batch inference | Mixed lengths | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| multi_node/internal_dp/config/DeepSeek-V3.yaml | test DeepSeek-V3 disaggregated_prefill | vllm-ascend/DeepSeek-V3-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | +| multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP_weekly.yaml | weekly test DeepSeek-V3.2-W8A8-EP disaggregated_prefill | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | ✅ | | | | ✅ | | | | | | | | +| multi_node/internal_dp/config/GLM-4.7-W8A8C8-Mooncake-Layerwise.yaml | test GLM-4.7-W8A8C8 PD separation with mooncake layerwise connector | vllm-ascend/GLM-4.7-W8A8C8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | ✅ | ✅ | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | +| single_node/configs/DeepSeek-V3.2-W8A8_A3_weekly.yaml | DeepSeek-V3.2-W8A8-weekly | vllm-ascend/DeepSeek-V3.2-W8A8 | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/GLM-5.yaml | GLM-5-TP16-DP1-decodegraph | Eco-Tech/GLM-5-w4a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | ✅ | | | | | ✅ | | | | | | ✅ | ✅ | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs10 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | +| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs20 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | +| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs32 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | +| single_node/configs/GLM-5_1-W8A8_A3_weekly.yaml | GLM-5_1-w8a8-High—Throughput-bs8 | Eco-Tech/GLM-5.1-w8a8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | ✅ | | ✅ | | | | | ✅ | | | | | | | ✅ | ✅ | | | | | | | | | | ✅ | | | | | | | | | | | | | | | +| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT20-32k-0.5k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT20-32k-0.5k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT50-32k-0.5k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5-32k-512.yaml | Kimi-K2.5-W4A8-TOPT50-32k-0.5k-prefix-cache90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-Case | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT20-128k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT20-16k-1k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT20-64k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT50-128k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT50-16k-1k | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Kimi-K2.5.yaml | Kimi-K2.5-W4A8-TOPT50-64k-1k-pc90 | Eco-Tech/Kimi-K2.5-W4A8 | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | | | | ✅ | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-W8A8-A3.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in128k-32-8 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in128k-4-1 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in128k-64-16 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in16k-120-30 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in16k-16-4 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-36-9 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-4-1 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-4-1-90 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in32k-80-20 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in64k-4-1 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml | MiniMax-M2.5-w8a8-in64k-72-18 | Eco-Tech/MiniMax-M2.5-w8a8-QuaRot | | ✅ | | | | | | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/Qwen2.5-VL-7B-Instruct-EPD.yaml | Qwen2.5-VL-7B-Instruct-epd | Qwen/Qwen2.5-VL-7B-Instruct | | | | | | | | ✅ | | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/configs/Qwen3-32B.yaml | Qwen3-32B-TP4 | Qwen/Qwen3-32B | | ✅ | | | | | | | ✅ | | | | | | | | | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A2.yaml | Qwen3.5-122B-A10B-W8A8-single-A2 | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | ✅ | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-A3 | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT20-16k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT20-32k-0.5k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT20-64k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT50-16k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT50-32k-0.5k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml | Qwen3.5-122B-A10B-W8A8-TPOT50-64k-1k | Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in128k-28-7 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in128k-4-1 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in16k-16-4 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in16k-56-14 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in32k-16-4 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in32k-56-14 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in32k-8-2 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-16-4 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-48-12 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-8-2 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-27B-w8a8-A3.yaml | Qwen3.5-27B-w8a8-in64k-8-2-90 | Eco-Tech/Qwen3.5-27B-w8a8-mtp | | | | | | | ✅ | | ✅ | | | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | ✅ | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml | Qwen3.5-397B-A17B-w8a8-mtp | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | ✅ | | | | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs136 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs144 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs32_in65536 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs48 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs8 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-High—Throughput-bs80 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs16 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs160 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs16_in65536 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs2 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs32 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | +| single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml | Qwen3-397B-A17B-w8a8-A3-Minimal-Delay-bs36 | Eco-Tech/Qwen3.5-397B-A17B-w8a8-mtp | | | | | | | ✅ | | ✅ | | ✅ | | | | | | | | | | | ✅ | | ✅ | | | | | | | | | | | | | | | | | | | | | | | | | | | ✅ | | | | | | diff --git a/tests/e2e/doctests/002-pip-binary-installation-test.sh b/tests/e2e/doctests/002-pip-binary-installation-test.sh index 44e92e28f..430d6995c 100644 --- a/tests/e2e/doctests/002-pip-binary-installation-test.sh +++ b/tests/e2e/doctests/002-pip-binary-installation-test.sh @@ -58,14 +58,8 @@ function install_binary_test() { PIP_VLLM_ASCEND_VERSION=$(get_version pip_vllm_ascend_version) _info "====> Install vllm==${PIP_VLLM_VERSION} and vllm-ascend ${PIP_VLLM_ASCEND_VERSION}" - # Setup extra-index-url for public PyPI mirror, Ascend packages, and PyTorch CPU wheels. - local pip_extra_index_urls=( - "https://mirrors.huaweicloud.com/repository/pypi/variant" - "https://mirrors.huaweicloud.com/ascend/repos/pypi" - "https://download.pytorch.org/whl/cpu/" - ) - local IFS=" " - pip config set global.extra-index-url "${pip_extra_index_urls[*]}" + # Setup extra-index-url for x86 & torch_npu dev version + pip config set global.extra-index-url "https://download.pytorch.org/whl/cpu/" # The vLLM version already in pypi, we install from pypi. pip install --default-timeout=300 --retries 3 vllm=="${PIP_VLLM_VERSION}" diff --git a/tests/e2e/generate_coverage_md.py b/tests/e2e/generate_coverage_md.py index 48815a9aa..58c08f8bf 100644 --- a/tests/e2e/generate_coverage_md.py +++ b/tests/e2e/generate_coverage_md.py @@ -67,6 +67,7 @@ "Xlite Graph", "CP KV Interleave", "Long Sequence", + "DeepSeek V4 patch", "FlashComm1 env", "Skipped", "Conditional skip", @@ -822,6 +823,9 @@ def _process_test_file(filepath, source_code, root_path=None): ) row["Long Sequence"] = CHECK if is_long_seq else EMPTY + has_dsv4 = "VLLM_ASCEND_APPLY_DSV4_PATCH" in env_vars and env_vars.get("VLLM_ASCEND_APPLY_DSV4_PATCH") == "1" + row["DeepSeek V4 patch"] = CHECK if has_dsv4 else EMPTY + has_flashcomm1 = ( "VLLM_ASCEND_ENABLE_FLASHCOMM1" in env_vars and env_vars.get("VLLM_ASCEND_ENABLE_FLASHCOMM1") == "1" ) @@ -1088,6 +1092,8 @@ def _build_row(test_name, model, features): if ascend_cc.get("enable_npugraph_ex"): has_cudagraph_sizes = True + has_dsv4 = env_vars.get("VLLM_ASCEND_APPLY_DSV4_PATCH") == "1" + row = {} row["Test file"] = display_path row["_orig_rel_path"] = rel_path @@ -1208,6 +1214,8 @@ def _build_row(test_name, model, features): is_long_seq = max_model_len is not None and max_model_len > 8192 row["Long Sequence"] = CHECK if is_long_seq else EMPTY + row["DeepSeek V4 patch"] = CHECK if has_dsv4 else EMPTY + has_flashcomm1 = env_vars.get("VLLM_ASCEND_ENABLE_FLASHCOMM1") == "1" if enable_flashcomm1: has_flashcomm1 = True diff --git a/tests/e2e/models/configs/accuracy_groups_a2.json b/tests/e2e/models/configs/accuracy_groups_a2.json index 8f793b5b6..65f40d4d2 100644 --- a/tests/e2e/models/configs/accuracy_groups_a2.json +++ b/tests/e2e/models/configs/accuracy_groups_a2.json @@ -17,8 +17,10 @@ "os": "linux-aarch64-a2b3-1", "model_list": [ "ERNIE-4.5-21B-A3B-PT", + "InternVL3_5-8B-hf", "Molmo-7B-D-0924", - "Llama-3.2-3B-Instruct" + "Llama-3.2-3B-Instruct", + "llava-onevision-qwen2-0.5b-ov-hf" ] }, { @@ -36,6 +38,7 @@ "model_list": [ "Qwen3-Next-80B-A3B-Instruct", "Qwen3-Omni-30B-A3B-Instruct", + "Hunyuan-A13B-Instruct", "Mixtral-8x7B-Instruct-v0.1" ] } @@ -47,17 +50,14 @@ "model_list": [ "gemma-3-4b-it", "internlm3-8b-instruct", - "Qwen3-ASR-1.7B", - "InternVL3_5-8B-hf", - "llava-onevision-qwen2-0.5b-ov-hf" + "Qwen3-ASR-1.7B" ] }, { "name": "pr-accuracy-group-2", "os": "linux-aarch64-a2b3-4", "model_list": [ - "Qwen2.5-Math-RM-72B", - "Hunyuan-A13B-Instruct" + "Qwen2.5-Math-RM-72B" ] } ] diff --git a/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-R1-W8A8-longseq.yaml b/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-R1-W8A8-longseq.yaml index 8dd4c6e8a..5d2d6f8cc 100644 --- a/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-R1-W8A8-longseq.yaml +++ b/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-R1-W8A8-longseq.yaml @@ -83,7 +83,6 @@ deployment: --compilation_config '{"cudagraph_capture_sizes":[4,8,16,32],"cudagraph_mode": "FULL_DECODE_ONLY"}' --enable-chunked-prefill --speculative-config '{"num_speculative_tokens": 3, "method":"mtp"}' - --additional-config '{"recompute_scheduler_enable":true}' --kv-transfer-config '{"kv_connector": "MooncakeConnectorV1", "kv_role": "kv_consumer", diff --git a/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3.1-BF16.yaml b/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3.1-BF16.yaml index 258456928..1fb33133f 100644 --- a/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3.1-BF16.yaml +++ b/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3.1-BF16.yaml @@ -14,7 +14,6 @@ env_common: &env_common VLLM_ASCEND_BALANCE_SCHEDULING: 1 HCCL_INTRA_PCIE_ENABLE: 1 HCCL_INTRA_ROCE_ENABLE: 0 - VLLM_ENGINE_READY_TIMEOUT_S: "3000" deployment: - diff --git a/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP.yaml b/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP.yaml index 2963d29e5..5b1f2aa77 100644 --- a/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP.yaml +++ b/tests/e2e/nightly/multi_node/internal_dp/config/DeepSeek-V3_2-W8A8-EP.yaml @@ -21,6 +21,8 @@ env_common: &env_common HCCL_INTRA_PCIE_ENABLE: 1 HCCL_INTRA_ROCE_ENABLE: 0 +special_dependencies: + transformers: "5.9.0" disaggregated_prefill: enabled: true diff --git a/tests/e2e/nightly/multi_node/internal_dp/config/Qwen3-235B-W8A8-EPLB.yaml b/tests/e2e/nightly/multi_node/internal_dp/config/Qwen3-235B-W8A8-EPLB.yaml index 033842db9..9385a4ccf 100644 --- a/tests/e2e/nightly/multi_node/internal_dp/config/Qwen3-235B-W8A8-EPLB.yaml +++ b/tests/e2e/nightly/multi_node/internal_dp/config/Qwen3-235B-W8A8-EPLB.yaml @@ -1,4 +1,4 @@ -test_name: "test Qwen3-235B-A22B-W8A8 EPLB" +test_name: "test Qwen3-235B-A22B-W8A8 disaggregated_prefill" model: "vllm-ascend/Qwen3-235B-A22B-W8A8" num_nodes: 2 npu_per_node: 16 @@ -39,7 +39,7 @@ deployment: --kv-transfer-config '{"kv_connector": "MooncakeConnectorV1", "kv_role": "kv_producer", - "kv_port": "30000", + "kv_port": "36000", "kv_connector_extra_config": { "prefill": { "dp_size": 2, @@ -52,7 +52,7 @@ deployment: } }' --additional-config - '{"eplb_config": {"dynamic_eplb":true,"expert_heat_collection_interval":50,"algorithm_execution_interval":5}}' + '{"eplb_config": {"dynamic_eplb":true,"expert_heat_collection_interval":2048,"algorithm_execution_interval":200}}' - envs: @@ -76,7 +76,7 @@ deployment: --kv-transfer-config '{"kv_connector": "MooncakeConnectorV1", "kv_role": "kv_consumer", - "kv_port": "30200", + "kv_port": "36100", "kv_connector_extra_config": { "prefill": { "dp_size": 2, @@ -89,5 +89,5 @@ deployment: } }' --additional-config - '{"eplb_config": {"dynamic_eplb":true,"expert_heat_collection_interval":600,"algorithm_execution_interval":50}}' + '{"eplb_config": {"dynamic_eplb":true,"expert_heat_collection_interval":2048,"algorithm_execution_interval":200}}' benchmarks: diff --git a/tests/e2e/nightly/single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml b/tests/e2e/nightly/single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml index f5b2ffdf4..8fcfa1a37 100644 --- a/tests/e2e/nightly/single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml +++ b/tests/e2e/nightly/single_node/models/configs/DeepSeek-R1-0528-W8A8.yaml @@ -8,7 +8,6 @@ _envs: &envs HCCL_BUFFSIZE: "1024" PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" SERVER_PORT: "DEFAULT_PORT" - VLLM_ENGINE_READY_TIMEOUT_S: "3000" _server_cmd: &server_cmd - "--quantization" diff --git a/tests/e2e/nightly/single_node/models/configs/DeepSeek-V3.2-W8A8.yaml b/tests/e2e/nightly/single_node/models/configs/DeepSeek-V3.2-W8A8.yaml index 1173a3ed1..0ab31a73d 100644 --- a/tests/e2e/nightly/single_node/models/configs/DeepSeek-V3.2-W8A8.yaml +++ b/tests/e2e/nightly/single_node/models/configs/DeepSeek-V3.2-W8A8.yaml @@ -5,6 +5,8 @@ test_cases: - name: "DeepSeek-V3.2-W8A8-TP8-DP2" model: "vllm-ascend/DeepSeek-V3.2-W8A8" + special_dependencies: + transformers: "5.9.0" envs: OMP_PROC_BIND: "false" OMP_NUM_THREADS: "1" @@ -61,10 +63,7 @@ test_cases: max_out_len: 32768 batch_size: 32 baseline: 86.67 - temperature: 1.0 - top_p: 0.95 - thinking: true - threshold: 10 + threshold: 5 perf_2: case_type: performance diff --git a/tests/e2e/nightly/single_node/models/configs/DeepSeek-V4-Flash-W8A8-A3.yaml b/tests/e2e/nightly/single_node/models/configs/DeepSeek-V4-Flash-W8A8-A3.yaml index f36b332bb..9f0e05e1e 100644 --- a/tests/e2e/nightly/single_node/models/configs/DeepSeek-V4-Flash-W8A8-A3.yaml +++ b/tests/e2e/nightly/single_node/models/configs/DeepSeek-V4-Flash-W8A8-A3.yaml @@ -11,12 +11,14 @@ test_cases: OMP_PROC_BIND: "false" OMP_NUM_THREADS: "1" PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" + USE_MULTI_BLOCK_POOL: "1" + USE_MULTI_GROUPS_KV_CACHE: "1" HCCL_BUFFSIZE: "1024" VLLM_ASCEND_ENABLE_FUSED_MC2: "0" VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" + VLLM_ASCEND_APPLY_DSV4_PATCH: "1" ASCEND_LAUNCH_BLOCKING: "0" SERVER_PORT: "DEFAULT_PORT" - VLLM_ENGINE_READY_TIMEOUT_S: "3000" server_cmd: - "--enable-prefix-caching" - "--max-model-len" @@ -28,9 +30,9 @@ test_cases: - "--max-num-seqs" - "64" - "--data-parallel-size" - - "4" + - "2" - "--tensor-parallel-size" - - "4" + - "8" - "--enable-expert-parallel" - "--tokenizer-mode" - "deepseek_v4" @@ -55,7 +57,7 @@ test_cases: - '{"cudagraph_mode": "FULL_DECODE_ONLY"}' - "--async-scheduling" - "--additional-config" - - '{"ascend_compilation_config":{"enable_npugraph_ex":true,"enable_static_kernel":false},"enable_cpu_binding":"true","enable_shared_expert_dp":true,"multistream_overlap_shared_expert":true}' + - '{"ascend_compilation_config":{"enable_npugraph_ex":true,"enable_static_kernel":false},"enable_cpu_binding":"true","enable_shared_expert_dp":true,"multistream_overlap_shared_expert":true,"multistream_dsa_preprocess":false}' benchmarks: acc-gpqa: case_type: accuracy @@ -69,12 +71,12 @@ test_cases: thinking: true perf: case_type: performance - dataset_path: vllm-ascend/GSM8K_prefix0_in32768_bs1000_deepseek + dataset_path: vllm-ascend/GSM8K_prefix90_in131072_bs100_deepseek request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf - num_prompts: 64 + num_prompts: 32 max_out_len: 1024 - batch_size: 16 + batch_size: 8 request_rate: 0 baseline: 1 threshold: 0.97 diff --git a/tests/e2e/nightly/single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml b/tests/e2e/nightly/single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml index efe3cccdc..30fec5ad2 100644 --- a/tests/e2e/nightly/single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml +++ b/tests/e2e/nightly/single_node/models/configs/MTPX-DeepSeek-R1-0528-W8A8.yaml @@ -9,7 +9,6 @@ _envs: &envs VLLM_RPC_TIMEOUT: "3600000" VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS: "3600000" SERVER_PORT: "DEFAULT_PORT" - VLLM_ENGINE_READY_TIMEOUT_S: "7200" _server_cmd: &server_cmd - "--quantization" diff --git a/tests/e2e/nightly/single_node/models/configs/Qwen3-30B-QuaRot-eagle3.yaml b/tests/e2e/nightly/single_node/models/configs/Qwen3-30B-QuaRot-eagle3.yaml index cc05cfaa0..dac4efbb7 100644 --- a/tests/e2e/nightly/single_node/models/configs/Qwen3-30B-QuaRot-eagle3.yaml +++ b/tests/e2e/nightly/single_node/models/configs/Qwen3-30B-QuaRot-eagle3.yaml @@ -8,7 +8,6 @@ test_cases: envs: VLLM_WORKER_MULTIPROC_METHOD: "spawn" SERVER_PORT: "DEFAULT_PORT" - HCCL_BUFFSIZE: "512" server_cmd: - "--enforce-eager" - "--no-enable-prefix-caching" @@ -18,7 +17,7 @@ test_cases: - "--port" - "$SERVER_PORT" - "--max-model-len" - - "8192" + - "4096" - "--trust-remote-code" - "--distributed-executor-backend" - "mp" diff --git a/tests/e2e/nightly/single_node/models/configs/Qwen3-32B-QuaRot-eagle3.yaml b/tests/e2e/nightly/single_node/models/configs/Qwen3-32B-QuaRot-eagle3.yaml index 90531e1cd..2de34ae5b 100644 --- a/tests/e2e/nightly/single_node/models/configs/Qwen3-32B-QuaRot-eagle3.yaml +++ b/tests/e2e/nightly/single_node/models/configs/Qwen3-32B-QuaRot-eagle3.yaml @@ -16,7 +16,7 @@ test_cases: - "--port" - "$SERVER_PORT" - "--max-model-len" - - "8192" + - "4096" - "--trust-remote-code" - "--distributed-executor-backend" - "mp" diff --git a/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A2-bak.yaml b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A2-bak.yaml new file mode 100644 index 000000000..e5e4c5a4e --- /dev/null +++ b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A2-bak.yaml @@ -0,0 +1,57 @@ +# ========================================== +# ACTUAL TEST CASES +# ========================================== + +test_cases: + - name: "Qwen3.5-27B-w8a8" + model: "Eco-Tech/Qwen3.5-27B-w8a8-mtp" + envs: + VLLM_USE_MODELSCOPE: "true" + PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" + HCCL_BUFFSIZE: "1024" + HCCL_OP_EXPANSION_MODE: "AIV" + OMP_NUM_THREADS: "1" + TASK_QUEUE_ENABLE: "1" + VLLM_ASCEND_ENABLE_PREFETCH_MLP: "1" + VLLM_ASCEND_ENABLE_DENSE_OPTIMIZE: "1" + VLLM_ASCEND_ENABLE_NZ: "1" + VLLM_ASCEND_ENABLE_FUSED_MC2: "1" + SERVER_PORT: "DEFAULT_PORT" + server_cmd: + - "--tensor-parallel-size" + - "2" + - "--port" + - "$SERVER_PORT" + - "--max-num-seqs" + - "128" + - "--quantization" + - "ascend" + - "--max-model-len" + - "262144" + - "--max-num-batched-tokens" + - "8192" + - "--trust-remote-code" + - "--gpu-memory-utilization" + - "0.95" + - "--additional-config" + - '{"enable_cpu_binding":true, "multistream_overlap_shared_expert": true, "enable_weight_nz_layout":true}' + - "--speculative_config" + - '{"method": "qwen3_5_mtp", "num_speculative_tokens": 3,"enforce_eager": true}' + - "--compilation-config" + - '{"cudagraph_mode":"FULL_DECODE_ONLY", "cudagraph_capture_sizes":[4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104,108,112,116,120,124,128,132,136,140,144]}' + - "--mm-processor-cache-gb" + - "0" + - "--mm_processor_cache_type" + - "shm" + benchmarks: + perf: + case_type: performance + dataset_path: vllm-ascend/GSM8K-in3500-bs8000-qwen3 + request_conf: vllm_api_stream_chat + dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf + num_prompts: 128 + max_out_len: 1500 + batch_size: 32 + request_rate: 0 + baseline: 687 + threshold: 0.97 diff --git a/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A2.yaml b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A2.yaml index ba0ccc0e9..e5e4c5a4e 100644 --- a/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A2.yaml +++ b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A2.yaml @@ -53,5 +53,5 @@ test_cases: max_out_len: 1500 batch_size: 32 request_rate: 0 - baseline: 604 + baseline: 687 threshold: 0.97 diff --git a/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A3.yaml b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A3.yaml index ee998aed9..53883cd15 100644 --- a/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A3.yaml +++ b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-27B-w8a8-A3.yaml @@ -21,7 +21,7 @@ test_cases: - "--port" - "$SERVER_PORT" - "--max-num-seqs" - - "128" + - "32" - "--quantization" - "ascend" - "--max-model-len" @@ -51,7 +51,7 @@ test_cases: max_out_len: 65536 batch_size: 32 baseline: 90 - threshold: 10 + threshold: 5 ignore_eos: false thinking: true temperature: 1.0 diff --git a/tests/e2e/nightly/single_node/models/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml index eae13db5c..be2c32ecf 100644 --- a/tests/e2e/nightly/single_node/models/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml +++ b/tests/e2e/nightly/single_node/models/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3.yaml @@ -56,13 +56,7 @@ test_cases: max_out_len: 32768 batch_size: 32 baseline: 90 - threshold: 10 - temperature: 0.6 - top_p: 0.95 - top_k: 20 - min_p: 0.0 - presence_penalty: 0.0 - repetition_penalty: 1.0 + threshold: 5 perf: case_type: performance dataset_path: vllm-ascend/GSM8K-in131072-bs100-qwen3 diff --git a/tests/e2e/nightly/single_node/ops/singlecard_ops/test_fused_gdn_gating.py b/tests/e2e/nightly/single_node/ops/singlecard_ops/test_fused_gdn_gating.py index 0bda0932a..143e11877 100644 --- a/tests/e2e/nightly/single_node/ops/singlecard_ops/test_fused_gdn_gating.py +++ b/tests/e2e/nightly/single_node/ops/singlecard_ops/test_fused_gdn_gating.py @@ -24,8 +24,6 @@ NUM_HEADS_VALUES = [4, 6, 8, 12, 16, 24, 32, 48, 64, 128] BATCH_SIZES = [1, 7, 37, 128, 512, 4096, 16384] DTYPES = [torch.bfloat16, torch.float16] -PARAM_DTYPES = [torch.float32, torch.bfloat16, torch.float16] -DTYPE_COMBINATIONS = [(dtype, param_dtype) for dtype in DTYPES for param_dtype in PARAM_DTYPES] # --------------------------------------------------------------------------- @@ -39,12 +37,11 @@ def _golden_fused_gdn_gating( b: torch.Tensor, dt_bias: torch.Tensor, beta: float = 1.0, - threshold: float = 20.0, ) -> tuple[torch.Tensor, torch.Tensor]: """CPU golden reference for fused_gdn_gating. - Uses the same softplus threshold semantics as the Triton kernel: - where(beta * x <= threshold, log(1 + exp(beta * x)) / beta, x) + Uses the same numerically stable softplus form as the AscendC kernel: + softplus_o = max(x, 0) + log(1 + exp(-|beta*x|)) / beta Returns: g: [1, batch, num_heads], fp32. @@ -63,11 +60,7 @@ def _golden_fused_gdn_gating( x = a_f + dt_bias_expanded beta_x = beta * x - softplus_o = torch.where( - beta_x <= threshold, - torch.log1p(torch.exp(beta_x)) / beta, - x, - ) + softplus_o = torch.maximum(x, torch.tensor(0.0)) + torch.log(1.0 + torch.exp(-torch.abs(beta_x))) / beta g = -torch.exp(A_log_expanded) * softplus_o g = g.unsqueeze(0) @@ -87,46 +80,30 @@ def _make_inputs( num_heads: int, batch: int, dtype: torch.dtype, - param_dtype: torch.dtype = torch.float32, seed: int = SEED, ): """Build random tensors on CPU for both golden and NPU execution.""" torch.manual_seed(seed) - A_log = torch.randn(num_heads, dtype=param_dtype) - dt_bias = torch.randn(num_heads, dtype=param_dtype) + A_log = torch.randn(num_heads, dtype=torch.float32) + dt_bias = torch.randn(num_heads, dtype=torch.float32) a = torch.randn(batch, num_heads, dtype=dtype) b = torch.randn(batch, num_heads, dtype=dtype) return A_log, a, b, dt_bias -def _force_softplus_threshold_cases( - a: torch.Tensor, - dt_bias: torch.Tensor, - beta: float, - threshold: float, -) -> None: - """Force beta * (a + dt_bias) to cover threshold and non-threshold paths.""" - if a.shape[0] < 4 or a.shape[1] < 4: - return - - dt_bias[:4] = 0 - boundary = threshold / beta - a[0, 0] = boundary + 2.0 # linear branch - a[1, 1] = boundary # softplus branch at equality - a[2, 2] = boundary - 0.5 # softplus branch below threshold - a[3, 3] = -boundary - 2.0 # negative softplus input - - def _npu_op_exec( A_log: torch.Tensor, a: torch.Tensor, b: torch.Tensor, dt_bias: torch.Tensor, beta: float = 1.0, - threshold: float = 20.0, ) -> tuple[torch.Tensor, torch.Tensor]: """Execute the AscendC operator on NPU and return CPU tensors.""" - # Ensure contiguity for the NPU operator. + # Ensure correct dtypes and contiguity for the NPU operator. + if A_log.dtype != torch.float32: + A_log = A_log.to(torch.float32) + if dt_bias.dtype != torch.float32: + dt_bias = dt_bias.to(torch.float32) if not A_log.is_contiguous(): A_log = A_log.contiguous() if not dt_bias.is_contiguous(): @@ -138,7 +115,6 @@ def _npu_op_exec( b.npu(), dt_bias.npu(), float(beta), - float(threshold), ) return g.cpu(), beta_output.cpu() @@ -185,7 +161,6 @@ def test_fused_gdn_gating_vs_reference(num_heads, batch, dtype): @pytest.mark.parametrize("batch", [1, 37]) def test_fused_gdn_gating_non_default_params(num_heads, batch): A_log, a, b, dt_bias = _make_inputs(num_heads, batch, torch.bfloat16) - _force_softplus_threshold_cases(a, dt_bias, beta=0.5, threshold=1.0) ref_g, ref_beta = _golden_fused_gdn_gating( A_log, @@ -193,7 +168,6 @@ def test_fused_gdn_gating_non_default_params(num_heads, batch): b, dt_bias, beta=0.5, - threshold=1.0, ) npu_g, npu_beta = _npu_op_exec( A_log, @@ -201,39 +175,6 @@ def test_fused_gdn_gating_non_default_params(num_heads, batch): b, dt_bias, beta=0.5, - threshold=1.0, - ) - - _assert_close(npu_g, npu_beta, ref_g, ref_beta) - - gc.collect() - torch.npu.empty_cache() - torch.npu.reset_peak_memory_stats() - - -@pytest.mark.parametrize(("dtype", "param_dtype"), DTYPE_COMBINATIONS) -def test_fused_gdn_gating_dtype_matrix(dtype, param_dtype): - A_log, a, b, dt_bias = _make_inputs( - 32, - 37, - dtype, - param_dtype=param_dtype, - ) - _force_softplus_threshold_cases(a, dt_bias, beta=1.0, threshold=2.0) - - ref_g, ref_beta = _golden_fused_gdn_gating( - A_log, - a, - b, - dt_bias, - threshold=2.0, - ) - npu_g, npu_beta = _npu_op_exec( - A_log, - a, - b, - dt_bias, - threshold=2.0, ) _assert_close(npu_g, npu_beta, ref_g, ref_beta) diff --git a/tests/e2e/nightly/single_node/ops/singlecard_ops/test_store_kv_block.py b/tests/e2e/nightly/single_node/ops/singlecard_ops/test_store_kv_block.py deleted file mode 100644 index 5af1ae746..000000000 --- a/tests/e2e/nightly/single_node/ops/singlecard_ops/test_store_kv_block.py +++ /dev/null @@ -1,152 +0,0 @@ -import gc -import time - -import numpy as np -import pytest -import torch -import torch_npu - -from vllm_ascend.utils import enable_custom_op - -torch.set_printoptions(threshold=np.inf) - -enable_custom_op() - - -def cal_slot(key, key_cache, slot_mapping, block_size): - key_expect = key_cache.clone() - for i, slot in enumerate(slot_mapping): - if slot < 0: - continue - token_key = key[i] - block_index = slot // block_size - block_offset = slot % block_size - key_expect[block_index][block_offset] = token_key - return key_expect.npu() - - -def cal_scatternd(key, key_cache, slot_mapping, block_size): - key_expect = key_cache.clone() - for i, slot in enumerate(slot_mapping): - if slot < 0: - continue - token_key = key[i] - key_expect[slot] = token_key - - return key_expect.npu() - - -# slot_mapping[].shape=torch.Size([4]) -@pytest.mark.parametrize("num_tokens", [16]) # 6398 -@pytest.mark.parametrize("num_head", [1]) # 512 -@pytest.mark.parametrize("block_size", [128]) # 128 -@pytest.mark.parametrize("num_blocks", [1773]) # 1599 -@pytest.mark.parametrize("count", [1]) -def test_siso(num_tokens, num_head, block_size, num_blocks, count): - head_size_k = 1 - key = torch.rand((num_tokens, num_head, head_size_k), dtype=torch.float16).npu() - # key = torch.randint(low=0,high=128,size=(num_tokens,head_size_k), dtype=torch.int8 ) - key_cache = torch.rand((num_blocks, block_size, num_head, head_size_k), dtype=torch.float16).npu() - # key_cache = torch.randint(low=0,high=128,size=(num_blocks, block_size, num_head,head_size_k), dtype=torch.int8 ) - - slot_list = [] - for i in range(0, num_tokens): - slot_list.append(2 + i) - assert num_tokens == len(slot_list) - slot_list_np = np.array(slot_list) - slot_mapping_npu = torch.from_numpy(slot_list_np).to(torch.int32).npu() - - key_expect = cal_slot(key, key_cache, slot_mapping_npu, block_size) - - warm_up = 0 - for _ in range(warm_up): - torch_npu._npu_reshape_and_cache_siso(key, key_cache, slot_mapping_npu) - N = 101 - - for _ in range(N): - torch_npu._npu_reshape_and_cache_siso(key, key_cache, slot_mapping_npu) - - torch.testing.assert_close(key_expect, key_cache, atol=0.001, rtol=0.1) - - -@pytest.mark.parametrize("num_tokens", [16]) # 6398 -@pytest.mark.parametrize("num_head", [1]) # 512 -@pytest.mark.parametrize("block_size", [128]) # 128 -@pytest.mark.parametrize("num_blocks", [1773]) # 1599 -@pytest.mark.parametrize("count", [1]) -def test_scatter(num_tokens, num_head, block_size, num_blocks, count): - head_size_k = 64 - key = torch.randint(low=0, high=128, size=(num_tokens, num_head, head_size_k), dtype=torch.int8).npu() - # key = torch.rand((num_tokens, num_head,head_size_k), dtype=torch.float16).npu() - - key_cache = torch.randint( - low=0, high=128, size=(num_blocks * block_size, num_head, head_size_k), dtype=torch.int8 - ).npu() - # key_cache = torch.rand((num_blocks* block_size, num_head,head_size_k), dtype=torch.float16).npu() - slot_list = [] - for i in range(0, num_tokens): - slot_list.append([2 + i]) - # slot_list.append(6+i) - assert num_tokens == len(slot_list) - slot_list_np = np.array(slot_list) - slot_mapping_npu = torch.from_numpy(slot_list_np).to(torch.int32).npu() - - key_expect = cal_scatternd(key, key_cache, slot_mapping_npu, block_size) - N = 101 - for i in range(N): - torch_npu.npu_scatter_nd_update_(key_cache, slot_mapping_npu, key) - torch.testing.assert_close(key_expect, key_cache, atol=0.001, rtol=0.1) - - -@pytest.mark.parametrize("num_tokens", [16]) # 6398 -@pytest.mark.parametrize("num_head", [1]) # 512 -@pytest.mark.parametrize("block_size", [128]) # 128 -@pytest.mark.parametrize("num_blocks", [1773]) # 1599 -@pytest.mark.parametrize("count", [1]) -def test_myops(num_tokens, num_head, block_size, num_blocks, count): - head_size_k = 64 - # key_cache = torch.rand((num_blocks, block_size, num_head,head_size_k), dtype=torch.float16) - key_cache = torch.randint(low=0, high=128, size=(num_blocks, block_size, num_head, head_size_k), dtype=torch.int8) - key_cache_npu = key_cache.npu() - - slot_list = [] - for i in range(0, num_tokens): - slot_list.append(2 + i) - - slot_list_np = np.array(slot_list) - slot_mapping_npu = torch.from_numpy(slot_list_np).to(torch.int32).npu() - # slot_mapping_cpu = slot_mapping_npu.to("cpu",non_blocking=True) - # num_draft_tensor = slot_mapping_npu.to("cpu", non_blocking=True) - slot_mapping_cpu = torch.empty_like(slot_mapping_npu, device="cpu").pin_memory() - slot_mapping_cpu.copy_(slot_mapping_npu, non_blocking=True) - - # key = torch.rand((num_tokens, num_head,head_size_k), dtype=torch.float16) - key = torch.randint(low=0, high=128, size=(num_tokens, head_size_k), dtype=torch.int8) - key_npu = key.npu() - key_expect = cal_slot(key_npu, key_cache_npu, slot_list_np, block_size) - - time.sleep(0.1) - - slot_mapping_list = slot_mapping_cpu.tolist() - warm_up = 0 - for _ in range(warm_up): - group_len, group_key_idx, group_key_cache_idx = torch.ops._C_ascend.store_kv_block_pre( - slot_mapping_npu, slot_mapping_list, block_size - ) - torch.ops._C_ascend.store_kv_block( - key_npu, key_cache_npu, group_len, group_key_idx, group_key_cache_idx, block_size - ) - N = 101 - for zt_i in range(N): - group_len, group_key_idx, group_key_cache_idx = torch.ops._C_ascend.store_kv_block_pre( - slot_mapping_npu, slot_mapping_list, block_size - ) - torch.ops._C_ascend.store_kv_block( - key_npu, key_cache_npu, group_len, group_key_idx, group_key_cache_idx, block_size - ) - - torch.testing.assert_close(key_expect, key_cache_npu, atol=0.001, rtol=0.1) - - gc.collect() - torch.npu.empty_cache() - torch.npu.reset_peak_memory_stats() diff --git a/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_fused_qkvzba_split_reshape_cat.py b/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_fused_qkvzba_split_reshape_cat.py index d7b2d6e0d..fbd1dfeb9 100644 --- a/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_fused_qkvzba_split_reshape_cat.py +++ b/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_fused_qkvzba_split_reshape_cat.py @@ -3,7 +3,7 @@ import pytest import torch from einops import rearrange -from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention # type: ignore[import-not-found] +from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention from vllm_ascend.ops.triton.fla.fused_qkvzba_split_reshape import fused_qkvzba_split_reshape_cat diff --git a/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_penality.py b/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_penality.py index 7a0a7a948..92ed33aa1 100644 --- a/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_penality.py +++ b/tests/e2e/nightly/single_node/ops/singlecard_ops/triton/test_penality.py @@ -5,14 +5,13 @@ from vllm_ascend.worker.v2.sample.penalties import apply_penalties -NUM_TOKENS = [1, 4] -VOCAB_SIZE = [1000] -NUM_STATUS = [1, 4] -NUM_SPECULATIVE_TOKENS = [0, 1, 3] DTYPES = [torch.bfloat16, torch.float16] -SEEDS = [42] +NUM_TOKENS = [2, 4, 8] +VOCAB_SIZE = [151936] +NUM_STATUS = [1, 4, 8, 16] +SEEDS = [0] DEVICES = [f"npu:{0}"] - +NUM_SPECULATIVE_TOKENS = [0, 1, 3] DEFAULT_ATOL = 1e-3 DEFAULT_RTOL = 1e-3 @@ -27,6 +26,7 @@ def pytorch_apply_penalties( presence_penalty: torch.Tensor, prompt_bin_mask: torch.Tensor, output_bin_counts: torch.Tensor, + num_speculative_tokens: int, ) -> torch.Tensor: """ Pytorch equivalent implementation @@ -45,8 +45,6 @@ def pytorch_apply_penalties( for state_idx in range(num_status): for packed_idx in range(num_packed): packed_val = prompt_bin_mask[state_idx, packed_idx].item() - if packed_val == 0: - continue start_idx = packed_idx * 32 end_idx = min(start_idx + 32, vocab_size) @@ -54,8 +52,6 @@ def pytorch_apply_penalties( if (packed_val >> bit_pos) & 1: prompt_masks_unpacked[state_idx, start_idx + bit_pos] = True - start_idx_in_batch = torch.arange(num_tokens, device=device) - expanded_local_pos - for token_idx in range(num_tokens): req_state_idx = idx_mapping[token_idx].item() @@ -72,33 +68,33 @@ def pytorch_apply_penalties( continue current_prompt_mask = prompt_masks_unpacked[req_state_idx] - base_counts = output_bin_counts[req_state_idx].clone() + base_output_counts = output_bin_counts[req_state_idx] # Compute cumulative draft counts pos = expanded_local_pos[token_idx].item() + start_idx_in_batch = token_idx - pos draft_counts = torch.zeros(vocab_size, device=device, dtype=torch.int32) - for prev_pos in range(pos): - prev_token_idx = start_idx_in_batch[token_idx] + prev_pos + 1 - if 0 <= prev_token_idx < num_tokens: - prev_token = token_ids[prev_token_idx].item() - if 0 <= prev_token < vocab_size: - draft_counts[prev_token] += 1 + for prev_pos in range(num_speculative_tokens): + if prev_pos < pos: + prev_token = token_ids[start_idx_in_batch + prev_pos + 1].item() + draft_counts[prev_token] += 1 # Total counts = base output counts + cumulative draft counts - total_counts = base_counts + draft_counts - output_bin_mask = total_counts > 0 + total_output_counts = base_output_counts + draft_counts + output_bin_mask = total_output_counts > 0 if use_rep_penalty: - need_scale = current_prompt_mask | output_bin_mask - scale = torch.where(need_scale, rep_penalty, 1.0) + scale = torch.ones(vocab_size, device=device) + mask = current_prompt_mask | output_bin_mask + scale[mask] = rep_penalty pos_mask = logits_float[token_idx] > 0 scale_factor = torch.where(pos_mask, 1.0 / scale, scale) logits_float[token_idx] *= scale_factor if use_freq_penalty: - logits_float[token_idx] -= freq_penalty * total_counts.float() + logits_float[token_idx] -= freq_penalty * total_output_counts.float() if use_pres_penalty: logits_float[token_idx] -= pres_penalty * output_bin_mask.float() @@ -150,7 +146,7 @@ def create_test_data( for state_idx in range(num_status): num_tokens_in_prompt = max(1, vocab_size // 20) - prompt_tokens = torch.randperm(vocab_size, device=device)[:num_tokens_in_prompt] + prompt_tokens = torch.randperm(vocab_size)[:num_tokens_in_prompt] for token_id in prompt_tokens: packed_idx = token_id // 32 @@ -160,8 +156,8 @@ def create_test_data( output_bin_counts = torch.zeros(num_status, vocab_size, device=device, dtype=torch.int32) for state_idx in range(num_status): num_output_tokens = max(1, vocab_size // 20) - output_tokens = torch.randint(0, vocab_size, (num_output_tokens,), device=device) - counts = torch.randint(1, 10, (num_output_tokens,), device=device) + output_tokens = torch.randint(0, vocab_size, (num_output_tokens,)) + counts = torch.randint(1, 10, (num_output_tokens,)) for token, count in zip(output_tokens, counts): output_bin_counts[state_idx, token] = count @@ -176,71 +172,78 @@ def create_test_data( presence_penalty, prompt_bin_mask, output_bin_counts, + num_speculative_tokens, + ) + + +@pytest.mark.skip( + reason="The test case failed and took one hour. Yang Cheng \ + has been notified to fix it after the holiday." +) +@pytest.mark.parametrize("num_tokens", NUM_TOKENS) +@pytest.mark.parametrize("vocab_size", VOCAB_SIZE) +@pytest.mark.parametrize("num_status", NUM_STATUS) +@pytest.mark.parametrize("num_speculative_tokens", NUM_SPECULATIVE_TOKENS) +@pytest.mark.parametrize("dtype", DTYPES) +@pytest.mark.parametrize("seed", SEEDS) +@pytest.mark.parametrize("device", DEVICES) +@torch.inference_mode() +def test_apply_penalties(num_tokens, vocab_size, num_status, num_speculative_tokens, dtype, seed, device): + ( + logits_triton, + idx_mapping, + token_ids, + expanded_local_pos, + repetition_penalty, + frequency_penalty, + presence_penalty, + prompt_bin_mask, + output_bin_counts, + num_spec_tokens, + ) = create_test_data( + num_tokens=num_tokens, + vocab_size=vocab_size, + num_status=num_status, + num_speculative_tokens=num_speculative_tokens, + device=device, + dtype=dtype, + seed=seed, ) + logits_pytorch = logits_triton.clone() + + apply_penalties( + logits_triton, + idx_mapping, + token_ids, + expanded_local_pos, + repetition_penalty, + frequency_penalty, + presence_penalty, + prompt_bin_mask, + output_bin_counts, + num_spec_tokens, + ) + + logits_pytorch_result = pytorch_apply_penalties( + logits_pytorch, + idx_mapping, + token_ids, + expanded_local_pos, + repetition_penalty, + frequency_penalty, + presence_penalty, + prompt_bin_mask, + output_bin_counts, + num_spec_tokens, + ) -class TestApplyPenalties: - @pytest.mark.parametrize("num_tokens", NUM_TOKENS) - @pytest.mark.parametrize("vocab_size", VOCAB_SIZE) - @pytest.mark.parametrize("num_status", NUM_STATUS) - @pytest.mark.parametrize("num_speculative_tokens", NUM_SPECULATIVE_TOKENS) - @pytest.mark.parametrize("dtype", DTYPES) - @pytest.mark.parametrize("seed", SEEDS) - @pytest.mark.parametrize("device", DEVICES) - @torch.inference_mode() - def test_apply_penalties(self, num_tokens, vocab_size, num_status, num_speculative_tokens, dtype, seed, device): - ( - logits_triton, - idx_mapping, - token_ids, - expanded_local_pos, - repetition_penalty, - frequency_penalty, - presence_penalty, - prompt_bin_mask, - output_bin_counts, - ) = create_test_data( - num_tokens=num_tokens, - vocab_size=vocab_size, - num_status=num_status, - num_speculative_tokens=num_speculative_tokens, - device=device, - dtype=dtype, - seed=seed, - ) - - logits_pytorch = logits_triton.clone() - - apply_penalties( - logits_triton, - idx_mapping, - token_ids, - expanded_local_pos, - repetition_penalty, - frequency_penalty, - presence_penalty, - prompt_bin_mask, - output_bin_counts, - ) - - logits_pytorch_result = pytorch_apply_penalties( - logits_pytorch, - idx_mapping, - token_ids, - expanded_local_pos, - repetition_penalty, - frequency_penalty, - presence_penalty, - prompt_bin_mask, - output_bin_counts, - ) - - atol = DEFAULT_ATOL - rtol = DEFAULT_RTOL - if dtype == torch.bfloat16: - atol = 1e-02 - rtol = 1e-02 - assert torch.allclose(logits_triton, logits_pytorch_result, atol=atol, rtol=rtol) - gc.collect() - torch.npu.empty_cache() - torch.npu.reset_peak_memory_stats() + atol = DEFAULT_ATOL + rtol = DEFAULT_RTOL + if dtype == torch.bfloat16: + atol = 1e-02 + rtol = 1e-02 + assert torch.allclose(logits_triton, logits_pytorch_result, atol=atol, rtol=rtol) + gc.collect() + torch.npu.empty_cache() + torch.npu.reset_peak_memory_stats() diff --git a/tests/e2e/pull_request/four_card/_310p/test_vl_model_310p.py b/tests/e2e/pull_request/four_card/_310p/test_vl_model_310p.py index d060eebb2..d2d259cc5 100644 --- a/tests/e2e/pull_request/four_card/_310p/test_vl_model_310p.py +++ b/tests/e2e/pull_request/four_card/_310p/test_vl_model_310p.py @@ -28,7 +28,7 @@ from tests.e2e.pull_request.utils_310p import run_vl_model_test -@wait_until_npu_memory_free(target_free_percentage=0.7) +@wait_until_npu_memory_free(target_free_percentage=0.95) def test_qwen3_vl_8b_tp2_fp16(): """Qwen3-VL-8B dual-card FP16 test""" run_vl_model_test(model_name="Qwen/Qwen3-VL-8B-Instruct", tensor_parallel_size=2, max_tokens=5) diff --git a/tests/e2e/pull_request/four_card/long_sequence/test_basic.py b/tests/e2e/pull_request/four_card/long_sequence/test_basic.py index 2a267bf63..632d1fca4 100644 --- a/tests/e2e/pull_request/four_card/long_sequence/test_basic.py +++ b/tests/e2e/pull_request/four_card/long_sequence/test_basic.py @@ -20,10 +20,13 @@ from pathlib import Path from unittest.mock import patch +import pytest +import torch from PIL import Image from vllm import SamplingParams from tests.e2e.conftest import VllmRunner, wait_until_npu_memory_free +from vllm_ascend.utils import vllm_version_is os.environ["HCCL_BUFFSIZE"] = "768" @@ -111,11 +114,17 @@ def test_models_pcp_dcp_basic(): @patch.dict( os.environ, { + "VLLM_ASCEND_APPLY_DSV4_PATCH": "1", "VLLM_ASCEND_ENABLE_FLASHCOMM1": "1", "PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True", }, ) @wait_until_npu_memory_free() +@pytest.mark.skipif( + torch.npu.device_count() < 4, + reason="DeepSeek V4 DSA CP e2e test requires at least 4 NPUs.", +) +@pytest.mark.skipif(not vllm_version_is("0.20.2"), reason="broken in main") def test_deepseek_v4_w4a8_dsa_cp_basic_greedy(): prompts = [ "Hello, my name is", @@ -144,6 +153,7 @@ def test_deepseek_v4_w4a8_dsa_cp_basic_greedy(): additional_config={ "enable_flashcomm1": True, "enable_dsa_cp": True, + "multistream_dsa_preprocess": True, }, ) as runner: outputs = runner.generate_greedy(prompts, max_tokens) @@ -387,6 +397,10 @@ def test_dcp_piece_wise(): }, ) @wait_until_npu_memory_free() +@pytest.mark.skipif( + torch.npu.device_count() < 4, + reason="Qwen3-VL-8B-Instruct multimodal test requires at least 4 NPUs.", +) def test_qwen3_vl_8b_multimodal_single_and_multi_image(): image = Image.open(QWEN_IMAGE_PATH).convert("RGB") @@ -455,6 +469,10 @@ def test_qwen3_vl_8b_multimodal_single_and_multi_image(): }, ) @wait_until_npu_memory_free() +@pytest.mark.skipif( + torch.npu.device_count() < 4, + reason="Qwen3.5-4B multimodal test requires at least 4 NPUs.", +) def test_qwen3_5_4b_multimodal_single_and_multi_image(): image_1 = Image.open(QWEN_IMAGE_PATH).convert("RGB") image_2 = Image.open(QWEN_IMAGE_PATH).convert("RGB") diff --git a/tests/e2e/pull_request/four_card/long_sequence/test_mtp.py b/tests/e2e/pull_request/four_card/long_sequence/test_mtp.py index 97af29708..5400f6324 100644 --- a/tests/e2e/pull_request/four_card/long_sequence/test_mtp.py +++ b/tests/e2e/pull_request/four_card/long_sequence/test_mtp.py @@ -19,8 +19,6 @@ import os -import pytest - from tests.e2e.conftest import VllmRunner, wait_until_npu_memory_free os.environ["HCCL_BUFFSIZE"] = "512" @@ -59,7 +57,6 @@ def test_pcp_dcp_mtp1_eager(): runner.generate_greedy(prompts, 32) -@pytest.mark.skip(reason="Skip for now, test failed") @wait_until_npu_memory_free() def test_pcp_dcp_mtp3_eager(): with VllmRunner( diff --git a/tests/e2e/pull_request/four_card/test_data_parallel_tp2.py b/tests/e2e/pull_request/four_card/test_data_parallel_tp2.py index a9c75c47d..20c33491e 100644 --- a/tests/e2e/pull_request/four_card/test_data_parallel_tp2.py +++ b/tests/e2e/pull_request/four_card/test_data_parallel_tp2.py @@ -6,18 +6,14 @@ import pytest -from tests.e2e.conftest import wait_until_npu_memory_free - MODELS = ["Qwen/Qwen3-30B-A3B"] REPO_ROOT = Path(__file__).resolve().parents[4] DATA_PARALLEL_SCRIPT = REPO_ROOT / "examples" / "offline_data_parallel.py" -@pytest.mark.skip(reason="broken, fix me") @pytest.mark.parametrize("model", MODELS) @pytest.mark.parametrize("max_tokens", [32]) @patch.dict(os.environ, {"ASCEND_RT_VISIBLE_DEVICES": "0,1,2,3"}) -@wait_until_npu_memory_free(target_free_percentage=0.7) def test_qwen3_inference_dp2_tp2(model, max_tokens): env = os.environ.copy() diff --git a/tests/e2e/pull_request/four_card/test_deepseek_v4.py b/tests/e2e/pull_request/four_card/test_deepseek_v4.py index cc3754b3c..b209005c4 100644 --- a/tests/e2e/pull_request/four_card/test_deepseek_v4.py +++ b/tests/e2e/pull_request/four_card/test_deepseek_v4.py @@ -28,6 +28,7 @@ @patch.dict( os.environ, { + "VLLM_ASCEND_APPLY_DSV4_PATCH": "1", "VLLM_ASCEND_ENABLE_FLASHCOMM1": "1", }, ) @@ -36,6 +37,7 @@ def test_deepseek_v4_w4a8_tp4_basic_greedy(): """Verify DeepSeek V4 W4A8 basic greedy generation with TP4 and EP.""" example_prompts = [ "Hello, my name is", + "The capital of France is", "What is the meaning of life?", ] max_tokens = 5 @@ -55,23 +57,19 @@ def test_deepseek_v4_w4a8_tp4_basic_greedy(): compilation_config={ "cudagraph_mode": "FULL_DECODE_ONLY", }, - speculative_config={"num_speculative_tokens": 1, "method": "mtp", "enforce_eager": True}, ) as vllm_model: outputs = vllm_model.generate_greedy(example_prompts, max_tokens) - expected_token_ids = [ - [19923, 14, 1026, 2329, 344, 680, 2852, 95, 305, 342], - [3085, 344, 270, 5281, 294, 1988, 33, 3955, 361, 582, 3085, 344], - ] + assert len(outputs) == len(example_prompts) - for i, (output_ids, output_str) in enumerate(outputs): + for output_ids, output_str in outputs: assert len(output_str) > 0 assert len(output_ids) > 0 - assert output_ids == expected_token_ids[i] @patch.dict( os.environ, { + "VLLM_ASCEND_APPLY_DSV4_PATCH": "1", "VLLM_ASCEND_ENABLE_FLASHCOMM1": "1", }, ) diff --git a/tests/e2e/pull_request/four_card/test_qwen3_next.py b/tests/e2e/pull_request/four_card/test_qwen3_next.py index 946e9bda8..c624e713c 100644 --- a/tests/e2e/pull_request/four_card/test_qwen3_next.py +++ b/tests/e2e/pull_request/four_card/test_qwen3_next.py @@ -106,7 +106,7 @@ def test_qwen3_next_distributed_mp_graph_mode_tp4(): "Qwen/Qwen3-Next-80B-A3B-Instruct", tensor_parallel_size=4, max_model_len=4096, - gpu_memory_utilization=0.8, + gpu_memory_utilization=0.7, distributed_executor_backend="mp", enable_expert_parallel=True, enforce_eager=False, diff --git a/tests/e2e/pull_request/one_card/_310p/test_dense_model_310p.py b/tests/e2e/pull_request/one_card/_310p/test_dense_model_310p.py index f6c87e467..6b2692978 100644 --- a/tests/e2e/pull_request/one_card/_310p/test_dense_model_310p.py +++ b/tests/e2e/pull_request/one_card/_310p/test_dense_model_310p.py @@ -17,55 +17,6 @@ from tests.e2e.conftest import VllmRunner, wait_until_npu_memory_free -from tests.e2e.model_utils import check_outputs_equal - -QWEN3_5_PREFIX_MAMBA_PROMPT = ( - "You are reading a compact synthetic operations ledger. " - "Use only the rows below when answering the final question.\n" - + "\n".join( - f"Row {i}: route R{i:03d} moves cargo from zone {i % 11} to zone {(i * 7) % 13}; priority is {i % 5}." - for i in range(64) - ) - + "\n" -) - -QWEN3_5_PREFIX_MAMBA_PROMPTS = [ - QWEN3_5_PREFIX_MAMBA_PROMPT + "Question: What route is listed in row 17? Answer briefly.", - QWEN3_5_PREFIX_MAMBA_PROMPT + "Question: What priority is listed in row 42? Answer briefly.", -] - - -def _generate_qwen3_5_prefix_mamba_outputs(enable_prefix_caching: bool) -> list[tuple[list[int], str]]: - outputs: list[tuple[list[int], str]] = [] - - if enable_prefix_caching: - with VllmRunner( - "Qwen/Qwen3.5-4B", - tensor_parallel_size=1, - enforce_eager=True, - dtype="float16", - max_model_len=2048, - max_num_batched_tokens=2048, - enable_prefix_caching=True, - mamba_cache_mode="align", - mamba_ssm_cache_dtype="float16", - ) as vllm_model: - for prompt in QWEN3_5_PREFIX_MAMBA_PROMPTS: - outputs.extend(vllm_model.generate_greedy([prompt], max_tokens=8)) - else: - with VllmRunner( - "Qwen/Qwen3.5-4B", - tensor_parallel_size=1, - enforce_eager=True, - dtype="float16", - max_model_len=2048, - max_num_batched_tokens=2048, - enable_prefix_caching=False, - mamba_ssm_cache_dtype="float16", - ) as vllm_model: - for prompt in QWEN3_5_PREFIX_MAMBA_PROMPTS: - outputs.extend(vllm_model.generate_greedy([prompt], max_tokens=8)) - return outputs def test_qwen3_dense_tp1_fp16(): @@ -136,20 +87,6 @@ def test_qwen3_5_dense_tp1_fp16(): vllm_model.generate_greedy(example_prompts, max_tokens) -@wait_until_npu_memory_free(0.7) -def test_qwen3_5_dense_prefix_mamba_cache_tp1_fp16(): - prefix_cache_outputs = _generate_qwen3_5_prefix_mamba_outputs(enable_prefix_caching=True) - no_prefix_cache_outputs = _generate_qwen3_5_prefix_mamba_outputs(enable_prefix_caching=False) - - assert len(prefix_cache_outputs) == len(no_prefix_cache_outputs) == len(QWEN3_5_PREFIX_MAMBA_PROMPTS) - check_outputs_equal( - outputs_0_lst=no_prefix_cache_outputs, - outputs_1_lst=prefix_cache_outputs, - name_0="no_prefix_cache_outputs", - name_1="prefix_cache_outputs", - ) - - @wait_until_npu_memory_free(0.7) def test_qwen3_5_dense_tp1_fp16_aclgraph(): example_prompts = [ diff --git a/tests/e2e/pull_request/one_card/_310p/test_spec_decode_mtp_310p.py b/tests/e2e/pull_request/one_card/_310p/test_spec_decode_mtp_310p.py deleted file mode 100644 index 3c86a83ed..000000000 --- a/tests/e2e/pull_request/one_card/_310p/test_spec_decode_mtp_310p.py +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# Copyright 2023 The vLLM team. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. - -from tests.e2e.conftest import VllmRunner - - -def test_qwen3_5_mtp_tp1_eager(): - example_prompts = ["Hello, my name is"] - with VllmRunner( - "Qwen/Qwen3.5-4B", - tensor_parallel_size=1, - enforce_eager=True, - dtype="float16", - max_model_len=2048, - mamba_ssm_cache_dtype="float16", - speculative_config={ - "method": "qwen3_5_mtp", - "num_speculative_tokens": 1, - }, - ) as vllm_model: - vllm_model.generate_greedy(example_prompts, max_tokens=8) diff --git a/tests/e2e/pull_request/one_card/_310p/test_vl_model_310p.py b/tests/e2e/pull_request/one_card/_310p/test_vl_model_310p.py index 76d0c3802..464ddf5dc 100644 --- a/tests/e2e/pull_request/one_card/_310p/test_vl_model_310p.py +++ b/tests/e2e/pull_request/one_card/_310p/test_vl_model_310p.py @@ -28,7 +28,7 @@ from tests.e2e.pull_request.utils_310p import run_vl_model_test -@wait_until_npu_memory_free(target_free_percentage=0.7) +@wait_until_npu_memory_free(target_free_percentage=0.95) def test_qwen3_vl_8b_tp1_fp16(): """Qwen3-VL-8B single-card FP16 test""" run_vl_model_test(model_name="Qwen/Qwen3-VL-8B-Instruct", tensor_parallel_size=1, max_tokens=5) diff --git a/tests/e2e/pull_request/one_card/aclgraph/test_aclgraph_mem.py b/tests/e2e/pull_request/one_card/aclgraph/test_aclgraph_mem.py index 447f8dc62..4c193019f 100644 --- a/tests/e2e/pull_request/one_card/aclgraph/test_aclgraph_mem.py +++ b/tests/e2e/pull_request/one_card/aclgraph/test_aclgraph_mem.py @@ -24,7 +24,6 @@ from vllm import SamplingParams from tests.e2e.conftest import VllmRunner -from tests.e2e.utils import fork_new_process_for_each_test from vllm_ascend.worker.model_runner_v1 import NPUModelRunner MODELS = ["Qwen/Qwen3-0.6B", "vllm-ascend/DeepSeek-V2-Lite-W8A8"] @@ -32,11 +31,10 @@ @pytest.mark.parametrize("model", MODELS) @pytest.mark.parametrize("max_tokens", [4]) -@fork_new_process_for_each_test -@patch.dict(os.environ, {"VLLM_ENABLE_V1_MULTIPROCESSING": "0"}) @patch.dict(os.environ, {"VLLM_ASCEND_FLASHCOMM2_PARALLEL_SIZE": "0"}) @patch.dict(os.environ, {"ASCEND_RT_VISIBLE_DEVICES": "0,1"}) def test_aclgraph_mem_use(model: str, max_tokens: int) -> None: + del os.environ["VLLM_WORKER_MULTIPROC_METHOD"] capture_called = multiprocessing.Value("i", 0) # int, 0 or 1 capture_mem_before = multiprocessing.Value("q", -1) # long long (64-bit) capture_mem_after = multiprocessing.Value("q", -1) # long long @@ -65,19 +63,18 @@ def wrapped(self): ] sampling_params = SamplingParams(max_tokens=max_tokens, temperature=0.0) if model == "vllm-ascend/DeepSeek-V2-Lite-W8A8": - with VllmRunner( + vllm_model = VllmRunner( model, max_model_len=1024, quantization="ascend", compilation_config={"cudagraph_mode": "PIECEWISE"}, - ) as vllm_model: - _ = vllm_model.generate(prompts, sampling_params) + ) else: - with VllmRunner( + vllm_model = VllmRunner( model, compilation_config={"cudagraph_mode": "PIECEWISE"}, - ) as vllm_model: - _ = vllm_model.generate(prompts, sampling_params) + ) + _ = vllm_model.generate(prompts, sampling_params) assert capture_called.value == 1, "capture_model was not called during test" assert capture_mem_before.value != -1, "capture_mem_before not set" @@ -103,3 +100,4 @@ def wrapped(self): f"Used: {mem_used_by_capture / (1024**3):.2f} GiB, " f"Expected: < {max_capture_mem_gib:.2f} GiB" ) + os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" diff --git a/tests/e2e/pull_request/one_card/model_runner_v2/test_basic.py b/tests/e2e/pull_request/one_card/model_runner_v2/test_basic.py index 58c3b221a..99608cb50 100644 --- a/tests/e2e/pull_request/one_card/model_runner_v2/test_basic.py +++ b/tests/e2e/pull_request/one_card/model_runner_v2/test_basic.py @@ -29,11 +29,6 @@ MAIN_MODELS = ["LLM-Research/Meta-Llama-3.1-8B-Instruct"] EGALE_MODELS = ["vllm-ascend/EAGLE-LLaMA3.1-Instruct-8B"] -pytestmark = pytest.mark.skipif( - vllm_version_is("0.22.1"), - reason="v2 model runner patches not supported on v0.22.1", -) - @pytest.mark.skipif(True, reason="Fix me, it's broken after CANN and trition-ascend are upgraded.") @pytest.mark.parametrize("model", MODELS) @@ -70,6 +65,7 @@ def test_qwen3_dense_eager_mode( runner.model.generate(prompts, sampling_params) +@pytest.mark.skipif(vllm_version_is("0.20.2"), reason="no need to support model_runner for v0.20.2") @pytest.mark.parametrize("model", MAIN_MODELS) @pytest.mark.parametrize("eagle_model", EGALE_MODELS) @pytest.mark.parametrize("max_tokens", [32]) @@ -115,6 +111,7 @@ def test_egale_spec_decoding( runner.model.generate(prompts, sampling_params) +@pytest.mark.skipif(vllm_version_is("0.20.2"), reason="no need to support model_runner for v0.20.2") @pytest.mark.parametrize("model", MODELS) @pytest.mark.parametrize("max_tokens", [32]) @pytest.mark.parametrize("enforce_eager", [False]) diff --git a/tests/e2e/pull_request/one_card/pooling/test_classification.py b/tests/e2e/pull_request/one_card/pooling/test_classification.py index 86f61a0d6..6e3ce16b6 100644 --- a/tests/e2e/pull_request/one_card/pooling/test_classification.py +++ b/tests/e2e/pull_request/one_card/pooling/test_classification.py @@ -3,15 +3,9 @@ from modelscope import snapshot_download # type: ignore[import-untyped] from transformers import AutoModelForSequenceClassification -from tests.e2e.conftest import ( - HfRunner, - VllmRunner, - cleanup_dist_env_and_memory, - wait_until_npu_memory_free, -) +from tests.e2e.conftest import HfRunner, VllmRunner -@wait_until_npu_memory_free(target_free_percentage=0.7) def test_qwen_pooling_classify_correctness() -> None: model_name = snapshot_download( "Howeee/Qwen2.5-1.5B-apeach", @@ -24,11 +18,6 @@ def test_qwen_pooling_classify_correctness() -> None: "The capital of France is", "The future of AI is what", ] - - with HfRunner(model_name, dtype="float32", auto_cls=AutoModelForSequenceClassification) as hf_runner: - hf_outputs = hf_runner.classify(prompts) - cleanup_dist_env_and_memory() - with VllmRunner( model_name, runner="pooling", @@ -37,6 +26,9 @@ def test_qwen_pooling_classify_correctness() -> None: ) as vllm_runner: vllm_outputs = vllm_runner.classify(prompts) + with HfRunner(model_name, dtype="float32", auto_cls=AutoModelForSequenceClassification) as hf_runner: + hf_outputs = hf_runner.classify(prompts) + for hf_output, vllm_output in zip(hf_outputs, vllm_outputs): hf_output = torch.tensor(hf_output) vllm_output = torch.tensor(vllm_output) diff --git a/tests/e2e/pull_request/one_card/spec_decode/test_extract_hidden_states.py b/tests/e2e/pull_request/one_card/spec_decode/test_extract_hidden_states.py index 53d73fbbd..e38f98606 100644 --- a/tests/e2e/pull_request/one_card/spec_decode/test_extract_hidden_states.py +++ b/tests/e2e/pull_request/one_card/spec_decode/test_extract_hidden_states.py @@ -13,23 +13,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""E2E tests for the extract_hidden_states speculative decoding method. +"""E2E tests for extract_hidden_states speculative decoding method. -Follows the pattern from vllm's test_extraction.py, validating that hidden -states are correctly extracted and saved on the Ascend NPU. Parametrized over: - -* a dense model (Qwen3-8B) in both eager and ACL graph modes, using real - weights so outputs can be checked to be non-zero, and -* a hybrid attention model (Qwen3.5-0.8B, GatedDeltaNet + full_attention) - loaded with dummy weights as a shape/round-trip smoke test. The hybrid case - mirrors upstream vLLM PR #39949. +This test file follows the pattern from vllm's test_extraction.py, +testing that hidden states are correctly extracted and saved. """ from __future__ import annotations import os import tempfile -from dataclasses import dataclass import pytest import torch @@ -38,88 +31,14 @@ os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" -DENSE_MODEL = "Qwen/Qwen3-8B" -# Qwen3-8B has 36 layers; pick a spread of layer indices to extract. -DENSE_AUX_HIDDEN_STATE_LAYER_IDS = [2, 18, 34] - -HYBRID_MODEL = "Qwen/Qwen3.5-0.8B" -HYBRID_AUX_HIDDEN_STATE_LAYER_IDS = [5, 11, 17] - - -@dataclass -class ExtractHiddenStatesCase: - model_name: str - aux_hidden_state_layer_ids: list[int] - prompts: list[str] - enforce_eager: bool - # ``None`` means "do not pass the argument", preserving each model's - # original defaults. - gpu_memory_utilization: float | None = None - max_num_seqs: int | None = None - max_model_len: int | None = None - load_format: str | None = None - # Dummy-weight runs can't assert non-zero outputs; real-weight runs can. - verify_nonzero: bool = True - # Hybrid smoke test additionally checks the token_ids round-trip. - verify_token_ids: bool = False - - -CASES = [ - pytest.param( - ExtractHiddenStatesCase( - model_name=DENSE_MODEL, - aux_hidden_state_layer_ids=DENSE_AUX_HIDDEN_STATE_LAYER_IDS, - prompts=[ - "Hello, how are you?", - "What is machine learning?", - "Explain quantum computing briefly.", - ], - enforce_eager=True, - gpu_memory_utilization=0.8, - max_num_seqs=16, - ), - id="dense_eager", - ), - pytest.param( - ExtractHiddenStatesCase( - model_name=DENSE_MODEL, - aux_hidden_state_layer_ids=DENSE_AUX_HIDDEN_STATE_LAYER_IDS, - prompts=[ - "Hello, how are you?", - "What is machine learning?", - ], - enforce_eager=False, - max_num_seqs=16, - ), - id="dense_aclgraph", - ), - pytest.param( - ExtractHiddenStatesCase( - model_name=HYBRID_MODEL, - aux_hidden_state_layer_ids=HYBRID_AUX_HIDDEN_STATE_LAYER_IDS, - prompts=[ - "Hello world", - "Test prompt with several tokens", - ], - enforce_eager=True, - gpu_memory_utilization=0.4, - max_model_len=256, - load_format="dummy", - verify_nonzero=False, - verify_token_ids=True, - ), - id="hybrid_dummy_eager", - ), -] +# Use Qwen3-8B as the test model (standard HuggingFace format) +MODEL_NAME = "Qwen/Qwen3-8B" +# Layer indices for hidden state extraction (Qwen3-8B has 36 layers) +EAGLE_AUX_HIDDEN_STATE_LAYER_IDS = [2, 18, 34] -@pytest.fixture -def sampling_config(): - return SamplingParams(temperature=0, max_tokens=1) - - -def _verify_output(output, expected_shape, *, verify_nonzero, verify_token_ids): - """Verify a single hidden-states dump (matches vllm's check pattern).""" +def _verify_output(output, expected_shape): + """Verify hidden states output (matches vllm's get_and_check_output pattern).""" assert output.kv_transfer_params is not None hidden_states_path = output.kv_transfer_params.get("hidden_states_path") assert hidden_states_path is not None @@ -128,32 +47,42 @@ def _verify_output(output, expected_shape, *, verify_nonzero, verify_token_ids): with safe_open(hidden_states_path, "pt") as f: tensor_names = f.keys() assert "hidden_states" in tensor_names + hidden_states = f.get_tensor("hidden_states") assert hidden_states.shape == expected_shape - if verify_token_ids: - token_ids = f.get_tensor("token_ids") - assert torch.equal(token_ids, torch.tensor(output.prompt_token_ids)) + # Verify hidden_states are not all zeros + assert not torch.allclose(hidden_states, torch.zeros_like(hidden_states)) - if verify_nonzero: - assert not torch.allclose(hidden_states, torch.zeros_like(hidden_states)) + return hidden_states -@pytest.mark.parametrize("case", CASES) -def test_extract_hidden_states(case: ExtractHiddenStatesCase, sampling_config): - """Extract hidden states from the target model and validate the dump.""" +@pytest.fixture +def sampling_config(): + return SamplingParams(temperature=0, max_tokens=1) + + +def test_extract_hidden_states_eager_mode(sampling_config): + """ + Test extract_hidden_states with enforce_eager=True. + + This extracts hidden states from the target model and saves them to disk. + Pattern matches vllm's test_extract_hidden_states_with_predictable_dummy_model. + """ with tempfile.TemporaryDirectory() as tmpdirname: - llm_kwargs = dict( - model=case.model_name, + llm = LLM( + model=MODEL_NAME, tensor_parallel_size=1, - enforce_eager=case.enforce_eager, + enforce_eager=True, + max_num_seqs=16, + gpu_memory_utilization=0.8, enable_chunked_prefill=False, speculative_config={ "method": "extract_hidden_states", "num_speculative_tokens": 1, "draft_model_config": { "hf_config": { - "eagle_aux_hidden_state_layer_ids": case.aux_hidden_state_layer_ids, + "eagle_aux_hidden_state_layer_ids": EAGLE_AUX_HIDDEN_STATE_LAYER_IDS, } }, }, @@ -165,29 +94,72 @@ def test_extract_hidden_states(case: ExtractHiddenStatesCase, sampling_config): }, }, ) - if case.gpu_memory_utilization is not None: - llm_kwargs["gpu_memory_utilization"] = case.gpu_memory_utilization - if case.max_num_seqs is not None: - llm_kwargs["max_num_seqs"] = case.max_num_seqs - if case.max_model_len is not None: - llm_kwargs["max_model_len"] = case.max_model_len - if case.load_format is not None: - llm_kwargs["load_format"] = case.load_format - - llm = LLM(**llm_kwargs) - - outputs = llm.generate(case.prompts, sampling_config) + + prompts = [ + "Hello, how are you?", + "What is machine learning?", + "Explain quantum computing briefly.", + ] + + outputs = llm.generate(prompts, sampling_config) hidden_size = llm.llm_engine.model_config.get_hidden_size() - num_layers = len(case.aux_hidden_state_layer_ids) + num_layers = len(EAGLE_AUX_HIDDEN_STATE_LAYER_IDS) - assert len(outputs) == len(case.prompts) + assert len(outputs) == len(prompts) for output in outputs: num_tokens = len(output.prompt_token_ids) expected_shape = (num_tokens, num_layers, hidden_size) - _verify_output( - output, - expected_shape, - verify_nonzero=case.verify_nonzero, - verify_token_ids=case.verify_token_ids, - ) + _verify_output(output, expected_shape) + + +def test_extract_hidden_states_aclgraph_mode(sampling_config): + """ + Test extract_hidden_states with enforce_eager=False (ACL graph mode). + + This tests that ACL graph capture works correctly with extract_hidden_states. + """ + with tempfile.TemporaryDirectory() as tmpdirname: + llm = LLM( + model=MODEL_NAME, + tensor_parallel_size=1, + enforce_eager=False, + max_num_seqs=16, + enable_chunked_prefill=False, + speculative_config={ + "method": "extract_hidden_states", + "num_speculative_tokens": 1, + "draft_model_config": { + "hf_config": { + "eagle_aux_hidden_state_layer_ids": EAGLE_AUX_HIDDEN_STATE_LAYER_IDS, + } + }, + }, + kv_transfer_config={ + "kv_connector": "ExampleHiddenStatesConnector", + "kv_role": "kv_producer", + "kv_connector_extra_config": { + "shared_storage_path": tmpdirname, + }, + }, + ) + + prompts = [ + "Hello, how are you?", + "What is machine learning?", + ] + + outputs = llm.generate(prompts, sampling_config) + hidden_size = llm.llm_engine.model_config.get_hidden_size() + num_layers = len(EAGLE_AUX_HIDDEN_STATE_LAYER_IDS) + + assert len(outputs) == len(prompts) + + hidden_states_count = 0 + for output in outputs: + num_tokens = len(output.prompt_token_ids) + expected_shape = (num_tokens, num_layers, hidden_size) + _verify_output(output, expected_shape) + hidden_states_count += 1 + + assert hidden_states_count > 0, "No hidden states were saved" diff --git a/tests/e2e/pull_request/one_card/spec_decode/utils.py b/tests/e2e/pull_request/one_card/spec_decode/utils.py index 687ef4f96..e23519ae5 100644 --- a/tests/e2e/pull_request/one_card/spec_decode/utils.py +++ b/tests/e2e/pull_request/one_card/spec_decode/utils.py @@ -3,6 +3,8 @@ import os from typing import Any +from vllm_ascend.utils import vllm_version_is + os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn" MODELS = { @@ -30,7 +32,11 @@ "eagle": [0.74, 0.44, 0.29], "eagle3": [0.68, 0.40, 0.18], "draft_parallel": [0.83, 0.50, 0.33, 0.17, 0.17, 0.17, 0.17, 0.00], - "dflash": [0.60, 0.50, 0.30, 0.20, 0.20, 0.10, 0.00, 0.00], + "dflash": ( + [0.67, 0.67, 0.44, 0.33, 0.11, 0.00, 0.00, 0.00] + if vllm_version_is("0.20.2") + else [0.60, 0.50, 0.30, 0.20, 0.20, 0.10, 0.00, 0.00] + ), } diff --git a/tests/e2e/pull_request/one_card/test_cpu_weight_offload.py b/tests/e2e/pull_request/one_card/test_cpu_weight_offload.py deleted file mode 100644 index f5c133280..000000000 --- a/tests/e2e/pull_request/one_card/test_cpu_weight_offload.py +++ /dev/null @@ -1,130 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# This file is a part of the vllm-ascend project. -# -"""End-to-end tests for CPU weight offloading on Ascend NPU. - -Covers both the prefetch backend (NPUPrefetchOffloader) and the UVA -backend (functional_call fallback path, since UVA is not available on -NPU hardware). Tests verify that offloading produces the same outputs -as the baseline (no offloading). -""" - -import os - -import pytest - -from tests.e2e.conftest import wait_until_npu_memory_free -from tests.e2e.pull_request.utils import PROMPTS_SHORT, compare_logprobs - -MODEL = "Qwen/Qwen3-0.6B" - - -# -------------------- Prefetch backend tests -------------------- - - -@wait_until_npu_memory_free() -def test_prefetch_offload_eager(): - """Test prefetch CPU offloading in eager mode. - - Compares outputs between: - 1. Baseline (eager, no offloading) - 2. Prefetch offloading (group_size=4, num_in_group=1) - with enforce_eager=True (no ACL graph capture) - """ - runner_kwargs = { - "model_name": MODEL, - "max_model_len": 512, - "enforce_eager": True, - "offload_backend": "prefetch", - "offload_group_size": 4, - "offload_num_in_group": 1, - } - compare_logprobs(runner_kwargs=runner_kwargs, prompts=PROMPTS_SHORT) - - -@wait_until_npu_memory_free() -def test_prefetch_offload_aclgraph(): - """Test prefetch CPU offloading with ACL graph capture. - - Compares outputs between: - 1. Baseline (eager, no offloading) - 2. Prefetch offloading (group_size=4, num_in_group=1) - with ACL graph capture enabled (default, non-eager) - """ - runner_kwargs = { - "model_name": MODEL, - "max_model_len": 512, - "cudagraph_capture_sizes": [1, 2, 4, 8], - "offload_backend": "prefetch", - "offload_group_size": 4, - "offload_num_in_group": 1, - } - compare_logprobs(runner_kwargs=runner_kwargs, prompts=PROMPTS_SHORT) - - -@wait_until_npu_memory_free() -def test_prefetch_offload_selective_params(): - """Test selective parameter offloading (MLP weights only). - - Only offloads gate_up_proj and down_proj parameters, leaving - attention weights on NPU. - """ - runner_kwargs = { - "model_name": MODEL, - "max_model_len": 512, - "enforce_eager": True, - "offload_backend": "prefetch", - "offload_group_size": 8, - "offload_num_in_group": 2, - "offload_prefetch_step": 1, - "offload_params": {"gate_up_proj", "down_proj"}, - } - compare_logprobs(runner_kwargs=runner_kwargs, prompts=PROMPTS_SHORT) - - -# -------------------- UVA backend tests -------------------- -# UVA (Unified Virtual Addressing) is not available on Ascend NPU, so -# the UVA offloader falls back to the functional_call path that moves -# weights to device on-demand. Tests below mirror the upstream -# test_cpu_offload.py parametrization but only exercise the non-UVA -# (functional_call) path with enforce_eager, as NPU does not support -# UVA zero-copy. - - -@pytest.mark.parametrize("disable_pin_memory", [False, True]) -@wait_until_npu_memory_free() -def test_uva_offload_functional_call(disable_pin_memory): - """Test UVA offloader's functional_call fallback on NPU. - - With UVA disabled (forced by env var), the UVA offloader falls back - to moving weights to device inside a functional_call wrapper. - enforce_eager is required because this fallback is incompatible - with graph capture. - - Parametrized over pin_memory to cover both pinned and unpinned - CPU storage paths. - """ - old_uva = os.environ.get("VLLM_WEIGHT_OFFLOADING_DISABLE_UVA") - old_pin = os.environ.get("VLLM_WEIGHT_OFFLOADING_DISABLE_PIN_MEMORY") - try: - os.environ["VLLM_WEIGHT_OFFLOADING_DISABLE_UVA"] = "1" - os.environ["VLLM_WEIGHT_OFFLOADING_DISABLE_PIN_MEMORY"] = str(int(disable_pin_memory)) - runner_kwargs = { - "model_name": MODEL, - "max_model_len": 512, - "enforce_eager": True, - "cpu_offload_gb": 1, - } - compare_logprobs(runner_kwargs=runner_kwargs, prompts=PROMPTS_SHORT) - finally: - if old_uva is None: - os.environ.pop("VLLM_WEIGHT_OFFLOADING_DISABLE_UVA", None) - else: - os.environ["VLLM_WEIGHT_OFFLOADING_DISABLE_UVA"] = old_uva - if old_pin is None: - os.environ.pop("VLLM_WEIGHT_OFFLOADING_DISABLE_PIN_MEMORY", None) - else: - os.environ["VLLM_WEIGHT_OFFLOADING_DISABLE_PIN_MEMORY"] = old_pin diff --git a/tests/e2e/pull_request/one_card/test_guided_decoding.py b/tests/e2e/pull_request/one_card/test_guided_decoding.py index b57bf93d7..c0ab404e8 100644 --- a/tests/e2e/pull_request/one_card/test_guided_decoding.py +++ b/tests/e2e/pull_request/one_card/test_guided_decoding.py @@ -38,7 +38,7 @@ @pytest.fixture(params=[False, True], ids=["v1", "v2"]) def model_runner_env(request): use_v2_model_runner = request.param - if use_v2_model_runner and vllm_version_is("0.22.1"): + if use_v2_model_runner and vllm_version_is("0.20.1"): pytest.skip("No need to support v2 model runner for vLLM tag version.") with patch.dict(os.environ, {"VLLM_USE_V2_MODEL_RUNNER": "1" if use_v2_model_runner else "0"}): diff --git a/tests/e2e/pull_request/two_card/test_data_parallel.py b/tests/e2e/pull_request/two_card/test_data_parallel.py index 4cd6663dc..1f60c0ba8 100644 --- a/tests/e2e/pull_request/two_card/test_data_parallel.py +++ b/tests/e2e/pull_request/two_card/test_data_parallel.py @@ -39,7 +39,7 @@ @pytest.mark.parametrize("max_tokens", [32]) @patch.dict(os.environ, {"ASCEND_RT_VISIBLE_DEVICES": "0,1"}) @patch.dict(os.environ, {"HCCL_BUFFSIZE": "1024"}) -@wait_until_npu_memory_free(target_free_percentage=0.7) +@wait_until_npu_memory_free(target_free_percentage=0.95) def test_qwen3_inference_dp2(model, max_tokens): moe_models = ["Qwen/Qwen3-30B-A3B", "vllm-ascend/Qwen3-30B-A3B-W8A8"] quantization_models = ["vllm-ascend/Qwen3-30B-A3B-W8A8"] diff --git a/tests/e2e/pull_request/two_card/test_external_launcher.py b/tests/e2e/pull_request/two_card/test_external_launcher.py index 226e6032c..d4c1dced3 100644 --- a/tests/e2e/pull_request/two_card/test_external_launcher.py +++ b/tests/e2e/pull_request/two_card/test_external_launcher.py @@ -80,7 +80,7 @@ def test_qwen3_external_launcher(model): @pytest.mark.parametrize("model", MOE_MODELS) -@wait_until_npu_memory_free(target_free_percentage=0.7) +@wait_until_npu_memory_free(target_free_percentage=0.95) def test_qwen3_moe_external_launcher_ep_tp2(model): env = os.environ.copy() # TODO: Change to 2 when ci machine has 4 cards @@ -119,7 +119,7 @@ def test_qwen3_moe_external_launcher_ep_tp2(model): @patch.dict(os.environ, {"VLLM_ASCEND_ENABLE_NZ": "0"}) -@wait_until_npu_memory_free(target_free_percentage=0.7) +@wait_until_npu_memory_free(target_free_percentage=0.95) def test_qwen3_external_launcher_with_sleepmode(): env = os.environ.copy() # TODO: Change to 2 when ci machine has 4 cards @@ -162,7 +162,7 @@ def test_qwen3_external_launcher_with_sleepmode(): @patch.dict(os.environ, {"VLLM_ASCEND_ENABLE_NZ": "0"}) -@wait_until_npu_memory_free(target_free_percentage=0.7) +@wait_until_npu_memory_free(target_free_percentage=0.95) def test_qwen3_external_launcher_with_sleepmode_level2(): env = os.environ.copy() model_path = snapshot_download( @@ -215,7 +215,7 @@ def test_qwen3_external_launcher_with_sleepmode_level2(): reason="This test is only for Ascend910B devices.", ) @pytest.mark.parametrize("model", MODELS) -@wait_until_npu_memory_free(target_free_percentage=0.7) +@wait_until_npu_memory_free(target_free_percentage=0.95) @patch.dict(os.environ, {"VLLM_ASCEND_ENABLE_MATMUL_ALLREDUCE": "1", "HCCL_BUFFSIZE": "500"}) def test_qwen3_external_launcher_with_matmul_allreduce(model): env = os.environ.copy() diff --git a/tests/e2e/pull_request/two_card/test_hccl_weight_transfer.py b/tests/e2e/pull_request/two_card/test_hccl_weight_transfer.py deleted file mode 100644 index 2012bc4f7..000000000 --- a/tests/e2e/pull_request/two_card/test_hccl_weight_transfer.py +++ /dev/null @@ -1,342 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# Copyright 2023 The vLLM team. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# -"""End-to-end test for the HCCL weight transfer engine. - -This test starts a vLLM server with dummy weights and the HCCL weight transfer -backend enabled, then runs the trainer side of an RLHF-style weight sync from a -separate NPU. It exercises the full control plane (HTTP) + data plane (HCCL -packed broadcast + layerwise reload) and asserts the server's weights actually -change after the broadcast. - -To keep the test self-contained and download-free, the trainer model is built -from the architecture config with random weights (only the tiny config/tokenizer -are needed, which the server already fetches). The parameter names/shapes/dtypes -match the real checkpoint, so the broadcast pipeline is fully exercised; we just -don't assert "coherent text" since the broadcast weights are random. Set -``WEIGHT_TRANSFER_TEST_MODEL=/path/to/checkpoint`` to instead broadcast real -weights from a local checkpoint. - -Topology (requires 2 NPUs): -- NPU 0: vLLM inference worker (rank 1 in the HCCL group) -- NPU 1: trainer / weight source (rank 0 in the HCCL group) - -Refer to ``examples/rl/rlhf_http_hccl.py`` for the end-user workflow. - -Run with:: - - pytest tests/e2e/multicard/2-cards/test_weight_transfer_hccl.py -""" - -import os -import threading - -import pytest -import requests -import torch -import torch_npu # noqa: F401 # registers the NPU backend -from transformers import AutoConfig, AutoModelForCausalLM -from vllm.utils.network_utils import get_ip, get_open_port - -from tests.e2e.conftest import RemoteOpenAIServer - -MODEL_NAME = "Qwen/Qwen3-0.6B" - -# Device 0 hosts the inference worker, device 1 hosts the trainer. -INFERENCE_WORLD_SIZE = 1 -TRAINER_DEVICE_INDEX = INFERENCE_WORLD_SIZE - -PROMPTS = [ - "Hello, my name is", - "The capital of France is", -] - -# HTTP timeouts (seconds). Weight broadcast can take a while for large models. -INIT_TIMEOUT = 120 -UPDATE_TIMEOUT = 300 -CONTROL_TIMEOUT = 60 - - -def _log(message: str) -> None: - """Flushed log so step markers show up immediately even when stdout is piped.""" - print(f"[trainer] {message}", flush=True) - - -def _build_trainer_model(device_index: int): - """Build the trainer-side model without downloading the checkpoint weights. - - By default the model is instantiated from the architecture config with random - weights (no ``model.safetensors`` download required); only the tiny config is - read, which the server already fetches. Its ``named_parameters`` carry the - same names/shapes/dtypes as the real checkpoint, so the HCCL broadcast + - layerwise reload path is exercised exactly as with real weights. - - Set ``WEIGHT_TRANSFER_TEST_MODEL=/path/to/checkpoint`` to broadcast real - weights from a local directory instead. - """ - device = f"npu:{device_index}" - override_path = os.getenv("WEIGHT_TRANSFER_TEST_MODEL") - if override_path: - _log(f"loading real trainer weights from {override_path}") - model = AutoModelForCausalLM.from_pretrained(override_path, dtype=torch.bfloat16) - else: - _log("building trainer model from config with random weights (download-free)") - config = AutoConfig.from_pretrained(MODEL_NAME, trust_remote_code=True) - model = AutoModelForCausalLM.from_config(config) - model = model.to(device=device, dtype=torch.bfloat16) - return model - - -def _post(server: RemoteOpenAIServer, route: str, *, json=None, timeout=CONTROL_TIMEOUT): - response = requests.post(server.url_for(route), json=json, timeout=timeout) - response.raise_for_status() - return response - - -class _BackgroundPost(threading.Thread): - """Run an HTTP POST in a thread while keeping its exception visible. - - The trainer side blocks on collective HCCL ops, so the matching server-side - RPC must run concurrently. If that RPC fails, swallowing the exception would - deadlock the trainer forever; instead we record it and surface it on join(). - """ - - def __init__(self, server: RemoteOpenAIServer, route: str, *, json=None, timeout=CONTROL_TIMEOUT): - super().__init__(daemon=True) - self._server = server - self._route = route - self._json = json - self._timeout = timeout - self.error: BaseException | None = None - - def run(self) -> None: - try: - _post(self._server, self._route, json=self._json, timeout=self._timeout) - _log(f"background POST /{self._route} done") - except BaseException as exc: # noqa: BLE001 - re-raised on join via raise_if_failed - self.error = exc - _log(f"background POST /{self._route} FAILED: {exc!r}") - - def raise_if_failed(self) -> None: - if self.error is not None: - raise RuntimeError(f"server-side /{self._route} failed") from self.error - - -def _generate(client, model, prompts): - completions = [] - for prompt in prompts: - response = client.completions.create( - model=model, - prompt=prompt, - max_tokens=16, - temperature=0, - ) - completions.append(response.choices[0].text) - return completions - - -def _collect_weight_metadata(train_model): - """Collect parameter metadata and size the packed buffer for broadcasting.""" - names: list[str] = [] - dtype_names: list[str] = [] - shapes: list[list[int]] = [] - max_tensor_bytes = 0 - for name, parameter in train_model.named_parameters(): - names.append(name) - dtype_names.append(str(parameter.dtype).split(".")[-1]) - shapes.append(list(parameter.shape)) - tensor_bytes = parameter.numel() * parameter.element_size() - max_tensor_bytes = max(max_tensor_bytes, tensor_bytes) - - # Keep the 1 GiB default unless a single tensor needs more (+128 MiB headroom). - packed_buffer_size_bytes = max(max_tensor_bytes + 128 * 2**20, 2**30) - return names, dtype_names, shapes, packed_buffer_size_bytes - - -def _has_lifecycle_endpoints(server: RemoteOpenAIServer) -> bool: - """Detect whether the server exposes the vLLM-main start/finish endpoints. - - On vLLM main, ``/start_weight_update`` and ``/finish_weight_update`` drive - the layerwise reload lifecycle. On v0.20.2 these endpoints do not exist and - ``update_weights`` is self-contained, so a probe returns 404. - """ - try: - response = requests.post( - server.url_for("start_weight_update"), - json={"is_checkpoint_format": True}, - timeout=CONTROL_TIMEOUT, - ) - except requests.RequestException: - return False - if response.status_code == 404: - return False - response.raise_for_status() - return True - - -@pytest.mark.skipif( - torch.npu.device_count() < 2, - reason="HCCL weight transfer e2e test requires at least 2 NPUs.", -) -def test_hccl_weight_transfer_updates_server_weights(): - port = get_open_port() - server_args = [ - "--enforce-eager", - "--load-format", - "dummy", - "--weight-transfer-config", - '{"backend": "nccl"}', - "--tensor-parallel-size", - str(INFERENCE_WORLD_SIZE), - "--max-model-len", - "1024", - "--gpu-memory-utilization", - "0.6", - "--port", - str(port), - "--trust-remote-code", - ] - # The dev-mode endpoints (/init_weight_transfer_engine, /update_weights, - # /pause, /resume, ...) are only registered when VLLM_SERVER_DEV_MODE=1. - # Pin the server to NPU 0 so the trainer can own NPU 1 exclusively. - env_dict = { - "VLLM_SERVER_DEV_MODE": "1", - "ASCEND_RT_VISIBLE_DEVICES": "0", - "VLLM_ASCEND_ENABLE_NZ": "0", - } - - _log(f"starting server on port {port} (device 0, dummy weights) ...") - with RemoteOpenAIServer( - MODEL_NAME, - vllm_serve_args=server_args, - # Health check, OpenAI client and control-plane requests all target this - # host; use loopback explicitly so they reach the local server directly. - server_host="127.0.0.1", - server_port=port, - env_dict=env_dict, - auto_port=False, - ) as server: - client = server.get_client() - - # 1) Baseline generation with dummy weights (expected to be nonsense). - _log("generating baseline outputs (dummy weights) ...") - outputs_before = _generate(client, MODEL_NAME, PROMPTS) - _log(f"outputs BEFORE weight update: {outputs_before}") - - # 2) Build the trainer model on the trainer NPU (download-free by default). - _log(f"preparing trainer model on npu:{TRAINER_DEVICE_INDEX} ...") - torch.npu.set_device(TRAINER_DEVICE_INDEX) - train_model = _build_trainer_model(TRAINER_DEVICE_INDEX) - _log("trainer model ready") - - # Import after the server is up so the HCCL engine plugin is registered. - from vllm_ascend.distributed.weight_transfer.hccl_engine import ( - HCCLTrainerSendWeightsArgs, - HCCLWeightTransferEngine, - ) - - master_address = get_ip() - master_port = get_open_port() - rank_offset = 1 - world_size = INFERENCE_WORLD_SIZE + 1 # workers + trainer - - # 3) Build the HCCL process group on both sides. The server side blocks - # until the trainer connects, so kick it off in a background thread. - init_info = dict( - master_address=master_address, - master_port=master_port, - rank_offset=rank_offset, - world_size=world_size, - ) - _log(f"HCCL rendezvous at {master_address}:{master_port} (world_size={world_size}) ...") - init_thread = _BackgroundPost( - server, - "init_weight_transfer_engine", - json={"init_info": init_info}, - timeout=INIT_TIMEOUT, - ) - init_thread.start() - model_update_group = HCCLWeightTransferEngine.trainer_init( - dict( - master_address=master_address, - master_port=master_port, - world_size=world_size, - ), - ) - _log("trainer_init returned, waiting for server init RPC ...") - init_thread.join() - init_thread.raise_if_failed() - _log("HCCL process group established") - - # 4) Pause generation and start the weight update lifecycle. On vLLM - # main this probe also performs the actual /start_weight_update call, - # so we must not call it again below. - _post(server, "pause") - use_lifecycle = _has_lifecycle_endpoints(server) - _log(f"paused; lifecycle endpoints available: {use_lifecycle}") - - names, dtype_names, shapes, packed_buffer_size_bytes = _collect_weight_metadata(train_model) - update_info = dict( - names=names, - dtype_names=dtype_names, - shapes=shapes, - packed=True, - packed_buffer_size_bytes=packed_buffer_size_bytes, - ) - if not use_lifecycle: - # v0.20.2 folds the layerwise reload lifecycle into update_weights. - update_info["is_checkpoint_format"] = True - - # update_weights blocks on the server while it waits for HCCL broadcasts, - # so run it in a thread while the trainer produces the data. - _log(f"broadcasting {len(names)} tensors via HCCL (packed) ...") - update_thread = _BackgroundPost( - server, - "update_weights", - json={"update_info": update_info}, - timeout=UPDATE_TIMEOUT, - ) - update_thread.start() - - trainer_args = HCCLTrainerSendWeightsArgs( - group=model_update_group, - packed=True, - packed_buffer_size_bytes=packed_buffer_size_bytes, - ) - HCCLWeightTransferEngine.trainer_send_weights( - iterator=train_model.named_parameters(), - trainer_args=trainer_args, - ) - _log("trainer finished sending weights, waiting for server update RPC ...") - update_thread.join() - update_thread.raise_if_failed() - _log("weight broadcast complete") - - # 5) Finalize the lifecycle and resume generation. - if use_lifecycle: - _post(server, "finish_weight_update") - _post(server, "resume") - - # 6) Generation after the broadcast weights are loaded. - outputs_after = _generate(client, MODEL_NAME, PROMPTS) - _log(f"outputs AFTER weight update: {outputs_after}") - - # Reaching here means the full HCCL transfer pipeline succeeded: every - # control-plane RPC raised on a non-2xx response and each background POST - # re-raised on join(). The broadcast weights differ from the server's dummy - # init, so the served model must now produce different generations. - assert outputs_after != outputs_before, "server weights did not change after HCCL transfer" diff --git a/tests/e2e/pull_request/two_card/test_moe_routing_replay.py b/tests/e2e/pull_request/two_card/test_moe_routing_replay.py index 7d993f07f..5b84270e9 100644 --- a/tests/e2e/pull_request/two_card/test_moe_routing_replay.py +++ b/tests/e2e/pull_request/two_card/test_moe_routing_replay.py @@ -26,7 +26,6 @@ def test_qwen3_moe_routing_replay(model): cudagraph_capture_sizes=[1, 2, 4, 8], distributed_executor_backend="mp", enable_return_routed_experts=True, - async_scheduling=False, ) as vllm_model: sampling_params = SamplingParams( max_tokens=5, temperature=0.8, top_p=0.95, output_kind=RequestOutputKind.FINAL_ONLY diff --git a/tests/e2e/pull_request/two_card/test_offline_weight_load.py b/tests/e2e/pull_request/two_card/test_offline_weight_load.py index 19a205b31..7f2816d51 100644 --- a/tests/e2e/pull_request/two_card/test_offline_weight_load.py +++ b/tests/e2e/pull_request/two_card/test_offline_weight_load.py @@ -33,7 +33,6 @@ EXTERNAL_LAUNCHER_SCRIPT = REPO_ROOT / "examples" / "offline_external_launcher.py" -@pytest.mark.skip("fix me, unstable, timeout") @pytest.mark.parametrize("model", MODELS) @patch.dict(os.environ, {"VLLM_ASCEND_ENABLE_NZ": "0"}) @wait_until_npu_memory_free(0.7) diff --git a/tests/e2e/pull_request/two_card/test_qwen3_6_27b_fia.py b/tests/e2e/pull_request/two_card/test_qwen3_6_27b_fia.py deleted file mode 100644 index c701b5e18..000000000 --- a/tests/e2e/pull_request/two_card/test_qwen3_6_27b_fia.py +++ /dev/null @@ -1,103 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -from unittest.mock import patch - -from vllm.assets.image import ImageAsset - -from tests.e2e.conftest import VllmRunner, qwen_prompt, wait_until_npu_memory_free - -MODEL = "Qwen/Qwen3.6-27B/" - - -@patch.dict(os.environ, {"HCCL_BUFFSIZE": "1024"}) -@wait_until_npu_memory_free() -def test_qwen3_6_27b_multimodel_fia_eager(): - """Verify multimodal generation with FIA op and eager mode.""" - image = ImageAsset("cherry_blossom").pil_image.convert("RGB") - questions = [ - "What is the content of this image?", - "Describe the content of this image in detail.", - "What's in the image?", - "Where is this image taken?", - ] - - images = [image] * len(questions) - prompts = qwen_prompt(questions) - - with VllmRunner( - MODEL, - max_model_len=4096, - tensor_parallel_size=2, - language_model_only=False, - gpu_memory_utilization=0.9, - limit_mm_per_prompt={"image": 1}, - mm_processor_kwargs={ - "min_pixels": 28 * 28, - "max_pixels": 1280 * 28 * 28, - "fps": 1, - }, - enforce_eager=True, - ) as vllm_model: - outputs = vllm_model.generate_greedy( - prompts=prompts, - images=images, - max_tokens=64, - ) - - assert outputs[0][1] - - -@patch.dict(os.environ, {"HCCL_BUFFSIZE": "1024"}) -@wait_until_npu_memory_free() -def test_qwen3_6_27b_multimodel_fia_acl_graph(): - """Verify multimodal generation with FIA op and FULL_AND_PIECEWISE graph mode.""" - image = ImageAsset("cherry_blossom").pil_image.convert("RGB") - questions = [ - "What is the content of this image?", - "Describe the content of this image in detail.", - "What's in the image?", - "Where is this image taken?", - ] - - images = [image] * len(questions) - prompts = qwen_prompt(questions) - - with VllmRunner( - MODEL, - max_model_len=4096, - tensor_parallel_size=2, - language_model_only=False, - gpu_memory_utilization=0.9, - limit_mm_per_prompt={"image": 1}, - mm_processor_kwargs={ - "min_pixels": 28 * 28, - "max_pixels": 1280 * 28 * 28, - "fps": 1, - }, - compilation_config={ - "cudagraph_mm_encoder": False, - "cudagraph_capture_sizes": [1], - "encoder_cudagraph_token_budgets": [128, 256, 512, 1024, 1536, 2048, 2560, 3072, 3584, 4096], - }, - ) as vllm_model: - outputs = vllm_model.generate_greedy( - prompts=prompts, - images=images, - max_tokens=64, - ) - - assert outputs[0][1] diff --git a/tests/e2e/weekly/single_node/configs/Kimi-K2.5.yaml b/tests/e2e/weekly/single_node/configs/Kimi-K2.5.yaml index f684e4f2d..fcc36e769 100644 --- a/tests/e2e/weekly/single_node/configs/Kimi-K2.5.yaml +++ b/tests/e2e/weekly/single_node/configs/Kimi-K2.5.yaml @@ -13,7 +13,6 @@ _envs: &envs VLLM_ASCEND_ENABLE_MLAPO: "1" VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" VLLM_ASCEND_ENABLE_NZ: "1" - VLLM_ENGINE_READY_TIMEOUT_S: "3000" _server_cmd: &server_cmd - "--enable-expert-parallel" @@ -49,17 +48,18 @@ _benchmark_acc: &_benchmark_acc threshold: 5 _benchmark_TPOT50_3_5k_1_5k: &_benchmark_TPOT50_3_5k_1_5k - case_type: performance - dataset_path: vllm-ascend/GSM8K-in3500-bs2800 - request_conf: vllm_api_stream_chat - dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf - num_prompts: 392 - max_out_len: 1500 - batch_size: 98 - trust_remote_code: true - request_rate: 0 - baseline: 1 - threshold: 0.97 + perf: + case_type: performance + dataset_path: vllm-ascend/GSM8K-in3500-bs2800 + request_conf: vllm_api_stream_chat + dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf + num_prompts: 392 + max_out_len: 1500 + batch_size: 98 + trust_remote_code: true + request_rate: 0 + baseline: 1 + threshold: 0.97 _benchmark_TPOT50_16K_1K: &_benchmark_TPOT50_16K_1K case_type: performance @@ -248,8 +248,6 @@ test_cases: HCCL_BUFFSIZE: "400" VLLM_ASCEND_BALANCE_SCHEDULING: "1" server_cmd: - - "--port" - - "$SERVER_PORT" - "--tool-call-parser" - "kimi_k2" - "--reasoning-parser" diff --git a/tests/e2e/weekly/single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml b/tests/e2e/weekly/single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml index 228414d42..d179c130a 100644 --- a/tests/e2e/weekly/single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml +++ b/tests/e2e/weekly/single_node/configs/MiniMax-M2.5-w8a8-QuaRot-A3.yaml @@ -3,6 +3,68 @@ # ========================================== test_cases: + - name: "MiniMax-M2.5-w8a8" + model: "Eco-Tech/MiniMax-M2.5-w8a8-QuaRot" + envs: + HCCL_BUFFSIZE: "512" + HCCL_OP_EXPANSION_MODE: "AIV" + PYTORCH_NPU_ALLOC_CONF: "expandable_segments:True" + OMP_NUM_THREADS: "1" + TASK_QUEUE_ENABLE: "1" + VLLM-ASCEND_ENABLE_NZ: "1" + VLLM_ASCEND_ENABLE__FUSED_MC2: "1" + VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" + VLLM-ASCEND_BALANCE_SCHEDULING: "1" + VLLM_USE_MODELSCOPE: "true" + SERVER_PORT: "DEFAULT_PORT" + server_cmd: + - "--tensor-parallel-size" + - "8" + - "--data-parallel-size" + - "1" + - "--port" + - "$SERVER_PORT" + - "--enable-expert-parallel" + - "--max-num-seqs" + - "128" + - "--max-model-len" + - "196608" + - "--max-num-batched-tokens" + - "16384" + - "--gpu-memory-utilization" + - "0.85" + - "--trust-remote-code" + - "--quantization" + - "ascend" + - "--no-enable-prefix-caching" + - "--compilation-config" + - '{"cudagraph_mode":"FULL_DECODE_ONLY"}' + - "--additional-config" + - '{"enable_cpu_binding":true}' + - "--speculative_config" + - '{"method":"eagle3","model":"vllm-ascend/MiniMax-M2.5-eagle-model-0318","num_speculative_tokens":3}' + benchmarks: + acc: + case_type: accuracy + dataset_path: vllm-ascend/aime2025 + request_conf: vllm_api_general_chat + dataset_conf: aime2025/aime2025_gen_0_shot_chat_prompt + max_out_len: 65536 + batch_size: 32 + baseline: 83 + threshold: 5 + temperature: 1.0 + perf: + case_type: performance + dataset_path: vllm-ascend/GSM8K-in3500-bs2800 + request_conf: vllm_api_stream_chat + dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf + num_prompts: 512 + max_out_len: 1500 + batch_size: 128 + request_rate: 0 + baseline: 1 + threshold: 0.97 - name: "MiniMax-M2.5-w8a8-in16k-120-30" model: "Eco-Tech/MiniMax-M2.5-w8a8-QuaRot" envs: diff --git a/tests/e2e/weekly/single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml b/tests/e2e/weekly/single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml index 019a5ea89..ce0e0fc62 100644 --- a/tests/e2e/weekly/single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml +++ b/tests/e2e/weekly/single_node/configs/Qwen3.5-122B-A10B-W8A8-A3.yaml @@ -9,6 +9,7 @@ _envs: &envs OMP_NUM_THREADS: "1" TASK_QUEUE_ENABLE: "1" VLLM_ASCEND_ENABLE_FUSED_MC2: "1" + VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" SERVER_PORT: "DEFAULT_PORT" _server_cmd: &server_cmd @@ -18,6 +19,7 @@ _server_cmd: &server_cmd - "1" - "--tensor-parallel-size" - "4" + - "--enable-expert-parallel" - "--max-num-seqs" - "128" - "--gpu-memory-utilization" @@ -32,8 +34,6 @@ _server_cmd: &server_cmd - "0" - "--additional-config" - '{"enable_cpu_binding": true, "enable_shared_expert_dp": true}' - - "--api-server-count" - - "1" _benchmark_acc: &_benchmark_acc acc_GPQA: @@ -41,7 +41,7 @@ _benchmark_acc: &_benchmark_acc dataset_path: vllm-ascend/gpqa request_conf: vllm_api_general_chat dataset_conf: gpqa/gpqa_gen_0_shot_cot_chat_prompt - max_out_len: 65536 + max_out_len: 32768 batch_size: 32 baseline: 85 threshold: 5 @@ -58,7 +58,7 @@ _benchmark_acc: &_benchmark_acc dataset_path: vllm-ascend/aime2025 request_conf: vllm_api_general_chat dataset_conf: aime2025/aime2025_gen_0_shot_chat_prompt - max_out_len: 65536 + max_out_len: 32768 batch_size: 32 baseline: 90 threshold: 5 @@ -71,18 +71,6 @@ _benchmark_acc: &_benchmark_acc repetition_penalty: 1.0 ignore_eos: false -_benchmark_TPOT50_3_5k_1_5K: &_benchmark_TPOT50_3_5k_1_5K - case_type: performance - dataset_path_local: vllm-ascend/GSM8K-in3500-bs8000-qwen3 - request_conf: vllm_api_stream_chat - dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf - num_prompts: 320 - max_out_len: 1500 - batch_size: 80 - request_rate: 0 - baseline: 1052.9 - threshold: 0.95 - _benchmark_TPOT50_16K_1K: &_benchmark_TPOT50_16K_1K case_type: performance dataset_path: vllm-ascend/GSM8K_prefix0_in16384_bs200_qwen @@ -92,8 +80,8 @@ _benchmark_TPOT50_16K_1K: &_benchmark_TPOT50_16K_1K max_out_len: 1024 batch_size: 24 request_rate: 0 - baseline: 400.183 - threshold: 0.95 + baseline: 1 + threshold: 0.97 _benchmark_TPOT20_16K_1K: &_benchmark_TPOT20_16K_1K case_type: performance @@ -104,8 +92,8 @@ _benchmark_TPOT20_16K_1K: &_benchmark_TPOT20_16K_1K max_out_len: 1024 batch_size: 8 request_rate: 0 - baseline: 325.5 - threshold: 0.95 + baseline: 1 + threshold: 0.97 _benchmark_TPOT50_32K_0_5K: &_benchmark_TPOT50_32K_0_5K case_type: performance @@ -113,11 +101,11 @@ _benchmark_TPOT50_32K_0_5K: &_benchmark_TPOT50_32K_0_5K request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 36 - max_out_len: 512 + max_out_len: 1024 batch_size: 9 request_rate: 0 - baseline: 123.8 - threshold: 0.95 + baseline: 1 + threshold: 0.97 _benchmark_TPOT20_32K_0_5K: &_benchmark_TPOT20_32K_0_5K case_type: performance @@ -125,11 +113,11 @@ _benchmark_TPOT20_32K_0_5K: &_benchmark_TPOT20_32K_0_5K request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 16 - max_out_len: 512 + max_out_len: 1024 batch_size: 4 request_rate: 0 - baseline: 113.8 - threshold: 0.95 + baseline: 1 + threshold: 0.97 _benchmark_TPOT50_64K_1K: &_benchmark_TPOT50_64K_1K case_type: performance @@ -140,8 +128,8 @@ _benchmark_TPOT50_64K_1K: &_benchmark_TPOT50_64K_1K max_out_len: 1024 batch_size: 8 request_rate: 0 - baseline: 114.1 - threshold: 0.95 + baseline: 1 + threshold: 0.97 _benchmark_TPOT20_64K_1K: &_benchmark_TPOT20_64K_1K case_type: performance @@ -152,8 +140,8 @@ _benchmark_TPOT20_64K_1K: &_benchmark_TPOT20_64K_1K max_out_len: 1024 batch_size: 4 request_rate: 0 - baseline: 99.28 - threshold: 0.95 + baseline: 1 + threshold: 0.97 test_cases: @@ -161,10 +149,8 @@ test_cases: model: "Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp" envs: <<: *envs - VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" server_cmd: *server_cmd server_cmd_extra: - - "--enable-expert-parallel" - "--max-model-len" - "8000" - "--max-num-batched-tokens" @@ -174,15 +160,23 @@ test_cases: - "--speculative_config" - '{"method": "qwen3_5_mtp", "num_speculative_tokens": 3, "enforce_eager": true}' benchmarks: - perf: *_benchmark_TPOT50_3_5k_1_5K + perf: + case_type: performance + dataset_path: vllm-ascend/GSM8K-in3500-bs8000-qwen3 + request_conf: vllm_api_stream_chat + dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf + num_prompts: 320 + max_out_len: 1500 + batch_size: 80 + request_rate: 0 + baseline: 1 + threshold: 0.97 - name: "Qwen3.5-122B-A10B-W8A8-TPOT50-16k-1k" model: "Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp" envs: <<: *envs - VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" server_cmd: *server_cmd server_cmd_extra: - - "--enable-expert-parallel" - "--max-model-len" - "18432" - "--max-num-batched-tokens" @@ -213,10 +207,8 @@ test_cases: model: "Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp" envs: <<: *envs - VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" server_cmd: *server_cmd server_cmd_extra: - - "--enable-expert-parallel" - "--max-model-len" - "34304" - "--max-num-batched-tokens" @@ -247,10 +239,8 @@ test_cases: model: "Eco-Tech/Qwen3.5-122B-A10B-w8a8-mtp" envs: <<: *envs - VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" server_cmd: *server_cmd server_cmd_extra: - - "--enable-expert-parallel" - "--max-model-len" - "67584" - "--max-num-batched-tokens" diff --git a/tests/e2e/weekly/single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml b/tests/e2e/weekly/single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml index 52ccc30e2..c2ab3b5f7 100644 --- a/tests/e2e/weekly/single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml +++ b/tests/e2e/weekly/single_node/configs/Qwen3.5-397B-A17B-W8A8-mtp-A3_weekly.yaml @@ -11,7 +11,8 @@ _envs: &envs OMP_NUM_THREADS: "1" TASK_QUEUE_ENABLE: "1" SERVER_PORT: "DEFAULT_PORT" - VLLM_ASCEND_ENABLE_FUSED_MC2: "0" + VLLM_ASCEND_ENABLE_FUSED_MC2: "1" + VLLM_ASCEND_ENABLE_FLASHCOMM1: "1" VLLM_ENGINE_READY_TIMEOUT_S: "3000" VLLM_RPC_TIMEOUT: "600" _server_cmd: &server_cmd @@ -56,7 +57,7 @@ _benchmarks: &benchmarks _benchmarks_bs144: &benchmarks_bs144 perf: case_type: performance - dataset_path: vllm-ascend/GSM8K_prefix0_in16384_bs200_qwen + dataset_path: vllm-ascend/GSM8K_prefix0_in16384_bs200_qwen3 request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 144 @@ -69,7 +70,7 @@ _benchmarks_bs144: &benchmarks_bs144 _benchmarks_bs36: &benchmarks_bs36 perf: case_type: performance - dataset_path: vllm-ascend/GSM8K_prefix0_in16384_bs200_qwen + dataset_path: vllm-ascend/GSM8K_prefix0_in16384_bs100_qwen3_5 request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 36 @@ -82,7 +83,7 @@ _benchmarks_bs36: &benchmarks_bs36 _benchmarks_bs48: &benchmarks_bs48 perf: case_type: performance - dataset_path: vllm-ascend/GSM8K_prefix0_in32768_bs100_qwen + dataset_path: vllm-ascend/GSM8K_prefix0_in32768_bs100_qwen3 request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 48 @@ -95,7 +96,7 @@ _benchmarks_bs48: &benchmarks_bs48 _benchmarks_bs16: &benchmarks_bs16 perf: case_type: performance - dataset_path: vllm-ascend/GSM8K_prefix0_in32768_bs100_qwen + dataset_path: vllm-ascend/GSM8K_prefix0_in32768_bs100_qwen3 request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 16 @@ -135,7 +136,7 @@ _benchmarks_bs32: &benchmarks_bs32 _benchmarks_bs8: &benchmarks_bs8 perf: case_type: performance - dataset_path: vllm-ascend/GSM8K_prefix0_in65536_bs100_qwen + dataset_path: vllm-ascend/GSM8K_prefix0_in65536_bs200_qwen3_5 request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 8 @@ -148,7 +149,7 @@ _benchmarks_bs8: &benchmarks_bs8 _benchmarks_bs32_in65536: &benchmarks_bs32_in65536 perf: case_type: performance - dataset_path: vllm-ascend/GSM8K_prefix0_in65536_bs100_qwen + dataset_path: vllm-ascend/GSM8K_prefix0_in65536_bs200_qwen3_5 request_conf: vllm_api_stream_chat dataset_conf: gsm8k/gsm8k_gen_0_shot_cot_str_perf num_prompts: 32 diff --git a/tests/ut/_310p/attention/test_attention_mask_310.py b/tests/ut/_310p/attention/test_attention_mask_310.py index dab84617d..c6eb2552d 100644 --- a/tests/ut/_310p/attention/test_attention_mask_310.py +++ b/tests/ut/_310p/attention/test_attention_mask_310.py @@ -29,7 +29,6 @@ def setUp(self): @patch("torch_npu.npu_format_cast") def test_get_attention_mask_310(self, mock_format_cast): mock_format_cast.side_effect = lambda x, y: x - self.attention_mask_builder.support_compressed_mask = False model_config = MagicMock() attn_mask = self.attention_mask_builder.get_attention_mask(causal=True, model_config=model_config) self.assertEqual(attn_mask.shape, (1, self.max_seqlen // 16, self.max_seqlen, 16)) diff --git a/tests/ut/_310p/attention/test_attention_v1_310.py b/tests/ut/_310p/attention/test_attention_v1_310.py index 5cd624aa1..bb1cc8141 100644 --- a/tests/ut/_310p/attention/test_attention_v1_310.py +++ b/tests/ut/_310p/attention/test_attention_v1_310.py @@ -75,8 +75,10 @@ def setUp(self): @patch("torch_npu._npu_reshape_and_cache") @patch("torch_npu._npu_flash_attention") @patch("vllm_ascend.ascend_forward_context.get_forward_context") - def test_forward_prefill_310(self, mock_get_forward_context, mock_npu_flash_attention, mock_npu_reshape_and_cache): - """Test forward pass in PrefillNoCache state.""" + def test_forward_prefill_310( + self, mock_get_forward_context, mock_npu_npu_flash_attention, mock_npu_reshape_and_cache + ): + """Test forward pass in PrefillNoCache state""" query = torch.randn(10, 8, 64) key = torch.randn(10, 8, 64) value = torch.randn(10, 8, 64) @@ -94,23 +96,11 @@ def test_forward_prefill_310(self, mock_get_forward_context, mock_npu_flash_atte metadata.num_prefills = 10 metadata.slot_mapping = torch.zeros(10, dtype=torch.long) - self.impl.support_compressed_mask = False mock_get_forward_context.return_value = MagicMock(capturing=False) - mock_npu_flash_attention.return_value = torch.ones(10, 8, 64) - result = self.impl.forward_impl(query, key, value, None, metadata, output) - - mock_npu_flash_attention.assert_called_once() - _, kwargs = mock_npu_flash_attention.call_args - self.assertIs(kwargs["query"], query) - self.assertIs(kwargs["key"], key) - self.assertIs(kwargs["value"], value) - self.assertIs(kwargs["mask"], metadata.attn_mask) - self.assertIs(kwargs["seq_len"], metadata.seq_lens) - self.assertEqual(kwargs["scale_value"], self.impl.scale) - self.assertEqual(kwargs["num_heads"], self.impl.num_heads) - self.assertEqual(kwargs["num_kv_heads"], self.impl.num_kv_heads) - self.assertIs(kwargs["out"], output) - self.assertIs(result, output) + mock_npu_npu_flash_attention.return_value = torch.ones(10, 8, 64) + output = self.impl.forward_impl(query, key, value, None, metadata, output) + + mock_npu_npu_flash_attention.assert_called_once() @patch("torch_npu.npu_format_cast", return_value=torch.randn((1, 128, 16, 16), dtype=torch.float16)) @patch("torch_npu._npu_reshape_and_cache") @@ -141,7 +131,6 @@ def test_forward_chunked_prefill_310( metadata.num_prefills = 10 metadata.slot_mapping = torch.zeros(10, dtype=torch.long) - self.impl.support_compressed_mask = False mock_get_forward_context.return_value = MagicMock(capturing=False) mock_npu_paged_attention_splitfuse.return_value = torch.ones(5, 8, 64) output = self.impl.forward_impl(query, key, value, None, metadata, output) @@ -177,7 +166,6 @@ def test_forward_prefill_cache_hit_310( metadata.num_prefills = 10 metadata.slot_mapping = torch.zeros(10, dtype=torch.long) - self.impl.support_compressed_mask = False mock_get_forward_context.return_value = MagicMock(capturing=False) mock_npu_paged_attention_splitfuse.return_value = torch.ones(5, 8, 64) output = self.impl.forward_impl(query, key, value, None, metadata, output) @@ -212,16 +200,11 @@ def test_forward_paged_attention_310( mock_paged_attention.assert_called_once() - @patch("vllm_ascend._310p.attention.attention_v1.AscendAttentionBackendImpl310.forward_chunked_prefill_310") - def test_forward_mtp_310(self, mock_chunked_prefill): + def test_forward_mtp_310(self): query = torch.randn(4, 8 * 64) key, value = None, None output = torch.empty_like(query) metadata = self.attn_metadata metadata.attn_state = AscendAttentionState.SpecDecoding - mock_chunked_prefill.return_value = output - - result = self.impl.forward_impl(query, key, value, None, metadata, output) - - mock_chunked_prefill.assert_called_once_with(query, metadata, output) - self.assertIs(result, output) + with self.assertRaises(NotImplementedError): + output = self.impl.forward_impl(query, key, value, None, metadata, output) diff --git a/tests/ut/_310p/sample/test_sampler_310.py b/tests/ut/_310p/sample/test_sampler_310.py index be5d8e0d1..dae2bdeee 100644 --- a/tests/ut/_310p/sample/test_sampler_310.py +++ b/tests/ut/_310p/sample/test_sampler_310.py @@ -56,23 +56,13 @@ def cpu(self): def npu(self): return self - def exponential_(self, generator=None): - if generator is None: - self.default_exponential_called = True + def exponential_(self): + self.default_exponential_called = True return self def __getitem__(self, idx): return self.rows[idx] - def __setitem__(self, idx, value): - self.rows[idx] = value - - -def _empty_like_side_effect(q_instances, template): - if isinstance(template, _FakeRow): - return _FakeRow() - return next(q_instances) - class _FakeCPUGenerator: def __init__(self, device=None): @@ -100,7 +90,6 @@ def test_random_sample_310p_reuse_cpu_generator_cache(self): fake_q_first = _FakeQ(batch_size=2) fake_q_second = _FakeQ(batch_size=2) - q_instances = iter([fake_q_first, fake_q_second]) npu_stream = MagicMock() generator = MagicMock() @@ -111,11 +100,7 @@ def test_random_sample_310p_reuse_cpu_generator_cache(self): with ( patch.object(sampler_310p, "npu_stream_switch", return_value=nullcontext()), patch.object(sampler_310p, "global_stream", return_value=MagicMock()), - patch.object( - sampler_310p.torch, - "empty_like", - side_effect=lambda template: _empty_like_side_effect(q_instances, template), - ), + patch.object(sampler_310p.torch, "empty_like", side_effect=[fake_q_first, fake_q_second]), patch.object(sampler_310p.torch, "Generator", side_effect=_FakeCPUGenerator) as gen_ctor, patch.object( sampler_310p.torch, @@ -146,7 +131,6 @@ def test_random_sample_310p_fallback_to_initial_seed_when_set_state_failed(self) probs.view.return_value = torch.tensor([1]) fake_q = _FakeQ(batch_size=1) - q_instances = iter([fake_q]) npu_stream = MagicMock() generator = MagicMock() generator.get_state.side_effect = RuntimeError("state read failed") @@ -160,11 +144,7 @@ def set_state(self, state): with ( patch.object(sampler_310p, "npu_stream_switch", return_value=nullcontext()), patch.object(sampler_310p, "global_stream", return_value=MagicMock()), - patch.object( - sampler_310p.torch, - "empty_like", - side_effect=lambda template: _empty_like_side_effect(q_instances, template), - ), + patch.object(sampler_310p.torch, "empty_like", return_value=fake_q), patch.object(sampler_310p.torch, "Generator", side_effect=_FailSetStateCPUGenerator), patch.object( sampler_310p.torch, @@ -191,7 +171,6 @@ def test_random_sample_310p_rebuild_cache_when_generator_identity_changes(self): fake_q_first = _FakeQ(batch_size=1) fake_q_second = _FakeQ(batch_size=1) - q_instances = iter([fake_q_first, fake_q_second]) npu_stream = MagicMock() generator_first = MagicMock() @@ -205,11 +184,7 @@ def test_random_sample_310p_rebuild_cache_when_generator_identity_changes(self): with ( patch.object(sampler_310p, "npu_stream_switch", return_value=nullcontext()), patch.object(sampler_310p, "global_stream", return_value=MagicMock()), - patch.object( - sampler_310p.torch, - "empty_like", - side_effect=lambda template: _empty_like_side_effect(q_instances, template), - ), + patch.object(sampler_310p.torch, "empty_like", side_effect=[fake_q_first, fake_q_second]), patch.object(sampler_310p.torch, "Generator", side_effect=_FakeCPUGenerator) as gen_ctor, patch.object( sampler_310p.torch, diff --git a/tests/ut/_310p/test_kv_block_zeroer_310p.py b/tests/ut/_310p/test_kv_block_zeroer_310p.py deleted file mode 100644 index da93ccb6b..000000000 --- a/tests/ut/_310p/test_kv_block_zeroer_310p.py +++ /dev/null @@ -1,73 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# - -from types import SimpleNamespace - -import torch -from vllm.v1.kv_cache_interface import FullAttentionSpec - -from tests.ut.base import TestBase -from vllm_ascend._310p.kv_block_zeroer import AscendKVBlockZeroer310 - - -class TestAscendKVBlockZeroer310(TestBase): - def setUp(self): - self.zeroer = AscendKVBlockZeroer310(torch.device("cpu"), pin_memory=False) - - def test_zero_block_ids_noop_when_empty(self): - kv = torch.ones(4, 2, 3) - self.zeroer._kv_tensors = [kv] - self.zeroer._logical_page_ratio = 1 - - self.zeroer.zero_block_ids([]) - self.assertTrue(torch.all(kv == 1)) - - def test_zero_block_ids_zeros_target_slices(self): - kv = torch.ones(6, 2, 3) - self.zeroer._kv_tensors = [kv] - self.zeroer._logical_page_ratio = 2 - - self.zeroer.zero_block_ids([1]) - - self.assertTrue(torch.all(kv[:2] == 1)) - self.assertTrue(torch.all(kv[2:4] == 0)) - self.assertTrue(torch.all(kv[4:] == 1)) - - def test_init_meta_deduplicates_kv_pointers(self): - k_cache = torch.zeros(4, 2, 3) - v_cache = k_cache - layer_context = SimpleNamespace(kv_cache=(k_cache, v_cache)) - spec = FullAttentionSpec( - block_size=128, - num_kv_heads=2, - head_size=64, - dtype=torch.float16, - ) - group = SimpleNamespace( - kv_cache_spec=spec, - kv_cache_group_id=0, - layer_names=["layer_0"], - ) - - self.zeroer.init_meta( - attn_groups_iter=[group], - kernel_block_sizes=[[64]], - cache_dtype="float16", - runner_only_attn_layers=set(), - static_forward_context={"layer_0": layer_context}, - ) - - self.assertEqual(len(self.zeroer._kv_tensors), 1) - self.assertEqual(self.zeroer._logical_page_ratio, 2) diff --git a/tests/ut/attention/a2/test_attention_v1.py b/tests/ut/attention/a2/test_attention_v1.py index dfc169e5f..4ac75d5ec 100644 --- a/tests/ut/attention/a2/test_attention_v1.py +++ b/tests/ut/attention/a2/test_attention_v1.py @@ -82,32 +82,6 @@ def test_reorder_batch(self): self.assertFalse(result) - def test_unpadded_preserves_internal_seq_lens_cpu(self): - internal_seq_lens_cpu = torch.tensor([4, 5, 6], dtype=torch.int32) - common_attn_metadata = AscendCommonAttentionMetadata( - query_start_loc=torch.tensor([0, 2, 5, 9]), - query_start_loc_cpu=torch.tensor([0, 2, 5, 9]), - seq_lens=torch.tensor([4, 5, 6], dtype=torch.int32), - _seq_lens_cpu=internal_seq_lens_cpu, - seq_lens_cpu=None, - num_computed_tokens_cpu=None, - num_reqs=3, - num_actual_tokens=9, - max_query_len=4, - block_table_tensor=torch.zeros((3, 1), dtype=torch.int32), - slot_mapping=torch.arange(9, dtype=torch.int32), - causal=True, - actual_seq_lengths_q=[2, 3, 4], - positions=torch.arange(9), - attn_state=AscendAttentionState.ChunkedPrefill, - max_seq_len=6, - ) - - unpadded_metadata = common_attn_metadata.unpadded(num_actual_tokens=5, num_actual_reqs=2) - - self.assertTrue(torch.equal(unpadded_metadata._seq_lens_cpu, internal_seq_lens_cpu[:2])) - self.assertIsNone(unpadded_metadata.seq_lens_cpu) - @patch("vllm_ascend.attention.attention_v1.AscendMetadata") def test_build(self, mock_ascend_metadata): common_attn_metadata = AscendCommonAttentionMetadata( diff --git a/tests/ut/attention/a2/test_sfa_v1.py b/tests/ut/attention/a2/test_sfa_v1.py index ad5c75f5b..673183d40 100644 --- a/tests/ut/attention/a2/test_sfa_v1.py +++ b/tests/ut/attention/a2/test_sfa_v1.py @@ -6,7 +6,6 @@ from tests.ut.attention.utils import patch_distributed_groups from tests.ut.base import TestBase -from vllm_ascend.ascend_config import init_ascend_config from vllm_ascend.attention.attention_v1 import AscendAttentionState if "torch_npu._inductor" not in sys.modules: @@ -124,23 +123,9 @@ def setUp(self, mock_tp): self.mock_cfg.speculative_config.num_speculative_tokens = 0 - self.mock_cfg.additional_config = {"refresh": True} - init_ascend_config(self.mock_cfg) - self.patcher = patch("vllm.config.get_current_vllm_config", return_value=self.mock_cfg) self.patcher.start() - mock_ascend_config = MagicMock() - mock_ascend_config.c8_enable_reshape_optim = False - mock_ascend_config.enable_mlapo = True - mock_ascend_config.enable_shared_expert_dp = False - mock_ascend_config.layer_sharding = None - self.ascend_config_patcher = patch( - "vllm_ascend.attention.sfa_v1.get_ascend_config", - return_value=mock_ascend_config, - ) - self.ascend_config_patcher.start() - # Mock parent class __init__ to avoid complex initialization, # but still set the essential attributes that child class needs def mock_parent_init( @@ -168,7 +153,6 @@ def mock_parent_init( def tearDown(self): self.patcher.stop() - self.ascend_config_patcher.stop() self.parent_init_patcher.stop() @patch_distributed_groups(dcp_size=2, pcp_size=2, needs_mocks=False) diff --git a/tests/ut/core/test_recompute_scheduler.py b/tests/ut/core/test_recompute_scheduler.py deleted file mode 100644 index 64b80fd95..000000000 --- a/tests/ut/core/test_recompute_scheduler.py +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project - -from types import MethodType - -from vllm.sampling_params import SamplingParams -from vllm.v1.request import Request -from vllm.v1.sample.rejection_sampler import PLACEHOLDER_TOKEN_ID - -from vllm_ascend.core.recompute_scheduler import RecomputeScheduler - - -def test_pd_consumer_first_step_injects_placeholder_spec_tokens(): - scheduler = RecomputeScheduler.__new__(RecomputeScheduler) - scheduler.requests = {} - scheduler.is_kv_producer = False - scheduler.is_hybrid_model = False - scheduler.is_mtp_kv_consumer = True - scheduler.num_spec_tokens = 1 - scheduler.max_model_len = 1024 - scheduler.log_stats = False - - enqueued_requests = [] - - def enqueue_waiting_request(self, request): - enqueued_requests.append(request) - - scheduler._enqueue_waiting_request = MethodType(enqueue_waiting_request, scheduler) - - request = Request( - request_id="pd-consumer-first-step", - prompt_token_ids=[1, 2, 3, 4], - sampling_params=SamplingParams(max_tokens=8), - pooling_params=None, - ) - - scheduler.add_request(request) - - assert enqueued_requests == [request] - assert scheduler.requests[request.request_id] is request - assert request.spec_token_ids == [PLACEHOLDER_TOKEN_ID] - assert request.num_tokens_with_spec == request.num_tokens + 1 diff --git a/tests/ut/device/test_device_op.py b/tests/ut/device/test_device_op.py index 5be7db43b..b0acae083 100644 --- a/tests/ut/device/test_device_op.py +++ b/tests/ut/device/test_device_op.py @@ -1,119 +1,2 @@ -from unittest import mock - -import pytest -import torch - -from vllm_ascend.device.device_op import A5DeviceAdaptor, BaseDeviceAdaptor - - -def test_npu_flash_attention_uses_fusion_attention_for_fp32(): - query = torch.randn(5, 4, 64, dtype=torch.float32) - key = torch.randn_like(query) - value = torch.randn_like(query) - seq_lens_cpu = torch.tensor([2, 3], dtype=torch.int32) - expected = torch.randn_like(query) - - with ( - mock.patch( - "vllm_ascend.device.device_op.torch_npu.npu_fusion_attention", - return_value=(expected,), - ) as mock_fusion_attention, - mock.patch( - "vllm_ascend.device.device_op.torch_npu._npu_flash_attention_unpad", - create=True, - ) as mock_flash_attention, - ): - output = BaseDeviceAdaptor.npu_flash_attention( - query=query, - key=key, - value=value, - seq_lens_cpu=seq_lens_cpu, - head_num=4, - scale_value=0.125, - num_kv_heads=4, - ) - - assert output is expected - mock_flash_attention.assert_not_called() - mock_fusion_attention.assert_called_once() - call_kwargs = mock_fusion_attention.call_args.kwargs - assert call_kwargs["query"] is query - assert call_kwargs["key"] is key - assert call_kwargs["value"] is value - assert call_kwargs["actual_seq_qlen"] == [2, 5] - assert all(isinstance(seq_len, int) for seq_len in call_kwargs["actual_seq_qlen"]) - assert call_kwargs["actual_seq_kvlen"] is call_kwargs["actual_seq_qlen"] - assert call_kwargs["head_num"] == 4 - assert call_kwargs["scale"] == 0.125 - assert call_kwargs["input_layout"] == "TND" - - -@pytest.mark.parametrize("dtype", [torch.float16, torch.bfloat16]) -def test_npu_flash_attention_uses_unpad_attention_for_low_precision(dtype): - query = torch.randn(5, 4, 64, dtype=dtype) - key = torch.randn_like(query) - value = torch.randn_like(query) - seq_lens_cpu = torch.tensor([2, 3], dtype=torch.int32) - - def fake_flash_attention(*, query, key, value, seq_len, scale_value, num_heads, num_kv_heads, out): - out.copy_(query + 1) - - with ( - mock.patch( - "vllm_ascend.device.device_op.torch_npu.npu_fusion_attention", - ) as mock_fusion_attention, - mock.patch( - "vllm_ascend.device.device_op.torch_npu._npu_flash_attention_unpad", - side_effect=fake_flash_attention, - create=True, - ) as mock_flash_attention, - ): - output = BaseDeviceAdaptor.npu_flash_attention( - query=query, - key=key, - value=value, - seq_lens_cpu=seq_lens_cpu, - head_num=4, - scale_value=0.125, - num_kv_heads=4, - ) - - mock_fusion_attention.assert_not_called() - mock_flash_attention.assert_called_once() - call_kwargs = mock_flash_attention.call_args.kwargs - assert call_kwargs["query"] is query - assert call_kwargs["key"] is key - assert call_kwargs["value"] is value - assert call_kwargs["seq_len"] is seq_lens_cpu - assert call_kwargs["num_heads"] == 4 - assert call_kwargs["num_kv_heads"] == 4 - assert call_kwargs["scale_value"] == 0.125 - torch.testing.assert_close(output, query + 1) - - -def test_a5_npu_flash_attention_uses_python_sequence_lengths(): - query = torch.randn(5, 4, 64, dtype=torch.float16) - key = torch.randn_like(query) - value = torch.randn_like(query) - seq_lens_cpu = torch.tensor([2, 3], dtype=torch.int32) - expected = torch.randn_like(query) - - with mock.patch( - "vllm_ascend.device.device_op.torch_npu.npu_fusion_attention", - return_value=(expected,), - ) as mock_fusion_attention: - output = A5DeviceAdaptor.npu_flash_attention( - query=query, - key=key, - value=value, - seq_lens_cpu=seq_lens_cpu, - head_num=4, - scale_value=0.125, - num_kv_heads=4, - ) - - assert output is expected - call_kwargs = mock_fusion_attention.call_args.kwargs - assert call_kwargs["actual_seq_qlen"] == [2, 5] - assert all(isinstance(seq_len, int) for seq_len in call_kwargs["actual_seq_qlen"]) - assert call_kwargs["actual_seq_kvlen"] is call_kwargs["actual_seq_qlen"] +def test_device_op_placeholder(): + pass diff --git a/tests/ut/device_allocator/test_cpu_binding.py b/tests/ut/device_allocator/test_cpu_binding.py index 952b43147..a1acfc9d4 100644 --- a/tests/ut/device_allocator/test_cpu_binding.py +++ b/tests/ut/device_allocator/test_cpu_binding.py @@ -84,10 +84,6 @@ def test_execute_command_kills_timed_out_process(self, mock_popen): @patch("vllm_ascend.cpu_binding.execute_command") def setUp(self, mock_execute_command): - visible_devices_patcher = patch("vllm_ascend.cpu_binding.ASCEND_RT_VISIBLE_DEVICES", None) - visible_devices_patcher.start() - self.addCleanup(visible_devices_patcher.stop) - mock_execute_command.side_effect = [ ("NPU ID Chip ID Chip Logic ID Chip Name\n0 0 0 Ascend\n0 1 - Mcu\n1 0 1 Ascend", 0), ("| NPU Chip | Process id |\n| 0 0 | 1234 | vllm | 56000 |\n| 1 0 | 1235 | vllm | 56000 |", 0), @@ -111,25 +107,6 @@ def test_get_npu_map_info(self, mock_execute_command): expected = result_list.pop(0) self.assertEqual(npu_map_info, expected) - @patch("vllm_ascend.cpu_binding.execute_command") - def test_get_npu_map_info_without_chip_logic_id_uses_npu_id(self, mock_execute_command): - mock_execute_command.return_value = ( - "NPU ID Slot ID Chip ID Chip Phy-ID Chip Name\n" - "0 0 0 0 Ascend950DT\n" - "1 1 0 1 Ascend950DT\n" - "2 2 0 2 Ascend950DT", - 0, - ) - - self.assertEqual( - self.device_info.get_npu_map_info(), - { - "0": {"0": "0"}, - "1": {"0": "1"}, - "2": {"0": "2"}, - }, - ) - @patch("vllm_ascend.cpu_binding.execute_command") def test_get_running_npus(self, mock_execute_command): mock_execute_command.side_effect = [ @@ -172,45 +149,6 @@ def test_get_running_npus_skips_non_pipe_rows_inside_process_section(self, mock_ self.assertEqual(device_info.get_running_npus(), [0]) - @patch("vllm_ascend.cpu_binding.execute_command") - def test_get_running_npus_from_npu_only_process_table(self, mock_execute_command): - device_info = object.__new__(DeviceInfo) - device_info.npu_map_info = {"0": {"0": "0"}, "1": {"0": "1"}} - mock_execute_command.return_value = ( - "| NPU ID | Process id | Process name | Process memory(MB) |\n" - "| 0 | 2018733 | VLLMWorker_TP | 30212 |\n" - "| 1 | 2018734 | VLLMWorker_TP | 30212 |", - 0, - ) - - self.assertEqual(device_info.get_running_npus(), [0, 1]) - - @patch("vllm_ascend.cpu_binding.execute_command") - def test_get_running_npus_from_npu_chip_process_table_with_extra_spaces(self, mock_execute_command): - device_info = object.__new__(DeviceInfo) - device_info.npu_map_info = {"0": {"0": "0"}, "1": {"0": "1"}} - mock_execute_command.return_value = ( - "| NPU Chip | Process id | Process name | Process memory(MB) |\n" - "| 0 0 | 3428811 | python3.10 | 56600 |\n" - "| 1 0 | 3428818 | python3.10 | 56474 |", - 0, - ) - - self.assertEqual(device_info.get_running_npus(), [0, 1]) - - @patch("vllm_ascend.cpu_binding.execute_command") - def test_get_running_npus_raises_for_ambiguous_npu_only_map(self, mock_execute_command): - device_info = object.__new__(DeviceInfo) - device_info.npu_map_info = {"0": {"0": "0", "1": "1"}} - mock_execute_command.return_value = ( - "| NPU ID | Process id | Process name | Process memory(MB) |\n" - "| 0 | 1234 | vllm | 56000 |", - 0, - ) - - with self.assertRaises(RuntimeError): - device_info.get_running_npus() - @patch("vllm_ascend.cpu_binding.execute_command") def test_parse_topo_affinity(self, mock_execute_command): mock_execute_command.return_value = ("NPU0 X HCCS HCCS HCCS HCCS HCCS HCCS HCCS 0-3", 0) @@ -226,32 +164,7 @@ def test_parse_topo_affinity_skips_affinity_header_and_non_npu_rows(self, mock_e 0, ) - self.assertEqual(device_info.parse_topo_affinity(), {0: [2, 3]}) - - @patch("vllm_ascend.cpu_binding.execute_command") - def test_parse_topo_affinity_skips_topo_matrix_without_cpu_affinity(self, mock_execute_command): - device_info = object.__new__(DeviceInfo) - mock_execute_command.return_value = ( - " NPU0 NPU1 NIC0\n" - "NPU0 X UB NA\n" - "NPU1 UB X NA\n" - "NIC0 NA NA X", - 0, - ) - - self.assertEqual(device_info.parse_topo_affinity(), {}) - - def test_resolve_logic_id_from_npu_only_single_chip_map(self): - device_info = object.__new__(DeviceInfo) - device_info.npu_map_info = {"7": {"0": "7"}} - - self.assertEqual(device_info.resolve_logic_id("7", None), 7) - - def test_resolve_logic_id_from_chip_aware_map(self): - device_info = object.__new__(DeviceInfo) - device_info.npu_map_info = {"0": {"0": "0", "1": "1"}} - - self.assertEqual(device_info.resolve_logic_id("0", "1"), 1) + self.assertEqual(device_info.parse_topo_affinity(), {1: [2, 3]}) def test_expand_cpu_list(self): result = self.device_info.expand_cpu_list("0-2, 4, 6-8") @@ -288,14 +201,6 @@ def test_parse_allowed_cpus_raises_when_field_missing(self, _mock_open, _mock_ex class TestCpuAlloc(unittest.TestCase): @patch("vllm_ascend.cpu_binding.execute_command") def setUp(self, mock_execute_command): - visible_devices_patcher = patch("vllm_ascend.cpu_binding.ASCEND_RT_VISIBLE_DEVICES", None) - visible_devices_patcher.start() - self.addCleanup(visible_devices_patcher.stop) - - device_type_patcher = patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A2) - device_type_patcher.start() - self.addCleanup(device_type_patcher.stop) - mock_execute_command.side_effect = [ ("NPU ID Chip ID Chip Logic ID Chip Name\n0 0 0 Ascend\n0 1 - Mcu\n1 0 1 Ascend", 0), ("| NPU Chip | Process id |\n| 0 0 | 1234 | vllm | 56000 |\n| 1 0 | 1235 | vllm | 56000 |", 0), @@ -331,8 +236,6 @@ def test_binding_mode_table(self, mock_get_device_type): self.assertEqual(self.cpu_alloc._binding_mode(), "topo_affinity") mock_get_device_type.return_value = AscendDeviceType.A3 self.assertEqual(self.cpu_alloc._binding_mode(), "global_slice") - mock_get_device_type.return_value = AscendDeviceType.A5 - self.assertEqual(self.cpu_alloc._binding_mode(), "global_slice") @patch("vllm_ascend.cpu_binding.get_ascend_device_type") def test_build_cpu_pools_fallback_to_global_slice(self, mock_get_device_type): @@ -442,8 +345,7 @@ def test_build_global_slice_cpu_pool_fallback_to_running_len(self): self.assertEqual(self.cpu_alloc.npu_cpu_pool[0], [0, 1, 2, 3, 4, 5]) self.assertEqual(self.cpu_alloc.npu_cpu_pool[1], [6, 7, 8, 9, 10, 11]) - @patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A2) - def test_build_global_slice_cpu_pool_raises_when_cpu_insufficient(self, _mock_get_device_type): + def test_build_global_slice_cpu_pool_raises_when_cpu_insufficient(self): self.cpu_alloc.device_info.running_npu_list = [0, 1] self.cpu_alloc.device_info.allowed_cpus = list(range(8)) self.cpu_alloc.device_info.total_logic_npus = 2 @@ -451,17 +353,6 @@ def test_build_global_slice_cpu_pool_raises_when_cpu_insufficient(self, _mock_ge with self.assertRaises(RuntimeError): self.cpu_alloc.build_global_slice_cpu_pool() - @patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A5) - def test_build_global_slice_cpu_pool_allows_ascend_950_without_irq_reservation(self, _mock_get_device_type): - self.cpu_alloc.device_info.running_npu_list = [0, 1] - self.cpu_alloc.device_info.allowed_cpus = list(range(6)) - self.cpu_alloc.device_info.total_logic_npus = 2 - - self.cpu_alloc.build_global_slice_cpu_pool() - - self.assertEqual(self.cpu_alloc.npu_cpu_pool[0], [0, 1, 2]) - self.assertEqual(self.cpu_alloc.npu_cpu_pool[1], [3, 4, 5]) - def test_build_global_slice_cpu_pool_raises_invalid_npu_id(self): self.cpu_alloc.device_info.running_npu_list = [2] self.cpu_alloc.device_info.allowed_cpus = list(range(12)) @@ -481,9 +372,8 @@ def test_build_global_slice_cpu_pool_returns_when_running_or_allowed_empty(self) self.cpu_alloc.build_global_slice_cpu_pool() self.assertEqual(self.cpu_alloc.npu_cpu_pool, {}) - @patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A2) @patch("vllm_ascend.cpu_binding.execute_command") - def test_allocate(self, _mock_execute_command, _mock_get_device_type): + def test_allocate(self, _mock_execute_command): self.cpu_alloc.device_info.running_npu_list = [0] self.cpu_alloc.npu_cpu_pool = {0: [0, 1, 2, 3, 4]} self.cpu_alloc.allocate() @@ -494,30 +384,6 @@ def test_allocate(self, _mock_execute_command, _mock_get_device_type): with self.assertRaises(RuntimeError): self.cpu_alloc.allocate() - @patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A5) - def test_allocate_ascend_950_uses_unreserved_cpus_for_main(self, _mock_get_device_type): - self.cpu_alloc.device_info.running_npu_list = [0] - self.cpu_alloc.npu_cpu_pool = {0: [0, 1, 2, 3, 4]} - - self.cpu_alloc.allocate() - - self.assertEqual(self.cpu_alloc.assign_main[0], [0, 1, 2]) - self.assertEqual(self.cpu_alloc.assign_acl[0], [3]) - self.assertEqual(self.cpu_alloc.assign_rel[0], [4]) - - self.cpu_alloc.assign_main = {} - self.cpu_alloc.assign_acl = {} - self.cpu_alloc.assign_rel = {} - self.cpu_alloc.npu_cpu_pool = {0: [0, 1, 2]} - self.cpu_alloc.allocate() - self.assertEqual(self.cpu_alloc.assign_main[0], [0]) - self.assertEqual(self.cpu_alloc.assign_acl[0], [1]) - self.assertEqual(self.cpu_alloc.assign_rel[0], [2]) - - self.cpu_alloc.npu_cpu_pool = {0: [0, 1]} - with self.assertRaises(RuntimeError): - self.cpu_alloc.allocate() - @patch("vllm_ascend.cpu_binding.execute_command") def test_bind_threads(self, mock_execute_command): thread_message = "1234 1234 ? 00:00:03 acl_thread\n4567 4567 ? 00:00:03 release_thread" @@ -553,15 +419,6 @@ def test_bind_npu_irq_a3_uses_card_chip_mapping( class TestCpuBindingSupplemental(unittest.TestCase): - def setUp(self): - visible_devices_patcher = patch("vllm_ascend.cpu_binding.ASCEND_RT_VISIBLE_DEVICES", None) - visible_devices_patcher.start() - self.addCleanup(visible_devices_patcher.stop) - - device_type_patcher = patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A2) - device_type_patcher.start() - self.addCleanup(device_type_patcher.stop) - def test_cpu_to_mask_handles_single_and_multi_group_masks(self): self.assertEqual(CpuAlloc.cpu_to_mask(3), "00000008") self.assertEqual(CpuAlloc.cpu_to_mask(35), "00000008,00000000") @@ -810,36 +667,17 @@ def test_bind_threads_binds_main_acl_and_release_threads(self, _mock_execute_com ) mock_bind_memory.assert_called_once_with("1000", 0) - @patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A2) @patch("vllm_ascend.cpu_binding.os.access", return_value=False) @patch("vllm_ascend.cpu_binding.execute_command") - def test_bind_npu_irq_returns_when_irq_path_not_writable( - self, mock_execute_command, _mock_access, _mock_get_device_type - ): - cpu_alloc = make_cpu_alloc() - cpu_alloc.bind_npu_irq() - - mock_execute_command.assert_not_called() - - @patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A5) - @patch("vllm_ascend.cpu_binding.os.access") - @patch("vllm_ascend.cpu_binding.execute_command") - def test_bind_npu_irq_skips_on_ascend_950(self, mock_execute_command, mock_access, _mock_get_device_type): + def test_bind_npu_irq_returns_when_irq_path_not_writable(self, mock_execute_command, _mock_access): cpu_alloc = make_cpu_alloc() - cpu_alloc.device_info.running_npu_list = [0] - cpu_alloc.npu_cpu_pool = {0: [8, 9, 10]} - cpu_alloc.bind_npu_irq() - mock_access.assert_not_called() mock_execute_command.assert_not_called() - @patch("vllm_ascend.cpu_binding.get_ascend_device_type", return_value=AscendDeviceType.A2) @patch("vllm_ascend.cpu_binding.os.access", return_value=True) @patch("vllm_ascend.cpu_binding.execute_command") - def test_bind_npu_irq_returns_when_current_npu_has_no_cpu_pool( - self, mock_execute_command, _mock_access, _mock_get_device_type - ): + def test_bind_npu_irq_returns_when_current_npu_has_no_cpu_pool(self, mock_execute_command, _mock_access): cpu_alloc = make_cpu_alloc() cpu_alloc.device_info.running_npu_list = [0] cpu_alloc.npu_cpu_pool = {} diff --git a/tests/ut/distributed/test_parallel_state.py b/tests/ut/distributed/test_parallel_state.py index a75c0a7ef..d555382ee 100644 --- a/tests/ut/distributed/test_parallel_state.py +++ b/tests/ut/distributed/test_parallel_state.py @@ -1,4 +1,3 @@ -from types import SimpleNamespace from unittest.mock import MagicMock, patch import pytest @@ -14,7 +13,6 @@ destroy_ascend_model_parallel, get_flashcomm2_odp_group, get_flashcomm2_otp_group, - get_global_rank, get_lmhead_tp_group, get_mc2_group, get_otp_group, @@ -90,68 +88,3 @@ def test_init_ascend_model_parallel(mock_distributed, parallel_config): assert _FLASHCOMM2_OTP is None assert _FLASHCOMM2_ODP is None assert _P_TP is None - - -def _build_parallel_config( - tensor_parallel_size=1, - pipeline_parallel_size=1, - prefill_context_parallel_size=1, - data_parallel_index=0, -): - return SimpleNamespace( - tensor_parallel_size=tensor_parallel_size, - pipeline_parallel_size=pipeline_parallel_size, - prefill_context_parallel_size=prefill_context_parallel_size, - data_parallel_index=data_parallel_index, - ) - - -@pytest.mark.parametrize( - "parallel_config_kwargs, rank_in_group, expected", - [ - # No parallelism at all (single card): replica_size == 1. - (dict(tensor_parallel_size=1), 0, 0), - # TP only: rank_in_group is the local rank within the single replica. - (dict(tensor_parallel_size=4), 0, 0), - (dict(tensor_parallel_size=4), 3, 3), - # Dense DP: world group spans one replica, rank_in_group is local and - # data_parallel_index supplies the DP offset. - (dict(tensor_parallel_size=4, data_parallel_index=0), 2, 2), - (dict(tensor_parallel_size=4, data_parallel_index=1), 2, 6), - # MoE DP / external_launcher: world group spans all DP ranks, so - # rank_in_group is already global; the modulo strips the DP offset and - # data_parallel_index re-adds it (result equals rank_in_group). - (dict(tensor_parallel_size=4, data_parallel_index=1), 6, 6), - (dict(tensor_parallel_size=4, data_parallel_index=1), 7, 7), - # TP * PP * prefill-CP all contribute to replica_size; DCP/EP do not. - (dict(tensor_parallel_size=2, pipeline_parallel_size=2, data_parallel_index=1), 1, 5), - ( - dict( - tensor_parallel_size=2, pipeline_parallel_size=2, prefill_context_parallel_size=2, data_parallel_index=1 - ), - 3, - 11, - ), - ], -) -def test_get_global_rank(parallel_config_kwargs, rank_in_group, expected): - parallel_config = _build_parallel_config(**parallel_config_kwargs) - with patch("vllm_ascend.distributed.parallel_state.get_world_group") as mock_group: - mock_group.return_value.rank_in_group = rank_in_group - assert get_global_rank(parallel_config) == expected - - -def test_get_global_rank_defaults_to_current_config(): - parallel_config = _build_parallel_config(tensor_parallel_size=4, data_parallel_index=1) - mock_vllm_config = MagicMock() - mock_vllm_config.parallel_config = parallel_config - with ( - patch( - "vllm_ascend.distributed.parallel_state.get_current_vllm_config", - return_value=mock_vllm_config, - ), - patch("vllm_ascend.distributed.parallel_state.get_world_group") as mock_group, - ): - mock_group.return_value.rank_in_group = 3 - # data_parallel_index(1) * replica_size(4) + 3 == 7 - assert get_global_rank() == 7 diff --git a/tests/ut/kv_offload/test_mooncake_connector.py b/tests/ut/kv_offload/test_mooncake_connector.py index 723cefbb7..2af6a014f 100644 --- a/tests/ut/kv_offload/test_mooncake_connector.py +++ b/tests/ut/kv_offload/test_mooncake_connector.py @@ -70,7 +70,6 @@ ensure_zmq_recv, ensure_zmq_send, group_concurrent_contiguous, - split_if_not_byte_contiguous, string_to_int64_hash, zmq_ctx, ) @@ -92,7 +91,6 @@ def make_agent_metadata(**overrides: Any) -> MooncakeAgentMetadata: "block_size_scale": [[1]], "num_blocks": 2, "block_lens": [[1024]], - "block_strides": [[1024]], } metadata.update(overrides) return MooncakeAgentMetadata(**metadata) @@ -311,7 +309,6 @@ def setUp(self): side_channel_port=30000, local_kv_caches_base_addr=[[0x1000], [0x2000]], block_len_per_addr=[[1024], [2048]], - block_stride_per_addr=[[1024], [2048]], ready_event=self.ready_event, vllm_config=self.vllm_config, kv_caches=self.kv_caches, @@ -347,13 +344,13 @@ def test_add_request(self): self.assertEqual(queued["num_computed_tokens"], 0) def test_mark_and_is_failed(self): - self.thread._mark_failed_recv_request("req1", [[10, 20]]) + self.thread._mark_failed_recv_request("req1", [10, 20]) self.assertTrue(self.thread._is_failed_recv_request("req1")) self.assertIn(10, self.thread.invalid_block_ids) self.assertIn(20, self.thread.invalid_block_ids) def test_clear_failed_recv_request(self): - self.thread._mark_failed_recv_request("req2", [[30]]) + self.thread._mark_failed_recv_request("req2", [30]) self.thread._clear_failed_recv_request("req2") self.assertFalse(self.thread._is_failed_recv_request("req2")) @@ -386,7 +383,6 @@ def setUp(self): side_channel_port=30000, local_kv_caches_base_addr=[[0x1000], [0x2000]], block_len_per_addr=[[1024], [2048]], - block_stride_per_addr=[[1024], [2048]], ready_event=self.ready_event, vllm_config=self.vllm_config, kv_caches=self.kv_caches, @@ -443,7 +439,6 @@ def setUp(self): side_channel_port=30000, local_kv_caches_base_addr=[[0x1000], [0x2000]], block_len_per_addr=[[1024], [2048]], - block_stride_per_addr=[[1024], [2048]], ready_event=self.ready_event, vllm_config=self.vllm_config, kv_caches=self.kv_caches, @@ -463,12 +458,10 @@ def setUp(self): "all_task_done": True, } self.thread.kv_group2layeridx = {0: ({"kv_cache_spec_type": "FullAttentionSpec"}, [0])} - self.thread.group_compress_ratios = {0: 1} self.thread.block_size_scale = [[1]] self.thread.task_tracker = MagicMock() self.engine.batch_transfer_sync_read.return_value = 0 self.thread.remote_te_port = {"remote_engine": {6666: 7777}} - self.thread.remote_block_stride_per_addr["remote_engine"][6666] = [[1024]] @patch.object(KVCacheRecvingThread, "_transfer_kv_cache_all_groups") @patch.object(KVCacheRecvingThread, "_send_done_recv_signal") @@ -519,35 +512,6 @@ def test_transfer_prefix_cache_uses_computed_token_offset(self, mock_get_meta): self.assertEqual(call_args[3], [3 * 1024]) mock_get_meta.assert_not_called() - @patch.object(KVCacheRecvingThread, "_get_remote_metadata") - def test_transfer_prefix_cache_offset_uses_compress_ratio(self, mock_get_meta): - req = dict(self.test_req) - req["local_block_ids"] = [[1, 2]] - req["remote_block_ids"] = [[3, 4]] - req["num_computed_tokens"] = 32 - self.thread.kv_group2layeridx = { - 0: ( - { - "kv_cache_spec_type": "UniformTypeKVCacheSpecs", - "kv_cache_spec": {"layer_0": {"compress_ratio": 4}}, - }, - [0], - ) - } - self.thread.group_compress_ratios = {0: 4} - with patch("vllm_ascend.distributed.kv_transfer.kv_p2p.mooncake_connector.get_ascend_config") as mock_config: - mock_config.return_value.enable_kv_nz = False - self.thread.kv_caches_base_addr["remote_engine"] = {6666: [[0x3000]]} - self.thread.block_size_scale = [[2]] - self.thread.remote_block_size_scale["remote_engine"] = {6666: [[2]]} - self.thread._transfer_kv_cache_all_groups(req) - - call_args, _ = self.engine.batch_transfer_sync_read.call_args - self.assertEqual(call_args[1], [0x1000 + 2 * 1024]) - self.assertEqual(call_args[2], [0x3000 + 7 * 1024]) - self.assertEqual(call_args[3], [3 * 1024]) - mock_get_meta.assert_not_called() - @patch.object(KVCacheRecvingThread, "_get_remote_metadata") def test_transfer_prefix_cache_trims_remote_kernel_blocks(self, mock_get_meta): req = dict(self.test_req) @@ -567,52 +531,6 @@ def test_transfer_prefix_cache_trims_remote_kernel_blocks(self, mock_get_meta): self.assertEqual(call_args[3], [2 * 1024]) mock_get_meta.assert_not_called() - @patch.object(KVCacheRecvingThread, "_get_remote_metadata") - def test_transfer_kv_cache_uses_block_stride_for_block_offsets(self, mock_get_meta): - req = dict(self.test_req) - req["local_block_ids"] = [[1, 2]] - req["remote_block_ids"] = [[3, 4]] - with patch("vllm_ascend.distributed.kv_transfer.kv_p2p.mooncake_connector.get_ascend_config") as mock_config: - mock_config.return_value.enable_kv_nz = False - self.thread.kv_caches_base_addr["remote_engine"] = {6666: [[0x3000]]} - self.thread.remote_block_size_scale["remote_engine"] = {6666: [[1]]} - self.thread.block_len_per_addr = [[1024]] - self.thread.block_stride_per_addr = [[2048]] - self.thread.remote_block_stride_per_addr["remote_engine"][6666] = [[4096]] - - self.thread._transfer_kv_cache_all_groups(req) - - call_args, _ = self.engine.batch_transfer_sync_read.call_args - self.assertEqual(call_args[1], [0x1000 + 1 * 2048, 0x1000 + 2 * 2048]) - self.assertEqual(call_args[2], [0x3000 + 3 * 4096, 0x3000 + 4 * 4096]) - self.assertEqual(call_args[3], [1024, 1024]) - mock_get_meta.assert_not_called() - - def test_append_mamba_transfer_meta_uses_block_stride_for_block_offsets(self): - src_list: list[int] = [] - dst_list: list[int] = [] - length_list: list[int] = [] - - self.thread._append_mamba_transfer_meta( - src_list, - dst_list, - length_list, - group_spec={"kv_cache_spec_type": "MambaSpec"}, - src_layer_base_addr=[0x1000, 0x2000], - dst_layer_base_addr=[0x3000, 0x4000], - block_len=[100, 200], - block_stride=[128, 256], - remote_block_stride=[160, 512], - remote_block_id=3, - local_block_id=2, - tp_num_need_pulls=1, - remote_tp_offset=0, - ) - - self.assertEqual(src_list, [0x1000 + 2 * 128, 0x2000 + 2 * 256]) - self.assertEqual(dst_list, [0x3000 + 3 * 160, 0x4000 + 3 * 512]) - self.assertEqual(length_list, [100, 200]) - def test_transfer_kv_cache_failure(self): self.engine.batch_transfer_sync_read.return_value = -1 self.thread.kv_caches_base_addr["remote_engine"] = {6666: [[0x3000]]} @@ -638,7 +556,6 @@ def setUp(self): side_channel_port=30000, local_kv_caches_base_addr=[[0x1000], [0x2000]], block_len_per_addr=[[1024], [2048]], - block_stride_per_addr=[[1024], [2048]], ready_event=self.ready_event, vllm_config=self.vllm_config, kv_caches=self.kv_caches, @@ -667,7 +584,6 @@ def test_get_remote_metadata_success(self, mock_recv, mock_send): mock_send.assert_called_once_with(mock_socket, self.thread.encoder.encode((GET_META_MSG, "")), "host1:5555") mock_recv.assert_called_once_with(mock_socket, self.thread.remote_poller, "host1:5555") self.assertEqual(self.thread.kv_caches_base_addr["remote_engine"][5555], [[0x3000], [0x4000]]) - self.assertEqual(self.thread.remote_block_stride_per_addr["remote_engine"][5555], [[1024]]) @patch("vllm_ascend.distributed.kv_transfer.kv_p2p.mooncake_connector.ensure_zmq_send") @patch( @@ -705,7 +621,6 @@ def setUp(self): side_channel_port=30000, local_kv_caches_base_addr=[[0x1000], [0x2000]], block_len_per_addr=[[1024], [2048]], - block_stride_per_addr=[[1024], [2048]], ready_event=self.ready_event, vllm_config=self.vllm_config, kv_caches=self.kv_caches, @@ -970,36 +885,6 @@ def test_group_concurrent_contiguous_empty(self): self.assertEqual(src_groups, []) self.assertEqual(dst_groups, []) - def test_group_concurrent_contiguous_uses_stride_for_memory_contiguity(self): - src: list[int] = [1, 2] - dst: list[int] = [10, 11] - - src_groups, dst_groups = group_concurrent_contiguous( - src, - dst, - src_block_stride=4096, - dst_block_stride=2048, - block_len=1024, - ) - - self.assertEqual(src_groups, [[1], [2]]) - self.assertEqual(dst_groups, [[10], [11]]) - - def test_split_if_not_byte_contiguous_fast_path(self): - src_groups = [[1, 2]] - dst_groups = [[10, 11]] - - src_result, dst_result = split_if_not_byte_contiguous( - src_groups, - dst_groups, - src_block_stride=1024, - dst_block_stride=1024, - block_len=1024, - ) - - self.assertIs(src_result, src_groups) - self.assertIs(dst_result, dst_groups) - def test_string_to_int64_hash(self): hash1 = string_to_int64_hash("test_string") hash2 = string_to_int64_hash("test_string") @@ -1184,98 +1069,6 @@ def test_request_finished_no_remote_decode(self): self.assertFalse(delay_free) self.assertIsNone(params) - def test_get_transfer_block_ids_trims_attention_mtp_blocks(self): - self.scheduler.group_transfer_info = [ - types.SimpleNamespace( - tokens_per_block=16, - blocks_per_window=0, - is_state_group=False, - ) - ] - - block_ids = self.scheduler._get_transfer_block_ids(([10, 11, 12, 13, 14],), prompt_len=33) - - self.assertEqual(block_ids, ([10, 11, 12],)) - - def test_get_transfer_block_ids_keeps_state_group(self): - self.scheduler.group_transfer_info = [ - types.SimpleNamespace( - tokens_per_block=16, - blocks_per_window=0, - is_state_group=True, - ) - ] - - block_ids = self.scheduler._get_transfer_block_ids(([20, 21, 22, 23],), prompt_len=16) - - self.assertEqual(block_ids, ([20, 21, 22, 23],)) - - def test_get_transfer_block_ids_uses_compressed_prompt_len(self): - self.scheduler.group_transfer_info = [ - types.SimpleNamespace( - tokens_per_block=32, - blocks_per_window=0, - is_state_group=False, - ) - ] - - block_ids = self.scheduler._get_transfer_block_ids(([30, 31, 32, 33],), prompt_len=64) - - self.assertEqual(block_ids, ([30, 31],)) - - def test_get_transfer_block_ids_trims_sliding_window_mtp_blocks(self): - self.scheduler.group_transfer_info = [ - types.SimpleNamespace( - tokens_per_block=16, - blocks_per_window=3, - is_state_group=False, - ) - ] - - block_ids = self.scheduler._get_transfer_block_ids(([40, 41, 42, 43, 44],), prompt_len=48) - - self.assertEqual(block_ids, ([40, 41, 42],)) - - def test_get_swa_transfer_block_ids_clips_sliding_window_group(self): - self.scheduler.group_transfer_info = [ - types.SimpleNamespace( - tokens_per_block=16, - blocks_per_window=3, - is_state_group=False, - ) - ] - - block_ids = self.scheduler._get_swa_transfer_block_ids(([40, 41, 42, 43, 44],)) - - self.assertEqual(block_ids, ([42, 43, 44],)) - - def test_get_swa_transfer_block_ids_drops_zero_from_sliding_window_tail(self): - self.scheduler.group_transfer_info = [ - types.SimpleNamespace( - tokens_per_block=16, - blocks_per_window=2, - is_state_group=False, - ) - ] - - block_ids = self.scheduler._get_swa_transfer_block_ids(([0, 10],)) - - self.assertEqual(block_ids, ([10],)) - - def test_transfer_block_ids_trims_mtp_before_swa_zero_filter(self): - self.scheduler.group_transfer_info = [ - types.SimpleNamespace( - tokens_per_block=16, - blocks_per_window=3, - is_state_group=False, - ) - ] - - block_ids = self.scheduler._get_transfer_block_ids(([0, 10, 11, 12, 13],), prompt_len=32) - block_ids = self.scheduler._get_swa_transfer_block_ids(block_ids) - - self.assertEqual(block_ids, ([10],)) - class TestUtils(unittest.TestCase): def test_string_to_int64_hash(self): diff --git a/tests/ut/kv_offload/test_mooncake_hybrid_connector.py b/tests/ut/kv_offload/test_mooncake_hybrid_connector.py deleted file mode 100644 index f0361d1b0..000000000 --- a/tests/ut/kv_offload/test_mooncake_hybrid_connector.py +++ /dev/null @@ -1,95 +0,0 @@ -import sys -import types -import unittest -from unittest.mock import MagicMock - -fake_engine = types.ModuleType("mooncake.engine") -fake_engine.TransferEngine = MagicMock() # type: ignore[attr-defined] -sys.modules["mooncake.engine"] = fake_engine - -from vllm.v1.request import RequestStatus # noqa: E402 - -from vllm_ascend.distributed.kv_transfer.kv_p2p.mooncake_hybrid_connector import ( # noqa: E402 - MooncakeConnectorScheduler, -) - - -class MockRequest: - def __init__( - self, - request_id, - prompt_token_ids, - kv_transfer_params, - status, - num_prompt_tokens=None, - ): - self.request_id = request_id - self.prompt_token_ids = prompt_token_ids - if num_prompt_tokens is None: - num_prompt_tokens = len(prompt_token_ids) if prompt_token_ids is not None else 0 - self.num_prompt_tokens = num_prompt_tokens - self.kv_transfer_params = kv_transfer_params - self.status = status - self.output_token_ids = [101] - - -class TestMooncakeHybridConnectorScheduler(unittest.TestCase): - def _make_scheduler(self): - scheduler = object.__new__(MooncakeConnectorScheduler) - scheduler.use_hybrid = True - scheduler.use_compress = True - scheduler.num_swa_blocks = [0, 2] - scheduler.group_block_size = [128, 128] - scheduler.group_compress_ratio = [4, 1] - scheduler._reqs_need_send = {} - scheduler.block_size = 128 - scheduler.engine_id = "engine" - scheduler.side_channel_host = "127.0.0.1" - scheduler.side_channel_port = 12345 - scheduler.tp_size = 1 - scheduler.multi_nodes_meta_mapping = {} - return scheduler - - def test_compute_transfer_block_ids_trims_swa_groups(self): - scheduler = self._make_scheduler() - block_ids = (list(range(10)), [100, 101, 102, 103]) - - transfer_block_ids = scheduler._compute_transfer_block_ids(block_ids, prompt_len=129) - - self.assertEqual(transfer_block_ids, ([0], [100, 101])) - - def test_request_finished_trims_before_swa_clip(self): - scheduler = self._make_scheduler() - request = MockRequest( - "req1", - prompt_token_ids=list(range(129)), - kv_transfer_params={"do_remote_decode": True}, - status=RequestStatus.FINISHED_LENGTH_CAPPED, - ) - block_ids = (list(range(10)), [100, 101, 102, 103]) - - delay_free, params = scheduler.request_finished_all_groups(request, block_ids) - - self.assertTrue(delay_free) - self.assertIsNotNone(params) - self.assertEqual(params["remote_block_ids"], ([0], [100, 101])) - self.assertEqual(params["num_prompt_blocks"], 2) - self.assertIn("req1", scheduler._reqs_need_send) - - def test_request_finished_uses_num_prompt_tokens(self): - scheduler = self._make_scheduler() - request = MockRequest( - "req1", - prompt_token_ids=None, - kv_transfer_params={"do_remote_decode": True}, - status=RequestStatus.FINISHED_LENGTH_CAPPED, - num_prompt_tokens=129, - ) - block_ids = (list(range(10)), [100, 101, 102, 103]) - - delay_free, params = scheduler.request_finished_all_groups(request, block_ids) - - self.assertTrue(delay_free) - self.assertIsNotNone(params) - self.assertEqual(params["remote_block_ids"], ([0], [100, 101])) - self.assertEqual(params["num_prompt_blocks"], 2) diff --git a/tests/ut/kv_offload/test_mooncake_layerwise_connector.py b/tests/ut/kv_offload/test_mooncake_layerwise_connector.py index 5661c4278..a2ec71559 100644 --- a/tests/ut/kv_offload/test_mooncake_layerwise_connector.py +++ b/tests/ut/kv_offload/test_mooncake_layerwise_connector.py @@ -1,5 +1,4 @@ import contextlib -import importlib.util import os import sys import threading @@ -14,17 +13,6 @@ fake_engine = types.ModuleType("mooncake.engine") fake_engine.TransferEngine = MagicMock() # type: ignore[attr-defined] sys.modules["mooncake.engine"] = fake_engine -fake_torch_npu = types.ModuleType("torch_npu") -fake_torch_npu.__spec__ = importlib.util.spec_from_loader("torch_npu", loader=None) -fake_torch_npu.npu = MagicMock() # type: ignore[attr-defined] -fake_torch_npu.npu.current_device = MagicMock(return_value=0) # type: ignore[attr-defined] -fake_torch_npu.npu.Stream = MagicMock # type: ignore[attr-defined] -fake_torch_npu.npu_fusion_attention = MagicMock() # type: ignore[attr-defined] -sys.modules.setdefault("torch_npu", fake_torch_npu) -torch.npu = fake_torch_npu.npu # type: ignore[attr-defined] -fake_uvloop = types.ModuleType("uvloop") -fake_uvloop.__spec__ = importlib.util.spec_from_loader("uvloop", loader=None) -sys.modules.setdefault("uvloop", fake_uvloop) # Clean up stale mock modules installed by other test files # (e.g., ascend_store/_mock_deps.py) that replace real kv_transfer @@ -615,16 +603,12 @@ class MockRequest: def __init__(self, request_id, prompt_token_ids=None, kv_transfer_params=None, status=None): self.request_id = request_id self.prompt_token_ids = prompt_token_ids or [1, 2, 3, 4] - self.prompt_embeds = None self.kv_transfer_params = kv_transfer_params or {} self.status = status or "running" self.output_token_ids = [101, 102] self.num_computed_tokens = 0 - self.num_prompt_tokens = len(self.prompt_token_ids) - self.max_tokens = 16 self.all_token_ids = list(self.prompt_token_ids) - self._all_token_ids = list(self.prompt_token_ids) class TestMooncakeLayerwiseConnectorMetadata(unittest.TestCase): @@ -671,29 +655,6 @@ def test_get_num_new_matched_tokens(self): self.assertEqual(tokens, 4) self.assertTrue(async_flag) - def test_get_num_new_matched_tokens_hybrid_excludes_last_token(self): - self.scheduler.need_truncate = True - request = MockRequest("req1", prompt_token_ids=list(range(17)), kv_transfer_params={"do_remote_prefill": True}) - - tokens, async_flag = self.scheduler.get_num_new_matched_tokens(request, 0) - - self.assertEqual(tokens, 16) - self.assertTrue(async_flag) - - def test_get_num_new_matched_tokens_hybrid_truncates_prefill_request(self): - self.scheduler.need_truncate = True - request = MockRequest("req1", prompt_token_ids=list(range(4)), kv_transfer_params={"do_remote_decode": True}) - - tokens, async_flag = self.scheduler.get_num_new_matched_tokens(request, 0) - - self.assertEqual(tokens, 0) - self.assertFalse(async_flag) - self.assertEqual(request.prompt_token_ids, [0, 1, 2]) - self.assertEqual(request._all_token_ids, [0, 1, 2]) - self.assertEqual(request.num_prompt_tokens, 3) - self.assertEqual(request.max_tokens, 1) - self.assertTrue(request.kv_transfer_params["_p_side_truncated"]) - def test_build_connector_meta(self): self.scheduler.vllm_config.kv_transfer_config.is_kv_consumer = True request = MockRequest("req1") @@ -714,21 +675,6 @@ def test_build_connector_meta(self): self.assertEqual(meta.requests["req1"].remote_block_ids, [[1, 2, 3]]) self.assertEqual(len(self.scheduler._reqs_need_recv), 0) - def test_update_state_after_alloc_hybrid_trims_remote_block_with_only_last_token(self): - self.scheduler.need_truncate = True - request = MockRequest( - "req1", - prompt_token_ids=list(range(17)), - kv_transfer_params={"do_remote_prefill": True, "metaserver": "http://meta"}, - ) - blocks = _MockBlocks(unhashed=[], block_ids_tuple=([4, 5],)) - self.scheduler.executor.submit = MagicMock() - - self.scheduler.update_state_after_alloc(request, blocks, num_external_tokens=16) - - _, kwargs = self.scheduler.executor.submit.call_args - self.assertEqual(kwargs["message"]["remote_block_ids"], ([4],)) - class _MockBlocks: def __init__(self, unhashed, block_ids_tuple=None): diff --git a/tests/ut/model_loader/netloader/test_netloader.py b/tests/ut/model_loader/netloader/test_netloader.py index 0dedafc69..ced55d56e 100644 --- a/tests/ut/model_loader/netloader/test_netloader.py +++ b/tests/ut/model_loader/netloader/test_netloader.py @@ -38,7 +38,6 @@ class DummyVllmConfig: device_config = DummyDeviceConfig() parallel_config = DummyParallelConfig() additional_config = None - quant_config = None class DummyModelConfig: diff --git a/tests/ut/ops/a2/test_gdn_chunk_meta.py b/tests/ut/ops/a2/test_gdn_chunk_meta.py index b82a96fa5..8b3bbc06c 100644 --- a/tests/ut/ops/a2/test_gdn_chunk_meta.py +++ b/tests/ut/ops/a2/test_gdn_chunk_meta.py @@ -157,8 +157,6 @@ def test_chunk_gated_delta_rule_fwd_threads_prebuilt_chunk_offsets( (), { "block_indices_cumsum": None, - "cu_seqlens_host": (0, 4, 7), - "chunk_indices_chunk64_host": (0, 0, 1, 0), "chunk_indices_chunk64": None, "chunk_offsets_chunk64": chunk_offsets, "update_chunk_offsets_chunk64": update_chunk_offsets, @@ -259,86 +257,6 @@ def fake_chunk_fwd_o_update(*args, **kwargs): assert pcp_calls == [("o_update", chunk_offsets)] -def test_chunk_gated_delta_rule_fwd_uses_prebuilt_host_meta_without_runtime_tolist( - monkeypatch: pytest.MonkeyPatch, -): - prebuilt_meta = type( - "PrebuiltMeta", - (), - { - "block_indices_cumsum": None, - "cu_seqlens_host": (0, 4, 7), - "chunk_indices_chunk64_host": (0, 0, 1, 0), - "chunk_indices_chunk64": torch.tensor([[0, 0], [1, 0]], dtype=torch.int32), - "chunk_offsets_chunk64": torch.tensor([0, 1, 2], dtype=torch.int32), - "update_chunk_offsets_chunk64": torch.tensor([0, 2, 4], dtype=torch.int32), - "final_chunk_indices_chunk64": torch.tensor([1, 3], dtype=torch.int32), - "chunk_indices_large_block": None, - }, - )() - - q = _DummyTensor("q") - k = _DummyTensor("k") - v = _DummyTensor("v") - g = _DummyTensor("g") - beta = _DummyTensor("beta") - initial_state = _DummyTensor("initial_state") - - captured: dict[str, tuple[int, ...] | None] = {} - - monkeypatch.setattr(chunk, "get_forward_context", lambda: type("Ctx", (), {"attn_metadata": None})()) - monkeypatch.setattr( - chunk, - "get_pcp_group", - lambda: type("Group", (), {"world_size": 1, "rank_in_group": 0})(), - ) - monkeypatch.setattr(chunk, "chunk_local_cumsum", lambda *args, **kwargs: _DummyTensor("g_cumsum")) - monkeypatch.setattr(chunk, "chunk_scaled_dot_kkt_fwd", lambda *args, **kwargs: _DummyTensor("A")) - monkeypatch.setattr(chunk, "solve_tril", lambda *args, **kwargs: _DummyTensor("A_solved")) - monkeypatch.setattr(chunk, "recompute_w_u_fwd", lambda *args, **kwargs: (_DummyTensor("w"), _DummyTensor("u"))) - monkeypatch.setattr( - torch.ops._C_ascend, - "chunk_gated_delta_rule_fwd_h", - lambda *args, **kwargs: ( - captured.update( - { - "cu_seqlens": kwargs["cu_seqlens"], - "chunk_indices": kwargs["chunk_indices"], - } - ) - or (_DummyTensor("h"), _DummyTensor("v_new"), _DummyTensor("final_state")) - ), - raising=False, - ) - monkeypatch.setattr( - torch.ops._C_ascend, - "chunk_fwd_o", - lambda *args, **kwargs: _DummyTensor("o_ascend"), - raising=False, - ) - monkeypatch.setattr( - torch.Tensor, - "tolist", - lambda self: pytest.fail("runtime should not convert device tensors to host tuples"), - ) - - chunk.chunk_gated_delta_rule_fwd( - q=q, - k=k, - v=v, - g=g, - beta=beta, - scale=1.0, - initial_state=initial_state, - output_final_state=False, - cu_seqlens=torch.tensor([0, 4, 7], dtype=torch.int32), - prebuilt_meta=prebuilt_meta, - ) - - assert captured["cu_seqlens"] == prebuilt_meta.cu_seqlens_host - assert captured["chunk_indices"] == prebuilt_meta.chunk_indices_chunk64_host - - def test_build_chunk_meta_device_rejects_non_npu_input(): cu_seqlens = torch.tensor([0, 4, 4, 12], dtype=torch.int32) diff --git a/tests/ut/ops/a3_2/test_activation.py b/tests/ut/ops/a3_2/test_activation.py index 57a487721..e1bb4fa41 100644 --- a/tests/ut/ops/a3_2/test_activation.py +++ b/tests/ut/ops/a3_2/test_activation.py @@ -61,6 +61,7 @@ def test_AscendQuickGELU_forward_oot(mock_gelu, dummy_tensor, default_vllm_confi mock_gelu.assert_called_once_with(dummy_tensor) +@pytest.mark.skipif(is_310p_hw(), reason="non_310P device unittest case.") @patch("vllm_ascend.ops.activation.get_weight_prefetch_method", return_value=MagicMock()) @patch("torch_npu.npu_swiglu", side_effect=lambda x: x + 1) def test_SiluAndMul_forward( @@ -83,6 +84,7 @@ def test_SiluAndMul_forward( assert torch.allclose(out, expected_out) +@pytest.mark.skipif(is_310p_hw(), reason="non_310P device unittest case.") @patch("vllm_ascend.ops.activation.get_weight_prefetch_method") @patch("torch_npu.npu_swiglu", side_effect=lambda x: x + 1) def test_AscendSiluAndMul_forward_oot_prefetch( @@ -208,6 +210,7 @@ def test_ascend_quick_gelu_matches_cpu_reference_on_npu(self, dtype, atol, rtol, assert torch.allclose(result.float(), expected, atol=atol, rtol=rtol) + @pytest.mark.skipif(is_310p_hw(), reason="non_310P device unittest case.") @pytest.mark.parametrize( "dtype,atol,rtol", [ diff --git a/tests/ut/ops/test_layernorm.py b/tests/ut/ops/test_layernorm.py index adf0e93bf..ea112504b 100644 --- a/tests/ut/ops/test_layernorm.py +++ b/tests/ut/ops/test_layernorm.py @@ -41,6 +41,7 @@ def default_vllm_config(): @pytest.mark.skip("Skip as register_kernels has NPU SocName checking in CANN 8.5.0.") +@pytest.mark.skipif(is_310p_hw(), reason="non_310P device unittest case.") @pytest.mark.parametrize("residual", [None, torch.randn(4, 8, dtype=torch.float32)]) @patch("torch_npu.npu_rms_norm", side_effect=mock_rms_norm) @patch("torch_npu.npu_add_rms_norm", side_effect=mock_add_rms_norm) diff --git a/tests/ut/patch/platform/test_patch_anthropic_system_message.py b/tests/ut/patch/platform/test_patch_anthropic_system_message.py deleted file mode 100644 index 72e0f8691..000000000 --- a/tests/ut/patch/platform/test_patch_anthropic_system_message.py +++ /dev/null @@ -1,100 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -from vllm.entrypoints.anthropic.protocol import ( - AnthropicCountTokensRequest, - AnthropicMessagesRequest, -) -from vllm.entrypoints.anthropic.serving import AnthropicServingMessages - -from vllm_ascend.patch.platform import patch_anthropic_system_message # noqa: F401 - - -def _make_request( - messages: list[dict], - **kwargs, -) -> AnthropicMessagesRequest: - return AnthropicMessagesRequest( - model="test-model", - max_tokens=128, - messages=messages, - **kwargs, - ) - - -def test_inline_system_role_is_accepted_by_anthropic_requests(): - request = _make_request([{"role": "system", "content": "Be concise."}]) - count_request = AnthropicCountTokensRequest( - model="test-model", - messages=[{"role": "system", "content": "Be concise."}], - ) - - assert request.messages[0].role == "system" - assert count_request.messages[0].role == "system" - - -def test_inline_system_string_is_merged_and_not_kept_as_chat_message(): - request = _make_request( - [ - {"role": "user", "content": "Hello"}, - {"role": "system", "content": "Be concise."}, - ], - system="Top-level prompt.", - ) - - result = AnthropicServingMessages._convert_anthropic_to_openai_request(request) - - assert result.messages == [ - {"role": "system", "content": "Top-level prompt.Be concise."}, - {"role": "user", "content": "Hello"}, - ] - - -def test_inline_system_list_content_is_merged_with_billing_header_stripped(): - request = _make_request( - [ - {"role": "user", "content": "help?"}, - { - "role": "system", - "content": [ - { - "type": "text", - "text": "x-anthropic-billing-header: cc_version=2.1.160;", - }, - {"type": "text", "text": "Use short answers. "}, - {"type": "text", "text": "Prefer examples."}, - ], - }, - ], - system=[ - {"type": "text", "text": "Existing system. "}, - { - "type": "text", - "text": "x-anthropic-billing-header: cch=d1d48;", - }, - ], - ) - - result = AnthropicServingMessages._convert_anthropic_to_openai_request(request) - - assert result.messages[0] == { - "role": "system", - "content": "Existing system. Use short answers. Prefer examples.", - } - assert result.messages[1] == {"role": "user", "content": "help?"} - - -def test_multiple_inline_system_messages_are_all_merged(): - request = _make_request( - [ - {"role": "system", "content": "First."}, - {"role": "user", "content": "Hello"}, - {"role": "system", "content": "Second."}, - ] - ) - - result = AnthropicServingMessages._convert_anthropic_to_openai_request(request) - - assert result.messages == [ - {"role": "system", "content": "First.Second."}, - {"role": "user", "content": "Hello"}, - ] diff --git a/tests/ut/patch/platform/test_deepseek_v4_thinking.py b/tests/ut/patch/platform/test_patch_deepseek_v4_thinking.py similarity index 97% rename from tests/ut/patch/platform/test_deepseek_v4_thinking.py rename to tests/ut/patch/platform/test_patch_deepseek_v4_thinking.py index cdb946122..b1866588b 100644 --- a/tests/ut/patch/platform/test_deepseek_v4_thinking.py +++ b/tests/ut/patch/platform/test_patch_deepseek_v4_thinking.py @@ -3,6 +3,8 @@ from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest from vllm.tokenizers import deepseek_v4 +from vllm_ascend.patch.platform import patch_deepseek_v4_thinking # noqa: F401 + class FakeTokenizer: vocab_size = 1 diff --git a/tests/ut/patch/platform/test_patch_glm47_tool_call_parser.py b/tests/ut/patch/platform/test_patch_glm47_tool_call_parser.py index aa5aa1063..96a6b56d2 100644 --- a/tests/ut/patch/platform/test_patch_glm47_tool_call_parser.py +++ b/tests/ut/patch/platform/test_patch_glm47_tool_call_parser.py @@ -4,26 +4,13 @@ from unittest.mock import MagicMock from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest -from vllm.entrypoints.openai.chat_completion.serving import OpenAIServingChat +from vllm.parser.abstract_parser import _WrappedParser from vllm.reasoning.deepseek_v3_reasoning_parser import ( DeepSeekV3ReasoningWithThinkingParser, ) from vllm.tool_parsers.glm47_moe_tool_parser import Glm47MoeModelToolParser from vllm_ascend.patch.platform import patch_glm47_tool_call_parser # noqa: F401 -from vllm_ascend.utils import vllm_version_is - -if vllm_version_is("0.22.1"): - from vllm.parser.abstract_parser import _WrappedParser # type: ignore[import-not-found] -else: - # vLLM main removed the ``_WrappedParser`` helper; the base ``Parser`` - # already instantiates from ``reasoning_parser_cls`` / ``tool_parser_cls`` - # class attributes, so a thin ``DelegatingParser`` subclass is equivalent. - from vllm.parser.abstract_parser import DelegatingParser # type: ignore[import-not-found] - - class _WrappedParser(DelegatingParser): # type: ignore[no-redef] - pass - MOCK_TOKENIZER = MagicMock() MOCK_TOKENIZER.get_vocab.return_value = { @@ -63,14 +50,6 @@ def _collect_tool_args(tool_calls): return "".join(tc.function.arguments for tc in tool_calls if tc.function.arguments) -def _parse_delta(parser, *args, finished=False, **kwargs): - # vLLM main added a required keyword-only ``finished`` arg to - # ``parse_delta``; v0.22.1 has no such parameter. - if vllm_version_is("0.22.1"): - return parser.parse_delta(*args, **kwargs) - return parser.parse_delta(*args, finished=finished, **kwargs) - - def test_glm47_streaming_inline_zero_arg_tool_call_waits_until_complete(): request = _request() parser = Glm47MoeModelToolParser(MOCK_TOKENIZER, request.tools) @@ -101,10 +80,6 @@ def test_glm47_streaming_inline_zero_arg_tool_call_waits_until_complete(): assert second.tool_calls[0].function.name == "get_current_time" assert json.loads(_collect_tool_args(second.tool_calls)) == {} - finished = OpenAIServingChat._create_remaining_args_delta(second, "", 0) - assert finished.tool_calls[0].function.name == "get_current_time" - assert json.loads(_collect_tool_args(finished.tool_calls)) == {} - def test_glm45_reasoning_glm47_streaming_inline_zero_arg_tool_call(): request = _request() @@ -112,20 +87,16 @@ def test_glm45_reasoning_glm47_streaming_inline_zero_arg_tool_call(): _WrappedParser.tool_parser_cls = Glm47MoeModelToolParser parser = _WrappedParser(MOCK_TOKENIZER, request.tools) - first = _parse_delta( - parser, + first = parser.parse_delta( "Need current time.", [2001, 2002], request, prompt_token_ids=[], - finished=False, ) - second = _parse_delta( - parser, + second = parser.parse_delta( "get_current_time", [154842, 154843, 455, 11075, 3009, 154844], request, - finished=True, ) assert first is not None diff --git a/tests/ut/patch/platform/test_patch_glm_tool_call_streaming.py b/tests/ut/patch/platform/test_patch_glm_tool_call_streaming.py index b030e7d06..4189ef877 100644 --- a/tests/ut/patch/platform/test_patch_glm_tool_call_streaming.py +++ b/tests/ut/patch/platform/test_patch_glm_tool_call_streaming.py @@ -14,7 +14,7 @@ ) -def test_remaining_args_delta_preserves_metadata_by_default(): +def test_remaining_args_delta_omits_metadata_by_default(): original_delta = DeltaMessage( tool_calls=[ DeltaToolCall( @@ -37,44 +37,14 @@ def test_remaining_args_delta_preserves_metadata_by_default(): tc = result.tool_calls[0] assert tc.index == 0 - assert tc.id == "call_current" - assert tc.type == "function" - assert tc.function.name == "current_name" + assert tc.id is None + assert tc.type is None + assert tc.function.name is None assert tc.function.arguments == "]}" serialized = tc.model_dump(exclude_unset=True) - assert serialized["id"] == "call_current" - assert serialized["type"] == "function" - assert serialized["function"]["name"] == "current_name" - - -def test_empty_remaining_args_delta_keeps_original_delta(): - original_delta = DeltaMessage( - tool_calls=[ - DeltaToolCall( - index=0, - id="call_current", - type="function", - function=DeltaFunctionCall( - name="current_name", - arguments="", - ), - ), - DeltaToolCall( - index=0, - function=DeltaFunctionCall(arguments="{}"), - ), - ] - ) - - result = OpenAIServingChat._create_remaining_args_delta( - original_delta, - "", - 0, - ) - - assert result is original_delta - assert result.tool_calls[0].function.name == "current_name" - assert result.tool_calls[1].function.arguments == "{}" + assert "id" not in serialized + assert "type" not in serialized + assert "name" not in serialized["function"] def test_remaining_args_delta_uses_explicit_fallback_metadata(): diff --git a/tests/ut/patch/platform/test_patch_pp_mtp.py b/tests/ut/patch/platform/test_patch_pp_mtp.py deleted file mode 100644 index 3d3541d3f..000000000 --- a/tests/ut/patch/platform/test_patch_pp_mtp.py +++ /dev/null @@ -1,57 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -from types import SimpleNamespace - -import pytest -from vllm.config.model import ModelConfig - - -def test_model_config_validates_local_mtp_drafter_as_single_pp_rank(monkeypatch): - fake_registry = SimpleNamespace( - is_pp_supported_model=lambda _architectures, _model_config: False, - ) - monkeypatch.setattr(ModelConfig, "registry", property(lambda _self: fake_registry)) - - model_config = ModelConfig.__new__(ModelConfig) - model_config.hf_config = SimpleNamespace(model_type="qwen3_5_mtp") - model_config.runner = "draft" - model_config.model_arch_config = SimpleNamespace( - total_num_attention_heads=1, - architectures=["Qwen3_5MTP"], - ) - model_config.multimodal_config = None - - parallel_config = SimpleNamespace( - tensor_parallel_size=1, - enable_expert_parallel=False, - pipeline_parallel_size=2, - decode_context_parallel_size=1, - ) - - ModelConfig.verify_with_parallel_config(model_config, parallel_config) - assert parallel_config.pipeline_parallel_size == 2 - - -def test_model_config_keeps_target_model_pp_validation(monkeypatch): - fake_registry = SimpleNamespace( - is_pp_supported_model=lambda _architectures, _model_config: False, - ) - monkeypatch.setattr(ModelConfig, "registry", property(lambda _self: fake_registry)) - - model_config = ModelConfig.__new__(ModelConfig) - model_config.hf_config = SimpleNamespace(model_type="qwen3_5_mtp") - model_config.runner = "generate" - model_config.model_arch_config = SimpleNamespace( - total_num_attention_heads=1, - architectures=["UnsupportedForPP"], - ) - - parallel_config = SimpleNamespace( - tensor_parallel_size=1, - enable_expert_parallel=False, - pipeline_parallel_size=2, - decode_context_parallel_size=1, - ) - - with pytest.raises(NotImplementedError): - ModelConfig.verify_with_parallel_config(model_config, parallel_config) diff --git a/tests/ut/patch/platform/test_patch_tool_choice_none_content.py b/tests/ut/patch/platform/test_patch_tool_choice_none_content.py index 3d8600735..a3b746e02 100644 --- a/tests/ut/patch/platform/test_patch_tool_choice_none_content.py +++ b/tests/ut/patch/platform/test_patch_tool_choice_none_content.py @@ -1,6 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -import pytest from openai.types.chat.chat_completion import ChatCompletion as OpenAIChatCompletion from openai.types.chat.chat_completion_chunk import ChatCompletionChunk from vllm.entrypoints.openai.chat_completion.protocol import ( @@ -24,7 +23,6 @@ from vllm.parser.abstract_parser import DelegatingParser from vllm_ascend.patch.platform import patch_tool_choice_none_content # noqa: F401 -from vllm_ascend.utils import vllm_version_is class _DummyDelegatingParser(DelegatingParser): @@ -52,9 +50,6 @@ def extract_tool_calls(self, model_output: str, request): return None -@pytest.mark.skipif( - not vllm_version_is("0.22.1"), reason="OpenAIServing._parse_tool_calls_from_content exists only in v0.22.1" -) def test_parse_tool_calls_from_content_allows_named_tool_choice_with_none_content(): request = ChatCompletionRequest.model_validate( { diff --git a/tests/ut/patch/platform/test_prefix_cache_cp_patches.py b/tests/ut/patch/platform/test_prefix_cache_cp_patches.py index 946c6869f..7f2074de9 100644 --- a/tests/ut/patch/platform/test_prefix_cache_cp_patches.py +++ b/tests/ut/patch/platform/test_prefix_cache_cp_patches.py @@ -13,20 +13,16 @@ KVCacheGroupSpec, KVCacheTensor, MambaSpec, - MLAAttentionSpec, - UniformTypeKVCacheSpecs, ) from vllm_ascend.patch.platform.patch_kv_cache_coordinator import ( AscendHybridKVCacheCoordinator, - _is_deepseek_v4_kv_cache_spec, get_kv_cache_coordinator, ) from vllm_ascend.patch.platform.patch_kv_cache_utils import ( _ascend_resolve_kv_cache_block_sizes, ) from vllm_ascend.patch.platform.patch_mamba_manager import AscendMambaManager -from vllm_ascend.utils import vllm_version_is def _make_hybrid_kv_cache_config( @@ -58,40 +54,6 @@ def _make_hybrid_kv_cache_config( ) -def _make_deepseek_v4_kv_cache_config() -> KVCacheConfig: - c4_spec = MLAAttentionSpec( - block_size=128, - num_kv_heads=1, - head_size=128, - dtype=torch.float16, - compress_ratio=4, - model_version="deepseek_v4", - ) - c128_spec = MLAAttentionSpec( - block_size=128, - num_kv_heads=1, - head_size=128, - dtype=torch.float16, - compress_ratio=128, - model_version="deepseek_v4", - ) - c4_group_spec = UniformTypeKVCacheSpecs.from_specs({"c4_attn": c4_spec}) - c128_group_spec = UniformTypeKVCacheSpecs.from_specs({"c128_attn": c128_spec}) - assert c4_group_spec is not None - assert c128_group_spec is not None - return KVCacheConfig( - num_blocks=10, - kv_cache_tensors=[ - KVCacheTensor(size=c4_spec.page_size_bytes * 10, shared_by=["c4_attn"]), - KVCacheTensor(size=c128_spec.page_size_bytes * 10, shared_by=["c128_attn"]), - ], - kv_cache_groups=[ - KVCacheGroupSpec(layer_names=["c4_attn"], kv_cache_spec=c4_group_spec), - KVCacheGroupSpec(layer_names=["c128_attn"], kv_cache_spec=c128_group_spec), - ], - ) - - def _make_vllm_config( *, enable_prefix_caching: bool, @@ -271,52 +233,6 @@ def _fake_orig(*args, **kwargs): assert coordinator is sentinel -def test_get_kv_cache_coordinator_uses_ascend_for_deepseek_v4(monkeypatch) -> None: - sentinel = object() - kv_cache_config = _make_deepseek_v4_kv_cache_config() - - def _fake_orig(*args, **kwargs): - raise AssertionError("DeepSeek V4 should use AscendHybridKVCacheCoordinator") - - def _fake_ascend_coordinator(*args, **kwargs): - return sentinel - - monkeypatch.setattr( - "vllm_ascend.patch.platform.patch_kv_cache_coordinator._orig_get_kv_cache_coordinator", - _fake_orig, - ) - monkeypatch.setattr( - "vllm_ascend.patch.platform.patch_kv_cache_coordinator.AscendHybridKVCacheCoordinator", - _fake_ascend_coordinator, - ) - - coordinator = get_kv_cache_coordinator( - kv_cache_config, - max_model_len=1024, - max_num_batched_tokens=1024, - use_eagle=False, - enable_caching=True, - enable_kv_cache_events=False, - dcp_world_size=1, - pcp_world_size=1, - hash_block_size=128, - ) - - assert coordinator is sentinel - - -def test_deepseek_v4_detection_handles_non_mapping_nested_specs() -> None: - kv_cache_spec = SimpleNamespace( - kv_cache_specs=[ - SimpleNamespace(model_version="deepseek_v4"), - ] - ) - unknown_spec = SimpleNamespace(kv_cache_specs=object()) - - assert _is_deepseek_v4_kv_cache_spec(kv_cache_spec) - assert not _is_deepseek_v4_kv_cache_spec(unknown_spec) - - def test_ascend_mamba_manager_uses_logical_block_size_with_prefix_caching() -> None: mamba_spec = MambaSpec( block_size=16, @@ -332,7 +248,7 @@ def test_ascend_mamba_manager_uses_logical_block_size_with_prefix_caching() -> N MagicMock(), ) - manager_kwargs = dict( + manager = AscendMambaManager( kv_cache_spec=mamba_spec, block_pool=block_pool, enable_caching=True, @@ -340,10 +256,5 @@ def test_ascend_mamba_manager_uses_logical_block_size_with_prefix_caching() -> N dcp_world_size=2, pcp_world_size=2, ) - # vLLM main added a required ``scheduler_block_size`` arg to - # ``SingleTypeKVCacheManager.__init__``; v0.22.1 has no such parameter. - if not vllm_version_is("0.22.1"): - manager_kwargs["scheduler_block_size"] = mamba_spec.block_size - manager = AscendMambaManager(**manager_kwargs) assert manager.block_size == mamba_spec.block_size diff --git a/tests/ut/ops/test_gdn_attn_builder.py b/tests/ut/patch/worker/patch_common/test_patch_gdn_attn.py similarity index 81% rename from tests/ut/ops/test_gdn_attn_builder.py rename to tests/ut/patch/worker/patch_common/test_patch_gdn_attn.py index eb6fd1195..28c912b95 100644 --- a/tests/ut/ops/test_gdn_attn_builder.py +++ b/tests/ut/patch/worker/patch_common/test_patch_gdn_attn.py @@ -9,19 +9,15 @@ from vllm.config.compilation import CUDAGraphMode from vllm.model_executor.layers.fla.ops import index as _fla_index from vllm.v1.attention.backend import CommonAttentionMetadata +from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadataBuilder from vllm.v1.kv_cache_interface import MambaSpec -from vllm_ascend.ops import gdn_attn_builder as ascend_gdn_attn_builder +import vllm_ascend.patch.worker.patch_gdn_attn as patch_gdn_attn from vllm_ascend.ops.gdn import ( - AscendGatedDeltaNetAttention, get_non_spec_causal_conv1d_host_args, get_non_spec_chunked_prefill_meta, to_int64_tuple, ) -from vllm_ascend.ops.gdn_attn_builder import ( - AscendGDNAttentionBackend, - AscendGDNAttentionMetadataBuilder, -) from vllm_ascend.ops.triton.fla import utils as fla_utils from vllm_ascend.ops.triton.fla.utils import ( prepare_chunk_indices as runtime_prepare_chunk_indices, @@ -155,7 +151,7 @@ def _make_builder(*, device: torch.device, num_heads: int, num_speculative_token dtypes=(torch.float32,), mamba_cache_mode="none", ) - return AscendGDNAttentionMetadataBuilder(spec, ["layer0"], vllm_config, device) + return GDNAttentionMetadataBuilder(spec, ["layer0"], vllm_config, device) def _build_attn_metadata( @@ -195,31 +191,31 @@ def _build_attn_metadata( def _assert_chunk_meta_matches_runtime(builder, chunk_meta, cu_seqlens: torch.Tensor) -> None: assert torch.equal( chunk_meta.chunk_indices_chunk64, - runtime_prepare_chunk_indices(cu_seqlens, ascend_gdn_attn_builder._GDN_CHUNK_SIZE), + runtime_prepare_chunk_indices(cu_seqlens, patch_gdn_attn._GDN_CHUNK_SIZE), ) assert torch.equal( chunk_meta.chunk_offsets_chunk64, - runtime_prepare_chunk_offsets(cu_seqlens, ascend_gdn_attn_builder._GDN_CHUNK_SIZE), + runtime_prepare_chunk_offsets(cu_seqlens, patch_gdn_attn._GDN_CHUNK_SIZE), ) assert torch.equal( chunk_meta.update_chunk_offsets_chunk64, runtime_prepare_update_chunk_offsets( cu_seqlens, - ascend_gdn_attn_builder._GDN_CHUNK_SIZE, + patch_gdn_attn._GDN_CHUNK_SIZE, ), ) assert torch.equal( chunk_meta.final_chunk_indices_chunk64, runtime_prepare_final_chunk_indices( cu_seqlens, - ascend_gdn_attn_builder._GDN_CHUNK_SIZE, + patch_gdn_attn._GDN_CHUNK_SIZE, ), ) assert torch.equal( chunk_meta.chunk_indices_large_block, runtime_prepare_chunk_indices( cu_seqlens, - ascend_gdn_attn_builder._GDN_SOLVE_TRIL_LARGE_BLOCK_SIZE, + patch_gdn_attn._GDN_SOLVE_TRIL_LARGE_BLOCK_SIZE, ), ) assert torch.equal( @@ -242,28 +238,6 @@ def _patch_missing_runtime_cdiv(monkeypatch: pytest.MonkeyPatch) -> None: ) -def test_ascend_gdn_attention_uses_ascend_backend(): - assert AscendGatedDeltaNetAttention.get_attn_backend(object()) is AscendGDNAttentionBackend - assert AscendGDNAttentionBackend.get_builder_cls() is AscendGDNAttentionMetadataBuilder - - -def test_sequence_index_buffers_cover_spec_decode_when_cudagraph_disabled(): - builder = _make_builder( - device=torch.device("cpu"), - num_heads=32, - num_speculative_tokens=3, - ) - assert builder.spec_sequence_indices_cpu.numel() >= builder.vllm_config.scheduler_config.max_num_seqs - - spec_indices, non_spec_indices = builder._copy_sequence_indices_to_device( - torch.tensor([True], dtype=torch.bool), - num_spec_decodes=1, - ) - - assert torch.equal(spec_indices.cpu(), torch.tensor([0])) - assert non_spec_indices.numel() == 0 - - def _expected_conv1d_host_args(attn_metadata) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]: return ( to_int64_tuple(attn_metadata.non_spec_query_start_loc), @@ -344,7 +318,7 @@ def to(self, *args, **kwargs): has_initial_state=torch.tensor([True, False]), ) - host_meta = ascend_gdn_attn_builder._build_non_spec_causal_conv1d_host_meta( + host_meta = patch_gdn_attn._build_non_spec_causal_conv1d_host_meta( builder, attn_metadata, non_spec_query_start_loc_cpu=torch.tensor([0, 4, 12], dtype=torch.int32), @@ -365,44 +339,37 @@ def test_build_non_spec_causal_conv1d_host_meta_requires_has_initial_state(): has_initial_state=None, ) with pytest.raises(RuntimeError, match="has_initial_state"): - ascend_gdn_attn_builder._build_non_spec_causal_conv1d_host_meta( + patch_gdn_attn._build_non_spec_causal_conv1d_host_meta( builder, attn_metadata, non_spec_query_start_loc_cpu=torch.tensor([0, 4, 12], dtype=torch.int32), ) -def test_get_non_spec_causal_conv1d_host_args_falls_back_to_runtime_metadata(): +def test_get_non_spec_causal_conv1d_host_args_requires_prefill_fallback_meta(): attn_metadata = SimpleNamespace( non_spec_prefill_fallback_meta=None, - non_spec_query_start_loc=torch.tensor([0, 4, 12], dtype=torch.int32), - non_spec_state_indices_tensor=torch.tensor([3, 9], dtype=torch.int32), - has_initial_state=torch.tensor([True, False]), + non_spec_causal_conv1d_meta=SimpleNamespace( + query_start_loc_opt=(0, 4, 12), + cache_indices_opt=(3, 9), + initial_state_mode_opt=(1, 0), + ), ) - assert get_non_spec_causal_conv1d_host_args(attn_metadata) == ( - (0, 4, 12), - (3, 9), - (1, 0), - ) + with pytest.raises(RuntimeError, match="non_spec_prefill_fallback_meta\\.causal_conv1d"): + get_non_spec_causal_conv1d_host_args( + attn_metadata, + ) -def test_get_non_spec_causal_conv1d_host_args_requires_runtime_metadata(): +def test_get_non_spec_chunked_prefill_meta_requires_prefill_fallback_meta(): attn_metadata = SimpleNamespace( non_spec_prefill_fallback_meta=None, - non_spec_query_start_loc=torch.tensor([0, 4, 12], dtype=torch.int32), - non_spec_state_indices_tensor=torch.tensor([3, 9], dtype=torch.int32), - has_initial_state=None, + non_spec_chunked_prefill_meta=SimpleNamespace(chunk_offsets_chunk64=torch.tensor([0, 1])), ) - with pytest.raises(RuntimeError, match="has_initial_state"): - get_non_spec_causal_conv1d_host_args(attn_metadata) - - -def test_get_non_spec_chunked_prefill_meta_allows_missing_prefill_fallback_meta(): - attn_metadata = SimpleNamespace(non_spec_prefill_fallback_meta=None) - - assert get_non_spec_chunked_prefill_meta(attn_metadata) is None + with pytest.raises(RuntimeError, match="non_spec_prefill_fallback_meta\\.chunk"): + get_non_spec_chunked_prefill_meta(attn_metadata) def test_builder_uses_device_chunk_builder_with_non_spec_query_start_loc(monkeypatch): @@ -420,8 +387,8 @@ def test_builder_uses_device_chunk_builder_with_non_spec_query_start_loc(monkeyp builder._ascend_gdn_chunk_meta_initialized = True builder._ascend_gdn_chunk_meta_device = SimpleNamespace(type="npu") - builder._ascend_gdn_chunk_size = ascend_gdn_attn_builder._GDN_CHUNK_SIZE - builder._ascend_gdn_large_block_size = ascend_gdn_attn_builder._GDN_SOLVE_TRIL_LARGE_BLOCK_SIZE + builder._ascend_gdn_chunk_size = patch_gdn_attn._GDN_CHUNK_SIZE + builder._ascend_gdn_large_block_size = patch_gdn_attn._GDN_SOLVE_TRIL_LARGE_BLOCK_SIZE builder._ascend_gdn_cumsum_block_size = 256 builder._ascend_gdn_chunked_prefill_pool_idx = -1 builder._ascend_gdn_chunked_prefill_pool = [ @@ -444,11 +411,16 @@ def fake_build_chunk_meta_device(**kwargs): helper_calls[kwargs["chunk_size"]] = kwargs monkeypatch.setattr( - ascend_gdn_attn_builder, + patch_gdn_attn, "build_chunk_meta_device", fake_build_chunk_meta_device, raising=False, ) + monkeypatch.setattr( + patch_gdn_attn, + "_prepare_chunk_counts_cpu", + lambda *args, **kwargs: pytest.fail("_prepare_chunk_counts_cpu should not be used on the device path"), + ) attn_metadata = builder.build( 0, @@ -459,22 +431,22 @@ def fake_build_chunk_meta_device(**kwargs): expected_chunk_indices = runtime_prepare_chunk_indices( attn_metadata.non_spec_query_start_loc, - ascend_gdn_attn_builder._GDN_CHUNK_SIZE, + patch_gdn_attn._GDN_CHUNK_SIZE, ) expected_chunk_offsets = runtime_prepare_chunk_offsets( attn_metadata.non_spec_query_start_loc, - ascend_gdn_attn_builder._GDN_CHUNK_SIZE, + patch_gdn_attn._GDN_CHUNK_SIZE, ) expected_update_chunk_offsets = runtime_prepare_update_chunk_offsets( attn_metadata.non_spec_query_start_loc, - ascend_gdn_attn_builder._GDN_CHUNK_SIZE, + patch_gdn_attn._GDN_CHUNK_SIZE, ) expected_final_chunk_indices = runtime_prepare_final_chunk_indices( attn_metadata.non_spec_query_start_loc, - ascend_gdn_attn_builder._GDN_CHUNK_SIZE, + patch_gdn_attn._GDN_CHUNK_SIZE, ) - chunk64_call = helper_calls[ascend_gdn_attn_builder._GDN_CHUNK_SIZE] + chunk64_call = helper_calls[patch_gdn_attn._GDN_CHUNK_SIZE] out_chunk_indices = cast(torch.Tensor, chunk64_call["out_chunk_indices"]) out_chunk_offsets = cast(torch.Tensor, chunk64_call["out_chunk_offsets"]) out_update_chunk_offsets = cast( @@ -485,7 +457,6 @@ def fake_build_chunk_meta_device(**kwargs): torch.Tensor, chunk64_call["out_final_chunk_indices"], ) - fallback_meta = get_non_spec_chunked_prefill_meta(attn_metadata) assert chunk64_call["cu_seqlens"] is attn_metadata.non_spec_query_start_loc assert out_chunk_indices.shape == expected_chunk_indices.shape assert out_chunk_indices.dtype == expected_chunk_indices.dtype @@ -495,10 +466,6 @@ def fake_build_chunk_meta_device(**kwargs): assert out_update_chunk_offsets.dtype == torch.int32 assert out_final_chunk_indices.shape == expected_final_chunk_indices.shape assert out_final_chunk_indices.dtype == torch.int32 - assert fallback_meta.cu_seqlens_host == tuple(attn_metadata.non_spec_query_start_loc.to(torch.int64).tolist()) - assert fallback_meta.chunk_indices_chunk64_host == tuple( - expected_chunk_indices.to(torch.int64).reshape(-1).tolist() - ) @pytest.mark.parametrize( diff --git a/tests/ut/patch/worker/test_patch_qwen3_5_mtp.py b/tests/ut/patch/worker/test_patch_qwen3_5_mtp.py deleted file mode 100644 index c7fa8f273..000000000 --- a/tests/ut/patch/worker/test_patch_qwen3_5_mtp.py +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -from types import SimpleNamespace -from unittest.mock import MagicMock, patch - -import pytest -import torch -from vllm.sequence import IntermediateTensors - -from vllm_ascend.patch.worker import patch_qwen3_5 - - -@pytest.mark.skipif( - patch_qwen3_5.Qwen3_5MultiTokenPredictor is None, - reason="Qwen3.5 MTP model is not available in this vLLM version.", -) -def test_qwen3_5_mtp_forward_uses_local_inputs_on_last_pp_rank(): - predictor = patch_qwen3_5.Qwen3_5MultiTokenPredictor.__new__(patch_qwen3_5.Qwen3_5MultiTokenPredictor) - predictor.num_mtp_layers = 2 - predictor.embed_input_ids = MagicMock(return_value=torch.ones(2, 4)) - predictor.pre_fc_norm_embedding = MagicMock(side_effect=lambda x: x + 1) - predictor.pre_fc_norm_hidden = MagicMock(side_effect=lambda x: x + 2) - predictor.fc = MagicMock(side_effect=lambda x: x[:, :4] + x[:, 4:]) - layer0 = MagicMock(return_value=(torch.full((2, 4), 3.0), torch.full((2, 4), 4.0))) - layer1 = MagicMock(return_value=(torch.full((2, 4), 5.0), torch.full((2, 4), 6.0))) - predictor.layers = [layer0, layer1] - predictor.norm = MagicMock(return_value=(torch.full((2, 4), 7.0), None)) - - with patch( - "vllm_ascend.patch.worker.patch_qwen3_5.get_pp_group", - return_value=SimpleNamespace(is_last_rank=True), - ): - output = predictor.forward( - input_ids=torch.tensor([1, 2]), - positions=torch.tensor([0, 1]), - hidden_states=torch.zeros(2, 4), - intermediate_tensors=IntermediateTensors({"hidden_states": torch.full((2, 4), 99.0)}), - spec_step_idx=3, - ) - - predictor.embed_input_ids.assert_called_once() - layer1.assert_called_once() - predictor.norm.assert_called_once() - assert torch.equal(output, torch.full((2, 4), 7.0)) - - -@pytest.mark.skipif( - patch_qwen3_5.Qwen3_5MultiTokenPredictor is None, - reason="Qwen3.5 MTP model is not available in this vLLM version.", -) -def test_qwen3_5_mtp_forward_returns_intermediate_tensors_on_non_last_pp_rank(): - predictor = patch_qwen3_5.Qwen3_5MultiTokenPredictor.__new__(patch_qwen3_5.Qwen3_5MultiTokenPredictor) - predictor.num_mtp_layers = 1 - predictor.embed_input_ids = MagicMock(return_value=torch.ones(1, 4)) - predictor.pre_fc_norm_embedding = MagicMock(side_effect=lambda x: x) - predictor.pre_fc_norm_hidden = MagicMock(side_effect=lambda x: x) - predictor.fc = MagicMock(side_effect=lambda x: x[:, :4]) - predictor.layers = [MagicMock(return_value=(torch.full((1, 4), 3.0), torch.full((1, 4), 4.0)))] - predictor.norm = MagicMock() - - with patch( - "vllm_ascend.patch.worker.patch_qwen3_5.get_pp_group", - return_value=SimpleNamespace(is_last_rank=False), - ): - output = predictor.forward( - input_ids=torch.tensor([1]), - positions=torch.tensor([0]), - hidden_states=torch.zeros(1, 4), - ) - - assert isinstance(output, IntermediateTensors) - assert torch.equal(output["hidden_states"], torch.full((1, 4), 3.0)) - assert torch.equal(output["residual"], torch.full((1, 4), 4.0)) - predictor.norm.assert_not_called() diff --git a/tests/ut/quantization/methods/test_w8a8fp8_dynamic.py b/tests/ut/quantization/methods/test_w8a8fp8_dynamic.py deleted file mode 100644 index 776a64e6c..000000000 --- a/tests/ut/quantization/methods/test_w8a8fp8_dynamic.py +++ /dev/null @@ -1,136 +0,0 @@ -from unittest.mock import MagicMock, Mock, patch - -import torch - -from tests.ut.base import TestBase -from tests.ut.quantization.conftest_quantization import ( - create_mock_ascend_config, - create_mock_vllm_config, -) -from vllm_ascend.ascend_forward_context import MoECommType -from vllm_ascend.quantization.methods.w8a8fp8_dynamic import ( - AscendW8A8FP8DynamicFusedMoEMethod, - AscendW8A8FP8DynamicLinearMethod, -) - - -class TestAscendW8A8FP8DynamicLinearMethod(TestBase): - def setUp(self): - self.method = AscendW8A8FP8DynamicLinearMethod() - - def test_act_quant_type(self): - self.assertEqual(self.method.act_quant_type, torch.float8_e4m3fn) - - def test_get_weight_various_sizes(self): - sizes = [(64, 128), (256, 512), (1024, 2048)] - for input_size, output_size in sizes: - weight = self.method.get_weight(input_size, output_size, torch.bfloat16) - self.assertEqual(weight["weight"].dtype, torch.float8_e4m3fn) - self.assertEqual(weight["weight"].shape, (output_size, input_size)) - - def test_get_perchannel_param_dtype_variations(self): - dtypes = [torch.bfloat16, torch.float16] - for dtype in dtypes: - params = self.method.get_perchannel_param(128, dtype) - self.assertEqual(params["weight_scale"].dtype, torch.float32) - self.assertEqual(params["weight_offset"].dtype, dtype) - self.assertEqual(params["weight_scale"].shape, (128, 1)) - self.assertEqual(params["weight_offset"].shape, (128, 1)) - - -class TestAscendW8A8FP8FusedMoEMethod(TestBase): - num_experts = 8 - hidden_size = 128 - intermediate_size = 128 - - @patch("torch.distributed.get_rank") - @patch("vllm_ascend.quantization.methods.w8a8_dynamic.get_mc2_group") - @patch("vllm_ascend.quantization.methods.w8a8_dynamic.get_ascend_config") - def setUp(self, mock_ascend, mock_mc2, mock_rank): - with patch("vllm_ascend.quantization.methods.w8a8_dynamic.get_current_vllm_config") as mock_vllm: - mock_vllm.return_value = create_mock_vllm_config() - mock_ascend.return_value = create_mock_ascend_config() - mock_mc2.return_value = MagicMock( - device_group=Mock( - _get_backend=Mock(return_value=Mock(get_hccl_comm_name=Mock(return_value="test_comm"))) - ) - ) - mock_rank.return_value = 0 - self.quant_method = AscendW8A8FP8DynamicFusedMoEMethod() - - def test_quant_type_is_w8a8fp8(self): - from vllm_ascend.quantization.quant_type import QuantType - - self.assertEqual(self.quant_method.quant_type, QuantType.W8A8FP8) - - def test_get_weight_dtype_is_float8_e4m3fn(self): - param_dict = self.quant_method.get_weight( - self.num_experts, self.intermediate_size, self.hidden_size, torch.bfloat16 - ) - self.assertEqual(param_dict["w13_weight"].dtype, torch.float8_e4m3fn) - self.assertEqual(param_dict["w2_weight"].dtype, torch.float8_e4m3fn) - self.assertEqual( - param_dict["w13_weight"].shape, (self.num_experts, 2 * self.intermediate_size, self.hidden_size) - ) - self.assertEqual(param_dict["w2_weight"].shape, (self.num_experts, self.hidden_size, self.intermediate_size)) - - def test_get_weight_various_expert_counts(self): - expert_counts = [4, 8, 16, 32] - for num_experts in expert_counts: - param_dict = self.quant_method.get_weight( - num_experts, self.intermediate_size, self.hidden_size, torch.bfloat16 - ) - self.assertEqual(param_dict["w13_weight"].shape[0], num_experts) - self.assertEqual(param_dict["w2_weight"].shape[0], num_experts) - - @patch("vllm_ascend.quantization.methods.w8a8_dynamic._EXTRA_CTX") - @patch("vllm_ascend.quantization.methods.w8a8_dynamic.select_experts") - def test_apply_uses_explicit_dispatch_and_mlp_args(self, mock_select_experts, mock_extra_ctx): - tokens = 4 - hidden_size = self.hidden_size - layer = torch.nn.Module() - layer.w13_weight = torch.randn( - self.num_experts, 2 * self.intermediate_size, hidden_size, dtype=torch.bfloat16 - ).to(torch.float8_e4m3fn) - layer.w2_weight = torch.randn(self.num_experts, hidden_size, self.intermediate_size, dtype=torch.bfloat16).to( - torch.float8_e4m3fn - ) - layer.w13_weight_scale_fp32 = torch.ones(self.num_experts, 2 * self.intermediate_size, dtype=torch.float32) - layer.w2_weight_scale = torch.ones(self.num_experts, hidden_size, dtype=torch.float32) - layer.swiglu_limit = 1000000 - - x = torch.randn(tokens, hidden_size, dtype=torch.float32) - router_logits = torch.randn(tokens, self.num_experts, dtype=torch.float32) - topk_weights = torch.randn(tokens, 2, dtype=torch.float32) - topk_ids = torch.randint(0, self.num_experts, (tokens, 2), dtype=torch.int64) - mc2_mask = torch.tensor([1, 0, 1, 0], dtype=torch.bool) - pertoken_scale = torch.randn(tokens, dtype=torch.float32) - - mock_select_experts.return_value = (topk_weights, topk_ids) - mock_comm = Mock() - mock_comm.fused_experts.return_value = torch.randn(tokens, hidden_size, dtype=torch.float32) - mock_extra_ctx.moe_comm_method = mock_comm - mock_extra_ctx.moe_comm_type = MoECommType.ALLGATHER - self.quant_method.multistream_overlap_gate = False - self.quant_method.in_dtype = torch.float32 - - self.quant_method.apply( - layer=layer, - x=x, - router_logits=router_logits, - top_k=2, - renormalize=True, - num_experts=self.num_experts, - activation="gelu", - apply_router_weight_on_input=True, - mc2_mask=mc2_mask, - pertoken_scale=pertoken_scale, - ) - - fused_experts_input = mock_comm.fused_experts.call_args.kwargs["fused_experts_input"] - self.assertEqual(fused_experts_input.activation, "gelu") - self.assertTrue(fused_experts_input.routing.apply_router_weight_on_input) - self.assertIs(fused_experts_input.routing.mc2_mask, mc2_mask) - self.assertIs(fused_experts_input.routing.pertoken_scale, pertoken_scale) - self.assertIs(fused_experts_input.topk_weights, topk_weights) - self.assertIs(fused_experts_input.topk_ids, topk_ids) diff --git a/tests/ut/sample/a2/test_gumbel_sampling.py b/tests/ut/sample/a2/test_gumbel_sampling.py deleted file mode 100644 index e675dccd3..000000000 --- a/tests/ut/sample/a2/test_gumbel_sampling.py +++ /dev/null @@ -1,570 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# This file is a part of the vllm-ascend project. -# -# Tests for vllm_ascend.worker.v2.sample.gumbel on Ascend NPU. -# Validates gumbel_sample and apply_temperature against PyTorch references. - -import pytest -import torch - -from vllm_ascend.worker.v2.sample.gumbel import apply_temperature, gumbel_sample - -DEVICE = "npu" - - -def _ref_apply_temperature( - logits: torch.Tensor, - expanded_idx_mapping: torch.Tensor, - temperature: torch.Tensor, -) -> torch.Tensor: - """Pure-Python reference for temperature scaling.""" - out = logits.clone().float() - for tok in range(logits.shape[0]): - req = expanded_idx_mapping[tok].item() - temp = temperature[req].item() - if temp == 0.0 or temp == 1.0: - continue - out[tok] = out[tok] / temp - return out - - -class TestGumbelSampling: - @pytest.mark.parametrize( - "num_tokens,vocab_size", - [ - (1, 32000), - (8, 32000), - (48, 102400), - (64, 151936), - ], - ) - def test_apply_temperature(self, num_tokens, vocab_size): - """Temperature kernel matches PyTorch reference for various vocab sizes.""" - torch.manual_seed(0) - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.randint(0, num_tokens, (num_tokens,), dtype=torch.int32, device=DEVICE) - temperature = torch.rand(num_tokens, dtype=torch.float32, device=DEVICE) * 1.8 + 0.2 - # inject edge cases - temperature[0] = 0.0 - if num_tokens > 1: - temperature[1] = 1.0 - - logits_triton = logits.clone() - apply_temperature(logits_triton, expanded_idx_mapping, temperature) - torch.npu.synchronize() - - logits_ref = _ref_apply_temperature(logits, expanded_idx_mapping, temperature) - - assert torch.allclose(logits_triton.float(), logits_ref, atol=1e-4, rtol=1e-5), ( - f"apply_temperature mismatch: max_diff={(logits_triton.float() - logits_ref).abs().max().item():.6f}" - ) - - def test_apply_temperature_skip_zero_and_one(self): - """Logits should be unchanged for temp=0.0 and temp=1.0.""" - torch.manual_seed(10) - num_tokens = 4 - vocab_size = 32000 - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.tensor([0.0, 1.0, 0.0, 1.0], dtype=torch.float32, device=DEVICE) - - original = logits.clone() - apply_temperature(logits, expanded_idx_mapping, temperature) - torch.npu.synchronize() - - assert torch.equal(logits, original), "Logits changed for temp=0.0 or temp=1.0" - - @pytest.mark.parametrize( - "num_tokens,num_reqs,vocab_size", - [ - (1, 1, 32000), - (4, 4, 32000), - (8, 4, 32000), # expanded: multiple tokens per request - (16, 8, 102400), - ], - ) - def test_gumbel_sample_greedy(self, num_tokens, num_reqs, vocab_size): - """temperature=0 must return argmax (greedy).""" - torch.manual_seed(42) - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.randint(0, num_reqs, (num_tokens,), dtype=torch.int32, device=DEVICE) - temperature = torch.zeros(num_reqs, dtype=torch.float32, device=DEVICE) - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - sampled = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - torch.npu.synchronize() - - expected = logits.argmax(dim=-1) - assert torch.equal(sampled, expected), ( - f"Greedy mismatch: sampled={sampled.tolist()} expected={expected.tolist()}" - ) - - def test_gumbel_sample_greedy_apply_temp_flag_irrelevant(self): - """With temp=0, apply_temperature flag should not affect result (both greedy).""" - torch.manual_seed(55) - num_tokens, num_reqs, vocab_size = 4, 4, 32000 - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.zeros(num_reqs, dtype=torch.float32, device=DEVICE) - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - s_false = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - s_true = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=True) - torch.npu.synchronize() - - expected = logits.argmax(dim=-1) - assert torch.equal(s_false, expected) - assert torch.equal(s_true, expected) - - @pytest.mark.parametrize( - "num_tokens,num_reqs,vocab_size", - [ - (4, 4, 32000), - (8, 4, 32000), - (16, 8, 102400), - ], - ) - def test_gumbel_sample_deterministic(self, num_tokens, num_reqs, vocab_size): - """Same seed must produce identical results across runs.""" - torch.manual_seed(7) - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.randint(0, num_reqs, (num_tokens,), dtype=torch.int32, device=DEVICE) - temperature = torch.rand(num_reqs, dtype=torch.float32, device=DEVICE) * 1.5 + 0.5 - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - r1 = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - torch.npu.synchronize() - r2 = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - torch.npu.synchronize() - - assert torch.equal(r1, r2), "gumbel_sample is non-deterministic with same seed" - - def test_gumbel_sample_different_seeds(self): - """Different seeds must (almost surely) produce different results.""" - torch.manual_seed(8) - num_tokens, num_reqs, vocab_size = 16, 16, 32000 - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.ones(num_reqs, dtype=torch.float32, device=DEVICE) * 1.0 - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - seed1 = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - seed2 = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - # Ensure seeds differ - seed2[0] = seed1[0] + 1 - - r1 = gumbel_sample(logits, expanded_idx_mapping, temperature, seed1, pos, apply_temperature=False) - r2 = gumbel_sample(logits, expanded_idx_mapping, temperature, seed2, pos, apply_temperature=False) - torch.npu.synchronize() - - # With 16 tokens and vocab 32000 at temp=1.0, identical results are astronomically unlikely - assert not torch.equal(r1, r2), "Different seeds produced identical results" - - @pytest.mark.parametrize( - "num_tokens,num_reqs,vocab_size", - [ - (4, 4, 32000), - (8, 4, 32000), - (16, 8, 102400), - ], - ) - def test_gumbel_sample_valid_token_ids(self, num_tokens, num_reqs, vocab_size): - """Sampled token IDs must be in [0, vocab_size).""" - torch.manual_seed(3) - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.randint(0, num_reqs, (num_tokens,), dtype=torch.int32, device=DEVICE) - temperature = torch.rand(num_reqs, dtype=torch.float32, device=DEVICE) + 0.1 - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - sampled = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - torch.npu.synchronize() - - assert sampled.shape == (num_tokens,) - assert (sampled >= 0).all() and (sampled < vocab_size).all(), ( - f"Out-of-range token IDs: min={sampled.min()}, max={sampled.max()}" - ) - - def test_gumbel_sample_temperature_affects_distribution(self): - """Higher temperature should increase sampling entropy (less concentrated). - - Strategy: create logits with a clear winner. At low temp the winner should - be sampled most often. At high temp other tokens get more probability. - """ - vocab_size = 100 - num_trials = 256 - logits_base = torch.zeros(1, vocab_size, dtype=torch.float32, device=DEVICE) - logits_base[0, 0] = 10.0 # strong signal at token 0 - - expanded_idx_mapping = torch.zeros(1, dtype=torch.int32, device=DEVICE) - - low_temp = torch.tensor([0.1], dtype=torch.float32, device=DEVICE) - high_temp = torch.tensor([5.0], dtype=torch.float32, device=DEVICE) - - low_temp_winner_count = 0 - high_temp_winner_count = 0 - - for i in range(num_trials): - seed = torch.tensor([i * 1000 + 42], dtype=torch.int64, device=DEVICE) - pos = torch.tensor([i], dtype=torch.int32, device=DEVICE) - - s_low = gumbel_sample( - logits_base.clone(), expanded_idx_mapping, low_temp, seed, pos, apply_temperature=True - ) - s_high = gumbel_sample( - logits_base.clone(), expanded_idx_mapping, high_temp, seed, pos, apply_temperature=True - ) - if s_low.item() == 0: - low_temp_winner_count += 1 - if s_high.item() == 0: - high_temp_winner_count += 1 - - torch.npu.synchronize() - # Low temp should pick the winner much more often than high temp - assert low_temp_winner_count > high_temp_winner_count, ( - f"Low temp winner count ({low_temp_winner_count}) should be > " - f"high temp winner count ({high_temp_winner_count})" - ) - # Low temp with such a strong signal should almost always pick token 0 - assert low_temp_winner_count > num_trials * 0.9, ( - f"Low temp winner count ({low_temp_winner_count}/{num_trials}) should be >90%" - ) - - @pytest.mark.parametrize( - "num_tokens,num_reqs,vocab_size", - [ - (4, 4, 32000), - (8, 4, 32000), - ], - ) - def test_gumbel_sample_mixed_temperature(self, num_tokens, num_reqs, vocab_size): - """Mix of temp=0 and temp>0: temp=0 tokens must be greedy.""" - torch.manual_seed(11) - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - # identity mapping: token i -> request i (for simplicity) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.rand(num_tokens, dtype=torch.float32, device=DEVICE) + 0.5 - # force first half to greedy - temperature[: num_tokens // 2] = 0.0 - seed = torch.randint(0, 2**31, (num_tokens,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - sampled = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - torch.npu.synchronize() - - greedy = logits.argmax(dim=-1) - for tok in range(num_tokens // 2): - assert sampled[tok].item() == greedy[tok].item(), ( - f"Token {tok} (temp=0) should be greedy: got {sampled[tok].item()}, expected {greedy[tok].item()}" - ) - - def test_gumbel_sample_expanded_idx_mapping(self): - """Multiple tokens mapping to the same request must work correctly.""" - torch.manual_seed(99) - num_tokens = 6 - num_reqs = 2 - vocab_size = 32000 - - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - # tokens 0,1,2 -> req 0; tokens 3,4,5 -> req 1 - expanded_idx_mapping = torch.tensor([0, 0, 0, 1, 1, 1], dtype=torch.int32, device=DEVICE) - temperature = torch.zeros(num_reqs, dtype=torch.float32, device=DEVICE) - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - sampled = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - torch.npu.synchronize() - - expected = logits.argmax(dim=-1) - assert torch.equal(sampled, expected), ( - f"Expanded mapping greedy mismatch: {sampled.tolist()} vs {expected.tolist()}" - ) - - def test_gumbel_sample_shared_seed_same_request(self): - """Tokens mapping to the same request share seed, so with same pos they - should produce the same Gumbel noise and therefore the same sample (given - same logits).""" - torch.manual_seed(42) - vocab_size = 32000 - num_reqs = 1 - - # Two tokens with identical logits, same request, same position - logits_row = torch.randn(1, vocab_size, dtype=torch.float32, device=DEVICE) - logits = logits_row.repeat(2, 1) - expanded_idx_mapping = torch.tensor([0, 0], dtype=torch.int32, device=DEVICE) - temperature = torch.tensor([0.8], dtype=torch.float32, device=DEVICE) - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - # Same pos -> same Gumbel noise - pos = torch.tensor([5, 5], dtype=torch.int32, device=DEVICE) - - sampled = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=True) - torch.npu.synchronize() - - assert sampled[0].item() == sampled[1].item(), ( - f"Tokens with same logits, seed, and pos should sample the same token: " - f"got {sampled[0].item()} vs {sampled[1].item()}" - ) - - def test_gumbel_sample_apply_temperature_true_nonzero(self): - """apply_temperature=True with temp>0 must divide logits by temperature - before adding Gumbel noise. Verify via processed_logits output.""" - torch.manual_seed(77) - num_tokens, num_reqs, vocab_size = 4, 4, 32000 - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.rand(num_reqs, dtype=torch.float32, device=DEVICE) * 1.5 + 0.5 - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - # Use processed_logits to verify temperature was applied - out_logits = torch.zeros(num_reqs, vocab_size, dtype=torch.float32, device=DEVICE) - gumbel_sample( - logits, - expanded_idx_mapping, - temperature, - seed, - pos, - apply_temperature=True, - output_processed_logits=out_logits, - ) - torch.npu.synchronize() - - for tok in range(num_tokens): - req = expanded_idx_mapping[tok].item() - temp = temperature[req].item() - expected = logits[tok].float() / temp - assert torch.allclose(out_logits[req].float(), expected, atol=1e-4, rtol=1e-4), ( - f"processed_logits mismatch at token {tok} (req {req}, temp={temp:.3f}): " - f"max_diff={(out_logits[req].float() - expected).abs().max().item():.6f}" - ) - - def test_gumbel_sample_apply_temperature_false_nonzero(self): - """apply_temperature=False with temp>0: processed_logits must contain - raw logits (no temperature division), but Gumbel noise is still added - to sampling.""" - torch.manual_seed(78) - num_tokens, num_reqs, vocab_size = 4, 4, 32000 - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.rand(num_reqs, dtype=torch.float32, device=DEVICE) * 1.5 + 0.5 - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - out_logits = torch.zeros(num_reqs, vocab_size, dtype=torch.float32, device=DEVICE) - gumbel_sample( - logits, - expanded_idx_mapping, - temperature, - seed, - pos, - apply_temperature=False, - output_processed_logits=out_logits, - ) - torch.npu.synchronize() - - for tok in range(num_tokens): - req = expanded_idx_mapping[tok].item() - # Without temperature application, stored logits should match raw logits - expected = logits[tok].float() - assert torch.allclose(out_logits[req].float(), expected, atol=1e-4, rtol=1e-4), ( - f"processed_logits should be raw logits when apply_temperature=False: " - f"max_diff={(out_logits[req].float() - expected).abs().max().item():.6f}" - ) - - def test_gumbel_sample_processed_logits_req_state_idx(self): - """Processed logits must be stored at req_state_idx position, not token_idx. - - This tests the EAGLE speculative decoding scenario where the idx_mapping - is non-contiguous (e.g., active requests [2,5,7,0] out of 8 slots). - The buffer is shaped [max_num_reqs, vocab_size] and the kernel must store - at the correct request slot. - """ - torch.manual_seed(200) - num_tokens = 4 - max_num_reqs = 8 - vocab_size = 4096 - - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - # Non-contiguous mapping: tokens 0-3 map to requests 2,5,7,0 - expanded_idx_mapping = torch.tensor([2, 5, 7, 0], dtype=torch.int32, device=DEVICE) - temperature = torch.ones(max_num_reqs, dtype=torch.float32, device=DEVICE) * 0.8 - seed = torch.randint(0, 2**31, (max_num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - out_logits = torch.zeros(max_num_reqs, vocab_size, dtype=torch.float32, device=DEVICE) - gumbel_sample( - logits, - expanded_idx_mapping, - temperature, - seed, - pos, - apply_temperature=True, - output_processed_logits=out_logits, - ) - torch.npu.synchronize() - - for tok in range(num_tokens): - req = expanded_idx_mapping[tok].item() - temp = temperature[req].item() - expected = logits[tok].float() / temp - actual = out_logits[req] - assert torch.allclose(actual.float(), expected, atol=1e-4, rtol=1e-4), ( - f"Req {req} (tok={tok}, temp={temp:.3f}): max_diff={(actual.float() - expected).abs().max().item():.6f}" - ) - - # Also verify that unused request slots remain zero - used_reqs = set(expanded_idx_mapping.tolist()) - for req in range(max_num_reqs): - if req not in used_reqs: - assert (out_logits[req] == 0).all(), f"Unused request slot {req} should be all zeros" - - def test_gumbel_sample_processed_logits_col(self): - """output_processed_logits_col selects which column (draft step) to write. - - Simulates EAGLE with buffer [max_num_reqs, num_steps, vocab_size]. - """ - torch.manual_seed(201) - num_tokens = 3 - max_num_reqs = 4 - vocab_size = 2048 - num_steps = 3 - - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.ones(max_num_reqs, dtype=torch.float32, device=DEVICE) * 0.9 - seed = torch.randint(0, 2**31, (max_num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - # Buffer: [max_num_reqs, num_steps, vocab_size] - draft_logits = torch.zeros(max_num_reqs, num_steps, vocab_size, dtype=torch.float32, device=DEVICE) - - # Write to column (step) 1 - col_tensor = torch.tensor(1, dtype=torch.int32, device=DEVICE) - gumbel_sample( - logits, - expanded_idx_mapping, - temperature, - seed, - pos, - apply_temperature=True, - output_processed_logits=draft_logits, - output_processed_logits_col=col_tensor, - ) - torch.npu.synchronize() - - for tok in range(num_tokens): - req = expanded_idx_mapping[tok].item() - temp = temperature[req].item() - expected = logits[tok].float() / temp - # Data should be at draft_logits[req, 1, :] (column 1) - actual = draft_logits[req, 1, :] - assert torch.allclose(actual.float(), expected, atol=1e-4, rtol=1e-4), ( - f"Token {tok} at col=1: mismatch, max_diff={(actual.float() - expected).abs().max().item():.6f}" - ) - # Column 0 and 2 should be untouched (zeros) - assert (draft_logits[req, 0, :] == 0).all(), f"Col 0 should be zeros for req {req}" - assert (draft_logits[req, 2, :] == 0).all(), f"Col 2 should be zeros for req {req}" - - def test_gumbel_sample_processed_logits_mixed_temp(self): - """Processed logits with mixed temperature (1:1 token-to-request mapping): - - temp=0: stored logits should be raw (no scaling) - - temp>0 with apply_temperature=True: stored logits should be logits/temp - - Note: In practice, output_processed_logits is only used by EAGLE - speculative decoding, which always has 1:1 token-to-request mapping. - Multiple tokens per request would cause a write race (undefined order). - """ - torch.manual_seed(88) - num_tokens = 4 - num_reqs = 4 - vocab_size = 4096 - - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - # 1:1 mapping: token i -> request i (matches EAGLE usage) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.tensor([0.0, 0.8, 1.5, 0.0], dtype=torch.float32, device=DEVICE) - seed = torch.randint(0, 2**31, (num_reqs,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - out_logits = torch.zeros(num_reqs, vocab_size, dtype=torch.float32, device=DEVICE) - gumbel_sample( - logits, - expanded_idx_mapping, - temperature, - seed, - pos, - apply_temperature=True, - output_processed_logits=out_logits, - ) - torch.npu.synchronize() - - for tok in range(num_tokens): - req = expanded_idx_mapping[tok].item() - temp = temperature[req].item() - if temp == 0.0: - expected = logits[tok].float() - else: - expected = logits[tok].float() / temp - actual = out_logits[req] - assert torch.allclose(actual.float(), expected, atol=1e-4, rtol=1e-4), ( - f"Req {req} (tok={tok}, temp={temp:.3f}): max_diff={(actual.float() - expected).abs().max().item():.6f}" - ) - - def test_gumbel_sample_single_token(self): - """Single token with temperature > 0 should work.""" - torch.manual_seed(42) - logits = torch.randn(1, 32000, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.tensor([0], dtype=torch.int32, device=DEVICE) - temperature = torch.tensor([0.7], dtype=torch.float32, device=DEVICE) - seed = torch.tensor([12345], dtype=torch.int64, device=DEVICE) - pos = torch.tensor([0], dtype=torch.int32, device=DEVICE) - - sampled = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=True) - torch.npu.synchronize() - - assert sampled.shape == (1,) - assert 0 <= sampled.item() < 32000 - - def test_gumbel_sample_large_vocab(self): - """Large vocabulary (151936 = Qwen2) should work correctly.""" - torch.manual_seed(401) - vocab_size = 151936 - num_tokens = 4 - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - temperature = torch.zeros(num_tokens, dtype=torch.float32, device=DEVICE) - seed = torch.randint(0, 2**31, (num_tokens,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - sampled = gumbel_sample(logits, expanded_idx_mapping, temperature, seed, pos, apply_temperature=False) - torch.npu.synchronize() - - expected = logits.argmax(dim=-1) - assert torch.equal(sampled, expected), "Large vocab greedy mismatch" - - def test_gumbel_sample_extreme_temperatures(self): - """Very low and very high temperatures should not crash.""" - torch.manual_seed(42) - num_tokens, vocab_size = 4, 32000 - logits = torch.randn(num_tokens, vocab_size, dtype=torch.float32, device=DEVICE) - expanded_idx_mapping = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - seed = torch.randint(0, 2**31, (num_tokens,), dtype=torch.int64, device=DEVICE) - pos = torch.arange(num_tokens, dtype=torch.int32, device=DEVICE) - - # Very low temperature (near-greedy) - low_temp = torch.tensor([0.01, 0.01, 0.01, 0.01], dtype=torch.float32, device=DEVICE) - s1 = gumbel_sample(logits, expanded_idx_mapping, low_temp, seed, pos, apply_temperature=True) - torch.npu.synchronize() - assert (s1 >= 0).all() and (s1 < vocab_size).all() - - # Very high temperature (near-uniform) - high_temp = torch.tensor([100.0, 100.0, 100.0, 100.0], dtype=torch.float32, device=DEVICE) - s2 = gumbel_sample(logits, expanded_idx_mapping, high_temp, seed, pos, apply_temperature=True) - torch.npu.synchronize() - assert (s2 >= 0).all() and (s2 < vocab_size).all() diff --git a/tests/ut/sample/test_rejection_sampler.py b/tests/ut/sample/test_rejection_sampler.py index 39eba53b2..a24634d9f 100644 --- a/tests/ut/sample/test_rejection_sampler.py +++ b/tests/ut/sample/test_rejection_sampler.py @@ -114,100 +114,6 @@ def test_rejection_random_sample_pytorch(self): assert output_token_ids[0, 1].item() == 0 assert output_token_ids[0, 2].item() == 100 - @patch("torch.arange", new=mock_pin_memory(torch.arange)) - @patch("torch.ones", new=mock_pin_memory(torch.ones)) - @patch("torch.full", new=mock_pin_memory(torch.full)) - @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_rejection_random_sample_pytorch_rejects_placeholder(self): - batch_size = 1 - max_spec_len = 1 - output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - - cu_num_draft_tokens = torch.tensor([1]) - draft_token_ids = torch.tensor([PLACEHOLDER_TOKEN_ID]) - target_probs = torch.tensor([[0.0, 0.0, 1.0]]) - bonus_token_ids = torch.tensor([[100]]) - recovered_token_ids = torch.tensor([2]) - uniform_probs = torch.tensor([0.0]) - is_greedy = torch.tensor([False]) - - rejection_random_sample_pytorch( - output_token_ids, - cu_num_draft_tokens, - draft_token_ids, - None, - target_probs, - bonus_token_ids, - recovered_token_ids, - uniform_probs, - is_greedy, - max_spec_len, - vocab_size=3, - IS_NGRAM=True, - ) - - assert output_token_ids.tolist() == [[2, PLACEHOLDER_TOKEN_ID]] - - @patch("torch.arange", new=mock_pin_memory(torch.arange)) - @patch("torch.ones", new=mock_pin_memory(torch.ones)) - @patch("torch.full", new=mock_pin_memory(torch.full)) - @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_rejection_random_sample_pytorch_rejects_all_placeholder_mtp3(self): - batch_size = 1 - max_spec_len = 3 - output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - - cu_num_draft_tokens = torch.tensor([3]) - draft_token_ids = torch.tensor([PLACEHOLDER_TOKEN_ID, PLACEHOLDER_TOKEN_ID, PLACEHOLDER_TOKEN_ID]) - # Placeholder draft tokens must reject regardless of target probability. - # The recovered token is passed in after recovery sampling. - target_probs = torch.zeros((max_spec_len, 3)) - bonus_token_ids = torch.tensor([[100]]) - recovered_token_ids = torch.tensor([2, 1, 0]) - uniform_probs = torch.tensor([0.0, 0.0, 0.0]) - is_greedy = torch.tensor([False]) - - rejection_random_sample_pytorch( - output_token_ids, - cu_num_draft_tokens, - draft_token_ids, - None, - target_probs, - bonus_token_ids, - recovered_token_ids, - uniform_probs, - is_greedy, - max_spec_len, - vocab_size=3, - IS_NGRAM=True, - ) - - assert output_token_ids.tolist() == [[2, PLACEHOLDER_TOKEN_ID, PLACEHOLDER_TOKEN_ID, PLACEHOLDER_TOKEN_ID]] - - @patch("torch.arange", new=mock_pin_memory(torch.arange)) - @patch("torch.ones", new=mock_pin_memory(torch.ones)) - @patch("torch.full", new=mock_pin_memory(torch.full)) - @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_sample_recovered_tokens_pytorch_keeps_placeholder_distribution(self): - output_token_ids = torch.empty(1, dtype=torch.int32) - cu_num_draft_tokens = torch.tensor([1]) - draft_token_ids = torch.tensor([PLACEHOLDER_TOKEN_ID]) - target_probs = torch.tensor([[0.1, 0.2, 0.7]]) - q = torch.ones((1, 3), dtype=torch.float32) - - sample_recovered_tokens_pytorch( - output_token_ids, - cu_num_draft_tokens, - draft_token_ids, - None, - target_probs, - q, - vocab_size=3, - IS_NGRAM=True, - ) - - assert output_token_ids.tolist() == [2] - @patch("torch.arange", new=mock_pin_memory(torch.arange)) @patch("torch.ones", new=mock_pin_memory(torch.ones)) @patch("torch.full", new=mock_pin_memory(torch.full)) @@ -511,30 +417,30 @@ def test_reduce_sample_recovered_tokens_blockwise_pytorch(self): @patch("torch.ones", new=mock_pin_memory(torch.ones)) @patch("torch.full", new=mock_pin_memory(torch.full)) @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_rejection_random_sample_block_verify_pytorch_standard(self): - """Test block verify without reduce_sampling: standard full-vocab path.""" + def test_rejection_random_sample_block_verify_pytorch(self): + """Test random rejection sampling for block verify: accept based on uniform probability""" batch_size = 2 max_spec_len = 3 output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - cu_num_draft_tokens = torch.tensor([2, 3]) + cu_num_draft_tokens = torch.tensor([2, 1]) draft_token_ids = torch.tensor([1, 0, 2]) draft_probs = torch.tensor( [ [0.0, 0.6, 0.0, 0.4], - [0.2, 0.0, 0.3, 0.5], - [0.0, 0.0, 0.5, 0.5], + [0.1, 0.2, 0.3, 0.4], + [0.5, 0.5, 0.0, 0.0], ] ) target_probs = torch.tensor( [ [0.0, 0.8, 0.0, 0.2], - [0.1, 0.0, 0.3, 0.6], - [0.0, 0.0, 0.9, 0.1], + [0.2, 0.1, 0.3, 0.4], + [0.9, 0.1, 0.0, 0.0], ] ) bonus_token_ids = torch.tensor([[100], [200]]) - recovered_token_ids = torch.tensor([99, 88, 77]) + recovered_token_ids = torch.tensor([1, 2, 3]) uniform_probs = torch.tensor([0.7, 0.6, 0.5]) is_greedy = torch.tensor([False, False]) vocab_size = 4 @@ -557,185 +463,46 @@ def test_rejection_random_sample_block_verify_pytorch_standard(self): assert output_token_ids[0, 0].item() == 1 assert output_token_ids[0, 1].item() == 0 assert output_token_ids[0, 2].item() == 100 - assert output_token_ids[1, 0].item() == 2 - assert output_token_ids[1, 1].item() == 200 - - -class TestEntropyVerify(TestBase): - """Test ENTROPY_VERIFY mode in rejection sampling. - - Entropy verify modifies the acceptance threshold based on the entropy - of the original target distribution: - - High entropy (uncertain) → lower effective threshold → more accepting - - Low entropy (certain) → higher effective threshold → stricter - """ - - @patch("torch.arange", new=mock_pin_memory(torch.arange)) - @patch("torch.ones", new=mock_pin_memory(torch.ones)) - @patch("torch.full", new=mock_pin_memory(torch.full)) - @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_entropy_verify_standard_high_entropy_accepts_more(self): - """High entropy (uniform-like) makes acceptance easier via lower threshold.""" - batch_size = 2 - max_spec_len = 2 - output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - - cu_num_draft_tokens = torch.tensor([2, 1]) - draft_token_ids = torch.tensor([1, 0, 2]) - draft_probs = torch.tensor( - [ - [0.6, 0.4, 0.0], - [0.2, 0.8, 0.0], - [0.5, 0.5, 0.0], - ] - ) - target_probs = torch.tensor( - [ - [0.8, 0.2, 0.0], - [0.1, 0.9, 0.0], - [0.9, 0.1, 0.0], - ] - ) - bonus_token_ids = torch.tensor([[100], [200]]) - recovered_token_ids = torch.tensor([99, 88, 77]) - uniform_probs = torch.tensor([0.7, 0.6, 0.5]) - is_greedy = torch.tensor([False, False]) - vocab_size = 3 - - ori_target_probs = torch.tensor( - [ - [0.8, 0.19, 0.01], - [0.09, 0.9, 0.01], - [0.9, 0.09, 0.01], - ] - ) - - rejection_random_sample_pytorch( - output_token_ids, - cu_num_draft_tokens, - draft_token_ids, - draft_probs, - target_probs, - bonus_token_ids, - recovered_token_ids, - uniform_probs, - is_greedy, - max_spec_len, - vocab_size, - IS_NGRAM=False, - ENTROPY_VERIFY=True, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, - ) - - assert output_token_ids[0, 0].item() == 99 - assert output_token_ids[0, 1].item() == -1 - assert output_token_ids[0, 2].item() == -1 - - @patch("torch.arange", new=mock_pin_memory(torch.arange)) - @patch("torch.ones", new=mock_pin_memory(torch.ones)) - @patch("torch.full", new=mock_pin_memory(torch.full)) - @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_entropy_verify_standard_low_entropy_stricter(self): - """Low entropy (peaked distribution) keeps threshold near POSTERIOR_THRESHOLD.""" - batch_size = 1 - max_spec_len = 2 - output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - - cu_num_draft_tokens = torch.tensor([2]) - draft_token_ids = torch.tensor([1, 0]) - draft_probs = torch.tensor( - [ - [0.6, 0.4, 0.0], - [0.8, 0.2, 0.0], - ] - ) - target_probs = torch.tensor( - [ - [0.8, 0.2, 0.0], - [0.1, 0.9, 0.0], - ] - ) - bonus_token_ids = torch.tensor([[100]]) - recovered_token_ids = torch.tensor([99, 88]) - uniform_probs = torch.tensor([0.7, 0.6]) - is_greedy = torch.tensor([False]) - vocab_size = 3 - - ori_target_probs = torch.tensor( - [ - [0.8, 0.19, 0.01], - [0.09, 0.9, 0.01], - ] - ) - - rejection_random_sample_pytorch( - output_token_ids, - cu_num_draft_tokens, - draft_token_ids, - draft_probs, - target_probs, - bonus_token_ids, - recovered_token_ids, - uniform_probs, - is_greedy, - max_spec_len, - vocab_size, - IS_NGRAM=False, - ENTROPY_VERIFY=True, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, - ) - - assert output_token_ids[0, 0].item() == 99 - assert output_token_ids[0, 1].item() == -1 - assert output_token_ids[0, 2].item() == -1 @patch("torch.arange", new=mock_pin_memory(torch.arange)) @patch("torch.ones", new=mock_pin_memory(torch.ones)) @patch("torch.full", new=mock_pin_memory(torch.full)) @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_entropy_verify_block_verify(self): - """Entropy verify with block verify mode.""" + def test_rejection_random_reduce_sample_pytorch(self): + """Test random rejection sampling: accept based on uniform probability""" batch_size = 2 max_spec_len = 3 output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - cu_num_draft_tokens = torch.tensor([2, 1]) draft_token_ids = torch.tensor([1, 0, 2]) draft_probs = torch.tensor( [ - [0.6, 0.4, 0.0, 0.0], - [0.2, 0.8, 0.0, 0.0], - [0.5, 0.5, 0.0, 0.0], + [0.0, 0.6, 0.0, 0.4, 0.0], # vocab_size=5 + [0.1, 0.2, 0.3, 0.4, 0.0], + [0.5, 0.5, 0.0, 0.0, 0.0], ] ) target_probs = torch.tensor( [ - [0.8, 0.2, 0.0, 0.0], - [0.1, 0.9, 0.0, 0.0], + [0.0, 0.8, 0.0, 0.2], + [0.2, 0.1, 0.3, 0.4], [0.9, 0.1, 0.0, 0.0], ] ) bonus_token_ids = torch.tensor([[100], [200]]) - recovered_token_ids = torch.tensor([99, 88, 77]) + recovered_token_ids = torch.tensor([1, 2, 3]) uniform_probs = torch.tensor([0.7, 0.6, 0.5]) is_greedy = torch.tensor([False, False]) - vocab_size = 4 - - ori_target_probs = torch.tensor( + vocab_size = 5 + target_indices = torch.tensor( [ - [0.8, 0.18, 0.01, 0.01], - [0.88, 0.9, 0.01, 0.01], - [0.9, 0.08, 0.01, 0.01], + [0, 1, 2, 3], + [0, 1, 2, 3], + [0, 1, 2, 3], ] ) - - rejection_random_sample_block_verify_pytorch( + enable_reduce_sampling = True + rejection_random_sample_pytorch( output_token_ids, cu_num_draft_tokens, draft_token_ids, @@ -748,188 +515,89 @@ def test_entropy_verify_block_verify(self): max_spec_len, vocab_size, IS_NGRAM=False, - ENTROPY_VERIFY=True, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, + target_indices=target_indices, + enable_reduce_sampling=enable_reduce_sampling, ) - - assert output_token_ids[0, 0].item() == 99 - assert output_token_ids[0, 1].item() == -1 - assert output_token_ids[0, 2].item() == -1 + assert output_token_ids[0, 0].item() == 1 + assert output_token_ids[0, 1].item() == 0 + assert output_token_ids[0, 2].item() == 100 @patch("torch.arange", new=mock_pin_memory(torch.arange)) @patch("torch.ones", new=mock_pin_memory(torch.ones)) @patch("torch.full", new=mock_pin_memory(torch.full)) @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_entropy_verify_ngram(self): - """ENTROPY_VERIFY with IS_NGRAM: draft_probs=None, draft_token_probs=1.0. - - In NGRAM mode, acceptance depends on target_prob alone (since - draft_prob=1.0). Entropy verify lowers the threshold for high-entropy - tokens, making acceptance easier when the target distribution is - uncertain. - """ - batch_size = 1 - max_spec_len = 2 - output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - - cu_num_draft_tokens = torch.tensor([2]) - draft_token_ids = torch.tensor([0, 1]) + def test_sample_recovered_tokens_blockwise_pytorch_ngram(self): + """Test recovered token sampling for blockwise speculative decoding with n-gram.""" + output_token_ids = torch.empty(2, dtype=torch.int32) + cu_num_draft_tokens = torch.tensor([1, 2]) + draft_token_ids = torch.tensor([1, 2]) draft_probs = None target_probs = torch.tensor( [ - [0.6, 0.2, 0.2], - [0.1, 0.1, 0.8], + [0.1, 0.2, 0.7], + [0.3, 0.3, 0.4], ] ) - bonus_token_ids = torch.tensor([[100]]) - recovered_token_ids = torch.tensor([99, 88]) - uniform_probs = torch.tensor([0.7, 0.6]) - is_greedy = torch.tensor([False]) - vocab_size = 3 - - ori_target_probs = torch.tensor( + q = torch.tensor( [ - [0.6, 0.2, 0.2], - [0.1, 0.1, 0.8], + [0.1, 0.2, 0.7], + [0.5, 0.4, 0.1], ] ) + vocab_size = 3 - rejection_random_sample_pytorch( + sample_recovered_tokens_blockwise_pytorch( output_token_ids, cu_num_draft_tokens, draft_token_ids, draft_probs, target_probs, - bonus_token_ids, - recovered_token_ids, - uniform_probs, - is_greedy, - max_spec_len, + q, vocab_size, IS_NGRAM=True, - ENTROPY_VERIFY=True, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, ) - assert output_token_ids[0, 0].item() == 0 - assert output_token_ids[0, 1].item() == 88 + assert output_token_ids[0].item() == 0 + assert output_token_ids[1].item() == 1 @patch("torch.arange", new=mock_pin_memory(torch.arange)) @patch("torch.ones", new=mock_pin_memory(torch.ones)) @patch("torch.full", new=mock_pin_memory(torch.full)) @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_entropy_verify_block_verify_ngram(self): - """ENTROPY_VERIFY + IS_NGRAM + block_verify combined. - - Tests the interaction of all three modes: NGRAM (draft_probs=None), - block verify (cumulative acceptance), and entropy-based threshold - adjustment. - """ - batch_size = 1 - max_spec_len = 3 - output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - - cu_num_draft_tokens = torch.tensor([2]) + def test_sample_recovered_tokens_blockwise_pytorch(self): + """Test recovered token sampling for blockwise speculative decoding.""" + output_token_ids = torch.empty(2, dtype=torch.int32) + cu_num_draft_tokens = torch.tensor([1, 2]) draft_token_ids = torch.tensor([0, 1]) - draft_probs = None - target_probs = torch.tensor( - [ - [0.6, 0.2, 0.2, 0.0], - [0.1, 0.1, 0.8, 0.0], - ] - ) - bonus_token_ids = torch.tensor([[100]]) - recovered_token_ids = torch.tensor([99, 88]) - uniform_probs = torch.tensor([0.7, 0.6]) - is_greedy = torch.tensor([False]) - vocab_size = 4 - - ori_target_probs = torch.tensor( + draft_probs = torch.tensor( [ - [0.6, 0.2, 0.2, 0.0], - [0.1, 0.1, 0.8, 0.0], + [0.6, 0.1, 0.3], + [0.2, 0.7, 0.1], ] ) - - rejection_random_sample_block_verify_pytorch( - output_token_ids, - cu_num_draft_tokens, - draft_token_ids, - draft_probs, - target_probs, - bonus_token_ids, - recovered_token_ids, - uniform_probs, - is_greedy, - max_spec_len, - vocab_size, - IS_NGRAM=True, - ENTROPY_VERIFY=True, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, - ) - - assert output_token_ids[0, 0].item() == 0 - assert output_token_ids[0, 1].item() == 88 - - @patch("torch.arange", new=mock_pin_memory(torch.arange)) - @patch("torch.ones", new=mock_pin_memory(torch.ones)) - @patch("torch.full", new=mock_pin_memory(torch.full)) - @patch("torch.tensor", new=mock_pin_memory(torch.tensor)) - def test_entropy_verify_no_ori_probs_fallback(self): - """When ori_target_probs is None, fallback to target_probs for entropy.""" - batch_size = 1 - max_spec_len = 2 - output_token_ids = torch.full((batch_size, max_spec_len + 1), PLACEHOLDER_TOKEN_ID) - - cu_num_draft_tokens = torch.tensor([2]) - draft_token_ids = torch.tensor([1, 0]) - draft_probs = torch.tensor( + target_probs = torch.tensor( [ - [0.6, 0.4, 0.0], - [0.8, 0.2, 0.0], + [0.8, 0.1, 0.1], + [0.3, 0.6, 0.1], ] ) - target_probs = torch.tensor( + q = torch.tensor( [ - [0.35, 0.33, 0.32], - [0.34, 0.34, 0.32], + [0.5, 0.3, 0.2], + [0.1, 0.8, 0.1], ] ) - bonus_token_ids = torch.tensor([[100]]) - recovered_token_ids = torch.tensor([99, 88]) - uniform_probs = torch.tensor([0.7, 0.6]) - is_greedy = torch.tensor([False]) vocab_size = 3 - rejection_random_sample_pytorch( + sample_recovered_tokens_blockwise_pytorch( output_token_ids, cu_num_draft_tokens, draft_token_ids, draft_probs, target_probs, - bonus_token_ids, - recovered_token_ids, - uniform_probs, - is_greedy, - max_spec_len, + q, vocab_size, IS_NGRAM=False, - ENTROPY_VERIFY=True, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=None, ) - - assert output_token_ids[0, 0].item() == 1 - assert output_token_ids[0, 1].item() in (0, 88) - assert output_token_ids[0, 2].item() == 100 + assert output_token_ids[0].item() == 0 + assert output_token_ids[1].item() == 0 diff --git a/tests/ut/spec_decode/a2/test_eagle_proposer.py b/tests/ut/spec_decode/a2/test_eagle_proposer.py index b44c08f93..e6f8242e1 100644 --- a/tests/ut/spec_decode/a2/test_eagle_proposer.py +++ b/tests/ut/spec_decode/a2/test_eagle_proposer.py @@ -144,50 +144,6 @@ def assert_attr_equal(attr: str | tuple[str, Any, Any], expect: Any, actual: Any assert expect_value == actual_value, f"{attr_name} value mismatch" -def test_prepare_inputs_padded_preserves_internal_seq_lens_cpu(): - proposer = AscendEagleProposer.__new__(AscendEagleProposer) - proposer.pcp_size = 1 - proposer.arange = torch.arange(16, dtype=torch.int32) - proposer.runner = MagicMock() - proposer.runner.actual_seq_lengths_q = [3, 3] - proposer.runner.attn_state = AscendAttentionState.SpecDecoding - proposer.runner.decode_token_per_req = 4 - - internal_seq_lens_cpu = torch.tensor([7, 9], dtype=torch.int32) - common_attn_metadata = AscendCommonAttentionMetadata( - query_start_loc=torch.tensor([0, 3, 6], dtype=torch.int32), - query_start_loc_cpu=torch.tensor([0, 3, 6], dtype=torch.int32), - seq_lens=torch.tensor([7, 9], dtype=torch.int32), - _seq_lens_cpu=internal_seq_lens_cpu, - seq_lens_cpu=None, - num_computed_tokens_cpu=None, - num_reqs=2, - num_actual_tokens=6, - num_input_tokens=6, - max_query_len=3, - actual_seq_lengths_q=[3, 3], - block_table_tensor=torch.zeros((2, 1), dtype=torch.int32), - slot_mapping=torch.arange(6, dtype=torch.int32), - positions=torch.arange(6), - attn_state=AscendAttentionState.SpecDecoding, - decode_token_per_req=4, - max_seq_len=9, - ) - spec_decode_metadata = MagicMock() - spec_decode_metadata.cu_num_draft_tokens = torch.tensor([2, 3], dtype=torch.int32) - valid_sampled_tokens_count = torch.tensor([3, 1], dtype=torch.int32) - - with patch.object(llm_base_proposer, "HAS_TRITON", False): - spec_common_attn_metadata, *_ = proposer.prepare_inputs_padded( - common_attn_metadata, - spec_decode_metadata, - valid_sampled_tokens_count, - ) - - assert spec_common_attn_metadata._seq_lens_cpu is internal_seq_lens_cpu - assert spec_common_attn_metadata.seq_lens_cpu is None - - class TestEagleProposerInitialization(TestBase): def setUp(self): self.vllm_config = MagicMock(spec=VllmConfig) diff --git a/tests/ut/spec_decode/test_extract_hidden_states_proposer.py b/tests/ut/spec_decode/test_extract_hidden_states_proposer.py index 66e0a140a..ea45e80f8 100644 --- a/tests/ut/spec_decode/test_extract_hidden_states_proposer.py +++ b/tests/ut/spec_decode/test_extract_hidden_states_proposer.py @@ -32,6 +32,7 @@ from vllm_ascend.spec_decode.extract_hidden_states_proposer import ( AscendExtractHiddenStatesProposer, ) +from vllm_ascend.utils import vllm_version_is @pytest.fixture(autouse=True) @@ -40,12 +41,15 @@ def _no_pin_memory(): # pin_memory=True) triggers aclInit and fails. Patch # is_pin_memory_available so vllm's ExtractHiddenStatesProposer.__init__ # creates CpuGpuBuffer with pin_memory=False. - # is_pin_memory_available was introduced in vllm after v0.22.1; - # v0.22.1 and older don't use CpuGpuBuffer, so no patch needed. - with patch( - "vllm.v1.spec_decode.extract_hidden_states.is_pin_memory_available", - return_value=False, - ): + # is_pin_memory_available was introduced in vllm after v0.20.2 (commit + # 165460941); v0.20.2 and older don't use CpuGpuBuffer, so no patch needed. + if not vllm_version_is("0.20.2"): + with patch( + "vllm.v1.spec_decode.extract_hidden_states.is_pin_memory_available", + return_value=False, + ): + yield + else: yield diff --git a/tests/ut/test_ascend_config.py b/tests/ut/test_ascend_config.py index d331b351b..ed0a05bfb 100644 --- a/tests/ut/test_ascend_config.py +++ b/tests/ut/test_ascend_config.py @@ -291,20 +291,3 @@ def test_init_ascend_config_dump_config_type_validation(self, mock_fix_incompati test_vllm_config.additional_config = {"dump_config": "/tmp/config.json"} with self.assertRaises(ValueError): init_ascend_config(test_vllm_config) - - @_clean_up_ascend_config - @patch("vllm_ascend.platform.NPUPlatform.check_and_update_config") - def test_init_ascend_config_recreates_for_new_vllm_config(self, mock_fix_incompatible_config): - first_vllm_config = VllmConfig() - first_vllm_config.additional_config = { - "ascend_compilation_config": { - "enable_npugraph_ex": False, - } - } - first_ascend_config = init_ascend_config(first_vllm_config) - self.assertFalse(first_ascend_config.ascend_compilation_config.enable_npugraph_ex) - - second_vllm_config = VllmConfig() - second_ascend_config = init_ascend_config(second_vllm_config) - self.assertIsNot(first_ascend_config, second_ascend_config) - self.assertTrue(second_ascend_config.ascend_compilation_config.enable_npugraph_ex) diff --git a/tests/ut/test_compressed_prefix_cache.py b/tests/ut/test_compressed_prefix_cache.py deleted file mode 100644 index 2214941f1..000000000 --- a/tests/ut/test_compressed_prefix_cache.py +++ /dev/null @@ -1,207 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Ascend project - -import pytest -import torch -from vllm.sampling_params import SamplingParams -from vllm.utils.hashing import sha256 -from vllm.v1.core.block_pool import BlockPool -from vllm.v1.core.kv_cache_utils import ( - BlockHashListWithBlockSize, - get_block_hash, - get_request_block_hasher, - init_none_hash, -) -from vllm.v1.kv_cache_interface import ( - FullAttentionSpec, - KVCacheConfig, - KVCacheGroupSpec, - MLAAttentionSpec, -) -from vllm.v1.request import Request - -from vllm_ascend.core.single_type_kv_cache_manager import CompressAttentionManager -from vllm_ascend.patch.platform.patch_kv_cache_coordinator import AscendHybridKVCacheCoordinator -from vllm_ascend.utils import vllm_version_is - -pytestmark = pytest.mark.cpu_test - - -@pytest.fixture(autouse=True) -def _init_hash_seed(): - init_none_hash(sha256) - - -def _make_request(request_id: str, token_ids: list[int], hash_block_size: int) -> Request: - sampling_params = SamplingParams(max_tokens=1) - sampling_params.update_from_generation_config({}, eos_token_id=100) - return Request( - request_id=request_id, - prompt_token_ids=token_ids, - sampling_params=sampling_params, - pooling_params=None, - block_hasher=get_request_block_hasher(hash_block_size, sha256), - ) - - -def _make_compress_manager( - block_size: int = 128, - compress_ratio: int = 4, -) -> tuple[MLAAttentionSpec, BlockPool, CompressAttentionManager]: - spec = MLAAttentionSpec( - block_size=block_size, - num_kv_heads=1, - head_size=1, - dtype=torch.float32, - compress_ratio=compress_ratio, - model_version="deepseek_v4", - ) - block_pool = BlockPool( - num_gpu_blocks=8, - enable_caching=True, - hash_block_size=block_size, - ) - if vllm_version_is("0.22.1"): - manager = CompressAttentionManager( - spec, - block_pool=block_pool, - enable_caching=True, - kv_cache_group_id=0, - ) - else: - manager = CompressAttentionManager( - spec, - block_pool=block_pool, - enable_caching=True, - kv_cache_group_id=0, - scheduler_block_size=block_size, - ) - return spec, block_pool, manager - - -def test_compressed_prefix_cache_uses_logical_block_hash() -> None: - block_size = 128 - compress_ratio = 4 - logical_block_size = block_size * compress_ratio - spec, block_pool, manager = _make_compress_manager(block_size, compress_ratio) - - request_a_tokens = list(range(logical_block_size)) - request_b_tokens = request_a_tokens.copy() - request_b_tokens[block_size + 7] = 999_999 - - request_a = _make_request("a", request_a_tokens, block_size) - request_b = _make_request("b", request_b_tokens, block_size) - - manager.allocate_new_blocks( - request_a.request_id, - num_tokens=logical_block_size, - num_tokens_main_model=logical_block_size, - ) - manager.cache_blocks(request_a, num_tokens=logical_block_size) - - cached_hash = get_block_hash(manager.req_to_blocks[request_a.request_id][0].block_hash) - expected_hash = BlockHashListWithBlockSize( - request_a.block_hashes, - block_size, - logical_block_size, - )[0] - assert cached_hash == expected_hash - - hit_blocks = CompressAttentionManager.find_longest_cache_hit( - block_hashes=request_b.block_hashes, - max_length=logical_block_size, - kv_cache_group_ids=[0], - block_pool=block_pool, - kv_cache_spec=spec, - use_eagle=False, - alignment_tokens=logical_block_size, - )[0] - - assert hit_blocks == [] - - -def test_compressed_prefix_cache_hits_identical_logical_block() -> None: - block_size = 128 - compress_ratio = 4 - logical_block_size = block_size * compress_ratio - spec, block_pool, manager = _make_compress_manager(block_size, compress_ratio) - - request = _make_request("a", list(range(logical_block_size)), block_size) - manager.allocate_new_blocks( - request.request_id, - num_tokens=logical_block_size, - num_tokens_main_model=logical_block_size, - ) - manager.cache_blocks(request, num_tokens=logical_block_size) - - hit_blocks = CompressAttentionManager.find_longest_cache_hit( - block_hashes=request.block_hashes, - max_length=logical_block_size, - kv_cache_group_ids=[0], - block_pool=block_pool, - kv_cache_spec=spec, - use_eagle=False, - alignment_tokens=logical_block_size, - )[0] - - assert hit_blocks == manager.req_to_blocks[request.request_id] - - -def test_hybrid_coordinator_rejects_partial_compressed_prefix_hit() -> None: - block_size = 128 - compress_ratio = 4 - logical_block_size = block_size * compress_ratio - request_a_tokens = list(range(logical_block_size)) - request_b_tokens = request_a_tokens.copy() - request_b_tokens[block_size + 7] = 999_999 - - request_a = _make_request("a", request_a_tokens, block_size) - request_b = _make_request("b", request_b_tokens, block_size) - compressed_spec = MLAAttentionSpec( - block_size=block_size, - num_kv_heads=1, - head_size=1, - dtype=torch.float32, - compress_ratio=compress_ratio, - model_version="deepseek_v4", - ) - full_spec = FullAttentionSpec( - block_size=block_size, - num_kv_heads=1, - head_size=1, - dtype=torch.float32, - ) - coordinator = AscendHybridKVCacheCoordinator( - kv_cache_config=KVCacheConfig( - num_blocks=16, - kv_cache_tensors=[], - kv_cache_groups=[ - KVCacheGroupSpec(["compressed"], compressed_spec), - KVCacheGroupSpec(["full"], full_spec), - ], - ), - max_model_len=logical_block_size, - use_eagle=False, - enable_caching=True, - enable_kv_cache_events=False, - dcp_world_size=1, - pcp_world_size=1, - hash_block_size=block_size, - max_num_batched_tokens=logical_block_size, - ) - - for manager in coordinator.single_type_managers: - manager.allocate_new_blocks( - request_a.request_id, - num_tokens=logical_block_size, - num_tokens_main_model=logical_block_size, - ) - manager.cache_blocks(request_a, num_tokens=logical_block_size) - - hit_blocks, hit_length = coordinator.find_longest_cache_hit( - request_b.block_hashes, - max_cache_hit_length=logical_block_size, - ) - - assert hit_length == 0 - assert hit_blocks == ([], []) diff --git a/tests/ut/test_logger.py b/tests/ut/test_logger.py deleted file mode 100644 index 3a7729a12..000000000 --- a/tests/ut/test_logger.py +++ /dev/null @@ -1,201 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. - -import logging - -from tests.ut.base import TestBase - - -class TestLoggerModule(TestBase): - def test_is_ascend_module_with_ascend_path(self): - from vllm_ascend.logger import _is_ascend_module - - self.assertTrue(_is_ascend_module("/path/to/vllm_ascend/platform.py")) - self.assertTrue(_is_ascend_module("\\path\\to\\vllm_ascend\\compilation\\acl_graph.py")) - - def test_is_ascend_module_with_vllm_path(self): - from vllm_ascend.logger import _is_ascend_module - - self.assertFalse(_is_ascend_module("/path/to/vllm/model.py")) - self.assertFalse(_is_ascend_module("")) - - def test_infer_module_name_root_file(self): - from vllm_ascend.logger import _infer_module_name - - self.assertEqual( - _infer_module_name("/vllm_ascend/platform.py"), - "platform", - ) - self.assertEqual( - _infer_module_name("/vllm_ascend/utils.py"), - "utils", - ) - - def test_infer_module_name_nested_file(self): - from vllm_ascend.logger import _infer_module_name - - self.assertEqual( - _infer_module_name("/vllm_ascend/compilation/acl_graph.py"), - "compilation", - ) - self.assertEqual( - _infer_module_name("/vllm_ascend/worker/worker.py"), - "worker", - ) - - def test_infer_module_name_edge_cases(self): - from vllm_ascend.logger import _infer_module_name - - self.assertEqual(_infer_module_name(""), "core") - self.assertEqual( - _infer_module_name("/vllm/model.py"), - "core", - ) - - def test_ascend_formatter_adds_prefix_root_file(self): - from vllm_ascend.logger import _DATE_FORMAT, _FORMAT, AscendFormatter - - fmt = AscendFormatter(fmt=_FORMAT, datefmt=_DATE_FORMAT) - record = logging.LogRecord( - name="vllm.logger", - level=logging.INFO, - pathname="/vllm_ascend/ascend_config.py", - lineno=42, - msg="test message", - args=(), - exc_info=None, - ) - result = fmt.format(record) - self.assertIn("[vllm-ascend]", result) - self.assertNotIn("[vllm-ascend] [ascend_config]", result) - self.assertIn("test message", result) - - def test_ascend_formatter_adds_prefix_nested_file(self): - from vllm_ascend.logger import _DATE_FORMAT, _FORMAT, AscendFormatter - - fmt = AscendFormatter(fmt=_FORMAT, datefmt=_DATE_FORMAT) - record = logging.LogRecord( - name="vllm.logger", - level=logging.INFO, - pathname="/vllm_ascend/compilation/acl_graph.py", - lineno=42, - msg="test message", - args=(), - exc_info=None, - ) - result = fmt.format(record) - self.assertIn("[vllm-ascend] [compilation]", result) - self.assertIn("test message", result) - - def test_ascend_formatter_pass_through_vllm_logs(self): - from vllm_ascend.logger import _DATE_FORMAT, _FORMAT, AscendFormatter - - fmt = AscendFormatter(fmt=_FORMAT, datefmt=_DATE_FORMAT) - record = logging.LogRecord( - name="vllm.logger", - level=logging.INFO, - pathname="/vllm/model.py", - lineno=42, - msg="vllm message", - args=(), - exc_info=None, - ) - result = fmt.format(record) - self.assertNotIn("[vllm-ascend]", result) - self.assertIn("vllm message", result) - - def test_ascend_colored_formatter_adds_prefix(self): - from vllm_ascend.logger import _DATE_FORMAT, _FORMAT, AscendColoredFormatter - - fmt = AscendColoredFormatter(fmt=_FORMAT, datefmt=_DATE_FORMAT) - record = logging.LogRecord( - name="vllm.logger", - level=logging.INFO, - pathname="/vllm_ascend/compilation/acl_graph.py", - lineno=42, - msg="colored test", - args=(), - exc_info=None, - ) - result = fmt.format(record) - self.assertIn("[vllm-ascend] [compilation]", result) - self.assertIn("colored test", result) - - def test_log_dir_constant(self): - from vllm_ascend.logger import _LOG_DIR - - self.assertIn("ascend", _LOG_DIR) - self.assertIn("vllm_ascend", _LOG_DIR) - - def test_setup_file_logging_creates_handler(self): - import os - import tempfile - - import regex as re - - import vllm_ascend.logger as logger_module - from vllm_ascend.logger import RotatingAscendFileHandler, _setup_file_logging - - logger_module._file_logging_configured = False - try: - with tempfile.TemporaryDirectory() as tmpdir: - log_dir = os.path.join(tmpdir, "ascend", "log", "vllm_ascend") - vllm_logger = logging.getLogger("vllm") - if not vllm_logger.handlers: - vllm_logger.addHandler(logging.StreamHandler()) - expected_level = vllm_logger.handlers[0].level - handler_count_before = len(vllm_logger.handlers) - - _setup_file_logging(log_dir) - - handler_count_after = len(vllm_logger.handlers) - self.assertEqual(handler_count_after, handler_count_before + 1) - - new_handler = vllm_logger.handlers[-1] - self.assertIsInstance(new_handler, RotatingAscendFileHandler) - self.assertEqual(new_handler.level, expected_level) - self.assertTrue(os.path.exists(log_dir)) - - base = os.path.basename(new_handler.baseFilename) # type: ignore[attr-defined] - pattern = r"^vllm_ascend_\d{8}_\d{6}_\d+\.log$" - self.assertTrue(re.match(pattern, base), f"Filename '{base}' does not match pattern") - - vllm_logger.removeHandler(new_handler) - finally: - logger_module._file_logging_configured = False - - def test_rotating_handler_rotates_on_size(self): - import os - import tempfile - - from vllm_ascend.logger import RotatingAscendFileHandler - - with tempfile.TemporaryDirectory() as tmpdir: - handler = RotatingAscendFileHandler(tmpdir, max_bytes=100) - handler.setFormatter(logging.Formatter("%(message)s")) - logger = logging.getLogger("test_rotate") - logger.addHandler(handler) - logger.setLevel(logging.DEBUG) - - for i in range(200): - logger.info("line %04d padding to fill size", i) - - handler.close() - logger.removeHandler(handler) - - files = sorted(os.listdir(tmpdir)) - self.assertGreaterEqual(len(files), 2) - self.assertTrue(files[0].endswith(".log")) - self.assertNotIn("_002", files[0]) - self.assertIn("_002", files[1]) diff --git a/tests/ut/test_platform.py b/tests/ut/test_platform.py index 18f12ea6d..882803a18 100644 --- a/tests/ut/test_platform.py +++ b/tests/ut/test_platform.py @@ -25,15 +25,7 @@ def mock_vllm_config(): mock_vllm_config.model_config = MagicMock() mock_vllm_config.model_config.is_hybrid = False mock_vllm_config.model_config.is_encoder_decoder = False - mock_vllm_config.device_config = MagicMock() - mock_vllm_config.device_config.device_type = "npu" mock_vllm_config.parallel_config = MagicMock() - mock_vllm_config.parallel_config.data_parallel_size = 1 - mock_vllm_config.parallel_config.prefill_context_parallel_size = 1 - mock_vllm_config.parallel_config.tensor_parallel_size = 1 - mock_vllm_config.parallel_config.pipeline_parallel_size = 1 - mock_vllm_config.parallel_config.context_parallel_size = 1 - mock_vllm_config.parallel_config.decode_context_parallel_size = 1 mock_vllm_config.cache_config = MagicMock() mock_vllm_config.scheduler_config = MagicMock() mock_vllm_config.scheduler_config.max_num_seqs = None @@ -233,32 +225,6 @@ def test_get_device_name(self, mock_get_device_name): self.assertEqual(self.platform.get_device_name(device_id), device_name) mock_get_device_name.assert_called_once_with(0) - @patch("torch.npu.get_device_properties") - @patch("vllm_ascend.platform.subprocess.check_output") - def test_get_device_total_memory_prefers_npu_smi(self, mock_check_output, mock_get_device_properties): - mock_check_output.return_value = ( - " DDR Capacity(MB) : 0\n HBM Capacity(MB) : 32768\n" - ) - - self.assertEqual(self.platform.get_device_total_memory(0), 32768 * 1024 * 1024) - mock_check_output.assert_called_once_with( - ["npu-smi", "info", "-t", "memory", "-i", "0"], - stderr=-3, - text=True, - ) - mock_get_device_properties.assert_not_called() - - @patch("torch.npu.get_device_properties") - @patch("vllm_ascend.platform.subprocess.check_output", side_effect=PermissionError) - def test_get_device_total_memory_falls_back_on_permission_error( - self, mock_check_output, mock_get_device_properties - ): - mock_get_device_properties.return_value.total_memory = 16 * 1024 * 1024 - - self.assertEqual(self.platform.get_device_total_memory(0), 16 * 1024 * 1024) - mock_check_output.assert_called_once() - mock_get_device_properties.assert_called_once_with(0) - @patch("torch.npu.get_device_properties") def test_get_device_uuid(self, mock_get_device_properties): device_id = 0 @@ -326,11 +292,12 @@ def test_set_additional_forward_context_reads_v2_profile_override(self): @patch("vllm_ascend.quantization.utils.maybe_auto_detect_quantization") @patch("vllm_ascend.ascend_config.init_ascend_config") + @patch("vllm_ascend.utils.update_aclgraph_sizes") @patch("vllm_ascend.utils.get_ascend_device_type", return_value=AscendDeviceType.A3) @patch("os.environ", {}) @patch("vllm_ascend.core.recompute_scheduler.RecomputeSchedulerConfig.initialize_from_config") def test_check_and_update_config_basic_config_update( - self, mock_init_recompute, mock_soc_version, mock_init_ascend, mock_auto_detect + self, mock_init_recompute, mock_soc_version, mock_update_acl, mock_init_ascend, mock_auto_detect ): mock_init_ascend.return_value = TestNPUPlatform.mock_vllm_ascend_config() vllm_config = TestNPUPlatform.mock_vllm_config() @@ -410,10 +377,7 @@ def test_check_and_update_config_enforce_eager_mode( ): self.platform.check_and_update_config(vllm_config) - self.assertTrue( - any("Compilation disabled, using eager mode by default" in log for log in cm.output), - cm.output, - ) + self.assertTrue(any("Compilation disabled, using eager mode by default" in output for output in cm.output)) self.assertEqual( vllm_config.compilation_config.mode, @@ -702,77 +666,6 @@ def test_validate_layer_sharding_config_rejects_kv_both(self): with pytest.raises(ValueError, match="layer_sharding can only be enabled in PD-disaggregated's P node"): self.platform._validate_layer_sharding_config(vllm_config) - def test_validate_parallel_config_rejects_pcp_plus_dp(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.parallel_config.data_parallel_size = 2 - vllm_config.parallel_config.prefill_context_parallel_size = 2 - - with pytest.raises(ValueError, match="PCP \(Prefill Context Parallelism\) and DP \(Data Parallelism\)"): - self.platform._validate_parallel_config(vllm_config) - - def test_validate_parallel_config_accepts_dp_only(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.parallel_config.data_parallel_size = 2 - vllm_config.parallel_config.prefill_context_parallel_size = 1 - - self.platform._validate_parallel_config(vllm_config) - - def test_validate_parallel_config_accepts_pcp_only(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.parallel_config.data_parallel_size = 1 - vllm_config.parallel_config.prefill_context_parallel_size = 2 - - self.platform._validate_parallel_config(vllm_config) - - def test_validate_parallel_config_accepts_neither(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.parallel_config.data_parallel_size = 1 - vllm_config.parallel_config.prefill_context_parallel_size = 1 - - self.platform._validate_parallel_config(vllm_config) - - def test_validate_pd_pp_mtp_config_accepts_prefill_producer(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.speculative_config = MagicMock(method="mtp") - vllm_config.parallel_config.pipeline_parallel_size = 2 - vllm_config.kv_transfer_config = MagicMock(is_kv_producer=True, kv_role="kv_producer") - - self.platform._validate_pd_pp_mtp_config(vllm_config) - - def test_validate_pd_pp_mtp_config_accepts_decode_dp_mtp(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.speculative_config = MagicMock(method="mtp") - vllm_config.parallel_config.pipeline_parallel_size = 1 - vllm_config.kv_transfer_config = MagicMock(is_kv_producer=False, kv_role="kv_consumer") - - self.platform._validate_pd_pp_mtp_config(vllm_config) - - def test_validate_pd_pp_mtp_config_rejects_decode_pp_mtp(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.speculative_config = MagicMock(method="mtp") - vllm_config.parallel_config.pipeline_parallel_size = 2 - vllm_config.kv_transfer_config = MagicMock(is_kv_producer=False, kv_role="kv_consumer") - - with pytest.raises(ValueError, match=r"PP\+MTP.*P nodes.*D nodes.*pipeline_parallel_size=1"): - self.platform._validate_pd_pp_mtp_config(vllm_config) - - def test_validate_pd_pp_mtp_config_rejects_non_pd_pp_mtp(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.speculative_config = MagicMock(method="mtp") - vllm_config.parallel_config.pipeline_parallel_size = 2 - vllm_config.kv_transfer_config = None - - with pytest.raises(ValueError, match=r"PP\+MTP.*PD-disaggregated P nodes"): - self.platform._validate_pd_pp_mtp_config(vllm_config) - - def test_validate_pd_pp_mtp_config_allows_non_mtp_spec_decode(self): - vllm_config = TestNPUPlatform.mock_vllm_config() - vllm_config.speculative_config = MagicMock(method="eagle") - vllm_config.parallel_config.pipeline_parallel_size = 2 - vllm_config.kv_transfer_config = None - - self.platform._validate_pd_pp_mtp_config(vllm_config) - @patch("vllm_ascend.quantization.utils.maybe_auto_detect_quantization") @patch("vllm_ascend.utils.get_ascend_device_type", return_value=AscendDeviceType.A3) @patch("vllm_ascend.ascend_config.init_ascend_config") diff --git a/tests/ut/test_utils.py b/tests/ut/test_utils.py index 5a35b7b6f..1c6fa2eae 100644 --- a/tests/ut/test_utils.py +++ b/tests/ut/test_utils.py @@ -19,8 +19,10 @@ import pytest import torch +from vllm.config import CompilationConfig, ModelConfig, ParallelConfig, VllmConfig from tests.ut.base import TestBase +from tests.ut.conftest import _npu_available from vllm_ascend import utils from vllm_ascend.utils import REGISTERED_ASCEND_OPS @@ -274,6 +276,25 @@ def to_dict(self): utils.get_max_hidden_layers(NoLayerConfig()) self.assertIn("num_hidden_layers", str(context.exception)) + @mock.patch.dict(os.environ, {"VLLM_ASCEND_ENABLE_FLASHCOMM1": "0"}) + @pytest.mark.skipif(not _npu_available, reason="Requires real NPU (ModelConfig inspection)") + def test_update_aclgraph_sizes(self): + test_compilation_config = CompilationConfig(cudagraph_capture_sizes=[i for i in range(150)]) + model_path = os.path.join(os.path.dirname(__file__), "_fake_weight") + test_model_config = ModelConfig(model=model_path, enforce_eager=True) + test_parallel_config = ParallelConfig() + test_vllm_config = VllmConfig( + model_config=test_model_config, + compilation_config=test_compilation_config, + parallel_config=test_parallel_config, + ) + utils.update_aclgraph_sizes(test_vllm_config) + os.environ["HCCL_OP_EXPANSION_MODE"] = "AIV" + utils.update_aclgraph_sizes(test_vllm_config) + del os.environ["HCCL_OP_EXPANSION_MODE"] + + self.assertEqual(0, len(test_vllm_config.compilation_config.cudagraph_capture_sizes)) + @mock.patch("vllm.model_executor.custom_op.CustomOp") @mock.patch("vllm_ascend.ops.activation.AscendQuickGELU") @mock.patch("vllm_ascend.ops.activation.AscendSiluAndMul") diff --git a/tests/ut/worker/a2/test_kvcomp_utils.py b/tests/ut/worker/a2/test_kvcomp_utils.py index 72dfe53fa..ab4575cf4 100644 --- a/tests/ut/worker/a2/test_kvcomp_utils.py +++ b/tests/ut/worker/a2/test_kvcomp_utils.py @@ -31,6 +31,9 @@ torch_npu.npu.config.allow_internal_format = True +NPU_AVAILABLE = hasattr(torch, "npu") and torch.npu.is_available() +print(f"NPU_AVAILABLE={NPU_AVAILABLE}") + # ============================================================================= # test KVCompConfig # ============================================================================= @@ -78,6 +81,7 @@ def test_kvcomp_config_to_json_from_json_roundtrip(): # # ============================================================================= +@pytest.mark.skipif(not NPU_AVAILABLE, reason="NPU not available") def test_hash_encoder(): """Test HashEncoder init with valid params (NPU only).""" encoder = HashEncoder( diff --git a/tests/ut/worker/a2/test_model_runner_v1.py b/tests/ut/worker/a2/test_model_runner_v1.py index fdd403296..d52fa9fed 100644 --- a/tests/ut/worker/a2/test_model_runner_v1.py +++ b/tests/ut/worker/a2/test_model_runner_v1.py @@ -2,7 +2,6 @@ from types import SimpleNamespace from unittest.mock import MagicMock, patch -import numpy as np import torch from vllm.v1.kv_cache_interface import FullAttentionSpec, KVCacheConfig, KVCacheGroupSpec, KVCacheTensor @@ -110,7 +109,6 @@ def test_sample_updates_output_token_ids_before_sampler(self, mock_lmhead_tp_ena [1, 2, 3, -1], [4, 5, -1], ] - input_batch.sampling_metadata.top_k = None input_batch.num_reqs = 2 input_batch.top_k_cpu = None input_batch.prev_req_id_to_index = { @@ -154,68 +152,6 @@ def mock_update_output_token_ids(): self.assertEqual(actual_output_token_ids[0], [1, 2, 3, 6]) self.assertEqual(actual_output_token_ids[1], [4, 5, 7]) - def test_placeholder_spec_tokens_are_sanitized_only_for_forward(self): - runner = self._build_runner() - runner.input_ids = SimpleNamespace( - cpu=torch.tensor([11, -1, 33, -1], dtype=torch.int32), - gpu=torch.tensor([11, -1, 33, -1], dtype=torch.int32), - ) - scheduler_output = SimpleNamespace( - scheduled_spec_decode_tokens={"req0": [-1]}, - ) - - runner._sanitize_placeholder_input_ids_for_forward( - scheduler_output, - num_forward_tokens=4, - ) - - self.assertEqual(runner.input_ids.gpu.tolist(), [11, 0, 33, 0]) - self.assertEqual(runner.input_ids.cpu.tolist(), [11, -1, 33, -1]) - - def test_placeholder_sanitization_is_scoped_to_current_forward(self): - runner = self._build_runner() - runner.input_ids = SimpleNamespace( - cpu=torch.tensor([11, -1, 33, -1], dtype=torch.int32), - gpu=torch.tensor([11, -1, 33, -1], dtype=torch.int32), - ) - scheduler_output = SimpleNamespace( - scheduled_spec_decode_tokens={"req0": [-1]}, - ) - - runner._sanitize_placeholder_input_ids_for_forward( - scheduler_output, - num_forward_tokens=2, - ) - - self.assertEqual(runner.input_ids.gpu.tolist(), [11, 0, 33, -1]) - - def test_mtp3_placeholder_metadata_is_preserved_before_sanitizing_forward(self): - runner = self._build_runner() - runner.pcp_size = 1 - runner.arange_np = np.arange(8, dtype=np.int32) - runner._arange_scratch = np.empty(8, dtype=np.int32) - runner.input_ids = SimpleNamespace( - cpu=torch.tensor([11, -1, -1, -1], dtype=torch.int32), - gpu=torch.tensor([11, -1, -1, -1], dtype=torch.int32), - ) - scheduler_output = SimpleNamespace( - scheduled_spec_decode_tokens={"req0": [-1, -1, -1]}, - ) - - spec_decode_metadata = runner._calc_spec_decode_metadata( - num_draft_tokens=np.array([3], dtype=np.int32), - cu_num_scheduled_tokens=np.array([4], dtype=np.int32), - num_pcp_pads=None, - ) - runner._sanitize_placeholder_input_ids_for_forward( - scheduler_output, - num_forward_tokens=4, - ) - - self.assertEqual(spec_decode_metadata.draft_token_ids.tolist(), [-1, -1, -1]) - self.assertEqual(runner.input_ids.gpu.tolist(), [11, 0, 0, 0]) - self.assertEqual(runner.input_ids.cpu.tolist(), [11, -1, -1, -1]) - class TestNPUModelRunnerDebugger(unittest.TestCase): def _build_runner(self, debugger=None): diff --git a/tests/ut/worker/a2/test_model_runner_v1_with_device.py b/tests/ut/worker/a2/test_model_runner_v1_with_device.py index d4d860873..567223ade 100644 --- a/tests/ut/worker/a2/test_model_runner_v1_with_device.py +++ b/tests/ut/worker/a2/test_model_runner_v1_with_device.py @@ -1,5 +1,4 @@ import os -from unittest.mock import MagicMock, patch import numpy as np import pytest @@ -12,7 +11,6 @@ VllmConfig, set_current_vllm_config, ) -from vllm.distributed.parallel_state import GroupCoordinator from vllm.model_executor.layers.attention import Attention from vllm.platforms import current_platform from vllm.v1.kv_cache_interface import ( @@ -95,20 +93,7 @@ def get_vllm_config(): @pytest.fixture def model_runner(): vllm_config = get_vllm_config() - with ( - set_current_vllm_config(vllm_config), - patch("vllm_ascend.worker.block_table.get_dcp_group") as mock_get_dcp_group, - patch("vllm_ascend.worker.block_table.get_pcp_group") as mock_get_pcp_group, - ): - mock_dcp_group = MagicMock(spec=GroupCoordinator) - mock_dcp_group.world_size = 1 - mock_dcp_group.rank_in_group = 0 - mock_get_dcp_group.return_value = mock_dcp_group - mock_pcp_group = MagicMock(spec=GroupCoordinator) - mock_pcp_group.world_size = 1 - mock_pcp_group.rank_in_group = 0 - mock_get_pcp_group.return_value = mock_pcp_group - + with set_current_vllm_config(vllm_config): model_config = vllm_config.model_config num_heads = model_config.get_num_kv_heads(vllm_config.parallel_config) head_size = model_config.get_head_size() diff --git a/tests/ut/worker/a2/test_worker_multi_instance.py b/tests/ut/worker/a2/test_worker_multi_instance.py index 3fb5c6b7a..55a70551a 100644 --- a/tests/ut/worker/a2/test_worker_multi_instance.py +++ b/tests/ut/worker/a2/test_worker_multi_instance.py @@ -15,10 +15,8 @@ # limitations under the License. # -from types import SimpleNamespace from unittest.mock import MagicMock, patch -from vllm.config import CUDAGraphMode from vllm.utils.mem_constants import GiB_bytes from tests.ut.base import TestBase @@ -51,17 +49,10 @@ def _make_worker( worker.model_runner = MagicMock() worker.model_runner.model_memory_usage = model_memory_usage - mock_vllm_config = MagicMock() - mock_vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE - worker.vllm_config = mock_vllm_config - mock_cache_config = MagicMock() mock_cache_config.kv_cache_memory_bytes = None - mock_cache_config.gpu_memory_utilization = requested_memory / init_total_memory worker.cache_config = mock_cache_config - worker.model_config = SimpleNamespace(hf_config=SimpleNamespace(model_type="qwen3")) - mock_snapshot = MagicMock() mock_snapshot.free_memory = init_free_memory mock_snapshot.total_memory = init_total_memory @@ -139,67 +130,6 @@ def test_single_instance_positive_kv_cache(self, mock_logger): self.assertEqual(result, expected) self.assertGreater(result, 0) - @patch("vllm_ascend.worker.worker.logger") - def test_deepseek_v4_compressed_skips_npugraph_memory_profile(self, mock_logger): - """DSV4 DSA must not run the pre-KV graph memory profiling path.""" - total = int(64 * GiB_bytes) - requested_memory = int(total * 0.9) - init_free = int(60 * GiB_bytes) - non_kv_cache = int(1 * GiB_bytes) - - worker = self._make_worker(requested_memory, init_free, total) - worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.FULL_DECODE_ONLY - worker.model_config.hf_config.model_type = "deepseek_v4" - worker.model_runner.use_compress = True - worker.model_runner.profile_cudagraph_memory.return_value = int(2 * GiB_bytes) - profile_result = self._make_profile_result( - free_memory_after=init_free - non_kv_cache, - non_kv_cache_memory=non_kv_cache, - ) - - with self._patch_memory_profiling(profile_result): - result = worker.determine_available_memory() - - worker.model_runner.profile_run.assert_called_once() - worker.model_runner.profile_cudagraph_memory.assert_not_called() - self.assertEqual( - worker.vllm_config.compilation_config.cudagraph_mode, - CUDAGraphMode.FULL_DECODE_ONLY, - ) - self.assertEqual(worker.npugraph_memory_estimate, 0) - self.assertEqual(result, requested_memory - non_kv_cache) - - @patch("vllm_ascend.worker.worker.logger") - def test_non_deepseek_compressed_still_profiles_npugraph_memory(self, mock_logger): - """The DSV4 guard must not disable graph memory profiling globally.""" - total = int(64 * GiB_bytes) - requested_memory = int(total * 0.9) - init_free = int(60 * GiB_bytes) - non_kv_cache = int(1 * GiB_bytes) - npugraph_memory = int(2 * GiB_bytes) - - worker = self._make_worker(requested_memory, init_free, total) - worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.FULL_DECODE_ONLY - worker.model_runner.use_compress = True - worker.model_runner.profile_cudagraph_memory.return_value = npugraph_memory - profile_result = self._make_profile_result( - free_memory_after=init_free - non_kv_cache, - non_kv_cache_memory=non_kv_cache, - ) - - with ( - self._patch_memory_profiling(profile_result), - patch( - "vllm_ascend.worker.worker.envs_vllm.VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS", - False, - ), - ): - result = worker.determine_available_memory() - - worker.model_runner.profile_cudagraph_memory.assert_called_once_with() - self.assertEqual(worker.npugraph_memory_estimate, npugraph_memory) - self.assertEqual(result, requested_memory - non_kv_cache) - @patch("vllm_ascend.worker.worker.logger") def test_second_instance_on_same_card_positive_kv_cache(self, mock_logger): """ diff --git a/tests/ut/worker/a2/test_worker_v1.py b/tests/ut/worker/a2/test_worker_v1.py index 78014804d..93e9e3226 100644 --- a/tests/ut/worker/a2/test_worker_v1.py +++ b/tests/ut/worker/a2/test_worker_v1.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, patch import torch -from vllm.config import CacheConfig, CUDAGraphMode, ModelConfig, ParallelConfig, ProfilerConfig, VllmConfig +from vllm.config import CacheConfig, ModelConfig, ParallelConfig, ProfilerConfig, VllmConfig from tests.ut.base import TestBase @@ -46,7 +46,6 @@ def setUp(self): self.vllm_config_mock.quant_config = MagicMock() self.vllm_config_mock.speculative_config = None self.vllm_config_mock.observability_config = None - self.vllm_config_mock.weight_transfer_config = None self.local_rank = 0 self.rank = 0 @@ -571,8 +570,6 @@ def test_determine_available_memory_normal_case( worker.requested_memory = 10000 * 0.8 worker.model_runner = MagicMock() worker.model_runner.model_memory_usage = 500 - worker.vllm_config = MagicMock() - worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE worker.cache_config = MagicMock() worker.cache_config.gpu_memory_utilization = 0.8 worker.cache_config.kv_cache_memory_bytes = None @@ -634,8 +631,6 @@ def test_determine_available_memory_with_non_torch_allocations( worker.requested_memory = 10000 * 0.9 worker.model_runner = MagicMock() worker.model_runner.model_memory_usage = 500 - worker.vllm_config = MagicMock() - worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE worker.cache_config = MagicMock() worker.cache_config.gpu_memory_utilization = 0.9 worker.cache_config.kv_cache_memory_bytes = None @@ -654,14 +649,8 @@ def test_determine_available_memory_with_non_torch_allocations( @patch("torch.npu.mem_get_info") @patch("torch.npu.reset_peak_memory_stats") @patch("torch.npu.empty_cache") - @patch("torch_npu.npu.memory_stats") def test_determine_available_memory_memory_profiling_error( - self, - mock_torch_memory_stats, - mock_torch_empty_cache, - mock_torch_reset_peak_memory_stats, - mock_torch_mem_get_info, - mock_memory_profiling, + self, mock_torch_empty_cache, mock_torch_reset_peak_memory_stats, mock_torch_mem_get_info, mock_memory_profiling ): """Test determine_available_memory throws exception on memory profiling error""" from vllm_ascend.worker.worker import NPUWorker @@ -690,16 +679,11 @@ def test_determine_available_memory_memory_profiling_error( worker.init_snapshot = mock_init_snapshot worker.requested_memory = 10000 * 0.8 worker.model_runner = MagicMock() - worker.model_runner.model_memory_usage = 0 - worker.vllm_config = MagicMock() - worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE worker.cache_config = MagicMock() worker.cache_config.gpu_memory_utilization = 0.8 worker.cache_config.kv_cache_memory_bytes = None worker.device = torch.device("npu:0") - mock_torch_memory_stats.return_value = {"allocated_bytes.all.peak": 0} - # Test should throw assertion error with self.assertRaises(AssertionError) as cm: worker.determine_available_memory() @@ -749,8 +733,6 @@ def test_determine_available_memory_negative_result( worker.requested_memory = 10000 * 0.8 worker.model_runner = MagicMock() worker.model_runner.model_memory_usage = 500 - worker.vllm_config = MagicMock() - worker.vllm_config.compilation_config.cudagraph_mode = CUDAGraphMode.NONE worker.cache_config = MagicMock() worker.cache_config.gpu_memory_utilization = 0.8 worker.cache_config.kv_cache_memory_bytes = None @@ -974,7 +956,6 @@ def test_load_model_with_sleep_mode(self, mock_allocator_class): worker.vllm_config = MagicMock() worker.vllm_config.model_config = MagicMock() worker.vllm_config.model_config.enable_sleep_mode = True - worker.vllm_config.weight_transfer_config = None # Setup allocator mock mock_allocator = MagicMock() @@ -1003,7 +984,6 @@ def test_load_model_without_sleep_mode(self): worker.vllm_config = MagicMock() worker.vllm_config.model_config = MagicMock() worker.vllm_config.model_config.enable_sleep_mode = False - worker.vllm_config.weight_transfer_config = None # Test load_model worker.load_model() @@ -1165,7 +1145,6 @@ def test_initialize_from_config_with_sleep_mode(self, mock_allocator_class, mock worker = NPUWorker() worker.model_runner = MagicMock() worker.vllm_config = MagicMock() - worker.vllm_config.speculative_config = None worker.vllm_config.model_config = MagicMock() worker.vllm_config.model_config.enable_sleep_mode = True @@ -1196,7 +1175,6 @@ def test_initialize_from_config_without_sleep_mode(self, mock_ensure_kv_transfer worker = NPUWorker() worker.model_runner = MagicMock() worker.vllm_config = MagicMock() - worker.vllm_config.speculative_config = None worker.vllm_config.model_config = MagicMock() worker.vllm_config.model_config.enable_sleep_mode = False @@ -1269,188 +1247,3 @@ def test_execute_model_kv_connector_not_finished( # When both flags are False, return EMPTY_MODEL_RUNNER_OUTPUT directly. self.assertEqual(result, mock_empty_output) - - -class TestNPUWorkerWeightUpdate(TestBase): - def _make_worker(self, engine=None): - from vllm_ascend.worker.worker import NPUWorker - - with patch.object(NPUWorker, "__init__", lambda x, **kwargs: None): - worker = NPUWorker() - worker.weight_transfer_engine = engine - worker._weight_update_active = False - worker._is_checkpoint_format = True - worker.device = torch.device("cpu") - worker.model_runner = MagicMock() - worker.model_runner.model = MagicMock() - worker.model_config = MagicMock() - return worker - - def test_check_engine_raises_when_unconfigured(self): - worker = self._make_worker(engine=None) - with self.assertRaises(RuntimeError): - worker.init_weight_transfer_engine({}) - with self.assertRaises(RuntimeError): - worker.start_weight_update() - with self.assertRaises(RuntimeError): - worker.update_weights({}) - with self.assertRaises(RuntimeError): - worker.finish_weight_update() - - def test_init_weight_transfer_engine_dispatches_to_engine(self): - engine = MagicMock() - engine.parse_init_info.return_value = "typed_init" - worker = self._make_worker(engine=engine) - - init_info = {"master_address": "127.0.0.1", "master_port": 12345} - worker.init_weight_transfer_engine(init_info) - - engine.parse_init_info.assert_called_once_with(init_info) - engine.init_transfer_engine.assert_called_once_with("typed_init") - - @patch("vllm.model_executor.model_loader.reload.initialize_layerwise_reload") - @patch.dict("os.environ", {"VLLM_ASCEND_ENABLE_NZ": "0"}) - def test_start_weight_update_checkpoint_format(self, mock_init_reload): - engine = MagicMock() - worker = self._make_worker(engine=engine) - - worker.start_weight_update(is_checkpoint_format=True) - - mock_init_reload.assert_called_once_with(worker.model_runner.model) - self.assertTrue(worker._weight_update_active) - self.assertTrue(worker._is_checkpoint_format) - - @patch("vllm.model_executor.model_loader.reload.initialize_layerwise_reload") - @patch.dict("os.environ", {"VLLM_ASCEND_ENABLE_NZ": "0"}) - def test_start_weight_update_kernel_format(self, mock_init_reload): - engine = MagicMock() - worker = self._make_worker(engine=engine) - - worker.start_weight_update(is_checkpoint_format=False) - - mock_init_reload.assert_not_called() - self.assertTrue(worker._weight_update_active) - self.assertFalse(worker._is_checkpoint_format) - - @patch.dict("os.environ", {"VLLM_ASCEND_ENABLE_NZ": "0"}) - def test_start_weight_update_rejects_reentry(self): - engine = MagicMock() - worker = self._make_worker(engine=engine) - worker._weight_update_active = True - - with self.assertRaises(RuntimeError): - worker.start_weight_update() - - @patch.dict("os.environ", {"VLLM_ASCEND_ENABLE_NZ": "1"}) - def test_start_weight_update_rejects_nz(self): - engine = MagicMock() - worker = self._make_worker(engine=engine) - - with self.assertRaises(ValueError): - worker.start_weight_update() - - def test_update_weights_requires_start(self): - engine = MagicMock() - worker = self._make_worker(engine=engine) - with self.assertRaises(RuntimeError): - worker.update_weights({"names": [], "dtype_names": [], "shapes": []}) - - @patch("torch.npu.synchronize", create=True) - @patch("vllm.model_executor.model_loader.reload.finalize_layerwise_reload") - @patch("vllm.model_executor.model_loader.reload.initialize_layerwise_reload") - @patch.dict("os.environ", {"VLLM_ASCEND_ENABLE_NZ": "0"}) - def test_update_weights_checkpoint_format(self, mock_init_reload, mock_finalize_reload, mock_sync): - engine = MagicMock() - worker = self._make_worker(engine=engine) - - engine.parse_update_info.return_value = "typed_update" - worker._weight_update_active = True - worker._is_checkpoint_format = True - - worker.update_weights({"foo": "bar"}) - - engine.parse_update_info.assert_called_once_with({"foo": "bar"}) - engine.receive_weights.assert_called_once() - _, kwargs = engine.receive_weights.call_args - self.assertIs(kwargs["load_weights"], worker.model_runner.model.load_weights) - mock_sync.assert_called_once() - - # reload lifecycle is split across start_weight_update / finish_weight_update - mock_init_reload.assert_not_called() - mock_finalize_reload.assert_not_called() - - @patch("torch.npu.synchronize", create=True) - @patch.dict("os.environ", {"VLLM_ASCEND_ENABLE_NZ": "0"}) - def test_update_weights_kernel_format(self, mock_sync): - engine = MagicMock() - - def fake_receive(update_info, load_weights): - load_weights([("layer.weight", torch.zeros(2))]) - - engine.receive_weights.side_effect = fake_receive - worker = self._make_worker(engine=engine) - param = torch.nn.Parameter(torch.ones(2), requires_grad=True) - worker.model_runner.model.get_parameter.return_value = param - - engine.parse_update_info.return_value = "typed_update" - worker._weight_update_active = True - worker._is_checkpoint_format = False - - worker.update_weights({"foo": "bar"}) - - worker.model_runner.model.get_parameter.assert_called_once_with("layer.weight") - torch.testing.assert_close(param.detach(), torch.zeros(2)) - self.assertTrue(param.requires_grad) - - @patch("vllm.model_executor.model_loader.reload.finalize_layerwise_reload") - def test_finish_weight_update_resets_state(self, mock_finalize_reload): - engine = MagicMock() - worker = self._make_worker(engine=engine) - worker._weight_update_active = True - worker._is_checkpoint_format = True - - worker.finish_weight_update() - - mock_finalize_reload.assert_called_once_with(worker.model_runner.model, worker.model_config) - self.assertFalse(worker._weight_update_active) - self.assertTrue(worker._is_checkpoint_format) - - def test_finish_without_start_raises(self): - engine = MagicMock() - worker = self._make_worker(engine=engine) - - with self.assertRaises(RuntimeError): - worker.finish_weight_update() - - def test_double_finish_raises(self): - engine = MagicMock() - worker = self._make_worker(engine=engine) - worker._weight_update_active = True - worker._is_checkpoint_format = False - - worker.finish_weight_update() - - with self.assertRaises(RuntimeError): - worker.finish_weight_update() - - @patch("torch.npu.synchronize", create=True) - def test_update_after_finish_requires_restart(self, _mock_sync): - engine = MagicMock() - engine.parse_update_info.return_value = "typed" - worker = self._make_worker(engine=engine) - worker._weight_update_active = True - worker._is_checkpoint_format = False - worker.finish_weight_update() - - with self.assertRaises(RuntimeError): - worker.update_weights({"names": [], "dtype_names": [], "shapes": []}) - - @patch("vllm.distributed.kv_transfer.ensure_kv_transfer_shutdown", create=True) - def test_shutdown_releases_engine(self, _mock_kv_shutdown): - engine = MagicMock() - worker = self._make_worker(engine=engine) - worker.profiler = None - - worker.shutdown() - - engine.shutdown.assert_called_once() diff --git a/vllm_ascend/_310p/attention/attention_mask.py b/vllm_ascend/_310p/attention/attention_mask.py index 5e68eb66d..a9063068a 100644 --- a/vllm_ascend/_310p/attention/attention_mask.py +++ b/vllm_ascend/_310p/attention/attention_mask.py @@ -21,17 +21,9 @@ from vllm_ascend.attention.attention_v1 import AscendMetadata from vllm_ascend.utils import ACL_FORMAT_FRACTAL_NZ, nd_to_nz_2d, nd_to_nz_spec -COMPRESSED_MASK_SEQ_LEN = 2048 -PAGED_ATTENTION_COMPRESSED_MASK_VALUE = -10000.0 - - -def is_compressed_mask_supported() -> bool: - return hasattr(torch_npu, "_npu_flash_attention_v3") and hasattr(torch_npu, "_npu_paged_attention_splitfuse_v2") - class AttentionMaskBuilder310: chunked_prefill_attn_mask = None - compressed_chunked_prefill_attn_mask = None max_seqlen = 16384 def __init__(self, device: torch.device, max_seqlen: int): @@ -45,7 +37,6 @@ def __init__(self, device: torch.device, max_seqlen: int): AttentionMaskBuilder310.max_seqlen = max_seqlen self.causal_attn_mask_cache = None self.non_causal_attn_mask_cache = None - self.support_compressed_mask = is_compressed_mask_supported() self.device = device @staticmethod @@ -98,33 +89,12 @@ def get_splitfuse_mask(cls, attn_metadata: AscendMetadata, device: torch.device) splitfuse_mask_nz = torch_npu.npu_format_cast(nd_to_nz_spec(splitfuse_mask).contiguous(), ACL_FORMAT_FRACTAL_NZ) return splitfuse_mask_nz - @classmethod - def get_compressed_splitfuse_mask(cls, device: torch.device): - """ - Generates the fixed ND attention mask for compressed SplitFuse PA. - - Returns: - torch.Tensor: A [2048, 2048] float16 ND mask on the target device. - """ - if ( - cls.compressed_chunked_prefill_attn_mask is None - or cls.compressed_chunked_prefill_attn_mask.device != device - ): - mask = torch.ones( - size=(COMPRESSED_MASK_SEQ_LEN, COMPRESSED_MASK_SEQ_LEN), - dtype=torch.float16, - device=device, - ) - mask = torch.triu(mask, diagonal=1) - cls.compressed_chunked_prefill_attn_mask = mask.mul_(PAGED_ATTENTION_COMPRESSED_MASK_VALUE) - return cls.compressed_chunked_prefill_attn_mask - def get_attention_mask(self, causal: bool, model_config) -> torch.Tensor: """ Retrieves the appropriate attention mask based on the model configuration. - When compressed mask is supported, the mask is generated as a fixed - [2048, 2048] logical mask and converted to 4D FRACTAL_NZ. + It explicitly checks for 'pooling' runner types which are not supported + on 310P hardware. Args: causal (bool): Whether to generate a causal mask. @@ -136,21 +106,23 @@ def get_attention_mask(self, causal: bool, model_config) -> torch.Tensor: Raises: NotImplementedError: If the runner_type is 'pooling'. """ - max_seq_len = COMPRESSED_MASK_SEQ_LEN if self.support_compressed_mask else self.max_seqlen if getattr(model_config, "runner_type", None) == "pooling": if causal: - return self._get_causal_mask(max_seq_len) + return self._get_causal_mask(self.max_seqlen) else: - return self._get_non_causal_mask(max_seq_len, model_config.dtype) + return self._get_non_causal_mask(self.max_seqlen, model_config.dtype) - return self._get_causal_mask(max_seq_len) + return self._get_causal_mask(self.max_seqlen) def _get_causal_mask(self, max_seq_len: int) -> torch.Tensor: """ Internal method to get or update the cached causal attention mask. - If the cache is empty, a new mask is generated and converted to the - NPU fractal format. + If the cache is empty or the requested length exceeds the cached length, + a new mask is generated and converted to the NPU fractal format. + + Args: + max_seq_len (int): The required sequence length. Returns: torch.Tensor: The cached causal mask in ACL_FORMAT_FRACTAL_NZ. @@ -164,8 +136,11 @@ def _get_non_causal_mask(self, max_seq_len: int, dtype: torch.dtype) -> torch.Te """ Internal method to get or update the cached non-causal attention mask. - If the cache is empty, a new mask is generated and converted to the - NPU fractal format. + If the cache is empty or the requested length exceeds the cached length, + a new mask is generated and converted to the NPU fractal format. + + Args: + max_seq_len (int): The required sequence length. Returns: torch.Tensor: The cached causal mask in ACL_FORMAT_FRACTAL_NZ. @@ -173,11 +148,7 @@ def _get_non_causal_mask(self, max_seq_len: int, dtype: torch.dtype) -> torch.Te if self.non_causal_attn_mask_cache is not None: return self.non_causal_attn_mask_cache - attention_mask_npu = torch.zeros( - size=(max_seq_len, max_seq_len), - dtype=dtype, - device=self.device, - ) + attention_mask_npu = torch.zeros(size=(max_seq_len, max_seq_len), dtype=dtype, device=self.device) attention_mask_npu = nd_to_nz_2d(attention_mask_npu) self.non_causal_attn_mask_cache = torch_npu.npu_format_cast( attention_mask_npu.contiguous(), ACL_FORMAT_FRACTAL_NZ diff --git a/vllm_ascend/_310p/attention/attention_v1.py b/vllm_ascend/_310p/attention/attention_v1.py index 4dff07b91..80261d75a 100644 --- a/vllm_ascend/_310p/attention/attention_v1.py +++ b/vllm_ascend/_310p/attention/attention_v1.py @@ -24,14 +24,8 @@ register_backend, ) -from vllm_ascend._310p.attention.attention_mask import ( - AttentionMaskBuilder310, - is_compressed_mask_supported, -) -from vllm_ascend._310p.attention.metadata_builder import ( - AscendAttentionMetadataBuilder310, - get_query_lens_cpu, -) +from vllm_ascend._310p.attention.attention_mask import AttentionMaskBuilder310 +from vllm_ascend._310p.attention.metadata_builder import AscendAttentionMetadataBuilder310 from vllm_ascend.attention.attention_v1 import ( AscendAttentionBackend, AscendAttentionBackendImpl, @@ -40,9 +34,6 @@ AscendMetadata, ) -MASK_TYPE_NORM_COMPRESS_SELF_ATTENTION = 3 -MASK_TYPE_NORM_COMPRESS_PAGED_ATTENTION = 5 - @register_backend(AttentionBackendEnum.CUSTOM, "ASCEND") class AscendAttentionBackend310(AscendAttentionBackend): @@ -105,64 +96,27 @@ class AscendAttentionBackendImpl310(AscendAttentionBackendImpl): optimized for the Ascend 310P architecture. """ - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.support_compressed_mask = is_compressed_mask_supported() - - def _flash_attention( + def _forward_encoder_attention( self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, - mask: torch.Tensor, - seq_len: torch.Tensor, + attn_metadata: AscendMetadata, output: torch.Tensor, ) -> torch.Tensor: - if not self.support_compressed_mask: - torch_npu._npu_flash_attention( - query=query, - key=key, - value=value, - mask=mask, - seq_len=seq_len, - scale_value=self.scale, - num_heads=self.num_heads, - num_kv_heads=self.num_kv_heads, - out=output, - ) - return output - - torch_npu._npu_flash_attention_v3( + torch_npu._npu_flash_attention( query=query, key=key, value=value, - mask=mask, - seq_len=seq_len, + mask=attn_metadata.attn_mask, + seq_len=attn_metadata.seq_lens, scale_value=self.scale, num_heads=self.num_heads, num_kv_heads=self.num_kv_heads, - mask_type=MASK_TYPE_NORM_COMPRESS_SELF_ATTENTION, out=output, ) return output - def _forward_encoder_attention( - self, - query: torch.Tensor, - key: torch.Tensor, - value: torch.Tensor, - attn_metadata: AscendMetadata, - output: torch.Tensor, - ) -> torch.Tensor: - return self._flash_attention( - query, - key, - value, - attn_metadata.attn_mask, - attn_metadata.seq_lens, - output, - ) - def forward_paged_attention( self, query: Any, @@ -230,7 +184,18 @@ def forward_prefill_310(self, query, key, value, attn_metadata, output): seq_len[-1] += delta mask = attn_metadata.attn_mask - return self._flash_attention(query, key, value, mask, seq_len, output) + torch_npu._npu_flash_attention( + query=query, + key=key, + value=value, + mask=mask, + seq_len=seq_len, + scale_value=self.scale, + num_heads=self.num_heads, + num_kv_heads=self.num_kv_heads, + out=output, + ) + return output def forward_chunked_prefill_310(self, query, attn_metadata, output): """ @@ -247,50 +212,21 @@ def forward_chunked_prefill_310(self, query, attn_metadata, output): """ num_actual_tokens = int(attn_metadata.num_actual_tokens) query = query[:num_actual_tokens] - output_slice = output[:num_actual_tokens] + output = output[:num_actual_tokens] - # Host qLens filled in AscendAttentionMetadataBuilder310.build(); eager fallback only. - qlens = get_query_lens_cpu(attn_metadata) - if qlens is None: - from vllm_ascend.ascend_forward_context import _EXTRA_CTX - - if _EXTRA_CTX.capturing: - raise RuntimeError( - "310P splitfuse requires attn_metadata.query_lens_cpu during graph capture; " - "ensure AscendAttentionMetadataBuilder310.build() ran before forward." - ) - qsl_cpu = attn_metadata.query_start_loc.cpu() - qlens = qsl_cpu[1:] - qsl_cpu[:-1] + # Calculate query lengths from start locations + qsl_cpu = attn_metadata.query_start_loc.cpu() + qlens = qsl_cpu[1:] - qsl_cpu[:-1] + context_lens = attn_metadata.seq_lens block_table = attn_metadata.block_tables - if attn_metadata.seq_lens.device != query.device: - attn_metadata.seq_lens = attn_metadata.seq_lens.to( - device=query.device, - non_blocking=True, - ) - - if self.support_compressed_mask: - # splitfuse_v2 requires fixed ND [2048, 2048]; parent build() may set FRACTAL_NZ mask. - mask = AttentionMaskBuilder310.get_compressed_splitfuse_mask(query.device) - torch_npu._npu_paged_attention_splitfuse_v2( - query=query, - key_cache=self.key_cache, - value_cache=self.value_cache, - mask=mask, - block_table=block_table, - seq_len=qlens, - context_lens=attn_metadata.seq_lens, - num_kv_heads=self.num_kv_heads, - num_heads=self.num_heads, - scale_value=self.scale, - mask_type=MASK_TYPE_NORM_COMPRESS_PAGED_ATTENTION, - out=output_slice, - ) - return output - # Generate the specific mask for splitfuse mask = AttentionMaskBuilder310.get_splitfuse_mask(attn_metadata, query.device) + + if context_lens.device != query.device: + context_lens = context_lens.to(query.device, non_blocking=True) + torch_npu._npu_paged_attention_splitfuse( query=query, key_cache=self.key_cache, @@ -298,11 +234,11 @@ def forward_chunked_prefill_310(self, query, attn_metadata, output): mask=mask, block_table=block_table, seq_len=qlens, - context_lens=attn_metadata.seq_lens, + context_lens=context_lens, num_kv_heads=self.num_kv_heads, num_heads=self.num_heads, scale_value=self.scale, - out=output_slice, + out=output, ) return output @@ -333,13 +269,13 @@ def forward_impl(self, query, key, value, kv_cache, attn_metadata, output): # Condition for DecodeOnly: Pure decoding phase where each request generates one token elif state == AscendAttentionState.DecodeOnly: output = self.forward_paged_attention(query, attn_metadata, output) - # ChunkedPrefill / PrefillCacheHit: chunked prefill or mixed batches. - # SpecDecoding: MTP uniform spec verify (splitfuse on 310P). - elif ( - state in [AscendAttentionState.ChunkedPrefill, AscendAttentionState.PrefillCacheHit] - or state == AscendAttentionState.SpecDecoding - ): + # Condition for ChunkedPrefill: + # 1. During speculative decoding scenarios (except mtp) + # 2. Processing large prefill requests in chunks + # Condition for PrefillCacheHit: Indicates prefill with some cached tokens already processed + elif state in [AscendAttentionState.ChunkedPrefill, AscendAttentionState.PrefillCacheHit]: output = self.forward_chunked_prefill_310(query, attn_metadata, output) + # Condition for SpecDecoding: Specified for mtp, which is not supported yet. else: raise NotImplementedError(f"AscendAttentionState: {state} is not supported for 310P currently.") return output diff --git a/vllm_ascend/_310p/attention/metadata_builder.py b/vllm_ascend/_310p/attention/metadata_builder.py index ea93f819e..64371c620 100644 --- a/vllm_ascend/_310p/attention/metadata_builder.py +++ b/vllm_ascend/_310p/attention/metadata_builder.py @@ -21,30 +21,8 @@ from vllm.config import VllmConfig from vllm.v1.kv_cache_interface import AttentionSpec -from vllm_ascend._310p.attention.attention_mask import ( - AttentionMaskBuilder310, - is_compressed_mask_supported, -) -from vllm_ascend.attention.attention_v1 import ( - AscendAttentionMetadataBuilder, - AscendAttentionState, - AscendMetadata, -) -from vllm_ascend.attention.utils import AscendCommonAttentionMetadata - -QUERY_LENS_CPU_ATTR = "query_lens_cpu" - - -def set_query_lens_cpu(attn_metadata: AscendMetadata, query_lens_cpu: torch.Tensor) -> None: - """Attach host qLens for ATB splitfuse without extending upstream AscendMetadata.""" - setattr(attn_metadata, QUERY_LENS_CPU_ATTR, query_lens_cpu) - - -def get_query_lens_cpu(attn_metadata: AscendMetadata) -> torch.Tensor | None: - value = getattr(attn_metadata, QUERY_LENS_CPU_ATTR, None) - if value is None: - return None - return value +from vllm_ascend._310p.attention.attention_mask import AttentionMaskBuilder310 +from vllm_ascend.attention.attention_v1 import AscendAttentionMetadataBuilder class AscendAttentionMetadataBuilder310(AscendAttentionMetadataBuilder): @@ -78,58 +56,3 @@ def __init__( # Override the mask builder with the 310P-specific version max_model_len = vllm_config.model_config.max_model_len self.attn_mask_builder: Any = AttentionMaskBuilder310(self.device, max_model_len) - - self._query_lens_cpu_buffer: torch.Tensor | None = None - if device.type != "cpu": - max_num_seqs = vllm_config.scheduler_config.max_num_seqs - self._query_lens_cpu_buffer = torch.empty(max_num_seqs, dtype=torch.int32, device="cpu", pin_memory=True) - - def _fill_query_lens_cpu( - self, - num_reqs: int, - query_start_loc_cpu: torch.Tensor, - ) -> torch.Tensor: - """Pinned CPU per-request query lengths for ATB splitfuse (host qLensTensor).""" - if self._query_lens_cpu_buffer is None: - return (query_start_loc_cpu[1 : num_reqs + 1] - query_start_loc_cpu[:num_reqs]).contiguous() - - buffer = self._query_lens_cpu_buffer - torch.sub( - query_start_loc_cpu[1 : num_reqs + 1], - query_start_loc_cpu[:num_reqs], - out=buffer[:num_reqs], - ) - return buffer[:num_reqs] - - def build( - self, - common_prefix_len: int, - common_attn_metadata: AscendCommonAttentionMetadata, - fast_build: bool = False, - ) -> AscendMetadata: - attn_metadata = super().build(common_prefix_len, common_attn_metadata, fast_build) - - num_reqs = common_attn_metadata.num_reqs - - splitfuse_states = ( - AscendAttentionState.SpecDecoding, - AscendAttentionState.ChunkedPrefill, - ) - if attn_metadata.attn_state not in splitfuse_states: - return attn_metadata - - query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu[: num_reqs + 1] - # ATB splitfuse qLensTensor must be host; filled here (outside graph forward). - set_query_lens_cpu( - attn_metadata, - self._fill_query_lens_cpu(num_reqs, query_start_loc_cpu), - ) - - # Bind device-side views for in-place graph replay updates. - attn_metadata.seq_lens = common_attn_metadata.seq_lens[:num_reqs] - attn_metadata.query_start_loc = common_attn_metadata.query_start_loc[: num_reqs + 1] - - if is_compressed_mask_supported(): - attn_metadata.attn_mask = AttentionMaskBuilder310.get_compressed_splitfuse_mask(self.device) - - return attn_metadata diff --git a/vllm_ascend/_310p/kv_block_zeroer.py b/vllm_ascend/_310p/kv_block_zeroer.py deleted file mode 100644 index dfbf987d5..000000000 --- a/vllm_ascend/_310p/kv_block_zeroer.py +++ /dev/null @@ -1,82 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. - -from collections.abc import Iterable -from typing import Any - -import torch -from vllm.v1.kv_cache_interface import FullAttentionSpec -from vllm.v1.worker.utils import AttentionGroup, KVBlockZeroer - - -class AscendKVBlockZeroer310(KVBlockZeroer): - """310P KV block zeroer without Triton. - - Atlas 300I DUO does not support Triton. For MTP >= 2 hybrid models, newly - allocated attention KV blocks must be zeroed via direct tensor writes. - """ - - def __init__(self, device: torch.device, pin_memory: bool) -> None: - self.device = device - self.pin_memory = pin_memory - self._kv_tensors: list[torch.Tensor] = [] - self._logical_page_ratio: int = 1 - - def init_meta( - self, - attn_groups_iter: Iterable["AttentionGroup"], - kernel_block_sizes: list[list[int]], - cache_dtype: str, - runner_only_attn_layers: set[str], - static_forward_context: dict[str, Any], - ) -> None: - seen_ptrs: set[int] = set() - self._kv_tensors = [] - self._logical_page_ratio = 1 - - for group in attn_groups_iter: - spec = group.kv_cache_spec - if not isinstance(spec, FullAttentionSpec): - continue - if group.kv_cache_group_id >= len(kernel_block_sizes): - continue - kernel_bs = kernel_block_sizes[group.kv_cache_group_id][0] - ratio = spec.block_size // kernel_bs - if not self._kv_tensors: - self._logical_page_ratio = ratio - - for layer_name in group.layer_names: - if layer_name in runner_only_attn_layers: - continue - kv_tuple = static_forward_context[layer_name].kv_cache - assert len(kv_tuple) == 2, "K and V are not stored separately" - for kv in kv_tuple: - dp = kv.data_ptr() - if dp in seen_ptrs: - continue - seen_ptrs.add(dp) - self._kv_tensors.append(kv) - - def zero_block_ids(self, block_ids: list[int]) -> None: - if not block_ids or not self._kv_tensors: - return - - ratio = self._logical_page_ratio - for block_id in block_ids: - start = block_id * ratio - end = start + ratio - for kv in self._kv_tensors: - kv[start:end].zero_() diff --git a/vllm_ascend/_310p/model_runner_310p.py b/vllm_ascend/_310p/model_runner_310p.py index 39fd70fc0..a6a6124ae 100644 --- a/vllm_ascend/_310p/model_runner_310p.py +++ b/vllm_ascend/_310p/model_runner_310p.py @@ -39,14 +39,13 @@ MambaSpec, UniformTypeKVCacheSpecs, ) +from vllm.v1.sample.rejection_sampler import RejectionSampler from vllm.v1.spec_decode.metadata import SpecDecodeMetadata from vllm.v1.worker.cp_utils import get_total_cp_world_size from vllm_ascend._310p.block_table import MultiGroupBlockTable as MultiGroupBlockTable310 -from vllm_ascend._310p.kv_block_zeroer import AscendKVBlockZeroer310 from vllm_ascend._310p.npu_input_batch import NPUInputBatch310 as NPUInputBatch from vllm_ascend._310p.ops.rotary_embedding import prepare_mrope_cos_sin_slices_from_runner -from vllm_ascend._310p.sample.rejection_sampler import AscendRejectionSampler310 from vllm_ascend._310p.sample.sampler import AscendSampler310 from vllm_ascend.attention.attention_v1 import AscendAttentionState from vllm_ascend.spec_decode.utils import update_num_computed_tokens_for_batch_change @@ -58,20 +57,8 @@ class NPUModelRunner310(NPUModelRunner): - """ - 310P model runner with a distinct ACL graph capture/replay contract from 910B: - - - Capture: ACLGraphWrapper records the full forward inside ``torch.npu.graph``. - 310P attention calls NPU ops directly (paged / splitfuse), without mainline - ``full_graph_fia`` / ``full_graph_pa`` graph_task registration. - - Replay: refresh shared runner buffers (block_table, seq_lens, query_start_loc, - slot_mapping via CPU prepare + copy_to_gpu) so tensor addresses stay stable, - then ``aclgraph.replay()``. - """ - # Inherited from parent runner; annotated here to satisfy strict type checks. uniform_decode_query_len: int - _mtp_spec_dummy_capture: bool = False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -96,7 +83,7 @@ def __init__(self, *args, **kwargs): logger.info_once("Weight layout uses FRACTAL_NZ.") self.sampler = AscendSampler310() if getattr(self, "rejection_sampler", None) is not None: - self.rejection_sampler = AscendRejectionSampler310(self.sampler) + self.rejection_sampler = RejectionSampler(self.sampler) if self.speculative_config is not None and self.speculative_config.method == "ngram": # 310P ngram requires decode-only graph shapes to be built with q_len=1. # Keep dispatcher's internal query_len in sync to avoid key-init assert. @@ -150,18 +137,6 @@ def _determine_batch_execution_and_padding( if self.attn_state in (AscendAttentionState.ChunkedPrefill, AscendAttentionState.PrefillCacheHit): force_eager = True - # MTP graph replay is only valid for uniform spec-decode batches (q_len = 1 + K). - if ( - self.speculative_config is not None - and self.speculative_config.method == "mtp" - and ( - self.attn_state != AscendAttentionState.SpecDecoding - or max_num_scheduled_tokens != self.uniform_decode_query_len - or num_tokens != max_num_scheduled_tokens * num_reqs - ) - ): - force_eager = True - if force_uniform_decode is None and self.attn_state == AscendAttentionState.DecodeOnly: decode_query_len = _NGRAM_GRAPH_UNIFORM_DECODE_QUERY_LEN if ( @@ -186,13 +161,6 @@ def _determine_batch_execution_and_padding( num_encoder_reqs=num_encoder_reqs, ) - def _build_attention_metadata(self, *args: Any, **kwargs: Any): - # Parent dummy_run assigns ChunkedPrefill for non-MLA MTP (910B FIA graph). - # 310P must capture SpecDecoding + splitfuse for MTP uniform decode graphs. - if self._mtp_spec_dummy_capture: - self.attn_state = AscendAttentionState.SpecDecoding - return super()._build_attention_metadata(*args, **kwargs) - def _pad_query_start_loc_for_fia( self, num_tokens_padded: int, @@ -226,18 +194,6 @@ def _pad_query_start_loc_for_fia( self.query_start_loc.copy_to_gpu() return num_reqs_padded - def _build_attn_state(self, num_reqs, num_scheduled_tokens, num_valid_tokens): - attn_state = super()._build_attn_state(num_reqs, num_scheduled_tokens, num_valid_tokens) - if ( - self.speculative_config is not None - and self.speculative_config.method == "mtp" - and not np.all(self.input_batch.num_computed_tokens_cpu[:num_reqs] == 0) - and np.all(num_scheduled_tokens == self.uniform_decode_query_len) - ): - attn_state = AscendAttentionState.SpecDecoding - self.attn_state = attn_state - return attn_state - def _prepare_inputs( # type: ignore[override] self, scheduler_output: SchedulerOutput, @@ -596,33 +552,22 @@ def _dummy_run( profile_seq_lens: int | None = None, ): temporary_context = self.temporary_modify_uniform_decode_query_len() if uniform_decode else nullcontext() - mtp_spec_dummy_capture = ( - uniform_decode - and not is_profile - and self.speculative_config is not None - and self.speculative_config.method == "mtp" - and not self.vllm_config.model_config.use_mla - ) with temporary_context: - self._mtp_spec_dummy_capture = mtp_spec_dummy_capture - try: - return super()._dummy_run( - num_tokens=num_tokens, - with_prefill=with_prefill, - cudagraph_runtime_mode=cudagraph_runtime_mode, - force_attention=force_attention, - uniform_decode=uniform_decode, - is_profile=is_profile, - create_mixed_batch=create_mixed_batch, - allow_microbatching=allow_microbatching, - skip_eplb=skip_eplb, - remove_lora=remove_lora, - is_graph_capturing=is_graph_capturing, - num_active_loras=num_active_loras, - profile_seq_lens=profile_seq_lens, - ) - finally: - self._mtp_spec_dummy_capture = False + return super()._dummy_run( + num_tokens=num_tokens, + with_prefill=with_prefill, + cudagraph_runtime_mode=cudagraph_runtime_mode, + force_attention=force_attention, + uniform_decode=uniform_decode, + is_profile=is_profile, + create_mixed_batch=create_mixed_batch, + allow_microbatching=allow_microbatching, + skip_eplb=skip_eplb, + remove_lora=remove_lora, + is_graph_capturing=is_graph_capturing, + num_active_loras=num_active_loras, + profile_seq_lens=profile_seq_lens, + ) def _model_forward( self, @@ -649,24 +594,12 @@ def _check_and_update_cudagraph_mode( self, attention_backends, kv_cache_groups, - is_profiling=False, ) -> None: # 910B does not need this branch because runner/dispatcher query_len are # naturally consistent there. 310P ngram needs temporary alignment. with self.temporary_modify_uniform_decode_query_len(): super()._check_and_update_cudagraph_mode(attention_backends, kv_cache_groups) - def _init_kv_zero_meta(self) -> None: - """310P uses torch zeroing because Triton is not available.""" - self._kv_block_zeroer = AscendKVBlockZeroer310(self.device, self.pin_memory) - self._kv_block_zeroer.init_meta( - attn_groups_iter=self._kv_cache_spec_attn_group_iterator(), - kernel_block_sizes=self.kernel_block_sizes, - cache_dtype=self.cache_config.cache_dtype, - runner_only_attn_layers=self.runner_only_attn_layers, - static_forward_context=(self.compilation_config.static_forward_context), - ) - def initialize_kv_cache_tensors(self, kv_cache_config: KVCacheConfig) -> dict[str, torch.Tensor]: """ Override the base class method. @@ -832,7 +765,7 @@ def _prepare_input_ids( prev_common_req_indices.append(prev_index) draft_len = len(scheduled_spec_tokens.get(req_id, ())) total_num_spec_tokens += draft_len - flattened_index = int(cu_num_tokens[cur_index]) - 1 + flattened_index = cu_num_tokens[cur_index].item() - 1 sample_flattened_indices.append(flattened_index - draft_len) spec_flattened_indices.extend(range(flattened_index - draft_len + 1, flattened_index + 1)) start = prev_index * self.num_spec_tokens diff --git a/vllm_ascend/_310p/ops/fla/gdn_310.py b/vllm_ascend/_310p/ops/fla/gdn_310.py index f4bad1687..6a4547b9f 100644 --- a/vllm_ascend/_310p/ops/fla/gdn_310.py +++ b/vllm_ascend/_310p/ops/fla/gdn_310.py @@ -20,95 +20,22 @@ import torch import torch.nn.functional as F from vllm.forward_context import get_forward_context -from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention from vllm.v1.attention.backend import AttentionMetadata # type: ignore + +from vllm_ascend.utils import enable_sp, vllm_version_is + +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers.mamba.gdn_linear_attn import ( # type: ignore[import-not-found] + GatedDeltaNetAttention, + ) +else: + from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata from vllm.v1.attention.backends.utils import PAD_SLOT_ID from vllm_ascend._310p.ops.fla.chunk_gated_delta_rule import chunk_gated_delta_rule_pytorch from vllm_ascend._310p.ops.fla.fused_gdn_gating import fused_gdn_gating_pytorch -from vllm_ascend.ascend_forward_context import _EXTRA_CTX from vllm_ascend.attention.utils import maybe_save_kv_layer_to_connector -from vllm_ascend.compilation.acl_graph import get_draft_graph_params, get_graph_params -from vllm_ascend.utils import enable_sp, weak_ref_tensors - -_CONV1D_310_OP_BACKEND = "310" -_CONV1D_310_BUFFER_REPLAY = "buffer_replay" - - -def _copy_host_tuple_to_int64_buffer( - buffer: torch.Tensor, - host_tuple: tuple[int, ...], -) -> None: - if not host_tuple: - return - num_elements = len(host_tuple) - cpu_values = torch.tensor(host_tuple, dtype=torch.int64, device="cpu", pin_memory=buffer.is_pinned()) - buffer[:num_elements].copy_(cpu_values, non_blocking=True) - - -def _as_int64_device_view(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype == torch.int64: - return tensor - return tensor.to(torch.int64) - - -def _get_spec_causal_conv1d_device_args( - attn_metadata: GDNAttentionMetadata, -) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: - num_spec_decodes = attn_metadata.num_spec_decodes - query_start_loc_buf = _as_int64_device_view(attn_metadata.spec_query_start_loc) - cache_indices_buf = _as_int64_device_view(attn_metadata.spec_state_indices_tensor[:, 0]) - num_accepted_buf = _as_int64_device_view(attn_metadata.num_accepted_tokens) - return ( - query_start_loc_buf[: num_spec_decodes + 1], - cache_indices_buf[:num_spec_decodes], - num_accepted_buf[:num_spec_decodes], - query_start_loc_buf, - cache_indices_buf, - num_accepted_buf, - ) - - -def _register_310_conv1d_buffer_replay( - graph_params, - num_actual_tokens: int, - *, - mixed_qkv, - conv_weights, - conv_state, - bias, - activation_num: int, - run_mode: int, - branch: str, - layer_prefix: str, - qsl_dev: torch.Tensor, - cidx_dev: torch.Tensor, - nat_dev: torch.Tensor | None, - q_per_seq: int, -) -> None: - graph_params.conv1d_params[num_actual_tokens].append( - ( - None, - weak_ref_tensors(mixed_qkv), - weak_ref_tensors(conv_weights), - weak_ref_tensors(conv_state), - bias, - activation_num, - PAD_SLOT_ID, - run_mode, - branch, - layer_prefix, - weak_ref_tensors(qsl_dev), - weak_ref_tensors(cidx_dev), - weak_ref_tensors(nat_dev) if nat_dev is not None else None, - q_per_seq, - _CONV1D_310_OP_BACKEND, - _CONV1D_310_BUFFER_REPLAY, - ) - ) - graph_params.conv1d_handles[num_actual_tokens].append(None) - graph_params.conv1d_events[num_actual_tokens].append(None) def _l2norm(x: torch.Tensor, eps: float = 1e-6) -> torch.Tensor: @@ -123,29 +50,15 @@ def _flatten_state_indices( if ssm_state_indices.ndim == 1: return ssm_state_indices[:total_tokens].to(torch.int32).contiguous() - num_seqs = (cu_seqlens[1:] - cu_seqlens[:-1]).shape[0] - seq_lens = cu_seqlens[1 : num_seqs + 1] - cu_seqlens[:num_seqs] - ssm_state_indices = ssm_state_indices[:num_seqs] - - # Uniform spec-decode ACL graph uses fixed q_len per request; reshape avoids - # NPU masked_select which breaks stream capture (aclnnMaskedSelect / 107027). - if _EXTRA_CTX.capturing or (seq_lens.numel() > 0 and torch.all(seq_lens == seq_lens[0])): - q_per_seq = ssm_state_indices.shape[1] - flat = ssm_state_indices[:, :q_per_seq].reshape(-1) - return flat[:total_tokens].to(torch.int32).contiguous() - - # Eager mixed batches with variable seq_lens: compact on CPU, copy back async. - ssm_cpu = ssm_state_indices.cpu() - seq_lens_cpu = seq_lens.cpu() - q_per_seq = ssm_cpu.shape[1] - positions = torch.arange(q_per_seq) - valid = positions.unsqueeze(0) < seq_lens_cpu.unsqueeze(1) - flat_cpu = ssm_cpu.masked_select(valid).to(torch.int32).contiguous()[:total_tokens] - if not flat_cpu.is_pinned: - flat_cpu = flat_cpu.pin_memory() - flat_dev = torch.empty(flat_cpu.numel(), dtype=torch.int32, device=ssm_state_indices.device) - flat_dev.copy_(flat_cpu, non_blocking=True) - return flat_dev.contiguous() + seq_lens = cu_seqlens[1:] - cu_seqlens[:-1] + ssm_state_indices = ssm_state_indices[: seq_lens.shape[0]] + positions = torch.arange( + ssm_state_indices.shape[1], + device=ssm_state_indices.device, + dtype=seq_lens.dtype, + ) + valid = positions.unsqueeze(0) < seq_lens.unsqueeze(1) + return ssm_state_indices.masked_select(valid)[:total_tokens].to(torch.int32).contiguous() def npu_recurrent_gated_delta_rule_310( @@ -195,33 +108,6 @@ def _310p_get_state_dtype(self) -> tuple[torch.dtype, torch.dtype]: _original_get_state_dtype = GatedDeltaNetAttention.get_state_dtype -def _merge_spec_and_non_spec_outputs_310( - core_attn_out: torch.Tensor, - num_actual_tokens: int, - spec_token_indx: torch.Tensor, - non_spec_token_indx: torch.Tensor, - core_attn_out_spec: torch.Tensor, - core_attn_out_non_spec: torch.Tensor, -) -> None: - """Merge spec/non-spec GDN outputs back into the batch layout. - - Avoid NPU ``index_copy_`` (IndexPutV2) which fails on some layouts; use - direct indexing instead. Validate lengths so mixed prefill+spec batches - do not pass mismatched tensors from spec ops. - """ - spec_out = core_attn_out_spec.squeeze(0) - non_spec_out = core_attn_out_non_spec.squeeze(0) - n_spec = spec_token_indx.numel() - n_non_spec = non_spec_token_indx.numel() - if spec_out.shape[0] != n_spec: - raise RuntimeError(f"GDN spec output length {spec_out.shape[0]} != spec_token_indx {n_spec}") - if non_spec_out.shape[0] != n_non_spec: - raise RuntimeError(f"GDN non-spec output length {non_spec_out.shape[0]} != non_spec_token_indx {n_non_spec}") - out = core_attn_out[:num_actual_tokens] - out[spec_token_indx] = spec_out - out[non_spec_token_indx] = non_spec_out - - class AscendGatedDeltaNetAttention310(GatedDeltaNetAttention): get_state_dtype = _310p_get_state_dtype @@ -285,59 +171,19 @@ def _forward_core( # 1.1: Process the multi-query part if spec_sequence_masks is not None: - # Align with spec sub-batch only (mixed prefill+spec has fewer spec decodes - # than total requests; full-batch tensor fails tiling / wrong state offset). - spec_num_accepted = num_accepted_tokens[: attn_metadata.num_spec_decodes].to(torch.int64) - uniform_spec_only = attn_metadata.num_prefills == 0 and attn_metadata.num_decodes == 0 - if _EXTRA_CTX.capturing and uniform_spec_only: - qsl_dev, cidx_dev, nat_dev, qsl_buf, cidx_buf, nat_buf = _get_spec_causal_conv1d_device_args( - attn_metadata - ) - spec_q_per_seq = int(attn_metadata.spec_state_indices_tensor.size(-1)) - graph_params = get_draft_graph_params() if _EXTRA_CTX.is_draft_model else get_graph_params() - _register_310_conv1d_buffer_replay( - graph_params, - num_actual_tokens, - mixed_qkv=mixed_qkv_spec, - conv_weights=conv_weights, - conv_state=conv_state, - bias=self.conv1d.bias, - activation_num=activation_num, - run_mode=1, - branch="spec", - layer_prefix=self.prefix, - qsl_dev=qsl_buf, - cidx_dev=cidx_buf, - nat_dev=nat_buf, - q_per_seq=spec_q_per_seq, - ) - mixed_qkv_spec = torch.ops._C_ascend.npu_causal_conv1d_310( - mixed_qkv_spec, - conv_weights, - bias=self.conv1d.bias, - conv_states=conv_state, - query_start_loc=qsl_dev, - cache_indices=cidx_dev, - initial_state_mode=None, - num_accepted_tokens=nat_dev, - activation_mode=activation_num, - pad_slot_id=PAD_SLOT_ID, - run_mode=1, - ) - else: - mixed_qkv_spec = torch.ops._C_ascend.npu_causal_conv1d_310( - mixed_qkv_spec, - conv_weights, - bias=self.conv1d.bias, - conv_states=conv_state, - query_start_loc=spec_query_start_loc.to(torch.int64), - cache_indices=spec_state_indices_tensor[:, 0][: attn_metadata.num_spec_decodes].to(torch.int64), - initial_state_mode=None, - num_accepted_tokens=spec_num_accepted, - activation_mode=activation_num, - pad_slot_id=PAD_SLOT_ID, - run_mode=1, - ) + mixed_qkv_spec = torch.ops._C_ascend.npu_causal_conv1d_310( + mixed_qkv_spec, + conv_weights, + bias=self.conv1d.bias, + conv_states=conv_state, + query_start_loc=spec_query_start_loc.to(torch.int64), + cache_indices=spec_state_indices_tensor[:, 0][: attn_metadata.num_spec_decodes].to(torch.int64), + initial_state_mode=None, + num_accepted_tokens=num_accepted_tokens.to(torch.int64), + activation_mode=activation_num, + pad_slot_id=PAD_SLOT_ID, + run_mode=1, + ) # 1.2: Process the remaining part if attn_metadata.num_prefills > 0: @@ -406,7 +252,7 @@ def _forward_core( state=ssm_state, cu_seqlens=spec_query_start_loc[: attn_metadata.num_spec_decodes + 1], ssm_state_indices=spec_state_indices_tensor, - num_accepted_tokens=spec_num_accepted, + num_accepted_tokens=num_accepted_tokens, use_qk_l2norm_in_kernel=True, ) else: @@ -463,14 +309,17 @@ def _forward_core( ) # 3. Merge core attention output if spec_sequence_masks is not None and core_attn_out_non_spec is not None: - _merge_spec_and_non_spec_outputs_310( - core_attn_out, - num_actual_tokens, - spec_token_indx, - non_spec_token_indx, - core_attn_out_spec, - core_attn_out_non_spec, + merged_out = torch.empty( + (1, num_actual_tokens, *core_attn_out_spec.shape[2:]), + dtype=core_attn_out_non_spec.dtype, + device=core_attn_out_non_spec.device, ) + merged_out.index_copy_(1, spec_token_indx, core_attn_out_spec) + merged_out.index_copy_(1, non_spec_token_indx, core_attn_out_non_spec) + if not enable_sp(): + core_attn_out[:num_actual_tokens] = merged_out.squeeze(0) + else: + core_attn_out[:num_actual_tokens] = merged_out.squeeze(0)[:num_actual_tokens] elif spec_sequence_masks is not None: if not enable_sp(): core_attn_out[:num_actual_tokens] = core_attn_out_spec.squeeze(0) @@ -482,95 +331,3 @@ def _forward_core( else: core_attn_out[:num_actual_tokens] = core_attn_out_non_spec.squeeze(0)[:num_actual_tokens] maybe_save_kv_layer_to_connector("", []) - - -def _get_spec_causal_conv1d_update_host_args_310p( - attn_metadata: GDNAttentionMetadata, -) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]: - """Build spec conv1d host args from GDN metadata without patch_gdn_attn.""" - from vllm_ascend.ops.gdn import to_int64_tuple - - num_spec_decodes = attn_metadata.num_spec_decodes - return ( - to_int64_tuple(attn_metadata.spec_query_start_loc[: num_spec_decodes + 1]), - to_int64_tuple(attn_metadata.spec_state_indices_tensor[:, 0][:num_spec_decodes]), - to_int64_tuple(attn_metadata.num_accepted_tokens[:num_spec_decodes]), - ) - - -def update_conv1d_graph_params_310p( - update_stream, - forward_context, - num_tokens, - vllm_config, - is_draft_model=False, - draft_attn_metadatas=None, -): - """310P uniform spec-decode GDN conv1d graph replay via device buffer updates.""" - from vllm_ascend.ops.gdn import _pad_conv1d_host_args_to_capture - - graph_params = get_draft_graph_params() if is_draft_model else get_graph_params() - - if ( - graph_params is None - or num_tokens not in graph_params.conv1d_params - or len(graph_params.conv1d_params[num_tokens]) == 0 - ): - return - - attn_metadata = forward_context.attn_metadata - if is_draft_model and draft_attn_metadatas is not None: - attn_metadata = draft_attn_metadatas - - with torch.npu.stream(update_stream): - for param in graph_params.conv1d_params[num_tokens]: - param_list = list(param) - if len(param_list) < 16: - continue - op_backend = param_list[14] - replay_mode = param_list[15] - if op_backend != _CONV1D_310_OP_BACKEND or replay_mode != _CONV1D_310_BUFFER_REPLAY: - continue - - ( - _output, - mixed_qkv, - _conv_weights, - _conv_state, - _bias, - _activation_num, - _pad_slot_id, - run_mode, - branch, - layer_prefix, - qsl_dev, - cidx_dev, - nat_dev, - q_per_seq, - ) = param_list[:14] - - if run_mode != 1 or branch != "spec" or attn_metadata is None: - continue - - meta = attn_metadata - if isinstance(meta, dict): - meta = meta.get(layer_prefix, None) - if not isinstance(meta, GDNAttentionMetadata): - continue - if meta.spec_sequence_masks is None: - continue - - cap_x_dim0 = int(mixed_qkv.size(0)) - qsl_host, cidx_host, num_accepted_host = _get_spec_causal_conv1d_update_host_args_310p(meta) - new_query_start_loc, new_cache_indices, new_num_accepted = _pad_conv1d_host_args_to_capture( - qsl_host, - cidx_host, - num_accepted_host, - cap_x_dim0=cap_x_dim0, - q_per_seq=q_per_seq, - with_num_accepted=True, - ) - _copy_host_tuple_to_int64_buffer(qsl_dev, new_query_start_loc) - _copy_host_tuple_to_int64_buffer(cidx_dev, new_cache_indices) - if nat_dev is not None: - _copy_host_tuple_to_int64_buffer(nat_dev, new_num_accepted) diff --git a/vllm_ascend/_310p/sample/rejection_sampler.py b/vllm_ascend/_310p/sample/rejection_sampler.py deleted file mode 100644 index 076edcea8..000000000 --- a/vllm_ascend/_310p/sample/rejection_sampler.py +++ /dev/null @@ -1,111 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# - -from contextlib import contextmanager - -import torch -from vllm.v1.outputs import SamplerOutput -from vllm.v1.sample.metadata import SamplingMetadata -from vllm.v1.spec_decode.metadata import SpecDecodeMetadata - -import vllm_ascend.sample.rejection_sampler as rejection_sampler_module -from vllm_ascend._310p.sample.sampler import fill_exponential_310p -from vllm_ascend.sample.rejection_sampler import ( - AscendRejectionSampler, - sample_recovered_tokens_blockwise_pytorch, - sample_recovered_tokens_pytorch, -) - - -@contextmanager -def _bind_sample_recovered_tokens(fn): - original = rejection_sampler_module.sample_recovered_tokens - rejection_sampler_module.sample_recovered_tokens = fn - try: - yield - finally: - rejection_sampler_module.sample_recovered_tokens = original - - -class AscendRejectionSampler310(AscendRejectionSampler): - """310P rejection sampler: PyTorch recovered-token path with CPU RNG (no Triton).""" - - def forward( - self, - metadata: SpecDecodeMetadata, - draft_probs: torch.Tensor | None, - logits: torch.Tensor, - sampling_metadata: SamplingMetadata, - ) -> SamplerOutput: - with _bind_sample_recovered_tokens(self.sample_recovered_tokens): - return super().forward(metadata, draft_probs, logits, sampling_metadata) - - def sample_recovered_tokens( - self, - max_spec_len: int, - num_draft_tokens: list[int], - cu_num_draft_tokens: torch.Tensor, - draft_token_ids: torch.Tensor, - draft_probs: torch.Tensor | None, - target_probs: torch.Tensor, - sampling_metadata: SamplingMetadata, - device: torch.device, - use_block_verify: bool = False, - target_indices: torch.Tensor | None = None, - global_vocab_size: int | None = None, - enable_reduce_sampling: bool = False, - ) -> torch.Tensor: - batch_size = len(num_draft_tokens) - vocab_size = target_probs.shape[-1] - - q = torch.empty( - (batch_size, vocab_size), - dtype=torch.float32, - device=device, - ) - num_draft_tensor = torch.tensor(num_draft_tokens, pin_memory=True).to(device, non_blocking=True) - has_draft_mask = num_draft_tensor > 0 - fill_exponential_310p(q, sampling_metadata.generators, has_draft_mask) - - recovered_token_ids = torch.empty_like(draft_token_ids) - if use_block_verify: - sample_recovered_tokens_blockwise_pytorch( - recovered_token_ids, - cu_num_draft_tokens, - draft_token_ids, - draft_probs, - target_probs, - q, - vocab_size, - IS_NGRAM=draft_probs is None, - target_indices=target_indices, - enable_reduce_sampling=enable_reduce_sampling, - ) - else: - sample_recovered_tokens_pytorch( - recovered_token_ids, - cu_num_draft_tokens, - draft_token_ids, - draft_probs, - target_probs, - q, - vocab_size, - IS_NGRAM=draft_probs is None, - target_indices=target_indices, - enable_reduce_sampling=enable_reduce_sampling, - ) - return recovered_token_ids diff --git a/vllm_ascend/_310p/sample/sampler.py b/vllm_ascend/_310p/sample/sampler.py index c6dc139cf..a0e91497f 100644 --- a/vllm_ascend/_310p/sample/sampler.py +++ b/vllm_ascend/_310p/sample/sampler.py @@ -29,61 +29,30 @@ _CPU_GENERATOR_CACHE_310P: dict[int, tuple[torch.Generator, int]] = {} -def _get_cpu_generator_310p(i: int, generator: torch.Generator) -> torch.Generator: - cache_entry = _CPU_GENERATOR_CACHE_310P.get(i) - if cache_entry is None or cache_entry[1] != id(generator): - cpu_generator = torch.Generator(device="cpu") - try: - # Keep RNG stream consistent with the original generator. - cpu_generator.set_state(generator.get_state()) - except Exception: - cpu_generator.manual_seed(generator.initial_seed()) - cache_entry = (cpu_generator, id(generator)) - _CPU_GENERATOR_CACHE_310P[i] = cache_entry - return cache_entry[0] - - -def _fill_cpu_exponential_310p( - q_cpu: torch.Tensor, - generators: dict[int, torch.Generator], - has_draft_mask: torch.Tensor | None = None, -) -> None: - """Fill a CPU tensor with exponential values for 310P stability.""" - if len(generators) != q_cpu.shape[0]: - q_cpu.exponential_() - if not generators: - return - for i, generator in generators.items(): - cpu_gen = _get_cpu_generator_310p(i, generator) - if has_draft_mask is not None: - temp_q = torch.empty_like(q_cpu[i]) - temp_q.exponential_(generator=cpu_gen) - q_cpu[i] = torch.where(has_draft_mask[i], temp_q, q_cpu[i]) - else: - q_cpu[i].exponential_(generator=cpu_gen) - - -def fill_exponential_310p( - q: torch.Tensor, - generators: dict[int, torch.Generator], - has_draft_mask: torch.Tensor | None = None, -) -> None: - """Fill ``q`` with exponential values using CPU RNG for 310P stability.""" - with npu_stream_switch(global_stream()): - q_cpu = q.cpu() - _fill_cpu_exponential_310p(q_cpu, generators, has_draft_mask) - q.copy_(q_cpu.to(q.device)) - torch.npu.current_stream().wait_stream(global_stream()) - - def _random_sample_310p( probs: torch.Tensor, generators: dict[int, torch.Generator], ) -> torch.Tensor: """310P-specific random sampling with CPU exponential generation for q.""" with npu_stream_switch(global_stream()): - q = torch.empty_like(probs).cpu() - _fill_cpu_exponential_310p(q, generators) + q = torch.empty_like(probs) + q = q.cpu() + if len(generators) != q.shape[0]: + q.exponential_() + if generators: + for i, generator in generators.items(): + cache_entry = _CPU_GENERATOR_CACHE_310P.get(i) + if cache_entry is None or cache_entry[1] != id(generator): + cpu_generator = torch.Generator(device="cpu") + try: + # Keep RNG stream consistent with the original generator. + cpu_generator.set_state(generator.get_state()) + except Exception: + cpu_generator.manual_seed(generator.initial_seed()) + cache_entry = (cpu_generator, id(generator)) + _CPU_GENERATOR_CACHE_310P[i] = cache_entry + cpu_generator, _ = cache_entry + q[i].exponential_(generator=cpu_generator) q = q.npu() torch.npu.current_stream().wait_stream(global_stream()) return probs.div_(q).argmax(dim=-1).view(-1) diff --git a/vllm_ascend/_310p/spec_decode/__init__.py b/vllm_ascend/_310p/spec_decode/__init__.py deleted file mode 100644 index d9b463e5a..000000000 --- a/vllm_ascend/_310p/spec_decode/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# - -from vllm_ascend._310p.spec_decode.llm_base_proposer_310 import AscendSpecDecodeBaseProposer310 - -__all__ = [ - "AscendSpecDecodeBaseProposer310", -] diff --git a/vllm_ascend/_310p/spec_decode/llm_base_proposer_310.py b/vllm_ascend/_310p/spec_decode/llm_base_proposer_310.py deleted file mode 100644 index 15298ebd0..000000000 --- a/vllm_ascend/_310p/spec_decode/llm_base_proposer_310.py +++ /dev/null @@ -1,60 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# This file is a part of the vllm-ascend project. -# - -import numpy as np -import torch -from vllm.v1.worker.gpu_input_batch import InputBatch -from vllm.v1.worker.gpu_model_runner import CachedRequestState - -from vllm_ascend.spec_decode.llm_base_proposer import AscendSpecDecodeBaseProposer - - -class AscendSpecDecodeBaseProposer310(AscendSpecDecodeBaseProposer): - """310P proposer base: guard empty discard indices before NPU index_fill_.""" - - def prepare_next_token_ids_padded( - self, - sampled_token_ids: torch.Tensor, - requests: dict[str, CachedRequestState], - gpu_input_batch: InputBatch, - discard_request_indices: torch.Tensor, - num_discarded_requests: int, - ) -> tuple[torch.Tensor, torch.Tensor]: - num_reqs = gpu_input_batch.num_reqs - seq_lens_list = (gpu_input_batch.num_tokens_no_spec[:num_reqs] - 1).tolist() - self.backup_next_token_ids.np[:num_reqs] = np.array( - [requests[gpu_input_batch.req_ids[i]].get_token_id(seq_lens_list[i]) for i in range(num_reqs)] - ) - self.backup_next_token_ids.copy_to_gpu(num_reqs) - - discard_sampled_tokens_req_indices = discard_request_indices[:num_discarded_requests] - valid_sampled_token_ids_gpu = sampled_token_ids.clone() - if discard_sampled_tokens_req_indices.numel() != 0: - valid_sampled_token_ids_gpu.index_fill_(0, discard_sampled_tokens_req_indices, -1) - - valid_mask = (valid_sampled_token_ids_gpu != -1) & (valid_sampled_token_ids_gpu < gpu_input_batch.vocab_size) - valid_sampled_tokens_count = valid_mask.sum(dim=1) - last_valid_indices = valid_sampled_tokens_count - 1 - last_valid_indices_safe = torch.clamp(last_valid_indices, min=0) - selected_tokens = torch.gather(valid_sampled_token_ids_gpu, 1, last_valid_indices_safe.unsqueeze(1)).squeeze(1) - batch_size = valid_sampled_token_ids_gpu.shape[0] - next_token_ids = torch.where( - last_valid_indices != -1, - selected_tokens, - self.backup_next_token_ids.gpu[:batch_size], - ) - return next_token_ids, valid_sampled_tokens_count diff --git a/vllm_ascend/__init__.py b/vllm_ascend/__init__.py index 4c70552fa..3208a58e3 100644 --- a/vllm_ascend/__init__.py +++ b/vllm_ascend/__init__.py @@ -15,8 +15,6 @@ # This file is a part of the vllm-ascend project. # -import vllm_ascend.logger # noqa: F401 - _GLOBAL_PATCH_APPLIED = False @@ -47,10 +45,8 @@ def register_connector(): _ensure_global_patch() from vllm_ascend.distributed.kv_transfer import register_connector - from vllm_ascend.distributed.weight_transfer import register_engine register_connector() - register_engine() def register_model_loader(): diff --git a/vllm_ascend/ascend_config.py b/vllm_ascend/ascend_config.py index dfb405425..e393eae5b 100644 --- a/vllm_ascend/ascend_config.py +++ b/vllm_ascend/ascend_config.py @@ -94,13 +94,6 @@ def __init__(self, vllm_config: "VllmConfig"): # Dump / PrecisionDebugger configuration self.dump_config_path = self._resolve_dump_config_path(additional_config) - - # Log configuration - self.ascend_log_path = additional_config.get( - "ascend_log_path", - os.path.join(os.path.expanduser("~"), "ascend", "log", "vllm_ascend"), - ) - self.layer_sharding = additional_config.get("layer_sharding", None) if self.layer_sharding: logger.info_once( @@ -140,7 +133,10 @@ def __init__(self, vllm_config: "VllmConfig"): # PD-disaggregated only (kv_producer/kv_consumer); invalid in PD-mixed (kv_both / no kv_transfer_config). self.recompute_scheduler_enable = additional_config.get("recompute_scheduler_enable", False) self.enable_cpu_binding = additional_config.get("enable_cpu_binding", True) - self.multistream_dsv4_dsa_overlap = additional_config.get("multistream_dsv4_dsa_overlap", True) + self.multistream_dsa_preprocess = additional_config.get("multistream_dsa_preprocess", False) + self.multistream_dsv4_dsa_overlap = additional_config.get("multistream_dsv4_dsa_overlap", False) + self.prefill_comm_compute_overlap = additional_config.get("prefill_comm_compute_overlap", False) + self.enable_matmul_allreduce = self._get_config_value( additional_config, "enable_matmul_allreduce", @@ -181,11 +177,7 @@ def __init__(self, vllm_config: "VllmConfig"): self.pd_tp_ratio = 1 self.pd_head_ratio = 1 self.num_head_replica = 1 - if ( - vllm_config.kv_transfer_config is not None - and vllm_config.model_config is not None - and not vllm_config.model_config.is_deepseek_mla - ): + if vllm_config.kv_transfer_config is not None and not vllm_config.model_config.is_deepseek_mla: prefill_tp_size = vllm_config.kv_transfer_config.get_from_extra_config("prefill", {"tp_size": 1})["tp_size"] decode_tp_size = vllm_config.kv_transfer_config.get_from_extra_config("decode", {"tp_size": 1})["tp_size"] assert prefill_tp_size % decode_tp_size == 0, "Prefill TP size must be divisible by Decode TP size." @@ -233,16 +225,12 @@ def __init__(self, vllm_config: "VllmConfig"): bool(additional_config.get("enable_async_exponential", False)) and not envs.VLLM_BATCH_INVARIANT ) - use_sparse = ( - vllm_config.model_config is not None - and hasattr(vllm_config.model_config, "hf_text_config") - and hasattr(vllm_config.model_config.hf_text_config, "index_topk") + use_sparse = hasattr(vllm_config.model_config, "hf_text_config") and hasattr( + vllm_config.model_config.hf_text_config, "index_topk" ) self.enable_kv_nz = additional_config.get("enable_kv_nz", False) if self.enable_kv_nz: - if vllm_config.model_config is None: - raise RuntimeError("enable_kv_nz requires a valid model_config.") if not vllm_config.model_config.is_deepseek_mla or use_sparse: raise RuntimeError("enable_kv_nz is only supported for mla currently.") if vllm_config.kv_transfer_config is None or not vllm_config.kv_transfer_config.is_kv_consumer: @@ -251,7 +239,6 @@ def __init__(self, vllm_config: "VllmConfig"): ) self.enable_sparse_c8 = additional_config.get("enable_sparse_c8", False) and use_sparse - self.c8_enable_reshape_optim = self.enable_sparse_c8 and additional_config.get("c8_enable_reshape_optim", False) quant_config = getattr(vllm_config, "quant_config", None) self._sparse_c8_layer_ids, self._sparse_c8_layer_names = self._parse_sparse_c8_layers_from_quant_config( quant_config @@ -266,10 +253,6 @@ def __init__(self, vllm_config: "VllmConfig"): # Enable dispatch/combine op inter-node communication by ROCE self.enable_mc2_hierarchy_comm = additional_config.get("enable_mc2_hierarchy_comm", False) - # Whether to use NPU device group for DP metadata all_reduce. - # "True": use NPU device group, "False" (default): use CPU group. - self.dp_allreduce_on_npu = additional_config.get("dp_allreduce_on_npu", False) - # Enable optimized reduce sampling scheme self.enable_reduce_sample = additional_config.get("enable_reduce_sample", False) @@ -281,10 +264,6 @@ def __init__(self, vllm_config: "VllmConfig"): self.sparse_json = self.hamming_sparse["sparse_json_location"] self._check_enable_hamming_sparse() - # Enable Block Verify and Entropy Verify in Rejection Sampler - rejection_sampler_config = additional_config.get("rejection_sampler_config", {}) - self.rejection_sampler_config = RejectionSamplerConfig(rejection_sampler_config) - @staticmethod def _get_config_value(additional_config: dict[str, Any], config_key: str, env_key: str, env_value: Any) -> Any: if config_key in additional_config: @@ -447,7 +426,7 @@ def __init__(self, finegrained_tp_config: dict, vllm_config): enabled_configs.append(f"oproj_tensor_parallel_size={self.oproj_tensor_parallel_size}") # dummy_run does not run the entire attention module in eager mode, # so the o_proj tp split can only be used in graph mode. - if vllm_config.model_config and vllm_config.model_config.enforce_eager: + if vllm_config.model_config.enforce_eager: raise AssertionError("oproj_tensor_parallel_size is only supported in graph mode") if vllm_config.kv_transfer_config is None or not vllm_config.kv_transfer_config.is_kv_consumer: raise AssertionError( @@ -457,7 +436,7 @@ def __init__(self, finegrained_tp_config: dict, vllm_config): enabled_configs.append(f"olora_tensor_parallel_size={self.olora_tensor_parallel_size}") # dummy_run does not run the entire attention module in eager mode, # so the o_lora tp split can only be used in graph mode. - if vllm_config.model_config and vllm_config.model_config.enforce_eager: + if vllm_config.model_config.enforce_eager is True: raise AssertionError("olora_tensor_parallel_size is only supported in graph mode") if vllm_config.kv_transfer_config is None or not vllm_config.kv_transfer_config.is_kv_consumer: raise AssertionError( @@ -642,74 +621,6 @@ def _validate(self): raise ValueError(f"profiling_chunk_config.min_chunk must be positive, got {self.min_chunk}") -class RejectionSamplerConfig: - """Configuration for Block Verify and Entropy Verify in Rejection Sampler. - - Block Verify improves acceptance rate by evaluating all draft tokens - as a block using cumulative probability products. Entropy Verify - adjusts the acceptance threshold based on the entropy of the target - distribution, allowing higher acceptance for high-entropy (uncertain) - tokens and stricter rejection for low-entropy (confident) tokens. - - Usage (online):: - - vllm serve --additional-config \ - '{"rejection_sampler_config": {"enable_block_verify": true, \ - "enable_entropy_verify": true, "posterior_threshold": 0.95, \ - "posterior_alpha": 0.4}}' - - Usage (offline):: - - llm = LLM( - model, - additional_config={ - "rejection_sampler_config": { - "enable_block_verify": true, - "enable_entropy_verify": true, - "posterior_threshold": 0.95, - "posterior_alpha": 0.4, - } - }, - ) - """ - - def __init__(self, config: dict | None = None): - if config is None: - config = {} - self.enable_block_verify: bool = config.get("enable_block_verify", False) - self.enable_entropy_verify: bool = config.get("enable_entropy_verify", False) - self.posterior_threshold: float = config.get("posterior_threshold", 0.95) - self.posterior_alpha: float = config.get("posterior_alpha", 0.4) - self._validate() - - def _validate(self): - if not isinstance(self.enable_block_verify, bool): - raise ValueError( - f"rejection_sampler_config.enable_block_verify must be a bool, " - f"got {type(self.enable_block_verify).__name__}" - ) - if not isinstance(self.enable_entropy_verify, bool): - raise ValueError( - f"rejection_sampler_config.enable_entropy_verify must be a bool, " - f"got {type(self.enable_entropy_verify).__name__}" - ) - if not isinstance(self.posterior_threshold, (int, float)): - raise ValueError( - f"rejection_sampler_config.posterior_threshold must be a float, " - f"got {type(self.posterior_threshold).__name__}" - ) - if not isinstance(self.posterior_alpha, (int, float)): - raise ValueError( - f"rejection_sampler_config.posterior_alpha must be a float, got {type(self.posterior_alpha).__name__}" - ) - if not (0 < self.posterior_threshold <= 1): - raise ValueError( - f"rejection_sampler_config.posterior_threshold must be in (0, 1], got {self.posterior_threshold}" - ) - if self.posterior_alpha < 0: - raise ValueError(f"rejection_sampler_config.posterior_alpha must be >= 0, got {self.posterior_alpha}") - - class EplbConfig: """ Configuration Object for xlite_graph_config from additional_config @@ -793,12 +704,7 @@ def init_ascend_config(vllm_config): additional_config = vllm_config.additional_config if vllm_config.additional_config is not None else {} refresh = additional_config.get("refresh", False) if additional_config else False global _ASCEND_CONFIG - if ( - _ASCEND_CONFIG is not None - and not refresh - and _is_ascend_config_initialized(_ASCEND_CONFIG) - and getattr(_ASCEND_CONFIG, "vllm_config", None) is vllm_config - ): + if _ASCEND_CONFIG is not None and not refresh and _is_ascend_config_initialized(_ASCEND_CONFIG): return _ASCEND_CONFIG new_config = AscendConfig(vllm_config) if _is_ascend_config_initialized(new_config): diff --git a/vllm_ascend/attention/attention_v1.py b/vllm_ascend/attention/attention_v1.py index 53ad9be5a..f467acf94 100644 --- a/vllm_ascend/attention/attention_v1.py +++ b/vllm_ascend/attention/attention_v1.py @@ -103,7 +103,7 @@ def get_kv_cache_shape( block_size: int, num_kv_heads: int, head_size: int, - cache_dtype_str: str = "", + cache_type: str = "", ) -> tuple[int, ...]: return (2, num_blocks, block_size, num_kv_heads, head_size) diff --git a/vllm_ascend/attention/context_parallel/attention_cp.py b/vllm_ascend/attention/context_parallel/attention_cp.py index e5f24bc80..6fd3d5191 100644 --- a/vllm_ascend/attention/context_parallel/attention_cp.py +++ b/vllm_ascend/attention/context_parallel/attention_cp.py @@ -23,6 +23,8 @@ from vllm.config import VllmConfig from vllm.distributed import ( get_dcp_group, + get_decode_context_model_parallel_rank, + get_decode_context_model_parallel_world_size, get_pcp_group, ) from vllm.v1.attention.backend import AttentionCGSupport @@ -55,10 +57,6 @@ update_graph_params_workspaces, ) from vllm_ascend.device.device_op import DeviceOperator -from vllm_ascend.distributed.utils import ( - get_decode_context_model_parallel_rank, - get_decode_context_model_parallel_world_size, -) from vllm_ascend.utils import cp_chunkedprefill_comm_stream, weak_ref_tensors @@ -803,6 +801,7 @@ def reshape_and_cache( attn_metadata: AscendMetadata, output: torch.Tensor, ): + num_tokens = query.shape[0] num_decode_tokens = attn_metadata.num_decode_tokens has_decode = attn_metadata.num_decodes > 0 has_prefill = attn_metadata.num_prefills > 0 @@ -836,9 +835,8 @@ def reshape_and_cache( key, value = all_kv.split([self.head_size, self.head_size], dim=-1) else: query, key, value = self._gather_and_restore_pcp_qkv(query, key, value, attn_metadata) - output_local_padded_tokens_fa = ( - attn_metadata.num_actual_tokens_pcp_padded // self.pcp_size - output_padded.shape[0] - ) + num_actual_tokens_pcp_padded = attn_metadata.num_actual_tokens_pcp_padded + output_local_padded_tokens_fa = num_actual_tokens_pcp_padded // self.pcp_size - num_tokens if output_local_padded_tokens_fa > 0: output_padded = F.pad( output, pad=(0, 0, 0, 0, 0, output_local_padded_tokens_fa), mode="constant", value=0 @@ -879,10 +877,7 @@ def _gather_and_restore_pcp_qkv( [query.reshape(num_tokens, -1), key.reshape(num_tokens, -1), value.reshape(num_tokens, -1)], dim=-1, ) - # The hybrid linear partitioning may result in different data on two cards, so padding is required here. - real_num_tokens = attn_metadata.prefill.pcp_metadata.total_num_scheduled_tokens - qkv_fla = qkv_fla[:real_num_tokens] - if pcp_padded_tokens_fla > 0: + if num_tokens == attn_metadata.prefill.pcp_metadata.total_num_scheduled_tokens and pcp_padded_tokens_fla > 0: qkv_fla = F.pad(qkv_fla, pad=(0, 0, 0, pcp_padded_tokens_fla), mode="constant", value=0) all_qkv = get_pcp_group().all_gather( qkv_fla[: attn_metadata.prefill.pcp_metadata.max_num_tokens_across_pcp].contiguous(), dim=0 @@ -901,7 +896,6 @@ def _gather_and_restore_pcp_qkv( pcp_unpad_mask = attn_metadata.prefill.pcp_metadata.pcp_unpad_mask[attn_metadata.num_decodes * self.pcp_size :] qkv_fa_padding_workspace[decode_offset:][pcp_unpad_mask] = actual_qkv[decode_offset:] - qkv_fa_padding_workspace[decode_offset:][~pcp_unpad_mask] = 0 q, k, v = qkv_fa_padding_workspace.split( [ diff --git a/vllm_ascend/attention/context_parallel/common_cp.py b/vllm_ascend/attention/context_parallel/common_cp.py index 2f909e48a..090e2b796 100644 --- a/vllm_ascend/attention/context_parallel/common_cp.py +++ b/vllm_ascend/attention/context_parallel/common_cp.py @@ -3,9 +3,7 @@ import torch import torch.distributed as dist import torch_npu -from vllm.distributed import get_dcp_group, get_pcp_group - -from vllm_ascend.distributed.utils import get_decode_context_model_parallel_world_size +from vllm.distributed import get_dcp_group, get_decode_context_model_parallel_world_size, get_pcp_group @dataclass diff --git a/vllm_ascend/attention/context_parallel/dsa_cp.py b/vllm_ascend/attention/context_parallel/dsa_cp.py index 06cf6d267..7985d6127 100644 --- a/vllm_ascend/attention/context_parallel/dsa_cp.py +++ b/vllm_ascend/attention/context_parallel/dsa_cp.py @@ -8,22 +8,30 @@ import torch_npu from vllm.config import VllmConfig, get_current_vllm_config from vllm.distributed import get_tp_group +from vllm.triton_utils import HAS_TRITON from vllm.v1.attention.backend import AttentionCGSupport, AttentionMetadataBuilder from vllm.v1.kv_cache_interface import AttentionSpec, MLAAttentionSpec +from vllm_ascend.ascend_config import get_ascend_config from vllm_ascend.attention.abstract import DSAAttentionImpl from vllm_ascend.attention.attention_v1 import AscendAttentionState from vllm_ascend.attention.utils import AscendCommonAttentionMetadata, split_decodes_and_prefills -from vllm_ascend.device.device_op import DeviceOperator from vllm_ascend.ops.linear import AscendUnquantizedLinearMethod from vllm_ascend.ops.rope_dsv4 import get_cos_and_sin_dsa from vllm_ascend.quantization.methods.w8a8_dynamic import AscendW8A8DynamicLinearMethod from vllm_ascend.utils import ( AscendDeviceType, + attention_calculation_stream, get_ascend_device_type, + npu_stream_switch, olora_tp_enable, ) +if HAS_TRITON: + from vllm_ascend.ops.triton.rms_norm import triton_q_rms # noqa: F811 +else: + triton_q_rms = None # type: ignore + def hadamard_transform_ref( x: torch.Tensor, @@ -173,7 +181,6 @@ def __init__( self.block_table: torch.Tensor = None self.slot_mapping: torch.Tensor = None self.seq_lens: torch.Tensor = None - self.seq_lens_cpu: torch.Tensor = None self.compressor_ratio = getattr(kv_cache_spec, "compress_ratio", 0) hf_config = self.model_config.hf_config @@ -184,9 +191,7 @@ def __init__( try: from scipy.linalg import hadamard # type: ignore[import-untyped] except ImportError as e: - raise ImportError( - "DeepSeek-V4 indexer attention requires SciPy for Hadamard transform. Please install scipy." - ) from e + raise ImportError("Please install scipy") from e log_dim = math.ceil(math.log2(indexer_head_dim)) dim_padded = 2**log_dim AscendDSACPMetadataBuilder.hadamard = torch.tensor( @@ -198,23 +203,25 @@ def __init__( self.cu_seqlens_ori_kv = torch.tensor([], device=self.device) self.cu_seqlens_cmp_kv = torch.tensor([], device=self.device) self.seqused_q = torch.tensor([], device=self.device) - self._zero_i32 = torch.tensor([0], device=self.device, dtype=torch.int32) self.local_query_start_loc = torch.zeros( scheduler_config.max_num_seqs + 1, dtype=torch.int32, device=self.device ) self.local_seq_lens = torch.zeros(scheduler_config.max_num_seqs, dtype=torch.int32, device=self.device) + # Note(qcs): we use two dimension slot_mapping for kvcache + # with shape [block_nums, block_size, head_num, head_dim] + self.slot_mapping = torch.zeros( + (vllm_config.scheduler_config.max_num_batched_tokens, 2), dtype=torch.int32, device=self.device + ) self.speculative_config = vllm_config.speculative_config self.decode_threshold = 1 self.spec_slot_mapping = None - if get_ascend_device_type() in {AscendDeviceType.A5}: - self.slot_mapping_shape = (vllm_config.scheduler_config.max_num_batched_tokens,) # type: ignore - else: - self.slot_mapping_shape = (vllm_config.scheduler_config.max_num_batched_tokens, 2) # type: ignore if self.speculative_config: spec_token_num = self.speculative_config.num_speculative_tokens self.spec_slot_mapping = [ - torch.zeros(self.slot_mapping_shape, dtype=torch.int32, device=self.device) + torch.zeros( + (vllm_config.scheduler_config.max_num_batched_tokens, 2), dtype=torch.int32, device=self.device + ) for _ in range(spec_token_num) ] self.spec_local_query_start_loc = [ @@ -233,9 +240,6 @@ def __init__( ) self.reorder_batch_threshold = self.decode_threshold - # Note(qcs): we use two dimension slot_mapping for kvcache with shape - # [block_nums, block_size, head_num, head_dim] - self.slot_mapping = torch.zeros(self.slot_mapping_shape, dtype=torch.int32, device=self.device) @classmethod def get_cudagraph_support( @@ -285,16 +289,6 @@ def build( self.common_ratio_to_sas_metadata["sin"] = sin self.seq_lens = common_attn_metadata.seq_lens[:num_reqs] self.common_ratio_to_sas_metadata["seq_lens"] = self.seq_lens - # Prefer _seq_lens_cpu (always available, updated during draft - # iterations) over seq_lens_cpu (None in async spec decode mode). - if common_attn_metadata._seq_lens_cpu is not None: - _seq_lens_cpu = common_attn_metadata._seq_lens_cpu - elif common_attn_metadata.seq_lens_cpu is not None: - _seq_lens_cpu = common_attn_metadata.seq_lens_cpu - else: - _seq_lens_cpu = common_attn_metadata.seq_lens.cpu() - self.seq_lens_cpu = _seq_lens_cpu - self.common_ratio_to_sas_metadata["seq_lens_cpu"] = self.seq_lens_cpu else: self.num_decodes, self.num_prefills, self.num_decode_tokens, self.num_prefill_tokens = ( self.common_ratio_to_sas_metadata["num_decodes"], @@ -306,10 +300,11 @@ def build( input_positions_cpu = self.common_ratio_to_sas_metadata["input_positions_cpu"] cos, sin = self.common_ratio_to_sas_metadata["cos"], self.common_ratio_to_sas_metadata["sin"] self.seq_lens = self.common_ratio_to_sas_metadata["seq_lens"] - self.seq_lens_cpu = self.common_ratio_to_sas_metadata["seq_lens_cpu"] slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens] - self.slot_mapping[:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping(slot_mapping, self.block_size) + self.slot_mapping[:num_input_tokens] = torch.stack( + [slot_mapping // self.block_size, slot_mapping % self.block_size], dim=-1 + ) self.block_table = common_attn_metadata.block_table_tensor[:num_reqs] @@ -364,8 +359,8 @@ def build_for_drafting( slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens] assert self.spec_slot_mapping is not None - self.spec_slot_mapping[draft_step - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping( - slot_mapping, self.block_size + self.spec_slot_mapping[draft_step - 1][:num_input_tokens] = torch.stack( + [slot_mapping // self.block_size, slot_mapping % self.block_size], dim=-1 ) self.block_table = common_attn_metadata.block_table_tensor[:num_reqs] @@ -404,7 +399,6 @@ def build_req_metadata_for_drafting( """Build DSA-CP metadata for one draft step.""" num_reqs = common_attn_metadata.num_reqs query_start_loc = common_attn_metadata.query_start_loc - query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu seq_lens_q = query_start_loc[1:] - query_start_loc[:-1] has_prefill = _has_prefill(common_attn_metadata.attn_state) @@ -431,17 +425,9 @@ def build_req_metadata_for_drafting( local_query_start_loc = local_query_start_loc.clone() local_seq_lens = local_seq_lens.clone() - _, _, _, _, local_query_start_loc_cpu, local_seq_lens_cpu, _, _ = self._build_local_token_metadata( - num_reqs=num_reqs, - num_input_tokens=num_input_tokens, - input_positions=None, - query_start_loc=query_start_loc_cpu, - seq_lens=self.seq_lens_cpu[:num_reqs], - use_cache=False, - ) - local_seq_lens_q_cpu = local_query_start_loc_cpu[1 : num_reqs + 1] - local_query_start_loc_cpu[:num_reqs] - max_local_query_len = max(1, int(local_seq_lens_q_cpu.max().item())) - max_local_seq_lens = max(1, int(local_seq_lens_cpu.max().item())) + local_seq_lens_q = local_query_start_loc[1 : num_reqs + 1] - local_query_start_loc[:num_reqs] + max_local_query_len = max(1, int(local_seq_lens_q.max().item())) + max_local_seq_lens = max(1, int(local_seq_lens.max().item())) start_pos = self.seq_lens[:num_reqs] - seq_lens_q @@ -449,32 +435,13 @@ def build_req_metadata_for_drafting( slot_mapping = self.spec_slot_mapping[draft_step - 1][: self.num_actual_tokens] num_heads = self.model_config.hf_config.num_attention_heads - metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op() - metadata_kwargs = DeviceOperator.get_dsa_sparse_attn_metadata_kwargs(self.seqused_q.device) - metadata_kwargs.setdefault("device", str(self.seqused_q.device)) - cu_seqlens_ori_kv = ( - local_query_start_loc - if has_prefill - else DeviceOperator.get_dsa_decode_cu_seqlens_ori_kv( - None, - "draft_cu_seqlens_ori_kv", - local_seq_lens, - num_reqs, - self._zero_i32, - self.cu_seqlens_ori_kv, - ) - ) - cu_seqlens_cmp_kv = ( - None if has_prefill else DeviceOperator.get_dsa_decode_cu_seqlens_cmp_kv(self.cu_seqlens_cmp_kv) - ) - sas_metadata = metadata_op( - **metadata_kwargs, + sas_metadata = torch.ops._C_ascend.npu_sparse_attn_sharedkv_metadata( num_heads_q=num_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), cu_seqlens_q=local_query_start_loc, - cu_seqlens_ori_kv=cu_seqlens_ori_kv, - cu_seqlens_cmp_kv=cu_seqlens_cmp_kv, + cu_seqlens_ori_kv=local_query_start_loc if has_prefill else self.cu_seqlens_ori_kv, + cu_seqlens_cmp_kv=None, seqused_q=self.seqused_q, seqused_kv=local_seq_lens, max_seqlen_q=max_local_query_len, @@ -488,6 +455,7 @@ def build_req_metadata_for_drafting( layout_kv="PA_ND", has_ori_kv=True, has_cmp_kv=False, + device=str(self.seqused_q.device), ) cp_metadata = DSACPMetadata( @@ -531,7 +499,6 @@ def build_req_metadata( num_reqs = common_attn_metadata.num_reqs has_prefill = _has_prefill(attn_state) query_start_loc = common_attn_metadata.query_start_loc - query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu seq_lens_q = query_start_loc[1:] - query_start_loc[:-1] @@ -558,18 +525,9 @@ def build_req_metadata( local_seq_lens=self.local_seq_lens, ) local_seq_lens_q = local_query_start_loc[1 : num_reqs + 1] - local_query_start_loc[:num_reqs] - - _, _, _, _, local_query_start_loc_cpu, local_seq_lens_cpu, _, _ = self._build_local_token_metadata( - num_reqs=num_reqs, - num_input_tokens=num_input_tokens, - input_positions=None, - query_start_loc=query_start_loc_cpu, - seq_lens=self.seq_lens_cpu[:num_reqs], - use_cache=False, - ) - local_seq_lens_q_cpu = local_query_start_loc_cpu[1 : num_reqs + 1] - local_query_start_loc_cpu[:num_reqs] - max_local_query_len = max(1, int(local_seq_lens_q_cpu.max().item())) - max_local_seq_lens = max(1, int(local_seq_lens_cpu.max().item())) + # TODO(qcs): remove this .item() to avoid D2H synchronization. + max_local_query_len = max(1, int(local_seq_lens_q.max().item())) + max_local_seq_lens = max(1, int(local_seq_lens.max().item())) # start_pos: context length before current query start_pos = self.seq_lens[:num_reqs] - seq_lens_q @@ -585,21 +543,17 @@ def build_req_metadata( # --- Compressed positions --- compress_cos, compress_sin = None, None cu_cmp_seqlens = self._get_cmp_seqlens_for_metadata(has_prefill) - actual_num_tokens = self.num_actual_tokens - actual_input_positions_cpu = input_positions_cpu[:actual_num_tokens] if self.compressor_ratio > 1: layer_name = f"c{self.compressor_ratio}" compressed_input_positions = self._get_padded_compressed_position( - actual_input_positions_cpu, self.compressor_ratio, num_reqs, actual_num_tokens + input_positions_cpu, self.compressor_ratio, num_reqs, num_input_tokens ) compress_cos, compress_sin = get_cos_and_sin_dsa( {layer_name: compressed_input_positions}, use_cache=not has_prefill ) - slot_mapping_size = self._get_slot_mapping_size( - actual_input_positions_cpu, self.compressor_ratio, num_reqs, actual_num_tokens - ) + slot_mapping_size = self._get_slot_mapping_size(input_positions_cpu, self.compressor_ratio) slot_mapping = self.slot_mapping[:slot_mapping_size] # --- SAS metadata (all requests combined) --- @@ -663,8 +617,8 @@ def _build_local_token_metadata( query_start_loc, seq_lens, use_cache, - local_query_start_loc=None, - local_seq_lens=None, + local_query_start_loc, + local_seq_lens, ): """ For example: @@ -691,46 +645,29 @@ def _build_local_token_metadata( local_start = tp_rank * tokens_per_rank local_end = local_start + tokens_per_rank - if local_query_start_loc is not None: - local_query_start_loc.fill_(0) - local_seq_lens.fill_(0) + local_query_start_loc.fill_(0) + local_seq_lens.fill_(0) # Intersect each request's global token interval with this rank's local # token interval, then build the per-rank query_start_loc from lengths. local_query_start = torch.clamp(query_start_loc[:-1], min=local_start, max=local_end) local_query_end = torch.clamp(query_start_loc[1:], min=local_start, max=local_end) local_query_lens = local_query_end - local_query_start - if local_query_start_loc is not None: - local_query_start_loc[1 : num_reqs + 1] = torch.cumsum(local_query_lens, dim=0) - else: - local_query_start_loc = torch.cat( - [ - torch.tensor([0], dtype=local_query_lens.dtype, device=local_query_lens.device), - torch.cumsum(local_query_lens, dim=0), - ], - 0, - ) + local_query_start_loc[1 : num_reqs + 1] = torch.cumsum(local_query_lens, dim=0) # For requests that cross the local slice boundary, offset removes the # tokens that live on later ranks so local_seq_lens matches local queries. offset = query_start_loc[1:] - local_query_end - if local_seq_lens is not None: - local_seq_lens[:num_reqs] = (local_query_lens > 0) * (seq_lens - offset) - else: - local_seq_lens = (local_query_lens > 0) * (seq_lens - offset) + local_seq_lens[:num_reqs] = (local_query_lens > 0) * (seq_lens - offset) # RoPE tables are generated on the padded global positions first, then # sliced to this rank so local tokens keep their original positions. - if input_positions is not None: - pad_tokens = num_tokens_pad - input_positions.shape[0] - if pad_tokens > 0: - input_positions = F.pad(input_positions, (0, pad_tokens), value=0) - local_cos, local_sin = get_cos_and_sin_dsa(input_positions, use_cache=use_cache) - local_cos = local_cos[local_start:local_end] - local_sin = local_sin[local_start:local_end] - else: - local_cos = None - local_sin = None + pad_tokens = num_tokens_pad - input_positions.shape[0] + if pad_tokens > 0: + input_positions = F.pad(input_positions, (0, pad_tokens), value=0) + local_cos, local_sin = get_cos_and_sin_dsa(input_positions, use_cache=use_cache) + local_cos = local_cos[local_start:local_end] + local_sin = local_sin[local_start:local_end] return ( local_start, local_end, @@ -758,14 +695,13 @@ def _get_cmp_seqlens_for_metadata(self, has_prefill): return None if has_prefill: return None - return DeviceOperator.get_dsa_decode_cu_seqlens_cmp_kv(self.cu_seqlens_cmp_kv) + return self.cu_seqlens_cmp_kv - def _get_slot_mapping_size(self, input_positions, compress_ratio, num_reqs, num_input_tokens): + def _get_slot_mapping_size(self, input_positions, compress_ratio): if compress_ratio <= 1: return self.num_actual_tokens - return self._get_padded_compressed_position(input_positions, compress_ratio, num_reqs, num_input_tokens).shape[ - 0 - ] + mask = ((input_positions + 1) % compress_ratio) == 0 + return mask.sum() def _build_sas_metadata( self, @@ -784,26 +720,9 @@ def _build_sas_metadata( cache_key = f"cp_sas_c{cmp_ratio}" metadata = self.common_ratio_to_sas_metadata.get(cache_key) if metadata is None: - cu_seqlens_ori_kv = ( - query_start_loc - if has_prefill - else DeviceOperator.get_dsa_decode_cu_seqlens_ori_kv( - self.common_ratio_to_sas_metadata, - f"{cache_key}_cu_seqlens_ori_kv", - seq_lens, - num_reqs, - self._zero_i32, - self.cu_seqlens_ori_kv, - ) - ) - cu_seqlens_cmp_kv = ( - None if has_prefill else DeviceOperator.get_dsa_decode_cu_seqlens_cmp_kv(self.cu_seqlens_cmp_kv) - ) - metadata_op = DeviceOperator.get_dsa_sparse_attn_metadata_op() - metadata_kwargs = DeviceOperator.get_dsa_sparse_attn_metadata_kwargs(self.seqused_q.device) - metadata_kwargs.setdefault("device", str(self.seqused_q.device)) + cu_seqlens_ori_kv = query_start_loc if has_prefill else self.cu_seqlens_ori_kv + cu_seqlens_cmp_kv = None if has_prefill else self.cu_seqlens_cmp_kv kw = dict( - **metadata_kwargs, num_heads_q=num_heads, num_heads_kv=1, head_dim=self.model_config.get_head_size(), @@ -821,6 +740,7 @@ def _build_sas_metadata( layout_q="TND", layout_kv="PA_ND", has_ori_kv=True, + device=str(self.seqused_q.device), ) if self.compressor_ratio > 1: @@ -836,7 +756,7 @@ def _build_sas_metadata( kw["cmp_ratio"] = cmp_ratio kw["has_cmp_kv"] = False - metadata = metadata_op(**kw) + metadata = torch.ops._C_ascend.npu_sparse_attn_sharedkv_metadata(**kw) self.common_ratio_to_sas_metadata[cache_key] = metadata self.req_sas_metadata[:1024] = metadata return self.req_sas_metadata[:1024] @@ -890,7 +810,7 @@ def build_for_graph_capture( ) else: raise NotImplementedError( - f"Graph capture only supports DecodeOnly and SpecDecoding attn states, got {attn_state}." + "Currently we only support building dummy metadata for DecodeOnly and SpecDecoding state" ) assert attn_metadata is not None @@ -941,7 +861,6 @@ def __init__( self.wq_b = kwargs["wq_b"] self.wkv = kwargs["wkv"] self.q_norm = kwargs["q_norm"] - self.q_norm_without_weight = kwargs.get("q_norm_without_weight") self.kv_norm = kwargs["kv_norm"] self.indexer = kwargs.get("indexer") @@ -954,6 +873,9 @@ def __init__( self.attn_sink = kwargs["attn_sink"] + ascend_config = get_ascend_config() + self.multistream_dsa_preprocess = ascend_config.multistream_dsa_preprocess + self.vllm_config = get_current_vllm_config() # indexer param @@ -1015,42 +937,23 @@ def forward( # type: ignore[override] num_tokens = o_proj_input.shape[0] # o - if get_ascend_device_type() in {AscendDeviceType.A5}: - o = o_proj_input.view(num_tokens, self.n_local_groups, -1) - o, swiglu_out_scale = torch_npu.npu_dynamic_mx_quant(o, dst_type=torch.float8_e4m3fn) - o = torch_npu.npu_transpose_quant_batchmatmul( - o, + o_proj_input = o_proj_input.view(num_tokens, self.n_local_groups, -1) + if olora_tp_enable(): + o_proj_tmp = self.wo_a(o_proj_input) + else: + # wo_a = self.wo_a.weight.view(self.n_local_groups, self.o_lora_rank, -1) + # o = torch.einsum("tgd,grd->tgr", o, wo_a) + o_proj_tmp = torch_npu.npu_transpose_batchmatmul( + o_proj_input, self.wo_a.weight, - dtype=torch.bfloat16, bias=None, - group_sizes=(0, 0, 32), - x1_scale=swiglu_out_scale.view(torch.float8_e8m0fnu), - x2_scale=self.wo_a.weight_scale.view(torch.float8_e8m0fnu), + scale=None, perm_x1=(1, 0, 2), perm_x2=(0, 1, 2), perm_y=(1, 0, 2), - ) - o = o.reshape(num_tokens, -1) - output[...] = self.wo_b(o) - else: - o_proj_input = o_proj_input.view(num_tokens, self.n_local_groups, -1) - if olora_tp_enable(): - o_proj_input = self.wo_a(o_proj_input) - else: - # wo_a = self.wo_a.weight.view(self.n_local_groups, self.o_lora_rank, -1) - # o = torch.einsum("tgd,grd->tgr", o, wo_a) - o_proj_input = torch_npu.npu_transpose_batchmatmul( - o_proj_input, - self.wo_a.weight, - bias=None, - scale=None, - perm_x1=(1, 0, 2), - perm_x2=(0, 1, 2), - perm_y=(1, 0, 2), - batch_split_factor=1, - ) - o_proj_input = o_proj_input.reshape(num_tokens, -1) - output[...] = self.wo_b(o_proj_input) + batch_split_factor=1, + ).view(num_tokens, -1) + output[...] = self.wo_b(o_proj_tmp) return output @@ -1063,18 +966,28 @@ def _forward( need_gather_q_kv: bool = False, ): """Run full-sequence KV cache updates and local-token attention.""" - (compress_kv_cache, swa_kv_cache, state_cache, _, _, _) = DeviceOperator.unpack_dsa_forward_kv_cache( - kv_cache, self.compress_ratio - ) if self.compress_ratio == 4: + (compress_kv_cache, swa_kv_cache, state_cache, _, _, _) = kv_cache (compressor_attn_metadata, compressor_kv_state_metadata, _, _, swa_metadata) = attn_metadata elif self.compress_ratio == 128: + (compress_kv_cache, swa_kv_cache, state_cache, _, _, _) = kv_cache (compressor_attn_metadata, compressor_kv_state_metadata, swa_metadata) = attn_metadata else: + (_, swa_kv_cache, _, _, _, _) = kv_cache (swa_metadata,) = attn_metadata common_attn_metadata = attn_metadata[0] - hidden_states = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(hidden_states_local, need_gather_q_kv) + overlap_hidden_states_allgather = self.multistream_dsa_preprocess and need_gather_q_kv + wait_hidden_states_local_event = ( + torch.npu.current_stream().record_event() if overlap_hidden_states_allgather else None + ) + with npu_stream_switch(attention_calculation_stream(), enabled=overlap_hidden_states_allgather): + if wait_hidden_states_local_event: + torch.npu.current_stream().wait_event(wait_hidden_states_local_event) + hidden_states = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(hidden_states_local, need_gather_q_kv) + wait_hidden_states_allgather_event = ( + torch.npu.current_stream().record_event() if overlap_hidden_states_allgather else None + ) assert common_attn_metadata.req_metadata is not None assert swa_metadata.req_metadata is not None @@ -1088,7 +1001,6 @@ def _forward( local_seq_lengths_query = cp_metadata.local_query_start_loc local_seq_lengths_key = cp_metadata.local_seq_lens has_prefill = _has_prefill(common_attn_metadata.attn_state) - hidden_states_cache = hidden_states[: common_attn_metadata.num_actual_tokens] if (not isinstance(self.wq_b.quant_method, AscendUnquantizedLinearMethod)) and isinstance( self.wq_b.quant_method.quant_method, AscendW8A8DynamicLinearMethod @@ -1139,7 +1051,7 @@ def _forward( q = q.unflatten(-1, (self.num_heads, self.head_dim)) - q = DeviceOperator.apply_dsa_q_rms(q, self.eps, self.q_norm_without_weight) + q = triton_q_rms(q, self.eps) torch.ops._C_ascend.inplace_partial_rotary_mul( q.unsqueeze(1), local_cos, @@ -1148,18 +1060,21 @@ def _forward( partial_slice=[self.nope_head_dim, self.head_dim], ) - kv = self.wkv(hidden_states_cache) + if wait_hidden_states_allgather_event: + torch.npu.current_stream().wait_event(wait_hidden_states_allgather_event) + + kv = self.wkv(hidden_states) kv = self.kv_norm(kv) assert self.rope_head_dim is not None kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) torch.ops._C_ascend.inplace_partial_rotary_mul( kv.unsqueeze(1), - cos[: kv.shape[0]], - sin[: kv.shape[0]], + cos, + sin, rotary_mode="interleave", partial_slice=[self.nope_head_dim, self.head_dim], ) - DeviceOperator.dsa_kv_compress_scatter(swa_kv_cache, kv, swa_metadata.req_metadata.slot_mapping) + torch.ops._C_ascend.npu_scatter_nd_update_v2(swa_kv_cache, swa_metadata.req_metadata.slot_mapping, kv) compress_topk_idxs = None if self.compress_ratio > 1: @@ -1169,7 +1084,7 @@ def _forward( compress_sin = req_metadata.compress_sin[layer_name] if self.compress_ratio == 4: self._update_indexer_cache( - x=hidden_states_cache, + x=hidden_states, kv_cache=kv_cache, attn_metadata=attn_metadata, compressed_cos=compress_cos, @@ -1190,7 +1105,7 @@ def _forward( coff = 2 if self.compressor_overlap else 1 compressed_kv = torch.ops._C_ascend.compressor( - hidden_states_cache, + hidden_states, self.compressor_wkv.weight, self.compressor_wgate.weight, state_cache.squeeze(-2), @@ -1212,15 +1127,8 @@ def _forward( if compressed_kv.numel() == 0: compressed_kv = None - DeviceOperator.dsa_kv_compress_scatter( - compress_kv_cache, compressed_kv, compressor_attn_metadata.req_metadata.slot_mapping - ) - - attn_op = DeviceOperator.get_dsa_sparse_attn_op() - extra_attn_kwargs: dict = DeviceOperator.get_dsa_sparse_attn_base_kwargs() - if has_prefill: - DeviceOperator.add_dsa_sparse_attn_extra_kwargs( - extra_attn_kwargs, cu_seqlens_ori_kv=local_seq_lengths_query + torch.ops._C_ascend.npu_scatter_nd_update_v2( + compress_kv_cache, compressor_attn_metadata.req_metadata.slot_mapping, compressed_kv ) common_attn_kwargs = dict( @@ -1228,17 +1136,18 @@ def _forward( seqused_kv=local_seq_lengths_key, sinks=self.attn_sink, softmax_scale=self.softmax_scale, - cmp_ratio=max(self.compress_ratio, 1), + cmp_ratio=self.compress_ratio, ori_mask_mode=4, ori_win_left=self.window_size - 1, ori_win_right=0, layout_q="TND", layout_kv="PA_ND", - **extra_attn_kwargs, ) + if has_prefill: + common_attn_kwargs["cu_seqlens_ori_kv"] = local_seq_lengths_query if self.compress_ratio <= 1: - attn_output = attn_op( + attn_output = torch.ops._C_ascend.npu_sparse_attn_sharedkv( q, ori_kv=swa_kv_cache, ori_block_table=swa_metadata.req_metadata.block_table, @@ -1247,31 +1156,27 @@ def _forward( )[0] elif self.compress_ratio == 4: assert compressor_attn_metadata.req_metadata is not None - DeviceOperator.add_dsa_sparse_attn_extra_kwargs( - common_attn_kwargs, cu_seqlens_cmp_kv=req_metadata.cu_cmp_seqlen_list - ) - attn_output = attn_op( + attn_output = torch.ops._C_ascend.npu_sparse_attn_sharedkv( q, ori_kv=swa_kv_cache, cmp_kv=compress_kv_cache, cmp_sparse_indices=compress_topk_idxs, ori_block_table=swa_metadata.req_metadata.block_table, cmp_block_table=compressor_attn_metadata.req_metadata.block_table, + cu_seqlens_cmp_kv=req_metadata.cu_cmp_seqlen_list, metadata=req_metadata.sas_metadata, cmp_mask_mode=3, **common_attn_kwargs, )[0] else: assert compressor_attn_metadata.req_metadata is not None - DeviceOperator.add_dsa_sparse_attn_extra_kwargs( - common_attn_kwargs, cu_seqlens_cmp_kv=req_metadata.cu_cmp_seqlen_list - ) - attn_output = attn_op( + attn_output = torch.ops._C_ascend.npu_sparse_attn_sharedkv( q, ori_kv=swa_kv_cache, cmp_kv=compress_kv_cache, ori_block_table=swa_metadata.req_metadata.block_table, cmp_block_table=compressor_attn_metadata.req_metadata.block_table, + cu_seqlens_cmp_kv=req_metadata.cu_cmp_seqlen_list, metadata=compressor_attn_metadata.req_metadata.sas_metadata, cmp_mask_mode=3, **common_attn_kwargs, @@ -1318,9 +1223,7 @@ def _update_indexer_cache( compressed_sin: torch.Tensor, actual_seq_lengths_query: torch.Tensor, ) -> None: - (indexer_state_cache, indexer_k_cache, indexer_scale_cache, indexer_full_cache) = ( - DeviceOperator.unpack_dsa_indexer_kv_cache(kv_cache) - ) + (_, _, _, indexer_state_cache, indexer_k_cache, indexer_scale_cache) = kv_cache (_, _, indexer_kv_state_metadata, indexer_kv_scale_metadata, _) = attn_metadata coff = 2 if self.compressor_overlap else 1 assert indexer_kv_scale_metadata is not None @@ -1354,18 +1257,19 @@ def _update_indexer_cache( if self.indexer.compressor.rotate: kv = rotate_activation(kv, indexer_kv_scale_metadata.hadamard) - _, kv_scale = DeviceOperator.indexer_quant_scatter_part1( - kv, - indexer_k_cache, - indexer_full_cache, - indexer_kv_scale_metadata.req_metadata.slot_mapping, + soc_version = get_ascend_device_type() + dst_type = torch.float8_e4m3fn if soc_version in {AscendDeviceType.A5} else torch.int8 + kv, kv_scale = torch_npu.npu_dynamic_quant(kv, dst_type=dst_type) + kv_scale = kv_scale.unsqueeze(-1) + if soc_version not in {AscendDeviceType.A5}: + kv_scale = kv_scale.to(torch.float16).unsqueeze(-1) + + torch.ops._C_ascend.npu_scatter_nd_update_v2( + indexer_k_cache, indexer_kv_scale_metadata.req_metadata.slot_mapping, kv + ) + torch.ops._C_ascend.npu_scatter_nd_update_v2( + indexer_scale_cache, indexer_kv_scale_metadata.req_metadata.slot_mapping, kv_scale ) - if kv_scale is not None: - DeviceOperator.dsa_indexer_scatter_scale_part3( - kv_scale, - indexer_scale_cache, - indexer_kv_scale_metadata.req_metadata.slot_mapping, - ) def _indexer_select_topk( self, @@ -1379,7 +1283,7 @@ def _indexer_select_topk( actual_seq_lengths_key: torch.Tensor, qr_pertoken_scale: torch.Tensor = None, ): - (_, indexer_k_cache, indexer_scale_cache, _) = DeviceOperator.unpack_dsa_indexer_kv_cache(kv_cache) + (_, _, _, _, indexer_k_cache, indexer_scale_cache) = kv_cache (_, _, _, indexer_kv_scale_metadata, _) = attn_metadata assert indexer_kv_scale_metadata is not None @@ -1387,7 +1291,6 @@ def _indexer_select_topk( (not isinstance(self.inderxer_wq_b.quant_method, AscendUnquantizedLinearMethod)) and isinstance(self.inderxer_wq_b.quant_method.quant_method, AscendW8A8DynamicLinearMethod) and qr_pertoken_scale is not None - and get_ascend_device_type() not in {AscendDeviceType.A5} ): q = torch_npu.npu_quant_matmul( qr, @@ -1410,7 +1313,11 @@ def _indexer_select_topk( q = rotate_activation(q, indexer_kv_scale_metadata.hadamard) weights = self.weights_proj(x) * (self.indexer_softmax_scale * self.indexer_heads**-0.5) - q, q_scale = DeviceOperator.indexer_quantize_query(q) + soc_version = get_ascend_device_type() + dst_type = torch.float8_e4m3fn if soc_version in {AscendDeviceType.A5} else torch.int8 + q, q_scale = torch_npu.npu_dynamic_quant(q, dst_type=dst_type) + if soc_version not in {AscendDeviceType.A5}: + q_scale = q_scale.to(torch.float16) assert indexer_kv_scale_metadata.req_metadata is not None qli_metadata = indexer_kv_scale_metadata.req_metadata.qli_metadata @@ -1418,9 +1325,9 @@ def _indexer_select_topk( topk_idxs, _ = torch.ops._C_ascend.npu_quant_lightning_indexer( query=q, key=indexer_k_cache, - weights=DeviceOperator.prepare_dsa_indexer_weights(weights), - query_dequant_scale=DeviceOperator.prepare_dsa_indexer_query_scale(q_scale), - key_dequant_scale=DeviceOperator.prepare_dsa_indexer_key_scale(indexer_scale_cache), + weights=weights.to(torch.float16), + query_dequant_scale=q_scale, + key_dequant_scale=indexer_scale_cache.squeeze(-2), actual_seq_lengths_query=actual_seq_lengths_query[1:], actual_seq_lengths_key=actual_seq_lengths_key, block_table=block_table, diff --git a/vllm_ascend/attention/context_parallel/mla_cp.py b/vllm_ascend/attention/context_parallel/mla_cp.py index b556b17dd..b11f92863 100644 --- a/vllm_ascend/attention/context_parallel/mla_cp.py +++ b/vllm_ascend/attention/context_parallel/mla_cp.py @@ -6,6 +6,8 @@ from vllm.config import VllmConfig from vllm.distributed import ( get_dcp_group, + get_decode_context_model_parallel_rank, + get_decode_context_model_parallel_world_size, get_pcp_group, ) from vllm.utils.math_utils import cdiv @@ -14,10 +16,6 @@ from vllm_ascend.attention.attention_v1 import AscendAttentionState from vllm_ascend.device.device_op import DeviceOperator -from vllm_ascend.distributed.utils import ( - get_decode_context_model_parallel_rank, - get_decode_context_model_parallel_world_size, -) # isort: off from vllm_ascend.attention.mla_v1 import ( diff --git a/vllm_ascend/attention/dsa_v1.py b/vllm_ascend/attention/dsa_v1.py index f04a57b5f..297a4cbcd 100644 --- a/vllm_ascend/attention/dsa_v1.py +++ b/vllm_ascend/attention/dsa_v1.py @@ -25,6 +25,7 @@ from vllm_ascend.quantization.methods.w8a8_dynamic import AscendW8A8DynamicLinearMethod from vllm_ascend.utils import ( AscendDeviceType, + attention_calculation_stream, get_ascend_device_type, npu_stream_switch, olora_tp_enable, @@ -107,15 +108,6 @@ def hadamard_scale(out: torch.Tensor, x_shape: tuple[int, ...], dim: int, scale: return out[..., :dim].reshape(*x_shape) -def _is_w8a8_dynamic(linear) -> bool: - """True iff ``linear`` is wired up with ``AscendW8A8DynamicLinearMethod``.""" - qm = getattr(linear, "quant_method", None) - if qm is None or isinstance(qm, AscendUnquantizedLinearMethod): - return False - inner = getattr(qm, "quant_method", None) - return isinstance(inner, AscendW8A8DynamicLinearMethod) - - def pad_to_blocks(x: torch.Tensor, length_list: torch.Tensor, block_size: int = 128): """ Pads a ragged/packed tensor into fixed-size blocks. @@ -216,7 +208,8 @@ def get_impl_cls() -> type["DSAAttentionImpl"]: @staticmethod def get_supported_kernel_block_sizes() -> list[int]: - return [2, 4, 8, 16, 32, 64, 128] + kernel_block_sizes = DeviceOperator.get_dsa_kernel_block_sizes() + return kernel_block_sizes @dataclass @@ -376,14 +369,12 @@ def __init__( self.speculative_config = vllm_config.speculative_config self.decode_threshold = 1 self.spec_slot_mapping = None - if get_ascend_device_type() in {AscendDeviceType.A5}: - self.slot_mapping_shape = (vllm_config.scheduler_config.max_num_batched_tokens,) # type: ignore - else: - self.slot_mapping_shape = (vllm_config.scheduler_config.max_num_batched_tokens, 2) # type: ignore if self.speculative_config: spec_token_num = self.speculative_config.num_speculative_tokens self.spec_slot_mapping = [ - torch.zeros(self.slot_mapping_shape, dtype=torch.int32, device=self.device) + torch.zeros( + (vllm_config.scheduler_config.max_num_batched_tokens, 2), dtype=torch.int32, device=self.device + ) for _ in range(spec_token_num) ] self.decode_threshold += spec_token_num @@ -437,7 +428,9 @@ def __init__( self._zero_i32 = torch.tensor([0], device=self.device, dtype=torch.int32) # Note(qcs): we use two dimension slot_mapping for kvcache with shape # [block_nums, block_size, head_num, head_dim] - self.slot_mapping = torch.zeros(self.slot_mapping_shape, dtype=torch.int32, device=self.device) + self.slot_mapping = torch.zeros( + (vllm_config.scheduler_config.max_num_batched_tokens, 2), dtype=torch.int32, device=self.device + ) @classmethod def get_cudagraph_support( @@ -564,7 +557,7 @@ def build( # NOTE: Currently, MTP-fullgraph is incompatibility pcp slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens] - self.slot_mapping[:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping(slot_mapping, self.block_size) + self.slot_mapping = DeviceOperator.format_dsa_slot_mapping(slot_mapping, self.block_size) self.graph_pad_size = common_attn_metadata.graph_pad_size block_table_size = self.get_block_table_size(common_attn_metadata, BUILD_METADATA_STEP_PREFILL) @@ -1141,7 +1134,7 @@ def build_for_drafting( cos, sin = get_cos_and_sin_dsa(input_positions, use_cache=False) slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens] - self.spec_slot_mapping[draft_step - 1][:num_input_tokens] = DeviceOperator.format_dsa_slot_mapping( # type: ignore[index] + self.spec_slot_mapping[draft_step - 1] = DeviceOperator.format_dsa_slot_mapping( # type: ignore[index] slot_mapping, self.block_size ) @@ -1434,7 +1427,9 @@ def __init__( self.attn_sink = kwargs["attn_sink"] ascend_config = get_ascend_config() + self.multistream_dsa_preprocess = ascend_config.multistream_dsa_preprocess self.multistream_dsv4_dsa_overlap = ascend_config.multistream_dsv4_dsa_overlap + self.prefill_comm_compute_overlap = ascend_config.prefill_comm_compute_overlap self.vllm_config = get_current_vllm_config() # indexer param @@ -1481,6 +1476,51 @@ def __init__( False, ) + def dsa_warmup_with_multistream(self, hidden_states: torch.Tensor) -> None: + """ + Warmup function for DSA profiling run. + When dual-stream is enabled, the aux stream runs ops during forward that have never been + exercised during profiling. This warmup ensures all aux-stream op patterns are captured + for ACL graph compatibility. + """ + if hasattr(self, "multistream_dsv4_dsa_overlap") and self.multistream_dsv4_dsa_overlap: + hidden_states_dummy = torch.zeros( + 1, hidden_states.shape[-1], dtype=hidden_states.dtype, device=hidden_states.device + ) + aux_stream = dsv4_dsa_overlap_stream() + e_warmup = torch.npu.current_stream().record_event() + with npu_stream_switch(aux_stream, enabled=True): + torch.npu.current_stream().wait_event(e_warmup) + if hasattr(self.wkv, "weight_scale") and self.wkv.weight.dtype == torch.int8: + kv_q_dummy, kv_s_dummy = torch_npu.npu_dynamic_quant(hidden_states_dummy) + _ = torch_npu.npu_quant_matmul( + kv_q_dummy, + self.wkv.weight, + self.wkv.weight_scale, + pertoken_scale=kv_s_dummy, + output_dtype=hidden_states.dtype, + ) + else: + _ = self.cv_wkv.quantize(hidden_states_dummy) + _ = self.cv_wkv.matmul(hidden_states_dummy, None) + assert self.rope_head_dim is not None + kv_dummy = torch.zeros( + 1, self.nope_head_dim + self.rope_head_dim, dtype=hidden_states.dtype, device=hidden_states.device + ) + _ = self.kv_norm(kv_dummy) + + # indexer module aux stream ops + # Part1 aux: kv_quant + scatter (device-dispatched via DeviceOperator) + # In profiling stage, create dummy tensors to ensure ACL graph captures scatter operator. + if self.compress_ratio == 4 and self.indexer is not None: + slot_mapping_dummy = torch.zeros(1, dtype=torch.int64, device=hidden_states.device) + DeviceOperator.warmup_indexer_quant_scatter(hidden_states_dummy, slot_mapping_dummy) + + # Warm up weights_proj on the aux stream. + _ = self.weights_proj(hidden_states_dummy) + + torch.npu.current_stream().wait_stream(aux_stream) + def _get_indexcache_topk_indices(self, num_tokens: int, offset: int = 0) -> torch.Tensor: if self.topk_indices_buffer is None: raise RuntimeError("IndexCache requires topk_indices_buffer when skip_topk is enabled.") @@ -1544,10 +1584,25 @@ def forward( # type: ignore[override] decode_tokens = attn_metadata[0].num_decode_tokens actual_tokens = attn_metadata[0].num_actual_tokens - # Process for Flash Comm V1 - hidden_states = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(hidden_states, need_gather_q_kv) - prefill_hidden_states = hidden_states[decode_tokens:actual_tokens] - decode_hidden_states = hidden_states[:decode_tokens] + # Delay allgather optimization: when prefill_comm_compute_overlap is + # enabled and the batch is pure-prefill, wq_a/wkv can compute on the + # local SP partition first, then allgather smaller intermediates. + # Mutually exclusive with multistream_dsv4_dsa_overlap (multistream wins). + need_prefill_gather = ( + self.prefill_comm_compute_overlap + and not self.multistream_dsv4_dsa_overlap + and need_gather_q_kv + and has_prefill + and not has_decode + ) + if need_prefill_gather: + prefill_hidden_states = hidden_states + decode_hidden_states = hidden_states[:0] + else: + # Process for Flash Comm V1 + hidden_states = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(hidden_states, need_gather_q_kv) + prefill_hidden_states = hidden_states[decode_tokens:actual_tokens] + decode_hidden_states = hidden_states[:decode_tokens] forward_context = get_forward_context() o_proj_input_shape = (forward_context.num_tokens, self.n_local_heads, self.head_dim) @@ -1560,6 +1615,7 @@ def forward( # type: ignore[override] prefill_hidden_states, kv_cache, attn_metadata, + need_prefill_gather, ) # type: ignore[arg-type] o_proj_input[decode_tokens:actual_tokens] = output_prefill cos = attn_metadata[0].prefill.cos[layer_name] @@ -1619,7 +1675,7 @@ def forward( # type: ignore[override] perm_y=(1, 0, 2), batch_split_factor=1, ) - o_proj_input = o_proj_input.reshape(num_tokens, -1) + o_proj_input = o_proj_input.reshape(num_tokens, -1) output[...] = self.wo_b(o_proj_input) return output_padded @@ -1639,7 +1695,9 @@ def _mla_prolog_multistream(self, hidden_states, cos, sin, swa_kv_cache, slot_ma main_stream = torch.npu.current_stream() aux_stream = dsv4_dsa_overlap_stream() - is_w8a8 = _is_w8a8_dynamic(self.wq_b) + is_w8a8 = (not isinstance(self.wq_b.quant_method, AscendUnquantizedLinearMethod)) and isinstance( + self.wq_b.quant_method.quant_method, AscendW8A8DynamicLinearMethod + ) # Part1: q_quant[V] -> q_a_down[C] || kv_quant[V] q_quant, q_pertoken_scale = self.cv_wq_a.quantize(hidden_states) @@ -1721,12 +1779,91 @@ def _mla_prolog_multistream(self, hidden_states, cos, sin, swa_kv_cache, slot_ma return q, qr, qr_pertoken_scale + def _mla_prolog_prefill_overlap(self, hidden_states, cos, sin, swa_kv_cache, slot_mapping): + """Delayed allgather + compute-communication overlap for pure prefill. + + hidden_states: [N/tp, dim] local SP partition. + Returns: (q, qr, full_hidden_states) + - q: [actual_tokens, n_local_heads, head_dim] with RoPE applied + - qr: [actual_tokens, q_lora_rank] for indexer + - full_hidden_states: [actual_tokens, dim] for compressor + """ + main_stream = torch.npu.current_stream() + aux_stream = dsv4_dsa_overlap_stream() + num_actual_tokens = cos.shape[0] + + # === Phase 1: q_a_down [main/C] || kv_quant [aux/V] === + q_quant, q_pertoken_scale = self.cv_wq_a.quantize(hidden_states) + e_phase1 = main_stream.record_event() + + with npu_stream_switch(aux_stream, enabled=True): + torch.npu.current_stream().wait_event(e_phase1) + kv_quant, kv_pertoken_scale = self.cv_wkv.quantize(hidden_states) + + main_stream.wait_stream(aux_stream) + q_a_down = self.cv_wq_a.matmul(q_quant, q_pertoken_scale) + qr = self.q_norm(q_a_down) + + # === Phase 2: allgather(qr) [main/comm] || kv_proj + kv_norm [aux/compute] === + e_phase2 = main_stream.record_event() + + with npu_stream_switch(aux_stream, enabled=True): + torch.npu.current_stream().wait_event(e_phase2) + kv = self.cv_wkv.matmul(kv_quant, kv_pertoken_scale) + kv = self.kv_norm(kv) + + qr = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(qr, True) + qr = qr[:num_actual_tokens] + main_stream.wait_stream(aux_stream) + + # === Phase 3: allgather(kv)+allgather(hs) [main/comm] || wq_b+q_rms [aux/compute] === + e_phase3 = main_stream.record_event() + + with npu_stream_switch(aux_stream, enabled=True): + torch.npu.current_stream().wait_event(e_phase3) + q_b_quant, q_b_scale = self.cv_wq_b.quantize(qr) + q = self.cv_wq_b.matmul(q_b_quant, q_b_scale).unflatten(-1, (self.n_local_heads, self.head_dim)) + q = DeviceOperator.apply_dsa_q_rms(q, self.eps, self.q_norm_without_weight) + + kv = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(kv, True) + kv = kv[:num_actual_tokens] + if self.compress_ratio > 1: + full_hidden_states = torch.ops.vllm.maybe_all_gather_and_maybe_unpad(hidden_states, True) + full_hidden_states = full_hidden_states[:num_actual_tokens] + else: + full_hidden_states = hidden_states + + main_stream.wait_stream(aux_stream) + + # === Tail: q_rope + kv_rope + scatter (main stream, serial) === + torch.ops._C_ascend.inplace_partial_rotary_mul( + q.unsqueeze(1), + cos, + sin, + rotary_mode="interleave", + partial_slice=[self.nope_head_dim, self.head_dim], + ) + + assert self.rope_head_dim is not None + kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) + torch.ops._C_ascend.inplace_partial_rotary_mul( + kv.unsqueeze(1), + cos, + sin, + rotary_mode="interleave", + partial_slice=[self.nope_head_dim, self.head_dim], + ) + DeviceOperator.dsa_kv_compress_scatter(swa_kv_cache, kv, slot_mapping) + + return q, qr, full_hidden_states + def _forward_prefill( self, layer_name, hidden_states: torch.Tensor, kv_cache: tuple[torch.Tensor, ...], attn_metadata: DSAMetadataList, + need_prefill_gather: bool = False, ): compress_common_attn_metadata = None (compress_kv_cache, swa_kv_cache, state_cache, indexer_k_cache, indexer_scale_cache, indexer_full_cache) = ( @@ -1760,39 +1897,22 @@ def _forward_prefill( q, qr, _ = self._mla_prolog_multistream( hidden_states, cos, sin, swa_kv_cache, swa_prefill_metadata.slot_mapping, is_prefill=True ) + elif need_prefill_gather: + # Delayed allgather + compute-communication overlap + assert swa_metadata.prefill is not None + q, qr, hidden_states = self._mla_prolog_prefill_overlap( + hidden_states, + cos, + sin, + swa_kv_cache, + swa_prefill_metadata.slot_mapping, + ) + qr_pertoken_scale = None # noqa: F841 else: # mlaprolog - share_hs_quant = _is_w8a8_dynamic(self.wq_a) and _is_w8a8_dynamic(self.wkv) - if share_hs_quant: - hs_int8, hs_pertoken_scale = torch_npu.npu_dynamic_quant(hidden_states) - q_a = torch_npu.npu_quant_matmul( - hs_int8, - self.wq_a.weight, - self.wq_a.weight_scale, - pertoken_scale=hs_pertoken_scale, - bias=self.wq_a.bias, - output_dtype=hidden_states.dtype, - ) - else: - q_a = self.wq_a(hidden_states) - # q - if _is_w8a8_dynamic(self.wq_b): - qr, qr_pertoken_scale = torch.ops._C_ascend.npu_rms_norm_dynamic_quant( - q_a, self.q_norm.weight, epsilon=self.eps - ) - q = torch_npu.npu_quant_matmul( - qr, - self.wq_b.weight, - self.wq_b.weight_scale, - pertoken_scale=qr_pertoken_scale, - bias=self.wq_b.bias, - output_dtype=hidden_states.dtype, - ).unflatten(-1, (self.n_local_heads, self.head_dim)) - else: - qr = self.q_norm(q_a) - q = self.wq_b(qr).unflatten(-1, (self.n_local_heads, self.head_dim)) - qr_pertoken_scale = None + qr = self.q_norm(self.wq_a(hidden_states)) + q = self.wq_b(qr).unflatten(-1, (self.n_local_heads, self.head_dim)) q = DeviceOperator.apply_dsa_q_rms(q, self.eps, self.q_norm_without_weight) torch.ops._C_ascend.inplace_partial_rotary_mul( @@ -1803,17 +1923,7 @@ def _forward_prefill( partial_slice=[self.nope_head_dim, self.head_dim], ) # win kv & tok_dis - if share_hs_quant: - kv = torch_npu.npu_quant_matmul( - hs_int8, - self.wkv.weight, - self.wkv.weight_scale, - pertoken_scale=hs_pertoken_scale, - bias=self.wkv.bias, - output_dtype=hidden_states.dtype, - ) - else: - kv = self.wkv(hidden_states) + kv = self.wkv(hidden_states) kv = self.kv_norm(kv) assert self.rope_head_dim is not None kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) @@ -1895,7 +2005,6 @@ def _forward_prefill( actual_seq_lengths_query=actual_seq_lengths_query, actual_seq_lengths_key=actual_seq_lengths_key, with_prefill=True, - qr_pertoken_scale=qr_pertoken_scale, ) coff = 2 if self.compressor_overlap else 1 @@ -2069,25 +2178,15 @@ def _forward_decode( hidden_states, cos, sin, swa_kv_cache, swa_decode_metadata.slot_mapping, is_prefill=False ) else: - # Share one dynamic-quant of hidden_states between wq_a (main stream) - # and wkv (attention stream) when both sides are W8A8 dynamic. - share_hs_quant = _is_w8a8_dynamic(self.wq_a) and _is_w8a8_dynamic(self.wkv) - if share_hs_quant: - hs_int8, hs_pertoken_scale = torch_npu.npu_dynamic_quant(hidden_states) + wait_hidden_state_cal_event = ( + torch.npu.current_stream().record_event() if self.multistream_dsa_preprocess else None + ) # q - if _is_w8a8_dynamic(self.wq_b): - if share_hs_quant: - q_a = torch_npu.npu_quant_matmul( - hs_int8, - self.wq_a.weight, - self.wq_a.weight_scale, - pertoken_scale=hs_pertoken_scale, - bias=self.wq_a.bias, - output_dtype=hidden_states.dtype, - ) - else: - q_a = self.wq_a(hidden_states) + if (not isinstance(self.wq_b.quant_method, AscendUnquantizedLinearMethod)) and isinstance( + self.wq_b.quant_method.quant_method, AscendW8A8DynamicLinearMethod + ): + q_a = self.wq_a(hidden_states) qr, qr_pertoken_scale = torch.ops._C_ascend.npu_rms_norm_dynamic_quant( q_a, self.q_norm.weight, epsilon=self.eps ) @@ -2100,18 +2199,7 @@ def _forward_decode( output_dtype=hidden_states.dtype, ).unflatten(-1, (self.n_local_heads, self.head_dim)) else: - if share_hs_quant: - q_a = torch_npu.npu_quant_matmul( - hs_int8, - self.wq_a.weight, - self.wq_a.weight_scale, - pertoken_scale=hs_pertoken_scale, - bias=self.wq_a.bias, - output_dtype=hidden_states.dtype, - ) - qr = q = self.q_norm(q_a) - else: - qr = q = self.q_norm(self.wq_a(hidden_states)) + qr = q = self.q_norm(self.wq_a(hidden_states)) q = self.wq_b(q).unflatten(-1, (self.n_local_heads, self.head_dim)) qr_pertoken_scale = None @@ -2125,32 +2213,33 @@ def _forward_decode( partial_slice=[self.nope_head_dim, self.head_dim], ) - # win kv & tok_dis - if share_hs_quant: - kv = torch_npu.npu_quant_matmul( - hs_int8, - self.wkv.weight, - self.wkv.weight_scale, - pertoken_scale=hs_pertoken_scale, - bias=self.wkv.bias, - output_dtype=hidden_states.dtype, - ) - else: + with npu_stream_switch(attention_calculation_stream(), enabled=self.multistream_dsa_preprocess): + if wait_hidden_state_cal_event: + torch.npu.current_stream().wait_event(wait_hidden_state_cal_event) + + # win kv & tok_dis kv = self.wkv(hidden_states) - kv = self.kv_norm(kv) - assert self.rope_head_dim is not None - kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) + kv = self.kv_norm(kv) + assert self.rope_head_dim is not None + kv = kv.view(-1, 1, self.nope_head_dim + self.rope_head_dim) + + torch.ops._C_ascend.inplace_partial_rotary_mul( + kv.unsqueeze(1), + cos, + sin, + rotary_mode="interleave", + partial_slice=[self.nope_head_dim, self.head_dim], + ) - torch.ops._C_ascend.inplace_partial_rotary_mul( - kv.unsqueeze(1), - cos, - sin, - rotary_mode="interleave", - partial_slice=[self.nope_head_dim, self.head_dim], - ) + # swa exec kv + DeviceOperator.dsa_kv_compress_scatter(swa_kv_cache, kv, swa_decode_metadata.slot_mapping) - # swa exec kv - DeviceOperator.dsa_kv_compress_scatter(swa_kv_cache, kv, swa_decode_metadata.slot_mapping) + wait_attention_cal_event = ( + torch.npu.current_stream().record_event() if self.multistream_dsa_preprocess else None + ) + + if wait_attention_cal_event: + torch.npu.current_stream().wait_event(wait_attention_cal_event) if self.compress_ratio > 1: compressor_decode_metadata = _require_decode_metadata(compressor_attn_metadata) @@ -2362,7 +2451,8 @@ def _indexer_qkv_prepare( ) = attn_metadata if ( - _is_w8a8_dynamic(self.inderxer_wq_b) + (not isinstance(self.inderxer_wq_b.quant_method, AscendUnquantizedLinearMethod)) + and isinstance(self.inderxer_wq_b.quant_method.quant_method, AscendW8A8DynamicLinearMethod) and qr_pertoken_scale is not None and get_ascend_device_type() not in {AscendDeviceType.A5} ): @@ -2601,7 +2691,11 @@ def cv_indexer_select_qli( aux_stream = dsv4_dsa_overlap_stream() # ===== Part0: Pre-compute on main ===== - if _is_w8a8_dynamic(self.inderxer_wq_b) and qr_pertoken_scale is not None: + if ( + (not isinstance(self.inderxer_wq_b.quant_method, AscendUnquantizedLinearMethod)) + and isinstance(self.inderxer_wq_b.quant_method.quant_method, AscendW8A8DynamicLinearMethod) + and qr_pertoken_scale is not None + ): qr_quant_ready = qr qr_scale_ready = qr_pertoken_scale else: @@ -2664,7 +2758,11 @@ def cv_indexer_select_qli( ) # Main: matmul q from qr (directly submit, V/C different engines dispatch naturally) - if _is_w8a8_dynamic(self.inderxer_wq_b) and qr_pertoken_scale is not None: + if ( + (not isinstance(self.inderxer_wq_b.quant_method, AscendUnquantizedLinearMethod)) + and isinstance(self.inderxer_wq_b.quant_method.quant_method, AscendW8A8DynamicLinearMethod) + and qr_pertoken_scale is not None + ): q = torch_npu.npu_quant_matmul( qr_quant_ready, self.inderxer_wq_b.weight, diff --git a/vllm_ascend/attention/sfa_v1.py b/vllm_ascend/attention/sfa_v1.py index 425207b28..82335b65d 100644 --- a/vllm_ascend/attention/sfa_v1.py +++ b/vllm_ascend/attention/sfa_v1.py @@ -158,10 +158,6 @@ class AscendSFAMetadata: num_decodes: int = 0 num_decode_tokens: int = 0 num_prefills: int = 0 - block_size: int = 0 - group_len: torch.Tensor | None = None - group_key_idx: torch.Tensor | None = None - group_key_cache_idx: torch.Tensor | None = None M = TypeVar("M", bound=AscendSFAMetadata) @@ -245,10 +241,6 @@ def build( slot_mapping = common_attn_metadata.slot_mapping[:num_input_tokens] input_positions = common_attn_metadata.positions[:num_input_tokens].long() - block_size = 128 - if get_ascend_config().c8_enable_reshape_optim: - slot_mapping_cpu = common_attn_metadata.slot_mapping_cpu[:num_input_tokens] - cum_query_lens = common_attn_metadata.query_start_loc[1 : num_reqs + 1] seq_lens = common_attn_metadata.seq_lens[:num_reqs] @@ -342,14 +334,6 @@ def build( actual_seq_lengths_key=actual_seq_lengths_key, ) - if get_ascend_config().c8_enable_reshape_optim: - slot_mapping_list = slot_mapping_cpu.tolist() - group_len, group_key_idx, group_key_cache_idx = torch.ops._C_ascend.store_kv_block_pre( - slot_mapping, slot_mapping_list, block_size - ) - else: - group_len, group_key_idx, group_key_cache_idx = None, None, None - return self.metadata_cls( # type: ignore num_input_tokens=common_attn_metadata.num_input_tokens, num_actual_tokens=num_actual_tokens, @@ -364,10 +348,6 @@ def build( sin=sin[:num_input_tokens], cos=cos[:num_input_tokens], dsa_cp_context=dsa_cp_context, - block_size=block_size, - group_len=group_len, - group_key_idx=group_key_idx, - group_key_cache_idx=group_key_cache_idx, ) def build_for_graph_capture( @@ -1285,43 +1265,22 @@ def forward( dsa_k_cache_idx = 2 dsa_k_scale_cache_idx = 3 - if self.is_kv_producer and get_ascend_config().c8_enable_reshape_optim: - torch.ops._C_ascend.store_kv_block( - k_li, - kv_cache[dsa_k_cache_idx], - attn_metadata.group_len, - attn_metadata.group_key_idx, - attn_metadata.group_key_cache_idx, - attn_metadata.block_size, - ) - else: - torch_npu.npu_scatter_nd_update_( - kv_cache[dsa_k_cache_idx].view(-1, k_li.shape[-1]), - slot_mapping.view(-1, 1), - k_li.view(-1, k_li.shape[-1]), - ) # b, s, n, d + torch_npu.npu_scatter_nd_update_( + kv_cache[dsa_k_cache_idx].view(-1, k_li.shape[-1]), + slot_mapping.view(-1, 1), + k_li.view(-1, k_li.shape[-1]), + ) # b, s, n, d if self.use_sparse_c8_indexer: if get_ascend_device_type() == AscendDeviceType.A5: assert len(kv_cache) == 3 else: assert len(kv_cache) == 4 if k_li_scale is not None: - if self.is_kv_producer and get_ascend_config().c8_enable_reshape_optim: - torch.ops._C_ascend.store_kv_block( - k_li_scale, - kv_cache[dsa_k_scale_cache_idx], - attn_metadata.group_len, - attn_metadata.group_key_idx, - attn_metadata.group_key_cache_idx, - attn_metadata.block_size, - ) - else: - torch_npu.npu_scatter_nd_update_( - kv_cache[dsa_k_scale_cache_idx].view(-1, k_li_scale.shape[-1]), - slot_mapping.view(-1, 1), - k_li_scale.view(-1, k_li_scale.shape[-1]), - ) - + torch_npu.npu_scatter_nd_update_( + kv_cache[dsa_k_scale_cache_idx].view(-1, k_li_scale.shape[-1]), + slot_mapping.view(-1, 1), + k_li_scale.view(-1, k_li_scale.shape[-1]), + ) if self.is_kv_producer: attn_metadata.reshape_cache_event.record() diff --git a/vllm_ascend/attention/utils.py b/vllm_ascend/attention/utils.py index 04db0fb4b..26d4cdadc 100644 --- a/vllm_ascend/attention/utils.py +++ b/vllm_ascend/attention/utils.py @@ -174,9 +174,6 @@ class AscendCommonAttentionMetadata(CommonAttentionMetadata): positions: torch.Tensor = None positions_cpu: torch.Tensor = None - # CPU tensor of slot mapping for host-side operations. - slot_mapping_cpu: torch.Tensor = None - # Current attention state (e.g., ChunkedPrefill, DecodeOnly). attn_state: Any = None @@ -212,7 +209,6 @@ def _slice_reqs(x): # This is really strange since vLLM slices them as well block_table_tensor=self.block_table_tensor, slot_mapping=self.slot_mapping, - slot_mapping_cpu=self.slot_mapping_cpu, causal=self.causal, actual_seq_lengths_q=self.actual_seq_lengths_q[:num_actual_tokens], positions=self.positions, diff --git a/vllm_ascend/compilation/acl_graph.py b/vllm_ascend/compilation/acl_graph.py index 00cbb82cd..02da5a3f2 100644 --- a/vllm_ascend/compilation/acl_graph.py +++ b/vllm_ascend/compilation/acl_graph.py @@ -2,11 +2,10 @@ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import dataclasses -import weakref from collections.abc import Callable from contextlib import ExitStack from dataclasses import dataclass -from typing import Any, ClassVar +from typing import Any from unittest.mock import patch import torch @@ -24,31 +23,6 @@ from ..utils import weak_ref_tensors -_STREAM_RESOURCE_ERROR_CODE = "207008" -_STREAM_RESOURCE_ERROR_MARKERS = ( - "insufficient_stream_resources", - "stream resources are insufficient", -) -_STREAM_RESOURCE_GUIDANCE = ( - "ACL graph capture failed with a known stream-resource exhaustion " - "signature. Consider upgrading to a newer HDK/CANN stack, reducing " - "cudagraph_capture_sizes, lowering max_cudagraph_capture_size, preferring " - "FULL or FULL_DECODE_ONLY for mostly uniform decode workloads, or " - "temporarily disabling graph mode to confirm the failure is capture-related." -) - - -def _is_stream_resource_capture_error(exc: RuntimeError) -> bool: - message = str(exc) - lowered_message = message.lower() - has_error_code = _STREAM_RESOURCE_ERROR_CODE in message - has_stream_resource_marker = any(marker in lowered_message for marker in _STREAM_RESOURCE_ERROR_MARKERS) - return has_stream_resource_marker or (has_error_code and "stream resource" in lowered_message) - - -def _raise_stream_resource_capture_error(exc: RuntimeError) -> None: - raise RuntimeError(f"{_STREAM_RESOURCE_GUIDANCE}\nOriginal error:\n{exc}") from exc - @dataclasses.dataclass class ACLGraphEntry: @@ -86,13 +60,6 @@ class ACLGraphWrapper: guaranteed when VLLM_LOGGING_LEVEL == "DEBUG". """ - _all_instances: ClassVar[weakref.WeakSet["ACLGraphWrapper"]] = weakref.WeakSet() - - @classmethod - def clear_all_graphs(cls) -> None: - for instance in list(cls._all_instances): - instance.clear_graphs() - def __init__( self, runnable: Callable, @@ -126,8 +93,6 @@ def __init__( self.enable_enpu = enable_enpu self.use_eagle = use_eagle - ACLGraphWrapper._all_instances.add(self) - def __getattr__(self, key: str): # allow accessing the attributes of the runnable. if hasattr(self.runnable, key): @@ -142,13 +107,6 @@ def unwrap(self) -> Callable: # in case we need to access the original runnable. return self.runnable - @property - def cudagraph_wrapper(self) -> "ACLGraphWrapper": - return self - - def clear_graphs(self) -> None: - self.concrete_aclgraph_entries.clear() - def __call__(self, *args, **kwargs): forward_context = get_forward_context() batch_descriptor = forward_context.batch_descriptor @@ -195,34 +153,18 @@ def __call__(self, *args, **kwargs): stack.enter_context(patch("torch.npu.empty_cache", lambda: None)) # mind-exploding: carefully manage the reference and memory. - - # Sync offloader's copy stream before capture. - # Ensure any pre-capture prefetches from offloader are complete. - from vllm.model_executor.offloader.base import get_offloader - - get_offloader().sync_prev_onload() forward_context.capturing = True - try: - with torch.npu.graph(aclgraph, pool=self.graph_pool): - # `output` is managed by pytorch's aclgraph pool - output = self.runnable(*args, **kwargs) - # Join offloader's copy stream after forward to avoid - # unjoined stream error. The last layer's start_prefetch - # forks copy_stream, but wait_prefetch only happens in - # the next forward pass. - get_offloader().join_after_forward() - if self.aclgraph_options.weak_ref_output: - # by converting it to weak ref, - # the original `output` will immediately be released - # to save memory. It is only safe to do this for - # the last graph in piecewise aclgraph mode, because - # the output of the last graph will not be used by - # any other acl graph. - output = weak_ref_tensors(output) - except RuntimeError as exc: - if _is_stream_resource_capture_error(exc): - _raise_stream_resource_capture_error(exc) - raise + with torch.npu.graph(aclgraph, pool=self.graph_pool): + # `output` is managed by pytorch's aclgraph pool + output = self.runnable(*args, **kwargs) + if self.aclgraph_options.weak_ref_output: + # by converting it to weak ref, + # the original `output` will immediately be released + # to save memory. It is only safe to do this for + # the last graph in piecewise aclgraph mode, because + # the output of the last graph will not be used by + # any other acl graph. + output = weak_ref_tensors(output) # here we always use weak ref for the workspaces # to save memory @@ -330,13 +272,6 @@ class GraphParams: _graph_params: GraphParams | None = None -def reset_graph_params(): - global _graph_params, _draft_graph_params, _draft_graph_prefill_params - _graph_params = None - _draft_graph_params = None - _draft_graph_prefill_params = None - - def set_graph_params(aclgraph_capture_sizes: list[int]): global _graph_params if _graph_params is not None: diff --git a/vllm_ascend/compilation/compiler_interface.py b/vllm_ascend/compilation/compiler_interface.py index f9c1ba3ba..c0d2e680d 100644 --- a/vllm_ascend/compilation/compiler_interface.py +++ b/vllm_ascend/compilation/compiler_interface.py @@ -85,28 +85,6 @@ def _configure_backend( vllm_config: VllmConfig, process_kwargs_options: Callable | None = None, ) -> None: - if ascend_compilation_config.enable_static_kernel: - # npugraph_ex's static_kernel requires LOCAL_WORLD_SIZE to determine the - # physical node topology for creating per-node Gloo groups, which - # coordinate static kernel compilation and .run package installation. - # vLLM does not set this env var by default (unlike torchrun), so we - # compute it from parallel config: - # local_world_size: processes per node for one DP replica - # data_parallel_size_local: number of DP replicas on this node - # actual_local_world_size: total processes on this physical machine - if "LOCAL_WORLD_SIZE" not in os.environ: - actual_local_world_size = ( - vllm_config.parallel_config.local_world_size * vllm_config.parallel_config.data_parallel_size_local - ) - os.environ["LOCAL_WORLD_SIZE"] = str(actual_local_world_size) - logger.info_once( - "Setting LOCAL_WORLD_SIZE=%d for static kernel (local_world_size=%d * data_parallel_size_local=%d).", - actual_local_world_size, - vllm_config.parallel_config.local_world_size, - vllm_config.parallel_config.data_parallel_size_local, - scope="global", - ) - if process_kwargs_options is not None: # npugraph_ex (both old and new): build options dict and use _process_kwargs_options. # It maps flat option names to nested config paths for old versions, @@ -116,8 +94,6 @@ def _configure_backend( options: dict[str, Any] = { "force_eager": True, "inplace_pass": False, - "clone_input": False, - "clone_output": False, } if ascend_compilation_config.enable_static_kernel: logger.info_once( diff --git a/vllm_ascend/compilation/graph_fusion_pass_manager.py b/vllm_ascend/compilation/graph_fusion_pass_manager.py index 42fbb943a..26a54eb10 100644 --- a/vllm_ascend/compilation/graph_fusion_pass_manager.py +++ b/vllm_ascend/compilation/graph_fusion_pass_manager.py @@ -66,7 +66,7 @@ def configure(self, config: VllmConfig): self.passes.append(MatmulAllReduceAddRMSNormPass(config)) - if self.ascend_compilation_config.get("fuse_muls_add", True) and not is_310p(): + if self.ascend_compilation_config.get("fuse_muls_add", True): from .passes.muls_add_pass import MulsAddFusionPass self.passes.append(MulsAddFusionPass(config)) diff --git a/vllm_ascend/core/profiling_chunk_predictor.py b/vllm_ascend/core/profiling_chunk_predictor.py index 116aee0b7..be9bac1c2 100644 --- a/vllm_ascend/core/profiling_chunk_predictor.py +++ b/vllm_ascend/core/profiling_chunk_predictor.py @@ -60,7 +60,7 @@ def __init__(self, smooth_factor: float = 0.8, min_chunk: int = 4096): self.min_chunk = min_chunk self.history_fitted = False - def clamp_quadratic_and_linear_if_negative(self, fitted_a: float, fitted_b: float) -> float: + def clamp_quadratic_and_linear_if_negative(self, fitted_a: float, fitted_b: float) -> tuple[float, float]: """In theory, for the Transfomur structure of LLM, the fitted quadratic and linear terms should not be negative. Can perform zero clamping for inaccurate fitting """ @@ -68,9 +68,10 @@ def clamp_quadratic_and_linear_if_negative(self, fitted_a: float, fitted_b: floa logger.warning("Fitted a=%.2e is not positive. Setting a=1e-9.", fitted_a) fitted_a = 1e-9 if fitted_b < 0: - logger.warning("Fitted b=%.2e is not positive. The performance may deteriorate..", fitted_b) + logger.warning("Fitted b=%.2e is not positive. Setting b=0.0.", fitted_b) + fitted_b = 1e-9 - return fitted_a + return fitted_a, fitted_b def fit(self, seq_lens: list[int], latencies: list[float]) -> bool: """Fit quadratic coefficients f(l) = al^2 + bl + c from data points. @@ -112,7 +113,7 @@ def fit(self, seq_lens: list[int], latencies: list[float]) -> bool: logger.warning("Failed to fit quadratic model: %s", fallback_error) return False - fitted_a = self.clamp_quadratic_and_linear_if_negative(fitted_a, fitted_b) + fitted_a, fitted_b = self.clamp_quadratic_and_linear_if_negative(fitted_a, fitted_b) self.quadratic_coeff_a = fitted_a self.linear_coeff_b = fitted_b @@ -161,7 +162,7 @@ def fit_chunk(self, chunked_data: list) -> bool: logger.warning("Failed to fit chunked model: %s", e) return False - fitted_a = self.clamp_quadratic_and_linear_if_negative(fitted_a, fitted_b) + fitted_a, fitted_b = self.clamp_quadratic_and_linear_if_negative(fitted_a, fitted_b) self.quadratic_chunk_a = fitted_a self.linear_chunk_b = fitted_b @@ -223,41 +224,17 @@ def predict( num_computed_tokens: int, base_chunk_size: int, page_size: int, - target_time: float = 0, ) -> int | None: - """Predict next chunk size x such that f(L+x) - f(L) = target_latency. - - Args: - num_computed_tokens: Number of tokens already computed (L), - representing the current position in the sequence. - base_chunk_size: The default/fallback chunk size used as a - baseline for smoothing the predicted value. - page_size: The memory page size, used together with 64 to - determine the alignment granularity of the final chunk size. - target_time: Override target latency in seconds. If > 0, this - value is used instead of self.target_latency for the - prediction equation. - - Returns: - The predicted and aligned chunk size as an int, or None if - the model is not ready, the discriminant is negative, or the - prediction yields an invalid result. - """ + """Predict next chunk size x such that f(L+x) - f(L) = target_latency.""" if not self.is_ready or self.target_latency is None: return None if self.quadratic_coeff_a <= 0: return None - # f(L+x)-f(L) = a*x^2 + (2a*L+b)*x = target_latency - # Standard form: A*x^2 + B*x + C = 0 A = self.quadratic_coeff_a B = 2 * self.quadratic_coeff_a * num_computed_tokens + self.linear_coeff_b - - if target_time > 0: - C = -target_time - else: - C = -self.target_latency + C = -self.target_latency discriminant = B * B - 4 * A * C if discriminant < 0: @@ -285,27 +262,9 @@ def predict_with_history( num_computed_tokens: int, base_chunk_size: int, page_size: int, - target_time: float = 0, ) -> int | None: """Predict next chunk size x using the history-aware model - f(C,H) = a*C(C+H) + b*C + c*H. - - Args: - num_computed_tokens: Number of tokens already computed (C), - representing the current computed position in the sequence. - base_chunk_size: The default/fallback chunk size used as a - baseline for smoothing the predicted value. - page_size: The memory page size, used together with 64 to - determine the alignment granularity of the final chunk size. - target_time: Override target latency in seconds. If > 0, this - value is used instead of self.target_latency for the - prediction equation. - - Returns: - The predicted and aligned chunk size as an int, or None if - the model is not ready, the discriminant is negative, or the - prediction yields an invalid result. - """ + f(C,H) = a*C(C+H) + b*C + c*H.""" if not self.is_ready or self.target_latency is None: return None @@ -315,15 +274,10 @@ def predict_with_history( if self.quadratic_chunk_a <= 0: return None - # f(x,H) = a*x*(x+H) + b*x + c*H, solving f(x,H)=T gives: - # a*x^2 + (a*H + b)*x + (b*H + c - T) = 0 - # Standard form: A*x^2 + B*x + C = 0, where H=num_computed_tokens, T=target_latency + # a*C^2 + (a*H + b)*C + b*H + c - T = 0 A = self.quadratic_chunk_a B = self.quadratic_chunk_a * num_computed_tokens + self.linear_chunk_b - if target_time > 0: - C = self.linear_chunk_b * num_computed_tokens + self.constant_chunk_c - target_time - else: - C = self.linear_chunk_b * num_computed_tokens + self.constant_chunk_c - self.target_latency + C = self.linear_chunk_b * num_computed_tokens + self.constant_chunk_c - self.target_latency discriminant = B * B - 4 * A * C if discriminant < 0: @@ -382,15 +336,19 @@ def predict_chunk_size(self, num_computed_tokens: int, target_time: float) -> in if not self.is_ready: return None - if not self.history_ready or num_computed_tokens == 0: + # NOTE(gjc): We found that the FIA operator has abnormal performance + # when processing multiple request groups in a batch, so the target_latency + # feature is temporarily fixed. It will be enabled again after the + # issues with the FIA operator are resolved. Therefore, in multi-request + # concurrent scenarios, there is still room for performance improvement in CPP. + # self.predictor.target_latency = target_time + + if not self.history_ready: predict_func = self.predictor.predict else: predict_func = self.predictor.predict_with_history return predict_func( - num_computed_tokens=num_computed_tokens, - base_chunk_size=self.base_chunk_size, - page_size=self.page_size, - target_time=target_time, + num_computed_tokens=num_computed_tokens, base_chunk_size=self.base_chunk_size, page_size=self.page_size ) def predict_time(self, num_new_tokens: int, num_computed_tokens: int) -> float: @@ -398,7 +356,7 @@ def predict_time(self, num_new_tokens: int, num_computed_tokens: int) -> float: if not self.is_ready: return 0.0 - if not self.history_ready or num_computed_tokens == 0: + if not self.history_ready: predict_func = self.predictor.get_time else: predict_func = self.predictor.get_time_with_history diff --git a/vllm_ascend/core/recompute_scheduler.py b/vllm_ascend/core/recompute_scheduler.py index 47fe3f66c..4f54bac99 100644 --- a/vllm_ascend/core/recompute_scheduler.py +++ b/vllm_ascend/core/recompute_scheduler.py @@ -28,7 +28,6 @@ from vllm.distributed.kv_transfer.kv_connector.v1.base import KVConnectorMetadata from vllm.distributed.kv_transfer.kv_connector.v1.metrics import KVConnectorStats from vllm.logger import logger -from vllm.v1.core.kv_cache_coordinator import HybridKVCacheCoordinator from vllm.v1.core.kv_cache_manager import KVCacheBlocks from vllm.v1.core.sched.async_scheduler import AsyncScheduler from vllm.v1.core.sched.interface import PauseState @@ -47,8 +46,6 @@ from vllm.v1.spec_decode.metrics import SpecDecodingStats from vllm.v1.utils import ConstantList, record_function_or_nullcontext -from vllm_ascend.utils import vllm_version_is - # `spec_manager_map` in single_type_kv_cache_manager is a module-level dict # whose keys are class objects bound at import time. When the async @@ -64,15 +61,14 @@ # Fix: whenever this patch is applied, register AscendMLAAttentionSpec as # an additional key in spec_manager_map (if the module is already loaded). def register_ascend_mla_spec_in_manager(): + import sys as _sys + from vllm.v1.core.single_type_kv_cache_manager import FullAttentionManager from vllm.v1.kv_cache_interface import MLAAttentionSpec as AscendMLAAttentionSpec - if vllm_version_is("0.22.1"): - import sys as _sys - - _stm = _sys.modules.get("vllm.v1.core.single_type_kv_cache_manager") - if _stm is not None and AscendMLAAttentionSpec not in _stm.spec_manager_map: - _stm.spec_manager_map[AscendMLAAttentionSpec] = FullAttentionManager + _stm = _sys.modules.get("vllm.v1.core.single_type_kv_cache_manager") + if _stm is not None and AscendMLAAttentionSpec not in _stm.spec_manager_map: + _stm.spec_manager_map[AscendMLAAttentionSpec] = FullAttentionManager @dataclass @@ -148,6 +144,10 @@ def add_request(self, request: Request) -> None: request.streaming_queue = deque() # Fill in placeholder tokens to enable full graph compatibility. Without # placeholders, graph matching may fail, forcing eager mode execution. + if self.is_kv_producer and self.is_hybrid_model and request.num_tokens > 1: + request.prompt_token_ids.pop() + request._all_token_ids.pop() + request.num_prompt_tokens -= 1 if self.is_mtp_kv_consumer and (self.max_model_len >= (request.num_tokens + self.num_spec_tokens)): request.spec_token_ids = [PLACEHOLDER_TOKEN_ID] * self.num_spec_tokens self._enqueue_waiting_request(request) @@ -155,6 +155,60 @@ def add_request(self, request: Request) -> None: if self.log_stats: request.record_event(EngineCoreEventType.QUEUED) + def _update_waiting_for_remote_kv(self, request: Request) -> None: + """ + KV Connector: update request state after async recv is finished. + + The finished_recving_kv_req_ids list is populated + on the previous steps()'s update_from_output based + on the worker side connector. + + When the kv transfer is ready, we cache the blocks + and the request state will be moved back to WAITING from + WAITING_FOR_REMOTE_KV. + + NOTE: The check for whether request.request_id is in + finished_recving_kv_req_ids is now done by the caller + (_try_promote_blocked_waiting_request in the parent Scheduler), + so this method is only called when the recv is confirmed finished. + """ + assert self.connector is not None + + if request.request_id in self.failed_recving_kv_req_ids: + # Request had KV load failures; num_computed_tokens was already + # updated in _update_requests_with_invalid_blocks + if request.num_computed_tokens: + # Cache any valid computed tokens. + self.kv_cache_manager.cache_blocks(request, request.num_computed_tokens) + else: + # No valid computed tokens, release allocated blocks. + # There may be a local cache hit on retry. + self.kv_cache_manager.free(request) + + self.failed_recving_kv_req_ids.remove(request.request_id) + else: + # Now that the blocks are ready, actually cache them. + # Use Ascend-specific block_ids logic to handle multi-group KV + # cache configurations (e.g. MLA) where len(block_ids) > 1. + block_ids = self.kv_cache_manager.get_block_ids(request.request_id) + if len(block_ids) == 1: + num_computed_tokens = len(block_ids[0]) * self.block_size + # Handle the case where num request tokens less than one block. + num_computed_tokens = min(num_computed_tokens, request.num_tokens) + else: + num_computed_tokens = request.num_tokens + # on a full prompt hit, we need to re-compute the last token + # in order to be able to sample the next token + if num_computed_tokens == request.num_tokens: + num_computed_tokens -= 1 + # This will cache the blocks iff caching is enabled. + self.kv_cache_manager.cache_blocks(request, num_computed_tokens) + + # Update the request state for scheduling. + request.num_computed_tokens = num_computed_tokens + + self.finished_recving_kv_req_ids.remove(request.request_id) + def schedule(self) -> RecomputeSchedulerOutput: # NOTE(woosuk) on the scheduling algorithm: # There's no "decoding phase" nor "prefill phase" in the scheduler. @@ -422,32 +476,9 @@ def schedule(self) -> RecomputeSchedulerOutput: # Get already-cached tokens. if request.num_computed_tokens == 0: # Get locally-cached tokens. - if ( - self.connector is not None - and self.has_mamba_layers - and isinstance( - self.kv_cache_manager.coordinator, - HybridKVCacheCoordinator, - ) - ): - computed_blocks, num_new_local_computed_tokens = ( - self.kv_cache_manager.coordinator.find_longest_cache_hit_per_group( - request.block_hashes, - request.num_tokens - 1, - ) - ) - new_computed_blocks = self.kv_cache_manager.create_kv_cache_blocks(computed_blocks) - if self.kv_cache_manager.log_stats: - assert self.kv_cache_manager.prefix_cache_stats is not None - self.kv_cache_manager.prefix_cache_stats.record( - num_tokens=request.num_tokens, - num_hits=num_new_local_computed_tokens, - preempted=request.num_preemptions > 0, - ) - else: - new_computed_blocks, num_new_local_computed_tokens = self.kv_cache_manager.get_computed_blocks( - request - ) + new_computed_blocks, num_new_local_computed_tokens = self.kv_cache_manager.get_computed_blocks( + request + ) # Get externally-cached tokens if using a KVConnector. if self.connector is not None: @@ -532,7 +563,7 @@ def schedule(self) -> RecomputeSchedulerOutput: # The request cannot be scheduled. break - if self.need_mamba_block_aligned_split and not load_kv_async: + if self.need_mamba_block_aligned_split: num_new_tokens = self._mamba_block_aligned_split( request, num_new_tokens, @@ -807,22 +838,6 @@ def update_from_output( ) ) - # Persist per-step routed experts into the scheduler-side slot buffer. - # MUST precede the per-request routing reads below. - routing_data = None - routing_offsets: dict[str, int] = {} - if getattr(self, "enable_return_routed_experts", False) and model_runner_output.routed_experts is not None: - re = model_runner_output.routed_experts - self.routed_experts_mgr.store_batch(re.routing_data, re.slot_mapping) - routing_data = re.routing_data.astype( - self.routed_experts_mgr.routed_experts_by_slot.dtype, - copy=False, - ) - offset = 0 - for rid in model_runner_output.req_ids: - routing_offsets[rid] = offset - offset += num_scheduled_tokens[rid] - # NOTE(woosuk): As len(num_scheduled_tokens) can be up to 1K or more, # the below loop can be a performance bottleneck. We should do our best # to avoid expensive operations inside the loop. @@ -877,7 +892,6 @@ def update_from_output( pooler_output = pooler_outputs[req_index] if pooler_outputs else None kv_transfer_params = None status_before_stop = request.status - num_output_tokens_before = len(request._output_token_ids) # Check for stop and update request status. if new_token_ids: @@ -888,31 +902,10 @@ def update_from_output( stopped = True routed_experts = None - if getattr(self, "enable_return_routed_experts", False) and routing_data is not None and new_token_ids: - req_offset = routing_offsets[req_id] - end = req_offset + num_tokens_scheduled - block_ids = self._re_block_ids.pop(req_id, []) - if num_output_tokens_before == 0: - if ( - request.sampling_params is not None - and request.sampling_params.routed_experts_prompt_start is not None - ): - prompt_start = request.sampling_params.routed_experts_prompt_start - assert prompt_start < request.num_prompt_tokens - else: - prompt_start = 0 - routed_experts = self.routed_experts_mgr.get( - block_ids, - request.num_prompt_tokens, - token_start=prompt_start, - ) - elif scheduled_spec_token_ids: - routed_experts = routing_data[req_offset : req_offset + len(new_token_ids)] - else: - routed_experts = routing_data[end - len(new_token_ids) : end] - finish_reason = None if stopped: + routed_experts = self._get_routed_experts(request) + # Capture finish_reason BEFORE _handle_stopped_request, which may # reset the status to WAITING for streaming requests that continue. finish_reason = request.get_finished_reason() diff --git a/vllm_ascend/core/scheduler_profiling_chunk.py b/vllm_ascend/core/scheduler_profiling_chunk.py index 7c4fae4b4..3545fbf92 100644 --- a/vllm_ascend/core/scheduler_profiling_chunk.py +++ b/vllm_ascend/core/scheduler_profiling_chunk.py @@ -41,6 +41,7 @@ from vllm.v1.utils import record_function_or_nullcontext from vllm_ascend.core.profiling_chunk_predictor import ProfilingChunkManager +from vllm_ascend.utils import vllm_version_is class ProfilingChunkScheduler(Scheduler): @@ -81,7 +82,6 @@ def __init__( init_ascend_config(vllm_config) profiling_cfg = get_ascend_config().profiling_chunk_config - self.profiling_chunk_config = profiling_cfg base_chunk = self.max_num_scheduled_tokens self.profiling_chunk_manager = ProfilingChunkManager( @@ -245,8 +245,13 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 req_to_new_blocks: dict[str, KVCacheBlocks] = {} num_scheduled_tokens: dict[str, int] = {} # >>> PROFILING CHUNK >>> - target_latency = self.profiling_chunk_manager.predictor.target_latency - time_budget = target_latency if target_latency is not None else float("inf") + # NOTE(gjc): We found that the FIA operator has abnormal performance + # when processing multiple request groups in a batch, so the time_budget + # feature is temporarily disabled. It will be enabled again after the + # issues with the FIA operator are resolved. Therefore, in multi-request + # concurrent scenarios, there is still room for performance improvement in CPP. + # time_budget = self.profiling_chunk_manager.predictor.target_latency + time_budget = 0.01 # <<< PROFILING CHUNK <<< token_budget = self.max_num_scheduled_tokens if self._pause_state == PauseState.PAUSED_ALL: @@ -313,8 +318,8 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 if ( self.profiling_chunk_manager is not None and self.profiling_chunk_manager.is_ready - and request.num_computed_tokens < request.num_prompt_tokens - and (request.num_computed_tokens > 0 or not self.profiling_chunk_config.need_timing) + and num_new_tokens > 1 + and request.num_computed_tokens > 0 ): predicted_chunk = self.profiling_chunk_manager.predict_chunk_size( num_computed_tokens=request.num_computed_tokens, @@ -322,8 +327,6 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 ) if predicted_chunk is not None and predicted_chunk > 0: num_new_tokens = min(predicted_chunk, num_new_tokens) - else: - break # <<< PROFILING CHUNK <<< if self.need_mamba_block_aligned_split: @@ -383,7 +386,7 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 token_budget -= num_new_tokens # Decode requests (num_new_tokens == 1) have negligible latency; # skip time_budget accounting so they don't starve other requests. - if request.num_computed_tokens < request.num_prompt_tokens: + if num_new_tokens > 1: time_budget -= self.profiling_chunk_manager.predict_time(num_new_tokens, request.num_computed_tokens) req_index += 1 @@ -522,8 +525,8 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 if ( self.profiling_chunk_manager is not None and self.profiling_chunk_manager.is_ready - and request.num_computed_tokens < request.num_prompt_tokens - and (request.num_computed_tokens > 0 or not self.profiling_chunk_config.need_timing) + and num_new_tokens > 1 + and request.num_computed_tokens > 0 ): predicted_chunk = self.profiling_chunk_manager.predict_chunk_size( num_computed_tokens=num_computed_tokens, @@ -531,8 +534,6 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 ) if predicted_chunk is not None and predicted_chunk > 0: num_new_tokens = min(num_new_tokens, predicted_chunk) - else: - break # <<< PROFILING CHUNK <<< if not self.scheduler_config.enable_chunked_prefill and num_new_tokens > token_budget: @@ -575,6 +576,21 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 if self.is_encoder_decoder and request.has_encoder_inputs and encoder_inputs_to_schedule: num_encoder_tokens = sum(request.get_num_encoder_embeds(i) for i in encoder_inputs_to_schedule) + if ( + vllm_version_is("0.20.2") + and self.scheduler_reserve_full_isl + and not self.kv_cache_manager.can_fit_full_sequence( + request, + num_new_computed_tokens=num_new_local_computed_tokens, + new_computed_blocks=new_computed_blocks, + num_external_computed_tokens=num_external_computed_tokens, + num_encoder_tokens=num_encoder_tokens, + ) + ): + if request.has_encoder_inputs: + self.encoder_cache_manager.free(request) + break + new_blocks = self.kv_cache_manager.allocate_slots( request, num_new_tokens, @@ -584,7 +600,9 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 num_external_computed_tokens=num_external_computed_tokens, delay_cache_blocks=load_kv_async, num_encoder_tokens=num_encoder_tokens, - full_sequence_must_fit=self.scheduler_reserve_full_isl, + **( + {} if vllm_version_is("0.20.2") else {"full_sequence_must_fit": self.scheduler_reserve_full_isl} + ), ) if new_blocks is None: @@ -629,7 +647,7 @@ def schedule(self) -> SchedulerOutput: # noqa: C901 token_budget -= num_new_tokens # Decode requests (num_new_tokens == 1) have negligible latency; # skip time_budget accounting so they don't starve other requests. - if request.num_computed_tokens < request.num_prompt_tokens: + if num_new_tokens > 1: time_budget -= self.profiling_chunk_manager.predict_time( num_new_tokens, request.num_computed_tokens ) diff --git a/vllm_ascend/core/single_type_kv_cache_manager.py b/vllm_ascend/core/single_type_kv_cache_manager.py index 41fa6c418..140b6ec2f 100644 --- a/vllm_ascend/core/single_type_kv_cache_manager.py +++ b/vllm_ascend/core/single_type_kv_cache_manager.py @@ -5,14 +5,11 @@ from vllm.utils.math_utils import cdiv from vllm.v1.core.block_pool import BlockPool -from vllm.v1.core.kv_cache_utils import ( - BlockHashList, - BlockHashListWithBlockSize, - KVCacheBlock, -) +from vllm.v1.core.kv_cache_utils import BlockHashList, KVCacheBlock from vllm.v1.core.single_type_kv_cache_manager import ( FullAttentionManager, SingleTypeKVCacheManager, + spec_manager_map, ) from vllm.v1.kv_cache_interface import ( ChunkedLocalAttentionSpec, @@ -23,8 +20,6 @@ ) from vllm.v1.request import Request -from vllm_ascend.utils import vllm_version_is - class CompressAttentionManager(FullAttentionManager): def __init__(self, kv_cache_spec: MLAAttentionSpec, block_pool: BlockPool, **kwargs) -> None: @@ -158,8 +153,6 @@ def cache_blocks( self, request: Request, num_tokens: int, - retention_interval: int | None = None, - *, alignment_tokens: int | None = None, ) -> None: """ @@ -169,10 +162,6 @@ def cache_blocks( request: The request. num_tokens: The total number of tokens that need to be cached (including tokens that are already cached). - retention_interval: Prefix-cache retention interval. - alignment_tokens: Cache-hit alignment passed by hybrid KV cache - coordinators. Compressed attention caches logical blocks, so no - extra block mask is needed here. """ num_cached_blocks = self.num_cached_block.get(request.request_id, 0) num_full_blocks = num_tokens // (self.block_size * self.compress_ratio) @@ -198,27 +187,22 @@ def find_longest_cache_hit( kv_cache_group_ids: list[int], block_pool: BlockPool, kv_cache_spec: KVCacheSpec, + use_eagle: bool, alignment_tokens: int, dcp_world_size: int = 1, pcp_world_size: int = 1, - use_eagle: bool = False, - drop_eagle_block: bool = False, ) -> tuple[list[KVCacheBlock], ...]: - # vLLM B renamed ``use_eagle`` to ``drop_eagle_block``; accept both. - eagle_drop = use_eagle if vllm_version_is("0.22.1") else drop_eagle_block # assert isinstance( # kv_cache_spec, Compress4AttentionSpec | Compress128AttentionSpec | C4IndexerSpec # ), ( # "CompressAttentionManager can only be used for compressor attention groups" # ) computed_blocks: tuple[list[KVCacheBlock], ...] = tuple([] for _ in range(len(kv_cache_group_ids))) - block_size = kv_cache_spec.block_size + block_size = kv_cache_spec.block_size * kv_cache_spec.compress_ratio if dcp_world_size * pcp_world_size > 1: block_size *= dcp_world_size * pcp_world_size - logical_block_size = block_size * kv_cache_spec.compress_ratio - logical_block_hashes = BlockHashListWithBlockSize(block_hashes, block_size, logical_block_size) - max_num_blocks = max_length // logical_block_size - for block_hash in itertools.islice(logical_block_hashes, max_num_blocks): + max_num_blocks = max_length // block_size + for block_hash in itertools.islice(block_hashes, max_num_blocks): # block_hashes is a chain of block hashes. If a block hash is not # in the cached_block_hash_to_id, the following block hashes are # not computed yet for sure. @@ -227,14 +211,16 @@ def find_longest_cache_hit( computed.append(cached) else: break - if eagle_drop and computed_blocks[0]: + if use_eagle and computed_blocks[0]: # Need to drop the last matched block if eagle is enabled. for computed in computed_blocks: computed.pop() + # NOTE: Div the compress ratio when finding the longest cache hit token length. + alignment_tokens = cdiv(alignment_tokens, kv_cache_spec.compress_ratio) while ( - logical_block_size != alignment_tokens # Faster for common case. - and len(computed_blocks[0]) * logical_block_size % alignment_tokens != 0 + block_size != alignment_tokens # Faster for common case. + and len(computed_blocks[0]) * block_size % alignment_tokens != 0 ): for computed in computed_blocks: computed.pop() @@ -264,15 +250,7 @@ def get_manager_for_kv_cache_spec( this value matches the pool sizer and makes admission consistent with the block budget actually held. """ - if vllm_version_is("0.22.1"): - from vllm.v1.core.single_type_kv_cache_manager import spec_manager_map # type: ignore[import-not-found] - - manager_class = spec_manager_map[type(kv_cache_spec)] - else: - from vllm.v1.kv_cache_spec_registry import KVCacheSpecRegistry # type: ignore[import-not-found] - - manager_class = KVCacheSpecRegistry.get_manager_class(kv_cache_spec) - assert manager_class is not None, f"No KV cache manager registered for {type(kv_cache_spec).__name__}" + manager_class = spec_manager_map[type(kv_cache_spec)] if isinstance(kv_cache_spec, MLAAttentionSpec) and kv_cache_spec.compress_ratio > 1: manager_class = CompressAttentionManager if max_model_len is not None: diff --git a/vllm_ascend/cpu_binding.py b/vllm_ascend/cpu_binding.py index 5a947c7bd..7b815e313 100644 --- a/vllm_ascend/cpu_binding.py +++ b/vllm_ascend/cpu_binding.py @@ -7,14 +7,12 @@ from collections import defaultdict import psutil -import regex as re from vllm.logger import logger from vllm_ascend.utils import AscendDeviceType, get_ascend_device_type MASK_BIT = 32 # Number of bits in a CPU affinity mask group MIN_CPUS_PER_NPU = 5 # 2(IRQ) + 1(main, at least 1 CPU) + 1(acl) + 1(release) = 5 CPUs per NPU -MIN_CPUS_PER_NPU_WITHOUT_IRQ = 3 # 1(main, at least 1 CPU) + 1(acl) + 1(release) ALLOWED_CPUS_PATH = "/proc/self/status" ASCEND_RT_VISIBLE_DEVICES = os.getenv("ASCEND_RT_VISIBLE_DEVICES") @@ -25,9 +23,7 @@ AscendDeviceType.A2: TOPO_AFFINITY_MODE, AscendDeviceType.A3: GLOBAL_SLICE_MODE, AscendDeviceType._310P: TOPO_AFFINITY_MODE, - AscendDeviceType.A5: GLOBAL_SLICE_MODE, } -NO_IRQ_BINDING_DEVICE_TYPES = {AscendDeviceType.A5} def is_arm_cpu() -> bool: @@ -67,14 +63,6 @@ def __init__(self): self.all_logic_npus: list[int] = self.get_all_logic_npus() self.total_logic_npus: int = len(self.all_logic_npus) - @staticmethod - def split_npu_smi_header(line: str) -> list[str]: - return [item.strip() for item in re.split(r"\s{2,}", line.strip()) if item.strip()] - - @staticmethod - def is_cpu_list(cpu_list_str: str) -> bool: - return bool(re.fullmatch(r"\d+(?:-\d+)?(?:,\s*\d+(?:-\d+)?)*", cpu_list_str)) - @staticmethod def expand_cpu_list(allowed_list_str: str) -> list[int]: allowed_cpus_list: list[int] = [] @@ -104,25 +92,9 @@ def get_all_logic_npus(self) -> list[int]: def get_npu_map_info() -> dict[str, dict[str, str]]: npu_map_info: dict[str, dict[str, str]] = {} npu_info, _ = execute_command(["npu-smi", "info", "-m"]) - npu_map = [line.strip() for line in npu_info.splitlines() if line.strip()] - - header = DeviceInfo.split_npu_smi_header(npu_map[0]) - npu_id_idx = header.index("NPU ID") - chip_id_idx = header.index("Chip ID") if "Chip ID" in header else None - chip_logic_id_idx = header.index("Chip Logic ID") if "Chip Logic ID" in header else None - - for line in npu_map[1:]: - parts = line.split() - npu_id = parts[npu_id_idx] - - chip_id = "0" - if chip_id_idx is not None: - chip_id = parts[chip_id_idx] - - if chip_logic_id_idx is not None: - chip_logic_id = parts[chip_logic_id_idx] - else: - chip_logic_id = npu_id + npu_map = npu_info.strip().split("\n")[1:] + for line in npu_map: + npu_id, chip_id, chip_logic_id = line.strip().split()[:3] if not chip_logic_id.isdigit(): continue if npu_id not in npu_map_info: @@ -130,31 +102,13 @@ def get_npu_map_info() -> dict[str, dict[str, str]]: npu_map_info[npu_id][chip_id] = chip_logic_id return npu_map_info - def resolve_logic_id(self, npu_id: str, chip_id: str | None) -> int: - chip_map = self.npu_map_info.get(npu_id, {}) - if chip_id is not None: - chip_logic_id = chip_map.get(chip_id) - elif len(chip_map) == 1: - chip_logic_id = next(iter(chip_map.values())) - else: - raise RuntimeError( - "Failed to resolve chip_logic_id because the process table does not contain chip_id " - f"and NPU {npu_id} has {len(chip_map)} mapped chips." - ) - if not chip_logic_id or not chip_logic_id.isdigit(): - raise RuntimeError("Failed to get correct chip_logic_id from command 'npu-smi info -m'.") - return int(chip_logic_id) - def get_running_npus(self) -> list[int]: npu_message, _ = execute_command(["npu-smi", "info"]) in_proc_section = False - proc_npu_field = "" running_npu_set = set() for line in npu_message.splitlines(): line = line.strip() if line.startswith("| NPU") and "Process id" in line: - parts = [p.strip() for p in line.strip("|").split("|")] - proc_npu_field = " ".join(parts[0].split()) in_proc_section = True continue if not in_proc_section: @@ -163,14 +117,14 @@ def get_running_npus(self) -> list[int]: parts = [p.strip() for p in line.strip("|").split("|")] if len(parts) < 2: continue - npu_chip_parts = parts[0].split() - npu_id = npu_chip_parts[0] - chip_id = None - if proc_npu_field == "NPU Chip": - chip_id = npu_chip_parts[1] - if not npu_id.isdigit() or (chip_id is not None and not chip_id.isdigit()): + npu_id = parts[0].split()[0] + chip_id = parts[0].split()[1] + if not npu_id.isdigit() or not chip_id.isdigit(): continue - running_npu_set.add(self.resolve_logic_id(npu_id, chip_id)) + chip_logic_id = self.npu_map_info.get(npu_id, {}).get(chip_id) + if not chip_logic_id or not chip_logic_id.isdigit(): + raise RuntimeError("Failed to get correct chip_logic_id from command 'npu-smi info -m'.") + running_npu_set.add(int(chip_logic_id)) if ASCEND_RT_VISIBLE_DEVICES: devices_str = ASCEND_RT_VISIBLE_DEVICES devices_list = [int(x) for x in devices_str.split(",")] @@ -189,17 +143,16 @@ def parse_allowed_cpus(self) -> list[int]: raise RuntimeError("Can not found specific 'Cpus_allowed_list' in the '/proc/self/status' file.") def parse_topo_affinity(self) -> dict[int, list[int]]: + chip_logic_id = 0 affinity: dict[int, list[int]] = {} affinity_message, _ = execute_command(["npu-smi", "info", "-t", "topo"]) for line in affinity_message.splitlines(): if line.startswith("NPU"): parts = line.split() - npu_match = re.fullmatch(r"NPU(\d+)", parts[0]) - if not npu_match: - continue last_part = parts[-1] - if self.is_cpu_list(last_part): - affinity[int(npu_match.group(1))] = self.expand_cpu_list(last_part) + if last_part != "Affinity": + affinity[chip_logic_id] = self.expand_cpu_list(last_part) + chip_logic_id += 1 return affinity @@ -305,7 +258,7 @@ def build_global_slice_cpu_pool(self) -> None: Notes: - This strategy does NOT rely on npu-smi topo affinity. - NUMA locality is achieved only if CPU numbering aligns with NUMA layout. - - Requires enough CPUs for each device's role split. + - Requires per-NPU slice size >= 5 (IRQ(2) + main(>=1) + acl(1) + release(1)). """ running = list(self.device_info.running_npu_list) if not running: @@ -342,16 +295,15 @@ def build_global_slice_cpu_pool(self) -> None: allowed[-16:], ) - min_cpus_per_npu = self._min_cpus_per_npu() - # Enforce the minimum per-NPU slice length. + # Enforce per-NPU slice length >= 5. # Because with remainder distribution, some NPUs may get 'base' cores and some get 'base+1'. # The minimum slice size is 'base'. - if base < min_cpus_per_npu: + if base < MIN_CPUS_PER_NPU: raise RuntimeError( - "Insufficient CPUs for binding with CPU role reservations: " + "Insufficient CPUs for binding with IRQ/ACL/REL reservations: " f"total_allowed={total_cpu}, total_npus={total_npus}, " - f"min_per_npu={base} (<{min_cpus_per_npu}). " - f"Need at least {total_npus * min_cpus_per_npu} CPUs in cpuset." + f"min_per_npu={base} (<{MIN_CPUS_PER_NPU}). " + f"Need at least {total_npus * MIN_CPUS_PER_NPU} CPUs in cpuset." ) def _slice_for_npu(global_npu_id: int) -> list[int]: @@ -372,16 +324,6 @@ def _binding_mode() -> str: device_type = get_ascend_device_type() return DEVICE_BINDING_MODE.get(device_type, TOPO_AFFINITY_MODE) - @staticmethod - def _reserve_irq_cpus() -> bool: - return get_ascend_device_type() not in NO_IRQ_BINDING_DEVICE_TYPES - - @staticmethod - def _min_cpus_per_npu() -> int: - if CpuAlloc._reserve_irq_cpus(): - return MIN_CPUS_PER_NPU - return MIN_CPUS_PER_NPU_WITHOUT_IRQ - def build_cpu_pools(self) -> None: self.build_cpu_node_map() @@ -432,19 +374,14 @@ def build_cpu_pools(self) -> None: self.npu_cpu_pool = {npu: final[npu] for npu in self.device_info.running_npu_list} def allocate(self) -> None: - reserve_irq_cpus = self._reserve_irq_cpus() - min_cpus_per_npu = self._min_cpus_per_npu() for npu, pool in self.npu_cpu_pool.items(): - if len(pool) >= min_cpus_per_npu: - if reserve_irq_cpus: - main = pool[2:-2] - else: - main = pool[:-2] + if len(pool) >= MIN_CPUS_PER_NPU: + main = pool[2:-2] acl = [pool[-2]] rel = [pool[-1]] else: raise RuntimeError( - f"The number of CPUs is insufficient. Each NPU requires at least {min_cpus_per_npu} CPUs." + f"The number of CPUs is insufficient. Each NPU requires at least {MIN_CPUS_PER_NPU} CPUs." ) self.assign_main[npu] = main self.assign_acl[npu] = acl @@ -506,10 +443,6 @@ def bind_threads(self) -> None: self.bind_memory(main_pid, current_npu) def bind_npu_irq(self) -> None: - if not self._reserve_irq_cpus(): - logger.info("[irq] IRQ binding skipped on Ascend 950.") - return - if not os.access("/proc/irq", os.W_OK): return diff --git a/vllm_ascend/device/device_op.py b/vllm_ascend/device/device_op.py index 8de0d5839..992c02491 100644 --- a/vllm_ascend/device/device_op.py +++ b/vllm_ascend/device/device_op.py @@ -20,7 +20,6 @@ import torch import torch.nn.functional as F import torch_npu -from vllm.triton_utils import HAS_TRITON from vllm_ascend.device.mxfp_compat import ( FLOAT8_E8M0FNU_DTYPE, @@ -33,11 +32,6 @@ from vllm_ascend.quantization.quant_type import QuantType from vllm_ascend.utils import AscendDeviceType, get_ascend_device_type -if HAS_TRITON: - from vllm_ascend.ops.triton.rms_norm import triton_q_rms # noqa: F811 -else: - triton_q_rms = None # type: ignore - class BaseDeviceAdaptor: @classmethod @@ -117,7 +111,7 @@ def npu_dynamic_quant( raise RuntimeError("MXFP MoE quantization is only supported on Ascend A5.") if dynamic_scale is None: - return torch_npu.npu_dynamic_quant(hidden_states, dst_type=act_quant_type) + return torch_npu.npu_dynamic_quant(hidden_states) return hidden_states, dynamic_scale @@ -467,22 +461,7 @@ def execute_sparse_flash_attention_process( ) return attn_output - @staticmethod def npu_flash_attention(query, key, value, seq_lens_cpu, head_num, scale_value, num_kv_heads): - if query.dtype == torch.float32: - # _npu_flash_attention_unpad does not support FP32. - cumulative_seq_lens = seq_lens_cpu.cumsum(0).tolist() - return torch_npu.npu_fusion_attention( - query=query, - key=key, - value=value, - actual_seq_qlen=cumulative_seq_lens, - actual_seq_kvlen=cumulative_seq_lens, - head_num=head_num, - scale=scale_value, - input_layout="TND", - )[0] - context_layer = torch.empty_like(query) torch_npu._npu_flash_attention_unpad( @@ -614,14 +593,13 @@ def prepare_dsa_indexer_key_scale(indexer_scale_cache): def apply_dsa_q_rms(q, eps, q_norm_without_weight=None): """Apply Q RMS norm. Non-A5: triton_q_rms. A5: uses q_norm_without_weight callable when provided.""" - if triton_q_rms is not None: + from vllm.triton_utils import HAS_TRITON + + if HAS_TRITON: + from vllm_ascend.ops.triton.rms_norm import triton_q_rms + return triton_q_rms(q, eps) - else: - dtype = q.dtype - q = q.float() - variance = q.square().mean(-1, keepdim=True) - q = q * torch.rsqrt(variance + eps) - return q.to(dtype) + return q # ===== KV Cache Helpers ===== @@ -750,7 +728,7 @@ def npu_gemma_rms_norm(x, weight, variance_epsilon): @staticmethod def fused_gdn_gating(A_log: torch.Tensor, a: torch.Tensor, b: torch.Tensor, dt_bias: torch.Tensor): - return torch.ops._C_ascend.npu_fused_gdn_gating(A_log, a, b, dt_bias.to(A_log.dtype)) + return torch.ops._C_ascend.npu_fused_gdn_gating(A_log, a, b, dt_bias.to(torch.float32)) @staticmethod def split_qkv_rmsnorm_rope( @@ -896,28 +874,16 @@ def npu_grouped_matmul_swiglu_quant( mxfp_quant_dtype: QuantType | None = None, ): if not use_mxfp_quant: - if act_quant_type == torch.float8_e4m3fn: - out, out_scale = torch_npu.npu_grouped_matmul_swiglu_quant_v2( - x=x, - weight=[weight], - weight_scale=[weight_scale], - x_scale=x_scale, - group_list=group_list, - quant_dtype=torch.float8_e4m3fn, - dequant_dtype=torch.float32, - ) - return out, out_scale, None - else: - return torch_npu.npu_grouped_matmul_swiglu_quant_v2( - x=x, - weight=weight, - group_list=group_list, - weight_scale=weight_scale, - x_scale=x_scale, - bias=bias, - swiglu_limit=swiglu_limit, - use_mxfp_quant=False, - ) + return torch_npu.npu_grouped_matmul_swiglu_quant_v2( + x=x, + weight=weight, + group_list=group_list, + weight_scale=weight_scale, + x_scale=x_scale, + bias=bias, + swiglu_limit=swiglu_limit, + use_mxfp_quant=False, + ) # W4A8 mxfp if mxfp_quant_dtype == QuantType.W4A8MXFP: @@ -1021,8 +987,6 @@ def npu_grouped_matmul_gmm2( mxfp_quant_dtype: QuantType | None = None, ) -> torch.Tensor: if not use_mxfp_quant: - if act_quant_type == torch.float8_e4m3fn: - fallback_output_dtype = torch.bfloat16 return BaseDeviceAdaptor.npu_grouped_matmul_gmm2( hidden_states=hidden_states, weight=weight, @@ -1258,15 +1222,13 @@ def apply_dsa_q_rms(q, eps, q_norm_without_weight=None): """Apply Q RMS norm. A5: uses q_norm_without_weight callable.""" if q_norm_without_weight is not None: return q_norm_without_weight(q) + from vllm.triton_utils import HAS_TRITON + + if HAS_TRITON: + from vllm_ascend.ops.triton.rms_norm import triton_q_rms - if triton_q_rms is not None: return triton_q_rms(q, eps) - else: - dtype = q.dtype - q = q.float() - variance = q.square().mean(-1, keepdim=True) - q = q * torch.rsqrt(variance + eps) - return q.to(dtype) + return q # ===== KV Cache Helpers ===== @@ -1561,16 +1523,15 @@ def execute_sparse_flash_attention_process( ) return attn_output - @staticmethod def npu_flash_attention(query, key, value, seq_lens_cpu, head_num, scale_value, num_kv_heads): - cumulative_seq_lens = seq_lens_cpu.cumsum(0).tolist() + seq_lens_cpu = list(seq_lens_cpu.cumsum(0)) context_layer = torch_npu.npu_fusion_attention( query=query, key=key, value=value, - actual_seq_qlen=cumulative_seq_lens, - actual_seq_kvlen=cumulative_seq_lens, + actual_seq_qlen=seq_lens_cpu, + actual_seq_kvlen=seq_lens_cpu, head_num=head_num, scale=scale_value, input_layout="TND", diff --git a/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_connector.py b/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_connector.py index 0ce4477ab..9ce48a844 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_connector.py +++ b/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_connector.py @@ -35,6 +35,8 @@ SupportsHMA, ) from vllm.distributed.parallel_state import ( + get_decode_context_model_parallel_rank, + get_decode_context_model_parallel_world_size, get_pp_group, get_tensor_model_parallel_rank, get_tensor_model_parallel_world_size, @@ -42,14 +44,12 @@ ) from vllm.distributed.utils import get_pp_indices from vllm.logger import logger -from vllm.utils.math_utils import cdiv from vllm.utils.network_utils import get_ip, make_zmq_path, make_zmq_socket from vllm.v1.core.sched.output import SchedulerOutput from vllm.v1.kv_cache_interface import ( FullAttentionSpec, KVCacheConfig, MambaSpec, - SlidingWindowSpec, UniformTypeKVCacheSpecs, ) from vllm.v1.request import RequestStatus @@ -57,16 +57,7 @@ from vllm_ascend import envs as ascend_envs from vllm_ascend.ascend_config import get_ascend_config, init_ascend_config from vllm_ascend.distributed.kv_transfer.utils.mooncake_transfer_engine import global_te -from vllm_ascend.distributed.kv_transfer.utils.utils import ( - RegisterRegions, - collect_storage_merged_register_regions, - get_transfer_timeout_value, - validate_register_region_count, -) -from vllm_ascend.distributed.utils import ( - get_decode_context_model_parallel_rank, - get_decode_context_model_parallel_world_size, -) +from vllm_ascend.distributed.kv_transfer.utils.utils import get_transfer_timeout_value from vllm_ascend.utils import enable_custom_op # isort: off @@ -95,7 +86,6 @@ class MooncakeAgentMetadata(msgspec.Struct, omit_defaults=True, dict=True): block_size_scale: list[list[int]] num_blocks: int block_lens: list[list[int]] - block_strides: list[list[int]] local_ip: str = "" @@ -125,13 +115,6 @@ class GroupPull: is_group_transfer_end: bool = False -@dataclass(frozen=True) -class GroupTransferInfo: - tokens_per_block: int - blocks_per_window: int - is_state_group: bool - - @dataclass class SizedDict(OrderedDict): def __init__(self, max_size=16000, *args, **kwargs): @@ -219,7 +202,7 @@ def _retrieve_expired_requests(self): self.delayed_free_requests.popitem(last=False) self.reqs_to_process.discard(request_id) expired_requests.add(request_id) - logger.error( + logger.info( "Force freed expired request: %s. " "Reason: Request exceeded timeout threshold (%s seconds). " "Action: Resources have been forcibly released to prevent memory leak.", @@ -406,7 +389,6 @@ def __init__( side_channel_port: int, local_kv_caches_base_addr: list[list[int]], block_len_per_addr: list[list[int]], - block_stride_per_addr: list[list[int]], is_hma_required=False, ready_event: threading.Event | None = None, vllm_config: VllmConfig | None = None, @@ -433,23 +415,11 @@ def __init__( self.kv_caches_base_addr: dict[str, dict[int, list[list[int]]]] = SizedDict() self.kv_caches_base_addr[local_engine_id][local_handshake_port] = local_kv_caches_base_addr self.block_len_per_addr = block_len_per_addr - self.block_stride_per_addr = block_stride_per_addr if kv_group2layeridx is None: kv_group2layeridx = {} self.kv_group2layeridx = kv_group2layeridx - self.group_compress_ratios: dict[int, int] = {} - for group_id, (group_spec, _) in self.kv_group2layeridx.items(): - compress_ratio = 1 - kv_cache_spec = group_spec.get("kv_cache_spec") - if isinstance(kv_cache_spec, dict): - for spec in kv_cache_spec.values(): - if isinstance(spec, dict) and isinstance(spec.get("compress_ratio"), int): - compress_ratio = max(1, spec["compress_ratio"]) - break - self.group_compress_ratios[group_id] = compress_ratio self.remote_te_port: dict[str, dict[int, int]] = SizedDict() self.remote_block_size_scale: dict[str, dict[int, list[list[int]]]] = SizedDict() - self.remote_block_stride_per_addr: dict[str, dict[int, list[list[int]]]] = SizedDict() self.remote_kv_group2layeridx: dict[str, dict[int, dict[int, tuple[dict[str, Any], list[int]]]]] = SizedDict() self.request_queue: queue.Queue[Any] = queue.Queue() @@ -564,10 +534,10 @@ def _is_failed_recv_request(self, request_id: str) -> bool: with self.failed_recv_requests_lock: return request_id in self.failed_recv_requests - def _mark_failed_recv_request(self, request_id: str, local_block_ids: BlockIds) -> None: + def _mark_failed_recv_request(self, request_id: str, local_block_ids: list[int]) -> None: with self.failed_recv_requests_lock: self.failed_recv_requests.add(request_id) - self.invalid_block_ids.update(local_block_ids[0]) + self.invalid_block_ids.update(local_block_ids) def _clear_failed_recv_request(self, request_id: str) -> None: with self.failed_recv_requests_lock: @@ -662,7 +632,6 @@ def _transfer_kv_cache_all_groups(self, req_meta: dict[str, Any]): local_kv_caches_base_addrs = self.kv_caches_base_addr[self.local_engine_id][self.local_handshake_port] remote_transfer_port = self.remote_te_port[remote_engine_id][remote_handshake_port] remote_block_size_scale = self.remote_block_size_scale[remote_engine_id][remote_handshake_port] - remote_block_stride_per_addr = self.remote_block_stride_per_addr[remote_engine_id][remote_handshake_port] session_id = f"{remote_host}:{remote_transfer_port}" req_start_time = time.perf_counter() @@ -702,8 +671,7 @@ def pp_layer_indices(layer_indices: list[int], prefill_pp_rank: int) -> list[int # For FullAttentionSpec prefix cache with hybrid kernel blocks. num_computed_tokens = req_meta.get("num_computed_tokens", 0) remote_kernel_block_size = self.block_size // remote_scale - remote_kernel_token_size = remote_kernel_block_size * self.group_compress_ratios[group_idx] - remote_start_idx = num_computed_tokens // remote_kernel_token_size + remote_start_idx = num_computed_tokens // remote_kernel_block_size kernel_remote_block_ids = kernel_remote_block_ids[remote_start_idx:] num_kernel_blocks = min(len(kernel_remote_block_ids), len(kernel_local_block_ids)) kernel_remote_block_ids = kernel_remote_block_ids[:num_kernel_blocks] @@ -723,8 +691,9 @@ def pp_layer_indices(layer_indices: list[int], prefill_pp_rank: int) -> list[int ) ) else: - # When Prefix Caching is enabled on both P and D nodes, num_block should not be forced to match, - # as the D-node requires dynamic allocation based on its specific cache hit rate. + # For MambaSpec num block should equal on P node and D node + if len(local_group_block_ids) != len(remote_group_block_ids): + raise RuntimeError("For MambaSpec num block should equal on P node and D node.") transfer_block_idx = len(remote_group_block_ids) - self.num_speculative_tokens - 1 grouped_remote_block_ids = [[remote_group_block_ids[transfer_block_idx]]] grouped_local_block_ids = [[local_group_block_ids[0]]] @@ -740,8 +709,6 @@ def pp_layer_indices(layer_indices: list[int], prefill_pp_rank: int) -> list[int src_layer_base_addr=local_kv_caches_base_addrs[layer_idx], dst_layer_base_addr=remote_kv_caches_base_addrs[layer_idx], block_len=self.block_len_per_addr[layer_idx], - block_stride=self.block_stride_per_addr[layer_idx], - remote_block_stride=remote_block_stride_per_addr[layer_idx], remote_block_id=grouped_remote_block_ids[0][0], local_block_id=grouped_local_block_ids[0][0], tp_num_need_pulls=tp_num_need_pulls, @@ -771,19 +738,10 @@ def pp_layer_indices(layer_indices: list[int], prefill_pp_rank: int) -> list[int src_layer_base_addr = local_kv_caches_base_addrs[layer_idx][cache_idx] dst_layer_base_addr = remote_kv_caches_base_addrs[layer_idx][cache_idx] block_len = self.block_len_per_addr[layer_idx][cache_idx] - block_stride = self.block_stride_per_addr[layer_idx][cache_idx] - remote_block_stride = remote_block_stride_per_addr[layer_idx][cache_idx] inner_block_len = block_len // tp_num_need_pulls - transfer_remote_block_ids, transfer_local_block_ids = split_if_not_byte_contiguous( - grouped_remote_block_ids, - grouped_local_block_ids, - src_block_stride=remote_block_stride, - dst_block_stride=block_stride, - block_len=inner_block_len, - ) - for remote_block_id, local_block_id in zip(transfer_remote_block_ids, transfer_local_block_ids): - src = src_layer_base_addr + local_block_id[0] * block_stride + inner_offset * inner_block_len - dst = dst_layer_base_addr + remote_block_id[0] * remote_block_stride + for remote_block_id, local_block_id in zip(grouped_remote_block_ids, grouped_local_block_ids): + src = src_layer_base_addr + local_block_id[0] * block_len + inner_offset * inner_block_len + dst = dst_layer_base_addr + remote_block_id[0] * inner_block_len length = inner_block_len * len(local_block_id) src_list.append(src) dst_list.append(dst) @@ -926,8 +884,6 @@ def _append_mamba_transfer_meta( src_layer_base_addr: list[int], dst_layer_base_addr: list[int], block_len: list[int], - block_stride: list[int], - remote_block_stride: list[int], remote_block_id: int, local_block_id: int, tp_num_need_pulls: int, @@ -940,8 +896,6 @@ def _append_mamba_transfer_meta( remote_conv_addr, remote_ssm_addr = dst_layer_base_addr[:2] local_conv_addr, local_ssm_addr = src_layer_base_addr[:2] local_conv_len, local_ssm_len = block_len[:2] - local_conv_stride, local_ssm_stride = block_stride[:2] - remote_conv_stride, remote_ssm_stride = remote_block_stride[:2] tp_ratio = tp_num_need_pulls remote_conv_len = local_conv_len // tp_ratio @@ -950,14 +904,14 @@ def _append_mamba_transfer_meta( if tp_ratio == 1: src_list.extend( [ - local_conv_addr + local_block_id * local_conv_stride, - local_ssm_addr + local_block_id * local_ssm_stride, + local_conv_addr + local_block_id * local_conv_len, + local_ssm_addr + local_block_id * local_ssm_len, ] ) dst_list.extend( [ - remote_conv_addr + remote_block_id * remote_conv_stride, - remote_ssm_addr + remote_block_id * remote_ssm_stride, + remote_conv_addr + remote_block_id * remote_conv_len, + remote_ssm_addr + remote_block_id * remote_ssm_len, ] ) length_list.extend([remote_conv_len, remote_ssm_len]) @@ -992,14 +946,14 @@ def _append_mamba_transfer_meta( local_addr_offset = ( (i * remote_conv_width + remote_conv_offset) * tp_ratio + remote_tp_offset * remote_conv_size ) * conv_dtype_size - src_list.append(local_conv_addr + local_block_id * local_conv_stride + local_addr_offset) - dst_list.append(remote_conv_addr + remote_block_id * remote_conv_stride + remote_addr_offset) + src_list.append(local_conv_addr + local_block_id * local_conv_len + local_addr_offset) + dst_list.append(remote_conv_addr + remote_block_id * remote_conv_len + remote_addr_offset) length_list.append(remote_conv_size * conv_dtype_size) src_list.append( - local_ssm_addr + local_block_id * local_ssm_stride + remote_tp_offset * local_ssm_len // tp_num_need_pulls + local_ssm_addr + local_block_id * local_ssm_len + remote_tp_offset * local_ssm_len // tp_num_need_pulls ) - dst_list.append(remote_ssm_addr + remote_block_id * remote_ssm_stride) + dst_list.append(remote_ssm_addr + remote_block_id * remote_ssm_len) length_list.append(remote_ssm_len) def _get_group_kv_caches(self, group_idx: int, layer_indices: list[int] | None = None) -> dict[str, Any]: @@ -1195,7 +1149,6 @@ def _get_remote_metadata(self, remote_host: str, remote_handshake_port: int) -> self.kv_caches_base_addr[engine_id][remote_handshake_port] = agent_meta.kv_caches_base_addr self.remote_te_port[engine_id][remote_handshake_port] = agent_meta.te_rpc_port self.remote_block_size_scale[engine_id][remote_handshake_port] = agent_meta.block_size_scale - self.remote_block_stride_per_addr[engine_id][remote_handshake_port] = agent_meta.block_strides finally: if sock is not None: self._return_remote_socket(sock, remote_host, remote_handshake_port) @@ -1454,119 +1407,6 @@ def __init__(self, vllm_config: VllmConfig, engine_id: str, kv_cache_config: KVC # master-slave meta information for cross-nodes self.multi_nodes_meta_mapping: dict[str, dict[str, Any]] = {} self.kv_cache_groups = kv_cache_config.kv_cache_groups - self.use_hybrid = ( - not vllm_config.scheduler_config.disable_hybrid_kv_cache_manager - and any(not isinstance(g.kv_cache_spec, FullAttentionSpec) for g in kv_cache_config.kv_cache_groups) - and len(kv_cache_config.kv_cache_groups) > 1 - ) - self.use_compress = self._model_uses_compress() - self.group_transfer_info = [self._get_group_transfer_info(group) for group in kv_cache_config.kv_cache_groups] - self.need_truncate = self.use_compress or any(info.is_state_group for info in self.group_transfer_info) - - def _model_uses_compress(self) -> bool: - hf_config = getattr(self.vllm_config.model_config, "hf_config", None) - compress_ratios = getattr(hf_config, "compress_ratios", None) - return isinstance(compress_ratios, (list, tuple, dict)) - - def _get_group_transfer_info(self, group: Any) -> GroupTransferInfo: - specs = self._get_group_unique_specs(group) - first_spec = specs[0] if specs else group.kv_cache_spec - block_size = getattr(group.kv_cache_spec, "block_size", getattr(first_spec, "block_size", self.block_size)) - is_state_group = any(isinstance(spec, MambaSpec) for spec in specs) - sliding_window = 0 - compress_ratio = 1 - for spec in specs: - if isinstance(spec, SlidingWindowSpec): - sliding_window = spec.sliding_window - elif hasattr(spec, "compress_ratio"): - compress_ratio = spec.compress_ratio - - return GroupTransferInfo( - tokens_per_block=block_size * max(1, int(compress_ratio)), - blocks_per_window=cdiv(sliding_window, block_size) + 1 if sliding_window else 0, - is_state_group=is_state_group, - ) - - def _get_group_unique_specs(self, group: Any) -> list[Any]: - if not isinstance(group.kv_cache_spec, UniformTypeKVCacheSpecs): - return [group.kv_cache_spec] - - specs = [] - for layer_name in group.layer_names: - layer_spec = group.kv_cache_spec.kv_cache_specs[layer_name] - if layer_spec not in specs: - specs.append(layer_spec) - return specs - - def _get_transfer_block_ids(self, block_ids: BlockIds, prompt_len: int) -> BlockIds: - """Return blocks that contain prompt KV, dropping MTP extra blocks. - - State groups such as Mamba are not context-block aligned with attention - KV, so keep them unchanged and only clip attention-like groups here. - SWA tail clipping is handled as a separate step after this. - """ - if len(block_ids) == 0: - return block_ids - - assert len(block_ids) == len(self.group_transfer_info), "Number of KV cache groups must match" - - transfer_block_ids = [] - for blocks, group_info in zip(block_ids, self.group_transfer_info): - if group_info.is_state_group: - transfer_block_ids.append(blocks) - else: - num_prompt_blocks = cdiv(prompt_len, group_info.tokens_per_block) - transfer_block_ids.append(blocks[:num_prompt_blocks]) - return tuple(transfer_block_ids) - - def _get_swa_transfer_block_ids(self, block_ids: BlockIds) -> BlockIds: - """Clip SWA groups to their window tail and drop placeholder block 0.""" - if len(block_ids) == 0: - return block_ids - - assert len(block_ids) == len(self.group_transfer_info), "Number of KV cache groups must match" - - transfer_block_ids = [] - for blocks, group_info in zip(block_ids, self.group_transfer_info): - if group_info.is_state_group or group_info.blocks_per_window == 0: - transfer_block_ids.append(blocks) - else: - window_blocks = blocks[-group_info.blocks_per_window :] - transfer_block_ids.append([block_id for block_id in window_blocks if block_id != 0]) - return tuple(transfer_block_ids) - - def _state_prefill_token_count(self, num_prompt_tokens: int) -> int: - """D-side only. Returns N-1 for Mamba models since the decoder - always recomputes the last token and must start from h(N-1).""" - if self.need_truncate and num_prompt_tokens > 1: - return num_prompt_tokens - 1 - return num_prompt_tokens - - def _truncate_request_for_prefill(self, request: "Request") -> None: - """P-side only: drop the last prompt token so the prefiller computes - h(N-1) instead of h(N). The decoder recomputes the last token to - derive h(N) correctly. - - Guarded by ``_p_side_truncated`` to avoid repeated truncation if the - request is preempted and rescheduled.""" - params = request.kv_transfer_params - if ( - params is not None - # Guard against repeated truncation after preemption/reschedule. - and not params.get("_p_side_truncated") - and request.num_prompt_tokens > 1 - ): - if request.prompt_token_ids is not None: - request.prompt_token_ids.pop() - elif request.prompt_embeds is not None: - request.prompt_embeds = request.prompt_embeds[:-1] - else: - return - - request._all_token_ids.pop() - request.num_prompt_tokens -= 1 - request.max_tokens = 1 - params["_p_side_truncated"] = True def get_num_new_matched_tokens(self, request: "Request", num_computed_tokens: int) -> tuple[int, bool]: """ @@ -1593,15 +1433,11 @@ def get_num_new_matched_tokens(self, request: "Request", num_computed_tokens: in if params is not None and params.get("do_remote_prefill"): # Remote prefill: get all prompt blocks from remote. - token_ids = request.prompt_token_ids or [] - actual = self._state_prefill_token_count(len(token_ids)) + assert num_computed_tokens % self.block_size == 0 params["num_computed_tokens"] = num_computed_tokens - count = max(actual - num_computed_tokens, 0) - if count > 0: - return count, True - - if params is not None and params.get("do_remote_decode") and self.need_truncate: - self._truncate_request_for_prefill(request) + # Note: We use the full token count as transmit data here. + count = max(len(request.prompt_token_ids) - num_computed_tokens, 0) + return count, count > 0 # No remote prefill for this request. return 0, False @@ -1679,15 +1515,21 @@ def request_finished( ): return False, None - num_prompt_blocks = math.ceil(len(request.prompt_token_ids) / self.block_size) - computed_block_ids = self._get_transfer_block_ids(block_ids, len(request.prompt_token_ids)) - computed_block_ids = self._get_swa_transfer_block_ids(computed_block_ids) + computed_block_ids = block_ids computed_block_lens = [len(block_id_list) for block_id_list in computed_block_ids] delay_free_blocks = sum(computed_block_lens) > 0 if delay_free_blocks: - logger.info("Delaying free of %d blocks for request %s", sum(computed_block_lens), request.request_id) + logger.info("Delaying free of %d blocks for request %s", len(computed_block_ids), request.request_id) self._reqs_need_send[request.request_id] = time.time() + num_prompt_blocks = math.ceil(len(request.prompt_token_ids) / self.block_size) + computed_block_ids = tuple( + block_ids[:num_prompt_blocks] + if not isinstance(self.kv_cache_groups[i].kv_cache_spec, MambaSpec) + else block_ids + for i, block_ids in enumerate(computed_block_ids) + ) + return delay_free_blocks, dict( do_remote_prefill=True, do_remote_decode=False, @@ -1744,7 +1586,7 @@ def __init__(self, vllm_config: VllmConfig, engine_id: str, kv_cache_config: KVC self.kv_caches: dict[str, torch.Tensor] = {} self.side_channel_host = get_ip() self.pcp_size = get_pcp_group().world_size - self.total_layers = vllm_config.model_config.get_total_num_hidden_layers() + self.total_layers = vllm_config.model_config.get_num_layers(vllm_config.parallel_config) # Assert that pp_size and pcp_size cannot both be greater than 1 assert not (self.pp_size > 1 and self.pcp_size > 1), "pp and pcp cannot open in same time" self.pcp_rank = get_pcp_group().rank_in_group if self.pcp_size > 1 else 0 @@ -1759,11 +1601,6 @@ def __init__(self, vllm_config: VllmConfig, engine_id: str, kv_cache_config: KVC self.kv_cache_config = kv_cache_config self.num_blocks: int = kv_cache_config.num_blocks self.kv_group2layeridx: dict[int, tuple[dict[str, Any], list[int]]] = {} - self.use_hybrid = ( - not self.vllm_config.scheduler_config.disable_hybrid_kv_cache_manager - and any(not isinstance(g.kv_cache_spec, FullAttentionSpec) for g in self.kv_cache_config.kv_cache_groups) - and len(self.kv_cache_config.kv_cache_groups) > 1 - ) self._is_hma_required = not vllm_config.scheduler_config.disable_hybrid_kv_cache_manager and any( not isinstance(g.kv_cache_spec, FullAttentionSpec) for g in kv_cache_config.kv_cache_groups ) @@ -1868,22 +1705,12 @@ def _build_kv_group2layeridx(self) -> dict[int, tuple[dict[str, Any], list[int]] next_mtp_layer_idx = self.total_layers for group_id, group_spec in enumerate(self.kv_cache_config.kv_cache_groups): layer_indices = [] - # For eagle3 method there is no "mtp" in layer names, and upstream model initiation assigns the layer id - # that is sliced by Pipeline Parallel. So the eagle layer id will confilt with target model layers. - # Here we determine whether the current layer is an eagle layer based on whether the layer id has been - # assigned to previous layers. If the layer id has been assigned, we treat the current layer as - # an eagle layer and assign a new layer id starting from total_layers. - assigned_indices: set[int] = set() for layer_name in group_spec.layer_names: if "mtp" in layer_name: layer_idx = next_mtp_layer_idx next_mtp_layer_idx += 1 else: layer_idx = extract_layer_index(layer_name, num_attn_module) - if assigned_indices and layer_idx < min(assigned_indices) or layer_idx in assigned_indices: - layer_idx = next_mtp_layer_idx - next_mtp_layer_idx += 1 - assigned_indices.add(layer_idx) layer_indices.append(layer_idx) kv_group2layeridx[group_id] = (self._serialize_kv_group_spec(group_spec), layer_indices) return kv_group2layeridx @@ -1936,27 +1763,6 @@ def _get_registered_kv_tensor_buffers(self, kv_caches: dict[str, torch.Tensor]) return ptrs, lengths - def _get_registered_kv_tensor_buffers_hybrid( - self, kv_caches: dict[str, torch.Tensor] - ) -> tuple[list[int], list[int]]: - ptrs: list[int] = [] - lengths: list[int] = [] - - for kv_cache_tensor in self.kv_cache_config.kv_cache_tensors: - shared_addrs: list[int] = [] - for layer_name in kv_cache_tensor.shared_by: - for single_kv_cache in self._as_kv_cache_tuple(kv_caches[layer_name]): - shared_addrs.append(single_kv_cache.data_ptr()) - - if not shared_addrs: - continue - base_addr = min(shared_addrs) - assert base_addr % (2 * 1024 * 1024) == 0, f"Tensor start addr {base_addr} is not align with 2M." - ptrs.append(base_addr) - lengths.append(kv_cache_tensor.size) - - return ptrs, lengths - def _get_registered_layer_buffers(self, kv_caches: dict[str, torch.Tensor]) -> tuple[list[int], list[int]]: ptrs: list[int] = [] lengths: list[int] = [] @@ -1995,15 +1801,7 @@ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): # Per-layer byte length of one tensor block: # [layer_idx][cache_idx] -> element_size * prod(block_shape). self.block_len_per_addr: list[list[int]] = [[] for _ in range(metadata_layers)] - # Per-layer full tensor shape for each registered KV cache address: - # [layer_idx][cache_idx] -> cache tensor shape, including num_blocks. - self.block_shape_per_addr: list[list[int]] = [[] for _ in range(metadata_layers)] - # Per-layer byte stride between consecutive tensor blocks: - # [layer_idx][cache_idx] -> stride(0) * element_size. - self.block_stride_per_addr: list[list[int]] = [[] for _ in range(metadata_layers)] - - # TODO: For DSV4 use_compress, metadata/transfer can be optimized by - # aggregating layer views that share the same raw KVCacheTensor. + for layer_name, kv_cache_tuple in kv_caches.items(): layer_idx = layer_name_to_idx[layer_name] for single_kv_cache in self._as_kv_cache_tuple(kv_cache_tuple): @@ -2011,38 +1809,24 @@ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): block_size_scale = tensor_num_blocks // self.num_blocks block_shape = single_kv_cache.shape[1:] self.block_len_per_addr[layer_idx].append(single_kv_cache.element_size() * math.prod(block_shape)) - self.block_stride_per_addr[layer_idx].append(single_kv_cache.stride(0) * single_kv_cache.element_size()) - self.block_shape_per_addr[layer_idx].append(single_kv_cache.shape) self.block_size_scale[layer_idx].append(block_size_scale) self.kv_caches_base_addr[layer_idx].append(single_kv_cache.data_ptr()) if has_mamba_group: ptrs, lengths = self._get_registered_kv_tensor_buffers(kv_caches) - register_regions = RegisterRegions(ptrs=ptrs, lengths=lengths) - elif self.use_hybrid: - ptrs, lengths = self._get_registered_kv_tensor_buffers_hybrid(kv_caches) - register_regions = RegisterRegions(ptrs=ptrs, lengths=lengths) else: - # For normal attention / sparse-c8 KV cache, keep metadata at the - # logical tensor level but merge registration ranges by underlying - # storage to avoid exceeding the HCCL per-process region limit. - register_regions = collect_storage_merged_register_regions(kv_caches) - - validate_register_region_count(register_regions) - global_te.register_buffer(register_regions.ptrs, register_regions.lengths) + ptrs, lengths = self._get_registered_layer_buffers(kv_caches) + global_te.register_buffer(ptrs, lengths) logger.debug( "Mooncake register kv caches metadata: kv_group2layeridx=%s, kv_caches_base_addr=%s, " - "block_len_per_addr=%s, block_stride_per_addr=%s, block_shape_per_addr=%s, " - "block_size_scale=%s, ptrs=%s, lengths=%s", + "block_len_per_addr=%s, block_size_scale=%s, ptrs=%s, lengths=%s", self.kv_group2layeridx, self.kv_caches_base_addr, self.block_len_per_addr, - self.block_stride_per_addr, - self.block_shape_per_addr, self.block_size_scale, - register_regions.ptrs, - register_regions.lengths, + ptrs, + lengths, ) # After KV Caches registered, start the sending or receiving thread. metadata = MooncakeAgentMetadata( @@ -2054,7 +1838,6 @@ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): block_size_scale=self.block_size_scale, num_blocks=self.num_blocks, block_lens=self.block_len_per_addr, - block_strides=self.block_stride_per_addr, local_ip=get_ip(), ) self.xfer_handshake_metadata = metadata @@ -2085,7 +1868,6 @@ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): self.side_channel_port, self.kv_caches_base_addr, self.block_len_per_addr, - self.block_stride_per_addr, self._is_hma_required, ready_event, self.vllm_config, @@ -2742,22 +2524,16 @@ def zmq_ctx(socket_type: Any, addr: str) -> Iterator[zmq.Socket]: # type: ignor def group_concurrent_contiguous( - src: list[int], - dst: list[int], - src_block_stride: int = 1, - dst_block_stride: int = 1, - block_len: int = 1, -) -> tuple[list[list[int]], list[list[int]]]: - """Group block ids that are contiguous in both id space and memory.""" + src: list[int], dst: list[int] +) -> tuple[list[npt.NDArray[np.int64]], list[npt.NDArray[np.int64]]]: + """Vectorised NumPy implementation.""" src_indices: npt.NDArray[np.int64] = np.array(src, dtype=np.int64) dst_indices: npt.NDArray[np.int64] = np.array(dst, dtype=np.int64) if src_indices.size == 0: return [], [] - src_byte_contiguous = np.diff(src_indices) * src_block_stride == block_len - dst_byte_contiguous = np.diff(dst_indices) * dst_block_stride == block_len - brk = np.where(~(src_byte_contiguous & dst_byte_contiguous))[0] + 1 + brk = np.where((np.diff(src_indices) != 1) | (np.diff(dst_indices) != 1))[0] + 1 src_groups = np.split(src_indices, brk) dst_groups = np.split(dst_indices, brk) @@ -2767,27 +2543,6 @@ def group_concurrent_contiguous( return src_groups, dst_groups -def split_if_not_byte_contiguous( - src_groups: list[list[int]], - dst_groups: list[list[int]], - src_block_stride: int, - dst_block_stride: int, - block_len: int, -) -> tuple[list[list[int]], list[list[int]]]: - if src_block_stride == block_len and dst_block_stride == block_len: - return src_groups, dst_groups - - src = [bid for group in src_groups for bid in group] - dst = [bid for group in dst_groups for bid in group] - return group_concurrent_contiguous( - src, - dst, - src_block_stride=src_block_stride, - dst_block_stride=dst_block_stride, - block_len=block_len, - ) - - def string_to_int64_hash(input_str): """ Hash the string using SHA-256 and convert it into an int64 integer. diff --git a/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_hybrid_connector.py b/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_hybrid_connector.py index 925dbb17e..873eb92f8 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_hybrid_connector.py +++ b/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_hybrid_connector.py @@ -1206,15 +1206,17 @@ def _truncate_request_for_prefill(self, request: "Request") -> None: def _compute_transfer_block_ids(self, block_ids: BlockIds, prompt_len: int) -> BlockIds: transfer_block_ids = [] for i, blocks in enumerate(block_ids): - if self.use_compress and self.num_swa_blocks[i] == 0: - group_token_len = prompt_len // self.group_compress_ratio[i] - else: - group_token_len = prompt_len - group_block_len = math.ceil(group_token_len / self.group_block_size[i]) - if group_block_len > 0: - transfer_block_ids.append(blocks[:group_block_len]) + if self.num_swa_blocks[i] == 0: + if self.use_compress: + group_block_len = math.ceil((prompt_len // self.group_compress_ratio[i]) / self.group_block_size[i]) + else: + group_block_len = math.ceil(prompt_len / self.group_block_size[i]) + if group_block_len > 0: + transfer_block_ids.append(blocks[:group_block_len]) + else: + transfer_block_ids.append([]) else: - transfer_block_ids.append([]) + transfer_block_ids.append(blocks) return tuple(transfer_block_ids) def get_num_new_matched_tokens(self, request: "Request", num_computed_tokens: int) -> tuple[int, bool]: @@ -1327,17 +1329,16 @@ def request_finished_all_groups( ): return False, None - # P-side truncation can leave block ids allocated for the original - # prompt length. Drop those unwritten blocks before SWA tail clipping. - computed_block_ids = self._compute_transfer_block_ids(block_ids, request.num_prompt_tokens) - computed_block_ids = self.get_sw_clipped_blocks(computed_block_ids) + computed_block_ids = block_ids computed_block_lens = [len(block_id_list) for block_id_list in computed_block_ids] delay_free_blocks = sum(computed_block_lens) > 0 if delay_free_blocks: - logger.info("Delaying free of %d blocks for request %s", sum(computed_block_lens), request.request_id) + logger.info("Delaying free of %d blocks for request %s", len(computed_block_ids), request.request_id) self._reqs_need_send[request.request_id] = time.time() + computed_block_ids = self.get_sw_clipped_blocks(computed_block_ids) - num_prompt_blocks = math.ceil(request.num_prompt_tokens / self.block_size) + num_prompt_blocks = math.ceil(len(request.prompt_token_ids) / self.block_size) + computed_block_ids = self._compute_transfer_block_ids(computed_block_ids, len(request.prompt_token_ids)) return delay_free_blocks, dict( do_remote_prefill=True, @@ -1540,7 +1541,6 @@ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): if share_tensor_addr: ptrs.append(min(share_tensor_addr)) lengths.append(kv_cache_tensor.size) - self.block_stride_per_addr.extend(self.block_len_per_addr) elif self.use_compress: layer_group_idx = dict[str, int]() for i, group in enumerate(self.kv_cache_config.kv_cache_groups): diff --git a/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_layerwise_connector.py b/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_layerwise_connector.py index eed4f095a..161c8188d 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_layerwise_connector.py +++ b/vllm_ascend/distributed/kv_transfer/kv_p2p/mooncake_layerwise_connector.py @@ -33,6 +33,7 @@ SupportsHMA, ) from vllm.distributed.parallel_state import ( + get_decode_context_model_parallel_rank, get_tensor_model_parallel_rank, get_tp_group, get_world_group, @@ -57,9 +58,7 @@ from vllm_ascend.distributed.kv_transfer.kv_p2p.mooncake_connector import GET_META_MSG from vllm_ascend.distributed.kv_transfer.utils.mooncake_transfer_engine import global_te from vllm_ascend.distributed.kv_transfer.utils.utils import ( - RegisterRegions, align_memory, - collect_storage_merged_register_regions, context_parallel_parameters_check, get_cp_group, get_local_remote_block_port_mappings, @@ -67,9 +66,7 @@ get_transfer_timeout_value, kv_alltoall_and_rearrange, parallel_info, - validate_register_region_count, ) -from vllm_ascend.distributed.utils import get_decode_context_model_parallel_rank from vllm_ascend.utils import npu_stream_switch, trans_nd_to_nz # isort: off @@ -313,8 +310,8 @@ def get_transfer_meta(self, send_task: SendTask, req_id: str, req_meta: ReqMeta, ) dst_list.extend( [ - remote_conv_addr + remote_block_ids[-1] * local_conv_len, - remote_ssm_addr + remote_block_ids[-1] * local_ssm_len, + remote_conv_addr + remote_block_ids[0] * local_conv_len, + remote_ssm_addr + remote_block_ids[0] * local_ssm_len, ] ) length_list.extend([local_conv_len, local_ssm_len]) @@ -349,12 +346,12 @@ def get_transfer_meta(self, send_task: SendTask, req_id: str, req_meta: ReqMeta, src_list.append( local_conv_addr + local_block_ids[transfer_block_idx] * local_conv_len + local_addr_offset ) - dst_list.append(remote_conv_addr + remote_block_ids[-1] * remote_conv_len + remote_addr_offset) + dst_list.append(remote_conv_addr + remote_block_ids[0] * remote_conv_len + remote_addr_offset) length_list.append(local_conv_size * get_dtype_size(conv_dtype)) # ssm remote_addr_offset = (self.tp_rank % tp_ratio) * math.prod(ssm_shape) * get_dtype_size(ssm_dtype) src_list.append(local_ssm_addr + local_block_ids[transfer_block_idx] * local_ssm_len) - dst_list.append(remote_ssm_addr + remote_block_ids[-1] * remote_ssm_len + remote_addr_offset) + dst_list.append(remote_ssm_addr + remote_block_ids[0] * remote_ssm_len + remote_addr_offset) length_list.append(local_ssm_len) else: if self.pd_head_ratio == 1: @@ -808,7 +805,6 @@ def __init__(self, vllm_config: VllmConfig, kv_cache_config: KVCacheConfig, engi # the scheduler. Used to make metadata passed to Worker. self._reqs_need_recv: dict[str, tuple[Request, list[int], list[list[int]]]] = {} self._reqs_need_send_layerwise: dict[str, SendReqInfo] = {} - self.need_truncate = self._has_attn_mamba_hybrid_cache(kv_cache_config) self.executor = ThreadPoolExecutor(32) tls_config: dict[str, Any] = vllm_config.kv_transfer_config.get_from_extra_config("tls_config", {}) ssl_keyfile = tls_config.get("ssl_keyfile") @@ -825,63 +821,6 @@ def __init__(self, vllm_config: VllmConfig, kv_cache_config: KVCacheConfig, engi else: self.metaserver_client = httpx.Client(limits=httpx.Limits(max_connections=100000), timeout=None) - @staticmethod - def _iter_kv_cache_specs(kv_cache_config: KVCacheConfig): - for kv_cache_group in kv_cache_config.kv_cache_groups: - kv_cache_spec = kv_cache_group.kv_cache_spec - if isinstance(kv_cache_spec, UniformTypeKVCacheSpecs): - yield from kv_cache_spec.kv_cache_specs.values() - else: - yield kv_cache_spec - - @classmethod - def _has_attn_mamba_hybrid_cache(cls, kv_cache_config: KVCacheConfig) -> bool: - has_attn = False - has_mamba = False - for kv_cache_spec in cls._iter_kv_cache_specs(kv_cache_config): - has_attn = has_attn or isinstance(kv_cache_spec, AttentionSpec) - has_mamba = has_mamba or isinstance(kv_cache_spec, MambaSpec) - return has_attn and has_mamba - - def _hybrid_prefill_token_count(self, num_prompt_tokens: int) -> int: - if self.need_truncate and num_prompt_tokens > 1: - return num_prompt_tokens - 1 - return num_prompt_tokens - - def _truncate_request_for_hybrid_prefill(self, request: "Request") -> None: - params = request.kv_transfer_params - if ( - params is None - or not self.need_truncate - or params.get("_p_side_truncated") - or getattr(request, "num_prompt_tokens", len(request.prompt_token_ids or [])) <= 1 - ): - return - - if request.prompt_token_ids is not None: - request.prompt_token_ids.pop() - elif request.prompt_embeds is not None: - request.prompt_embeds = request.prompt_embeds[:-1] - else: - return - - request._all_token_ids.pop() - request.num_prompt_tokens -= 1 - request.max_tokens = 1 - params["_p_side_truncated"] = True - - def _trim_hybrid_remote_block_ids(self, block_ids: tuple[list[int], ...], prompt_len: int) -> tuple[list[int], ...]: - if not self.need_truncate or prompt_len <= 1: - return block_ids - - trimmed_block_ids: list[list[int]] = [] - for group_block_ids, block_size in zip(block_ids, self.block_size): - if prompt_len % block_size == 1: - trimmed_block_ids.append(list(group_block_ids[:-1])) - else: - trimmed_block_ids.append(list(group_block_ids)) - return tuple(trimmed_block_ids) - def get_num_new_matched_tokens(self, request: "Request", num_computed_tokens: int) -> tuple[int, bool]: """ For remote prefill, pull all prompt blocks from remote @@ -908,12 +847,10 @@ def get_num_new_matched_tokens(self, request: "Request", num_computed_tokens: in if params is not None and params.get("do_remote_prefill"): # Remote prefill: get all prompt blocks from remote. assert num_computed_tokens % min(self.block_size) == 0 - count = max(self._hybrid_prefill_token_count(len(request.prompt_token_ids)) - num_computed_tokens, 0) + # Note: We use the full token count as transmit data here. + count = max(len(request.prompt_token_ids) - num_computed_tokens, 0) return count, count > 0 - if params is not None and params.get("do_remote_decode"): - self._truncate_request_for_hybrid_prefill(request) - # No remote prefill for this request. return 0, False @@ -928,7 +865,6 @@ def update_state_after_alloc(self, request: "Request", blocks: "KVCacheBlocks", if params is not None and params.get("do_remote_prefill"): do_virtual = params.get("do_virtual", False) local_block_ids = (blocks.get_block_ids()) if num_external_tokens > 0 else [] - remote_block_ids = self._trim_hybrid_remote_block_ids(local_block_ids, len(request.prompt_token_ids)) remote_cached_tokens = request.num_computed_tokens # Get unhashed blocks to pull from remote. logger.debug( @@ -952,7 +888,7 @@ def update_state_after_alloc(self, request: "Request", blocks: "KVCacheBlocks", request_id=external_req_id, do_remote_prefill=False, do_remote_decode=True, - remote_block_ids=remote_block_ids, + remote_block_ids=local_block_ids, remote_block_size=self.block_size, remote_engine_id=self.engine_id, remote_host=self.side_channel_host, @@ -1133,7 +1069,6 @@ def __init__(self, vllm_config: VllmConfig, kv_cache_config: KVCacheConfig, engi self.kv_cache_specs: list[KVCacheSpec] = [spec.kv_cache_spec for spec in self.kv_cache_config.kv_cache_groups] self.local_engine_id: str = " " self.engine_id = engine_id - self.dp_rank: int = vllm_config.parallel_config.data_parallel_rank self.tp_rank: int = get_tensor_model_parallel_rank() self.tp_size: int = vllm_config.parallel_config.tensor_parallel_size self.pcp_size: int = vllm_config.parallel_config.prefill_context_parallel_size @@ -1155,8 +1090,7 @@ def __init__(self, vllm_config: VllmConfig, kv_cache_config: KVCacheConfig, engi # Handshake base port self.side_channel_port = ( vllm_config.kv_transfer_config.kv_port - + self.dp_rank * self.pcp_size * self.tp_size - + self.pcp_rank * self.tp_size + + vllm_config.parallel_config.data_parallel_rank * vllm_config.parallel_config.tensor_parallel_size ) self.handshake_port = self.side_channel_port + self.tp_rank self.sockets: dict = {} @@ -1315,16 +1249,7 @@ def register_kv_caches(self, kv_caches: dict[str, torch.Tensor]): ptrs.append(min(set(tensor_addrs))) lengths.append(kv_cache_tensor.size) - if self.use_attn_mamba_hybrid: - register_regions = RegisterRegions(ptrs=ptrs, lengths=lengths) - else: - # For normal attention / sparse-c8 KV cache, register merged memory - # ranges while keeping layer metadata at logical tensor addresses. - register_regions = collect_storage_merged_register_regions(kv_caches) - - validate_register_region_count(register_regions) - global_te.register_buffer(register_regions.ptrs, register_regions.lengths) - + global_te.register_buffer(ptrs, lengths) if use_kv_buffer: self.create_kv_buffer(kv_buffer) diff --git a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/memcache_backend.py b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/memcache_backend.py index 55751501f..772bbdd6c 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/memcache_backend.py +++ b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/memcache_backend.py @@ -105,37 +105,20 @@ def exists(self, keys: list[str]) -> list[int]: def get(self, key: list[str], addr: list[list[int]], size: list[list[int]]): if self._lazy_init and not self._store_initialized: - logger.error( - "Failed to get %d keys out of %d. Store is not initialized; " - "call put() first to trigger initialization.", - len(key), - len(key), - ) - logger.debug("Failed to get key details. keys=%s", key) + logger.error("get() called before store init. keys=%s. Call put() first to trigger initialization.", key) return assert self.store is not None try: res = self.store.batch_get_into_layers(key, addr, size, MmcDirect.COPY_G2L.value) - failed_codes = [int(value) for value in res if value != 0] - failed_count = len(failed_codes) - if failed_count: - error_codes = sorted(set(failed_codes)) - logger.error( - "Failed to get %d keys out of %d. error_codes=%s. Check key existence and memory state.", - failed_count, - len(key), - error_codes, - ) - logger.debug("Failed to get key details. keys=%s, result=%s", key, res) + for value in res: + if value != 0: + logger.error( + "Failed to get key. keys=%s, result=%s. Check key existence and memory state.", key, res + ) return res except Exception as e: logger.error( - "Failed to get %d keys out of %d. Check store state and network.", - len(key), - len(key), - ) - logger.debug( - "Failed to get key details. keys=%s, type=%s, error=%s", + "Failed to get key. keys=%s, type=%s, error=%s. Check store state and network.", key, type(e).__name__, e, @@ -147,30 +130,14 @@ def put(self, key: list[str], addr: list[list[int]], size: list[list[int]]): self._ensure_initialized() assert self.store is not None res = self.store.batch_put_from_layers(key, addr, size, MmcDirect.COPY_L2G.value) - failed_codes = [int(value) for value in res if value != 0] - failed_count = len(failed_codes) - if failed_count: - error_codes = sorted(set(failed_codes)) - logger.error( - "Failed to put %d keys out of %d. error_codes=%s. Check memory and store capacity.", - failed_count, - len(key), - error_codes, - ) - logger.debug("Failed to put key details. keys=%s, result=%s", key, res) - if self._lazy_init: - logger.warning("First DSV4(compress) request failure is expected. This is normal behavior.") + for value in res: + if value != 0: + logger.error("Failed to put key. keys=%s, result=%s. Check memory and store capacity.", key, res) + if self._lazy_init: + logger.warning("First DSV4(compress) request failure is expected. This is normal behavior.") except Exception as e: logger.error( - "Failed to put %d keys out of %d. Check store state and memory.", - len(key), - len(key), - ) - logger.debug( - "Failed to put key details. keys=%s, type=%s, error=%s", - key, - type(e).__name__, - e, + "Failed to put key. keys=%s, type=%s, error=%s. Check store state and memory.", key, type(e).__name__, e ) if self._lazy_init: logger.warning("First DSV4(compress) request failure is expected. This is normal behavior.") diff --git a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py index 4d6d937ea..5541649a2 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py +++ b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/mooncake_backend.py @@ -18,7 +18,6 @@ from vllm_ascend.distributed.kv_transfer.kv_pool.ascend_store.backend.backend import Backend from vllm_ascend.distributed.kv_transfer.utils.mooncake_transfer_engine import global_te -from vllm_ascend.distributed.parallel_state import get_global_rank DEFAULT_GLOBAL_SEGMENT_SIZE = 1073741824 # 1.0 GiB DEFAULT_LOCAL_BUFFER_SIZE = 1073741824 # 1.0 GiB @@ -101,11 +100,12 @@ def _setup_store(self): store = MooncakeDistributedStore() local_hostname = get_ip() ssd_kwargs = _ssd_setup_kwargs(self.config) + # Each TP rank must use a separate SSD directory to avoid bucket file + # collisions (independent BucketStorageBackend instances generate the + # same bucket_id sequence and would overwrite each other's data). if ssd_kwargs and ssd_kwargs.get("ssd_offload_path"): - # Per-rank SSD directory keyed by the globally unique rank so that - # DP/TP/PP/CP replicas never share a directory (dense and MoE alike). - global_rank = get_global_rank() - rank_path = os.path.join(str(ssd_kwargs["ssd_offload_path"]), f"rank_{global_rank}") + local_rank = get_world_group().local_rank + rank_path = os.path.join(str(ssd_kwargs["ssd_offload_path"]), f"rank_{local_rank}") try: os.makedirs(rank_path, exist_ok=True) except OSError as e: @@ -186,27 +186,14 @@ def put(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): config.preferred_segment = self.local_seg config.prefer_alloc_in_same_node = self.config.prefer_alloc_in_same_node res = self.store.batch_put_from_multi_buffers(keys, addrs, sizes, config) - failed_codes = [int(value) for value in res if value < 0] - failed_count = len(failed_codes) - if failed_count: - error_codes = sorted(set(failed_codes)) - logger.error( - "Failed to put %d keys out of %d. error_codes=%s. Check memory and store capacity.", - failed_count, - len(keys), - error_codes, - ) - logger.debug("Failed to put key details. keys=%s, result=%s", keys, res) - if self._lazy_init: - logger.warning("First DSV4(compress) request failure is expected. This is normal behavior.") + for value in res: + if value < 0: + logger.error("Failed to put key. keys=%s, result=%s. Check memory and store capacity.", keys, res) + if self._lazy_init: + logger.warning("First DSV4(compress) request failure is expected. This is normal behavior.") except Exception as e: logger.error( - "Failed to put %d keys out of %d. Check store state and memory.", - len(keys), - len(keys), - ) - logger.debug( - "Failed to put key details. keys=%s, type=%s, error=%s", + "Failed to put key. keys=%s, type=%s, error=%s. Check store state and memory.", keys, type(e).__name__, e, @@ -216,13 +203,7 @@ def put(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): def get(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): if self._lazy_init and not self._store_initialized: - logger.error( - "Failed to get %d keys out of %d. Store is not initialized; " - "call put() first to trigger initialization.", - len(keys), - len(keys), - ) - logger.debug("Failed to get key details. keys=%s", keys) + logger.error("get() called before store init. keys=%s. Call put() first to trigger initialization.", keys) return assert self.store is not None logger.debug( @@ -233,29 +214,23 @@ def get(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): try: res = self.store.batch_get_into_multi_buffers(keys, addrs, sizes) res_list = list(res) - failed_codes = [int(value) for value in res_list if value < 0] - failed_count = len(failed_codes) - error_codes = sorted(set(failed_codes)) - if failed_count: - logger.error( - "Failed to get %d keys out of %d. error_codes=%s. Check key existence and memory state.", - failed_count, - len(keys), - error_codes, - ) - logger.debug("Failed to get key details. keys=%s, result=%s", keys, res_list) + logger.debug( + "MooncakeBackend.get result keys=%d result_sample=%s negative_count=%d", + len(keys), + res_list[:12], + sum(1 for value in res_list if value < 0), + ) for i, value in enumerate(res_list): - if value > 0: + if value < 0: + logger.error( + "Failed to get key. keys=%s, result=%s. Check key existence and memory state.", keys, res_list + ) + elif value > 0: res_list[i] = 0 return res_list except Exception as e: logger.error( - "Failed to get %d keys out of %d. Check store state and network.", - len(keys), - len(keys), - ) - logger.debug( - "Failed to get key details. keys=%s, type=%s, error=%s", + "Failed to get key. keys=%s, type=%s, error=%s. Check store state and network.", keys, type(e).__name__, e, diff --git a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/yuanrong_backend.py b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/yuanrong_backend.py index fdd1e8b5e..c172ab327 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/yuanrong_backend.py +++ b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/backend/yuanrong_backend.py @@ -155,11 +155,9 @@ def exists(self, keys: list[str]) -> list[int]: def get(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): if len(keys) == 0: return - failed_keys_for_log = keys try: self._ensure_device_ready() keys = self._helper.normalize_keys(keys) - failed_keys_for_log = keys blob_lists = self._helper.make_blob_lists(addrs, sizes) failed_keys: list[str] = [] if len(keys) <= self._DS_MAX_BATCH_KEYS: @@ -168,7 +166,6 @@ def get(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): ) else: for start, end in _iter_slices(len(keys), self._DS_MAX_BATCH_KEYS): - failed_keys_for_log = keys[start:end] failed_keys.extend( self._hetero_client.mget_h2d( # type: ignore[union-attr] keys[start:end], blob_lists[start:end], 0 @@ -176,20 +173,14 @@ def get(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): ) if failed_keys: logger.error( - "Failed to get %d keys out of %d. Check key existence and memory state.", + "Failed to get keys. failed_count=%d, sample_keys=%s. Check key existence and memory state.", len(failed_keys), - len(keys), + failed_keys[:10], ) - logger.debug("Failed to get key details. failed_keys=%s", failed_keys) except Exception as exc: logger.error( - "Failed to get %d keys out of %d. Check network and yuanrong service.", - len(failed_keys_for_log), + "Failed to get keys. keys_count=%d, type=%s, error=%s. Check network and yuanrong service.", len(keys), - ) - logger.debug( - "Failed to get key details. keys=%s, type=%s, error=%s", - failed_keys_for_log, type(exc).__name__, exc, ) @@ -197,11 +188,9 @@ def get(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): def put(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): if len(keys) == 0: return - failed_keys_for_log = keys try: self._ensure_device_ready() keys = self._helper.normalize_keys(keys) - failed_keys_for_log = keys blob_lists = self._helper.make_blob_lists(addrs, sizes) if len(keys) <= self._DS_MAX_BATCH_KEYS: self._hetero_client.mset_d2h( # type: ignore[union-attr] @@ -209,19 +198,13 @@ def put(self, keys: list[str], addrs: list[list[int]], sizes: list[list[int]]): ) else: for start, end in _iter_slices(len(keys), self._DS_MAX_BATCH_KEYS): - failed_keys_for_log = keys[start:end] self._hetero_client.mset_d2h( # type: ignore[union-attr] keys[start:end], blob_lists[start:end], self._ds_set_param ) except Exception as exc: logger.error( - "Failed to put %d keys out of %d. Check network and yuanrong service.", - len(failed_keys_for_log), + "Failed to put keys. keys_count=%d, type=%s, error=%s. Check network and yuanrong service.", len(keys), - ) - logger.debug( - "Failed to put key details. keys=%s, type=%s, error=%s", - failed_keys_for_log, type(exc).__name__, exc, ) diff --git a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/config_data.py b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/config_data.py index 3ffd5da10..c0d75bd82 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/config_data.py +++ b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/config_data.py @@ -476,11 +476,6 @@ def _rehash_block_hash_group(block_hashes: Sequence[BlockHash | str]) -> BlockHa def _block_hash_to_bytes(block_hash: BlockHash | str) -> bytes: if isinstance(block_hash, str): - if len(block_hash) == 64: - try: - return bytes.fromhex(block_hash) - except ValueError: - return block_hash.encode("utf-8") return block_hash.encode("utf-8") return bytes(block_hash) diff --git a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/pool_worker.py b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/pool_worker.py index d08681111..76eb71246 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/pool_worker.py +++ b/vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/pool_worker.py @@ -8,6 +8,8 @@ import torch from vllm.config import VllmConfig from vllm.distributed import ( + get_decode_context_model_parallel_rank, + get_decode_context_model_parallel_world_size, get_pcp_group, get_tensor_model_parallel_rank, get_tensor_model_parallel_world_size, @@ -41,10 +43,6 @@ KVTransferThread, record_failed_blocks, ) -from vllm_ascend.distributed.utils import ( - get_decode_context_model_parallel_rank, - get_decode_context_model_parallel_world_size, -) backend_map = { "mooncake": { diff --git a/vllm_ascend/distributed/kv_transfer/kv_pool/cpu_offload/cpu_kv_cache_manager.py b/vllm_ascend/distributed/kv_transfer/kv_pool/cpu_offload/cpu_kv_cache_manager.py index 79c22e55c..a65f168e1 100644 --- a/vllm_ascend/distributed/kv_transfer/kv_pool/cpu_offload/cpu_kv_cache_manager.py +++ b/vllm_ascend/distributed/kv_transfer/kv_pool/cpu_offload/cpu_kv_cache_manager.py @@ -5,13 +5,11 @@ from vllm.utils.hashing import sha256 from vllm.v1.core.block_pool import BlockPool from vllm.v1.core.kv_cache_utils import BlockHash, KVCacheBlock +from vllm.v1.core.single_type_kv_cache_manager import get_manager_for_kv_cache_spec from vllm.v1.kv_cache_interface import KVCacheSpec from vllm.v1.metrics.stats import CachingMetrics, PrefixCacheStats from vllm.v1.request import Request -from vllm_ascend.core.single_type_kv_cache_manager import get_manager_for_kv_cache_spec -from vllm_ascend.utils import vllm_version_is - class CPUCacheStats: def __init__(self, enable_prefix_caching: bool, log_stats: bool = False): @@ -68,18 +66,11 @@ def __init__( self.caching_hash_fn = sha256 if caching_hash_algo == "sha256" else hash self.use_eagle = use_eagle self.block_pool = BlockPool(self.num_cpu_blocks, True, self.block_size, enable_kv_cache_events) - max_model_len = self.num_cpu_blocks * self.block_size - manager_kwargs = dict( + self.single_type_manager = get_manager_for_kv_cache_spec( kv_cache_spec=kv_cache_spec, block_pool=self.block_pool, - enable_caching=True, kv_cache_group_id=0, - max_num_batched_tokens=max_model_len, - max_model_len=max_model_len, ) - if not vllm_version_is("0.22.1"): - manager_kwargs["scheduler_block_size"] = kv_cache_spec.block_size - self.single_type_manager = get_manager_for_kv_cache_spec(**manager_kwargs) # Record kv block hashes, avoid redundant computation. self.req_to_block_hashes: defaultdict[str, list[BlockHash]] = defaultdict(list) # Record blocks touched in get_matched_num_and_touch(). @@ -103,17 +94,13 @@ def get_matched_num_and_touch(self, request: Request) -> tuple[int, bool]: block_hashes = request.block_hashes self.req_to_block_hashes[request_id] = block_hashes max_cache_hit_length = request.num_tokens - 1 - if vllm_version_is("0.22.1"): - eagle_kwarg = {"use_eagle": self.use_eagle} - else: - eagle_kwarg = {"drop_eagle_block": self.use_eagle} computed_blocks = self.single_type_manager.find_longest_cache_hit( block_hashes=block_hashes, max_length=max_cache_hit_length, kv_cache_group_ids=[0], block_pool=self.block_pool, kv_cache_spec=self.single_type_manager.kv_cache_spec, - **eagle_kwarg, + use_eagle=self.use_eagle, alignment_tokens=self.block_size, ) num_computed_tokens = len(computed_blocks[0]) * self.block_size @@ -143,13 +130,10 @@ def allocate_slots(self, req_to_num_tokens: dict[str, int], unallocated_req_ids: if self.req_failed_to_allocate[request_id]: continue new_computed_blocks = self.req_to_computed_blocks[request_id] - num_local_computed_tokens = len(new_computed_blocks) * self.block_size num_blocks_to_allocate = self.single_type_manager.get_num_blocks_to_allocate( request_id=request_id, num_tokens=num_tokens, new_computed_blocks=new_computed_blocks, - total_computed_tokens=num_local_computed_tokens, - num_tokens_main_model=num_tokens, ) if num_blocks_to_allocate > self.block_pool.get_num_free_blocks(): self._release_ahead_touch(request_id) @@ -157,18 +141,9 @@ def allocate_slots(self, req_to_num_tokens: dict[str, int], unallocated_req_ids: continue # Append the new computed blocks to the request blocks until now to # avoid the case where the new blocks cannot be allocated. - self.single_type_manager.allocate_new_computed_blocks( - request_id, - new_computed_blocks, - num_local_computed_tokens=num_local_computed_tokens, - num_external_computed_tokens=0, - ) + self.single_type_manager.save_new_computed_blocks(request_id, new_computed_blocks) # Allocate new blocks but do not cache now. - new_blocks = self.single_type_manager.allocate_new_blocks( - request_id, - num_tokens, - num_tokens, - ) + new_blocks = self.single_type_manager.allocate_new_blocks(request_id, num_tokens) self.req_to_num_tokens[request_id] = num_tokens # No need to release ref_cnt because we use officially. self.req_to_computed_blocks.pop(request_id, None) diff --git a/vllm_ascend/distributed/kv_transfer/utils/utils.py b/vllm_ascend/distributed/kv_transfer/utils/utils.py index b76fcfa46..36b8b3eab 100644 --- a/vllm_ascend/distributed/kv_transfer/utils/utils.py +++ b/vllm_ascend/distributed/kv_transfer/utils/utils.py @@ -1,7 +1,6 @@ import math import os -from collections import OrderedDict, defaultdict -from collections.abc import Iterator +from collections import defaultdict from dataclasses import dataclass from typing import Any @@ -11,9 +10,6 @@ from vllm_ascend.distributed.parallel_state import get_p_tp_group -MAX_HCCL_REGISTER_REGIONS = 256 -REGISTER_MERGE_GAP_BYTES = 4096 - def kv_alltoall_and_rearrange(pd_tp_ratio: int, key: torch.Tensor, value: torch.TensorType): if pd_tp_ratio <= 1: @@ -304,142 +300,3 @@ def get_transfer_mappings( block_dict["trans_count"] = d_trans_count_mapping[(host, port)] logger.debug("MooncakeLayerwiseConnector Request %s transfer tasks: %s", req_id, transfer_mappings) return transfer_mappings - - -@dataclass -class RegisterRange: - start: int - end: int - - -@dataclass -class RegisterRegions: - ptrs: list[int] - lengths: list[int] - logical_tensor_count: int | None = None - logical_total_bytes: int | None = None - - @property - def registered_bytes(self) -> int: - return sum(self.lengths) - - -def iter_kv_cache_tensors(obj: Any) -> Iterator[torch.Tensor]: - """Flatten kv_caches into tensors without materializing new tensors.""" - if obj is None: - return - - if isinstance(obj, torch.Tensor): - yield obj - return - - if isinstance(obj, (tuple, list)): - for item in obj: - yield from iter_kv_cache_tensors(item) - return - - if isinstance(obj, dict): - for item in obj.values(): - yield from iter_kv_cache_tensors(item) - return - - -def tensor_storage_key(tensor: torch.Tensor) -> int: - """Return a stable grouping key for tensors sharing the same storage. - - Do NOT use this key as the register address directly. For aligned KV cache - views, tensor.untyped_storage().data_ptr() may point to the original raw - allocation, whose address can be unaligned. We only use it to group views. - """ - try: - return tensor.untyped_storage().data_ptr() - except Exception: - try: - return tensor.storage().data_ptr() - except Exception: - return tensor.data_ptr() - - -def collect_storage_merged_register_regions( - kv_caches: dict[str, Any], -) -> RegisterRegions: - """Collect HCCL/Mooncake register regions with storage-aware merging. - - Metadata should still use each logical tensor's own data_ptr(). - register_buffer should use the merged memory ranges returned here. - """ - ranges_by_storage: OrderedDict[int, list[RegisterRange]] = OrderedDict() - logical_tensor_count = 0 - logical_total_bytes = 0 - - for tensor in iter_kv_cache_tensors(kv_caches): - if tensor is None or tensor.numel() == 0: - continue - - if not tensor.is_contiguous(): - logger.warning( - "Mooncake register_buffer got a non-contiguous KV cache " - "tensor: shape=%s, dtype=%s, data_ptr=%s. " - "Registration will use logical numel * element_size.", - tuple(tensor.shape), - tensor.dtype, - hex(tensor.data_ptr()), - ) - - nbytes = tensor.nbytes - start = tensor.data_ptr() - end = start + nbytes - storage_key = tensor_storage_key(tensor) - - logical_tensor_count += 1 - logical_total_bytes += nbytes - - ranges_by_storage.setdefault(storage_key, []).append(RegisterRange(start, end)) - - register_ptrs: list[int] = [] - register_lengths: list[int] = [] - - for ranges in ranges_by_storage.values(): - ranges.sort(key=lambda r: r.start) - - merged_start = ranges[0].start - merged_end = ranges[0].end - - for region in ranges[1:]: - if region.start <= merged_end + REGISTER_MERGE_GAP_BYTES: - merged_end = max(merged_end, region.end) - else: - register_ptrs.append(merged_start) - register_lengths.append(merged_end - merged_start) - merged_start = region.start - merged_end = region.end - - register_ptrs.append(merged_start) - register_lengths.append(merged_end - merged_start) - - return RegisterRegions( - ptrs=register_ptrs, - lengths=register_lengths, - logical_tensor_count=logical_tensor_count, - logical_total_bytes=logical_total_bytes, - ) - - -def validate_register_region_count(regions: RegisterRegions) -> None: - region_count = len(regions.ptrs) - if region_count <= MAX_HCCL_REGISTER_REGIONS: - return - - detail = f"registered_bytes={regions.registered_bytes}" - if regions.logical_tensor_count is not None: - detail += f", logical_tensors={regions.logical_tensor_count}, logical_bytes={regions.logical_total_bytes}" - - raise RuntimeError( - "Mooncake register_buffer region count " - f"{region_count} exceeds HCCL per-process limit " - f"{MAX_HCCL_REGISTER_REGIONS}. " - "KV cache registration would fail. " - f"{detail}. " - "Please reduce KV cache allocation fragmentation or merge " - "k/v/dsa/scale allocations further." - ) diff --git a/vllm_ascend/distributed/parallel_state.py b/vllm_ascend/distributed/parallel_state.py index 0e433ff9d..5ed7d3dd9 100644 --- a/vllm_ascend/distributed/parallel_state.py +++ b/vllm_ascend/distributed/parallel_state.py @@ -339,36 +339,3 @@ def destroy_ascend_model_parallel(): if _DYNAMIC_EPLB: _DYNAMIC_EPLB.destroy() _DYNAMIC_EPLB = None - - -def get_global_rank(parallel_config: ParallelConfig | None = None) -> int: - """Return a globally unique rank for the current worker across all parallel - dimensions (TP/PP/CP/DP), compatible with both dense and MoE models. - - vLLM does not expose a single ready-to-use cross-DP global rank: - - For dense models each DP rank is launched as an independent DP=1 engine, - so ``data_parallel_rank`` is reset to 0 and ``get_world_group()`` only - spans one replica (``rank_in_group`` is the local rank in the replica). - - For MoE DP / external_launcher the world group spans all DP ranks, so - ``rank_in_group`` already encodes the DP offset. - - ``data_parallel_index`` always keeps the true DP rank (it is never reset), - and ``rank_in_group % replica_size`` yields the local rank within a replica - in both cases, so the formula below is correct everywhere. It mirrors vLLM's - own ``data_parallel_rank * world_size + rank`` (see - vllm/distributed/parallel_state.py). - - Note: DCP (decode context parallel) reuses the TP NPUs and EP overlays - TP/DP, so neither adds new ranks and they are intentionally excluded from - ``replica_size``. - """ - if parallel_config is None: - parallel_config = get_current_vllm_config().parallel_config - # Number of NPUs in a single DP replica (TP * PP * prefill-CP). - replica_size = ( - parallel_config.tensor_parallel_size - * parallel_config.pipeline_parallel_size - * parallel_config.prefill_context_parallel_size - ) - rank_in_replica = get_world_group().rank_in_group % replica_size - return parallel_config.data_parallel_index * replica_size + rank_in_replica diff --git a/vllm_ascend/distributed/utils.py b/vllm_ascend/distributed/utils.py index 15f53bb72..4bf127e48 100644 --- a/vllm_ascend/distributed/utils.py +++ b/vllm_ascend/distributed/utils.py @@ -1,6 +1,5 @@ import torch import torch.distributed as dist -from vllm.distributed import get_dcp_group from vllm.distributed.parallel_state import GroupCoordinator, get_dp_group from vllm.forward_context import get_forward_context @@ -8,16 +7,6 @@ from vllm_ascend.distributed.parallel_state import get_fc3_quant_x_group -def get_decode_context_model_parallel_world_size() -> int: - """Return DCP world size (v0.22.1 helper removed on vLLM main).""" - return get_dcp_group().world_size - - -def get_decode_context_model_parallel_rank() -> int: - """Return DCP rank within group (v0.22.1 helper removed on vLLM main).""" - return get_dcp_group().rank_in_group - - def fc3_all_gather_and_maybe_unpad_impl( x: torch.Tensor, ) -> torch.Tensor: diff --git a/vllm_ascend/distributed/weight_transfer/__init__.py b/vllm_ascend/distributed/weight_transfer/__init__.py deleted file mode 100644 index 2c77cc3f3..000000000 --- a/vllm_ascend/distributed/weight_transfer/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# This file is a part of the vllm-ascend project. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from vllm.distributed.weight_transfer.factory import WeightTransferEngineFactory - - -def register_engine(): - """Register HCCL weight transfer engine as a vLLM plugin.""" - WeightTransferEngineFactory.register_engine( - "hccl", - "vllm_ascend.distributed.weight_transfer.hccl_engine", - "HCCLWeightTransferEngine", - ) diff --git a/vllm_ascend/distributed/weight_transfer/hccl_engine.py b/vllm_ascend/distributed/weight_transfer/hccl_engine.py deleted file mode 100644 index 03e86253c..000000000 --- a/vllm_ascend/distributed/weight_transfer/hccl_engine.py +++ /dev/null @@ -1,338 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project -"""HCCL-based weight transfer engine.""" - -from collections.abc import Callable, Iterator -from dataclasses import dataclass -from typing import TYPE_CHECKING, Any - -import torch - -if TYPE_CHECKING: - from vllm_ascend.distributed.device_communicators.pyhccl import PyHcclCommunicator - -from vllm.config.parallel import ParallelConfig -from vllm.config.weight_transfer import WeightTransferConfig -from vllm.distributed.weight_transfer.base import ( - WeightTransferEngine, - WeightTransferInitInfo, - WeightTransferUpdateInfo, -) - -from vllm_ascend.distributed.weight_transfer.packed_tensor import ( - DEFAULT_PACKED_BUFFER_SIZE_BYTES, - DEFAULT_PACKED_NUM_BUFFERS, - packed_broadcast_consumer, -) -from vllm_ascend.utils import vllm_version_is - - -@dataclass -class HCCLWeightTransferInitInfo(WeightTransferInitInfo): - """Initialization info for HCCL weight transfer backend.""" - - master_address: str - """IP address of the trainer (rank 0) for HCCL process group setup.""" - master_port: int - """Port on the trainer for HCCL process group setup.""" - rank_offset: int - """Offset added to each vLLM worker's rank within the HCCL group. - Typically 1 (trainer is rank 0, workers start at rank 1).""" - world_size: int - """Total number of participants in the HCCL group (trainer + all workers).""" - - -@dataclass -class HCCLTrainerSendWeightsArgs: - """Arguments for HCCL trainer_send_weights method.""" - - group: Any - """Process group (PyHcclCommunicator) for HCCL communication.""" - src: int = 0 - """Source rank (default 0, trainer is typically rank 0).""" - post_iter_func: Callable[[tuple[str, torch.Tensor]], torch.Tensor] | None = None - """Optional function to apply to each (name, tensor) pair before broadcasting. - If None, extracts just the tensor.""" - packed: bool = False - """Whether to use packed tensor broadcasting for efficiency. - When True, multiple tensors are batched together before broadcasting - to reduce HCCL communication overhead.""" - stream: torch.npu.Stream | None = None - """ACL stream to use for broadcasting if packed is False. - If packed is True, new streams will be created for each buffer.""" - packed_buffer_size_bytes: int = DEFAULT_PACKED_BUFFER_SIZE_BYTES - """Size in bytes for each packed tensor buffer. - Must match the value used in HCCLWeightTransferUpdateInfo.""" - packed_num_buffers: int = DEFAULT_PACKED_NUM_BUFFERS - """Number of buffers for double/triple buffering during packed transfer. - Must match the value used in HCCLWeightTransferUpdateInfo.""" - - -@dataclass -class HCCLWeightTransferUpdateInfo(WeightTransferUpdateInfo): - """Update info for HCCL weight transfer backend.""" - - names: list[str] - dtype_names: list[str] - shapes: list[list[int]] - packed: bool = False - """Whether to use packed tensor broadcasting for efficiency. - When True, multiple tensors are batched together before broadcasting - to reduce HCCL communication overhead.""" - packed_buffer_size_bytes: int = DEFAULT_PACKED_BUFFER_SIZE_BYTES - """Size in bytes for each packed tensor buffer. - Both producer and consumer must use the same value.""" - packed_num_buffers: int = DEFAULT_PACKED_NUM_BUFFERS - """Number of buffers for double/triple buffering during packed transfer. - Both producer and consumer must use the same value.""" - - def __post_init__(self): - """Validate that all lists have the same length.""" - num_params = len(self.names) - if len(self.dtype_names) != num_params: - raise ValueError( - f"`dtype_names` should be of the same size as `names`: " - f"got {len(self.dtype_names)} and {len(self.names)}" - ) - if len(self.shapes) != num_params: - raise ValueError( - f"`shapes` should be of the same size as `names`: got {len(self.shapes)} and {len(self.names)}" - ) - - -class HCCLWeightTransferEngine(WeightTransferEngine[HCCLWeightTransferInitInfo, HCCLWeightTransferUpdateInfo]): - """ - Weight transfer engine using HCCL for communication between trainer and workers. - - This implementation uses HCCL broadcast operations to transfer weights from - the trainer (rank 0) to all inference workers in a process group. - """ - - # Define backend-specific dataclass types - init_info_cls = HCCLWeightTransferInitInfo - update_info_cls = HCCLWeightTransferUpdateInfo - - def __init__( - self, - config: WeightTransferConfig, - parallel_config: ParallelConfig, - model: torch.nn.Module | None = None, - ) -> None: - """ - Initialize the HCCL weight transfer engine. - - Args: - config: The configuration for the weight transfer engine - parallel_config: The configuration for the parallel setup - model: The local model instance which will receive the weights. - Not available on v0.21.0 (base class does not accept it). - """ - if vllm_version_is("0.21.0"): - super().__init__(config, parallel_config) - else: - super().__init__(config, parallel_config, model) - self.model_update_group: PyHcclCommunicator | None = None - - def init_transfer_engine(self, init_info: HCCLWeightTransferInitInfo) -> None: - """ - Initialize HCCL process group with the trainer. - - Args: - init_info: HCCL initialization info containing master address, port, - rank offset, and world size - """ - - # Calculate the global rank in the trainer-worker process group - # Must account for data parallel to get unique ranks across all workers - dp_rank = self.parallel_config.data_parallel_index - world_size_per_dp = self.parallel_config.world_size # TP * PP - rank_within_dp = self.parallel_config.rank - - # Unique rank across all DP groups - worker_rank = dp_rank * world_size_per_dp + rank_within_dp - rank = worker_rank + init_info.rank_offset - # Create stateless process group - device = torch.accelerator.current_device_index() - self.model_update_group = HCCLWeightTransferEngine._stateless_init_process_group( - init_info.master_address, - init_info.master_port, - rank, - init_info.world_size, - device=device, - ) - - def receive_weights( - self, - update_info: HCCLWeightTransferUpdateInfo, - load_weights: Callable[[list[tuple[str, torch.Tensor]]], None], - ) -> None: - """ - Receive weights from trainer via HCCL broadcast and load them incrementally. - - If update_info.packed is True, uses packed tensor broadcasting for - efficient transfer of multiple weights in batches. Otherwise, uses simple - one-by-one broadcasting. - - Args: - update_info: HCCL update info containing parameter names, dtypes, shapes, - and packed flag - load_weights: Callable that loads weights into the model. Called - incrementally for each batch of weights to avoid OOM. - """ - if self.model_update_group is None: - raise RuntimeError("HCCL weight transfer not initialized. Call init_transfer_engine() first.") - - if update_info.packed: - # Build iterator of (name, (shape, dtype)) from update_info - def state_dict_info_iterator(): - for name, dtype_name, shape in zip(update_info.names, update_info.dtype_names, update_info.shapes): - dtype = getattr(torch, dtype_name) - yield (name, (shape, dtype)) - - packed_broadcast_consumer( - iterator=state_dict_info_iterator(), - group=self.model_update_group, - src=0, - post_unpack_func=load_weights, - buffer_size_bytes=update_info.packed_buffer_size_bytes, - num_buffers=update_info.packed_num_buffers, - ) - else: - # Use simple one-by-one broadcasting - for name, dtype_name, shape in zip(update_info.names, update_info.dtype_names, update_info.shapes): - dtype = getattr(torch, dtype_name) - weight = torch.empty(shape, dtype=dtype, device="npu") - self.model_update_group.broadcast(weight, src=0, stream=torch.npu.current_stream()) - load_weights([(name, weight)]) - del weight - - def shutdown(self) -> None: - if self.model_update_group is not None: - # Clean up the communicator by removing the reference - self.model_update_group = None - - @staticmethod - def trainer_send_weights( - iterator: Iterator[tuple[str, torch.Tensor]], - trainer_args: dict[str, Any] | HCCLTrainerSendWeightsArgs, - ) -> None: - """Broadcast weights from trainer to vLLM workers. - - Args: - iterator: Iterator of model parameters. Returns (name, tensor) tuples - trainer_args: Dictionary or HCCLTrainerSendWeightsArgs instance containing - HCCL-specific arguments. If a dict, should contain keys from - HCCLTrainerSendWeightsArgs. - - Example: - >>> from vllm.distributed.weight_transfer.hccl_engine import ( - ... HCCLWeightTransferEngine, - ... HCCLTrainerSendWeightsArgs, - ... ) - >>> param_iter = ((n, p) for n, p in model.named_parameters()) - >>> args = HCCLTrainerSendWeightsArgs(group=group, packed=True) - >>> HCCLWeightTransferEngine.trainer_send_weights(param_iter, args) - """ - # Parse trainer args - accept either dict or dataclass instance - if isinstance(trainer_args, dict): - args = HCCLTrainerSendWeightsArgs(**trainer_args) - else: - args = trainer_args - - if args.post_iter_func is None: - # Default: extract just the tensor from (name, tensor) tuple - post_iter_func = lambda x: x[1] - else: - post_iter_func = args.post_iter_func - - if args.packed: - # Use packed tensor broadcasting for efficiency - from vllm_ascend.distributed.weight_transfer.packed_tensor import ( - packed_broadcast_producer, - ) - - packed_broadcast_producer( - iterator=iterator, - group=args.group, - src=args.src, - post_iter_func=post_iter_func, - buffer_size_bytes=args.packed_buffer_size_bytes, - num_buffers=args.packed_num_buffers, - ) - else: - # Use simple one-by-one broadcasting - for item in iterator: - tensor = post_iter_func(item) - args.group.broadcast( - tensor, - src=args.src, - stream=args.stream or torch.npu.current_stream(), - ) - - @staticmethod - def trainer_init( - init_info: HCCLWeightTransferInitInfo | dict, - ) -> "PyHcclCommunicator": - """ - Initialize HCCL process group for trainer-side weight transfer. - - The trainer is always rank 0 in the process group. Uses the current - Ascend device (torch.accelerator.current_device_index()). - - Args: - init_info: Either an HCCLWeightTransferInitInfo object or a dict with keys: - - master_address: str - - master_port: int - - world_size: int - - Returns: - PyHcclCommunicator for weight transfer. - - Example: - >>> from vllm.distributed.weight_transfer.hccl_engine import ( - ... HCCLWeightTransferEngine, - ... ) - >>> group = HCCLWeightTransferEngine.trainer_init( - ... dict( - ... master_address=master_address, - ... master_port=master_port, - ... world_size=world_size, - ... ), - ... ) - """ - if isinstance(init_info, dict): - master_address = init_info["master_address"] - master_port = init_info["master_port"] - world_size = init_info["world_size"] - else: - # HCCLWeightTransferInitInfo object - master_address = init_info.master_address - master_port = init_info.master_port - world_size = init_info.world_size - - # Trainer is always rank 0 - device = torch.accelerator.current_device_index() - return HCCLWeightTransferEngine._stateless_init_process_group( - master_address, - master_port, - 0, - world_size, - device, - ) - - @staticmethod - def _stateless_init_process_group(master_address, master_port, rank, world_size, device): - """ - vLLM provides `StatelessProcessGroup` to create a process group - without considering the global process group in torch.distributed. - It is recommended to create `StatelessProcessGroup`, and then initialize - the data-plane communication (HCCL) between external (train processes) - and vLLM workers. - """ - from vllm.distributed.utils import StatelessProcessGroup - - from vllm_ascend.distributed.device_communicators.pyhccl import PyHcclCommunicator - - pg = StatelessProcessGroup.create(host=master_address, port=master_port, rank=rank, world_size=world_size) - pyhccl = PyHcclCommunicator(pg, device=device) - return pyhccl diff --git a/vllm_ascend/distributed/weight_transfer/packed_tensor.py b/vllm_ascend/distributed/weight_transfer/packed_tensor.py deleted file mode 100644 index eb2b17c23..000000000 --- a/vllm_ascend/distributed/weight_transfer/packed_tensor.py +++ /dev/null @@ -1,194 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project -"""Packed tensor utilities for efficient weight transfer.""" - -import math -from collections.abc import Callable, Iterator -from typing import Any - -import torch - -# Default values for packed tensor configuration. -# These are imported by HCCLWeightTransferUpdateInfo and trainer_send_weights. -DEFAULT_PACKED_BUFFER_SIZE_BYTES = 1024 * 1024 * 1024 # 1GB -DEFAULT_PACKED_NUM_BUFFERS = 2 - - -def packed_broadcast_producer( - iterator: Iterator[tuple[str, torch.Tensor]], - group: Any, - src: int, - post_iter_func: Callable[[tuple[str, torch.Tensor]], torch.Tensor], - buffer_size_bytes: int = DEFAULT_PACKED_BUFFER_SIZE_BYTES, - num_buffers: int = DEFAULT_PACKED_NUM_BUFFERS, -) -> None: - """Broadcast tensors in a packed manner from trainer to workers. - - Args: - iterator: Iterator of model parameters. Returns a tuple of (name, tensor) - group: Process group (PyHcclCommunicator) - src: Source rank (0 in current implementation) - post_iter_func: Function to apply to each (name, tensor) pair before - packing, should return a tensor - buffer_size_bytes: Size in bytes for each packed tensor buffer. - Both producer and consumer must use the same value. - num_buffers: Number of buffers for double/triple buffering. - Both producer and consumer must use the same value. - """ - target_packed_tensor_size = buffer_size_bytes - - streams = [torch.npu.Stream() for _ in range(num_buffers)] - buffer_idx = 0 - - packing_tensor_list: list[list[torch.Tensor]] = [[] for _ in range(num_buffers)] - packing_tensor_sizes: list[int] = [0 for _ in range(num_buffers)] - packed_tensors: list[torch.Tensor] = [torch.empty(0, dtype=torch.uint8, device="npu") for _ in range(num_buffers)] - - done = False - while not done: - # Synchronize the current stream (waits for previous - # iteration's work on this buffer to finish) - streams[buffer_idx].synchronize() - # Start tasks for the new buffer in a new stream - with torch.npu.stream(streams[buffer_idx]): - # Initialize the packing tensor list and sizes - packing_tensor_list[buffer_idx] = [] - packing_tensor_sizes[buffer_idx] = 0 - # Pack the tensors - while True: - try: - item = next(iterator) - except StopIteration: - done = True - break - # Apply post processing and convert to linearized uint8 tensor - tensor = post_iter_func(item).contiguous().view(torch.uint8).view(-1) - packing_tensor_list[buffer_idx].append(tensor) - packing_tensor_sizes[buffer_idx] += tensor.numel() - if packing_tensor_sizes[buffer_idx] > target_packed_tensor_size: - break - if len(packing_tensor_list[buffer_idx]) > 0: - # Pack the tensors - packed_tensors[buffer_idx] = torch.cat(packing_tensor_list[buffer_idx], dim=0) - - if len(packing_tensor_list[buffer_idx]) == 0: - # No more tensors — nothing left to broadcast - break - - # torch.cat runs on the custom stream. Synchronize before - # broadcasting on the default stream so the packed data is ready. - streams[buffer_idx].synchronize() - group.broadcast(packed_tensors[buffer_idx], src=src) - - # Move to the next buffer - buffer_idx = (buffer_idx + 1) % num_buffers - - # Ensure the last broadcast on the default stream has completed - # before returning, so NPU tensor cleanup at exit doesn't hang. - torch.npu.current_stream().synchronize() - - -def packed_broadcast_consumer( - iterator: Iterator[tuple[str, tuple[list[int], torch.dtype]]], - group: Any, - src: int, - post_unpack_func: Callable[[list[tuple[str, torch.Tensor]]], None], - buffer_size_bytes: int = DEFAULT_PACKED_BUFFER_SIZE_BYTES, - num_buffers: int = DEFAULT_PACKED_NUM_BUFFERS, -) -> None: - """Consume packed tensors and unpack them into a list of tensors. - - Args: - iterator: Iterator of parameter metadata. Returns (name, (shape, dtype)) - group: Process group (PyHcclCommunicator) - src: Source rank (0 in current implementation) - post_unpack_func: Function to apply to each list of (name, tensor) after - unpacking - buffer_size_bytes: Size in bytes for each packed tensor buffer. - Both producer and consumer must use the same value. - num_buffers: Number of buffers for double/triple buffering. - Both producer and consumer must use the same value. - """ - - def unpack_tensor( - packed_tensor: torch.Tensor, - names: list[str], - shapes: list[list[int]], - dtypes: list[torch.dtype], - tensor_sizes: list[int], - ) -> list[tuple[str, torch.Tensor]]: - """Unpack a packed uint8 tensor into a list of typed tensors.""" - unpacked_tensors = packed_tensor.split(tensor_sizes) - unpacked_list = [ - (name, tensor.contiguous().view(dtype).view(*shape)) - for name, shape, dtype, tensor in zip(names, shapes, dtypes, unpacked_tensors) - ] - return unpacked_list - - target_packed_tensor_size = buffer_size_bytes - - streams = [torch.npu.Stream() for _ in range(num_buffers)] - default_stream = torch.npu.current_stream() - buffer_idx = 0 - - packing_tensor_meta_data: list[list[tuple[str, list[int], torch.dtype, int]]] = [[] for _ in range(num_buffers)] - packing_tensor_sizes: list[int] = [0 for _ in range(num_buffers)] - packed_tensors: list[torch.Tensor] = [torch.empty(0, dtype=torch.uint8, device="npu") for _ in range(num_buffers)] - - done = False - while not done: - # Synchronize the current stream (waits for previous - # iteration's load_weights on this buffer to finish) - streams[buffer_idx].synchronize() - with torch.npu.stream(streams[buffer_idx]): - # Collect parameter metadata for this buffer - packing_tensor_meta_data[buffer_idx] = [] - packing_tensor_sizes[buffer_idx] = 0 - while True: - try: - name, (shape, dtype) = next(iterator) - except StopIteration: - done = True - break - tensor_size = math.prod(shape) * dtype.itemsize - packing_tensor_meta_data[buffer_idx].append((name, shape, dtype, tensor_size)) - packing_tensor_sizes[buffer_idx] += tensor_size - if packing_tensor_sizes[buffer_idx] > target_packed_tensor_size: - break - if len(packing_tensor_meta_data[buffer_idx]) > 0: - packed_tensors[buffer_idx] = torch.empty( - packing_tensor_sizes[buffer_idx], - dtype=torch.uint8, - device="npu", - ) - - if len(packing_tensor_meta_data[buffer_idx]) == 0: - break - - # Broadcast on the default stream. - group.broadcast(packed_tensors[buffer_idx], src=src) - - # Synchronize the default stream so broadcast completes before - # load_weights (running on the custom stream) reads the data. - default_stream.synchronize() - - # Unpack and load weights on the custom stream - with torch.npu.stream(streams[buffer_idx]): - names, shapes, dtypes, tensor_sizes = zip(*packing_tensor_meta_data[buffer_idx]) - post_unpack_func( - unpack_tensor( - packed_tensors[buffer_idx], - list(names), - list(shapes), - list(dtypes), - list(tensor_sizes), - ) - ) - - # Move to the next buffer - buffer_idx = (buffer_idx + 1) % num_buffers - - # Wait for all in-flight load_weights (on custom streams) to finish. - # Otherwise NPU tensor cleanup at exit may hang. - for s in streams: - s.synchronize() diff --git a/vllm_ascend/envs.py b/vllm_ascend/envs.py index 3c8c319c6..4d8648394 100644 --- a/vllm_ascend/envs.py +++ b/vllm_ascend/envs.py @@ -110,6 +110,8 @@ # Control the aclrtMemcpyBatchAsync compile path for KV cache offloading. # "1": force enable, "0": force disable, None: auto-detect from CANN headers. "VLLM_ASCEND_ENABLE_BATCH_MEMCPY": lambda: os.getenv("VLLM_ASCEND_ENABLE_BATCH_MEMCPY", None), + # Whether to use MultiBlockPool for KV cache management + "VLLM_ASCEND_APPLY_DSV4_PATCH": lambda: bool(int(os.getenv("VLLM_ASCEND_APPLY_DSV4_PATCH", "0"))), } # end-env-vars-definition diff --git a/vllm_ascend/eplb/core/eplb_device_transfer_loader.py b/vllm_ascend/eplb/core/eplb_device_transfer_loader.py index 0d213ed03..67ea76a00 100644 --- a/vllm_ascend/eplb/core/eplb_device_transfer_loader.py +++ b/vllm_ascend/eplb/core/eplb_device_transfer_loader.py @@ -46,9 +46,7 @@ def set_adator(self, eplb_adaptor): def generate_expert_d2d_transfer_task(self, expert_send_info, expert_recv_info, updated_expert_map, layer_id): # When current send/recv and weight.expert_map update tasks are not finished, cannot accept new d2d task if self.state != ExpertWeightUpdateState.WAITING: - logger.warning_once( - "[eplb/d2d_loader] Current D2D weight update is on-going, cannot accept new update task" - ) + logger.warning_once("current d2d weight update tasks are on-going, cannot accept new weight update task") return self.updated_expert_map = updated_expert_map @@ -115,14 +113,8 @@ def update_expert_map_and_weight(self, reqs): local_expert_to_replace, buffer_tensor_id = recv_expert_info self.eplb_adaptor.do_update_expert_weight(self.layer_id, local_expert_to_replace, buffer_tensor_id) - logger.debug( - "[eplb/d2d_loader] Layer %s D2D transfer completed, updated_experts=%s", - self.layer_id, - len(self.recv_expert_list), - ) - if self.layer_id == self.num_layers - 1: - logger.info("[eplb/d2d_loader] Full expert weight update cycle completed, total_layers=%s", self.num_layers) + logger.info("[EPLB] finished update expert weight.") self.recv_expert_list = [] self.updated_expert_map = None diff --git a/vllm_ascend/eplb/core/eplb_utils.py b/vllm_ascend/eplb/core/eplb_utils.py index dc7bd5233..226a02d9a 100644 --- a/vllm_ascend/eplb/core/eplb_utils.py +++ b/vllm_ascend/eplb/core/eplb_utils.py @@ -21,7 +21,13 @@ import numpy as np import torch from vllm.logger import logger -from vllm.model_executor.layers.fused_moe.expert_map_manager import determine_expert_map + +from vllm_ascend.utils import vllm_version_is + +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers.fused_moe.layer import determine_expert_map +else: + from vllm.model_executor.layers.fused_moe.expert_map_manager import determine_expert_map def expert_file_to_tensor(expert_map_path, layer_id): @@ -32,7 +38,7 @@ def expert_file_to_tensor(expert_map_path, layer_id): if layer_id > data["moe_layer_count"]: raise ValueError("Invalid EPLB Table") if layer_id == data["moe_layer_count"]: - logger.warning("[eplb/utils] Init expert map of mtp/eagle when using sample.") + logger.warning("Init expert map of mtp/eagle when using sample.") for device in data["layer_list"][0]["device_list"]: physical_count += len(device["device_expert"]) return None, physical_count diff --git a/vllm_ascend/eplb/core/eplb_worker.py b/vllm_ascend/eplb/core/eplb_worker.py index f94f05565..f2618534d 100644 --- a/vllm_ascend/eplb/core/eplb_worker.py +++ b/vllm_ascend/eplb/core/eplb_worker.py @@ -56,7 +56,6 @@ def do_update(self): # Get MOE load information load_info = self.fetch_and_sum_load_info() if load_info is None: - logger.debug("[eplb/worker] No moe_load data available yet, skipping this cycle") return # Get the updated expert table based on the workload information @@ -85,7 +84,7 @@ def do_update(self): } # ms-service-metric end. logger.info( - "[eplb/worker] Expert hotness imbalance, current: mean=%.3f max=%.3f, updated: mean=%.3f max=%.3f", + "[Expert Hotness] Current: mean=%.3f, max=%.3f, Updated: mean=%.3f, max=%.3f", current_mean, current_max, update_mean, @@ -100,7 +99,7 @@ def do_update(self): update_info = self.compose_expert_update_info_greedy(new_expert_maps, self.old_expert_maps) self.old_expert_maps = new_expert_maps - logger.debug("[eplb/worker] EPLB Process compute complete") + logger.debug("EPLB Process compute complete") packed_update_info = self.pack_update_info(update_info) @@ -113,7 +112,7 @@ def check_expert_placement(self, old_placement, new_placement): for layer_id in range(num_layers): # check if any logical expert is not placed on any rank if torch.unique(new_placement[layer_id]).numel() < torch.unique(old_placement[layer_id]).numel(): - logger.error("[eplb/worker] There exists expert not placed on any rank in layer %s", layer_id) + logger.error("There exists expert not placed on any rank in layer %s", layer_id) new_placement[layer_id] = old_placement[layer_id] continue @@ -124,7 +123,7 @@ def check_expert_placement(self, old_placement, new_placement): # check if same logical experts are placed on the same NPU if new_placement_check.numel() != torch.unique(new_placement_check).numel(): logger.error( - "[eplb/worker] Replicated experts are placed on the same NPU; " + "Replicated experts are placed on the same NPU; " "expert placement on layer %s, rank %s is invalid", layer_id, rank_id, @@ -136,8 +135,7 @@ def check_expert_placement(self, old_placement, new_placement): expert_not_move = torch.isin(new_placement_check, old_placement_check) if not torch.equal(new_placement_check[expert_not_move], old_placement_check[expert_not_move]): logger.error( - "[eplb/worker] Expert movement inside NPU detected; " - "expert placement on layer %s, rank %s is invalid", + "There exists expert movement inside NPU; expert placement on layer %s, rank %s is invalid", layer_id, rank_id, ) @@ -371,7 +369,7 @@ def worker_process(self, planner_q, block_update_q): except Exception as e: logger.warning( - "[eplb/worker] Subprocess crashed, EPLB optimization will stop. error=%s", + "[EPLB subprocess exiting due to error: %s]", e, exc_info=True, ) diff --git a/vllm_ascend/eplb/core/policy/policy_factory.py b/vllm_ascend/eplb/core/policy/policy_factory.py index 9fe80007a..73f4adab9 100644 --- a/vllm_ascend/eplb/core/policy/policy_factory.py +++ b/vllm_ascend/eplb/core/policy/policy_factory.py @@ -1,7 +1,5 @@ # Copyright Huawei Technologies Co., Ltd. 2023-2024. All rights reserved. # Todo: Once https://github.com/vllm-project/vllm/pull/24069 is merged in vllm. Remove this factory. -from vllm.logger import logger - from .policy_abstract import EplbPolicy from .policy_default_eplb import DefaultEplb from .policy_flashlb import FlashLB, warm_up @@ -25,16 +23,7 @@ def generate_policy(policy_type: int) -> EplbPolicy: # Multi-Shot Enhancement and Incremental Adjustment 3: FlashLB, } - policy_class = policy.get(policy_type) - if policy_class is None: - policy_class = RandomLoadBalance - logger.warning( - "[eplb/policy] Unrecognized policy_type=%s, falling back to %s", - policy_type, - policy_class.__name__, - ) - else: - logger.info("[eplb/policy] Policy: %s (type=%s)", policy_class.__name__, policy_type) + policy_class = policy.get(policy_type, RandomLoadBalance) policy_instance = policy_class() if policy_type == 3: warm_up() diff --git a/vllm_ascend/eplb/core/policy/policy_flashlb.py b/vllm_ascend/eplb/core/policy/policy_flashlb.py index 02fedffb9..4ad2b7331 100644 --- a/vllm_ascend/eplb/core/policy/policy_flashlb.py +++ b/vllm_ascend/eplb/core/policy/policy_flashlb.py @@ -867,7 +867,7 @@ def rebalance_experts( self.update_threshold_value = 0.9 self.true_update = True except Exception: - logger.info("[eplb/policy] Dynamic eplb group not initialized yet, using default thresholds") + logger.info("Dynamic eplb group is not initialized now") current_deployment = np.array(current_expert_table) expert_workload = np.array(expert_workload) diff --git a/vllm_ascend/eplb/eplb_updator.py b/vllm_ascend/eplb/eplb_updator.py index 5e9995030..7ea783114 100644 --- a/vllm_ascend/eplb/eplb_updator.py +++ b/vllm_ascend/eplb/eplb_updator.py @@ -59,7 +59,6 @@ def init_eplb(self, expert_map_path, process): self.num_expert_load_gather = self.expert_heat_collection_interval self.periodic_load_gather = False except Exception: - logger.debug("[eplb/updator] VLLM_ALLOW_EXPERT_LOAD_COLLECTING unavailable in current vllm version.") self.num_expert_load_gather = self.expert_heat_collection_interval self.periodic_load_gather = False @@ -72,14 +71,13 @@ def init_eplb(self, expert_map_path, process): self.process = process - logger.info("[eplb/updator] Launched EPLB subprocess, pid=%s", self.process.pid) + logger.info("[ModelRunner] Launched EPLB process (pid=%s)", self.process.pid) def update_iteration(self): self.cur_iterations += 1 if self.cur_iterations == ( self.expert_heat_collection_interval + self.algorithm_execution_interval + self.num_moe_layers ): - logger.debug("[eplb/updator] Full EPLB cycle completed, clearing moe loads and resetting iteration counter") if self.expert_map_record_path is not None: self.adaptor._export_tensor_to_file(self.shared_dict["expert_maps"], self.expert_map_record_path) @@ -143,19 +141,17 @@ def compute_and_set_moe_load(self): moe_load = moe_load.permute(2, 0, 1, 3) self.shared_dict["moe_load"] = moe_load - logger.debug("[eplb/updator] Updated shared_dict['moe_load'] shape=%s", moe_load.shape) + logger.debug("[ModelRunner] Updated shared_dict['moe_load'] shape=%s", moe_load.shape) return moe_load def warm_up_eplb(self): - logger.info("[eplb/updator] Starting EPLB warm-up, rank=%s, world_size=%s", self.rank_id, self.world_size) self.shared_dict["expert_maps"] = self.adaptor.get_global_expert_map() self.compute_and_set_moe_load() src_tensor = torch.empty((1,), device=self.device) comm_op_list = [] - reqs = [] for dst_rank in range(self.world_size): if dst_rank == self.rank_id: @@ -171,7 +167,6 @@ def warm_up_eplb(self): for req in reqs: req.wait() - logger.info("[eplb/updator] EPLB warm-up completed") def shutdown(self): """ @@ -180,4 +175,4 @@ def shutdown(self): if self.process.is_alive(): self.process.terminate() self.process.join() - logger.info("[eplb/updator] EPLB subprocess terminated") + logger.info("[ModelRunner] EPLB process terminated") diff --git a/vllm_ascend/logger.py b/vllm_ascend/logger.py deleted file mode 100644 index 10bf84dcf..000000000 --- a/vllm_ascend/logger.py +++ /dev/null @@ -1,208 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project -"""Logging configuration for vLLM-Ascend. - -Provides two logging mechanisms: -1. Console: A dedicated handler on the vllm_ascend logger with - [vllm-ascend] [module] prefix. No modification to vLLM's global - logging state — safe for upstream tests and multiprocessing. -2. File: A rotating file handler on both vllm and vllm_ascend loggers, - capturing all logs with Ascend formatting. -""" - -import logging -import os -import sys -from datetime import datetime - -from vllm import envs -from vllm.logging_utils import ColoredFormatter, NewLineFormatter - -_FORMAT = "%(levelname)s %(asctime)s [%(fileinfo)s:%(lineno)d] %(message)s" -_DATE_FORMAT = "%m-%d %H:%M:%S" - -_LOG_DIR = os.path.join(os.path.expanduser("~"), "ascend", "log", "vllm_ascend") -_LOG_MAX_BYTES = 20 * 1024 * 1024 - - -def _use_color() -> bool: - """Determine if colored output should be used.""" - if envs.NO_COLOR or envs.VLLM_LOGGING_COLOR == "0": - return False - if envs.VLLM_LOGGING_COLOR == "1": - return True - if envs.VLLM_LOGGING_STREAM == "ext://sys.stdout": - return hasattr(sys.stdout, "isatty") and sys.stdout.isatty() - elif envs.VLLM_LOGGING_STREAM == "ext://sys.stderr": - return hasattr(sys.stderr, "isatty") and sys.stderr.isatty() - return False - - -def _is_ascend_module(pathname: str) -> bool: - if not pathname: - return False - return "vllm_ascend" in pathname.replace("\\", "/") - - -def _infer_module_name(pathname: str) -> str: - """Infer module name from the file path of the log caller.""" - if not pathname: - return "core" - parts = pathname.replace("\\", "/").split("/") - try: - idx = parts.index("vllm_ascend") - if idx + 1 >= len(parts): - return "core" - item = parts[idx + 1] - if idx + 2 >= len(parts): - return item[:-3] if item.endswith(".py") else item - return item - except ValueError: - return "core" - - -def _format_with_ascend_prefix(self, record, super_format): - if not _is_ascend_module(record.pathname): - return super_format(record) - module = _infer_module_name(record.pathname) - if record.filename == module + ".py": - prefix = "[vllm-ascend]" - else: - prefix = f"[vllm-ascend] [{module}]" - orig_msg = record.msg - orig_args = record.args - try: - record.msg = f"{prefix} - {record.getMessage()}" - record.args = () - return super_format(record) - finally: - record.msg = orig_msg - record.args = orig_args - - -class AscendFormatter(NewLineFormatter): - """Extends NewLineFormatter with [vllm-ascend] prefix and module name.""" - - def format(self, record): - return _format_with_ascend_prefix(self, record, super().format) - - -class AscendColoredFormatter(ColoredFormatter): - """Extends ColoredFormatter with [vllm-ascend] prefix and module name.""" - - def format(self, record): - return _format_with_ascend_prefix(self, record, super().format) - - -class RotatingAscendFileHandler(logging.FileHandler): - """FileHandler that rotates log files when they exceed a size limit. - - Naming convention: - vllm_ascend_{timestamp}_{pid}.log <- first file - vllm_ascend_{timestamp}_{pid}_002.log <- second file - vllm_ascend_{timestamp}_{pid}_003.log <- third file - """ - - def __init__(self, log_dir: str, max_bytes: int = _LOG_MAX_BYTES) -> None: - self._log_dir = log_dir - self._max_bytes = max_bytes - self._sequence = 1 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - self._base_name = f"vllm_ascend_{timestamp}_{os.getpid()}" - log_file = os.path.join(log_dir, f"{self._base_name}.log") - super().__init__(log_file, encoding="utf-8") - - def emit(self, record) -> None: - try: - if self.stream is not None and os.path.isfile(self.baseFilename): - if os.path.getsize(self.baseFilename) >= self._max_bytes: - self._rotate() - except OSError: - pass - super().emit(record) - - def _rotate(self) -> None: - self.stream.close() - self.stream = None # type: ignore[assignment] - self._sequence += 1 - new_file = os.path.join(self._log_dir, f"{self._base_name}_{self._sequence:03d}.log") - self.baseFilename = new_file - self.stream = self._open() - - -_file_logging_configured = False -_file_handler: logging.Handler | None = None - - -def _setup_file_logging(log_dir: str | None = None) -> None: - global _file_logging_configured, _file_handler - if _file_logging_configured: - return - target_dir = log_dir or _LOG_DIR - os.makedirs(target_dir, exist_ok=True) - file_handler = RotatingAscendFileHandler(target_dir) - vllm_logger = logging.getLogger("vllm") - ascend_logger = logging.getLogger("vllm_ascend") - log_level = logging.INFO - if vllm_logger.handlers: - log_level = vllm_logger.handlers[0].level - file_handler.setLevel(log_level) - file_handler.setFormatter(AscendFormatter(fmt=_FORMAT, datefmt=_DATE_FORMAT)) - vllm_logger.addHandler(file_handler) - ascend_logger.addHandler(file_handler) - _file_handler = file_handler - _file_logging_configured = True - - -def configure_ascend_file_logging() -> None: - global _file_logging_configured, _file_handler - log_dir = _LOG_DIR - try: - from vllm_ascend.ascend_config import get_ascend_config - - ascend_config = get_ascend_config() - log_dir = ascend_config.ascend_log_path - except Exception: - pass - if log_dir != _LOG_DIR: - vllm_logger = logging.getLogger("vllm") - ascend_logger = logging.getLogger("vllm_ascend") - if _file_handler is not None: - vllm_logger.removeHandler(_file_handler) - ascend_logger.removeHandler(_file_handler) - _file_handler.close() - _file_handler = None - _file_logging_configured = False - _setup_file_logging(log_dir) - - -def configure_ascend_logging() -> None: - """Configure vllm_ascend logger with Ascend formatters. - - Creates a dedicated handler for the vllm_ascend logger namespace, - avoiding any modification to vLLM's global logging state. - This approach is safe for upstream tests and multiprocessing. - """ - ascend_logger = logging.getLogger("vllm_ascend") - if ascend_logger.handlers: - return - - # Parse stream parameter - if envs.VLLM_LOGGING_STREAM == "ext://sys.stdout": - stream = sys.stdout - elif envs.VLLM_LOGGING_STREAM == "ext://sys.stderr": - stream = sys.stderr - else: - stream = sys.stderr - - handler = logging.StreamHandler(stream) - handler.setLevel(envs.VLLM_LOGGING_LEVEL) - - if _use_color(): - handler.setFormatter(AscendColoredFormatter(fmt=_FORMAT, datefmt=_DATE_FORMAT)) - else: - handler.setFormatter(AscendFormatter(fmt=_FORMAT, datefmt=_DATE_FORMAT)) - - ascend_logger.addHandler(handler) - ascend_logger.setLevel(envs.VLLM_LOGGING_LEVEL) - ascend_logger.propagate = False diff --git a/vllm_ascend/lora/punica_npu.py b/vllm_ascend/lora/punica_npu.py index 1cf8f8af3..d38a6ad4a 100755 --- a/vllm_ascend/lora/punica_npu.py +++ b/vllm_ascend/lora/punica_npu.py @@ -74,7 +74,7 @@ def _shrink_decode( w_t_all: torch.Tensor, scale: float, ): - self.bgmv_shrink(x, w_t_all, y, self._get_token_lora_indices(x), scale) + self.bgmv_shrink(x, w_t_all, y, self.token_lora_indices, scale) def _expand_prefill( self, @@ -101,7 +101,7 @@ def _expand_decode( w_t_all: torch.Tensor, add_inputs: bool, ): - self.bgmv_expand(x, w_t_all, y, self._get_token_lora_indices(x), add_inputs) + self.bgmv_expand(x, w_t_all, y, self.token_lora_indices, add_inputs) def _expand_slice_prefill( self, @@ -134,18 +134,7 @@ def _expand_slice_decode( y_slice_size: int, add_inputs: bool, ): - self.bgmv_expand_slice( - x, - w_t_all, - y, - self._get_token_lora_indices(x), - y_offset, - y_slice_size, - add_inputs, - ) - - def _get_token_lora_indices(self, x: torch.Tensor) -> torch.Tensor: - return torch.narrow(self._token_lora_indices, 0, 0, x.size(0)) + self.bgmv_expand_slice(x, w_t_all, y, self.token_lora_indices, y_offset, y_slice_size, add_inputs) def _apply_expand( self, @@ -355,7 +344,7 @@ def add_lora_logits( if buffer is None: buffer = torch.zeros((x.size(0), r), dtype=torch.float32, device=x.device) - indices = torch.narrow(self._sampler_indices, 0, 0, x.size(0)) + indices = self.sampler_indices self.bgmv_shrink(x, lora_a_stacked, buffer, indices, scale) self.bgmv_expand(buffer, lora_b_stacked, y, indices, add_inputs=True) diff --git a/vllm_ascend/model_executor/__init__.py b/vllm_ascend/model_executor/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/vllm_ascend/model_executor/offloader/__init__.py b/vllm_ascend/model_executor/offloader/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/vllm_ascend/model_executor/offloader/prefetch.py b/vllm_ascend/model_executor/offloader/prefetch.py deleted file mode 100644 index c8ab1b7ba..000000000 --- a/vllm_ascend/model_executor/offloader/prefetch.py +++ /dev/null @@ -1,263 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project -"""NPU port of PrefetchOffloader — uses torch.npu.* APIs including is_current_stream_capturing.""" - -from collections.abc import Generator - -import torch -import torch.nn as nn -import torch_npu # noqa: F401 -import vllm.model_executor.offloader.prefetch_ops # noqa: F401 -from vllm.logger import logger -from vllm.model_executor.offloader.base import BaseOffloader, should_pin_memory -from vllm.model_executor.offloader.prefetch import ( - ParamInfo, - StaticBufferPool, - _BaseParamOffloader, -) - - -class NPUPrefetchOffloader(BaseOffloader): - """NPU version of PrefetchOffloader — replaces torch.cuda.* with torch.npu.*.""" - - def __init__( - self, - group_size: int, - num_in_group: int, - prefetch_step: int, - offload_params: set[str] | None = None, - mode: str = "cpu", - ): - self.group_size = group_size - self.num_in_group = num_in_group - self.prefetch_step = prefetch_step - self.offload_params = offload_params or set() - self.mode = mode - self.copy_stream = torch.npu.Stream() - self.module_offloaders: list[_NPUModuleOffloader] = [] - self.buffer_pool: StaticBufferPool | None = None - self.total_offloaded_bytes = 0 - - def wrap_modules( - self, - modules_generator: Generator[nn.Module, None, None], - ) -> list[nn.Module]: - assert len(self.module_offloaders) == 0 - - all_modules = [] - offload_modules = [] - - for module_index, module in enumerate(modules_generator): - all_modules.append(module) - if module_index % self.group_size >= self.group_size - self.num_in_group: - if self.offload_params: - whitelist = [ - name - for name, _ in module.named_parameters() - if any(f".{p}." in f".{name}." for p in self.offload_params) - ] - else: - whitelist = [name for name, _ in module.named_parameters()] - - if not whitelist: - continue - - offload_modules.append(module) - self.module_offloaders.append( - _NPUModuleOffloader( - mode=self.mode, - module=module, - copy_stream=self.copy_stream, - whitelist_param_names=whitelist, - layer_idx=len(self.module_offloaders), - ) - ) - - for index, module in enumerate(offload_modules): - self._hook_module_forward(index, module) - - return all_modules - - def _hook_module_forward(self, index: int, module: nn.Module): - original_forward = module.forward - - def forward(*args, **kwargs): - module.forward = original_forward - input_tensor = args[0] if args else kwargs.get("hidden_states") - torch.ops.vllm.wait_prefetch(input_tensor, index) - output = original_forward(*args, **kwargs) - next_index = (index + self.prefetch_step) % len(self.module_offloaders) - if isinstance(output, tuple): - torch.ops.vllm.start_prefetch(output[0], next_index) - else: - torch.ops.vllm.start_prefetch(output, next_index) - module.forward = forward - return output - - module.forward = forward - - def _wait_for_layer(self, layer_idx: int): - offloader = self.module_offloaders[layer_idx] - if torch.npu.is_current_stream_capturing(): - if not offloader._prefetch_in_capture: - return - torch.npu.current_stream().wait_event(offloader._copy_done_event) - offloader._prefetch_in_capture = False - else: - if offloader._event_valid_for_eager: - torch.npu.current_stream().wait_event(offloader._copy_done_event) - else: - torch.npu.current_stream().wait_stream(self.copy_stream) - - def _start_prefetch(self, layer_idx: int): - self.module_offloaders[layer_idx].start_onload_to_static() - - def sync_prev_onload(self): - torch.npu.current_stream().wait_stream(self.copy_stream) - - def join_after_forward(self): - for offloader in self.module_offloaders: - if offloader._prefetch_in_capture: - torch.npu.current_stream().wait_event(offloader._copy_done_event) - offloader._prefetch_in_capture = False - - def post_init(self): - for offloader in self.module_offloaders: - offloader.sync_cpu_storage() - - param_infos: list[ParamInfo] = [] - device: torch.device | None = None - - for offloader in self.module_offloaders: - param_infos.extend(offloader.get_param_infos()) - if device is None: - device = offloader.device - - if device is None: - return - - self.buffer_pool = StaticBufferPool( - param_infos=param_infos, - slot_capacity=self.prefetch_step, - device=device, - ) - - for idx, offloader in enumerate(self.module_offloaders): - slot_idx = idx % self.prefetch_step - offloader.assign_buffer_slot(self.buffer_pool, slot_idx) - - for offloader in self.module_offloaders: - offloader.post_init() - self.total_offloaded_bytes += offloader.offloaded_bytes - - logger.info_once( - f"[NPUPrefetchOffloader] Initialized {len(self.module_offloaders)} modules. " - f"Total NPU memory saved: {self.total_offloaded_bytes / 1e9:.4f} GB, " - f"Static buffer pool: {self.buffer_pool.total_bytes / 1e9:.4f} GB " - f"(group_size={self.group_size}, num_in_group={self.num_in_group}, " - f"prefetch_step={self.prefetch_step})" - ) - - for i in range(min(self.prefetch_step, len(self.module_offloaders))): - self.module_offloaders[i].start_onload_to_static() - - -class _NPUModuleOffloader: - """NPU version of _ModuleOffloader: all torch.cuda.* → torch.npu.*.""" - - def __init__( - self, - mode: str, - module: nn.Module, - copy_stream: torch.npu.Stream, - whitelist_param_names: list[str], - layer_idx: int, - ): - self.mode = mode - self.module = module - self.device = next(module.parameters()).device - self.copy_stream = copy_stream - self.layer_idx = layer_idx - self.offloaded_bytes = 0 - - self._copy_done_event = torch.npu.Event() - self._event_valid_for_eager = False - self._prefetch_in_capture = False - - assert self.device != torch.device("cpu") - - self._buffer_pool: StaticBufferPool | None = None - self._buffer_slot_idx: int = 0 - - param_dict = dict(self.module.named_parameters()) - assert all(name in param_dict for name in whitelist_param_names) - - self._param_offloaders = { - name: _BaseParamOffloader.create(mode, module=module, param_name=name) for name in whitelist_param_names - } - - def post_init(self): - for param_offloader in self._param_offloaders.values(): - param_offloader.post_init() - self.offloaded_bytes += param_offloader.offloaded_bytes - - def sync_cpu_storage(self): - for param_offloader in self._param_offloaders.values(): - param_offloader.sync_cpu_storage() - - deleted = [ - name for name, offloader in self._param_offloaders.items() if getattr(offloader, "_param_deleted", False) - ] - for name in deleted: - del self._param_offloaders[name] - - def get_param_infos(self) -> list[ParamInfo]: - infos = [] - for name, offloader in self._param_offloaders.items(): - cpu_storage = offloader._cpu_storage - assert cpu_storage is not None - infos.append( - ParamInfo( - name=name, - shape=tuple(cpu_storage.shape), - stride=tuple(cpu_storage.stride()), - dtype=cpu_storage.dtype, - ) - ) - return infos - - def assign_buffer_slot(self, pool: StaticBufferPool, slot_idx: int): - self._buffer_pool = pool - self._buffer_slot_idx = slot_idx - for name, offloader in self._param_offloaders.items(): - cpu_storage = offloader._cpu_storage - assert cpu_storage is not None - buffer = pool.get_buffer( - name=name, - shape=tuple(cpu_storage.shape), - stride=tuple(cpu_storage.stride()), - dtype=cpu_storage.dtype, - slot_idx=slot_idx, - ) - offloader.assign_static_buffer(buffer) - - def start_onload_to_static(self): - assert self._buffer_pool is not None - - self._prefetch_in_capture = torch.npu.is_current_stream_capturing() - - fork_event = torch.npu.Event() - torch.npu.current_stream().record_event(fork_event) - self.copy_stream.wait_event(fork_event) - - with torch.npu.stream(self.copy_stream): - for name, offloader in self._param_offloaders.items(): - cpu_storage = offloader._cpu_storage - gpu_buffer = offloader._gpu_buffer - assert cpu_storage is not None - assert gpu_buffer is not None - assert not should_pin_memory() or cpu_storage.is_pinned(), f"CPU storage for {name} is not pinned!" - gpu_buffer.copy_(cpu_storage, non_blocking=True) - - self._copy_done_event.record(self.copy_stream) - self._event_valid_for_eager = not torch.npu.is_current_stream_capturing() diff --git a/vllm_ascend/model_loader/netloader/netloader.py b/vllm_ascend/model_loader/netloader/netloader.py index 0e167165f..29b3df99d 100644 --- a/vllm_ascend/model_loader/netloader/netloader.py +++ b/vllm_ascend/model_loader/netloader/netloader.py @@ -178,8 +178,7 @@ def load_model(self, vllm_config: VllmConfig, model_config: ModelConfig, prefix: else: target_device = torch.device(device_config.device) - _quant_config = getattr(vllm_config, "quant_config", None) - _quant_config = deepcopy(_quant_config) if _quant_config is not None else None + vllm_config_backup = deepcopy(vllm_config) model_config_backup = deepcopy(model_config) with set_default_torch_dtype(model_config.dtype): @@ -221,8 +220,7 @@ def load_model(self, vllm_config: VllmConfig, model_config: ModelConfig, prefix: if model is None: logger.warning("Netloader elastic loading fails, use load format DefaultModelLoader") - if hasattr(vllm_config, "quant_config"): - vllm_config.quant_config = _quant_config + vllm_config = vllm_config_backup model_config = model_config_backup del model diff --git a/vllm_ascend/models/deepseek_v4.py b/vllm_ascend/models/deepseek_v4.py index f516c35ee..4d96cfef6 100644 --- a/vllm_ascend/models/deepseek_v4.py +++ b/vllm_ascend/models/deepseek_v4.py @@ -64,13 +64,9 @@ maybe_prefix, sequence_parallel_chunk, ) -from vllm.models.deepseek_v4.attention import DeepseekV4IndexerCache # type: ignore[import-not-found,no-redef] -from vllm.models.deepseek_v4.compressor import CompressorStateCache # type: ignore[import-not-found,no-redef] from vllm.platforms import current_platform from vllm.sequence import IntermediateTensors from vllm.transformers_utils.configs.deepseek_v4 import DeepseekV4Config -from vllm.v1.attention.backends.mla.sparse_swa import DeepseekV4SWACache as VllmDeepseekV4SWACache -from vllm.v1.kv_cache_interface import KVCacheSpec, SlidingWindowMLASpec from vllm_ascend.ascend_config import get_ascend_config from vllm_ascend.ops.dsa import AscendDeepseekSparseAttention, DSAModules @@ -82,127 +78,15 @@ extract_dsv4_layer_index, get_ascend_device_type, get_dsv4_compress_ratio, + vllm_version_is, ) - -def _get_ascend_dsa_backend(): - # Keep this lazy to avoid vLLM model-inspection circular imports. - from vllm_ascend.attention.dsa_v1 import AscendDSABackend - - return AscendDSABackend - - -def _dsv4_block_sizes(): - # Lazy import to avoid the circular import chain (layer -> dsa_v1 -> - # attention_v1 -> device_op) hit during vLLM subprocess model inspection. - from vllm_ascend.models.layer.attention.layer import DSV4_BLOCK_SIZES - - return DSV4_BLOCK_SIZES - - -class AscendCompressorStateCache(CompressorStateCache): - def __init__( - self, - state_dim: int, - dtype: torch.dtype, - compress_ratio: int, - block_size: int, - prefix: str, - ): - super().__init__(state_dim, dtype, compress_ratio, prefix) - self.compress_ratio = compress_ratio - self.block_size = block_size - - def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec: - pads = _dsv4_block_sizes()[vllm_config.cache_config.block_size][1] - page_size_padded = pads[0] if self.state_dim == 2 * 256 and self.compress_ratio == 4 else pads[1] - - return SlidingWindowMLASpec( - block_size=self.block_size, - num_kv_heads=1, - head_size=self.state_dim, - dtype=self.dtype, - sliding_window=self.sliding_window, - alignment=None, - page_size_padded=page_size_padded, - ) - - def forward(self): ... - - def get_attn_backend(self): - return _get_ascend_dsa_backend() - - -class AscendDeepseekV4IndexerCache(DeepseekV4IndexerCache): - def __init__( - self, - head_dim: int, - dtype: torch.dtype, - prefix: str, - cache_config: CacheConfig, - compress_ratio: int = 1, - ): - super().__init__(head_dim, dtype, prefix, cache_config, compress_ratio) - - def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec: - if get_ascend_device_type() in {AscendDeviceType.A5}: - self.dtype = torch.float8_e4m3fn - vllm_config.cache_config.cache_dtype = "float8_e4m3fn" - - from vllm.v1.kv_cache_interface import MLAAttentionSpec - - return MLAAttentionSpec( - block_size=_dsv4_block_sizes()[vllm_config.cache_config.block_size][0][0], - num_kv_heads=1, - head_size=self.head_dim, - dtype=self.dtype, - model_version="deepseek_v4", - compress_ratio=self.compress_ratio, - cache_dtype_str=self.cache_config.cache_dtype, - scale_dim=1 if self.head_dim == 128 else 0, - scale_dtype=torch.float if get_ascend_device_type() in {AscendDeviceType.A5} else torch.float16, - ) - - def forward(self): ... - - def get_attn_backend(self): - return _get_ascend_dsa_backend() - - -class AscendDeepseekV4SWACache(VllmDeepseekV4SWACache): - def __init__( - self, - head_dim: int, - window_size: int, - dtype: torch.dtype, - prefix: str, - cache_config: CacheConfig, - ): - super().__init__(head_dim, window_size, torch.uint8, prefix, cache_config) - self.dtype = dtype - - self.block_size = _dsv4_block_sizes()[cache_config.block_size][0][1] - - def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec: - if get_ascend_device_type() in {AscendDeviceType.A5}: - self.dtype = torch.float8_e4m3fn - vllm_config.cache_config.cache_dtype = "float8_e4m3fn" - cached_head_size = self.head_dim + 128 if get_ascend_device_type() in {AscendDeviceType.A5} else self.head_dim - return SlidingWindowMLASpec( - block_size=self.block_size, - num_kv_heads=1, - head_size=cached_head_size, - dtype=self.dtype, - sliding_window=self.window_size, - cache_dtype_str=self.cache_config.cache_dtype, - model_version="deepseek_v4", - alignment=None, - ) - - def forward(self): ... - - def get_attn_backend(self): - return _get_ascend_dsa_backend() +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers.deepseek_compressor import CompressorStateCache # type:ignore + from vllm.model_executor.layers.deepseek_v4_attention import DeepseekV4IndexerCache # type:ignore +else: + from vllm.models.deepseek_v4.attention import DeepseekV4IndexerCache + from vllm.models.deepseek_v4.compressor import CompressorStateCache def hadamard_transform_ref(x: torch.Tensor, scale=1.0): @@ -561,7 +445,7 @@ def __init__( if self.compress_ratio == 4: # TODO(cmq): change the dtype of cache - self.k_cache = AscendDeepseekV4IndexerCache( + self.k_cache = DeepseekV4IndexerCache( head_dim=self.head_dim, dtype=k_dtype, prefix=f"{prefix}.k_cache", @@ -635,20 +519,20 @@ def __init__( state_dtype = torch.float32 # TODO(zyj): change following codes if block_size is configurable & refactor the magic numbers if compress_ratio == 4: - self.state_cache = AscendCompressorStateCache( + self.state_cache = CompressorStateCache( state_dim=2 * self.coff * self.head_dim, # kv_state + score_state dtype=state_dtype, compress_ratio=compress_ratio, prefix=f"{prefix}.state_cache", - block_size=_dsv4_block_sizes()[cache_config.block_size][0][2], # type: ignore[union-attr] + block_size=8, ) elif compress_ratio == 128: - self.state_cache = AscendCompressorStateCache( + self.state_cache = CompressorStateCache( state_dim=2 * self.head_dim, # kv_state + score_state dtype=state_dtype, compress_ratio=compress_ratio, prefix=f"{prefix}.state_cache", - block_size=_dsv4_block_sizes()[cache_config.block_size][0][3], # type: ignore[union-attr] + block_size=16 if get_ascend_device_type() in {AscendDeviceType.A5} else 32, ) else: raise ValueError( @@ -842,16 +726,6 @@ def __init__( if 0 <= indexer_seq_idx < len(pattern): skip_topk = pattern[indexer_seq_idx] == "S" - ascend_device_type = get_ascend_device_type() - k_dtype = torch.float8_e4m3fn if ascend_device_type == AscendDeviceType.A5 else torch.bfloat16 - swa_cache_layer = AscendDeepseekV4SWACache( - head_dim=self.head_dim, - window_size=self.window_size, - dtype=k_dtype, - prefix=f"{prefix}.swa_cache", - cache_config=cache_config, - ) - dsa_modules = DSAModules( wq_a=self.wq_a, q_norm=self.q_norm, @@ -864,7 +738,6 @@ def __init__( attn_sink=self.attn_sink, indexer=self.indexer, compressor=self.compressor, - swa_cache_layer=swa_cache_layer, topk_indices_buffer=topk_indices_buffer, skip_topk=skip_topk, ) diff --git a/vllm_ascend/models/deepseek_v4_mtp.py b/vllm_ascend/models/deepseek_v4_mtp.py index 30e95b90f..64796cbaa 100644 --- a/vllm_ascend/models/deepseek_v4_mtp.py +++ b/vllm_ascend/models/deepseek_v4_mtp.py @@ -306,7 +306,7 @@ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]: if ".w3." in name: name = name.replace(".w3.", ".up_proj.") - if name.endswith(".scale"): + if ".scale" in name: name = name.replace(".scale", ".weight_scale") if ".head." in name: diff --git a/vllm_ascend/models/layer/attention/layer.py b/vllm_ascend/models/layer/attention/layer.py index 9deaedead..96ae5a8f9 100644 --- a/vllm_ascend/models/layer/attention/layer.py +++ b/vllm_ascend/models/layer/attention/layer.py @@ -28,27 +28,6 @@ ) -def get_dsv4_block_sizes(): - # cache_config.block_size: [mla, swa, c4 state, c128 state], [page_size_padded_t1, page_size_padded_t2] - _DSV4_BLOCK_SIZES = { - 128: [[128, 128, 8, 32], [16640, 131072]], - 64: [[64, 64, 4, 16], [8320, 65536]], - 32: [[32, 32, 2, 8], [4160, 32768]], - } - _DSV4_BLOCK_SIZES_A5 = { - 128: [[128, 128, 8, 16], [16896, 81920]], - 64: [[64, 64, 4, 8], [8448, 40960]], - 32: [[32, 32, 2, 4], [4224, 20480]], - } - if get_ascend_device_type() in {AscendDeviceType.A5}: - return _DSV4_BLOCK_SIZES_A5 - else: - return _DSV4_BLOCK_SIZES - - -DSV4_BLOCK_SIZES = get_dsv4_block_sizes() - - class DSAAttention(nn.Module, AttentionLayerBase): """Multi-Head Latent Attention layer. @@ -183,7 +162,7 @@ def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec: (self.head_size + 128) if get_ascend_device_type() in {AscendDeviceType.A5} else self.head_size ) return MLAAttentionSpec( - block_size=DSV4_BLOCK_SIZES[vllm_config.cache_config.block_size][0][0], + block_size=128, num_kv_heads=1, head_size=cached_head_size, dtype=kv_cache_dtype, diff --git a/vllm_ascend/ops/bailing_moe_linear_attn.py b/vllm_ascend/ops/bailing_moe_linear_attn.py index e33a61b62..ad3d122db 100644 --- a/vllm_ascend/ops/bailing_moe_linear_attn.py +++ b/vllm_ascend/ops/bailing_moe_linear_attn.py @@ -27,25 +27,16 @@ import torch.nn.functional as F from vllm.forward_context import get_forward_context from vllm.model_executor.layers.fla.ops.layernorm_guard import layernorm_fn +from vllm.model_executor.layers.mamba.linear_attn import ( + clear_linear_attention_cache_for_new_sequences, + linear_attention_decode, + linear_attention_prefill_and_mix, +) from vllm.model_executor.models.bailing_moe_linear import BailingMoELinearAttention from vllm.v1.attention.backend import AttentionMetadata from vllm.v1.attention.backends.linear_attn import LinearAttentionMetadata from vllm_ascend.ops.triton.mamba.lightning_attn import AscendLightningAttentionKernel -from vllm_ascend.utils import vllm_version_is - -if vllm_version_is("0.22.1"): - from vllm.model_executor.layers.mamba.linear_attn import ( # type: ignore[import-not-found] - clear_linear_attention_cache_for_new_sequences, - linear_attention_decode, - linear_attention_prefill_and_mix, - ) -else: - from vllm.model_executor.layers.mamba.linear.minimax_linear_attn import ( # type: ignore[import-not-found] - clear_linear_attention_cache_for_new_sequences, - linear_attention_decode, - linear_attention_prefill_and_mix, - ) class AscendBailingMoELinearAttention(BailingMoELinearAttention): diff --git a/vllm_ascend/ops/dsa.py b/vllm_ascend/ops/dsa.py index a87516324..fe15c95ed 100644 --- a/vllm_ascend/ops/dsa.py +++ b/vllm_ascend/ops/dsa.py @@ -29,6 +29,7 @@ from vllm.model_executor.layers.quantization import QuantizationConfig from vllm.utils.torch_utils import direct_register_custom_op from vllm.v1.attention.backend import AttentionMetadata +from vllm.v1.attention.backends.mla.sparse_swa import DeepseekV4SWACache from vllm_ascend.models.layer.attention.layer import DSAAttention from vllm_ascend.utils import ( @@ -52,7 +53,6 @@ class DSAModules: attn_sink: torch.nn.Module indexer: torch.nn.Module | None compressor: torch.nn.Module | None - swa_cache_layer: torch.nn.Module topk_indices_buffer: torch.Tensor | None indexer_rotary_emb: torch.nn.Module | None = None skip_topk: bool = False @@ -112,7 +112,15 @@ def __init__( self.skip_topk = dsa_modules.skip_topk self.prefix = prefix - self.swa_cache_layer = dsa_modules.swa_cache_layer + ascend_device_type = get_ascend_device_type() + k_dtype = torch.float8_e4m3fn if ascend_device_type == AscendDeviceType.A5 else torch.bfloat16 + self.swa_cache_layer = DeepseekV4SWACache( + head_dim=self.head_dim, + window_size=self.window_size, + dtype=k_dtype, + prefix=f"{prefix}.swa_cache", + cache_config=cache_config, + ) self.dsa_attn = DSAAttention( dim=self.dim, @@ -189,6 +197,7 @@ def dsa_forward( attn_metadata = forward_context.attn_metadata if attn_metadata is None: + self.dsa_attn.impl.dsa_warmup_with_multistream(hidden_states) output.fill_(0) return diff --git a/vllm_ascend/ops/fused_moe/fused_moe.py b/vllm_ascend/ops/fused_moe/fused_moe.py index bdbacf7cd..c97cae848 100644 --- a/vllm_ascend/ops/fused_moe/fused_moe.py +++ b/vllm_ascend/ops/fused_moe/fused_moe.py @@ -27,6 +27,7 @@ from vllm.logger import logger from vllm.model_executor.layers.fused_moe.config import FusedMoEConfig from vllm.model_executor.layers.fused_moe.layer import FusedMoE, UnquantizedFusedMoEMethod +from vllm.model_executor.layers.fused_moe.routed_experts_capturer import RoutedExpertsCapturer from vllm.model_executor.layers.fused_moe.runner.moe_runner import MoERunner # type: ignore from vllm_ascend.ascend_config import get_ascend_config @@ -46,16 +47,20 @@ npu_stream_switch, shared_expert_dp_enabled, shared_experts_calculation_stream, + vllm_version_is, ) +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers.fused_moe.layer import get_compressed_expert_map +else: -def get_compressed_expert_map(expert_map: torch.Tensor) -> str: - global_indices = torch.where(expert_map != -1)[0] - local_indices = expert_map[global_indices] - return ", ".join( - f"{local_index.item()}->{global_index.item()}" - for local_index, global_index in zip(local_indices, global_indices) - ) + def get_compressed_expert_map(expert_map: torch.Tensor) -> str: + global_indices = torch.where(expert_map != -1)[0] + local_indices = expert_map[global_indices] + return ", ".join( + f"{local_index.item()}->{global_index.item()}" + for local_index, global_index in zip(local_indices, global_indices) + ) @dataclass @@ -175,7 +180,11 @@ def apply( input_ids=input_ids, ) if layer.vllm_config.model_config is not None and layer.vllm_config.model_config.enable_return_routed_experts: - capturer = getattr(layer, "_ascend_routed_experts_capturer", None) + if vllm_version_is("0.20.2"): + # In 0.20.2, capturer is a process-wide singleton. + capturer = RoutedExpertsCapturer.get_instance() + else: + capturer = getattr(layer, "_ascend_routed_experts_capturer", None) if capturer is not None: capturer.capture(layer_id=layer.layer_id, topk_ids=topk_ids) @@ -283,14 +292,6 @@ def _maybe_reduce_shared_expert_output( # output. Skip any additional reduction here. return shared_output - def _maybe_reduce_final_output( - self, - states: torch.Tensor, - trunc_size: int, - ) -> torch.Tensor: - states = torch.ops.vllm.maybe_all_reduce_tensor_model_parallel(states) - return states[..., :trunc_size] - # TODO: Remove this after drop v0.19.1 support def forward_impl( self, @@ -383,19 +384,13 @@ def __init__(self, *args, **kwargs): ascend_config = get_ascend_config() self.multistream_overlap_shared_expert = ascend_config.multistream_overlap_shared_expert and has_shared_experts self.shared_multistream_overlap_gate = ascend_config.multistream_overlap_gate and has_shared_experts - if self.multistream_overlap_shared_expert: - logger.info_once("[fused_moe/layer] Multistream overlap shared expert is enabled.") if enable_sp() and has_shared_experts: - logger.info_once( - "[fused_moe/layer] Sequence parallelism is enabled, shared experts are replicated for best performance." - ) + logger.info_once("Sequence parallelism is enabled, shared experts are replicated for best performance.") # flashcommon3 gate stream self.multistream_overlap_gate = ascend_config.multistream_overlap_gate if self.multistream_overlap_gate and AscendFusedMoE.gate_stream is None: AscendFusedMoE.gate_stream = torch.npu.Stream() - if self.multistream_overlap_gate: - logger.info_once("[fused_moe/layer] Multistream overlap gate is enabled.") vllm_config = get_current_vllm_config() if ( self.custom_routing_function is None @@ -419,13 +414,14 @@ def __init__(self, *args, **kwargs): self.global_num_experts = num_experts + self.global_redundant_expert_num self.dynamic_eplb = eplb_config.dynamic_eplb and (self.log2phy is not None) self.local_num_experts = self.global_num_experts // self.ep_size - self.expert_map_manager._local_num_experts = self.local_num_experts - self.expert_map_manager._expert_map = self._expert_map + if not vllm_version_is("0.20.2"): + self.expert_map_manager._local_num_experts = self.local_num_experts + self.expert_map_manager._expert_map = self._expert_map if self._expert_map is not None: logger.info_once( - "[fused_moe/layer] Expert parallelism is enabled." - " ep_rank=%s/%s, local_num_experts=%s, global_num_experts=%s," - " expert_map=%s", + "[EP Rank %s/%s] Expert parallelism is enabled. Local/global" + " number of experts: %s/%s. Experts local to global index map:" + " %s.", self.ep_rank, self.ep_size, self.local_num_experts, @@ -504,24 +500,14 @@ def _validate_shared_expert_consistency(self): if not torch.allclose(integrated_out, split_out): diff = (integrated_out - split_out).abs() + logger.error("FusedMoE shared experts split computation does not match the integrated computation.") + logger.error("Max absolute difference: %s", diff.max().item()) logger.error( - "[fused_moe/layer] Shared expert split computation validation failed." - " The split-path computation does not match the integrated-path result." - " max_abs_diff=%s, integrated_sum=%s, integrated_norm=%s," - " split_sum=%s, split_norm=%s, hidden_size=%s, dtype=%s.", - diff.max().item(), - integrated_out.sum().item(), - integrated_out.norm().item(), - split_out.sum().item(), - split_out.norm().item(), - self.hidden_size, - self.moe_config.in_dtype, + "Integrated output - sum: %s, norm: %s", integrated_out.sum().item(), integrated_out.norm().item() ) + logger.error("Split output - sum: %s, norm: %s", split_out.sum().item(), split_out.norm().item()) raise ValueError("FusedMoE shared experts split computation does not match the integrated computation.") - logger.info_once( - "[fused_moe/layer] Shared expert split computation validation passed." - " Integrated and split-path results are consistent." - ) + logger.info_once("FusedMoE shared experts split computation matches the integrated computation.") def _shared_experts_part1(self, hidden_states: torch.Tensor): shared_gate_up, _ = self._shared_experts.gate_up_proj(hidden_states) # type: ignore @@ -800,7 +786,17 @@ def maybe_wait_event(evt: torch.npu.Event | None): # Execute the gate projection and activation concurrently with the # dispatch communication. maybe_wait_event(fused_moe_evts.before_dispatch) - hidden_states = self._shared_experts.gate_up_proj((quantized_x, pertoken_scale))[0] + hidden_states = torch_npu.npu_quant_matmul( + quantized_x, + self._shared_experts.gate_up_proj.weight, + self._shared_experts.gate_up_proj.weight_scale, + scale_dtype=torch_npu.float8_e8m0fnu, + pertoken_scale=pertoken_scale, + pertoken_scale_dtype=torch_npu.float8_e8m0fnu, + bias=None, + output_dtype=original_dtype, + group_sizes=[1, 1, 32], + ) # Execute activation concurrently with gmm2. maybe_wait_event(fused_moe_evts.before_gmm2) quantized_x, swiglu_out_scale, _ = torch.ops._C_ascend.npu_swiglu_group_quant( @@ -814,7 +810,17 @@ def maybe_wait_event(evt: torch.npu.Event | None): # Execute the down projection concurrently with the combine # communication. maybe_wait_event(fused_moe_evts.before_combine) - shared_out = self._shared_experts.down_proj((quantized_x, swiglu_out_scale))[0] + shared_out = torch_npu.npu_quant_matmul( + quantized_x, + self._shared_experts.down_proj.weight, + self._shared_experts.down_proj.weight_scale, + scale_dtype=torch_npu.float8_e8m0fnu, + pertoken_scale=swiglu_out_scale, + pertoken_scale_dtype=torch_npu.float8_e8m0fnu, + bias=None, + output_dtype=original_dtype, + group_sizes=[1, 1, 32], + ) else: # Ensure the shared experts wait for hidden_states to be ready. torch.npu.current_stream().wait_event(fused_moe_evts.before_routed_experts) diff --git a/vllm_ascend/ops/fused_moe/moe_comm_method.py b/vllm_ascend/ops/fused_moe/moe_comm_method.py index 15cdc3052..c0baf66f5 100644 --- a/vllm_ascend/ops/fused_moe/moe_comm_method.py +++ b/vllm_ascend/ops/fused_moe/moe_comm_method.py @@ -130,7 +130,7 @@ def fused_experts( torch.bfloat16, torch.int8, torch.float8_e4m3fn, - ], f"Unsupported hidden_states dtype: {fused_experts_input.hidden_states.dtype}" + ] moe_comm_method = _EXTRA_CTX.moe_comm_method assert moe_comm_method is not None, "Missing communication context" diff --git a/vllm_ascend/ops/fused_moe/moe_mlp.py b/vllm_ascend/ops/fused_moe/moe_mlp.py index 2700efb0d..a29b6a4bf 100644 --- a/vllm_ascend/ops/fused_moe/moe_mlp.py +++ b/vllm_ascend/ops/fused_moe/moe_mlp.py @@ -440,7 +440,7 @@ def unified_apply_mlp(*, mlp_compute_input: MoEMlpComputeInput) -> torch.Tensor: ) assert w1_scale is not None and w2_scale is not None - act_quant_type = torch.int8 if mlp_compute_input.quant.is_int_quant else torch.float8_e4m3fn + act_quant_type = torch.float8_e4m3fn weight_quant_type = torch.float8_e4m3fn scale_type = None per_token_scale_type = None diff --git a/vllm_ascend/ops/fused_moe/moe_runtime_args.py b/vllm_ascend/ops/fused_moe/moe_runtime_args.py index 719f26978..11736d6f1 100644 --- a/vllm_ascend/ops/fused_moe/moe_runtime_args.py +++ b/vllm_ascend/ops/fused_moe/moe_runtime_args.py @@ -223,7 +223,7 @@ def build_mlp_compute_input( weights=fused_experts_input.weights, quant=fused_experts_input.quant, fusion=fused_experts_input.quant.quant_type - in (QuantType.W8A8, QuantType.MXFP8, QuantType.MXFP4, QuantType.W4A8MXFP, QuantType.W8A8FP8) + in (QuantType.W8A8, QuantType.MXFP8, QuantType.MXFP4, QuantType.W4A8MXFP) and use_fusion_ops, activation=fused_experts_input.activation, need_trans=fused_experts_input.need_trans, diff --git a/vllm_ascend/ops/fused_moe/moe_stage_params.py b/vllm_ascend/ops/fused_moe/moe_stage_params.py index ed8e0434b..4e19b4aea 100644 --- a/vllm_ascend/ops/fused_moe/moe_stage_params.py +++ b/vllm_ascend/ops/fused_moe/moe_stage_params.py @@ -75,24 +75,13 @@ def is_mxfp(self) -> bool: def is_int_quant(self) -> bool: return self.quant_type in (QuantType.W8A8, QuantType.W4A8) - @property - def is_fp8(self) -> bool: - return self.quant_type == QuantType.W8A8FP8 - @property def use_w4a8_per_channel_gmm_swiglu(self) -> bool: return self.quant_type == QuantType.W4A8 and self.is_per_channel_weight @property def dispatch_with_quant(self) -> bool: - return self.quant_type in ( - QuantType.W8A8, - QuantType.W4A8, - QuantType.MXFP8, - QuantType.MXFP4, - QuantType.W4A8MXFP, - QuantType.W8A8FP8, - ) + return self.quant_type in (QuantType.W8A8, QuantType.W4A8, QuantType.MXFP8, QuantType.MXFP4, QuantType.W4A8MXFP) __all__ = [ diff --git a/vllm_ascend/ops/fused_moe/prepare_finalize.py b/vllm_ascend/ops/fused_moe/prepare_finalize.py index 98fe5940d..3cbbfd6ab 100644 --- a/vllm_ascend/ops/fused_moe/prepare_finalize.py +++ b/vllm_ascend/ops/fused_moe/prepare_finalize.py @@ -392,26 +392,6 @@ def _prepare_with_ep_group( if self.multistream_overlap_gate: torch.npu.current_stream().wait_stream(PrepareAndFinalize.quant_stream) - if self.moe_config.pcp_size > 1: - max_tokens_across_pcp = _EXTRA_CTX.max_tokens_across_pcp - - self.num_tokens_pcp = hidden_states.shape[0] - pad_size = max_tokens_across_pcp - self.num_tokens_pcp - if pad_size > 0: - hidden_states = nn.functional.pad(hidden_states, (0, 0, 0, pad_size)) - router_logits = nn.functional.pad(router_logits, (0, 0, 0, pad_size)) - if pertoken_scale is not None: - pertoken_scale = ( - nn.functional.pad(pertoken_scale, (0, pad_size)) - if pertoken_scale.dim() == 1 - else nn.functional.pad(pertoken_scale, (0, 0, 0, pad_size)) - ) - - hidden_states = get_pcp_group().all_gather(hidden_states, dim=0) - router_logits = get_pcp_group().all_gather(router_logits, dim=0) - if pertoken_scale is not None: - pertoken_scale = get_pcp_group().all_gather(pertoken_scale, dim=0) - return MoEPrepareOutput( hidden_states=hidden_states, router_logits=router_logits, @@ -515,10 +495,6 @@ def _finalize_with_ep_group(self, hidden_states: torch.Tensor) -> torch.Tensor: 2 Reduce_results is True usually happens when model has no shared experts. We still do reduce scatter here, then skip allreudce in FusedMoe. """ - if self.moe_config.pcp_size > 1: - hidden_states = get_pcp_group().reduce_scatter(hidden_states, dim=0) - hidden_states = hidden_states[: self.num_tokens_pcp] - hidden_states = torch.ops.vllm.maybe_pad_and_reduce(hidden_states, True) return hidden_states @@ -539,5 +515,4 @@ def _finalize_with_dp_group(self, hidden_states: torch.Tensor, reduce_results: b if self.moe_config.pcp_size > 1: hidden_states = get_pcp_group().reduce_scatter(hidden_states, dim=0) - hidden_states = hidden_states[: self.num_tokens_pcp] return hidden_states diff --git a/vllm_ascend/ops/fused_moe/token_dispatcher.py b/vllm_ascend/ops/fused_moe/token_dispatcher.py index 8774955dc..31cc441d0 100644 --- a/vllm_ascend/ops/fused_moe/token_dispatcher.py +++ b/vllm_ascend/ops/fused_moe/token_dispatcher.py @@ -201,7 +201,7 @@ def get_dispatch_mc2_kwargs( # Only dispatch-enabled MXFP paths pass y_dtype through MC2. if ( self.a5_need_extra_args - and (token_dispatch_input.quant.is_mxfp or token_dispatch_input.quant.is_fp8) + and token_dispatch_input.quant.is_mxfp and token_dispatch_input.quant.dispatch_with_quant ): y_dtype = torch.float8_e4m3fn @@ -356,9 +356,7 @@ def token_dispatch( # TODO: After AllGather MXFP4 communication quantization thorough verification, remove this judgment. # MXFP4 keeps dispatch unquantized in AllGather path, and quantizes again inside the MLP path. with_quant = ( - token_dispatch_input.quant.dispatch_with_quant - and token_dispatch_input.quant.quant_type != QuantType.MXFP4 - and token_dispatch_input.quant.quant_type != QuantType.W8A8FP8 + token_dispatch_input.quant.dispatch_with_quant and token_dispatch_input.quant.quant_type != QuantType.MXFP4 ) is_mxfp = token_dispatch_input.quant.is_mxfp hidden_states = token_dispatch_input.hidden_states @@ -468,7 +466,7 @@ def token_dispatch( self, token_dispatch_input: MoETokenDispatchInput, ): - with_quant = token_dispatch_input.quant.is_int_quant or token_dispatch_input.quant.is_fp8 + with_quant = token_dispatch_input.quant.is_int_quant hidden_states = token_dispatch_input.hidden_states topk_weights = token_dispatch_input.topk_weights topk_ids = token_dispatch_input.topk_ids @@ -486,10 +484,7 @@ def token_dispatch( dynamic_scale_after_all2all = None if with_quant: - dst_type = torch.float8_e4m3fn if token_dispatch_input.quant.is_fp8 else torch.int8 - permutated_local_input_tokens, dynamic_scale = torch_npu.npu_dynamic_quant( - permutated_local_input_tokens, dst_type=dst_type - ) + permutated_local_input_tokens, dynamic_scale = torch_npu.npu_dynamic_quant(permutated_local_input_tokens) _, dynamic_scale_after_all2all, permute2_ep_all_to_all_handle = async_all_to_all( dynamic_scale, output_splits, input_splits, self.ep_group ) diff --git a/vllm_ascend/ops/gdn.py b/vllm_ascend/ops/gdn.py index 954f4e840..8148a026c 100644 --- a/vllm_ascend/ops/gdn.py +++ b/vllm_ascend/ops/gdn.py @@ -21,10 +21,19 @@ from vllm.distributed import get_pcp_group from vllm.forward_context import get_forward_context from vllm.model_executor.layers.fla.ops.l2norm import l2norm_fwd -from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention + +from vllm_ascend.utils import vllm_version_is + +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers.mamba.gdn_linear_attn import ( # type: ignore[import-not-found] + GatedDeltaNetAttention, + ) +else: + from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention + from vllm.model_executor.layers.mamba.mamba_utils import MambaStateShapeCalculator from vllm.triton_utils import triton -from vllm.v1.attention.backend import AttentionBackend, AttentionMetadata # type: ignore +from vllm.v1.attention.backend import AttentionMetadata # type: ignore from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata from vllm.v1.attention.backends.utils import PAD_SLOT_ID @@ -35,12 +44,11 @@ get_graph_params, ) from vllm_ascend.device.device_op import DeviceOperator -from vllm_ascend.ops.gdn_attn_builder import AscendGDNAttentionBackend from vllm_ascend.ops.triton.fla.chunk import chunk_gated_delta_rule from vllm_ascend.ops.triton.fla.fused_qkvzba_split_reshape import fused_qkvzba_split_reshape_cat from vllm_ascend.ops.triton.fla.utils import clear_ssm_states from vllm_ascend.ops.triton.mamba.causal_conv1d import causal_conv1d_fn -from vllm_ascend.utils import vllm_version_is, weak_ref_tensors +from vllm_ascend.utils import weak_ref_tensors def to_int64_tuple(tensor: torch.Tensor) -> tuple[int, ...]: @@ -52,29 +60,14 @@ def to_int64_tuple(tensor: torch.Tensor) -> tuple[int, ...]: def _check_and_get_host_args(attn_metadata, field_name: str, sub_field_name: str): if (fallback_meta := getattr(attn_metadata, field_name, None)) is None: - raise RuntimeError(f"Expected attn_metadata.{field_name}.{sub_field_name} for Ascend GDN fallback path.") + raise RuntimeError( + f"Expected attn_metadata.{field_name}.{sub_field_name} for patched GDN non-spec prefill path." + ) return fallback_meta -def _check_and_get_runtime_prefill_args(attn_metadata, field_name: str): - value = getattr(attn_metadata, field_name, None) - if value is None: - raise RuntimeError(f"Expected attn_metadata.{field_name} for Ascend GDN prefill path.") - return value - - def get_non_spec_causal_conv1d_host_args(attn_metadata) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]: - fallback_meta = getattr(attn_metadata, "non_spec_prefill_fallback_meta", None) - if fallback_meta is None: - query_start_loc = _check_and_get_runtime_prefill_args(attn_metadata, "non_spec_query_start_loc") - cache_indices = _check_and_get_runtime_prefill_args(attn_metadata, "non_spec_state_indices_tensor") - has_initial_state = _check_and_get_runtime_prefill_args(attn_metadata, "has_initial_state") - return ( - to_int64_tuple(query_start_loc), - to_int64_tuple(cache_indices), - to_int64_tuple(has_initial_state), - ) - + fallback_meta = _check_and_get_host_args(attn_metadata, "non_spec_prefill_fallback_meta", "causal_conv1d") causal_conv1d_meta = fallback_meta.causal_conv1d return ( to_int64_tuple(causal_conv1d_meta.query_start_loc_cpu), @@ -253,9 +246,7 @@ def update_conv1d_graph_params( def get_non_spec_chunked_prefill_meta(attn_metadata): - fallback_meta = getattr(attn_metadata, "non_spec_prefill_fallback_meta", None) - if fallback_meta is None: - return None + fallback_meta = _check_and_get_host_args(attn_metadata, "non_spec_prefill_fallback_meta", "chunk") return fallback_meta.chunk @@ -284,9 +275,6 @@ def _warmup_prefill_kernels(self, qkv_or_qkvz: torch.Tensor, v_dim: int) -> None def _warmup_prefill_kernels_v0202(self, mixed_qkv: torch.Tensor) -> None: return - def get_attn_backend(self) -> type[AttentionBackend]: - return AscendGDNAttentionBackend - def forward( self, hidden_states: torch.Tensor, @@ -304,10 +292,10 @@ def forward( ba, _ = self.in_proj_ba(hidden_states) z, _ = self.in_proj_z(hidden_states) z = z.reshape(z.size(0), -1, self.head_v_dim) - if vllm_version_is("0.22.1"): + if vllm_version_is("0.20.2"): b, a = ba.chunk(2, dim=-1) else: - b, a = self._split_ba_for_tp(ba) + b, a = self.split_ba(ba) b = b.contiguous() a = a.contiguous() else: @@ -319,10 +307,10 @@ def forward( mixed_qkv, z = mixed_qkvz.split([qkv_size, z_size], dim=-1) z = z.reshape(z.size(0), -1, self.head_v_dim) ba, _ = self.in_proj_ba(hidden_states) - if vllm_version_is("0.22.1"): + if vllm_version_is("0.20.2"): b, a = ba.chunk(2, dim=-1) else: - b, a = self._split_ba_for_tp(ba) + b, a = self.split_ba(ba) b = b.contiguous() a = a.contiguous() @@ -351,13 +339,12 @@ def forward( device=hidden_states.device, ) - if vllm_version_is("0.22.1"): - torch.ops.vllm.qwen_gdn_attention_core( + if vllm_version_is("0.20.2"): + torch.ops.vllm.gdn_attention_core( mixed_qkv, b, a, core_attn_out, - False, self.prefix, ) else: @@ -366,8 +353,8 @@ def forward( b, a, core_attn_out, - self.prefix, False, + self.prefix, ) # ============================================================ diff --git a/vllm_ascend/ops/mm_encoder_attention.py b/vllm_ascend/ops/mm_encoder_attention.py index 3acfbd16b..6ae573399 100644 --- a/vllm_ascend/ops/mm_encoder_attention.py +++ b/vllm_ascend/ops/mm_encoder_attention.py @@ -15,34 +15,27 @@ # limitations under the License. # -"""Ascend implementation of upstream :class:`MMEncoderAttention`. - -Eager and ACL-graph capture both use Fused Infer Attention (``npu_fused_infer_attention_score``) -with ``graph_task_group_begin/end`` so replay-time host metadata can be rebound from the update stream, -matching the LLM full-graph pattern in :mod:`vllm_ascend.attention.attention_v1`. -""" - -from __future__ import annotations - import einops import numpy as np import torch import torch.nn.functional as F -import torch_npu from vllm.model_executor.layers.attention.mm_encoder_attention import MMEncoderAttention # type: ignore from vllm.v1.attention.backends.registry import AttentionBackendEnum -from vllm_ascend.utils import weak_ref_tensors -from vllm_ascend.worker.encoder_acl_graph import ( - get_encoder_forward_context, - get_encoder_graph_params, - update_encoder_graph_workspace, -) +from vllm_ascend.device.device_op import DeviceOperator -MIN_PAD_SIZE: int = 64 -MAX_PAD_SIZE: int = 128 -SWA_INT_MAX: int = 2147483647 -FIA_BLOCK_SIZE: int = 128 +MIN_PAD_SIZE: int = 64 # min_size to pad weight +MAX_PAD_SIZE: int = 128 # max_size to pad weight + +# Use seq_lens CPU cache to avoid frequent d2h copy. +# AscendMMEncoderAttention will copy the cu_seqlens from NPU to CPU in every +# forward, since the op _npu_flash_attention_unpad() requires CPU cu_seqlens +# (otherwise it will break down). +# Thus, we use seq_lens_cpu_cache to cache this tensor, since it's shared +# between all layers, but may change in different forward step. When the +# current layer_index is 0, we update the cache, otherwise we directly use the +# cache to avoid frequent diff and copy operations, which are costful. +seq_lens_cpu_cache: torch.Tensor = None class AscendMMEncoderAttention(MMEncoderAttention): @@ -90,27 +83,6 @@ def maybe_compute_seq_lens( return seq_lens - def _maybe_compute_actual_seq_lengths( - self, - bsz: int, - q_len: int, - cu_seqlens: torch.Tensor | None, - sequence_lengths: torch.Tensor | None, - ) -> tuple[list[int], list[int]]: - """Build FIA ``actual_seq_lengths`` as cumulative host-side ``list[int]``.""" - if sequence_lengths is not None: - seq_lens_cpu = sequence_lengths - if seq_lens_cpu.device.type != "cpu": - seq_lens_cpu = seq_lens_cpu.to("cpu") - actual = seq_lens_cpu.cumsum(0).to(torch.int64).tolist() - else: - cu = self._maybe_compute_cu_seqlens(bsz, q_len, cu_seqlens) - if cu.device.type != "cpu": - cu = cu.to("cpu") - actual = cu[1:].to(torch.int64).tolist() - - return actual, actual - def _reshape_qkv_to_3d( self, query: torch.Tensor, @@ -150,240 +122,56 @@ def _maybe_compute_cu_seqlens( cu_seqlens = torch.arange(0, (bsz + 1) * q_len, step=q_len, dtype=torch.int32, device="cpu") return cu_seqlens - def _maybe_pad_qkv( - self, - q: torch.Tensor, - k: torch.Tensor, - v: torch.Tensor, - ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, int | None]: - if not self.enable_pad: - return q, k, v, None - origin_head_dim = q.shape[-1] - pad_len = MAX_PAD_SIZE - origin_head_dim - q = F.pad(q, (0, pad_len), mode="constant", value=0) - k = F.pad(k, (0, pad_len), mode="constant", value=0) - v = F.pad(v, (0, pad_len), mode="constant", value=0) - return q, k, v, origin_head_dim - - @staticmethod - def _maybe_unpad_output( - context_layer: torch.Tensor, - origin_head_dim: int | None, - ) -> torch.Tensor: - if origin_head_dim is not None: - return context_layer[..., :origin_head_dim] - return context_layer - - @staticmethod - def _restore_batch_layout( - context_layer: torch.Tensor, - *, - bsz: int, - q_len: int, - is_reshaped: bool, - ) -> torch.Tensor: - if is_reshaped: - return einops.rearrange(context_layer, "(b s) h d -> b s h d", b=bsz, s=q_len).contiguous() - return einops.rearrange(context_layer, "(b s) h d -> b s (h d)", b=bsz, s=q_len).contiguous() - - def _run_vit_fia( - self, - query: torch.Tensor, - key: torch.Tensor, - value: torch.Tensor, - actual_seq_lengths_q: list[int], - actual_seq_lengths_kv: list[int], - *, - out: torch.Tensor | None = None, - softmax_lse: torch.Tensor | None = None, - workspace: torch.Tensor | None = None, - ) -> torch.Tensor: - fia_kwargs = dict( - query=query, - key=key, - value=value, - atten_mask=None, - block_table=None, - input_layout="TND", - block_size=FIA_BLOCK_SIZE, - actual_seq_lengths=actual_seq_lengths_q, - actual_seq_lengths_kv=actual_seq_lengths_kv, - num_key_value_heads=self.num_kv_heads, - num_heads=self.num_heads, - scale=self.scale_value, - sparse_mode=0, - pre_tokens=SWA_INT_MAX, - next_tokens=SWA_INT_MAX, - ) - if out is None: - context_layer, _ = torch_npu.npu_fused_infer_attention_score(**fia_kwargs) - return context_layer - if workspace is None: - workspace = torch_npu._npu_fused_infer_attention_score_get_max_workspace(**fia_kwargs) - if softmax_lse is None: - softmax_lse = torch.empty(1, dtype=query.dtype, device=query.device) - torch_npu.npu_fused_infer_attention_score.out( - workspace=workspace, - out=[out, softmax_lse], - **fia_kwargs, - ) - return out - - def _forward_eager_fia( - self, - query: torch.Tensor, - key: torch.Tensor, - value: torch.Tensor, - *, - seq_lens: torch.Tensor, - cu_seqlens: torch.Tensor, - is_reshaped: bool, - bsz: int, - q_len: int, - ) -> torch.Tensor: - actual_seq_lengths_q, actual_seq_lengths_kv = self._maybe_compute_actual_seq_lengths( # TODO - bsz, - q_len, - cu_seqlens, - seq_lens, - ) - q, k, v, origin_head_dim = self._maybe_pad_qkv(query, key, value) - context_layer = self._run_vit_fia(q, k, v, actual_seq_lengths_q, actual_seq_lengths_kv) - context_layer = self._maybe_unpad_output(context_layer, origin_head_dim) - return self._restore_batch_layout( - context_layer, - bsz=bsz, - q_len=q_len, - is_reshaped=is_reshaped, - ) - - def _forward_capture_fia( - self, - query: torch.Tensor, - key: torch.Tensor, - value: torch.Tensor, - *, - cu_seqlens: torch.Tensor | None, - sequence_lengths: torch.Tensor | None, - is_reshaped: bool, - bsz: int, - q_len: int, - kv_len: int, - ) -> torch.Tensor: - context = get_encoder_forward_context() - token_budget = context.token_budget - params = get_encoder_graph_params() - if token_budget is None or params is None: - raise RuntimeError("Encoder graph capture state was not initialized (missing token_budget).") - - actual_seq_lengths_q, actual_seq_lengths_kv = self._maybe_compute_actual_seq_lengths( - bsz, - q_len, - cu_seqlens, - sequence_lengths, - ) - q, k, v, origin_head_dim = self._maybe_pad_qkv(query, key, value) - - out = torch.empty_like(q) - softmax_lse = torch.empty(1, dtype=q.dtype, device=q.device) - - workspace = params.workspaces.get(token_budget) - if workspace is None: - workspace = torch_npu._npu_fused_infer_attention_score_get_max_workspace( - query=q, - key=k, - value=v, - atten_mask=None, - block_table=None, - input_layout="TND", - block_size=FIA_BLOCK_SIZE, - actual_seq_lengths=actual_seq_lengths_q, - actual_seq_lengths_kv=actual_seq_lengths_kv, - num_key_value_heads=self.num_kv_heads, - num_heads=self.num_heads, - sparse_mode=0, - scale=self.scale_value, - pre_tokens=SWA_INT_MAX, - next_tokens=SWA_INT_MAX, - ) - update_encoder_graph_workspace(token_budget, workspace) - - stream = torch_npu.npu.current_stream() - event = torch.npu.ExternalEvent() - event.wait(stream) - event.reset(stream) - - torch.npu.graph_task_group_begin(stream) - self._run_vit_fia( - q, - k, - v, - actual_seq_lengths_q, - actual_seq_lengths_kv, - out=out, - softmax_lse=softmax_lse, - workspace=workspace, - ) - handle = torch.npu.graph_task_group_end(stream) - - vit_layer_idx = context.capture_layer_cursor - context.capture_layer_cursor = vit_layer_idx + 1 - uses_sequence_lengths_host = sequence_lengths is not None - packed = ( - weak_ref_tensors(q), - weak_ref_tensors(k), - weak_ref_tensors(v), - None, - None, - FIA_BLOCK_SIZE, - uses_sequence_lengths_host, - vit_layer_idx, - self.num_kv_heads, - self.num_heads, - self.scale_value, - weak_ref_tensors(out), - weak_ref_tensors(softmax_lse), - ) - params.attn_params[token_budget].append(packed) - params.events[token_budget].append(event) - params.handles[token_budget].append(handle) - - context_layer = self._maybe_unpad_output(out, origin_head_dim) - return self._restore_batch_layout( - context_layer, - bsz=bsz, - q_len=q_len, - is_reshaped=is_reshaped, - ) - def forward_oot( self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, cu_seqlens: torch.Tensor | None = None, - max_seqlen: torch.Tensor | None = None, # Unused on Ascend (upstream API compat) + max_seqlen: torch.Tensor | None = None, # Only used for Flash Attention sequence_lengths: torch.Tensor | None = None, ): bsz, q_len = query.size()[:2] kv_len = key.size(1) is_reshaped = query.dim() == 4 - q, k, v = self._reshape_qkv_to_3d(query, key, value, bsz, q_len, kv_len) + if sequence_lengths is not None: + # Use pre-compute seq_lens before vision blocks. + if sequence_lengths.device.type != "cpu": + sequence_lengths = sequence_lengths.to("cpu") + seq_lens_cpu = sequence_lengths + else: + # Convert cu_seqlens to seq_lens and move it to CPU, since FA requires CPU seq_lens. + # NOTE: This will considerably hurt performance. + cu_seqlens = self._maybe_compute_cu_seqlens(bsz, q_len, cu_seqlens) + seq_lens_cpu = torch.diff(cu_seqlens).to("cpu") - if get_encoder_forward_context().capturing: - return self._forward_capture_fia( - q, - k, - v, - cu_seqlens=cu_seqlens, - sequence_lengths=sequence_lengths, - is_reshaped=is_reshaped, - bsz=bsz, - q_len=q_len, - kv_len=kv_len, - ) + # q, k, v: [b, s, head, head_dim] -> [b * s, head, head_dim] + q, k, v = self._reshape_qkv_to_3d(query, key, value, bsz, q_len, kv_len) - return self._forward_eager_fia( - q, k, v, seq_lens=sequence_lengths, cu_seqlens=cu_seqlens, is_reshaped=is_reshaped, bsz=bsz, q_len=q_len + if self.enable_pad: + origin_shape = q.shape[-1] + pad_len = MAX_PAD_SIZE - origin_shape + # [b * s, head, head_dim] -> [b * s, head, MAX_PAD_SIZE] + q = F.pad(q, (0, pad_len), mode="constant", value=0) + k = F.pad(k, (0, pad_len), mode="constant", value=0) + v = F.pad(v, (0, pad_len), mode="constant", value=0) + + context_layer = DeviceOperator.npu_flash_attention( + query=q, + key=k, + value=v, + seq_lens_cpu=seq_lens_cpu, + head_num=self.num_heads, + scale_value=self.scale_value, + num_kv_heads=self.num_kv_heads, ) + + if self.enable_pad: + context_layer = context_layer[..., :origin_shape] + + if is_reshaped: + context_layer = einops.rearrange(context_layer, "(b s) h d -> b s h d", b=bsz).contiguous() + else: + context_layer = einops.rearrange(context_layer, "(b s) h d -> b s (h d)", b=bsz).contiguous() + return context_layer diff --git a/vllm_ascend/ops/triton/activation/swiglu_quant.py b/vllm_ascend/ops/triton/activation/swiglu_quant.py index 832acf0ba..c90f2e393 100644 --- a/vllm_ascend/ops/triton/activation/swiglu_quant.py +++ b/vllm_ascend/ops/triton/activation/swiglu_quant.py @@ -66,7 +66,7 @@ def _swiglu_quant_kernel( def swiglu_quant(x, group_list, group_list_type, need_quant=True): # group_list_type must be 0 cusum or 1 count if group_list_type not in [0, 1]: - raise ValueError(f"swiglu_quant: group_list_type must be 0 or 1, but got {group_list_type}") + raise ValueError(f"group_list_type must be 0 or 1, but got {group_list_type}") s, h = x.shape out_dtype = torch.int8 if need_quant else x.dtype out = torch.empty((s, h // 2), dtype=out_dtype, device=x.device) @@ -78,9 +78,7 @@ def swiglu_quant(x, group_list, group_list_type, need_quant=True): elif group_list.dtype == torch.int32: num_experts_algin = (num_experts + 15) // 16 * 16 else: - raise ValueError( - f"swiglu_quant: group_list dtype must be torch.int32 or torch.int64, but got {group_list.dtype}" - ) + raise ValueError(f"group_list dtype must be torch.int32 or torch.int64, but got {group_list.dtype}") num_vectorcore = get_vectorcore_num() _swiglu_quant_kernel[(num_vectorcore,)]( diff --git a/vllm_ascend/ops/triton/bincount.py b/vllm_ascend/ops/triton/bincount.py index 4299dd58a..be9c0dc7d 100644 --- a/vllm_ascend/ops/triton/bincount.py +++ b/vllm_ascend/ops/triton/bincount.py @@ -28,7 +28,7 @@ from vllm_ascend.ops.triton.triton_utils import get_vectorcore_num -@triton.jit(do_not_specialize=["batch_size", "seq_len"]) +@triton.jit def token_bin_counts_and_mask_kernel( tokens_ptr, tokens_batch_stride, @@ -40,43 +40,52 @@ def token_bin_counts_and_mask_kernel( tp_rank, counts_batch_stride, counts_vocab_stride, - total_blocks, SEQ_BLOCK: tl.constexpr, ): """Count token occurrences per batch row. - 1D grid with grid-stride loop: each program processes blocks at - stride=num_programs to stay within the Triton-Ascend coreDim - limit (65535) while distributing work evenly across cores. + 2D tiling: + - axis=0: core/program group dimension + - axis=1: block id dimension + + We linearize (batch_idx, seq_block_id) into a single global block id and + distribute blocks across all programs to improve utilization when + batch_size is small but seq_len is large (typical prefill). + + Tokens with value >= vocab_size (e.g. padding) are skipped. """ - pid = tl.program_id(axis=0) - num_progs = tl.num_programs(axis=0) + pid0 = tl.program_id(axis=0) + pid1 = tl.program_id(axis=1) + progs = tl.num_programs(axis=0) vocab_start_idx = tp_rank * vocab_size n_seq_blocks = tl.cdiv(seq_len, SEQ_BLOCK) + linear_block = pid1 * progs + pid0 + total_blocks = batch_size * n_seq_blocks + if linear_block >= total_blocks: + return + + batch_idx = linear_block // n_seq_blocks + seq_block_id = linear_block - batch_idx * n_seq_blocks + seq_start = seq_block_id * SEQ_BLOCK + + batch_tokens_start = tokens_ptr + batch_idx * tokens_batch_stride + batch_counts_start = bin_counts_ptr + batch_idx * counts_batch_stride + + pos_offsets = seq_start + tl.arange(0, SEQ_BLOCK) + pos_mask = pos_offsets < seq_len + token = tl.load( + batch_tokens_start + pos_offsets * tokens_seq_stride, + mask=pos_mask, + other=vocab_size + vocab_start_idx, + ) - for linear_block in tl.range(pid, total_blocks, num_progs): - batch_idx = linear_block // n_seq_blocks - seq_block_id = linear_block - batch_idx * n_seq_blocks - seq_start = seq_block_id * SEQ_BLOCK - - batch_tokens_start = tokens_ptr + batch_idx * tokens_batch_stride - batch_counts_start = bin_counts_ptr + batch_idx * counts_batch_stride - - pos_offsets = seq_start + tl.arange(0, SEQ_BLOCK) - pos_mask = pos_offsets < seq_len - token = tl.load( - batch_tokens_start + pos_offsets * tokens_seq_stride, - mask=pos_mask, - other=vocab_size + vocab_start_idx, - ) - - local_token = token - vocab_start_idx - token_in_range = pos_mask & (token >= vocab_start_idx) & (local_token < vocab_size) + local_token = token - vocab_start_idx + token_in_range = pos_mask & (token >= vocab_start_idx) & (local_token < vocab_size) - safe_local_token = tl.where(token_in_range, local_token, 0) - count_ptr = batch_counts_start + safe_local_token * counts_vocab_stride - tl.atomic_add(count_ptr, 1, mask=token_in_range) + safe_local_token = tl.where(token_in_range, local_token, 0) + count_ptr = batch_counts_start + safe_local_token * counts_vocab_stride + tl.atomic_add(count_ptr, 1, mask=token_in_range) def get_token_bin_counts_and_mask_triton( @@ -101,7 +110,8 @@ def get_token_bin_counts_and_mask_triton( assert n_rows == num_seqs, f"tokens rows must match num_seqs: tokens.shape[0]={n_rows}, num_seqs={num_seqs}" n_rows = num_seqs if num_seqs is not None else n_rows - if n_rows == 0 or n_cols == 0: + # seq_len == 0 is valid for empty decode history; return directly. + if n_cols == 0: bin_counts = torch.zeros((n_rows, vocab_size), dtype=torch.int32, device=tokens.device) return bin_counts, bin_counts > 0 @@ -111,20 +121,21 @@ def get_token_bin_counts_and_mask_triton( if not tokens.is_contiguous(): tokens = tokens.contiguous() - # 1D grid: distribute all (batch, seq_block) work items across - # vector cores via a loop inside the kernel. This avoids the - # Triton-Ascend grid-size limit of 65535. + # 2D grid: (progs, blocks_per_prog_group) + # Keep axis-0 bounded by vector core count, and distribute (batch, seq_block) + # blocks across all programs to increase utilization when n_rows is small. SEQ_BLOCK = 256 n_seq_blocks = triton.cdiv(n_cols, SEQ_BLOCK) total_blocks = n_rows * n_seq_blocks - grid_size = min(core_num, total_blocks) + progs = min(core_num, total_blocks) + grid = (progs, triton.cdiv(total_blocks, progs)) if get_ascend_config().enable_reduce_sample: tp_group = get_tp_group() tp_rank = tp_group.rank_in_group else: tp_rank = 0 - token_bin_counts_and_mask_kernel[(grid_size,)]( + token_bin_counts_and_mask_kernel[grid]( tokens, tokens.stride(0), tokens.stride(1), @@ -135,8 +146,6 @@ def get_token_bin_counts_and_mask_triton( tp_rank, bin_counts.stride(0), bin_counts.stride(1), - total_blocks, SEQ_BLOCK=SEQ_BLOCK, - multibuffer=False, ) return bin_counts, bin_counts > 0 diff --git a/vllm_ascend/ops/triton/fla/chunk.py b/vllm_ascend/ops/triton/fla/chunk.py index e4d765aa9..2178a80a1 100644 --- a/vllm_ascend/ops/triton/fla/chunk.py +++ b/vllm_ascend/ops/triton/fla/chunk.py @@ -48,9 +48,7 @@ def chunk_gated_delta_rule_fwd( num_decodes = attn_metadata.num_decodes chunk_size = 64 block_indices_cumsum = None if prebuilt_meta is None else prebuilt_meta.block_indices_cumsum - cu_seqlens_host = None if prebuilt_meta is None else prebuilt_meta.cu_seqlens_host chunk_indices_chunk64 = None if prebuilt_meta is None else prebuilt_meta.chunk_indices_chunk64 - chunk_indices_chunk64_host = None if prebuilt_meta is None else prebuilt_meta.chunk_indices_chunk64_host chunk_offsets_chunk64 = None if prebuilt_meta is None else prebuilt_meta.chunk_offsets_chunk64 update_chunk_offsets_chunk64 = None if prebuilt_meta is None else prebuilt_meta.update_chunk_offsets_chunk64 final_chunk_indices_chunk64 = None if prebuilt_meta is None else prebuilt_meta.final_chunk_indices_chunk64 @@ -93,12 +91,8 @@ def chunk_gated_delta_rule_fwd( g_ascendc = g.transpose(1, 2).contiguous() q_ascendc = q.to(torch.bfloat16).transpose(1, 2).contiguous() - cu_seqlens = None if cu_seqlens is None else cu_seqlens.to(torch.int64) + cu_seqlens = cu_seqlens.to(torch.int64) chunk_indices = None if chunk_indices_chunk64 is None else chunk_indices_chunk64.to(torch.int64) - if cu_seqlens_host is None and cu_seqlens is not None: - cu_seqlens_host = tuple(cu_seqlens.tolist()) - if chunk_indices_chunk64_host is None and chunk_indices is not None: - chunk_indices_chunk64_host = tuple(chunk_indices.flatten().tolist()) h, v_new, final_state = torch.ops._C_ascend.chunk_gated_delta_rule_fwd_h( k_ascendc, w_ascendc, @@ -109,8 +103,8 @@ def chunk_gated_delta_rule_fwd( output_final_state=True, chunk_size=64, save_new_value=True, - cu_seqlens=cu_seqlens_host, - chunk_indices=chunk_indices_chunk64_host, + cu_seqlens=cu_seqlens.tolist() if cu_seqlens is not None else None, + chunk_indices=chunk_indices.flatten().tolist() if chunk_indices is not None else None, use_exp2=False, transpose_state_layout=False, ) @@ -175,8 +169,8 @@ def chunk_gated_delta_rule_fwd( scale, g=g_ascendc, g_gamma=None, - cu_seqlens=cu_seqlens_host, - chunk_indices=chunk_indices_chunk64_host, + cu_seqlens=cu_seqlens.tolist() if cu_seqlens is not None else None, + chunk_indices=chunk_indices.flatten().tolist() if chunk_indices is not None else None, chunk_size=64, transpose_state_layout=False, ) @@ -315,14 +309,14 @@ def chunk_gated_delta_rule( if head_first: raise DeprecationWarning( - "chunk_gated_delta_rule: head_first is deprecated and will be removed in a future version. " + "head_first is deprecated and will be removed in a future version. " "Please use head_first=False for now instead.", stacklevel=2, ) q, k, v, beta, g = map(lambda x: rearrange(x, "b h t ... -> b t h ..."), (q, k, v, beta, g)) if not head_first and q.shape[1] < q.shape[2]: warnings.warn( - f"chunk_gated_delta_rule: Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). " "This may indicate the inputs were passed in head-first format [B, H, T, ...] " "when head_first=False was specified. " "Please verify your input tensor format matches the expected shape [B, T, H, ...].", @@ -331,12 +325,12 @@ def chunk_gated_delta_rule( if cu_seqlens is not None: if q.shape[0] != 1: raise ValueError( - f"chunk_gated_delta_rule: The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." f"Please flatten variable-length inputs before processing." ) if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: raise ValueError( - f"chunk_gated_delta_rule: The number of initial states is expected to be equal to the number of input sequences, " + f"The number of initial states is expected to be equal to the number of input sequences, " f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}." ) if scale is None: diff --git a/vllm_ascend/ops/triton/fla/cumsum.py b/vllm_ascend/ops/triton/fla/cumsum.py index 5c101c7e8..959656170 100644 --- a/vllm_ascend/ops/triton/fla/cumsum.py +++ b/vllm_ascend/ops/triton/fla/cumsum.py @@ -138,7 +138,7 @@ def chunk_local_cumsum( ) else: raise ValueError( - f"chunk_local_cumsum: Unsupported input shape {g.shape}, " + f"Unsupported input shape {g.shape}, " f"which should be (B, T, H, D) if `head_first=False` " f"or (B, H, T, D) otherwise" ) diff --git a/vllm_ascend/ops/triton/fla/l2norm.py b/vllm_ascend/ops/triton/fla/l2norm.py index 5f85ae484..28abeccd8 100644 --- a/vllm_ascend/ops/triton/fla/l2norm.py +++ b/vllm_ascend/ops/triton/fla/l2norm.py @@ -45,7 +45,7 @@ def l2norm_fwd(x: torch.Tensor, eps: float = 1e-6, output_dtype: torch.dtype | N MAX_FUSED_SIZE = 65536 // x.element_size() BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) if D > BD: - raise RuntimeError(f"l2norm_fwd: This layer doesn't support feature dim >= 64KB, got {D}.") + raise RuntimeError("This layer doesn't support feature dim >= 64KB.") MBLOCK = 69 # M, N = x.shape diff --git a/vllm_ascend/ops/triton/fla/layernorm_guard.py b/vllm_ascend/ops/triton/fla/layernorm_guard.py index 2b68ca4e0..ad2327adf 100644 --- a/vllm_ascend/ops/triton/fla/layernorm_guard.py +++ b/vllm_ascend/ops/triton/fla/layernorm_guard.py @@ -128,7 +128,7 @@ def _layer_norm_fwd( MAX_FUSED_SIZE = 65536 // x.element_size() BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(group_size)) if group_size > BLOCK_N: - raise RuntimeError(f"_layer_norm_fwd: This layer norm doesn't support feature dim >= 64KB, got {group_size}.") + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") # heuristics for number of warps num_warps = min(max(BLOCK_N // 256, 1), 8) grid = (M if M < MAX_CORES else MAX_CORES, ngroups) diff --git a/vllm_ascend/ops/triton/fla/sigmoid_gating.py b/vllm_ascend/ops/triton/fla/sigmoid_gating.py index c530d0299..bd683cb6d 100644 --- a/vllm_ascend/ops/triton/fla/sigmoid_gating.py +++ b/vllm_ascend/ops/triton/fla/sigmoid_gating.py @@ -243,9 +243,13 @@ def fused_sigmoid_gating_delta_rule_update_kernel( b_h = tl.zeros([BK, BV], dtype=tl.float32) if USE_INITIAL_STATE: idx = tl.load(h0_indices + i_n) - if idx >= 0: - p_h0 = h0_source + idx * HV * K * V + i_hv * K * V + o_k[:, None] * V + o_v[None, :] - b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + # if idx >= 0: + tmp0 = tl.where(idx < 0, 0, idx) + p_h0 = h0_source + tmp0 * HV * K * V + i_hv * K * V + o_k[:, None] * V + o_v[None, :] + temp1 = tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + temp2 = tl.zeros_like(temp1) + value0 = tl.where(idx < 0, temp2, temp1) + b_h += value0 # tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) for i in range(0, T): # Load inputs diff --git a/vllm_ascend/ops/triton/fla/utils.py b/vllm_ascend/ops/triton/fla/utils.py index ca4ab9981..c85c3257f 100644 --- a/vllm_ascend/ops/triton/fla/utils.py +++ b/vllm_ascend/ops/triton/fla/utils.py @@ -115,9 +115,8 @@ def clear_ssm_states(ssm_states: torch.Tensor, has_initial_state: torch.Tensor) if num_rows == 0: return if has_initial_state.numel() != num_rows: - raise ValueError( - f"clear_ssm_states: has_initial_state size mismatch: expected {num_rows}, got {has_initial_state.numel()}" - ) + raise ValueError(f"has_initial_state size mismatch: expected {num_rows}, got {has_initial_state.numel()}") + inner_size = ssm_states.numel() // num_rows if inner_size == 0: return diff --git a/vllm_ascend/ops/triton/gdn_chunk_meta.py b/vllm_ascend/ops/triton/gdn_chunk_meta.py index 01cfa5e47..7af19b914 100644 --- a/vllm_ascend/ops/triton/gdn_chunk_meta.py +++ b/vllm_ascend/ops/triton/gdn_chunk_meta.py @@ -92,36 +92,30 @@ def _validate_optional_output( if tensor is None: return if tensor.device != expected_device: - raise ValueError( - f"chunk_gated_delta_rule meta: {name} must be on device {expected_device}, got {tensor.device}" - ) + raise ValueError(f"{name} must be on device {expected_device}, got {tensor.device}") if tensor.dtype not in (torch.int32, torch.int64): - raise ValueError(f"chunk_gated_delta_rule meta: {name} must have int32 or int64 dtype, got {tensor.dtype}") + raise ValueError(f"{name} must have int32 or int64 dtype, got {tensor.dtype}") if not tensor.is_contiguous(): - raise ValueError(f"chunk_gated_delta_rule meta: {name} must be contiguous") + raise ValueError(f"{name} must be contiguous") if expected_shape is not None and tuple(tensor.shape) != expected_shape: - raise ValueError( - f"chunk_gated_delta_rule meta: {name} must have shape {expected_shape}, got {tuple(tensor.shape)}" - ) + raise ValueError(f"{name} must have shape {expected_shape}, got {tuple(tensor.shape)}") def _validate_cu_seqlens(cu_seqlens: torch.Tensor, chunk_size: int) -> None: if not isinstance(cu_seqlens, torch.Tensor): - raise TypeError("chunk_gated_delta_rule meta: cu_seqlens must be a torch.Tensor") + raise TypeError("cu_seqlens must be a torch.Tensor") if cu_seqlens.device.type != "npu": - raise ValueError(f"chunk_gated_delta_rule meta: cu_seqlens must be on NPU, got {cu_seqlens.device}") + raise ValueError(f"cu_seqlens must be on NPU, got {cu_seqlens.device}") if cu_seqlens.dtype not in (torch.int32, torch.int64): - raise ValueError( - f"chunk_gated_delta_rule meta: cu_seqlens must have int32 or int64 dtype, got {cu_seqlens.dtype}" - ) + raise ValueError(f"cu_seqlens must have int32 or int64 dtype, got {cu_seqlens.dtype}") if cu_seqlens.ndim != 1: - raise ValueError(f"chunk_gated_delta_rule meta: cu_seqlens must be 1D, got shape {tuple(cu_seqlens.shape)}") + raise ValueError(f"cu_seqlens must be 1D, got shape {tuple(cu_seqlens.shape)}") if cu_seqlens.shape[0] < 1: - raise ValueError("chunk_gated_delta_rule meta: cu_seqlens must contain at least one element") + raise ValueError("cu_seqlens must contain at least one element") if not cu_seqlens.is_contiguous(): - raise ValueError("chunk_gated_delta_rule meta: cu_seqlens must be contiguous") + raise ValueError("cu_seqlens must be contiguous") if chunk_size <= 0: - raise ValueError(f"chunk_gated_delta_rule meta: chunk_size must be positive, got {chunk_size}") + raise ValueError(f"chunk_size must be positive, got {chunk_size}") def _build_seq_lens(cu_seqlens: torch.Tensor) -> torch.Tensor: @@ -205,10 +199,7 @@ def _build_chunk_meta_device_from_seq_lens( expected_device=seq_lens.device, ) if out_chunk_indices is not None and (out_chunk_indices.ndim != 2 or out_chunk_indices.shape[1] != 2): - raise ValueError( - f"chunk_gated_delta_rule meta: out_chunk_indices must have shape [num_chunks, 2]," - f"got {tuple(out_chunk_indices.shape)}" - ) + raise ValueError(f"out_chunk_indices must have shape [num_chunks, 2], got {tuple(out_chunk_indices.shape)}") _validate_optional_output( "out_chunk_offsets", out_chunk_offsets, @@ -293,7 +284,7 @@ def build_chunk_meta_device( if validate_inputs: _validate_cu_seqlens(cu_seqlens, chunk_size) elif chunk_size <= 0: - raise ValueError(f"chunk_gated_delta_rule meta: chunk_size must be positive, got {chunk_size}") + raise ValueError(f"chunk_size must be positive, got {chunk_size}") _build_chunk_meta_device_from_seq_lens( _build_seq_lens(cu_seqlens) if seq_lens is None else seq_lens, chunk_size, diff --git a/vllm_ascend/ops/triton/layernorm_gated.py b/vllm_ascend/ops/triton/layernorm_gated.py index a0b442df1..0da630526 100644 --- a/vllm_ascend/ops/triton/layernorm_gated.py +++ b/vllm_ascend/ops/triton/layernorm_gated.py @@ -138,7 +138,7 @@ def layer_norm_fwd_npu( MAX_FUSED_SIZE = 65536 // x.element_size() BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(group_size)) if group_size > BLOCK_N: - raise RuntimeError(f"layer_norm_fwd_npu: Feature dim too large, got {group_size}, max supported is {BLOCK_N}.") + raise RuntimeError("Feature dim too large.") # Choose BLOCK_M: e.g., 16, 32, 64 — depends on NPU vector core capacity BLOCK_M = 64 # Tune this based on your NPU's register/shared memory diff --git a/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope_simt.py b/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope_simt.py index d1a40db8f..a7d21634f 100644 --- a/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope_simt.py +++ b/vllm_ascend/ops/triton/linearnorm/split_qkv_rmsnorm_rope_simt.py @@ -13,10 +13,10 @@ def precompute_rope_cos_sin_kernel( cos_sin_cache_gm_ptr, out_cos_sin_gm_ptr, batch_size, - N, batch_size_per_vec: tl.constexpr, ROPE_DIM: tl.constexpr, num_vectorcore: tl.constexpr, + N: tl.constexpr, ): row_pid = tl.program_id(0) input_batch_offset = row_pid * batch_size_per_vec @@ -317,10 +317,10 @@ def split_qkv_rmsnorm_rope_simt_impl( cos_sin_cache, cos_sin_precomputed, batch_size, - N, batch_size_per_vec_cos_sin, rope_dim, num_vectorcore, + N, force_simt_only=True, ) diff --git a/vllm_ascend/ops/triton/linearnorm/split_qkv_tp_rmsnorm_rope.py b/vllm_ascend/ops/triton/linearnorm/split_qkv_tp_rmsnorm_rope.py index 2f00b4b63..21feafa0c 100644 --- a/vllm_ascend/ops/triton/linearnorm/split_qkv_tp_rmsnorm_rope.py +++ b/vllm_ascend/ops/triton/linearnorm/split_qkv_tp_rmsnorm_rope.py @@ -25,13 +25,11 @@ from vllm_ascend.ops.triton.triton_utils import extract_slice, get_vectorcore_num, insert_slice -# TODO: UB size differs across chips; consider whether BLOCK_SIZE can -# be dynamically computed with a formula instead of autotuning {1,2,4}. @triton.autotune( configs=[ - triton.Config({"BLOCK_SIZE": 1}), - triton.Config({"BLOCK_SIZE": 2}), - triton.Config({"BLOCK_SIZE": 4}), + triton.Config({"BLOCK": 64}), + triton.Config({"BLOCK": 128}), + triton.Config({"BLOCK": 256}), ], key=["q_cols", "k_cols"], ) @@ -45,84 +43,65 @@ def _split_qkv_and_compute_local_qk_var_kernel( num_tokens, q_cols: tl.constexpr, k_cols: tl.constexpr, - q_cols_pow2: tl.constexpr, - k_cols_pow2: tl.constexpr, - qkv_stride: tl.constexpr, - q_inv_size: tl.constexpr, - k_inv_size: tl.constexpr, - BLOCK_SIZE: tl.constexpr, + BLOCK: tl.constexpr, ): - """ - Grid Stride Loop + batch loading + precomputed reciprocal. - (BLOCK_SIZE is limited to 1-4 to prevent UB overflow for large hidden_size) - """ - pid = tl.program_id(0) - num_pids = tl.num_programs(0) - block_range = tl.arange(0, BLOCK_SIZE) - - # Grid Stride Loop: each program processes BLOCK_SIZE tokens at a time - stride = num_pids * BLOCK_SIZE - start_token_idx = pid * BLOCK_SIZE - - for block_start in tl.range(start_token_idx, num_tokens, stride): - token_indices = block_start + block_range - token_mask = (token_indices < num_tokens)[:, None] - - # === Batch load QKV data === - # Q: [BLOCK_SIZE, q_cols] - q_offset = tl.arange(0, q_cols_pow2)[None, :] - q_mask = token_mask & (q_offset < q_cols) - q_batch = tl.load( - input_ptr + token_indices[:, None] * qkv_stride + q_offset, - mask=q_mask, - other=0.0, - ) - q_batch_f32 = q_batch.to(tl.float32) - - # K: [BLOCK_SIZE, k_cols], K follows immediately after Q - k_offset = tl.arange(0, k_cols_pow2)[None, :] - k_mask = token_mask & (k_offset < k_cols) - k_batch = tl.load( - input_ptr + token_indices[:, None] * qkv_stride + q_cols + k_offset, - mask=k_mask, - other=0.0, - ) - k_batch_f32 = k_batch.to(tl.float32) - - # V: [BLOCK_SIZE, k_cols], V is at offset Q + 2*K - v_offset = tl.arange(0, k_cols_pow2)[None, :] - v_mask = token_mask & (v_offset < k_cols) - v_batch = tl.load( - input_ptr + token_indices[:, None] * qkv_stride + q_cols + k_cols + v_offset, - mask=v_mask, - other=0.0, - ) - - # === Batch compute sum of squares === - q_squaresum = tl.sum(q_batch_f32 * q_batch_f32, axis=-1) * q_inv_size - k_squaresum = tl.sum(k_batch_f32 * k_batch_f32, axis=-1) * k_inv_size - - # === Batch store QKV output === - # Store Q - q_out_offset = token_indices[:, None] * q_cols + q_offset - q_out_mask = token_mask & (q_offset < q_cols) - tl.store(q_out_ptr + q_out_offset, q_batch, mask=q_out_mask) - - # Store K - k_out_offset = token_indices[:, None] * k_cols + k_offset - k_out_mask = token_mask & (k_offset < k_cols) - tl.store(k_out_ptr + k_out_offset, k_batch, mask=k_out_mask) - - # Store V - v_out_offset = token_indices[:, None] * k_cols + v_offset - v_out_mask = token_mask & (v_offset < k_cols) - tl.store(v_out_ptr + v_out_offset, v_batch, mask=v_out_mask) + pid = tl.program_id(0).to(tl.int64) + num_programs = tl.num_programs(0) + tokens_per_program = tl.cdiv(num_tokens, num_programs) + iter_num_per_program = tokens_per_program + program_token_offset = pid * tokens_per_program + program_token_end = min(program_token_offset + tokens_per_program, num_tokens) + input_row_stride = q_cols + 2 * k_cols - # === Store variance === - var_offset = token_indices * 2 - var_mask = token_indices < num_tokens - tl.store(qk_var_ptr + var_offset, q_squaresum, mask=var_mask) - tl.store(qk_var_ptr + var_offset + 1, k_squaresum, mask=var_mask) + for iter in tl.range(iter_num_per_program): + idx = program_token_offset + iter + token_mask = idx < program_token_end + input_base = input_ptr + idx * input_row_stride + + q_in_base = input_base + q_out_base = q_out_ptr + idx * q_cols + q_sum = tl.zeros((), dtype=tl.float32) + q_comp = tl.zeros((), dtype=tl.float32) + for q_off in tl.static_range(0, q_cols, BLOCK): + q_offsets = q_off + tl.arange(0, BLOCK) + q_mask = token_mask & (q_offsets < q_cols) + q_vals = tl.load(q_in_base + q_offsets, mask=q_mask, other=0.0) + q_vals_f32 = q_vals.to(tl.float32) + q_chunk = tl.sum(q_vals_f32 * q_vals_f32, axis=0) + y = q_chunk - q_comp + t = q_sum + y + q_comp = (t - q_sum) - y + q_sum = t + tl.store(q_out_base + q_offsets, q_vals, mask=q_mask) + q_var = q_sum / q_cols + + k_in_base = input_base + q_cols + k_out_base = k_out_ptr + idx * k_cols + k_sum = tl.zeros((), dtype=tl.float32) + k_comp = tl.zeros((), dtype=tl.float32) + for k_off in tl.static_range(0, k_cols, BLOCK): + k_offsets = k_off + tl.arange(0, BLOCK) + k_mask = token_mask & (k_offsets < k_cols) + k_vals = tl.load(k_in_base + k_offsets, mask=k_mask, other=0.0) + k_vals_f32 = k_vals.to(tl.float32) + k_chunk = tl.sum(k_vals_f32 * k_vals_f32, axis=0) + y = k_chunk - k_comp + t = k_sum + y + k_comp = (t - k_sum) - y + k_sum = t + tl.store(k_out_base + k_offsets, k_vals, mask=k_mask) + k_var = k_sum / k_cols + + v_in_base = input_base + q_cols + k_cols + v_out_base = v_out_ptr + idx * k_cols + for v_off in tl.static_range(0, k_cols, BLOCK): + v_offsets = v_off + tl.arange(0, BLOCK) + v_mask = token_mask & (v_offsets < k_cols) + v_vals = tl.load(v_in_base + v_offsets, mask=v_mask, other=0.0) + tl.store(v_out_base + v_offsets, v_vals, mask=v_mask) + + tl.store(qk_var_ptr + idx * 2, q_var, mask=token_mask) + tl.store(qk_var_ptr + idx * 2 + 1, k_var, mask=token_mask) @triton.jit @@ -279,13 +258,11 @@ def split_qkv_tp_rmsnorm_rope_impl( q_num_heads = q_hidden_size // head_dim k_num_heads = kv_hidden_size // head_dim + cos_2d = cos.view(num_tokens, -1) + sin_2d = sin.view(num_tokens, -1) + q_2d = q.view(num_tokens, -1) + k_2d = k.view(num_tokens, -1) qk_var = torch.empty(num_tokens, 2, dtype=torch.float32, device=q.device) - # Precompute reciprocal to avoid division inside kernel - q_inv_size = 1.0 / q_cols - k_inv_size = 1.0 / k_cols - # Pad to power-of-2 for tl.arange (required by Ascend NPU Triton backend) - q_cols_pow2 = 1 << (q_cols - 1).bit_length() - k_cols_pow2 = 1 << (k_cols - 1).bit_length() _split_qkv_and_compute_local_qk_var_kernel[grid]( input_2d, q, @@ -295,19 +272,10 @@ def split_qkv_tp_rmsnorm_rope_impl( num_tokens, q_cols, k_cols, - q_cols_pow2, - k_cols_pow2, - q_cols + 2 * k_cols, - q_inv_size, - k_inv_size, ) if tp_world > 1: qk_var = tensor_model_parallel_all_reduce(qk_var) - cos_2d = cos.view(num_tokens, -1) - sin_2d = sin.view(num_tokens, -1) - q_2d = q.view(num_tokens, -1) - k_2d = k.view(num_tokens, -1) _apply_global_rmsnorm_kernel[grid]( q_2d, k_2d, diff --git a/vllm_ascend/ops/triton/mamba/causal_conv1d.py b/vllm_ascend/ops/triton/mamba/causal_conv1d.py index 4e96587ec..58e3e17a7 100644 --- a/vllm_ascend/ops/triton/mamba/causal_conv1d.py +++ b/vllm_ascend/ops/triton/mamba/causal_conv1d.py @@ -16,8 +16,6 @@ from vllm.triton_utils import HAS_TRITON, tl, triton from vllm.v1.attention.backends.utils import PAD_SLOT_ID # type: ignore -from vllm_ascend.ops.triton.triton_utils import get_vectorcore_num - if not HAS_TRITON: from vllm_ascend._310p.ops.causal_conv1d import ( causal_conv1d_update as _pytorch_update, @@ -44,7 +42,7 @@ def causal_conv1d_ref( out: (batch, dim, seqlen) """ if activation not in [None, "silu", "swish"]: - raise NotImplementedError(f"causal_conv1d_ref activation must be None, silu, or swish, got {activation}") + raise NotImplementedError("activation must be None, silu, or swish") dtype_in = x.dtype x = x.to(weight.dtype) seqlen = x.shape[-1] @@ -115,7 +113,7 @@ def causal_conv1d_fn( num_decodes = attn_metadata.num_decodes if activation not in [None, "silu", "swish"]: - raise NotImplementedError(f"causal_conv1d_fn: activation must be None, silu, or swish, got {activation}") + raise NotImplementedError("activation must be None, silu, or swish") if x.stride(-1) != 1: x = x.contiguous() bias = bias.contiguous() if bias is not None else None @@ -126,14 +124,13 @@ def causal_conv1d_fn( seqlens = seqlens.tolist() splits = torch.split(x, seqlens, dim=-1) width = weight.shape[1] - state_len = width - 1 - last_width_prefill_x = extract_last_width(x, query_start_loc[num_decodes:], state_len) + last_width_prefill_x = extract_last_width(x, query_start_loc[num_decodes:], conv_states.shape[-1]) if get_pcp_group().world_size > 1: all_last_width_prefill_x = get_pcp_group().all_gather(last_width_prefill_x.unsqueeze(0).contiguous(), 0) pcp_rank = get_pcp_group().rank_in_group if pcp_rank > 0: - conv_states[cache_indices[num_decodes:], :, :state_len] = all_last_width_prefill_x[pcp_rank - 1, ...] + conv_states[cache_indices[num_decodes:]] = all_last_width_prefill_x[pcp_rank - 1, ...] for i in range(len(seqlens)): x_s = splits[i] @@ -152,7 +149,7 @@ def causal_conv1d_fn( ) if get_pcp_group().world_size > 1: - conv_states[cache_indices[num_decodes:], :, :state_len] = all_last_width_prefill_x[-1, ...] + conv_states[cache_indices[num_decodes:]] = all_last_width_prefill_x[-1, ...] out_ref.append(torch.cat([t[0] for t in out_ref_b], dim=-1)) out_ref_tensor = torch.cat(out_ref, dim=0) return out_ref_tensor @@ -653,7 +650,9 @@ def causal_conv1d_update_npu( # -------- tiling heuristic-------- # keep program count around ~[80..160] - CORE_HINT = get_vectorcore_num() + # vector core 40 + # TODO: use driver to get the vector core num + CORE_HINT = 40 # channel tile: 512 when dim large (reduce tasks), else 256 block_n = 512 if dim >= 512 else 256 g = triton.cdiv(dim, block_n) diff --git a/vllm_ascend/ops/triton/reject_sample.py b/vllm_ascend/ops/triton/reject_sample.py index 89c4427ba..bf0ebc7d1 100644 --- a/vllm_ascend/ops/triton/reject_sample.py +++ b/vllm_ascend/ops/triton/reject_sample.py @@ -155,17 +155,10 @@ def rejection_random_sample_kernel( vocab_size, # vocab_size or selected_vocab_size if ENABLE_REDUCE_SAMPLING global_vocab_size, # global vocab size for draft_probs indexing (only used if ENABLE_REDUCE_SAMPLING) vec_len, - ori_target_probs_ptr, # [num_tokens, ori_vocab_size] original probs for entropy - NO_ORI_TARGET_PROBS: tl.constexpr, NO_DRAFT_PROBS: tl.constexpr, ENABLE_REDUCE_SAMPLING: tl.constexpr, # Whether using reduce sampling - ENTROPY_VERIFY: tl.constexpr, BLOCK_SIZE: tl.constexpr, VOCAB_BLOCK_SIZE: tl.constexpr = 512, - POSTERIOR_THRESHOLD: tl.constexpr = 0.95, - POSTERIOR_ALPHA: tl.constexpr = 0.4, - SUB_BLOCK: tl.constexpr = 4096, - EPSILON: tl.constexpr = 1e-10, ): block_idx = tl.program_id(0) offsets = block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) @@ -190,106 +183,69 @@ def rejection_random_sample_kernel( token_idx = start_idx + pos draft_token_id = tl.load(draft_token_ids_ptr + token_idx) - if draft_token_id == -1: + target_prob = 0.0 + found = False + + for v_offset in range(0, vocab_size, VOCAB_BLOCK_SIZE): + if not found: + vocab_offsets = v_offset + tl.arange(0, VOCAB_BLOCK_SIZE) + vocab_mask = vocab_offsets < vocab_size + + candidate_indices = tl.load( + target_indices_ptr + token_idx * vocab_size + vocab_offsets, + mask=vocab_mask, + other=-1, + ) + + match_mask = candidate_indices == draft_token_id + + candidate_probs = tl.load( + target_probs_ptr + token_idx * vocab_size + vocab_offsets, + mask=vocab_mask, + other=0.0, + ) + + current_match_prob = tl.sum(candidate_probs * match_mask, axis=0) + if current_match_prob > 0.0: + target_prob = current_match_prob + found = True + + if NO_DRAFT_PROBS: + draft_prob = 1 + else: + draft_prob = tl.load(draft_probs_ptr + token_idx * global_vocab_size + draft_token_id) + + uniform_prob = tl.load(uniform_probs_ptr + token_idx) + + # Acceptance condition + if draft_prob > 0 and target_prob / draft_prob >= uniform_prob: + # Accept + token_id = draft_token_id + else: + # Reject - use recovered token rejected = True token_id = tl.load(recovered_token_ids_ptr + token_idx) - else: - target_prob = 0.0 - found = False - - for v_offset in range(0, vocab_size, VOCAB_BLOCK_SIZE): - if not found: - vocab_offsets = v_offset + tl.arange(0, VOCAB_BLOCK_SIZE) - vocab_mask = vocab_offsets < vocab_size - - candidate_indices = tl.load( - target_indices_ptr + token_idx * vocab_size + vocab_offsets, - mask=vocab_mask, - other=-1, - ) - - match_mask = candidate_indices == draft_token_id - - candidate_probs = tl.load( - target_probs_ptr + token_idx * vocab_size + vocab_offsets, - mask=vocab_mask, - other=0.0, - ) - - current_match_prob = tl.sum(candidate_probs * match_mask, axis=0) - if current_match_prob > 0.0: - target_prob = current_match_prob - found = True - - if NO_DRAFT_PROBS: - draft_prob = 1 - else: - draft_prob = tl.load(draft_probs_ptr + token_idx * global_vocab_size + draft_token_id) - - uniform_prob = tl.load(uniform_probs_ptr + token_idx) - - # Acceptance condition - if draft_prob > 0 and target_prob / draft_prob >= uniform_prob: - # Accept - token_id = draft_token_id - else: - # Reject - use recovered token - rejected = True - token_id = tl.load(recovered_token_ids_ptr + token_idx) tl.store(output_token_ids_ptr + req_idx * (max_spec_len + 1) + pos, token_id) else: - token_idx = start_idx + pos - draft_token_id = tl.load(draft_token_ids_ptr + token_idx) - if draft_token_id == -1: - rejected = True - token_id = tl.load(recovered_token_ids_ptr + token_idx) + draft_token_id = tl.load(draft_token_ids_ptr + start_idx + pos) + target_prob = tl.load(target_probs_ptr + (start_idx + pos) * global_vocab_size + draft_token_id) + if NO_DRAFT_PROBS: + draft_prob = 1 else: - target_prob = tl.load(target_probs_ptr + token_idx * global_vocab_size + draft_token_id) - if NO_DRAFT_PROBS: - draft_prob = 1 - else: - draft_prob = tl.load(draft_probs_ptr + token_idx * global_vocab_size + draft_token_id) - uniform_prob = tl.load(uniform_probs_ptr + token_idx) - - if ENTROPY_VERIFY: - loop = (vocab_size + SUB_BLOCK - 1) // SUB_BLOCK - entropy = 0.0 - for loop_i in range(loop): - vocab_start = loop_i * SUB_BLOCK - vocab_offset = vocab_start + tl.arange(0, SUB_BLOCK) - vocab_mask = vocab_offset < vocab_size - if NO_ORI_TARGET_PROBS: - probs = tl.load( - target_probs_ptr + token_idx * vocab_size + vocab_offset, - vocab_mask, - other=0, - ) - else: - probs = tl.load( - ori_target_probs_ptr + token_idx * vocab_size + vocab_offset, - vocab_mask, - other=0, - ) - log_probs = tl.log(probs + EPSILON) - entropy_contrib = -probs * log_probs - entropy += tl.sum(entropy_contrib) - - exp_neg_entropy = tl.exp(-entropy * POSTERIOR_ALPHA) - threshold_by_entropy = exp_neg_entropy - threshold = tl.minimum(threshold_by_entropy, POSTERIOR_THRESHOLD) - _uniform_prob = threshold * uniform_prob - else: - _uniform_prob = uniform_prob - # NOTE(woosuk): While the draft probability should never be 0, - # we check it to avoid NaNs. If it happens to be 0, we reject. - if draft_prob > 0 and target_prob / draft_prob >= _uniform_prob: - # Accept. - token_id = draft_token_id - else: - # Reject. Use recovered token. - rejected = True - token_id = tl.load(recovered_token_ids_ptr + token_idx) + draft_prob = tl.load( + draft_probs_ptr + (start_idx + pos) * global_vocab_size + draft_token_id + ) + uniform_prob = tl.load(uniform_probs_ptr + start_idx + pos) + # NOTE(woosuk): While the draft probability should never be 0, + # we check it to avoid NaNs. If it happens to be 0, we reject. + if draft_prob > 0 and target_prob / draft_prob >= uniform_prob: + # Accept. + token_id = draft_token_id + else: + # Reject. Use recovered token. + rejected = True + token_id = tl.load(recovered_token_ids_ptr + start_idx + pos) tl.store(output_token_ids_ptr + req_idx * (max_spec_len + 1) + pos, token_id) if not rejected: @@ -343,9 +299,9 @@ def sample_recovered_tokens_kernel( vocab_size, global_vocab_size, NO_DRAFT_PROBS: tl.constexpr, + BLOCK_VERIFY: tl.constexpr, ENABLE_REDUCE_SAMPLING: tl.constexpr, SUB_BLOCK: tl.constexpr, - VOCAB_BLOCK_SIZE: tl.constexpr = 512, ): req_idx = tl.program_id(0) pos = tl.program_id(1) @@ -362,15 +318,15 @@ def sample_recovered_tokens_kernel( if ENABLE_REDUCE_SAMPLING: C = vocab_size - n_loop = tl.cdiv(C, VOCAB_BLOCK_SIZE) + n_loop = tl.cdiv(C, SUB_BLOCK) global_max_p = tl.full((), -float("inf"), tl.float32) global_recovered_id = tl.full((), -1, tl.int64) draft_token_id = tl.load(draft_token_ids_ptr + token_idx).to(tl.int64) for li in range(n_loop): - c_start = li * VOCAB_BLOCK_SIZE - offs = c_start + tl.arange(0, VOCAB_BLOCK_SIZE) + c_start = li * SUB_BLOCK + offs = c_start + tl.arange(0, SUB_BLOCK) mask = offs < C # Load target prob and global index @@ -408,53 +364,53 @@ def sample_recovered_tokens_kernel( loop = (vocab_size + SUB_BLOCK - 1) // SUB_BLOCK global_recovered_id = -1 global_max_p = -1.0 - if NO_DRAFT_PROBS: - draft_token_id = tl.load(draft_token_ids_ptr + start_idx + pos) - for loop_i in range(loop): - vocab_start = loop_i * SUB_BLOCK - vocab_offset = vocab_start + tl.arange(0, SUB_BLOCK) - prob = tl.load( - target_probs_ptr + (start_idx + pos) * vocab_size + vocab_offset, - mask=vocab_offset < vocab_size, - other=0, - ) + prefix_prob = 1.0 + if BLOCK_VERIFY: + for prev_pos in range(pos): + prev_token_idx = start_idx + prev_pos + prev_draft_token_id = tl.load(draft_token_ids_ptr + prev_token_idx) + prev_target_prob = tl.load(target_probs_ptr + prev_token_idx * vocab_size + prev_draft_token_id) + if NO_DRAFT_PROBS: + prev_draft_prob = 1.0 + else: + prev_draft_prob = tl.load(draft_probs_ptr + prev_token_idx * vocab_size + prev_draft_token_id) + if prev_draft_prob > 0: + prefix_prob = min(prefix_prob * prev_target_prob / prev_draft_prob, 1.0) + else: + prefix_prob = 0.0 + + draft_token_id = tl.load(draft_token_ids_ptr + start_idx + pos) + for loop_i in range(loop): + vocab_start = loop_i * SUB_BLOCK + vocab_offset = vocab_start + tl.arange(0, SUB_BLOCK) + target_prob = tl.load( + target_probs_ptr + (start_idx + pos) * vocab_size + vocab_offset, + mask=vocab_offset < vocab_size, + other=0, + ) + if NO_DRAFT_PROBS: + prob = prefix_prob * target_prob if BLOCK_VERIFY else target_prob prob = tl.where(vocab_offset == draft_token_id, 0.0, prob) - q = tl.load( - q_ptr + req_idx * vocab_size + vocab_offset, mask=vocab_offset < vocab_size, other=float("-inf") - ) - new_p = prob / q - recovered_id = tl.argmax(new_p, axis=-1) - max_p = get_element(new_p, (recovered_id,)) - if max_p > global_max_p: - global_max_p = max_p - global_recovered_id = vocab_start + recovered_id - else: - for loop_i in range(loop): - vocab_start = loop_i * SUB_BLOCK - vocab_offset = vocab_start + tl.arange(0, SUB_BLOCK) + else: draft_prob = tl.load( draft_probs_ptr + (start_idx + pos) * vocab_size + vocab_offset, mask=vocab_offset < vocab_size, other=0, ) - target_prob = tl.load( - target_probs_ptr + (start_idx + pos) * vocab_size + vocab_offset, - mask=vocab_offset < vocab_size, - other=0, - ) - prob = tl.maximum(target_prob - draft_prob, 0) - # NOTE(woosuk): We don't need `prob = prob / tl.sum(prob)` here because - # `tl.argmax` will select the maximum value. + if BLOCK_VERIFY: + prob = tl.maximum(prefix_prob * target_prob - draft_prob, 0.0) + else: + prob = tl.maximum(target_prob - draft_prob, 0.0) - q = tl.load( - q_ptr + req_idx * vocab_size + vocab_offset, mask=vocab_offset < vocab_size, other=float("-inf") - ) - new_p = prob / q - recovered_id = tl.argmax(new_p, axis=-1) - max_p = get_element(new_p, (recovered_id,)) - if max_p > global_max_p: - global_max_p = max_p - global_recovered_id = vocab_start + recovered_id + q = tl.load( + q_ptr + req_idx * vocab_size + vocab_offset, mask=vocab_offset < vocab_size, other=float("-inf") + ) + new_p = prob / q + recovered_id = tl.argmax(new_p, axis=-1) + max_p = get_element(new_p, (recovered_id,)) + if max_p > global_max_p: + global_max_p = max_p + global_recovered_id = vocab_start + recovered_id tl.store(output_token_ids_ptr + start_idx + pos, global_recovered_id) @@ -528,17 +484,10 @@ def rejection_random_sample_block_verify_kernel( vocab_size, # vocab_size or selected_vocab_size if ENABLE_REDUCE_SAMPLING global_vocab_size, # global vocab size for draft_probs indexing (only used if ENABLE_REDUCE_SAMPLING) vec_len, - ori_target_probs_ptr, # [num_tokens, ori_vocab_size] original probs for entropy - NO_ORI_TARGET_PROBS: tl.constexpr, NO_DRAFT_PROBS: tl.constexpr, ENABLE_REDUCE_SAMPLING: tl.constexpr, # Whether using reduce_sampling BLOCK_SIZE: tl.constexpr, - ENTROPY_VERIFY: tl.constexpr, - VOCAB_BLOCK_SIZE: tl.constexpr = 512, - POSTERIOR_THRESHOLD: tl.constexpr = 0.95, - POSTERIOR_ALPHA: tl.constexpr = 0.4, - SUB_BLOCK: tl.constexpr = 4096, - EPSILON: tl.constexpr = 1e-10, + SUB_BLOCK: tl.constexpr = 512, ): block_idx = tl.program_id(0) offsets = block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) @@ -566,48 +515,41 @@ def rejection_random_sample_block_verify_kernel( token_idx = start_idx + pos draft_token_id = tl.load(draft_token_ids_ptr + token_idx) - if draft_token_id == -1: - pi = 0.0 - else: - target_prob = 0.0 - found = False + target_prob = 0.0 + found = False - for v_offset in range(0, vocab_size, VOCAB_BLOCK_SIZE): - if not found: - vocab_offsets = v_offset + tl.arange(0, VOCAB_BLOCK_SIZE) - vocab_mask = vocab_offsets < vocab_size + for v_offset in range(0, vocab_size, SUB_BLOCK): + if not found: + vocab_offsets = v_offset + tl.arange(0, SUB_BLOCK) + vocab_mask = vocab_offsets < vocab_size - candidate_indices = tl.load( - target_indices_ptr + token_idx * vocab_size + vocab_offsets, - mask=vocab_mask, - other=-1, - ) + candidate_indices = tl.load( + target_indices_ptr + token_idx * vocab_size + vocab_offsets, mask=vocab_mask, other=-1 + ) - match_mask = candidate_indices == draft_token_id + match_mask = candidate_indices == draft_token_id - candidate_probs = tl.load( - target_probs_ptr + token_idx * vocab_size + vocab_offsets, - mask=vocab_mask, - other=0.0, - ) + candidate_probs = tl.load( + target_probs_ptr + token_idx * vocab_size + vocab_offsets, mask=vocab_mask, other=0.0 + ) - current_match_prob = tl.sum(candidate_probs * match_mask, axis=0) + current_match_prob = tl.sum(candidate_probs * match_mask, axis=0) - if current_match_prob > 0.0: - target_prob = current_match_prob - found = True + if current_match_prob > 0.0: + target_prob = current_match_prob + found = True - tmp_uniform_prob = tl.load(uniform_probs_ptr + token_idx) - uniform_prob = uniform_prob * tmp_uniform_prob + tmp_uniform_prob = tl.load(uniform_probs_ptr + token_idx) + uniform_prob = uniform_prob * tmp_uniform_prob - if NO_DRAFT_PROBS: - draft_prob = 1.0 - else: - draft_prob = tl.load(draft_probs_ptr + token_idx * global_vocab_size + draft_token_id) + if NO_DRAFT_PROBS: + draft_prob = 1.0 + else: + draft_prob = tl.load(draft_probs_ptr + token_idx * global_vocab_size + draft_token_id) - pi = min(pi * target_prob / draft_prob, 1.0) - if draft_prob > 0 and pi >= uniform_prob: - last_accepted_token_pos = pos + pi = min(pi * target_prob / draft_prob, 1.0) + if draft_prob > 0 and pi >= uniform_prob: + last_accepted_token_pos = pos # Store accepted tokens if last_accepted_token_pos > -1: @@ -628,83 +570,83 @@ def rejection_random_sample_block_verify_kernel( bonus_token_id = tl.load(bonus_token_ids_ptr + req_idx) tl.store(output_token_ids_ptr + req_idx * (max_spec_len + 1) + num_draft_tokens, bonus_token_id) else: + vocab_size = global_vocab_size + loop = (vocab_size + SUB_BLOCK - 1) // SUB_BLOCK for req_i in range(BLOCK_SIZE): not_greedy = get_element(not_greedy_mask, (req_i,)) if not_greedy: - pi = 1.0 - uniform_prob = 1.0 - last_accepted_token_pos = -1 start_idx = get_element(start_idxs, (req_i,)) req_idx = block_idx * BLOCK_SIZE + req_i num_draft_tokens = get_element(n_num_draft_tokens, (req_i,)) + if num_draft_tokens == 0: + bonus_token_id = tl.load(bonus_token_ids_ptr + req_idx) + tl.store( + output_token_ids_ptr + req_idx * (max_spec_len + 1), + bonus_token_id, + ) + continue + accepted_len = 0 + prefix_prob = 1.0 for pos in range(num_draft_tokens): token_idx = start_idx + pos draft_token_id = tl.load(draft_token_ids_ptr + token_idx) + target_prob = tl.load(target_probs_ptr + token_idx * vocab_size + draft_token_id) - if draft_token_id == -1: - pi = 0.0 + if NO_DRAFT_PROBS: + draft_prob = 1.0 else: - target_prob = tl.load(target_probs_ptr + token_idx * vocab_size + draft_token_id) + draft_prob = tl.load(draft_probs_ptr + token_idx * vocab_size + draft_token_id) - tmp_uniform_prob = tl.load(uniform_probs_ptr + token_idx) - uniform_prob = uniform_prob * tmp_uniform_prob + if draft_prob > 0: + prefix_prob = min(prefix_prob * target_prob / draft_prob, 1.0) + else: + prefix_prob = 0.0 + if pos == num_draft_tokens - 1: + h_block = prefix_prob + else: + next_token_idx = token_idx + 1 if NO_DRAFT_PROBS: - draft_prob = 1.0 + next_draft_token_id = tl.load(draft_token_ids_ptr + next_token_idx) + next_target_prob = tl.load( + target_probs_ptr + next_token_idx * vocab_size + next_draft_token_id + ) + residual_mass = prefix_prob * (1.0 - next_target_prob) else: - vocab_for_draft = global_vocab_size if ENABLE_REDUCE_SAMPLING else vocab_size - draft_prob = tl.load(draft_probs_ptr + token_idx * vocab_for_draft + draft_token_id) - - if ENTROPY_VERIFY: - loop = (vocab_size + SUB_BLOCK - 1) // SUB_BLOCK - entropy = 0.0 + residual_mass = 0.0 for loop_i in range(loop): vocab_start = loop_i * SUB_BLOCK vocab_offset = vocab_start + tl.arange(0, SUB_BLOCK) - vocab_mask = vocab_offset < vocab_size - if NO_ORI_TARGET_PROBS: - probs = tl.load( - target_probs_ptr + token_idx * vocab_size + vocab_offset, - vocab_mask, - other=0, - ) - else: - probs = tl.load( - ori_target_probs_ptr + token_idx * vocab_size + vocab_offset, - vocab_mask, - other=0, - ) - log_probs = tl.log(probs + EPSILON) - entropy_contrib = -probs * log_probs - entropy += tl.sum(entropy_contrib) - - exp_neg_entropy = tl.exp(-entropy * POSTERIOR_ALPHA) - threshold_by_entropy = exp_neg_entropy - threshold = tl.minimum(threshold_by_entropy, POSTERIOR_THRESHOLD) - _uniform_prob = threshold * uniform_prob - else: - _uniform_prob = uniform_prob + next_draft_prob = tl.load( + draft_probs_ptr + next_token_idx * vocab_size + vocab_offset, + mask=vocab_offset < vocab_size, + other=0, + ) + next_target_prob = tl.load( + target_probs_ptr + next_token_idx * vocab_size + vocab_offset, + mask=vocab_offset < vocab_size, + other=0, + ) + residual_prob = tl.maximum(prefix_prob * next_target_prob - next_draft_prob, 0.0) + residual_mass += tl.sum(residual_prob, axis=0) + denom = residual_mass + 1.0 - prefix_prob + h_block = residual_mass / denom if denom > 0 else 0.0 - pi = min(pi * target_prob / draft_prob, 1.0) - if draft_prob > 0 and pi >= _uniform_prob: - last_accepted_token_pos = pos + uniform_prob = tl.load(uniform_probs_ptr + token_idx) + if uniform_prob <= h_block: + accepted_len = pos + 1 - # Store accepted tokens - if last_accepted_token_pos > -1: - for pos in range(last_accepted_token_pos + 1): - token_id = tl.load(draft_token_ids_ptr + start_idx + pos) - tl.store(output_token_ids_ptr + req_idx * (max_spec_len + 1) + pos, token_id) + for pos in range(accepted_len): + token_id = tl.load(draft_token_ids_ptr + start_idx + pos) + tl.store(output_token_ids_ptr + req_idx * (max_spec_len + 1) + pos, token_id) - # Store recovered or bonus token - if last_accepted_token_pos + 1 < num_draft_tokens: - # Rejected - store recovered token - recovered_token_id = tl.load(recovered_token_ids_ptr + start_idx + last_accepted_token_pos + 1) + if accepted_len == num_draft_tokens: + bonus_token_id = tl.load(bonus_token_ids_ptr + req_idx) + tl.store(output_token_ids_ptr + req_idx * (max_spec_len + 1) + num_draft_tokens, bonus_token_id) + else: + recovered_token_id = tl.load(recovered_token_ids_ptr + start_idx + accepted_len) tl.store( - output_token_ids_ptr + req_idx * (max_spec_len + 1) + last_accepted_token_pos + 1, + output_token_ids_ptr + req_idx * (max_spec_len + 1) + accepted_len, recovered_token_id, ) - else: - # All accepted - store bonus token - bonus_token_id = tl.load(bonus_token_ids_ptr + req_idx) - tl.store(output_token_ids_ptr + req_idx * (max_spec_len + 1) + num_draft_tokens, bonus_token_id) diff --git a/vllm_ascend/ops/triton/rms_norm.py b/vllm_ascend/ops/triton/rms_norm.py index 57ebd76ba..fb8b61733 100644 --- a/vllm_ascend/ops/triton/rms_norm.py +++ b/vllm_ascend/ops/triton/rms_norm.py @@ -42,7 +42,7 @@ def triton_q_rms( q = q.view(total_batch, dim) if dim > 2048: - raise NotImplementedError(f"triton_q_rms: dim > 2048 not supported, got {dim}") + raise NotImplementedError("dim > 2048 not supported") device_properties = triton.runtime.driver.active.utils.get_device_properties(q.device) num_vectorcore = device_properties.get("num_vectorcore", -1) diff --git a/vllm_ascend/ops/triton/spec_decode/utils.py b/vllm_ascend/ops/triton/spec_decode/utils.py index 483684de3..3429117c2 100644 --- a/vllm_ascend/ops/triton/spec_decode/utils.py +++ b/vllm_ascend/ops/triton/spec_decode/utils.py @@ -70,7 +70,6 @@ def copy_and_expand_dflash_inputs_kernel_single_grid( # Inputs next_token_ids_ptr, # [num_reqs] target_positions_ptr, # [num_context] - context_slot_mapping_ptr, # [num_context] # Outputs out_input_ids_ptr, # [num_query_total] (output) out_context_positions_ptr, # [num_context] (output) @@ -83,7 +82,6 @@ def copy_and_expand_dflash_inputs_kernel_single_grid( block_table_stride, # stride of block_table dim 0 (in elements) # Metadata query_start_loc_ptr, # [num_reqs + 1] - seq_lens_ptr, # [num_reqs] num_rejected_tokens_ptr, # [num_reqs] or null (0) when not padded # Scalars parallel_drafting_token_id, # tl.int32 @@ -104,18 +102,17 @@ def copy_and_expand_dflash_inputs_kernel_single_grid( pos = tl.load(target_positions_ptr + ctx_pos_idx) tl.store(out_context_positions_ptr + ctx_pos_idx, pos) - slot = tl.load(context_slot_mapping_ptr + ctx_pos_idx) + block_num = pos // block_size + block_id = tl.load(block_table_ptr + req_idx * block_table_stride + block_num).to(tl.int64) + slot = block_id * block_size + (pos % block_size) tl.store(out_context_slot_mapping_ptr + ctx_pos_idx, slot) if HAS_NUM_REJECTED: num_rejected = tl.load(num_rejected_tokens_ptr + req_idx) valid_ctx_end = ctx_end - num_rejected else: - num_rejected = 0 valid_ctx_end = ctx_end - seq_len = tl.load(seq_lens_ptr + req_idx) - effective_seq_len = seq_len - num_rejected last_pos = tl.load(target_positions_ptr + valid_ctx_end - 1) for q_idx in range(0, num_query_per_req): @@ -124,10 +121,9 @@ def copy_and_expand_dflash_inputs_kernel_single_grid( tl.store(out_query_positions_ptr + query_out_idx, query_pos) - query_cache_pos = effective_seq_len + q_idx - block_num_q = query_cache_pos // block_size + block_num_q = query_pos // block_size block_id_q = tl.load(block_table_ptr + req_idx * block_table_stride + block_num_q).to(tl.int64) - slot_q = block_id_q * block_size + (query_cache_pos % block_size) + slot_q = block_id_q * block_size + (query_pos % block_size) tl.store(out_query_slot_mapping_ptr + query_out_idx, slot_q) if q_idx == 0: diff --git a/vllm_ascend/patch/__init__.py b/vllm_ascend/patch/__init__.py index f99772e54..2d29f21b3 100644 --- a/vllm_ascend/patch/__init__.py +++ b/vllm_ascend/patch/__init__.py @@ -200,24 +200,6 @@ # Remove this patch once the supported vLLM version contains the upstream # GLM47 inline zero-argument streaming parser fix. # -# ** 7c. File: platform/patch_anthropic_system_message.py** -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 1. `vllm.entrypoints.anthropic.protocol.AnthropicMessage` -# `vllm.entrypoints.anthropic.serving.AnthropicServingMessages` -# Why: -# Recent Claude Code clients can send `role: system` entries inside the -# Anthropic Messages API `messages` array. The pinned vLLM rejects those -# requests before inference starts. -# How: -# Monkey-patch Anthropic message role validation to accept `system`, merge -# inline system messages with the top-level system prompt, and skip inline -# system entries when converting the remaining chat history. -# Related PR (if no, explain why): -# https://github.com/vllm-project/vllm/issues/44000 -# https://github.com/vllm-project/vllm/pull/44283 -# Future Plan: -# Remove this patch once the supported vLLM version contains PR #44283. -# # ** 10a. File: platform/patch_kv_cache_utils.py** # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # 1. `vllm.v1.core.kv_cache_utils.resolve_kv_cache_block_sizes` @@ -287,27 +269,6 @@ # profiling startup and per-step timing callbacks without monkey-patching # `EngineCore` and the multiprocess entry point. # -# ** 10b. File: platform/patch_pp_mtp.py** -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 1. `vllm.config.model.ModelConfig.verify_with_parallel_config` -# Why: -# Local Eagle/MTP drafters are loaded on the last PP stage rather than -# partitioned across all PP ranks. Upstream `ModelConfig.verify_with_parallel_config` -# validates against `pipeline_parallel_size`, which fails for these drafters -# since they run locally with effective PP=1. -# How: -# Monkey-patch `verify_with_parallel_config` to detect Eagle/MTP drafter -# models (by `model_type` and `architectures`) when `runner="draft"` and -# `pipeline_parallel_size > 1`. For such configs, call the original verify -# with a patched `pipeline_parallel_size=1` copy, preserving normal target-model -# validation for non-drafter models. -# Related PR (if no, explain why): -# Backport of local vLLM PP+MTP branch changes. -# Future Plan: -# Remove this patch once upstream vLLM's `ModelConfig.verify_with_parallel_config` -# supports local drafter models with PP > 1, or moves the PP validation to a -# separate hook that can be overridden per-model-type. -# # ** 11. File: platform/patch_tool_choice_none_content.py** # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # 1. `vllm.entrypoints.openai.engine.serving.OpenAIServing._parse_tool_calls_from_content` @@ -344,11 +305,31 @@ # Remove this patch if upstream streaming behavior is updated to satisfy the # same DeepSeek DSML incrementality contract. # -# ** 12a. File: platform/patch_minimax_m2_tool_call_parser.py** +# ** 12a. File: platform/patch_deepseek_v4_thinking.py** +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# 1. `vllm.entrypoints.openai.chat_completion.protocol.ChatCompletionRequest` +# `vllm.tokenizers.deepseek_v4` +# Why: +# Supported vLLM v0.20.2 predates newer DeepSeek V4 reasoning-effort +# handling: `minimal`, `xhigh`, and `max` are rejected at request +# validation time, reasoning effort does not automatically enable +# thinking, and `reasoning_effort="none"` does not force chat mode in +# the DeepSeek V4 tokenizer. +# How: +# Extend the request field validation to the newer accepted values, +# backport the newer `build_chat_params` enable_thinking behavior, and +# monkey-patch the DeepSeek V4 tokenizer reasoning-effort mapping. +# Related PR (if no, explain why): +# Upstream vLLM main behavior after v0.20.2. +# Future Plan: +# Remove this patch once vllm-ascend upgrades to a vLLM version with the +# same DeepSeek V4 thinking behavior. +# +# ** 12b. File: platform/patch_minimax_m2_tool_call_parser.py** # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # 1. `vllm.tool_parsers.minimax_m2_tool_parser.MinimaxM2ToolParser` # Why: -# vLLM 0.21.0 only emits MiniMax-M2 tool-call arguments after a complete +# vLLM 0.20.2 only emits MiniMax-M2 tool-call arguments after a complete # `...` block, so long arguments are buffered instead of # streamed incrementally. # How: @@ -392,48 +373,6 @@ # https://github.com/vllm-project/vllm/pull/43935 # Future Plan: # Remove this patch if upstream streaming behavior is updated to support mamba external KV connector -# ** 15. File: platform/patch_weight_transfer_engine.py** -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 1. `vllm.distributed.weight_transfer.factory.WeightTransferEngineFactory._registry["nccl"]` -# Why: -# Upstream vLLM's WeightTransferConfig.backend is a pydantic Literal["nccl", "ipc"] -# which does not accept "hccl". On Ascend NPU, NCCL is unavailable and HCCL must -# be used for trainer-to-worker weight broadcasting. -# How: -# Replace the "nccl" factory entry with a lambda that returns -# HCCLWeightTransferEngine. Users pass the already-accepted "nccl" string -# (e.g. --weight-transfer-config '{"backend": "nccl"}') and the factory -# resolves it to the HCCL engine at runtime. -# Related PR (if no, explain why): -# No. Adding "hccl" to the Literal requires modifying pydantic core schemas, -# which is fragile across pydantic versions. -# Future Plan: -# Remove this patch when upstream vLLM relaxes the Literal type to str or -# provides an extension point for out-of-tree weight transfer backends. -# -# ** 15. File: platform/patch_kv_cache_coordinator.py** -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 1. `vllm.v1.core.kv_cache_coordinator.HybridKVCacheCoordinator.find_longest_cache_hit_per_group` -# Why: -# In PD disaggregation with hybrid Mamba models, the D side receives -# FullAttention KV blocks from the P side but has no local prefix-cache -# hit for Mamba groups. Upstream's min-reduction across all KV groups -# collapses the FullAttention hit length to 0, preventing partial -# FullAttention-only prefix cache reuse on the D side. -# How: -# For Mamba hybrid models, -# num_new_local_computed_tokens should be the FA hit -# length. This value is passed to the connector's -# get_num_new_matched_tokens which computes: -# external = total - local_computed. -# Using the FA hit skips re-transferring FA blocks -# already cached on D-side. -# Related PR (if no, explain why): -# https://github.com/vllm-project/vllm/pull/42524 -# https://github.com/vllm-project/vllm/pull/44243 -# Future Plan: -# Remove this patch when vLLM PR #42524 and #44243 is included in the supported -# upstream vLLM version. # # * Worker Patch: # =============== @@ -473,7 +412,9 @@ # How: # Import `triton` from vllm.triton_utils (which handles both # real Triton and TritonPlaceholder) and inject `next_power_of_2` -# onto the module, reusing `vllm.utils.math_utils.next_power_of_2`. +# onto the module. For vLLM versions that have +# `vllm.utils.math_utils.next_power_of_2`, reuse that implementation; +# for v0.20.2 (which lacks it), skip the patch. # Related PR (if no, explain why): # No, torch_npu Triton compatibility issue. # Future Plan: @@ -508,28 +449,27 @@ # to override them, then delete the patch file `worker/patch_rejection_sampler.py`. # 2. make these functions as costom op, then remove AscendRejectionSampler # -## ** 6. File: worker/patch_module.py** +# ** 7. File: worker/patch_gdn_attn.py** # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 1. `vllm.v1.attention.backends.gdn_attn.torch.argsort` +# 1. `vllm.v1.attention.backends.gdn_attn.GDNAttentionMetadataBuilder.build` # Why: -# 1. 'torch.argsort' func of npu does not support bool. -# 2. Without `stable=True`, the output will have a lot of redundant tokens. +# Qwen3.5/Qwen3Next GDN prefill on NPU needs prebuilt varlen chunk metadata +# to avoid forward-time host round-trips that break async scheduling. # How: -# Replace with a new torch.argsort that will cast the input to torch.int32 -# and do stable sort. -# Related PR (if no, explain why): -# 1. It depends on torch_npu. -# 2. https://github.com/vllm-project/vllm/pull/30632 +# Monkey-patch the upstream builder in-place, keep upstream code untouched, +# and attach prebuilt device metadata bundle onto the returned attention +# metadata object for Ascend-specific consumers. # Future Plan: -# Remove this patch when bool is supported in 'torch.argsort' func of npu. -# Make 'torch.argsort' in `vllm.v1.attention.backends.gdn_attn` be stable. -# 2. `vllm_ascend.ops.gdn_attn_builder.AscendGDNAttentionMetadataBuilder.build` +# Remove this patch when upstream exposes a backend hook for extending GDN +# metadata or when the optimization is accepted upstream directly. +# 2. `vllm.v1.attention.backends.gdn_attn.GDNAttentionMetadataBuilde.build` # Why: # Qwen3.5/Qwen3Next GDN Decode/Specific Decode on NPU needs prebuilt varlen chunk metadata # to avoid forward-time host round-trips that break async scheduling. # How: -# Override the GDN attention metadata builder for Ascend backend and attach -# prebuilt device metadata bundle onto the returned attention metadata object. +# Monkey-patch the upstream builder in-place, keep upstream code untouched, +# and attach prebuilt device metadata bundle onto the returned attention +# metadata object for Ascend-specific consumers. # Future Plan: # Remove this patch when upstream exposes a backend hook for extending GDN # metadata or when the optimization is accepted upstream directly. @@ -784,37 +724,6 @@ # Rotary quant is a unique feature of vllm-ascend. # Future Plan: # Remove this patch when vllm supports rotary quant or pluggable `MultiTokenPredictorLayer`. -# 4. `vllm.model_executor.models.deepseek_v2.GlmMoeDsaForCausalLM.load_weights` -# Why: -# After vllm PR #41706, GlmMoeDsaForCausalLM.load_weights uses `AutoWeightsLoader` which -# does not skip `rot.weight`, and will cause ValueError while loading weights. -# How: -# Use the `skip_prefixes` parameter to skip certain weight tensors. -# Related PR (if no, explain why): -# https://github.com/vllm-project/vllm/pull/41706 -# Future Plan: -# Remove this patch when vllm supports rotary quant or pluggable `MultiTokenPredictorLayer`. -# ** 19b. File: worker/model_runner_v1.py** -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# 1. `NPUModelRunner._check_and_update_cudagraph_mode` -# Why: -# The upstream `GPUModelRunner._check_and_update_cudagraph_mode` initializes -# drafter cudagraph keys unconditionally, but in PP mode only the last rank -# loads the drafter. The previous hacky workaround temporarily set -# `self.speculative_config = None` to bypass super()'s drafter init, then -# restored it and called a separate `_maybe_initialize_drafter_cudagraph_keys` -# helper. This state-mutation pattern is fragile and hard to maintain. -# How: -# Directly inline the upstream cudagraph mode resolution logic with Ascend-specific -# additions: wrap `resolve_cudagraph_mode_and_sizes` with `update_pass_config` for -# `enable_sp`, add PP last-rank guard for drafter initialization, and call -# `set_graph_params`/`set_draft_graph_params` for ACL graph params. Remove the -# `_maybe_initialize_drafter_cudagraph_keys` helper entirely. -# Related PR (if no, explain why): -# No, cleaner PP+MTP support without speculative_config state mutation. -# Future Plan: -# Remove this override once upstream exposes a hook for drafter cudagraph key -# initialization that respects PP rank boundaries. # ** 20. File: worker/patch_mamba_utils.py** # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # 1. `vllm.v1.worker.mamba_utils.batch_memcpy_kernel = batch_memcpy_kernel` diff --git a/vllm_ascend/patch/platform/__init__.py b/vllm_ascend/patch/platform/__init__.py index b526c5fc6..d6dcab408 100644 --- a/vllm_ascend/patch/platform/__init__.py +++ b/vllm_ascend/patch/platform/__init__.py @@ -21,7 +21,7 @@ import vllm_ascend.patch.platform.patch_kv_cache_interface # noqa import vllm_ascend.patch.platform.patch_kv_cache_utils # noqa import vllm_ascend.patch.platform.patch_mla_prefill_backend # noqa -import vllm_ascend.patch.platform.patch_pp_mtp # noqa +from vllm_ascend import envs from vllm_ascend.utils import is_310p if not is_310p(): @@ -32,10 +32,9 @@ import vllm_ascend.patch.platform.patch_minimax_usage_accounting # noqa import vllm_ascend.patch.platform.patch_glm_tool_call_streaming # noqa import vllm_ascend.patch.platform.patch_glm47_tool_call_parser # noqa -import vllm_ascend.patch.platform.patch_anthropic_system_message # noqa import vllm_ascend.patch.platform.patch_minimax_m2_tool_call_parser # noqa import vllm_ascend.patch.platform.patch_deepseek_v4_tool_call_parser # noqa -import vllm_ascend.patch.platform.patch_weight_transfer_engine # noqa +import vllm_ascend.patch.platform.patch_deepseek_v4_thinking # noqa import vllm_ascend.patch.platform.patch_torch_accelerator # noqa import vllm_ascend.patch.platform.patch_tool_choice_none_content # noqa import vllm_ascend.patch.platform.patch_mamba_manager # noqa @@ -46,6 +45,8 @@ import vllm_ascend.patch.platform.patch_balance_schedule # noqa import vllm_ascend.patch.platform.patch_kv_cache_coordinator # noqa -import vllm_ascend.patch.platform.patch_speculative_config # noqa + +if envs.VLLM_ASCEND_APPLY_DSV4_PATCH: + import vllm_ascend.patch.platform.patch_speculative_config # noqa import vllm_ascend.patch.platform.patch_scheduler # noqa diff --git a/vllm_ascend/patch/platform/patch_anthropic_system_message.py b/vllm_ascend/patch/platform/patch_anthropic_system_message.py deleted file mode 100644 index f21a51b82..000000000 --- a/vllm_ascend/patch/platform/patch_anthropic_system_message.py +++ /dev/null @@ -1,100 +0,0 @@ -# -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# This file is a part of the vllm-ascend project. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Anthropic Messages API: backport inline system message support. -# - -from __future__ import annotations - -from typing import Any, Literal - -from vllm.entrypoints.anthropic.protocol import ( - AnthropicCountTokensRequest, - AnthropicMessage, - AnthropicMessagesRequest, -) -from vllm.entrypoints.anthropic.serving import AnthropicServingMessages - -_ANTHROPIC_MESSAGE_ROLES = Literal["user", "assistant", "system"] - -AnthropicMessage.__annotations__["role"] = _ANTHROPIC_MESSAGE_ROLES -AnthropicMessage.model_fields["role"].annotation = _ANTHROPIC_MESSAGE_ROLES -AnthropicMessage.model_rebuild(force=True) -AnthropicMessagesRequest.model_rebuild(force=True) -AnthropicCountTokensRequest.model_rebuild(force=True) - - -def _append_system_text(system_parts: list[str], text: str | None) -> None: - if not text: - return - if text.startswith("x-anthropic-billing-header"): - return - system_parts.append(text) - - -def _append_system_content( - system_parts: list[str], - content: str | list[Any], -) -> None: - if isinstance(content, str): - _append_system_text(system_parts, content) - return - - for block in content: - if block.type == "text": - _append_system_text(system_parts, block.text) - - -def _patched_convert_system_message( - cls, - anthropic_request: AnthropicMessagesRequest | AnthropicCountTokensRequest, - openai_messages: list[dict[str, Any]], -) -> None: - system_parts: list[str] = [] - - if anthropic_request.system: - _append_system_content(system_parts, anthropic_request.system) - - for msg in anthropic_request.messages: - if msg.role == "system": - _append_system_content(system_parts, msg.content) - - if system_parts: - openai_messages.append({"role": "system", "content": "".join(system_parts)}) - - -def _patched_convert_messages( - cls, - messages: list, - openai_messages: list[dict[str, Any]], -) -> None: - for msg in messages: - if msg.role == "system": - continue - - openai_msg: dict[str, Any] = {"role": msg.role} # type: ignore - - if isinstance(msg.content, str): - openai_msg["content"] = msg.content - else: - cls._convert_message_content(msg, openai_msg, openai_messages) - - if not (msg.role == "user" and "content" not in openai_msg): - openai_messages.append(openai_msg) - - -AnthropicServingMessages._convert_system_message = classmethod(_patched_convert_system_message) -AnthropicServingMessages._convert_messages = classmethod(_patched_convert_messages) diff --git a/vllm_ascend/patch/platform/patch_deepseek_v4_thinking.py b/vllm_ascend/patch/platform/patch_deepseek_v4_thinking.py new file mode 100644 index 000000000..553e5727e --- /dev/null +++ b/vllm_ascend/patch/platform/patch_deepseek_v4_thinking.py @@ -0,0 +1,153 @@ +# +# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. +# This file is a part of the vllm-ascend project. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# DeepSeek V4 thinking compatibility with newer vLLM request/tokenizer behavior. +# + +from __future__ import annotations + +import copy +from typing import Any, Literal + +from transformers import PreTrainedTokenizerFast +from vllm.entrypoints.openai.chat_completion import protocol as chat_protocol +from vllm.renderers.params import ChatParams +from vllm.tokenizers import deepseek_v4 as deepseek_v4_tokenizer + +DeepSeekV4ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"] | None + + +def _rebuild_model_field(model_cls, field_name: str, annotation) -> None: + model_cls.__annotations__[field_name] = annotation + model_cls.model_fields[field_name].annotation = annotation + model_cls.model_rebuild(force=True) + + +_rebuild_model_field( + chat_protocol.ChatCompletionRequest, + "reasoning_effort", + DeepSeekV4ReasoningEffort, +) + +_original_build_chat_params = chat_protocol.ChatCompletionRequest.build_chat_params + + +def _patched_build_chat_params( + self: chat_protocol.ChatCompletionRequest, + default_template: str | None, + default_template_content_format, +) -> ChatParams: + params = _original_build_chat_params( + self, + default_template, + default_template_content_format, + ) + user_kwargs = self.chat_template_kwargs or {} + if self.reasoning_effort is None or "enable_thinking" in user_kwargs: + return params + + chat_template_kwargs = dict(params.chat_template_kwargs) + chat_template_kwargs["enable_thinking"] = self.reasoning_effort != "none" + return ChatParams( + chat_template=params.chat_template, + chat_template_content_format=params.chat_template_content_format, + chat_template_kwargs=chat_template_kwargs, + media_io_kwargs=params.media_io_kwargs, + mm_processor_kwargs=params.mm_processor_kwargs, + ) + + +chat_protocol.ChatCompletionRequest.build_chat_params = _patched_build_chat_params + + +def _patched_get_deepseek_v4_tokenizer(tokenizer: deepseek_v4_tokenizer.HfTokenizer): + dsv4_tokenizer = copy.copy(tokenizer) + + added_vocab = tokenizer.get_added_vocab() + added_vocab_size = len(added_vocab) + tokenizer_vocab_size = tokenizer.vocab_size + + class _DeepseekV4Tokenizer(tokenizer.__class__): # type: ignore + def apply_chat_template( + self, + messages: list[chat_protocol.ChatCompletionMessageParam], + tools: list[dict[str, Any]] | None = None, + **kwargs, + ) -> str | list[int]: + thinking = kwargs.get("thinking", False) + enable_thinking = kwargs.get("enable_thinking", False) + thinking = thinking or enable_thinking + thinking_mode = "thinking" if thinking else "chat" + + conversation = kwargs.get("conversation", messages) + messages = conversation.copy() + if tools is not None and len(tools) > 0: + messages.insert(0, {"role": "system"}) + messages[0]["tools"] = tools # type: ignore[typeddict-unknown-key] + + reasoning_effort = kwargs.get("reasoning_effort") + if not isinstance(reasoning_effort, str): + reasoning_effort = None + elif reasoning_effort == "none": + thinking_mode = "chat" + reasoning_effort = None + elif reasoning_effort in ("max", "xhigh"): + reasoning_effort = "max" + else: + reasoning_effort = "high" + + prompt_str = deepseek_v4_tokenizer.encode_messages( + messages, + thinking_mode=thinking_mode, + drop_thinking=kwargs.get("drop_thinking", True), + reasoning_effort=reasoning_effort, + ) + + if kwargs.get("tokenize", True): + tokenizer_kwargs = {k: kwargs[k] for k in ("truncation", "max_length") if k in kwargs} + return self.encode( + prompt_str, + add_special_tokens=False, + **tokenizer_kwargs, + ) + + return prompt_str + + def num_special_tokens_to_add(self) -> int: + return len(self.encode("")) + + def __len__(self) -> int: + return tokenizer_vocab_size + added_vocab_size + + def get_added_vocab(self) -> dict[str, int]: + return added_vocab.copy() + + def __reduce__(self): + return _patched_get_deepseek_v4_tokenizer, (tokenizer,) + + _DeepseekV4Tokenizer.__name__ = f"DSV4{tokenizer.__class__.__name__}" + + dsv4_tokenizer.__class__ = _DeepseekV4Tokenizer + return dsv4_tokenizer + + +def _patched_deepseek_v4_from_pretrained(cls, *args, **kwargs): + tokenizer = PreTrainedTokenizerFast.from_pretrained(*args, **kwargs) + return deepseek_v4_tokenizer.get_cached_tokenizer(_patched_get_deepseek_v4_tokenizer(tokenizer)) + + +deepseek_v4_tokenizer.get_deepseek_v4_tokenizer = _patched_get_deepseek_v4_tokenizer +deepseek_v4_tokenizer.DeepseekV4Tokenizer.from_pretrained = classmethod(_patched_deepseek_v4_from_pretrained) diff --git a/vllm_ascend/patch/platform/patch_glm_tool_call_streaming.py b/vllm_ascend/patch/platform/patch_glm_tool_call_streaming.py index f29d8b441..582d1ffde 100644 --- a/vllm_ascend/patch/platform/patch_glm_tool_call_streaming.py +++ b/vllm_ascend/patch/platform/patch_glm_tool_call_streaming.py @@ -39,36 +39,18 @@ def _create_remaining_args_delta( fallback_tool_call_type: str | None = None, fallback_tool_call_name: str | None = None, ) -> DeltaMessage: - if remaining_call == "": - return delta_message - - original_tool_call = next( - (tool_call for tool_call in delta_message.tool_calls if tool_call.index == index), - None, - ) - original_function = original_tool_call.function if original_tool_call else None - function_kwargs: dict[str, str] = {"arguments": remaining_call} - function_name = original_function.name if original_function else None - if function_name is None: - function_name = fallback_tool_call_name - if function_name is not None: - function_kwargs["name"] = function_name + if fallback_tool_call_name is not None: + function_kwargs["name"] = fallback_tool_call_name tool_call_kwargs: dict[str, Any] = { "index": index, "function": DeltaFunctionCall(**function_kwargs), } - tool_call_id = original_tool_call.id if original_tool_call else None - if tool_call_id is None: - tool_call_id = fallback_tool_call_id - if tool_call_id is not None: - tool_call_kwargs["id"] = tool_call_id - tool_call_type = original_tool_call.type if original_tool_call else None - if tool_call_type is None: - tool_call_type = fallback_tool_call_type - if tool_call_type is not None: - tool_call_kwargs["type"] = tool_call_type + if fallback_tool_call_id is not None: + tool_call_kwargs["id"] = fallback_tool_call_id + if fallback_tool_call_type is not None: + tool_call_kwargs["type"] = fallback_tool_call_type return DeltaMessage(tool_calls=[DeltaToolCall(**tool_call_kwargs)]) diff --git a/vllm_ascend/patch/platform/patch_kv_cache_coordinator.py b/vllm_ascend/patch/platform/patch_kv_cache_coordinator.py index 4616c8c4b..3693828f4 100644 --- a/vllm_ascend/patch/platform/patch_kv_cache_coordinator.py +++ b/vllm_ascend/patch/platform/patch_kv_cache_coordinator.py @@ -1,12 +1,9 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM projectx import sys -from collections.abc import Mapping from math import lcm import vllm -import vllm.envs as envs_vllm -import vllm.v1.core.kv_cache_coordinator as vllm_kv_cache_coordinator from vllm.v1.core.block_pool import BlockPool from vllm.v1.core.kv_cache_coordinator import ( HybridKVCacheCoordinator, @@ -27,34 +24,14 @@ MambaSpec, ) +from vllm_ascend import envs from vllm_ascend.core.single_type_kv_cache_manager import get_manager_for_kv_cache_spec -from vllm_ascend.utils import vllm_version_is USE_MULTI_GROUPS_KV_CACHE = True _orig_get_kv_cache_coordinator = vllm.v1.core.kv_cache_coordinator.get_kv_cache_coordinator -def _is_deepseek_v4_kv_cache_spec(kv_cache_spec: KVCacheSpec) -> bool: - if getattr(kv_cache_spec, "model_version", None) == "deepseek_v4": - return True - - nested_specs = getattr(kv_cache_spec, "kv_cache_specs", None) - if nested_specs is None: - return False - - if isinstance(nested_specs, Mapping): - nested_specs = nested_specs.values() - elif not isinstance(nested_specs, (list, tuple, set)): - return False - - return any(getattr(spec, "model_version", None) == "deepseek_v4" for spec in nested_specs) - - -def _is_deepseek_v4_kv_cache_config(kv_cache_config: KVCacheConfig) -> bool: - return any(_is_deepseek_v4_kv_cache_spec(group.kv_cache_spec) for group in kv_cache_config.kv_cache_groups) - - class AscendHybridKVCacheCoordinator(HybridKVCacheCoordinator): """ KV cache coordinator for hybrid models with multiple KV cache types, and @@ -77,11 +54,9 @@ def __init__( eagle_attn_layer_names: list[str] | None = None, metrics_collector: KVCacheMetricsCollector | None = None, max_num_batched_tokens: int | None = None, - scheduler_block_size: int | None = None, ): self.dcp_world_size = dcp_world_size self.pcp_world_size = pcp_world_size - self.scheduler_block_size = scheduler_block_size self.kv_cache_config = kv_cache_config self.max_model_len = max_model_len self.enable_caching = enable_caching @@ -91,18 +66,6 @@ def __init__( if max_num_batched_tokens is None: max_num_batched_tokens = max_model_len self.max_num_batched_tokens = max_num_batched_tokens - self.retention_interval = getattr(envs_vllm, "VLLM_PREFIX_CACHE_RETENTION_INTERVAL", None) - validate_retention_interval = getattr( - vllm_kv_cache_coordinator, - "_validate_prefix_cache_retention_interval", - None, - ) - if self.retention_interval is not None and validate_retention_interval is not None: - validate_retention_interval( - self.retention_interval, - self.scheduler_block_size, - kv_cache_config, - ) self.block_pool = BlockPool( kv_cache_config.num_blocks, @@ -118,10 +81,6 @@ def __init__( if use_eagle and not self.eagle_group_ids: self.eagle_group_ids = set(range(len(kv_cache_config.kv_cache_groups))) - # v0.22.1 managers don't accept `scheduler_block_size`. - extra_mgr_kwargs: dict = {} - if not vllm_version_is("0.22.1"): - extra_mgr_kwargs["scheduler_block_size"] = scheduler_block_size self.single_type_managers = tuple( get_manager_for_kv_cache_spec( kv_cache_spec=kv_cache_group.kv_cache_spec, @@ -132,7 +91,6 @@ def __init__( pcp_world_size=pcp_world_size, max_num_batched_tokens=max_num_batched_tokens, max_model_len=max_model_len, - **extra_mgr_kwargs, ) for i, kv_cache_group in enumerate(self.kv_cache_config.kv_cache_groups) ) @@ -158,8 +116,7 @@ def _get_effective_block_size(self, kv_cache_spec: KVCacheSpec) -> int: if self.dcp_world_size * self.pcp_world_size > 1: block_size *= self.dcp_world_size * self.pcp_world_size if hasattr(kv_cache_spec, "compress_ratio"): - compress_ratio = kv_cache_spec.compress_ratio or 1 - compress_ratio = compress_ratio if compress_ratio >= 1 else 1 + compress_ratio = kv_cache_spec.compress_ratio if kv_cache_spec.compress_ratio >= 1 else 1 block_size *= compress_ratio return block_size @@ -233,12 +190,10 @@ def find_longest_cache_hit( """ def _get_block_hashes(kv_cache_spec: KVCacheSpec) -> BlockHashList: - target_block_size = kv_cache_spec.block_size - if not isinstance(kv_cache_spec, MambaSpec) and self.dcp_world_size * self.pcp_world_size > 1: - target_block_size *= self.dcp_world_size * self.pcp_world_size - if target_block_size == self.hash_block_size: + effective_block_size = self._get_effective_block_size(kv_cache_spec) + if kv_cache_spec.block_size == self.hash_block_size: return block_hashes - return BlockHashListWithBlockSize(block_hashes, self.hash_block_size, target_block_size) + return BlockHashListWithBlockSize(block_hashes, self.hash_block_size, effective_block_size) num_groups = len(self.kv_cache_config.kv_cache_groups) hit_length = max_cache_hit_length @@ -274,124 +229,13 @@ def _get_block_hashes(kv_cache_spec: KVCacheSpec) -> BlockHashList: if use_eagle: # Eagle needs to match one more block and then pop the last. _max_length = min(curr_hit_length + spec.block_size, max_cache_hit_length) - # vLLM B renamed the ``use_eagle`` kwarg to ``drop_eagle_block``. - if vllm_version_is("0.22.1"): - eagle_kwarg = {"use_eagle": use_eagle} - else: - eagle_kwarg = {"drop_eagle_block": use_eagle} - hit_blocks = manager_cls.find_longest_cache_hit( - block_hashes=_get_block_hashes(spec), - max_length=_max_length, - kv_cache_group_ids=group_ids, - block_pool=self.block_pool, - kv_cache_spec=spec, - **eagle_kwarg, - alignment_tokens=self.lcm_block_size, - dcp_world_size=self.dcp_world_size, - pcp_world_size=self.pcp_world_size, - ) - _new_hit_length = len(hit_blocks[0]) * effective_block_size - if use_eagle: - eagle_verified.add(idx) - elif _new_hit_length < curr_hit_length: - # length shrunk; invalidate previous eagle verifications - eagle_verified.clear() - curr_hit_length = _new_hit_length - curr_hit_length = len(hit_blocks[0]) * effective_block_size - for group_id, blocks in zip(group_ids, hit_blocks): - hit_blocks_by_group[group_id] = blocks - - if curr_hit_length >= hit_length: - break - hit_length = curr_hit_length - if is_simple_hybrid: - break - - # Truncate full attention blocks to final hit_length (if present) - # NOTE(zxr): for deepseek-v4, there is two fullattn groups, but - # in this function, only the first fullattn group is truncate by - # the belowing codes(c4), c128 layer does not truncate, which may - # have prefix cache block hit. - # Due to slidingwindow attn, deepseek-v4 decode node can't have - # any prefix cache hit, because `hit_length` of SWA is 0. - spec, group_ids, _ = self.attention_groups[0] - if isinstance(spec, FullAttentionSpec): - num_blocks = hit_length // self._get_effective_block_size(spec) - for group_id in group_ids: - if (blks := hit_blocks_by_group[group_id]) is not None: - del blks[num_blocks:] - - return tuple(blocks if blocks is not None else [] for blocks in hit_blocks_by_group), hit_length - - def find_longest_cache_hit_per_group( - self, - block_hashes: list[BlockHash], - max_cache_hit_length: int, - ) -> tuple[tuple[list[KVCacheBlock], ...], int]: - def _get_block_hashes(kv_cache_spec: KVCacheSpec) -> BlockHashList: - target_block_size = kv_cache_spec.block_size - if not isinstance(kv_cache_spec, MambaSpec) and self.dcp_world_size * self.pcp_world_size > 1: - target_block_size *= self.dcp_world_size * self.pcp_world_size - if target_block_size == self.hash_block_size: - return block_hashes - return BlockHashListWithBlockSize(block_hashes, self.hash_block_size, target_block_size) - - num_groups = len(self.kv_cache_config.kv_cache_groups) - hit_length = max_cache_hit_length - hit_blocks_by_group: list[list[KVCacheBlock] | None] = [None] * num_groups - - # Simple hybrid (1 full attn + 1 other): one iteration suffices. - # Full attn is always first if it exists. - is_simple_hybrid = len(self.attention_groups) == 2 and isinstance( - self.attention_groups[0][0], FullAttentionSpec - ) - - # Attention-group indices whose EAGLE drop is verified at the current - # ``curr_hit_length``. Each eagle group applies the drop at most once - # per candidate length (see issue #32802). - eagle_verified: set[int] = set() - while True: - curr_hit_length = hit_length - for idx, (spec, group_ids, manager_cls) in enumerate(self.attention_groups): - # In PD disaggregation, Mamba running/temporal state is transferred - # via the KV connector, but the D side has no local Mamba prefix - # cache hit. If we let Mamba groups participate in the min-reduction, - # their zero hit collapses the FullAttention hit length to 0 and - # defeats prefix caching on the D side. Skip them instead. - if isinstance(spec, MambaSpec): - if hit_blocks_by_group[group_ids[0]] is None: - for gid in group_ids: - hit_blocks_by_group[gid] = [] - continue - - effective_block_size = self._get_effective_block_size(spec) - cached_blocks = hit_blocks_by_group[group_ids[0]] - if isinstance(spec, FullAttentionSpec) and cached_blocks is not None: - # Full attention is downward-closed: we only need to look - # up cached blocks once; on subsequent iterations just trim - # to the (reduced) current hit length. - num_blocks = curr_hit_length // effective_block_size - curr_hit_length = num_blocks * effective_block_size - continue - - use_eagle = idx in self.eagle_attn_group_indices and idx not in eagle_verified - - _max_length = curr_hit_length - if use_eagle: - # Eagle needs to match one more block and then pop the last. - _max_length = min(curr_hit_length + spec.block_size, max_cache_hit_length) - # vLLM B renamed the ``use_eagle`` kwarg to ``drop_eagle_block``. - if vllm_version_is("0.21.0"): - eagle_kwarg = {"use_eagle": use_eagle} - else: - eagle_kwarg = {"drop_eagle_block": use_eagle} hit_blocks = manager_cls.find_longest_cache_hit( block_hashes=_get_block_hashes(spec), max_length=_max_length, kv_cache_group_ids=group_ids, block_pool=self.block_pool, kv_cache_spec=spec, - **eagle_kwarg, + use_eagle=use_eagle, alignment_tokens=self.lcm_block_size, dcp_world_size=self.dcp_world_size, pcp_world_size=self.pcp_world_size, @@ -440,11 +284,10 @@ def get_kv_cache_coordinator( dcp_world_size: int, pcp_world_size: int, hash_block_size: int, - scheduler_block_size: int | None = None, eagle_attn_layer_names: list[str] | None = None, metrics_collector: KVCacheMetricsCollector | None = None, ) -> KVCacheCoordinator: - if _is_deepseek_v4_kv_cache_config(kv_cache_config): + if envs.VLLM_ASCEND_APPLY_DSV4_PATCH: return AscendHybridKVCacheCoordinator( kv_cache_config, max_model_len, @@ -457,26 +300,25 @@ def get_kv_cache_coordinator( eagle_attn_layer_names=eagle_attn_layer_names, metrics_collector=metrics_collector, max_num_batched_tokens=max_num_batched_tokens, - scheduler_block_size=scheduler_block_size, ) - if len(kv_cache_config.kv_cache_groups) == 1 or not enable_caching: - orig_kwargs = dict( - kv_cache_config=kv_cache_config, - max_model_len=max_model_len, - max_num_batched_tokens=max_num_batched_tokens, - use_eagle=use_eagle, - enable_caching=enable_caching, - enable_kv_cache_events=enable_kv_cache_events, - dcp_world_size=dcp_world_size, - pcp_world_size=pcp_world_size, - hash_block_size=hash_block_size, - metrics_collector=metrics_collector, - ) - if not vllm_version_is("0.22.1"): - orig_kwargs["scheduler_block_size"] = scheduler_block_size - return _orig_get_kv_cache_coordinator(**orig_kwargs) + cp_enabled = dcp_world_size > 1 or pcp_world_size > 1 + # Only CP hybrid prefix caching needs AscendHybridKVCacheCoordinator. + # Otherwise keep upstream coordinators (non-CP / unitary / no-prefix-cache). + if not cp_enabled or len(kv_cache_config.kv_cache_groups) == 1 or not enable_caching: + return _orig_get_kv_cache_coordinator( + kv_cache_config, + max_model_len, + max_num_batched_tokens, + use_eagle, + enable_caching, + enable_kv_cache_events, + dcp_world_size, + pcp_world_size, + hash_block_size, + metrics_collector, + ) return AscendHybridKVCacheCoordinator( kv_cache_config, max_model_len, @@ -489,7 +331,6 @@ def get_kv_cache_coordinator( eagle_attn_layer_names=eagle_attn_layer_names, metrics_collector=metrics_collector, max_num_batched_tokens=max_num_batched_tokens, - scheduler_block_size=scheduler_block_size, ) diff --git a/vllm_ascend/patch/platform/patch_mamba_config.py b/vllm_ascend/patch/platform/patch_mamba_config.py index 0a12dfad9..adf719218 100644 --- a/vllm_ascend/patch/platform/patch_mamba_config.py +++ b/vllm_ascend/patch/platform/patch_mamba_config.py @@ -9,24 +9,6 @@ from vllm.utils.torch_utils import STR_DTYPE_TO_TORCH_DTYPE, get_dtype_size -def _using_kv_store(vllm_config) -> bool: - """ - Check whether AscendStoreConnector is used. - In the scenario where only PD separation is used, mamba_cache_mode is not automatically set to align. - """ - if not vllm_config.kv_transfer_config: - return False - if vllm_config.kv_transfer_config.kv_connector == "AscendStoreConnector": - return True - if vllm_config.kv_transfer_config.kv_connector == "MultiConnector": - kv_connector_extra_config = vllm_config.kv_transfer_config.kv_connector_extra_config - if not kv_connector_extra_config: - return False - if connectors := kv_connector_extra_config.get("connectors"): - return any(connector.get("kv_connector") == "AscendStoreConnector" for connector in connectors) - return False - - @classmethod def verify_and_update_config(cls, vllm_config) -> None: """ @@ -39,10 +21,9 @@ def verify_and_update_config(cls, vllm_config) -> None: Args: vllm_config: vLLM Config """ - using_kv_store_with_hybrid = not vllm_config.scheduler_config.disable_hybrid_kv_cache_manager and _using_kv_store( - vllm_config + using_kv_transfer_with_hybrid = ( + not vllm_config.scheduler_config.disable_hybrid_kv_cache_manager and vllm_config.kv_transfer_config ) - logger.debug("Using kv store: %s", using_kv_store_with_hybrid) # Enable FULL_AND_PIECEWISE by default MambaModelConfig.verify_and_update_config(vllm_config) @@ -122,18 +103,7 @@ def verify_and_update_config(cls, vllm_config) -> None: "exactly equal.", mamba_padding_pct, ) - # The extract_hidden_states connector (ExampleHiddenStatesConnector) only - # manages the dedicated hidden-state cache-only layer; it does not migrate - # mamba KV blocks across instances, so it does not require the block-aligned - # mamba cache mode. Forcing "align" for it would route hybrid models onto - # vLLM's fused GPU postprocess Triton kernel (introduced in vLLM #40172), - # which the Ascend Triton backend cannot compile. Leave the mode as vLLM - # derived it (e.g. "none" when prefix caching is off) for this case. - spec_config = vllm_config.speculative_config - is_extract_hidden_states = ( - spec_config is not None and getattr(spec_config, "method", None) == "extract_hidden_states" - ) - if using_kv_store_with_hybrid and not is_extract_hidden_states: + if using_kv_transfer_with_hybrid: if cache_config.mamba_cache_mode == "none": cache_config.mamba_cache_mode = "align" else: diff --git a/vllm_ascend/patch/platform/patch_mamba_manager.py b/vllm_ascend/patch/platform/patch_mamba_manager.py index 8e83d5987..79b678139 100644 --- a/vllm_ascend/patch/platform/patch_mamba_manager.py +++ b/vllm_ascend/patch/platform/patch_mamba_manager.py @@ -14,8 +14,6 @@ MambaSpec, ) -from vllm_ascend.utils import vllm_version_is - class AscendMambaManager(MambaManager): def __init__(self, kv_cache_spec: MambaSpec, block_pool: BlockPool, **kwargs) -> None: @@ -31,11 +29,10 @@ def find_longest_cache_hit( kv_cache_group_ids: list[int], block_pool: BlockPool, kv_cache_spec: KVCacheSpec, + use_eagle: bool, alignment_tokens: int, dcp_world_size: int = 1, pcp_world_size: int = 1, - use_eagle: bool = False, - drop_eagle_block: bool = False, ) -> tuple[list[KVCacheBlock], ...]: assert isinstance(kv_cache_spec, MambaSpec), "MambaManager can only be used for mamba groups" computed_blocks: tuple[list[KVCacheBlock], ...] = tuple([] for _ in range(len(kv_cache_group_ids))) @@ -53,5 +50,4 @@ def find_longest_cache_hit( single_type_kv_cache_manager.MambaManager = AscendMambaManager -if vllm_version_is("0.22.1"): - single_type_kv_cache_manager.spec_manager_map[MambaSpec] = AscendMambaManager +single_type_kv_cache_manager.spec_manager_map[MambaSpec] = AscendMambaManager diff --git a/vllm_ascend/patch/platform/patch_mla_prefill_backend.py b/vllm_ascend/patch/platform/patch_mla_prefill_backend.py index 542fc7a3e..75615ed9a 100644 --- a/vllm_ascend/patch/platform/patch_mla_prefill_backend.py +++ b/vllm_ascend/patch/platform/patch_mla_prefill_backend.py @@ -14,35 +14,39 @@ import torch import vllm.model_executor.layers.attention.mla_attention -from vllm.v1.attention.backends.mla.prefill.base import MLAPrefillBackend - - -class AscendMLAPrefillBackend(MLAPrefillBackend): - @staticmethod - def get_name() -> str: - return "ASCEND" - - @classmethod - def is_available(cls) -> bool: - return True - - def run_prefill_new_tokens( - self, - q: torch.Tensor, - k: torch.Tensor, - v: torch.Tensor, - return_softmax_lse: bool, - ) -> torch.Tensor: - raise NotImplementedError("Ascend MLA prefill is handled by AscendSFAImpl/AscendMLAImpl") - - def run_prefill_context_chunk( - self, - chunk_idx: int, - q: torch.Tensor, - k: torch.Tensor, - v: torch.Tensor, - ) -> tuple[torch.Tensor, torch.Tensor]: - raise NotImplementedError("Ascend MLA prefill is handled by AscendSFAImpl/AscendMLAImpl") - - -vllm.model_executor.layers.attention.mla_attention.get_mla_prefill_backend = lambda vllm_config: AscendMLAPrefillBackend + +from vllm_ascend.utils import vllm_version_is + +if not vllm_version_is("0.20.2"): + from vllm.v1.attention.backends.mla.prefill.base import MLAPrefillBackend + + class AscendMLAPrefillBackend(MLAPrefillBackend): + @staticmethod + def get_name() -> str: + return "ASCEND" + + @classmethod + def is_available(cls) -> bool: + return True + + def run_prefill_new_tokens( + self, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + return_softmax_lse: bool, + ) -> torch.Tensor: + raise NotImplementedError("Ascend MLA prefill is handled by AscendSFAImpl/AscendMLAImpl") + + def run_prefill_context_chunk( + self, + chunk_idx: int, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + ) -> tuple[torch.Tensor, torch.Tensor]: + raise NotImplementedError("Ascend MLA prefill is handled by AscendSFAImpl/AscendMLAImpl") + + vllm.model_executor.layers.attention.mla_attention.get_mla_prefill_backend = ( + lambda vllm_config: AscendMLAPrefillBackend + ) diff --git a/vllm_ascend/patch/platform/patch_pp_mtp.py b/vllm_ascend/patch/platform/patch_pp_mtp.py deleted file mode 100644 index 63b4422ff..000000000 --- a/vllm_ascend/patch/platform/patch_pp_mtp.py +++ /dev/null @@ -1,84 +0,0 @@ -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# This file is a part of the vllm-ascend project. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Backport vLLM PP + MTP runtime support. - -The local Eagle/MTP drafter returns the draft tokens that belong to the model -output being processed. With PP batch_queue, EngineCore schedules a newer batch -before consuming the older output, so updating ``request.spec_token_ids`` from -``post_step`` observes live Request state from the newer schedule step. -""" - -from __future__ import annotations - -import copy -from functools import wraps - -from vllm.logger import logger - -_PATCHED = False - - -def _patch_model_config_validation() -> None: - from typing import get_args - - from vllm.config.model import ModelConfig - from vllm.config.speculative import MTPModelTypes - - original_verify = ModelConfig.verify_with_parallel_config - if getattr(original_verify, "_vllm_ascend_pp_mtp_patched", False): - return - - mtp_model_types = set(get_args(MTPModelTypes)) - - @wraps(original_verify) - def _patched_verify_with_parallel_config(self, parallel_config): - hf_config = getattr(self, "hf_config", None) - model_type = getattr(hf_config, "model_type", None) - is_eagle_drafter = (model_type == "eagle" or model_type == "speculators") and any( - arch.startswith("Eagle") or arch.endswith("Eagle3") for arch in getattr(self, "architectures", ()) - ) - is_mtp_drafter = model_type in mtp_model_types - if ( - getattr(self, "runner", None) == "draft" - and (is_eagle_drafter or is_mtp_drafter) - and getattr(parallel_config, "pipeline_parallel_size", 1) > 1 - ): - # Local Eagle/MTP drafters are loaded on the last PP stage rather - # than partitioned across all PP stages. Keep normal target-model - # validation intact, but validate these draft models as PP=1. - logger.warning( - "Validating local Eagle/MTP drafter with pipeline_parallel_size=1 " - "because it is loaded locally on the last pipeline stage." - ) - patched_config = copy.copy(parallel_config) - patched_config.pipeline_parallel_size = 1 - return original_verify(self, patched_config) - return original_verify(self, parallel_config) - - _patched_verify_with_parallel_config._vllm_ascend_pp_mtp_patched = True # type: ignore[attr-defined] - ModelConfig.verify_with_parallel_config = _patched_verify_with_parallel_config - - -def _apply_patch() -> None: - global _PATCHED - if _PATCHED: - return - _PATCHED = True - _patch_model_config_validation() - - -_apply_patch() diff --git a/vllm_ascend/patch/platform/patch_tool_choice_none_content.py b/vllm_ascend/patch/platform/patch_tool_choice_none_content.py index fb6c22b98..f18913c39 100644 --- a/vllm_ascend/patch/platform/patch_tool_choice_none_content.py +++ b/vllm_ascend/patch/platform/patch_tool_choice_none_content.py @@ -28,10 +28,9 @@ ChatCompletionResponse, ChatCompletionStreamResponse, ) +from vllm.entrypoints.openai.engine.serving import OpenAIServing from vllm.parser.abstract_parser import DelegatingParser -from vllm_ascend.utils import vllm_version_is - _NO_FORCED_TOOL_CALL = "_vllm_ascend_no_forced_tool_call" _original_chat_completion_response_model_dump = ChatCompletionResponse.model_dump @@ -112,6 +111,33 @@ def _patched_named_tool_choice_bool(self) -> bool: _patch_named_tool_choice_bool() + +_original_parse_tool_calls_from_content = OpenAIServing._parse_tool_calls_from_content + + +def _patched_parse_tool_calls_from_content( + request, + tokenizer, + enable_auto_tools: bool, + tool_parser_cls, + content: str | None = None, +): + if content is None and _is_forced_tool_choice(request): + _set_no_forced_tool_call(request, True) + return [], None + + _set_no_forced_tool_call(request, False) + return _original_parse_tool_calls_from_content( + request=request, + tokenizer=tokenizer, + enable_auto_tools=enable_auto_tools, + tool_parser_cls=tool_parser_cls, + content=content, + ) + + +OpenAIServing._parse_tool_calls_from_content = staticmethod(_patched_parse_tool_calls_from_content) + _original_delegating_parse_tool_calls = DelegatingParser._parse_tool_calls @@ -133,30 +159,3 @@ def _patched_delegating_parse_tool_calls( DelegatingParser._parse_tool_calls = _patched_delegating_parse_tool_calls - -if vllm_version_is("0.22.1"): - from vllm.entrypoints.openai.engine.serving import OpenAIServing # type: ignore[import-not-found] - - _original_parse_tool_calls_from_content = OpenAIServing._parse_tool_calls_from_content - - def _patched_parse_tool_calls_from_content( - request, - tokenizer, - enable_auto_tools: bool, - tool_parser_cls, - content: str | None = None, - ): - if content is None and _is_forced_tool_choice(request): - _set_no_forced_tool_call(request, True) - return [], None - - _set_no_forced_tool_call(request, False) - return _original_parse_tool_calls_from_content( - request=request, - tokenizer=tokenizer, - enable_auto_tools=enable_auto_tools, - tool_parser_cls=tool_parser_cls, - content=content, - ) - - OpenAIServing._parse_tool_calls_from_content = staticmethod(_patched_parse_tool_calls_from_content) diff --git a/vllm_ascend/patch/platform/patch_weight_transfer_engine.py b/vllm_ascend/patch/platform/patch_weight_transfer_engine.py deleted file mode 100644 index a29a7a5f8..000000000 --- a/vllm_ascend/patch/platform/patch_weight_transfer_engine.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved. -# This file is a part of the vllm-ascend project. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# Patch target: vllm.distributed.weight_transfer.factory.WeightTransferEngineFactory -# -# Replace the "nccl" factory entry with HCCLWeightTransferEngine so that -# --weight-transfer-config '{"backend": "nccl"}' loads the HCCL engine -# instead of the (unavailable) NCCL engine on Ascend NPU. -# -# Why this approach (factory swap) instead of patching Literal["nccl", "ipc"]: -# WeightTransferConfig.backend is a pydantic Literal["nccl", "ipc"]. -# Adding "hccl" would require modifying pydantic core schemas — fragile -# across pydantic versions. Swapping the factory entry means users pass -# the already-accepted "nccl" string, but the factory resolves it to HCCL. -# -# Timing — guaranteed to run before first factory usage: -# -# vllm serve main() -# line 24: from vllm.entrypoints.utils import ... -# → vllm.platforms.__getattr__("current_platform") -# → resolve_current_platform_cls_qualname() -# → vllm_ascend:register() → NPUPlatform() -# → NPUPlatform.pre_register_and_update() -# → adapt_patch(is_global_patch=True) -# → imports vllm_ascend.patch.platform -# → THIS PATCH RUNS ← "nccl" now points to HCCLWeightTransferEngine -# ... -# lines 82-86: subparser_init() → make_arg_parser() -# line 87: parse_args() → validates backend="nccl" via Literal (passes) -# ... -# later: worker init → WeightTransferEngineFactory.create_engine(config) -# → config.backend == "nccl" → factory loads HCCLWeightTransferEngine -# -# Future Plan: -# Remove this patch when upstream vllm relaxes the Literal type to str -# or provides an extension point for out-of-tree backends. - -from vllm.distributed.weight_transfer.factory import WeightTransferEngineFactory - -from vllm_ascend.distributed.weight_transfer.hccl_engine import ( - HCCLWeightTransferEngine, -) - -WeightTransferEngineFactory._registry["nccl"] = lambda: HCCLWeightTransferEngine diff --git a/vllm_ascend/patch/worker/__init__.py b/vllm_ascend/patch/worker/__init__.py index 1746dc8a4..6966b8f8a 100644 --- a/vllm_ascend/patch/worker/__init__.py +++ b/vllm_ascend/patch/worker/__init__.py @@ -19,13 +19,8 @@ from vllm_ascend.utils import is_310p, vllm_version_is -# The v2 model runner is intentionally NOT made compatible with the v0.22.1 -# release. vLLM v0.22.1 and the verified main commit are diverged, and the v2 -# worker patches target main-only APIs; rather than maintain a separate v0.22.1 -# compatibility path we keep v2 main-only. With v0.22.1 installed this flag is -# False, so none of the patch_v2.* / routed-experts-capture patches below are -# imported and the v2 worker stays dormant (the release uses the v1 runner). -_V2_MODEL_RUNNER_SUPPORTED = not vllm_version_is("0.22.1") +# v2 model runner is only supported on vllm > 0.20.2. +_V2_MODEL_RUNNER_SUPPORTED = not vllm_version_is("0.20.2") if HAS_TRITON: import vllm_ascend.patch.worker.patch_triton @@ -40,9 +35,11 @@ import vllm_ascend.patch.worker.patch_minimax_m2_linear_attn # noqa import vllm_ascend.patch.worker.patch_mamba_utils # noqa import vllm_ascend.patch.worker.patch_qwen3_next_mtp # noqa +import vllm_ascend.patch.worker.patch_deepseek_compressor # noqa if not is_310p(): import vllm_ascend.patch.worker.patch_qwen3_5 # noqa + import vllm_ascend.patch.worker.patch_gdn_attn # noqa import vllm_ascend.patch.worker.patch_qwen3_dflash # noqa import vllm_ascend.patch.worker.patch_qwen3vl # noqa else: diff --git a/vllm_ascend/patch/worker/patch_deepseek_compressor.py b/vllm_ascend/patch/worker/patch_deepseek_compressor.py new file mode 100644 index 000000000..04c51a1d6 --- /dev/null +++ b/vllm_ascend/patch/worker/patch_deepseek_compressor.py @@ -0,0 +1,156 @@ +import torch +import vllm +from vllm.config import VllmConfig, get_current_vllm_config +from vllm.config.cache import CacheConfig +from vllm.v1.attention.backends.mla.sparse_swa import DeepseekV4SWACache +from vllm.v1.kv_cache_interface import ( + KVCacheSpec, + SlidingWindowMLASpec, +) + +from vllm_ascend.attention.dsa_v1 import AscendDSABackend +from vllm_ascend.patch.platform.patch_kv_cache_interface import AscendMLAAttentionSpec +from vllm_ascend.utils import AscendDeviceType, get_ascend_device_type, vllm_version_is + +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers import ( + deepseek_compressor, # type:ignore + deepseek_v4_attention, # type:ignore + ) + from vllm.model_executor.layers.deepseek_compressor import CompressorStateCache # type:ignore + from vllm.model_executor.layers.deepseek_v4_attention import DeepseekV4IndexerCache # type:ignore +else: + import vllm.models.deepseek_v4.attention as deepseek_v4_attention + import vllm.models.deepseek_v4.compressor as deepseek_compressor + from vllm.models.deepseek_v4.attention import DeepseekV4IndexerCache + from vllm.models.deepseek_v4.compressor import CompressorStateCache + + +class AscendCompressorStateCache(CompressorStateCache): + def __init__( + self, + state_dim: int, + dtype: torch.dtype, + compress_ratio: int, + block_size: int, + prefix: str, + ): + torch.nn.Module.__init__(self) + self.state_dim = state_dim + self.dtype = dtype + self.prefix = prefix + self.kv_cache = torch.tensor([]) + compilation_config = get_current_vllm_config().compilation_config + if prefix in compilation_config.static_forward_context: + raise ValueError(f"Duplicate layer name: {prefix}") + compilation_config.static_forward_context[prefix] = self + + assert self.dtype == torch.float32 + assert compress_ratio in [4, 128] + self.compress_ratio = compress_ratio + coff = 1 + (compress_ratio == 4) + self.sliding_window = coff * compress_ratio + + self.block_size = block_size + + def get_kv_cache_spec(self, vllm_config) -> KVCacheSpec: + if get_ascend_device_type() in {AscendDeviceType.A5}: + page_size_padded = 16896 if self.state_dim == 2 * 256 and self.compress_ratio == 4 else 81920 + else: + page_size_padded = 16640 if self.state_dim == 2 * 256 and self.compress_ratio == 4 else 131072 + + return SlidingWindowMLASpec( # only has one vector instead of K + V + block_size=self.block_size, + num_kv_heads=1, + head_size=self.state_dim, + dtype=self.dtype, + sliding_window=self.sliding_window, + alignment=None, # NOTE: FlashMLA requires 576B alignment + page_size_padded=page_size_padded, + ) + + def forward(self): ... + + def get_attn_backend(self): + return AscendDSABackend + + +class AscendDeepseekV4IndexerCache(DeepseekV4IndexerCache): + def __init__( + self, + head_dim: int, + dtype: torch.dtype, + prefix: str, + cache_config: CacheConfig, + compress_ratio: int = 1, + ): + super().__init__(head_dim, dtype, prefix, cache_config, compress_ratio) + + def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec: + if get_ascend_device_type() in {AscendDeviceType.A5}: + self.dtype = torch.float8_e4m3fn + vllm_config.cache_config.cache_dtype = "float8_e4m3fn" + + return AscendMLAAttentionSpec( # Only has one vector instead of K + V + block_size=128, + num_kv_heads=1, + head_size=self.head_dim, + dtype=self.dtype, + model_version="deepseek_v4", + compress_ratio=self.compress_ratio, + cache_dtype_str=self.cache_config.cache_dtype, + scale_dim=1 if self.head_dim == 128 else 0, + scale_dtype=torch.float if get_ascend_device_type() in {AscendDeviceType.A5} else torch.float16, + ) + + def forward(self): ... + + def get_attn_backend(self): + return AscendDSABackend + + +class AscendDeepseekV4SWACache(DeepseekV4SWACache): + def __init__( + self, + head_dim: int, + window_size: int, + dtype: torch.dtype, + prefix: str, + cache_config: CacheConfig, + ): + super().__init__(head_dim, window_size, torch.uint8, prefix, cache_config) + self.dtype = dtype + + # Block size is constrained by tensor sharing between SWA and C4A KV blocks. + # Since both block types share the same physical tensor, they must use the + # same page size. The C4A KV block shape [256//4, head_dim] = [64, head_dim] + # determines the SWA block size of 64 tokens per block. + # TODO(cmq): make SWA block size automatically determined and configurable. + self.block_size = 128 + + def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec: + if get_ascend_device_type() in {AscendDeviceType.A5}: + self.dtype = torch.float8_e4m3fn + vllm_config.cache_config.cache_dtype = "float8_e4m3fn" + # TODO(cmq): alignment = 0 if A3 else 128 + cached_head_size = (self.head_dim + 128) if get_ascend_device_type() in {AscendDeviceType.A5} else self.head_dim + return SlidingWindowMLASpec( + block_size=self.block_size, + num_kv_heads=1, + head_size=cached_head_size, + dtype=self.dtype, + sliding_window=self.window_size, + cache_dtype_str=self.cache_config.cache_dtype, + model_version="deepseek_v4", + alignment=None, # NOTE: FlashMLA requires 576B alignment + ) + + def forward(self): ... + + def get_attn_backend(self): + return AscendDSABackend + + +deepseek_compressor.CompressorStateCache = AscendCompressorStateCache +deepseek_v4_attention.DeepseekV4IndexerCache = AscendDeepseekV4IndexerCache +vllm.v1.attention.backends.mla.sparse_swa.DeepseekV4SWACache = AscendDeepseekV4SWACache diff --git a/vllm_ascend/patch/worker/patch_deepseek_mtp.py b/vllm_ascend/patch/worker/patch_deepseek_mtp.py index fce882150..696d3e551 100644 --- a/vllm_ascend/patch/worker/patch_deepseek_mtp.py +++ b/vllm_ascend/patch/worker/patch_deepseek_mtp.py @@ -4,8 +4,8 @@ from transformers import DeepseekV2Config, DeepseekV3Config from vllm.config import VllmConfig from vllm.model_executor.models.deepseek_mtp import DeepSeekMTP, DeepSeekMultiTokenPredictorLayer -from vllm.model_executor.models.deepseek_v2 import GlmMoeDsaForCausalLM -from vllm.model_executor.models.utils import AutoWeightsLoader + +from vllm_ascend.utils import vllm_version_is MTP_ROT_WEIGHT_NAME = "rot.weight" @@ -23,6 +23,17 @@ def get_spec_layer_idx_from_weight_name(config: DeepseekV2Config | DeepseekV3Con return None +def get_spec_layer_idx_from_weight_name_020( + config: DeepseekV2Config | DeepseekV3Config, weight_name: str +) -> int | None: + if hasattr(config, "num_nextn_predict_layers") and config.num_nextn_predict_layers > 0: + layer_idx = config.num_hidden_layers + for i in range(config.num_nextn_predict_layers): + if weight_name.startswith(f"model.layers.{layer_idx + i}.") or weight_name.startswith(MTP_ROT_WEIGHT_NAME): + return layer_idx + i + return None + + class AscendDeepSeekMultiTokenPredictorLayer(DeepSeekMultiTokenPredictorLayer): def __init__(self, vllm_config: VllmConfig, prefix: str) -> None: super().__init__(vllm_config, prefix) @@ -63,14 +74,14 @@ def _rewrite_spec_layer_name(self, spec_layer: int, name: str) -> str: return f"model.layers.{spec_layer}.rot.weight" -class AscendGlmMoeDsaForCausalLM(GlmMoeDsaForCausalLM): - def load_weights(self, weights): - loader = AutoWeightsLoader(self, skip_prefixes=[MTP_ROT_WEIGHT_NAME]) - return loader.load_weights(weights) - +if vllm_version_is("0.20.2"): + vllm.model_executor.models.deepseek_v2.get_spec_layer_idx_from_weight_name = get_spec_layer_idx_from_weight_name_020 + vllm.model_executor.models.deepseek_mtp.get_spec_layer_idx_from_weight_name = ( + get_spec_layer_idx_from_weight_name_020 + ) +else: + vllm.model_executor.models.deepseek_v2.get_spec_layer_idx_from_weight_name = get_spec_layer_idx_from_weight_name + vllm.model_executor.models.deepseek_mtp.get_spec_layer_idx_from_weight_name = get_spec_layer_idx_from_weight_name -vllm.model_executor.models.deepseek_v2.get_spec_layer_idx_from_weight_name = get_spec_layer_idx_from_weight_name -vllm.model_executor.models.deepseek_mtp.get_spec_layer_idx_from_weight_name = get_spec_layer_idx_from_weight_name vllm.model_executor.models.deepseek_mtp.DeepSeekMultiTokenPredictorLayer = AscendDeepSeekMultiTokenPredictorLayer vllm.model_executor.models.deepseek_mtp.DeepSeekMTP = AscendDeepSeekMTP -vllm.model_executor.models.deepseek_v2.GlmMoeDsaForCausalLM = AscendGlmMoeDsaForCausalLM diff --git a/vllm_ascend/ops/gdn_attn_builder.py b/vllm_ascend/patch/worker/patch_gdn_attn.py similarity index 51% rename from vllm_ascend/ops/gdn_attn_builder.py rename to vllm_ascend/patch/worker/patch_gdn_attn.py index 7a3cbb189..1ae910329 100644 --- a/vllm_ascend/ops/gdn_attn_builder.py +++ b/vllm_ascend/patch/worker/patch_gdn_attn.py @@ -16,44 +16,28 @@ from dataclasses import dataclass import torch -from vllm.config import VllmConfig -from vllm.v1.attention.backend import AttentionCGSupport, CommonAttentionMetadata -from vllm.v1.attention.backends.gdn_attn import ( - GDNAttentionBackend, - GDNAttentionMetadata, - GDNAttentionMetadataBuilder, -) -from vllm.v1.attention.backends.utils import ( - NULL_BLOCK_ID, - compute_causal_conv1d_metadata, - mamba_get_block_table_tensor, - split_decodes_and_prefills, -) -from vllm.v1.kv_cache_interface import AttentionSpec +import vllm.v1.attention.backends.gdn_attn as gdn_attn +from vllm.v1.attention.backends.utils import NULL_BLOCK_ID from vllm_ascend.ops.triton.gdn_chunk_meta import ( _build_seq_lens, _validate_cu_seqlens, build_chunk_meta_device, ) +from vllm_ascend.utils import is_310p _GDN_CHUNK_SIZE = 64 # Keep this aligned with solve_tril.LARGE_BLOCK_T in ops/triton/fla/solve_tril.py. _GDN_SOLVE_TRIL_LARGE_BLOCK_SIZE = 608 * 2 _GDN_CUMSUM_WORKING_SET = 2**18 - -def _stable_argsort_for_npu(tensor: torch.Tensor) -> torch.Tensor: - if tensor.dtype == torch.bool: - tensor = tensor.to(torch.int32) - return torch.argsort(tensor, stable=True) +_IS_PATCHED = False +_ORIGINAL_BUILD = gdn_attn.GDNAttentionMetadataBuilder.build +_ORIGINAL_INIT_THRESHOLD = gdn_attn.GDNAttentionMetadataBuilder._init_reorder_batch_threshold @dataclass class GDNChunkedPrefillMetadata: - cu_seqlens_cpu: torch.Tensor - cu_seqlens_host: tuple[int, ...] - chunk_indices_chunk64_host: tuple[int, ...] chunk_indices_chunk64: torch.Tensor chunk_offsets_chunk64: torch.Tensor update_chunk_offsets_chunk64: torch.Tensor @@ -161,14 +145,6 @@ def _fill_chunk_indices_cpu(out: torch.Tensor, chunk_counts: torch.Tensor) -> in return cursor -def _build_chunk_indices_host(cu_seqlens_cpu: torch.Tensor, chunk_size: int) -> tuple[int, ...]: - chunk_counts = _prepare_chunk_counts_cpu(cu_seqlens_cpu, chunk_size) - num_chunk_indices = int(chunk_counts.sum().item()) - chunk_indices = torch.empty((num_chunk_indices, 2), dtype=torch.int64) - _fill_chunk_indices_cpu(chunk_indices, chunk_counts) - return tuple(chunk_indices.reshape(-1).tolist()) - - def _fill_chunk_offsets_cpu(out: torch.Tensor, chunk_counts: torch.Tensor) -> int: out[0] = 0 if chunk_counts.numel() > 0: @@ -365,16 +341,9 @@ def _build_chunked_prefill_metadata( builder, tensors: dict[str, torch.Tensor], *, - cu_seqlens_cpu: torch.Tensor, slot: _GDNChunkedPrefillBufferSlot | None = None, ) -> GDNChunkedPrefillMetadata: return GDNChunkedPrefillMetadata( - cu_seqlens_cpu=cu_seqlens_cpu, - cu_seqlens_host=tuple(cu_seqlens_cpu.to(torch.int64).tolist()), - chunk_indices_chunk64_host=_build_chunk_indices_host( - cu_seqlens_cpu, - builder._ascend_gdn_chunk_size, - ), chunk_indices_chunk64=tensors["chunk_indices_chunk64"], chunk_offsets_chunk64=tensors["chunk_offsets_chunk64"], update_chunk_offsets_chunk64=tensors["update_chunk_offsets_chunk64"], @@ -460,13 +429,9 @@ def _build_spec_sequence_masks_cpu(builder, num_decode_draft_tokens_cpu: torch.T def _build_non_spec_query_start_loc_cpu( builder, - attn_metadata, common_attn_metadata, num_decode_draft_tokens_cpu: torch.Tensor | None, ) -> torch.Tensor | None: - if attn_metadata.num_prefills <= 0 and attn_metadata.num_decodes <= 0: - return None - query_start_loc_cpu = common_attn_metadata.query_start_loc_cpu spec_sequence_masks_cpu = _build_spec_sequence_masks_cpu(builder, num_decode_draft_tokens_cpu) if spec_sequence_masks_cpu is None: @@ -496,6 +461,7 @@ def _build_spec_query_start_loc_cpu( if spec_sequence_masks_cpu is None: return query_start_loc_cpu + # query_start_loc_cpu is type of cumsum query_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1] spec_query_lens_cpu = query_lens_cpu[spec_sequence_masks_cpu] spec_query_start_loc_cpu = torch.zeros( @@ -627,9 +593,11 @@ def _build_non_spec_causal_conv1d_host_meta( ) -> GDNCausalConv1dHostMetadata: assert attn_metadata.num_prefills > 0 if attn_metadata.non_spec_state_indices_tensor is None: - raise RuntimeError("Expected attn_metadata.non_spec_state_indices_tensor for Ascend GDN non-spec prefill path.") + raise RuntimeError( + "Expected attn_metadata.non_spec_state_indices_tensor for patched GDN non-spec prefill path." + ) if attn_metadata.has_initial_state is None: - raise RuntimeError("Expected attn_metadata.has_initial_state for Ascend GDN non-spec prefill path.") + raise RuntimeError("Expected attn_metadata.has_initial_state for patched GDN non-spec prefill path.") slot = None if ( @@ -661,7 +629,7 @@ def _build_non_spec_decode_causal_conv1d_host_meta( non_spec_query_start_loc_cpu: torch.Tensor, ) -> GDNCausalConv1dHostMetadata: if attn_metadata.non_spec_state_indices_tensor is None: - raise RuntimeError("Expected attn_metadata.non_spec_state_indices_tensor for Ascend GDN non-spec decode path.") + raise RuntimeError("Expected attn_metadata.non_spec_state_indices_tensor for patched GDN non-spec decode path.") slot = None if attn_metadata.non_spec_state_indices_tensor.device.type != "cpu": @@ -687,7 +655,7 @@ def _build_spec_causal_conv1d_host_meta( ) -> GDNSpecCausalConv1dHostMetadata: assert attn_metadata.spec_sequence_masks is not None if attn_metadata.spec_state_indices_tensor is None: - raise RuntimeError("Expected attn_metadata.spec_state_indices_tensor for Ascend GDN speculative path.") + raise RuntimeError("Expected attn_metadata.spec_state_indices_tensor for patched GDN speculative path.") slot = None if attn_metadata.spec_state_indices_tensor.device.type != "cpu": @@ -716,7 +684,7 @@ def _build_non_spec_chunked_prefill_meta_cpu(builder, cu_seqlens_cpu: torch.Tens shape_info = _build_chunk_meta_shape_info(builder, cu_seqlens_cpu) tensors = _allocate_chunk_meta_cpu_tensors(shape_info) _fill_chunk_meta_cpu_tensors(tensors, shape_info) - return _build_chunked_prefill_metadata(builder, tensors, cu_seqlens_cpu=cu_seqlens_cpu) + return _build_chunked_prefill_metadata(builder, tensors) def _build_non_spec_chunked_prefill_meta( @@ -735,527 +703,167 @@ def _build_non_spec_chunked_prefill_meta( slot = builder._ascend_gdn_chunked_prefill_pool[builder._ascend_gdn_chunked_prefill_pool_idx] tensors = _slice_chunk_meta_slot_tensors(slot, shape_info) _fill_chunk_meta_device_tensors(builder, cu_seqlens, tensors) - return _build_chunked_prefill_metadata(builder, tensors, cu_seqlens_cpu=cu_seqlens_cpu, slot=slot) + return _build_chunked_prefill_metadata(builder, tensors, slot=slot) -class AscendGDNAttentionMetadataBuilder(GDNAttentionMetadataBuilder): - _cudagraph_support = AttentionCGSupport.UNIFORM_BATCH - - def __init__( +def _patched_build( + self, + common_prefix_len: int, + common_attn_metadata, + num_accepted_tokens: torch.Tensor | None = None, + num_decode_draft_tokens_cpu: torch.Tensor | None = None, + fast_build: bool = False, +): + attn_metadata = _ORIGINAL_BUILD( self, - kv_cache_spec: AttentionSpec, - layer_names: list[str], - vllm_config: VllmConfig, - device: torch.device, - ): - super().__init__(kv_cache_spec, layer_names, vllm_config, device) - sequence_index_capacity = max( - self.vllm_config.scheduler_config.max_num_seqs, - self.decode_cudagraph_max_bs, - ) - self.spec_sequence_masks_cpu: torch.Tensor = torch.empty( - (sequence_index_capacity,), - dtype=torch.bool, - device="cpu", - pin_memory=device.type != "cpu", - ) - self.spec_sequence_indices_cpu: torch.Tensor = torch.empty( - (sequence_index_capacity,), - dtype=torch.int64, - device="cpu", - pin_memory=device.type != "cpu", - ) - self.non_spec_sequence_indices_cpu: torch.Tensor = torch.empty( - (sequence_index_capacity,), - dtype=torch.int64, - device="cpu", - pin_memory=device.type != "cpu", - ) - self.spec_sequence_indices: torch.Tensor = torch.empty( - (sequence_index_capacity,), - dtype=torch.int64, - device=device, - ) - self.non_spec_sequence_indices: torch.Tensor = torch.empty( - (sequence_index_capacity,), - dtype=torch.int64, - device=device, - ) + common_prefix_len, + common_attn_metadata, + num_accepted_tokens=num_accepted_tokens, + num_decode_draft_tokens_cpu=num_decode_draft_tokens_cpu, + fast_build=fast_build, + ) + attn_metadata.non_spec_prefill_fallback_meta = None + attn_metadata.non_spec_decode_fallback_meta = None + attn_metadata.spec_decode_fallback_meta = None + if attn_metadata.spec_sequence_masks is not None: + _patched_build_spec(self, attn_metadata, common_attn_metadata, num_decode_draft_tokens_cpu) - def _init_reorder_batch_threshold( - self, - reorder_batch_threshold: int | None = 1, - supports_spec_as_decode: bool = False, - supports_dcp_with_varlen: bool = False, - ) -> None: - super()._init_reorder_batch_threshold( - reorder_batch_threshold, - supports_spec_as_decode, - supports_dcp_with_varlen, - ) - if self.reorder_batch_threshold != 1: # type: ignore - speculative_config = self.vllm_config.speculative_config - if ( - speculative_config is not None - and speculative_config.num_speculative_tokens is not None - and hasattr(speculative_config, "method") - and speculative_config.method == "dflash" - ): - self.reorder_batch_threshold = 1 + speculative_config.num_speculative_tokens - - def _copy_sequence_indices_to_device( - self, - spec_sequence_masks_cpu: torch.Tensor, - num_spec_decodes: int, - ) -> tuple[torch.Tensor, torch.Tensor]: - num_reqs = spec_sequence_masks_cpu.numel() - num_non_spec_decodes = num_reqs - num_spec_decodes - - spec_indices_cpu = self.spec_sequence_indices_cpu[:num_spec_decodes] - spec_indices_cpu.copy_( - torch.nonzero(spec_sequence_masks_cpu, as_tuple=True)[0], - ) - spec_indices = self.spec_sequence_indices[:num_spec_decodes] - spec_indices.copy_(spec_indices_cpu, non_blocking=True) + if attn_metadata.num_prefills > 0: + _patched_build_prefill(self, attn_metadata, common_attn_metadata, num_decode_draft_tokens_cpu) - non_spec_indices_cpu = self.non_spec_sequence_indices_cpu[:num_non_spec_decodes] - non_spec_indices_cpu.copy_( - torch.nonzero(~spec_sequence_masks_cpu, as_tuple=True)[0], - ) - non_spec_indices = self.non_spec_sequence_indices[:num_non_spec_decodes] - non_spec_indices.copy_(non_spec_indices_cpu, non_blocking=True) + if attn_metadata.num_decodes > 0: + _patched_build_decode(self, attn_metadata, common_attn_metadata, num_decode_draft_tokens_cpu) - return spec_indices, non_spec_indices + if ( + self.use_full_cuda_graph + and attn_metadata.num_prefills == 0 + and attn_metadata.num_spec_decodes == 0 + and attn_metadata.num_decodes <= self.decode_cudagraph_max_bs + ): + self.non_spec_state_indices_tensor[attn_metadata.num_actual_tokens :].fill_(NULL_BLOCK_ID) + return attn_metadata - def _attach_non_spec_prefill_fallback_meta( - self, - attn_metadata: GDNAttentionMetadata, - common_attn_metadata: CommonAttentionMetadata, - non_spec_query_start_loc_cpu: torch.Tensor | None, - ) -> GDNAttentionMetadata: - attn_metadata.non_spec_prefill_fallback_meta = None - if attn_metadata.num_prefills <= 0: - return attn_metadata - - _ensure_chunk_meta_state(self, common_attn_metadata.query_start_loc.device) - _ensure_causal_conv1d_host_meta_state( - self, - common_attn_metadata.query_start_loc.device, - ) - if non_spec_query_start_loc_cpu is None: - raise RuntimeError("Expected non_spec_query_start_loc_cpu for Ascend GDN non-spec prefill path.") - if attn_metadata.non_spec_query_start_loc is None: - raise RuntimeError("Expected attn_metadata.non_spec_query_start_loc for Ascend GDN non-spec prefill path.") - - attn_metadata.non_spec_prefill_fallback_meta = GDNPrefillFallbackMeta( - causal_conv1d=_build_non_spec_causal_conv1d_host_meta( - self, - attn_metadata, - non_spec_query_start_loc_cpu, - ), - chunk=_build_non_spec_chunked_prefill_meta( - self, - non_spec_query_start_loc_cpu, - attn_metadata.non_spec_query_start_loc, - ), - ) - return attn_metadata - def _attach_spec_decode_fallback_meta( - self, - attn_metadata: GDNAttentionMetadata, - common_attn_metadata: CommonAttentionMetadata, - num_decode_draft_tokens_cpu: torch.Tensor | None, - ) -> GDNAttentionMetadata: - attn_metadata.spec_decode_fallback_meta = None - if attn_metadata.spec_sequence_masks is None: - return attn_metadata - - _ensure_spec_causal_conv1d_host_meta_state( - self, - common_attn_metadata.query_start_loc.device, - ) - spec_query_start_loc_cpu = _build_spec_query_start_loc_cpu( - self, - common_attn_metadata, - num_decode_draft_tokens_cpu, - ) - if spec_query_start_loc_cpu is None: - raise RuntimeError("Expected spec query_start_loc_cpu for Ascend GDN speculative path.") - if attn_metadata.spec_query_start_loc is None: - raise RuntimeError("Expected attn_metadata.spec_query_start_loc for Ascend GDN speculative path.") - - attn_metadata.spec_decode_fallback_meta = GDNSpecDecodeFallbackMeta( - spec_causal_conv1d=_build_spec_causal_conv1d_host_meta( - self, - attn_metadata, - spec_query_start_loc_cpu, - ), - ) - return attn_metadata +def _patched_build_prefill( + self, + attn_metadata: gdn_attn.GDNAttentionMetadata, + common_attn_metadata, + num_decode_draft_tokens_cpu: torch.Tensor | None = None, +): + assert attn_metadata.num_prefills > 0 - def _attach_non_spec_decode_fallback_meta( + _ensure_chunk_meta_state(self, common_attn_metadata.query_start_loc.device) + _ensure_causal_conv1d_host_meta_state( self, - attn_metadata: GDNAttentionMetadata, - common_attn_metadata: CommonAttentionMetadata, - num_decode_draft_tokens_cpu: torch.Tensor | None, - ) -> GDNAttentionMetadata: - attn_metadata.non_spec_decode_fallback_meta = None - if attn_metadata.num_decodes <= 0: - return attn_metadata - - _ensure_causal_conv1d_host_meta_state( - self, - common_attn_metadata.query_start_loc.device, - ) - non_spec_query_start_loc_cpu = _build_non_spec_query_start_loc_cpu( + common_attn_metadata.query_start_loc.device, + ) + non_spec_query_start_loc_cpu = _build_non_spec_query_start_loc_cpu( + self, + common_attn_metadata, + num_decode_draft_tokens_cpu, + ) + assert non_spec_query_start_loc_cpu is not None + if attn_metadata.non_spec_query_start_loc is None: + raise RuntimeError("Expected attn_metadata.non_spec_query_start_loc for patched GDN non-spec prefill path.") + attn_metadata.non_spec_prefill_fallback_meta = GDNPrefillFallbackMeta( + causal_conv1d=_build_non_spec_causal_conv1d_host_meta( self, attn_metadata, - common_attn_metadata, - num_decode_draft_tokens_cpu, - ) - if non_spec_query_start_loc_cpu is None: - raise RuntimeError("Expected non-spec query_start_loc_cpu for Ascend GDN non-spec decode path.") - - attn_metadata.non_spec_decode_fallback_meta = GDNDecodeFallbackMeta( - causal_conv1d=_build_non_spec_decode_causal_conv1d_host_meta( - self, - attn_metadata, - non_spec_query_start_loc_cpu, - ), - ) - return attn_metadata + non_spec_query_start_loc_cpu, + ), + chunk=_build_non_spec_chunked_prefill_meta( + self, + non_spec_query_start_loc_cpu, + attn_metadata.non_spec_query_start_loc, + ), + ) + return attn_metadata + - def build( # type: ignore[override] +def _init_reorder_batch_threshold( + self, + reorder_batch_threshold: int | None = 1, + supports_spec_as_decode: bool = False, + supports_dcp_with_varlen: bool = False, +) -> None: + _ORIGINAL_INIT_THRESHOLD( self, - common_prefix_len: int, - common_attn_metadata: CommonAttentionMetadata, - num_accepted_tokens: torch.Tensor | None = None, - num_decode_draft_tokens_cpu: torch.Tensor | None = None, - fast_build: bool = False, - ) -> GDNAttentionMetadata: - m = common_attn_metadata - - query_start_loc = m.query_start_loc - query_start_loc_cpu = m.query_start_loc_cpu - context_lens_tensor = m.compute_num_computed_tokens() - nums_dict, batch_ptr, token_chunk_offset_ptr = None, None, None - block_table_tensor = mamba_get_block_table_tensor( - m.block_table_tensor, - m.seq_lens, - self.kv_cache_spec, - self.vllm_config.cache_config.mamba_cache_mode, - ) + reorder_batch_threshold, + supports_spec_as_decode, + supports_dcp_with_varlen, + ) + if self.reorder_batch_threshold != 1: + speculative_config = self.vllm_config.speculative_config + if ( + speculative_config is not None + and speculative_config.num_speculative_tokens is not None + and hasattr(speculative_config, "method") + and speculative_config.method == "dflash" + ): + self.reorder_batch_threshold = 1 + speculative_config.num_speculative_tokens - spec_sequence_masks_cpu: torch.Tensor | None = None - spec_sequence_indices: torch.Tensor | None = None - non_spec_sequence_indices: torch.Tensor | None = None - if not self.use_spec_decode or num_decode_draft_tokens_cpu is None: - spec_sequence_masks = None - num_spec_decodes = 0 - else: - num_reqs = num_decode_draft_tokens_cpu.numel() - spec_sequence_masks_cpu = self.spec_sequence_masks_cpu[:num_reqs] - torch.ge( - num_decode_draft_tokens_cpu, - 0, - out=spec_sequence_masks_cpu, - ) - num_spec_decodes = spec_sequence_masks_cpu.sum().item() - if num_spec_decodes == 0: - spec_sequence_masks = None - spec_sequence_masks_cpu = None - else: - spec_sequence_masks = self.spec_sequence_masks[:num_reqs] - spec_sequence_masks.copy_(spec_sequence_masks_cpu, non_blocking=True) - spec_sequence_indices, non_spec_sequence_indices = self._copy_sequence_indices_to_device( - spec_sequence_masks_cpu, - num_spec_decodes, - ) - - if spec_sequence_masks is None: - num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = split_decodes_and_prefills( - m, - decode_threshold=1, - ) - num_spec_decode_tokens = 0 - spec_token_indx = None - non_spec_token_indx = None - spec_state_indices_tensor = None - non_spec_state_indices_tensor = block_table_tensor[:, 0] - spec_query_start_loc = None - non_spec_query_start_loc = query_start_loc - non_spec_query_start_loc_cpu = query_start_loc_cpu - num_accepted_tokens = None - else: - query_lens = query_start_loc[1:] - query_start_loc[:-1] - query_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1] - assert spec_sequence_masks_cpu is not None - assert spec_sequence_indices is not None - assert non_spec_sequence_indices is not None - - non_spec_query_lens_cpu = query_lens_cpu[~spec_sequence_masks_cpu] - num_decodes = (non_spec_query_lens_cpu == 1).sum().item() - num_zero_len = (non_spec_query_lens_cpu == 0).sum().item() - num_prefills = non_spec_query_lens_cpu.size(0) - num_decodes - num_zero_len - num_decode_tokens = num_decodes - num_prefill_tokens = non_spec_query_lens_cpu.sum().item() - num_decode_tokens - num_spec_decode_tokens = query_lens_cpu.sum().item() - num_prefill_tokens - num_decode_tokens - - if num_decodes > 0 and num_spec_decodes > 0: - num_prefills += num_decodes - num_prefill_tokens += num_decode_tokens - num_decodes = 0 - num_decode_tokens = 0 - - if num_prefills == 0 and num_decodes == 0: - spec_token_size = min( - num_spec_decodes * (self.num_spec + 1), - query_start_loc_cpu[-1].item(), - ) - spec_token_indx = torch.arange( - spec_token_size, - dtype=torch.int32, - device=query_start_loc.device, - ) - non_spec_token_indx = torch.empty( - 0, - dtype=torch.int32, - device=query_start_loc.device, - ) - spec_state_indices_tensor = torch.index_select( - block_table_tensor[:, : self.num_spec + 1], - 0, - spec_sequence_indices, - ) - non_spec_state_indices_tensor = None - spec_query_start_loc = query_start_loc[: num_spec_decodes + 1] - non_spec_query_start_loc = None - non_spec_query_start_loc_cpu = None - else: - spec_token_masks = torch.repeat_interleave( - spec_sequence_masks, - query_lens, - output_size=query_start_loc_cpu[-1].item(), - ) - index = _stable_argsort_for_npu(spec_token_masks) - num_non_spec_tokens = num_prefill_tokens + num_decode_tokens - non_spec_token_indx = index[:num_non_spec_tokens] - spec_token_indx = index[num_non_spec_tokens:] - - spec_state_indices_tensor = torch.index_select( - block_table_tensor[:, : self.num_spec + 1], - 0, - spec_sequence_indices, - ) - non_spec_state_indices_tensor = torch.index_select( - block_table_tensor[:, 0], - 0, - non_spec_sequence_indices, - ) - spec_query_lens = torch.index_select( - query_lens, - 0, - spec_sequence_indices, - ) - non_spec_query_lens = torch.index_select( - query_lens, - 0, - non_spec_sequence_indices, - ) - - spec_query_start_loc = torch.zeros( - num_spec_decodes + 1, - dtype=torch.int32, - device=query_start_loc.device, - ) - torch.cumsum( - spec_query_lens, - dim=0, - out=spec_query_start_loc[1:], - ) - non_spec_query_start_loc = torch.zeros( - query_lens.size(0) - num_spec_decodes + 1, - dtype=torch.int32, - device=query_start_loc.device, - ) - torch.cumsum( - non_spec_query_lens, - dim=0, - out=non_spec_query_start_loc[1:], - ) - non_spec_query_start_loc_cpu = torch.zeros( - query_lens_cpu.size(0) - num_spec_decodes + 1, - dtype=torch.int32, - ) - torch.cumsum( - query_lens_cpu[~spec_sequence_masks_cpu], - dim=0, - out=non_spec_query_start_loc_cpu[1:], - ) - - assert num_accepted_tokens is not None - num_accepted_tokens = torch.index_select( - num_accepted_tokens, - 0, - spec_sequence_indices, - ) - - chunk_indices: torch.Tensor | None = None - chunk_offsets: torch.Tensor | None = None - if num_prefills > 0: - from vllm.model_executor.layers.fla.ops.index import ( - prepare_chunk_indices, - prepare_chunk_offsets, - ) - from vllm.model_executor.layers.fla.ops.utils import FLA_CHUNK_SIZE - - gpu_device = query_start_loc.device - chunk_indices = prepare_chunk_indices( - non_spec_query_start_loc_cpu, - FLA_CHUNK_SIZE, - ).to(device=gpu_device, non_blocking=True) - chunk_offsets = prepare_chunk_offsets( - non_spec_query_start_loc_cpu, - FLA_CHUNK_SIZE, - ).to(device=gpu_device, non_blocking=True) - - if num_prefills > 0: - has_initial_state = context_lens_tensor > 0 - if spec_sequence_masks_cpu is not None: - assert non_spec_sequence_indices is not None - has_initial_state = torch.index_select( - has_initial_state, - 0, - non_spec_sequence_indices, - ) - assert non_spec_query_start_loc_cpu is not None - nums_dict, batch_ptr, token_chunk_offset_ptr = compute_causal_conv1d_metadata( - non_spec_query_start_loc_cpu, - device=query_start_loc.device, - ) - else: - has_initial_state = None - - assert not (num_decodes > 0 and num_spec_decodes > 0), ( - f"num_decodes: {num_decodes}, num_spec_decodes: {num_spec_decodes}" - ) - batch_size = m.num_actual_tokens +def _patched_build_spec( + self, + attn_metadata: gdn_attn.GDNAttentionMetadata, + common_attn_metadata, + num_decode_draft_tokens_cpu: torch.Tensor | None = None, +): + assert attn_metadata.spec_sequence_masks is not None + _ensure_spec_causal_conv1d_host_meta_state( + self, + common_attn_metadata.query_start_loc.device, + ) + spec_query_start_loc_cpu = _build_spec_query_start_loc_cpu( + self, + common_attn_metadata, + num_decode_draft_tokens_cpu, + ) + assert spec_query_start_loc_cpu is not None + if attn_metadata.spec_query_start_loc is None: + raise RuntimeError("Expected attn_metadata.spec_query_start_loc for patched GDN speculative path.") + attn_metadata.spec_decode_fallback_meta = GDNSpecDecodeFallbackMeta( + spec_causal_conv1d=_build_spec_causal_conv1d_host_meta( + self, + attn_metadata, + spec_query_start_loc_cpu, + ), + ) + return attn_metadata - if ( - self.use_full_cuda_graph - and num_prefills == 0 - and num_decodes == 0 - and num_spec_decodes <= self.decode_cudagraph_max_bs - and num_spec_decode_tokens <= self.decode_cudagraph_max_bs - ): - assert spec_sequence_masks is not None - self.spec_state_indices_tensor[:num_spec_decodes].copy_( - spec_state_indices_tensor, - non_blocking=True, - ) - spec_state_indices_tensor = self.spec_state_indices_tensor[:batch_size] - spec_state_indices_tensor[num_spec_decodes:].fill_(NULL_BLOCK_ID) - - self.spec_sequence_masks[:num_spec_decodes].copy_( - spec_sequence_masks[:num_spec_decodes], - non_blocking=True, - ) - spec_sequence_masks = self.spec_sequence_masks[:batch_size] - spec_sequence_masks[num_spec_decodes:].fill_(False) - - assert non_spec_token_indx is not None and spec_token_indx is not None - self.non_spec_token_indx[: non_spec_token_indx.size(0)].copy_( - non_spec_token_indx, - non_blocking=True, - ) - non_spec_token_indx = self.non_spec_token_indx[: non_spec_token_indx.size(0)] - - self.spec_token_indx[: spec_token_indx.size(0)].copy_( - spec_token_indx, - non_blocking=True, - ) - spec_token_indx = self.spec_token_indx[: spec_token_indx.size(0)] - - self.spec_query_start_loc[: num_spec_decodes + 1].copy_( - spec_query_start_loc, - non_blocking=True, - ) - spec_num_query_tokens = spec_query_start_loc[-1] # type: ignore - spec_query_start_loc = self.spec_query_start_loc[: batch_size + 1] - spec_query_start_loc[num_spec_decodes + 1 :].fill_(spec_num_query_tokens) - - self.num_accepted_tokens[:num_spec_decodes].copy_( - num_accepted_tokens, - non_blocking=True, - ) - num_accepted_tokens = self.num_accepted_tokens[:batch_size] - num_accepted_tokens[num_spec_decodes:].fill_(1) - if ( - self.use_full_cuda_graph - and num_prefills == 0 - and num_spec_decodes == 0 - and num_decodes <= self.decode_cudagraph_max_bs - ): - self.non_spec_state_indices_tensor[batch_size:].fill_(NULL_BLOCK_ID) - self.non_spec_state_indices_tensor[:num_decodes].copy_( - non_spec_state_indices_tensor, - non_blocking=True, - ) - non_spec_state_indices_tensor = self.non_spec_state_indices_tensor[:batch_size] - non_spec_state_indices_tensor[num_decodes:].fill_(NULL_BLOCK_ID) - - self.non_spec_query_start_loc[: num_decodes + 1].copy_( - non_spec_query_start_loc, - non_blocking=True, - ) - non_spec_num_query_tokens = non_spec_query_start_loc[-1] - non_spec_query_start_loc = self.non_spec_query_start_loc[: batch_size + 1] - non_spec_query_start_loc[num_decodes + 1 :].fill_(non_spec_num_query_tokens) - - attn_metadata = GDNAttentionMetadata( - num_prefills=num_prefills, - num_prefill_tokens=num_prefill_tokens, - num_decodes=num_decodes, - num_decode_tokens=num_decode_tokens, - num_spec_decodes=num_spec_decodes, - num_spec_decode_tokens=num_spec_decode_tokens, - num_actual_tokens=m.num_actual_tokens, - has_initial_state=has_initial_state, - chunk_indices=chunk_indices, - chunk_offsets=chunk_offsets, - spec_query_start_loc=spec_query_start_loc, - non_spec_query_start_loc=non_spec_query_start_loc, - spec_state_indices_tensor=spec_state_indices_tensor, - non_spec_state_indices_tensor=non_spec_state_indices_tensor, - spec_sequence_masks=spec_sequence_masks, - spec_token_indx=spec_token_indx, - non_spec_token_indx=non_spec_token_indx, - num_accepted_tokens=num_accepted_tokens, - nums_dict=nums_dict, - batch_ptr=batch_ptr, - token_chunk_offset_ptr=token_chunk_offset_ptr, - ) - attn_metadata = self._attach_non_spec_prefill_fallback_meta( +def _patched_build_decode( + self, + attn_metadata: gdn_attn.GDNAttentionMetadata, + common_attn_metadata, + num_decode_draft_tokens_cpu: torch.Tensor | None = None, +): + assert attn_metadata.num_decodes > 0 + _ensure_causal_conv1d_host_meta_state( + self, + common_attn_metadata.query_start_loc.device, + ) + non_spec_query_start_loc_cpu = _build_non_spec_query_start_loc_cpu( + self, + common_attn_metadata, + num_decode_draft_tokens_cpu, + ) + if non_spec_query_start_loc_cpu is None: + raise RuntimeError("Expected non-spec query_start_loc_cpu for patched GDN non-spec decode path.") + attn_metadata.non_spec_decode_fallback_meta = GDNDecodeFallbackMeta( + causal_conv1d=_build_non_spec_decode_causal_conv1d_host_meta( + self, attn_metadata, - common_attn_metadata, non_spec_query_start_loc_cpu, - ) - attn_metadata = self._attach_spec_decode_fallback_meta( - attn_metadata, - common_attn_metadata, - num_decode_draft_tokens_cpu, - ) - return self._attach_non_spec_decode_fallback_meta( - attn_metadata, - common_attn_metadata, - num_decode_draft_tokens_cpu, - ) + ), + ) + return attn_metadata -class AscendGDNAttentionBackend(GDNAttentionBackend): - @staticmethod - def get_builder_cls() -> type[AscendGDNAttentionMetadataBuilder]: - return AscendGDNAttentionMetadataBuilder +if not _IS_PATCHED and not is_310p(): + gdn_attn.GDNChunkedPrefillMetadata = GDNChunkedPrefillMetadata + gdn_attn.GDNCausalConv1dHostMetadata = GDNCausalConv1dHostMetadata + gdn_attn.GDNPrefillFallbackMeta = GDNPrefillFallbackMeta + gdn_attn.GDNAttentionMetadataBuilder.build = _patched_build + gdn_attn.GDNAttentionMetadataBuilder._init_reorder_batch_threshold = _init_reorder_batch_threshold + _IS_PATCHED = True diff --git a/vllm_ascend/patch/worker/patch_gqa_c8.py b/vllm_ascend/patch/worker/patch_gqa_c8.py index 81bb09efb..0539fc4be 100644 --- a/vllm_ascend/patch/worker/patch_gqa_c8.py +++ b/vllm_ascend/patch/worker/patch_gqa_c8.py @@ -21,14 +21,12 @@ import torch from vllm.model_executor.model_loader.weight_utils import default_weight_loader from vllm.model_executor.models.glm4_moe import Glm4MoeForCausalLM -from vllm.model_executor.models.minimax_m2 import MiniMaxM2ForCausalLM from vllm.model_executor.models.qwen3 import Qwen3ForCausalLM logger = logging.getLogger(__name__) _orig_qwen3_causal_lm_load_weights = Qwen3ForCausalLM.load_weights _orig_Glm4_causal_lm_load_weights = Glm4MoeForCausalLM.load_weights -_orig_Minimax_m2_causal_lm_load_weights = MiniMaxM2ForCausalLM.load_weights def _patched_causal_lm_load_weights( @@ -73,6 +71,3 @@ def _intercept_c8_scales( Glm4MoeForCausalLM.load_weights = lambda self, weights: _patched_causal_lm_load_weights( self, weights, _orig_Glm4_causal_lm_load_weights ) -MiniMaxM2ForCausalLM.load_weights = lambda self, weights: _patched_causal_lm_load_weights( - self, weights, _orig_Minimax_m2_causal_lm_load_weights -) diff --git a/vllm_ascend/patch/worker/patch_idex_310.py b/vllm_ascend/patch/worker/patch_idex_310.py index aded3bb62..9c1202f4c 100644 --- a/vllm_ascend/patch/worker/patch_idex_310.py +++ b/vllm_ascend/patch/worker/patch_idex_310.py @@ -1,33 +1,30 @@ import vllm -from vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn import QwenGatedDeltaNetAttention -import vllm_ascend.ops.gdn as gdn_ops -from vllm_ascend._310p.ops.fla.gdn_310 import ( - AscendGatedDeltaNetAttention310, - update_conv1d_graph_params_310p, -) +from vllm_ascend._310p.ops.fla.gdn_310 import AscendGatedDeltaNetAttention310 from vllm_ascend._310p.ops.fla.idex import ( prepare_chunk_indices_310, prepare_chunk_offsets_310, ) -from vllm_ascend._310p.spec_decode.llm_base_proposer_310 import AscendSpecDecodeBaseProposer310 -from vllm_ascend.spec_decode.llm_base_proposer import AscendSpecDecodeBaseProposer +from vllm_ascend.utils import vllm_version_is vllm.model_executor.layers.fla.ops.index.prepare_chunk_indices = prepare_chunk_indices_310 vllm.model_executor.layers.fla.ops.index.prepare_chunk_offsets = prepare_chunk_offsets_310 -# 310P GDN causal conv1d uses buffer_replay; keep shared gdn.py unchanged. -gdn_ops.update_conv1d_graph_params = update_conv1d_graph_params_310p - -# 310P: skip NPU index_fill_ when there are no discarded requests. -AscendSpecDecodeBaseProposer.prepare_next_token_ids_padded = ( # type: ignore[method-assign] - AscendSpecDecodeBaseProposer310.prepare_next_token_ids_padded -) - # Patch _warmup_prefill_kernels to no-op on 310P: triton.next_power_of_2 does # not exist in the triton version used on 310P CI, and NPU does not use these # CUDA warmup kernel anyway. -QwenGatedDeltaNetAttention._warmup_prefill_kernels = lambda self, qkv_or_qkvz, v_dim: None # type: ignore[method-assign] -QwenGatedDeltaNetAttention._forward_core = AscendGatedDeltaNetAttention310._forward_core -QwenGatedDeltaNetAttention.get_state_dtype = AscendGatedDeltaNetAttention310.get_state_dtype +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers.mamba.gdn_linear_attn import ( # type: ignore[import-not-found] + GatedDeltaNetAttention, + ) + + GatedDeltaNetAttention._warmup_prefill_kernels = lambda self, mixed_qkv: None # type: ignore[method-assign] + GatedDeltaNetAttention._forward_core = AscendGatedDeltaNetAttention310._forward_core + GatedDeltaNetAttention.get_state_dtype = AscendGatedDeltaNetAttention310.get_state_dtype +else: + from vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn import QwenGatedDeltaNetAttention + + QwenGatedDeltaNetAttention._warmup_prefill_kernels = lambda self, qkv_or_qkvz, v_dim: None # type: ignore[method-assign] + QwenGatedDeltaNetAttention._forward_core = AscendGatedDeltaNetAttention310._forward_core + QwenGatedDeltaNetAttention.get_state_dtype = AscendGatedDeltaNetAttention310.get_state_dtype diff --git a/vllm_ascend/patch/worker/patch_mamba_utils.py b/vllm_ascend/patch/worker/patch_mamba_utils.py index f4dfa9d7b..4c933b585 100644 --- a/vllm_ascend/patch/worker/patch_mamba_utils.py +++ b/vllm_ascend/patch/worker/patch_mamba_utils.py @@ -1,6 +1,5 @@ # mypy: ignore-errors -import itertools from typing import Any import torch @@ -141,11 +140,7 @@ def preprocess_mamba( # TODO(Chen): we need to optimize this function a lot # assert cache_config.enable_prefix_caching block_size = mamba_spec.block_size - finished_req_ids = scheduler_output.finished_req_ids - preempted_req_ids = scheduler_output.preempted_req_ids or set() - resumed_req_ids = scheduler_output.scheduled_cached_reqs.resumed_req_ids - for req_id in itertools.chain(finished_req_ids, preempted_req_ids, resumed_req_ids): - mamba_state_idx.pop(req_id, None) + mamba_utils.cleanup_mamba_state_idx(scheduler_output, mamba_state_idx) copy_bufs.offset = 0 for i, req_id in enumerate(input_batch.req_ids): diff --git a/vllm_ascend/patch/worker/patch_minimax_m2.py b/vllm_ascend/patch/worker/patch_minimax_m2.py index 31f3388bb..a2794437b 100644 --- a/vllm_ascend/patch/worker/patch_minimax_m2.py +++ b/vllm_ascend/patch/worker/patch_minimax_m2.py @@ -25,6 +25,7 @@ get_tensor_model_parallel_rank, get_tensor_model_parallel_world_size, ) +from vllm.model_executor.layers.mamba.linear_attn import MiniMaxText01RMSNormTP from vllm.model_executor.models.minimax_m2 import ( MiniMaxM2Attention, MiniMaxM2ForCausalLM, @@ -35,16 +36,6 @@ from vllm.sequence import IntermediateTensors from vllm_ascend.ops.rotary_embedding import get_cos_and_sin_slice -from vllm_ascend.utils import vllm_version_is - -if vllm_version_is("0.22.1"): - from vllm.model_executor.layers.mamba.linear_attn import ( # type: ignore[import-not-found] - MiniMaxText01RMSNormTP, - ) -else: - from vllm.model_executor.layers.minimax_rms_norm import ( # type: ignore[import-not-found] - MiniMaxText01RMSNormTP, - ) FP8_DTYPES = tuple( getattr(torch, dtype_name) diff --git a/vllm_ascend/patch/worker/patch_minimax_m2_linear_attn.py b/vllm_ascend/patch/worker/patch_minimax_m2_linear_attn.py index dd8c76997..06c7ddae8 100644 --- a/vllm_ascend/patch/worker/patch_minimax_m2_linear_attn.py +++ b/vllm_ascend/patch/worker/patch_minimax_m2_linear_attn.py @@ -28,19 +28,9 @@ tensor_model_parallel_all_reduce, ) from vllm.model_executor.custom_op import CustomOp +from vllm.model_executor.layers.mamba.linear_attn import MiniMaxText01RMSNormTP from vllm.platforms import current_platform -from vllm_ascend.utils import vllm_version_is - -if vllm_version_is("0.22.1"): - from vllm.model_executor.layers.mamba.linear_attn import ( # type: ignore[import-not-found] - MiniMaxText01RMSNormTP, - ) -else: - from vllm.model_executor.layers.minimax_rms_norm import ( # type: ignore[import-not-found] - MiniMaxText01RMSNormTP, - ) - logger = logging.getLogger(__name__) _ORIG_QK_METHOD_NAME: str | None = None diff --git a/vllm_ascend/patch/worker/patch_qwen3_5.py b/vllm_ascend/patch/worker/patch_qwen3_5.py index e6a1f36da..4a1310484 100644 --- a/vllm_ascend/patch/worker/patch_qwen3_5.py +++ b/vllm_ascend/patch/worker/patch_qwen3_5.py @@ -19,23 +19,21 @@ import torch from vllm.distributed import get_tensor_model_parallel_world_size -from vllm.distributed.parallel_state import get_pp_group -from vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn import QwenGatedDeltaNetAttention as _GDNBaseCls from vllm.model_executor.models.qwen3_5 import Qwen3_5DecoderLayer - -try: - from vllm.model_executor.models.qwen3_5_mtp import Qwen3_5MultiTokenPredictor - from vllm.sequence import IntermediateTensors -except ImportError: - Qwen3_5MultiTokenPredictor = None - IntermediateTensors = None from vllm.model_executor.models.qwen3_next import Qwen3NextAttention from vllm_ascend.ascend_forward_context import _EXTRA_CTX from vllm_ascend.ops.gdn import AscendGatedDeltaNetAttention -from vllm_ascend.utils import is_310p +from vllm_ascend.utils import is_310p, vllm_version_is + +if vllm_version_is("0.20.2"): + from vllm.model_executor.layers.mamba.gdn_linear_attn import GatedDeltaNetAttention as _GDNBaseCls + + _GDN_PATCH_TARGET = _GDNBaseCls +else: + from vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn import QwenGatedDeltaNetAttention as _GDNBaseCls -_GDN_PATCH_TARGET = _GDNBaseCls + _GDN_PATCH_TARGET = _GDNBaseCls class AscendQwen3NextAttention(Qwen3NextAttention): @@ -148,55 +146,10 @@ def forward( return hidden_states, residual -if Qwen3_5MultiTokenPredictor is not None: - - def qwen3_5_mtp_forward( - self, - input_ids: torch.Tensor, - positions: torch.Tensor, - hidden_states: torch.Tensor, - intermediate_tensors: IntermediateTensors | None = None, - inputs_embeds: torch.Tensor | None = None, - spec_step_idx: int = 0, - ) -> torch.Tensor: - # Backport upstream Qwen3.5 MTP behavior: the local drafter runs on the - # last PP stage and should always combine token embeddings with the - # target hidden states instead of consuming PP intermediate tensors. - if inputs_embeds is None: - inputs_embeds = self.embed_input_ids(input_ids) - assert hidden_states.shape[-1] == inputs_embeds.shape[-1] - inputs_embeds = self.pre_fc_norm_embedding(inputs_embeds) - hidden_states = self.pre_fc_norm_hidden(hidden_states) - hidden_states = torch.cat([inputs_embeds, hidden_states], dim=-1) - hidden_states = self.fc(hidden_states) - residual = None - - current_step_idx = spec_step_idx % self.num_mtp_layers - hidden_states, residual = self.layers[current_step_idx]( - positions=positions, - hidden_states=hidden_states, - residual=residual, - ) - - if not get_pp_group().is_last_rank: - return IntermediateTensors( - { - "hidden_states": hidden_states, - "residual": residual, - } - ) - - hidden_states, _ = self.norm(hidden_states, residual) - return hidden_states - - Qwen3_5MultiTokenPredictor.forward = qwen3_5_mtp_forward - - Qwen3_5DecoderLayer.forward = AscendQwen3_5DecoderLayer.forward Qwen3NextAttention.forward = AscendQwen3NextAttention.forward _GDN_PATCH_TARGET._split_ba_for_tp = AscendGatedDeltaNetAttention._split_ba_for_tp _GDN_PATCH_TARGET.get_state_shape = AscendGatedDeltaNetAttention.get_state_shape -_GDN_PATCH_TARGET.get_attn_backend = AscendGatedDeltaNetAttention.get_attn_backend if is_310p(): from vllm_ascend._310p.ops.fla.gdn_310 import AscendGatedDeltaNetAttention310 @@ -206,4 +159,7 @@ def qwen3_5_mtp_forward( else: _GDN_PATCH_TARGET.forward = AscendGatedDeltaNetAttention.forward _GDN_PATCH_TARGET._forward_core = AscendGatedDeltaNetAttention._forward_core - _GDN_PATCH_TARGET._warmup_prefill_kernels = AscendGatedDeltaNetAttention._warmup_prefill_kernels + if vllm_version_is("0.20.2"): + _GDN_PATCH_TARGET._warmup_prefill_kernels = AscendGatedDeltaNetAttention._warmup_prefill_kernels_v0202 + else: + _GDN_PATCH_TARGET._warmup_prefill_kernels = AscendGatedDeltaNetAttention._warmup_prefill_kernels diff --git a/vllm_ascend/patch/worker/patch_triton.py b/vllm_ascend/patch/worker/patch_triton.py index 1162c8907..1e3c371e8 100644 --- a/vllm_ascend/patch/worker/patch_triton.py +++ b/vllm_ascend/patch/worker/patch_triton.py @@ -8,8 +8,10 @@ from vllm_ascend.ops.triton.fla.layernorm_guard import LayerNormFn from vllm_ascend.ops.triton.fla.sigmoid_gating import fused_recurrent_gated_delta_rule_fwd_kernel from vllm_ascend.ops.triton.mamba.causal_conv1d import causal_conv1d_fn, causal_conv1d_update_npu +from vllm_ascend.utils import vllm_version_is -triton.next_power_of_2 = next_power_of_2 +if not vllm_version_is("0.20.2"): + triton.next_power_of_2 = next_power_of_2 vllm.model_executor.layers.mamba.ops.causal_conv1d.causal_conv1d_update = causal_conv1d_update_npu vllm.model_executor.layers.mamba.ops.causal_conv1d.causal_conv1d_fn = causal_conv1d_fn @@ -22,8 +24,9 @@ # On NPU platforms without an active Triton backend (e.g. 310P), replace the # Triton-based fused_post_conv_prep with a pure-PyTorch fallback so that # qwen_gdn_linear_attn's from-import picks up the replacement before model -# load. -if not HAS_TRITON: +# load. fused_post_conv_prep was introduced alongside Qwen3-Next GDN support +# and does not exist in v0.20.2. +if not HAS_TRITON and not vllm_version_is("0.20.2"): import torch import torch.nn.functional as _F diff --git a/vllm_ascend/patch/worker/patch_v2/patch_attn_utils.py b/vllm_ascend/patch/worker/patch_v2/patch_attn_utils.py index 072cbc22a..819304777 100644 --- a/vllm_ascend/patch/worker/patch_v2/patch_attn_utils.py +++ b/vllm_ascend/patch/worker/patch_v2/patch_attn_utils.py @@ -1,6 +1,15 @@ import vllm -from vllm_ascend.worker.v2.attn_utils import _allocate_kv_cache, _reshape_kv_cache_v2 +from vllm_ascend.utils import vllm_version_is +from vllm_ascend.worker.v2.attn_utils import _allocate_kv_cache, _reshape_kv_cache, _reshape_kv_cache_v2 -vllm.v1.worker.gpu.attn_utils._allocate_kv_cache = _allocate_kv_cache -vllm.v1.worker.gpu.attn_utils._reshape_kv_cache = _reshape_kv_cache_v2 +if vllm_version_is("0.20.2"): + + def _allocate_kv_cache_compat(kv_cache_config, device): + return _allocate_kv_cache(kv_cache_config, {}, device) + + vllm.v1.worker.gpu.attn_utils._allocate_kv_cache = _allocate_kv_cache_compat + vllm.v1.worker.gpu.attn_utils._reshape_kv_cache = _reshape_kv_cache +else: + vllm.v1.worker.gpu.attn_utils._allocate_kv_cache = _allocate_kv_cache + vllm.v1.worker.gpu.attn_utils._reshape_kv_cache = _reshape_kv_cache_v2 diff --git a/vllm_ascend/platform.py b/vllm_ascend/platform.py index 52f902d42..184bf9f07 100644 --- a/vllm_ascend/platform.py +++ b/vllm_ascend/platform.py @@ -19,7 +19,6 @@ import math import os -import subprocess from importlib import import_module, util from typing import TYPE_CHECKING, Any from uuid import uuid4 @@ -49,6 +48,7 @@ get_ascend_device_type, is_moe_model, refresh_block_size, + update_aclgraph_sizes, update_cudagraph_capture_sizes, is_310p, enable_sp, @@ -71,36 +71,6 @@ FlexibleArgumentParser = None _CUSTOM_OP_REGISTERED = False -# Delete after the driver is released; temporarily hard-coded to 4 -MAX_CAPTURE_SIZES_FOR_950 = 4 - - -def _get_npu_smi_field(lines: list[str], key: str) -> str | None: - for line in lines: - normalized = " ".join(line.split()) - if normalized.startswith(f"{key} :"): - return normalized.split(":", 1)[1].strip() - return None - - -def _get_npu_smi_hbm_capacity_mb(device_id: int) -> int | None: - try: - output = subprocess.check_output( - ["npu-smi", "info", "-t", "memory", "-i", str(device_id)], - stderr=subprocess.DEVNULL, - text=True, - ) - except (OSError, subprocess.CalledProcessError): - return None - - value = _get_npu_smi_field(output.splitlines(), "HBM Capacity(MB)") - if value is None: - return None - - try: - return int(value) - except ValueError: - return None def config_deprecated_logging(): @@ -133,25 +103,6 @@ def one_line_formatwarning(message, category, filename, lineno, line=None): warnings_logger.propagate = False -def prune_capture_sizes_for_950(vllm_config): - original_sizes = vllm_config.compilation_config.cudagraph_capture_sizes - if not original_sizes: - return - if len(original_sizes) <= MAX_CAPTURE_SIZES_FOR_950: - return - step = (len(original_sizes) - 1) / (MAX_CAPTURE_SIZES_FOR_950 - 1) - indices = [round(i * step) for i in range(MAX_CAPTURE_SIZES_FOR_950)] - indices[0], indices[-1] = 0, len(original_sizes) - 1 - sampled_sizes = [original_sizes[i] for i in indices] - update_cudagraph_capture_sizes(vllm_config, sampled_sizes) - logger.warning( - "Adjusted ACL graph batch sizes for model: %d → %d sizes due to HDK incompatibility" - "and this warning will be cleared soon.", - len(original_sizes), - MAX_CAPTURE_SIZES_FOR_950, - ) - - class NPUPlatform(Platform): _enum = PlatformEnum.OOT device_name: str = "npu" @@ -174,14 +125,6 @@ class NPUPlatform(Platform): def is_sleep_mode_available(self) -> bool: return True - def is_cumem_allocator_available(self) -> bool: - # vLLM main gates sleep mode on the platform reporting a - # usable cumem allocator. NPU provides its own ``CaMemAllocator`` - # (vllm_ascend.device_allocator.camem), so report availability here. - # ModelConfig validation runs before custom-op init, so avoid importing - # the extension and just declare support. - return True - @property def pass_key(self) -> str: """ @@ -299,20 +242,6 @@ def get_device_uuid(cls, device_id: int = 0) -> str: return device_props.uuid @classmethod - def get_device_total_memory(cls, device_id: int = 0) -> int: - """ - Return total memory of the device in bytes. - """ - hbm_capacity_mb = _get_npu_smi_hbm_capacity_mb(device_id) - if hbm_capacity_mb is not None: - return hbm_capacity_mb * 1024 * 1024 - - device_props = torch.npu.get_device_properties(device_id) - total_memory = getattr(device_props, "total_memory", None) - if total_memory is not None: - return int(total_memory) - raise RuntimeError(f"Unable to determine total memory for device {device_id}.") - def num_compute_units(cls, device_id: int = 0) -> int: """Return the number of Cube Cores on the NPU device. This is the NPU equivalent of CUDA's ``multi_processor_count`` @@ -378,18 +307,6 @@ def _validate_layer_sharding_config(cls, vllm_config: VllmConfig) -> None: if kv_transfer_config is None or kv_transfer_config.kv_role != "kv_producer": raise ValueError("additional_config.layer_sharding can only be enabled in PD-disaggregated's P node.") - @classmethod - def _validate_parallel_config(cls, vllm_config: VllmConfig) -> None: - parallel_config = vllm_config.parallel_config - if parallel_config.data_parallel_size > 1 and parallel_config.prefill_context_parallel_size > 1: - raise ValueError( - "PCP (Prefill Context Parallelism) and DP (Data Parallelism) " - "cannot be enabled simultaneously in the current version of vLLM Ascend. " - f"Got data_parallel_size={parallel_config.data_parallel_size} and " - f"prefill_context_parallel_size={parallel_config.prefill_context_parallel_size}. " - "Please set either --data-parallel-size 1 or --prefill-context-parallel-size 1." - ) - @classmethod def _validate_draft_decode_context_parallel_config( cls, @@ -451,68 +368,21 @@ def _validate_draft_decode_context_parallel_config( f"({decode_context_parallel_size})." ) - @staticmethod - def _is_mtp_speculative_config(speculative_config: Any | None) -> bool: - if speculative_config is None: - return False - - method = getattr(speculative_config, "method", None) - return method is not None and "mtp" in str(method).lower() - - @classmethod - def _validate_pd_pp_mtp_config(cls, vllm_config: VllmConfig) -> None: - speculative_config = getattr(vllm_config, "speculative_config", None) - if not cls._is_mtp_speculative_config(speculative_config): - return - - parallel_config = vllm_config.parallel_config - if getattr(parallel_config, "pipeline_parallel_size", 1) <= 1: - return - - kv_transfer_config = getattr(vllm_config, "kv_transfer_config", None) - if kv_transfer_config is not None and getattr(kv_transfer_config, "kv_role", None) == "kv_producer": - return - - raise ValueError( - "PP+MTP is only supported on PD-disaggregated P nodes " - "(kv_role='kv_producer'). D nodes must use " - "pipeline_parallel_size=1 and may combine data parallelism with MTP." - ) - @classmethod def check_and_update_config(cls, vllm_config: VllmConfig) -> None: from vllm_ascend.quantization.utils import maybe_auto_detect_quantization - device_config = getattr(vllm_config, "device_config", None) - if device_config is not None and getattr(device_config, "device_type", cls.device_type) != cls.device_type: - logger.debug( - "Skipping Ascend-specific config updates for device type %s.", - device_config.device_type, - ) - return - - if vllm_config.model_config is None: - logger.warning("Model config is missing. Skipping Ascend-specific config updates.") - return - - maybe_auto_detect_quantization(vllm_config) + if vllm_config.model_config is not None: + maybe_auto_detect_quantization(vllm_config) cls._validate_layer_sharding_config(vllm_config) cls._validate_draft_decode_context_parallel_config(vllm_config) - cls._validate_parallel_config(vllm_config) - cls._validate_pd_pp_mtp_config(vllm_config) # initialize ascend config from vllm additional_config cls._fix_incompatible_config(vllm_config) ascend_config = init_ascend_config(vllm_config) - from vllm_ascend.logger import configure_ascend_file_logging - from vllm_ascend.logger import configure_ascend_logging - - configure_ascend_file_logging() - configure_ascend_logging() - if vllm_config.kv_transfer_config is not None: check_kv_extra_config(vllm_config) if not getattr(vllm_config.kv_transfer_config, "_engine_id_patched", False): @@ -543,7 +413,13 @@ def check_and_update_config(cls, vllm_config: VllmConfig) -> None: vars(ascend_fusion_config) if not isinstance(ascend_fusion_config, dict) else ascend_fusion_config ) - enforce_eager = getattr(model_config, "enforce_eager", False) + if model_config is None: + logger.info( + "Model config is missing. This may indicate that we are running a test case. context: model_config=None" + ) + enforce_eager = False + else: + enforce_eager = getattr(model_config, "enforce_eager", False) from vllm.config.compilation import CUDAGraphMode @@ -649,9 +525,7 @@ def check_and_update_config(cls, vllm_config: VllmConfig) -> None: "vllm::dsa_forward", ] ) - # TODO(2026/7/15): Delete the reduced gear after the new driver is released. - if get_ascend_device_type() == AscendDeviceType.A5: - prune_capture_sizes_for_950(vllm_config) + update_aclgraph_sizes(vllm_config) ascend_config.ascend_compilation_config.enable_npugraph_ex = False elif compilation_config.cudagraph_mode.has_full_cudagraphs(): # We don't want to have our FX graph split for the sake of static kernel feature, diff --git a/vllm_ascend/quantization/compressed_tensors_config.py b/vllm_ascend/quantization/compressed_tensors_config.py index f6be543a6..c69f3f0c3 100644 --- a/vllm_ascend/quantization/compressed_tensors_config.py +++ b/vllm_ascend/quantization/compressed_tensors_config.py @@ -353,10 +353,7 @@ def _detect_quant_type( return "W8A8" if self._is_dynamic_token_w8a8(weight_quant, input_quant): - if weight_quant.type == QuantizationType.FLOAT and input_quant.type == QuantizationType.FLOAT: - return "W8A8FP8_DYNAMIC" - else: - return "W8A8_DYNAMIC" + return "W8A8_DYNAMIC" if self._is_dynamic_token_w4a8(weight_quant, input_quant): return "W4A8_DYNAMIC" diff --git a/vllm_ascend/quantization/methods/__init__.py b/vllm_ascend/quantization/methods/__init__.py index d5f77d204..95eace095 100644 --- a/vllm_ascend/quantization/methods/__init__.py +++ b/vllm_ascend/quantization/methods/__init__.py @@ -49,7 +49,6 @@ from .w8a8_mxfp8 import AscendW8A8MXFP8DynamicLinearMethod from .w8a8_pdmix import AscendW8A8PDMixFusedMoeMethod, AscendW8A8PDMixLinearMethod from .w8a8_static import AscendW8A8LinearMethod -from .w8a8fp8_dynamic import AscendW8A8FP8DynamicFusedMoEMethod, AscendW8A8FP8DynamicLinearMethod from .w8a16 import AscendW8A16LinearMethod @@ -81,8 +80,6 @@ def is_mx_quant_type(instance: Any) -> bool: "AscendW8A8LinearMethod", "AscendW8A8DynamicLinearMethod", "AscendW8A8DynamicFusedMoEMethod", - "AscendW8A8FP8DynamicLinearMethod", - "AscendW8A8FP8DynamicFusedMoEMethod", "AscendW8A8MXFP8DynamicLinearMethod", "AscendW8A8PDMixLinearMethod", "AscendW8A8PDMixFusedMoeMethod", diff --git a/vllm_ascend/quantization/methods/w4a8_mxfp4.py b/vllm_ascend/quantization/methods/w4a8_mxfp4.py index 63985b94d..7189dad13 100644 --- a/vllm_ascend/quantization/methods/w4a8_mxfp4.py +++ b/vllm_ascend/quantization/methods/w4a8_mxfp4.py @@ -65,12 +65,9 @@ def apply( bias: torch.Tensor | None = None, tp_rank: int | None = 0, ) -> torch.Tensor: - if isinstance(x, tuple): - quantized_x, dynamic_scale = x - output_dtype = torch.bfloat16 - else: - quantized_x, dynamic_scale = torch_npu.npu_dynamic_mx_quant(x, dst_type=torch.float8_e4m3fn) - output_dtype = x.dtype + quantized_x, dynamic_scale = torch_npu.npu_dynamic_mx_quant(x, dst_type=torch.float8_e4m3fn) + + output_dtype = x.dtype if not isinstance(x, tuple) else x[0].dtype output = torch_npu.npu_quant_matmul( quantized_x, diff --git a/vllm_ascend/quantization/methods/w8a8_dynamic.py b/vllm_ascend/quantization/methods/w8a8_dynamic.py index aa15be7d3..3e4dc2e69 100644 --- a/vllm_ascend/quantization/methods/w8a8_dynamic.py +++ b/vllm_ascend/quantization/methods/w8a8_dynamic.py @@ -53,8 +53,6 @@ class AscendW8A8DynamicLinearMethod(AscendLinearScheme): and per-channel quantization for weights. """ - act_quant_type: torch.dtype = torch.int8 - def __init__(self): pass @@ -79,7 +77,7 @@ def apply( bias: torch.Tensor | None = None, tp_rank: int | None = 0, ) -> torch.Tensor: - quantized_x, pertoken_scale = torch_npu.npu_dynamic_quant(x, dst_type=self.act_quant_type) + quantized_x, pertoken_scale = torch_npu.npu_dynamic_quant(x) need_unsqz = False if pertoken_scale.dim() == 2: need_unsqz = True @@ -117,7 +115,7 @@ def apply( layer.weight, layer.weight_scale, pertoken_scale=pertoken_scale, - bias=bias if self.act_quant_type == torch.int8 else None, + bias=bias, output_dtype=x.dtype, ) if need_unsqz: @@ -145,8 +143,7 @@ def process_weights_after_loading(self, layer): del layer.weight_offset else: # cast quantized weight tensors in NZ format for higher inference speed - if self.act_quant_type == torch.int8: - layer.weight.data = maybe_trans_nz(layer.weight.data) + layer.weight.data = maybe_trans_nz(layer.weight.data) layer.weight_scale.data = layer.weight_scale.data.flatten() layer.weight_scale_fp32 = layer.weight_scale.data.to(torch.float32) layer.weight_offset.data = layer.weight_offset.data.flatten() @@ -355,9 +352,8 @@ def process_weights_after_loading(self, layer): layer.w2_weight.data = layer.w2_weight.data.transpose(1, 2).contiguous() # TODO(zzzzwwjj): Currently, `torch_npu.npu_grouped_matmul_swiglu_quant` # can only support weight nz. - if self.quant_type == QuantType.W8A8: - layer.w13_weight.data = torch_npu.npu_format_cast(layer.w13_weight.data, ACL_FORMAT_FRACTAL_NZ) - layer.w2_weight.data = torch_npu.npu_format_cast(layer.w2_weight.data, ACL_FORMAT_FRACTAL_NZ) + layer.w13_weight.data = torch_npu.npu_format_cast(layer.w13_weight.data, ACL_FORMAT_FRACTAL_NZ) + layer.w2_weight.data = torch_npu.npu_format_cast(layer.w2_weight.data, ACL_FORMAT_FRACTAL_NZ) layer.w13_weight_scale.data = layer.w13_weight_scale.data.view(layer.w13_weight_scale.data.shape[0], -1) layer.w13_weight_scale_fp32 = layer.w13_weight_scale.data.to(torch.float32) layer.w13_weight_offset.data = layer.w13_weight_offset.data.view(layer.w13_weight_offset.data.shape[0], -1) diff --git a/vllm_ascend/quantization/methods/w8a8_mxfp8.py b/vllm_ascend/quantization/methods/w8a8_mxfp8.py index 9eed9a6d3..e4895c85f 100644 --- a/vllm_ascend/quantization/methods/w8a8_mxfp8.py +++ b/vllm_ascend/quantization/methods/w8a8_mxfp8.py @@ -70,22 +70,16 @@ def get_pergroup_param( def apply( self, layer: torch.nn.Module, - x: torch.Tensor | tuple[torch.Tensor, torch.Tensor], + x: torch.Tensor, bias: torch.Tensor | None = None, tp_rank: int | None = 0, ) -> torch.Tensor: - if isinstance(x, tuple): - quantized_x, pertoken_scale = x - original_shape = quantized_x.shape - output_dtype = torch.bfloat16 - else: - # reshape x for Qwen VL models - original_shape = x.shape - if x.dim() > 2: - x = x.view(-1, x.shape[-1]) - quantized_x, pertoken_scale = torch_npu.npu_dynamic_mx_quant(x, dst_type=torch.float8_e4m3fn) - output_dtype = x.dtype - + # reshape x for Qwen VL models + original_shape = x.shape + if x.dim() > 2: + x = x.view(-1, x.shape[-1]) + quantized_x, dynamic_scale = torch_npu.npu_dynamic_mx_quant(x, dst_type=torch.float8_e4m3fn) + output_dtype = x.dtype if bias is not None and bias.dtype != torch.float32: bias = bias.to(torch.float32) @@ -94,7 +88,7 @@ def apply( layer.weight, layer.weight_scale, scale_dtype=FLOAT8_E8M0FNU_DTYPE, - pertoken_scale=pertoken_scale, + pertoken_scale=dynamic_scale, pertoken_scale_dtype=FLOAT8_E8M0FNU_DTYPE, bias=bias, output_dtype=output_dtype, diff --git a/vllm_ascend/quantization/methods/w8a8fp8_dynamic.py b/vllm_ascend/quantization/methods/w8a8fp8_dynamic.py deleted file mode 100644 index 5b7e829b5..000000000 --- a/vllm_ascend/quantization/methods/w8a8fp8_dynamic.py +++ /dev/null @@ -1,102 +0,0 @@ -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# This file is a part of the vllm-ascend project. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from typing import Any - -import torch - -from .base import QuantType -from .registry import register_scheme -from .w8a8_dynamic import AscendW8A8DynamicFusedMoEMethod, AscendW8A8DynamicLinearMethod - - -@register_scheme("W8A8FP8_DYNAMIC", "linear") -class AscendW8A8FP8DynamicLinearMethod(AscendW8A8DynamicLinearMethod): - """Linear method for Ascend W8A8FP8_DYNAMIC. - - This scheme uses FP8 dynamic per-token quantization for activations - and FP8 per-channel quantization for weights. - """ - - act_quant_type: torch.dtype = torch.float8_e4m3fn - - def __init__(self): - pass - - def get_weight(self, input_size: int, output_size: int, params_dtype: torch.dtype) -> dict[str, Any]: - params_dict = {"weight": torch.empty(output_size, input_size, dtype=torch.float8_e4m3fn)} - return params_dict - - def get_perchannel_param( - self, - output_size: int, - params_dtype: torch.dtype, - ) -> dict[str, Any]: - params_dict = {} - params_dict["weight_scale"] = torch.empty(output_size, 1, dtype=torch.float32) - params_dict["weight_offset"] = torch.empty(output_size, 1, dtype=params_dtype) - return params_dict - - def apply( - self, - layer: torch.nn.Module, - x: torch.Tensor, - bias: torch.Tensor | None = None, - tp_rank: int | None = 0, - ) -> torch.Tensor: - output = super().apply(layer, x, bias, tp_rank) - # TODO: there is a bug in npu_quant_matmul for fp8 with bias - # after the bug is fixed, the whole apply method can be removed. - if bias is not None: - output = (output + bias).to(x.dtype) - return output - - -@register_scheme("W8A8FP8_DYNAMIC", "moe") -class AscendW8A8FP8DynamicFusedMoEMethod(AscendW8A8DynamicFusedMoEMethod): - """FusedMoE method for Ascend W8A8FP8_DYNAMIC.""" - - quant_type: QuantType = QuantType.W8A8FP8 - - def __init__(self): - super().__init__() - - def get_weight( - self, num_experts: int, intermediate_size_per_partition: int, hidden_sizes: int, params_dtype: torch.dtype - ) -> dict[str, Any]: - param_dict = {} - param_dict["w13_weight"] = torch.empty( - num_experts, 2 * intermediate_size_per_partition, hidden_sizes, dtype=torch.float8_e4m3fn - ) - param_dict["w2_weight"] = torch.empty( - num_experts, hidden_sizes, intermediate_size_per_partition, dtype=torch.float8_e4m3fn - ) - return param_dict - - def get_dynamic_quant_param( - self, num_experts: int, intermediate_size_per_partition: int, hidden_sizes: int, params_dtype: torch.dtype - ) -> dict[str, Any]: - param_dict = {} - param_dict["w13_weight_scale"] = torch.empty( - num_experts, 2 * intermediate_size_per_partition, 1, dtype=torch.float32 - ) - param_dict["w13_weight_offset"] = torch.empty( - num_experts, 2 * intermediate_size_per_partition, 1, dtype=params_dtype - ) - param_dict["w2_weight_scale"] = torch.empty(num_experts, hidden_sizes, 1, dtype=torch.float32) - param_dict["w2_weight_offset"] = torch.empty(num_experts, hidden_sizes, 1, dtype=params_dtype) - return param_dict diff --git a/vllm_ascend/quantization/modelslim_config.py b/vllm_ascend/quantization/modelslim_config.py index e29261bd9..962c5c3d2 100644 --- a/vllm_ascend/quantization/modelslim_config.py +++ b/vllm_ascend/quantization/modelslim_config.py @@ -270,7 +270,7 @@ QUANT_MODEL_SUBSTR_MAPPINGS = { "deepseek_v4": { - ".attn.": ".self_attn.", + ".attn.": ".sefl_attn.", ".w1.": ".gate_proj.", ".w2.": ".down_proj.", ".w3.": ".up_proj.", diff --git a/vllm_ascend/quantization/quant_type.py b/vllm_ascend/quantization/quant_type.py index 1e0dfb53a..ca182d9c8 100644 --- a/vllm_ascend/quantization/quant_type.py +++ b/vllm_ascend/quantization/quant_type.py @@ -33,4 +33,3 @@ class QuantType(Enum): W4A16 = 4 MXFP4 = 5 W4A8MXFP = 6 - W8A8FP8 = 7 diff --git a/vllm_ascend/sample/rejection_sampler.py b/vllm_ascend/sample/rejection_sampler.py index 689b07442..29946b824 100644 --- a/vllm_ascend/sample/rejection_sampler.py +++ b/vllm_ascend/sample/rejection_sampler.py @@ -4,7 +4,6 @@ import torch from vllm.distributed.parallel_state import get_tp_group -from vllm.logger import logger from vllm.triton_utils import HAS_TRITON from vllm.v1.outputs import SamplerOutput from vllm.v1.sample.metadata import SamplingMetadata @@ -54,11 +53,6 @@ def apply_penalties( """Use Triton-Ascend penalties on NPU when Triton is available; else vLLM default.""" if not HAS_TRITON: - logger.warning_once( - "[sample/rejection_sampler] Triton not available, falling back to vLLM default " - "penalty implementation in rejection sampler. Rejection sampling performance " - "may be degraded on NPU. " - ) return Sampler.apply_penalties(logits, sampling_metadata, output_token_ids) assert sampling_metadata.prompt_token_ids is not None @@ -86,14 +80,6 @@ def __init__(self, sampler): # Store Ascend-specific optimizations self._ascend_optimizations_enabled = True self.top_k = None - logger.debug( - "[sample/rejection_sampler] AscendRejectionSampler initialized. " - "ascend_optimizations_enabled=%s, triton_available=%s, " - "reduce_sample=%s", - self._ascend_optimizations_enabled, - HAS_TRITON, - get_ascend_config().enable_reduce_sample, - ) def forward( self, @@ -178,7 +164,6 @@ def forward( target_logits, bonus_token_ids, sampling_metadata, - ori_target_logits=raw_target_logits, ) logprobs_tensors = None @@ -277,10 +262,6 @@ def apply_sampling_constraints( # New flow: top_k -> allgather -> top_p # Returns processed logits and indices if get_ascend_config().enable_reduce_sample: - logger.debug_once( - "[sample/rejection_sampler] Using reduce-sample path for " - "apply_sampling_constraints. top-k/top-p with TP all-gather.", - ) return apply_top_k_top_p(logits, k, p, top_k) else: return apply_top_k_top_p(logits, k, p) @@ -305,7 +286,6 @@ def rejection_sample( sampling_metadata: SamplingMetadata, synthetic_mode: bool = False, synthetic_conditional_rates: torch.Tensor | None = None, - ori_target_logits: torch.Tensor | None = None, ) -> torch.Tensor: """ Rejection sampling for speculative decoding in distributed setting. @@ -348,27 +328,10 @@ def rejection_sample( assert bonus_token_ids.is_contiguous() assert target_logits.shape[0] == num_tokens - # Block verify requires enable_block_verify config and max_spec_len >= 3. - using_block_verify = max_spec_len >= 3 and bool(get_ascend_config().rejection_sampler_config.enable_block_verify) - using_entropy_verify = bool(get_ascend_config().rejection_sampler_config.enable_entropy_verify) - posterior_threshold = float(get_ascend_config().rejection_sampler_config.posterior_threshold) - posterior_alpha = float(get_ascend_config().rejection_sampler_config.posterior_alpha) - logger.debug_once( - "[sample/rejection_sampler] Rejection sampling path: " - "block_verify=%s, entropy_verify=%s, all_greedy=%s, all_random=%s, " - "reduce_sample=%s, triton=%s", - using_block_verify, - using_entropy_verify, - sampling_metadata.all_greedy, - sampling_metadata.all_random, - get_ascend_config().enable_reduce_sample, - HAS_TRITON, - ) - - if using_entropy_verify and ori_target_logits is not None: - ori_target_probs = ori_target_logits.softmax(dim=-1, dtype=torch.float32) - else: - ori_target_probs = None + # When num_speculative_tokens>=3, using block verify. + # Skip block verify when draft_probs is None (suffix/ngram methods) + # to avoid incorrect verification results. + using_block_verify = max_spec_len >= 3 and draft_probs is not None # Create output buffer. output_token_ids = torch.empty( @@ -384,22 +347,6 @@ def rejection_sample( is_greedy = sampling_metadata.temperature == GREEDY_TEMPERATURE if HAS_TRITON: grid, block_size = cal_grid_and_block_size(batch_size) - - if using_block_verify or using_entropy_verify: - logger.info_once( - "RejectionSampler config: block_verify=%s, entropy_verify=%s, " - "posterior_threshold=%s, posterior_alpha=%s, reduce_sample=%s, " - "has_triton=%s, all_greedy=%s, all_random=%s", - using_block_verify, - using_entropy_verify, - posterior_threshold, - posterior_alpha, - target_indices is not None, - HAS_TRITON, - sampling_metadata.all_greedy, - sampling_metadata.all_random, - ) - # For greedy sampling, we need to do allgather first to get global argmax if not sampling_metadata.all_random: if get_ascend_config().enable_reduce_sample: @@ -472,6 +419,7 @@ def rejection_sample( target_probs, sampling_metadata, device, + use_block_verify=using_block_verify, target_indices=target_indices, global_vocab_size=global_vocab_size, enable_reduce_sampling=True, @@ -495,16 +443,9 @@ def rejection_sample( selected_vocab_size, global_vocab_size, batch_size, - ori_target_probs, - NO_ORI_TARGET_PROBS=ori_target_probs is None, NO_DRAFT_PROBS=draft_probs is None, ENABLE_REDUCE_SAMPLING=True, - ENTROPY_VERIFY=using_entropy_verify, BLOCK_SIZE=block_size, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - SUB_BLOCK=4 * 1024, - EPSILON=1e-10, ) else: rejection_random_sample_pytorch( @@ -522,15 +463,8 @@ def rejection_sample( IS_NGRAM=draft_probs is None, target_indices=target_indices, enable_reduce_sampling=True, - ENTROPY_VERIFY=using_entropy_verify, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, ) else: - # MagicMTP: Improving acceptance rate with Block Verify. - # Entropy_verify: Improving acceptance rate with entropy Verify. if HAS_TRITON: rejection_random_sample_block_verify_kernel[(grid,)]( output_token_ids, @@ -547,16 +481,9 @@ def rejection_sample( selected_vocab_size, global_vocab_size, batch_size, - ori_target_probs, - NO_ORI_TARGET_PROBS=ori_target_probs is None, NO_DRAFT_PROBS=draft_probs is None, ENABLE_REDUCE_SAMPLING=True, - ENTROPY_VERIFY=using_entropy_verify, BLOCK_SIZE=block_size, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - SUB_BLOCK=4 * 1024, - EPSILON=1e-10, ) else: rejection_random_sample_block_verify_pytorch( @@ -574,22 +501,10 @@ def rejection_sample( IS_NGRAM=draft_probs is None, target_indices=target_indices, enable_reduce_sampling=True, - ENTROPY_VERIFY=using_entropy_verify, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, ) else: # Fallback to original mode # This path should not be used in the new distributed flow - logger.warning_once( - "[sample/rejection_sampler] Using fallback (non-reduce-sample) path in " - "rejection_sample. This path should not be used in the new distributed flow. " - "enable_reduce_sample=%s, has_target_indices=%s", - get_ascend_config().enable_reduce_sample, - target_indices is not None, - ) vocab_size = target_logits.shape[-1] global_vocab_size = draft_probs.shape[-1] if draft_probs is not None else vocab_size @@ -615,6 +530,7 @@ def rejection_sample( target_probs, sampling_metadata, device, + use_block_verify=using_block_verify, target_indices=None, global_vocab_size=vocab_size, enable_reduce_sampling=False, @@ -637,16 +553,9 @@ def rejection_sample( vocab_size, global_vocab_size, # global_vocab_size batch_size, - ori_target_probs, - NO_ORI_TARGET_PROBS=ori_target_probs is None, NO_DRAFT_PROBS=draft_probs is None, ENABLE_REDUCE_SAMPLING=False, - ENTROPY_VERIFY=using_entropy_verify, BLOCK_SIZE=block_size, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - SUB_BLOCK=4 * 1024, - EPSILON=1e-10, ) else: rejection_random_sample_pytorch( @@ -664,11 +573,6 @@ def rejection_sample( IS_NGRAM=draft_probs is None, target_indices=None, enable_reduce_sampling=False, - ENTROPY_VERIFY=using_entropy_verify, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, ) else: if HAS_TRITON: @@ -687,16 +591,9 @@ def rejection_sample( vocab_size, global_vocab_size, # global_vocab_size batch_size, - ori_target_probs, - NO_ORI_TARGET_PROBS=ori_target_probs is None, NO_DRAFT_PROBS=draft_probs is None, ENABLE_REDUCE_SAMPLING=False, - ENTROPY_VERIFY=using_entropy_verify, BLOCK_SIZE=block_size, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - SUB_BLOCK=4 * 1024, - EPSILON=1e-10, ) else: rejection_random_sample_block_verify_pytorch( @@ -714,11 +611,6 @@ def rejection_sample( IS_NGRAM=draft_probs is None, target_indices=None, enable_reduce_sampling=False, - ENTROPY_VERIFY=using_entropy_verify, - POSTERIOR_THRESHOLD=posterior_threshold, - POSTERIOR_ALPHA=posterior_alpha, - EPSILON=1e-10, - ori_target_probs=ori_target_probs, ) return output_token_ids @@ -812,9 +704,9 @@ def sample_recovered_tokens( vocab_size, global_vocab_size if global_vocab_size is not None else vocab_size, NO_DRAFT_PROBS=draft_probs is None, + BLOCK_VERIFY=use_block_verify, ENABLE_REDUCE_SAMPLING=enable_reduce_sampling, - VOCAB_BLOCK_SIZE=512, - SUB_BLOCK=4 * 1024, + SUB_BLOCK=512, # TODO: enable multibuffer when accuracy problem is solved. multibuffer=False, ) @@ -931,11 +823,6 @@ def rejection_random_sample_pytorch( IS_NGRAM=False, target_indices=None, # [num_tokens, selected_vocab_size] global vocab indices enable_reduce_sampling=False, - ENTROPY_VERIFY=False, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=None, ): """ This function implements the Speculative Decoding rejection sampling step. @@ -978,15 +865,13 @@ def rejection_random_sample_pytorch( global_token_indices = cu_start[:, None] + pos_indices global_token_indices = global_token_indices.clamp(0, draft_token_ids.shape[0] - 1) draft_tokens = draft_token_ids[global_token_indices] # [batch_size, max_draft_len] - placeholder_mask = draft_tokens == PLACEHOLDER_TOKEN_ID - safe_draft_tokens = draft_tokens.masked_fill(placeholder_mask, 0) if IS_NGRAM: ones_cpu = torch.ones(1, pin_memory=True, dtype=torch.float32) draft_token_probs = ones_cpu.to(device, non_blocking=True).expand_as(draft_tokens) else: flat_indices = global_token_indices.flatten() - flat_draft_tokens = safe_draft_tokens.flatten() + flat_draft_tokens = draft_tokens.flatten() flat_draft_probs = draft_probs[flat_indices, flat_draft_tokens] draft_token_probs = flat_draft_probs.view(batch_size, max_draft_len) @@ -1011,7 +896,7 @@ def rejection_random_sample_pytorch( target_token_probs = target_token_probs_flat.view(batch_size, max_draft_len) else: flat_indices = global_token_indices.flatten() - flat_draft_tokens = safe_draft_tokens.flatten() + flat_draft_tokens = draft_tokens.flatten() flat_target_probs = target_probs[flat_indices, flat_draft_tokens] target_token_probs = flat_target_probs.view(batch_size, max_draft_len) @@ -1021,22 +906,9 @@ def rejection_random_sample_pytorch( zero_threshold_cpu = torch.tensor([0.0], pin_memory=True, dtype=torch.float32) zero_threshold = zero_threshold_cpu.to(device, non_blocking=True) - if ENTROPY_VERIFY: - entropy_probs = ori_target_probs if ori_target_probs is not None else target_probs - all_target_dist = entropy_probs[global_token_indices] - entropy = -(all_target_dist * torch.log(all_target_dist + EPSILON)).sum(dim=-1) - exp_neg_entropy = torch.exp(-entropy * POSTERIOR_ALPHA) - posterior_threshold_device = torch.tensor(POSTERIOR_THRESHOLD, device=device, dtype=torch.float32) - threshold = torch.minimum(exp_neg_entropy, posterior_threshold_device) - modified_uniform_token_probs = threshold * uniform_token_probs - acceptance_condition = (draft_token_probs > zero_threshold) & ( - target_token_probs / draft_token_probs >= modified_uniform_token_probs - ) - else: - acceptance_condition = (draft_token_probs > zero_threshold) & ( - target_token_probs / draft_token_probs >= uniform_token_probs - ) - acceptance_condition = acceptance_condition & (~placeholder_mask) + acceptance_condition = (draft_token_probs > zero_threshold) & ( + target_token_probs / draft_token_probs >= uniform_token_probs + ) first_rejection = (~acceptance_condition) & valid_mask @@ -1201,9 +1073,8 @@ def sample_recovered_tokens_pytorch( prob = target_probs.clone() for i in range(num_tokens): draft_id = draft_token_ids[i] - if draft_id != PLACEHOLDER_TOKEN_ID: - mask = target_indices[i] == draft_id - prob[i, mask] = 0 + mask = target_indices[i] == draft_id + prob[i, mask] = 0 else: # Gather draft probs at candidate indices flat_indices = target_indices.flatten() @@ -1227,11 +1098,7 @@ def sample_recovered_tokens_pytorch( token_indices = torch.arange(num_tokens, device=device) modified_target_probs = target_probs.clone() - valid_draft_mask = draft_token_ids != PLACEHOLDER_TOKEN_ID - modified_target_probs[ - token_indices[valid_draft_mask], - draft_token_ids[valid_draft_mask], - ] = 0 + modified_target_probs[token_indices, draft_token_ids] = 0 prob = modified_target_probs else: @@ -1275,11 +1142,6 @@ def rejection_random_sample_block_verify_pytorch( IS_NGRAM=False, target_indices=None, # [num_tokens, selected_vocab_size] global vocab indices enable_reduce_sampling=False, - ENTROPY_VERIFY=False, - POSTERIOR_THRESHOLD=0.95, - POSTERIOR_ALPHA=0.4, - EPSILON=1e-10, - ori_target_probs=None, ): batch_size = output_token_ids.shape[0] device = output_token_ids.device @@ -1296,15 +1158,13 @@ def rejection_random_sample_block_verify_pytorch( global_token_indices = cu_start[:, None] + pos_indices global_token_indices = global_token_indices.clamp(0, draft_token_ids.shape[0] - 1) draft_tokens = draft_token_ids[global_token_indices] - placeholder_mask = draft_tokens == PLACEHOLDER_TOKEN_ID - safe_draft_tokens = draft_tokens.masked_fill(placeholder_mask, 0) if IS_NGRAM: ones_cpu = torch.ones(1, pin_memory=True, dtype=torch.float32) draft_token_probs = ones_cpu.to(device, non_blocking=True).expand_as(draft_tokens) else: flat_indices = global_token_indices.flatten() - flat_draft_tokens = safe_draft_tokens.flatten() + flat_draft_tokens = draft_tokens.flatten() flat_draft_probs = draft_probs[flat_indices, flat_draft_tokens] draft_token_probs = flat_draft_probs.view(batch_size, max_spec_len) @@ -1329,7 +1189,7 @@ def rejection_random_sample_block_verify_pytorch( target_token_probs = target_token_probs_flat.view(batch_size, max_spec_len) else: flat_indices = global_token_indices.flatten() - flat_draft_tokens = safe_draft_tokens.flatten() + flat_draft_tokens = draft_tokens.flatten() flat_target_probs = target_probs[flat_indices, flat_draft_tokens] target_token_probs = flat_target_probs.view(batch_size, max_spec_len) @@ -1339,20 +1199,9 @@ def rejection_random_sample_block_verify_pytorch( pi = target_token_probs / draft_token_probs pi = pi.clamp(max=1.0) pi = torch.cumprod(pi, dim=-1) - cum_uniform_token_probs = torch.cumprod(uniform_token_probs, dim=-1) - - if ENTROPY_VERIFY: - entropy_probs = ori_target_probs if ori_target_probs is not None else target_probs - all_target_dist = entropy_probs[global_token_indices] - entropy = -(all_target_dist * torch.log(all_target_dist + EPSILON)).sum(dim=-1) - exp_neg_entropy = torch.exp(-entropy * POSTERIOR_ALPHA) - posterior_threshold_device = torch.tensor(POSTERIOR_THRESHOLD, device=device, dtype=torch.float32) - threshold = torch.minimum(exp_neg_entropy, posterior_threshold_device) - modified_cum_uniform_token_probs = threshold * cum_uniform_token_probs - legal_mask = (draft_token_probs > 0) & (pi >= modified_cum_uniform_token_probs) - else: - legal_mask = (draft_token_probs > 0) & (pi >= cum_uniform_token_probs) - legal_mask = legal_mask & valid_mask & (~placeholder_mask) + uniform_token_probs = torch.cumprod(uniform_token_probs, dim=-1) + legal_mask = (draft_token_probs > 0) & (pi >= uniform_token_probs) + legal_mask = legal_mask & valid_mask last_accept_pos = torch.where( legal_mask.any(dim=-1, keepdim=True), @@ -1415,14 +1264,7 @@ def sample_recovered_tokens_blockwise_pytorch( if IS_NGRAM: draft_token_scalar_probs = torch.ones(num_tokens, device=device, dtype=torch.float32) else: - valid_draft_mask = draft_token_ids != PLACEHOLDER_TOKEN_ID - safe_draft_token_ids = draft_token_ids.masked_fill(~valid_draft_mask, 0) - draft_token_scalar_probs = draft_probs[token_indices, safe_draft_token_ids] - draft_token_scalar_probs = torch.where( - valid_draft_mask, - draft_token_scalar_probs, - torch.zeros_like(draft_token_scalar_probs), - ) + draft_token_scalar_probs = draft_probs[token_indices, draft_token_ids] # Get target probability for each draft token if enable_reduce_sampling: @@ -1437,14 +1279,7 @@ def sample_recovered_tokens_blockwise_pytorch( torch.tensor(0.0, device=device), ).sum(dim=1) # [num_tokens] else: - valid_draft_mask = draft_token_ids != PLACEHOLDER_TOKEN_ID - safe_draft_token_ids = draft_token_ids.masked_fill(~valid_draft_mask, 0) - target_token_scalar_probs = target_probs[token_indices, safe_draft_token_ids] - target_token_scalar_probs = torch.where( - valid_draft_mask, - target_token_scalar_probs, - torch.zeros_like(target_token_scalar_probs), - ) + target_token_scalar_probs = target_probs[token_indices, draft_token_ids] per_token_ratio = torch.where( draft_token_scalar_probs > 0, @@ -1469,9 +1304,8 @@ def sample_recovered_tokens_blockwise_pytorch( prob = target_probs.clone() for i in range(num_tokens): draft_id = draft_token_ids[i] - if draft_id != PLACEHOLDER_TOKEN_ID: - mask = target_indices[i] == draft_id - prob[i, mask] = 0 + mask = target_indices[i] == draft_id + prob[i, mask] = 0 residual = torch.clamp(p_i_expanded * prob, min=0.0) else: # Gather draft probs at candidate indices (same as sample_recovered_tokens_pytorch) @@ -1491,11 +1325,7 @@ def sample_recovered_tokens_blockwise_pytorch( # normal mode if IS_NGRAM: modified_target = target_probs.clone() - valid_draft_mask = draft_token_ids != PLACEHOLDER_TOKEN_ID - modified_target[ - token_indices[valid_draft_mask], - draft_token_ids[valid_draft_mask], - ] = 0.0 + modified_target[token_indices, draft_token_ids] = 0.0 residual = torch.clamp(p_i_expanded * modified_target, min=0.0) else: residual = torch.clamp(p_i_expanded * target_probs - draft_probs, min=0.0) diff --git a/vllm_ascend/sample/sampler.py b/vllm_ascend/sample/sampler.py index c6f330a17..2245deb73 100644 --- a/vllm_ascend/sample/sampler.py +++ b/vllm_ascend/sample/sampler.py @@ -1,7 +1,6 @@ import torch import vllm.envs as envs from vllm.distributed.parallel_state import get_tp_group -from vllm.logger import logger from vllm.triton_utils import HAS_TRITON from vllm.v1.sample.metadata import SamplingMetadata from vllm.v1.sample.ops.topk_topp_sampler import TopKTopPSampler @@ -51,10 +50,6 @@ def apply_penalties( ) -> torch.Tensor: """Use Triton-Ascend penalties on NPU when Triton is available; else vLLM default.""" if not HAS_TRITON: - logger.warning_once( - "[sample/sampler] Triton not available, falling back to vLLM default " - "penalty implementation. Penalty performance may be degraded on NPU. " - ) return Sampler.apply_penalties(logits, sampling_metadata, output_token_ids) if sampling_metadata.no_penalties: @@ -74,11 +69,6 @@ def __init__(self, logprobs_mode=DEFAULT_LOGPROBS_MODE): super().__init__(logprobs_mode=logprobs_mode) self.topk_topp_sampler = AscendTopKTopPSampler(logprobs_mode=logprobs_mode) self.async_exponential_event = torch.npu.Event() - logger.debug( - "[sample/sampler] AscendSampler initialized. logprobs_mode=%s, triton_available=%s", - logprobs_mode, - HAS_TRITON, - ) def set_q_event(self, q, event): self.topk_topp_sampler.set_q_event(q, event) @@ -104,10 +94,6 @@ def do_async_exponential(self, b_s, head_dim, generators): @staticmethod def greedy_sample(logits: torch.Tensor) -> torch.Tensor: if get_ascend_config().enable_reduce_sample: - logger.debug_once( - "[sample/sampler] Using reduce-sample greedy sampling. " - "TP all-gather will be performed to find global argmax.", - ) tp_group = get_tp_group() B, V_local = logits.shape rank = tp_group.rank_in_group @@ -147,17 +133,9 @@ def forward_native(self, logits, generators, k, p): # when batch_invariant mode is enabled, we should use vllm's implementation. # or it will make batch_invariant mode not working. if envs.VLLM_BATCH_INVARIANT: - logger.debug_once( - "[sample/sampler] BATCH_INVARIANT mode enabled, " - "falling back to vLLM native top-k/top-p implementation.", - ) return super().forward_native(logits, generators, k, p) if get_ascend_config().enable_reduce_sample: - logger.debug_once( - "[sample/sampler] Using reduce-sample path in forward_native. " - "top-k/top-p with TP all-gather for distributed sampling.", - ) cand_logits, cand_idx = self.apply_top_k_top_p(logits, k, p, self.top_k) logits_to_return = None if self.logprobs_mode == "processed_logits": @@ -165,7 +143,7 @@ def forward_native(self, logits, generators, k, p): elif self.logprobs_mode == "processed_logprobs": logits_to_return = cand_logits.log_softmax(dim=-1, dtype=torch.float32) - probs = cand_logits.softmax(dim=-1, dtype=torch.float32) + probs = torch.softmax(cand_logits, dim=-1) pos = random_sample(probs, generators) # [B] next_token = cand_idx.gather(dim=1, index=pos.unsqueeze(1)).squeeze(1) # [B] @@ -181,10 +159,6 @@ def forward_native(self, logits, generators, k, p): probs = logits.softmax(dim=-1, dtype=torch.float32) if get_ascend_config().enable_async_exponential: # Add synchronize to prevent synchronize error. - logger.debug_once( - "[sample/sampler] Using async-exponential sampling path. " - "Pre-computed exponential randoms from separate stream will be used.", - ) self.async_event.synchronize() return probs.div_(self.q).argmax(dim=-1).view(-1), logits_to_return return random_sample(probs, generators), logits_to_return @@ -199,31 +173,31 @@ def _apply_top_k_top_p_pytorch( if get_ascend_config().enable_reduce_sample: tp_group = get_tp_group() B, V_local = logits.shape + world_size = tp_group.world_size rank = tp_group.rank_in_group + V_global = V_local * world_size - if top_k is None or (p is None and k is None): - k_for_topk = V_local - else: - k_for_topk = min(top_k, V_local) + local_vals, local_idx = torch.topk(logits, k=top_k, dim=-1) # [B, top_k], [B, top_k] + local_global_idx = local_idx + rank * V_local # [B, top_k] - local_vals, local_idx = torch.topk(logits, k=k_for_topk, dim=-1) - local_global_idx = local_idx + rank * V_local - gathered_vals = tp_group.all_gather(local_vals, dim=-1) - gathered_idx = tp_group.all_gather(local_global_idx, dim=-1) + gathered_vals = tp_group.all_gather(local_vals, dim=-1) # [B, top_k*tp] + gathered_idx = tp_group.all_gather(local_global_idx, dim=-1) # [B, top_k*tp] - if p is None and k is None: - return gathered_vals, gathered_idx + full_logits = logits.new_full((B, V_global), -float("inf")) + full_logits.scatter_(dim=-1, index=gathered_idx, src=gathered_vals) - probs = gathered_vals.softmax(dim=-1) + if p is None and k is None: + return full_logits + probs = full_logits.softmax(dim=-1) probs_sort, _ = probs.sort(dim=-1, descending=False) if k is not None: - kk = k.to(torch.long).clamp(min=1, max=V_local) + kk = k.to(torch.long).clamp(min=1, max=V_global) top_k_count = (probs_sort.size(1) - kk).unsqueeze(1) # [B,1] top_k_cutoff = probs_sort.gather(-1, top_k_count) - no_top_k_mask = (kk == V_local).unsqueeze(1) + no_top_k_mask = (kk == V_global).unsqueeze(1) top_k_cutoff.masked_fill_(no_top_k_mask, -float("inf")) elements_to_discard = probs < top_k_cutoff - gathered_vals.masked_fill_(elements_to_discard, -float("inf")) + full_logits.masked_fill_(elements_to_discard, -float("inf")) if p is not None: cumprob = torch.cumsum(probs_sort, dim=-1) top_p_mask = cumprob <= (1 - p.unsqueeze(1)) @@ -231,8 +205,8 @@ def _apply_top_k_top_p_pytorch( top_p_count = top_p_mask.sum(dim=-1, keepdim=True) top_p_cutoff = probs_sort.gather(-1, top_p_count) elements_to_discard = probs < top_p_cutoff - gathered_vals.masked_fill_(elements_to_discard, -float("inf")) - return gathered_vals, gathered_idx + full_logits.masked_fill_(elements_to_discard, -float("inf")) + return full_logits else: if p is None and k is None: return logits @@ -276,23 +250,21 @@ def _apply_top_k_top_p_ascendc( B, V_local = logits.shape rank = tp_group.rank_in_group - if top_k is None or (p is None and k is None): - k_for_topk = V_local - else: - k_for_topk = min(top_k, V_local) + local_vals, local_idx = torch.topk(logits, k=top_k, dim=-1) # [B, top_k], [B, top_k] - local_vals, local_idx = torch.topk(logits, k=k_for_topk, dim=-1) - local_global_idx = local_idx + rank * V_local - gathered_vals = tp_group.all_gather(local_vals, dim=-1) - gathered_idx = tp_group.all_gather(local_global_idx, dim=-1) + local_global_idx = local_idx + rank * V_local # [B, top_k] - if not (p is None and k is None): - gathered_vals = torch.ops._C_ascend.npu_apply_top_k_top_p(gathered_vals, k=k, p=p) - return gathered_vals, gathered_idx + gathered_vals = tp_group.all_gather(local_vals, dim=-1) # [B, top_k*tp] + gathered_idx = tp_group.all_gather(local_global_idx, dim=-1) # [B, top_k*tp] - if p is None and k is None: - return logits - return torch.ops._C_ascend.npu_apply_top_k_top_p(logits, k=k, p=p) + if p is None and k is None: + return logits + gathered_vals = torch.ops._C_ascend.npu_apply_top_k_top_p(gathered_vals, k=k, p=p) + return gathered_vals, gathered_idx + else: + if p is None and k is None: + return logits + return torch.ops._C_ascend.npu_apply_top_k_top_p(logits, k=k, p=p) apply_top_k_top_p = ( diff --git a/vllm_ascend/spec_decode/dflash_proposer.py b/vllm_ascend/spec_decode/dflash_proposer.py index 893d6a787..d8ea4ed8a 100644 --- a/vllm_ascend/spec_decode/dflash_proposer.py +++ b/vllm_ascend/spec_decode/dflash_proposer.py @@ -96,7 +96,6 @@ def set_inputs_first_pass( # Inputs next_token_ids_ptr=next_token_ids, target_positions_ptr=target_positions, - context_slot_mapping_ptr=cad.slot_mapping, # Outputs out_input_ids_ptr=self.input_ids, out_context_positions_ptr=self._context_positions_buffer, @@ -109,7 +108,6 @@ def set_inputs_first_pass( block_table_stride=cad.block_table_tensor.stride(0), # Metadata query_start_loc_ptr=cad.query_start_loc, - seq_lens_ptr=cad.seq_lens, num_rejected_tokens_ptr=(num_rejected_tokens_gpu if has_num_rejected else 0), # Scalars parallel_drafting_token_id=self.parallel_drafting_token_id, diff --git a/vllm_ascend/spec_decode/eagle_proposer.py b/vllm_ascend/spec_decode/eagle_proposer.py index b25d10031..96dc06841 100644 --- a/vllm_ascend/spec_decode/eagle_proposer.py +++ b/vllm_ascend/spec_decode/eagle_proposer.py @@ -8,5 +8,12 @@ class AscendEagleProposer(EagleProposer, AscendSpecDecodeBaseProposer): - def __init__(self, vllm_config: VllmConfig, device: torch.device, runner=None): - AscendSpecDecodeBaseProposer.__init__(self, vllm_config, device, True, runner=runner) + def __init__( + self, + vllm_config: VllmConfig, + device: torch.device, + runner=None, + ): + AscendSpecDecodeBaseProposer.__init__( + self, vllm_config, device, pass_hidden_states_to_model=True, runner=runner + ) diff --git a/vllm_ascend/spec_decode/llm_base_proposer.py b/vllm_ascend/spec_decode/llm_base_proposer.py index 9ade3e8fd..596e4d0ea 100644 --- a/vllm_ascend/spec_decode/llm_base_proposer.py +++ b/vllm_ascend/spec_decode/llm_base_proposer.py @@ -16,6 +16,7 @@ get_tp_group, get_world_group, init_model_parallel_group, + patch_tensor_parallel_group, ) from vllm.forward_context import BatchDescriptor, ForwardContext, get_forward_context from vllm.logger import logger @@ -51,30 +52,7 @@ from vllm_ascend.distributed.parallel_state import get_lmhead_tp_group from vllm_ascend.ops.triton.spec_decode.utils import prepare_inputs_padded_kernel from vllm_ascend.ops.triton.triton_utils import get_vectorcore_num -from vllm_ascend.utils import enable_sp, lmhead_tp_enable, shared_expert_dp_enabled, vllm_version_is - -if vllm_version_is("0.22.1"): - from vllm.distributed.parallel_state import patch_tensor_parallel_group # type: ignore[import-not-found] -else: - import vllm.distributed.parallel_state as _ps # type: ignore[import-not-found] - - @contextmanager - def patch_tensor_parallel_group(tp_group): - """Temporarily swap the global TP group for draft-model spec decode. - - Backports vllm 0.21's ``patch_tensor_parallel_group`` which was removed - on vLLM main. Used so the draft model can run with a TP degree that - differs from the target model. - """ - old_tp_group = _ps.get_tp_group() - _ps._TP_STATE_PATCHED = True - _ps._TP = tp_group - try: - yield - finally: - _ps._TP_STATE_PATCHED = False - _ps._TP = old_tp_group - +from vllm_ascend.utils import enable_sp, lmhead_tp_enable, shared_expert_dp_enabled # Currently we will fix block size to a small one since `num_reqs` can't be too large _PREPARE_INPUTS_BLOCK_SIZE = 4 @@ -139,19 +117,6 @@ def __init__(self, vllm_config: VllmConfig, device: torch.device, pass_hidden_st # Assign runner before it's used in the methods below self.runner = runner - logger.debug( - "[spec_decode/base] Initializing spec decode proposer: method=%s," - " num_speculative_tokens=%s, hidden_size=%s, pass_hidden_states=%s," - " parallel_drafting=%s, use_cuda_graph=%s, device=%s", - self.method, - self.num_speculative_tokens, - self.hidden_size, - pass_hidden_states_to_model, - self.speculative_config.parallel_drafting if self.speculative_config else False, - runner._use_aclgraph() if runner else False, - device, - ) - self.use_async_scheduling = self.vllm_config.scheduler_config.async_scheduling self.use_compress = hasattr(self.vllm_config.model_config.hf_config, "compress_ratios") self.pass_hidden_states_to_model = pass_hidden_states_to_model @@ -252,8 +217,7 @@ def _get_model(self) -> nn.Module: draft_vllm_config = self._create_draft_vllm_config() draft_load_config = self.speculative_config.draft_load_config logger.info( - "[spec_decode/base] Loading draft model: method=%s, load_format=%s, model=%s", - self.method, + "AscendSpecDecodeBaseProposer._get_model(): loading draft model with load_format=%s, model=%s", getattr(draft_load_config, "load_format", None), getattr(self.speculative_config.draft_model_config, "model", None), ) @@ -266,8 +230,6 @@ def _get_model(self) -> nn.Module: return model def load_model(self, model: nn.Module) -> None: - assert get_pp_group().is_last_rank, f"{self.method} drafter must be loaded on the last pipeline stage." - target_attn_layer_names = set(get_layers_from_vllm_config(self.vllm_config, AttentionLayerBase).keys()) with self.maybe_eager_context: @@ -332,13 +294,6 @@ def load_model(self, model: nn.Module) -> None: else self.model.mask_hidden.view(self.hidden_size) ) - logger.info( - "[spec_decode/base] Draft model loaded successfully: method=%s, num_draft_attn_layers=%d, block_size=%d", - self.method, - len(self._draft_attn_layer_names), - self.kernel_block_size, - ) - def _maybe_share_embeddings(self, target_language_model: nn.Module) -> None: """ Some draft models may not have their own embedding layers, and some may @@ -361,9 +316,9 @@ def _maybe_share_embeddings(self, target_language_model: nn.Module) -> None: if not self.model.has_own_embed_tokens: share_embeddings = True logger.info( - "[spec_decode/base] Detected EAGLE model without its own" - " embed_tokens in the checkpoint. Sharing target model" - " embedding weights with the draft model." + "Detected EAGLE model without its own embed_tokens in the" + " checkpoint. Sharing target model embedding weights with the" + " draft model." ) elif ( isinstance(target_embed_tokens.weight, torch.Tensor) @@ -377,24 +332,20 @@ def _maybe_share_embeddings(self, target_language_model: nn.Module) -> None: ): share_embeddings = True logger.info( - "[spec_decode/base] Detected EAGLE model with embed_tokens" - " identical to the target model. Sharing target model embedding" - " weights with the draft model." + "Detected EAGLE model with embed_tokens identical to the target" + " model. Sharing target model embedding weights with the draft" + " model." ) else: logger.info( - "[spec_decode/base] Detected EAGLE model with distinct" - " embed_tokens weights. Keeping separate embedding weights" - " from the target model." + "Detected EAGLE model with distinct embed_tokens weights. " + "Keeping separate embedding weights from the target model." ) else: # MTP model share_embeddings = not self.use_compress if share_embeddings: - logger.info( - "[spec_decode/base] Detected MTP model. Sharing target model" - " embedding weights with the draft model." - ) + logger.info("Detected MTP model. Sharing target model embedding weights with the draft model.") if share_embeddings: if hasattr(self.model.model, "embed_tokens"): @@ -402,7 +353,7 @@ def _maybe_share_embeddings(self, target_language_model: nn.Module) -> None: self.model.model.embed_tokens = target_embed_tokens else: logger.info( - "[spec_decode/base] PP>1: draft model loaded its own vocab embedding" + "Since PP > 1 or other reasons the model head loaded its own vocab embedding" " weights instead of sharing them with the target model." ) @@ -423,23 +374,17 @@ def _maybe_share_lm_head(self, model: nn.Module) -> None: ) if draft_has_own_lm_head: logger.info( - "[spec_decode/base] DFlash draft uses d2t vocab remapping;" - " keeping the draft's own lm_head instead of sharing the target" - " lm_head." + "DFlash draft uses d2t vocab remapping; keeping the draft's " + "own lm_head instead of sharing the target lm_head." ) else: - logger.info("[spec_decode/base] Loading EAGLE/DFLASH LM head weights from the target model.") + logger.info("Loading EAGLE or DFLASH LM head weights from the target model.") if hasattr(model, "lm_head"): self.model.lm_head = model.lm_head elif hasattr(model, "get_language_model") and hasattr(model.get_language_model(), "lm_head"): self.model.lm_head = model.get_language_model().lm_head else: - logger.warning( - "[spec_decode/base] Target model has no accessible lm_head" - " for sharing. Draft model will use its own lm_head." - " This may cause incorrect logits if the draft lm_head" - " is not trained." - ) + logger.warning("Target model has no accessible lm_head for sharing.") if self.method == "mtp" and self.vllm_config.model_config.is_deepseek_mla: for _, layer_module in self.model.model.layers.items(): @@ -447,12 +392,6 @@ def _maybe_share_lm_head(self, model: nn.Module) -> None: layer_module.shared_head.head = model.lm_head if self.vllm_config.compilation_config.cudagraph_mode.has_full_cudagraphs() and self.use_cuda_graph: - logger.info( - "[spec_decode/base] Wrapping draft model with ACLGraphWrapper:" - " runtime_mode=FULL, use_eagle=%s, enable_enpu=%s", - self.use_eagle, - self.enable_enpu, - ) self.update_stream = torch.npu.Stream() self._runnable = ACLGraphWrapper( self._run_merged_draft, @@ -468,8 +407,8 @@ def _maybe_share_topk_indices(self, target_language_model: nn.Module) -> None: del self.model.model.topk_indices_buffer self.model.model.topk_indices_buffer = target_language_model.model.topk_indices_buffer logger.info( - "[spec_decode/base] Detected MTP model with topk_indices_buffer." - " Sharing target model topk_indices_buffer with the draft model." + "Detecting MTP model with topk_indices_buffer." + "Sharing target model topk_indices_buffer with the draft model." ) def get_model(self) -> nn.Module: @@ -552,7 +491,6 @@ def dummy_run( block_table_tensor=self.runner.input_batch.block_table[0].get_device_tensor()[:num_reqs], # This is used to hold a position. slot_mapping=self.runner.input_batch.block_table[0].slot_mapping.gpu, - slot_mapping_cpu=self.runner.input_batch.block_table[0].slot_mapping.cpu, positions=self.runner.positions, attn_state=self.runner.attn_state, decode_token_per_req=self.runner.decode_token_per_req, @@ -1328,10 +1266,7 @@ def set_inputs_first_pass( self._split_pcp_input(req_scheduled_tokens_p, input_ids_p, target_hidden_states_p) ) num_tokens = num_tokens_d + num_tokens_p - if self.uses_mrope: - target_positions = target_positions[:, :num_tokens] - else: - target_positions = target_positions[:num_tokens] + target_positions = target_positions[:num_tokens] self.input_ids[:num_tokens].copy_(torch.cat([input_ids_d, input_ids_p], dim=0)) target_hidden_states = torch.cat([target_hidden_states_d, target_hidden_states_p], dim=0) # 2. update sample_indices according to main model @@ -1563,16 +1498,15 @@ def attn_update_stack_num_spec_norm( common_attn_metadata.seq_lens[:batch_size] += 1 # For the requests that exceed the max model length, we set the # sequence length to 1 to minimize their overheads in attention. - exceeds_mask = common_attn_metadata.seq_lens[:batch_size] > self.max_model_len - common_attn_metadata.seq_lens[:batch_size].masked_fill_(exceeds_mask, 1) + common_attn_metadata.seq_lens[:batch_size].masked_fill_(exceeds_max_model_len, 1) if common_attn_metadata.seq_lens_cpu is not None: common_attn_metadata.seq_lens_cpu[:batch_size] = common_attn_metadata.seq_lens_cpu[:batch_size] + 1 - exceeds_mask_cpu = common_attn_metadata.seq_lens_cpu[:batch_size] > self.max_model_len - common_attn_metadata.seq_lens_cpu[:batch_size].masked_fill_(exceeds_mask_cpu, 1) + exceeds_mask = common_attn_metadata.seq_lens_cpu[:batch_size] >= self.max_model_len + common_attn_metadata.seq_lens_cpu[:batch_size].masked_fill_(exceeds_mask, 1) if common_attn_metadata._seq_lens_cpu is not None: common_attn_metadata._seq_lens_cpu[:batch_size] = common_attn_metadata._seq_lens_cpu[:batch_size] + 1 - exceeds_mask_internal_cpu = common_attn_metadata._seq_lens_cpu[:batch_size] > self.max_model_len - common_attn_metadata._seq_lens_cpu[:batch_size].masked_fill_(exceeds_mask_internal_cpu, 1) + exceeds_mask_internal = common_attn_metadata._seq_lens_cpu[:batch_size] >= self.max_model_len + common_attn_metadata._seq_lens_cpu[:batch_size].masked_fill_(exceeds_mask_internal, 1) if common_attn_metadata.num_computed_tokens_cpu is not None: common_attn_metadata.num_computed_tokens_cpu[:batch_size] += 1 if self.uses_mrope: @@ -1834,7 +1768,6 @@ def prepare_inputs( max_query_len=new_query_len_per_req.max().item(), block_table_tensor=common_attn_metadata.block_table_tensor, slot_mapping=common_attn_metadata.slot_mapping, - slot_mapping_cpu=common_attn_metadata.slot_mapping_cpu, actual_seq_lengths_q=self.runner.actual_seq_lengths_q, positions=common_attn_metadata.positions[token_indices], positions_cpu=common_attn_metadata.positions_cpu[token_indices] @@ -1926,7 +1859,6 @@ def prepare_inputs_padded( actual_seq_lengths_q=self.runner.actual_seq_lengths_q, block_table_tensor=common_attn_metadata.block_table_tensor, slot_mapping=common_attn_metadata.slot_mapping, - slot_mapping_cpu=common_attn_metadata.slot_mapping_cpu, positions=common_attn_metadata.positions, positions_cpu=common_attn_metadata.positions_cpu, attn_state=self.runner.attn_state, diff --git a/vllm_ascend/utils.py b/vllm_ascend/utils.py index f0ef65006..da2d0aa39 100644 --- a/vllm_ascend/utils.py +++ b/vllm_ascend/utils.py @@ -630,6 +630,149 @@ def update_cudagraph_capture_sizes(vllm_config: VllmConfig, cudagraph_capture_si vllm_config.compilation_config.post_init_cudagraph_sizes() +def update_aclgraph_sizes(vllm_config: VllmConfig) -> None: + """Update ACL graph capture sizes based on hardware limitations""" + # NOTE: Currently, we can only capture 1800 graphs at most, + # due to the limitation of ACL graph. This number is bounded by + # the number of streams, which is 2048, we save 248 streams + # as a buffer. + # Maximum number of graphs that can be captured by ACL Graph + MAX_CAPTURE_SIZE = 1800 + + # enable pcp or dcp will add new communication and consume additional approximately less than 100 streams + CP_ADDITIONAL_STREAM_NUM = 100 + + # Store original configuration and temporarily clear it + compilation_config = vllm_config.compilation_config + original_sizes, compilation_config.cudagraph_capture_sizes = compilation_config.cudagraph_capture_sizes, None + + # TODO: Find out if we can have different sizes for mixed batch and uniform batch + # If so, we'll only have to reduce the sizes for mixed batch + from vllm.config.compilation import CUDAGraphMode + + cudagraph_mode = compilation_config.cudagraph_mode + if cudagraph_mode == CUDAGraphMode.FULL_AND_PIECEWISE: + MAX_CAPTURE_SIZE = max(0, MAX_CAPTURE_SIZE - len(original_sizes)) + + # Calculate parallel configuration factor + if not vllm_config.model_config: + logger.warning( + "Got empty model config. " + "This may indicate a configuration loading issue or an empty configuration file. " + "Please check the model configuration file and loading process." + ) + + return + + hf_config = vllm_config.model_config.hf_text_config + if hasattr(hf_config, "num_hidden_layers"): + num_hidden_layers = hf_config.num_hidden_layers + else: + num_hidden_layers = get_max_hidden_layers(hf_config) + parallel_config = vllm_config.parallel_config + + # Calculate maximum supported batch sizes considering model architecture + resources_per_graph = num_hidden_layers + 1 + # For suffix decoding, use the suffix path when no draft_model_config is provided. + if (spec := vllm_config.speculative_config) and (draft := spec.draft_model_config): + # Use get_total_num_hidden_layers() to correctly handle MTP models, + # which store layer count in num_nextn_predict_layers or + # mtp_num_hidden_layers (for Qwen3.5) instead of num_hidden_layers. + resources_per_graph += draft.get_total_num_hidden_layers() + 1 + + # TODO: Find out whether we need to take into account the pp_size + num_comm_groups = sum( + size > 1 + for size in [ + parallel_config.data_parallel_size, + parallel_config.tensor_parallel_size, + ] + ) + + if os.getenv("HCCL_OP_EXPANSION_MODE") == "AIV": + # TODO: Find out whether we need to take into account the pp_size + parallel_factor = ( + 1 + + num_comm_groups + + int(parallel_config.enable_expert_parallel) + + int(vllm_config.additional_config.get("multistream_overlap_shared_expert", False)) + ) + if is_moe_model(vllm_config): + parallel_factor += parallel_config.data_parallel_size > 1 + else: + # When AIV mode is enabled, the allreduce operator of the dense + # layer model will occupy additional streams, which are buffered here. + MAX_CAPTURE_SIZE = MAX_CAPTURE_SIZE - parallel_factor * resources_per_graph + + # Calculate maximum supported batch sizes considering model architecture on the A2 Hardware Device + # Assume the following case: + # MAX_CAPTURE_SIZE = 1920, num_hidden_layers = 48, data_parallel_size is 1, tensor_parallel_size is 4, + # According to the formula, max_num_batch_sizes = math.floor(1920 / (48 + 1) / 2) = 19 + max_num_batch_sizes = math.floor(MAX_CAPTURE_SIZE / resources_per_graph / parallel_factor) + logger.info("Calculated maximum supported batch sizes for ACL graph: %s", max_num_batch_sizes) + else: + # enable pcp or dcp will add new communication and consume additional approximately less than 100 streams + if parallel_config.prefill_context_parallel_size > 1: + MAX_CAPTURE_SIZE = MAX_CAPTURE_SIZE - CP_ADDITIONAL_STREAM_NUM + if parallel_config.decode_context_parallel_size > 1: + MAX_CAPTURE_SIZE = MAX_CAPTURE_SIZE - CP_ADDITIONAL_STREAM_NUM + + # The above describes an empirical formula applicable to the A2 hardware. + # Under this configuration, HCCL employs the FFTS+ method for execution unfolding, + # which adds only 1 concurrent stream without consuming collective communication execution unfolding streams. + # On A3 hardware, HCCL defaults to the AICPU method. + # This approach may additionally allocate up to rank_size (max 16) - 1 streams per collective communication + # domain on the device (worst case). + # Using the default collective communication unfolding method on A3 will lead to a significant reduction + # in the maximum supported sizes. + # Therefore, the calculation formula has been modified as follows: + # Assume the following case: + # MAX_CAPTURE_SIZE = 1920, num_hidden_layers = 48, data_parallel_size is 1, tensor_parallel_size is 4, + # According to the formula, max_num_batch_sizes = math.floor((1920 - 1 * 40) / (48 + 1) / (1 + 1 * 2)) = 12 + max_num_batch_sizes = math.floor( + (MAX_CAPTURE_SIZE - num_comm_groups * 40) / resources_per_graph / (1 + num_comm_groups * 2) + ) + logger.info("Calculated maximum supported batch sizes for ACL graph: %s", max_num_batch_sizes) + logger.warning( + "Currently, communication is performed using FFTS+ method. " + "impact: reduces available streams, limits runtime shapes. " + "solution: set HCCL_OP_EXPANSION_MODE=AIV to improve performance and increase supported shapes. " + ) + + arch_name = vllm_config.model_config.architecture + + # If original sizes exceed maximum, sample a representative subset + if max_num_batch_sizes < len(original_sizes): + # Sample uniformly from original sizes + if max_num_batch_sizes <= 1: + # Avoid division by zero when only one capture size can be kept. + sampled_sizes = [original_sizes[-1]] + else: + step = (len(original_sizes) - 1) / (max_num_batch_sizes - 1) + indices = [round(i * step) for i in range(max_num_batch_sizes)] + indices[0], indices[-1] = 0, len(original_sizes) - 1 + sampled_sizes = [original_sizes[i] for i in indices] + update_cudagraph_capture_sizes(vllm_config, sampled_sizes) + logger.info( + "Adjusted ACL graph batch sizes for %s model (layers: %d): %d → %d sizes", + arch_name, + num_hidden_layers, + len(original_sizes), + len( + compilation_config.cudagraph_capture_sizes # type: ignore[arg-type] + ), + ) + else: + # No adjustment needed + compilation_config.cudagraph_capture_sizes = original_sizes + logger.info( + "No adjustment needed for ACL graph batch sizes: %s model (layers: %d) with %d sizes", + arch_name, + num_hidden_layers, + len(original_sizes), + ) + + # TODO(wxy): Move to ops module def dispose_tensor(x: torch.Tensor): x.set_(torch.empty((0,), device=x.device, dtype=x.dtype)) @@ -893,10 +1036,6 @@ def is_drafter_moe_model(vllm_config: VllmConfig): if _IS_DRAFTER_MOE_MODEL is None: model_configs = vllm_config.speculative_config.draft_model_config.hf_text_config.to_dict() _IS_DRAFTER_MOE_MODEL = _is_contain_expert(model_configs) - if not model_configs or not model_configs.get("architectures"): - return _IS_DRAFTER_MOE_MODEL - if "Eagle3DeepseekV2ForCausalLM" in model_configs["architectures"]: - _IS_DRAFTER_MOE_MODEL = False return _IS_DRAFTER_MOE_MODEL @@ -1261,15 +1400,8 @@ def refresh_block_size(vllm_config): return if model_config.hf_config.model_type == "deepseek_v4": - if cache_config.block_size is None: - cache_config.block_size = 32 - elif cache_config.block_size not in [32, 64, 128]: - logger.warning( - "For deepseek_v4 model, block size should be 32, 64 or 128. " - "Setting block size to 32 for better performance." - ) - cache_config.block_size = 32 - return + # TODO(qcs): generalize the block_size + cache_config.block_size = 128 if model_config.is_hybrid: # Hybrid attention+mamba models rely on the model-specific sizing @@ -1549,13 +1681,6 @@ def kv_cache_spec_uses_sparse_c8(kv_cache_spec) -> bool: return isinstance(kv_cache_spec, MLAAttentionSpec) and bool(getattr(kv_cache_spec, "cache_sparse_c8", False)) -def is_hidden_state_cache_spec(spec) -> bool: - """Whether ``spec`` marks an ``extract_hidden_states`` cache-only layer.""" - from vllm.v1.kv_cache_interface import HiddenStateCacheSpec - - return isinstance(spec, HiddenStateCacheSpec) - - @lru_cache(maxsize=1) def _libc_getenv(): import ctypes diff --git a/vllm_ascend/worker/block_table.py b/vllm_ascend/worker/block_table.py index 6637e9e51..0bf561191 100644 --- a/vllm_ascend/worker/block_table.py +++ b/vllm_ascend/worker/block_table.py @@ -3,7 +3,7 @@ from vllm.distributed import get_dcp_group, get_pcp_group from vllm.utils.math_utils import cdiv from vllm.v1.attention.backends.utils import PAD_SLOT_ID -from vllm.v1.kv_cache_interface import KVCacheGroupSpec, MambaSpec +from vllm.v1.kv_cache_interface import KVCacheGroupSpec from vllm.v1.utils import CpuGpuBuffer from vllm.v1.worker.block_table import _compute_slot_mapping_kernel from vllm.v1.worker.cp_utils import get_total_cp_world_size @@ -24,10 +24,6 @@ def __init__( kv_cache_group: KVCacheGroupSpec = None, ): self.max_num_reqs = max_num_reqs - self.pcp_world_size = get_pcp_group().world_size - self.pcp_rank = get_pcp_group().rank_in_group if self.pcp_world_size > 1 else 0 - self.dcp_world_size = get_dcp_group().world_size - self.dcp_rank = get_dcp_group().rank_in_group compress_ratio = 1 if ( kv_cache_group is not None @@ -35,24 +31,24 @@ def __init__( and hasattr(kv_cache_group.kv_cache_spec, "compress_ratio") ): compress_ratio = kv_cache_group.kv_cache_spec.compress_ratio - if ( - kv_cache_group is not None - and hasattr(kv_cache_group, "kv_cache_spec") - and (self.pcp_world_size * self.dcp_world_size > 1) - and isinstance(kv_cache_group.kv_cache_spec, MambaSpec) - ): - max_num_blocks_per_req = max_num_blocks_per_req * self.pcp_world_size * self.dcp_world_size max_num_blocks_per_req = max(cdiv(max_num_blocks_per_req, compress_ratio), 1) self.max_num_blocks_per_req = max_num_blocks_per_req self.max_num_batched_tokens = max_num_batched_tokens self.pin_memory = pin_memory self.device = device self.physical_block_size = block_size - self.is_mamba_group = ( - kv_cache_group is not None - and hasattr(kv_cache_group, "kv_cache_spec") - and isinstance(kv_cache_group.kv_cache_spec, MambaSpec) - ) + + try: + self.pcp_world_size = get_pcp_group().world_size + self.pcp_rank = get_pcp_group().rank_in_group if self.pcp_world_size > 1 else 0 + self.dcp_world_size = get_dcp_group().world_size + self.dcp_rank = get_dcp_group().rank_in_group + except AssertionError: + # DCP might not be initialized in testing + self.dcp_world_size = 1 + self.dcp_rank = 0 + self.pcp_world_size = 1 + self.pcp_rank = 0 # If kernel_sizes is None or [0], use physical block size (no splitting) if kernel_sizes is None or kernel_sizes == [0]: @@ -148,28 +144,21 @@ def compute_slot_mapping( num_tokens = positions.shape[0] total_cp_world_size = self.pcp_world_size * self.dcp_world_size total_cp_rank = self.pcp_rank * self.dcp_world_size + self.dcp_rank - if self.dcp_world_size * self.pcp_world_size > 1: - req_indices = torch.repeat_interleave( - torch.arange(num_reqs, dtype=torch.int32, device=query_start_loc.device), - query_start_loc[1:] - query_start_loc[:-1], - ) - self._compute_pcp_dcp_slot_mapping(req_indices, positions) - else: - _compute_slot_mapping_kernel[(num_reqs + 1,)]( - num_tokens, - self.max_num_batched_tokens, - query_start_loc, - positions, - self.block_table.gpu, - self.block_table.gpu.stride(0), - self.block_size, - self.slot_mapping.gpu, - TOTAL_CP_WORLD_SIZE=total_cp_world_size, - TOTAL_CP_RANK=total_cp_rank, - CP_KV_CACHE_INTERLEAVE_SIZE=self.cp_kv_cache_interleave_size, - PAD_ID=PAD_SLOT_ID, - BLOCK_SIZE=1024, - ) + _compute_slot_mapping_kernel[(num_reqs + 1,)]( + num_tokens, + self.max_num_batched_tokens, + query_start_loc, + positions, + self.block_table.gpu, + self.block_table.gpu.stride(0), + self.block_size, + self.slot_mapping.gpu, + TOTAL_CP_WORLD_SIZE=total_cp_world_size, + TOTAL_CP_RANK=total_cp_rank, + CP_KV_CACHE_INTERLEAVE_SIZE=self.cp_kv_cache_interleave_size, + PAD_ID=PAD_SLOT_ID, + BLOCK_SIZE=1024, + ) def compute_slot_mapping_draft(self, req_indices: np.ndarray, positions: np.ndarray) -> None: # E.g., [0, 1, 0, 1, 2, 3, 4, 0, 1, 2] @@ -180,7 +169,46 @@ def compute_slot_mapping_draft(self, req_indices: np.ndarray, positions: np.ndar # block_size. if self.dcp_world_size * self.pcp_world_size > 1: - self._compute_pcp_dcp_slot_mapping(torch.from_numpy(req_indices), torch.from_numpy(positions)) + # Note(hc): The DCP implement store kvcache with an interleave + # style, the kvcache for the token whose token_idx is i is + # always stored on the GPU whose dcp_rank equals i % pcp_world_size: + + # Use a "virtual block" which equals to world_size * block_size + # for block_table_indices calculation. + virtual_block_size = self.block_size * self.dcp_world_size * self.pcp_world_size + + # IMPORTANT: In hybrid mode, positions are in logical block space, + # but we need to map them to the correct logical block table indices + logical_block_idx = positions // virtual_block_size + + # Account for the expanded logical table + # (always needed with unified tensor) + # Each physical block is split into multiple logical blocks + # The logical table has been expanded to accommodate this + block_table_indices = ( + req_indices * self.max_num_blocks_per_req * self.blocks_per_phys_block + logical_block_idx + ) + + block_numbers = self.block_table.np.ravel()[block_table_indices] + # Use virtual_block_size for mask calculation, which marks local + # tokens. + virtual_block_offsets = positions % virtual_block_size + self.current_rank = self.dcp_world_size * self.pcp_rank + self.dcp_rank + mask = ( + virtual_block_offsets // self.cp_kv_cache_interleave_size % (self.dcp_world_size * self.pcp_world_size) + == self.current_rank + ) + # Calculate local block_offsets + block_offsets = ( + virtual_block_offsets + // (self.dcp_world_size * self.pcp_world_size * self.cp_kv_cache_interleave_size) + * self.cp_kv_cache_interleave_size + + virtual_block_offsets % self.cp_kv_cache_interleave_size + ) + # Calculate slot_mapping + slot_mapping = block_numbers * self.block_size + block_offsets + # Write final slots, use -1 for not-local + self.slot_mapping.np[: req_indices.shape[0]] = np.where(mask, slot_mapping, -1) else: assert self.kernel_sizes is not None assert self.block_size == self.kernel_sizes[0] @@ -201,53 +229,6 @@ def compute_slot_mapping_draft(self, req_indices: np.ndarray, positions: np.ndar np.add(block_numbers * self.block_size, block_offsets, out=self.slot_mapping.np[: req_indices.shape[0]]) self.slot_mapping.copy_to_gpu(req_indices.shape[0]) - def _compute_pcp_dcp_slot_mapping( - self, - req_indices: torch.Tensor, - positions: torch.Tensor, - ) -> None: - # Note(hc): The DCP implement store kvcache with an interleave - # style, the kvcache for the token whose token_idx is i is - # always stored on the GPU whose dcp_rank equals i % pcp_world_size: - - # Use a "virtual block" which equals to world_size * block_size - # for block_table_indices calculation. - # virtual_block_size = self.block_size * self.dcp_world_size * self.pcp_world_size - - # IMPORTANT: In hybrid mode, positions are in logical block space, - # but we need to map them to the correct logical block table indices - # logical_block_idx = positions // virtual_block_size - - total_cp_world_size = self.dcp_world_size * self.pcp_world_size - virtual_physical_block_size = self.physical_block_size * total_cp_world_size - physical_block_idx = positions // virtual_physical_block_size - virtual_block_offsets = positions % virtual_physical_block_size - - self.current_rank = self.dcp_world_size * self.pcp_rank + self.dcp_rank - mask = virtual_block_offsets // self.cp_kv_cache_interleave_size % total_cp_world_size == self.current_rank - local_physical_offsets = ( - virtual_block_offsets - // (total_cp_world_size * self.cp_kv_cache_interleave_size) - * self.cp_kv_cache_interleave_size - + virtual_block_offsets % self.cp_kv_cache_interleave_size - ) - logical_block_idx = physical_block_idx * self.blocks_per_phys_block + ( - local_physical_offsets // self.block_size - ) - - block_table_indices = req_indices * self.max_num_blocks_per_req * self.blocks_per_phys_block + logical_block_idx - - block_offsets = local_physical_offsets % self.block_size - - if block_table_indices.device.type != "cpu": - block_numbers = self.block_table.gpu.flatten()[block_table_indices] - slot_mapping = block_numbers * self.block_size + block_offsets - self.slot_mapping.gpu[: req_indices.shape[0]] = torch.where(mask, slot_mapping, -1) - else: - block_numbers = self.block_table.cpu.flatten()[block_table_indices] - slot_mapping = block_numbers * self.block_size + block_offsets - self.slot_mapping.cpu[: req_indices.shape[0]] = torch.where(mask, slot_mapping, -1) - def commit_block_table(self, num_reqs: int) -> None: self.block_table.copy_to_gpu(num_reqs) @@ -394,8 +375,6 @@ def compute_slot_mapping( req_indices_compressed_list: list[np.ndarray] | None = None, ) -> None: for i, block_table in enumerate(self.block_tables): - if block_table.is_mamba_group: - continue if positions_compressed_list and req_indices_compressed_list: block_table.compute_slot_mapping_draft(req_indices_compressed_list[i], positions_compressed_list[i]) else: @@ -409,8 +388,6 @@ def compute_slot_mapping_draft( req_indices_compressed_list: list[np.ndarray] | None = None, ) -> None: for i, block_table in enumerate(self.block_tables): - if block_table.is_mamba_group: - continue if positions_compressed_list and req_indices_compressed_list: block_table.compute_slot_mapping_draft(req_indices_compressed_list[i], positions_compressed_list[i]) else: diff --git a/vllm_ascend/worker/encoder_acl_graph.py b/vllm_ascend/worker/encoder_acl_graph.py deleted file mode 100644 index 7f1b204d3..000000000 --- a/vllm_ascend/worker/encoder_acl_graph.py +++ /dev/null @@ -1,394 +0,0 @@ -# -# Copyright (c) 2025 Huawei Technologies Co., Ltd. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""NPU-specific encoder ACL graph: params, runtime context, FIA replay updates, and manager.""" - -from __future__ import annotations - -import inspect -from contextlib import contextmanager -from dataclasses import dataclass, field -from typing import Any - -import torch -import torch_npu -from vllm.logger import logger -from vllm.platforms import current_platform -from vllm.v1.worker.encoder_cudagraph import BudgetGraphMetadata, EncoderCudaGraphManager - -from vllm_ascend.utils import weak_ref_tensors - -_ENCODER_CAPTURE_SUPPORTS_GRAPH_POOL = "graph_pool" in inspect.signature(EncoderCudaGraphManager.capture).parameters - - -# --------------------------------------------------------------------------- -# Per–encoder-budget ACL graph bookkeeping (ViT FIA tasks) -# --------------------------------------------------------------------------- - - -@dataclass -class EncoderGraphParams: - """Mirrors :class:`vllm_ascend.compilation.acl_graph.GraphParams` but keyed by encoder token budget.""" - - events: dict[int, list[torch.npu.ExternalEvent]] = field(default_factory=dict) - workspaces: dict[int, torch.Tensor | None] = field(default_factory=dict) - handles: dict[int, list[Any]] = field(default_factory=dict) - # Flattened per-forward insertion order (one entry per ViT block invocation). - attn_params: dict[int, list[tuple]] = field(default_factory=dict) - - -_encoder_graph_params: EncoderGraphParams | None = None - - -def set_encoder_graph_params(token_budgets: list[int]) -> None: - global _encoder_graph_params - budgets_sorted_unique = sorted(token_budgets) - _encoder_graph_params = EncoderGraphParams( - events={b: [] for b in budgets_sorted_unique}, - workspaces={b: None for b in budgets_sorted_unique}, - handles={b: [] for b in budgets_sorted_unique}, - attn_params={b: [] for b in budgets_sorted_unique}, - ) - - -def get_encoder_graph_params() -> EncoderGraphParams | None: - return _encoder_graph_params - - -def update_encoder_graph_workspace(token_budget: int, workspace: torch.Tensor) -> None: - if _encoder_graph_params is None: - return - _encoder_graph_params.workspaces[token_budget] = workspace - - -# --------------------------------------------------------------------------- -# Capture / replay runtime state (thread-local module singleton) -# --------------------------------------------------------------------------- - - -@dataclass -class EncoderForwardContext: - """Vision encoder NPUGraph runtime flags and host-side FIA arguments. - - Captured tensors stay on device; FIA ``graph_task_update`` needs Python ``list[int]`` - lengths that are refreshed each replay from encoder metadata buffers on device (see RFC). - """ - - token_budget: int | None = None - capturing: bool = False - capture_layer_cursor: int = 0 - cu_seqlens_cpu: torch.Tensor | None = None - cu_window_seqlens_cpu: torch.Tensor | None = None - sequence_lengths_cpu: torch.Tensor | None = None - - -_context = EncoderForwardContext() - - -def get_encoder_forward_context() -> EncoderForwardContext: - return _context - - -def _reset_encoder_forward_context() -> None: - """Clear replay-time host length fields.""" - - _context.token_budget = None - _context.capturing = False - _context.cu_seqlens_cpu = None - _context.cu_window_seqlens_cpu = None - _context.sequence_lengths_cpu = None - - -@contextmanager -def set_encoder_forward_context( - token_budget: int, - capturing: bool, - *, - cu_seqlens_cpu: list[int] | None = None, - cu_window_seqlens_cpu: list[int] | None = None, - sequence_lengths_cpu: list[int] | None = None, -): - """Enter encoder graph replay (FIA host args): callers must pass lengths each time. - - On exit, replay host fields are **cleared** (not restored). Lists must not be reused - across replays without repopulating from the current batch buffers. - """ - - _context.token_budget = token_budget - _context.capturing = capturing - _context.cu_seqlens_cpu = cu_seqlens_cpu - _context.cu_window_seqlens_cpu = cu_window_seqlens_cpu - _context.sequence_lengths_cpu = sequence_lengths_cpu - _context.capture_layer_cursor = 0 - try: - yield _context - finally: - _reset_encoder_forward_context() - - -# --------------------------------------------------------------------------- -# Replay-time FIA task updates -# --------------------------------------------------------------------------- - - -def _pad_actual_seq_lengths_for_fia(actual_seq_lengths: list[int], num_tokens: int) -> list[int]: - """TND FIA requires ``query.shape[0] == actual_seq_lengths[-1]``.""" - if not actual_seq_lengths or actual_seq_lengths[-1] != num_tokens: - actual_seq_lengths.append(num_tokens) - return actual_seq_lengths - - -def _maybe_compute_actual_seq_lengths( - *, - num_query_tokens: int, - uses_seq_len_host: bool, - vit_layer_idx: int, - fullatt_block_indexes: set[int] | frozenset[int] | None, -) -> tuple[list[int], list[int]]: - context = get_encoder_forward_context() - if uses_seq_len_host: - if context.sequence_lengths_cpu is None: - raise RuntimeError("context.sequence_lengths_cpu is None during encoder replay.") - actual = context.sequence_lengths_cpu.cumsum(0).to(torch.int64).tolist() - elif fullatt_block_indexes is not None: - if vit_layer_idx in fullatt_block_indexes: - if context.cu_seqlens_cpu is None: - raise RuntimeError("context.cu_seqlens_cpu is None during encoder replay.") - actual = context.cu_seqlens_cpu[1:].to(torch.int64).tolist() - else: - if context.cu_window_seqlens_cpu is None: - raise RuntimeError("context.cu_window_seqlens_cpu is None during encoder replay.") - actual = context.cu_window_seqlens_cpu[1:].to(torch.int64).tolist() - else: - if context.cu_seqlens_cpu is None: - raise RuntimeError("context.cu_seqlens_cpu is None during encoder replay.") - actual = context.cu_seqlens_cpu[1:].to(torch.int64).tolist() - - aligned = _pad_actual_seq_lengths_for_fia(actual, num_query_tokens) - return aligned, aligned - - -def update_encoder_graph_params( - update_stream: torch.npu.Stream, - token_budget: int, - *, - fullatt_block_indexes: set[int] | frozenset[int] | None = None, -) -> None: - """Re-bind fused infer attention host tensors inside the encoder NPUGraph (parallel to LLM path). - - Qwen2.5-VL: layers listed in ``fullatt_block_indexes`` use ``cu_seqlens`` host endpoints; - others use ``cu_window_seqlens``. Those layouts are **not** baked at capture — only here. - - This deliberately bypasses :class:`AttentionBackend` — ViT attention is not registered there — but reuses - the same ``graph_task_update_{begin,end}`` + ``ExternalEvent`` ordering pattern as - :meth:`AscendAttentionBackendImpl.update_graph_params`. - """ - - params = get_encoder_graph_params() - if params is None or token_budget not in params.handles: - return - - handles = params.handles[token_budget] - events = params.events[token_budget] - attn_blocks = params.attn_params[token_budget] - workspace = params.workspaces.get(token_budget) - - if len(handles) != len(events) or len(handles) != len(attn_blocks): - raise RuntimeError( - "Encoder graph bookkeeping is inconsistent: " - f"budget={token_budget} handles={len(handles)} " - f"events={len(events)} attn_blocks={len(attn_blocks)}" - ) - - with torch.npu.stream(update_stream): - for handle, event, packed in zip(handles, events, attn_blocks): - ( - query, - key, - value, - block_table, - attn_mask, - block_size, - uses_sequence_lengths_host, - vit_layer_idx, - num_kv_heads, - num_heads, - scale, - output, - softmax_lse, - ) = packed - - num_query_tokens = query.shape[0] - actual_seq_lengths_q, actual_seq_lengths_kv = _maybe_compute_actual_seq_lengths( - num_query_tokens=num_query_tokens, - uses_seq_len_host=uses_sequence_lengths_host, - vit_layer_idx=vit_layer_idx, - fullatt_block_indexes=fullatt_block_indexes, - ) - - torch.npu.graph_task_update_begin(update_stream, handle) - torch_npu.npu_fused_infer_attention_score.out( - query=query, - key=key, - value=value, - atten_mask=attn_mask, - block_table=block_table, - input_layout="TND", - block_size=block_size, - actual_seq_lengths=actual_seq_lengths_q, - actual_seq_lengths_kv=actual_seq_lengths_kv, - num_key_value_heads=num_kv_heads, - num_heads=num_heads, - scale=scale, - sparse_mode=0, - workspace=workspace, - out=[output, softmax_lse], - ) - torch.npu.graph_task_update_end(update_stream) - event.record(update_stream) - - -# --------------------------------------------------------------------------- -# Encoder NPUGraph manager -# --------------------------------------------------------------------------- -class EncoderAclGraphManager(EncoderCudaGraphManager): - """Hooks encoder capture/replay into Ascend FIA graph-task infrastructure.""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - self.graph_pool = current_platform.get_global_graph_pool() - self.update_stream: torch.npu.Stream | None = None - visual = getattr(self.model, "visual", None) - fa_raw = getattr(visual, "fullatt_block_indexes", None) if visual is not None else None - self.fullatt = frozenset(fa_raw) if fa_raw is not None else None - - def capture(self, graph_pool: Any | None = None): - encoder_graph_pool = graph_pool if graph_pool is not None else self.graph_pool - self.graph_pool = encoder_graph_pool - - set_encoder_graph_params(self.token_budgets) - - if _ENCODER_CAPTURE_SUPPORTS_GRAPH_POOL: - super().capture(graph_pool=encoder_graph_pool) - else: - super().capture() - - weak_ref_encoder_graph_workspaces() - - def _capture_budget_graph(self, token_budget: int): - logger.debug( - "Capturing encoder aclgraph for budget=%d, max_batch_size=%d, max_frames_per_batch=%d", - token_budget, - self.max_batch_size, - self.max_frames_per_batch, - ) - - capture_inputs = self.model.prepare_encoder_cudagraph_capture_inputs( - token_budget, - self.max_batch_size, - self.max_frames_per_batch, - self.device, - self.dtype, - ) - - mm_kwargs = capture_inputs.mm_kwargs - buffers = capture_inputs.buffers - - with torch.inference_mode(): - output = self.model.encoder_cudagraph_forward(mm_kwargs, buffers) - output_buffer = torch.empty_like(output) - - graph = torch.npu.NPUGraph() - with ( - set_encoder_forward_context(token_budget, True), - torch.inference_mode(), - torch.npu.graph(graph, self.graph_pool), - ): - output = self.model.encoder_cudagraph_forward(mm_kwargs, buffers) - output_buffer.copy_(output) - - input_key = self.config.input_key_by_modality["image"] - self.budget_graphs[token_budget] = BudgetGraphMetadata( - token_budget=token_budget, - max_batch_size=self.max_batch_size, - max_frames_per_batch=self.max_frames_per_batch, - graph=graph, - input_buffer=mm_kwargs[input_key], - metadata_buffers=buffers, - output_buffer=weak_ref_tensors(output_buffer), - ) - - def _run_budget_graph( - self, - mm_kwargs: dict[str, Any], - token_budget: int, - replay_buffers: dict[str, torch.Tensor | None], - ) -> torch.Tensor | None: - num_items = len(self._get_item_specs(mm_kwargs)) - if token_budget not in self.budget_graphs: - self.graph_misses += num_items - return None - - graph_meta = self.budget_graphs[token_budget] - - input_key = self.config.input_key_by_modality[self.model.get_input_modality(mm_kwargs)] - src = mm_kwargs[input_key] - n = src.shape[0] - graph_meta.input_buffer[:n].copy_(src) - - for key in self.config.buffer_keys: - src_buf = replay_buffers.get(key) - if src_buf is None: - continue - buf = graph_meta.metadata_buffers[key] - if src_buf.ndim == 0: - buf.copy_(src_buf) - else: - slice_n = src_buf.shape[0] - buf.zero_() - buf[:slice_n].copy_(src_buf) - - meta = graph_meta.metadata_buffers - cu_seqlens_cpu = None if meta.get("cu_seqlens") is None else meta.get("cu_seqlens").cpu() - cu_window_seqlens_cpu = None if meta.get("cu_window_seqlens") is None else meta.get("cu_window_seqlens").cpu() - seq_lens_cpu = None if meta.get("sequence_lengths") is None else meta.get("sequence_lengths").cpu() - - update_stream = self.update_stream - if update_stream is None: - update_stream = torch.npu.Stream() - - graph_meta.graph.replay() - - with set_encoder_forward_context( - token_budget, - False, - cu_seqlens_cpu=cu_seqlens_cpu, - cu_window_seqlens_cpu=cu_window_seqlens_cpu, - sequence_lengths_cpu=seq_lens_cpu, - ): - update_encoder_graph_params(update_stream, token_budget, fullatt_block_indexes=self.fullatt) - - self.graph_hits += num_items - return graph_meta.output_buffer - - -def weak_ref_encoder_graph_workspaces() -> None: - params = get_encoder_graph_params() - if params is None: - return - for budget, ws in list(params.workspaces.items()): - if ws is None: - continue - params.workspaces[budget] = weak_ref_tensors(ws) diff --git a/vllm_ascend/worker/model_runner_v1.py b/vllm_ascend/worker/model_runner_v1.py index 256d34a82..0d27624dd 100644 --- a/vllm_ascend/worker/model_runner_v1.py +++ b/vllm_ascend/worker/model_runner_v1.py @@ -17,7 +17,6 @@ # Adapted from vllm-project/vllm/vllm/worker/gpu_model_runner.py # -import gc import math import sys import time @@ -52,11 +51,7 @@ from vllm.utils.math_utils import cdiv, round_up from vllm.utils.mem_utils import DeviceMemoryProfiler from vllm.utils.torch_utils import get_dtype_size -from vllm.v1.attention.backend import ( - AttentionBackend, - AttentionCGSupport, - AttentionMetadata, -) +from vllm.v1.attention.backend import AttentionBackend, AttentionMetadata from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadataBuilder from vllm.v1.attention.backends.utils import CommonAttentionMetadata from vllm.v1.attention.selector import get_attn_backend # type: ignore @@ -64,7 +59,6 @@ from vllm.v1.kv_cache_interface import ( AttentionSpec, EncoderOnlyAttentionSpec, - HiddenStateCacheSpec, KVCacheConfig, KVCacheGroupSpec, KVCacheSpec, @@ -80,13 +74,18 @@ LogprobsLists, LogprobsTensors, ModelRunnerOutput, - RoutedExpertsLists, SamplerOutput, make_empty_encoder_model_runner_output, ) +from vllm.v1.worker.utils import select_common_block_size + +from vllm_ascend.utils import AscendDeviceType, get_ascend_device_type, vllm_version_is + +if not vllm_version_is("0.20.2"): + from vllm.v1.outputs import RoutedExpertsLists from vllm.v1.sample.logits_processor import build_logitsprocs from vllm.v1.sample.metadata import SamplingMetadata -from vllm.v1.sample.rejection_sampler import PLACEHOLDER_TOKEN_ID, RejectionSampler +from vllm.v1.sample.rejection_sampler import RejectionSampler from vllm.v1.spec_decode.metadata import SpecDecodeMetadata from vllm.v1.spec_decode.ngram_proposer_gpu import copy_num_valid_draft_tokens from vllm.v1.structured_output.utils import apply_grammar_bitmask @@ -100,7 +99,7 @@ UBatchSlices, maybe_create_ubatch_slices, ) -from vllm.v1.worker.utils import AttentionGroup, select_common_block_size +from vllm.v1.worker.utils import AttentionGroup # yapf: enable from vllm_ascend.ascend_config import get_ascend_config @@ -114,7 +113,6 @@ # yapf: disable from vllm_ascend.compilation.acl_graph import ( ACLGraphWrapper, - reset_graph_params, set_draft_graph_params, set_graph_params, update_full_graph_params, @@ -141,16 +139,13 @@ from vllm_ascend.spec_decode.suffix_proposer import AscendSuffixDecodingProposer from vllm_ascend.spec_decode.utils import update_num_computed_tokens_for_batch_change from vllm_ascend.utils import ( - AscendDeviceType, calc_split_factor, check_gdn_layer, enable_sp, enable_sp_by_pass, - get_ascend_device_type, get_c_env, get_compressed_pos_and_indices, global_stream, - is_hidden_state_cache_spec, kv_cache_spec_uses_sparse_c8, lmhead_tp_enable, set_weight_prefetch_method, @@ -158,7 +153,6 @@ ) from vllm_ascend.worker.npu_input_batch import NPUInputBatch from vllm_ascend.worker.pcp_utils import PCPManager -from vllm_ascend.worker.utils import AscendKVBlockZeroer from vllm_ascend.ascend_forward_context import ( # isort: skip MoECommType, @@ -168,6 +162,7 @@ set_mc2_mask, set_mc2_tokens_capacity, ) +from vllm.model_executor.layers.fused_moe.routed_experts_capturer import RoutedExpertsCapturer from vllm_ascend.sample.rejection_sampler import AscendRejectionSampler @@ -271,21 +266,6 @@ def __init__(self, vllm_config: VllmConfig, device: torch.device): with _torch_cuda_wrapper(): super().__init__(vllm_config, device) - # Replace the CUDA PrefetchOffloader set by parent __init__ with NPU version. - offload_cfg = vllm_config.offload_config - if (offload_cfg is not None - and getattr(offload_cfg, "prefetch", None) is not None - and getattr(offload_cfg.prefetch, "offload_group_size", 0) > 0): - from vllm.model_executor.offloader.base import set_offloader - - from vllm_ascend.model_executor.offloader.prefetch import NPUPrefetchOffloader - set_offloader(NPUPrefetchOffloader( - group_size=offload_cfg.prefetch.offload_group_size, - num_in_group=offload_cfg.prefetch.offload_num_in_group, - prefetch_step=offload_cfg.prefetch.offload_prefetch_step, - offload_params=offload_cfg.prefetch.offload_params, - )) - # NOTE: For FULL mode we change +1 to +2 to reserve extra space for padding. # See _pad_query_start_loc_for_fia. self.query_start_loc = self._make_buffer( @@ -392,15 +372,6 @@ def __init__(self, vllm_config: VllmConfig, device: torch.device): and self.model_config.is_mm_prefix_lm, ) - # reinit valid_sampled_token_count_cpu with torch.int64 dtype - if self.use_async_scheduling and self.num_spec_tokens: - self.valid_sampled_token_count_cpu = torch.empty( - self.max_num_reqs, - dtype=torch.int64, - device="cpu", - pin_memory=self.pin_memory, - ) - try: self.dcp_size = get_dcp_group().world_size self.dcp_rank = get_dcp_group().rank_in_group @@ -641,20 +612,10 @@ def _sync_metadata_across_dp( num_tokens_after_padding = torch.tensor([num_tokens] * self.dp_size, device="cpu", dtype=torch.int32) return num_tokens, num_tokens_after_padding, cudagraph_mode - # On certain devices, CPU-side all_reduce may return dirty data. - # When dp_allreduce_on_npu is True, route DP metadata - # synchronization through the NPU device group to avoid data corruption. - device_str, group = ( - ("npu", get_dp_group().device_group) - if self.ascend_config.dp_allreduce_on_npu - else ("cpu", get_dp_group().cpu_group) - ) - packed_tensor = torch.zeros(2, self.dp_size, device=device_str, dtype=torch.int32) + packed_tensor = torch.zeros(2, self.dp_size, device="cpu", dtype=torch.int32) packed_tensor[0][self.dp_rank] = num_tokens packed_tensor[1][self.dp_rank] = cudagraph_mode.value - dist.all_reduce(packed_tensor, group=group) - if device_str == "npu": - packed_tensor = packed_tensor.cpu() + dist.all_reduce(packed_tensor, group=get_dp_group().cpu_group) # Unpack the results num_tokens_across_dp = packed_tensor[0, :] @@ -1043,24 +1004,23 @@ def _prepare_inputs( # CPU values are optimistic (all drafts accepted). The kernel # corrects on GPU using the previous step's # valid_sampled_token_count_gpu. Otherwise, just copy from CPU. - if self.use_async_spec_decode: - computed_token_tensor_cpu = self.input_batch.num_computed_tokens_cpu_tensor[:num_reqs].to( - device=self.device, non_blocking=True - ) if ( self.use_async_spec_decode - and self.valid_sampled_token_count_gpu is not None # type: ignore[has-type] + and self.valid_sampled_token_count_gpu is not None and prev_req_id_to_index ): self.prev_positions.copy_to_gpu(num_reqs) self.prev_num_draft_tokens.copy_to_gpu() + cpu_values = self.input_batch.num_computed_tokens_cpu_tensor[:num_reqs].to( + device=self.device, non_blocking=True + ) update_num_computed_tokens_for_batch_change( self.num_computed_tokens, self.num_accepted_tokens.gpu[:num_reqs], self.prev_positions.gpu[:num_reqs], - self.valid_sampled_token_count_gpu, # type: ignore[has-type] + self.valid_sampled_token_count_gpu, self.prev_num_draft_tokens.gpu, - computed_token_tensor_cpu, + cpu_values, ) else: self.num_computed_tokens[:num_reqs].copy_( @@ -1076,58 +1036,12 @@ def _prepare_inputs( self.num_scheduled_tokens.np[:num_reqs] = num_scheduled_tokens self.num_scheduled_tokens.copy_to_gpu(num_reqs) num_scheduled_tokens_gpu = self.num_scheduled_tokens.gpu[:num_reqs] - - # Rebuild CP/spec inputs after async accepted-token correction. - has_decode_req = bool(np.any( - self.input_batch.num_computed_tokens_cpu[:num_reqs] - >= self.input_batch.num_prompt_tokens[:num_reqs] - )) - should_rebuild_async_inputs = ( - self.use_cp - and self.use_async_spec_decode - and self.valid_sampled_token_count_gpu is not None # type: ignore[has-type] - and prev_req_id_to_index - and has_decode_req - ) - base_num_computed_tokens_np = None - if should_rebuild_async_inputs: - # Async spec decode corrects num_computed_tokens on device. - # Rebuild CPU-side inputs from the corrected positions. - corrected_num_computed_tokens_np = ( - self.num_computed_tokens[:num_reqs].cpu().numpy() - ) - - # Mixed batch: only decode requests use async-corrected lengths. - # Prefill requests keep CPU-side prompt progress. - base_num_computed_tokens_np = ( - self.input_batch.num_computed_tokens_cpu[:num_reqs].copy() - ) - num_decode_reqs = self.pcp_manager.num_decode_reqs - base_num_computed_tokens_np[:num_decode_reqs] = ( - corrected_num_computed_tokens_np[:num_decode_reqs] - ) - - position_offsets = ( - position_pcp - if self.pcp_size > 1 - else self.query_pos.np - ) - - self._rebuild_input_ids_with_corrected_positions( - scheduler_output, - num_reqs, - total_num_scheduled_tokens, - req_indices, - position_offsets, - positions_np, - cu_num_tokens, - base_num_computed_tokens_np, - ) - - if self.pcp_size > 1 or should_rebuild_async_inputs: - # PCP and async rebuild both compute the correct positions on CPU. - # Copy positions_np to GPU so input_ids and positions stay aligned. - + # fix prefix cache ci test + if self.pcp_size > 1: + # When PCP (Prefill Context Parallel) is enabled, positions use + # special PCP offsets (position_pcp) that are only computed on CPU. + # Copy the correctly-computed CPU positions to GPU instead of + # recomputing on GPU (which would miss the PCP offsets). self.positions[:total_num_scheduled_tokens].copy_( torch.from_numpy( positions_np[:total_num_scheduled_tokens] @@ -1139,58 +1053,11 @@ def _prepare_inputs( self.num_computed_tokens[req_indices_gpu].to(torch.int64) + self.query_pos.gpu[:total_num_scheduled_tokens] ) - self.seq_lens[:num_reqs] = ( self.num_computed_tokens[:num_reqs] + num_scheduled_tokens_gpu ) self.seq_lens[num_reqs:].fill_(0) - if should_rebuild_async_inputs: - req_indices_full = self.pcp_manager.async_rebuild_req_indices_full - cu_num_tokens_full = self.pcp_manager.async_rebuild_cu_num_tokens_full - num_tokens_full = self.pcp_manager.async_rebuild_num_tokens_full - - assert base_num_computed_tokens_np is not None - base = base_num_computed_tokens_np - - token_counts = np.diff(np.concatenate(([0], cu_num_tokens_full))) - token_starts = np.repeat(cu_num_tokens_full - token_counts, token_counts) - query_pos = self.arange_np[:num_tokens_full] - token_starts - - positions_full = np.empty(num_tokens_full, dtype=np.int64) - np.add(base[req_indices_full], query_pos, out=positions_full) - - if self.pcp_size > 1: - pre_pcp_query_start_loc = torch.zeros( - num_reqs + 1, - dtype=torch.int32, - device=self.device, - ) - pre_pcp_query_start_loc[1 : num_reqs + 1] = torch.from_numpy( - cu_num_tokens_full - ).to(dtype=torch.int32, device=self.device) - - self.input_batch.block_table.compute_slot_mapping( - num_reqs, - pre_pcp_query_start_loc, - torch.from_numpy(positions_full).to(self.device), - ) - - self.pcp_manager.generate_pcp_mtp_input( - num_tokens_full, - scheduler_output.num_scheduled_tokens, - with_prefill, - self.input_batch, - self.arange_np, - req_indices_full, - positions_full, - cu_num_tokens_full, - self._draft_token_ids, # type: ignore[has-type] - scheduler_output, - self.num_spec_tokens, - precomputed_positions_np=positions_full, - ) - # In async spec decode mode, num_computed_tokens was corrected on GPU # by update_num_computed_tokens_for_batch_change, so seq_lens (GPU) is # correct but optimistic_seq_lens_cpu is stale (it assumed all drafts @@ -1201,7 +1068,7 @@ def _prepare_inputs( if ( self._needs_seq_lens_cpu_sync and self.use_async_spec_decode - and self.valid_sampled_token_count_gpu is not None # type: ignore[has-type] + and self.valid_sampled_token_count_gpu is not None and prev_req_id_to_index ): self.optimistic_seq_lens_cpu[:num_reqs].copy_( @@ -1220,7 +1087,7 @@ def _prepare_inputs( if ( self.use_compress and self.use_async_spec_decode - and self.valid_sampled_token_count_gpu is not None # type: ignore[has-type] + and self.valid_sampled_token_count_gpu is not None and prev_req_id_to_index ): # Async spec decode keeps the CPU counter optimistic until after @@ -1262,7 +1129,9 @@ def _prepare_inputs( if self.use_async_spec_decode and (self.uses_mrope or self.uses_xdrope_dim > 0): drift = self.num_computed_tokens[req_indices_gpu].to( torch.int64 - ) - computed_token_tensor_cpu[req_indices_gpu] + ) - self.input_batch.num_computed_tokens_cpu_tensor[req_indices].to( + device=self.device, dtype=torch.int64, non_blocking=True + ) target = self.mrope_positions if self.uses_mrope else self.xdrope_positions target.gpu[:, :total_num_scheduled_tokens] += drift @@ -1347,45 +1216,6 @@ def _prepare_inputs( num_scheduled_tokens_compressed_list ) - def _rebuild_input_ids_with_corrected_positions( - self, - scheduler_output, - num_reqs, - total_num_scheduled_tokens, - req_indices, - position_offsets, - positions_np, - cu_num_tokens, - base_num_computed_tokens_np, - ) -> None: - # base_num_computed_tokens_np contains per-request starts: - # decode requests use async-corrected lengths, prefill requests use CPU lengths. - base = base_num_computed_tokens_np - np.add( - base[req_indices], - position_offsets[:total_num_scheduled_tokens], - out=positions_np, - ) - - token_indices = ( - positions_np[:total_num_scheduled_tokens] - + req_indices * self.input_batch.token_ids_cpu.shape[1] - ) - torch.index_select( - self.input_batch.token_ids_cpu_tensor.flatten(), - 0, - torch.from_numpy(token_indices), - out=self.input_ids.cpu[:total_num_scheduled_tokens], - ) - - self.input_ids.copy_to_gpu(total_num_scheduled_tokens) - self._prepare_input_ids( - scheduler_output, - num_reqs, - total_num_scheduled_tokens, - cu_num_tokens, - ) - def _preprocess( self, scheduler_output: "SchedulerOutput", @@ -1518,23 +1348,6 @@ def _build_attn_state(self, num_reqs, num_scheduled_tokens, num_valid_tokens): return attn_state - def _sanitize_placeholder_input_ids_for_forward( - self, - scheduler_output: "SchedulerOutput", - num_forward_tokens: int, - ) -> None: - scheduled_spec_tokens = scheduler_output.scheduled_spec_decode_tokens - if not scheduled_spec_tokens: - return - if not any( - PLACEHOLDER_TOKEN_ID in token_ids - for token_ids in scheduled_spec_tokens.values() - ): - return - - input_ids = self.input_ids.gpu[:num_forward_tokens] - input_ids.masked_fill_(input_ids == PLACEHOLDER_TOKEN_ID, 0) - def _calc_spec_decode_metadata( self, num_draft_tokens: np.ndarray, @@ -1621,8 +1434,8 @@ def _copy_valid_sampled_token_count( # Initialize a new stream to overlap the copy operation with # prepare_input of draft model. default_stream = torch.npu.current_stream() - with torch.npu.stream(self.valid_sampled_token_count_copy_stream): - self.valid_sampled_token_count_copy_stream.wait_stream(default_stream) + with torch.npu.stream(self.valid_sampled_token_count_copy_stream): + self.valid_sampled_token_count_copy_stream.wait_stream(default_stream) counts = valid_sampled_tokens_count counts_cpu = self.valid_sampled_token_count_cpu assert counts_cpu is not None @@ -1763,7 +1576,6 @@ def propose_draft_token_ids( self.discard_request_indices.gpu, self.num_discarded_requests, ) - self._copy_valid_sampled_token_count(next_token_ids, valid_sampled_tokens_count) req_scheduled_tokens = scheduler_output.num_scheduled_tokens if self.use_cp: @@ -1861,6 +1673,8 @@ def propose_draft_token_ids( num_scheduled_tokens=num_scheduled_tokens, num_rejected_tokens_gpu=num_rejected_tokens_gpu, ) + if not self.vllm_config.speculative_config.disable_padded_drafter_batch: + self._copy_valid_sampled_token_count(next_token_ids, valid_sampled_tokens_count) else: raise ValueError(f"Unknown speculative decoding method: {self.speculative_config.method}") @@ -1903,7 +1717,11 @@ def execute_model( intermediate_tensors: IntermediateTensors | None = None, ) -> ModelRunnerOutput | IntermediateTensors | None: if self.vllm_config.model_config.enable_return_routed_experts: - if self.routed_experts_initialized: + if vllm_version_is("0.20.2"): + capturer = RoutedExpertsCapturer.get_instance() + if capturer is not None: + capturer.clear_buffer() + elif self.routed_experts_initialized: self.routed_experts_capturer.clear_buffer() if self.ascend_config.profiling_chunk_config.need_timing: @@ -2091,8 +1909,12 @@ def execute_model( if deferred_state_corrections_fn: deferred_state_corrections_fn() deferred_state_corrections_fn = None - mamba_bufs = self._get_mamba_bufs() - preprocess_bufs = mamba_bufs.preprocess + if vllm_version_is("0.20.2"): + mamba_bufs = self._get_mamba_copy_bufs() + preprocess_bufs = mamba_bufs + else: + mamba_bufs = self._get_mamba_bufs() + preprocess_bufs = mamba_bufs.preprocess mamba_utils.preprocess_mamba( scheduler_output, self.kv_cache_config, @@ -2113,7 +1935,7 @@ def execute_model( ) self.num_accepted_tokens.copy_to_gpu(num_reqs) - if mamba_bufs.postprocess_align is not None: + if not vllm_version_is("0.20.2") and mamba_bufs.postprocess_align is not None: mamba_utils.stage_postprocess_inputs_to_gpu( mamba_bufs.postprocess_align, scheduler_output, @@ -2167,13 +1989,6 @@ def execute_model( num_scheduled_tokens_compressed_list=num_scheduled_tokens_compressed_list, ) - self._sanitize_placeholder_input_ids_for_forward( - scheduler_output, - num_tokens_padded - if not (self.use_cp and self.pcp_manager.pcp_use_hybrid_attn) - else total_num_scheduled_tokens, - ) - ( input_ids, inputs_embeds, @@ -2183,7 +1998,9 @@ def execute_model( ec_connector_output, ) = self._preprocess( scheduler_output, - num_tokens_padded, + num_tokens_padded + if not (self.use_cp and self.pcp_manager.pcp_use_hybrid_attn) + else total_num_scheduled_tokens, intermediate_tensors, ) @@ -2419,6 +2236,10 @@ def propose_draft_token_ids(sampled_token_ids): with record_function_or_nullcontext("draft_token"): if self.speculative_config: + input_fits_in_drafter = spec_decode_common_attn_metadata is not None and ( + spec_decode_common_attn_metadata.max_seq_len + self.num_spec_tokens + <= self.effective_drafter_max_model_len + ) use_padded_batch = ( self.speculative_config and ( @@ -2432,8 +2253,27 @@ def propose_draft_token_ids(sampled_token_ids): if use_padded_batch: # EAGLE speculative decoding can use the GPU sampled tokens # as inputs, and does not need to wait for bookkeeping to finish. - propose_draft_token_ids(sampler_output.sampled_token_ids) - if self.speculative_config and not use_padded_batch: + sampled_token_ids = sampler_output.sampled_token_ids + if input_fits_in_drafter: + propose_draft_token_ids(sampler_output.sampled_token_ids) + elif self.valid_sampled_token_count_event is not None: + assert spec_decode_common_attn_metadata is not None + if self.drafter is not None: # Fix mypy type check for drafter None check + next_token_ids, valid_sampled_tokens_count = self.drafter.prepare_next_token_ids_padded( + sampled_token_ids, + self.requests, + self.input_batch, + self.discard_request_indices.gpu, + self.num_discarded_requests, + ) + self._copy_valid_sampled_token_count( + next_token_ids, valid_sampled_tokens_count + ) + self._draft_token_ids = torch.zeros( + 1, device=self.device, dtype=torch.int32 + ).expand(len(self.input_batch.req_ids), self.num_spec_tokens) + self._copy_draft_token_ids_to_cpu(scheduler_output, zeros_only=True) + if self.speculative_config and not use_padded_batch and input_fits_in_drafter: # ngram and other speculative decoding methods use the sampled # tokens on the CPU, so they are run after bookkeeping. propose_draft_token_ids(valid_sampled_token_ids) @@ -2446,7 +2286,11 @@ def propose_draft_token_ids(sampled_token_ids): routed_experts_lists = None if self.model_config.enable_return_routed_experts: - if self.routed_experts_initialized: + if vllm_version_is("0.20.2"): + capturer = RoutedExpertsCapturer.get_instance() + if capturer is not None: + capturer.save_captured_experts(indices=self.cpu_slot_mapping) + elif self.routed_experts_initialized: buf = self.routed_experts_capturer.get_device_buffer() total = scheduler_output.total_num_scheduled_tokens self.routed_experts_cpu[:total].copy_(buf[:total], non_blocking=True) @@ -2470,7 +2314,9 @@ def propose_draft_token_ids(sampled_token_ids): pooler_output=[], ec_connector_output=ec_connector_output if self.supports_mm_inputs else None, cudagraph_stats=cudagraph_stats, - routed_experts=routed_experts_lists, + **( + {} if vllm_version_is("0.20.2") else {"routed_experts": routed_experts_lists} + ), ) if self.ascend_config.profiling_chunk_config.need_timing and hasattr(self, '_execution_start_time'): self._sync_device() @@ -2517,8 +2363,8 @@ def propose_draft_token_ids(sampled_token_ids): # overwrite _sample for lmhead_tp_enable and need_accepted_tokens def _sample(self, logits, spec_decode_metadata): # Sample the next token and get logprobs if needed. - self.input_batch.update_async_output_token_ids() sampling_metadata = self.input_batch.sampling_metadata + self.input_batch.update_async_output_token_ids() if spec_decode_metadata is None: if lmhead_tp_enable() and logits is not None: logits = logits[: self.input_batch.num_reqs] @@ -2801,7 +2647,7 @@ def sync_and_gather_intermediate_tensors( intermediate_tensors: IntermediateTensors | None, sync_self: bool, ) -> IntermediateTensors: - # vllm renamed sync_and_slice to sync_and_gather. + # vllm renamed sync_and_slice to sync_and_gather in v0.20.2. # The Ascend override logic is identical: skip the upstream all_gather # (flashcomm1 does not scatter residual before PP send). return self.sync_and_slice_intermediate_tensors( @@ -2952,12 +2798,6 @@ def _build_attention_metadata( def _get_pcp_metadata(block_table_tensor): if not self.use_cp: return None, block_table_tensor - - fixed_decode_seq_lens_cpu = None - if self.use_async_spec_decode: - fixed_decode_seq_lens_cpu = self.optimistic_seq_lens_cpu[:num_reqs].numpy() - - assert num_reqs_padded is not None return self.pcp_manager.generate_pcp_metadata( num_tokens, self.query_lens, @@ -2966,7 +2806,6 @@ def _get_pcp_metadata(block_table_tensor): block_table_tensor, num_reqs_padded, num_reqs, - fixed_decode_seq_lens_cpu, ) def _get_block_table_and_slot_mapping(kv_cache_gid: int, total_num_scheduled_tokens_compressed_list: list[int]): @@ -2996,8 +2835,8 @@ def _get_block_table_and_slot_mapping(kv_cache_gid: int, total_num_scheduled_tok else: blk_table = self.input_batch.block_table[kv_cache_gid] slot_mapping = blk_table.slot_mapping.gpu[:maybe_pcp_full_tokens] - self.cpu_slot_mapping = blk_table.slot_mapping.cpu[:maybe_pcp_full_tokens] - blk_table_tensor = blk_table.get_device_tensor()[:num_reqs_padded] + blk_table_tensor = blk_table.get_device_tensor()[:num_reqs_padded] + # Fill unused with -1. Needed for reshape_and_cache in full cuda # graph mode. `blk_table_tensor` -1 to match mamba PAD_SLOT_ID if self.pcp_size == 1: @@ -3024,7 +2863,9 @@ def _get_block_table_and_slot_mapping(kv_cache_gid: int, total_num_scheduled_tok kv_cache_gid, ) if self.model_config.enable_return_routed_experts and kv_cache_gid == 0: - if self.routed_experts_initialized: + if vllm_version_is("0.20.2"): + self.cpu_slot_mapping = slot_mapping.cpu().numpy() + elif self.routed_experts_initialized: # snapshot slot_mapping into a private device # buffer so the next ``_prepare_inputs`` does not # overwrite it while D2H is still pending. @@ -3083,7 +2924,6 @@ def _get_block_table_and_slot_mapping(kv_cache_gid: int, total_num_scheduled_tok max_seq_len=max_seq_len, block_table_tensor=block_table_gid_0, slot_mapping=slot_mapping_gid_0, - slot_mapping_cpu=self.cpu_slot_mapping, causal=True, is_prefilling=is_prefilling, num_input_tokens=num_tokens_padded, @@ -3630,9 +3470,6 @@ def mock_pass(param1, param2): self.model_memory_usage = m.consumed_memory logger.info("Loading model weights took %.4f GB", m.consumed_memory / float(2**30)) - from vllm.model_executor.offloader.base import get_offloader - get_offloader().post_init() - # wrap the model with full graph wrapper if needed. if self.compilation_config.cudagraph_mode.has_full_cudagraphs(): self.update_stream: torch.npu.Stream = torch.npu.Stream() @@ -3662,11 +3499,7 @@ def _finalize_dump_data(self, **kwargs) -> None: self.debugger.step(**kwargs) - def initialize_kv_cache( - self, - kv_cache_config: KVCacheConfig, - is_profiling: bool = False, - ) -> None: + def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None: """ Initialize KV cache based on `kv_cache_config`. Args: @@ -3680,7 +3513,7 @@ def initialize_kv_cache( self.may_add_encoder_only_layers_to_kv_cache_config() self.maybe_add_kv_sharing_layers_to_kv_cache_groups(kv_cache_config) # NOTE(cmq): initialize_attn_backend must before using self.attn_groups - self.initialize_attn_backend(kv_cache_config, is_profiling=is_profiling) + self.initialize_attn_backend(kv_cache_config) self.use_hybrid_blocks = len(self.attn_groups) > 1 # NOTE: Currently, we determine whether we need `num_accepted_tokens` through `MambaSpec`. self.need_accepted_tokens = any( @@ -3690,36 +3523,32 @@ def initialize_kv_cache( self.may_reinitialize_input_batch(kv_cache_config) kv_caches = self.initialize_kv_cache_tensors(kv_cache_config) # TODO: refactor the logic of attention - if ( - self.speculative_config - and self.drafter is not None - and ( - self.speculative_config.use_eagle() - or self.speculative_config.uses_draft_model() - ) + # Initialize drafter attention group initialization + if self.speculative_config and ( + self.speculative_config.use_eagle() or self.speculative_config.uses_draft_model() ): - assert isinstance( - self.drafter, - AscendEagleProposer | AscendDflashProposer | AscendDraftModelProposer, - ) + assert isinstance(self.drafter, AscendEagleProposer | AscendDflashProposer | AscendDraftModelProposer) block_size = (self.kernel_block_sizes[0] if isinstance( - self.kernel_block_sizes, list) else self.kernel_block_sizes) + self.kernel_block_sizes, list) else self.kernel_block_sizes) self.drafter.initialize_attn_backend(kv_cache_config, block_size) - if has_kv_transfer_group() and not is_profiling: + if has_kv_transfer_group(): get_kv_transfer_group().register_kv_caches(kv_caches) if self.model_config.enable_return_routed_experts: self.init_routed_experts_capturer() - def _bind_routed_experts_capturer(self, capturer=None) -> None: + def _bind_routed_experts_capturer(self, capturer) -> None: # Upstream binds via ``module.router.set_capture_fn(...)`` on # FusedMoE layers whose router is a ``BaseRouter``. Ascend's # ``select_experts`` does not go through ``BaseRouter``, so the # upstream hook never fires. Instead, stash the capturer as a # plain attribute on every FusedMoE layer; ``apply()`` reads it - # back on the hot path. + # back on the hot path. Only used on vLLM main (PR #39568+); + # the 0.20.2 path uses the ``RoutedExpertsCapturer.get_instance`` + # singleton and never calls this method. from vllm.model_executor.layers.fused_moe.layer import FusedMoE + for module in self.compilation_config.static_forward_context.values(): if isinstance(module, FusedMoE): module._ascend_routed_experts_capturer = capturer @@ -3813,88 +3642,6 @@ def _get_attention_kv_cache_dims(self, layer_name: str, kv_cache_spec: Attention head_size_v = kv_cache_spec.head_size_v if hasattr(kv_cache_spec, "head_size_v") else kv_cache_spec.head_size return kv_cache_spec.head_size, head_size_v - @staticmethod - def _align_up(value: int, alignment: int) -> int: - return (value + alignment - 1) // alignment * alignment - - def _allocate_int8_cache_tensor( - self, - numel: int, - alignment: int, - ) -> torch.Tensor: - """Allocate an int8 raw cache tensor. - - When KV transfer is enabled, the returned tensor's data_ptr is aligned - to `alignment`. This keeps the original Mooncake/ADXL alignment behavior. - """ - if numel <= 0: - raise ValueError(f"Invalid cache tensor size: {numel}") - - if self.vllm_config.kv_transfer_config is None: - return torch.zeros(numel, dtype=torch.int8, device=self.device) - - raw_tensor = torch.zeros( - numel + alignment, - dtype=torch.int8, - device=self.device, - ) - return self._align_memory(raw_tensor, alignment)[:numel] - - def _allocate_sparse_c8_indexer_tensors( - self, - dsa_k_tensor_size: int, - dsa_k_scale_tensor_size: int, - alignment: int, - scale_dtype: torch.dtype, - ) -> tuple[torch.Tensor, torch.Tensor]: - """Allocate dsa_k and dsa_k_scale from one aligned int8 raw allocation. - - Both returned tensors are logical views into the same underlying storage: - - sparse_c8_raw - ├── dsa_k_tensor int8 raw bytes - └── dsa_k_scale_tensor scale dtype raw bytes stored as int8 view - - `dsa_k_scale_tensor` is still returned as int8 raw storage. Later reshape - code should continue to use: - - raw_dsa_k_scale_tensor.view(scale_dtype).view(scale_shape) - - This reduces HCCL/Mooncake registration count because register_buffer - can merge these two views into one registered memory range. - """ - if dsa_k_tensor_size <= 0: - raise ValueError( - f"Invalid dsa_k_tensor_size: {dsa_k_tensor_size}" - ) - if dsa_k_scale_tensor_size <= 0: - raise ValueError( - f"Invalid dsa_k_scale_tensor_size: {dsa_k_scale_tensor_size}" - ) - - scale_dtype_size = torch.empty((), dtype=scale_dtype).element_size() - - # Ensure the scale view starts at an address aligned for scale_dtype. - scale_offset = self._align_up(dsa_k_tensor_size, scale_dtype_size) - total_raw_size = scale_offset + dsa_k_scale_tensor_size - - sparse_c8_raw_tensor = self._allocate_int8_cache_tensor( - total_raw_size, - alignment, - ) - - dsa_k_tensor = sparse_c8_raw_tensor[:dsa_k_tensor_size] - dsa_k_scale_tensor = sparse_c8_raw_tensor[ - scale_offset : scale_offset + dsa_k_scale_tensor_size - ] - - assert dsa_k_tensor.is_contiguous() - assert dsa_k_scale_tensor.is_contiguous() - assert dsa_k_scale_tensor.data_ptr() % scale_dtype_size == 0 - assert dsa_k_scale_tensor.numel() % scale_dtype_size == 0 - - return dsa_k_tensor, dsa_k_scale_tensor - def _allocate_kv_cache_tensors(self, kv_cache_config: KVCacheConfig) -> dict[str, torch.Tensor]: """ Initializes the KV cache buffer with the correct size. The buffer needs @@ -3935,7 +3682,6 @@ def _allocate_kv_cache_tensors(self, kv_cache_config: KVCacheConfig) -> dict[str "linear_attn" in layer_name or self.hybrid_with_attn_and_mamba or "cache_only_layers" in layer_name - or is_hidden_state_cache_spec(layer_kv_cache_spec.get(layer_name)) ) and layer_name not in kv_cache_raw_tensors: # for mamba linear attention, attn-linear hybrid, or cache_only_layers (extract_hidden_states) if self.vllm_config.kv_transfer_config is None: @@ -4014,42 +3760,39 @@ def _allocate_kv_cache_tensors(self, kv_cache_config: KVCacheConfig) -> dict[str if self.use_sparse and current_sparse_c8: dsa_k_scale_tensor_size = int(kv_cache_tensor.size // dsa_k_scale_tensor_split_factor) - # Allocate raw int8 tensors. Even bf16/fp16 KV cache entries - # are allocated as int8 raw bytes first and then viewed as - # the target dtype in _reshape_kv_cache_tensors. - dsa_k_tensor = None - dsa_k_scale_tensor = None - v_tensor = None - k_tensor = self._allocate_int8_cache_tensor( - k_tensor_size, - alignment, - ) - if v_tensor_size is not None: - v_tensor = self._allocate_int8_cache_tensor( - v_tensor_size, - alignment, - ) - - if self.use_sparse: - assert dsa_k_tensor_size is not None - - if current_sparse_c8: - assert dsa_k_scale_tensor_size is not None - - ( - dsa_k_tensor, - dsa_k_scale_tensor, - ) = self._allocate_sparse_c8_indexer_tensors( - dsa_k_tensor_size=dsa_k_tensor_size, - dsa_k_scale_tensor_size=dsa_k_scale_tensor_size, - alignment=alignment, - scale_dtype=current_kv_cache_spec.scale_dtype, + # for other attentions, e.g., self_attn, sliding window attn + if self.vllm_config.kv_transfer_config is None: + k_tensor = torch.zeros(k_tensor_size, dtype=torch.int8, device=self.device) + v_tensor = None + if v_tensor_size is not None: + v_tensor = torch.zeros(v_tensor_size, dtype=torch.int8, device=self.device) + #### for deepseek sparse attention + if dsa_k_tensor_size is not None: + dsa_k_tensor = torch.zeros(dsa_k_tensor_size, dtype=torch.int8, device=self.device) + if dsa_k_scale_tensor_size is not None: + dsa_k_scale_tensor = torch.zeros( + dsa_k_scale_tensor_size, dtype=torch.int8, device=self.device ) - else: - dsa_k_tensor = self._allocate_int8_cache_tensor( - dsa_k_tensor_size, - alignment, + else: + k_tensor = torch.zeros(k_tensor_size + alignment, dtype=torch.int8, device=self.device) + v_tensor = None + if v_tensor_size is not None: + v_tensor = torch.zeros(v_tensor_size + alignment, dtype=torch.int8, device=self.device) + v_tensor = self._align_memory(v_tensor, alignment)[:v_tensor_size] + k_tensor = self._align_memory(k_tensor, alignment)[:k_tensor_size] + #### for deepseek sparse attention + if dsa_k_tensor_size is not None: + dsa_k_tensor = torch.zeros( + dsa_k_tensor_size + alignment, dtype=torch.int8, device=self.device + ) + dsa_k_tensor = self._align_memory(dsa_k_tensor, alignment)[:dsa_k_tensor_size] + if dsa_k_scale_tensor_size is not None: + dsa_k_scale_tensor = torch.zeros( + dsa_k_scale_tensor_size + alignment, dtype=torch.int8, device=self.device ) + dsa_k_scale_tensor = self._align_memory( + dsa_k_scale_tensor, alignment + )[:dsa_k_scale_tensor_size] for layer_name_inner in kv_cache_tensor.shared_by: # shared the attn kvcache for all shared layers @@ -4229,20 +3972,12 @@ def _reshape_kv_cache_tensors( layer_name] assert raw_dsa_k_tensor is not None sum_page_size_bytes = raw_k_tensor.numel() + raw_v_tensor.numel() + raw_dsa_k_tensor.numel() - elif ( - self.use_hybrid_blocks - and self.hybrid_with_attn_and_mamba - and "cache_only_layers" not in layer_name - and not is_hidden_state_cache_spec(current_kv_cache_spec) - ): + elif self.use_hybrid_blocks and self.hybrid_with_attn_and_mamba: # Currently, we ensure that the same kvcache format is used even if there # is no shared layer, such as the full attention mtp layer of qwen3.5, etc. raw_k_tensor, raw_v_tensor = kv_cache_raw_tensors[layer_name], kv_cache_raw_tensors[layer_name] sum_page_size_bytes = raw_k_tensor.numel() - elif ( - "cache_only_layers" in layer_name - or is_hidden_state_cache_spec(current_kv_cache_spec) - ): + elif "cache_only_layers" in layer_name: # Single tensor for extract_hidden_states (no K/V split) raw_tensor = kv_cache_raw_tensors[layer_name] assert raw_tensor is not None @@ -4255,26 +3990,7 @@ def _reshape_kv_cache_tensors( current_kv_cache_spec.num_kv_heads, current_kv_cache_spec.head_size, ) - raw_tensor = raw_tensor.view(current_kv_cache_spec.dtype) - page_size_padded = getattr( - current_kv_cache_spec, "page_size_padded", None - ) - if page_size_padded is not None: - # The cache-only page is aligned to the hybrid common - # page, so each block has trailing padding. Stride the - # block dim (dim 0) by the full padded page to skip it - # (cf. upstream GPUModelRunner page_size_padded view). - dtype_size = get_dtype_size(current_kv_cache_spec.dtype) - page_stride = page_size_padded // dtype_size - strides = [1] * len(kv_cache_shape) - for dim_idx in range(len(kv_cache_shape) - 2, -1, -1): - strides[dim_idx] = strides[dim_idx + 1] * kv_cache_shape[dim_idx + 1] - strides[0] = page_stride - k_cache = torch.as_strided( - raw_tensor, size=kv_cache_shape, stride=tuple(strides) - ) - else: - k_cache = raw_tensor.view(kv_cache_shape) + k_cache = raw_tensor.view(current_kv_cache_spec.dtype).view(kv_cache_shape) kv_caches[layer_name] = k_cache continue # Skip the rest of the AttentionSpec handling else: @@ -4548,14 +4264,9 @@ def may_reinitialize_input_batch(self, kv_cache_config: KVCacheConfig) -> None: kernel_block_sizes=self.kernel_block_sizes, max_num_blocks_per_req=max_num_blocks, kv_cache_groups=kv_cache_config.kv_cache_groups, - cp_kv_cache_interleave_size=self.parallel_config.cp_kv_cache_interleave_size, ) - def initialize_attn_backend( - self, - kv_cache_config: KVCacheConfig, - is_profiling: bool = False, - ) -> None: + def initialize_attn_backend(self, kv_cache_config: KVCacheConfig) -> None: """ Initialize the attention backends and attention metadata builders. """ @@ -4617,11 +4328,7 @@ def create_attn_groups( attention_backend_maps.append(attn_backends[0]) attention_backend_list.append(attn_backends[1]) - self._check_and_update_cudagraph_mode( - attention_backend_list, - kv_cache_config.kv_cache_groups, - is_profiling=is_profiling, - ) + self._check_and_update_cudagraph_mode(attention_backend_list, kv_cache_config.kv_cache_groups) for i, attn_backend_map in enumerate(attention_backend_maps): self.attn_groups.append(create_attn_groups(attn_backend_map, i)) @@ -4734,13 +4441,14 @@ def get_kv_cache_spec(self) -> dict[str, KVCacheSpec]: # the indexer's k_cache is replaced by IndexerWrapper, so its # KV cache is unused. if spec := attn_module.get_kv_cache_spec(self.vllm_config): - # Rebuild to a fresh, picklable spec (the returned one - # references a stale MLAAttentionSpec class shadowed by - # patch_kv_cache_interface.py). Keep the HiddenStateCacheSpec - # type so get_kv_cache_groups isolates this cache-only layer - # into its own group; downgrading to MLAAttentionSpec would - # break page-size unification on hybrid models (e.g. Qwen3.5). - kv_cache_spec[layer_name] = HiddenStateCacheSpec( + # CacheOnlyAttentionLayer's module imports MLAAttentionSpec + # before the patch runs, so the spec it returns is an + # instance of the original (unpatched) class. Rebuilding + # with the patched AscendMLAAttentionSpec makes the spec + # picklable and keeps this branch consistent with the + # MLAAttention branch above. + from vllm.v1.kv_cache_interface import MLAAttentionSpec as AscendMLAAttentionSpec + kv_cache_spec[layer_name] = AscendMLAAttentionSpec( block_size=spec.block_size, num_kv_heads=spec.num_kv_heads, head_size=spec.head_size, @@ -4766,50 +4474,10 @@ def _check_and_update_cudagraph_mode( self, attention_backends: list[set[type[AttentionBackend]]], kv_cache_groups: list[KVCacheGroupSpec], - is_profiling: bool = False, ) -> None: - min_cg_support = AttentionCGSupport.ALWAYS - min_cg_attn_backend = None - - for attn_backend_set, kv_cache_group in zip( - attention_backends, kv_cache_groups - ): - for attn_backend in attn_backend_set: - builder_cls = attn_backend.get_builder_cls() - cg_support = builder_cls.get_cudagraph_support( - self.vllm_config, kv_cache_group.kv_cache_spec - ) - if cg_support.value < min_cg_support.value: - min_cg_support = cg_support - min_cg_attn_backend = attn_backend.__name__ - with update_pass_config(self): - cudagraph_mode = self.compilation_config.resolve_cudagraph_mode_and_sizes( - min_cg_support, - min_cg_attn_backend, - self.uniform_decode_query_len, - self.parallel_config.tensor_parallel_size, - self.kv_cache_config, - self.max_num_reqs, - is_profiling=is_profiling, - ) - self.cudagraph_dispatcher.initialize_cudagraph_keys( - cudagraph_mode, self.uniform_decode_query_len - ) + super()._check_and_update_cudagraph_mode(attention_backends, kv_cache_groups) - if ( - self.speculative_config - and self.drafter is not None - and ( - self.speculative_config.use_eagle() - or self.speculative_config.uses_extract_hidden_states() - ) - ): - assert isinstance( - self.drafter, - AscendEagleProposer | AscendDflashProposer | AscendExtractHiddenStatesProposer, - ) - self.drafter.initialize_cudagraph_keys(cudagraph_mode) capture_descs = self.cudagraph_dispatcher.get_capture_descs() capture_sizes = sorted({ @@ -4820,46 +4488,16 @@ def _check_and_update_cudagraph_mode( # NOTE: Since aclgraph_batch_sizes cannot be determined until here, # we set the graph params right before initializing the keys. - # Profiling still runs real graph warmup/capture paths, so the NPU-side - # graph params must exist there as well. if self.use_aclgraph: set_graph_params(capture_sizes) if self.speculative_config: set_draft_graph_params(capture_sizes) - def profile_cudagraph_memory(self) -> int: - parent_module_name = _get_gpu_model_runner_module_name(self) - with _torch_cuda_wrapper(), _replace_gpu_model_runner_function_wrapper(parent_module_name): - result = GPUModelRunner.profile_cudagraph_memory(self) - - reset_graph_params() - - # NOTE: This is a serious problem that we maintain two extra copies of the KV cache as the instance - # variable of the attention layers, when they are local variables in the upstream vLLM code. - # We have to manually clear them here to release memory after profiling. - for layer in self.compilation_config.static_forward_context.values(): - if hasattr(layer, "impl"): - if hasattr(layer.impl, "key_cache"): - layer.impl.key_cache = None - if hasattr(layer.impl, "value_cache"): - layer.impl.value_cache = None - - gc.collect() - torch.accelerator.empty_cache() - - return result - def capture_model(self) -> int: """Capture NPU graphs and return actual graph pool memory bytes consumed.""" parent_module_name = _get_gpu_model_runner_module_name(self) with _torch_cuda_wrapper(), _replace_gpu_model_runner_function_wrapper(parent_module_name): - cuda_graph_size = GPUModelRunner.capture_model(self) - - mgr = self.encoder_cudagraph_manager - if mgr is not None and hasattr(self, "update_stream"): - mgr.update_stream = self.update_stream - - return cuda_graph_size + return GPUModelRunner.capture_model(self) def _prepare_multimodal_fields(self): """ @@ -4886,21 +4524,6 @@ def _prepare_multimodal_fields(self): if isinstance(tensor, torch.Tensor) and tensor.device.type != "cpu": mm_data[field] = tensor.cpu() - def _init_kv_zero_meta(self) -> None: - """One-time precomputation for _zero_block_ids. - - Delegates to KVBlockZeroer.init_meta with the runner's state. - Called from gpu_worker.py outside the CuMem pool context. - """ - self._kv_block_zeroer = AscendKVBlockZeroer(self.device, self.pin_memory) - self._kv_block_zeroer.init_meta( - attn_groups_iter=self._kv_cache_spec_attn_group_iterator(), - kernel_block_sizes=self.kernel_block_sizes, - cache_dtype=self.cache_config.cache_dtype, - runner_only_attn_layers=self.runner_only_attn_layers, - static_forward_context=(self.compilation_config.static_forward_context), - ) - def _post_process_cudagraph_mode(tensor: torch.Tensor) -> int: """ @@ -4972,30 +4595,14 @@ def __init__(self, *args, **kwargs) -> None: # TODO: This method will be removed subsequently and implemented in platform. @contextmanager def _replace_gpu_model_runner_function_wrapper(target_module_name): - import vllm.v1.worker.encoder_cudagraph as _vllm_encoder_cudagraph - - from vllm_ascend.worker.encoder_acl_graph import EncoderAclGraphManager - - _encoder_mgr_orig = _vllm_encoder_cudagraph.EncoderCudaGraphManager - _vllm_encoder_cudagraph.EncoderCudaGraphManager = EncoderAclGraphManager - target_module = None - original_attrs = {} try: target_module = sys.modules[target_module_name] - if hasattr(target_module, "graph_capture"): - original_attrs["graph_capture"] = target_module.graph_capture setattr(target_module, "graph_capture", graph_capture) # noqa: B010 - if hasattr(target_module, "CUDAGraphWrapper"): - original_attrs["CUDAGraphWrapper"] = target_module.CUDAGraphWrapper - setattr(target_module, "CUDAGraphWrapper", ACLGraphWrapper) # noqa: B010 yield except Exception as e: raise RuntimeError(f"NPUModelRunner failed, error is {e}") finally: - _vllm_encoder_cudagraph.EncoderCudaGraphManager = _encoder_mgr_orig - if target_module is not None: - for attr_name, attr_value in original_attrs.items(): - setattr(target_module, attr_name, attr_value) # noqa: B010 + setattr(target_module, "graph_capture", graph_capture) # noqa: B010 # TODO: remove it when flash_comm1 is removed diff --git a/vllm_ascend/worker/pcp_utils.py b/vllm_ascend/worker/pcp_utils.py index 878c86bce..af03f7f30 100644 --- a/vllm_ascend/worker/pcp_utils.py +++ b/vllm_ascend/worker/pcp_utils.py @@ -151,12 +151,6 @@ def __init__( self._local_num_scheduled_tokens: np.ndarray | None = None self._local_total_num_scheduled_tokens: int | None = None - # Full pre-PCP token layout used to rebuild draft slot mapping - # after async scheduling corrects num_computed_tokens. - self.async_rebuild_req_indices_full = None - self.async_rebuild_cu_num_tokens_full = None - self.async_rebuild_num_tokens_full = 0 - def _get_cumsum_and_arange( self, num_scheduled_tokens: np.ndarray, @@ -871,7 +865,6 @@ def generate_pcp_mtp_input( draft_token_ids=None, scheduler_output=None, num_spec_tokens=None, - precomputed_positions_np=None, ): """ While pcp > 1, model inputs (input_ids, position, etc.) are split across pcp group, @@ -892,17 +885,7 @@ def generate_pcp_mtp_input( ) arange_pcp_full = arange_np[:total_num_scheduled_tokens_pcp_full] - cumsums_offsets_pcp_full positions_pcp_full_np = self.positions_pcp_full_np[:total_num_scheduled_tokens_pcp_full] - if precomputed_positions_np is None: - np.add( - input_batch.num_computed_tokens_cpu[req_indices_pcp_full], - arange_pcp_full, - out=positions_pcp_full_np, - ) - else: - np.copyto( - positions_pcp_full_np, - precomputed_positions_np[:total_num_scheduled_tokens_pcp_full], - ) + np.add(input_batch.num_computed_tokens_cpu[req_indices_pcp_full], arange_pcp_full, out=positions_pcp_full_np) token_indices_pcp_full = positions_pcp_full_np + req_indices_pcp_full * input_batch.token_ids_cpu.shape[1] torch.index_select( input_batch.token_ids_cpu_tensor.flatten(), @@ -922,14 +905,6 @@ def generate_pcp_mtp_input( self.query_start_loc_pcp_full.copy_to_gpu() self.input_ids_pcp_full.copy_to_gpu(total_num_scheduled_tokens_pcp_full) self.cu_num_tokens_pcp_full = cu_num_tokens_pcp_full - - if self.use_async_scheduling and precomputed_positions_np is None: - # Save full pre-CP layout so async scheduling can rebuild - # speculative inputs with corrected num_computed_tokens. - self.async_rebuild_req_indices_full = req_indices.copy() - self.async_rebuild_cu_num_tokens_full = cu_num_tokens.copy() - self.async_rebuild_num_tokens_full = total_num_scheduled_tokens - # For mtpx, pre-allocate mtp slot_mapping here if self.decode_threshold > 2 and not with_prefill: num_tokens_ori = sum(list(num_scheduled_tokens.values())) @@ -1072,7 +1047,6 @@ def generate_pcp_metadata( block_table_tensor: torch.Tensor, num_reqs_padded: int, num_reqs: int, - fixed_decode_seq_lens_cpu: np.ndarray | None = None, ): from vllm_ascend.attention.utils import AscendPrefillContextParallelMetadata @@ -1085,13 +1059,10 @@ def generate_pcp_metadata( ori_query_lens_cpu = self.query_lens_pcp_full.cpu[:num_reqs_padded] if self.pcp_world_size * self.dcp_world_size > 1: assert num_scheduled_tokens is not None - if fixed_decode_seq_lens_cpu is not None: - decode_context_lens = fixed_decode_seq_lens_cpu[: self.num_decode_reqs] - else: - decode_context_lens = ( - input_batch.num_computed_tokens_cpu[: self.num_decode_reqs] - + num_scheduled_tokens[: self.num_decode_reqs] - ) + decode_context_lens = ( + input_batch.num_computed_tokens_cpu[: self.num_decode_reqs] + + num_scheduled_tokens[: self.num_decode_reqs] + ) prefill_context_lens = input_batch.num_computed_tokens_cpu[self.num_decode_reqs : self.num_reqs] context_lens = np.concatenate([decode_context_lens, prefill_context_lens]) @@ -1264,13 +1235,8 @@ def generate_pcp_metadata( and num_scheduled_tokens is not None ): # Extract decode request info from input_batch and num_scheduled_tokens + decode_num_computed_tokens = input_batch.num_computed_tokens_cpu[: self.num_decode_reqs].tolist() decode_num_scheduled_tokens = num_scheduled_tokens[: self.num_decode_reqs] - if fixed_decode_seq_lens_cpu is not None: - decode_num_computed_tokens = ( - fixed_decode_seq_lens_cpu[: self.num_decode_reqs] - decode_num_scheduled_tokens - ).tolist() - else: - decode_num_computed_tokens = input_batch.num_computed_tokens_cpu[: self.num_decode_reqs].tolist() dcp_mtp_attn_mask = self.generate_mtp_attention_mask_for_decode( decode_num_computed_tokens, decode_num_scheduled_tokens diff --git a/vllm_ascend/worker/utils.py b/vllm_ascend/worker/utils.py deleted file mode 100644 index 570de76be..000000000 --- a/vllm_ascend/worker/utils.py +++ /dev/null @@ -1,184 +0,0 @@ -from collections.abc import Iterable -from itertools import product as iprod -from typing import Any - -import torch -from vllm.triton_utils import tl, triton -from vllm.utils.math_utils import largest_power_of_2_divisor -from vllm.v1.kv_cache_interface import FullAttentionSpec -from vllm.v1.worker.utils import AttentionGroup, KVBlockZeroer - -from vllm_ascend.ops.triton.triton_utils import get_vectorcore_num - - -@triton.jit -def _zero_kv_blocks_kernel( - seg_addrs_ptr, - block_ids_ptr, - n_blocks, - N_SEGS: tl.constexpr, - PAGE_SIZE_EL: tl.constexpr, - BLOCK_SIZE: tl.constexpr, - GRID_SIZE: tl.constexpr, -): - """Zero KV cache blocks across all segments in a single launch. - - Each segment is a contiguous region of one block's data. For backends - where blocks are outermost (block_dim=0) there is one segment per - buffer. For backends where K/V is outermost (block_dim=1) there are - two segments per buffer (one for K, one for V). - - seg_addrs_ptr holds absolute byte addresses (int64) for each segment, - allowing segments to live in different CUDA allocations. - - Programs are mapped as (block_index, seg_index, chunk_index). - """ - pid = tl.program_id(0) - chunks = PAGE_SIZE_EL // BLOCK_SIZE - work_per_block = N_SEGS * chunks - total_work = n_blocks * work_per_block - for work_idx in range(pid, total_work, GRID_SIZE): - block_index = work_idx // work_per_block - remainder = work_idx % work_per_block - seg_index = remainder // chunks - chunk_index = remainder % chunks - block_id = tl.load(block_ids_ptr + block_index) - seg_addr = tl.load(seg_addrs_ptr + seg_index) - ptr = tl.cast(seg_addr, tl.pointer_type(tl.int32)) - offset = block_id.to(tl.int64) * PAGE_SIZE_EL + chunk_index.to(tl.int64) * BLOCK_SIZE - cols = tl.arange(0, BLOCK_SIZE).to(tl.int64) - tl.store(ptr + offset + cols, tl.zeros([BLOCK_SIZE], dtype=tl.int32)) - - -class AscendKVBlockZeroer(KVBlockZeroer): - """Manages efficient zeroing of KV cache blocks via a Triton kernel. - - Call :meth:`init_meta` once after KV caches are allocated to precompute - segment addresses, then call :meth:`zero_block_ids` each step to zero - newly-allocated blocks. - """ - - def __init__(self, device: torch.device, pin_memory: bool) -> None: - self.device = device - self.pin_memory = pin_memory - self._meta: tuple[torch.Tensor, int, int, int] | None = None - self._id_cap: int = 0 - self._ids_pinned: torch.Tensor | None = None - self._ids_gpu: torch.Tensor | None = None - - def init_meta( - self, - attn_groups_iter: Iterable["AttentionGroup"], - kernel_block_sizes: list[int], - cache_dtype: str, - runner_only_attn_layers: set[str], - static_forward_context: dict[str, Any], - ) -> None: - """One-time precomputation for zero_block_ids. - - Builds absolute-address table for the Triton zeroing kernel. - Each entry is the absolute byte address of a segment start on the - GPU, so segments in different CUDA allocations work correctly. - - Block IDs from the scheduler reference logical blocks whose size - may differ from the kernel block size (virtual block splitting). - PAGE_SIZE_EL accounts for this ratio so that - ``block_id * PAGE_SIZE_EL`` lands at the correct offset. - - Only AttentionSpec layers are processed; Mamba layers are skipped. - """ - seen_ptrs: set[int] = set() - seg_addrs: list[int] = [] - page_size_el: int | None = None - - for group in attn_groups_iter: - spec = group.kv_cache_spec - if not isinstance(spec, FullAttentionSpec): - continue - if group.kv_cache_group_id >= len(kernel_block_sizes): - continue - kernel_bs = kernel_block_sizes[group.kv_cache_group_id][0] - ratio = spec.block_size // kernel_bs - block_dim = 0 - - for layer_name in group.layer_names: - if layer_name in runner_only_attn_layers: - continue - kv_tuple = static_forward_context[layer_name].kv_cache - assert len(kv_tuple) == 2, "K and V are not stored separately" - for kv in kv_tuple: - block_dim = 0 - dp = kv.data_ptr() - if dp in seen_ptrs: - continue - seen_ptrs.add(dp) - - el = kv.element_size() - cur_bytes = kv.stride(block_dim) * el - assert cur_bytes % 4 == 0 - kernel_block_el = cur_bytes // 4 - cur_page_el = kernel_block_el * ratio - if page_size_el is None: - page_size_el = cur_page_el - else: - assert page_size_el == cur_page_el, f"Non-uniform page sizes: {page_size_el} vs {cur_page_el}" - - block_stride_bytes = cur_bytes - outer_dims = [d for d in range(block_dim) if kv.stride(d) * el > block_stride_bytes] - outer_strides = [kv.stride(d) * el for d in outer_dims] - for outer in iprod(*(range(kv.shape[d]) for d in outer_dims)): - off_bytes = sum(i * s for i, s in zip(outer, outer_strides)) - seg_addrs.append(dp + off_bytes) - - if not seg_addrs or page_size_el is None: - self._meta = None - return - - # _zero_kv_blocks_kernel will use int64 zeros, to meet the UB size, we use blk_size=64B/8B=8192 - blk_size = min(largest_power_of_2_divisor(page_size_el), 8192) - self._id_cap = 8192 - self._ids_pinned = torch.empty( - self._id_cap, - dtype=torch.int64, - pin_memory=self.pin_memory, - ) - self._ids_gpu = torch.empty(self._id_cap, dtype=torch.int64, device=self.device) - self._meta = ( - torch.tensor(seg_addrs, dtype=torch.uint64, device=self.device), - page_size_el, - blk_size, - len(seg_addrs), - ) - - def zero_block_ids(self, block_ids: list[int]) -> None: - """Zero the KV cache memory for the given block IDs.""" - if not block_ids or self._meta is None: - return - seg_addrs, page_size_el, blk_size, n_segs = self._meta - n_blocks = len(block_ids) - if n_blocks > self._id_cap: - self._id_cap = n_blocks * 2 - self._ids_pinned = torch.empty( - self._id_cap, - dtype=torch.int64, - pin_memory=self.pin_memory, - ) - self._ids_gpu = torch.empty(self._id_cap, dtype=torch.int64, device=self.device) - assert self._ids_pinned is not None and self._ids_gpu is not None - self._ids_pinned[:n_blocks].numpy()[:] = block_ids - idx = self._ids_gpu[:n_blocks] - idx.copy_(self._ids_pinned[:n_blocks], non_blocking=True) - chunks = page_size_el // blk_size - total_work = n_blocks * n_segs * chunks - grid = min(total_work, get_vectorcore_num()) if total_work > 0 else 0 - if grid == 0: - return - _zero_kv_blocks_kernel[(grid,)]( - seg_addrs, - idx, - n_blocks, - N_SEGS=n_segs, - PAGE_SIZE_EL=page_size_el, - BLOCK_SIZE=blk_size, - GRID_SIZE=grid, - ) diff --git a/vllm_ascend/worker/v2/block_table.py b/vllm_ascend/worker/v2/block_table.py index e4efdee73..e5ca76b0f 100644 --- a/vllm_ascend/worker/v2/block_table.py +++ b/vllm_ascend/worker/v2/block_table.py @@ -21,6 +21,8 @@ from vllm.v1.attention.backends.utils import PAD_SLOT_ID from vllm.v1.worker.gpu.block_table import BlockTables, _load_ptr +from vllm_ascend.utils import vllm_version_is + class AscendBlockTables(BlockTables): """Block table for Ascend NPUs.""" @@ -37,19 +39,31 @@ def __init__( cp_rank: int = 0, cp_interleave: int = 1, ): - if kernel_block_sizes is None: - kernel_block_sizes = block_sizes - super().__init__( - block_sizes, - max_num_reqs, - max_num_batched_tokens, - max_num_blocks_per_group, - device, - kernel_block_sizes, - cp_size, - cp_rank, - cp_interleave, - ) + if vllm_version_is("0.20.2"): + super().__init__( + block_sizes, + max_num_reqs, + max_num_batched_tokens, + max_num_blocks_per_group, + device, + cp_size, + cp_rank, + cp_interleave, + ) + else: + if kernel_block_sizes is None: + kernel_block_sizes = block_sizes + super().__init__( + block_sizes, + max_num_reqs, + max_num_batched_tokens, + max_num_blocks_per_group, + device, + kernel_block_sizes, + cp_size, + cp_rank, + cp_interleave, + ) # because we will override these attribute, delete these attribute to # make sure it's collected by python gc immediately. del self.slot_mappings diff --git a/vllm_ascend/worker/v2/model_runner.py b/vllm_ascend/worker/v2/model_runner.py index 939f2bc62..2236b76db 100644 --- a/vllm_ascend/worker/v2/model_runner.py +++ b/vllm_ascend/worker/v2/model_runner.py @@ -263,9 +263,9 @@ def prepare_inputs( query_start_loc_np = query_start_loc_np[: num_reqs_padded + 1] query_start_loc = self.input_buffers.query_start_loc[: num_reqs + 1] - prefill_len_np = self.req_states.prefill_len.np[idx_mapping_np] - num_computed_prefill_tokens_np = self.req_states.num_computed_prefill_tokens[idx_mapping_np] - is_prefilling_np = num_computed_prefill_tokens_np < prefill_len_np + is_prefilling_np = ( + self.req_states.num_computed_prefill_tokens[idx_mapping_np] < self.req_states.prefill_len.np[idx_mapping_np] + ) # Get prefill tokens if any. if np.any(is_prefilling_np): @@ -318,11 +318,6 @@ def prepare_inputs( out=seq_lens_cpu_upper_bound_np[:num_reqs], ) seq_lens_cpu_upper_bound = torch.from_numpy(seq_lens_cpu_upper_bound_np) - num_computed_tokens_np = self.req_states.num_computed_tokens_np[idx_mapping_np] - max_seq_len_np = None - if getattr(self, "use_pp", False): - # max_seq_len is only consumed by the PP `compute_need_sampled_mask`. - max_seq_len_np = self.req_states.max_seq_len[idx_mapping_np] self.input_batch = AscendInputBatch( req_ids=req_ids, @@ -343,10 +338,6 @@ def prepare_inputs( seq_lens_cpu_upper_bound=seq_lens_cpu_upper_bound, dcp_local_seq_lens=None, # TODO(Ronald1995): support cp. is_prefilling_np=is_prefilling_np, - num_computed_tokens_np=num_computed_tokens_np, - prefill_len_np=prefill_len_np, - num_computed_prefill_tokens_np=num_computed_prefill_tokens_np, - max_seq_len_np=max_seq_len_np, input_ids=input_ids, positions=positions, logits_indices=logits_indices, @@ -381,28 +372,6 @@ def postprocess( num_rejected, ) - self._copy_num_computed_tokens_to_cpu() - - def postprocess_sampled( - self, - idx_mapping, - sampled_tokens, - num_sampled, - num_rejected, - query_start_loc=None, - ): - """Override GPUModelRunner.postprocess_sampled for Ascend NPUs.""" - super().postprocess_sampled( - idx_mapping, - sampled_tokens, - num_sampled, - num_rejected, - query_start_loc, - ) - - self._copy_num_computed_tokens_to_cpu() - - def _copy_num_computed_tokens_to_cpu(self): # npu attention backend still need to use seq_lens_cpu, # we need to copy num_computed_tokens back to cpu. default_stream = torch.cuda.current_stream() diff --git a/vllm_ascend/worker/v2/sample/gumbel.py b/vllm_ascend/worker/v2/sample/gumbel.py index 13e549c46..1f726cc91 100644 --- a/vllm_ascend/worker/v2/sample/gumbel.py +++ b/vllm_ascend/worker/v2/sample/gumbel.py @@ -16,12 +16,13 @@ # See the License for the specific language governing permissions and # limitations under the License. # This file is a part of the vllm-ascend project. +# import torch from vllm.triton_utils import tl, triton -@triton.jit(do_not_specialize=["logits_stride", "vocab_size"]) +@triton.jit def _temperature_kernel( logits_ptr, logits_stride, @@ -34,7 +35,7 @@ def _temperature_kernel( req_state_idx = tl.load(expanded_idx_mapping_ptr + token_idx) temperature = tl.load(temperature_ptr + req_state_idx).to(tl.float32) if temperature == 0.0 or temperature == 1.0: - # Early return to avoid loading logits + # Early return to avoid loading logits. return block_idx = tl.program_id(1) @@ -73,15 +74,7 @@ def apply_temperature( ) -@triton.jit( - do_not_specialize=[ - "local_argmax_stride", - "local_max_stride", - "processed_logits_stride", - "logits_stride", - "vocab_size", - ] -) +@triton.jit def _gumbel_sample_kernel( local_argmax_ptr, local_argmax_stride, @@ -92,7 +85,7 @@ def _gumbel_sample_kernel( processed_logits_col_ptr, logits_ptr, logits_stride, - expanded_idx_mapping_ptr, + idx_mapping_ptr, seeds_ptr, pos_ptr, temp_ptr, @@ -100,26 +93,26 @@ def _gumbel_sample_kernel( BLOCK_SIZE: tl.constexpr, APPLY_TEMPERATURE: tl.constexpr, ): - token_idx = tl.program_id(0) + batch_idx = tl.program_id(0) + req_state_idx = tl.load(idx_mapping_ptr + batch_idx) + block_idx = tl.program_id(1) block = block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) mask = block < vocab_size logits = tl.load( - logits_ptr + token_idx * logits_stride + block, + logits_ptr + batch_idx * logits_stride + block, mask=mask, other=float("-inf"), ) logits = logits.to(tl.float32) - req_state_idx = tl.load(expanded_idx_mapping_ptr + token_idx) temp = tl.load(temp_ptr + req_state_idx).to(tl.float32) - if temp != 0.0 and APPLY_TEMPERATURE: # NOTE(woosuk): Match the behavior of _temperature_kernel. + # E.g., if the kernel uses tl.div_rn, we should use tl.div_rn here too. logits = logits / temp if processed_logits_ptr is not None: - # Store the temperature-applied logits. if processed_logits_col_ptr is not None: col = tl.load(processed_logits_col_ptr) else: @@ -134,14 +127,16 @@ def _gumbel_sample_kernel( # Calculate the seed for gumbel noise. seed = tl.load(seeds_ptr + req_state_idx) # NOTE(Ronald1995): change pos's dtype to tl.int32, because triton-ascend's - # compiler doesn't support uint64 of pos arg. - pos = tl.load(pos_ptr + token_idx).to(tl.int32) + # compiler doesn't support unint64 of pos arg. + pos = tl.load(pos_ptr + batch_idx).to(tl.int32) gumbel_seed = tl.randint(seed, pos) + # Generate gumbel noise. # NOTE(Ronald1995): r is tl.float64 in vllm, change it to tl.float32, - # because triton-ascend's compiler does not support float64. + # or triton-ascend's compiler will raise error. r = tl.rand(gumbel_seed, block).to(tl.float32) gumbel_noise = -tl.log(-tl.log(r + 1e-20) + 1e-20) + gumbel_noise = gumbel_noise.to(tl.float32) # Apply gumbel noise. logits = tl.where(mask, logits + gumbel_noise, float("-inf")) @@ -149,16 +144,16 @@ def _gumbel_sample_kernel( idx = tl.argmax(logits, axis=0) token_id = block_idx * BLOCK_SIZE + idx value = tl.max(logits, axis=0) - tl.store(local_argmax_ptr + token_idx * local_argmax_stride + block_idx, token_id) - tl.store(local_max_ptr + token_idx * local_max_stride + block_idx, value) + tl.store(local_argmax_ptr + batch_idx * local_argmax_stride + block_idx, token_id) + tl.store(local_max_ptr + batch_idx * local_max_stride + block_idx, value) def gumbel_sample( - logits: torch.Tensor, # [num_tokens, vocab_size] - expanded_idx_mapping: torch.Tensor, # [num_tokens] - temperature: torch.Tensor, # [max_num_reqs] - seed: torch.Tensor, # [max_num_reqs] - pos: torch.Tensor, # [num_tokens] + logits: torch.Tensor, # [num_reqs, vocab_size] + idx_mapping: torch.Tensor, # [num_reqs] + temperature: torch.Tensor, # [num_reqs] + seed: torch.Tensor, # [num_reqs] + pos: torch.Tensor, # [num_reqs] apply_temperature: bool, output_processed_logits: torch.Tensor | None = None, output_processed_logits_col: torch.Tensor | None = None, @@ -166,22 +161,24 @@ def gumbel_sample( ) -> torch.Tensor: if use_fp64: raise NotImplementedError("FP64 Gumbel sampling is not supported on NPU.") - num_tokens, vocab_size = logits.shape + + num_reqs, vocab_size = logits.shape BLOCK_SIZE = 1024 num_blocks = triton.cdiv(vocab_size, BLOCK_SIZE) local_argmax = torch.empty( - num_tokens, + num_reqs, num_blocks, dtype=torch.int64, device=logits.device, ) local_max = torch.empty( - num_tokens, + num_reqs, num_blocks, dtype=torch.float32, device=logits.device, ) - _gumbel_sample_kernel[(num_tokens, num_blocks)]( + # TODO(Ronald1995): Optimize the performance of the kernel in npu. + _gumbel_sample_kernel[(num_reqs, num_blocks)]( local_argmax, local_argmax.stride(0), local_max, @@ -191,7 +188,7 @@ def gumbel_sample( output_processed_logits_col, logits, logits.stride(0), - expanded_idx_mapping, + idx_mapping, seed, pos, temperature, diff --git a/vllm_ascend/worker/v2/sample/penalties.py b/vllm_ascend/worker/v2/sample/penalties.py index 0fb5b80ab..5aedfb1d9 100644 --- a/vllm_ascend/worker/v2/sample/penalties.py +++ b/vllm_ascend/worker/v2/sample/penalties.py @@ -26,24 +26,27 @@ def _penalties_kernel( logits_ptr, logits_stride, - expanded_idx_mapping_ptr, + idx_mapping_ptr, token_ids_ptr, expanded_local_pos_ptr, - repetition_penalty_ptr, - frequency_penalty_ptr, - presence_penalty_ptr, + penalties_ptr, + penalties_stride, prompt_bin_mask_ptr, prompt_bin_mask_stride, output_bin_counts_ptr, output_bin_counts_stride, vocab_size, BLOCK_SIZE: tl.constexpr, + INNER_BLOCK_SIZE: tl.constexpr, + MAX_SPEC_LEN: tl.constexpr, ): token_idx = tl.program_id(0) - req_state_idx = tl.load(expanded_idx_mapping_ptr + token_idx) - rep_penalty = tl.load(repetition_penalty_ptr + req_state_idx) - freq_penalty = tl.load(frequency_penalty_ptr + req_state_idx) - pres_penalty = tl.load(presence_penalty_ptr + req_state_idx) + req_state_idx = tl.load(idx_mapping_ptr + token_idx) + + # first load penalties once + rep_penalty = tl.load(penalties_ptr + req_state_idx * penalties_stride + 0) + freq_penalty = tl.load(penalties_ptr + req_state_idx * penalties_stride + 1) + pres_penalty = tl.load(penalties_ptr + req_state_idx * penalties_stride + 2) use_rep_penalty = rep_penalty != 1.0 use_freq_penalty = freq_penalty != 0.0 @@ -52,64 +55,73 @@ def _penalties_kernel( # NPU doesn't support chained 'or' operations like 'A or B or C' use_penalty = use_rep_penalty or use_freq_penalty use_penalty = use_penalty or use_pres_penalty + if not use_penalty: # Early return to avoid loading logits. return + bit_masks = tl.full((INNER_BLOCK_SIZE // 32, 32), 1, dtype=tl.int32) << tl.arange(0, 32) block_idx = tl.program_id(1) - block = block_idx * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) - mask = block < vocab_size - logits = tl.load(logits_ptr + token_idx * logits_stride + block, mask=mask) - logits = logits.to(tl.float32) - - base_output_counts = tl.load( - output_bin_counts_ptr + req_state_idx * output_bin_counts_stride + block, - mask=mask, - other=0, - ) + block_start = block_idx * BLOCK_SIZE - # Accumulate draft token counts from previous positions directly into - # output_bin_counts (preserves its native tensor layout, avoiding an - # expensive shared-memory layout conversion after the loop). pos = tl.load(expanded_local_pos_ptr + token_idx) start_idx = token_idx - pos - output_bin_counts = base_output_counts - for prev_pos in tl.range(pos): - prev_token = tl.load(token_ids_ptr + start_idx + prev_pos + 1) - token_match = block == prev_token - output_bin_counts = output_bin_counts + token_match.to(tl.int32) - output_bin_mask = output_bin_counts != 0 - - # Apply repetition penalties. - if use_rep_penalty: - packed_block = block_idx * BLOCK_SIZE // 32 + tl.arange(0, BLOCK_SIZE // 32) - packed_mask = tl.load( - prompt_bin_mask_ptr + req_state_idx * prompt_bin_mask_stride + packed_block, - mask=packed_block < tl.cdiv(vocab_size, 32), + + inv_rep = 1.0 / rep_penalty + + for inner_offset in tl.static_range(0, BLOCK_SIZE, INNER_BLOCK_SIZE): + inner_block_start = block_start + inner_offset + inner_block = inner_block_start + tl.arange(0, INNER_BLOCK_SIZE) + inner_mask = inner_block < vocab_size + + logits = tl.load(logits_ptr + token_idx * logits_stride + inner_block, mask=inner_mask, other=0.0) + logits = logits.to(tl.float32) + + base_output_counts = tl.load( + output_bin_counts_ptr + req_state_idx * output_bin_counts_stride + inner_block, + mask=inner_mask, other=0, ) - bit_masks = 1 << tl.arange(0, 32) - bit_masks_expanded = bit_masks[None, :] - packed_expanded = packed_mask[:, None] - bits_matrix = (packed_expanded & bit_masks_expanded) != 0 - prompt_bin_mask = bits_matrix.reshape(BLOCK_SIZE) - # If token appears in prompt or output, apply, otherwise use 1.0 for no-op. - scale = tl.where(prompt_bin_mask | output_bin_mask, rep_penalty, 1.0) - # If logits are positive, divide by penalty, otherwise multiply by penalty. - logits *= tl.where(logits > 0, 1.0 / scale, scale) + # Compute cumulative draft_counts from previous positions in this request + total_counts = base_output_counts.to(tl.int32) + for prev_pos in tl.static_range(MAX_SPEC_LEN): + if prev_pos < pos: + load_idx = start_idx + prev_pos + 1 + prev_token = tl.load(token_ids_ptr + load_idx) + total_counts += inner_block == prev_token - # Apply frequency penalties. - logits -= freq_penalty * output_bin_counts - # Apply presence penalties. - logits -= pres_penalty * output_bin_mask - # Store back to logits. - tl.store(logits_ptr + token_idx * logits_stride + block, logits, mask=mask) + is_present = total_counts != 0 + + # Apply repetition penalties. + if use_rep_penalty: + packed_inner_block_start = inner_block_start // 32 + packed_block = packed_inner_block_start + tl.arange(0, INNER_BLOCK_SIZE // 32) + valid_packed_mask = packed_block < tl.cdiv(vocab_size, 32) + + packed_mask_val = tl.load( + prompt_bin_mask_ptr + req_state_idx * prompt_bin_mask_stride + packed_block, + mask=valid_packed_mask, + other=0, + ) + prompt_mask = ((packed_mask_val[:, None] & bit_masks) != 0).reshape(INNER_BLOCK_SIZE) + + needs_scaling = prompt_mask | is_present + + base_factor = tl.where(logits > 0, inv_rep, rep_penalty) + logits = tl.where(needs_scaling, logits * base_factor, logits) + + freq_term = freq_penalty * total_counts.to(tl.float32) + pres_term = pres_penalty * is_present.to(tl.float32) + + logits = logits - freq_term - pres_term + # Store back to logits. + tl.store(logits_ptr + token_idx * logits_stride + inner_block, logits, mask=inner_mask) def apply_penalties( logits: torch.Tensor, - expanded_idx_mapping: torch.Tensor, + idx_mapping: torch.Tensor, token_ids: torch.Tensor, expanded_local_pos: torch.Tensor, repetition_penalty: torch.Tensor, @@ -117,25 +129,34 @@ def apply_penalties( presence_penalty: torch.Tensor, prompt_bin_mask: torch.Tensor, output_bin_counts: torch.Tensor, + num_speculative_tokens: int, ) -> None: num_tokens, vocab_size = logits.shape - BLOCK_SIZE = 4096 + BLOCK_SIZE = 8192 + INNER_BLOCK_SIZE = 4096 num_blocks = triton.cdiv(vocab_size, BLOCK_SIZE) + + penalties = torch.stack( + [repetition_penalty[:num_tokens], frequency_penalty[:num_tokens], presence_penalty[:num_tokens]], dim=1 + ).contiguous() + penalties_stride = penalties.stride(0) + _penalties_kernel[(num_tokens, num_blocks)]( logits, logits.stride(0), - expanded_idx_mapping, + idx_mapping, token_ids, expanded_local_pos, - repetition_penalty, - frequency_penalty, - presence_penalty, + penalties, + penalties_stride, prompt_bin_mask, prompt_bin_mask.stride(0), output_bin_counts, output_bin_counts.stride(0), vocab_size, BLOCK_SIZE=BLOCK_SIZE, + INNER_BLOCK_SIZE=INNER_BLOCK_SIZE, + MAX_SPEC_LEN=num_speculative_tokens, ) diff --git a/vllm_ascend/worker/v2/spec_decode/eagle/aclgraph.py b/vllm_ascend/worker/v2/spec_decode/eagle/aclgraph.py index 49cfd843c..aaa7cf933 100644 --- a/vllm_ascend/worker/v2/spec_decode/eagle/aclgraph.py +++ b/vllm_ascend/worker/v2/spec_decode/eagle/aclgraph.py @@ -11,17 +11,13 @@ from vllm.logger import logger from vllm.v1.kv_cache_interface import KVCacheConfig from vllm.v1.worker.gpu.block_table import BlockTables -from vllm.v1.worker.gpu.cudagraph_utils import ( # type: ignore[import-not-found] - AttentionStatePair as CapturedAttentionState, -) from vllm.v1.worker.gpu.cudagraph_utils import BatchExecutionDescriptor from vllm.v1.worker.gpu.input_batch import InputBuffers from vllm.v1.worker.gpu.model_states.interface import ModelState -from vllm.v1.worker.gpu.spec_decode.autoregressive.cudagraph_utils import ( # type: ignore[import-not-found] - DecodeSpeculatorCudaGraphManager as DecodeEagleCudaGraphManager, -) -from vllm.v1.worker.gpu.spec_decode.autoregressive.cudagraph_utils import ( - PrefillSpeculatorCudaGraphManager as PrefillEagleCudaGraphManager, +from vllm.v1.worker.gpu.spec_decode.eagle.cudagraph import ( + CapturedAttentionState, + DecodeEagleCudaGraphManager, + PrefillEagleCudaGraphManager, ) from vllm.v1.worker.utils import AttentionGroup diff --git a/vllm_ascend/worker/v2/spec_decode/eagle/speculator.py b/vllm_ascend/worker/v2/spec_decode/eagle/speculator.py index 3fe85ecef..839018beb 100644 --- a/vllm_ascend/worker/v2/spec_decode/eagle/speculator.py +++ b/vllm_ascend/worker/v2/spec_decode/eagle/speculator.py @@ -25,26 +25,24 @@ from vllm.config import VllmConfig, get_layers_from_vllm_config from vllm.config.compilation import CUDAGraphMode from vllm.model_executor.layers.attention_layer_base import AttentionLayerBase -from vllm.v1.attention.backend import AttentionBackend from vllm.v1.kv_cache_interface import KVCacheConfig from vllm.v1.worker.gpu.block_table import BlockTables from vllm.v1.worker.gpu.input_batch import InputBatch from vllm.v1.worker.gpu.model_states.interface import ModelState -from vllm.v1.worker.gpu.spec_decode.autoregressive import ( # type: ignore[import-not-found] - speculator as vllm_speculator_module, # type: ignore[import-not-found] -) -from vllm.v1.worker.gpu.spec_decode.autoregressive.cudagraph_utils import ( # type: ignore[import-not-found] - PrefillSpeculatorCudaGraphManager, -) -from vllm.v1.worker.gpu.spec_decode.eagle.speculator import EagleSpeculator # type: ignore[import-not-found] +from vllm.v1.worker.gpu.spec_decode.eagle import speculator as vllm_speculator +from vllm.v1.worker.gpu.spec_decode.eagle.cudagraph import PrefillEagleCudaGraphManager +from vllm.v1.worker.gpu.spec_decode.eagle.speculator import EagleSpeculator, update_eagle_draft_inputs from vllm_ascend.attention.attention_v1 import AscendAttentionState +from vllm_ascend.utils import vllm_version_is from vllm_ascend.worker.v2.attn_utils import build_attn_metadata from vllm_ascend.worker.v2.input_batch import AscendInputBuffers from vllm_ascend.worker.v2.spec_decode.eagle.aclgraph import PrefillEagleAclGraphManager -_BUILD_ATTN_METADATA_MODULE = vllm.v1.worker.gpu.spec_decode.speculator -_PREFILL_CUDAGRAPH_MANAGER_CLS = PrefillSpeculatorCudaGraphManager +if vllm_version_is("0.20.2"): + from vllm.v1.worker.gpu.attn_utils import AttentionBackend +else: + from vllm.v1.attention.backend import AttentionBackend class AscendEagleSpeculator(EagleSpeculator): @@ -166,39 +164,73 @@ def set_attn( self.attn_backends = attn_backends - def _generate_draft( + def generate_draft( self, num_reqs: int, num_tokens_padded: int, - attn_metadata: dict[str, Any] | None, - slot_mappings: dict[str, torch.Tensor] | None, + attn_metadata: dict[str, Any], + slot_mappings: dict[str, torch.Tensor], num_tokens_across_dp: torch.Tensor | None, cudagraph_runtime_mode: CUDAGraphMode = CUDAGraphMode.NONE, - ) -> None: - """Override AutoRegressiveSpeculator._generate_draft for Ascend NPUs.""" - self._ascend_prepare_decode_draft(attn_metadata, num_reqs) - super()._generate_draft( - num_reqs, + ): + """Override GPU EagleSpeculator.generate_draft for Ascend NPUs, because + attn_metadata is created in super propose method, it does not have some + attribute that Ascend attention backend needs, so we update it. + """ + self._init_decode_attn_metadata(attn_metadata, num_reqs) + self._increment_decode_attn_metadata(attn_metadata) + idx_mapping = self.idx_mapping[:num_reqs] + positions = self.input_buffers.positions[:num_reqs] + # Run the eagle model forward pass. + last_hidden_states, hidden_states = self.run_model( num_tokens_padded, attn_metadata, slot_mappings, num_tokens_across_dp, cudagraph_runtime_mode, ) + last_hidden_states = last_hidden_states[:num_reqs] + + # Sample the draft tokens. + logits = self.model.compute_logits(last_hidden_states) + draft_tokens = self._sample_draft( + logits, + idx_mapping, + positions, + self.current_draft_step, + self.draft_logits, + ) + + # Update the inputs for the next step. + update_eagle_draft_inputs( + draft_tokens, + self.current_draft_step, + hidden_states, + self.draft_tokens, + self.hidden_states, + self.input_buffers, + num_reqs, + self.max_model_len, + self.num_speculative_steps, + ) + # npu's own update logic self._increment_decode_attn_metadata(attn_metadata) @torch.inference_mode() - def _run_model( + def run_model( self, num_tokens: int, - attn_metadata: dict[str, Any] | None, + attn_metadata: dict[str, Any], slot_mappings: dict[str, torch.Tensor] | None, num_tokens_across_dp: torch.Tensor | None, cudagraph_runtime_mode: CUDAGraphMode = CUDAGraphMode.NONE, mm_inputs: tuple[list[torch.Tensor], torch.Tensor] | None = None, ) -> tuple[torch.Tensor, torch.Tensor]: - """Override AutoRegressiveSpeculator._run_model for Ascend NPUs.""" - last_hidden_states, hidden_states = super()._run_model( + """Override GPU EagleSpeculator.run_model for Ascend NPUs, because + in decode phase, we need to update seq_lens_cpu in attn_metadata after + run model. + """ + last_hidden_states, hidden_states = super().run_model( num_tokens, attn_metadata, slot_mappings, @@ -206,7 +238,13 @@ def _run_model( cudagraph_runtime_mode, mm_inputs, ) - self._ascend_update_seq_lens(attn_metadata) + + # attn_metadata is None in profile_run and dummy_run. + if attn_metadata is not None: + for attn_meta in attn_metadata.values(): + # seq_lens in AscendMetadata is a cpu tensor. + attn_meta.seq_lens = attn_meta.seq_lens + 1 + attn_meta.seq_len_list = attn_meta.seq_lens.tolist() return last_hidden_states, hidden_states def build_draft_attn_metadatas(self, num_reqs_padded, is_draft_model_prefill): @@ -228,17 +266,7 @@ def build_draft_attn_metadatas(self, num_reqs_padded, is_draft_model_prefill): return draft_attn_metadatas - def _ascend_prepare_decode_draft(self, attn_metadata: dict[str, Any] | None, num_reqs: int) -> None: - self._init_decode_attn_metadata(attn_metadata, num_reqs) - self._increment_decode_attn_metadata(attn_metadata) - - def _ascend_update_seq_lens(self, attn_metadata: dict[str, Any] | None) -> None: - if attn_metadata is not None: - for attn_meta in attn_metadata.values(): - attn_meta.seq_lens = attn_meta.seq_lens + 1 - attn_meta.seq_len_list = attn_meta.seq_lens.tolist() - - def _init_decode_attn_metadata(self, attn_metadata: dict[str, Any] | None, num_reqs: int): + def _init_decode_attn_metadata(self, attn_metadata: dict[str, Any], num_reqs: int): """Initialize attention metadata for decode phase on Ascend NPUs.""" if attn_metadata is None: return @@ -252,7 +280,7 @@ def _init_decode_attn_metadata(self, attn_metadata: dict[str, Any] | None, num_r metadata.attn_state = attn_state metadata.seq_lens_cpu = seq_lens_cpu - def _init_decode_draft_attn_metadatas(self, attn_metadata: dict[str, Any] | None, num_reqs_padded: int): + def _init_decode_draft_attn_metadatas(self, attn_metadata: dict[str, Any], num_reqs_padded: int): """Initialize attention metadata for decode phase in graph mode on Ascend NPUs.""" if attn_metadata is None: return @@ -273,14 +301,12 @@ def _init_decode_draft_attn_metadatas(self, attn_metadata: dict[str, Any] | None return draft_attn_metadatas - def _increment_decode_attn_metadata(self, attn_metadata: dict[str, Any] | None): + def _increment_decode_attn_metadata(self, attn_metadata: dict[str, Any]): """Increment attention metadata for decode phase on Ascend NPUs.""" # in eager mode, attn_metadata's seq_lens_cpu and input_buffers's seq_lens_cpu shares the memory self._update_decode_attn_metadata(attn_metadata, 1) - def _update_decode_attn_metadata( - self, attn_metadata: dict[str, Any] | None, step: int, num_reqs: int | None = None - ): + def _update_decode_attn_metadata(self, attn_metadata: dict[str, Any], step: int, num_reqs: int | None = None): """Update attention metadata for decode phase on Ascend NPUs.""" if attn_metadata is None: return @@ -319,12 +345,12 @@ def _get_seq_lens_cpu(self) -> torch.Tensor: @contextmanager def build_attn_metadata_wrapper(): """Context manager to override attention metadata building for Ascend NPUs.""" - original_func = _BUILD_ATTN_METADATA_MODULE.build_attn_metadata + original_func = vllm.v1.worker.gpu.spec_decode.eagle.speculator.build_attn_metadata try: - _BUILD_ATTN_METADATA_MODULE.build_attn_metadata = build_attn_metadata + vllm.v1.worker.gpu.spec_decode.eagle.speculator.build_attn_metadata = build_attn_metadata yield finally: - _BUILD_ATTN_METADATA_MODULE.build_attn_metadata = original_func + vllm.v1.worker.gpu.spec_decode.eagle.speculator.build_attn_metadata = original_func # TODO Remove this patch when cann fix the gather bug. @@ -355,15 +381,13 @@ def torch_gather_wrapper(): @contextmanager def graph_manager_wrapper(speculator): """Context manager to override graph manager.""" - original_graph_manager = _PREFILL_CUDAGRAPH_MANAGER_CLS + original_graph_manager = PrefillEagleCudaGraphManager def factory(vllm_config: VllmConfig, device: torch.device, cudagraph_mode: CUDAGraphMode, decode_query_len: int): return PrefillEagleAclGraphManager(vllm_config, device, cudagraph_mode, decode_query_len, speculator) - manager_attr = "PrefillSpeculatorCudaGraphManager" - try: - setattr(vllm_speculator_module, manager_attr, factory) + vllm_speculator.PrefillEagleCudaGraphManager = factory yield finally: - setattr(vllm_speculator_module, manager_attr, original_graph_manager) + vllm_speculator.PrefillEagleCudaGraphManager = original_graph_manager diff --git a/vllm_ascend/worker/v2/states.py b/vllm_ascend/worker/v2/states.py index 1c2007039..9ef7acd94 100644 --- a/vllm_ascend/worker/v2/states.py +++ b/vllm_ascend/worker/v2/states.py @@ -58,14 +58,12 @@ def add_request( prompt_len, all_token_ids, num_computed_tokens, - max_tokens=None, ): super().add_request( req_id, prompt_len, all_token_ids, num_computed_tokens, - max_tokens=max_tokens, ) req_idx = self.req_id_to_index[req_id] self.num_computed_tokens_cpu[req_idx] = num_computed_tokens diff --git a/vllm_ascend/worker/worker.py b/vllm_ascend/worker/worker.py index 63ee94cf3..45ec242c2 100644 --- a/vllm_ascend/worker/worker.py +++ b/vllm_ascend/worker/worker.py @@ -31,13 +31,7 @@ from vllm.config import CUDAGraphMode, VllmConfig, set_current_vllm_config from vllm.distributed import ensure_model_parallel_initialized, init_distributed_environment from vllm.distributed.ec_transfer import ensure_ec_transfer_initialized -from vllm.distributed.kv_transfer import ( - ensure_kv_transfer_initialized, - ensure_kv_transfer_shutdown, - get_kv_transfer_group, - has_kv_transfer_group, -) -from vllm.distributed.kv_transfer.kv_connector.v1.base import KVConnectorHandshakeMetadata +from vllm.distributed.kv_transfer import ensure_kv_transfer_initialized, get_kv_transfer_group, has_kv_transfer_group from vllm.distributed.parallel_state import Handle, get_pp_group, get_tp_group from vllm.logger import logger from vllm.lora.request import LoRARequest @@ -49,7 +43,6 @@ from vllm.v1.core.sched.output import GrammarOutput, SchedulerOutput from vllm.v1.kv_cache_interface import KVCacheConfig, KVCacheSpec from vllm.v1.outputs import EMPTY_MODEL_RUNNER_OUTPUT, AsyncModelRunnerOutput, DraftTokenIds, ModelRunnerOutput -from vllm.v1.utils import report_usage_stats from vllm.v1.worker.gpu_worker import AsyncIntermediateTensors from vllm.v1.worker.worker_base import CompilationTimes, WorkerBase from vllm.v1.worker.workspace import init_workspace_manager @@ -117,9 +110,6 @@ def __init__( register_ascend_customop(vllm_config) # init ascend config and soc version init_ascend_config(vllm_config) - from vllm_ascend.logger import configure_ascend_file_logging - - configure_ascend_file_logging() check_ascend_device_type() super().__init__( @@ -145,12 +135,6 @@ def __init__( # Buffers saved before sleep self._sleep_saved_buffers: dict[str, torch.Tensor] = {} - # Weight transfer engine is created in `load_model` once the model - # is available, since the engine needs a reference to the model. - self.weight_transfer_engine = None - self._weight_update_active = False - self._is_checkpoint_format = True - # FixMe: this is a patch to fix the issue cause by https://github.com/vllm-project/vllm/commit/de94289a98d7ec52a5ef02719e01a1db8b505170 from vllm.model_executor.layers.linear import WEIGHT_LOADER_V2_SUPPORTED @@ -158,8 +142,8 @@ def __init__( WEIGHT_LOADER_V2_SUPPORTED.remove("UnquantizedLinearMethod") self.use_v2_model_runner = envs_vllm.VLLM_USE_V2_MODEL_RUNNER - if self.use_v2_model_runner and vllm_version_is("0.22.1"): - logger.warning("VLLM_USE_V2_MODEL_RUNNER is not supported on vllm 0.22.1; falling back to v1 model runner.") + if self.use_v2_model_runner and vllm_version_is("0.20.2"): + logger.warning("VLLM_USE_V2_MODEL_RUNNER is not supported on vllm 0.20.2; falling back to v1 model runner.") self.use_v2_model_runner = False self._pp_send_work: list[Handle] = [] @@ -269,115 +253,6 @@ def wake_up(self, tags: list[str] | None = None) -> None: buffer.data.copy_(self._sleep_saved_buffers[name].data) self._sleep_saved_buffers = {} - def _check_weight_transfer_engine(self) -> None: - if self.weight_transfer_engine is None: - raise RuntimeError( - "Weight transfer not configured. Please set weight_transfer_config to enable weight transfer." - ) - - def init_weight_transfer_engine(self, init_info: dict) -> None: - """Initialize the HCCL weight transfer process group with the trainer.""" - self._check_weight_transfer_engine() - assert self.weight_transfer_engine is not None - typed_init_info = self.weight_transfer_engine.parse_init_info(init_info) - self.weight_transfer_engine.init_transfer_engine(typed_init_info) - - def _check_nz_disabled(self) -> None: - if envs_ascend.VLLM_ASCEND_ENABLE_NZ: - raise ValueError( - "FRACTAL_NZ mode is enabled. This may cause model parameter " - "precision issues in the RL scenarios. Please set " - "VLLM_ASCEND_ENABLE_NZ=0." - ) - - def start_weight_update(self, is_checkpoint_format: bool = True) -> None: - """Begin a new weight update; prepares the model for layerwise reload.""" - self._check_weight_transfer_engine() - - if self._weight_update_active: - raise RuntimeError( - "start_weight_update called while a weight update is already active. Call finish_weight_update first." - ) - - self._check_nz_disabled() - - if is_checkpoint_format: - from vllm.model_executor.model_loader.reload import initialize_layerwise_reload - - model = self.model_runner.model - with torch.device(self.device): - initialize_layerwise_reload(model) - - self._is_checkpoint_format = is_checkpoint_format - self._weight_update_active = True - - def update_weights(self, update_info: dict) -> None: - """Receive a chunk of weights from the trainer and load them in place.""" - self._check_weight_transfer_engine() - assert self.weight_transfer_engine is not None - - typed_update_info = self.weight_transfer_engine.parse_update_info(update_info) - model = self.model_runner.model - - # state machine driven by start/finish. - if not self._weight_update_active: - raise RuntimeError("start_weight_update must be called before update_weights.") - - with torch.device(self.device): - if self._is_checkpoint_format: - self.weight_transfer_engine.receive_weights( - typed_update_info, - load_weights=model.load_weights, - ) - else: - - def load_weights_direct(weights: list[tuple[str, torch.Tensor]]) -> None: - with torch.no_grad(): - for name, weight in weights: - param = model.get_parameter(name) - param.copy_(weight) - - self.weight_transfer_engine.receive_weights( - typed_update_info, - load_weights=load_weights_direct, - ) - - # HCCL broadcast / packed paths are asynchronous. - # Sync so the next step uses the new weights. - torch.npu.synchronize() - - def finish_weight_update(self) -> None: - """Finish the current weight update; runs layerwise postprocessing.""" - self._check_weight_transfer_engine() - - if not self._weight_update_active: - raise RuntimeError("start_weight_update must be called before finish_weight_update.") - - if self._is_checkpoint_format: - from vllm.model_executor.model_loader.reload import finalize_layerwise_reload - - model = self.model_runner.model - with torch.device(self.device): - finalize_layerwise_reload(model, self.model_config) - - self._weight_update_active = False - self._is_checkpoint_format = True - - def shutdown(self) -> None: - if ensure_kv_transfer_shutdown is not None: - ensure_kv_transfer_shutdown() - - if self.profiler is not None: - self.profiler.shutdown() - - if weight_transfer_engine := getattr(self, "weight_transfer_engine", None): - weight_transfer_engine.shutdown() - - if model_runner := getattr(self, "model_runner", None): - shutdown_fn = getattr(model_runner, "shutdown", None) - if callable(shutdown_fn): - shutdown_fn() - def initialize_cache(self, num_gpu_blocks: int, num_cpu_blocks: int) -> None: self.cache_config.num_gpu_blocks = num_gpu_blocks self.cache_config.num_cpu_blocks = num_cpu_blocks @@ -456,10 +331,6 @@ def init_device(self): else: self.model_runner = NPUModelRunner(self.vllm_config, self.device) - if self.rank == 0: - # If usage stat is enabled, collect relevant info. - report_usage_stats(self.vllm_config) - @torch.inference_mode() def determine_available_memory(self) -> int: """Profiles the peak memory usage of the model to determine how much @@ -503,21 +374,6 @@ def determine_available_memory(self) -> int: # on exit, but we override it below with this pre-graph value. profile_torch_peak = torch.npu.memory_stats(self.device).get("allocated_bytes.all.peak", 0) - npugraph_memory_estimate = 0 - should_profile_npugraph_memory = self.vllm_config.compilation_config.cudagraph_mode != CUDAGraphMode.NONE - if should_profile_npugraph_memory and getattr(self.model_runner, "use_compress", False): - hf_config = self.model_config.hf_config - if getattr(hf_config, "model_type", None) == "deepseek_v4": - logger.warning_once( - "Skipping ACL graph memory profiling for DeepSeek-V4 " - "DSA compressed attention. Graph mode remains enabled; " - "the normal ACL graph capture still runs after KV cache " - "allocation." - ) - should_profile_npugraph_memory = False - if should_profile_npugraph_memory: - npugraph_memory_estimate = self.model_runner.profile_cudagraph_memory() - # Override torch_peak_increase with the pre-graph-capture value to # avoid double-counting graph pool memory as activation memory. profile_result.torch_peak_increase = profile_torch_peak - profile_result.before_profile.torch_peak @@ -525,14 +381,9 @@ def determine_available_memory(self) -> int: profile_result.non_torch_increase + profile_result.torch_peak_increase + profile_result.weights_memory ) - npugraph_memory_estimate_applied = ( - npugraph_memory_estimate if envs_vllm.VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS else 0 - ) - # Save per-category memory for use in compile_or_warm_up_model() (step 5). self.peak_activation_memory = profile_result.torch_peak_increase self.non_torch_memory = profile_result.non_torch_increase - self.npugraph_memory_estimate = npugraph_memory_estimate free_gpu_memory = profile_result.after_profile.free_memory assert self.init_snapshot.free_memory > free_gpu_memory, ( @@ -544,50 +395,13 @@ def determine_available_memory(self) -> int: "To fix this, ensure consistent GPU memory allocation or " "isolate vLLM in its own container." ) - self.available_kv_cache_memory_bytes = ( - self.requested_memory - profile_result.non_kv_cache_memory - npugraph_memory_estimate_applied - ) + self.available_kv_cache_memory_bytes = self.requested_memory - profile_result.non_kv_cache_memory logger.debug(profile_result) logger.info_once( "Available KV cache memory: %.2f GiB", GiB(self.available_kv_cache_memory_bytes), scope="local" ) - if npugraph_memory_estimate > 0: - total_mem = self.init_snapshot.total_memory - current_util = self.cache_config.gpu_memory_utilization - ng_util_delta = npugraph_memory_estimate / total_mem - suggested_util = min( - round(current_util + ng_util_delta, 4), - 1.0, - ) - if envs_vllm.VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS: - equiv_util = round(current_util - ng_util_delta, 4) - logger.info( - "ACL graph memory profiling is enabled (default since " - "v0.22.1). The current --gpu-memory-utilization=%.4f is " - "equivalent to --gpu-memory-utilization=%.4f without " - "ACL graph memory profiling. To maintain the same " - "effective KV cache size as before, increase " - "--gpu-memory-utilization to %.4f. To disable, set " - "VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=0.", - current_util, - equiv_util, - suggested_util, - ) - else: - logger.warning( - "ACL graph memory profiling is disabled " - "(VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=0). " - "Without it, ACL graph memory is not accounted for " - "during KV cache allocation, which may require lowering " - "--gpu-memory-utilization to avoid OOM. Consider " - "re-enabling it (the default as of v0.22.1) and increasing " - "--gpu-memory-utilization from %.4f to %.4f.", - current_util, - suggested_util, - ) - return int(self.available_kv_cache_memory_bytes) def profile_memory(self) -> None: @@ -683,25 +497,6 @@ def load_model(self) -> None: with context, set_current_vllm_config(self.vllm_config): self.model_runner.load_model() - if self.vllm_config.weight_transfer_config is not None: - from vllm.distributed.weight_transfer.factory import ( - WeightTransferEngineFactory, - ) - - if vllm_version_is("0.21.0"): - # v0.21.0: create_engine takes (config, parallel_config) - self.weight_transfer_engine = WeightTransferEngineFactory.create_engine( - self.vllm_config.weight_transfer_config, - self.vllm_config.parallel_config, - ) - else: - # main: create_engine takes (config, parallel_config, model) - self.weight_transfer_engine = WeightTransferEngineFactory.create_engine( - self.vllm_config.weight_transfer_config, - self.vllm_config.parallel_config, - self.model_runner.get_model(), - ) - def compile_or_warm_up_model(self) -> CompilationTimes: # Note: need to adapt for graph mode. warmup_sizes = (self.vllm_config.compilation_config.compile_sizes or []).copy() @@ -730,18 +525,6 @@ def compile_or_warm_up_model(self) -> CompilationTimes: if not self.model_config.enforce_eager: npugraph_memory_bytes = self.model_runner.capture_model() - # Compare actual vs estimated ACL graph memory (if we did profiling) - if hasattr(self, "npugraph_memory_estimate") and self.npugraph_memory_estimate > 0: - GiB = lambda b: round(b / GiB_bytes, 2) - diff = abs(npugraph_memory_bytes - self.npugraph_memory_estimate) - logger.info( - "ACL graph pool memory: %s GiB (actual), %s GiB (estimated), difference: %s GiB (%.1f%%).", - GiB(npugraph_memory_bytes), - GiB(self.npugraph_memory_estimate), - GiB(diff), - 100 * diff / max(npugraph_memory_bytes, 1), - ) - # Suggest an optimal --kv-cache-memory value for future runs. # Only emitted when we ran full profiling (kv_cache_memory_bytes was not # pre-specified) so that peak_activation_memory etc. are available. @@ -878,9 +661,7 @@ def profile_prefill_latency(self, num_tokens: int) -> float: return latency_ms - def get_kv_connector_handshake_metadata( - self, - ) -> dict[int, KVConnectorHandshakeMetadata] | dict[tuple[int, int], KVConnectorHandshakeMetadata] | None: + def get_kv_connector_handshake_metadata(self) -> dict | None: """Get KV connector metadata from this worker if available.""" if not has_kv_transfer_group(): return None @@ -891,12 +672,7 @@ def get_kv_connector_handshake_metadata( # metadata across workers. if (metadata := connector.get_handshake_metadata()) is None: return None - tp_rank = get_tp_group().rank_in_group - if vllm_version_is("0.22.1"): - return {tp_rank: metadata} - - pp_rank = get_pp_group().rank_in_group - return {(pp_rank, tp_rank): metadata} + return {self.rank: metadata} def get_kv_cache_spec(self) -> dict[str, KVCacheSpec]: return self.model_runner.get_kv_cache_spec() @@ -927,18 +703,6 @@ def initialize_from_config(self, kv_cache_config: KVCacheConfig) -> None: with context: self.model_runner.initialize_kv_cache(kv_cache_config) - # Build KV-zero metadata outside the CuMem pool so the bookkeeping - # GPU tensors (seg_addrs, block-id buffers) use the standard PyTorch - # allocator and are not discarded during sleep/wake cycles. - if ( - kv_cache_config.needs_kv_cache_zeroing - and hasattr(self.model_runner, "_init_kv_zero_meta") - and self.vllm_config is not None - and self.vllm_config.speculative_config is not None - and self.vllm_config.speculative_config.num_speculative_tokens > 1 - ): - self.model_runner._init_kv_zero_meta() - def profile(self, is_start: bool = True, profile_prefix: str | None = None): # Check if profiling is enabled (RFC #6954 - align with upstream vLLM) if self.profiler_config is None or self.profiler_config.profiler is None: diff --git a/vllm_ascend/xlite/utils.py b/vllm_ascend/xlite/utils.py index 42494ed4a..60fde0b5c 100644 --- a/vllm_ascend/xlite/utils.py +++ b/vllm_ascend/xlite/utils.py @@ -15,273 +15,13 @@ # """Utility functions for xlite.""" -import threading -from collections.abc import Callable, Generator, Sequence -from contextlib import contextmanager -from dataclasses import dataclass -from logging import Logger -from typing import Any, Literal, TypedDict - -import torch -import torch.nn as nn -from vllm.logger import logger -from xlite._C import Model, ModelConfig - -from vllm_ascend.attention.attention_v1 import AscendMetadata -from vllm_ascend.attention.mla_v1 import AscendMLAMetadata -from vllm_ascend.attention.sfa_v1 import AscendSFAMetadata +from typing import Any _MISSING = object() """Unique sentinel for missing attributes in this module.""" -class AttributeSetterMixin: - """A mixin that allows setting attributes safely without raising AttributeError for missing attributes. This is - useful for handling C++ extension objects that may not have all attributes defined in all versions. The class will - simply ignore attempts to set attributes that do not exist, while allowing setting existing attributes as usual. - - Additionally, a context manager interface is provided for checking the incoming value before setting the attribute. - The value is only set if the attribute exists and the optional `match_condition` is satisfied. - - Good for backwards compatibility. For subclasses, :mod:`AttributeSetterMixin` must be the first parent class in the - inheritance chain to work properly (i.e., the second object in the method resolution order (MRO)). - - Example usage:: - - class Model: - def __init__(self): - self.some_existing_attr = 0 - - - class MyModel(AttributeSetterMixin, Model): - _on_missing_attr = "ignore" # silently ignore missing attributes - - - model = MyModel(...) - model.some_existing_attr = 42 # sets the attribute as usual - model.some_missing_attr = "hello" # does nothing, no error raised - - with model.condition(lambda v: isinstance(v, int) and v > 0): - model.some_existing_attr = -1 # does not set because condition is not met - model.some_existing_attr = 100 # sets because condition is met - """ - - _on_missing_attr: Literal["raise", "warn", "ignore"] = "warn" - """Behavior when attempting to set a missing attribute. If `warn`, a logger must be provided to log a warning.""" - _logger: Logger | None = None - """Optional logger for warning about missing attributes. If None, no warnings will be logged.""" - - def __init_subclass__(cls) -> None: - if cls.__mro__[1] is not AttributeSetterMixin: - raise TypeError( - f"{cls.__name__} inherits from AttributeSetterMixin but does not have AttributeSetterMixin as the first" - f" parent class. Use `class {cls.__name__}(AttributeSetterMixin, ...)` to define the subclass, instead." - ) - - def _get_thread_local(self) -> threading.local: - """Lazily initialize a per-instance, per-thread local storage without going through __setattr__.""" - try: - return object.__getattribute__(self, "_thread_local") - except AttributeError: - local = threading.local() - object.__setattr__(self, "_thread_local", local) - return local - - def __setattr__(self, name: str, value: Any) -> None: - if not (hasattr(type(self), name) or name in self.__dict__): - if self._on_missing_attr == "raise": - raise AttributeError(f"{type(self).__name__} has no attribute {name}.") - elif self._on_missing_attr == "warn" and self._logger: - self._logger.warning( - "%s has no attribute %s. Your `xlite` version might be incompatible.", type(self).__name__, name - ) - return - match_condition = getattr(self._get_thread_local(), "match_condition", None) - if match_condition is not None and not match_condition(value): - return - super().__setattr__(name, value) - - @contextmanager - def condition(self, match_condition: Callable[..., bool]) -> Generator["AttributeSetterMixin", None, None]: - """Context manager that gates attribute setting on `match_condition`. - - Usage:: - - with obj.condition(lambda v: v > 0): - obj.some_attr = 42 # only set if 42 > 0 - """ - local = self._get_thread_local() - previous = getattr(local, "match_condition", None) # save for nesting - local.match_condition = match_condition - try: - yield self - finally: - local.match_condition = previous # always restore, even on exception - - -class XModel(AttributeSetterMixin, Model): - """:mod:`xlite._C.Model` subclass with safe attribute setting for better backwards compatibility.""" - - if torch.distributed.get_rank() == 0: - _logger = logger - - -class XModelConfig(AttributeSetterMixin, ModelConfig): - """:mod:`xlite._C.ModelConfig` subclass with safe attribute setting for better backwards compatibility.""" - - if torch.distributed.get_rank() == 0: - _logger = logger - - -@dataclass -class AttnMetadataRouter: - """A router for attention metadata objects of different types. This is used to handle the differences in attention - metadata across different model architectures and vLLM/vLLM-ascend versions in a more robust way. - - The router provides unified access to commonly used attention metadata attributes (e.g., actual sequence lengths for - query and block tables) via properties. - - Currently included metadata types: - - - `AscendMetadata` - - `AscendMLAMetadata` - - `AscendSFAMetadata` - - Typically, the attention metadata has the following notations:: - - |---------- N-1 iteration --------| - |---------------- N iteration ---------------------| - |- tokenA -|......................|-- newTokens ---| - |---------- context_len ----------| - |-------------------- seq_len ---------------------| - |-- query_len ---| - """ - - attn_metadata: Any - """The attention metadata object to route, e.g., an instance of `AscendMetadata` or `AscendSFAMetadata`.""" - device: str | torch.device | int | None = "cpu" - """Device specification for the returned tensors. If None, the tensors will be on the same device as the original - metadata tensors. The current implementation assumes `cpu` device for minimal data transfer.""" - - @contextmanager - def on_device(self, device: str | torch.device | int | None) -> Generator["AttnMetadataRouter", None, None]: - """Context manager to temporarily set the device for the router. This is useful for cases where we want to - access multiple properties on the same device without repeatedly specifying the device. - - Usage:: - - with router.on_device("cpu"): - query_lens = router.cu_query_lens # on cpu - block_tables = router.block_tables # also on cpu - """ - original_device = self.device - self.device = device - try: - yield self - finally: - self.device = original_device - - def __getattr__(self, name: str) -> Any: - """Route attribute access to the appropriate handler method based on the attribute name.""" - if (value := getattr(self.attn_metadata, name, _MISSING)) is not _MISSING: - return value - - raise AttributeError(f"{type(self.attn_metadata).__name__} has no attribute {name}.") - - @property - def cu_query_lens(self) -> torch.Tensor: - """Get the cumulative query lengths from the attention metadata, if available.""" - if isinstance(self.attn_metadata, (AscendMetadata, AscendMLAMetadata)): - return torch.as_tensor(self.attn_metadata.query_start_loc, device=self.device) - - if isinstance(self.attn_metadata, AscendSFAMetadata): - return torch.as_tensor(self.attn_metadata.cum_query_lens, device=self.device) - - for candidate in ["query_start_loc", "cum_query_lens", "actual_seq_lengths_q"]: - if (lengths := getattr(self.attn_metadata, candidate, None)) is not None: - return torch.as_tensor(lengths, device=self.device) - - raise ValueError( - f"Cannot find actual sequence lengths for query in attention metadata of type {type(self.attn_metadata)}." - ) - - @property - def block_tables(self) -> torch.Tensor: - """Get the block tables from the attention metadata, if available.""" - if isinstance(self.attn_metadata, AscendMetadata): - return torch.as_tensor(self.attn_metadata.block_tables, device=self.device) - - if isinstance(self.attn_metadata, AscendSFAMetadata): - return torch.as_tensor(self.attn_metadata.block_table, device=self.device) - - if isinstance(self.attn_metadata, AscendMLAMetadata): - # AscendMLAMetadataBuilder.build_decode_metadata breaks `AscendMLAMetadata.block_tables` - # thus we may need to patch together block tables from prefill and decode metadata if available - block_tables = [] - if self.attn_metadata.decode is not None: - block_tables.append(torch.as_tensor(self.attn_metadata.decode.block_table, device=self.device)) - if self.attn_metadata.prefill is not None: - block_tables.append(torch.as_tensor(self.attn_metadata.prefill.block_table, device=self.device)) - if block_tables: - return torch.concat(block_tables, dim=0) - return torch.as_tensor(self.attn_metadata.block_tables, device=self.device) - - for candidate in ["block_tables", "block_table"]: - if (tables := getattr(self.attn_metadata, candidate)) is not None: - return torch.as_tensor(tables, device=self.device) - - raise ValueError(f"Cannot find block tables in attention metadata of type {type(self.attn_metadata)}.") - - @property - def seq_lens(self) -> torch.Tensor: - """Return the per-sequence `seq_lens` tensor in a device-safe torch.Tensor form.""" - if isinstance(self.attn_metadata, (AscendMetadata, AscendSFAMetadata)): - return torch.as_tensor(self.attn_metadata.seq_lens_cpu, device=self.device) - - if isinstance(self.attn_metadata, AscendMLAMetadata): - # AscendMLAMetadataBuilder.build_decode_metadata breaks `AscendMLAMetadata.seq_lens` - # thus prefill metadata's seq_lens is preferentially used if available - if self.attn_metadata.prefill is not None: - return torch.as_tensor(self.attn_metadata.prefill.seq_lens, device=self.device) - return torch.as_tensor(self.attn_metadata.seq_lens_cpu, device=self.device) - - for candidate in ["seq_lens_cpu", "seq_lens"]: - if (s := getattr(self.attn_metadata, candidate)) is not None: - return torch.as_tensor(s, device=self.device) - - raise ValueError(f"Cannot find seq_lens in attention metadata of type {type(self.attn_metadata)}.") - - @property - def num_prefills(self) -> int: - for candidate in ["num_prefills"]: - if (num_prefills := getattr(self.attn_metadata, candidate)) is not None: - return int(num_prefills) - return 0 - - @property - def num_decodes(self) -> int: - for candidate in ["num_decodes"]: - if (num_decodes := getattr(self.attn_metadata, candidate)) is not None: - return int(num_decodes) - return 0 - - @property - def num_decode_tokens(self) -> int: - for candidate in ["num_decode_tokens"]: - if (num_decode_tokens := getattr(self.attn_metadata, candidate, None)) is not None: - return int(num_decode_tokens) - return 0 - - @property - def num_actual_tokens(self) -> int: - """Return the number of actual tokens (excluding padding).""" - for candidate in ["num_actual_tokens"]: - if (num_actual_tokens := getattr(self.attn_metadata, candidate)) is not None: - return int(num_actual_tokens) - raise ValueError(f"Cannot find num_actual_tokens in attention metadata of type {type(self.attn_metadata)}.") - - -def get_nested_attr(obj: Any, /, *attrs: str, default: Any = None, raises: bool = False) -> Any: +def _get_nested_attr(obj: Any, /, *attrs: str, default: Any = None) -> Any: """Get/collect a nested attribute from an object. The attribute path is specified as a sequence of attribute names. If any attribute in the path is missing, the @@ -291,7 +31,6 @@ def get_nested_attr(obj: Any, /, *attrs: str, default: Any = None, raises: bool obj (Any): Root object. *attrs (str): Sequence of attribute names to traverse. default (Any, keyword-only, default=None): Default value to return if any attribute is missing. - raises (bool, keyword-only, default=False): Whether to raise an error if any attribute is missing. Returns: Any: The resolved nested attribute. @@ -299,97 +38,14 @@ def get_nested_attr(obj: Any, /, *attrs: str, default: Any = None, raises: bool current = obj for attr in attrs: if (current := getattr(current, attr, _MISSING)) is _MISSING: - if raises: - raise AttributeError(f"{type(obj).__name__} has no attribute {'.'.join(attrs)} (failed at {attr}).") return default return current -def get_dotted_attr(obj: Any, dotted_attr: str, /, *, default: Any = None, raises: bool = False) -> Any: - """Get a nested attribute from an object using a dotted attribute string. - - This is a convenience wrapper around :meth:`_get_nested_attr` that allows specifying the attribute path as a single - dotted string. - - Args: - obj (Any): Root object. - dotted_attr (str): Dotted attribute string, e.g., "foo.bar.baz" to access `obj.foo.bar.baz`. - default (Any, keyword-only, default=None): Default value to return if any attribute is missing. - raises (bool, keyword-only, default=False): Whether to raise an error if any attribute is missing. - - Returns: - Any: The resolved nested attribute. - """ - return get_nested_attr(obj, *dotted_attr.split("."), default=default, raises=raises) - - -class WeightGetterConfig(TypedDict): - """Configuration dictionary for layer weight extraction in `get_layer_weights`. - - This class is written as a TypedDict for better type checking with `mypy` in the `xlite` module. - """ - - secondary_flattening: str | slice | None - post_processor: Callable[[torch.Tensor], torch.Tensor] | None - - -def get_layer_weights( - layers: Sequence[nn.Module], - layer_attr: str, - /, - *, - secondary_flattening: str | slice | None = None, - post_processor: Callable[[torch.Tensor], torch.Tensor] | None = None, - **kwargs: Any, -) -> list[torch.Tensor]: - """Extract specified weights from a sequence of layers with optional secondary flattening and post-processing. - - This function retrieves the specified attribute (e.g., "self_attn.q_proj.weight") from each layer in the provided - sequence. If `secondary_flattening` is specified, it will further expand the retrieved attribute as a list and - collect all items from these lists across layers. An optional `post_processor` can be applied to each retrieved - tensor before returning the final list of weights. - - Args: - layers (Sequence[nn.Module]): Sequence of layers to retrieve weights from. - layer_attr (str): Dotted attribute string specifying the layer attribute to retrieve (`layers.[i].[layer_attr]`) - , e.g., "self_attn.q_norm.weight". - secondary_flattening (str | slice | None, optional): If specified, indicates that the retrieved layer attribute - is a list of tensors and we need to further flatten it. The expansion can be specified as: - - - `str`: A dotted attribute string such that `layers.[i].[secondary_flattening]` gives the number of items - to flatten for that layer. - - `slice`: A slice specifying how to slice `layers.[i].[layer_attr]` and then flatten the sliced part. - - `None`: No secondary flattening; `layers.[i].[layer_attr]` is directly collected. - post_processor (Callable[[torch.Tensor], torch.Tensor] | None, optional): An optional function to apply to - each retrieved tensor before returning the final list of weights. - **kwargs: Additional keyword arguments for future extensions. - - Returns: - list[torch.Tensor]: List of retrieved weights. - """ - if not secondary_flattening: - weights = [ - weight for layer in layers if (weight := get_dotted_attr(layer, layer_attr, default=None)) is not None - ] - elif isinstance(secondary_flattening, str): - weights = [ - weight - for layer in layers - if (weight_lst := get_dotted_attr(layer, layer_attr, default=[])) is not None - for weight in weight_lst[: get_dotted_attr(layer, secondary_flattening, default=0)] - ] - elif isinstance(secondary_flattening, slice): - weights = [ - weight - for layer in layers - if (weight_lst := get_dotted_attr(layer, layer_attr, default=[])) is not None - for weight in weight_lst[secondary_flattening] - ] - else: - raise ValueError( - f"Invalid type for secondary_flattening: {type(secondary_flattening)}. Expected str, slice, or None." - ) - - if not post_processor: - return weights - return [post_processor(weight) for weight in weights] +def rgetattr(obj: Any, attr, default=None): + try: + for part in attr.split("."): + obj = getattr(obj, part) + return obj + except AttributeError: + return default diff --git a/vllm_ascend/xlite/xlite.py b/vllm_ascend/xlite/xlite.py index 46c0d2db6..7613bc28e 100644 --- a/vllm_ascend/xlite/xlite.py +++ b/vllm_ascend/xlite/xlite.py @@ -31,25 +31,14 @@ from vllm.forward_context import get_forward_context from vllm.logger import logger from vllm.sequence import IntermediateTensors -from xlite._C import AttnMeta, AttnMHA, Runtime, ScoringFuncSigmoid, ScoringFuncSoftmax +from xlite._C import AttnMeta, AttnMHA, Model, ModelConfig, Runtime, ScoringFuncSigmoid, ScoringFuncSoftmax from vllm_ascend.ascend_config import get_ascend_config from vllm_ascend.attention.attention_v1 import AscendAttentionState, AscendMetadata -from vllm_ascend.compilation.acl_graph import ACLGraphWrapper -from vllm_ascend.xlite.utils import ( - AttnMetadataRouter, - WeightGetterConfig, - XModel, - XModelConfig, - get_dotted_attr, - get_layer_weights, -) - -XliteInitResult: TypeAlias = tuple[XModel, torch.Tensor, int, torch.dtype] -XliteForwardResult: TypeAlias = torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]] +from vllm_ascend.xlite.utils import _get_nested_attr, rgetattr -_architecture_strategy_map: dict[str, type[XliteModel]] = {} -"""Mapping from model architecture names in `config.json` to their corresponding xlite adapter classes.""" +XliteInitResult: TypeAlias = tuple[Model, torch.Tensor, int, torch.dtype] +XliteForwardResult: TypeAlias = torch.Tensor | IntermediateTensors | tuple[torch.Tensor, list[torch.Tensor]] class XliteModel(ABC): @@ -67,32 +56,6 @@ class XliteModel(ABC): xlite_model (Model): Native xlite model container populated by subclasses. """ - _attn_metadata_type: type | tuple[type, ...] - """The expected type of attention metadata in the forward context for this architecture. Used for runtime checks - before forwarding. See :meth:`XliteWrapper.__call__` for usage.""" - _supported_architectures: Sequence[str] | str - """The list of model architecture names (from HuggingFace `config.json` "architectures" field) supported by this - adapter. Used for automatic adapter selection and registration.""" - - def __init_subclass__(cls, **kwargs: Any) -> None: - """Automatically register subclasses in the architecture strategy map and metadata type set.""" - ts = getattr(cls, "_attn_metadata_type", None) - if ts is None or (not isinstance(ts, type) and not all(isinstance(t, type) for t in ts)): - raise ValueError( - f"XliteModel subclass {cls.__name__} must define _attn_metadata_type as a type or a tuple of types." - ) - - arcs = getattr(cls, "_supported_architectures", None) - if arcs is None: - raise ValueError(f"XliteModel subclass {cls.__name__} must define _supported_architectures attribute.") - if isinstance(arcs, str): - arcs = [arcs] - for arc in arcs: - if arc in _architecture_strategy_map: - raise ValueError(f"Duplicate xlite adapter for architecture {arc}: {_architecture_strategy_map[arc]}") - _architecture_strategy_map[arc] = cls - super().__init_subclass__(**kwargs) - def __init__(self, runnable: nn.Module, vllm_config: VllmConfig) -> None: """Initialize the xlite model adapter. @@ -107,8 +70,8 @@ def __init__(self, runnable: nn.Module, vllm_config: VllmConfig) -> None: self.runnable = runnable self.vllm_config = vllm_config - self.xlite_config = XModelConfig() - self.xlite_model = XModel() + self.xlite_config = ModelConfig() + self.xlite_model = Model() def initialize(self) -> XliteInitResult: """Initialize an xlite model and precomputed RoPE cache. @@ -159,11 +122,11 @@ def _get_layers_and_model_prefix(self) -> tuple[Sequence[nn.Module], str]: """ if hasattr(self.runnable, "language_model"): layers = cast( - Sequence[nn.Module], get_dotted_attr(self.runnable.language_model, "model.layers", default=[]) + Sequence[nn.Module], _get_nested_attr(self.runnable.language_model, "model", "layers", default=[]) ) prefix = "language_model." else: - layers = cast(Sequence[nn.Module], get_dotted_attr(self.runnable, "model.layers", default=[])) + layers = cast(Sequence[nn.Module], _get_nested_attr(self.runnable, "model", "layers", default=[])) prefix = "" return layers, prefix @@ -182,50 +145,6 @@ def _precompute_freqs_cis(self) -> torch.Tensor: :meth:`_build_model_config` should be called prior to this method. """ - @staticmethod - def is_tensor_nz(t: torch.Tensor) -> bool: - """Check if a tensor is in NZ format. - - Args: - t (torch.Tensor): The tensor to check. - - Returns: - bool: True if the tensor is in NZ format, False otherwise. - """ - format = torch_npu.get_npu_format(t) - return format == torch_npu.Format.FRACTAL_NZ - - @staticmethod - def all_tensors_zero(tensors: torch.Tensor | list[torch.Tensor] | tuple[torch.Tensor] | None) -> bool: - """Check if all tensors in the list/tuple are zero tensors. - - Args: - tensors (torch.Tensor | list[torch.Tensor] | tuple[torch.Tensor] | None): The tensors to check. - - Returns: - bool: True if all tensors are zero tensors (or empty), False otherwise. - """ - if tensors is None: - return True - if not isinstance(tensors, (list, tuple)): - tensors = [tensors] - if len(tensors) == 0: - return True - return all(torch.allclose(t, t.new_zeros(1)) for t in tensors) - - @staticmethod - def _transform_deq_scale(deq_scale: torch.Tensor) -> torch.Tensor: - """ - The data format required by the fixpipe hardware is as follows: - - Data is stored in uint64_t, with the upper 32 bits being 0 and the lower 32 bits storing the FP32 format. The - lower 10 bits of the FP32 format are not involved in computation, and the actual data format is TF32. - """ - deq_scale_fp32 = deq_scale.to(torch.float32) - scale = deq_scale_fp32.new_zeros(deq_scale.shape[0] * 2) - scale[0::2] = deq_scale_fp32[0::1] - return scale - @property def hf_text_config(self) -> PretrainedConfig: """Convenience property to access HuggingFace text configuration from vLLM config. @@ -248,22 +167,12 @@ def hf_vision_config(self) -> PretrainedConfig | None: class LlamaXliteModel(XliteModel): - """xlite adapter base for Llama-like architectures. - - This is the *de facto* base adapter for all xlite-supported architectures and may contain configurations beyond - Llama-like dense models. `XliteModel` subclasses should inherit from this class unless there is a major divergence. - """ - - _attn_metadata_type = AscendMetadata - _supported_architectures = [ - "LlamaForCausalLM", - "Qwen2ForCausalLM", - "Qwen3ForCausalLM", - "Qwen3VLForConditionalGeneration", - ] + """xlite adapter for Llama-like dense transformer architectures.""" def _build_model_config(self) -> None: - xlite_config, vllm_config, hf_config = self.xlite_config, self.vllm_config, self.hf_text_config + vllm_config = self.vllm_config + hf_config = self.hf_text_config + xlite_config = self.xlite_config xlite_config.vocab_size = hf_config.vocab_size xlite_config.hidden_size = hf_config.hidden_size @@ -284,75 +193,119 @@ def _build_model_config(self) -> None: xlite_config.n_dense_layers = hf_config.num_hidden_layers xlite_config.intermediate_size = hf_config.intermediate_size xlite_config.def_tp_size = get_tensor_model_parallel_world_size() - xlite_config.def_dp_size = vllm_config.parallel_config.data_parallel_size - try: - ep_word_size = get_ep_group().world_size - xlite_config.moe_ep_size = ep_word_size if vllm_config.parallel_config.enable_expert_parallel else 1 - xlite_config.moe_tp_size = 1 if vllm_config.parallel_config.enable_expert_parallel else ep_word_size - except AssertionError: - xlite_config.moe_ep_size, xlite_config.moe_tp_size = 1, 1 - xlite_config.experts_weight_transpose = True + xlite_config.def_dp_size = 1 + xlite_config.moe_ep_size = 1 + xlite_config.moe_tp_size = 1 xlite_config.attn_type = AttnMHA - xlite_config.scoring_func = ScoringFuncSoftmax xlite_config.weight_nz = get_ascend_config().weight_nz_mode == 2 + scheduler_config = vllm_config.scheduler_config + max_batch_size = scheduler_config.max_num_seqs + max_seq_len = vllm_config.model_config.max_model_len xlite_config.max_m = ( - vllm_config.scheduler_config.max_num_batched_tokens + scheduler_config.max_num_batched_tokens if get_ascend_config().xlite_graph_config.full_mode - else vllm_config.scheduler_config.max_num_seqs + else scheduler_config.max_num_seqs ) - xlite_config.max_batch_size = vllm_config.scheduler_config.max_num_seqs - xlite_config.max_seq_len = vllm_config.model_config.max_model_len + xlite_config.max_batch_size = max_batch_size + xlite_config.max_seq_len = max_seq_len xlite_config.block_size = vllm_config.cache_config.block_size rope_parameters = getattr(hf_config, "rope_parameters", {}) - xlite_config.deepstack_num_level = len(getattr(self.hf_vision_config, "deepstack_visual_indexes", [])) - xlite_config.mrope_section = rope_parameters.get("mrope_section", []) - xlite_config.mrope_interleaved = rope_parameters.get("mrope_interleaved", False) + if hasattr(xlite_config, "deepstack_num_level"): + xlite_config.deepstack_num_level = len(getattr(self.hf_vision_config, "deepstack_visual_indexes", [])) + if hasattr(xlite_config, "mrope_section"): + xlite_config.mrope_section = rope_parameters.get("mrope_section", []) + if hasattr(xlite_config, "mrope_interleaved"): + xlite_config.mrope_interleaved = rope_parameters.get("mrope_interleaved", False) self.quantization = vllm_config.quant_config is not None def _build_model(self) -> None: - xlite_model, xlite_config, hf_config = self.xlite_model, self.xlite_config, self.hf_text_config + hf_config = self.hf_text_config + xlite_config = self.xlite_config + xlite_model = self.xlite_model + + params_dict = dict(self.runnable.named_parameters()) layers, model_prefix = self._get_layers_and_model_prefix() - xlite_model.embed = get_dotted_attr(self.runnable, f"{model_prefix}model.embed_tokens.weight", raises=True) - xlite_model.norm = get_dotted_attr(self.runnable, f"{model_prefix}model.norm.weight", raises=True) + def _require_param(param_name: str) -> torch.Tensor: + param = params_dict.get(param_name) + if not isinstance(param, torch.Tensor): + raise ValueError(f"Required parameter not found in the runnable: {param_name}") + return param + + xlite_model.embed = _require_param(f"{model_prefix}model.embed_tokens.weight") + xlite_model.norm = _require_param(f"{model_prefix}model.norm.weight") if hf_config.tie_word_embeddings: xlite_model.head = xlite_model.embed else: - xlite_model.head = get_dotted_attr(self.runnable, f"{model_prefix}lm_head.weight", raises=True) + xlite_model.head = _require_param(f"{model_prefix}lm_head.weight") - xlite_model.attn_norm = get_layer_weights(layers, "input_layernorm.weight") + xlite_model.attn_norm = [ + weight for layer in layers if (weight := _get_nested_attr(layer, "input_layernorm", "weight")) is not None + ] self.init_matmul_weights(layers, "mha_qkv", "self_attn.qkv_proj") self.init_matmul_weights(layers, "attn_out", "self_attn.o_proj") + mha_qkv_bias = [ + bias for layer in layers if (bias := _get_nested_attr(layer, "self_attn", "qkv_proj", "bias")) is not None + ] + q_norm = [ + weight + for layer in layers + if (weight := _get_nested_attr(layer, "self_attn", "q_norm", "weight")) is not None + ] + k_norm = [ + weight + for layer in layers + if (weight := _get_nested_attr(layer, "self_attn", "k_norm", "weight")) is not None + ] - mha_qkv_bias = get_layer_weights(layers, "self_attn.qkv_proj.bias") - xlite_config.qkv_bias = len(mha_qkv_bias) == xlite_config.n_layers - xlite_model.mha_qkv_bias = mha_qkv_bias if xlite_config.qkv_bias else [] - q_norm = get_layer_weights(layers, "self_attn.q_norm.weight") - k_norm = get_layer_weights(layers, "self_attn.k_norm.weight") - xlite_config.qk_norm = len(q_norm) == len(k_norm) == xlite_config.n_layers - xlite_model.mha_q_norm = q_norm if xlite_config.qk_norm else [] - xlite_model.mha_k_norm = k_norm if xlite_config.qk_norm else [] - - self.init_matmul_weights(layers, "mlp_up_gate", "mlp.gate_up_proj") - self.init_matmul_weights(layers, "mlp_down", "mlp.down_proj") - xlite_model.mlp_norm = get_layer_weights(layers, "post_attention_layernorm.weight") - - if not self.quantization: - return - - if xlite_model.mha_qkv: + if self.quantization: xlite_config.quant_attn_weight_nz = self.is_tensor_nz(xlite_model.mha_qkv[0]) xlite_config.quant_attn_weight_transpose = True - with xlite_model.condition(lambda tensors: not self.all_tensors_zero(tensors)): - xlite_model.norm_bias = get_dotted_attr(self.runnable, f"{model_prefix}model.norm.bias", raises=True) - xlite_model.attn_norm_bias = get_layer_weights(layers, "input_layernorm.bias") - xlite_model.mlp_norm_bias = get_layer_weights(layers, "post_attention_layernorm.bias") - if xlite_config.qk_norm: - xlite_model.mha_q_norm_bias = get_layer_weights(layers, "self_attn.q_norm.bias") - xlite_model.mha_k_norm_bias = get_layer_weights(layers, "self_attn.k_norm.bias") + norm_bias = params_dict.get(f"{model_prefix}model.norm.bias") + if norm_bias is not None and not self.all_tensors_zero([norm_bias]): + xlite_model.norm_bias = norm_bias + if params_dict.get(f"{model_prefix}model.layers.0.input_layernorm.bias") is not None: + attn_norm_bias = [layer.input_layernorm.bias for layer in layers] + if not self.all_tensors_zero(attn_norm_bias): + xlite_model.attn_norm_bias = attn_norm_bias + if params_dict.get(f"{model_prefix}model.layers.0.post_attention_layernorm.bias") is not None: + mlp_norm_bias = [layer.post_attention_layernorm.bias for layer in layers] + if not self.all_tensors_zero(mlp_norm_bias): + xlite_model.mlp_norm_bias = mlp_norm_bias + + if len(mha_qkv_bias) != xlite_config.n_layers: + xlite_config.qkv_bias = False + else: + xlite_config.qkv_bias = True + xlite_model.mha_qkv_bias = mha_qkv_bias + + if len(q_norm) != xlite_config.n_layers or len(k_norm) != xlite_config.n_layers: + xlite_config.qk_norm = False + else: + xlite_config.qk_norm = True + xlite_model.mha_q_norm = q_norm + xlite_model.mha_k_norm = k_norm + if self.quantization: + if params_dict.get(f"{model_prefix}model.layers.0.self_attn.q_norm.bias") is not None: + mha_q_norm_bias = [layer.self_attn.q_norm.bias for layer in layers] + if not self.all_tensors_zero(mha_q_norm_bias): + xlite_model.mha_q_norm_bias = mha_q_norm_bias + if params_dict.get(f"{model_prefix}model.layers.0.self_attn.k_norm.bias") is not None: + mha_k_norm_bias = [layer.self_attn.k_norm.bias for layer in layers] + if not self.all_tensors_zero(mha_k_norm_bias): + xlite_model.mha_k_norm_bias = mha_k_norm_bias + + xlite_model.mlp_norm = [ + weight + for layer in layers + if (weight := _get_nested_attr(layer, "post_attention_layernorm", "weight")) is not None + ] + + self.init_matmul_weights(layers, "mlp_up_gate", "mlp.gate_up_proj") + self.init_matmul_weights(layers, "mlp_down", "mlp.down_proj") def _precompute_freqs_cis(self) -> torch.Tensor: """Precompute rotary cosine/sine cache on NPU. @@ -383,137 +336,225 @@ def _precompute_freqs_cis(self) -> torch.Tensor: freq_cis = torch.cat((cos_cache, sin_cache), dim=-1) return freq_cis.to(device="npu") - def init_matmul_weights(self, layers: Sequence[torch.nn.Module], xlite_prefix: str, model_prefix: str) -> None: + def _prepare_deq_scale_weights(self, deq_scale: torch.Tensor): + """ + The data format required by the fixpipe hardware is as follows: + Data is stored in uint64_t, with the upper 32 bits being 0 and + the lower 32 bits storing the FP32 format.The lower 10 bits of + the FP32 format are not involved in computation, and the actual + data format is TF32. """ - Initialize MatMul-related weights with quantization support. + deq_scale_fp32 = deq_scale.to(torch.float32) + scale = torch.zeros(deq_scale.shape[0] * 2, dtype=torch.float32, device="npu") + scale[0::2] = deq_scale_fp32[0::1] + return scale - Args: - layers (Sequence[torch.nn.Module]): The transformer layers to extract weights from. - xlite_prefix (str): The prefix for the xlite model attributes to set. - model_prefix (str): The prefix for the model attributes to look up in each layer. + def is_tensor_nz(self, t: torch.Tensor): + format = torch_npu.get_npu_format(t) + return format == torch_npu.Format.FRACTAL_NZ + + def all_tensors_zero(self, tensors: list[torch.Tensor]) -> bool: + if not tensors: + return True + return all(torch.all(t == 0).item() for t in tensors) + + def init_matmul_weights(self, layers, xlite_prefix: str, vllm_prefix: str): + """ + init matmul weights, maybe quanted or unquanted """ + + def set_xlite_attr(layers, x_prefix: str, v_prefix: str): + obj = [weight for layer in layers if (weight := rgetattr(layer, f"{v_prefix}")) is not None] + setattr(xlite_model, f"{x_prefix}", obj) + + # Note: To be compatible with the attribute names of history, add the fallback mechanism + def setattr_fallback(obj: Any, value: Any, primary: str, fallback: str): + for attr in (primary, fallback): + if hasattr(obj, attr): + setattr(obj, attr, value) + return + xlite_model = self.xlite_model - setattr(xlite_model, xlite_prefix, get_layer_weights(layers, f"{model_prefix}.weight")) - if not self.quantization: - return - - def set_xlite_attr(xlite_attr: str, layer_attr: str): - setattr(xlite_model, xlite_attr, get_layer_weights(layers, layer_attr)) - - deq_scale = get_layer_weights(layers, f"{model_prefix}.deq_scale", post_processor=self._transform_deq_scale) - if len(deq_scale) > 0: # static quant - setattr(xlite_model, f"{xlite_prefix}_deq_scale", deq_scale) - set_xlite_attr(f"{xlite_prefix}_input_scale", f"{model_prefix}.aclnn_input_scale_reciprocal") - set_xlite_attr(f"{xlite_prefix}_input_offset", f"{model_prefix}.aclnn_input_offset") - set_xlite_attr(f"{xlite_prefix}_quant_bias", f"{model_prefix}.quant_bias") - else: - weight_scale = get_layer_weights( - layers, f"{model_prefix}.weight_scale", post_processor=self._transform_deq_scale - ) - setattr(xlite_model, f"{xlite_prefix}_deq_scale", weight_scale) + weight = [weight for layer in layers if (weight := rgetattr(layer, f"{vllm_prefix}.weight")) is not None] + setattr(xlite_model, f"{xlite_prefix}", weight) + + if self.quantization: + deq_scale = [ + self._prepare_deq_scale_weights(weight) + for layer in layers + if (weight := rgetattr(layer, f"{vllm_prefix}.deq_scale")) is not None + ] + is_static_quant = len(deq_scale) > 0 + if is_static_quant: + setattr_fallback(xlite_model, deq_scale, f"{xlite_prefix}_deq_scale", f"{xlite_prefix}_scale") + set_xlite_attr(layers, f"{xlite_prefix}_input_scale", f"{vllm_prefix}.aclnn_input_scale_reciprocal") + set_xlite_attr(layers, f"{xlite_prefix}_input_offset", f"{vllm_prefix}.aclnn_input_offset") + set_xlite_attr(layers, f"{xlite_prefix}_quant_bias", f"{vllm_prefix}.quant_bias") + else: + weight_scale = [ + self._prepare_deq_scale_weights(weight) + for layer in layers + if (weight := rgetattr(layer, f"{vllm_prefix}.weight_scale")) is not None + ] + setattr_fallback(xlite_model, weight_scale, f"{xlite_prefix}_deq_scale", f"{xlite_prefix}_scale") class QwenMoeXliteModel(LlamaXliteModel): """xlite adapter for Qwen MoE architectures.""" - _attn_metadata_type = AscendMetadata - _supported_architectures = ["Qwen3MoeForCausalLM", "Qwen3VLMoeForConditionalGeneration"] - def _build_model_config(self) -> None: super()._build_model_config() - xlite_config, hf_config = self.xlite_config, self.hf_text_config + vllm_config = self.vllm_config + hf_config = self.hf_text_config + xlite_config = self.xlite_config + + ep_group = get_ep_group() xlite_config.n_dense_layers = 0 xlite_config.n_routed_experts = hf_config.num_experts xlite_config.n_shared_experts = 0 xlite_config.n_act_experts = hf_config.num_experts_per_tok + xlite_config.def_dp_size = vllm_config.parallel_config.data_parallel_size + xlite_config.moe_ep_size = ep_group.world_size if vllm_config.parallel_config.enable_expert_parallel else 1 + xlite_config.moe_tp_size = 1 if vllm_config.parallel_config.enable_expert_parallel else ep_group.world_size + xlite_config.experts_weight_transpose = True xlite_config.moe_intermediate_size = hf_config.moe_intermediate_size xlite_config.norm_topk_prob = hf_config.norm_topk_prob + xlite_config.scoring_func = ScoringFuncSoftmax def _build_model(self) -> None: super()._build_model() - xlite_model, xlite_config = self.xlite_model, self.xlite_config - layers, _ = self._get_layers_and_model_prefix() - xlite_model.gate = get_layer_weights(layers, "mlp.gate.weight") - prefix = "mlp.experts." - kwargs: WeightGetterConfig = {"secondary_flattening": f"{prefix}local_num_experts", "post_processor": None} - xlite_model.re_up_gate = get_layer_weights(layers, f"{prefix}w13_weight", **kwargs) - xlite_model.re_down = get_layer_weights(layers, f"{prefix}w2_weight", **kwargs) + layers, _ = self._get_layers_and_model_prefix() + xlite_model = self.xlite_model + xlite_config = self.xlite_config + xlite_model.gate = [ + weight for layer in layers if (weight := _get_nested_attr(layer, "mlp", "gate", "weight")) is not None + ] + xlite_model.re_up_gate = [ + weight + for layer in layers + if (w13_weight := _get_nested_attr(layer, "mlp", "experts", "w13_weight")) is not None + for weight in w13_weight[: _get_nested_attr(layer, "mlp", "experts", "local_num_experts", default=0)] + ] + xlite_model.re_down = [ + weight + for layer in layers + if (w2_weight := _get_nested_attr(layer, "mlp", "experts", "w2_weight")) is not None + for weight in w2_weight[: _get_nested_attr(layer, "mlp", "experts", "local_num_experts", default=0)] + ] xlite_config.experts_weight_nz = self.is_tensor_nz(xlite_model.re_up_gate[0]) - if self.quantization: - kwargs["post_processor"] = self._transform_deq_scale - xlite_model.re_up_gate_scale = get_layer_weights(layers, f"{prefix}w13_weight_scale_fp32", **kwargs) - xlite_model.re_down_scale = get_layer_weights(layers, f"{prefix}w2_weight_scale", **kwargs) + xlite_model.re_up_gate_scale = [ + self._prepare_deq_scale_weights(weight) + for layer in layers + if (w13_weight := _get_nested_attr(layer, "mlp", "experts", "w13_weight_scale_fp32")) is not None + for weight in w13_weight[: layer.mlp.experts.local_num_experts] + ] + xlite_model.re_down_scale = [ + self._prepare_deq_scale_weights(weight) + for layer in layers + if (w2_weight := _get_nested_attr(layer, "mlp", "experts", "w2_weight_scale")) is not None + for weight in w2_weight[: layer.mlp.experts.local_num_experts] + ] class Glm4MoeXliteModel(LlamaXliteModel): """xlite adapter for GLM4 MoE architectures.""" - _attn_metadata_type = AscendMetadata - _supported_architectures = ["Glm4MoeForCausalLM"] - def _build_model_config(self) -> None: super()._build_model_config() - xlite_config, hf_config = self.xlite_config, self.hf_text_config + vllm_config = self.vllm_config + hf_config = self.hf_text_config + xlite_config = self.xlite_config + + ep_group = get_ep_group() if hasattr(hf_config, "partial_rotary_factor"): partial_rotary_factor = hf_config.partial_rotary_factor else: partial_rotary_factor = getattr(hf_config, "rope_parameters", {}).get("partial_rotary_factor", 1.0) - xlite_config.rope_head_dim = int(xlite_config.head_dim * partial_rotary_factor) + xlite_config.rope_head_dim = int(hf_config.head_dim * partial_rotary_factor) xlite_config.n_dense_layers = getattr(hf_config, "first_k_dense_replace", 0) xlite_config.n_routed_experts = hf_config.n_routed_experts xlite_config.n_shared_experts = hf_config.n_shared_experts xlite_config.n_act_experts = hf_config.num_experts_per_tok + xlite_config.def_dp_size = vllm_config.parallel_config.data_parallel_size + xlite_config.moe_ep_size = ep_group.world_size if vllm_config.parallel_config.enable_expert_parallel else 1 + xlite_config.moe_tp_size = 1 if vllm_config.parallel_config.enable_expert_parallel else ep_group.world_size + xlite_config.experts_weight_transpose = True xlite_config.moe_intermediate_size = hf_config.moe_intermediate_size xlite_config.norm_topk_prob = hf_config.norm_topk_prob xlite_config.scoring_func = ScoringFuncSigmoid xlite_config.route_scale = hf_config.routed_scaling_factor - xlite_config.gate_captured = False def _build_model(self) -> None: super()._build_model() - xlite_model, xlite_config = self.xlite_model, self.xlite_config - layers, _ = self._get_layers_and_model_prefix() - xlite_model.gate = get_layer_weights(layers, "mlp.gate.weight") - # NOTE: type conversion for numerical stability in xlite's implementation - xlite_model.gate_bias = get_layer_weights( - layers, "mlp.gate.e_score_correction_bias", post_processor=lambda b: b.to(torch.float32) - ) + layers, _ = self._get_layers_and_model_prefix() + xlite_model = self.xlite_model + xlite_config = self.xlite_config + xlite_model.gate = [ + weight for layer in layers if (weight := _get_nested_attr(layer, "mlp", "gate", "weight")) is not None + ] + xlite_model.gate_bias = [ + bias.to(torch.float32) # NOTE: type conversion for numerical stability in xlite's implementation + for layer in layers + if (bias := _get_nested_attr(layer, "mlp", "gate", "e_score_correction_bias")) is not None + ] self.init_matmul_weights(layers, "se_up_gate", "mlp.shared_experts.gate_up_proj") self.init_matmul_weights(layers, "se_down", "mlp.shared_experts.down_proj") + xlite_model.re_up_gate = [ + w13_weight_i + for layer in layers + if (w13_weight := _get_nested_attr(layer, "mlp", "experts", "w13_weight")) is not None + for w13_weight_i in w13_weight[: _get_nested_attr(layer, "mlp", "experts", "local_num_experts", default=0)] + ] + xlite_model.re_down = [ + w2_weight_i + for layer in layers + if (w2_weight := _get_nested_attr(layer, "mlp", "experts", "w2_weight")) is not None + for w2_weight_i in w2_weight[: _get_nested_attr(layer, "mlp", "experts", "local_num_experts", default=0)] + ] - prefix = "mlp.experts." - kwargs: WeightGetterConfig = {"secondary_flattening": f"{prefix}local_num_experts", "post_processor": None} - xlite_model.re_up_gate = get_layer_weights(layers, f"{prefix}w13_weight", **kwargs) - xlite_model.re_down = get_layer_weights(layers, f"{prefix}w2_weight", **kwargs) if xlite_model.re_up_gate: xlite_config.experts_weight_nz = self.is_tensor_nz(xlite_model.re_up_gate[0]) if self.quantization: - kwargs["post_processor"] = self._transform_deq_scale - xlite_model.re_up_gate_scale = get_layer_weights(layers, f"{prefix}w13_weight_scale_fp32", **kwargs) - xlite_model.re_down_scale = get_layer_weights(layers, f"{prefix}w2_weight_scale", **kwargs) + xlite_model.re_up_gate_scale = [ + self._prepare_deq_scale_weights(weight) + for layer in layers + if (w13_weight := _get_nested_attr(layer, "mlp", "experts", "w13_weight_scale_fp32")) is not None + for weight in w13_weight[: layer.mlp.experts.local_num_experts] + ] + xlite_model.re_down_scale = [ + self._prepare_deq_scale_weights(weight) + for layer in layers + if (w2_weight := _get_nested_attr(layer, "mlp", "experts", "w2_weight_scale")) is not None + for weight in w2_weight[: layer.mlp.experts.local_num_experts] + ] class MiniMaxM2XliteModel(LlamaXliteModel): """xlite adapter for MiniMax M2 architectures.""" - _attn_metadata_type = AscendMetadata - _supported_architectures = ["MiniMaxM2ForCausalLM"] - def _build_model_config(self) -> None: super()._build_model_config() - xlite_config, hf_config = self.xlite_config, self.hf_text_config + vllm_config = self.vllm_config + hf_config = self.hf_text_config + xlite_config = self.xlite_config + + ep_group = get_ep_group() xlite_config.rope_head_dim = hf_config.rotary_dim xlite_config.n_dense_layers = 0 xlite_config.n_routed_experts = hf_config.num_local_experts xlite_config.n_shared_experts = 0 xlite_config.n_act_experts = hf_config.num_experts_per_tok + xlite_config.def_dp_size = vllm_config.parallel_config.data_parallel_size + xlite_config.moe_ep_size = ep_group.world_size if vllm_config.parallel_config.enable_expert_parallel else 1 + xlite_config.moe_tp_size = 1 if vllm_config.parallel_config.enable_expert_parallel else ep_group.world_size + xlite_config.experts_weight_transpose = True xlite_config.moe_intermediate_size = hf_config.intermediate_size xlite_config.norm_topk_prob = True xlite_config.qk_norm_full = True @@ -521,31 +562,54 @@ def _build_model_config(self) -> None: def _build_model(self) -> None: super()._build_model() - xlite_model, xlite_config = self.xlite_model, self.xlite_config - layers, _ = self._get_layers_and_model_prefix() - xlite_model.gate = get_layer_weights(layers, "block_sparse_moe.gate.weight") - # NOTE: type conversion for numerical stability in xlite's implementation - xlite_model.gate_bias = get_layer_weights( - layers, "block_sparse_moe.e_score_correction_bias", post_processor=lambda b: b.to(torch.float32) - ) + layers, _ = self._get_layers_and_model_prefix() + xlite_model = self.xlite_model + xlite_config = self.xlite_config + xlite_model.gate = [ + weight + for layer in layers + if (weight := _get_nested_attr(layer, "block_sparse_moe", "gate", "weight")) is not None + ] + xlite_model.gate_bias = [ + bias.to(torch.float32) # NOTE: type conversion for numerical stability in xlite's implementation + for layer in layers + if (bias := _get_nested_attr(layer, "block_sparse_moe", "e_score_correction_bias")) is not None + ] + xlite_model.re_up_gate = [ + w13_weight_i + for layer in layers + if (w13_weight := _get_nested_attr(layer, "block_sparse_moe", "experts", "w13_weight")) is not None + for w13_weight_i in w13_weight[ + : _get_nested_attr(layer, "block_sparse_moe", "experts", "local_num_experts", default=0) + ] + ] + xlite_model.re_down = [ + w2_weight_i + for layer in layers + if (w2_weight := _get_nested_attr(layer, "block_sparse_moe", "experts", "w2_weight")) is not None + for w2_weight_i in w2_weight[ + : _get_nested_attr(layer, "block_sparse_moe", "experts", "local_num_experts", default=0) + ] + ] - prefix = "block_sparse_moe.experts." - kwargs: WeightGetterConfig = {"secondary_flattening": f"{prefix}local_num_experts", "post_processor": None} - xlite_model.re_up_gate = get_layer_weights(layers, f"{prefix}w13_weight", **kwargs) - xlite_model.re_down = get_layer_weights(layers, f"{prefix}w2_weight", **kwargs) if xlite_model.re_up_gate: xlite_config.experts_weight_nz = self.is_tensor_nz(xlite_model.re_up_gate[0]) - if self.quantization: - kwargs["post_processor"] = self._transform_deq_scale - xlite_model.re_up_gate_scale = get_layer_weights(layers, f"{prefix}w13_weight_scale_fp32", **kwargs) - xlite_model.re_down_scale = get_layer_weights(layers, f"{prefix}w2_weight_scale", **kwargs) + xlite_model.re_up_gate_scale = [ + self._prepare_deq_scale_weights(layer.block_sparse_moe.experts.w13_weight_scale_fp32[i]) + for layer in layers + for i in range(layer.block_sparse_moe.experts.local_num_experts) + ] + xlite_model.re_down_scale = [ + self._prepare_deq_scale_weights(layer.block_sparse_moe.experts.w2_weight_scale[i]) + for layer in layers + for i in range(layer.block_sparse_moe.experts.local_num_experts) + ] -def get_adapter_xlite_model(runnable: nn.Module, vllm_config: VllmConfig) -> XliteModel: - """Look up and initialize the appropriate xlite model adapter based on the architecture specified in vLLM config and - the runnable model. +def xlite_model_init(runnable: nn.Module, vllm_config: VllmConfig) -> XliteInitResult: + """Construct and initialize an architecture-specific xlite model adapter. Args: runnable (nn.Module): The runnable model instance. @@ -555,12 +619,24 @@ def get_adapter_xlite_model(runnable: nn.Module, vllm_config: VllmConfig) -> Xli ValueError: If the model architecture is not supported by xlite. Returns: - XliteModel: An initialized xlite model adapter ready for inference. + XliteInitResult: Initialized xlite model, RoPE cache, hidden size and dtype. """ + strategy_map: dict[str, type[XliteModel]] = { + "LlamaForCausalLM": LlamaXliteModel, + "Qwen2ForCausalLM": LlamaXliteModel, + "Qwen3ForCausalLM": LlamaXliteModel, + "Qwen3VLForConditionalGeneration": LlamaXliteModel, + "Qwen3MoeForCausalLM": QwenMoeXliteModel, + "Qwen3VLMoeForConditionalGeneration": QwenMoeXliteModel, + "Glm4MoeForCausalLM": Glm4MoeXliteModel, + "MiniMaxM2ForCausalLM": MiniMaxM2XliteModel, + } + architecture = vllm_config.model_config.architectures[0] - if not (strategy_class := _architecture_strategy_map.get(architecture)): + strategy_class = strategy_map.get(architecture) + if not strategy_class: raise ValueError(f"{architecture} not supported!") - return strategy_class(runnable, vllm_config) + return strategy_class(runnable, vllm_config).initialize() class XliteWrapper: @@ -584,8 +660,7 @@ def __init__(self, runnable: nn.Module, vllm_config: VllmConfig): self.data_parallel_size = vllm_config.parallel_config.data_parallel_size self.xlite_rt = Runtime(local_rank, 0, rank, get_tensor_model_parallel_world_size(), self.data_parallel_size) - self.adapter_xlite_model = get_adapter_xlite_model(runnable, vllm_config) - (self.xlite_model, self.freq_cis, hidden_size, dtype) = self.adapter_xlite_model.initialize() + (self.xlite_model, self.freq_cis, hidden_size, dtype) = xlite_model_init(runnable, vllm_config) rt_pool_size = self.xlite_model.get_tensor_pool_size() if rank == 0: @@ -608,21 +683,19 @@ def __getattr__(self, key: str) -> Any: Returns: Any: Attribute value resolved from the runnable. """ - try: + # allow accessing the attributes of the runnable. + if hasattr(self.runnable, key): return getattr(self.runnable, key) - except Exception: # runnable may raise various exceptions - raise AttributeError(f"{self.__class__.__name__} object has no attribute {key}") from None + raise AttributeError(f"Attribute {key} not exists in the runnable of xlite wrapper: {self.runnable}") def unwrap(self) -> Callable: - """Return the original runnable callable. See :meth:`ACLGraphWrapper.unwrap` for details. + """Return the original runnable callable. Returns: Callable: Original model runnable. """ # in case we need to access the original runnable. - if isinstance(runnable := self.runnable, ACLGraphWrapper): - return runnable.unwrap() - return runnable + return self.runnable def register_kv_caches(self, kv_caches: Any) -> None: """Register KV cache references used by xlite runtime. @@ -656,15 +729,14 @@ def __call__( if attn_metadata is None: return self.runnable(input_ids, positions, intermediate_tensors, inputs_embeds) - attn_metadata = attn_metadata[0] if isinstance(attn_metadata, list) else attn_metadata attn_metadata = next(iter(attn_metadata.values()), None) - if not isinstance(attn_metadata, self.adapter_xlite_model._attn_metadata_type): + if attn_metadata is None or not isinstance(attn_metadata, AscendMetadata): return self.runnable(input_ids, positions, intermediate_tensors, inputs_embeds) - with_prefill = attn_metadata.attn_state not in ( + with_prefill = attn_metadata.attn_state not in [ AscendAttentionState.DecodeOnly, AscendAttentionState.SpecDecoding, - ) + ] # Full: graph for prefill and decode # Decode-Only: runnable for prefill, graph for decode @@ -675,51 +747,57 @@ def __call__( else: use_xlite_graph = not with_prefill or self.full_mode - attn_metadata_router = AttnMetadataRouter(attn_metadata=attn_metadata, device="cpu") - if not use_xlite_graph: - # fall back to runnable for prefill in decode-only mode - # or when the number of tokens exceeds the graph capacity in non-full mode - return self.runnable(input_ids, positions, intermediate_tensors, inputs_embeds) + if use_xlite_graph: + # TODO: When vllm_ascend enables graph mode, attn_metadata.num_decodes + # will be padded in decode requests. Therefore, it is first fixed using + # num_decode_tokens. However, in the future, when MTP is enabled, there + # may be cases where a single request involves multiple tokens, which + # will need to be solved. + num_decodes = attn_metadata.num_decode_tokens + num_prefills = attn_metadata.num_prefills + batch = num_prefills + num_decodes + seq_lens = attn_metadata.seq_lens[:batch] + seq_tensor = torch.cat([torch.tensor([0]), torch.tensor(attn_metadata.actual_seq_lengths_q)], dim=0) + query_lens = seq_tensor[1:] - seq_tensor[:-1] + query_lens = query_lens[:batch] + cached_lens = seq_lens - query_lens - seq_lens = attn_metadata_router.seq_lens - cum_query_lens = attn_metadata_router.cu_query_lens[-seq_lens.size(0) :].to(device=seq_lens.device) - query_lens = torch.diff(cum_query_lens, prepend=seq_lens.new_zeros(1)) - cached_lens = torch.clamp(seq_lens - query_lens, min=0) - - num_tokens = forward_context.batch_descriptor.num_tokens - num_actual_tokens = attn_metadata.num_actual_tokens - xlite_attn_metadata = AttnMeta() - xlite_attn_metadata.lens = query_lens.tolist() - xlite_attn_metadata.cached_lens = cached_lens.tolist() - xlite_attn_metadata.block_tables_cpu = attn_metadata_router.block_tables.tolist() - if positions.ndim == 2: - xlite_attn_metadata.positions = positions[:, :num_actual_tokens].contiguous() - positions = positions[0] - else: - xlite_attn_metadata.positions = positions - - # Compatibility between DP and Non-DP scenarios - h = self.hidden_states[:num_tokens] - stream = torch.npu.current_stream().npu_stream - if inputs_embeds is None: - self.xlite_model.forward( - self.xlite_rt, input_ids, xlite_attn_metadata, self.kv_caches, self.freq_cis, h, stream - ) + num_tokens = forward_context.batch_descriptor.num_tokens + num_actual_tokens = attn_metadata.num_actual_tokens + xlite_attn_metadata = AttnMeta() + xlite_attn_metadata.lens = query_lens.tolist() + xlite_attn_metadata.cached_lens = cached_lens.tolist() + xlite_attn_metadata.is_prefills = [False] * num_decodes + [True] * num_prefills + xlite_attn_metadata.block_tables_cpu = attn_metadata.block_tables.cpu().tolist() + if positions.ndim == 2: + xlite_attn_metadata.positions = positions[:, : attn_metadata.num_actual_tokens].contiguous() + else: + xlite_attn_metadata.positions = positions + + # Compatibility between DP and Non-DP scenarios + h = self.hidden_states[:num_tokens] + stream = torch.npu.current_stream().npu_stream + if inputs_embeds is None: + self.xlite_model.forward( + self.xlite_rt, input_ids, xlite_attn_metadata, self.kv_caches, self.freq_cis, h, stream + ) + else: + deepstack_input_embeds = getattr(self.runnable, "deepstack_input_embeds", []) + xlite_deepstack_input_embeds = [ + deepstack_input[: inputs_embeds.size(0)] for deepstack_input in deepstack_input_embeds + ] + self.xlite_model.forward_with_inputs_embeds( + self.xlite_rt, + inputs_embeds, + xlite_attn_metadata, + self.kv_caches, + self.freq_cis, + h, + stream, + xlite_deepstack_input_embeds, + ) + if xlite_deepstack_input_embeds and hasattr(self.runnable, "_clear_deepstack_input_embeds"): + self.runnable._clear_deepstack_input_embeds(inputs_embeds.size(0)) + return h[:num_actual_tokens] else: - deepstack_input_embeds = getattr(self.runnable, "deepstack_input_embeds", []) - xlite_deepstack_input_embeds = [ - deepstack_input[: inputs_embeds.size(0)] for deepstack_input in deepstack_input_embeds - ] - self.xlite_model.forward_with_inputs_embeds( - self.xlite_rt, - inputs_embeds, - xlite_attn_metadata, - self.kv_caches, - self.freq_cis, - h, - stream, - xlite_deepstack_input_embeds, - ) - if xlite_deepstack_input_embeds and hasattr(self.runnable, "_clear_deepstack_input_embeds"): - self.runnable._clear_deepstack_input_embeds(inputs_embeds.size(0)) - return h[:num_actual_tokens] + return self.runnable(input_ids, positions, intermediate_tensors, inputs_embeds) diff --git a/vllm_ascend/xlite/xlite_model_runner.py b/vllm_ascend/xlite/xlite_model_runner.py index 94f682aff..c7322bc2d 100644 --- a/vllm_ascend/xlite/xlite_model_runner.py +++ b/vllm_ascend/xlite/xlite_model_runner.py @@ -24,21 +24,16 @@ class XliteModelRunner(NPUModelRunner): def get_model(self) -> nn.Module: - """See :meth:`NPUModelRunner.get_model` and :meth:`XliteWrapper.unwrap` for details.""" return self.model.unwrap() def load_model(self) -> None: + super().load_model() from vllm_ascend.xlite.xlite import XliteWrapper - super().load_model() self.model = XliteWrapper(self.model, self.vllm_config) - def initialize_kv_cache( - self, - kv_cache_config: KVCacheConfig, - is_profiling: bool = False, - ) -> None: - super().initialize_kv_cache(kv_cache_config, is_profiling=is_profiling) + def initialize_kv_cache(self, kv_cache_config: KVCacheConfig) -> None: + super().initialize_kv_cache(kv_cache_config) self.model.register_kv_caches(self.kv_caches) def _should_build_dummy_attn_metadata(