[Bugfix][CPU] Fix several bugs (#54042)

Signed-off-by: jiang1.li <jiang1.li@intel.com>

Li, Jiang committed Aug 31, 2026 at 15:33 UTC fdbf2ddbd22a82f8f59b97ba30e5436f0509d2c8
11 files changed +118 -85
csrc/cpu/cpu_fused_moe.cpp
+33 -26
@@ -136,60 +136,67 @@ void fused_moe_impl(scalar_t* __restrict__ output, scalar_t* __restrict__ input,
136 }();
137
138 // allocate buffers
139 - int32_t common_buffer_offset = 0;
140 - int32_t w13_thread_buffer_offset = 0;
141 - int32_t ws_thread_buffer_offset = 0;
139 + // Sizes/offsets below are byte counts across the whole (token_num *
140 + // topk_num) batch, which for large hidden sizes and batch sizes can
141 + // exceed INT32_MAX. Use int64_t throughout to avoid silently wrapping
142 + // into a negative size that gets sign-extended into a huge value when
143 + // passed to ScratchPadManager::realloc(size_t).
144 + int64_t common_buffer_offset = 0;
145 + int64_t w13_thread_buffer_offset = 0;
146 + int64_t ws_thread_buffer_offset = 0;
147
148 // common buffers
144 - const int32_t token_num_per_group_buffer_size =
149 + const int64_t token_num_per_group_buffer_size =
150 cpu_utils::round_up<64>(expert_num * sizeof(int32_t));
146 - const int32_t token_num_per_group_buffer_offset = common_buffer_offset;
151 + const int64_t token_num_per_group_buffer_offset = common_buffer_offset;
152 common_buffer_offset += token_num_per_group_buffer_size;
153
149 - const int32_t cu_token_num_per_group_buffer_size =
154 + const int64_t cu_token_num_per_group_buffer_size =
155 cpu_utils::round_up<64>((expert_num + 1) * sizeof(int32_t));
151 - const int32_t cu_token_num_per_group_buffer_offset = common_buffer_offset;
156 + const int64_t cu_token_num_per_group_buffer_offset = common_buffer_offset;
157 common_buffer_offset += cu_token_num_per_group_buffer_size;
158
154 - const int32_t expand_token_id_buffer_size =
155 - cpu_utils::round_up<64>(token_num * topk_num * sizeof(int32_t));
156 - const int32_t expand_token_id_buffer_offset = common_buffer_offset;
159 + const int64_t expand_token_id_buffer_size = cpu_utils::round_up<64>(
160 + static_cast<int64_t>(token_num) * topk_num * sizeof(int32_t));
161 + const int64_t expand_token_id_buffer_offset = common_buffer_offset;
162 common_buffer_offset += expand_token_id_buffer_size;
163
159 - const int32_t expand_token_id_index_buffer_size =
160 - cpu_utils::round_up<64>(token_num * topk_num * sizeof(int32_t));
161 - const int32_t expand_token_id_index_buffer_offset = common_buffer_offset;
164 + const int64_t expand_token_id_index_buffer_size = cpu_utils::round_up<64>(
165 + static_cast<int64_t>(token_num) * topk_num * sizeof(int32_t));
166 + const int64_t expand_token_id_index_buffer_offset = common_buffer_offset;
167 common_buffer_offset += expand_token_id_index_buffer_size;
168
164 - const int32_t w13_gemm_output_buffer_size = cpu_utils::round_up<64>(
165 - token_num * topk_num * (output_size_13 / 2) * sizeof(scalar_t));
166 - const int32_t w13_gemm_output_buffer_offset = common_buffer_offset;
169 + const int64_t w13_gemm_output_buffer_size =
170 + cpu_utils::round_up<64>(static_cast<int64_t>(token_num) * topk_num *
171 + (output_size_13 / 2) * sizeof(scalar_t));
172 + const int64_t w13_gemm_output_buffer_offset = common_buffer_offset;
173 common_buffer_offset += w13_gemm_output_buffer_size;
174
169 - const int32_t w2_gemm_output_buffer_size = cpu_utils::round_up<64>(
170 - token_num * topk_num * output_size_2 * sizeof(float));
171 - const int32_t w2_gemm_output_buffer_offset = common_buffer_offset;
175 + const int64_t w2_gemm_output_buffer_size =
176 + cpu_utils::round_up<64>(static_cast<int64_t>(token_num) * topk_num *
177 + output_size_2 * sizeof(float));
178 + const int64_t w2_gemm_output_buffer_offset = common_buffer_offset;
179 common_buffer_offset += w2_gemm_output_buffer_size;
180
181 // w13 GEMM thread buffers
175 - const int32_t w13_input_buffer_offset = w13_thread_buffer_offset;
182 + const int64_t w13_input_buffer_offset = w13_thread_buffer_offset;
183 w13_thread_buffer_offset += w13_input_buffer_size;
184
178 - const int32_t w13_output_buffer_size = cpu_utils::round_up<64>(
185 + const int64_t w13_output_buffer_size = cpu_utils::round_up<64>(
186 gemm_m_tile_size * w13_n_tile_size * sizeof(float));
180 - const int32_t w13_output_buffer_offset = w13_thread_buffer_offset;
187 + const int64_t w13_output_buffer_offset = w13_thread_buffer_offset;
188 w13_thread_buffer_offset += w13_output_buffer_size;
189
183 - const int32_t w2_input_buffer_offset = w13_thread_buffer_offset;
190 + const int64_t w2_input_buffer_offset = w13_thread_buffer_offset;
191 w13_thread_buffer_offset += w2_input_buffer_size;
192
193 // Weighted sum thread buffer
187 - const int32_t ws_output_buffer_size =
194 + const int64_t ws_output_buffer_size =
195 cpu_utils::round_up<64>(output_size_2 * sizeof(float));
189 - const int32_t ws_output_buffer_offset = ws_thread_buffer_offset;
196 + const int64_t ws_output_buffer_offset = ws_thread_buffer_offset;
197 ws_thread_buffer_offset += ws_output_buffer_size;
198
192 - const int32_t buffer_size =
199 + const int64_t buffer_size =
200 common_buffer_offset +
201 std::max(w13_thread_buffer_offset, ws_thread_buffer_offset) * thread_num;
202 cpu_utils::ScratchPadManager::get_scratchpad_manager()->realloc(buffer_size);
csrc/cpu/sgl-kernels/gemm.cpp
+4 -7
@@ -723,9 +723,10 @@ at::Tensor convert_scale_packed(at::Tensor& scale) {
723 // bias : [N]
724 // out : [*, N]
725 //
726 -at::Tensor
727 -weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at::Tensor>& bias, bool is_vnni) {
728 - auto packed_w = is_vnni ? mat2 : convert_weight_packed(mat2);
726 +void
727 +weight_packed_linear(at::Tensor& out, const at::Tensor& mat1, const at::Tensor& mat2, const std::optional<at::Tensor>& bias, bool is_vnni) {
728 + TORCH_CHECK(is_vnni);
729 + auto packed_w = mat2;
730 bool use_fma_gemm = false;
731 if (packed_w.scalar_type() == at::kFloat) {
732 use_fma_gemm = true;
@@ -746,7 +747,6 @@ weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at:
747 }
748
749 auto dispatch_type = mat1.scalar_type();
749 - auto out = at::empty({M, N}, mat1.options());
750 // strides
751 int64_t out_strideM = out.stride(0);
752 int64_t mat1_strideM = mat1.stride(-2);
@@ -784,9 +784,6 @@ weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2, const std::optional<at:
784 out_strideM);
785 }
786 });
787 -
788 - input_sizes[ndim - 1] = N;
789 - return out.view(input_sizes);
787 }
788
789 // mat1 : [M, K]
csrc/cpu/torch_bindings.cpp
+6 -5
@@ -58,9 +58,9 @@ std::vector<torch::Tensor> shm_recv_tensor_list(int64_t handle, int64_t src);
58
59 // SGL CPU kernels
60
61 -at::Tensor weight_packed_linear(at::Tensor& mat1, at::Tensor& mat2,
62 - const std::optional<at::Tensor>& bias,
63 - bool is_vnni);
61 +void weight_packed_linear(at::Tensor& out, const at::Tensor& mat1,
62 + const at::Tensor& mat2,
63 + const std::optional<at::Tensor>& bias, bool is_vnni);
64
65 at::Tensor convert_weight_packed(at::Tensor& weight);
66
@@ -507,8 +507,9 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
507 // sgl-kernels
508 #if defined(__AVX512BF16__) && defined(__AVX512F__) && defined(__AVX512VNNI__)
509 ops.def(
510 - "weight_packed_linear(Tensor(a0!) mat1, Tensor(a1!) mat2, Tensor(a2!)? "
511 - "bias, bool is_vnni) -> Tensor");
510 + "weight_packed_linear(Tensor(a0!) out, Tensor(a1) mat1, Tensor(a2) mat2, "
511 + "Tensor(a3)? "
512 + "bias, bool is_vnni) -> ()");
513 ops.impl("weight_packed_linear", torch::kCPU, &weight_packed_linear);
514 ops.def("convert_weight_packed(Tensor! weight) -> Tensor");
515 ops.impl("convert_weight_packed", torch::kCPU, &convert_weight_packed);
tests/conftest.py
+9 -1
@@ -934,6 +934,14 @@ def hf_runner():
934 return HfRunner
935
936
937 +def _default_block_size() -> int:
938 + if torch.xpu.is_available():
939 + return 64
940 + if current_platform.is_cpu():
941 + return 128
942 + return 16
943 +
944 +
945 class VllmRunner:
946 """
947 The default value of some arguments have been modified from
@@ -962,7 +970,7 @@ class VllmRunner:
970 dtype: str = "auto",
971 disable_log_stats: bool = True,
972 tensor_parallel_size: int = 1,
965 - block_size: int = 16 if not torch.xpu.is_available() else 64,
973 + block_size: int = _default_block_size(),
974 enable_chunked_prefill: bool | None = False,
975 enforce_eager: bool | None = False,
976 # Set this to avoid hanging issue
tests/v1/attention/test_group_head_counts.py
+3 -2
@@ -44,9 +44,10 @@ def _build(layer_num_heads: list[int]) -> CPUAttentionMetadataBuilder:
44 vllm_config = MagicMock()
45 vllm_config.model_config.dtype = torch.bfloat16
46 vllm_config.model_config.get_num_attention_heads.return_value = MODEL_WIDE_NUM_HEADS
47 - vllm_config.cache_config.block_size = 16
47 vllm_config.cache_config.cache_dtype = "auto"
49 - kv_cache_spec = SimpleNamespace(num_kv_heads=NUM_KV_HEADS, head_size=64)
48 + kv_cache_spec = SimpleNamespace(
49 + num_kv_heads=NUM_KV_HEADS, head_size=64, block_size=16
50 + )
51
52 with (
53 patch(
tests/v1/kv_connector/unit/test_bidirectional_kv_transfer.py
+4 -2
@@ -87,8 +87,10 @@ def _make_connector_with_fake_worker(
87 hand_shake_latency=0, cycles_before_done=0, do_handshake=True
88 ):
89 """Create a NixlConnector with FakeNixlConnectorWorker."""
90 - vllm_config = create_vllm_config(kv_connector_extra_config=BIDIR_KV_EXTRA_CONFIG)
91 - kv_cache_config = make_kv_cache_config(block_size=16, num_blocks=2)
90 + vllm_config = create_vllm_config(
91 + kv_connector_extra_config=BIDIR_KV_EXTRA_CONFIG, block_size=32
92 + )
93 + kv_cache_config = make_kv_cache_config(block_size=32, num_blocks=2)
94 connector = NixlConnector(vllm_config, KVConnectorRole.WORKER, kv_cache_config)
95 connector.connector_worker = FakeNixlConnectorWorker(
96 vllm_config,
vllm/_custom_ops.py
+11 -12
@@ -3328,18 +3328,17 @@ def dsv3_fused_a_gemm(
3328 torch.ops._C.dsv3_fused_a_gemm(output, mat_a, mat_b, enable_pdl)
3329
3330
3331 -if hasattr(torch.ops._C, "weight_packed_linear"):
3332 -
3333 - @register_fake("_C::weight_packed_linear")
3334 - def weight_packed_linear_fake(
3335 - mat1: torch.Tensor,
3336 - mat2: torch.Tensor,
3337 - bias: torch.Tensor | None,
3338 - is_vnni: bool,
3339 - ) -> torch.Tensor:
3340 - return torch.empty(
3341 - (mat1.size(0), mat2.size(0)), dtype=mat1.dtype, device=mat2.device
3342 - )
3331 +def weight_packed_linear_cpu(
3332 + x: torch.Tensor,
3333 + packed_weight: torch.Tensor,
3334 + n: int,
3335 + bias: torch.Tensor | None,
3336 +) -> torch.Tensor:
3337 + output = torch.empty((*x.shape[0:-1], n), dtype=x.dtype)
3338 + torch.ops._C.weight_packed_linear(
3339 + output.reshape(-1, n), x.reshape(-1, x.size(-1)), packed_weight, bias, True
3340 + )
3341 + return output
3342
3343
3344 class CPUQuantMethod(IntEnum):
vllm/model_executor/layers/mamba/gdn/qwen_gdn_linear_attn.py
+13 -1
@@ -150,6 +150,15 @@ def _log_gdn_backend_decision(
150 head_k_dim = getattr(
151 vllm_config.model_config.hf_text_config, "linear_key_head_dim", None
152 )
153 +
154 + if current_platform.is_cpu():
155 + logger.info_once(
156 + "Using %s GDN prefill kernel (head_k_dim=%s).",
157 + "CPU",
158 + head_k_dim,
159 + )
160 + return
161 +
162 chosen = {
163 "flashinfer": "FlashInfer",
164 "cutedsl": "CuteDSL",
@@ -492,7 +501,7 @@ class QwenGatedDeltaNetAttention(GatedDeltaNetAttention):
501 envs.VLLM_ENABLE_FLA_PACKED_RECURRENT_DECODE
502 )
503 self.gdn_decode_kernel = envs.VLLM_GDN_DECODE_KERNEL.strip().lower()
495 - if self.gdn_decode_kernel == "cuda":
504 + if self.gdn_decode_kernel == "cuda" and current_platform.is_cuda_alike():
505 reason = self._fused_gdn_decode_unsupported_reason(vllm_config)
506 if reason is not None:
507 if "VLLM_GDN_DECODE_KERNEL" in os.environ:
@@ -503,6 +512,9 @@ class QwenGatedDeltaNetAttention(GatedDeltaNetAttention):
512 "Falling back to the Triton GDN decode path: %s", reason
513 )
514 self.gdn_decode_kernel = "triton"
515 + elif current_platform.is_cpu():
516 + self.gdn_decode_kernel = "CPU"
517 +
518 self.enable_fused_gdn_decode = self.gdn_decode_kernel == "cuda"
519 logger.info_once("GDN decode kernel: %s", self.gdn_decode_kernel)
520
vllm/model_executor/layers/utils.py
+7 -5
@@ -505,13 +505,15 @@ def dispatch_cpu_unquantized_gemm(
505 # cross over to favoring oneDNN once batch size grows past decode-sized
506 # M, so they keep using oneDNN below.
507 if check_cpu_sgl_kernel(N, K, dtype):
508 + # For small size GEMM, packed_weight might be float32
509 packed_weight = torch.ops._C.convert_weight_packed(layer.weight)
510 if getattr(layer, "bias", None) is not None:
510 - bias_f32 = layer.bias.to(torch.float32)
511 - else:
512 - bias_f32 = None
513 - layer.cpu_linear = lambda x, weight, bias: torch.ops._C.weight_packed_linear(
514 - x, packed_weight, bias_f32 if bias is not None else None, True
511 + layer.bias.data = layer.bias.to(torch.float32)
512 + layer.cpu_linear = lambda x, weight, bias: ops.weight_packed_linear_cpu(
513 + x,
514 + packed_weight,
515 + N,
516 + bias,
517 )
518 if remove_weight:
519 layer.weight = torch.nn.Parameter(torch.empty(0), requires_grad=False)
vllm/triton_utils/importing.py
+14 -11
@@ -34,6 +34,20 @@ if HAS_TRITON:
34 if name != "cpu" and x.driver and x.driver.is_active()
35 ]
36
37 + # Check Triton CPU
38 + if "cpu" in version("vllm"):
39 + if "cpu" in backends:
40 + HAS_TRITON = True
41 + # Suppress following warnings on CPU-only platforms
42 + if len(active_drivers) == 0:
43 + active_drivers.append(backends["cpu"].driver)
44 + else:
45 + logger.warning(
46 + "Triton is installed, but doesn't include CPU backend. "
47 + "Disabling Triton."
48 + )
49 + HAS_TRITON = False
50 +
51 # Check if we're in a distributed environment where CUDA_VISIBLE_DEVICES
52 # or HIP_VISIBLE_DEVICES might be temporarily empty (e.g., Ray sets it to ""
53 # during actor init)
@@ -63,17 +77,6 @@ if HAS_TRITON:
77 len(active_drivers),
78 )
79 HAS_TRITON = False
66 -
67 - # Check Triton CPU
68 - if "cpu" in version("vllm"):
69 - if "cpu" in backends:
70 - HAS_TRITON = True
71 - else:
72 - logger.warning(
73 - "Triton is installed, but doesn't include CPU backend. "
74 - "Disabling Triton."
75 - )
76 - HAS_TRITON = False
80 except ImportError:
81 # This can occur if Triton is partially installed or triton.backends
82 # is missing.
vllm/v1/attention/backends/cpu_attn.py
+14 -13
@@ -13,7 +13,6 @@ from vllm import _custom_ops as ops
13 from vllm import envs
14 from vllm.config import (
15 VllmConfig,
16 - get_current_vllm_config,
16 get_layers_from_vllm_config,
17 )
18 from vllm.logger import init_logger
@@ -59,7 +58,7 @@ class CPUAttentionBackend(AttentionBackend):
58
59 @staticmethod
60 def get_supported_kernel_block_sizes() -> list[int | MultipleOf]:
62 - return [MultipleOf(16)]
61 + return [MultipleOf(32)]
62
63 @classmethod
64 def get_supported_head_sizes(cls) -> list[int]:
@@ -152,7 +151,7 @@ class CPUAttentionMetadataBuilder(AttentionMetadataBuilder[CPUAttentionMetadata]
151 self.head_dim = kv_cache_spec.head_size
152 self.dtype = vllm_config.model_config.dtype
153 self.window_size = self._group_sliding_window()
155 - self.block_size = vllm_config.cache_config.block_size
154 + self.block_size = kv_cache_spec.block_size
155 self.kv_cache_dtype = vllm_config.cache_config.cache_dtype
156 self.isa = _get_attn_isa(
157 self.dtype,
@@ -160,11 +159,21 @@ class CPUAttentionMetadataBuilder(AttentionMetadataBuilder[CPUAttentionMetadata]
159 self.head_dim,
160 self.kv_cache_dtype,
161 )
162 + self._set_isa_to_layers(layer_names)
163 self.is_cross_attention = isinstance(kv_cache_spec, CrossAttentionSpec)
164 self.is_encoder_only_attention = isinstance(
165 kv_cache_spec, EncoderOnlyAttentionSpec
166 )
167
168 + def _set_isa_to_layers(self, layer_names: list[str]) -> None:
169 + attn_layers = get_layers_from_vllm_config(
170 + self.vllm_config,
171 + Attention,
172 + layer_names,
173 + )
174 + for layer in attn_layers.values():
175 + layer.isa = self.isa # type: ignore
176 +
177 def _group_sliding_window(self) -> int:
178 """The window shared by every layer in this group, else -1 (no window).
179
@@ -325,14 +334,6 @@ class CPUAttentionBackendImpl(AttentionImpl):
334 "heads in the layer"
335 )
336
328 - vllm_config = get_current_vllm_config()
329 - self.isa = _get_attn_isa(
330 - vllm_config.model_config.dtype,
331 - vllm_config.cache_config.block_size,
332 - self.head_size,
333 - self.kv_cache_dtype,
334 - )
335 -
337 def forward(
338 self,
339 layer: AttentionLayer,
@@ -391,7 +392,7 @@ class CPUAttentionBackendImpl(AttentionImpl):
392 key_cache,
393 value_cache,
394 attn_metadata.slot_mapping,
394 - self.isa,
395 + layer.isa, # type: ignore
396 k_scale=layer._k_scale_float,
397 v_scale=layer._v_scale_float,
398 kv_cache_dtype=self.kv_cache_dtype,
@@ -440,7 +441,7 @@ class CPUAttentionBackendImpl(AttentionImpl):
441 key_cache,
442 value_cache,
443 slot_mapping,
443 - self.isa,
444 + layer.isa, # type: ignore
445 k_scale=layer._k_scale_float,
446 v_scale=layer._v_scale_float,
447 kv_cache_dtype=self.kv_cache_dtype,