From 97a245f47b7934b54eafc3b5c8fab966756fa550 Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 15:51:01 -0700 Subject: [PATCH 1/5] [REFACTOR] Enhance SlimmableWavenet model management - Introduced a staging model mechanism to allow for smoother transitions between model configurations without interrupting real-time processing. - Refactored the model rebuilding logic into separate functions for clarity and maintainability. - Updated the SetSlimmableSize method to utilize the new staging approach, improving performance during model updates. --- NAM/wavenet/slimmable.cpp | 50 +++++++++++++++++++++++++++++++++------ NAM/wavenet/slimmable.h | 9 ++++++- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/NAM/wavenet/slimmable.cpp b/NAM/wavenet/slimmable.cpp index 603239a..7fc599b 100644 --- a/NAM/wavenet/slimmable.cpp +++ b/NAM/wavenet/slimmable.cpp @@ -344,11 +344,8 @@ SlimmableWavenet::SlimmableWavenet(std::vector origin _rebuild_model(full_channels); } -void SlimmableWavenet::_rebuild_model(const std::vector& target_channels) +std::unique_ptr SlimmableWavenet::_create_wavenet_for_channels(const std::vector& target_channels) { - if (target_channels == _current_channels && _active_model) - return; - std::vector weights; std::vector modified_params; const std::vector* params_ptr; @@ -371,16 +368,51 @@ void SlimmableWavenet::_rebuild_model(const std::vector& target_channels) condition_dsp = get_dsp(_condition_dsp_json); double sampleRate = _current_sample_rate > 0 ? _current_sample_rate : GetExpectedSampleRate(); - _active_model = std::make_unique(_in_channels, *params_ptr, _head_scale, _with_head, std::nullopt, - std::move(weights), std::move(condition_dsp), sampleRate); + return std::make_unique(_in_channels, *params_ptr, _head_scale, _with_head, std::nullopt, + std::move(weights), std::move(condition_dsp), sampleRate); +} + +void SlimmableWavenet::_rebuild_model(const std::vector& target_channels) +{ + if (target_channels == _current_channels && _active_model) + return; + + _staging_model.reset(); + _staging_channels.clear(); + + _active_model = _create_wavenet_for_channels(target_channels); _current_channels = target_channels; if (_current_buffer_size > 0) _active_model->Reset(_current_sample_rate, _current_buffer_size); } +void SlimmableWavenet::_stage_rebuild_model(const std::vector& target_channels) +{ + if (target_channels == _current_channels && _active_model) + { + _staging_model.reset(); + _staging_channels.clear(); + return; + } + + if (_staging_model && target_channels == _staging_channels) + return; + + _staging_model = _create_wavenet_for_channels(target_channels); + _staging_channels = target_channels; + + if (_current_buffer_size > 0) + _staging_model->Reset(_current_sample_rate, _current_buffer_size); +} + void SlimmableWavenet::process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) { + if (_staging_model) + { + _active_model = std::move(_staging_model); + _current_channels = std::move(_staging_channels); + } if (_active_model) _active_model->process(input, output, num_frames); } @@ -389,6 +421,8 @@ void SlimmableWavenet::prewarm() { if (_active_model) _active_model->prewarm(); + if (_staging_model) + _staging_model->prewarm(); } void SlimmableWavenet::Reset(const double sampleRate, const int maxBufferSize) @@ -397,6 +431,8 @@ void SlimmableWavenet::Reset(const double sampleRate, const int maxBufferSize) _current_buffer_size = maxBufferSize; if (_active_model) _active_model->Reset(sampleRate, maxBufferSize); + if (_staging_model) + _staging_model->Reset(sampleRate, maxBufferSize); } void SlimmableWavenet::SetSlimmableSize(const double val) @@ -413,7 +449,7 @@ void SlimmableWavenet::SetSlimmableSize(const double val) target[i] = ratio_to_channels(val, allowed); } - _rebuild_model(target); + _stage_rebuild_model(target); } // ============================================================================ diff --git a/NAM/wavenet/slimmable.h b/NAM/wavenet/slimmable.h index a798108..6c11684 100644 --- a/NAM/wavenet/slimmable.h +++ b/NAM/wavenet/slimmable.h @@ -19,7 +19,9 @@ namespace slimmable_wavenet /// Stores the full WaveNet LayerArrayParams and weights. Each layer array has its /// own allowed_channels list (from the "slimmable" config field). On SetSlimmableSize(), /// maps the ratio to a channel count per array, extracts a weight subset, builds -/// modified LayerArrayParams, and reconstructs the WaveNet. +/// modified LayerArrayParams, and constructs a replacement WaveNet in a staging slot. +/// The next call to process() swaps the staged model into place so the real-time thread +/// never runs process() on a WaveNet that another thread is replacing. class SlimmableWavenet : public DSP, public SlimmableModel { public: @@ -53,11 +55,16 @@ class SlimmableWavenet : public DSP, public SlimmableModel nlohmann::json _condition_dsp_json; std::vector _full_weights; std::unique_ptr _active_model; + /// Built on SetSlimmableSize(); committed at the start of process(). + std::unique_ptr _staging_model; std::vector _current_channels; + std::vector _staging_channels; int _current_buffer_size = 0; double _current_sample_rate = 0.0; + std::unique_ptr _create_wavenet_for_channels(const std::vector& target_channels); void _rebuild_model(const std::vector& target_channels); + void _stage_rebuild_model(const std::vector& target_channels); }; // Config / registration From f632fb6437d1b2166c248babd02919440d90d12b Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 16:07:51 -0700 Subject: [PATCH 2/5] [REFACTOR] Improve SlimmableWavenet model handling with atomic operations - Replaced unique_ptr with shared_ptr for active and staging models to enable safe concurrent access. - Introduced StagedSlimModel struct to encapsulate model and channel information for atomic staging. - Updated model rebuilding and staging logic to utilize atomic operations, ensuring data-race-free transitions during processing. - Enhanced process, prewarm, and Reset methods to work with the new atomic staging mechanism, improving real-time performance. --- NAM/wavenet/slimmable.cpp | 39 ++++++++++++++++++++++----------------- NAM/wavenet/slimmable.h | 26 +++++++++++++++++++------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/NAM/wavenet/slimmable.cpp b/NAM/wavenet/slimmable.cpp index 7fc599b..09cd430 100644 --- a/NAM/wavenet/slimmable.cpp +++ b/NAM/wavenet/slimmable.cpp @@ -377,10 +377,9 @@ void SlimmableWavenet::_rebuild_model(const std::vector& target_channels) if (target_channels == _current_channels && _active_model) return; - _staging_model.reset(); - _staging_channels.clear(); + std::atomic_store_explicit(&_pending_staged, std::shared_ptr{}, std::memory_order_release); - _active_model = _create_wavenet_for_channels(target_channels); + _active_model = std::shared_ptr(_create_wavenet_for_channels(target_channels)); _current_channels = target_channels; if (_current_buffer_size > 0) @@ -391,27 +390,33 @@ void SlimmableWavenet::_stage_rebuild_model(const std::vector& target_chann { if (target_channels == _current_channels && _active_model) { - _staging_model.reset(); - _staging_channels.clear(); + std::atomic_store_explicit(&_pending_staged, std::shared_ptr{}, std::memory_order_release); return; } - if (_staging_model && target_channels == _staging_channels) - return; + if (auto pending = std::atomic_load_explicit(&_pending_staged, std::memory_order_acquire)) + { + if (pending->channels == target_channels) + return; + } - _staging_model = _create_wavenet_for_channels(target_channels); - _staging_channels = target_channels; + auto pack = std::make_shared(); + pack->model = std::shared_ptr(_create_wavenet_for_channels(target_channels)); + pack->channels = target_channels; if (_current_buffer_size > 0) - _staging_model->Reset(_current_sample_rate, _current_buffer_size); + pack->model->Reset(_current_sample_rate, _current_buffer_size); + + std::atomic_store_explicit(&_pending_staged, std::move(pack), std::memory_order_release); } void SlimmableWavenet::process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) { - if (_staging_model) + if (auto pack = std::atomic_exchange_explicit( + &_pending_staged, std::shared_ptr{}, std::memory_order_acq_rel)) { - _active_model = std::move(_staging_model); - _current_channels = std::move(_staging_channels); + _active_model = std::move(pack->model); + _current_channels = std::move(pack->channels); } if (_active_model) _active_model->process(input, output, num_frames); @@ -421,8 +426,8 @@ void SlimmableWavenet::prewarm() { if (_active_model) _active_model->prewarm(); - if (_staging_model) - _staging_model->prewarm(); + if (auto pending = std::atomic_load_explicit(&_pending_staged, std::memory_order_acquire)) + pending->model->prewarm(); } void SlimmableWavenet::Reset(const double sampleRate, const int maxBufferSize) @@ -431,8 +436,8 @@ void SlimmableWavenet::Reset(const double sampleRate, const int maxBufferSize) _current_buffer_size = maxBufferSize; if (_active_model) _active_model->Reset(sampleRate, maxBufferSize); - if (_staging_model) - _staging_model->Reset(sampleRate, maxBufferSize); + if (auto pending = std::atomic_load_explicit(&_pending_staged, std::memory_order_acquire)) + pending->model->Reset(sampleRate, maxBufferSize); } void SlimmableWavenet::SetSlimmableSize(const double val) diff --git a/NAM/wavenet/slimmable.h b/NAM/wavenet/slimmable.h index 6c11684..d88f540 100644 --- a/NAM/wavenet/slimmable.h +++ b/NAM/wavenet/slimmable.h @@ -19,9 +19,11 @@ namespace slimmable_wavenet /// Stores the full WaveNet LayerArrayParams and weights. Each layer array has its /// own allowed_channels list (from the "slimmable" config field). On SetSlimmableSize(), /// maps the ratio to a channel count per array, extracts a weight subset, builds -/// modified LayerArrayParams, and constructs a replacement WaveNet in a staging slot. -/// The next call to process() swaps the staged model into place so the real-time thread -/// never runs process() on a WaveNet that another thread is replacing. +/// modified LayerArrayParams, and constructs a replacement WaveNet published to an +/// staging slot via std::atomic_* on std::shared_ptr (release). process() exchanges that +/// slot (acquire/release) and +/// installs the new WaveNet before running DSP so another thread never frees the model +/// in use and the handoff is data-race-free in the C++ memory model. class SlimmableWavenet : public DSP, public SlimmableModel { public: @@ -54,11 +56,21 @@ class SlimmableWavenet : public DSP, public SlimmableModel bool _with_head; nlohmann::json _condition_dsp_json; std::vector _full_weights; - std::unique_ptr _active_model; - /// Built on SetSlimmableSize(); committed at the start of process(). - std::unique_ptr _staging_model; + /// Shared ownership so a staged model can be moved onto the active slot without + /// transferring from a concurrent writer. + std::shared_ptr _active_model; + + struct StagedSlimModel + { + std::shared_ptr model; + std::vector channels; + }; + /// Published with std::atomic_store_explicit (release); consumed with + /// std::atomic_exchange_explicit at the start of process() (acq_rel). Plain shared_ptr + /// plus the atomic_* free functions avoids libc++ rejecting std::atomic>. + std::shared_ptr _pending_staged; + std::vector _current_channels; - std::vector _staging_channels; int _current_buffer_size = 0; double _current_sample_rate = 0.0; From d4546057cbc170ef8bc5de54a5eb03bc8c23262d Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 18:19:17 -0700 Subject: [PATCH 3/5] [REFACTOR] Update SlimmableWavenet atomic operations for pending model handling - Refactored pending model management to use a consistent approach for both libc++ and other STL implementations. - Introduced private methods for atomic operations on the pending staged model, enhancing clarity and maintainability. - Updated model rebuilding and staging logic to utilize the new methods, ensuring data-race-free transitions during processing. --- NAM/wavenet/slimmable.cpp | 58 +++++++++++++++++++++++++++++++++------ NAM/wavenet/slimmable.h | 28 +++++++++++++------ 2 files changed, 70 insertions(+), 16 deletions(-) diff --git a/NAM/wavenet/slimmable.cpp b/NAM/wavenet/slimmable.cpp index 09cd430..c397914 100644 --- a/NAM/wavenet/slimmable.cpp +++ b/NAM/wavenet/slimmable.cpp @@ -291,6 +291,49 @@ bool is_full_size(const std::vector& params, const st } // anonymous namespace +#ifdef _LIBCPP_VERSION +void SlimmableWavenet::_pending_clear_release() +{ + std::atomic_store_explicit(&_pending_staged, std::shared_ptr{}, std::memory_order_release); +} + +std::shared_ptr SlimmableWavenet::_pending_load_acquire() const +{ + return std::atomic_load_explicit(&_pending_staged, std::memory_order_acquire); +} + +void SlimmableWavenet::_pending_store_release(std::shared_ptr p) +{ + std::atomic_store_explicit(&_pending_staged, std::move(p), std::memory_order_release); +} + +std::shared_ptr SlimmableWavenet::_pending_exchange_take_acq_rel() +{ + return std::atomic_exchange_explicit( + &_pending_staged, std::shared_ptr{}, std::memory_order_acq_rel); +} +#else +void SlimmableWavenet::_pending_clear_release() +{ + _pending_staged.store({}, std::memory_order_release); +} + +std::shared_ptr SlimmableWavenet::_pending_load_acquire() const +{ + return _pending_staged.load(std::memory_order_acquire); +} + +void SlimmableWavenet::_pending_store_release(std::shared_ptr p) +{ + _pending_staged.store(std::move(p), std::memory_order_release); +} + +std::shared_ptr SlimmableWavenet::_pending_exchange_take_acq_rel() +{ + return _pending_staged.exchange({}, std::memory_order_acq_rel); +} +#endif + // ============================================================================ // SlimmableWavenet // ============================================================================ @@ -377,7 +420,7 @@ void SlimmableWavenet::_rebuild_model(const std::vector& target_channels) if (target_channels == _current_channels && _active_model) return; - std::atomic_store_explicit(&_pending_staged, std::shared_ptr{}, std::memory_order_release); + _pending_clear_release(); _active_model = std::shared_ptr(_create_wavenet_for_channels(target_channels)); _current_channels = target_channels; @@ -390,11 +433,11 @@ void SlimmableWavenet::_stage_rebuild_model(const std::vector& target_chann { if (target_channels == _current_channels && _active_model) { - std::atomic_store_explicit(&_pending_staged, std::shared_ptr{}, std::memory_order_release); + _pending_clear_release(); return; } - if (auto pending = std::atomic_load_explicit(&_pending_staged, std::memory_order_acquire)) + if (auto pending = _pending_load_acquire()) { if (pending->channels == target_channels) return; @@ -407,13 +450,12 @@ void SlimmableWavenet::_stage_rebuild_model(const std::vector& target_chann if (_current_buffer_size > 0) pack->model->Reset(_current_sample_rate, _current_buffer_size); - std::atomic_store_explicit(&_pending_staged, std::move(pack), std::memory_order_release); + _pending_store_release(std::move(pack)); } void SlimmableWavenet::process(NAM_SAMPLE** input, NAM_SAMPLE** output, const int num_frames) { - if (auto pack = std::atomic_exchange_explicit( - &_pending_staged, std::shared_ptr{}, std::memory_order_acq_rel)) + if (auto pack = _pending_exchange_take_acq_rel()) { _active_model = std::move(pack->model); _current_channels = std::move(pack->channels); @@ -426,7 +468,7 @@ void SlimmableWavenet::prewarm() { if (_active_model) _active_model->prewarm(); - if (auto pending = std::atomic_load_explicit(&_pending_staged, std::memory_order_acquire)) + if (auto pending = _pending_load_acquire()) pending->model->prewarm(); } @@ -436,7 +478,7 @@ void SlimmableWavenet::Reset(const double sampleRate, const int maxBufferSize) _current_buffer_size = maxBufferSize; if (_active_model) _active_model->Reset(sampleRate, maxBufferSize); - if (auto pending = std::atomic_load_explicit(&_pending_staged, std::memory_order_acquire)) + if (auto pending = _pending_load_acquire()) pending->model->Reset(sampleRate, maxBufferSize); } diff --git a/NAM/wavenet/slimmable.h b/NAM/wavenet/slimmable.h index d88f540..57968a4 100644 --- a/NAM/wavenet/slimmable.h +++ b/NAM/wavenet/slimmable.h @@ -3,6 +3,12 @@ #include #include +#ifdef _LIBCPP_VERSION +// libc++: std::atomic> is not viable; staging uses deprecated atomic_* free functions. +#else +#include +#endif + #include "../dsp.h" #include "json.hpp" #include "../model_config.h" @@ -19,11 +25,10 @@ namespace slimmable_wavenet /// Stores the full WaveNet LayerArrayParams and weights. Each layer array has its /// own allowed_channels list (from the "slimmable" config field). On SetSlimmableSize(), /// maps the ratio to a channel count per array, extracts a weight subset, builds -/// modified LayerArrayParams, and constructs a replacement WaveNet published to an -/// staging slot via std::atomic_* on std::shared_ptr (release). process() exchanges that -/// slot (acquire/release) and -/// installs the new WaveNet before running DSP so another thread never frees the model -/// in use and the handoff is data-race-free in the C++ memory model. +/// modified LayerArrayParams, and constructs a replacement WaveNet published to a staging +/// slot (release). process() takes the slot (acquire/release) and installs the new WaveNet +/// before DSP. libc++ uses std::atomic_* free functions on shared_ptr; other STLs use +/// std::atomic>. class SlimmableWavenet : public DSP, public SlimmableModel { public: @@ -65,10 +70,12 @@ class SlimmableWavenet : public DSP, public SlimmableModel std::shared_ptr model; std::vector channels; }; - /// Published with std::atomic_store_explicit (release); consumed with - /// std::atomic_exchange_explicit at the start of process() (acq_rel). Plain shared_ptr - /// plus the atomic_* free functions avoids libc++ rejecting std::atomic>. +#ifdef _LIBCPP_VERSION + /// Staged model; synchronized via deprecated std::atomic_* overloads for shared_ptr only. std::shared_ptr _pending_staged; +#else + std::atomic> _pending_staged; +#endif std::vector _current_channels; int _current_buffer_size = 0; @@ -77,6 +84,11 @@ class SlimmableWavenet : public DSP, public SlimmableModel std::unique_ptr _create_wavenet_for_channels(const std::vector& target_channels); void _rebuild_model(const std::vector& target_channels); void _stage_rebuild_model(const std::vector& target_channels); + + void _pending_clear_release(); + std::shared_ptr _pending_load_acquire() const; + void _pending_store_release(std::shared_ptr p); + std::shared_ptr _pending_exchange_take_acq_rel(); }; // Config / registration From 5b9485320f8e1fff33f5682f1d563778db8fa30b Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 18:23:40 -0700 Subject: [PATCH 4/5] [FEATURE] Add option for using libc++ on Linux in CMake configuration - Introduced a new option, NAM_USE_LIBCXX_LINUX, to allow users to select libc++ for Linux builds. - Updated CMakeLists.txt to conditionally link against stdc++fs or libc++ based on the new option. - Enhanced compatibility for projects using Clang with libc++ on Linux, improving build flexibility. --- CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cab39e8..8f9fe79 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,17 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") endif() +option(NAM_USE_LIBCXX_LINUX "Use libc++ on Linux (Clang + libc++-dev packages; tests SlimmableWavenet _LIBCPP_VERSION path)" OFF) + if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") include_directories(SYSTEM /usr/local/include) elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") - link_libraries(stdc++fs) + if (NAM_USE_LIBCXX_LINUX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + # Clang links libc++/libc++abi; do not pull in libstdc++fs (libstdc++). + else() + link_libraries(stdc++fs) + endif() elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN) else() From 796b84341fbe47add83cb1c45d527580adfac7d2 Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Mon, 27 Apr 2026 18:29:13 -0700 Subject: [PATCH 5/5] Add GitHub Actions job to build and test with libc++ on Linux Exercises the _LIBCPP_VERSION code path; checkout v4 on both jobs. Made-with: Cursor --- .github/workflows/build.yml | 50 +++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 186026b..371f300 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,10 +5,10 @@ on: jobs: build-ubuntu: - name: Build Ubuntu + name: Build Ubuntu (libstdc++) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4 with: submodules: recursive @@ -55,3 +55,49 @@ jobs: ./build_inline/tools/benchmodel ./example_models/lstm.nam ./build_inline/tools/render ./example_models/wavenet.nam ./example_audio/input.wav ./example_audio/output.wav + build-ubuntu-libcxx: + name: Build Ubuntu (libc++) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + # If libc++-dev is removed or renamed on a future ubuntu-latest image, pin versioned + # packages (e.g. libc++-18-dev libc++abi-18-dev) or add the LLVM apt repository. + - name: Install Clang and libc++ + run: | + sudo apt-get update + sudo apt-get install -y clang libc++-dev libc++abi-dev + + - name: Create build directories + run: mkdir -p build_libcxx build_inline_libcxx + + - name: Build Tools + working-directory: ${{github.workspace}}/build_libcxx + env: + CXX: clang++ + run: | + cmake .. -DCMAKE_BUILD_TYPE=Debug -DNAM_USE_LIBCXX_LINUX=ON + cmake --build . -j4 + + - name: Build Tools (Inline GEMM) + working-directory: ${{github.workspace}}/build_inline_libcxx + env: + CXX: clang++ + run: | + cmake .. -DCMAKE_BUILD_TYPE=Debug -DNAM_USE_LIBCXX_LINUX=ON -DCMAKE_CXX_FLAGS="-DNAM_USE_INLINE_GEMM" + cmake --build . -j4 + + - name: Run tests + working-directory: ${{github.workspace}} + run: | + ./build_libcxx/tools/run_tests + ./build_libcxx/tools/benchmodel ./example_models/wavenet.nam + ./build_libcxx/tools/benchmodel ./example_models/lstm.nam + ./build_libcxx/tools/render ./example_models/wavenet.nam ./example_audio/input.wav ./example_audio/output.wav + ./build_inline_libcxx/tools/run_tests + ./build_inline_libcxx/tools/benchmodel ./example_models/wavenet.nam + ./build_inline_libcxx/tools/benchmodel ./example_models/lstm.nam + ./build_inline_libcxx/tools/render ./example_models/wavenet.nam ./example_audio/input.wav ./example_audio/output.wav +