use super::{
avg_pool2d::AvgPool2dNode, batch_norm::BatchNormNode, binary::BinaryNode, clip::ClipNode,
concat::ConcatNode, constant::ConstantNode, conv1d::Conv1dNode, conv2d::Conv2dNode,
conv_transpose_2d::ConvTranspose2dNode, dropout::DropoutNode, gather::GatherNode,
global_avg_pool::GlobalAvgPoolNode, linear::LinearNode, matmul::MatmulNode,
max_pool2d::MaxPool2dNode, reshape::ReshapeNode, unary::UnaryNode,
};
use crate::burn::{BurnImports, Scope, Type};
use burn::backend::NdArray;
use burn::record::PrecisionSettings;
use proc_macro2::TokenStream;
use serde::Serialize;
pub type SerializationBackend = NdArray<f32>;
pub trait NodeCodegen<PS: PrecisionSettings>: std::fmt::Debug {
fn input_types(&self) -> Vec<Type>;
fn output_types(&self) -> Vec<Type>;
fn forward(&self, scope: &mut Scope, node_position: usize) -> TokenStream;
fn into_node(self) -> Node<PS>;
fn register_imports(&self, _imports: &mut BurnImports) {}
fn field_type(&self) -> Option<Type> {
None
}
fn field_init(&self, _with_record: bool) -> Option<TokenStream> {
None
}
fn field_serialize<S: serde::Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
panic!("Serialization should be implemented when field_type is not None.");
}
}
#[derive(Debug, Clone)]
pub enum Node<PS: PrecisionSettings> {
AvgPool2d(AvgPool2dNode),
BatchNorm(BatchNormNode<PS>),
Binary(BinaryNode),
Clip(ClipNode),
Concat(ConcatNode),
Constant(ConstantNode<PS>),
Conv1d(Conv1dNode<PS>),
Conv2d(Conv2dNode<PS>),
ConvTranspose2d(ConvTranspose2dNode<PS>),
Dropout(DropoutNode),
Gather(GatherNode),
GlobalAvgPool(GlobalAvgPoolNode),
Linear(LinearNode<PS>),
Matmul(MatmulNode),
MaxPool2d(MaxPool2dNode),
Reshape(ReshapeNode),
Unary(UnaryNode),
}
macro_rules! match_all {
($self:expr, $func:expr) => {{
#[allow(clippy::redundant_closure_call)]
match $self {
Node::AvgPool2d(node) => $func(node),
Node::BatchNorm(node) => $func(node),
Node::Binary(node) => $func(node),
Node::Clip(node) => $func(node),
Node::Concat(node) => $func(node),
Node::Constant(node) => $func(node),
Node::Conv1d(node) => $func(node),
Node::Conv2d(node) => $func(node),
Node::ConvTranspose2d(node) => $func(node),
Node::Dropout(node) => $func(node),
Node::Gather(node) => $func(node),
Node::GlobalAvgPool(node) => $func(node),
Node::Linear(node) => $func(node),
Node::Matmul(node) => $func(node),
Node::MaxPool2d(node) => $func(node),
Node::Reshape(node) => $func(node),
Node::Unary(node) => $func(node),
}
}};
}
impl<PS: PrecisionSettings> Serialize for Node<PS> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.field_serialize(serializer)
}
}
impl<PS: PrecisionSettings> Node<PS> {
pub fn name(&self) -> &str {
match self {
Node::AvgPool2d(_) => "avg_pool2d",
Node::BatchNorm(_) => "batch_norm",
Node::Binary(binary) => binary.binary_type.as_str(),
Node::Concat(_) => "concat",
Node::Clip(_) => "clip",
Node::Constant(_) => "constant",
Node::Conv1d(_) => "conv1d",
Node::Conv2d(_) => "conv2d",
Node::ConvTranspose2d(_) => "conv_transpose2d",
Node::Dropout(_) => "dropout",
Node::Gather(_) => "gather",
Node::GlobalAvgPool(_) => "global_avg_pool",
Node::Linear(_) => "linear",
Node::Matmul(_) => "matmul",
Node::MaxPool2d(_) => "max_pool2d",
Node::Reshape(_) => "reshape",
Node::Unary(unary) => unary.kind.as_str(),
}
}
}
impl<PS: PrecisionSettings> NodeCodegen<PS> for Node<PS> {
fn output_types(&self) -> Vec<Type> {
match_all!(self, NodeCodegen::<PS>::output_types)
}
fn input_types(&self) -> Vec<Type> {
match_all!(self, NodeCodegen::<PS>::input_types)
}
fn forward(&self, scope: &mut Scope, node_position: usize) -> TokenStream {
match_all!(self, |node| NodeCodegen::<PS>::forward(
node,
scope,
node_position
))
}
fn field_type(&self) -> Option<Type> {
match_all!(self, NodeCodegen::<PS>::field_type)
}
fn field_init(&self, with_record: bool) -> Option<TokenStream> {
match_all!(self, |node| NodeCodegen::<PS>::field_init(
node,
with_record
))
}
fn register_imports(&self, imports: &mut BurnImports) {
match_all!(self, |node| NodeCodegen::<PS>::register_imports(
node, imports
))
}
fn into_node(self) -> Node<PS> {
self
}
fn field_serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match_all!(self, |node| NodeCodegen::<PS>::field_serialize(
node, serializer
))
}
}
#[cfg(test)]
pub(crate) mod tests {
use crate::burn::{
graph::BurnGraph,
node::{conv2d::Conv2dNode, matmul::MatmulNode, test::assert_tokens, NodeCodegen},
BurnImports, TensorType,
};
use burn::{
nn::conv::Conv2dConfig, nn::PaddingConfig2d, record::FullPrecisionSettings, tensor::Data,
};
use proc_macro2::TokenStream;
use quote::quote;
#[track_caller]
pub(crate) fn one_node_graph<T: NodeCodegen<FullPrecisionSettings> + Clone + 'static>(
node_gen: T,
forward: TokenStream,
input_names: Vec<String>,
output_names: Vec<String>,
) {
let mut graph = BurnGraph::<FullPrecisionSettings>::default();
graph.register(node_gen.clone());
graph.register_input_output(input_names, output_names);
let mut imports = BurnImports::default();
node_gen.register_imports(&mut imports);
let imports = imports.codegen();
let expected = quote! {
#imports
#[derive(Module, Debug)]
pub struct Model<B: Backend> {
phantom: core::marker::PhantomData<B>,
}
impl<B: Backend> Model <B> {
#[allow(unused_variables)]
pub fn new_with(record: ModelRecord<B>) -> Self {
Self {
phantom: core::marker::PhantomData,
}
}
#[allow(clippy::let_and_return, clippy::approx_constant)]
#forward
}
};
assert_tokens(graph.codegen(), expected);
}
#[test]
fn test_codegen_two_nodes() {
let mut graph = BurnGraph::<FullPrecisionSettings>::default();
graph.register(MatmulNode::new(
TensorType::new_float("tensor1", 4),
TensorType::new_float("tensor2", 4),
TensorType::new_float("tensor3", 4),
));
graph.register(Conv2dNode::new(
"conv2d",
TensorType::new_float("tensor3", 4),
TensorType::new_float("tensor4", 4),
Data::from([2.]).serialize(),
None,
Conv2dConfig::new([3, 3], [3, 3]).with_padding(PaddingConfig2d::Valid),
));
graph.register_input_output(
vec!["tensor1".to_string(), "tensor2".to_string()],
vec!["tensor4".to_string()],
);
let expected = quote! {
use burn::{
module::Module,
tensor::{backend::Backend, Tensor},
};
use burn::nn::conv::Conv2dConfig;
use burn::nn::conv::Conv2d;
use burn::nn::PaddingConfig2d;
#[derive(Module, Debug)]
pub struct Model <B: Backend> {
conv2d: Conv2d<B>,
phantom: core::marker::PhantomData<B>,
}
impl<B: Backend> Model <B> {
#[allow(unused_variables)]
pub fn new_with(record: ModelRecord<B>) -> Self {
let conv2d = Conv2dConfig::new([3, 3], [3, 3])
.with_stride([1, 1])
.with_padding(PaddingConfig2d::Valid)
.with_dilation([1, 1])
.with_groups(1)
.with_bias(true)
.init_with(record.conv2d);
Self {
conv2d,
phantom: core::marker::PhantomData,
}
}
#[allow(clippy::let_and_return, clippy::approx_constant)]
pub fn forward(
&self,
tensor1: Tensor<B, 4>,
tensor2: Tensor<B, 4>
) -> Tensor<B, 4> {
let tensor3 = tensor1.matmul(tensor2);
let tensor4 = self.conv2d.forward(tensor3);
tensor4
}
}
};
assert_tokens(graph.codegen(), expected);
}
#[test]
fn test_codegen_clone_tensor() {
let mut graph = BurnGraph::<FullPrecisionSettings>::default();
graph.register(MatmulNode::new(
TensorType::new_float("tensor1", 4),
TensorType::new_float("tensor2", 4),
TensorType::new_float("tensor3", 4),
));
graph.register(Conv2dNode::new(
"conv2d",
TensorType::new_float("tensor2", 4),
TensorType::new_float("tensor4", 4),
Data::from([2.]).serialize(),
None,
Conv2dConfig::new([3, 3], [3, 3]).with_padding(PaddingConfig2d::Valid),
));
graph.register(MatmulNode::new(
TensorType::new_float("tensor3", 4),
TensorType::new_float("tensor4", 4),
TensorType::new_float("output", 4),
));
graph.register_input_output(
vec!["tensor1".to_string(), "tensor2".to_string()],
vec!["output".to_string()],
);
let expected = quote! {
use burn::{
module::Module,
tensor::{backend::Backend, Tensor},
};
use burn::nn::PaddingConfig2d;
use burn::nn::conv::Conv2d;
use burn::nn::conv::Conv2dConfig;
#[derive(Module, Debug)]
pub struct Model <B: Backend> {
conv2d: Conv2d<B>,
phantom: core::marker::PhantomData<B>,
}
impl<B: Backend> Model <B> {
#[allow(unused_variables)]
pub fn new_with(record: ModelRecord<B>) -> Self {
let conv2d = Conv2dConfig::new([3, 3], [3, 3])
.with_stride([1, 1])
.with_padding(PaddingConfig2d::Valid)
.with_dilation([1, 1])
.with_groups(1)
.with_bias(true)
.init_with(record.conv2d);
Self {
conv2d,
phantom: core::marker::PhantomData,
}
}
#[allow(clippy::let_and_return, clippy::approx_constant)]
pub fn forward(
&self,
tensor1: Tensor<B, 4>,
tensor2: Tensor<B, 4>
) -> Tensor<B, 4> {
let tensor3 = tensor1.matmul(tensor2.clone());
let tensor4 = self.conv2d.forward(tensor2);
let output = tensor3.matmul(tensor4);
output
}
}
};
assert_tokens(graph.codegen(), expected);
}
}