init v0.23.0

Signed-off-by: Sun Ruoxi <sunruoxi@4paradigm.com>
This commit is contained in:
2026-08-27 15:11:51 +08:00
parent b582a8e7d1
commit 7f8a1b1f7a
2849 changed files with 712887 additions and 22001 deletions

View File

@@ -0,0 +1,493 @@
#
# Copyright (c) 2025 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.
#
"""PCP/DCP long-sequence accuracy guards.
Run `pytest tests/e2e/pull_request/four_card/context_parallel/test_accuracy.py`.
"""
import os
from collections.abc import Sequence
from dataclasses import dataclass
from pathlib import Path
from typing import Any, cast
from unittest.mock import patch
import pytest
from PIL import Image
from vllm import SamplingParams
from tests.e2e.conftest import DPVllmRunner, VllmRunner, wait_until_npu_memory_free
DEEPSEEK_V2_LITE = "vllm-ascend/DeepSeek-V2-Lite-W8A8"
DEEPSEEK_MTP = "wemaster/deepseek_mtp_main_random_bf16"
MAX_NUM_SEQS = 4
E2E_ROOT = Path(__file__).resolve().parents[3]
QWEN_IMAGE_PATH = E2E_ROOT / "prompts" / "qwen.png"
FULL_DECODE_GRAPH = {
"cudagraph_mode": "FULL_DECODE_ONLY",
"cudagraph_capture_sizes": [MAX_NUM_SEQS],
}
COMMON_PROMPTS = [
"The capital of France is",
"Hello, my name is Tom, I am",
"The president of United States is",
]
DSV2_PROMPTS = [
"The president of the United States is",
"The capital of France is",
]
DSV2_PCP_GOLDEN = [
"The president of the United States is a man who is not only a liar, but",
"The capital of France is Paris.\nThe currency of France is the Euro",
]
DSV2_DCP_GOLDEN = [
"The president of the United States is a man who is not only a liar, but",
"The capital of France is Paris.\nThe currency of France is the Euro",
]
QWEN3_GOLDEN = [
"The capital of France is Paris. The capital of",
"Hello, my name is Tom, I am 12 years old",
"The president of United States is the head of state and",
]
QWEN3_NEXT_GOLDEN = [
"The capital of France is Paris. The capital of",
"Hello, my name is Tom, I am 12 years old",
"The president of United States is the head of state and",
]
DSV3_2_GOLDEN = [
"The capital of France isbearerhaloce梗",
"Hello, my name is Tom, I am" + "ERIC slicpacelike Chop",
"The president of United States is平行astra unbehetroni",
]
DSV3_2_GOLDEN_BACKUPS = (
[
"The capital of France isbearerhaloce梗",
"Hello, my name is Tom, I am" + "ERIC slicpacelike Chop",
"The president of United States isoint054 Rund959arki",
],
[
"The capital of France isbearerdenomorthal",
"Hello, my name is Tom, I am" + "ERIC slicpacelike Chop",
"The president of United States is平行astra unbehetroni",
],
[
"The capital of France isbearerdenomorthal",
"Hello, my name is Tom, I am" + "ERIC slicpacelike Chop",
"The president of United States isoint054 Rund959arki",
],
)
DSV3_2_DCP_GOLDEN = [
"The capital of France isoint054 Rund959arki",
"Hello, my name is Tom, I am" + "ERIC slicpacelike挂",
"The president of United States isoint054 Rund959arki",
]
DEEPSEEK_MTP3_GOLDEN = [
"The capital of France is Salmonella团团 elsewhereッγκ",
"Hello, my name is Tom, I amEiSlowukt Analysis sprouts",
"The president of United States is Salmonella团团 elsewhereッγκ",
]
DEEPSEEK_V4_PROMPTS = [
"Hello, my name is",
"What is the meaning of life?",
]
DEEPSEEK_V4_GOLDEN = ["Hello, my name is {name} and I", 'What is the meaning of life?",\n "What is']
@dataclass(frozen=True)
class AccuracyCase:
name: str
model: str
prompts: Sequence[str]
expected_outputs: Sequence[str] | Sequence[Sequence[str]]
max_tokens: int
runner_kwargs: dict[str, Any]
def _run_accuracy_case(case: AccuracyCase) -> None:
runner_cls = DPVllmRunner if case.runner_kwargs.get("data_parallel_size", 1) > 1 else VllmRunner
with runner_cls(case.model, **case.runner_kwargs) as runner:
outputs = runner.generate_greedy(list(case.prompts), case.max_tokens)
if isinstance(case.expected_outputs[0], str):
expected_outputs = cast(Sequence[str], case.expected_outputs)
match_outputs_with_goldens(outputs, expected_outputs)
else:
# If multiple expected output sets are provided, the output is considered correct if it matches any of the sets.
multi_expected_outputs = cast(Sequence[Sequence[str]], case.expected_outputs)
tries = []
for expected in multi_expected_outputs:
try:
match_outputs_with_goldens(outputs, expected)
except AssertionError as exc:
tries.append(f"Output did not match expected set:\n{exc}")
else:
break
if len(tries) == len(multi_expected_outputs):
failure_details = "\n\n".join(tries)
raise AssertionError(f"Output did not match any of the expected output sets:\n{failure_details}")
def match_outputs_with_goldens(outputs: list[tuple[list[int], str]], goldens: Sequence[str]) -> None:
"""Helper function to compare output with golden output, ignoring whitespace differences."""
outputs_str: Sequence[str] = [output[1] for output in outputs]
assert len(outputs_str) == len(goldens)
for index, (output, golden) in enumerate(zip(outputs_str, goldens)):
assert isinstance(output, str) and isinstance(golden, str), "Both output and golden must be strings"
assert output and golden, "Output and golden should not be empty"
assert output.strip() == golden.strip()
@patch.dict(
os.environ,
{
"HCCL_BUFFSIZE": "768",
"VLLM_WORKER_MULTIPROC_METHOD": "spawn",
"OMP_NUM_THREADS": "1",
"OMP_PROC_BIND": "false",
"PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True",
},
)
@wait_until_npu_memory_free(target_free_percentage=0.8)
def test_qwen3_vl_multimodal_pcp_accuracy_guard() -> None:
image = Image.open(QWEN_IMAGE_PATH).convert("RGB")
single_image_prompt = (
"<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
"<|im_start|>user\n"
"<|vision_start|><|image_pad|><|vision_end|>"
"Describe this image in detail.<|im_end|>\n"
"<|im_start|>assistant\n"
)
multi_image_prompt = (
"<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n"
"<|im_start|>user\n"
"<|vision_start|><|image_pad|><|vision_end|>"
"<|vision_start|><|image_pad|><|vision_end|>"
"Compare these two images and describe similarities.<|im_end|>\n"
"<|im_start|>assistant\n"
)
inputs = [
{
"prompt": single_image_prompt,
"multi_modal_data": {"image": image},
},
{
"prompt": multi_image_prompt,
"multi_modal_data": {"image": [image, image.copy()]},
},
]
sampling_params = SamplingParams(max_tokens=16, temperature=0.0)
with VllmRunner(
"Qwen/Qwen3-VL-8B-Instruct",
enforce_eager=False,
max_model_len=4096,
tensor_parallel_size=2,
prefill_context_parallel_size=2,
decode_context_parallel_size=1,
max_num_batched_tokens=1024,
block_size=128,
limit_mm_per_prompt={"image": 2},
mm_processor_kwargs={
"min_pixels": 28 * 28,
"max_pixels": 1280 * 28 * 28,
"fps": 1,
},
compilation_config={
"cudagraph_mode": "FULL_DECODE_ONLY",
"cudagraph_capture_sizes": [MAX_NUM_SEQS],
},
) as runner:
outputs = runner.model.generate(inputs, sampling_params=sampling_params)
assert len(outputs) == len(inputs)
for output in outputs:
assert output.outputs and output.outputs[0].text.strip()
DSV2_COMMON_KWARGS: dict[str, Any] = {
"max_model_len": 1024,
"max_num_seqs": MAX_NUM_SEQS,
"max_num_batched_tokens": 1024,
"enable_expert_parallel": True,
"enable_chunked_prefill": True,
"enable_prefix_caching": True,
"block_size": 128,
"quantization": "ascend",
"compilation_config": FULL_DECODE_GRAPH,
"additional_config": {"enable_flashcomm1": True},
}
DSV2_PARALLEL_CASES = [
AccuracyCase(
name="dsv2_pcp_dcp_full_features",
model=DEEPSEEK_V2_LITE,
prompts=DSV2_PROMPTS,
expected_outputs=DSV2_PCP_GOLDEN,
max_tokens=10,
runner_kwargs={
**DSV2_COMMON_KWARGS,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 2,
"decode_context_parallel_size": 2,
"cp_kv_cache_interleave_size": 128,
"long_prefill_token_threshold": 4,
},
),
AccuracyCase(
name="dsv2_pcp_only_full_features",
model=DEEPSEEK_V2_LITE,
prompts=DSV2_PROMPTS,
expected_outputs=DSV2_PCP_GOLDEN,
max_tokens=10,
runner_kwargs={
**DSV2_COMMON_KWARGS,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 2,
"decode_context_parallel_size": 1,
"cp_kv_cache_interleave_size": 128,
"long_prefill_token_threshold": 4,
},
),
AccuracyCase(
name="dsv2_dcp_only_full_features",
model=DEEPSEEK_V2_LITE,
prompts=DSV2_PROMPTS,
expected_outputs=DSV2_DCP_GOLDEN,
max_tokens=10,
runner_kwargs={
**DSV2_COMMON_KWARGS,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 1,
"decode_context_parallel_size": 2,
"long_prefill_token_threshold": 64,
"compilation_config": {
**FULL_DECODE_GRAPH,
"pass_config": {"enable_sp": True},
},
},
),
]
FULL_FEATURE_MODEL_CASES = [
AccuracyCase(
name="qwen3_pcp_dcp_full_features",
model="vllm-ascend/Qwen3-30B-A3B-W8A8",
prompts=COMMON_PROMPTS,
expected_outputs=QWEN3_GOLDEN,
max_tokens=5,
runner_kwargs={
"max_model_len": 1024,
"max_num_seqs": MAX_NUM_SEQS,
"max_num_batched_tokens": 1024,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 2,
"decode_context_parallel_size": 1,
"enable_expert_parallel": True,
"enable_chunked_prefill": True,
"enable_prefix_caching": True,
"block_size": 128,
"quantization": "ascend",
"long_prefill_token_threshold": 4,
"compilation_config": FULL_DECODE_GRAPH,
"additional_config": {"enable_flashcomm1": True},
},
),
AccuracyCase(
name="qwen3_next_pcp_dcp_full_features",
model="Qwen/Qwen3-Next-80B-A3B-Instruct",
prompts=COMMON_PROMPTS,
expected_outputs=QWEN3_NEXT_GOLDEN,
max_tokens=5,
runner_kwargs={
"enforce_eager": True,
"max_model_len": 1024,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 2,
"decode_context_parallel_size": 1,
"max_num_batched_tokens": 1024,
"enable_expert_parallel": True,
# TODO(qcs): We should set `long_prefill_token_threshold` to 4
# when chunked prefill with PCP is stable.
"long_prefill_token_threshold": 128,
"gpu_memory_utilization": 0.8,
"block_size": 128,
# FlashComm1 is disabled for qwen3_next until the PCP decode path is fixed.
"additional_config": {"enable_flashcomm1": False},
},
),
AccuracyCase(
name="dsv3_2_pcp_dcp_full_features",
model="vllm-ascend/DeepSeek-V3.2-W8A8-Pruning",
prompts=COMMON_PROMPTS,
# TODO(qcs): Remove multi-expected_outputs after the first request output is stable.
expected_outputs=(DSV3_2_GOLDEN, *DSV3_2_GOLDEN_BACKUPS),
max_tokens=5,
runner_kwargs={
"max_model_len": 1024,
"max_num_seqs": MAX_NUM_SEQS,
"max_num_batched_tokens": 1024,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 2,
"decode_context_parallel_size": 2,
"enable_expert_parallel": True,
"enable_chunked_prefill": True,
"enable_prefix_caching": True,
"gpu_memory_utilization": 0.2,
"cp_kv_cache_interleave_size": 128,
"block_size": 128,
"quantization": "ascend",
# TODO(qcs): We should set `long_prefill_token_threshold` to 4
# when chunked prefill with PCP is stable.
"long_prefill_token_threshold": 128,
"compilation_config": FULL_DECODE_GRAPH,
"additional_config": {"enable_flashcomm1": True},
# graph_mode is disabled for dsv32 until the PCP gatherv3 out of index issue is fixed.
"enforce_eager": True,
},
),
AccuracyCase(
name="dsv3_2_sfa_dcp_replicated_indexer",
model="vllm-ascend/DeepSeek-V3.2-W8A8-Pruning",
prompts=COMMON_PROMPTS,
expected_outputs=DSV3_2_DCP_GOLDEN,
max_tokens=5,
runner_kwargs={
"max_model_len": 1024,
"max_num_seqs": MAX_NUM_SEQS,
"max_num_batched_tokens": 1024,
"data_parallel_size": 2,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 1,
"decode_context_parallel_size": 2,
"enable_expert_parallel": True,
"enable_chunked_prefill": True,
"enable_prefix_caching": True,
"gpu_memory_utilization": 0.4,
"cp_kv_cache_interleave_size": 1,
"block_size": 128,
"quantization": "ascend",
"long_prefill_token_threshold": 128,
"compilation_config": FULL_DECODE_GRAPH,
"additional_config": {
"enable_flashcomm1": True,
"enable_dsa_cp": True,
"enable_sparse_sfa_c8": True,
"enable_sparse_li_c8": True,
},
"speculative_config": {
"method": "mtp",
"num_speculative_tokens": 3,
},
},
),
pytest.param(
AccuracyCase(
name="deepseek_mtp3_pcp_dcp_full_features",
model=DEEPSEEK_MTP,
prompts=COMMON_PROMPTS,
expected_outputs=DEEPSEEK_MTP3_GOLDEN,
max_tokens=5,
runner_kwargs={
"max_model_len": 1024,
"max_num_seqs": MAX_NUM_SEQS,
"max_num_batched_tokens": 1024,
"tensor_parallel_size": 2,
"prefill_context_parallel_size": 2,
"decode_context_parallel_size": 2,
"enable_expert_parallel": True,
"enable_chunked_prefill": True,
"enable_prefix_caching": True,
"block_size": 128,
"long_prefill_token_threshold": 4,
"speculative_config": {
"method": "mtp",
"num_speculative_tokens": 3,
},
"compilation_config": FULL_DECODE_GRAPH,
"additional_config": {"enable_flashcomm1": True},
},
),
marks=pytest.mark.skip(reason="Temporarily skip MTP with PCP/DCP until the token layout issue is fixed."),
),
AccuracyCase(
name="deepseek_v4_w4a8_dsa_cp_full_features",
model="gdydems/DeepSeek-V4-Flash-w4a8-mtp",
prompts=DEEPSEEK_V4_PROMPTS,
expected_outputs=DEEPSEEK_V4_GOLDEN,
max_tokens=5,
runner_kwargs={
"max_model_len": 8192,
"max_num_seqs": 16,
"max_num_batched_tokens": 4096,
"dtype": "auto",
"tensor_parallel_size": 4,
"prefill_context_parallel_size": 1,
"decode_context_parallel_size": 1,
"enable_expert_parallel": True,
"gpu_memory_utilization": 0.9,
"quantization": "ascend",
"tokenizer_mode": "deepseek_v4",
"block_size": 128,
"compilation_config": {
"cudagraph_mode": "FULL_DECODE_ONLY",
},
"additional_config": {
"enable_flashcomm1": True,
"enable_dsa_cp": True,
},
},
),
]
@patch.dict(
os.environ,
{
"HCCL_BUFFSIZE": "768",
},
)
@wait_until_npu_memory_free(target_free_percentage=0.8)
@pytest.mark.parametrize("case", DSV2_PARALLEL_CASES, ids=lambda case: case.name)
def test_dsv2_lite_parallel_config_accuracy(case: AccuracyCase) -> None:
_run_accuracy_case(case)
@patch.dict(
os.environ,
{
"HCCL_BUFFSIZE": "768",
"PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True",
},
)
@wait_until_npu_memory_free(target_free_percentage=0.8)
@pytest.mark.parametrize("case", FULL_FEATURE_MODEL_CASES, ids=lambda case: case.name)
def test_models_pcp_dcp_full_feature_accuracy(case: AccuracyCase) -> None:
_run_accuracy_case(case)

