Files
vLLM/tests/v1/attention/test_cpu_mla_backend.py
T
maobaolongandLi, Jiang <jiang1.li@intel.com> 13726c80fe [CPU] Add MLA backend so DeepSeek-V2/V3 can run on CPU (#49453)
Signed-off-by: baoloongmao <baoloongmao@tencent.com>
Co-authored-by: Li, Jiang <jiang1.li@intel.com>
2026-08-06 16:51:41 +08:00

89 lines
3.1 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Smoke tests for the CPU MLA backend.
Two levels of coverage are provided:
* :func:`test_kv_cache_cpu_write` checks that the kv-cache write logic
inlined in the CPU MLA backend writes the latent KV cache in the same
layout the CPU decode kernel expects. This runs without downloading
any model weights and is safe for CI.
* :func:`test_cpu_mla_backend_smoke` exercises the full end-to-end path
(LLM engine, model construction, prefill + decode) against a shrunk
DeepSeek-V2-Lite with `hf_overrides`. It uses `dummy` weights so the
outputs are not meaningful, only shape / plumbing correctness is
asserted. Marked `cpu_model` so it is skipped on non-CPU CI.
"""
import pytest
import torch
from vllm.platforms import current_platform
@pytest.mark.skipif(not current_platform.is_cpu(), reason="CPU only")
def test_kv_cache_cpu_write() -> None:
kv_lora_rank = 512
pe_dim = 64
block_size = 16
num_blocks = 4
num_tokens = 8
dtype = torch.float32
kv_c = torch.randn(num_tokens, kv_lora_rank, dtype=dtype)
k_pe = torch.randn(num_tokens, pe_dim, dtype=dtype)
kv_cache = torch.zeros(num_blocks, block_size, kv_lora_rank + pe_dim, dtype=dtype)
slot_mapping = torch.arange(num_tokens, dtype=torch.long)
# Mark the last slot as padding to make sure we honour negative slots.
slot_mapping[-1] = -1
flat = kv_cache.view(-1, kv_lora_rank + pe_dim)
slots = slot_mapping.to(torch.long)
valid_mask = slots >= 0
if not valid_mask.all().item():
slots = slots[valid_mask]
kv_c = kv_c[valid_mask]
k_pe = k_pe[valid_mask]
target_dtype = flat.dtype
flat[slots, :kv_lora_rank] = kv_c.to(target_dtype)
flat[slots, kv_lora_rank:] = k_pe.to(target_dtype)
flat = kv_cache.view(-1, kv_lora_rank + pe_dim)
for i in range(num_tokens - 1):
assert torch.equal(flat[i, :kv_lora_rank], kv_c[i])
assert torch.equal(flat[i, kv_lora_rank:], k_pe[i])
# The padding slot must remain untouched.
assert torch.all(flat[num_tokens - 1] == 0)
@pytest.mark.cpu_model
@pytest.mark.skipif(not current_platform.is_cpu(), reason="CPU only")
def test_cpu_mla_backend_smoke() -> None:
# Shrink the architecture aggressively so the test is CI-friendly.
from vllm import LLM, SamplingParams
hf_overrides = {
"num_hidden_layers": 2,
"n_routed_experts": 4,
"first_k_dense_replace": 0,
"num_experts_per_tok": 2,
}
llm = LLM(
model="deepseek-ai/DeepSeek-V2-Lite",
trust_remote_code=True,
load_format="dummy",
enforce_eager=True,
max_model_len=128,
max_num_seqs=2,
block_size=16,
hf_overrides=hf_overrides,
)
sampling_params = SamplingParams(max_tokens=4, temperature=0.0)
outputs = llm.generate(["Hello", "MLA on CPU"], sampling_params)
assert len(outputs) == 2
for output in outputs:
# `dummy` weights do not produce meaningful text, but the number
# of generated tokens must match what we asked for.
assert len(output.outputs[0].token_ids) == 4