674 lines
24 KiB
Python
674 lines
24 KiB
Python
# 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.
|
||
|
||
import contextlib
|
||
import multiprocessing
|
||
import os
|
||
import queue
|
||
from typing import Any
|
||
from unittest.mock import patch
|
||
|
||
import pytest
|
||
import torch
|
||
from vllm import LLM, SamplingParams
|
||
from vllm.utils.network_utils import get_open_port
|
||
|
||
from tests.e2e.conftest import cleanup_dist_env_and_memory, wait_until_npu_memory_free
|
||
from tests.e2e.pull_request.utils import PROMPTS_LONG, PROMPTS_SHORT
|
||
|
||
QWEN3 = "Qwen/Qwen3-0.6B"
|
||
DEEPSEEK_V2_LITE = "vllm-ascend/DeepSeek-V2-Lite-W8A8"
|
||
|
||
QWEN3_PROMPTS_SHORT_BASELINE = [
|
||
{
|
||
"token_ids": [444, 2210, 13, 358, 2776, 264, 220, 17, 17, 4666, 6284, 5458, 504, 5616, 13, 358],
|
||
"logprobs": [
|
||
-4.082355976104736,
|
||
-1.8239692449569702,
|
||
-0.7117747664451599,
|
||
-0.19997872412204742,
|
||
-0.9261042475700378,
|
||
-1.156473994255066,
|
||
-1.6240551471710205,
|
||
-0.4462895393371582,
|
||
-1.7280645370483398,
|
||
-0.03872159123420715,
|
||
-0.006691192742437124,
|
||
-1.729214072227478,
|
||
-1.4082962274551392,
|
||
-1.2914694547653198,
|
||
-0.30865585803985596,
|
||
-0.19352123141288757,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [279, 1852, 438, 279, 4767, 315, 279, 3639, 19140, 13, 1096, 374, 1576, 279, 4767, 315],
|
||
"logprobs": [
|
||
-1.329056978225708,
|
||
-1.9254618883132935,
|
||
-0.16430772840976715,
|
||
-0.07656584680080414,
|
||
-0.20014727115631104,
|
||
-0.009215084835886955,
|
||
-0.6130291223526001,
|
||
-0.7763887047767639,
|
||
-0.7629368901252747,
|
||
-0.7878186702728271,
|
||
-1.5039337873458862,
|
||
-0.34936416149139404,
|
||
-1.1888322830200195,
|
||
-0.7042690515518188,
|
||
-0.7195678949356079,
|
||
-0.5817108154296875,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [12095, 13, 576, 6722, 315, 15344, 374, 21718, 13, 576, 6722, 315, 17689, 374, 24081, 13],
|
||
"logprobs": [
|
||
-0.4164676070213318,
|
||
-0.650071918964386,
|
||
-1.2497835159301758,
|
||
-0.41373029351234436,
|
||
-0.10615493357181549,
|
||
-1.7584394216537476,
|
||
-0.014708652161061764,
|
||
-0.09583187848329544,
|
||
-0.05152897536754608,
|
||
-0.5075417757034302,
|
||
-0.04731765389442444,
|
||
-0.004497650545090437,
|
||
-0.734763503074646,
|
||
-0.006638852413743734,
|
||
-0.03197822347283363,
|
||
-0.02921150252223015,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [537, 1101, 264, 29016, 8645, 714, 264, 27155, 17991, 315, 1246, 582, 3887, 11, 975, 11],
|
||
"logprobs": [
|
||
-2.253798246383667,
|
||
-0.6618977785110474,
|
||
-1.1329351663589478,
|
||
-1.5547151565551758,
|
||
-2.502274751663208,
|
||
-0.6954820156097412,
|
||
-0.16636334359645844,
|
||
-0.9172338247299194,
|
||
-0.8822890520095825,
|
||
-0.6346368789672852,
|
||
-1.1293692588806152,
|
||
-0.355191707611084,
|
||
-0.23319871723651886,
|
||
-0.3448694050312042,
|
||
-0.015147067606449127,
|
||
-0.023382289335131645,
|
||
],
|
||
},
|
||
]
|
||
|
||
QWEN3_PROMPTS_LONG_BASELINE = [
|
||
{
|
||
"token_ids": [4710, 1249, 11625, 419, 3491, 11, 582, 1184, 311, 990, 279, 7506, 315, 328, 1543, 323],
|
||
"logprobs": [
|
||
-1.1807091236114502,
|
||
-0.7548902034759521,
|
||
-0.06596270948648453,
|
||
-0.21140751242637634,
|
||
-0.17375628650188446,
|
||
-0.012642711400985718,
|
||
-0.8002182841300964,
|
||
-0.5415360331535339,
|
||
-0.005715575534850359,
|
||
-1.0228453874588013,
|
||
-0.49259668588638306,
|
||
-0.5897566080093384,
|
||
-0.0001705739414319396,
|
||
-0.008742272853851318,
|
||
-0.0008293526479974389,
|
||
-0.20622815191745758,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [4710, 1249, 11625, 419, 3491, 11, 582, 646, 990, 279, 2701, 5486, 25, 6771, 400, 47],
|
||
"logprobs": [
|
||
-0.6663188934326172,
|
||
-0.31078171730041504,
|
||
-0.1298152357339859,
|
||
-0.17452840507030487,
|
||
-0.14723201096057892,
|
||
-0.011572140268981457,
|
||
-0.5467657446861267,
|
||
-1.1380974054336548,
|
||
-0.8147172927856445,
|
||
-0.2714247703552246,
|
||
-1.2143880128860474,
|
||
-0.84015291929245,
|
||
-0.7829729318618774,
|
||
-1.5653221607208252,
|
||
-0.631846010684967,
|
||
-1.7602534294128418,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [4710, 1249, 11625, 419, 3491, 11, 582, 646, 990, 279, 2701, 5486, 25, 6771, 400, 1124],
|
||
"logprobs": [
|
||
-0.979120671749115,
|
||
-0.74333256483078,
|
||
-0.22203724086284637,
|
||
-0.31942218542099,
|
||
-0.2570137679576874,
|
||
-0.012989195063710213,
|
||
-0.939915657043457,
|
||
-1.038073182106018,
|
||
-0.4173763394355774,
|
||
-0.17641115188598633,
|
||
-1.119675874710083,
|
||
-1.2035267353057861,
|
||
-0.7646281719207764,
|
||
-1.5680660009384155,
|
||
-0.5041356086730957,
|
||
-1.0269478559494019,
|
||
],
|
||
},
|
||
]
|
||
|
||
DEEPSEEK_V2_LITE_PROMPTS_SHORT_BASELINE = [
|
||
{
|
||
"token_ids": [185, 40, 608, 245, 207, 17, 15, 1012, 1712, 12608, 11, 285, 304, 463, 803, 14079],
|
||
"logprobs": [
|
||
-4.621323108673096,
|
||
-3.077335834503174,
|
||
-1.3346226215362549,
|
||
-0.9667544364929199,
|
||
-2.628643274307251,
|
||
-1.0353751182556152,
|
||
-1.9108309745788574,
|
||
-0.9746326208114624,
|
||
-0.07258293032646179,
|
||
-2.375293016433716,
|
||
-1.7306381464004517,
|
||
-1.7152436971664429,
|
||
-0.5231161713600159,
|
||
-1.2002876996994019,
|
||
-0.945522665977478,
|
||
-2.263141393661499,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [245, 668, 779, 317, 441, 889, 245, 69524, 11, 548, 245, 42357, 11, 245, 39925, 11],
|
||
"logprobs": [
|
||
-1.9601335525512695,
|
||
-2.9202699661254883,
|
||
-0.935673713684082,
|
||
-1.9938912391662598,
|
||
-2.5300979614257812,
|
||
-2.1406760215759277,
|
||
-2.01202130317688,
|
||
-2.9169065952301025,
|
||
-0.5751016736030579,
|
||
-0.5648833513259888,
|
||
-1.2851604223251343,
|
||
-2.817136764526367,
|
||
-0.826416015625,
|
||
-0.8048403263092041,
|
||
-1.2173839807510376,
|
||
-0.32657456398010254,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [8913, 13, 185, 549, 19305, 280, 7239, 317, 254, 28071, 13, 185, 549, 13829, 13451, 279],
|
||
"logprobs": [
|
||
-0.39279282093048096,
|
||
-0.8086707592010498,
|
||
-0.7125738859176636,
|
||
-1.6490240097045898,
|
||
-1.9644602537155151,
|
||
-0.4904576539993286,
|
||
-0.0832064300775528,
|
||
-0.00726190535351634,
|
||
-0.38277116417884827,
|
||
-0.2940319776535034,
|
||
-0.7399694323539734,
|
||
-0.07564151287078857,
|
||
-0.7905924320220947,
|
||
-1.8350766897201538,
|
||
-0.4076140522956848,
|
||
-0.20184002816677094,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [6464, 11, 285, 359, 487, 82, 889, 1872, 276, 752, 34993, 13, 1733, 20838, 11106, 276],
|
||
"logprobs": [
|
||
-2.4984869956970215,
|
||
-0.7851194739341736,
|
||
-0.6003906726837158,
|
||
-1.4917504787445068,
|
||
-0.9962607026100159,
|
||
-0.004179196432232857,
|
||
-1.5068310499191284,
|
||
-0.4656505584716797,
|
||
-0.0027805021964013577,
|
||
-0.11925199627876282,
|
||
-0.35579147934913635,
|
||
-0.35492807626724243,
|
||
-1.605405330657959,
|
||
-1.38535737991333,
|
||
-0.930091142654419,
|
||
-0.004122450482100248,
|
||
],
|
||
},
|
||
]
|
||
|
||
DEEPSEEK_V2_LITE_PROMPTS_LONG_BASELINE = [
|
||
{
|
||
"token_ids": [185, 185, 1679, 26430, 279, 16145, 285, 8204, 185, 185, 13483, 9890, 16982, 457, 17693, 829],
|
||
"logprobs": [
|
||
-0.24134111404418945,
|
||
-0.06949862092733383,
|
||
-2.196256637573242,
|
||
-0.3235849142074585,
|
||
-5.745722592109814e-05,
|
||
-7.152554530875932e-07,
|
||
-9.381330892210826e-05,
|
||
-3.4570632578834193e-06,
|
||
-0.0006878394051454961,
|
||
-6.079655122448457e-06,
|
||
-7.152554530875932e-07,
|
||
-8.356221951544285e-05,
|
||
-1.8358061424805783e-05,
|
||
-1.2636104656849056e-05,
|
||
-1.5497195136049413e-06,
|
||
-2.0146166207268834e-05,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [185, 185, 1679, 26430, 279, 16145, 285, 8204, 185, 185, 13483, 9890, 16982, 457, 17693, 829],
|
||
"logprobs": [
|
||
-0.27419501543045044,
|
||
-0.08238636702299118,
|
||
-2.0612075328826904,
|
||
-1.0054221153259277,
|
||
-4.947062916471623e-05,
|
||
-4.768370445162873e-07,
|
||
-2.1457441107486375e-05,
|
||
-3.933898824470816e-06,
|
||
-0.00018630675913300365,
|
||
-1.1920920996999484e-06,
|
||
-4.172316494077677e-06,
|
||
-0.00010048838157672435,
|
||
-1.0847986231965479e-05,
|
||
-1.1920928244535389e-07,
|
||
-1.1920920996999484e-06,
|
||
-1.4185804502631072e-05,
|
||
],
|
||
},
|
||
{
|
||
"token_ids": [185, 185, 1679, 26430, 279, 16145, 285, 8204, 185, 185, 13483, 9890, 16982, 457, 17693, 829],
|
||
"logprobs": [
|
||
-0.14973750710487366,
|
||
-0.09690935909748077,
|
||
-1.8772021532058716,
|
||
-1.6962311267852783,
|
||
-0.0001429217227268964,
|
||
-8.344646289515367e-07,
|
||
-7.617183291586116e-05,
|
||
-3.814689989667386e-06,
|
||
-0.0010556369088590145,
|
||
-6.318072337307967e-06,
|
||
-1.1920920996999484e-06,
|
||
-0.00035553809721022844,
|
||
-2.729855441430118e-05,
|
||
-2.3841855067985307e-07,
|
||
-1.4305104514278355e-06,
|
||
-4.410733708937187e-06,
|
||
],
|
||
},
|
||
]
|
||
|
||
CASE_QWEN_ACLGRAPH = {
|
||
"model": QWEN3,
|
||
"quantization": None,
|
||
"prompts": {"short": PROMPTS_SHORT, "long": PROMPTS_LONG},
|
||
"compilation_config": {"max_cudagraph_capture_size": 24, "cudagraph_mode": "FULL"},
|
||
"tensor_parallel_size": 4,
|
||
"data_parallel_size": 1,
|
||
"enable_expert_parallel": False,
|
||
"golden_answers": {"short": QWEN3_PROMPTS_SHORT_BASELINE, "long": QWEN3_PROMPTS_LONG_BASELINE},
|
||
# TODO: it increases after profile graph memory is disabled, invetigate later
|
||
"baseline_capture_mem": 0.30,
|
||
"capture_mem_tolerance": 1.3,
|
||
}
|
||
|
||
CASE_DS_ACLGRAPH = {
|
||
"model": DEEPSEEK_V2_LITE,
|
||
"quantization": "ascend",
|
||
"prompts": {"short": PROMPTS_SHORT, "long": PROMPTS_LONG},
|
||
"compilation_config": {"max_cudagraph_capture_size": 24, "cudagraph_mode": "FULL_AND_PIECEWISE"},
|
||
"tensor_parallel_size": 2,
|
||
"data_parallel_size": 2,
|
||
"enable_expert_parallel": True,
|
||
"golden_answers": {
|
||
"short": DEEPSEEK_V2_LITE_PROMPTS_SHORT_BASELINE,
|
||
"long": DEEPSEEK_V2_LITE_PROMPTS_LONG_BASELINE,
|
||
},
|
||
"baseline_capture_mem": 0.68,
|
||
"capture_mem_tolerance": 1.5,
|
||
}
|
||
|
||
CASE_DS_ACLGRAPH_ENPU = {
|
||
**CASE_DS_ACLGRAPH,
|
||
"env_vars": {"ENPU_ENABLE": "true"},
|
||
}
|
||
|
||
# inherit from tests/e2e/pull_request/utils.py::compare_logprobs
|
||
ATOL = 0.0689
|
||
|
||
_SAMPLING_PARAMS = SamplingParams(
|
||
max_tokens=16,
|
||
temperature=0.0,
|
||
top_p=1.0,
|
||
top_k=0,
|
||
logprobs=20,
|
||
)
|
||
|
||
|
||
def _install_spies(metrics: dict[str, Any] | None):
|
||
"""Installs thread-safe spies on NPU methods to track invocation counts."""
|
||
if metrics is None:
|
||
return contextlib.nullcontext()
|
||
|
||
from vllm_ascend.worker.model_runner_v1 import NPUModelRunner
|
||
|
||
def make_spy(cls, method_name, capture_mem):
|
||
original = getattr(cls, method_name)
|
||
|
||
def spy(self, *args, **kwargs):
|
||
mem_before = torch.npu.mem_get_info()[0] # free memory
|
||
result = original(self, *args, **kwargs)
|
||
mem_after = torch.npu.mem_get_info()[0]
|
||
with capture_mem["call_counts"].get_lock():
|
||
capture_mem["call_counts"].value += 1
|
||
capture_mem["mem_before_capture"].value += mem_before
|
||
capture_mem["mem_after_capture"].value += mem_after
|
||
return result
|
||
|
||
return spy
|
||
|
||
stack = contextlib.ExitStack()
|
||
hooks = [
|
||
(NPUModelRunner, "capture_model", metrics["capture_mem"]),
|
||
]
|
||
|
||
for cls, method, metric in hooks:
|
||
stack.enter_context(patch.object(cls, method, make_spy(cls, method, metric)))
|
||
|
||
return stack
|
||
|
||
|
||
def _check_prefill_token(baseline, comp_ids, comp_logprobs, prompt_idx: int, atol: float) -> None:
|
||
"""Token 0 is produced by the prefill pass; both models see identical input,
|
||
so the chosen token *must* be the same and its logprob must match within atol."""
|
||
base_token_id = baseline["token_ids"][0]
|
||
comp_token_id = comp_ids[0]
|
||
assert base_token_id == comp_token_id, (
|
||
f"Prefill token mismatch at prompt {prompt_idx}: baseline={base_token_id}, compiled={comp_token_id}"
|
||
)
|
||
base_logprob = baseline["logprobs"][0]
|
||
comp_logprob = comp_logprobs[0][comp_token_id]
|
||
assert abs(base_logprob - comp_logprob) <= atol, (
|
||
f"Prefill logprob mismatch at prompt {prompt_idx}: "
|
||
f"baseline={base_logprob:.4f}, compiled={comp_logprob:.4f}, "
|
||
f"diff={abs(base_logprob - comp_logprob):.4f} > atol={atol}"
|
||
)
|
||
|
||
|
||
def _check_decode_token(baseline, comp_ids, comp_logprobs, token_idx: int, prompt_idx: int, decode_atol: float) -> None:
|
||
"""Tokens 1-2 come from decode passes. When the two models pick different
|
||
tokens the context has already diverged, so we cannot compare logprobs of
|
||
the chosen tokens directly. Instead we do a cross-lookup: find the
|
||
baseline's chosen token inside compiled's top-K distribution (and vice
|
||
versa) and assert that the assigned log-probability is close. This
|
||
confirms that the compiled model's distribution is numerically consistent
|
||
with the baseline's even when the argmax differs by a tiny margin.
|
||
"""
|
||
base_token_id = baseline["token_ids"][token_idx]
|
||
comp_token_id = comp_ids[token_idx]
|
||
base_logprob = baseline["logprobs"][token_idx]
|
||
comp_topk = comp_logprobs[token_idx]
|
||
if base_token_id == comp_token_id:
|
||
# Happy path: same token, direct logprob comparison.
|
||
diff = abs(base_logprob - comp_topk[comp_token_id])
|
||
assert diff <= decode_atol, (
|
||
f"Decode logprob mismatch at prompt {prompt_idx}, token {token_idx}: "
|
||
f"baseline={base_logprob:.4f}, "
|
||
f"compiled={comp_topk[comp_token_id]:.4f}, "
|
||
f"diff={diff:.4f} > decode_atol={decode_atol}"
|
||
)
|
||
return
|
||
|
||
# Tokens differ – cross-lookup in each model's top-K distribution.
|
||
comp_logprob = comp_topk[comp_token_id]
|
||
# Check: what log-probability did compiled assign to baseline's token?
|
||
assert base_token_id in comp_topk, (
|
||
f"Decode token mismatch at prompt {prompt_idx}, token {token_idx}: "
|
||
f"baseline chose token {base_token_id} (logprob={base_logprob:.4f}) but "
|
||
f"compiled chose token {comp_token_id} (logprob={comp_logprob:.4f}) and "
|
||
f"baseline's token does not appear in compiled's top-{_SAMPLING_PARAMS.logprobs} distribution"
|
||
)
|
||
comp_logprob_of_base_token = comp_topk[base_token_id]
|
||
diff = abs(base_logprob - comp_logprob_of_base_token)
|
||
assert diff <= decode_atol, (
|
||
f"Decode distribution mismatch at prompt {prompt_idx}, token {token_idx}: "
|
||
f"baseline chose token {base_token_id} with logprob={base_logprob:.4f}; "
|
||
f"compiled assigned logprob={comp_logprob_of_base_token:.4f} to that token, "
|
||
f"diff={diff:.4f} > decode_atol={decode_atol} "
|
||
f"(compiled chose token {comp_token_id} with logprob={comp_logprob:.4f})"
|
||
)
|
||
|
||
|
||
def _run_worker_process(
|
||
rank: int,
|
||
local_rank: int,
|
||
world_size: int,
|
||
cur_case: dict,
|
||
master_ip: str,
|
||
master_port: int,
|
||
result_queue: multiprocessing.Queue,
|
||
metrics: dict[str, Any] | None = None,
|
||
):
|
||
"""Main entry point for the worker process."""
|
||
os.environ.update(
|
||
{
|
||
"VLLM_DP_RANK": str(rank),
|
||
"VLLM_DP_RANK_LOCAL": str(local_rank),
|
||
"VLLM_DP_SIZE": str(world_size),
|
||
"VLLM_DP_MASTER_IP": master_ip,
|
||
"VLLM_DP_MASTER_PORT": str(master_port),
|
||
}
|
||
)
|
||
|
||
for key, value in cur_case.get("env_vars", {}).items():
|
||
os.environ[key] = str(value)
|
||
|
||
# Apply hooks and run inference
|
||
with _install_spies(metrics):
|
||
short_prompts = cur_case["prompts"]["short"]
|
||
chunk_size = len(short_prompts) // world_size
|
||
short_start_idx = rank * chunk_size
|
||
short_end_idx = short_start_idx + chunk_size if rank < world_size - 1 else len(short_prompts)
|
||
local_short_prompts = short_prompts[short_start_idx:short_end_idx]
|
||
|
||
long_prompts = cur_case["prompts"]["long"]
|
||
chunk_size = len(long_prompts) // world_size
|
||
long_start_idx = rank * chunk_size
|
||
long_end_idx = long_start_idx + chunk_size if rank < world_size - 1 else len(long_prompts)
|
||
local_long_prompts = long_prompts[long_start_idx:long_end_idx]
|
||
|
||
llm = LLM(
|
||
model=cur_case["model"],
|
||
max_model_len=1024,
|
||
compilation_config=cur_case["compilation_config"],
|
||
quantization=cur_case["quantization"],
|
||
tensor_parallel_size=cur_case["tensor_parallel_size"],
|
||
enable_expert_parallel=cur_case["enable_expert_parallel"],
|
||
trust_remote_code=True,
|
||
)
|
||
|
||
compiled_outputs_short = llm.generate(local_short_prompts, _SAMPLING_PARAMS)
|
||
compiled_outputs_long = llm.generate(local_long_prompts, _SAMPLING_PARAMS)
|
||
|
||
def extract_outputs(outputs):
|
||
extracted = []
|
||
for out in outputs:
|
||
gen = out.outputs[0]
|
||
extracted.append(
|
||
{
|
||
"text": gen.text,
|
||
"token_ids": list(gen.token_ids),
|
||
"logprobs": [
|
||
{token_id: lp.logprob for token_id, lp in step_logprobs.items()}
|
||
for step_logprobs in gen.logprobs
|
||
]
|
||
if gen.logprobs
|
||
else None,
|
||
}
|
||
)
|
||
return extracted
|
||
|
||
result_data = {
|
||
"rank": rank,
|
||
"short": {"prompt_idx": short_start_idx, "outputs": extract_outputs(compiled_outputs_short)},
|
||
"long": {"prompt_idx": long_start_idx, "outputs": extract_outputs(compiled_outputs_long)},
|
||
}
|
||
result_queue.put(result_data)
|
||
|
||
|
||
def _exit():
|
||
from vllm_ascend.ascend_config import clear_ascend_config
|
||
|
||
clear_ascend_config()
|
||
cleanup_dist_env_and_memory()
|
||
|
||
|
||
def check_accuracy(baselines, result, atol, decode_atol):
|
||
for idx, comp_out in enumerate(result["outputs"]):
|
||
prompt_idx = result["prompt_idx"] + idx
|
||
baseline = baselines[prompt_idx]
|
||
comp_ids = comp_out["token_ids"]
|
||
comp_logprobs = comp_out["logprobs"]
|
||
|
||
assert comp_logprobs is not None, f"logprobs not returned for prompt {prompt_idx}"
|
||
assert len(baseline["token_ids"]) == len(comp_ids) == _SAMPLING_PARAMS.max_tokens, (
|
||
f"Expected {_SAMPLING_PARAMS.max_tokens} tokens for prompt {prompt_idx}, "
|
||
f"got baseline={len(baseline['token_ids'])}, compiled={len(comp_ids)}"
|
||
)
|
||
_check_prefill_token(baseline, comp_ids, comp_logprobs, prompt_idx, atol)
|
||
for token_idx in range(1, _SAMPLING_PARAMS.max_tokens):
|
||
_check_decode_token(baseline, comp_ids, comp_logprobs, token_idx, prompt_idx, decode_atol)
|
||
|
||
|
||
def check_capture_mem(capture_mem, baseline_capture_mem=0.2, capture_mem_tolerance=1.3):
|
||
assert capture_mem["call_counts"].value != 0, (
|
||
f"capture_model was not called during test. capture_called({capture_mem['call_counts'].value})"
|
||
)
|
||
|
||
print("capture_called =", capture_mem["call_counts"].value)
|
||
print("capture_mem_before =", capture_mem["mem_before_capture"].value)
|
||
print("capture_mem_after =", capture_mem["mem_after_capture"].value)
|
||
|
||
mem_used_by_capture = (
|
||
capture_mem["mem_before_capture"].value - capture_mem["mem_after_capture"].value
|
||
) / capture_mem["call_counts"].value
|
||
# Empirical observation: capturing ACL graphs for Qwen3-0.6B uses ~0.20 GiB of NPU memory.
|
||
# DeepSeek-V2-Lite-W8A8 uses ~0.68 GiB of NPU memory
|
||
# a 1.3x tolerance is applied to account for runtime variance.
|
||
max_capture_mem_gib = baseline_capture_mem * capture_mem_tolerance
|
||
max_mem_expected = max_capture_mem_gib * (1024**3)
|
||
assert mem_used_by_capture < max_mem_expected, (
|
||
f"capture_model used more memory than expected. "
|
||
f"Used: {mem_used_by_capture / (1024**3):.2f} GiB, "
|
||
f"Expected: < {max_capture_mem_gib:.2f} GiB"
|
||
)
|
||
|
||
|
||
@wait_until_npu_memory_free(0.7)
|
||
@pytest.mark.parametrize("cur_case", [CASE_QWEN_ACLGRAPH, CASE_DS_ACLGRAPH, CASE_DS_ACLGRAPH_ENPU])
|
||
def test_aclgraph(cur_case: dict, monkeypatch: pytest.MonkeyPatch):
|
||
# Counter doesn't work in default "spawn" mode
|
||
metrics = None
|
||
if "DeepSeek-V2-Lite-W8A8" in cur_case["model"]:
|
||
# TODO(shihan-lin168): remove this env after set_device issue is resolved
|
||
monkeypatch.setenv("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
|
||
|
||
else:
|
||
monkeypatch.setenv("OMP_NUM_THREADS", "1")
|
||
monkeypatch.delenv("VLLM_WORKER_MULTIPROC_METHOD", raising=False)
|
||
metrics = {
|
||
"capture_mem": {
|
||
# Monitor key metrics of mem
|
||
"call_counts": multiprocessing.Value("i", 0),
|
||
"mem_before_capture": multiprocessing.Value("q", 0),
|
||
"mem_after_capture": multiprocessing.Value("q", 0),
|
||
},
|
||
}
|
||
|
||
port = get_open_port()
|
||
|
||
# Create a queue to collect results from both processes
|
||
result_queue: multiprocessing.Queue[dict] = multiprocessing.Queue()
|
||
|
||
# Launch workers
|
||
workers = []
|
||
for rank in range(cur_case["data_parallel_size"]):
|
||
p = multiprocessing.Process(
|
||
target=_run_worker_process,
|
||
args=(rank, rank, cur_case["data_parallel_size"], cur_case, "127.0.0.1", port, result_queue, metrics),
|
||
)
|
||
p.start()
|
||
workers.append(p)
|
||
|
||
all_dp_results = []
|
||
|
||
# get results
|
||
for _ in range(cur_case["data_parallel_size"]):
|
||
try:
|
||
result = result_queue.get(timeout=180)
|
||
all_dp_results.append(result)
|
||
except queue.Empty:
|
||
print("Error: Timeout waiting for worker results. A worker might have crashed.")
|
||
break
|
||
|
||
# Supervision loop
|
||
for p in workers:
|
||
p.join(timeout=30)
|
||
if p.exitcode != 0:
|
||
for k in workers:
|
||
if k.is_alive():
|
||
k.kill()
|
||
p.join(timeout=5)
|
||
raise RuntimeError(f"Worker {p.pid} failed with exit code {p.exitcode}")
|
||
_exit()
|
||
assert len(all_dp_results) == cur_case["data_parallel_size"], f"Expected 2 results, got {len(all_dp_results)}"
|
||
|
||
# check graph memory
|
||
if metrics is not None:
|
||
check_capture_mem(metrics["capture_mem"], cur_case["baseline_capture_mem"], cur_case["capture_mem_tolerance"])
|
||
|
||
# check accuracy
|
||
decode_atol = 2 * ATOL
|
||
for result in all_dp_results:
|
||
check_accuracy(cur_case["golden_answers"]["short"], result["short"], ATOL, decode_atol)
|
||
check_accuracy(cur_case["golden_answers"]["long"], result["long"], ATOL, decode_atol)
|