Skip to main content

DynamicSkeleton

Struct DynamicSkeleton 

Source
pub struct DynamicSkeleton<T, B: Backend = DefaultBackend, P: EvictionPolicy = LRUPolicy> { /* private fields */ }
Expand description

A cache (group) of skeletons with different shapes

This is a hashmap abstraction on top of a Skeleton to enable dynamic shapes. It calls the BuildFunction every time a group of tensors with never-before-seen layouts arrives, and stores the result in the cache. The cache size and eviction behavior are determined by the chosen policy, which must implement EvictionPolicy.

The build function must bind its slots in the same order as the layouts it receives, returning a Skeleton that supports that shape.

§Examples

use candela::skeleton::{DynamicSkeleton, SkeletonSlot};
use candela::{Layout, Tensor};

// One build rule, reused for whatever shape shows up.
let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(8, Box::new(|inputs: &[Layout]| {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a * 2.0).into_skeleton(&[a]).unwrap()
}));

// Two different shapes build (and cache) two different skeletons.
let out4 = sk.run(&[&Tensor::from_scalar(3.0, &[4])])?;
let out8 = sk.run(&[&Tensor::from_scalar(3.0, &[8])])?;
assert_eq!(out4.data(), &[6.0; 4]);
assert_eq!(out8.data(), &[6.0; 8]);

Implementations§

Source§

impl<P: EvictionPolicy, T, B> DynamicSkeleton<T, B, P>
where T: ComputeFor<B>, B: Backend,

Source

pub fn new(cache_size: usize, build: BuildFunction<T, B>) -> Self

Creates a new dynamic skeleton

Creates a cache of at least cache_size items, where each entry maps a Layout to a Skeleton.

On a miss it calls build to create and cache a new skeleton; the slots bound in build must be in the same order as its inputs argument.

§Examples
use candela::skeleton::{DynamicSkeleton, Skeleton, SkeletonSlot};
use candela::{Layout, Tensor};
use std::error::Error;

fn build(inputs: &[Layout]) -> Skeleton<f32> {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a * 2.0).into_skeleton(&[a]).unwrap()
}

fn main() -> Result<(), Box<dyn Error>> {
    let a = Tensor::from_scalar(0.3, &[4]);
    let b = Tensor::from_scalar(0.3, &[8]);

    let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(12, Box::new(build));
    let out_a = sk.run(&[&a])?;
    let out_b = sk.run(&[&b])?;

    println!("{out_a}");
    println!("{out_b}");

    Ok(())
}
Source

pub fn run(&self, inputs: &[&Tensor<T, B>]) -> Result<Tensor<T, B>, OpError>

Runs the cached skeleton for the inputs’ shapes, building one on a miss.

Looks up the Skeleton keyed by the inputs’ layouts and runs it, calling the build function first if no entry exists yet. See Skeleton::run.

§Examples
use candela::skeleton::{DynamicSkeleton, SkeletonSlot};
use candela::{Layout, Tensor};

let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(8, Box::new(|inputs: &[Layout]| {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a + 1.0).into_skeleton(&[a]).unwrap()
}));

let out = sk.run(&[&Tensor::from_scalar(3.0, &[4])])?;
assert_eq!(out.data(), &[4.0; 4]);
Source

pub fn compose<C>(&self, inputs: &[&C]) -> Result<BakedPromise<T, B>, OpError>
where C: Composable<T, B>,

Composes the cached skeleton for the inputs’ shapes, building one on a miss.

Like run, but embeds the skeleton’s plan into a BakedPromise instead of executing it. See Skeleton::compose.

§Examples
use candela::skeleton::{DynamicSkeleton, SkeletonSlot};
use candela::{Layout, Tensor};

let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(8, Box::new(|inputs: &[Layout]| {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a * 2.0).into_skeleton(&[a]).unwrap()
}));

