use super::Backward;
use crate::{
checkpoint::{
base::Checkpointer,
builder::{ActionType, CheckpointerBuilder},
retro_forward::RetroForward,
strategy::CheckpointStrategy,
},
grads::Gradients,
graph::{ComputingProperty, NodeId, NodeRef, Parent, Requirement, Step},
tensor::AutodiffTensor,
};
use alloc::boxed::Box;
use burn_backend::{Backend, TensorMetadata, tensor::FloatTensor};
use burn_std::Shape;
use core::marker::PhantomData;
#[cfg(feature = "distributed")]
use burn_backend::distributed::DistributedParams;
#[derive(new)]
pub struct OpsPrep<Backward, B, S, C, const N: usize, Mode = Init> {
nodes: [NodeRef; N],
requirement: Requirement,
backward: Backward,
compute_property: ComputingProperty,
checkpointer_builder: CheckpointerBuilder,
checkpoint_strategy: PhantomData<C>,
phantom_backend: PhantomData<B>,
phantom_state: PhantomData<S>,
marker: PhantomData<Mode>,
}
pub struct Init;
pub struct MemoryBound;
pub struct MemoryBoundRetroForward;
pub struct ComputePropertyDone;
pub struct Tracked;
pub struct UnTracked;
impl<BO, B, S, C, const N: usize> OpsPrep<BO, B, S, C, N, Init>
where
B: Backend,
BO: Backward<B, N, State = S>,
{
pub fn compute_bound(self) -> OpsPrep<BO, B, S, C, N, ComputePropertyDone> {
OpsPrep::new(
self.nodes,
self.requirement,
self.backward,
ComputingProperty::ComputeBound,
self.checkpointer_builder,
)
}
pub fn memory_bound(self) -> OpsPrep<BO, B, S, C, N, MemoryBound> {
OpsPrep::new(
self.nodes,
self.requirement,
self.backward,
self.compute_property,
self.checkpointer_builder,
)
}
}
impl<BO, B, S, C, const N: usize> OpsPrep<BO, B, S, C, N, MemoryBound>
where
B: Backend,
BO: Backward<B, N, State = S>,
C: CheckpointStrategy,
{
pub fn retro_forward<R: RetroForward>(
self,
retro_forward: R,
) -> OpsPrep<BO, B, S, C, N, MemoryBoundRetroForward> {
OpsPrep::new(
self.nodes,
self.requirement,
self.backward,
C::compute_property(retro_forward),
self.checkpointer_builder,
)
}
}
impl<BO, B, S, C, const N: usize> OpsPrep<BO, B, S, C, N, MemoryBoundRetroForward>
where
B: Backend,
BO: Backward<B, N, State = S>,
C: CheckpointStrategy,
{
pub fn parents<'a, B2, A>(mut self, parents: A) -> OpsPrep<BO, B, S, C, N, ComputePropertyDone>
where
B2: Backend,
A: IntoIterator<Item = &'a AutodiffTensor<B2>>,
{
let compute_property = match C::checkpoint_parents(parents, &mut self.checkpointer_builder)
{
Ok(..) => self.compute_property,
Err(..) => ComputingProperty::ComputeBound,
};
OpsPrep::new(
self.nodes,
self.requirement,
self.backward,
compute_property,
self.checkpointer_builder,
)
}
}
impl<BO, B, C, const N: usize> OpsPrep<BO, B, (), C, N, ComputePropertyDone>
where
B: Backend,
BO: Backward<B, N, State = ()>,
{
pub fn stateless(self, output: FloatTensor<B>) -> AutodiffTensor<B> {
match self.stateful() {
OpsKind::Tracked(prep) => prep.finish((), output),
OpsKind::UnTracked(prep) => prep.finish(output),
}
}
}
impl<BO, B, S, C, const N: usize> OpsPrep<BO, B, S, C, N, ComputePropertyDone>
where
B: Backend,
S: Clone + Send + core::fmt::Debug + 'static,
BO: Backward<B, N, State = S>,
{
pub fn stateful(self) -> OpsKind<BO, B, S, C, N> {
match self.requirement.is_none() {
false => OpsKind::Tracked(OpsPrep::new(
self.nodes,
self.requirement,
self.backward,
self.compute_property,
self.checkpointer_builder,
)),
true => OpsKind::UnTracked(OpsPrep::new(
self.nodes,
self.requirement,
self.backward,
self.compute_property,
self.checkpointer_builder,
)),
}
}
}
impl<BO, B, S, C, const N: usize> OpsPrep<BO, B, S, C, N, UnTracked>
where
B: Backend,
S: Clone + Send + core::fmt::Debug + 'static,
BO: Backward<B, N, State = S>,
{
pub fn finish(self, output: FloatTensor<B>) -> AutodiffTensor<B> {
let output = AutodiffTensor::from_parents(
output,
&self.nodes,
self.requirement,
self.compute_property,
);
let parents = self.nodes.map(|node| node.clone_if_require_grad());
let ops = Ops::new(parents, output.node.clone(), ());
output.register_step(UntrackedOpsStep::new(ops), self.checkpointer_builder)
}
}
impl<BO, B, S, C, const N: usize> OpsPrep<BO, B, S, C, N, Tracked>
where
B: Backend,
S: Clone + Send + core::fmt::Debug + 'static,
BO: Backward<B, N, State = S>,
{
pub fn finish(self, state: S, output: FloatTensor<B>) -> AutodiffTensor<B> {
let output = AutodiffTensor::from_parents(
output,
&self.nodes,
self.requirement,
self.compute_property,
);
let parents = self.nodes.map(|node| node.clone_if_require_grad());
let ops = Ops::new(parents, output.node.clone(), state);
output.register_step(OpsStep::new(ops, self.backward), self.checkpointer_builder)
}
pub fn checkpoint(&mut self, tensor: &AutodiffTensor<B>) -> NodeId {
self.checkpointer_builder
.checkpoint(tensor, ActionType::Explicit);
tensor.node.id
}
}
pub enum OpsKind<BO, B, S, C, const N: usize> {
Tracked(OpsPrep<BO, B, S, C, N, Tracked>),
UnTracked(OpsPrep<BO, B, S, C, N, UnTracked>),
}
#[derive(new, Debug)]
pub struct Ops<S, const N: usize> {
pub parents: [Option<NodeRef>; N],
pub node: NodeRef,
pub state: S,
}
#[derive(new, Debug)]
struct OpsStep<B, T, SB, const N: usize>
where
B: Backend,
T: Backward<B, N, State = SB>,
SB: Clone + Send + core::fmt::Debug + 'static,
{
ops: Ops<SB, N>,
backward: T,
phantom: PhantomData<B>,
}
impl<B, T, SB, const N: usize> Step for OpsStep<B, T, SB, N>
where
B: Backend,
T: Backward<B, N, State = SB>,
SB: Clone + Send + core::fmt::Debug + 'static,
{
fn step(self: Box<Self>, grads: &mut Gradients, checkpointer: &mut Checkpointer) {
self.backward.backward(self.ops, grads, checkpointer);
}
fn node(&self) -> NodeId {
self.ops.node.id
}
fn parents(&self) -> &[Parent] {
&self.ops.node.parents
}
fn depth(&self) -> usize {
self.ops.node.order
}
#[cfg(feature = "distributed")]
fn distributed_params(&self) -> Option<DistributedParams> {
self.ops.node.distributed_params.clone()
}
}
#[derive(new, Debug)]
struct UntrackedOpsStep<const N: usize> {
ops: Ops<(), N>,
}
impl<const N: usize> Step for UntrackedOpsStep<N> {
fn step(self: Box<Self>, _grads: &mut Gradients, _checkpointer: &mut Checkpointer) {
}
fn node(&self) -> NodeId {
self.ops.node.id
}
fn parents(&self) -> &[Parent] {
&self.ops.node.parents
}
fn depth(&self) -> usize {
self.ops.node.order
}
#[cfg(feature = "distributed")]
fn distributed_params(&self) -> Option<DistributedParams> {
self.ops.node.distributed_params.clone()
}
}
pub fn broadcast_shape<B: Backend>(mut grad: FloatTensor<B>, shape: &Shape) -> FloatTensor<B> {
let shape_grad = grad.shape();
let ndims = shape_grad.num_dims();
for i in 0..ndims {
if shape_grad[i] != shape[i] {
if shape[i] != 1 {
panic!(
"Invalid broadcast shapes: Next grad shape {:?}, Previous grad shape {:?}. {}",
shape, shape_grad, "Expected the shape of the next grad to be 1."
);
}
grad = B::float_sum_dim(grad, i);
}
}
grad
}