Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
fbb24ad
backprop init
ealmloff Mar 15, 2026
235d246
transformer example
ealmloff Mar 15, 2026
0528e0c
faster
ealmloff Mar 15, 2026
8b6839a
nanochat
ealmloff Mar 15, 2026
1ba33f6
wip
ealmloff Mar 17, 2026
bbb9cd3
drawing model + coop matmul
ealmloff Mar 18, 2026
c3b8b6d
data augmentation
ealmloff Mar 18, 2026
28bd897
better dataset
ealmloff Mar 18, 2026
8c25d67
wip
ealmloff Mar 18, 2026
9228712
multible outputs
ealmloff Mar 18, 2026
f3002a9
refactor
ealmloff Mar 18, 2026
973ffc7
ui close to working
ealmloff Mar 19, 2026
9fe42a9
ui working
ealmloff Mar 19, 2026
f315984
tab autocomplete
ealmloff Mar 19, 2026
a5d8f57
remove output files
ealmloff Mar 21, 2026
d6ac082
remove more output files
ealmloff Mar 21, 2026
f192867
move composite ops from core to fusor
ealmloff Mar 21, 2026
df2e0cf
remove pairwise
ealmloff Mar 21, 2026
14cf293
fix nanochat app drawing
ealmloff Mar 21, 2026
f58241d
more data
ealmloff Mar 21, 2026
986cc19
better token encoding and more dynamic synth data
ealmloff Mar 22, 2026
38fbb20
the start of a testing framework
ealmloff Mar 22, 2026
4b727e9
improved fuzzing cpu mode
ealmloff Mar 22, 2026
602ad86
more tests
ealmloff Mar 23, 2026
ca570c7
fix fuzzing tests
ealmloff Mar 23, 2026
3cc7595
fix strided softmax
ealmloff Mar 23, 2026
8428e87
fix clippy
ealmloff Mar 23, 2026
fb16b73
move more tests into conformance
ealmloff Mar 24, 2026
b2df02a
more fuzzy tests
ealmloff Mar 24, 2026
bcdcca3
more fuzzing
ealmloff Mar 24, 2026
1969d1e
pull out more common ops
ealmloff Mar 24, 2026
21abc15
expose layer_norm_last_dim_fused
ealmloff Mar 25, 2026
ef4f983
move bench
ealmloff Mar 25, 2026
1abdae6
more ops
ealmloff Mar 25, 2026
2079595
fix warnings
ealmloff Mar 25, 2026
05d3a65
Merge remote-tracking branch 'floneum/main' into backprop
ealmloff Jun 2, 2026
261ee9a
more cleanup
ealmloff Jul 1, 2026
39532da
Merge remote-tracking branch 'floneum/main' into backprop
ealmloff Jul 1, 2026
02a4378
drop demo app crates and revert incidental deltas vs main
ealmloff Jul 2, 2026
db9cb90
gate chunked submits to training-scale graphs, drop redundant first-t…
ealmloff Jul 2, 2026
5cdab21
shrink autograd surface: drop unused ops, dedup per-rank backward rules
ealmloff Jul 2, 2026
efa105c
compile broadcast and restride backwards through the graph
ealmloff Jul 2, 2026
9a2b6e8
express index and embedding backwards as graph ops, defer qmatmul deq…
ealmloff Jul 2, 2026
847389b
add gradient tests for untested backward ops
ealmloff Jul 2, 2026
74413f7
split autograd into module directory
ealmloff Jul 2, 2026
d3be0d3
fix clippy in autograd module
ealmloff Jul 2, 2026
e1b967d
restore main's exact cargo lock
ealmloff Jul 2, 2026
f3e91ec
support tensor-tensor comparison ops on gpu
ealmloff Jul 2, 2026
e467f6b
run every autograd test on both cpu and gpu
ealmloff Jul 2, 2026
6ffd801
add end-to-end xor classifier training test
ealmloff Jul 2, 2026
97ee879
default api supports backprop
ealmloff Jul 3, 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 fusor-ml/core/src/compute_graph/resolve/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,10 @@ impl Resolver {
if !self.execution_graph.contains_node(node_idx) {
return;
}
let inner_idx = self.execution_graph[node_idx].inner_idx;
if self.targets.contains(&inner_idx) {
return;
}
if self
.execution_graph
.neighbors_directed(node_idx, petgraph::Direction::Outgoing)
Expand Down
79 changes: 74 additions & 5 deletions fusor-ml/core/src/compute_graph/resolve/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,7 +534,24 @@ impl Resolver {
let mut dispatch_index = 0usize;
let mut command_index = 0usize;
let dispatches_per_pass = dispatches_per_pass(total_kernels);
let dispatches_per_submit = dispatches_per_submit(total_kernels, device.backend());
let wait_after_chunk_submit = device.backend() == wgpu::Backend::Metal;
let mut dispatches_in_submit = 0usize;
let mut encoder_has_commands = false;
while command_index < commands.len() {
if encoder_has_commands && dispatches_in_submit >= dispatches_per_submit {
let next_encoder = resolver_command_encoder(&device);
let ready_encoder = std::mem::replace(&mut command_encoder, next_encoder);
submit_resolver_encoder(
&device,
ready_encoder,
wait_after_chunk_submit,
host_trace,
&mut host_profile,
);
encoder_has_commands = false;
dispatches_in_submit = 0;
}
match &commands[command_index] {
CommandRecord::CopyBuffer(copy) => {
command_encoder.copy_buffer_to_buffer(
Expand All @@ -544,6 +561,7 @@ impl Resolver {
copy.destination_offset,
copy.size,
);
encoder_has_commands = true;
command_index += 1;
}
CommandRecord::Dispatch(_) => {
Expand All @@ -568,6 +586,8 @@ impl Resolver {
record.dispatch.run(&mut pass);
}
dispatch_index += 1;
dispatches_in_submit += 1;
encoder_has_commands = true;
command_index += 1;
continue;
}
Expand All @@ -582,6 +602,9 @@ impl Resolver {
if pass_dispatches >= dispatches_per_pass {
break;
}
if dispatches_in_submit >= dispatches_per_submit {
break;
}
let CommandRecord::Dispatch(record) = &commands[command_index] else {
break;
};
Expand All @@ -595,8 +618,10 @@ impl Resolver {
pass.write_timestamp(query_set, (dispatch_index * 2 + 1) as u32);
}
dispatch_index += 1;
dispatches_in_submit += 1;
command_index += 1;
pass_dispatches += 1;
encoder_has_commands = true;
}
}
}
Expand Down Expand Up @@ -625,11 +650,13 @@ impl Resolver {
let tail_result = tail(&data, &mut command_encoder);

// Submit any remaining commands.
let submit_start = host_trace.then(Instant::now);
device.wgpu_queue().submit(Some(command_encoder.finish()));
if let Some(start) = submit_start {
host_profile.submit += start.elapsed();
}
submit_resolver_encoder(
&device,
command_encoder,
false,
host_trace,
&mut host_profile,
);
#[cfg(not(target_arch = "wasm32"))]
{
if let Some((_, _, readback_buffer, raw_query_size)) = &query_resources {
Expand Down Expand Up @@ -726,3 +753,45 @@ fn dispatches_per_pass(total_kernels: usize) -> usize {

if total_kernels >= 1024 { 1 } else { usize::MAX }
}

fn dispatches_per_submit(total_kernels: usize, backend: wgpu::Backend) -> usize {
if let Ok(value) = std::env::var("FUSOR_RESOLVE_DISPATCHES_PER_SUBMIT")
&& let Ok(parsed) = value.parse::<usize>()
&& parsed > 0
{
return parsed;
}

// Chunked submits exist to bound in-flight memory on giant training
// graphs; small inference graphs must stay a single submit.
if backend == wgpu::Backend::Metal && total_kernels >= 1024 {
256
} else {
usize::MAX
}
}

fn resolver_command_encoder(device: &crate::Device) -> wgpu::CommandEncoder {
device
.wgpu_device()
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Resolver Encoder"),
})
}

fn submit_resolver_encoder(
device: &crate::Device,
command_encoder: wgpu::CommandEncoder,
wait: bool,
host_trace: bool,
host_profile: &mut ResolveHostProfile,
) {
let submit_start = host_trace.then(Instant::now);
device.wgpu_queue().submit(Some(command_encoder.finish()));
if wait {
device.poll_wait();
}
if let Some(start) = submit_start {
host_profile.submit += start.elapsed();
}
}
25 changes: 25 additions & 0 deletions fusor-ml/core/src/nary_direct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,31 @@ fn emit_function(function: &NaryFunction, values: &mut [(ValueTile, DataTypeEnum
values[1].0.clone(),
function.output_type,
),
NaryOp::Less => values[0].0.clone().compare(
tile_ir::TileCompareOp::Lt,
values[1].0.clone(),
function.output_type,
),
NaryOp::Equal => values[0].0.clone().compare(
tile_ir::TileCompareOp::Eq,
values[1].0.clone(),
function.output_type,
),
NaryOp::NotEqual => values[0].0.clone().compare(
tile_ir::TileCompareOp::Ne,
values[1].0.clone(),
function.output_type,
),
NaryOp::Greater => values[0].0.clone().compare(
tile_ir::TileCompareOp::Gt,
values[1].0.clone(),
function.output_type,
),
NaryOp::GreaterEqual => values[0].0.clone().compare(
tile_ir::TileCompareOp::Ge,
values[1].0.clone(),
function.output_type,
),
NaryOp::AddConst(scalar) => values[0].0.clone().binary(
tile_ir::TileBinaryOp::Add,
tile_literal(scalar).cast_to(values[0].1),
Expand Down
10 changes: 10 additions & 0 deletions fusor-ml/core/src/nary_wise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ pub(crate) enum NaryOp {
/// index-dependent masking (e.g. composed causal attention compares the
/// kv position against the query position).
LessEqual,
/// Binary `a < b` comparison producing 1/0 in the output type.
Less,
/// Binary `a == b` comparison producing 1/0 in the output type.
Equal,
/// Binary `a != b` comparison producing 1/0 in the output type.
NotEqual,
/// Binary `a > b` comparison producing 1/0 in the output type.
Greater,
/// Binary `a >= b` comparison producing 1/0 in the output type.
GreaterEqual,
AddConst(NaryScalar),
SubConst(NaryScalar),
RSubConst(NaryScalar),
Expand Down
50 changes: 49 additions & 1 deletion fusor-ml/core/src/pair_wise.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::ops::{Add, Div, Mul, Sub};

use crate::{
Tensor,
DataType, Tensor,
nary_wise::{NaryFunction, NaryOp},
};

Expand Down Expand Up @@ -81,3 +81,51 @@ macro_rules! impl_pairwise_method {
}

impl_pairwise_method!(pow, NaryOp::Pow, "pow", pow_, |a, b| a.pow(&b));

/// Emit a tensor-tensor comparison method producing 1/0 in the output type
/// `D`, mirroring the scalar comparisons in `element_wise`.
macro_rules! impl_pairwise_cmp {
($(#[$meta:meta])* $method:ident, $nary_op:expr, $op_name:literal) => {
impl Tensor {
$(#[$meta])*
pub fn $method<D: DataType>(&self, other: &Self) -> Tensor {
assert_eq!(self.datatype(), other.datatype());
self.binary_nary(
other,
NaryFunction::binary(
Some($op_name.to_string()),
$nary_op,
self.datatype(),
other.datatype(),
D::DATA_TYPE,
),
)
}
}
};
}

impl_pairwise_cmp!(
/// Element-wise `self == other` returning 1 for true and 0 for false.
eq_tensor, NaryOp::Equal, "eq"
);
impl_pairwise_cmp!(
/// Element-wise `self != other` returning 1 for true and 0 for false.
ne_tensor, NaryOp::NotEqual, "ne"
);
impl_pairwise_cmp!(
/// Element-wise `self < other` returning 1 for true and 0 for false.
lt_tensor, NaryOp::Less, "lt"
);
impl_pairwise_cmp!(
/// Element-wise `self <= other` returning 1 for true and 0 for false.
lte_tensor, NaryOp::LessEqual, "lte"
);
impl_pairwise_cmp!(
/// Element-wise `self > other` returning 1 for true and 0 for false.
gt_tensor, NaryOp::Greater, "gt"
);
impl_pairwise_cmp!(
/// Element-wise `self >= other` returning 1 for true and 0 for false.
gte_tensor, NaryOp::GreaterEqual, "gte"
);
Loading
Loading