@@ -773,8 +773,20 @@ 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- std::vector<float > v_pred (v_head_dim);
777- std::vector<float > delta (v_head_dim);
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;
778790
779791 for (int64_t batch = 0 ; batch < batch_size; ++batch) {
780792 for (int64_t head = 0 ; head < num_heads; ++head) {
@@ -809,47 +821,44 @@ std::tuple<Tensor&, Tensor&> channelwise_gated_delta_rule_out(
809821 const float beta_t = beta_head[token];
810822 auto * output_t = output_head + token * value_seq_stride;
811823
812- // 1. Per-key-channel decay: scale each state row by decay_t[k].
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 );
813835 for (int64_t k_idx = 0 ; k_idx < k_head_dim; ++k_idx) {
814836 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) {
825837 const float key_value = k_t [k_idx];
826838 const auto * state_row = state_head + k_idx * v_head_dim;
827839 for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
828- v_pred[v_idx] += state_row[v_idx] * key_value;
840+ v_pred[v_idx] += ( state_row[v_idx] * decay_value) * key_value;
829841 }
830842 }
831843
832- // 3. delta = beta * (v - v_pred).
844+ // delta = beta * (v - v_pred).
833845 for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
834846 delta[v_idx] = (v_t [v_idx] - v_pred[v_idx]) * beta_t ;
835847 }
836848
837- // 4. Rank-1 write: S += k (x) delta.
838- for (int64_t k_idx = 0 ; k_idx < k_head_dim; ++k_idx) {
839- const float key_value = k_t [k_idx];
840- auto * state_row = state_head + k_idx * v_head_dim;
841- for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
842- state_row[v_idx] += key_value * delta[v_idx];
843- }
844- }
845-
846- // 5. Output readout: o_t = S^T q.
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.
847851 std::fill (output_t , output_t + v_head_dim, 0 .0f );
848852 for (int64_t k_idx = 0 ; k_idx < k_head_dim; ++k_idx) {
853+ const float decay_value = decay_t [k_idx];
854+ const float key_value = k_t [k_idx];
849855 const float query_value = q_t [k_idx];
850- const auto * state_row = state_head + k_idx * v_head_dim;
856+ auto * state_row = state_head + k_idx * v_head_dim;
851857 for (int64_t v_idx = 0 ; v_idx < v_head_dim; ++v_idx) {
852- output_t [v_idx] += state_row[v_idx] * query_value;
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;
853862 }
854863 }
855864 }
0 commit comments