A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 3, 11:45 UTC
[CPU] Add fused GDN support for AMX CPU platform (#42707)
Signed-off-by: jiang1.li <jiang1.li@intel.com>
Li, Jiang committed
May 18, 2026 at 18:04 UTC
b4601ad43ff7ff2b9e2f52379144481e45bcf6c5
8 files changed
+407
-14
csrc/cpu/sgl-kernels/conv.cpp
+21
-10
@@ -213,7 +213,7 @@ struct tinygemm_kernel<at::BFloat16, K, BLOCK_N, has_bias, has_silu> {
213
weight + nb_start * width, \
214
out + bs * seqlen * dim + mb_start * dim + nb_start, \
215
has_bias ? bias + nb_start : nullptr, \
216
- has_conv_states ? conv_states + conv_state_index * (K - 1) * dim + nb_start : nullptr, \
216
+ has_conv_states ? conv_states + conv_state_index * conv_state_slot_stride + nb_start : nullptr, \
217
has_initial_states_value, \
218
mb_size, \
219
dim, \
@@ -233,7 +233,8 @@ void causal_conv1d_fwd_kernel_impl(
233
int64_t dim,
234
int64_t seqlen,
235
int64_t width,
236
- int64_t num_seq_blocks) {
236
+ int64_t num_seq_blocks,
237
+ int64_t conv_state_slot_stride) {
238
// handle 32 x 64 per block
239
constexpr int64_t BLOCK_M = block_size_m();
240
constexpr int64_t BLOCK_N = block_size_n() * 2;
@@ -282,7 +283,7 @@ void causal_conv1d_fwd_kernel_impl(
283
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
284
for (int64_t bs = begin; bs < end; ++bs) {
285
update_conv_state(
285
- conv_states + bs * (width - 1) * dim, input + bs * seqlen * dim, width, dim, seqlen, has_initial_state[bs]);
286
+ conv_states + bs * conv_state_slot_stride, input + bs * seqlen * dim, width, dim, seqlen, has_initial_state[bs]);
287
}
288
});
289
}
@@ -316,7 +317,8 @@ void causal_conv1d_fwd_varlen_kernel_impl(
317
int64_t batch,
318
int64_t dim,
319
int64_t width,
319
- int64_t num_seq_blocks) {
320
+ int64_t num_seq_blocks,
321
+ int64_t conv_state_slot_stride) {
322
// handle 32 x 64 per block
323
constexpr int64_t BLOCK_M = block_size_m();
324
constexpr int64_t BLOCK_N = block_size_n() * 2;
@@ -366,7 +368,7 @@ void causal_conv1d_fwd_varlen_kernel_impl(
368
int32_t seqlen = query_start_loc[bs + 1] - query_start_loc[bs];
369
int32_t batch_offset = query_start_loc[bs];
370
update_conv_state(
369
- conv_states + conv_state_index * (width - 1) * dim,
371
+ conv_states + conv_state_index * conv_state_slot_stride,
372
input + batch_offset * dim,
373
width,
374
dim,
@@ -389,7 +391,8 @@ void causal_conv1d_update_kernel_impl(
391
int64_t batch,
392
int64_t dim,
393
int64_t seqlen,
392
- int64_t width) {
394
+ int64_t width,
395
+ int64_t conv_state_slot_stride) {
396
// handle 32 x 64 per block
397
constexpr int64_t BLOCK_M = block_size_m();
398
constexpr int64_t BLOCK_N = block_size_n() * 2;
@@ -430,7 +433,7 @@ void causal_conv1d_update_kernel_impl(
433
});
434
});
435
433
-#define CONV_STATE_INDEXR(w) conv_states + conv_state_index*(width - 1) * dim + (w) * dim
436
+#define CONV_STATE_INDEXR(w) conv_states + conv_state_index*conv_state_slot_stride + (w) * dim
437
438
// update conv_states
439
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
@@ -592,6 +595,9 @@ at::Tensor causal_conv1d_fwd_cpu(
595
}
596
}
597
598
+// IMPORTANT: To make the kernal compatible with vLLM KV cache layout
599
+ int64_t conv_state_slot_stride = conv_states->stride(0);
600
+
601
// block size for sequence blocks, 32
602
constexpr int64_t BLOCK_M = block_size_m();
603
@@ -618,7 +624,8 @@ at::Tensor causal_conv1d_fwd_cpu(
624
batch,
625
dim,
626
width,
621
- num_seq_blocks);
627
+ num_seq_blocks,
628
+ conv_state_slot_stride);
629
} else {
630
causal_conv1d_fwd_kernel_impl<scalar_t>(
631
out.data_ptr<scalar_t>(),
@@ -633,7 +640,8 @@ at::Tensor causal_conv1d_fwd_cpu(
640
dim,
641
seqlen,
642
width,
636
- num_seq_blocks);
643
+ num_seq_blocks,
644
+ conv_state_slot_stride);
645
}
646
});
647
return out;
@@ -690,6 +698,8 @@ at::Tensor causal_conv1d_update_cpu(
698
conv_states.copy_(conv_states_copy);
699
}
700
701
+ // IMPORTANT: To make the kernal compatible with vLLM KV cache layout
702
+ int64_t conv_state_slot_stride = conv_states.stride(0);
703
at::Tensor out = at::empty_like(x);
704
AT_DISPATCH_REDUCED_FLOATING_TYPES(scalar_type, "causal_conv1d_update_kernel_impl", [&] {
705
causal_conv1d_update_kernel_impl<scalar_t>(
@@ -703,7 +713,8 @@ at::Tensor causal_conv1d_update_cpu(
713
batch,
714
dim,
715
seqlen,
706
- width);
716
+ width,
717
+ conv_state_slot_stride);
718
});
719
return out;
720
}
csrc/cpu/sgl-kernels/fla.cpp
+5
-1
@@ -847,6 +847,7 @@ void fused_sigmoid_gating_delta_rule_update_kernel_impl(
847
int64_t v_strideB,
848
int64_t v_strideS,
849
int64_t v_strideH,
850
+ int64_t state_slot_stride,
851
bool use_qk_l2norm_in_kernel,
852
double softplus_threshold) {
853
using bVec = at::vec::Vectorized<scalar_t>;
@@ -907,7 +908,7 @@ void fused_sigmoid_gating_delta_rule_update_kernel_impl(
908
data_index_init(begin, bi, batch_size, si, seq_len, ni, v_num_heads);
909
for (int64_t i = begin; i < end; ++i) {
910
int64_t cache_index = indices_ptr[bi];
910
- int64_t state_offset = (cache_index * v_num_heads + ni) * head_dim * v_head_dim;
911
+ int64_t state_offset = cache_index * state_slot_stride + ni * head_dim * v_head_dim;
912
float g_val = -std::exp(float(A_log_ptr[ni])) *
913
softplus(float(a_ptr[bi * v_num_heads + ni]) + float(dt_bias_ptr[ni]), softplus_threshold);
914
float g_val_exp = std::exp(g_val);
@@ -1321,6 +1322,8 @@ at::Tensor fused_sigmoid_gating_delta_rule_update_cpu(
1322
int64_t v_strideB = v.stride(1);
1323
int64_t v_strideS = v.stride(0);
1324
int64_t v_strideH = v.stride(2);
1325
+ // IMPORTANT: To make the kernal compatible with vLLM KV cache layout
1326
+ int64_t state_slot_stride = initial_state_source.stride(0);
1327
at::Tensor core_attn_out = at::empty({batch_size, seq_len, v_num_heads, v_head_dim}, q.options());
1328
at::Tensor qk_scale_buf = at::empty({2 * batch_size, seq_len, num_heads}, at::kFloat);
1329
@@ -1353,6 +1356,7 @@ at::Tensor fused_sigmoid_gating_delta_rule_update_cpu(
1356
v_strideB,
1357
v_strideS,
1358
v_strideH,
1359
+ state_slot_stride,
1360
use_qk_l2norm_in_kernel,
1361
softplus_threshold);
1362
});
csrc/cpu/torch_bindings.cpp
+80
@@ -98,6 +98,45 @@ at::Tensor int4_scaled_mm_cpu(at::Tensor& x, at::Tensor& w, at::Tensor& w_zeros,
98
at::Tensor& w_scales,
99
std::optional<at::Tensor> bias);
100
101
+// Adapted from sglang: GDN
102
+std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_cpu(
103
+ const at::Tensor& query, const at::Tensor& key, const at::Tensor& value,
104
+ const at::Tensor& g, const at::Tensor& beta,
105
+ const at::Tensor& initial_state, bool output_final_state,
106
+ const at::Tensor& cu_seqlens, bool head_first, bool use_qk_l2norm_in_kernel,
107
+ double eps = 1e-5);
108
+
109
+at::Tensor fused_sigmoid_gating_delta_rule_update_cpu(
110
+ const at::Tensor& A_log, const at::Tensor& dt_bias, const at::Tensor& q,
111
+ const at::Tensor& k, const at::Tensor& v, const at::Tensor& a,
112
+ const at::Tensor& b, at::Tensor& initial_state_source,
113
+ const at::Tensor& initial_state_indices, const at::Tensor& cu_seqlens,
114
+ bool use_qk_l2norm_in_kernel, double softplus_beta = 1.0,
115
+ double softplus_threshold = 20.0);
116
+
117
+std::tuple<at::Tensor, at::Tensor> fused_gdn_gating_cpu(
118
+ const at::Tensor& A_log, const at::Tensor& a, const at::Tensor& b,
119
+ const at::Tensor& dt_bias);
120
+
121
+// Adapted from sglang: casual_conv1d kernels
122
+at::Tensor causal_conv1d_weight_pack(const at::Tensor& weight);
123
+
124
+at::Tensor causal_conv1d_fwd_cpu(
125
+ const at::Tensor& x, const at::Tensor& weight,
126
+ const std::optional<at::Tensor>& bias,
127
+ const std::optional<at::Tensor>& conv_states,
128
+ const std::optional<at::Tensor>& query_start_loc,
129
+ const std::optional<at::Tensor>& cache_indices,
130
+ const std::optional<at::Tensor>& has_initial_state, bool silu_activation,
131
+ int64_t pad_slot_id, bool is_vnni);
132
+
133
+at::Tensor causal_conv1d_update_cpu(
134
+ const at::Tensor& x, const at::Tensor& conv_states,
135
+ const at::Tensor& weight, const std::optional<at::Tensor>& bias,
136
+ bool silu_activation, const std::optional<at::Tensor>& cache_seqlens,
137
+ const std::optional<at::Tensor>& conv_state_indices, int64_t pad_slot_id,
138
+ bool is_vnni);
139
+
140
void activation_lut_bf16(torch::Tensor& out, torch::Tensor& input,
141
const std::string& activation);
142
@@ -397,6 +436,47 @@ TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
436
"scales2, SymInt[] block_size, Tensor? bias, ScalarType out_dtype, "
437
"bool is_vnni) -> Tensor");
438
ops.impl("fp8_scaled_mm_cpu", torch::kCPU, &fp8_scaled_mm_cpu);
439
+
440
+ // Adapted from sglang: GDN kernels
441
+ ops.def(
442
+ "chunk_gated_delta_rule_cpu(Tensor query, Tensor key, Tensor value, "
443
+ "Tensor g, Tensor beta, "
444
+ "Tensor initial_state, bool output_final_state, Tensor cu_seqlens, bool "
445
+ "head_first, "
446
+ "bool use_qk_l2norm_in_kernel, float eps=1e-5) -> (Tensor, Tensor)");
447
+ ops.impl("chunk_gated_delta_rule_cpu", torch::kCPU,
448
+ &chunk_gated_delta_rule_cpu);
449
+ ops.def(
450
+ "fused_sigmoid_gating_delta_rule_update_cpu(Tensor A_log, Tensor "
451
+ "dt_bias, Tensor q, Tensor k, Tensor v, Tensor "
452
+ "a, Tensor b, Tensor(a!) initial_state_source, Tensor "
453
+ "initial_state_indices, Tensor cu_seqlens, bool "
454
+ "use_qk_l2norm_in_kernel, float softplus_beta=1.0, float "
455
+ "softplus_threshold=20.0) -> Tensor");
456
+ ops.impl("fused_sigmoid_gating_delta_rule_update_cpu", torch::kCPU,
457
+ &fused_sigmoid_gating_delta_rule_update_cpu);
458
+ ops.def(
459
+ "fused_gdn_gating_cpu(Tensor A_log, Tensor a, Tensor b, Tensor dt_bias) "
460
+ "-> (Tensor, Tensor)");
461
+ ops.impl("fused_gdn_gating_cpu", torch::kCPU, &fused_gdn_gating_cpu);
462
+
463
+ // Adapted from sglang: casual_conv1d kernels
464
+ ops.def("causal_conv1d_weight_pack(Tensor weight) -> Tensor");
465
+ ops.impl("causal_conv1d_weight_pack", torch::kCPU,
466
+ &causal_conv1d_weight_pack);
467
+ ops.def(
468
+ "causal_conv1d_fwd_cpu(Tensor x, Tensor weight, Tensor? bias, Tensor? "
469
+ "conv_states, Tensor? query_start_loc,"
470
+ "Tensor? cache_indices, Tensor? has_initial_state, bool silu_activation, "
471
+ "int pad_slot_id, bool is_vnni) -> "
472
+ "Tensor");
473
+ ops.impl("causal_conv1d_fwd_cpu", torch::kCPU, &causal_conv1d_fwd_cpu);
474
+ ops.def(
475
+ "causal_conv1d_update_cpu(Tensor x, Tensor(a!) conv_states, Tensor "
476
+ "weight, Tensor? bias, bool silu_activation,"
477
+ "Tensor? cache_seqlens, Tensor? conv_state_indices, int pad_slot_id, "
478
+ "bool is_vnni) -> Tensor");
479
+ ops.impl("causal_conv1d_update_cpu", torch::kCPU, &causal_conv1d_update_cpu);
480
#endif
481
482
// CPU attention kernels
vllm/_custom_ops.py
+129
@@ -3370,6 +3370,135 @@ def fp8_scaled_mm_cpu(
3370
)
3371
3372
3373
+def chunk_gated_delta_rule_cpu(
3374
+ query: torch.Tensor,
3375
+ key: torch.Tensor,
3376
+ value: torch.Tensor,
3377
+ g: torch.Tensor,
3378
+ beta: torch.Tensor,
3379
+ initial_state: torch.Tensor,
3380
+ output_final_state: bool,
3381
+ cu_seqlens: torch.Tensor,
3382
+ head_first: bool,
3383
+ use_qk_l2norm_in_kernel: bool,
3384
+ eps: float = 1e-5,
3385
+) -> tuple[torch.Tensor, torch.Tensor]:
3386
+ return torch.ops._C.chunk_gated_delta_rule_cpu(
3387
+ query,
3388
+ key,
3389
+ value,
3390
+ g,
3391
+ beta,
3392
+ initial_state,
3393
+ output_final_state,
3394
+ cu_seqlens,
3395
+ head_first,
3396
+ use_qk_l2norm_in_kernel,
3397
+ eps,
3398
+ )
3399
+
3400
+
3401
+def fused_sigmoid_gating_delta_rule_update_cpu(
3402
+ A_log: torch.Tensor,
3403
+ dt_bias: torch.Tensor,
3404
+ q: torch.Tensor,
3405
+ k: torch.Tensor,
3406
+ v: torch.Tensor,
3407
+ a: torch.Tensor,
3408
+ b: torch.Tensor,
3409
+ initial_state_source: torch.Tensor,
3410
+ initial_state_indices: torch.Tensor,
3411
+ cu_seqlens: torch.Tensor,
3412
+ use_qk_l2norm_in_kernel: bool,
3413
+ softplus_beta: float = 1.0,
3414
+ softplus_threshold: float = 20.0,
3415
+) -> torch.Tensor:
3416
+ return torch.ops._C.fused_sigmoid_gating_delta_rule_update_cpu(
3417
+ A_log,
3418
+ dt_bias,
3419
+ q,
3420
+ k,
3421
+ v,
3422
+ a,
3423
+ b,
3424
+ initial_state_source,
3425
+ initial_state_indices,
3426
+ cu_seqlens,
3427
+ use_qk_l2norm_in_kernel,
3428
+ softplus_beta,
3429
+ softplus_threshold,
3430
+ )
3431
+
3432
+
3433
+def fused_gdn_gating_cpu(
3434
+ A_log: torch.Tensor,
3435
+ a: torch.Tensor,
3436
+ b: torch.Tensor,
3437
+ dt_bias: torch.Tensor,
3438
+) -> tuple[torch.Tensor, torch.Tensor]:
3439
+ return torch.ops._C.fused_gdn_gating_cpu(
3440
+ A_log,
3441
+ a,
3442
+ b,
3443
+ dt_bias,
3444
+ )
3445
+
3446
+
3447
+def causal_conv1d_weight_pack(
3448
+ weight: torch.Tensor,
3449
+) -> torch.Tensor:
3450
+ return torch.ops._C.causal_conv1d_weight_pack(
3451
+ weight,
3452
+ )
3453
+
3454
+
3455
+def causal_conv1d_fwd_cpu(
3456
+ x: torch.Tensor,
3457
+ weight: torch.Tensor,
3458
+ bias: torch.Tensor | None,
3459
+ conv_states: torch.Tensor | None,
3460
+ query_start_loc: torch.Tensor | None,
3461
+ cache_indices: torch.Tensor | None,
3462
+ has_initial_state: torch.Tensor | None,
3463
+ silu_activation: bool,
3464
+ is_vnni: bool,
3465
+) -> torch.Tensor:
3466
+ return torch.ops._C.causal_conv1d_fwd_cpu(
3467
+ x,
3468
+ weight,
3469
+ bias,
3470
+ conv_states,
3471
+ query_start_loc,
3472
+ cache_indices,
3473
+ has_initial_state,
3474
+ silu_activation,
3475
+ -1,
3476
+ is_vnni,
3477
+ )
3478
+
3479
+
3480
+def causal_conv1d_update_cpu(
3481
+ x: torch.Tensor,
3482
+ conv_states: torch.Tensor,
3483
+ weight: torch.Tensor,
3484
+ bias: torch.Tensor | None,
3485
+ silu_activation: bool,
3486
+ conv_state_indices: torch.Tensor | None,
3487
+ is_vnni: bool,
3488
+) -> torch.Tensor:
3489
+ return torch.ops._C.causal_conv1d_update_cpu(
3490
+ x,
3491
+ conv_states,
3492
+ weight,
3493
+ bias,
3494
+ silu_activation,
3495
+ None,
3496
+ conv_state_indices,
3497
+ -1,
3498
+ is_vnni,
3499
+ )
3500
+
3501
+
3502
class CPUDNNLGEMMHandler:
3503
def __init__(self) -> None:
3504
self.handler_tensor: torch.Tensor | None = None
vllm/model_executor/layers/linear.py
-3
@@ -213,9 +213,6 @@ class UnquantizedLinearMethod(LinearMethodBase):
213
214
def process_weights_after_loading(self, layer: torch.nn.Module) -> None:
215
if current_platform.is_cpu():
216
- if layer.weight.ndim != 2:
217
- # this is not a linear layer
218
- return
216
from vllm.model_executor.layers.utils import dispatch_cpu_unquantized_gemm
217
218
dispatch_cpu_unquantized_gemm(layer, remove_weight=True)
vllm/model_executor/layers/mamba/ops/cpu/gdn_attention.py
+140
@@ -5,6 +5,7 @@ from __future__ import annotations
5
6
import torch
7
8
+import vllm._custom_ops as ops
9
from vllm.forward_context import ForwardContext, get_forward_context
10
from vllm.model_executor.layers.mamba.mamba_utils import is_conv_state_dim_first
11
from vllm.model_executor.layers.mamba.ops.cpu.causal_conv1d import (
@@ -55,6 +56,16 @@ def cpu_gdn_attention_core(
56
and attn_metadata_i.num_accepted_tokens is None
57
), "speculative decode not supported in CPU GDN attention."
58
59
+ if torch.cpu._is_amx_tile_supported():
60
+ return cpu_gdn_attention_core_amx(
61
+ mixed_qkv,
62
+ b,
63
+ a,
64
+ core_attn_out,
65
+ attn_metadata_i,
66
+ layer,
67
+ )
68
+
69
state_indices_tensor = attn_metadata_i.non_spec_state_indices_tensor
70
query_start_loc = attn_metadata_i.non_spec_query_start_loc
71
assert state_indices_tensor is not None
@@ -194,6 +205,135 @@ def cpu_gdn_attention_core_fake(
205
return
206
207
208
+def cpu_gdn_attention_core_amx(
209
+ mixed_qkv: torch.Tensor,
210
+ b: torch.Tensor,
211
+ a: torch.Tensor,
212
+ core_attn_out: torch.Tensor,
213
+ attn_metadata_i: GDNAttentionMetadata,
214
+ layer: torch.nn.Module,
215
+):
216
+ state_indices_tensor = attn_metadata_i.non_spec_state_indices_tensor
217
+ query_start_loc = attn_metadata_i.non_spec_query_start_loc
218
+ assert state_indices_tensor is not None
219
+ assert query_start_loc is not None
220
+
221
+ # [num_allocated_slots, kernel - 1, conv_dim]
222
+ conv_state = layer.kv_cache[0]
223
+ if is_conv_state_dim_first():
224
+ raise RuntimeError("AMX GDN attention requires `SD` conv_state layout.")
225
+ # reshape to [num_allocated_slots, conv_dim, kernel - 1]
226
+ conv_state_t = conv_state.transpose(1, 2)
227
+
228
+ # [num_allocated_slots, num_v_heads / tp_size, v_dim, k_dim]
229
+ ssm_state = layer.kv_cache[1]
230
+ # rehape to [num_allocated_slots, num_v_heads / tp_size, k_dim, v_dim]
231
+ num_allocated_slots, head_num, v_dim, k_dim = ssm_state.size()
232
+ ssm_state = ssm_state.view(
233
+ num_allocated_slots,
234
+ head_num,
235
+ k_dim,
236
+ v_dim,
237
+ )
238
+
239
+ mixed_qkv = mixed_qkv.contiguous()
240
+ a = a.contiguous()
241
+ b = b.contiguous()
242
+
243
+ num_decodes = attn_metadata_i.num_decodes
244
+ num_decode_tokens = attn_metadata_i.num_decode_tokens
245
+ num_prefills = attn_metadata_i.num_prefills
246
+ num_prefill_tokens = attn_metadata_i.num_prefill_tokens
247
+
248
+ if num_decodes > 0:
249
+ decode_mixed_qkv = mixed_qkv[:num_decode_tokens]
250
+ decode_b = b[:num_decode_tokens]
251
+ decode_a = a[:num_decode_tokens]
252
+ decode_state_indices = state_indices_tensor[:num_decodes]
253
+
254
+ decode_mixed_qkv = ops.causal_conv1d_update_cpu(
255
+ x=decode_mixed_qkv,
256
+ conv_states=conv_state_t,
257
+ weight=layer.conv1d.weight,
258
+ bias=layer.conv1d.bias,
259
+ silu_activation=layer.activation == "silu",
260
+ conv_state_indices=decode_state_indices,
261
+ is_vnni=True,
262
+ )
263
+
264
+ query, key, value = layer.rearrange_mixed_qkv(decode_mixed_qkv)
265
+ attn_out = ops.fused_sigmoid_gating_delta_rule_update_cpu(
266
+ A_log=layer.A_log,
267
+ dt_bias=layer.dt_bias,
268
+ q=query,
269
+ k=key,
270
+ v=value,
271
+ a=decode_a,
272
+ b=decode_b,
273
+ initial_state_source=ssm_state,
274
+ initial_state_indices=decode_state_indices,
275
+ cu_seqlens=query_start_loc[: num_decodes + 1],
276
+ use_qk_l2norm_in_kernel=True,
277
+ )
278
+ core_attn_out[:num_decode_tokens] = attn_out.squeeze(1)
279
+
280
+ if num_prefills > 0:
281
+ has_initial_state = attn_metadata_i.has_initial_state
282
+ assert has_initial_state is not None
283
+
284
+ prefill_token_start = num_decode_tokens
285
+ prefill_token_end = prefill_token_start + num_prefill_tokens
286
+ prefill_mixed_qkv = mixed_qkv[prefill_token_start:prefill_token_end]
287
+ prefill_b = b[prefill_token_start:prefill_token_end]
288
+ prefill_a = a[prefill_token_start:prefill_token_end]
289
+ prefill_state_indices = state_indices_tensor[
290
+ num_decodes : num_decodes + num_prefills
291
+ ]
292
+ prefill_query_start_loc = (
293
+ query_start_loc[num_decodes : num_decodes + num_prefills + 1]
294
+ - num_decode_tokens
295
+ )
296
+ prefill_has_initial_state = has_initial_state[
297
+ num_decodes : num_decodes + num_prefills
298
+ ]
299
+
300
+ prefill_mixed_qkv = ops.causal_conv1d_fwd_cpu(
301
+ x=prefill_mixed_qkv.transpose(0, 1),
302
+ weight=layer.conv1d.weight,
303
+ bias=layer.conv1d.bias,
304
+ conv_states=conv_state_t,
305
+ query_start_loc=prefill_query_start_loc,
306
+ cache_indices=prefill_state_indices,
307
+ has_initial_state=prefill_has_initial_state,
308
+ silu_activation=layer.activation == "silu",
309
+ is_vnni=True,
310
+ ).transpose(0, 1)
311
+
312
+ query, key, value = layer.rearrange_mixed_qkv(prefill_mixed_qkv)
313
+ g, beta = ops.fused_gdn_gating_cpu(
314
+ A_log=layer.A_log, a=prefill_a, b=prefill_b, dt_bias=layer.dt_bias
315
+ )
316
+
317
+ initial_state = ssm_state[prefill_state_indices]
318
+ initial_state[~prefill_has_initial_state, ...] = 0
319
+ attn_out, last_recurrent_state = ops.chunk_gated_delta_rule_cpu(
320
+ query=query,
321
+ key=key,
322
+ value=value,
323
+ g=g,
324
+ beta=beta,
325
+ initial_state=initial_state,
326
+ output_final_state=True,
327
+ cu_seqlens=prefill_query_start_loc,
328
+ head_first=False,
329
+ use_qk_l2norm_in_kernel=True,
330
+ )
331
+ ssm_state[prefill_state_indices] = last_recurrent_state.to(
332
+ ssm_state.dtype, copy=False
333
+ )
334
+ core_attn_out[prefill_token_start:prefill_token_end] = attn_out.squeeze(0)
335
+
336
+
337
def register_cpu_gdn_attention_ops() -> None:
338
global _CPU_GDN_ATTENTION_OPS_REGISTERED
339
if _CPU_GDN_ATTENTION_OPS_REGISTERED:
vllm/model_executor/layers/utils.py
+13
@@ -233,6 +233,19 @@ def dispatch_cpu_unquantized_gemm(
233
layer.cpu_linear = torch.nn.functional.linear
234
return
235
236
+ if layer.weight.ndim != 2:
237
+ # this is not a linear layer
238
+ # For now it should be a causal_conv1d op
239
+ if torch.cpu._is_amx_tile_supported():
240
+ # prepack conv weight
241
+ layer.weight.data = ops.causal_conv1d_weight_pack(
242
+ layer.weight.view(
243
+ layer.weight.size(0),
244
+ layer.weight.size(2),
245
+ )
246
+ )
247
+ return
248
+
249
N, K = layer.weight.size()
250
dtype = layer.weight.dtype
251
vllm/platforms/cpu.py
+19
@@ -139,6 +139,7 @@ class CpuPlatform(Platform):
139
# supported as it is not possible to set the OMP environment correctly
140
if parallel_config.distributed_executor_backend == "uni":
141
parallel_config.distributed_executor_backend = "mp"
142
+
143
if parallel_config.worker_cls == "auto":
144
parallel_config.worker_cls = "vllm.v1.worker.cpu_worker.CPUWorker"
145
# Disable DBO
@@ -146,6 +147,20 @@ class CpuPlatform(Platform):
147
logger.warning("Dual-Batch Overlap is not supported on CPU, disabled.")
148
parallel_config.enable_dbo = False
149
150
+ if torch.cpu._is_amx_tile_supported() and (
151
+ model_config is not None
152
+ and model_config.get_num_layers_by_block_type(
153
+ parallel_config, "linear_attention"
154
+ )
155
+ > 0
156
+ ):
157
+ cache_config.enable_prefix_caching = False
158
+ scheduler_config.enable_chunked_prefill = False
159
+ logger.warning(
160
+ "Disabled unsupported prefix caching and chunked prefill "
161
+ "for linear attention on AMX CPU platforms."
162
+ )
163
+
164
# Note: workaround for v1 gpu_model_runner
165
from vllm.config import CompilationMode
166
@@ -211,6 +226,10 @@ class CpuPlatform(Platform):
226
# Avoid inductor generates num_thread() and breaks the thread binding
227
os.environ["TORCHINDUCTOR_CPP_DYNAMIC_THREADS"] = "1"
228
229
+ # For efficient conv state memory access
230
+ if torch.cpu._is_amx_tile_supported():
231
+ os.environ["VLLM_SSM_CONV_STATE_LAYOUT"] = "SD"
232
+
233
ld_preload_str = os.getenv("LD_PRELOAD", "")
234
cpu_architecture = Platform.get_cpu_architecture()
235