1818#include < c10/util/irange.h>
1919#include < executorch/runtime/core/exec_aten/util/scalar_type_util.h>
2020#include < executorch/runtime/kernel/operator_registry.h>
21+ #include < executorch/runtime/platform/compiler.h>
2122#include < algorithm>
2223#include < cmath>
2324#include < memory>
2425#include < new>
2526#include < vector>
27+ #if defined(__APPLE__) && defined(__ARM_NEON)
28+ #include < arm_neon.h>
29+ #endif
2630
2731// parallel_for + get_thread_num have serial fallbacks without a threadpool.
2832#include < executorch/runtime/kernel/thread_parallel_interface.h>
@@ -698,6 +702,95 @@ Tensor& sdpa_with_kv_cache_out(
698702 return output;
699703}
700704
705+ ET_INLINE void vec_scale (float * data, float scale, int64_t size) {
706+ int64_t idx = 0 ;
707+ #if defined(__APPLE__) && defined(__ARM_NEON)
708+ constexpr int64_t vector_size = 4 ;
709+ for (; idx + 2 * vector_size <= size; idx += 2 * vector_size) {
710+ const float32x4_t data_vec0 = vld1q_f32 (data + idx);
711+ const float32x4_t data_vec1 = vld1q_f32 (data + idx + vector_size);
712+ vst1q_f32 (data + idx, vmulq_n_f32 (data_vec0, scale));
713+ vst1q_f32 (data + idx + vector_size, vmulq_n_f32 (data_vec1, scale));
714+ }
715+ for (; idx + vector_size <= size; idx += vector_size) {
716+ const float32x4_t data_vec = vld1q_f32 (data + idx);
717+ vst1q_f32 (data + idx, vmulq_n_f32 (data_vec, scale));
718+ }
719+ #endif
720+ for (; idx < size; ++idx) {
721+ data[idx] *= scale;
722+ }
723+ }
724+
725+ ET_INLINE void vec_axpy (float * y, const float * x, float alpha, int64_t size) {
726+ int64_t idx = 0 ;
727+ #if defined(__APPLE__) && defined(__ARM_NEON)
728+ constexpr int64_t vector_size = 4 ;
729+ for (; idx + 2 * vector_size <= size; idx += 2 * vector_size) {
730+ const float32x4_t x_vec0 = vld1q_f32 (x + idx);
731+ const float32x4_t x_vec1 = vld1q_f32 (x + idx + vector_size);
732+ const float32x4_t y_vec0 = vld1q_f32 (y + idx);
733+ const float32x4_t y_vec1 = vld1q_f32 (y + idx + vector_size);
734+ vst1q_f32 (y + idx, vmlaq_n_f32 (y_vec0, x_vec0, alpha));
735+ vst1q_f32 (y + idx + vector_size, vmlaq_n_f32 (y_vec1, x_vec1, alpha));
736+ }
737+ for (; idx + vector_size <= size; idx += vector_size) {
738+ const float32x4_t x_vec = vld1q_f32 (x + idx);
739+ const float32x4_t y_vec = vld1q_f32 (y + idx);
740+ vst1q_f32 (y + idx, vmlaq_n_f32 (y_vec, x_vec, alpha));
741+ }
742+ #endif
743+ for (; idx < size; ++idx) {
744+ y[idx] += x[idx] * alpha;
745+ }
746+ }
747+
748+ ET_INLINE void vec_state_update_and_output (
749+ float * state,
750+ const float * delta,
751+ float * output,
752+ float decay,
753+ float key,
754+ float query,
755+ int64_t size) {
756+ int64_t idx = 0 ;
757+ #if defined(__APPLE__) && defined(__ARM_NEON)
758+ constexpr int64_t vector_size = 4 ;
759+ for (; idx + 2 * vector_size <= size; idx += 2 * vector_size) {
760+ const float32x4_t state_vec0 = vld1q_f32 (state + idx);
761+ const float32x4_t state_vec1 = vld1q_f32 (state + idx + vector_size);
762+ const float32x4_t delta_vec0 = vld1q_f32 (delta + idx);
763+ const float32x4_t delta_vec1 = vld1q_f32 (delta + idx + vector_size);
764+ const float32x4_t updated_vec0 =
765+ vmlaq_n_f32 (vmulq_n_f32 (delta_vec0, key), state_vec0, decay);
766+ const float32x4_t updated_vec1 =
767+ vmlaq_n_f32 (vmulq_n_f32 (delta_vec1, key), state_vec1, decay);
768+ const float32x4_t output_vec0 = vld1q_f32 (output + idx);
769+ const float32x4_t output_vec1 = vld1q_f32 (output + idx + vector_size);
770+ vst1q_f32 (state + idx, updated_vec0);
771+ vst1q_f32 (state + idx + vector_size, updated_vec1);
772+ vst1q_f32 (output + idx, vmlaq_n_f32 (output_vec0, updated_vec0, query));
773+ vst1q_f32 (
774+ output + idx + vector_size,
775+ vmlaq_n_f32 (output_vec1, updated_vec1, query));
776+ }
777+ for (; idx + vector_size <= size; idx += vector_size) {
778+ const float32x4_t state_vec = vld1q_f32 (state + idx);
779+ const float32x4_t delta_vec = vld1q_f32 (delta + idx);
780+ const float32x4_t updated_vec =
781+ vmlaq_n_f32 (vmulq_n_f32 (delta_vec, key), state_vec, decay);
782+ const float32x4_t output_vec = vld1q_f32 (output + idx);
783+ vst1q_f32 (state + idx, updated_vec);
784+ vst1q_f32 (output + idx, vmlaq_n_f32 (output_vec, updated_vec, query));
785+ }
786+ #endif
787+ for (; idx < size; ++idx) {
788+ const float updated = state[idx] * decay + key * delta[idx];
789+ state[idx] = updated;
790+ output[idx] += updated * query;
791+ }
792+ }
793+
701794// Shared full-sequence recurrence: streams the K x V state twice per token (see
702795// the two-pass comments inline). Used by the T==1 decode path and as the exact
703796// prefill fallback when decay contains non-positive entries -- the chunked
@@ -810,9 +903,7 @@ void channelwise_gated_delta_rule_recurrence(
810903 const float decay_value = decay_t [k_idx];
811904 const float key_value = k_t [k_idx];
812905 const auto * state_row = state_head + k_idx * v_head_dim;
813- for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
814- v_pred[v_idx] += (state_row[v_idx] * decay_value) * key_value;
815- }
906+ vec_axpy (v_pred, state_row, decay_value * key_value, v_head_dim);
816907 }
817908
818909 // delta = beta * (v - v_pred).
@@ -826,16 +917,18 @@ void channelwise_gated_delta_rule_recurrence(
826917 auto * output_t = output_group + token * value_seq_stride;
827918 std::fill (output_t , output_t + v_head_dim, 0 .0f );
828919 for (int64_t k_idx = 0 ; k_idx < k_head_dim; ++k_idx) {
829- const float updated_decay = decay_t [k_idx];
920+ const float decay_value = decay_t [k_idx];
830921 const float key_value = k_t [k_idx];
831922 const float query_value = q_t [k_idx];
832923 auto * state_row = state_head + k_idx * v_head_dim;
833- for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
834- const float updated =
835- state_row[v_idx] * updated_decay + key_value * delta[v_idx];
836- state_row[v_idx] = updated;
837- output_t [v_idx] += updated * query_value;
838- }
924+ vec_state_update_and_output (
925+ state_row,
926+ delta,
927+ output_t ,
928+ decay_value,
929+ key_value,
930+ query_value,
931+ v_head_dim);
839932 }
840933 continue ;
841934 }
@@ -844,9 +937,33 @@ void channelwise_gated_delta_rule_recurrence(
844937 const float decay_value = decay_t [k_idx];
845938 const float key_value = k_t [k_idx];
846939 auto * state_row = state_head + k_idx * v_head_dim;
847- for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
848- state_row[v_idx] =
849- state_row[v_idx] * decay_value + key_value * delta[v_idx];
940+ int64_t idx = 0 ;
941+ #if defined(__APPLE__) && defined(__ARM_NEON)
942+ constexpr int64_t vector_size = 4 ;
943+ for (; idx + 2 * vector_size <= v_head_dim; idx += 2 * vector_size) {
944+ const float32x4_t state_vec0 = vld1q_f32 (state_row + idx);
945+ const float32x4_t state_vec1 =
946+ vld1q_f32 (state_row + idx + vector_size);
947+ const float32x4_t delta_vec0 = vld1q_f32 (delta + idx);
948+ const float32x4_t delta_vec1 = vld1q_f32 (delta + idx + vector_size);
949+ const float32x4_t updated_vec0 = vmlaq_n_f32 (
950+ vmulq_n_f32 (state_vec0, decay_value), delta_vec0, key_value);
951+ const float32x4_t updated_vec1 = vmlaq_n_f32 (
952+ vmulq_n_f32 (state_vec1, decay_value), delta_vec1, key_value);
953+ vst1q_f32 (state_row + idx, updated_vec0);
954+ vst1q_f32 (state_row + idx + vector_size, updated_vec1);
955+ }
956+ for (; idx + vector_size <= v_head_dim; idx += vector_size) {
957+ const float32x4_t state_vec = vld1q_f32 (state_row + idx);
958+ const float32x4_t delta_vec = vld1q_f32 (delta + idx);
959+ const float32x4_t updated_vec = vmlaq_n_f32 (
960+ vmulq_n_f32 (state_vec, decay_value), delta_vec, key_value);
961+ vst1q_f32 (state_row + idx, updated_vec);
962+ }
963+ #endif
964+ for (; idx < v_head_dim; ++idx) {
965+ state_row[idx] =
966+ state_row[idx] * decay_value + key_value * delta[idx];
850967 }
851968 }
852969
@@ -861,31 +978,14 @@ void channelwise_gated_delta_rule_recurrence(
861978 for (int64_t k_idx = 0 ; k_idx < k_head_dim; ++k_idx) {
862979 const float query_value = q_t [k_idx];
863980 const auto * state_row = state_head + k_idx * v_head_dim;
864- for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
865- output_t [v_idx] += state_row[v_idx] * query_value;
866- }
981+ vec_axpy (output_t , state_row, query_value, v_head_dim);
867982 }
868983 }
869984 }
870985 }
871986 }
872987}
873988
874- // T == 1 (autoregressive decode) route.
875- void channelwise_gated_delta_rule_decode (
876- RuntimeContext& ctx,
877- const Tensor& query,
878- const Tensor& key,
879- const Tensor& value,
880- const Tensor& decay,
881- const Tensor& beta,
882- const Tensor& initial_state,
883- Tensor& out,
884- Tensor& final_state_out) {
885- channelwise_gated_delta_rule_recurrence (
886- ctx, query, key, value, decay, beta, initial_state, out, final_state_out);
887- }
888-
889989// Chunk length for the chunkwise recurrence. Kept small so that
890990// exp(-cumsum(log decay)) over a chunk stays finite (see eg_inv in step 1).
891991constexpr int64_t CHUNK_SIZE = 32 ;
@@ -1151,9 +1251,7 @@ void channelwise_gated_delta_rule_chunked(
11511251 w_row[k_idx] += arj * eg_row[k_idx] * k_row[k_idx];
11521252 }
11531253 const float * __restrict__ v_row = v_c + j * v_head_dim;
1154- for (const auto v_idx : c10::irange (v_head_dim)) {
1155- u_row[v_idx] += arj * v_row[v_idx];
1156- }
1254+ vec_axpy (u_row, v_row, arj, v_head_dim);
11571255 }
11581256 }
11591257
@@ -1195,9 +1293,7 @@ void channelwise_gated_delta_rule_chunked(
11951293 for (const auto j : c10::irange (r + 1 )) {
11961294 const float a = sc.Aqk [r * CHUNK_SIZE + j];
11971295 const float * __restrict__ pv_row = sc.pv + j * v_head_dim;
1198- for (const auto v_idx : c10::irange (v_head_dim)) {
1199- o_row[v_idx] += a * pv_row[v_idx];
1200- }
1296+ vec_axpy (o_row, pv_row, a, v_head_dim);
12011297 }
12021298 }
12031299 }
@@ -1237,16 +1333,12 @@ void channelwise_gated_delta_rule_chunked(
12371333 const float qrk = q_c[r * k_head_dim + k_idx] *
12381334 sc.eg [r * k_head_dim + k_idx];
12391335 const float * __restrict__ s_row = S + k_idx * v_head_dim;
1240- for (const auto v_idx : c10::irange (v_head_dim)) {
1241- o_row[v_idx] += qrk * s_row[v_idx];
1242- }
1336+ vec_axpy (o_row, s_row, qrk, v_head_dim);
12431337 }
12441338 for (const auto j : c10::irange (r + 1 )) {
12451339 const float a = sc.Aqk [r * CHUNK_SIZE + j];
12461340 const float * __restrict__ pv_row = sc.pv + j * v_head_dim;
1247- for (const auto v_idx : c10::irange (v_head_dim)) {
1248- o_row[v_idx] += a * pv_row[v_idx];
1249- }
1341+ vec_axpy (o_row, pv_row, a, v_head_dim);
12501342 }
12511343 }
12521344 }
@@ -1263,15 +1355,11 @@ void channelwise_gated_delta_rule_chunked(
12631355 sc.de [r] = std::exp (gc_last - sc.gc [r * k_head_dim + k_idx]);
12641356 }
12651357 float * __restrict__ S_row = S + k_idx * v_head_dim;
1266- for (const auto v_idx : c10::irange (v_head_dim)) {
1267- S_row[v_idx] *= eg_last;
1268- }
1358+ vec_scale (S_row, eg_last, v_head_dim);
12691359 for (const auto r : c10::irange (cur)) {
12701360 const float c = k_c[r * k_head_dim + k_idx] * sc.de [r];
12711361 const float * __restrict__ pv_row = sc.pv + r * v_head_dim;
1272- for (const auto v_idx : c10::irange (v_head_dim)) {
1273- S_row[v_idx] += c * pv_row[v_idx];
1274- }
1362+ vec_axpy (S_row, pv_row, c, v_head_dim);
12751363 }
12761364 }
12771365 } // chunk
@@ -1395,7 +1483,7 @@ std::tuple<Tensor&, Tensor&> channelwise_gated_delta_rule_out(
13951483 // -- non-positive decay (log undefined) or tiny positive decay whose
13961484 // within-chunk cumulative -log(decay) would overflow exp(-gc) to inf -> NaN.
13971485 if (query.size (2 ) == 1 ) {
1398- channelwise_gated_delta_rule_decode (
1486+ channelwise_gated_delta_rule_recurrence (
13991487 ctx,
14001488 query,
14011489 key,
0 commit comments