use crate::nn::{
ops::dispatch::{gelu, matmul, relu, sigmoid, swish, tanh},
tensors::{Flatten, IntoWithGrad, StaticShape, WithGrad},
TensorFloat,
};
use box_closure::{
Align1, Align16, Align2, Align32, Align4, Align8, OpaqueFn, OpaqueFnMut, OpaqueFnOnce,
};
#[cfg(feature = "alloc")]
use alloc::boxed::Box;
mod model_def;
mod layers;
pub use self::layers::{
Activation, ActivationLayer, Collapse, CollapseLayer, Dense, DenseLayer, Layer, Softmax, SoftmaxLayer, Attention, AttentionLayer, Temporal, TemporalLayer,
};
pub mod test;
#[must_use]
pub fn asym_distr<const N: usize>(variance: f32, offset: usize) -> [TensorFloat; N] {
let f = |x: usize| {
(libm::sinf((x as f32) * variance + (offset as f32)) * variance) as TensorFloat
};
core::array::from_fn(f)
}
#[must_use]
pub fn adapt_lr(lr: (f32, f32, f32), loss: (f32, f32), mut panic: bool) -> (f32, f32, f32, bool) {
let (mut lr, mut min_lr, mut max_lr) = lr;
let (prev, loss) = loss;
if prev > loss {
panic = true;
} else if panic {
panic = false;
} else {
if ((loss - prev).abs() + crate::approx::F32_MIN_ERROR) < (lr as f32) {
if lr < min_lr {
lr *= 2.0;
} else {
lr /= 1.03; }
} else if lr < max_lr {
lr *= 1.01;
if lr > min_lr {
lr *= 1.01; } else if min_lr < max_lr {
max_lr /= 1.01;
} else {
min_lr /= 1.03;
}
} else {
max_lr *= 2.0;
min_lr /= 2.0;
lr *= 1.01;
}
}
(lr, min_lr, max_lr, panic)
}
#[must_use]
pub fn decay_lr(lr: (f32, f32, f32), loss: (f32, f32), mut panic: bool) -> (f32, f32, f32, bool) {
let (mut lr, min_lr, max_lr) = lr;
let (prev, loss) = loss;
let dif_loss = prev - loss;
if dif_loss >= 1.0 {
lr *= 1.0 / libm::powf(1.01, dif_loss);
} else if dif_loss > 0.0 {
lr *= 1.0 - (1.0 / libm::powf(2.0, 1.0 / dif_loss));
} else {
panic = true;
}
(lr, min_lr, max_lr, panic)
}
#[must_use]
pub fn decay_temp(temp: f32, scale: f32) -> f32 {
temp + (1.0 - temp) * scale
}
#[cfg(feature = "alloc")]
pub type BackFn<'a, In, Out> = Box<dyn Fn(In) -> Out + 'a>;
#[cfg(not(feature = "alloc"))]
pub type BackFn<'a, In, Out> = OpaqueFn<'a, In, Out, Align8<128>>;
#[cfg(not(feature = "dyntensor"))]
pub type Tensor<const D: usize, const N: usize> = crate::nn::tensors::Tensor<TensorFloat, N, D>;
#[cfg(feature = "dyntensor")]
pub type Tensor<const D: usize, const N: usize> = crate::nn::tensors::Tensor<TensorFloat>;
pub enum Backward<'a, const R: usize, const S0: usize, const S1: usize, const S2: usize> {
Unary(BackFn<'a, Tensor<R, S1>, Tensor<R, S2>>),
Binary(BackFn<'a, Tensor<R, S0>, (Tensor<R, S1>, Tensor<R, S2>)>),
}
pub trait ActivationFn {
fn kind() -> ActivationKind;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ActivationKind {
ReLU,
Sigmoid,
Tanh,
Swish,
GELU,
}
pub struct ReLU;
impl ActivationFn for ReLU {
fn kind() -> ActivationKind {
ActivationKind::ReLU
}
}
pub struct Sigmoid;
impl ActivationFn for Sigmoid {
fn kind() -> ActivationKind {
ActivationKind::Sigmoid
}
}
pub struct Tanh;
impl ActivationFn for Tanh {
fn kind() -> ActivationKind {
ActivationKind::Tanh
}
}
pub struct GELU;
impl ActivationFn for GELU {
fn kind() -> ActivationKind {
ActivationKind::GELU
}
}
pub struct Swish;
impl ActivationFn for Swish {
fn kind() -> ActivationKind {
ActivationKind::Swish
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Dataset<
const IN_RANK: usize,
const IN_SIZE: usize,
const OUT_RANK: usize,
const OUT_SIZE: usize,
> {
#[cfg(feature = "alloc")]
inputs: Box<WithGrad<Tensor<IN_RANK, IN_SIZE>>>,
#[cfg(feature = "alloc")]
targets: Box<Tensor<OUT_RANK, OUT_SIZE>>,
#[cfg(not(feature = "alloc"))]
inputs: WithGrad<Tensor<IN_RANK, IN_SIZE>>,
#[cfg(not(feature = "alloc"))]
targets: Tensor<OUT_RANK, OUT_SIZE>,
}
impl<const IR: usize, const IS: usize, const OR: usize, const OS: usize> Dataset<IR, IS, OR, OS> {
pub fn new<
F1: Flatten<IS, Flattened = [f32; IS]> + StaticShape<IR>,
F2: Flatten<OS, Flattened = [f32; OS]> + StaticShape<OR>,
>(
inputs: &F1,
targets: &F2,
) -> Self {
Self::from_parts(
*inputs.sliced_shape(),
inputs.flatten(),
*targets.sliced_shape(),
targets.flatten(),
)
}
#[must_use]
#[allow(clippy::unnecessary_cast)]
pub fn from_parts(
inputs_shape: [usize; IR],
inputs_data: [f32; IS],
targets_shape: [usize; OR],
targets_data: [f32; OS],
) -> Self {
let inputs_iter = {
#[cfg(feature = "f64")]
{
inputs_data.into_iter().map(TensorFloat::from)
}
#[cfg(not(feature = "f64"))]
{
inputs_data.into_iter()
}
};
let mut inputs_data = [0.0; IS];
for (i, val) in inputs_iter.enumerate() {
inputs_data[i] = val;
}
let targets_iter = {
#[cfg(feature = "f64")]
{
targets_data.into_iter().map(TensorFloat::from)
}
#[cfg(not(feature = "f64"))]
{
targets_data.into_iter()
}
};
let mut targets_data = [0.0; OS];
for (i, val) in targets_iter.enumerate() {
targets_data[i] = val;
}
let inputs_tensor = Tensor::<IR, IS>::new(&inputs_shape, &inputs_data);
let targets_tensor = Tensor::<OR, OS>::new(&targets_shape, &targets_data);
#[cfg(feature = "alloc")]
{
Self {
inputs: Box::new(inputs_tensor.with_grad()),
targets: Box::new(targets_tensor),
}
}
#[cfg(not(feature = "alloc"))]
{
Self {
inputs: inputs_tensor.with_grad(),
targets: targets_tensor,
}
}
}
#[must_use]
pub const fn inputs(&self) -> &WithGrad<Tensor<IR, IS>> {
&self.inputs
}
#[must_use]
pub const fn targets(&self) -> &Tensor<OR, OS> {
&self.targets
}
#[must_use]
pub const fn targets_mut(&mut self) -> &mut Tensor<OR, OS> {
&mut self.targets
}
pub const fn inputs_mut(&mut self) -> &mut WithGrad<Tensor<IR, IS>> {
&mut self.inputs
}
#[must_use]
pub const fn get_context(&self) -> &Context<IR, IS> {
unsafe {
&*(&raw const self.inputs).cast::<Context<IR, IS>>()
}
}
#[must_use]
pub fn clone_context(&self) -> Context<IR, IS> {
Context {
inputs: self.inputs.clone(),
}
}
#[must_use]
#[cfg_attr(not(feature = "alloc"), allow(clippy::missing_const_for_fn))]
pub fn into_context(self) -> Context<IR, IS> {
Context {
inputs: self.inputs,
}
}
}
#[repr(transparent)]
#[derive(Debug, Clone, PartialEq)]
pub struct Context<
const IN_RANK: usize,
const IN_SIZE: usize,
> {
#[cfg(feature = "alloc")]
inputs: Box<WithGrad<Tensor<IN_RANK, IN_SIZE>>>,
#[cfg(not(feature = "alloc"))]
inputs: WithGrad<Tensor<IN_RANK, IN_SIZE>>,
}
impl<const IR: usize, const IS: usize> Context<IR, IS> {
pub fn new<
F1: Flatten<IS, Flattened = [f32; IS]> + StaticShape<IR>,
>(
inputs: &F1,
) -> Self {
Self::from_parts(
*inputs.sliced_shape(),
inputs.flatten(),
)
}
#[must_use]
#[allow(clippy::unnecessary_cast)]
pub fn from_parts(
inputs_shape: [usize; IR],
inputs_data: [f32; IS],
) -> Self {
let inputs_iter = {
#[cfg(feature = "f64")]
{
inputs_data.into_iter().map(TensorFloat::from)
}
#[cfg(not(feature = "f64"))]
{
inputs_data.into_iter()
}
};
let mut inputs_data = [0.0; IS];
for (i, val) in inputs_iter.enumerate() {
inputs_data[i] = val;
}
let inputs_tensor = Tensor::<IR, IS>::new(&inputs_shape, &inputs_data);
#[cfg(feature = "alloc")]
{
Self {
inputs: Box::new(inputs_tensor.with_grad()),
}
}
#[cfg(not(feature = "alloc"))]
{
Self {
inputs: inputs_tensor.with_grad(),
}
}
}
#[must_use]
pub const fn raw(&self) -> &WithGrad<Tensor<IR, IS>> {
&self.inputs
}
pub const fn raw_mut(&mut self) -> &mut WithGrad<Tensor<IR, IS>> {
&mut self.inputs
}
}
pub trait Closure<Arg, Ret> {
fn invoke(&self, args: Arg) -> Ret;
}
pub trait ClosureMut<Arg, Ret> {
fn invoke(&mut self, args: Arg) -> Ret;
}
pub trait ClosureOnce<Arg, Ret> {
fn invoke(self, args: Arg) -> Ret;
}
macro_rules! __generate_opaque_closure_impl {
($struct:ident, $buf:ident) => {
impl<'a, A, R, const B: usize> Closure<A, R> for $struct<'a, A, R, $buf<B>> {
fn invoke(&self, args: A) -> R {
self.call(args)
}
}
};
}
macro_rules! __generate_opaque_mut_impl {
($struct:ident, $buf:ident) => {
impl<'a, A, R, const B: usize> ClosureMut<A, R> for $struct<'a, A, R, $buf<B>> {
fn invoke(&mut self, args: A) -> R {
self.call(args)
}
}
};
}
macro_rules! __generate_opaque_once_impl {
($struct:ident, $buf:ident) => {
impl<'a, A, R, const B: usize> ClosureOnce<A, R> for $struct<'a, A, R, $buf<B>> {
fn invoke(self, args: A) -> R {
self.call(args)
}
}
};
}
__generate_opaque_closure_impl!(OpaqueFn, Align1);
__generate_opaque_closure_impl!(OpaqueFn, Align2);
__generate_opaque_closure_impl!(OpaqueFn, Align4);
__generate_opaque_closure_impl!(OpaqueFn, Align8);
__generate_opaque_closure_impl!(OpaqueFn, Align16);
__generate_opaque_closure_impl!(OpaqueFn, Align32);
__generate_opaque_mut_impl!(OpaqueFnMut, Align1);
__generate_opaque_mut_impl!(OpaqueFnMut, Align2);
__generate_opaque_mut_impl!(OpaqueFnMut, Align4);
__generate_opaque_mut_impl!(OpaqueFnMut, Align8);
__generate_opaque_mut_impl!(OpaqueFnMut, Align16);
__generate_opaque_mut_impl!(OpaqueFnMut, Align32);
__generate_opaque_once_impl!(OpaqueFnOnce, Align1);
__generate_opaque_once_impl!(OpaqueFnOnce, Align2);
__generate_opaque_once_impl!(OpaqueFnOnce, Align4);
__generate_opaque_once_impl!(OpaqueFnOnce, Align8);
__generate_opaque_once_impl!(OpaqueFnOnce, Align16);
__generate_opaque_once_impl!(OpaqueFnOnce, Align32);
impl<A, R, F: ?Sized + Fn(A) -> R> Closure<A, R> for F {
fn invoke(&self, args: A) -> R {
self(args)
}
}
impl<A, R, F: ?Sized + FnMut(A) -> R> ClosureMut<A, R> for F {
fn invoke(&mut self, args: A) -> R {
self(args)
}
}
impl<A, R, F: FnOnce(A) -> R> ClosureOnce<A, R> for F {
fn invoke(self, args: A) -> R {
self(args)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_context_safety() {
let dataset: Dataset<2, 20, 1, 20> = Dataset::new(&[[5.0; 10]; 2], &[5.0; 20]);
let context;
{
context = dataset.get_context();
}
assert_eq!(context.raw(), dataset.inputs());
}
}