Skip to content

Commit c85672d

Browse files
authored
Merge branch 'main' into gh/JCNTH/56/orig
2 parents 49cc6e5 + 0fc1f61 commit c85672d

3 files changed

Lines changed: 27 additions & 125 deletions

File tree

extension/llm/custom_ops/BUCK

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,3 @@ fbcode_target(_kind = runtime.python_test,
9191
"//caffe2:torch",
9292
],
9393
)
94-
95-
fbcode_target(_kind = runtime.python_binary,
96-
name = "bench_channelwise_gated_delta_rule",
97-
srcs = ["bench_channelwise_gated_delta_rule.py"],
98-
main_module = "executorch.extension.llm.custom_ops.bench_channelwise_gated_delta_rule",
99-
preload_deps = [
100-
":custom_ops_aot_lib_mkl_noomp",
101-
":custom_ops_aot_py",
102-
],
103-
deps = ["//caffe2:torch"],
104-
)

extension/llm/custom_ops/bench_channelwise_gated_delta_rule.py

Lines changed: 0 additions & 78 deletions
This file was deleted.

extension/llm/custom_ops/op_sdpa.cpp

Lines changed: 27 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -773,20 +773,8 @@ std::tuple<Tensor&, Tensor&> channelwise_gated_delta_rule_out(
773773
const auto* initial_state_data = initial_state.const_data_ptr<float>();
774774
auto* state_data = final_state_out.mutable_data_ptr<float>();
775775
auto* output_data = out.mutable_data_ptr<float>();
776-
777-
const int64_t scratch_numel = 2 * v_head_dim;
778-
std::unique_ptr<float[]> fallback_scratch;
779-
float* scratch_data = nullptr;
780-
Result<void*> scratch = ctx.allocate_temp(
781-
scratch_numel * sizeof(float), /*alignment=*/alignof(float));
782-
if (scratch.ok()) {
783-
scratch_data = reinterpret_cast<float*>(scratch.get());
784-
} else {
785-
fallback_scratch = std::make_unique<float[]>(scratch_numel);
786-
scratch_data = fallback_scratch.get();
787-
}
788-
float* v_pred = scratch_data;
789-
float* delta = scratch_data + v_head_dim;
776+
std::vector<float> v_pred(v_head_dim);
777+
std::vector<float> delta(v_head_dim);
790778

791779
for (int64_t batch = 0; batch < batch_size; ++batch) {
792780
for (int64_t head = 0; head < num_heads; ++head) {
@@ -821,44 +809,47 @@ std::tuple<Tensor&, Tensor&> channelwise_gated_delta_rule_out(
821809
const float beta_t = beta_head[token];
822810
auto* output_t = output_head + token * value_seq_stride;
823811

824-
// The recurrence needs only two passes over the K x V state S:
825-
// pass 1 (read-only): v_pred = (Diag(decay) S)^T k
826-
// pass 2 (read+write): S = Diag(decay) S + k (x) delta; o = S^T q
827-
// Decay is folded into both passes (never materialized separately), and
828-
// the rank-1 write and output readout share pass 2, so S is streamed
829-
// twice per token instead of four times. Multiply groupings match the
830-
// naive form, so results are identical up to floating-point contraction
831-
// (e.g. compiler FMA).
832-
833-
// Pass 1: predicted value off the decayed state (S left untouched).
834-
std::fill(v_pred, v_pred + v_head_dim, 0.0f);
812+
// 1. Per-key-channel decay: scale each state row by decay_t[k].
835813
for (int64_t k_idx = 0; k_idx < k_head_dim; ++k_idx) {
836814
const float decay_value = decay_t[k_idx];
815+
auto* state_row = state_head + k_idx * v_head_dim;
816+
for (int64_t v_idx = 0; v_idx < v_head_dim; ++v_idx) {
817+
state_row[v_idx] *= decay_value;
818+
}
819+
}
820+
821+
// 2. v_pred = S^T k: the memory's predicted value for this key, read
822+
// off the decayed state.
823+
std::fill(v_pred.begin(), v_pred.end(), 0.0f);
824+
for (int64_t k_idx = 0; k_idx < k_head_dim; ++k_idx) {
837825
const float key_value = k_t[k_idx];
838826
const auto* state_row = state_head + k_idx * v_head_dim;
839827
for (int64_t v_idx = 0; v_idx < v_head_dim; ++v_idx) {
840-
v_pred[v_idx] += (state_row[v_idx] * decay_value) * key_value;
828+
v_pred[v_idx] += state_row[v_idx] * key_value;
841829
}
842830
}
843831

844-
// delta = beta * (v - v_pred).
832+
// 3. delta = beta * (v - v_pred).
845833
for (int64_t v_idx = 0; v_idx < v_head_dim; ++v_idx) {
846834
delta[v_idx] = (v_t[v_idx] - v_pred[v_idx]) * beta_t;
847835
}
848836

849-
// Pass 2: apply decay + rank-1 write in place, and read back the
850-
// updated row for the output projection in the same sweep.
851-
std::fill(output_t, output_t + v_head_dim, 0.0f);
837+
// 4. Rank-1 write: S += k (x) delta.
852838
for (int64_t k_idx = 0; k_idx < k_head_dim; ++k_idx) {
853-
const float decay_value = decay_t[k_idx];
854839
const float key_value = k_t[k_idx];
855-
const float query_value = q_t[k_idx];
856840
auto* state_row = state_head + k_idx * v_head_dim;
857841
for (int64_t v_idx = 0; v_idx < v_head_dim; ++v_idx) {
858-
const float updated =
859-
state_row[v_idx] * decay_value + key_value * delta[v_idx];
860-
state_row[v_idx] = updated;
861-
output_t[v_idx] += updated * query_value;
842+
state_row[v_idx] += key_value * delta[v_idx];
843+
}
844+
}
845+
846+
// 5. Output readout: o_t = S^T q.
847+
std::fill(output_t, output_t + v_head_dim, 0.0f);
848+
for (int64_t k_idx = 0; k_idx < k_head_dim; ++k_idx) {
849+
const float query_value = q_t[k_idx];
850+
const auto* state_row = state_head + k_idx * v_head_dim;
851+
for (int64_t v_idx = 0; v_idx < v_head_dim; ++v_idx) {
852+
output_t[v_idx] += state_row[v_idx] * query_value;
862853
}
863854
}
864855
}

0 commit comments

Comments
 (0)