|
2 | 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
3 | 3 |
|
4 | 4 | from types import SimpleNamespace |
5 | | -from unittest.mock import MagicMock, Mock |
| 5 | +from unittest.mock import Mock |
6 | 6 |
|
7 | 7 | import numpy as np |
8 | 8 | import pytest |
@@ -1756,58 +1756,65 @@ def test_mixed_none_tensor_and_list(self): |
1756 | 1756 | assert all((t == 0).all() for t in list_entry) |
1757 | 1757 |
|
1758 | 1758 |
|
1759 | | -def test_async_mamba_align_accepted_counts_race(): |
1760 | | - """Verify async MTP align mode reads accepted counts |
1761 | | - from unmutated CPU buffer (#51571). |
| 1759 | +class TestSyncNumAcceptedTokens: |
| 1760 | + """Test GPUModelRunner._sync_num_accepted_tokens() behavior |
| 1761 | + for async and non-async modes.""" |
1762 | 1762 |
|
1763 | | - Ensures that when InputBatch.condense() shifts row indices during async scheduling, |
1764 | | - the runner reads historical accepted counts from its unmutated CPU buffer |
1765 | | - snapshot rather than reading from mutated InputBatch rows. |
1766 | | - """ |
1767 | | - runner = MagicMock() |
1768 | | - runner.use_async_scheduling = True |
1769 | | - runner.num_accepted_tokens_event = MagicMock() |
1770 | | - |
1771 | | - num_reqs = 3 |
1772 | | - |
1773 | | - # 2. Step N: Runner D2H accepted counts buffer holds historical Step N output |
1774 | | - # Req A (Row 0) accepted 4, Req B (Row 1) accepted 3, Req C (Row 2) accepted 2 |
1775 | | - runner.num_accepted_tokens.np = np.array([4, 3, 2, 0, 0], dtype=np.int32) |
1776 | | - runner.num_accepted_tokens.cpu = torch.from_numpy(runner.num_accepted_tokens.np) |
1777 | | - |
1778 | | - # 3. Step N+1: InputBatch was condensed by _update_states() BEFORE event sync! |
1779 | | - # Req B (Row 1) finished. Req C moved from Row 2 -> Row 1. New Req D added at Row 2. |
1780 | | - # input_batch.num_accepted_tokens_cpu became [4, 2, 1] after condense. |
1781 | | - runner.input_batch.num_accepted_tokens_cpu = np.array( |
1782 | | - [4, 2, 1, 0, 0], dtype=np.int32 |
1783 | | - ) |
1784 | | - |
1785 | | - # 4. prev_positions mapping for Step N+1: |
1786 | | - # Row 0 came from prev Row 0 (Req A) |
1787 | | - # Row 1 came from prev Row 2 (Req C) |
1788 | | - # Row 2 is a new request (-1) (Req D) |
1789 | | - runner.prev_positions.np = np.array([0, 2, -1], dtype=np.int32) |
1790 | | - |
1791 | | - # 5. Gather logic |
1792 | | - prev_idx = runner.prev_positions.np[:num_reqs] |
1793 | | - new_mask = prev_idx < 0 |
1794 | | - src_accepted_counts = ( |
1795 | | - runner.num_accepted_tokens.np |
1796 | | - if runner.use_async_scheduling |
1797 | | - else runner.input_batch.num_accepted_tokens_cpu |
1798 | | - ) |
1799 | | - runner.num_accepted_tokens.np[:num_reqs] = src_accepted_counts[ |
1800 | | - np.where(new_mask, 0, prev_idx) |
1801 | | - ] |
1802 | | - runner.num_accepted_tokens.np[:num_reqs][new_mask] = 1 |
1803 | | - |
1804 | | - # 6. Assertions |
1805 | | - assert runner.num_accepted_tokens.np[0] == 4, "Expected 4, got {}".format( |
1806 | | - runner.num_accepted_tokens.np[0] |
1807 | | - ) # Req A |
1808 | | - assert runner.num_accepted_tokens.np[1] == 2, "Expected 2, got {}".format( |
1809 | | - runner.num_accepted_tokens.np[1] |
1810 | | - ) # Req C |
1811 | | - assert runner.num_accepted_tokens.np[2] == 1, "Expected 1, got {}".format( |
1812 | | - runner.num_accepted_tokens.np[2] |
1813 | | - ) # Req D (New) |
| 1763 | + def test_async_mamba_align_accepted_counts_race(self): |
| 1764 | + num_reqs = 3 |
| 1765 | + runner = Mock(spec=GPUModelRunner) |
| 1766 | + runner.use_async_scheduling = True |
| 1767 | + runner.num_accepted_tokens = SimpleNamespace( |
| 1768 | + np=np.array([4, 3, 2, 0, 0], dtype=np.int32), |
| 1769 | + ) |
| 1770 | + runner.input_batch = SimpleNamespace( |
| 1771 | + num_accepted_tokens_cpu=np.array([4, 2, 1, 0, 0], dtype=np.int32) |
| 1772 | + ) |
| 1773 | + runner.prev_positions = SimpleNamespace(np=np.array([0, 2, -1], dtype=np.int32)) |
| 1774 | + |
| 1775 | + GPUModelRunner._sync_num_accepted_tokens( |
| 1776 | + runner, num_reqs, {"req_a": 0, "req_c": 2} |
| 1777 | + ) |
| 1778 | + expected = np.array([4, 2, 1], dtype=np.int32) |
| 1779 | + np.testing.assert_array_equal( |
| 1780 | + runner.num_accepted_tokens.np[:num_reqs], expected |
| 1781 | + ) |
| 1782 | + np.testing.assert_array_equal( |
| 1783 | + runner.input_batch.num_accepted_tokens_cpu[:num_reqs], expected |
| 1784 | + ) |
| 1785 | + |
| 1786 | + def test_async_initial_step_empty_prev_index(self): |
| 1787 | + num_reqs = 2 |
| 1788 | + runner = Mock(spec=GPUModelRunner) |
| 1789 | + runner.use_async_scheduling = True |
| 1790 | + runner.num_accepted_tokens = SimpleNamespace(np=np.zeros(5, dtype=np.int32)) |
| 1791 | + runner.input_batch = SimpleNamespace( |
| 1792 | + num_accepted_tokens_cpu=np.zeros(5, dtype=np.int32) |
| 1793 | + ) |
| 1794 | + |
| 1795 | + GPUModelRunner._sync_num_accepted_tokens(runner, num_reqs, {}) |
| 1796 | + expected = np.array([1, 1], dtype=np.int32) |
| 1797 | + np.testing.assert_array_equal( |
| 1798 | + runner.num_accepted_tokens.np[:num_reqs], expected |
| 1799 | + ) |
| 1800 | + np.testing.assert_array_equal( |
| 1801 | + runner.input_batch.num_accepted_tokens_cpu[:num_reqs], expected |
| 1802 | + ) |
| 1803 | + |
| 1804 | + def test_non_async_mode_direct_copy(self): |
| 1805 | + num_reqs = 2 |
| 1806 | + runner = Mock(spec=GPUModelRunner) |
| 1807 | + runner.use_async_scheduling = False |
| 1808 | + runner.num_accepted_tokens = SimpleNamespace(np=np.zeros(5, dtype=np.int32)) |
| 1809 | + runner.input_batch = SimpleNamespace( |
| 1810 | + num_accepted_tokens_cpu=np.array([5, 4, 0, 0, 0], dtype=np.int32) |
| 1811 | + ) |
| 1812 | + |
| 1813 | + GPUModelRunner._sync_num_accepted_tokens(runner, num_reqs, None) |
| 1814 | + expected = np.array([5, 4], dtype=np.int32) |
| 1815 | + np.testing.assert_array_equal( |
| 1816 | + runner.num_accepted_tokens.np[:num_reqs], expected |
| 1817 | + ) |
| 1818 | + np.testing.assert_array_equal( |
| 1819 | + runner.input_batch.num_accepted_tokens_cpu[:num_reqs], expected |
| 1820 | + ) |
0 commit comments