memlnaut-nisps/nisps/ml/warm_start.hpp
monkey-w1n5t0n b6819fd26f feat(wasm)!: P2.2 — nisps_ml_create honours dims; runtime-shaped browser MLP + reshape
Operator-approved ABI change (P2 stop-point). The WASM MLP is now
MLPCore<DynamicStorage>:

- nisps_ml_create(input, output, hidden[3], n, seed) honours its args;
  non-positive/null fall back to the historical 32→[10,14,18]→126, so
  every pre-P2 caller (manifold, worker, parity harness) stays
  bit-identical. Invalid/oversized dims (>4096) → null.
- NEW nisps_ml_reshape(ml, in, out, hidden, n, spread): fresh net at the
  new dims, warm-started via nisps/ml/warm_start.hpp (overlapping region
  copied; rest keeps spread init); feedback controller re-created (state
  resets — reset-on-reshape modal is the front-end contract). Failure
  leaves the old net untouched.
- nisps_ml_describe(ml, out): takes the handle; null reports defaults.
- FeedbackController got the same storage split: algorithms in
  FeedbackControllerCore<FbStorage>; FixedFeedbackStorage keeps firmware/
  tests source-identical via the old alias; DynamicFeedbackStorage (one
  arena) sizes to the runtime net. Firmware .text unchanged (122692).
- MLHandle: per-instance scratch vectors; dropped the dead 2MB
  batch_out_scratch.
- TS: types.ts decls (+_nisps_ml_reshape), wasm-iml re-describes the
  created instance, worker carries a shape-contract note for P2.3.

Verified: ctest 4/4 incl. new warm-start grow/shrink test; reshape ABI
smoke (dims honoured, overlap survives, invalid rejected, outputs
bounded); parity PASS unchanged (2.4e-7); lint clean; manifold 9 unit +
20 e2e green; firmware .text 122692 (+0.30% vs pre-P2 baseline).
2026-07-14 03:38:06 +02:00

65 lines
2.5 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// nisps/ml/warm_start.hpp — copy overlapping weights between two MLPs of
// (possibly) different shapes.
//
// Used by the runtime-reshape path (one-core-engine-refactor P2): reshape =
// construct a NEW instance at the new dimensions, then warm-start it by
// copying every weight/bias whose (layer, node, input) coordinate exists in
// BOTH shapes. Weights outside the overlap keep the destination's fresh
// initialisation. Deterministic, allocation-free, works across storage
// policies (fixed→dynamic, dynamic→dynamic, fixed→fixed).
//
// Row-major layout per layer: w[node * fan_in + j]. The overlap is the
// top-left submatrix min(fan_out) × min(fan_in) plus the bias prefix
// min(fan_out).
#pragma once
#include <cstddef>
#include <span>
#include "../core/perf.hpp"
namespace nisps::ml {
namespace detail {
template <std::size_t L, typename DstMLP, typename SrcMLP>
NISPS_FORCE_INLINE void warm_start_copy_layer(DstMLP& dst, const SrcMLP& src) noexcept {
const std::size_t src_in = src.template fan_in_l<L>();
const std::size_t src_out = src.template fan_out_l<L>();
const std::size_t dst_in = dst.template fan_in_l<L>();
const std::size_t dst_out = dst.template fan_out_l<L>();
const std::size_t n_in = (src_in < dst_in) ? src_in : dst_in;
const std::size_t n_out = (src_out < dst_out) ? src_out : dst_out;
std::span<const float> sw = src.template weights_l<L>();
std::span<float> dw = dst.template weights_l<L>();
for (std::size_t node = 0; node < n_out; ++node) {
const std::size_t src_row = node * src_in;
const std::size_t dst_row = node * dst_in;
for (std::size_t j = 0; j < n_in; ++j) {
dw[dst_row + j] = sw[src_row + j];
}
}
std::span<const float> sb = src.template biases_l<L>();
std::span<float> db = dst.template biases_l<L>();
for (std::size_t node = 0; node < n_out; ++node) {
db[node] = sb[node];
}
}
} // namespace detail
// Copy the overlapping region of every layer from `src` into `dst`. Both
// must expose the MLP storage surface (fan_in_l/fan_out_l/weights_l/
// biases_l) — i.e. any MLPCore instantiation.
template <typename DstMLP, typename SrcMLP>
inline void warm_start_copy(DstMLP& dst, const SrcMLP& src) noexcept {
detail::warm_start_copy_layer<0u>(dst, src);
detail::warm_start_copy_layer<1u>(dst, src);
detail::warm_start_copy_layer<2u>(dst, src);
detail::warm_start_copy_layer<3u>(dst, src);
}
} // namespace nisps::ml