Skip to content

Commit a865834

Browse files
committed
Eliminate loop-level vector heap allocations in backpropagation
- Reuse rnn_grads_row, given_outputs_vec, current_target, and deltas vectors across batch/time iterations in FFLayer, FFOutputLayer, LSTMLayer, and ElmanRNNLayer. - Use pointer-based set_rnn_gradients inside output gradient calculation to prevent vector move/deallocation and reuse underlying vector memory capacity. - Add unit test case RnnGradientsReusesCapacity to verify GradientsAndOutputs capacity preservation.
1 parent 1e0266c commit a865834

5 files changed

Lines changed: 49 additions & 14 deletions

File tree

include/neuralnetwork/layers/elmanrnnlayer.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,11 +520,12 @@ void ElmanRNNLayer::calculate_output_gradients(std::vector<GradientsAndOutputs>&
520520
{
521521
MYODDWEB_PROFILE_FUNCTION("ElmanRNNLayer");
522522
const size_t N_this = get_number_neurons();
523+
std::vector<double> deltas;
523524
for (size_t b = 0; b < batch_size; ++b)
524525
{
525526
const auto& states = batch_hidden_states[b].at(get_layer_index());
526527
const size_t T = states.size();
527-
std::vector<double> deltas(T * N_this);
528+
deltas.resize(T * N_this);
528529
const std::vector<double>& targets = *(target_outputs_begin + b);
529530
for (size_t t = 0; t < T; ++t)
530531
{
@@ -544,7 +545,7 @@ void ElmanRNNLayer::calculate_output_gradients(std::vector<GradientsAndOutputs>&
544545
}
545546
double* dest_ptr = batch_gradients_and_outputs[b].get_gradients_raw(get_layer_index());
546547
std::copy(deltas.end() - N_this, deltas.end(), dest_ptr);
547-
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), std::move(deltas));
548+
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), deltas.data(), deltas.size());
548549
}
549550
}
550551

include/neuralnetwork/layers/fflayer.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,7 @@ void FFLayer::run_post_gemm_backward(
818818
MYODDWEB_PROFILE_FUNCTION("FFLayer");
819819

820820
std::vector<double> deriv_buf(N_this);
821+
std::vector<double> rnn_grads_row;
821822
for (size_t b = start; b < end; b++)
822823
{
823824
const auto& layer_states = batch_hidden_states[b].at(get_layer_index());
@@ -827,7 +828,7 @@ void FFLayer::run_post_gemm_backward(
827828
continue;
828829
}
829830

830-
std::vector<double> rnn_grads_row(num_time_steps * N_this, 0.0);
831+
rnn_grads_row.assign(num_time_steps * N_this, 0.0);
831832

832833
for (size_t t = 0; t < num_time_steps; ++t)
833834
{
@@ -859,7 +860,7 @@ void FFLayer::run_post_gemm_backward(
859860
{
860861
batch_gradients_and_outputs[b].set_gradients(get_layer_index(), rnn_grads_row.data() + rnn_grads_row.size() - N_this, N_this);
861862
}
862-
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), std::move(rnn_grads_row));
863+
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), rnn_grads_row.data(), rnn_grads_row.size());
863864
}
864865
}
865866

include/neuralnetwork/layers/ffoutputlayer.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,36 +269,40 @@ void FFOutputLayer::run_output_gradients(
269269
const size_t num_time_steps = batch_hidden_states[0].at(get_layer_index()).size();
270270

271271
std::vector<double> deriv_buf(num_neurons);
272+
std::vector<double> rnn_grads_row;
273+
std::vector<double> given_outputs_vec;
274+
std::vector<double> current_target;
275+
std::vector<double> deltas;
276+
272277
for (size_t b = start; b < end; b++)
273278
{
274279
const auto& target_outputs = *(target_outputs_begin + b);
275280
const auto& layer_states = batch_hidden_states[b].at(get_layer_index());
276-
std::vector<double> rnn_grads_row(num_time_steps * num_neurons, 0.0);
281+
rnn_grads_row.assign(num_time_steps * num_neurons, 0.0);
277282

278283
for (size_t t = 0; t < num_time_steps; ++t)
279284
{
280285
const auto& current_hidden_state = layer_states[t];
281286
const auto& given_outputs = current_hidden_state.get_hidden_state_values();
282-
std::vector<double> given_outputs_vec(given_outputs.begin(), given_outputs.end());
287+
given_outputs_vec.assign(given_outputs.begin(), given_outputs.end());
283288

284289
// Determine target for this time step
285-
std::vector<double> current_target;
286290
if (target_outputs.size() == num_time_steps * num_neurons)
287291
{
288292
current_target.assign(target_outputs.begin() + t * num_neurons, target_outputs.begin() + (t + 1) * num_neurons);
289293
}
290294
else if (t == num_time_steps - 1)
291295
{
292296
// Only one target provided, apply to the last step
293-
current_target = target_outputs;
297+
current_target.assign(target_outputs.begin(), target_outputs.end());
294298
}
295299
else
296300
{
297301
// No target for this step
298302
continue;
299303
}
300304

301-
std::vector<double> deltas(num_neurons, 0.0);
305+
deltas.assign(num_neurons, 0.0);
302306
calculate_error_deltas(deltas, current_target, given_outputs_vec);
303307

304308
const double* pre_act = current_hidden_state.get_pre_activation_sums().data();
@@ -346,7 +350,7 @@ void FFOutputLayer::run_output_gradients(
346350
{
347351
batch_gradients_and_outputs[b].set_gradients(get_layer_index(), rnn_grads_row.data() + rnn_grads_row.size() - num_neurons, num_neurons);
348352
}
349-
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), std::move(rnn_grads_row));
353+
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), rnn_grads_row.data(), rnn_grads_row.size());
350354
}
351355
}
352356

