Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ members = [
"components/spider-utils",
"examples/huntsman/complex/tasks",
"examples/huntsman/complex/types",
"examples/huntsman/nn/core",
"examples/huntsman/nn/tasks",
"tests/huntsman/em-runtime",
"tests/huntsman/integration-test-tasks",
"tests/huntsman/task-executor",
Expand Down
9 changes: 9 additions & 0 deletions examples/huntsman/nn/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "huntsman-nn-core"
version = "0.1.0"
edition = "2024"
publish = false

[lib]
name = "huntsman_nn_core"
path = "src/lib.rs"
160 changes: 160 additions & 0 deletions examples/huntsman/nn/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Pure neuron math for the Spider end-to-end neural-network test workload.
//!
//! A dense-layer neuron computes `activation(weighted_sum(inputs) + bias)` over a fixed fan-in of
//! 25 scalar `double` inputs.

/// The fixed neuron fan-in: each neuron consumes exactly this many scalar inputs.
pub const NUM_INPUTS: usize = 25;

/// The fixed per-input weights, one per input position. Deterministic values calculated as
/// (`WEIGHTS[k] = (k + 1) * 0.01 * (-1)^k`), alternating in sign starting positive.
pub const WEIGHTS: [f64; NUM_INPUTS] = [
0.01, -0.02, 0.03, -0.04, 0.05, -0.06, 0.07, -0.08, 0.09, -0.10, 0.11, -0.12, 0.13, -0.14,
0.15, -0.16, 0.17, -0.18, 0.19, -0.20, 0.21, -0.22, 0.23, -0.24, 0.25,
];
Comment on lines +10 to +14

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added negative values.


/// The fixed bias added to the weighted sum before the activation.
pub const BIAS: f64 = 0.5;

/// # Returns
///
/// The rectified-linear activation `max(0.0, x)`.
#[must_use]
pub const fn relu(x: f64) -> f64 {
f64::max(0.0, x)
}

/// # Returns
///
/// The logistic sigmoid activation `1.0 / (1.0 + exp(-x))`.
#[must_use]
pub fn sigmoid(x: f64) -> f64 {
1.0 / (1.0 + f64::exp(-x))
}

/// # Returns
///
/// The identity activation `x`.
#[must_use]
pub const fn identity(x: f64) -> f64 {
x
}

/// # Returns
///
/// The rectified-linear activation of the weighted sum of `inputs` plus [`BIAS`].
#[must_use]
pub fn dense_relu(inputs: &[f64; NUM_INPUTS]) -> f64 {
relu(weighted_sum(inputs))
}

/// # Returns
///
/// The logistic sigmoid of the weighted sum of `inputs` plus [`BIAS`].
#[must_use]
pub fn dense_sigmoid(inputs: &[f64; NUM_INPUTS]) -> f64 {
sigmoid(weighted_sum(inputs))
}

/// # Returns
///
/// The weighted sum of `inputs` plus [`BIAS`], unchanged by the activation.
#[must_use]
pub fn dense_identity(inputs: &[f64; NUM_INPUTS]) -> f64 {
identity(weighted_sum(inputs))
}

/// # Returns
///
/// The weighted sum `sum(WEIGHTS[k] * inputs[k]) + BIAS`.
fn weighted_sum(inputs: &[f64; NUM_INPUTS]) -> f64 {
let mut acc = BIAS;
for (w, x) in WEIGHTS.iter().zip(inputs.iter()) {
acc += w * x;
}
acc
}

#[cfg(test)]
mod tests {
use super::*;

/// Relative-tolerance float equality used to compare hand-computed and computed values.
fn assert_approx_eq(actual: f64, expected: f64) {
let diff = (actual - expected).abs();
let tol = 1.0e-12_f64 * (1.0 + expected.abs());
assert!(
diff <= tol,
"actual={actual}, expected={expected}, diff={diff}, tol={tol}",
);
}

#[test]
fn test_relu() {
assert_approx_eq(relu(-1.0), 0.0);
assert_approx_eq(relu(0.0), 0.0);
assert_approx_eq(relu(2.5), 2.5);
}

#[test]
fn test_sigmoid() {
assert_approx_eq(sigmoid(0.0), 0.5);
assert_approx_eq(sigmoid(100.0), 1.0);
assert_approx_eq(sigmoid(-100.0), 0.0);
assert!(sigmoid(-1.0) < sigmoid(0.0));
assert!(sigmoid(0.0) < sigmoid(1.0));
}

#[test]
fn test_identity() {
assert_approx_eq(identity(-3.0), -3.0);
assert_approx_eq(identity(0.0), 0.0);
assert_approx_eq(identity(7.25), 7.25);
}

#[test]
fn test_weighted_sum_all_zero_inputs_equals_bias() {
let inputs = [0.0_f64; NUM_INPUTS];
assert_approx_eq(weighted_sum(&inputs), BIAS);
}

#[test]
fn test_weighted_sum_all_one_inputs() {
let inputs = [1.0_f64; NUM_INPUTS];
assert_approx_eq(weighted_sum(&inputs), 0.63);
}

#[test]
fn test_dense_relu() {
let zero = [0.0_f64; NUM_INPUTS];
assert_approx_eq(dense_relu(&zero), 0.5);

let ones = [1.0_f64; NUM_INPUTS];
assert_approx_eq(dense_relu(&ones), 0.63);

// Negative weighted sum (large negative inputs) clamps to 0 under relu.
let neg = [-1000.0_f64; NUM_INPUTS];
assert_approx_eq(dense_relu(&neg), 0.0);
}

#[test]
fn test_dense_sigmoid() {
let zero = [0.0_f64; NUM_INPUTS];
assert_approx_eq(dense_sigmoid(&zero), sigmoid(BIAS));

let ones = [1.0_f64; NUM_INPUTS];
assert_approx_eq(dense_sigmoid(&ones), sigmoid(0.63));
}

#[test]
fn test_dense_identity() {
let zero = [0.0_f64; NUM_INPUTS];
assert_approx_eq(dense_identity(&zero), 0.5);

let ones = [1.0_f64; NUM_INPUTS];
assert_approx_eq(dense_identity(&ones), 0.63);

let neg = [-1000.0_f64; NUM_INPUTS];
assert_approx_eq(dense_identity(&neg), weighted_sum(&neg));
}
}
18 changes: 18 additions & 0 deletions examples/huntsman/nn/tasks/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "huntsman-nn-tasks"
version = "0.1.0"
edition = "2024"
publish = false

[lib]
crate-type = ["cdylib"]
name = "nn"
path = "src/lib.rs"

[dependencies]
huntsman-nn-core = { path = "../core" }
serde = { version = "1.0.228", features = ["derive"] }
spider-tdl = {
path = "../../../../components/spider-tdl",
features = ["derive"]
}
124 changes: 124 additions & 0 deletions examples/huntsman/nn/tasks/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//! Reference TDL package: dense-neuron computation.

#![allow(clippy::too_many_arguments)]

mod task_decl {
use spider_tdl::TaskContext;
use spider_tdl::TdlError;
use spider_tdl::r#std::double;
use spider_tdl::task;

#[task(name = "neuron::dense_relu")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming nn -> neuron:

  • The package name is already nn.
  • These tasks are actual simulated "neuron"s

pub fn dense_relu(
_ctx: TaskContext,
x0: double,
x1: double,
x2: double,
x3: double,
x4: double,
x5: double,
x6: double,
x7: double,
x8: double,
x9: double,
x10: double,
x11: double,
x12: double,
x13: double,
x14: double,
x15: double,
x16: double,
x17: double,
x18: double,
x19: double,
x20: double,
x21: double,
x22: double,
x23: double,
x24: double,
) -> Result<double, TdlError> {
Ok(huntsman_nn_core::dense_relu(&[
x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18,
x19, x20, x21, x22, x23, x24,
]))
}

#[task(name = "neuron::dense_sigmoid")]
pub fn dense_sigmoid(
_ctx: TaskContext,
x0: double,
x1: double,
x2: double,
x3: double,
x4: double,
x5: double,
x6: double,
x7: double,
x8: double,
x9: double,
x10: double,
x11: double,
x12: double,
x13: double,
x14: double,
x15: double,
x16: double,
x17: double,
x18: double,
x19: double,
x20: double,
x21: double,
x22: double,
x23: double,
x24: double,
) -> Result<double, TdlError> {
Ok(huntsman_nn_core::dense_sigmoid(&[
x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18,
x19, x20, x21, x22, x23, x24,
]))
}

#[task(name = "neuron::dense_identity")]
pub fn dense_identity(
_ctx: TaskContext,
x0: double,
x1: double,
x2: double,
x3: double,
x4: double,
x5: double,
x6: double,
x7: double,
x8: double,
x9: double,
x10: double,
x11: double,
x12: double,
x13: double,
x14: double,
x15: double,
x16: double,
x17: double,
x18: double,
x19: double,
x20: double,
x21: double,
x22: double,
x23: double,
x24: double,
) -> Result<double, TdlError> {
Ok(huntsman_nn_core::dense_identity(&[
x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18,
x19, x20, x21, x22, x23, x24,
]))
}
}

spider_tdl::register_tdl_package! {
package_name: "nn",
tasks: [
task_decl::dense_relu,
task_decl::dense_sigmoid,
task_decl::dense_identity,
],
}
Loading