Skip to content

Commit 84b2b8a

Browse files
authored
force neon vectorization for ios (pytorch#21479)
Differential Revision: D113940046 Pull Request resolved: pytorch#21479
1 parent 5b3da18 commit 84b2b8a

1 file changed

Lines changed: 138 additions & 50 deletions

File tree

‎extension/llm/custom_ops/op_sdpa.cpp‎

Lines changed: 138 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@
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).
891991
constexpr 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

Comments
 (0)