Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions NAM/activations.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,6 @@ class ActivationPReLU : public Activation
// Matrix is organized as (channels, time_steps)
unsigned long actual_channels = static_cast<unsigned long>(matrix.rows());

// Prepare the slopes for the current matrix size
std::vector<float> slopes_for_channels = negative_slopes;

// Fail loudly if input has more channels than activation
#ifndef NDEBUG
if (actual_channels != negative_slopes.size())
Expand All @@ -321,7 +318,7 @@ class ActivationPReLU : public Activation
// Apply the negative slope to all time steps in this channel
for (int time_step = 0; time_step < matrix.cols(); time_step++)
{
matrix(channel, time_step) = leaky_relu(matrix(channel, time_step), slopes_for_channels[channel]);
matrix(channel, time_step) = leaky_relu(matrix(channel, time_step), negative_slopes[channel]);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very odd that this was like that. Wonder why...

At any rate, PReLU isn't a super-popular choice so I'm not too worried about missing something.

}
}
}
Expand Down
18 changes: 10 additions & 8 deletions NAM/wavenet/model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,10 @@ long nam::wavenet::detail::LayerArray::get_receptive_field() const
void nam::wavenet::detail::LayerArray::Process(const Eigen::MatrixXf& layer_inputs, const Eigen::MatrixXf& condition,
const int num_frames)
{
// Zero head inputs accumulator (first layer array)
this->_head_inputs.setZero();
// Zero head inputs accumulator (first layer array). Only the first num_frames columns are ever
// read this call, so zeroing the whole maxBufferSize-wide buffer is wasted work when the host
// processes blocks smaller than the maximum it reserved.
this->_head_inputs.leftCols(num_frames).setZero();
ProcessInner(layer_inputs, condition, num_frames);
}

Expand Down Expand Up @@ -776,12 +778,12 @@ void nam::wavenet::WaveNet::process(NAM_SAMPLE** input, NAM_SAMPLE** output, con
if (this->_post_stack_head != nullptr)
{
assert(final_head_outputs.rows() == this->_post_stack_head->in_channels());
const int head_in = this->_post_stack_head->in_channels();
for (int ch = 0; ch < head_in; ch++)
{
for (int s = 0; s < num_frames; s++)
this->_scaled_head_scratch(ch, s) = this->_head_scale * final_head_outputs(ch, s);
}
// _scaled_head_scratch is sized (in_channels, maxBufferSize), and the assert above pins
// final_head_outputs to the same row count, so this is a straight scaled copy of the block.
// Expressed as one Eigen expression rather than a nested loop: the manual loop walked the
// column-major matrix with a row-major access pattern, striding by in_channels per step.
this->_scaled_head_scratch.leftCols(num_frames).noalias() =
this->_head_scale * final_head_outputs.leftCols(num_frames);
this->_post_stack_head->process(this->_scaled_head_scratch, num_frames);
const Eigen::MatrixXf& head_out = this->_post_stack_head->get_last_output();
assert(head_out.rows() == out_channels);
Expand Down
4 changes: 4 additions & 0 deletions tools/run_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <iostream>
#include "test/test_activations.cpp"
#include "test/test_activations_realtime_safe.cpp"
#include "test/test_conv1d.cpp"
#include "test/test_conv_1x1.cpp"
#include "test/test_convnet.cpp"
Expand Down Expand Up @@ -63,6 +64,9 @@ int main()
test_activations::TestPReLU::test_wrong_size_array();
test_activations::TestPReLU::test_valid_array_size();

test_activations_realtime_safe::test_prelu_apply_matrix_realtime_safe();
test_activations_realtime_safe::test_prelu_apply_pointer_realtime_safe();

// Typed ActivationConfig tests
test_activations::TestTypedActivationConfig::test_simple_config();
test_activations::TestTypedActivationConfig::test_all_simple_types();
Expand Down
65 changes: 65 additions & 0 deletions tools/test/test_activations_realtime_safe.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Test to verify activation apply() overloads are real-time safe (no allocations/frees)

#include <Eigen/Dense>
#include <cassert>
#include <cmath>
#include <vector>

#include "NAM/activations.h"
#include "allocation_tracking.h"

namespace test_activations_realtime_safe
{
using namespace allocation_tracking;

// PReLU's matrix overload is reached once per sample from the gating/blending activations, so a
// heap allocation here lands directly on the audio thread.
void test_prelu_apply_matrix_realtime_safe()
{
const int channels = 8;
const int time_steps = 64;

std::vector<float> slopes;
for (int i = 0; i < channels; i++)
slopes.push_back(0.01f * static_cast<float>(i + 1));

nam::activations::ActivationPReLU activation(slopes);

Eigen::MatrixXf matrix(channels, time_steps);
matrix.setConstant(-1.0f);

run_allocation_test_no_allocations(
nullptr, // No setup needed
[&]() { activation.apply(matrix); }, nullptr, // No teardown needed
"test_prelu_apply_matrix_realtime_safe");

// Each channel should have been scaled by its own slope.
for (int c = 0; c < channels; c++)
assert(std::abs(matrix(c, 0) - (-slopes[c])) < 1e-6f);
}

// The pointer/length overload is the one used by the plain (non-gated) path.
void test_prelu_apply_pointer_realtime_safe()
{
const int channels = 4;
const int time_steps = 32;

std::vector<float> slopes;
for (int i = 0; i < channels; i++)
slopes.push_back(0.05f * static_cast<float>(i + 1));

nam::activations::ActivationPReLU activation(slopes);

Eigen::MatrixXf matrix(channels, time_steps);
matrix.setConstant(-2.0f);

run_allocation_test_no_allocations(
nullptr, // No setup needed
[&]() { activation.apply(matrix.data(), static_cast<long>(channels) * time_steps); },
nullptr, // No teardown needed
"test_prelu_apply_pointer_realtime_safe");

for (int c = 0; c < channels; c++)
assert(std::abs(matrix(c, 0) - (-2.0f * slopes[c])) < 1e-6f);
}
} // namespace test_activations_realtime_safe
Loading