std1.0.0-beta-7 SafeC docs

Source ml/rnn.h

View in the repository

#pragma once
// SafeC Standard Library — Recurrent cells: RNN, GRU, LSTM, xLSTM.
//
// All vectors are row tensors: x_t is [1, inputSize], h_t/c_t are
// [1, hiddenSize]. Weight matrices are laid out so a plain
// tensor_matmul(x_t, W) gives the right shape directly (i.e. W is
// [inputSize, hiddenSize] or [hiddenSize, hiddenSize], the transpose of
// PyTorch's own [hiddenSize, inputSize] convention — chosen so this
// library's existing row-vector Tensor shape needs no extra transpose
// per step). Forward-only, like the rest of std/ml (see attention.h).
#include <std/ml/tensor.h>

#include <std/collections/vec.h>

namespace std {

// ── Plain RNN ────────────────────────────────────────────────────────────────
// h_t = tanh(x_t @ W_ih + h_{t-1} @ W_hh + b_ih + b_hh)
struct RNNCell {
    &FloatTensor W_ih; // [inputSize, hiddenSize]
    &FloatTensor W_hh; // [hiddenSize, hiddenSize]
    &FloatTensor b_ih; // [1, hiddenSize]
    &FloatTensor b_hh; // [1, hiddenSize]

    &FloatTensor forward(const &FloatTensor x_t, const &FloatTensor h_prev) const;
    // Parameterized (module.h): every weight tensor.
    void collect_params(&Vec<&FloatTensor> out) const;
    void collect_state(&Vec<&FloatTensor> out) const;
    void drop();
    void free() consume;   // = drop()

    static struct RNNCell new(unsigned long inputSize, unsigned long hiddenSize);
};

// ── GRU ──────────────────────────────────────────────────────────────────────
// r_t = sigmoid(x_t@W_ir + h@W_hr + b_ir + b_hr)          reset gate
// z_t = sigmoid(x_t@W_iz + h@W_hz + b_iz + b_hz)          update gate
// n_t = tanh(x_t@W_in + b_in + r_t * (h@W_hn + b_hn))     candidate
// h_t = (1 - z_t) * n_t + z_t * h_{t-1}
struct GRUCell {
    &FloatTensor W_ir; &FloatTensor W_hr; &FloatTensor b_ir; &FloatTensor b_hr;
    &FloatTensor W_iz; &FloatTensor W_hz; &FloatTensor b_iz; &FloatTensor b_hz;
    &FloatTensor W_in; &FloatTensor W_hn; &FloatTensor b_in; &FloatTensor b_hn;

    &FloatTensor forward(const &FloatTensor x_t, const &FloatTensor h_prev) const;
    // Parameterized (module.h): every weight tensor.
    void collect_params(&Vec<&FloatTensor> out) const;
    void collect_state(&Vec<&FloatTensor> out) const;
    void drop();
    void free() consume;   // = drop()

    static struct GRUCell new(unsigned long inputSize, unsigned long hiddenSize);
};

// ── LSTM ─────────────────────────────────────────────────────────────────────
// i_t = sigmoid(x_t@W_ii + h@W_hi + b_ii + b_hi)   input gate
// f_t = sigmoid(x_t@W_if + h@W_hf + b_if + b_hf)   forget gate
// g_t = tanh(x_t@W_ig + h@W_hg + b_ig + b_hg)      cell candidate
// o_t = sigmoid(x_t@W_io + h@W_ho + b_io + b_ho)   output gate
// c_t = f_t * c_{t-1} + i_t * g_t
// h_t = o_t * tanh(c_t)
struct LSTMCell {
    &FloatTensor W_ii; &FloatTensor W_hi; &FloatTensor b_ii; &FloatTensor b_hi;
    &FloatTensor W_if; &FloatTensor W_hf; &FloatTensor b_if; &FloatTensor b_hf;
    &FloatTensor W_ig; &FloatTensor W_hg; &FloatTensor b_ig; &FloatTensor b_hg;
    &FloatTensor W_io; &FloatTensor W_ho; &FloatTensor b_io; &FloatTensor b_ho;

    // Writes the new hidden state's *tensor* as the return value; '*c_out'
    // receives the new cell state (caller-owned, like the return value).
    &FloatTensor forward(const &FloatTensor x_t, const &FloatTensor h_prev, const &FloatTensor c_prev, &&FloatTensor c_out) const;
    // Parameterized (module.h): every weight tensor.
    void collect_params(&Vec<&FloatTensor> out) const;
    void collect_state(&Vec<&FloatTensor> out) const;
    void drop();
    void free() consume;   // = drop()

    static struct LSTMCell new(unsigned long inputSize, unsigned long hiddenSize);
};

// ── xLSTM (sLSTM variant, Beck et al. 2024) ─────────────────────────────────
// The scalar-memory sLSTM cell — xLSTM's headline idea is *exponential*
// gating (replacing LSTM's sigmoid input/forget gates with unbounded
// exp(), which can represent much larger gate values) made numerically
// stable with a running log-domain stabilizer state 'm_t', plus a
// normalizer state 'n_t' that keeps the exponential gates' magnitude in
// check without clipping. This is the scalar-memory (sLSTM) member of
// the xLSTM family specifically — mLSTM (matrix-memory, fully
// parallelizable) is a separate, larger cell not implemented here.
//   z_t = tanh(x_t@Wz + h@Rz + bz)                    cell input
//   i_raw = x_t@Wi + h@Ri + bi;  f_raw = x_t@Wf + h@Rf + bf   (pre-activation logits)
//   o_t = sigmoid(x_t@Wo + h@Ro + bo)
//   m_t = max(f_raw + m_{t-1}, i_raw)                 stabilizer (log-domain)
//   i_t = exp(i_raw - m_t);  f_t = exp(f_raw + m_{t-1} - m_t)
//   c_t = f_t * c_{t-1} + i_t * z_t
//   n_t = f_t * n_{t-1} + i_t
//   h_t = o_t * (c_t / n_t)
struct XLSTMCell {
    &FloatTensor Wz; &FloatTensor Rz; &FloatTensor bz;
    &FloatTensor Wi; &FloatTensor Ri; &FloatTensor bi;
    &FloatTensor Wf; &FloatTensor Rf; &FloatTensor bf;
    &FloatTensor Wo; &FloatTensor Ro; &FloatTensor bo;

    struct XLSTMState forward(const &FloatTensor x_t, const &XLSTMState prev) const;
    // Parameterized (module.h): every weight tensor.
    void collect_params(&Vec<&FloatTensor> out) const;
    void collect_state(&Vec<&FloatTensor> out) const;
    void drop();
    void free() consume;   // = drop()

    static struct XLSTMCell new(unsigned long inputSize, unsigned long hiddenSize);
};

struct XLSTMState {
    &FloatTensor h;
    &FloatTensor c;
    &FloatTensor n;
    &FloatTensor m; // [1,1] scalar stabilizer (broadcast across hidden dim)
    void drop();

    void free() consume;   // = drop()
};

} // namespace std