@@ -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//
663754at::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