// Compose over a lazy promise, not a materialized tensor.
let a = Tensor::from_scalar(1.0, &[4]) + 2.0; // TensorPromise, still unevaluated
let baked = sk.compose(&[&a])?;
assert_eq!(baked.to_promise().materialize().data(), &[6.0; 4]);
Source

pub fn remove(&self, key: &[&Tensor<T, B>]) -> Option<Arc<Skeleton<T, B>>>

Removes the entry for key

Returns the skeleton that was stored, or None if key was not present. The freed slot is returned to the cache for reuse.

§Examples
use candela::skeleton::{DynamicSkeleton, SkeletonSlot};
use candela::{Layout, Tensor};

let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(8, Box::new(|inputs: &[Layout]| {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a * 2.0).into_skeleton(&[a]).unwrap()
}));

let a = Tensor::from_scalar(3.0, &[4]);
sk.run(&[&a])?;                     // builds and caches an entry
assert!(sk.remove(&[&a]).is_some());
assert!(!sk.contains_key(&[&a]));   // gone now
Source

pub fn remove_by_layout(&self, key: &[Layout]) -> Option<Arc<Skeleton<T, B>>>

Removes the entry for key via layout

Same as Self::remove but using layouts instead.

§Examples
use candela::skeleton::{DynamicSkeleton, SkeletonSlot};
use candela::{Layout, Tensor};

let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(8, Box::new(|inputs: &[Layout]| {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a * 2.0).into_skeleton(&[a]).unwrap()
}));

sk.run(&[&Tensor::from_scalar(3.0, &[4])])?;
assert!(sk.remove_by_layout(&[Layout::new(&[4])]).is_some());
Source

pub fn contains_key(&self, key: &[&Tensor<T, B>]) -> bool

Returns whether key currently has an entry in the cache

§Examples
use candela::skeleton::{DynamicSkeleton, SkeletonSlot};
use candela::{Layout, Tensor};

let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(8, Box::new(|inputs: &[Layout]| {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a * 2.0).into_skeleton(&[a]).unwrap()
}));

let a = Tensor::from_scalar(3.0, &[4]);
assert!(!sk.contains_key(&[&a])); // nothing built yet
sk.run(&[&a])?;
assert!(sk.contains_key(&[&a]));  // now cached
Source

pub fn contains_key_by_layout(&self, key: &[Layout]) -> bool

Returns whether key currently has an entry in the cache by layout

§Examples
use candela::skeleton::{DynamicSkeleton, SkeletonSlot};
use candela::{Layout, Tensor};

let sk: DynamicSkeleton<f32> = DynamicSkeleton::new(8, Box::new(|inputs: &[Layout]| {
    let a = SkeletonSlot::new(inputs[0].clone());
    (&a * 2.0).into_skeleton(&[a]).unwrap()
}));

sk.run(&[&Tensor::from_scalar(3.0, &[4])])?;
assert!(sk.contains_key_by_layout(&[Layout::new(&[4])]));

Trait Implementations§

Source§

impl<T, B: Backend, P: EvictionPolicy> Debug for DynamicSkeleton<T, B, P>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T, B = CpuPure, P = LRUPolicy> !Freeze for DynamicSkeleton<T, B, P>

§

impl<T, B = CpuPure, P = LRUPolicy> !RefUnwindSafe for DynamicSkeleton<T, B, P>

§

impl<T, B = CpuPure, P = LRUPolicy> !UnwindSafe for DynamicSkeleton<T, B, P>

§

impl<T, B, P> Send for DynamicSkeleton<T, B, P>
where P: Send, B: Sync + Send, T: Sync + Send,

§

impl<T, B, P> Sync for DynamicSkeleton<T, B, P>
where P: Send, B: Sync + Send, T: Sync + Send,

§

impl<T, B, P> Unpin for DynamicSkeleton<T, B, P>
where P: Unpin,

§

impl<T, B, P> UnsafeUnpin for DynamicSkeleton<T, B, P>
where P: UnsafeUnpin,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.