diff --git a/tests/vllm/v1/worker/test_runner.cpp b/tests/vllm/v1/worker/test_runner.cpp index 107f2e8a9..2b5fd5cca 100644 --- a/tests/vllm/v1/worker/test_runner.cpp +++ b/tests/vllm/v1/worker/test_runner.cpp @@ -1527,3 +1527,32 @@ TEST_CASE("runner: full-attention-only step skips GDN metadata build (no OOB)") doctest::Contains("qwen3_5 dense paged forward"), std::runtime_error); } + +// ─── M3: THE BLOCK-SIZE CONTRACT AT ITS PRODUCTION CALL SITE ───────────────── +// +// `CheckKvCacheShape` is well tested in isolation (test_attn_backend_registry / +// test_common_attn_metadata), but its PRODUCTION call site — the install inside +// `GPUModelRunner::initialize_kv_cache` — was not: no test drove the runner +// with a non-multiple-of-16 block size, so deleting that install left every +// gate green (the #1065 Owed item). The FLASH_ATTN backend's own +// `get_kv_cache_shape` refuses block_size % 16 != 0, and the runner resolves +// FLASH_ATTN for the CPU device, so construction must throw the backend's +// `invalid_argument` from init — the same failure the server's --block-size +// validation and the bench rounding exist to prevent at the entry points. +TEST_CASE("runner: initialize_kv_cache refuses a non-multiple-of-16 block size") { + const HfConfig c = MakeDenseOnlyConfig(); + const Qwen3_5DenseWeights w = MakeDenseOnlyWeights(c); + + KVCacheConfig kv = MakeFaOnlyKvConfig(c); + kv.kv_cache_groups[0].kv_cache_spec = std::make_shared( + /*block_size=*/8, static_cast(c.num_key_value_heads), + static_cast(c.head_dim), vllm::v1::ResolveKvCacheDType()); + + auto make_runner = [&]() { + GPUModelRunner runner(c, w, kv, Q(), /*max_num_reqs=*/8, kMaxModelLen, + /*max_num_batched_tokens=*/64); + }; + CHECK_THROWS_WITH_AS(make_runner(), + doctest::Contains("Block size must be a multiple of 16"), + std::invalid_argument); +}