cubek-interpolate 0.3.0

CubeK: Interpolation
use cubecl::{
    benchmark::{Benchmark, ProfileDuration, TimingMethod},
    client::Client,
    future,
    prelude::*,
    std::tensor::TensorHandle,
    zspace::Shape,
};
use cubek_test_utils::{RunSamples, TestInput};

use crate::{definition::InterpolateProblem, interpolate, interpolate_backward};

use crate::InterpolateStrategy;

pub fn bench(
    strategy: &InterpolateStrategy,
    problem: &InterpolateProblem,
    num_samples: usize,
) -> Result<RunSamples, String> {
    let device = cubecl::test_device();
    let client = device.client();

    let dtype = f32::elem_type_native();

    let bench = InterpolateBench {
        problem: problem.clone(),
        strategy: *strategy,
        device,
        client: client.clone(),
        dtype,
        samples: num_samples,
    };

    let durations = bench
        .run(cubek_test_utils::timing_method(TimingMethod::Device))
        .map_err(|e| format!("benchmark failed: {e}"))?
        .durations;

    Ok(RunSamples::new(durations))
}

struct InterpolateBench {
    problem: InterpolateProblem,
    strategy: InterpolateStrategy,
    device: cubecl::Device,
    client: Client,
    dtype: ElemType,
    samples: usize,
}

impl Benchmark for InterpolateBench {
    type Input = TensorHandle;
    type Output = TensorHandle;

    fn prepare(&self) -> Self::Input {
        let shape = match &self.problem {
            InterpolateProblem::Forward(prob) => prob.input_shape(),
            InterpolateProblem::Backward(prob) => Shape::new(prob.out_grad_shape),
        };
        TestInput::builder(self.client.clone(), shape)
            .dtype(self.dtype)
            .uniform(0, -1., 1.)
            .generate_without_host_data()
    }

    fn execute(&self, input: Self::Input) -> Result<TensorHandle, String> {
        match &self.problem {
            InterpolateProblem::Forward(prob) => {
                let output = TensorHandle::empty(&self.client, prob.output_shape(), self.dtype);

                interpolate(
                    &self.client,
                    input.binding(),
                    output.clone().binding(),
                    prob.options,
                    self.strategy,
                    self.dtype,
                )
                .map_err(|err| format!("{err}"))?;

                Ok(output)
            }
            InterpolateProblem::Backward(prob) => {
                let [n, _, _, c] = prob.out_grad_shape;
                let input_grad_shape = vec![n, prob.input_size[0], prob.input_size[1], c];

                // Random input tensor for the backward pass. The actual values don't matter
                // for benchmarking, so we just fill it with random data.
                let backward_input =
                    TestInput::builder(self.client.clone(), input_grad_shape.clone())
                        .dtype(self.dtype)
                        .uniform(0, -1., 1.)
                        .generate_without_host_data();

                let input_grad = TensorHandle::empty(&self.client, input_grad_shape, self.dtype);

                interpolate_backward(
                    &self.client,
                    backward_input.binding(),
                    input.clone().binding(),
                    input_grad.clone().binding(),
                    prob.options,
                    self.dtype,
                )
                .map_err(|err| format!("{err}"))?;

                Ok(input_grad)
            }
        }
    }

    fn num_samples(&self) -> usize {
        self.samples
    }

    fn name(&self) -> String {
        match &self.problem {
            InterpolateProblem::Forward(prob) => format!(
                "interpolate-{:?}-{:?}-{:?}-{:?}-{:?}",
                self.strategy,
                self.dtype,
                prob.options.mode,
                self.device,
                prob.input_shape(),
            )
            .to_lowercase(),
            InterpolateProblem::Backward(prob) => format!(
                "interpolate-backward-{:?}-{:?}-{:?}-{:?}-{:?}",
                self.strategy, self.dtype, prob.options.mode, self.device, prob.out_grad_shape,
            )
            .to_lowercase(),
        }
    }

    fn sync(&self) {
        future::block_on(self.client.sync()).unwrap()
    }

    /// Measure with device timestamps around the launch, so the reported duration is the
    /// kernel's rather than the host's view of launch, output allocation and sync.
    fn profile(&self, args: Self::Input) -> Result<ProfileDuration, String> {
        cubek_test_utils::profile_launch(&self.client, "interpolate-bench", || self.execute(args))
    }
}