include/neuralnetwork/layers/lstmlayer.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -668,25 +668,32 @@ void LSTMLayer::calculate_output_gradients(std::vector<GradientsAndOutputs>& bat
668668
{
669669
MYODDWEB_PROFILE_FUNCTION("LSTMLayer");
670670
const size_t N_this = get_number_neurons();
671+
std::vector<double> deltas;
671672
for (size_t b = 0; b < batch_size; ++b)
672673
{
673674
const auto& states = batch_hidden_states[b].at(get_layer_index());
674675
const size_t T = states.size();
675-
std::vector<double> deltas(T * N_this);
676+
deltas.resize(T * N_this);
676677
const std::vector<double>& targets = *(target_outputs_begin + b);
677678
for (size_t t = 0; t < T; ++t)
678679
{
679680
const auto& given = states[t].get_hidden_state_values();
680681
for (size_t j = 0; j < N_this; ++j)
681682
{
682683
size_t idx = t * N_this + j;
683-
if (idx < targets.size()) deltas[idx] = given[j] - targets[idx];
684-
else deltas[idx] = 0.0;
684+
if (idx < targets.size())
685+
{
686+
deltas[idx] = given[j] - targets[idx];
687+
}
688+
else
689+
{
690+
deltas[idx] = 0.0;
691+
}
685692
}
686693
}
687694
double* dest_ptr = batch_gradients_and_outputs[b].get_gradients_raw(get_layer_index());
688695
std::copy(deltas.end() - N_this, deltas.end(), dest_ptr);
689-
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), std::move(deltas));
696+
batch_gradients_and_outputs[b].set_rnn_gradients(get_layer_index(), deltas.data(), deltas.size());
690697
}
691698
}
692699

tests/gradients_and_outputs_tests.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,25 @@ TEST_F(GradientsAndOutputsTest, PointerOverloadsAndRawBuffers)
192192
gao.set_rnn_gate_gradients(2, rnn_gate_vals.data(), rnn_gate_vals.size());
193193
EXPECT_EQ(gao.get_rnn_gate_gradients(2), rnn_gate_vals);
194194
}
195+
196+
TEST_F(GradientsAndOutputsTest, RnnGradientsReusesCapacity) {
197+
GradientsAndOutputs gao(topology);
198+
std::vector<double> rnn_grad_vals = {0.66, 0.77, 0.88, 0.99};
199+
200+
// First assignment
201+
gao.set_rnn_gradients(2, rnn_grad_vals.data(), rnn_grad_vals.size());
202+
const auto& vec_ref = gao.get_rnn_gradients(2);
203+
size_t initial_capacity = vec_ref.capacity();
204+
EXPECT_GE(initial_capacity, 4);
205+
206+
// Clear/zero
207+
gao.zero();
208+
EXPECT_TRUE(vec_ref.empty());
209+
size_t capacity_after_clear = vec_ref.capacity();
210+
EXPECT_EQ(capacity_after_clear, initial_capacity); // Capacity must be preserved by clear()
211+
212+
// Re-assign (smaller size)
213+
std::vector<double> new_vals = {1.1, 2.2};
214+
gao.set_rnn_gradients(2, new_vals.data(), new_vals.size());
215+
EXPECT_EQ(vec_ref.capacity(), capacity_after_clear); // Capacity must not change
216+
}

0 commit comments

Comments
 (0)