Skip to content

Commit 37f8bf6

Browse files
committed
[CPU][Spec Decode] Optimize GDN conv path for speculative decoding
Co-authored-by: Codex <codex@openai.com> Signed-off-by: Li, Tianmu <tianmu.li@intel.com>
1 parent 93e3bc8 commit 37f8bf6

4 files changed

Lines changed: 499 additions & 72 deletions

File tree

csrc/cpu/sgl-kernels/conv.cpp

Lines changed: 175 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,94 @@ void causal_conv1d_update_kernel_impl(
451451
});
452452
}
453453

454+
template <typename scalar_t>
455+
void causal_conv1d_update_multi_kernel_impl(
456+
scalar_t* __restrict__ out,
457+
const scalar_t* __restrict__ input,
458+
scalar_t* __restrict__ conv_states,
459+
const scalar_t* __restrict__ weight,
460+
const scalar_t* __restrict__ bias,
461+
const int32_t* __restrict__ history_offsets,
462+
const int32_t* __restrict__ conv_indices,
463+
bool silu_activation,
464+
int64_t batch,
465+
int64_t dim,
466+
int64_t seqlen,
467+
int64_t width,
468+
int64_t state_len,
469+
int64_t conv_state_slot_stride) {
470+
constexpr int64_t BLOCK_N = block_size_n() * 2;
471+
const int64_t NB = div_up(dim, BLOCK_N);
472+
473+
AT_DISPATCH_BOOL2(bias != nullptr, has_bias, silu_activation, has_silu, [&] {
474+
at::parallel_for(0, batch * NB, 0, [&](int64_t begin, int64_t end) {
475+
int64_t bs{0}, nb{0};
476+
data_index_init(begin, bs, batch, nb, NB);
477+
478+
for (int64_t i = begin; i < end; ++i) {
479+
const int64_t nb_start = nb * BLOCK_N;
480+
const int64_t nb_size = std::min(dim - nb_start, BLOCK_N);
481+
const int32_t conv_state_index = conv_indices[bs];
482+
const int32_t history_offset = history_offsets[bs];
483+
484+
switch (width << 4 | nb_size >> 4) {
485+
case 0x42:
486+
tinygemm_kernel<scalar_t, 4, 32, has_bias, has_silu>::apply(
487+
input + bs * seqlen * dim + nb_start,
488+
weight + nb_start * width,
489+
out + bs * seqlen * dim + nb_start,
490+
has_bias ? bias + nb_start : nullptr,
491+
conv_states + conv_state_index * conv_state_slot_stride +
492+
history_offset * dim + nb_start,
493+
true,
494+
seqlen,
495+
dim,
496+
true);
497+
break;
498+
case 0x44:
499+
tinygemm_kernel<scalar_t, 4, 64, has_bias, has_silu>::apply(
500+
input + bs * seqlen * dim + nb_start,
501+
weight + nb_start * width,
502+
out + bs * seqlen * dim + nb_start,
503+
has_bias ? bias + nb_start : nullptr,
504+
conv_states + conv_state_index * conv_state_slot_stride +
505+
history_offset * dim + nb_start,
506+
true,
507+
seqlen,
508+
dim,
509+
true);
510+
break;
511+
default:
512+
TORCH_CHECK(false, "Unexpected block size, ", width, " x ", nb_size);
513+
}
514+
515+
data_index_step(bs, batch, nb, NB);
516+
}
517+
});
518+
});
519+
520+
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
521+
for (int64_t bs = begin; bs < end; ++bs) {
522+
const int32_t conv_state_index = conv_indices[bs];
523+
const int32_t history_offset = history_offsets[bs];
524+
scalar_t* state = conv_states + conv_state_index * conv_state_slot_stride;
525+
526+
for (int64_t w = 0; w < state_len - seqlen; ++w) {
527+
std::memmove(
528+
state + w * dim,
529+
state + (history_offset + 1 + w) * dim,
530+
dim * sizeof(scalar_t));
531+
}
532+
for (int64_t s = 0; s < seqlen; ++s) {
533+
std::memcpy(
534+
state + (state_len - seqlen + s) * dim,
535+
input + (bs * seqlen + s) * dim,
536+
dim * sizeof(scalar_t));
537+
}
538+
}
539+
});
540+
}
541+
454542
} // anonymous namespace
455543

