use crate::{
CubeBackend, CubeDevice,
ops::{numeric::full_device_dtype, swap_dims},
tensor::CubeTensor,
};
use burn_backend::ops::{AttentionBackward, AttentionModuleOptions, CausalAlignment};
use burn_backend::{DType, Shape, cubecl::dtype_to_storage_type};
use cubecl::client::Client;
use cubecl::features::TypeUsage;
use cubecl::prelude::InputScalar;
use cubecl::std::tensor::TensorHandle;
use cubek::attention::kernels::select::selector::QueryPositions;
use cubek::attention::kernels::tune::{AttentionTuneInputs, launch_tuned};
use cubek::attention::kernels::{
AttentionScoring, AttentionTensors, AttentionVisibility, QueryPlacement, ScoreScale,
};
use cubek::attention_backward::{AttentionBackwardTensors, attention_backward as launch_backward};
use cubek::matmul::kernels::tensor::PlainTensor;
use cubek::std::autotune::GroupPriority;
pub fn backward_serves(
query: &CubeTensor,
key: &CubeTensor,
value: &CubeTensor,
options: &AttentionModuleOptions,
) -> bool {
let (q_shape, k_shape) = (query.meta.shape(), key.meta.shape());
let element = dtype_to_storage_type(query.dtype);
let elements = key.dtype == query.dtype
&& value.dtype == query.dtype
&& AttentionTensors::VERIFIED.contains(&element)
&& query
.client
.properties()
.type_usage(element)
.is_superset(TypeUsage::Arithmetic | TypeUsage::Buffer);
let grouped = k_shape[1] != 0 && q_shape[1].is_multiple_of(k_shape[1]);
let anchored = !options.is_causal
|| options.causal_alignment == CausalAlignment::BottomRight
|| q_shape[2] == k_shape[2];
elements && grouped && options.softcap.is_none() && anchored
}
pub fn attention_with_logsumexp(
query: CubeTensor,
key: CubeTensor,
value: CubeTensor,
options: AttentionModuleOptions,
) -> (CubeTensor, CubeTensor) {
let (client, device, dtype) = (query.client.clone(), query.device.clone(), query.dtype);
let [batch, seq_q, head_dim] = [
query.meta.shape()[0],
query.meta.shape()[2],
query.meta.shape()[3],
];
let seq_kv = key.meta.shape()[2];
let lengths = full_device_dtype(
client.clone(),
Shape::new([batch]),
device.clone(),
InputScalar::new(seq_kv as u32, dtype_to_storage_type(DType::U32)),
DType::U32,
);
let inputs = AttentionTuneInputs::new(
&client,
plain(query),
plain(key),
plain(value),
None,
plain(lengths),
None,
AttentionScoring {
visibility: visibility(&options),
scale: score_scale(&options, head_dim),
},
)
.unwrap_or_else(|why| panic!("no tile-DSL attention routine serves these operands: {why:?}"))
.with_logsumexp();
let logsumexp = inputs.logsumexp().expect("`with_logsumexp` binds one");
let out = launch_tuned(
QueryPositions::of(seq_q),
&device,
inputs,
GroupPriority::DEFAULT,
);
let out = cube_tensor(out, &client, &device, dtype);
let logsumexp = cube_tensor(logsumexp, &client, &device, DType::F32);
(swap_dims(out, 1, 2), logsumexp)
}
pub fn attention_backward(
query: CubeTensor,
key: CubeTensor,
value: CubeTensor,
out: CubeTensor,
logsumexp: CubeTensor,
out_grad: CubeTensor,
options: AttentionModuleOptions,
) -> AttentionBackward<CubeBackend> {
let (client, device, dtype) = (query.client.clone(), query.device.clone(), query.dtype);
let head_dim = query.meta.shape()[3];
let tensors = AttentionBackwardTensors::new(
&client,
plain(query),
plain(key),
plain(value),
plain(out),
plain(out_grad),
plain(logsumexp),
score_scale(&options, head_dim).value(),
visibility(&options),
)
.unwrap_or_else(|why| {
panic!("cubek's attention backward pass declines these operands: {why:?}")
});
let gradients = launch_backward(&device, tensors);
AttentionBackward::new(
cube_tensor(gradients.query, &client, &device, dtype),
cube_tensor(gradients.key, &client, &device, dtype),
cube_tensor(gradients.value, &client, &device, dtype),
)
}
fn cube_tensor(
handle: TensorHandle,
client: &Client,
device: &CubeDevice,
dtype: DType,
) -> CubeTensor {
CubeTensor::new(
client.clone(),
handle.handle,
*handle.metadata,
device.clone(),
dtype,
)
}
fn plain(tensor: CubeTensor) -> PlainTensor {
let elem = dtype_to_storage_type(tensor.dtype);
PlainTensor::new(tensor.binding(), elem)
}
fn visibility(options: &AttentionModuleOptions) -> AttentionVisibility {
AttentionVisibility::new(
options.is_causal,
None,
match options.causal_alignment {
CausalAlignment::BottomRight => QueryPlacement::Last,
CausalAlignment::TopLeft => QueryPlacement::First,
},
)
}
fn score_scale(options: &AttentionModuleOptions, head_dim: usize) -> ScoreScale {
match options.scale {
Some(scale) => ScoreScale::Custom(scale as f32),
None => ScoreScale::standard(head_dim),
}
}