Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
50cc292
Done implementation.
LinZhihao-723 Jul 8, 2026
0d73a2f
Merge branch 'main' into test-driver-impl
LinZhihao-723 Jul 8, 2026
26d6456
toml lint.
LinZhihao-723 Jul 8, 2026
f9bffb7
Merge branch 'test-driver-impl' of https://github.com/LinZhihao-723/s…
LinZhihao-723 Jul 8, 2026
efb2d57
Merge branch 'main' into test-driver-impl
sitaowang1998 Jul 8, 2026
c233c24
Merge branch 'main' into test-driver-impl
LinZhihao-723 Jul 8, 2026
e1aad84
Linter fix.
LinZhihao-723 Jul 8, 2026
560c7a1
Merge branch 'test-driver-impl' of github.com:LinZhihao-723/spider in…
sitaowang1998 Jul 9, 2026
3567013
Update comment.
LinZhihao-723 Jul 10, 2026
33edc61
Merge branch 'test-driver-impl' of github.com:LinZhihao-723/spider in…
sitaowang1998 Jul 10, 2026
3e37ef0
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 10, 2026
4eaf570
Add nn network
sitaowang1998 Jul 10, 2026
b3312ee
Reorder
sitaowang1998 Jul 10, 2026
a8d5cee
Add nn e2e test
sitaowang1998 Jul 10, 2026
d386d25
Remove e2e from unit test task
sitaowang1998 Jul 10, 2026
37db769
Fix style
sitaowang1998 Jul 10, 2026
b9854fe
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 10, 2026
4873ff1
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 10, 2026
22fa3f0
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 12, 2026
e083d12
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 12, 2026
63fdf44
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 13, 2026
4aac6e5
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 14, 2026
93f912a
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 14, 2026
b558c2b
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 15, 2026
9059e59
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 15, 2026
5e98861
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 15, 2026
48c3a7f
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 19, 2026
dfbdd18
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 19, 2026
d3e0d9d
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 22, 2026
5628462
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 23, 2026
76771d2
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 28, 2026
f1b286d
Merge branch 'main' into nn-test-driver
sitaowang1998 Jul 30, 2026
0a24773
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 5, 2026
785dfd0
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 7, 2026
2adf570
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 8, 2026
fada347
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 11, 2026
f8b2295
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 12, 2026
45d13b8
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 13, 2026
36de57d
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 18, 2026
d12a733
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 18, 2026
354aa03
Merge branch 'main' into nn-test-driver
sitaowang1998 Aug 18, 2026
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
4 changes: 4 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion taskfiles/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ tasks:
"{{.G_TDL_PACKAGES_DIR}}/complex/libcomplex.so"
cp "{{.G_RUST_RELEASE_DIR}}/libintegration_test_tasks.so" \
"{{.G_TDL_PACKAGES_DIR}}/integration_test_tasks/libintegration_test_tasks.so"
cargo nextest run --all --all-features --run-ignored all --release
cargo nextest run --all --all-features --run-ignored all --release \
-E 'not (package(e2e) & kind(test))'
- |-
for f in ${SPIDER_TEST_INSTRUMENT_OUTPUT_DIR}/*; do
if [ -f "$f" ]; then
Expand Down
4 changes: 4 additions & 0 deletions tests/huntsman/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ publish = false

[dependencies]
anyhow = { workspace = true }
huntsman-nn-core = { path = "../../../examples/huntsman/nn/core" }
rand = { workspace = true }
rmp-serde = { workspace = true }
serde = { workspace = true }
spider-client = { workspace = true }
spider-core = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync", "time"] }
Expand Down
3 changes: 3 additions & 0 deletions tests/huntsman/e2e/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! End-to-end integration-test harness for the huntsman suites.

pub mod nn;
pub mod payload_serde;
pub mod test_driver;
mod types;

pub use payload_serde::*;
pub use test_driver::SpiderTestDriver;
pub use types::*;
11 changes: 11 additions & 0 deletions tests/huntsman/e2e/src/nn/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Self-contained neural-network model for the end-to-end test.
//!
//! [`NeuralNetwork`] builds a layered `neuron::dense_*` task graph and reproduces it in-process via
//! [`NeuralNetwork::simulate`].

mod network;
mod neuron;
mod wiring;

pub use network::NeuralNetwork;
pub use neuron::Neuron;
179 changes: 179 additions & 0 deletions tests/huntsman/e2e/src/nn/network.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//! The neural-network model: a layered topology of `neuron::dense_*` neurons whose Spider
//! [`TaskGraph`] and in-process simulation describe the same DAG.

use huntsman_nn_core::NUM_INPUTS;
use rand::SeedableRng;
use rand::rngs::StdRng;
use spider_core::task::DataTypeDescriptor;
use spider_core::task::TaskDescriptor;
use spider_core::task::TaskGraph;
use spider_core::task::TaskIndex;
use spider_core::task::TaskInputOutputIndex;
use spider_core::task::TdlContext;
use spider_core::task::ValueTypeDescriptor;

use crate::nn::Neuron;
use crate::nn::wiring;

/// A randomly-wired, layered neural network of `neuron::dense_*` neurons.
pub struct NeuralNetwork {
/// The layers in layer order.
layers: Vec<Layer>,
}

impl NeuralNetwork {
/// Factory function.
///
/// Validates `layer_specs` via [`wiring::validate`] and generates the inner-layer fan-in
/// wiring deterministically from `seed`.
///
/// # Returns
///
/// The newly created [`NeuralNetwork`] on success.
///
/// # Errors
///
/// Returns an error if:
///
/// * Forwards [`wiring::validate`]'s return values on failure.
pub fn new(layer_specs: Vec<(usize, Neuron)>, seed: u64) -> anyhow::Result<Self> {
let sizes: Vec<usize> = layer_specs.iter().map(|(size, _)| *size).collect();
wiring::validate(&sizes)?;
let mut rng = StdRng::seed_from_u64(seed);
let fan_ins = wiring::build_wiring(&sizes, &mut rng);
let layers = layer_specs
.into_iter()
.zip(fan_ins)
.map(|((neuron_count, activation), fan_in)| Layer {
neuron_count,
activation,
fan_in,
})
.collect();
Ok(Self { layers })
}

/// # Returns
///
/// The number of graph inputs.
#[must_use]
pub fn num_graph_inputs(&self) -> usize {
self.layers[0].neuron_count * NUM_INPUTS
}

/// Builds the Spider [`TaskGraph`] for this network.
///
/// # Returns
///
/// The [`TaskGraph`] for this network on success.
///
/// # Errors
///
/// Returns an error if:
///
/// * Forwards [`TaskGraph::new`]'s return values on failure.
/// * Forwards [`TaskGraph::insert_task`]'s return values on failure.
pub fn to_task_graph(&self) -> anyhow::Result<TaskGraph> {
let float64 = DataTypeDescriptor::Value(ValueTypeDescriptor::float64());
let mut graph = TaskGraph::new(None, None)?;
let first = &self.layers[0];
let mut prev_layer: Vec<TaskIndex> = Vec::with_capacity(first.neuron_count);

for _ in 0..first.neuron_count {
let task_idx = graph.insert_task(TaskDescriptor {
tdl_context: TdlContext {
package: PACKAGE.to_owned(),
task_func: first.activation.task_name().to_owned(),
},
execution_policy: None,
inputs: vec![float64.clone(); NUM_INPUTS],
outputs: vec![float64.clone()],
input_sources: None,
})?;
prev_layer.push(task_idx);
}

for layer in self.layers.iter().skip(1) {
let mut curr_layer = Vec::with_capacity(layer.neuron_count);
for j in 0..layer.neuron_count {
let input_sources: Vec<TaskInputOutputIndex> = layer.fan_in[j]
.iter()
.map(|&src| TaskInputOutputIndex {
task_idx: prev_layer[src],
position: 0,
})
.collect();
let task_idx = graph.insert_task(TaskDescriptor {
tdl_context: TdlContext {
package: PACKAGE.to_owned(),
task_func: layer.activation.task_name().to_owned(),
},
execution_policy: None,
inputs: vec![float64.clone(); NUM_INPUTS],
outputs: vec![float64.clone()],
input_sources: Some(input_sources),
})?;
curr_layer.push(task_idx);
}
prev_layer = curr_layer;
}

Ok(graph)
}

/// Computes the network's outputs from graph inputs.
///
/// # Returns
///
/// The network's outputs on success.
///
/// # Errors
///
/// Returns an error if:
///
/// * [`anyhow::Error`] if `inputs` length is not [`Self::num_graph_inputs`].
pub fn simulate(&self, inputs: &[f64]) -> anyhow::Result<Vec<f64>> {
let expected = self.num_graph_inputs();
anyhow::ensure!(
inputs.len() == expected,
"expected {expected} graph inputs, got {}",
inputs.len(),
);

let first = &self.layers[0];
let mut layer_outputs: Vec<f64> = (0..first.neuron_count)
.map(|i| {
let start = i * NUM_INPUTS;
let mut neuron_inputs = [0.0_f64; NUM_INPUTS];
neuron_inputs.copy_from_slice(&inputs[start..start + NUM_INPUTS]);
first.activation.evaluate_func()(&neuron_inputs)
})
.collect();

for layer in self.layers.iter().skip(1) {
layer_outputs = (0..layer.neuron_count)
.map(|i| {
let neuron_inputs: [f64; NUM_INPUTS] =
std::array::from_fn(|j| layer_outputs[layer.fan_in[i][j]]);
layer.activation.evaluate_func()(&neuron_inputs)
})
.collect();
}

Ok(layer_outputs)
}
}

/// Name of the TDL package supplying the `neuron::dense_*` tasks.
const PACKAGE: &str = "nn";

/// One layer of the network: its neuron count, activation, and per-neuron fan-in.
struct Layer {
/// Number of neurons in this layer.
neuron_count: usize,
/// Activation applied by every neuron in this layer.
activation: Neuron,
/// Per-neuron fan-in, listing previous-layer output indices feeding each neuron.
/// Empty for layer 0, which reads graph inputs directly.
fan_in: Vec<Vec<usize>>,
}
46 changes: 46 additions & 0 deletions tests/huntsman/e2e/src/nn/neuron.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//! Activation functions for the end-to-end neural-network test workload.
//!
//! Each [`Neuron`] pairs a Spider `neuron::dense_*` task with the in-process
//! `huntsman_nn_core::dense_*` evaluation function so the task graph and [`super::NeuralNetwork`]'s
//! simulation share one source of truth.

use huntsman_nn_core::NUM_INPUTS;

/// A dense-layer neuron activation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Neuron {
/// Rectified-linear activation.
Relu,

/// Logistic-sigmoid activation.
Sigmoid,

/// Identity (no-op) activation.
Identity,
}

impl Neuron {
/// # Returns
///
/// The `neuron::dense_*` task function name that evaluates this activation.
#[must_use]
pub const fn task_name(self) -> &'static str {
match self {
Self::Relu => "neuron::dense_relu",
Self::Sigmoid => "neuron::dense_sigmoid",
Self::Identity => "neuron::dense_identity",
}
}

/// # Returns
///
/// The `huntsman_nn_core::dense_*` function that evaluates this activation.
#[must_use]
pub fn evaluate_func(self) -> fn(&[f64; NUM_INPUTS]) -> f64 {
match self {
Self::Relu => huntsman_nn_core::dense_relu,
Self::Sigmoid => huntsman_nn_core::dense_sigmoid,
Self::Identity => huntsman_nn_core::dense_identity,
}
}
}
Loading
Loading