Files
enginex-ascend-910-vllm/tests/ut/spec_decode/test_utils.py
Sun Ruoxi 7f8a1b1f7a init v0.23.0
Signed-off-by: Sun Ruoxi <sunruoxi@4paradigm.com>
2026-08-27 15:11:51 +08:00

187 lines
7.7 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Unit tests for vllm_ascend.spec_decode.utils.
These exercise the CPU/GPU correction helpers used by the async spec-decode
path on Ascend.
"""
from __future__ import annotations
import numpy as np
import torch
from vllm_ascend.spec_decode.utils import (
correct_optimistic_seq_lens_cpu,
update_num_computed_tokens_for_batch_change,
)
def _build_optimistic_seq_lens(
prev_step_computed: np.ndarray,
prev_drafts: np.ndarray,
num_scheduled_step_n: np.ndarray,
) -> np.ndarray:
"""Recreate the value the scheduler would write into ``optimistic_seq_lens_cpu``.
The scheduler advances ``num_computed_tokens_cpu`` by the previous step's
scheduled-token count (``prev_drafts + 1``), which is the optimistic count
assuming all drafts were accepted.
"""
optimistic_num_computed = prev_step_computed + (prev_drafts + 1)
return optimistic_num_computed + num_scheduled_step_n
def _ground_truth_seq_lens(
prev_step_computed: np.ndarray,
valid_count: np.ndarray,
num_scheduled_step_n: np.ndarray,
) -> np.ndarray:
"""``self.seq_lens`` (GPU) carries this exact value after correction."""
return prev_step_computed + valid_count + num_scheduled_step_n
def test_correct_optimistic_seq_lens_cpu_matches_gpu_seq_lens():
"""CPU correction must match the seq_lens that the GPU path computes."""
num_reqs = 5
# prev_step_computed is C[N-2], i.e. num_computed at end of step N-2
prev_step_computed = np.array([100, 200, 50, 0, 300], dtype=np.int32)
# Drafts scheduled in step N-1; participating reqs have prev_drafts > 0
prev_drafts = np.array([2, 4, 0, 0, 3], dtype=np.int32)
# Drafts actually accepted; valid_count == 1 + accepted (bonus + accepted)
accepted = np.array([2, 0, 0, 0, 1], dtype=np.int32)
valid_count = accepted + 1
# Step N's scheduled count
num_scheduled_step_n = np.array([3, 5, 1, 1, 4], dtype=np.int32)
# prev_positions: -1 for new requests, otherwise gather index
prev_positions = np.array([0, 1, 2, -1, 4], dtype=np.int32)
optimistic = _build_optimistic_seq_lens(prev_step_computed, prev_drafts, num_scheduled_step_n).astype(np.int32)
correct_optimistic_seq_lens_cpu(
optimistic,
prev_positions,
prev_drafts,
valid_count.astype(np.int32),
num_reqs,
)
expected = _ground_truth_seq_lens(prev_step_computed, valid_count, num_scheduled_step_n)
# Non-participating requests (prev_drafts == 0 or prev_positions < 0) keep
# the optimistic value, which already coincides with the truth because
# there were no drafts to reject.
non_participating = (prev_drafts == 0) | (prev_positions < 0)
expected[non_participating] = _build_optimistic_seq_lens(
prev_step_computed[non_participating],
prev_drafts[non_participating],
num_scheduled_step_n[non_participating],
)
np.testing.assert_array_equal(optimistic, expected)
def test_correct_optimistic_seq_lens_cpu_no_participants():
"""No participating reqs → optimistic_seq_lens unchanged."""
optimistic = np.array([10, 20, 30], dtype=np.int32)
correct_optimistic_seq_lens_cpu(
optimistic,
np.array([-1, -1, -1], dtype=np.int32),
np.zeros(3, dtype=np.int32),
np.zeros(3, dtype=np.int32),
3,
)
np.testing.assert_array_equal(optimistic, np.array([10, 20, 30]))
def test_correct_optimistic_seq_lens_cpu_all_accepted():
"""When every draft was accepted, the correction is a no-op."""
num_reqs = 3
prev_drafts = np.array([2, 3, 1], dtype=np.int32)
valid_count = (prev_drafts + 1).astype(np.int32) # all accepted
prev_positions = np.array([0, 1, 2], dtype=np.int32)
optimistic = np.array([105, 208, 51], dtype=np.int32)
expected = optimistic.copy()
correct_optimistic_seq_lens_cpu(optimistic, prev_positions, prev_drafts, valid_count, num_reqs)
np.testing.assert_array_equal(optimistic, expected)
def test_correct_optimistic_seq_lens_cpu_all_rejected():
"""When every draft was rejected (only bonus token kept), correction == prev_drafts."""
num_reqs = 3
prev_drafts = np.array([2, 3, 1], dtype=np.int32)
valid_count = np.array([1, 1, 1], dtype=np.int32) # only bonus kept
prev_positions = np.array([0, 1, 2], dtype=np.int32)
optimistic = np.array([105, 208, 51], dtype=np.int32)
expected = optimistic - prev_drafts
correct_optimistic_seq_lens_cpu(optimistic, prev_positions, prev_drafts, valid_count, num_reqs)
np.testing.assert_array_equal(optimistic, expected)
def test_correct_optimistic_seq_lens_cpu_in_place_mutation():
"""The function must modify the input array in place."""
optimistic = np.array([100, 200], dtype=np.int64)
prev_drafts = np.array([2, 0], dtype=np.int32)
valid_count = np.array([2, 0], dtype=np.int32) # 1 of 2 accepted, prefill
prev_positions = np.array([0, -1], dtype=np.int32)
pre_id = id(optimistic)
correct_optimistic_seq_lens_cpu(optimistic, prev_positions, prev_drafts, valid_count, 2)
assert id(optimistic) == pre_id
# req 0: correction = prev_drafts + 1 - valid_count = 2 + 1 - 2 = 1
# req 1: not participating → unchanged
np.testing.assert_array_equal(optimistic, np.array([99, 200]))
def test_correct_optimistic_seq_lens_cpu_partial_batch():
"""Only the first num_reqs entries of the buffer are touched."""
optimistic = np.array([100, 200, 999, 999], dtype=np.int32)
prev_drafts = np.array([2, 1, 0, 0], dtype=np.int32)
valid_count = np.array([2, 2, 0, 0], dtype=np.int32)
prev_positions = np.array([0, 1, -1, -1], dtype=np.int32)
correct_optimistic_seq_lens_cpu(optimistic, prev_positions, prev_drafts, valid_count, 2)
# req 0: correction = 2 + 1 - 2 = 1, → 99
# req 1: correction = 1 + 1 - 2 = 0, → 200 unchanged
# tail (idx 2,3) untouched
np.testing.assert_array_equal(optimistic, np.array([99, 200, 999, 999]))
def test_cpu_and_gpu_corrections_agree():
"""The CPU helper must agree with ``update_num_computed_tokens_for_batch_change``.
They live on different sides of the device boundary, but both implement
the same correction. We compare the post-correction seq_lens (= corrected
num_computed_tokens + num_scheduled_step_n) on each path.
"""
num_reqs = 6
prev_step_computed = np.array([100, 250, 80, 0, 410, 5], dtype=np.int32)
prev_drafts = np.array([2, 4, 0, 0, 3, 1], dtype=np.int32)
accepted = np.array([2, 1, 0, 0, 0, 1], dtype=np.int32)
valid_count = (accepted + 1).astype(np.int32)
num_scheduled_step_n = np.array([3, 5, 1, 1, 4, 2], dtype=np.int32)
prev_positions = np.array([0, 1, 2, -1, 4, 5], dtype=np.int32)
# CPU path
optimistic = _build_optimistic_seq_lens(prev_step_computed, prev_drafts, num_scheduled_step_n).astype(np.int32)
correct_optimistic_seq_lens_cpu(optimistic, prev_positions, prev_drafts, valid_count, num_reqs)
# GPU path on CPU device for portability
cpu_num_computed = torch.from_numpy(
prev_step_computed + prev_drafts + 1 # scheduler-bumped optimistic
).to(torch.int32)
num_computed_gpu = torch.from_numpy(prev_step_computed.copy()).to(torch.int32)
num_accepted_gpu = torch.zeros(num_reqs, dtype=torch.int32)
valid_count_t = torch.from_numpy(valid_count)
prev_positions_t = torch.from_numpy(prev_positions)
prev_drafts_t = torch.from_numpy(prev_drafts)
update_num_computed_tokens_for_batch_change(
num_computed_gpu,
num_accepted_gpu,
prev_positions_t,
valid_count_t,
prev_drafts_t,
cpu_num_computed,
)
gpu_seq_lens = num_computed_gpu.numpy() + num_scheduled_step_n
np.testing.assert_array_equal(optimistic, gpu_seq_lens)