Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion tests/e2e/coverage_taxonomy.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
"fully_sharded_lora",
"spec_decode",
"mtp",
"dspark",
"eagle3",
"sfa_dsa",
"sfa_pcp",
Expand Down
102 changes: 43 additions & 59 deletions tests/e2e/pull_request/eight_card/model_runner_v2/test_glm5_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,11 @@

import pytest
from vllm import SamplingParams
from vllm.config import CompilationConfig
from vllm.v1.metrics.reader import Counter, Vector

from tests.e2e.conftest import VllmRunner, wait_until_npu_memory_free
from tests.e2e.pull_request.utils import _run_speculative_decoding

MODEL = "Eco-Tech/GLM-5.2-w4a8"
DRAFT_MODEL = "RedHatAI/GLM-5.2-speculator.dspark"
EXPECTED_ACCEPTANCE_LENGTH = 3.0
DSPARK_EXPECTED_ACCEPTANCE_LENGTH = 3.5


@pytest.mark.e2e_model(MODEL)
Expand All @@ -50,20 +46,51 @@
)
@wait_until_npu_memory_free()
def test_glm5_2_mtp_full_decode_only() -> None:
_run_speculative_decoding(
model_name=MODEL,
prompts = [
"Hello, my name is",
"The president of the United States is",
"The capital of France is",
"The future of AI is",
]
num_speculative_tokens = 3
sampling_params = SamplingParams(max_tokens=1024, temperature=0.0)

with VllmRunner(
MODEL,
quantization="ascend",
tensor_parallel_size=8,
max_model_len=8192,
max_num_seqs=16,
enable_expert_parallel=True,
disable_log_stats=False,
compilation_config={"cudagraph_mode": "FULL_DECODE_ONLY"},
speculative_config={
"method": "mtp",
"num_speculative_tokens": 3,
},
expected_acceptance_length=EXPECTED_ACCEPTANCE_LENGTH,
runner_kwargs={
"quantization": "ascend",
"tensor_parallel_size": 8,
"max_model_len": 8192,
"compilation_config": CompilationConfig(cudagraph_mode="FULL_DECODE_ONLY"),
"num_speculative_tokens": num_speculative_tokens,
},
)
) as runner:
outputs = runner.model.generate(prompts, sampling_params)
metrics = runner.model.get_metrics()

assert len(outputs) == len(prompts)
assert all(output.outputs[0].token_ids for output in outputs)

num_drafts = 0
num_accepted_tokens_per_pos = [0] * num_speculative_tokens
for metric in metrics:
if metric.name == "vllm:spec_decode_num_drafts":
assert isinstance(metric, Counter)
num_drafts += metric.value
elif metric.name == "vllm:spec_decode_num_accepted_tokens_per_pos":
assert isinstance(metric, Vector)
assert len(metric.values) == num_speculative_tokens
for pos, value in enumerate(metric.values):
num_accepted_tokens_per_pos[pos] += value

assert num_drafts > 0, "Speculative decoding did not generate any draft tokens"
acceptance_per_pos = [accepted / num_drafts for accepted in num_accepted_tokens_per_pos]
assert any(acceptance_per_pos)
assert all(0 <= acceptance <= 1 for acceptance in acceptance_per_pos)


@pytest.mark.e2e_model(MODEL)
Expand Down Expand Up @@ -111,46 +138,3 @@ def test_glm5_2_sfa_pcp_full_decode_only() -> None:

assert len(outputs) == len(prompts)
assert all(output.outputs[0].token_ids for output in outputs)


@pytest.mark.e2e_model(MODEL)
@pytest.mark.e2e_coverage(
arch="moe",
feature="dspark",
parallel="TP,EP",
deploy="pd_mix",
hardware="A3",
quantization="W4A8",
graph_mode="eager",
)
@patch.dict(
os.environ,
{
"VLLM_USE_V2_MODEL_RUNNER": "1",
"VLLM_WORKER_MULTIPROC_METHOD": "spawn",
"PYTORCH_NPU_ALLOC_CONF": "expandable_segments:True",
"HCCL_BUFFSIZE": "1024",
},
)
@wait_until_npu_memory_free()
def test_glm5_2_dspark_eager() -> None:
_run_speculative_decoding(
model_name=MODEL,
speculative_config={
"method": "dspark",
"model": DRAFT_MODEL,
"num_speculative_tokens": 7,
"enforce_eager": True,
},
expected_acceptance_length=DSPARK_EXPECTED_ACCEPTANCE_LENGTH,
runner_kwargs={
"quantization": "ascend",
"tensor_parallel_size": 8,
"max_model_len": 4096,
"max_num_batched_tokens": 2048,
"enforce_eager": True,
"enable_prefix_caching": False,
"async_scheduling": False,
},
acceptance_length_rtol=0.1,
)
3 changes: 2 additions & 1 deletion tests/ut/quantization/test_modelslim_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from vllm.model_executor.models.utils import WeightsMapper

from tests.ut.base import TestBase
from vllm_ascend.models.llama_eagle3 import get_rotation_path
from vllm_ascend.ops.linear import AscendUnquantizedLinearMethod
from vllm_ascend.quantization.configs.modelslim_config import (
MODELSLIM_CONFIG_FILENAME,
Expand All @@ -21,7 +22,7 @@
get_linear_quant_type,
get_packed_modules_mapping,
)
from vllm_ascend.utils import ASCEND_QUANTIZATION_METHOD, get_rotation_path
from vllm_ascend.utils import ASCEND_QUANTIZATION_METHOD


