std1.0.0-beta-7 SafeC docs

Source ml/rnn.sc

View in the repository

// SafeC Standard Library — Recurrent cell implementations (see rnn.h).
#pragma once
#include <std/ml/rnn.h>
#include <std/ml/tensor.h>
#include <std/ml/tensor.h>
#include <std/ml/activations.h>
#include <std/ml/activations.h>
#include <std/math.h>
#include <std/math.h>
#include <std/mem.h>

namespace std {

// Small deterministic pseudo-random fill (not a real RNG — this library's
// point is correct *math*, not trained weights; callers that need real
// initialization should overwrite '.data' themselves, e.g. by loading
// trained weights). Values stay in a small range to keep gate pre-
// activations well-behaved.
static &FloatTensor __rnn_init_weight(unsigned long rows, unsigned long cols, unsigned long seed) {
    &FloatTensor t = FloatTensor::new_2d(rows, cols, 0);
    unsafe {
        unsigned long i = 0UL;
        while (i < t->size) {
            unsigned long h = (i + 1UL) * 2654435761UL + seed * 40503UL;
            h = (h ^ (h >> 13UL)) * 2246822519UL;
            float v = ((float)(h % 2000UL) / (float)1000.0) - (float)1.0; // [-1, 1)
            t->data[i] = v * (float)0.3;
            i = i + 1UL;
        }
    }
    return t;
}

static &FloatTensor __rnn_zeros(unsigned long rows, unsigned long cols) {
    return FloatTensor::new_2d(rows, cols, 0);
}

static &FloatTensor __row_add4(&FloatTensor a, &FloatTensor b,
                                  &FloatTensor c, &FloatTensor d) {
    &FloatTensor ab = tensor_add(a, b);
    &FloatTensor cd = tensor_add(c, d);
    &FloatTensor out = tensor_add(ab, cd);
    ab->free(); cd->free();
    unsafe { free((unsigned char*)ab); free((unsigned char*)cd); }
    return out;
}

static &FloatTensor __one_minus(&FloatTensor z) {
    unsafe { tensor_host_write_raw_((FloatTensor*)z); }
    &FloatTensor out = z.zeros_like();
    unsafe {
        unsigned long i = 0UL;
        while (i < out->size) { out->data[i] = (float)1.0 - z->data[i]; i = i + 1UL; }
    }
    return out;
}

// ── Plain RNN ────────────────────────────────────────────────────────────────

struct RNNCell RNNCell::new(unsigned long inputSize, unsigned long hiddenSize) {
    struct RNNCell c;
    c.W_ih = __rnn_init_weight(inputSize, hiddenSize, 1UL);
    c.W_hh = __rnn_init_weight(hiddenSize, hiddenSize, 2UL);
    c.b_ih = __rnn_zeros(1UL, hiddenSize);
    c.b_hh = __rnn_zeros(1UL, hiddenSize);
    return c;
}

&FloatTensor RNNCell::forward(const &FloatTensor x_t, const &FloatTensor h_prev) const {
    unsafe {
        &FloatTensor xi = tensor_matmul(x_t, self->W_ih);
        &FloatTensor hh = tensor_matmul(h_prev, self->W_hh);
        &FloatTensor sum = __row_add4(xi, hh, self->b_ih, self->b_hh);
        &FloatTensor h_t = sum.tanh_fwd();
        xi->free(); hh->free(); sum->free();
        free((unsigned char*)xi); free((unsigned char*)hh); free((unsigned char*)sum);
        return h_t;
    }
}

void RNNCell::free() { self.drop(); }

void RNNCell::drop() {
    unsafe {
        self->W_ih->free(); self->W_hh->free(); self->b_ih->free(); self->b_hh->free();
        free((unsigned char*)self->W_ih); free((unsigned char*)self->W_hh);
        free((unsigned char*)self->b_ih); free((unsigned char*)self->b_hh);
    }
}

// ── GRU ──────────────────────────────────────────────────────────────────────

struct GRUCell GRUCell::new(unsigned long inputSize, unsigned long hiddenSize) {
    struct GRUCell c;
    c.W_ir = __rnn_init_weight(inputSize, hiddenSize, 10UL); c.W_hr = __rnn_init_weight(hiddenSize, hiddenSize, 11UL);
    c.b_ir = __rnn_zeros(1UL, hiddenSize); c.b_hr = __rnn_zeros(1UL, hiddenSize);
    c.W_iz = __rnn_init_weight(inputSize, hiddenSize, 12UL); c.W_hz = __rnn_init_weight(hiddenSize, hiddenSize, 13UL);
    c.b_iz = __rnn_zeros(1UL, hiddenSize); c.b_hz = __rnn_zeros(1UL, hiddenSize);
    c.W_in = __rnn_init_weight(inputSize, hiddenSize, 14UL); c.W_hn = __rnn_init_weight(hiddenSize, hiddenSize, 15UL);
    c.b_in = __rnn_zeros(1UL, hiddenSize); c.b_hn = __rnn_zeros(1UL, hiddenSize);
    return c;
}

&FloatTensor GRUCell::forward(const &FloatTensor x_t, const &FloatTensor h_prev) const {
    unsafe {
        &FloatTensor r_sum = __row_add4(tensor_matmul(x_t, self->W_ir), tensor_matmul(h_prev, self->W_hr),
                                           self->b_ir, self->b_hr);
        &FloatTensor r_t = r_sum.sigmoid_fwd();

        &FloatTensor z_sum = __row_add4(tensor_matmul(x_t, self->W_iz), tensor_matmul(h_prev, self->W_hz),
                                           self->b_iz, self->b_hz);
        &FloatTensor z_t = z_sum.sigmoid_fwd();

        &FloatTensor hn = tensor_matmul(h_prev, self->W_hn);
        &FloatTensor hn_b = tensor_add(hn, self->b_hn);
        &FloatTensor r_hn = tensor_mul(r_t, hn_b);
        &FloatTensor in_ = tensor_matmul(x_t, self->W_in);
        &FloatTensor in_b = tensor_add(in_, self->b_in);
        &FloatTensor n_sum = tensor_add(in_b, r_hn);
        &FloatTensor n_t = n_sum.tanh_fwd();

        &FloatTensor one_minus_z = __one_minus(z_t);
        &FloatTensor term1 = tensor_mul(one_minus_z, n_t);
        &FloatTensor term2 = tensor_mul(z_t, h_prev);
        &FloatTensor h_t = tensor_add(term1, term2);

        &FloatTensor garbage[13];
        garbage[0]=r_sum; garbage[1]=r_t; garbage[2]=z_sum; garbage[3]=z_t;
        garbage[4]=hn; garbage[5]=hn_b; garbage[6]=r_hn; garbage[7]=in_;
        garbage[8]=in_b; garbage[9]=n_sum; garbage[10]=n_t;
        garbage[11]=one_minus_z; garbage[12]=term1;
        int gi = 0;
        while (gi < 13) { garbage[gi]->free(); free((unsigned char*)garbage[gi]); gi = gi + 1; }
        term2->free(); free((unsigned char*)term2);
        return h_t;
    }
}

void GRUCell::free() { self.drop(); }

void GRUCell::drop() {
    unsafe {
        &FloatTensor all[12];
        all[0]=self->W_ir; all[1]=self->W_hr; all[2]=self->b_ir; all[3]=self->b_hr;
        all[4]=self->W_iz; all[5]=self->W_hz; all[6]=self->b_iz; all[7]=self->b_hz;
        all[8]=self->W_in; all[9]=self->W_hn; all[10]=self->b_in; all[11]=self->b_hn;
        int i = 0;
        while (i < 12) { all[i]->free(); free((unsigned char*)all[i]); i = i + 1; }
    }
}

// ── LSTM ─────────────────────────────────────────────────────────────────────

struct LSTMCell LSTMCell::new(unsigned long inputSize, unsigned long hiddenSize) {
    struct LSTMCell c;
    c.W_ii = __rnn_init_weight(inputSize, hiddenSize, 20UL); c.W_hi = __rnn_init_weight(hiddenSize, hiddenSize, 21UL);
    c.b_ii = __rnn_zeros(1UL, hiddenSize); c.b_hi = __rnn_zeros(1UL, hiddenSize);
    c.W_if = __rnn_init_weight(inputSize, hiddenSize, 22UL); c.W_hf = __rnn_init_weight(hiddenSize, hiddenSize, 23UL);
    c.b_if = __rnn_zeros(1UL, hiddenSize); c.b_hf = __rnn_zeros(1UL, hiddenSize);
    c.W_ig = __rnn_init_weight(inputSize, hiddenSize, 24UL); c.W_hg = __rnn_init_weight(hiddenSize, hiddenSize, 25UL);
    c.b_ig = __rnn_zeros(1UL, hiddenSize); c.b_hg = __rnn_zeros(1UL, hiddenSize);
    c.W_io = __rnn_init_weight(inputSize, hiddenSize, 26UL); c.W_ho = __rnn_init_weight(hiddenSize, hiddenSize, 27UL);
    c.b_io = __rnn_zeros(1UL, hiddenSize); c.b_ho = __rnn_zeros(1UL, hiddenSize);
    return c;
}

&FloatTensor LSTMCell::forward(const &FloatTensor x_t, const &FloatTensor h_prev, const &FloatTensor c_prev, &&FloatTensor c_out) const {
    unsafe {
        &FloatTensor i_sum = __row_add4(tensor_matmul(x_t, self->W_ii), tensor_matmul(h_prev, self->W_hi),
                                           self->b_ii, self->b_hi);
        &FloatTensor i_t = i_sum.sigmoid_fwd();
        &FloatTensor f_sum = __row_add4(tensor_matmul(x_t, self->W_if), tensor_matmul(h_prev, self->W_hf),
                                           self->b_if, self->b_hf);
        &FloatTensor f_t = f_sum.sigmoid_fwd();
        &FloatTensor g_sum = __row_add4(tensor_matmul(x_t, self->W_ig), tensor_matmul(h_prev, self->W_hg),
                                           self->b_ig, self->b_hg);
        &FloatTensor g_t = g_sum.tanh_fwd();
        &FloatTensor o_sum = __row_add4(tensor_matmul(x_t, self->W_io), tensor_matmul(h_prev, self->W_ho),
                                           self->b_io, self->b_ho);
        &FloatTensor o_t = o_sum.sigmoid_fwd();

        &FloatTensor fc = tensor_mul(f_t, c_prev);
        &FloatTensor ig = tensor_mul(i_t, g_t);
        &FloatTensor c_t = tensor_add(fc, ig);
        &FloatTensor tanh_c = c_t.tanh_fwd();
        &FloatTensor h_t = tensor_mul(o_t, tanh_c);

        &FloatTensor garbage[9];
        garbage[0]=i_sum; garbage[1]=i_t; garbage[2]=f_sum; garbage[3]=f_t;
        garbage[4]=g_sum; garbage[5]=g_t; garbage[6]=o_sum; garbage[7]=o_t;
        garbage[8]=tanh_c;
        int gi = 0;
        while (gi < 9) { garbage[gi]->free(); free((unsigned char*)garbage[gi]); gi = gi + 1; }
        fc->free(); free((unsigned char*)fc);
        ig->free(); free((unsigned char*)ig);

        *c_out = c_t;
        return h_t;
    }
}

void LSTMCell::free() { self.drop(); }

void LSTMCell::drop() {
    unsafe {
        &FloatTensor all[16];
        all[0]=self->W_ii; all[1]=self->W_hi; all[2]=self->b_ii; all[3]=self->b_hi;
        all[4]=self->W_if; all[5]=self->W_hf; all[6]=self->b_if; all[7]=self->b_hf;
        all[8]=self->W_ig; all[9]=self->W_hg; all[10]=self->b_ig; all[11]=self->b_hg;
        all[12]=self->W_io; all[13]=self->W_ho; all[14]=self->b_io; all[15]=self->b_ho;
        int i = 0;
        while (i < 16) { all[i]->free(); free((unsigned char*)all[i]); i = i + 1; }
    }
}

// ── xLSTM (sLSTM) ─────────────────────────────────────────────────────────────

struct XLSTMCell XLSTMCell::new(unsigned long inputSize, unsigned long hiddenSize) {
    struct XLSTMCell c;
    c.Wz = __rnn_init_weight(inputSize, hiddenSize, 30UL); c.Rz = __rnn_init_weight(hiddenSize, hiddenSize, 31UL);
    c.bz = __rnn_zeros(1UL, hiddenSize);
    c.Wi = __rnn_init_weight(inputSize, hiddenSize, 32UL); c.Ri = __rnn_init_weight(hiddenSize, hiddenSize, 33UL);
    c.bi = __rnn_zeros(1UL, hiddenSize);
    c.Wf = __rnn_init_weight(inputSize, hiddenSize, 34UL); c.Rf = __rnn_init_weight(hiddenSize, hiddenSize, 35UL);
    c.bf = __rnn_zeros(1UL, hiddenSize);
    c.Wo = __rnn_init_weight(inputSize, hiddenSize, 36UL); c.Ro = __rnn_init_weight(hiddenSize, hiddenSize, 37UL);
    c.bo = __rnn_zeros(1UL, hiddenSize);
    return c;
}

struct XLSTMState XLSTMCell::forward(const &FloatTensor x_t, const &XLSTMState prev) const {
    unsafe {
        &FloatTensor z_sum = __row_add4(tensor_matmul(x_t, self->Wz), tensor_matmul(prev->h, self->Rz),
                                           self->bz, self->bz.zeros_like());
        &FloatTensor z_t = z_sum.tanh_fwd();

        &FloatTensor zerob = self->bi.zeros_like();
        &FloatTensor i_raw = __row_add4(tensor_matmul(x_t, self->Wi), tensor_matmul(prev->h, self->Ri),
                                           self->bi, zerob);
        &FloatTensor zerob2 = self->bf.zeros_like();
        &FloatTensor f_raw = __row_add4(tensor_matmul(x_t, self->Wf), tensor_matmul(prev->h, self->Rf),
                                           self->bf, zerob2);
        &FloatTensor zerob3 = self->bo.zeros_like();
        &FloatTensor o_sum = __row_add4(tensor_matmul(x_t, self->Wo), tensor_matmul(prev->h, self->Ro),
                                           self->bo, zerob3);
        &FloatTensor o_t = o_sum.sigmoid_fwd();

        // Stabilizer: m_t = max(f_raw + m_prev, i_raw), elementwise
        // (broadcasting prev->m's single scalar across the hidden dim).
        unsigned long hiddenSize;
        hiddenSize = i_raw->shape[1];
        &FloatTensor m_t = FloatTensor::new_2d(1UL, hiddenSize, 0);
        &FloatTensor i_t = FloatTensor::new_2d(1UL, hiddenSize, 0);
        &FloatTensor f_t = FloatTensor::new_2d(1UL, hiddenSize, 0);
        float mPrevScalar = prev->m->data[0];
        unsigned long k = 0UL;
        while (k < hiddenSize) {
            float fRaw = f_raw->data[k];
            float iRaw = i_raw->data[k];
            float candidateF = fRaw + mPrevScalar;
            float m = (candidateF > iRaw) ? candidateF : iRaw;
            m_t->data[k] = m;
            i_t->data[k] = exp_f(iRaw - m);
            f_t->data[k] = exp_f(candidateF - m);
            k = k + 1UL;
        }

        &FloatTensor fc = tensor_mul(f_t, prev->c);
        &FloatTensor iz = tensor_mul(i_t, z_t);
        &FloatTensor c_t = tensor_add(fc, iz);

        &FloatTensor fnT = tensor_mul(f_t, prev->n);
        &FloatTensor n_t = tensor_add(fnT, i_t);

        // h_t = o_t * (c_t / n_t), elementwise divide
        &FloatTensor ratio = FloatTensor::new_2d(1UL, hiddenSize, 0);
        k = 0UL;
        while (k < hiddenSize) {
            float denom = n_t->data[k];
            ratio->data[k] = (denom != (float)0.0) ? (c_t->data[k] / denom) : (float)0.0;
            k = k + 1UL;
        }
        &FloatTensor h_t = tensor_mul(o_t, ratio);

        // m_t is stored as a [1,1] scalar (the *maximum* over the hidden
        // dim, matching the paper's scalar-per-timestep stabilizer) —
        // reduce the elementwise m_t computed above.
        &FloatTensor m_scalar = FloatTensor::new_2d(1UL, 1UL, 0);
        float mMax = m_t->data[0];
        k = 1UL;
        while (k < hiddenSize) { if (m_t->data[k] > mMax) mMax = m_t->data[k]; k = k + 1UL; }
        m_scalar->data[0] = mMax;

        &FloatTensor garbage[15];
        garbage[0]=z_sum; garbage[1]=zerob; garbage[2]=i_raw; garbage[3]=zerob2;
        garbage[4]=f_raw; garbage[5]=zerob3; garbage[6]=o_sum; garbage[7]=m_t;
        garbage[8]=i_t; garbage[9]=f_t; garbage[10]=fc; garbage[11]=iz;
        garbage[12]=fnT; garbage[13]=ratio; garbage[14]=z_t;
        int gi = 0;
        while (gi < 15) { garbage[gi]->free(); free((unsigned char*)garbage[gi]); gi = gi + 1; }

        struct XLSTMState next;
        next.h = h_t;
        next.c = c_t;
        next.n = n_t;
        next.m = m_scalar;
        return next;
    }
}

void XLSTMCell::free() { self.drop(); }

void XLSTMCell::drop() {
    unsafe {
        &FloatTensor all[12];
        all[0]=self->Wz; all[1]=self->Rz; all[2]=self->bz;
        all[3]=self->Wi; all[4]=self->Ri; all[5]=self->bi;
        all[6]=self->Wf; all[7]=self->Rf; all[8]=self->bf;
        all[9]=self->Wo; all[10]=self->Ro; all[11]=self->bo;
        int i = 0;
        while (i < 12) { all[i]->free(); free((unsigned char*)all[i]); i = i + 1; }
    }
}

void XLSTMState::free() { self.drop(); }

void XLSTMState::drop() {
    unsafe {
        self->h->free(); self->c->free(); self->n->free(); self->m->free();
        free((unsigned char*)self->h); free((unsigned char*)self->c); free((unsigned char*)self->n); free((unsigned char*)self->m);
    }
}

void RNNCell::collect_params(&Vec<&FloatTensor> out) const {
    out.push(self.W_ih);
    out.push(self.W_hh);
    out.push(self.b_ih);
    out.push(self.b_hh);
}

void RNNCell::collect_state(&Vec<&FloatTensor> out) const { self.collect_params(out); }

void GRUCell::collect_params(&Vec<&FloatTensor> out) const {
    out.push(self.W_ir);
    out.push(self.W_hr);
    out.push(self.b_ir);
    out.push(self.b_hr);
    out.push(self.W_iz);
    out.push(self.W_hz);
    out.push(self.b_iz);
    out.push(self.b_hz);
    out.push(self.W_in);
    out.push(self.W_hn);
    out.push(self.b_in);
    out.push(self.b_hn);
}

void GRUCell::collect_state(&Vec<&FloatTensor> out) const { self.collect_params(out); }

void LSTMCell::collect_params(&Vec<&FloatTensor> out) const {
    out.push(self.W_ii);
    out.push(self.W_hi);
    out.push(self.b_ii);
    out.push(self.b_hi);
    out.push(self.W_if);
    out.push(self.W_hf);
    out.push(self.b_if);
    out.push(self.b_hf);
    out.push(self.W_ig);
    out.push(self.W_hg);
    out.push(self.b_ig);
    out.push(self.b_hg);
    out.push(self.W_io);
    out.push(self.W_ho);
    out.push(self.b_io);
    out.push(self.b_ho);
}

void LSTMCell::collect_state(&Vec<&FloatTensor> out) const { self.collect_params(out); }

void XLSTMCell::collect_params(&Vec<&FloatTensor> out) const {
    out.push(self.Wz);
    out.push(self.Rz);
    out.push(self.bz);
    out.push(self.Wi);
    out.push(self.Ri);
    out.push(self.bi);
    out.push(self.Wf);
    out.push(self.Rf);
    out.push(self.bf);
    out.push(self.Wo);
    out.push(self.Ro);
    out.push(self.bo);
}

void XLSTMCell::collect_state(&Vec<&FloatTensor> out) const { self.collect_params(out); }

} // namespace std