SFNPKG/1 --- path: capsule.toml [capsule] name = "sfn/layers" version = "0.1.3" description = "Neural network layer building blocks for Sailfin" [dependencies] [capabilities] required = ["gpu"] [build] entry = "src/mod.sfn" kind = "library" --- path: src/mod.sfn // sfn/layers — Neural network layer building blocks for Sailfin. // // Provides composable layer types for constructing neural networks. // Each layer stores its parameters and implements a forward pass. // // GPU-accelerated dispatch is planned but not yet implemented. // All operations currently execute on the CPU. // // Usage: // import { Linear, linear, forward } from "layers" // import { from_array } from "tensor" // // let layer = linear(3, 2); // 3 inputs, 2 outputs // let input = from_array([1, 2, 3], [1, 3]); // let output = forward(layer, input); // ---- Layer types ---- struct Linear { weights: float[]; // flat [out_features, in_features] row-major biases: float[]; // [out_features] in_features: int; out_features: int; } struct ReLULayer { size: int; // expected input size (for documentation) } struct SequentialLayer { // A sequence of layers applied in order. // For now, stores linear layers only. Generic layer types // will be supported once Sailfin's type system supports // trait-based dispatch (interface Layer { fn forward(...) }). linears: Linear[]; relu_after: boolean[]; // whether to apply ReLU after each linear } // ---- Constructors ---- fn linear(in_features: int, out_features: int) -> Linear { // Create a linear (fully connected) layer with zero-initialized parameters. // In practice, weights should be initialized with small random values. // Random initialization will use the `rand` effect once available. let weight_count = in_features * out_features; let mut weights: float[] = []; let mut biases: float[] = []; let mut i: int = 0; loop { if i >= weight_count { break; } weights.push(0.0); i += 1; } i = 0; loop { if i >= out_features { break; } biases.push(0.0); i += 1; } return Linear { weights: weights, biases: biases, in_features: in_features, out_features: out_features }; } fn relu_layer(size: int) -> ReLULayer { return ReLULayer { size: size }; } fn sequential(linears: Linear[], relu_after: boolean[]) -> SequentialLayer { return SequentialLayer { linears: linears, relu_after: relu_after }; } // ---- Forward pass ---- fn forward_linear(layer: Linear, input: float[]) -> float[] { // Forward pass for a linear layer: output = input * W^T + bias // input: [in_features], output: [out_features] let mut output: float[] = []; let mut o: int = 0; loop { if o >= layer.out_features { break; } let mut val: float = layer.biases[o]; let mut j: int = 0; loop { if j >= layer.in_features { break; } val = val + input[j] * layer.weights[o * layer.in_features + j]; j += 1; } output.push(val); o += 1; } return output; } fn forward_relu(input: float[]) -> float[] { // Apply ReLU activation: max(0, x) for each element. let mut output: float[] = []; let mut i: int = 0; loop { if i >= input.length { break; } if input[i] > 0 { output.push(input[i]); } else { output.push(0.0); } i += 1; } return output; } fn forward_sequential(model: SequentialLayer, input: float[]) -> float[] { // Forward pass through a sequential model. // relu_after must have the same length as linears. let mut current: float[] = input; let mut i: int = 0; let n = model.linears.length; loop { if i >= n { break; } current = forward_linear(model.linears[i], current); if i < model.relu_after.length && model.relu_after[i] { current = forward_relu(current); } i += 1; } return current; } // ---- Parameter access ---- fn parameter_count(layer: Linear) -> int { // Total trainable parameters (weights + biases). return layer.in_features * layer.out_features + layer.out_features; }