| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """DecoderReplayLayers runs the replay layers on the forward's replay batch.""" |
| 4 | |
| 5 | import pytest |
| 6 | import torch |
| 7 | |
| 8 | from vllm.forward_context import ( |
| 9 | ForwardContext, |
| 10 | get_forward_context, |
| 11 | override_forward_context, |
| 12 | ) |
| 13 | from vllm.models.deepseek_v41.decoder_replay_layers import DecoderReplayLayers |
| 14 | |
| 15 | pytestmark = pytest.mark.skipif(not torch.cuda.is_available(), reason="needs CUDA") |
| 16 | |
| 17 | DEVICE = torch.device("cuda") |
| 18 | NUM_TOKENS = 401 |
| 19 | HC, HIDDEN = 2, 3 |
| 20 | # decode (1 token), trimmed prefill (300 of 300), untrimmed prefill (100 of 300) |
| 21 | WINDOW = 128 |
| 22 | REPLAY_ROWS = [0, *range(301 - WINDOW, 301), *range(301, 401)] |
| 23 | |
| 24 | |
| 25 | def _context(metadata): |
| 26 | return ForwardContext( |
| 27 | no_compile_layers={}, |
| 28 | attn_metadata=metadata, |
| 29 | slot_mapping={}, |
| 30 | is_padding=torch.zeros(NUM_TOKENS, dtype=torch.bool, device=DEVICE), |
| 31 | ) |
| 32 | |
| 33 | |
| 34 | def _set_replay_batch(layers, rows, metadata): |
| 35 | layers.rows = torch.tensor(rows, device=DEVICE) |
| 36 | layers.forward_context = ForwardContext( |
| 37 | no_compile_layers={}, attn_metadata=metadata, slot_mapping={} |
| 38 | ) |
| 39 | |
| 40 | |
| 41 | def _states(num_tokens: int): |
| 42 | """(hidden_states, positions, input_ids, pre_mix, post_mix, res_mix, residual), |
| 43 | shaped as the model's: hidden states [T, H], mixes [T, HC], residual [T, HC, H].""" |
| 44 | hidden = torch.arange(num_tokens, device=DEVICE, dtype=torch.float32) |
| 45 | hidden = hidden[:, None].expand(num_tokens, HIDDEN).contiguous() |
| 46 | mix = hidden[:, :HC].contiguous() |
| 47 | residual = hidden[:, None, :].expand(num_tokens, HC, HIDDEN).contiguous() |
| 48 | return ( |
| 49 | hidden, |
| 50 | torch.arange(num_tokens, device=DEVICE), |
| 51 | None, |
| 52 | mix + 1, |
| 53 | mix + 2, |
| 54 | mix + 3, |
| 55 | residual, |
| 56 | ) |
| 57 | |
| 58 | |
| 59 | def test_run_gathers_states_and_realigns_shared_indexer_buffers(): |
| 60 | topk = torch.arange(NUM_TOKENS * 4, device=DEVICE).view(NUM_TOKENS, 4).int() |
| 61 | candidates = torch.arange(NUM_TOKENS * 3, device=DEVICE).view(NUM_TOKENS, 3).int() |
| 62 | topk_before, candidates_before = topk.clone(), candidates.clone() |
| 63 | seen = {} |
| 64 | |
| 65 | def run_layers(hidden_states, positions, input_ids, pre_mix, post, res, residual): |
| 66 | seen["hidden_states"] = hidden_states.clone() |
| 67 | replay_context = get_forward_context() |
| 68 | seen["attn_metadata"] = replay_context.attn_metadata |
| 69 | seen["is_padding"] = replay_context.is_padding |
| 70 | return residual, pre_mix |
| 71 | |
| 72 | layers = DecoderReplayLayers(WINDOW, run_layers, [topk, candidates]) |
| 73 | states = _states(NUM_TOKENS) |
| 74 | hidden, residual = states[0], states[-1] |
| 75 | full, sub = object(), object() |
| 76 | _set_replay_batch(layers, REPLAY_ROWS, sub) |
| 77 | context = _context(full) |
| 78 | with override_forward_context(context): |
| 79 | outputs = layers(*states) |
| 80 | assert get_forward_context() is context |
| 81 | |
| 82 | rows = torch.tensor(REPLAY_ROWS, device=DEVICE) |
| 83 | assert torch.equal(seen["hidden_states"], hidden[rows]) |
| 84 | assert seen["attn_metadata"] is sub and seen["is_padding"] is None |
| 85 | assert torch.equal(topk[: len(REPLAY_ROWS)], topk_before[rows]) |
| 86 | assert torch.equal(candidates[: len(REPLAY_ROWS)], candidates_before[rows]) |
| 87 | assert len(outputs) == 2 and outputs[0].shape == residual.shape |
| 88 | assert torch.equal(outputs[0][rows], residual[rows]) |
| 89 | assert outputs[0][1:173].abs().sum() == 0 |
| 90 | |
| 91 | |
| 92 | def test_no_replay_batch_runs_the_whole_batch(): |
| 93 | seen = {} |
| 94 | |
| 95 | def run_layers(hidden_states, *rest): |
| 96 | seen["attn_metadata"] = get_forward_context().attn_metadata |
| 97 | return (hidden_states,) |
| 98 | |
| 99 | layers = DecoderReplayLayers(WINDOW, run_layers, []) |
| 100 | states = _states(NUM_TOKENS) |
| 101 | full = object() |
| 102 | with override_forward_context(_context(full)): |
| 103 | (output,) = layers(*states) |
| 104 | assert output is states[0] and seen["attn_metadata"] is full |