456544
// from [dim, width] or [N, K]
@@ -545,7 +633,7 @@ at::Tensor get_block_indices(const std::optional<at::Tensor>& offsets, int64_t n
545633
// query_start_loc: (batch + 1) int32
546634
// cache_indices: (batch) int32
547635
// has_initial_state: (batch) bool
548-
// conv_states: (..., dim, width - 1) itype
636+
// conv_states: (..., dim, state_len) itype, where state_len >= width - 1
549637
// activation: either None or "silu" or "swish"
550638
// pad_slot_id: int
551639
//
@@ -586,11 +674,14 @@ at::Tensor causal_conv1d_fwd_cpu(
586674
CHECK_EQ(conv_states_val.scalar_type(), scalar_type);
587675
CHECK_GE(padded_batch, batch);
588676
CHECK_EQ(conv_states_val.size(1), dim);
589-
CHECK_EQ(conv_states_val.size(2), width - 1);
677+
const int64_t state_len = conv_states_val.size(2);
678+
CHECK_GE(state_len, width - 1);
590679

591680
// adjust `conv_states` to be contiguous on `dim`
592681
// should happen only once
593682
if (conv_states_val.stride(-2) != 1) {
683+
TORCH_CHECK(state_len == width - 1,
684+
"causal_conv1d_fwd_cpu: wide conv_states must be contiguous on dim.");
594685
auto conv_states_copy = conv_states_val.clone();
595686
conv_states_val.as_strided_({padded_batch, dim, width - 1}, {(width - 1) * dim, 1, dim});
596687
conv_states_val.copy_(conv_states_copy);
@@ -651,14 +742,14 @@ at::Tensor causal_conv1d_fwd_cpu(
651742

652743
// API aligned with GPUs
653744
//
654-
// x: (batch, dim) or (batch, dim, seqlen)
745+
// x: (batch, dim) or (batch, seqlen, dim)
655746
// conv_state: (..., dim, state_len), where state_len >= width - 1
656747
// weight: (dim, width)
657748
// bias: (dim,)
658749
// cache_seqlens: (batch,), dtype int32.
659750
// conv_state_indices: (batch,), dtype int32
660751
// pad_slot_id: int
661-
// out: (batch, dim) or (batch, dim, seqlen)
752+
// out: (batch, dim) or (batch, seqlen, dim)
662753
//
663754
at::Tensor causal_conv1d_update_cpu(
664755
const at::Tensor& x,
@@ -674,13 +765,13 @@ at::Tensor causal_conv1d_update_cpu(
674765
CHECK_CONTIGUOUS(weight);
675766
auto packed_w = is_vnni ? weight : causal_conv1d_weight_pack(weight);
676767

677-
// TODO: add multi-token prediction support
678-
TORCH_CHECK(x.dim() == 2, "causal_conv1d_update_cpu: expect x to be 2D tensor.");
679-
TORCH_CHECK(!cache_seqlens.has_value(), "causal_conv1d_update_cpu: don't support cache_seqlens.");
768+
TORCH_CHECK(
769+
x.dim() == 2 || x.dim() == 3,
770+
"causal_conv1d_update_cpu: expect x to be 2D or 3D tensor.");
680771

681772
int64_t batch = x.size(0);
682-
int64_t dim = x.size(1);
683-
int64_t seqlen = 1;
773+
int64_t dim = x.dim() == 2 ? x.size(1) : x.size(2);
774+
int64_t seqlen = x.dim() == 2 ? 1 : x.size(1);
684775
int64_t width = weight.size(-1);
685776

686777
const auto scalar_type = x.scalar_type();
@@ -690,10 +781,84 @@ at::Tensor causal_conv1d_update_cpu(
690781

691782
CHECK_EQ(conv_states.scalar_type(), scalar_type);
692783
CHECK_EQ(conv_states.size(1), dim);
693-
CHECK_EQ(conv_states.size(2), width - 1);
784+
const int64_t state_len = conv_states.size(2);
785+
CHECK_GE(state_len, width - 1);
786+
787+
if (x.dim() == 3) {
788+
TORCH_CHECK(
789+
cache_seqlens.has_value(),
790+
"causal_conv1d_update_cpu: cache_seqlens is required for 3D x.");
791+
TORCH_CHECK(
792+
conv_state_indices.has_value(),
793+
"causal_conv1d_update_cpu: conv_state_indices is required for 3D x.");
794+
CHECK_OPTIONAL_SHAPE_DTYPE(cache_seqlens, batch, at::kInt);
795+
TORCH_CHECK(
796+
width == 4,
797+
"causal_conv1d_update_cpu: support only width of 4 for 3D x.");
798+
TORCH_CHECK(
799+
seqlen > 0,
800+
"causal_conv1d_update_cpu: expect non-empty sequence for 3D x.");
801+
TORCH_CHECK(
802+
state_len >= seqlen,
803+
"causal_conv1d_update_cpu: state_len must be >= seqlen for 3D x.");
804+
TORCH_CHECK(
805+
conv_states.stride(-2) == 1 && conv_states.stride(-1) == dim,
806+
"causal_conv1d_update_cpu: 3D x requires SD conv_states layout.");
807+
808+
const int32_t* history_offsets = cache_seqlens.value().data_ptr<int32_t>();
809+
const int32_t* indices = conv_state_indices.value().data_ptr<int32_t>();
810+
const int64_t num_slots = conv_states.size(0);
811+
for (int64_t bs = 0; bs < batch; ++bs) {
812+
const int32_t history_offset = history_offsets[bs];
813+
const int32_t conv_state_index = indices[bs];
814+
TORCH_CHECK(
815+
conv_state_index != pad_slot_id,
816+
"causal_conv1d_update_cpu: 3D x does not support pad slots.");
817+
TORCH_CHECK(
818+
conv_state_index >= 0 && conv_state_index < num_slots,
819+
"causal_conv1d_update_cpu: conv_state_indices out of range.");
820+
TORCH_CHECK(
821+
history_offset >= 0,
822+
"causal_conv1d_update_cpu: history_offset must be non-negative.");
823+
TORCH_CHECK(
824+
history_offset + width - 1 <= state_len,
825+
"causal_conv1d_update_cpu: history window exceeds conv_states.");
826+
TORCH_CHECK(
827+
history_offset + 1 + (state_len - seqlen) <= state_len,
828+
"causal_conv1d_update_cpu: rolled state window exceeds conv_states.");
829+
}
830+
831+
int64_t conv_state_slot_stride = conv_states.stride(0);
832+
at::Tensor out = at::empty_like(x);
833+
AT_DISPATCH_REDUCED_FLOATING_TYPES(
834+
scalar_type, "causal_conv1d_update_multi_kernel_impl", [&] {
835+
causal_conv1d_update_multi_kernel_impl<scalar_t>(
836+
out.data_ptr<scalar_t>(),
837+
x.data_ptr<scalar_t>(),
838+
conv_states.data_ptr<scalar_t>(),
839+
packed_w.data_ptr<scalar_t>(),
840+
conditional_data_ptr<scalar_t>(bias),
841+
history_offsets,
842+
indices,
843+
silu_activation,
844+
batch,
845+
dim,
846+
seqlen,
847+
width,
848+
state_len,
849+
conv_state_slot_stride);
850+
});
851+
return out;
852+
}
853+
854+
TORCH_CHECK(
855+
!cache_seqlens.has_value(),
856+
"causal_conv1d_update_cpu: cache_seqlens is only supported for 3D x.");
694857

695858
// adjust `conv_states` to be contiguous on `dim`
696859
if (conv_states.stride(-2) != 1) {
860+
TORCH_CHECK(state_len == width - 1,
861+
"causal_conv1d_update_cpu: wide conv_states must be contiguous on dim.");
697862
int64_t num_cache_lines = conv_states.size(0);
698863
auto conv_states_copy = conv_states.clone();
699864
conv_states.as_strided_({num_cache_lines, dim, width - 1}, {(width - 1) * dim, 1, dim});

0 commit comments

Comments
 (0)