Skip to content

Commit bfe007a

Browse files
committed
Optimize FFLayer heap allocation frequency using thread-local vector buffers
1 parent 072ff33 commit bfe007a

2 files changed

Lines changed: 95 additions & 18 deletions

File tree

include/neuralnetwork/layers/fflayer.cpp

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ void FFLayer::calculate_forward_feed(
227227
}
228228

229229
const size_t effective_batch_size = batch_size * num_time_steps;
230-
std::vector<double> batch_inputs_buffer(effective_batch_size * N_prev);
230+
thread_local std::vector<double> batch_inputs_buffer;
231+
batch_inputs_buffer.resize(effective_batch_size * N_prev);
231232

232233
for (size_t b = 0; b < batch_size; ++b)
233234
{
@@ -246,7 +247,8 @@ void FFLayer::calculate_forward_feed(
246247
}
247248
}
248249

249-
std::vector<double> batch_pre_activation_sums_buffer(effective_batch_size * N_this, 0.0);
250+
thread_local std::vector<double> batch_pre_activation_sums_buffer;
251+
batch_pre_activation_sums_buffer.assign(effective_batch_size * N_this, 0.0);
250252

251253
// 2. Initialize with bias values
252254
if (has_bias())
@@ -275,16 +277,18 @@ void FFLayer::calculate_forward_feed(
275277
}
276278
else
277279
{
280+
auto& batch_inputs_buffer_ref = batch_inputs_buffer;
281+
auto& batch_pre_activation_sums_buffer_ref = batch_pre_activation_sums_buffer;
278282
size_t start = 0;
279283
for (unsigned int t = 0; t < active_gemm_threads; ++t)
280284
{
281285
size_t size = (effective_batch_size / active_gemm_threads) + (t < (effective_batch_size % active_gemm_threads) ? 1 : 0);
282286
size_t end = start + size;
283287
if (start < end)
284288
{
285-
_task_queue_pool->enqueue([start, end, N_prev, N_this, &batch_inputs_buffer, &batch_pre_activation_sums_buffer, this]()
289+
_task_queue_pool->enqueue([start, end, N_prev, N_this, &batch_inputs_buffer_ref, &batch_pre_activation_sums_buffer_ref, this]()
286290
{
287-
run_gemm(start, end, N_prev, N_this, batch_inputs_buffer, batch_pre_activation_sums_buffer);
291+
run_gemm(start, end, N_prev, N_this, batch_inputs_buffer_ref, batch_pre_activation_sums_buffer_ref);
288292
});
289293
}
290294
start = end;
@@ -301,16 +305,18 @@ void FFLayer::calculate_forward_feed(
301305
}
302306
else
303307
{
308+
auto& batch_inputs_buffer_ref = batch_inputs_buffer;
309+
auto& batch_pre_activation_sums_buffer_ref = batch_pre_activation_sums_buffer;
304310
size_t start = 0;
305311
for (unsigned int t = 0; t < active_post_threads; ++t)
306312
{
307313
size_t size = (batch_size / active_post_threads) + (t < (batch_size % active_post_threads) ? 1 : 0);
308314
size_t end = start + size;
309315
if (start < end)
310316
{
311-
_task_queue_pool->enqueue([start, end, num_time_steps, N_this, &batch_gradients_and_outputs, &batch_residual_output_values, &batch_hidden_states, &batch_inputs_buffer, &batch_pre_activation_sums_buffer, is_training, this]()
317+
_task_queue_pool->enqueue([start, end, num_time_steps, N_this, &batch_gradients_and_outputs, &batch_residual_output_values, &batch_hidden_states, &batch_inputs_buffer_ref, &batch_pre_activation_sums_buffer_ref, is_training, this]()
312318
{
313-
run_post_gemm(start, end, num_time_steps, N_this, batch_gradients_and_outputs, batch_residual_output_values, batch_hidden_states, batch_inputs_buffer, batch_pre_activation_sums_buffer, is_training);
319+
run_post_gemm(start, end, num_time_steps, N_this, batch_gradients_and_outputs, batch_residual_output_values, batch_hidden_states, batch_inputs_buffer_ref, batch_pre_activation_sums_buffer_ref, is_training);
314320
});
315321
}
316322
start = end;
@@ -488,7 +494,8 @@ void FFLayer::calculate_hidden_gradients(
488494
const size_t num_time_steps = batch_hidden_states[0].at(get_layer_index()).size();
489495
if (num_time_steps == 0) return;
490496

491-
std::vector<double> flattened_next_grads_buffer(batch_size * num_time_steps * N_next, 0.0);
497+
thread_local std::vector<double> flattened_next_grads_buffer;
498+
flattened_next_grads_buffer.assign(batch_size * num_time_steps * N_next, 0.0);
492499
const bool use_direct_gradients = batch_next_grad_matrix.empty();
493500
for (size_t b = 0; b < batch_size; ++b)
494501
{
@@ -531,7 +538,8 @@ void FFLayer::calculate_hidden_gradients(
531538
}
532539

533540
const size_t effective_batch_size = batch_size * num_time_steps;
534-
std::vector<double> flattened_this_grads_buffer(effective_batch_size * N_this, 0.0);
541+
thread_local std::vector<double> flattened_this_grads_buffer;
542+
flattened_this_grads_buffer.assign(effective_batch_size * N_this, 0.0);
535543
const double* W_next = next_layer.get_w_values().data();
536544

537545
const auto& num_threads = _task_queue_pool->get_number_of_threads();
@@ -548,9 +556,11 @@ void FFLayer::calculate_hidden_gradients(
548556
}
549557
else
550558
{
559+
auto& flattened_next_grads_buffer_ref = flattened_next_grads_buffer;
560+
auto& flattened_this_grads_buffer_ref = flattened_this_grads_buffer;
551561
if (!use_gemm_mt)
552562
{
553-
run_gemm_backward(0, effective_batch_size, N_next, N_this, W_next, flattened_next_grads_buffer, flattened_this_grads_buffer);
563+
run_gemm_backward(0, effective_batch_size, N_next, N_this, W_next, flattened_next_grads_buffer_ref, flattened_this_grads_buffer_ref);
554564
}
555565
else
556566
{
@@ -559,15 +569,15 @@ void FFLayer::calculate_hidden_gradients(
559569
{
560570
size_t size = (effective_batch_size / active_gemm_threads) + (t < (effective_batch_size % active_gemm_threads) ? 1 : 0);
561571
size_t end = start + size;
562-
if (start < end) _task_queue_pool->enqueue([start, end, N_next, N_this, W_next, &flattened_next_grads_buffer, &flattened_this_grads_buffer, this]() { run_gemm_backward(start, end, N_next, N_this, W_next, flattened_next_grads_buffer, flattened_this_grads_buffer); });
572+
if (start < end) _task_queue_pool->enqueue([start, end, N_next, N_this, W_next, &flattened_next_grads_buffer_ref, &flattened_this_grads_buffer_ref, this]() { run_gemm_backward(start, end, N_next, N_this, W_next, flattened_next_grads_buffer_ref, flattened_this_grads_buffer_ref); });
563573
start = end;
564574
}
565575
_task_queue_pool->get();
566576
}
567577

568578
if (!use_post_mt)
569579
{
570-
run_post_gemm_backward(0, batch_size, N_this, batch_gradients_and_outputs, batch_hidden_states, flattened_this_grads_buffer);
580+
run_post_gemm_backward(0, batch_size, N_this, batch_gradients_and_outputs, batch_hidden_states, flattened_this_grads_buffer_ref);
571581
}
572582
else
573583
{
@@ -576,7 +586,7 @@ void FFLayer::calculate_hidden_gradients(
576586
{
577587
size_t size = (batch_size / active_post_threads) + (t < (batch_size % active_post_threads) ? 1 : 0);
578588
size_t end = start + size;
579-
if (start < end) _task_queue_pool->enqueue([start, end, N_this, &batch_gradients_and_outputs, &batch_hidden_states, &flattened_this_grads_buffer, this]() { run_post_gemm_backward(start, end, N_this, batch_gradients_and_outputs, batch_hidden_states, flattened_this_grads_buffer); });
589+
if (start < end) _task_queue_pool->enqueue([start, end, N_this, &batch_gradients_and_outputs, &batch_hidden_states, &flattened_this_grads_buffer_ref, this]() { run_post_gemm_backward(start, end, N_this, batch_gradients_and_outputs, batch_hidden_states, flattened_this_grads_buffer_ref); });
580590
start = end;
581591
}
582592
_task_queue_pool->get();
@@ -594,7 +604,8 @@ void FFLayer::calculate_hidden_gradients_from_output_gradients(std::vector<Gradi
594604
if (num_time_steps == 0) return;
595605

596606
const size_t effective_batch_size = batch_size * num_time_steps;
597-
std::vector<double> flattened_this_grads_buffer(effective_batch_size * N_this, 0.0);
607+
thread_local std::vector<double> flattened_this_grads_buffer;
608+
flattened_this_grads_buffer.assign(effective_batch_size * N_this, 0.0);
598609
const bool use_direct_gradients = batch_output_gradients.empty();
599610
for (size_t b = 0; b < batch_size; ++b)
600611
{
@@ -643,16 +654,17 @@ void FFLayer::calculate_hidden_gradients_from_output_gradients(std::vector<Gradi
643654
}
644655
else
645656
{
657+
auto& flattened_this_grads_buffer_ref = flattened_this_grads_buffer;
646658
size_t start = 0;
647659
for (unsigned int t = 0; t < active_threads; ++t)
648660
{
649661
size_t size = (batch_size / active_threads) + (t < (batch_size % active_threads) ? 1 : 0);
650662
size_t end = start + size;
651663
if (start < end)
652664
{
653-
_task_queue_pool->enqueue([start, end, N_this, &batch_gradients_and_outputs, &batch_hidden_states, &flattened_this_grads_buffer, this]()
665+
_task_queue_pool->enqueue([start, end, N_this, &batch_gradients_and_outputs, &batch_hidden_states, &flattened_this_grads_buffer_ref, this]()
654666
{
655-
run_post_gemm_backward(start, end, N_this, batch_gradients_and_outputs, batch_hidden_states, flattened_this_grads_buffer);
667+
run_post_gemm_backward(start, end, N_this, batch_gradients_and_outputs, batch_hidden_states, flattened_this_grads_buffer_ref);
656668
});
657669
}
658670
start = end;
@@ -817,8 +829,9 @@ void FFLayer::run_post_gemm_backward(
817829
{
818830
MYODDWEB_PROFILE_FUNCTION("FFLayer");
819831

820-
std::vector<double> deriv_buf(N_this);
821-
std::vector<double> rnn_grads_row;
832+
thread_local std::vector<double> deriv_buf;
833+
deriv_buf.resize(N_this);
834+
thread_local std::vector<double> rnn_grads_row;
822835
for (size_t b = start; b < end; b++)
823836
{
824837
const auto& layer_states = batch_hidden_states[b].at(get_layer_index());

tests/fflayer_mt_tests.cpp

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include <gtest/gtest.h>
1+
#include <gtest/gtest.h>
22
#include "layers/fflayer.h"
33
#include "test_helper.h"
44
#include <vector>
@@ -232,3 +232,67 @@ TEST_F(FFLayerMTTest, GradientStorageMTConsistency)
232232
EXPECT_NEAR(b_grads_st[i], b_grads_mt[i], 1e-12) << "Bias grad mismatch at index " << i;
233233
}
234234
}
235+
236+
TEST_F(FFLayerMTTest, ThreadLocalBufferCorrectnessAndStress)
237+
{
238+
const unsigned num_inputs = 8;
239+
const unsigned num_neurons = 16;
240+
const unsigned next_neurons = 8;
241+
const unsigned num_threads = get_test_threads();
242+
243+
FFLayer layer(1, num_inputs, num_neurons, 0.0, Layer::Role::Hidden, activation(activation::method::tanh, 0.0), OptimiserType::SGD, -1, 0.0, nullptr, num_threads, true, 0.0);
244+
init_layer_weights(layer);
245+
246+
FFLayer next_layer(2, num_neurons, next_neurons, 0.0, Layer::Role::Hidden, activation(activation::method::linear, 0.0), OptimiserType::SGD, -1, 0.0, nullptr, 1, true, 0.0);
247+
std::vector<double> next_w(num_neurons * next_neurons, 0.1);
248+
next_layer.set_w_values(next_w);
249+
250+
std::vector<unsigned> topology = { num_inputs, num_neurons, next_neurons };
251+
MockLayer prev_layer(0, num_inputs);
252+
253+
std::vector<unsigned> batch_sizes = { 10, 50, 5, 100 };
254+
for (unsigned batch_size : batch_sizes)
255+
{
256+
auto batch_go = create_batch_gradients_and_outputs(topology, batch_size);
257+
auto batch_hs = create_batch_hidden_states(topology, batch_size, 1, 1);
258+
259+
for (size_t b = 0; b < batch_size; ++b)
260+
{
261+
std::vector<double> inputs(num_inputs);
262+
for (size_t i = 0; i < inputs.size(); ++i)
263+
{
264+
inputs[i] = std::sin(static_cast<double>(b + i + batch_size));
265+
}
266+
batch_go[b].set_outputs(0, inputs);
267+
}
268+
269+
layer.calculate_forward_feed(batch_go, prev_layer, {}, batch_hs, batch_size, true);
270+
271+
for (size_t b = 0; b < batch_size; ++b)
272+
{
273+
const auto& outputs = batch_go[b].get_outputs(1);
274+
ASSERT_EQ(outputs.size(), num_neurons);
275+
for (double out : outputs)
276+
{
277+
EXPECT_TRUE(out >= -1.0 && out <= 1.0);
278+
}
279+
}
280+
281+
std::vector<std::vector<double>> batch_next_grads(batch_size, std::vector<double>(next_neurons));
282+
for (size_t b = 0; b < batch_size; ++b)
283+
{
284+
for (size_t i = 0; i < next_neurons; ++i)
285+
{
286+
batch_next_grads[b][i] = std::cos(static_cast<double>(b * i + batch_size));
287+
}
288+
}
289+
290+
layer.calculate_hidden_gradients(batch_go, next_layer, batch_next_grads, batch_hs, batch_size, 0);
291+
292+
for (size_t b = 0; b < batch_size; ++b)
293+
{
294+
const auto& in_grads = batch_go[b].get_gradients(1);
295+
ASSERT_EQ(in_grads.size(), num_neurons);
296+
}
297+
}
298+
}

0 commit comments

Comments
 (0)