class TestAscendModelSlimConfig(TestBase):
Expand Down
100 changes: 0 additions & 100 deletions tests/ut/spec_decode/test_dspark_speculator.py

This file was deleted.

2 changes: 1 addition & 1 deletion vllm_ascend/models/deepseek_v4/dspark.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
self.config = vllm_config.speculative_config.draft_model_config.hf_config

# check if quant config exist
from vllm_ascend.utils import get_rotation_path
from vllm_ascend.models.llama_eagle3 import get_rotation_path

self.rotation_path = get_rotation_path(vllm_config) if vllm_config.quant_config is not None else None

Expand Down
2 changes: 1 addition & 1 deletion vllm_ascend/models/kimi_k3.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
from vllm.triton_utils import HAS_TRITON
from vllm.utils.math_utils import cdiv

from vllm_ascend.models.llama_eagle3 import get_rotation_path
from vllm_ascend.ops.kimi_kda import AscendKimiK3DeltaAttention # type: ignore[import-untyped]
from vllm_ascend.utils import get_rotation_path

if HAS_TRITON:
from vllm_ascend.ops.triton.kimi_k3.attention_residual import ( # type: ignore[import-untyped]
Expand Down
12 changes: 6 additions & 6 deletions vllm_ascend/models/kimi_k3_dspark.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@
from vllm_ascend.models.kimi_k3 import (
AscendKimiMLAAttention,
)
from vllm_ascend.models.llama_eagle3 import load_quarot_target_layer
from vllm_ascend.models.llama_eagle3 import (
get_rotation_matrix,
get_rotation_path,
load_quarot_target_layer,
)
from vllm_ascend.models.qwen3_dspark import (
TARGET_EMBED_WEIGHT_NAMES,
TARGET_LM_HEAD_WEIGHT_NAMES,
process_weight,
)
from vllm_ascend.ops.rotary_embedding import get_cos_and_sin_mla
from vllm_ascend.utils import (
get_rotation_matrix,
get_rotation_path,
vllm_version_is,
)
from vllm_ascend.utils import vllm_version_is


def _uses_causal_draft_attention(config) -> bool:
Expand Down
32 changes: 27 additions & 5 deletions vllm_ascend/models/llama_eagle3.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@
from vllm.config import VllmConfig
from vllm.model_executor.models.llama_eagle3 import Eagle3LlamaForCausalLM

from vllm_ascend.utils import (
get_rotation_matrix,
get_rotation_path,
)

logger = logging.getLogger(__name__)


Expand All @@ -33,6 +28,33 @@ def get_embedding_tensor(directory_path):
return None


def get_rotation_path(vllm_config: VllmConfig) -> Path | None:
quant_config = vllm_config.quant_config
if quant_config is None:
return None
target_model_path = vllm_config.model_config.model
try:
quant_description = quant_config.quant_description
rotation_relative_path = quant_description["optional"]["quarot"]["rotation_map"]["global_rotation"]
except KeyError:
return None
Comment on lines +36 to +40

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If quant_description is None or contains nested values that are not dictionaries (e.g., if "optional" is None), subscripting them will raise a TypeError or AttributeError. Since only KeyError is caught, this can lead to an unhandled exception and runtime crash.

To make this defensively robust, catch TypeError and AttributeError in addition to KeyError.

Suggested change
try:
quant_description = quant_config.quant_description
rotation_relative_path = quant_description["optional"]["quarot"]["rotation_map"]["global_rotation"]
except KeyError:
return None
try:
quant_description = quant_config.quant_description
rotation_relative_path = quant_description["optional"]["quarot"]["rotation_map"]["global_rotation"]
except (KeyError, TypeError, AttributeError):
return None

return Path(target_model_path) / rotation_relative_path


def get_rotation_matrix(rotation_path: Path | None) -> torch.Tensor:
"""Load the global rotation matrix."""
try:
safetensor_data = load_file(rotation_path)
Q = safetensor_data["global_rotation"]
return Q
except Exception as e:
logger.error(
"Failed to load rotation weight from '%s'. If you want to use quarot model with eagle3, take a check.",
rotation_path,
)
raise e


def _find_safetensors_weight(
model_path: Path,
weight_names: tuple[str, ...],
Expand Down
6 changes: 3 additions & 3 deletions vllm_ascend/models/qwen3_dspark.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from vllm.model_executor.models.qwen3_dspark import Qwen3DSparkForCausalLM
from vllm.model_executor.models.utils import AutoWeightsLoader, maybe_prefix

from vllm_ascend.models.llama_eagle3 import load_quarot_target_layer
from vllm_ascend.utils import (
from vllm_ascend.models.llama_eagle3 import (
get_rotation_matrix,
get_rotation_path,
vllm_version_is,
load_quarot_target_layer,
)
from vllm_ascend.utils import vllm_version_is

TARGET_EMBED_WEIGHT_NAMES = (
"language_model.model.embed_tokens.weight",
Expand Down
Loading
Loading