View File

@@ -0,0 +1,76 @@
#
# Copyright (c) 2025 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.
#
"""DeepSeek-V2-Lite prefix-cache CP guard.
Run `pytest tests/e2e/pull_request/four_card/context_parallel/test_prefix_caching_cp.py`.
"""
import os
from unittest.mock import patch
from tests.e2e.conftest import VllmRunner
# TODO(qcs): We should use Qwen3.5 for this test when it's fixed and available,
# so that we can test the hybrid kv cache with both full attention and linear attention.
MODEL = "vllm-ascend/DeepSeek-V2-Lite-W8A8"
MAX_NUM_SEQS = 2
THREAD_ENV = {
"OMP_NUM_THREADS": "1",
"MKL_NUM_THREADS": "1",
"OPENBLAS_NUM_THREADS": "1",
"NUMEXPR_NUM_THREADS": "1",
}
DSV2_LITE_PREFIX_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"
)
INPUT_PROMPTS = [
DSV2_LITE_PREFIX_PROMPT + "Question: What route is listed in row 17? Answer briefly.",
DSV2_LITE_PREFIX_PROMPT + "Question: What priority is listed in row 42? Answer briefly.",
]
@patch.dict(os.environ, THREAD_ENV)
def test_dsv2_lite_prefix_cache_with_pcp() -> None:
with VllmRunner(
MODEL,
block_size=128,
max_model_len=2048,
max_num_seqs=MAX_NUM_SEQS,
max_num_batched_tokens=2048,
tensor_parallel_size=2,
prefill_context_parallel_size=2,
decode_context_parallel_size=1,
enforce_eager=True,
enable_expert_parallel=True,
enable_prefix_caching=True,
quantization="ascend",
) as vllm_model:
prefix_cache_outputs = vllm_model.generate_greedy(INPUT_PROMPTS, 8)
assert len(prefix_cache_outputs) == len(INPUT_PROMPTS)
for output_ids, output_text in prefix_cache_outputs:
assert output_ids
assert output_text