#![cfg(feature = "vulkan")]
use std::sync::{Arc, RwLock};
#[allow(unused_imports)]
use fuel_ir::dispatch::OpKind;
use fuel_ir::probe::BackendId;
use fuel_ir::{DType, Error, Layout, Result};
use crate::kernel::{KernelBindingTable, OpParams};
use fuel_memory::{BackendStorage, Storage};
use crate::dispatch::{read_storage, write_storage};
fn vulkan_input(s: &Storage) -> Result<&fuel_vulkan_backend::VulkanStorageBytes> {
match &s.inner {
fuel_memory::BackendStorage::Vulkan(v) => Ok(v),
#[allow(unreachable_patterns)]
_ => Err(Error::Msg("vulkan kernel wrapper called with non-Vulkan input".to_string()).bt()),
}
}
fn vulkan_output(s: &mut Storage) -> Result<&mut fuel_vulkan_backend::VulkanStorageBytes> {
match &mut s.inner {
fuel_memory::BackendStorage::Vulkan(v) => Ok(v),
#[allow(unreachable_patterns)]
_ => {
Err(Error::Msg("vulkan kernel wrapper called with non-Vulkan output".to_string()).bt())
}
}
}
macro_rules! vk_binary_f32_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary::{}: expected 2 inputs + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.len() < 2 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary::{}: layouts.len() = {} < 2",
$label,
layouts.len(),
))
.bt());
}
let in0_guard = read_storage(&inputs[0])?;
let in1_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in0_guard)?;
let b = vulkan_input(&in1_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::binary::{}: input[0] has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table \
dispatch must come from alloc_bytes_handle / upload_bytes_handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.binary_f32_bytes($op_id, $label, a, b, out, &layouts[0], &layouts[1])
}
};
}
pub mod binary {
use super::*;
vk_binary_f32_wrapper!(add_f32, 0, "binary_add_f32");
vk_binary_f32_wrapper!(sub_f32, 1, "binary_sub_f32");
vk_binary_f32_wrapper!(mul_f32, 2, "binary_mul_f32");
vk_binary_f32_wrapper!(div_f32, 3, "binary_div_f32");
vk_binary_f32_wrapper!(maximum_f32, 4, "binary_maximum_f32");
vk_binary_f32_wrapper!(minimum_f32, 5, "binary_minimum_f32");
}
macro_rules! vk_binary_f16_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary_f16::{}: expected 2 inputs + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.len() < 2 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary_f16::{}: layouts.len() = {} < 2",
$label,
layouts.len(),
))
.bt());
}
let in0_guard = read_storage(&inputs[0])?;
let in1_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in0_guard)?;
let b = vulkan_input(&in1_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::binary_f16::{}: input[0] has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table \
dispatch must come from alloc_bytes_handle / upload_bytes_handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.binary_f16_bytes($op_id, $label, a, b, out, &layouts[0], &layouts[1])
}
};
}
pub mod binary_f16 {
use super::*;
vk_binary_f16_wrapper!(add_f16, 0, "binary_add_f16");
vk_binary_f16_wrapper!(sub_f16, 1, "binary_sub_f16");
vk_binary_f16_wrapper!(mul_f16, 2, "binary_mul_f16");
vk_binary_f16_wrapper!(div_f16, 3, "binary_div_f16");
vk_binary_f16_wrapper!(maximum_f16, 4, "binary_maximum_f16");
vk_binary_f16_wrapper!(minimum_f16, 5, "binary_minimum_f16");
}
macro_rules! vk_binary_f64_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary_f64::{}: expected 2 inputs + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.len() < 2 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary_f64::{}: layouts.len() = {} < 2",
$label,
layouts.len(),
))
.bt());
}
let in0_guard = read_storage(&inputs[0])?;
let in1_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in0_guard)?;
let b = vulkan_input(&in1_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::binary_f64::{}: input[0] has no VulkanBackend handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.binary_f64_bytes($op_id, $label, a, b, out, &layouts[0], &layouts[1])
}
};
}
pub mod binary_f64 {
use super::*;
vk_binary_f64_wrapper!(add_f64, 0, "binary_add_f64");
vk_binary_f64_wrapper!(sub_f64, 1, "binary_sub_f64");
vk_binary_f64_wrapper!(mul_f64, 2, "binary_mul_f64");
vk_binary_f64_wrapper!(div_f64, 3, "binary_div_f64");
vk_binary_f64_wrapper!(maximum_f64, 4, "binary_maximum_f64");
vk_binary_f64_wrapper!(minimum_f64, 5, "binary_minimum_f64");
}
macro_rules! vk_unary_f32_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::unary::{}: expected 1 input + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.is_empty() {
return Err(Error::Msg(format!(
"vulkan_dispatch::unary::{}: layouts.len() = 0, expected >= 1",
$label,
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::unary::{}: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table \
dispatch must come from alloc_bytes_handle / upload_bytes_handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.unary_f32_bytes($op_id, $label, a, out, &layouts[0])
}
};
}
pub mod unary {
use super::*;
vk_unary_f32_wrapper!(neg_f32, 0, "unary_neg_f32");
vk_unary_f32_wrapper!(sqr_f32, 1, "unary_sqr_f32");
vk_unary_f32_wrapper!(sqrt_f32, 2, "unary_sqrt_f32");
vk_unary_f32_wrapper!(exp_f32, 3, "unary_exp_f32");
vk_unary_f32_wrapper!(log_f32, 4, "unary_log_f32");
vk_unary_f32_wrapper!(sin_f32, 5, "unary_sin_f32");
vk_unary_f32_wrapper!(cos_f32, 6, "unary_cos_f32");
vk_unary_f32_wrapper!(tanh_f32, 7, "unary_tanh_f32");
vk_unary_f32_wrapper!(sigmoid_f32, 8, "unary_sigmoid_f32");
vk_unary_f32_wrapper!(silu_f32, 9, "unary_silu_f32");
vk_unary_f32_wrapper!(gelu_f32, 10, "unary_gelu_f32");
vk_unary_f32_wrapper!(relu_f32, 11, "unary_relu_f32");
vk_unary_f32_wrapper!(step_f32, 12, "unary_step_f32");
vk_unary_f32_wrapper!(abs_f32, 13, "unary_abs_f32");
vk_unary_f32_wrapper!(sign_f32, 14, "unary_sign_f32");
vk_unary_f32_wrapper!(recip_f32, 15, "unary_recip_f32");
}
macro_rules! vk_unary_f16_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::unary_f16::{}: expected 1 input + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.is_empty() {
return Err(Error::Msg(format!(
"vulkan_dispatch::unary_f16::{}: layouts.len() = 0, expected >= 1",
$label,
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::unary_f16::{}: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table \
dispatch must come from alloc_bytes_handle / upload_bytes_handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.unary_f16_bytes($op_id, $label, a, out, &layouts[0])
}
};
}
pub mod unary_f16 {
use super::*;
vk_unary_f16_wrapper!(neg_f16, 0, "unary_neg_f16");
vk_unary_f16_wrapper!(sqr_f16, 1, "unary_sqr_f16");
vk_unary_f16_wrapper!(sqrt_f16, 2, "unary_sqrt_f16");
vk_unary_f16_wrapper!(exp_f16, 3, "unary_exp_f16");
vk_unary_f16_wrapper!(log_f16, 4, "unary_log_f16");
vk_unary_f16_wrapper!(sin_f16, 5, "unary_sin_f16");
vk_unary_f16_wrapper!(cos_f16, 6, "unary_cos_f16");
vk_unary_f16_wrapper!(tanh_f16, 7, "unary_tanh_f16");
vk_unary_f16_wrapper!(sigmoid_f16, 8, "unary_sigmoid_f16");
vk_unary_f16_wrapper!(silu_f16, 9, "unary_silu_f16");
vk_unary_f16_wrapper!(gelu_f16, 10, "unary_gelu_f16");
vk_unary_f16_wrapper!(relu_f16, 11, "unary_relu_f16");
vk_unary_f16_wrapper!(step_f16, 12, "unary_step_f16");
vk_unary_f16_wrapper!(abs_f16, 13, "unary_abs_f16");
vk_unary_f16_wrapper!(sign_f16, 14, "unary_sign_f16");
vk_unary_f16_wrapper!(recip_f16, 15, "unary_recip_f16");
}
macro_rules! vk_unary_f64_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::unary_f64::{}: expected 1 input + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.is_empty() {
return Err(Error::Msg(format!(
"vulkan_dispatch::unary_f64::{}: layouts.len() = 0, expected >= 1",
$label,
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::unary_f64::{}: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table \
dispatch must come from alloc_bytes_handle / upload_bytes_handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.unary_f64_bytes($op_id, $label, a, out, &layouts[0])
}
};
}
pub mod unary_f64 {
use super::*;
vk_unary_f64_wrapper!(neg_f64, 0, "unary_neg_f64");
vk_unary_f64_wrapper!(sqr_f64, 1, "unary_sqr_f64");
vk_unary_f64_wrapper!(sqrt_f64, 2, "unary_sqrt_f64");
vk_unary_f64_wrapper!(exp_f64, 3, "unary_exp_f64");
vk_unary_f64_wrapper!(log_f64, 4, "unary_log_f64");
vk_unary_f64_wrapper!(sin_f64, 5, "unary_sin_f64");
vk_unary_f64_wrapper!(cos_f64, 6, "unary_cos_f64");
vk_unary_f64_wrapper!(tanh_f64, 7, "unary_tanh_f64");
vk_unary_f64_wrapper!(sigmoid_f64, 8, "unary_sigmoid_f64");
vk_unary_f64_wrapper!(silu_f64, 9, "unary_silu_f64");
vk_unary_f64_wrapper!(gelu_f64, 10, "unary_gelu_f64");
vk_unary_f64_wrapper!(relu_f64, 11, "unary_relu_f64");
vk_unary_f64_wrapper!(step_f64, 12, "unary_step_f64");
vk_unary_f64_wrapper!(abs_f64, 13, "unary_abs_f64");
vk_unary_f64_wrapper!(sign_f64, 14, "unary_sign_f64");
vk_unary_f64_wrapper!(recip_f64, 15, "unary_recip_f64");
}
pub mod softmax {
use super::*;
fn softmax_params(fn_name: &str, params: &OpParams) -> Result<(usize, usize)> {
match params {
OpParams::SoftmaxLastDim { outer_count, last_dim } => Ok((*outer_count, *last_dim)),
other => Err(Error::Msg(format!(
"vulkan_dispatch::softmax::{fn_name}: expected OpParams::SoftmaxLastDim, got {other:?}",
)).bt()),
}
}
pub fn softmax_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::softmax::softmax_f32: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim) = softmax_params("softmax_f32", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::softmax::softmax_f32: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.softmax_last_dim_f32_bytes(a, out, outer_count, last_dim)
}
pub fn softmax_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::softmax::softmax_f16: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim) = softmax_params("softmax_f16", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::softmax::softmax_f16: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.softmax_last_dim_f16_bytes(a, out, outer_count, last_dim)
}
pub fn softmax_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::softmax::softmax_bf16: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim) = softmax_params("softmax_bf16", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::softmax::softmax_bf16: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.softmax_last_dim_bf16_bytes(a, out, outer_count, last_dim)
}
pub fn softmax_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::softmax::softmax_f64: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim) = softmax_params("softmax_f64", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::softmax::softmax_f64: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.softmax_last_dim_f64_bytes(a, out, outer_count, last_dim)
}
pub fn softmax_last_dim_backward_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
softmax_backward_typed(
"softmax_last_dim_backward_f32",
inputs,
outputs,
layouts,
params,
|b, y, g, dx, oc, ld| b.softmax_last_dim_backward_f32_bytes(y, g, dx, oc, ld),
)
}
pub fn softmax_last_dim_backward_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
softmax_backward_typed(
"softmax_last_dim_backward_f16",
inputs,
outputs,
layouts,
params,
|b, y, g, dx, oc, ld| b.softmax_last_dim_backward_f16_bytes(y, g, dx, oc, ld),
)
}
pub fn softmax_last_dim_backward_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
softmax_backward_typed(
"softmax_last_dim_backward_bf16",
inputs,
outputs,
layouts,
params,
|b, y, g, dx, oc, ld| b.softmax_last_dim_backward_bf16_bytes(y, g, dx, oc, ld),
)
}
pub fn softmax_last_dim_backward_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
softmax_backward_typed(
"softmax_last_dim_backward_f64",
inputs,
outputs,
layouts,
params,
|b, y, g, dx, oc, ld| b.softmax_last_dim_backward_f64_bytes(y, g, dx, oc, ld),
)
}
fn softmax_backward_typed<F>(
label: &'static str,
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
call: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
usize,
) -> fuel_ir::Result<()>,
{
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::softmax::{label}: expected 2 inputs (y, g) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (outer_count, last_dim) = softmax_params(label, params)?;
let y_guard = read_storage(&inputs[0])?;
let g_guard = read_storage(&inputs[1])?;
let mut dx_guard = write_storage(&outputs[0])?;
let y = vulkan_input(&y_guard)?;
let g = vulkan_input(&g_guard)?;
let backend = y.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::softmax::{label}: y has no VulkanBackend handle.",
))
.bt()
})?;
let dx = vulkan_output(&mut dx_guard)?;
call(backend, y, g, dx, outer_count, last_dim)
}
}
pub mod norm {
use super::*;
fn norm_params(fn_name: &str, params: &OpParams) -> Result<(usize, usize, f64)> {
match params {
OpParams::NormLastDim {
outer_count,
last_dim,
eps,
} => Ok((*outer_count, *last_dim, *eps)),
other => Err(Error::Msg(format!(
"vulkan_dispatch::norm::{fn_name}: expected OpParams::NormLastDim, got {other:?}",
))
.bt()),
}
}
pub fn rms_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::norm::rms_f32: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim, eps) = norm_params("rms_f32", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::norm::rms_f32: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.rms_norm_last_dim_f32_bytes(a, out, outer_count, last_dim, eps)
}
pub fn rms_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::norm::rms_f16: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim, eps) = norm_params("rms_f16", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::norm::rms_f16: input has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.rms_norm_last_dim_f16_bytes(a, out, outer_count, last_dim, eps)
}
pub fn rms_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::norm::rms_bf16: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim, eps) = norm_params("rms_bf16", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::norm::rms_bf16: input has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.rms_norm_last_dim_bf16_bytes(a, out, outer_count, last_dim, eps)
}
pub fn layer_norm_backward_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_bwd_typed(
"layer_norm_backward_f32",
inputs,
outputs,
params,
|b, x, g, dx, oc, ld, eps| {
b.layer_norm_last_dim_backward_f32_bytes(x, g, dx, oc, ld, eps)
},
)
}
pub fn layer_norm_backward_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_bwd_typed(
"layer_norm_backward_f16",
inputs,
outputs,
params,
|b, x, g, dx, oc, ld, eps| {
b.layer_norm_last_dim_backward_f16_bytes(x, g, dx, oc, ld, eps)
},
)
}
pub fn layer_norm_backward_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_bwd_typed(
"layer_norm_backward_bf16",
inputs,
outputs,
params,
|b, x, g, dx, oc, ld, eps| {
b.layer_norm_last_dim_backward_bf16_bytes(x, g, dx, oc, ld, eps)
},
)
}
pub fn layer_norm_backward_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_bwd_typed(
"layer_norm_backward_f64",
inputs,
outputs,
params,
|b, x, g, dx, oc, ld, eps| {
b.layer_norm_last_dim_backward_f64_bytes(x, g, dx, oc, ld, eps)
},
)
}
fn layer_norm_bwd_typed<F>(
label: &'static str,
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
params: &OpParams,
call: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
usize,
f64,
) -> fuel_ir::Result<()>,
{
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::norm::{label}: expected 2 inputs (x, g) + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim, eps) = norm_params(label, params)?;
let x_guard = read_storage(&inputs[0])?;
let g_guard = read_storage(&inputs[1])?;
let mut dx_guard = write_storage(&outputs[0])?;
let x = vulkan_input(&x_guard)?;
let g = vulkan_input(&g_guard)?;
let backend = x.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::norm::{label}: x has no VulkanBackend handle.",
))
.bt()
})?;
let dx = vulkan_output(&mut dx_guard)?;
call(backend, x, g, dx, outer_count, last_dim, eps)
}
pub fn layer_norm_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_typed(
"layer_norm_f32",
inputs,
outputs,
params,
|b, i, o, oc, ld, eps| b.layer_norm_last_dim_f32_bytes(i, o, oc, ld, eps),
)
}
pub fn layer_norm_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_typed(
"layer_norm_f16",
inputs,
outputs,
params,
|b, i, o, oc, ld, eps| b.layer_norm_last_dim_f16_bytes(i, o, oc, ld, eps),
)
}
pub fn layer_norm_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_typed(
"layer_norm_bf16",
inputs,
outputs,
params,
|b, i, o, oc, ld, eps| b.layer_norm_last_dim_bf16_bytes(i, o, oc, ld, eps),
)
}
pub fn layer_norm_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
layer_norm_typed(
"layer_norm_f64",
inputs,
outputs,
params,
|b, i, o, oc, ld, eps| b.layer_norm_last_dim_f64_bytes(i, o, oc, ld, eps),
)
}
fn layer_norm_typed<F>(
label: &'static str,
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
params: &OpParams,
call: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
usize,
f64,
) -> fuel_ir::Result<()>,
{
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::norm::{label}: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim, eps) = norm_params(label, params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::norm::{label}: input has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
call(backend, a, out, outer_count, last_dim, eps)
}
pub fn rms_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::norm::rms_f64: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, last_dim, eps) = norm_params("rms_f64", params)?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::norm::rms_f64: input has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.rms_norm_last_dim_f64_bytes(a, out, outer_count, last_dim, eps)
}
}
pub mod attention {
use super::*;
pub fn rope_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::attention::rope_f32: expected 3 inputs (x,cos,sin) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
match params {
OpParams::Rope { .. } => {}
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::attention::rope_f32: expected OpParams::Rope, got {:?}",
other,
))
.bt());
}
}
if layouts.is_empty() {
return Err(Error::Msg(
"vulkan_dispatch::attention::rope_f32: layouts.len() = 0, need x-layout".into(),
)
.bt());
}
let x_guard = read_storage(&inputs[0])?;
let cos_guard = read_storage(&inputs[1])?;
let sin_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let x = vulkan_input(&x_guard)?;
let cos = vulkan_input(&cos_guard)?;
let sin = vulkan_input(&sin_guard)?;
let backend = x.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::attention::rope_f32: x has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.rope_f32_bytes(x, cos, sin, out, &layouts[0])
}
pub fn rope_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
rope_typed(
"rope_f16",
inputs,
outputs,
layouts,
params,
|b, x, c, s, o, l| b.rope_f16_bytes(x, c, s, o, l),
)
}
pub fn rope_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
rope_typed(
"rope_bf16",
inputs,
outputs,
layouts,
params,
|b, x, c, s, o, l| b.rope_bf16_bytes(x, c, s, o, l),
)
}
pub fn rope_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
rope_typed(
"rope_f64",
inputs,
outputs,
layouts,
params,
|b, x, c, s, o, l| b.rope_f64_bytes(x, c, s, o, l),
)
}
fn rope_typed<F>(
label: &'static str,
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
call: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
&Layout,
) -> fuel_ir::Result<()>,
{
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::attention::{label}: expected 3 inputs (x,cos,sin) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
match params {
OpParams::Rope { .. } => {}
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::attention::{label}: expected OpParams::Rope, got {other:?}",
))
.bt());
}
}
if layouts.is_empty() {
return Err(Error::Msg(format!(
"vulkan_dispatch::attention::{label}: layouts.len() = 0, need x-layout",
))
.bt());
}
let x_guard = read_storage(&inputs[0])?;
let cos_guard = read_storage(&inputs[1])?;
let sin_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let x = vulkan_input(&x_guard)?;
let cos = vulkan_input(&cos_guard)?;
let sin = vulkan_input(&sin_guard)?;
let backend = x.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::attention::{label}: x has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
call(backend, x, cos, sin, out, &layouts[0])
}
}
pub mod powi {
use super::*;
pub fn powi_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::powi::powi_f32: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.is_empty() {
return Err(Error::Msg(
"vulkan_dispatch::powi::powi_f32: layouts.len() = 0, expected >= 1".into(),
)
.bt());
}
let exp = match params {
OpParams::PowI { exp } => *exp,
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::powi::powi_f32: expected OpParams::PowI, got {:?}",
other,
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::powi::powi_f32: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.powi_f32_bytes(a, out, exp, &layouts[0])
}
}
pub mod write_slice {
use super::*;
macro_rules! vk_write_slice_wrapper {
($name:ident, $byte_width:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice::{}: expected 1 input + 1 output, got {} + {}",
stringify!($name), inputs.len(), outputs.len(),
)).bt());
}
let (dest_shape, ranges) = match params {
OpParams::WriteSlice { dest_shape, ranges, .. } => (dest_shape, ranges),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice::{}: expected OpParams::WriteSlice, got {:?}",
stringify!($name), other,
)).bt());
}
};
let rank = dest_shape.len();
let mut source_shape = Vec::with_capacity(rank);
let mut range_start = Vec::with_capacity(rank);
for &(start, end) in ranges.iter() {
source_shape.push(end - start);
range_start.push(start);
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let src_vk = vulkan_input(&in_guard)?;
let backend = src_vk.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::write_slice::{}: src has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle.",
stringify!($name),
)).bt()
})?;
let dst_vk = vulkan_output(&mut out_guard)?;
backend.write_slice_bytes($byte_width, src_vk, dst_vk, dest_shape, &source_shape, &range_start)
}
};
}
vk_write_slice_wrapper!(write_slice_b1, 1);
vk_write_slice_wrapper!(write_slice_b2, 2);
vk_write_slice_wrapper!(write_slice_b4, 4);
vk_write_slice_wrapper!(write_slice_b8, 8);
}
pub mod write_slice_rotating {
use super::*;
macro_rules! vk_write_slice_rotating_wrapper {
($name:ident, $byte_width:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice_rotating::{}: expected 2 inputs \
(source, position) + 1 output (dest), got {} + {}",
stringify!($name), inputs.len(), outputs.len(),
)).bt());
}
let (dest_shape, axis, modulus, ranges) = match params {
OpParams::WriteSliceRotating { dest_shape, axis, modulus, ranges } => {
(dest_shape, *axis, *modulus, ranges)
}
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice_rotating::{}: expected \
OpParams::WriteSliceRotating, got {:?}",
stringify!($name), other,
)).bt());
}
};
let rank = dest_shape.len();
if ranges.len() != rank {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice_rotating::{}: ranges.len() {} != dest rank {}",
stringify!($name), ranges.len(), rank,
)).bt());
}
let pos_guard = read_storage(&inputs[1])?;
let pos_vk = vulkan_input(&pos_guard)?;
let backend = pos_vk.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::write_slice_rotating::{}: position storage has no \
VulkanBackend handle",
stringify!($name),
)).bt()
})?;
let pos_bytes = backend.download_bytes(pos_vk)?;
if pos_bytes.len() < 4 {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice_rotating::{}: position storage has {} bytes, need >= 4",
stringify!($name), pos_bytes.len(),
)).bt());
}
let position = u32::from_ne_bytes([
pos_bytes[0], pos_bytes[1], pos_bytes[2], pos_bytes[3],
]) as usize;
let wrapped_start = position % modulus;
let mut slab_shape: Vec<usize> = Vec::with_capacity(rank);
for (i, &(start, end)) in ranges.iter().enumerate() {
if end < start {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice_rotating::{}: ranges[{}] = ({}, {}) has end < start",
stringify!($name), i, start, end,
)).bt());
}
let slab = end - start;
if i == axis && slab > modulus {
return Err(Error::Msg(format!(
"vulkan_dispatch::write_slice_rotating::{}: rotating-axis slab {} > modulus {}",
stringify!($name), slab, modulus,
)).bt());
}
slab_shape.push(slab);
}
let slab_axis_len = slab_shape[axis];
let slab_elems: usize = slab_shape.iter().copied().product();
if slab_elems == 0 {
return Ok(());
}
let first_len = slab_axis_len.min(modulus - wrapped_start);
let second_len = slab_axis_len - first_len;
let outer_count: usize = slab_shape[..axis].iter().copied().product();
let inner_per_row: usize = slab_shape[axis + 1..].iter().copied().product();
let row_bytes = inner_per_row * $byte_width;
let stride_bytes = slab_axis_len * row_bytes;
let src_guard = read_storage(&inputs[0])?;
let src_vk = vulkan_input(&src_guard)?;
if first_len > 0 {
let chunk_row_bytes = first_len * row_bytes;
let src_first = backend.extract_strided_to_new_handle(
src_vk, outer_count, stride_bytes, 0, chunk_row_bytes,
)?;
let mut sub_source_shape = slab_shape.clone();
sub_source_shape[axis] = first_len;
let mut sub_range_start: Vec<usize> = ranges.iter().map(|r| r.0).collect();
sub_range_start[axis] = wrapped_start;
let mut out_guard = write_storage(&outputs[0])?;
let dst_vk = vulkan_output(&mut out_guard)?;
backend.write_slice_bytes(
$byte_width, &src_first, dst_vk,
dest_shape, &sub_source_shape, &sub_range_start,
)?;
}
if second_len > 0 {
let chunk_row_bytes = second_len * row_bytes;
let offset_in_outer = first_len * row_bytes;
let src_second = backend.extract_strided_to_new_handle(
src_vk, outer_count, stride_bytes, offset_in_outer, chunk_row_bytes,
)?;
let mut sub_source_shape = slab_shape.clone();
sub_source_shape[axis] = second_len;
let mut sub_range_start: Vec<usize> = ranges.iter().map(|r| r.0).collect();
sub_range_start[axis] = 0;
let mut out_guard = write_storage(&outputs[0])?;
let dst_vk = vulkan_output(&mut out_guard)?;
backend.write_slice_bytes(
$byte_width, &src_second, dst_vk,
dest_shape, &sub_source_shape, &sub_range_start,
)?;
}
Ok(())
}
};
}
vk_write_slice_rotating_wrapper!(write_slice_rotating_b1, 1);
vk_write_slice_rotating_wrapper!(write_slice_rotating_b2, 2);
vk_write_slice_rotating_wrapper!(write_slice_rotating_b4, 4);
vk_write_slice_rotating_wrapper!(write_slice_rotating_b8, 8);
}
pub mod cast {
use super::*;
pub fn cast_f32_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast::cast_f32_f64: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let src_dtype = in_guard.dtype;
let dst_dtype = out_guard.dtype;
let a = vulkan_input(&in_guard)?;
let n_bytes = a.len_bytes();
let src_elem = match src_dtype {
DType::F32 => 4,
DType::F64 => 8,
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast::cast_f32_f64: unsupported src dtype {other:?}",
))
.bt());
}
};
if n_bytes % src_elem != 0 {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast::cast_f32_f64: input bytes {n_bytes} not a multiple of \
src elem size {src_elem} ({src_dtype:?})",
))
.bt());
}
let n = n_bytes / src_elem;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::cast::cast_f32_f64: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.cast_f32_f64_bytes(a, out, n, src_dtype, dst_dtype)
}
pub fn cast_f32_half(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast::cast_f32_half: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let src_dtype = in_guard.dtype;
let dst_dtype = out_guard.dtype;
let a = vulkan_input(&in_guard)?;
let n_bytes = a.len_bytes();
let src_elem = match src_dtype {
DType::F32 => 4,
DType::F16 => 2,
DType::BF16 => 2,
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast::cast_f32_half: unsupported src dtype {other:?}",
))
.bt());
}
};
if n_bytes % src_elem != 0 {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast::cast_f32_half: input bytes {n_bytes} not a multiple of \
src elem size {src_elem} ({src_dtype:?})",
))
.bt());
}
let n = n_bytes / src_elem;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::cast::cast_f32_half: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.cast_f32_bytes(a, out, n, src_dtype, dst_dtype)
}
}
pub mod clamp {
use super::*;
pub fn clamp_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::clamp::clamp_f32: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.is_empty() {
return Err(Error::Msg(
"vulkan_dispatch::clamp::clamp_f32: layouts.len() = 0, expected >= 1".into(),
)
.bt());
}
let (lo, hi) = match params {
OpParams::Clamp { min, max } => (*min, *max),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::clamp::clamp_f32: expected OpParams::Clamp, got {:?}",
other,
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::clamp::clamp_f32: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.clamp_f32_bytes(a, out, lo, hi, &layouts[0])
}
}
pub mod affine {
use super::*;
pub fn affine_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::affine::affine_f32: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.is_empty() {
return Err(Error::Msg(
"vulkan_dispatch::affine::affine_f32: layouts.len() = 0, expected >= 1".into(),
)
.bt());
}
let (mul, add) = match params {
OpParams::Affine { mul, add } => (*mul, *add),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::affine::affine_f32: expected OpParams::Affine, got {:?}",
other,
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::affine::affine_f32: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.affine_f32_bytes(a, out, mul, add, &layouts[0])
}
pub fn affine_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
affine_typed(
"affine_f64",
inputs,
outputs,
layouts,
params,
|b, a, out, mul, add, layout| b.affine_f64_bytes(a, out, mul, add, layout),
)
}
pub fn affine_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
affine_typed(
"affine_f16",
inputs,
outputs,
layouts,
params,
|b, a, out, mul, add, layout| b.affine_f16_bytes(a, out, mul, add, layout),
)
}
pub fn affine_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
affine_typed(
"affine_bf16",
inputs,
outputs,
layouts,
params,
|b, a, out, mul, add, layout| b.affine_bf16_bytes(a, out, mul, add, layout),
)
}
fn affine_typed<F>(
debug_name: &'static str,
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
f: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
f64,
f64,
&Layout,
) -> Result<()>,
{
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::affine::{debug_name}: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.is_empty() {
return Err(Error::Msg(format!(
"vulkan_dispatch::affine::{debug_name}: layouts.len() = 0",
))
.bt());
}
let (mul, add) = match params {
OpParams::Affine { mul, add } => (*mul, *add),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::affine::{debug_name}: expected OpParams::Affine, got {other:?}",
)).bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::affine::{debug_name}: input has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
f(backend, a, out, mul, add, &layouts[0])
}
}
pub mod matmul {
use super::*;
pub fn matmul_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f32: expected 2 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims.clone(), rhs_batch_dims.clone(), *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f32: expected OpParams::Matmul, got {:?}",
other,
))
.bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let lhs = vulkan_input(&lhs_guard)?;
let rhs = vulkan_input(&rhs_guard)?;
let backend = lhs.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::matmul::matmul_f32: lhs has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.matmul_f32_bytes(lhs, rhs, out, &lhs_batch_dims, &rhs_batch_dims, m, n, k)
}
pub fn matmul_f32_bf16_b(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f32_bf16_b: expected 2 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims.clone(), rhs_batch_dims.clone(), *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f32_bf16_b: expected OpParams::Matmul, got {:?}",
other,
)).bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
if lhs_guard.dtype != DType::F32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f32_bf16_b: lhs must be F32, got {:?}",
lhs_guard.dtype,
))
.bt());
}
if rhs_guard.dtype != DType::BF16 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f32_bf16_b: rhs must be BF16, got {:?}",
rhs_guard.dtype,
))
.bt());
}
let lhs = vulkan_input(&lhs_guard)?;
let rhs = vulkan_input(&rhs_guard)?;
let backend = lhs.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::matmul::matmul_f32_bf16_b: lhs has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.matmul_f32_bf16_b_bytes(lhs, rhs, out, &lhs_batch_dims, &rhs_batch_dims, m, n, k)
}
pub fn matmul_bf16_bf16_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_bf16_bf16_f32: expected 2 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims.clone(), rhs_batch_dims.clone(), *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_bf16_bf16_f32: expected OpParams::Matmul, got {other:?}",
)).bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
if lhs_guard.dtype != DType::BF16 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_bf16_bf16_f32: lhs must be BF16, got {:?}",
lhs_guard.dtype,
))
.bt());
}
if rhs_guard.dtype != DType::BF16 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_bf16_bf16_f32: rhs must be BF16, got {:?}",
rhs_guard.dtype,
))
.bt());
}
let lhs = vulkan_input(&lhs_guard)?;
let rhs = vulkan_input(&rhs_guard)?;
let backend = lhs.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::matmul::matmul_bf16_bf16_f32: lhs has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.matmul_bf16_bf16_f32_bytes(lhs, rhs, out, &lhs_batch_dims, &rhs_batch_dims, m, n, k)
}
pub fn matmul_f16_f16_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f16_f16_f32: expected 2 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims.clone(), rhs_batch_dims.clone(), *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f16_f16_f32: expected OpParams::Matmul, got {other:?}",
)).bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
if lhs_guard.dtype != DType::F16 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f16_f16_f32: lhs must be F16, got {:?}",
lhs_guard.dtype,
))
.bt());
}
if rhs_guard.dtype != DType::F16 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f16_f16_f32: rhs must be F16, got {:?}",
rhs_guard.dtype,
))
.bt());
}
let lhs = vulkan_input(&lhs_guard)?;
let rhs = vulkan_input(&rhs_guard)?;
let backend = lhs.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::matmul::matmul_f16_f16_f32: lhs has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.matmul_f16_f16_f32_bytes(lhs, rhs, out, &lhs_batch_dims, &rhs_batch_dims, m, n, k)
}
pub fn matmul_bf16_bf16_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_bf16_bf16_bf16: expected 2 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims.clone(), rhs_batch_dims.clone(), *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_bf16_bf16_bf16: expected OpParams::Matmul, got {other:?}",
)).bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
if lhs_guard.dtype != DType::BF16 || rhs_guard.dtype != DType::BF16 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_bf16_bf16_bf16: lhs/rhs must be BF16, got ({:?}, {:?})",
lhs_guard.dtype, rhs_guard.dtype,
)).bt());
}
let lhs = vulkan_input(&lhs_guard)?;
let rhs = vulkan_input(&rhs_guard)?;
let backend = lhs.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::matmul::matmul_bf16_bf16_bf16: lhs has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.matmul_bf16_bf16_bf16_bytes(
lhs,
rhs,
out,
&lhs_batch_dims,
&rhs_batch_dims,
m,
n,
k,
)
}
pub fn matmul_f16_f16_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f16_f16_f16: expected 2 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (lhs_batch_dims, rhs_batch_dims, m, n, k) = match params {
OpParams::Matmul {
lhs_batch_dims,
rhs_batch_dims,
m,
n,
k,
..
} => (lhs_batch_dims.clone(), rhs_batch_dims.clone(), *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f16_f16_f16: expected OpParams::Matmul, got {other:?}",
)).bt());
}
};
let lhs_guard = read_storage(&inputs[0])?;
let rhs_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
if lhs_guard.dtype != DType::F16 || rhs_guard.dtype != DType::F16 {
return Err(Error::Msg(format!(
"vulkan_dispatch::matmul::matmul_f16_f16_f16: lhs/rhs must be F16, got ({:?}, {:?})",
lhs_guard.dtype, rhs_guard.dtype,
)).bt());
}
let lhs = vulkan_input(&lhs_guard)?;
let rhs = vulkan_input(&rhs_guard)?;
let backend = lhs.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::matmul::matmul_f16_f16_f16: lhs has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.matmul_f16_f16_f16_bytes(lhs, rhs, out, &lhs_batch_dims, &rhs_batch_dims, m, n, k)
}
}
pub mod gather {
use super::*;
pub fn gather(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::gather::gather: expected 2 inputs (src, indices) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (source_shape, output_shape, dim) = match params {
OpParams::Gather {
source_shape,
output_shape,
dim,
} => (source_shape.clone(), output_shape.clone(), *dim),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::gather::gather: expected OpParams::Gather, got {other:?}",
))
.bt());
}
};
let src_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::gather::gather: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let elem_bytes = out_guard.dtype.size_in_bytes();
let src = vulkan_input(&src_guard)?;
let indices = vulkan_input(&idx_guard)?;
let backend = src.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::gather::gather: src has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.gather_bytes(
src,
indices,
out,
&source_shape,
&output_shape,
dim,
elem_bytes,
)
}
}
pub mod masked_fill {
use super::*;
pub fn masked_fill(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::masked_fill::masked_fill: expected 2 inputs (input, mask) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let fill_bytes = match params {
OpParams::MaskedFill { fill_bytes } => fill_bytes.clone(),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::masked_fill::masked_fill: expected OpParams::MaskedFill, got {other:?}",
)).bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mask_guard = read_storage(&inputs[1])?;
if mask_guard.dtype != DType::Bool {
return Err(Error::Msg(format!(
"vulkan_dispatch::masked_fill::masked_fill: mask must be Bool, got {:?} (GAP-168(c))",
mask_guard.dtype,
)).bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let elem_bytes = out_guard.dtype.size_in_bytes();
let a = vulkan_input(&in_guard)?;
let mask = vulkan_input(&mask_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::masked_fill::masked_fill: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
let n_elem = a.len_bytes() / elem_bytes;
backend.masked_fill_bytes(a, mask, out, n_elem, elem_bytes, &fill_bytes)
}
}
pub mod pad {
use super::*;
pub fn pad_backward(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::pad::pad_backward: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (in_shape, out_shape, padding, mode_tag) = match params {
OpParams::PadBackward {
in_shape,
out_shape,
padding,
mode_tag,
} => (in_shape, out_shape, padding, *mode_tag),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::pad::pad_backward: expected OpParams::PadBackward, got {other:?}",
)).bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let dtype = out_guard.dtype;
let elem_bytes = dtype.size_in_bytes();
let go = vulkan_input(&in_guard)?;
let backend = go.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::pad::pad_backward: grad_out has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let gi = vulkan_output(&mut out_guard)?;
let left_pad: Vec<usize> = padding.iter().map(|&(b, _)| b).collect();
match mode_tag {
0 => {
backend.pad_backward_const_bytes(go, gi, in_shape, out_shape, &left_pad, elem_bytes)
}
1 | 2 => match dtype {
DType::F32 | DType::F64 | DType::BF16 | DType::F16 => backend
.pad_backward_atomic_bytes(
dtype, go, gi, in_shape, out_shape, &left_pad, mode_tag,
),
_ => Err(Error::Msg(format!(
"vulkan_dispatch::pad::pad_backward: mode_tag {mode_tag} only \
supports F32/F64/BF16/F16 on Vulkan (atomic CAS path); got {dtype:?}",
))
.bt()),
},
other => Err(Error::Msg(format!(
"vulkan_dispatch::pad::pad_backward: unknown mode_tag {other}",
))
.bt()),
}
}
pub fn pad_const(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::pad::pad_const: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (in_shape, out_shape, padding, mode_tag, fill_bytes) = match params {
OpParams::Pad {
in_shape,
out_shape,
padding,
mode_tag,
fill_bytes,
} => (in_shape, out_shape, padding, *mode_tag, fill_bytes),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::pad::pad_const: expected OpParams::Pad, got {other:?}",
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let elem_bytes = out_guard.dtype.size_in_bytes();
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::pad::pad_const: input has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
let left_pad: Vec<usize> = padding.iter().map(|&(b, _)| b).collect();
match mode_tag {
0 => backend.pad_const_bytes(
a, out, in_shape, out_shape, &left_pad, elem_bytes, fill_bytes,
),
1 => backend.pad_reflect_bytes(a, out, in_shape, out_shape, &left_pad, elem_bytes),
2 => backend.pad_replicate_bytes(a, out, in_shape, out_shape, &left_pad, elem_bytes),
other => Err(Error::Msg(format!(
"vulkan_dispatch::pad::pad_const: unknown mode_tag {other}",
))
.bt()),
}
}
}
pub mod concat {
use super::*;
use fuel_ir::Shape;
use fuel_memory::BackendStorage;
fn concat_typed<F>(
label: &'static str,
elem_bytes: usize,
dtype: DType,
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
pair_call: F,
) -> Result<()>
where
F: Fn(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
&Layout,
&Layout,
) -> fuel_ir::Result<()>,
{
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::{label}: expected 1 output, got {}",
outputs.len(),
))
.bt());
}
if inputs.is_empty() {
return Err(Error::Msg(format!("vulkan_dispatch::concat::{label}: 0 inputs",)).bt());
}
let (outer_count, input_dim_sizes, inner_count, dim) = match params {
OpParams::Concat {
outer_count,
input_dim_sizes,
inner_count,
axis,
} => (*outer_count, input_dim_sizes.clone(), *inner_count, *axis),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::{label}: expected OpParams::Concat, got {other:?}",
))
.bt());
}
};
if input_dim_sizes.len() != inputs.len() {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::{label}: OpParams declares {} inputs but work item carries {}",
input_dim_sizes.len(), inputs.len(),
)).bt());
}
if layouts.len() < inputs.len() {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::{label}: layouts.len() = {} < inputs.len() = {}",
layouts.len(),
inputs.len(),
))
.bt());
}
let n_inputs = inputs.len();
if n_inputs == 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::{label}: N=1 concat unsupported \
(should be elided at graph level)",
))
.bt());
}
if n_inputs == 2 {
let a_guard = read_storage(&inputs[0])?;
let b_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&a_guard)?;
let b = vulkan_input(&b_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::concat::{label}: a has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
return pair_call(backend, a, b, out, dim, &layouts[0], &layouts[1]);
}
let in_guards: Vec<_> = inputs
.iter()
.map(read_storage)
.collect::<Result<Vec<_>>>()?;
let mut in_vk: Vec<&fuel_vulkan_backend::VulkanStorageBytes> = Vec::with_capacity(n_inputs);
for g in &in_guards {
in_vk.push(vulkan_input(g)?);
}
let backend = in_vk[0]
.backend()
.ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::concat::{label}: inputs[0] has no VulkanBackend handle.",
))
.bt()
})?
.clone();
let template_dims: Vec<usize> = layouts[0].shape().dims().to_vec();
let make_layout = |cum_dim: usize| -> Layout {
let mut dims = template_dims.clone();
dims[dim] = cum_dim;
Layout::contiguous(Shape::from_dims(&dims))
};
let mut acc_dim = input_dim_sizes[0] + input_dim_sizes[1];
let acc_elems = outer_count * acc_dim * inner_count;
let acc_bytes = backend
.alloc_bytes_handle(acc_elems * elem_bytes)
.map_err(|e| {
Error::Msg(format!(
"vulkan_dispatch::concat::{label}: alloc intermediate failed: {e}",
))
.bt()
})?;
let mut acc_storage = Storage::new(BackendStorage::Vulkan(acc_bytes), dtype);
{
let acc_vk = match &mut acc_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!("just allocated as Vulkan"),
};
pair_call(
&backend,
in_vk[0],
in_vk[1],
acc_vk,
dim,
&layouts[0],
&layouts[1],
)?;
}
for i in 2..(n_inputs - 1) {
let new_acc_dim = acc_dim + input_dim_sizes[i];
let new_elems = outer_count * new_acc_dim * inner_count;
let new_bytes = backend
.alloc_bytes_handle(new_elems * elem_bytes)
.map_err(|e| {
Error::Msg(format!(
"vulkan_dispatch::concat::{label}: alloc intermediate {i} failed: {e}",
))
.bt()
})?;
let mut new_storage = Storage::new(BackendStorage::Vulkan(new_bytes), dtype);
let acc_layout = make_layout(acc_dim);
let acc_vk_ref = match &acc_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!(),
};
let new_vk = match &mut new_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!(),
};
pair_call(
&backend,
acc_vk_ref,
in_vk[i],
new_vk,
dim,
&acc_layout,
&layouts[i],
)?;
acc_storage = new_storage;
acc_dim = new_acc_dim;
}
let acc_layout = make_layout(acc_dim);
let acc_vk_ref = match &acc_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!(),
};
let mut out_guard = write_storage(&outputs[0])?;
let out_vk = vulkan_output(&mut out_guard)?;
pair_call(
&backend,
acc_vk_ref,
in_vk[n_inputs - 1],
out_vk,
dim,
&acc_layout,
&layouts[n_inputs - 1],
)
}
pub fn concat_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
concat_typed(
"concat_f16",
2,
DType::F16,
inputs,
outputs,
layouts,
params,
|b, a, bb, o, dim, la, lb| b.concat_along_dim_f16_bytes(a, bb, o, dim, la, lb),
)
}
pub fn concat_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
concat_typed(
"concat_bf16",
2,
DType::BF16,
inputs,
outputs,
layouts,
params,
|b, a, bb, o, dim, la, lb| b.concat_along_dim_bf16_bytes(a, bb, o, dim, la, lb),
)
}
pub fn concat_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
concat_typed(
"concat_f64",
8,
DType::F64,
inputs,
outputs,
layouts,
params,
|b, a, bb, o, dim, la, lb| b.concat_along_dim_f64_bytes(a, bb, o, dim, la, lb),
)
}
pub fn concat_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::concat_f32: expected 1 output, got {}",
outputs.len(),
))
.bt());
}
if inputs.is_empty() {
return Err(
Error::Msg("vulkan_dispatch::concat::concat_f32: 0 inputs".to_string()).bt(),
);
}
let (outer_count, input_dim_sizes, inner_count, dim) = match params {
OpParams::Concat {
outer_count,
input_dim_sizes,
inner_count,
axis,
} => (*outer_count, input_dim_sizes.clone(), *inner_count, *axis),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::concat_f32: expected OpParams::Concat, got {:?}",
other,
))
.bt());
}
};
if input_dim_sizes.len() != inputs.len() {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::concat_f32: OpParams declares {} inputs but work item carries {}",
input_dim_sizes.len(), inputs.len(),
)).bt());
}
if layouts.len() < inputs.len() {
return Err(Error::Msg(format!(
"vulkan_dispatch::concat::concat_f32: layouts.len() = {} < inputs.len() = {}",
layouts.len(),
inputs.len(),
))
.bt());
}
let n_inputs = inputs.len();
if n_inputs == 1 {
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let _backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::concat::concat_f32: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
let a_bytes = a
.buffer_opt()
.ok_or_else(|| Error::Msg("concat_f32 N=1: a is host-evicted".into()).bt())?;
let out_buf = out
.buffer_opt()
.ok_or_else(|| Error::Msg("concat_f32 N=1: out is host-evicted".into()).bt())?;
let _ = (a_bytes, out_buf);
return Err(Error::Msg(
"vulkan_dispatch::concat::concat_f32: N=1 concat unsupported \
(should be elided at graph level)"
.into(),
)
.bt());
}
if n_inputs == 2 {
let a_guard = read_storage(&inputs[0])?;
let b_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&a_guard)?;
let b = vulkan_input(&b_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::concat::concat_f32: a has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
return backend.concat_along_dim_f32_bytes(a, b, out, dim, &layouts[0], &layouts[1]);
}
let in_guards: Vec<_> = inputs
.iter()
.map(read_storage)
.collect::<Result<Vec<_>>>()?;
let mut in_vk: Vec<&fuel_vulkan_backend::VulkanStorageBytes> = Vec::with_capacity(n_inputs);
for g in &in_guards {
in_vk.push(vulkan_input(g)?);
}
let backend = in_vk[0]
.backend()
.ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::concat::concat_f32: inputs[0] has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?
.clone();
let template_dims: Vec<usize> = layouts[0].shape().dims().to_vec();
let make_layout = |cum_dim: usize| -> Layout {
let mut dims = template_dims.clone();
dims[dim] = cum_dim;
Layout::contiguous(Shape::from_dims(&dims))
};
let mut acc_dim = input_dim_sizes[0] + input_dim_sizes[1];
let acc_elems = outer_count * acc_dim * inner_count;
let mut acc_storage = if n_inputs == 2 {
unreachable!()
} else {
let bytes = backend.alloc_bytes_handle(acc_elems * 4).map_err(|e| {
Error::Msg(format!(
"vulkan_dispatch::concat::concat_f32: alloc intermediate failed: {e}",
))
.bt()
})?;
Storage::new(BackendStorage::Vulkan(bytes), DType::F32)
};
{
let acc_vk = match &mut acc_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!("just allocated as Vulkan"),
};
backend.concat_along_dim_f32_bytes(
in_vk[0],
in_vk[1],
acc_vk,
dim,
&layouts[0],
&layouts[1],
)?;
}
for i in 2..(n_inputs - 1) {
let new_acc_dim = acc_dim + input_dim_sizes[i];
let new_elems = outer_count * new_acc_dim * inner_count;
let new_bytes = backend.alloc_bytes_handle(new_elems * 4).map_err(|e| {
Error::Msg(format!(
"vulkan_dispatch::concat::concat_f32: alloc intermediate {i} failed: {e}",
))
.bt()
})?;
let mut new_storage = Storage::new(BackendStorage::Vulkan(new_bytes), DType::F32);
let acc_layout = make_layout(acc_dim);
let acc_vk_ref = match &acc_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!(),
};
let new_vk = match &mut new_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!(),
};
backend.concat_along_dim_f32_bytes(
acc_vk_ref,
in_vk[i],
new_vk,
dim,
&acc_layout,
&layouts[i],
)?;
acc_storage = new_storage;
acc_dim = new_acc_dim;
}
let acc_layout = make_layout(acc_dim);
let acc_vk_ref = match &acc_storage.inner {
BackendStorage::Vulkan(v) => v,
_ => unreachable!(),
};
let mut out_guard = write_storage(&outputs[0])?;
let out_vk = vulkan_output(&mut out_guard)?;
backend.concat_along_dim_f32_bytes(
acc_vk_ref,
in_vk[n_inputs - 1],
out_vk,
dim,
&acc_layout,
&layouts[n_inputs - 1],
)
}
}
macro_rules! vk_reduce_f32_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::reduce::{}: expected 1 input + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
let dims = match params {
OpParams::Reduce { dims, keepdim: _ } => dims.clone(),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::reduce::{}: expected OpParams::Reduce, got {:?}",
$label, other,
))
.bt());
}
};
let layout = layouts.first().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::reduce::{}: layouts[0] required",
$label,
))
.bt()
})?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::reduce::{}: input has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table \
dispatch must come from alloc_bytes_handle / upload_bytes_handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.reduce_f32_bytes($op_id, $label, a, out, layout, &dims)
}
};
}
macro_rules! vk_arg_reduce_wrapper {
($name:ident, $op_id:expr, $label:expr, $dtype:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::arg_reduce::{}: expected 1 input + 1 output, got {} + {}",
$label, inputs.len(), outputs.len(),
)).bt());
}
let dim = match params {
OpParams::Reduce { dims, keepdim: _ } => {
if dims.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::arg_reduce::{}: expected single reduce dim, got {dims:?}",
$label,
)).bt());
}
dims[0]
}
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::arg_reduce::{}: expected OpParams::Reduce, got {other:?}",
$label,
)).bt());
}
};
let layout = layouts.first().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::arg_reduce::{}: layouts empty", $label,
)).bt()
})?;
let shape = layout.shape();
let rank = shape.dims().len();
if dim >= rank {
return Err(Error::Msg(format!(
"vulkan_dispatch::arg_reduce::{}: dim {dim} >= rank {rank}",
$label,
)).bt());
}
let dims_slice = shape.dims();
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::arg_reduce::{}: input has no VulkanBackend handle.",
$label,
)).bt()
})?;
let out = vulkan_output(&mut out_guard)?;
if dim == rank - 1 {
let last_dim = dims_slice[rank - 1];
let outer_count: usize = dims_slice[..rank - 1].iter().product::<usize>().max(1);
backend.arg_reduce_last_dim_bytes($dtype, $op_id, $label, a, out, outer_count, last_dim)
} else {
let n_outer: usize = dims_slice[..dim].iter().product::<usize>().max(1);
let d_dim = dims_slice[dim];
let n_inner: usize = dims_slice[dim + 1..].iter().product::<usize>().max(1);
backend.arg_reduce_any_dim_bytes($dtype, $op_id, $label, a, out, n_outer, d_dim, n_inner)
}
}
};
}
pub mod index_add {
use super::*;
pub fn index_add_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
index_add_dispatch(
inputs,
outputs,
params,
"index_add_f32",
|b, base, idx, src, out, oc, bds, ni, ic| {
b.index_add_f32_bytes(base, idx, src, out, oc, bds, ni, ic)
},
)
}
pub fn index_add_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
index_add_dispatch(
inputs,
outputs,
params,
"index_add_f64",
|b, base, idx, src, out, oc, bds, ni, ic| {
b.index_add_f64_bytes(base, idx, src, out, oc, bds, ni, ic)
},
)
}
pub fn index_add_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
index_add_dispatch(
inputs,
outputs,
params,
"index_add_bf16",
|b, base, idx, src, out, oc, bds, ni, ic| {
b.index_add_bf16_bytes(base, idx, src, out, oc, bds, ni, ic)
},
)
}
pub fn index_add_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
index_add_dispatch(
inputs,
outputs,
params,
"index_add_f16",
|b, base, idx, src, out, oc, bds, ni, ic| {
b.index_add_f16_bytes(base, idx, src, out, oc, bds, ni, ic)
},
)
}
fn index_add_dispatch<F>(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
params: &OpParams,
debug_name: &'static str,
f: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
usize,
usize,
usize,
) -> Result<()>,
{
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::index_add::{debug_name}: expected 3 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (outer_count, base_dim_size, n_indices, inner_count) = match params {
OpParams::IndexAdd {
outer_count,
base_dim_size,
n_indices,
inner_count,
} => (*outer_count, *base_dim_size, *n_indices, *inner_count),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::index_add::{debug_name}: expected OpParams::IndexAdd, got {other:?}",
)).bt());
}
};
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::index_add::{debug_name}: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base = vulkan_input(&base_guard)?;
let indices = vulkan_input(&idx_guard)?;
let src = vulkan_input(&src_guard)?;
let backend = base.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::index_add::{debug_name}: base has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
f(
backend,
base,
indices,
src,
out,
outer_count,
base_dim_size,
n_indices,
inner_count,
)
}
}
pub mod scatter_add {
use super::*;
pub fn scatter_add_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::scatter_add_f32: expected 3 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (base_shape, src_shape, dim) = match params {
OpParams::ScatterAdd {
base_shape,
src_shape,
dim,
} => (base_shape.clone(), src_shape.clone(), *dim),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::scatter_add_f32: expected OpParams::ScatterAdd, got {other:?}",
)).bt());
}
};
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::scatter_add_f32: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base = vulkan_input(&base_guard)?;
let indices = vulkan_input(&idx_guard)?;
let src = vulkan_input(&src_guard)?;
let backend = base.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::scatter_add::scatter_add_f32: base has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.scatter_add_f32_bytes(base, indices, src, out, &base_shape, &src_shape, dim)
}
pub fn scatter_add_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::scatter_add_f64: expected 3 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (base_shape, src_shape, dim) = match params {
OpParams::ScatterAdd {
base_shape,
src_shape,
dim,
} => (base_shape.clone(), src_shape.clone(), *dim),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::scatter_add_f64: expected OpParams::ScatterAdd, got {other:?}",
)).bt());
}
};
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::scatter_add_f64: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base = vulkan_input(&base_guard)?;
let indices = vulkan_input(&idx_guard)?;
let src = vulkan_input(&src_guard)?;
let backend = base.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::scatter_add::scatter_add_f64: base has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.scatter_add_f64_bytes(base, indices, src, out, &base_shape, &src_shape, dim)
}
pub fn scatter_add_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
scatter_add_subword(
inputs,
outputs,
params,
DType::BF16,
"scatter_add_bf16",
|backend, base, indices, src, out, bs, ss, dim| {
backend.scatter_add_bf16_bytes(base, indices, src, out, bs, ss, dim)
},
)
}
pub fn scatter_add_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
scatter_add_subword(
inputs,
outputs,
params,
DType::F16,
"scatter_add_f16",
|backend, base, indices, src, out, bs, ss, dim| {
backend.scatter_add_f16_bytes(base, indices, src, out, bs, ss, dim)
},
)
}
fn scatter_add_subword<F>(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
params: &OpParams,
expected_dtype: DType,
debug_name: &'static str,
f: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
&[usize],
&[usize],
usize,
) -> Result<()>,
{
if inputs.len() != 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::{debug_name}: expected 3 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (base_shape, src_shape, dim) = match params {
OpParams::ScatterAdd {
base_shape,
src_shape,
dim,
} => (base_shape.clone(), src_shape.clone(), *dim),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::{debug_name}: expected OpParams::ScatterAdd, got {other:?}",
)).bt());
}
};
let _ = expected_dtype;
let base_guard = read_storage(&inputs[0])?;
let idx_guard = read_storage(&inputs[1])?;
if idx_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::scatter_add::{debug_name}: indices must be U32, got {:?}",
idx_guard.dtype,
))
.bt());
}
let src_guard = read_storage(&inputs[2])?;
let mut out_guard = write_storage(&outputs[0])?;
let base = vulkan_input(&base_guard)?;
let indices = vulkan_input(&idx_guard)?;
let src = vulkan_input(&src_guard)?;
let backend = base.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::scatter_add::{debug_name}: base has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
f(
backend,
base,
indices,
src,
out,
&base_shape,
&src_shape,
dim,
)
}
}
pub mod arg_reduce {
use super::*;
vk_arg_reduce_wrapper!(argmax_f32, 0, "argmax_f32", DType::F32);
vk_arg_reduce_wrapper!(argmin_f32, 1, "argmin_f32", DType::F32);
vk_arg_reduce_wrapper!(argmax_f16, 0, "argmax_f16", DType::F16);
vk_arg_reduce_wrapper!(argmin_f16, 1, "argmin_f16", DType::F16);
vk_arg_reduce_wrapper!(argmax_bf16, 0, "argmax_bf16", DType::BF16);
vk_arg_reduce_wrapper!(argmin_bf16, 1, "argmin_bf16", DType::BF16);
vk_arg_reduce_wrapper!(argmax_f64, 0, "argmax_f64", DType::F64);
vk_arg_reduce_wrapper!(argmin_f64, 1, "argmin_f64", DType::F64);
}
macro_rules! vk_reduce_last_dim_wrapper {
($name:ident, $op_id:expr, $label:expr, $method:ident $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::reduce::{}: expected 1 input + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
let dims = match params {
OpParams::Reduce { dims, keepdim: _ } => dims.clone(),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::reduce::{}: expected OpParams::Reduce, got {:?}",
$label, other,
))
.bt());
}
};
let layout = layouts.first().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::reduce::{}: layouts[0] required",
$label,
))
.bt()
})?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::reduce::{}: input has no VulkanBackend handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.$method($op_id, $label, a, out, layout, &dims)
}
};
}
pub mod reduce {
use super::*;
vk_reduce_f32_wrapper!(sum_f32, 0, "reduce_sum_f32");
vk_reduce_f32_wrapper!(max_f32, 1, "reduce_max_f32");
vk_reduce_f32_wrapper!(min_f32, 2, "reduce_min_f32");
vk_reduce_f32_wrapper!(mean_f32, 3, "reduce_mean_f32");
vk_reduce_last_dim_wrapper!(sum_f16, 0, "reduce_sum_f16", reduce_f16_bytes);
vk_reduce_last_dim_wrapper!(max_f16, 1, "reduce_max_f16", reduce_f16_bytes);
vk_reduce_last_dim_wrapper!(min_f16, 2, "reduce_min_f16", reduce_f16_bytes);
vk_reduce_last_dim_wrapper!(mean_f16, 3, "reduce_mean_f16", reduce_f16_bytes);
vk_reduce_last_dim_wrapper!(sum_bf16, 0, "reduce_sum_bf16", reduce_bf16_bytes);
vk_reduce_last_dim_wrapper!(max_bf16, 1, "reduce_max_bf16", reduce_bf16_bytes);
vk_reduce_last_dim_wrapper!(min_bf16, 2, "reduce_min_bf16", reduce_bf16_bytes);
vk_reduce_last_dim_wrapper!(mean_bf16, 3, "reduce_mean_bf16", reduce_bf16_bytes);
vk_reduce_last_dim_wrapper!(sum_f64, 0, "reduce_sum_f64", reduce_f64_bytes);
vk_reduce_last_dim_wrapper!(max_f64, 1, "reduce_max_f64", reduce_f64_bytes);
vk_reduce_last_dim_wrapper!(min_f64, 2, "reduce_min_f64", reduce_f64_bytes);
vk_reduce_last_dim_wrapper!(mean_f64, 3, "reduce_mean_f64", reduce_f64_bytes);
}
pub mod indexing {
use super::*;
pub fn index_select_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::indexing::index_select_f32: expected 2 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (outer_count, source_dim_size, n_indices, inner_count) = match params {
OpParams::IndexSelect {
outer_count,
source_dim_size,
n_indices,
inner_count,
} => (*outer_count, *source_dim_size, *n_indices, *inner_count),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::indexing::index_select_f32: expected OpParams::IndexSelect, got {:?}",
other,
)).bt());
}
};
let src_guard = read_storage(&inputs[0])?;
let ids_guard = read_storage(&inputs[1])?;
if ids_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::indexing::index_select_f32: ids must be U32, got {:?}",
ids_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let src = vulkan_input(&src_guard)?;
let ids = vulkan_input(&ids_guard)?;
let backend = src.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::indexing::index_select_f32: src has no VulkanBackend handle. \
Storages flowing through the pipelined-executor binding-table dispatch \
must come from alloc_bytes_handle / upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.index_select_f32_bytes(
src,
ids,
out,
outer_count,
source_dim_size,
n_indices,
inner_count,
)
}
pub fn index_select_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
index_select_typed(
"index_select_f16",
inputs,
outputs,
layouts,
params,
|b, s, i, o, oc, sd, ni, ic| b.index_select_f16_bytes(s, i, o, oc, sd, ni, ic),
)
}
pub fn index_select_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
index_select_typed(
"index_select_bf16",
inputs,
outputs,
layouts,
params,
|b, s, i, o, oc, sd, ni, ic| b.index_select_bf16_bytes(s, i, o, oc, sd, ni, ic),
)
}
pub fn index_select_f64(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
index_select_typed(
"index_select_f64",
inputs,
outputs,
layouts,
params,
|b, s, i, o, oc, sd, ni, ic| b.index_select_f64_bytes(s, i, o, oc, sd, ni, ic),
)
}
fn index_select_typed<F>(
label: &'static str,
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
call: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
usize,
usize,
usize,
) -> fuel_ir::Result<()>,
{
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::indexing::{label}: expected 2 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (outer_count, source_dim_size, n_indices, inner_count) = match params {
OpParams::IndexSelect {
outer_count,
source_dim_size,
n_indices,
inner_count,
} => (*outer_count, *source_dim_size, *n_indices, *inner_count),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::indexing::{label}: expected OpParams::IndexSelect, got {other:?}",
)).bt());
}
};
let src_guard = read_storage(&inputs[0])?;
let ids_guard = read_storage(&inputs[1])?;
if ids_guard.dtype != DType::U32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::indexing::{label}: ids must be U32, got {:?}",
ids_guard.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let src = vulkan_input(&src_guard)?;
let ids = vulkan_input(&ids_guard)?;
let backend = src.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::indexing::{label}: src has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
call(
backend,
src,
ids,
out,
outer_count,
source_dim_size,
n_indices,
inner_count,
)
}
}
macro_rules! vk_unary_bf16_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::unary_bf16::{}: expected 1 input + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::unary_bf16::{}: input has no VulkanBackend handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.unary_bf16_bytes($op_id, $label, a, out)
}
};
}
pub mod unary_bf16 {
use super::*;
vk_unary_bf16_wrapper!(neg_bf16, 0, "unary_neg_bf16");
vk_unary_bf16_wrapper!(sqr_bf16, 1, "unary_sqr_bf16");
vk_unary_bf16_wrapper!(sqrt_bf16, 2, "unary_sqrt_bf16");
vk_unary_bf16_wrapper!(exp_bf16, 3, "unary_exp_bf16");
vk_unary_bf16_wrapper!(log_bf16, 4, "unary_log_bf16");
vk_unary_bf16_wrapper!(sin_bf16, 5, "unary_sin_bf16");
vk_unary_bf16_wrapper!(cos_bf16, 6, "unary_cos_bf16");
vk_unary_bf16_wrapper!(tanh_bf16, 7, "unary_tanh_bf16");
vk_unary_bf16_wrapper!(sigmoid_bf16, 8, "unary_sigmoid_bf16");
vk_unary_bf16_wrapper!(silu_bf16, 9, "unary_silu_bf16");
vk_unary_bf16_wrapper!(gelu_bf16, 10, "unary_gelu_bf16");
vk_unary_bf16_wrapper!(relu_bf16, 11, "unary_relu_bf16");
vk_unary_bf16_wrapper!(step_bf16, 12, "unary_step_bf16");
vk_unary_bf16_wrapper!(abs_bf16, 13, "unary_abs_bf16");
vk_unary_bf16_wrapper!(sign_bf16, 14, "unary_sign_bf16");
vk_unary_bf16_wrapper!(recip_bf16, 15, "unary_recip_bf16");
}
macro_rules! vk_binary_bf16_wrapper {
($name:ident, $op_id:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary_bf16::{}: expected 2 inputs + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
if layouts.len() < 2 {
return Err(Error::Msg(format!(
"vulkan_dispatch::binary_bf16::{}: layouts.len() = {} < 2",
$label,
layouts.len(),
))
.bt());
}
let in0_guard = read_storage(&inputs[0])?;
let in1_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&in0_guard)?;
let b = vulkan_input(&in1_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::binary_bf16::{}: input[0] has no VulkanBackend handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.binary_bf16_bytes($op_id, $label, a, b, out, &layouts[0], &layouts[1])
}
};
}
pub mod binary_bf16 {
use super::*;
vk_binary_bf16_wrapper!(add_bf16, 0, "binary_add_bf16");
vk_binary_bf16_wrapper!(sub_bf16, 1, "binary_sub_bf16");
vk_binary_bf16_wrapper!(mul_bf16, 2, "binary_mul_bf16");
vk_binary_bf16_wrapper!(div_bf16, 3, "binary_div_bf16");
vk_binary_bf16_wrapper!(maximum_bf16, 4, "binary_maximum_bf16");
vk_binary_bf16_wrapper!(minimum_bf16, 5, "binary_minimum_bf16");
}
fn byte_width_for_dtype(label: &str, dt: DType) -> Result<usize> {
match dt {
DType::F32 | DType::I32 | DType::U32 => Ok(4),
DType::F16 | DType::BF16 => Ok(2),
DType::F64 | DType::I64 => Ok(8),
other => Err(Error::Msg(format!(
"{label}: unsupported dtype {other:?} (need 2/4/8-byte elem)"
))
.bt()),
}
}
macro_rules! vk_triangular_wrapper {
($name:ident, $keep_upper:expr, $label:expr $(,)?) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::triangular::{}: expected 1 input + 1 output, got {} + {}",
$label,
inputs.len(),
outputs.len(),
))
.bt());
}
let (batch_count, rows, cols, diagonal) = match params {
OpParams::Triangular {
batch_count,
rows,
cols,
diagonal,
} => (*batch_count, *rows, *cols, *diagonal),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::triangular::{}: expected OpParams::Triangular, got {:?}",
$label, other,
))
.bt());
}
};
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let dt = in_guard.dtype;
let byte_width = byte_width_for_dtype($label, dt)?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::triangular::{}: input has no VulkanBackend handle.",
$label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.triangular_bytes(
byte_width,
$keep_upper,
a,
out,
batch_count,
rows,
cols,
diagonal,
)
}
};
}
pub mod triangular {
use super::*;
vk_triangular_wrapper!(triu, true, "triu");
vk_triangular_wrapper!(tril, false, "tril");
}
pub mod flip {
use super::*;
pub fn flip(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::flip::flip: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let axis = match params {
OpParams::Flip { axis, .. } => *axis,
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::flip::flip: expected OpParams::Flip, got {:?}",
other,
))
.bt());
}
};
let layout = layouts.first().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::flip::flip: Flip requires an input layout (layouts[0])"
.to_string(),
)
.bt()
})?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let byte_width = byte_width_for_dtype("flip", in_guard.dtype)?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::flip::flip: input has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.flip_bytes(byte_width, a, out, layout, axis)
}
}
pub mod roll {
use super::*;
pub fn roll(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::roll::roll: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (shift, axis) = match params {
OpParams::Roll { shift, axis, .. } => (*shift, *axis),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::roll::roll: expected OpParams::Roll, got {:?}",
other,
))
.bt());
}
};
let layout = layouts.first().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::roll::roll: Roll requires an input layout (layouts[0])"
.to_string(),
)
.bt()
})?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let byte_width = byte_width_for_dtype("roll", in_guard.dtype)?;
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::roll::roll: input has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.roll_bytes(byte_width, a, out, layout, axis, shift)
}
}
pub mod cumsum {
use super::*;
macro_rules! vk_cumsum_wrapper {
($name:ident, $backend_fn:ident, $dt:expr, $op_label:expr) => {
pub fn $name(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::cumsum::{}: expected 1 input + 1 output, got {} + {}",
$op_label,
inputs.len(),
outputs.len(),
))
.bt());
}
let axis = match params {
OpParams::CumSum { axis, .. } => *axis,
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::cumsum::{}: expected OpParams::CumSum, got {:?}",
$op_label, other,
))
.bt());
}
};
let layout = layouts.first().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::cumsum::{}: CumSum requires an input layout (layouts[0])",
$op_label,
))
.bt()
})?;
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
if in_guard.dtype != $dt {
return Err(Error::Msg(format!(
"vulkan_dispatch::cumsum::{}: dtype mismatch (got {:?}, expected {:?})",
$op_label, in_guard.dtype, $dt,
))
.bt());
}
let a = vulkan_input(&in_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::cumsum::{}: input has no VulkanBackend handle.",
$op_label,
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.$backend_fn(a, out, layout, axis)
}
};
}
vk_cumsum_wrapper!(cumsum_f32, cumsum_f32_bytes, DType::F32, "cumsum_f32");
vk_cumsum_wrapper!(cumsum_f64, cumsum_f64_bytes, DType::F64, "cumsum_f64");
vk_cumsum_wrapper!(cumsum_f16, cumsum_f16_bytes, DType::F16, "cumsum_f16");
vk_cumsum_wrapper!(cumsum_bf16, cumsum_bf16_bytes, DType::BF16, "cumsum_bf16");
}
pub mod qmatmul {
use super::*;
use fuel_graph::QuantType;
pub fn qmatmul_vk(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() != 2 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::qmatmul: expected 2 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let (quant_type, batch_count, m, n, k) = match params {
OpParams::QMatMul {
quant_type,
batch_count,
m,
n,
k,
} => (*quant_type, *batch_count, *m, *n, *k),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::qmatmul: expected OpParams::QMatMul, got {:?}",
other,
))
.bt());
}
};
let a_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let a = vulkan_input(&a_guard)?;
let w = vulkan_input(&w_guard)?;
let backend = a.backend().ok_or_else(|| {
Error::Msg("vulkan_dispatch::qmatmul: input has no VulkanBackend handle.".to_string())
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
match quant_type {
QuantType::Q4_0 => backend.matmul_q4_0_bytes(a, w, out, batch_count, m, k, n),
QuantType::Q4_K_M => backend.matmul_q4_km_bytes(a, w, out, batch_count, m, k, n),
QuantType::Q8_0 => backend.matmul_q8_0_bytes(a, w, out, batch_count, m, k, n),
other => Err(Error::Msg(format!(
"vulkan_dispatch::qmatmul: QuantType {other:?} not wired on Vulkan; \
add a dequant-then-matmul path or a fused kernel and retry"
))
.bt()),
}
}
}
pub mod flash_attn {
use super::*;
pub fn flash_attn_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() < 3 || inputs.len() > 4 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::flash_attn_f32: expected 3-4 inputs (q, k, v, [alibi]) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (b, hq, hkv, sq, sk, d, scale, causal, wl, wr, softcap) = match params {
OpParams::FlashAttn {
b,
hq,
hkv,
sq,
sk,
d,
k_len,
softmax_scale,
causal,
window_size_left,
window_size_right,
softcap,
} => {
if *k_len != *sk {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn: runtime k_len ({}) != K capacity ({}) \
not supported on Vulkan v1; route picker should fall back to CPU/CUDA",
*k_len, *sk,
))
.bt());
}
(
*b,
*hq,
*hkv,
*sq,
*sk,
*d,
*softmax_scale,
*causal,
*window_size_left,
*window_size_right,
*softcap,
)
}
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::flash_attn_f32: expected OpParams::FlashAttn, got {other:?}",
)).bt());
}
};
if wl.is_some() || wr.is_some() || softcap.is_some() {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::flash_attn_f32: window_left={wl:?}, window_right={wr:?}, softcap={softcap:?} \
not supported on Vulkan v1; route picker should fall back to CPU/CUDA",
)).bt());
}
let q_guard = read_storage(&inputs[0])?;
let k_guard = read_storage(&inputs[1])?;
let v_guard = read_storage(&inputs[2])?;
let alibi_guard = match inputs.get(3) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
let mut out_guard = write_storage(&outputs[0])?;
let q = vulkan_input(&q_guard)?;
let k = vulkan_input(&k_guard)?;
let v = vulkan_input(&v_guard)?;
let alibi = match &alibi_guard {
Some(g) => Some(vulkan_input(g)?),
None => None,
};
let backend = q.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::flash_attn::flash_attn_f32: q has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.flash_attn_f32_bytes(q, k, v, alibi, out, b, hq, hkv, sq, sk, d, scale, causal)
}
pub fn flash_attn_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
flash_attn_half_typed(
inputs,
outputs,
params,
DType::BF16,
"flash_attn_bf16",
|backend, q, k, v, a, out, b, hq, hkv, sq, sk, d, scale, causal| {
backend.flash_attn_bf16_bytes(q, k, v, a, out, b, hq, hkv, sq, sk, d, scale, causal)
},
)
}
pub fn flash_attn_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
flash_attn_half_typed(
inputs,
outputs,
params,
DType::F16,
"flash_attn_f16",
|backend, q, k, v, a, out, b, hq, hkv, sq, sk, d, scale, causal| {
backend.flash_attn_f16_bytes(q, k, v, a, out, b, hq, hkv, sq, sk, d, scale, causal)
},
)
}
fn flash_attn_half_typed<F>(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
params: &OpParams,
expected_dtype: DType,
debug_name: &'static str,
f: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
Option<&fuel_vulkan_backend::VulkanStorageBytes>,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
usize,
usize,
usize,
usize,
usize,
f32,
bool,
) -> Result<()>,
{
if inputs.len() < 3 || inputs.len() > 4 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: expected 3-4 inputs + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (b, hq, hkv, sq, sk, d, scale, causal, wl, wr, softcap) = match params {
OpParams::FlashAttn {
b,
hq,
hkv,
sq,
sk,
d,
k_len,
softmax_scale,
causal,
window_size_left,
window_size_right,
softcap,
} => {
if *k_len != *sk {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn: runtime k_len ({}) != K capacity ({}) \
not supported on Vulkan v1; route picker should fall back to CPU/CUDA",
*k_len, *sk,
))
.bt());
}
(
*b,
*hq,
*hkv,
*sq,
*sk,
*d,
*softmax_scale,
*causal,
*window_size_left,
*window_size_right,
*softcap,
)
}
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: expected OpParams::FlashAttn, got {other:?}",
)).bt());
}
};
if wl.is_some() || wr.is_some() || softcap.is_some() {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: window/softcap not supported on Vulkan v1",
)).bt());
}
let q_guard = read_storage(&inputs[0])?;
let k_guard = read_storage(&inputs[1])?;
let v_guard = read_storage(&inputs[2])?;
let alibi_guard = match inputs.get(3) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
for (name, dt) in [
("q", q_guard.dtype),
("k", k_guard.dtype),
("v", v_guard.dtype),
] {
if dt != expected_dtype {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: {name} must be {expected_dtype:?}, got {dt:?}",
)).bt());
}
}
if let Some(g) = &alibi_guard
&& g.dtype != expected_dtype
{
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: alibi must be {expected_dtype:?}, got {:?}", g.dtype,
)).bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let q = vulkan_input(&q_guard)?;
let k = vulkan_input(&k_guard)?;
let v = vulkan_input(&v_guard)?;
let alibi = match &alibi_guard {
Some(g) => Some(vulkan_input(g)?),
None => None,
};
let backend = q.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: q has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
f(
backend, q, k, v, alibi, out, b, hq, hkv, sq, sk, d, scale, causal,
)
}
pub fn flash_attn_backward_q_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
flash_attn_backward_f32_typed(
inputs,
outputs,
params,
"flash_attn_backward_q_f32",
|backend, q, k, v, dgrad, a, out, b, hq, hkv, sq, sk, d, scale, causal| {
backend.flash_attn_backward_q_f32_bytes(
q, k, v, dgrad, a, out, b, hq, hkv, sq, sk, d, scale, causal,
)
},
)
}
pub fn flash_attn_backward_k_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
flash_attn_backward_f32_typed(
inputs,
outputs,
params,
"flash_attn_backward_k_f32",
|backend, q, k, v, dgrad, a, out, b, hq, hkv, sq, sk, d, scale, causal| {
backend.flash_attn_backward_k_f32_bytes(
q, k, v, dgrad, a, out, b, hq, hkv, sq, sk, d, scale, causal,
)
},
)
}
pub fn flash_attn_backward_v_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
flash_attn_backward_f32_typed(
inputs,
outputs,
params,
"flash_attn_backward_v_f32",
|backend, q, k, v, dgrad, a, out, b, hq, hkv, sq, sk, d, scale, causal| {
backend.flash_attn_backward_v_f32_bytes(
q, k, v, dgrad, a, out, b, hq, hkv, sq, sk, d, scale, causal,
)
},
)
}
#[allow(clippy::too_many_arguments)]
fn flash_attn_backward_f32_typed<F>(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
params: &OpParams,
debug_name: &'static str,
f: F,
) -> Result<()>
where
F: FnOnce(
&fuel_vulkan_backend::VulkanBackend,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
&fuel_vulkan_backend::VulkanStorageBytes,
Option<&fuel_vulkan_backend::VulkanStorageBytes>,
&mut fuel_vulkan_backend::VulkanStorageBytes,
usize,
usize,
usize,
usize,
usize,
usize,
f32,
bool,
) -> Result<()>,
{
if inputs.len() < 4 || inputs.len() > 5 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: expected 4-5 inputs (q, k, v, do, [alibi]) + 1 output, got {} + {}",
inputs.len(), outputs.len(),
)).bt());
}
let (b, hq, hkv, sq, sk, d, scale, causal, wl, wr, softcap) = match params {
OpParams::FlashAttn {
b,
hq,
hkv,
sq,
sk,
d,
k_len,
softmax_scale,
causal,
window_size_left,
window_size_right,
softcap,
} => {
if *k_len != *sk {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn: runtime k_len ({}) != K capacity ({}) \
not supported on Vulkan v1; route picker should fall back to CPU/CUDA",
*k_len, *sk,
))
.bt());
}
(
*b,
*hq,
*hkv,
*sq,
*sk,
*d,
*softmax_scale,
*causal,
*window_size_left,
*window_size_right,
*softcap,
)
}
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: expected OpParams::FlashAttn, got {other:?}",
)).bt());
}
};
if wl.is_some() || wr.is_some() || softcap.is_some() {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: window/softcap not supported on Vulkan v1",
)).bt());
}
let q_guard = read_storage(&inputs[0])?;
let k_guard = read_storage(&inputs[1])?;
let v_guard = read_storage(&inputs[2])?;
let do_guard = read_storage(&inputs[3])?;
let alibi_guard = match inputs.get(4) {
Some(arc) => Some(read_storage(arc)?),
None => None,
};
for (name, dt) in [
("q", q_guard.dtype),
("k", k_guard.dtype),
("v", v_guard.dtype),
("do", do_guard.dtype),
] {
if dt != DType::F32 {
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: {name} must be F32, got {dt:?}",
))
.bt());
}
}
if let Some(g) = &alibi_guard
&& g.dtype != DType::F32
{
return Err(Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: alibi must be F32, got {:?}",
g.dtype,
))
.bt());
}
let mut out_guard = write_storage(&outputs[0])?;
let q = vulkan_input(&q_guard)?;
let k = vulkan_input(&k_guard)?;
let v = vulkan_input(&v_guard)?;
let do_storage = vulkan_input(&do_guard)?;
let alibi = match &alibi_guard {
Some(g) => Some(vulkan_input(g)?),
None => None,
};
let backend = q.backend().ok_or_else(|| {
Error::Msg(format!(
"vulkan_dispatch::flash_attn::{debug_name}: q has no VulkanBackend handle.",
))
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
f(
backend, q, k, v, do_storage, alibi, out, b, hq, hkv, sq, sk, d, scale, causal,
)
}
}
pub mod conv2d {
use super::*;
pub fn conv2d_f32(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() < 2 || inputs.len() > 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_f32: expected 2-3 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
if inputs.len() == 3 {
return Err(Error::Msg(
"vulkan_dispatch::conv2d::conv2d_f32: bias-fused conv2d not supported \
on Vulkan yet; bias is a follow-up. Route picker should choose a \
fused-conv alternative (CPU/CUDA) when bias is present."
.to_string(),
)
.bt());
}
let (x_shape, w_shape, stride, padding, dilation, groups) = match params {
OpParams::Conv2D {
x_shape,
w_shape,
out_shape: _,
stride,
padding,
dilation,
groups,
} => (*x_shape, *w_shape, *stride, *padding, *dilation, *groups),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_f32: expected OpParams::Conv2D, got {:?}",
other,
))
.bt());
}
};
if dilation != (1, 1) {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_f32: dilation {dilation:?} != (1,1) \
not yet supported on Vulkan; route picker should fall back to CPU/CUDA"
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let input = vulkan_input(&in_guard)?;
let weight = vulkan_input(&w_guard)?;
let backend = input.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::conv2d::conv2d_f32: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.conv2d_f32_bytes(
input, weight, out, x_shape, w_shape, stride, padding, groups,
)
}
pub fn conv2d_bf16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() < 2 || inputs.len() > 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_bf16: expected 2-3 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
if inputs.len() == 3 {
return Err(Error::Msg(
"vulkan_dispatch::conv2d::conv2d_bf16: bias-fused conv2d not supported \
on Vulkan yet; route picker should pick a fused-conv alternative when \
bias is present."
.to_string(),
)
.bt());
}
let (x_shape, w_shape, stride, padding, dilation, groups) = match params {
OpParams::Conv2D {
x_shape,
w_shape,
out_shape: _,
stride,
padding,
dilation,
groups,
} => (*x_shape, *w_shape, *stride, *padding, *dilation, *groups),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_bf16: expected OpParams::Conv2D, got {other:?}",
)).bt());
}
};
if dilation != (1, 1) {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_bf16: dilation {dilation:?} != (1,1) \
not yet supported on Vulkan; route picker should fall back to CPU/CUDA",
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let input = vulkan_input(&in_guard)?;
let weight = vulkan_input(&w_guard)?;
let backend = input.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::conv2d::conv2d_bf16: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.conv2d_bf16_bytes(
input, weight, out, x_shape, w_shape, stride, padding, groups,
)
}
pub fn conv2d_f16(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
params: &OpParams,
) -> Result<()> {
if inputs.len() < 2 || inputs.len() > 3 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_f16: expected 2-3 inputs + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
if inputs.len() == 3 {
return Err(Error::Msg(
"vulkan_dispatch::conv2d::conv2d_f16: bias-fused conv2d not supported \
on Vulkan yet"
.to_string(),
)
.bt());
}
let (x_shape, w_shape, stride, padding, dilation, groups) = match params {
OpParams::Conv2D {
x_shape,
w_shape,
out_shape: _,
stride,
padding,
dilation,
groups,
} => (*x_shape, *w_shape, *stride, *padding, *dilation, *groups),
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_f16: expected OpParams::Conv2D, got {other:?}",
))
.bt());
}
};
if dilation != (1, 1) {
return Err(Error::Msg(format!(
"vulkan_dispatch::conv2d::conv2d_f16: dilation {dilation:?} != (1,1) \
not yet supported on Vulkan",
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let w_guard = read_storage(&inputs[1])?;
let mut out_guard = write_storage(&outputs[0])?;
let input = vulkan_input(&in_guard)?;
let weight = vulkan_input(&w_guard)?;
let backend = input.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::conv2d::conv2d_f16: input has no VulkanBackend handle."
.to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.conv2d_f16_bytes(
input, weight, out, x_shape, w_shape, stride, padding, groups,
)
}
}
pub mod cast_f8e4m3 {
use super::*;
pub fn cast_f8e4m3(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast_f8e4m3: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let mut out_guard = write_storage(&outputs[0])?;
let src_dtype = in_guard.dtype;
let dst_dtype = out_guard.dtype;
let src_elem = match src_dtype {
DType::F32 => 4,
DType::F16 => 2,
DType::BF16 => 2,
DType::F8E4M3 => 1,
other => {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast_f8e4m3: unsupported src dtype {other:?}",
))
.bt());
}
};
let a = vulkan_input(&in_guard)?;
let n_bytes = a.len_bytes();
if n_bytes % src_elem != 0 {
return Err(Error::Msg(format!(
"vulkan_dispatch::cast_f8e4m3: input bytes {n_bytes} not a multiple of \
src elem size {src_elem} ({src_dtype:?})",
))
.bt());
}
let n = n_bytes / src_elem;
let backend = a.backend().ok_or_else(|| {
Error::Msg(
"vulkan_dispatch::cast_f8e4m3: input has no VulkanBackend handle.".to_string(),
)
.bt()
})?;
let out = vulkan_output(&mut out_guard)?;
backend.cast_f8e4m3_bytes(src_dtype, dst_dtype, a, out, n)
}
}
pub fn copy_to_cpu_vulkan(
inputs: &[Arc<RwLock<Storage>>],
outputs: &mut [Arc<RwLock<Storage>>],
_layouts: &[Layout],
_params: &OpParams,
) -> Result<()> {
if inputs.len() != 1 || outputs.len() != 1 {
return Err(Error::Msg(format!(
"copy_to_cpu_vulkan: expected 1 input + 1 output, got {} + {}",
inputs.len(),
outputs.len(),
))
.bt());
}
let in_guard = read_storage(&inputs[0])?;
let vk_src = vulkan_input(&in_guard)?;
let backend = vk_src.backend().ok_or_else(|| {
Error::Msg(
"copy_to_cpu_vulkan: Vulkan input has no backend handle. \
Storages flowing through the pipelined-executor binding-table \
Copy path must come from VulkanBackend::alloc_bytes_handle / \
upload_bytes_handle."
.to_string(),
)
.bt()
})?;
let mut out_guard = write_storage(&outputs[0])?;
match &mut out_guard.inner {
BackendStorage::Cpu(_) => {
let bytes = backend.download_bytes(vk_src)?;
let dst = crate::dispatch::cpu_output(&mut out_guard)?;
let n = bytes.len().min(dst.len_bytes());
dst.bytes_mut()[..n].copy_from_slice(&bytes[..n]);
Ok(())
}
BackendStorage::Vulkan(_) => {
let copied = backend.slot_copy_to_new_handle(vk_src, 0, vk_src.len_bytes())?;
let dst = vulkan_output(&mut out_guard)?;
*dst = copied;
Ok(())
}
#[allow(unreachable_patterns)]
other => Err(Error::Msg(format!(
"copy_to_cpu_vulkan: unsupported output substrate {:?} \
(Vulkan sources copy to CPU or Vulkan outputs only; \
cross-vendor GPU transfer goes through host staging as \
two Copy hops)",
std::mem::discriminant(other),
))
.bt()),
}
}
const VULKAN_CAST_CONTRACT: &str = include_str!("../../docs/kernel-contracts/vulkan/cast.fkc.md");
fn register_vulkan_cast_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_CAST_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan cast contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan cast contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan cast contract must register into the binding table");
}
const VULKAN_ELEMENTWISE_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/elementwise.fkc.md");
fn register_vulkan_elementwise_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_ELEMENTWISE_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan elementwise contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan elementwise contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan elementwise contract must register into the binding table");
}
const VULKAN_MATMUL_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/matmul.fkc.md");
fn register_vulkan_matmul_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_MATMUL_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan matmul contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan matmul contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan matmul contract must register into the binding table");
}
const VULKAN_CONV_CONTRACT: &str = include_str!("../../docs/kernel-contracts/vulkan/conv.fkc.md");
fn register_vulkan_conv_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_CONV_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan conv contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan conv contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan conv contract must register into the binding table");
}
const VULKAN_QMATMUL_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/qmatmul.fkc.md");
fn register_vulkan_qmatmul_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_QMATMUL_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan qmatmul contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan qmatmul contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan qmatmul contract must register into the binding table");
}
const VULKAN_SELECT_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/select.fkc.md");
fn register_vulkan_select_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_SELECT_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan select contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan select contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan select contract must register into the binding table");
}
const VULKAN_SCATTER_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/scatter.fkc.md");
fn register_vulkan_scatter_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_SCATTER_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan scatter contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan scatter contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan scatter contract must register into the binding table");
}
const VULKAN_MOVEMENT_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/movement.fkc.md");
fn register_vulkan_movement_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_MOVEMENT_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan movement contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan movement contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan movement contract must register into the binding table");
}
const VULKAN_SHAPE_CONTRACT: &str = include_str!("../../docs/kernel-contracts/vulkan/shape.fkc.md");
fn register_vulkan_shape_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_SHAPE_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan shape contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan shape contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan shape contract must register into the binding table");
}
const VULKAN_PAD_COPY_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/pad-copy.fkc.md");
fn register_vulkan_pad_copy_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_PAD_COPY_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan pad-copy contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan pad-copy contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan pad-copy contract must register into the binding table");
}
const VULKAN_WRITE_SLICE_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/write-slice.fkc.md");
fn register_vulkan_write_slice_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_WRITE_SLICE_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan write-slice contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan write-slice contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan write-slice contract must register into the binding table");
}
const VULKAN_ROPE_CONTRACT: &str = include_str!("../../docs/kernel-contracts/vulkan/rope.fkc.md");
fn register_vulkan_rope_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_ROPE_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan rope contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan rope contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan rope contract must register into the binding table");
}
const VULKAN_REDUCE_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/reduce-prims.fkc.md");
fn register_vulkan_reduce_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_REDUCE_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan reduce contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan reduce contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan reduce contract must register into the binding table");
}
const VULKAN_NORM_CONTRACT: &str = include_str!("../../docs/kernel-contracts/vulkan/norm.fkc.md");
fn register_vulkan_norm_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_NORM_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan norm contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan norm contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan norm contract must register into the binding table");
}
const VULKAN_ATTENTION_CONTRACT: &str =
include_str!("../../docs/kernel-contracts/vulkan/attention.fkc.md");
fn register_vulkan_attention_from_contract(table: &mut KernelBindingTable) {
let provider =
crate::fkc::import_bundle_str(VULKAN_ATTENTION_CONTRACT, &crate::fkc::VulkanLinkRegistry)
.expect(
"authored Vulkan attention contract must import \
(embedded via include_str!, resolved through VulkanLinkRegistry)",
);
debug_assert!(
provider.fused.is_empty(),
"vulkan attention contract declares no fused ops",
);
let mut fused = crate::fused::FusedKernelRegistry::new();
provider
.register_into(table, &mut fused)
.expect("Vulkan attention contract must register into the binding table");
}
pub fn register_vulkan_kernels(table: &mut KernelBindingTable) {
let vk = BackendId::Vulkan;
{
}
register_vulkan_cast_from_contract(table);
register_vulkan_elementwise_from_contract(table);
register_vulkan_matmul_from_contract(table);
register_vulkan_conv_from_contract(table);
register_vulkan_select_from_contract(table);
register_vulkan_scatter_from_contract(table);
register_vulkan_movement_from_contract(table);
register_vulkan_shape_from_contract(table);
register_vulkan_pad_copy_from_contract(table);
register_vulkan_write_slice_from_contract(table);
register_vulkan_rope_from_contract(table);
register_vulkan_reduce_from_contract(table);
register_vulkan_norm_from_contract(table);
register_vulkan_qmatmul_from_contract(table);
register_vulkan_attention_from_contract(table);
table.fill_unset_cost_for_backend(vk, crate::cost::default_cost_for_op_kind);
}
#[cfg(test)]
mod cast_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_cast_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let cases: &[(DType, DType, KernelRef)] = &[
(DType::F32, DType::F16, cast::cast_f32_half),
(DType::F16, DType::F32, cast::cast_f32_half),
(DType::F32, DType::BF16, cast::cast_f32_half),
(DType::BF16, DType::F32, cast::cast_f32_half),
(DType::F32, DType::F64, cast::cast_f32_f64),
(DType::F64, DType::F32, cast::cast_f32_f64),
(DType::F32, DType::F8E4M3, cast_f8e4m3::cast_f8e4m3),
(DType::F8E4M3, DType::F32, cast_f8e4m3::cast_f8e4m3),
(DType::F16, DType::F8E4M3, cast_f8e4m3::cast_f8e4m3),
(DType::F8E4M3, DType::F16, cast_f8e4m3::cast_f8e4m3),
(DType::BF16, DType::F8E4M3, cast_f8e4m3::cast_f8e4m3),
(DType::F8E4M3, DType::BF16, cast_f8e4m3::cast_f8e4m3),
];
let mut checked = 0usize;
for (src, dst, expected) in cases {
let alts = table.lookup_alternatives(OpKind::Cast, &[*src, *dst], vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize)
.unwrap_or_else(|| {
panic!(
"Cast [{src:?}, {dst:?}]/Vulkan: the production wrapper must be bound \
FROM the vulkan cast contract in register_vulkan_kernels — found {} \
alternative(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
)
});
assert_eq!(
entry.kernel_source, "vulkan-slang",
"Cast [{src:?}, {dst:?}]: cast family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"Cast [{src:?}, {dst:?}]: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
}
assert_eq!(checked, 12, "all 12 (SRC, DST) cast pairs checked");
}
}
#[cfg(test)]
mod elementwise_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_elementwise_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let cases: &[(OpKind, &[DType], KernelRef, bool)] = &[
(
OpKind::NegElementwise,
&[DType::F32, DType::F32],
unary::neg_f32 as KernelRef,
true,
),
(
OpKind::NegElementwise,
&[DType::F16, DType::F16],
unary_f16::neg_f16 as KernelRef,
true,
),
(
OpKind::NegElementwise,
&[DType::F64, DType::F64],
unary_f64::neg_f64 as KernelRef,
true,
),
(
OpKind::NegElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::neg_bf16 as KernelRef,
false,
),
(
OpKind::SqrElementwise,
&[DType::F32, DType::F32],
unary::sqr_f32 as KernelRef,
true,
),
(
OpKind::SqrElementwise,
&[DType::F16, DType::F16],
unary_f16::sqr_f16 as KernelRef,
true,
),
(
OpKind::SqrElementwise,
&[DType::F64, DType::F64],
unary_f64::sqr_f64 as KernelRef,
true,
),
(
OpKind::SqrElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::sqr_bf16 as KernelRef,
false,
),
(
OpKind::SqrtElementwise,
&[DType::F32, DType::F32],
unary::sqrt_f32 as KernelRef,
true,
),
(
OpKind::SqrtElementwise,
&[DType::F16, DType::F16],
unary_f16::sqrt_f16 as KernelRef,
true,
),
(
OpKind::SqrtElementwise,
&[DType::F64, DType::F64],
unary_f64::sqrt_f64 as KernelRef,
true,
),
(
OpKind::SqrtElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::sqrt_bf16 as KernelRef,
false,
),
(
OpKind::ExpElementwise,
&[DType::F32, DType::F32],
unary::exp_f32 as KernelRef,
true,
),
(
OpKind::ExpElementwise,
&[DType::F16, DType::F16],
unary_f16::exp_f16 as KernelRef,
true,
),
(
OpKind::ExpElementwise,
&[DType::F64, DType::F64],
unary_f64::exp_f64 as KernelRef,
true,
),
(
OpKind::ExpElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::exp_bf16 as KernelRef,
false,
),
(
OpKind::LogElementwise,
&[DType::F32, DType::F32],
unary::log_f32 as KernelRef,
true,
),
(
OpKind::LogElementwise,
&[DType::F16, DType::F16],
unary_f16::log_f16 as KernelRef,
true,
),
(
OpKind::LogElementwise,
&[DType::F64, DType::F64],
unary_f64::log_f64 as KernelRef,
true,
),
(
OpKind::LogElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::log_bf16 as KernelRef,
false,
),
(
OpKind::SinElementwise,
&[DType::F32, DType::F32],
unary::sin_f32 as KernelRef,
true,
),
(
OpKind::SinElementwise,
&[DType::F16, DType::F16],
unary_f16::sin_f16 as KernelRef,
true,
),
(
OpKind::SinElementwise,
&[DType::F64, DType::F64],
unary_f64::sin_f64 as KernelRef,
true,
),
(
OpKind::SinElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::sin_bf16 as KernelRef,
false,
),
(
OpKind::CosElementwise,
&[DType::F32, DType::F32],
unary::cos_f32 as KernelRef,
true,
),
(
OpKind::CosElementwise,
&[DType::F16, DType::F16],
unary_f16::cos_f16 as KernelRef,
true,
),
(
OpKind::CosElementwise,
&[DType::F64, DType::F64],
unary_f64::cos_f64 as KernelRef,
true,
),
(
OpKind::CosElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::cos_bf16 as KernelRef,
false,
),
(
OpKind::TanhElementwise,
&[DType::F32, DType::F32],
unary::tanh_f32 as KernelRef,
true,
),
(
OpKind::TanhElementwise,
&[DType::F16, DType::F16],
unary_f16::tanh_f16 as KernelRef,
true,
),
(
OpKind::TanhElementwise,
&[DType::F64, DType::F64],
unary_f64::tanh_f64 as KernelRef,
true,
),
(
OpKind::TanhElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::tanh_bf16 as KernelRef,
false,
),
(
OpKind::SigmoidElementwise,
&[DType::F32, DType::F32],
unary::sigmoid_f32 as KernelRef,
true,
),
(
OpKind::SigmoidElementwise,
&[DType::F16, DType::F16],
unary_f16::sigmoid_f16 as KernelRef,
true,
),
(
OpKind::SigmoidElementwise,
&[DType::F64, DType::F64],
unary_f64::sigmoid_f64 as KernelRef,
true,
),
(
OpKind::SigmoidElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::sigmoid_bf16 as KernelRef,
false,
),
(
OpKind::SiluElementwise,
&[DType::F32, DType::F32],
unary::silu_f32 as KernelRef,
true,
),
(
OpKind::SiluElementwise,
&[DType::F16, DType::F16],
unary_f16::silu_f16 as KernelRef,
true,
),
(
OpKind::SiluElementwise,
&[DType::F64, DType::F64],
unary_f64::silu_f64 as KernelRef,
true,
),
(
OpKind::SiluElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::silu_bf16 as KernelRef,
false,
),
(
OpKind::GeluTanhElementwise,
&[DType::F32, DType::F32],
unary::gelu_f32 as KernelRef,
true,
),
(
OpKind::GeluTanhElementwise,
&[DType::F16, DType::F16],
unary_f16::gelu_f16 as KernelRef,
true,
),
(
OpKind::GeluTanhElementwise,
&[DType::F64, DType::F64],
unary_f64::gelu_f64 as KernelRef,
true,
),
(
OpKind::GeluTanhElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::gelu_bf16 as KernelRef,
false,
),
(
OpKind::ReluElementwise,
&[DType::F32, DType::F32],
unary::relu_f32 as KernelRef,
true,
),
(
OpKind::ReluElementwise,
&[DType::F16, DType::F16],
unary_f16::relu_f16 as KernelRef,
true,
),
(
OpKind::ReluElementwise,
&[DType::F64, DType::F64],
unary_f64::relu_f64 as KernelRef,
true,
),
(
OpKind::ReluElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::relu_bf16 as KernelRef,
false,
),
(
OpKind::StepElementwise,
&[DType::F32, DType::F32],
unary::step_f32 as KernelRef,
true,
),
(
OpKind::StepElementwise,
&[DType::F16, DType::F16],
unary_f16::step_f16 as KernelRef,
true,
),
(
OpKind::StepElementwise,
&[DType::F64, DType::F64],
unary_f64::step_f64 as KernelRef,
true,
),
(
OpKind::StepElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::step_bf16 as KernelRef,
false,
),
(
OpKind::AbsElementwise,
&[DType::F32, DType::F32],
unary::abs_f32 as KernelRef,
true,
),
(
OpKind::AbsElementwise,
&[DType::F16, DType::F16],
unary_f16::abs_f16 as KernelRef,
true,
),
(
OpKind::AbsElementwise,
&[DType::F64, DType::F64],
unary_f64::abs_f64 as KernelRef,
true,
),
(
OpKind::AbsElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::abs_bf16 as KernelRef,
false,
),
(
OpKind::SignElementwise,
&[DType::F32, DType::F32],
unary::sign_f32 as KernelRef,
true,
),
(
OpKind::SignElementwise,
&[DType::F16, DType::F16],
unary_f16::sign_f16 as KernelRef,
true,
),
(
OpKind::SignElementwise,
&[DType::F64, DType::F64],
unary_f64::sign_f64 as KernelRef,
true,
),
(
OpKind::SignElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::sign_bf16 as KernelRef,
false,
),
(
OpKind::RecipElementwise,
&[DType::F32, DType::F32],
unary::recip_f32 as KernelRef,
true,
),
(
OpKind::RecipElementwise,
&[DType::F16, DType::F16],
unary_f16::recip_f16 as KernelRef,
true,
),
(
OpKind::RecipElementwise,
&[DType::F64, DType::F64],
unary_f64::recip_f64 as KernelRef,
true,
),
(
OpKind::RecipElementwise,
&[DType::BF16, DType::BF16],
unary_bf16::recip_bf16 as KernelRef,
false,
),
(
OpKind::AddElementwise,
&[DType::F32, DType::F32, DType::F32],
binary::add_f32 as KernelRef,
true,
),
(
OpKind::AddElementwise,
&[DType::F16, DType::F16, DType::F16],
binary_f16::add_f16 as KernelRef,
true,
),
(
OpKind::AddElementwise,
&[DType::F64, DType::F64, DType::F64],
binary_f64::add_f64 as KernelRef,
true,
),
(
OpKind::AddElementwise,
&[DType::BF16, DType::BF16, DType::BF16],
binary_bf16::add_bf16 as KernelRef,
true,
),
(
OpKind::SubElementwise,
&[DType::F32, DType::F32, DType::F32],
binary::sub_f32 as KernelRef,
true,
),
(
OpKind::SubElementwise,
&[DType::F16, DType::F16, DType::F16],
binary_f16::sub_f16 as KernelRef,
true,
),
(
OpKind::SubElementwise,
&[DType::F64, DType::F64, DType::F64],
binary_f64::sub_f64 as KernelRef,
true,
),
(
OpKind::SubElementwise,
&[DType::BF16, DType::BF16, DType::BF16],
binary_bf16::sub_bf16 as KernelRef,
true,
),
(
OpKind::MulElementwise,
&[DType::F32, DType::F32, DType::F32],
binary::mul_f32 as KernelRef,
true,
),
(
OpKind::MulElementwise,
&[DType::F16, DType::F16, DType::F16],
binary_f16::mul_f16 as KernelRef,
true,
),
(
OpKind::MulElementwise,
&[DType::F64, DType::F64, DType::F64],
binary_f64::mul_f64 as KernelRef,
true,
),
(
OpKind::MulElementwise,
&[DType::BF16, DType::BF16, DType::BF16],
binary_bf16::mul_bf16 as KernelRef,
true,
),
(
OpKind::DivElementwise,
&[DType::F32, DType::F32, DType::F32],
binary::div_f32 as KernelRef,
true,
),
(
OpKind::DivElementwise,
&[DType::F16, DType::F16, DType::F16],
binary_f16::div_f16 as KernelRef,
true,
),
(
OpKind::DivElementwise,
&[DType::F64, DType::F64, DType::F64],
binary_f64::div_f64 as KernelRef,
true,
),
(
OpKind::DivElementwise,
&[DType::BF16, DType::BF16, DType::BF16],
binary_bf16::div_bf16 as KernelRef,
true,
),
(
OpKind::MaximumElementwise,
&[DType::F32, DType::F32, DType::F32],
binary::maximum_f32 as KernelRef,
true,
),
(
OpKind::MaximumElementwise,
&[DType::F16, DType::F16, DType::F16],
binary_f16::maximum_f16 as KernelRef,
true,
),
(
OpKind::MaximumElementwise,
&[DType::F64, DType::F64, DType::F64],
binary_f64::maximum_f64 as KernelRef,
true,
),
(
OpKind::MaximumElementwise,
&[DType::BF16, DType::BF16, DType::BF16],
binary_bf16::maximum_bf16 as KernelRef,
true,
),
(
OpKind::MinimumElementwise,
&[DType::F32, DType::F32, DType::F32],
binary::minimum_f32 as KernelRef,
true,
),
(
OpKind::MinimumElementwise,
&[DType::F16, DType::F16, DType::F16],
binary_f16::minimum_f16 as KernelRef,
true,
),
(
OpKind::MinimumElementwise,
&[DType::F64, DType::F64, DType::F64],
binary_f64::minimum_f64 as KernelRef,
true,
),
(
OpKind::MinimumElementwise,
&[DType::BF16, DType::BF16, DType::BF16],
binary_bf16::minimum_bf16 as KernelRef,
true,
),
(
OpKind::Affine,
&[DType::F32, DType::F32],
affine::affine_f32 as KernelRef,
true,
),
(
OpKind::Affine,
&[DType::F16, DType::F16],
affine::affine_f16 as KernelRef,
true,
),
(
OpKind::Affine,
&[DType::F64, DType::F64],
affine::affine_f64 as KernelRef,
true,
),
(
OpKind::Affine,
&[DType::BF16, DType::BF16],
affine::affine_bf16 as KernelRef,
false,
),
(
OpKind::ClampElementwise,
&[DType::F32, DType::F32],
clamp::clamp_f32 as KernelRef,
true,
),
(
OpKind::PowIElementwise,
&[DType::F32, DType::F32],
powi::powi_f32 as KernelRef,
true,
),
];
let mut checked = 0usize;
for (op, key, expected, expect_strided) in cases {
let alts = table.lookup_alternatives(*op, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan elementwise contract; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: elementwise family must be contract-sourced (kernel_source=vulkan-slang); got {:?}",
entry.kernel_source,
);
assert_eq!(
entry.caps.strided_input, *expect_strided,
"{op:?} {key:?}: caps must ride through the import truthfully (strided_input matches the hand-written reg)",
);
checked += 1;
}
assert_eq!(
checked, 94,
"all 94 (op, dtype) elementwise bindings checked"
);
}
}
#[cfg(test)]
mod matmul_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_matmul_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let cases: &[(&[DType], KernelRef)] = &[
(
&[DType::F32, DType::F32, DType::F32],
matmul::matmul_f32 as KernelRef,
),
(
&[DType::F32, DType::BF16, DType::F32],
matmul::matmul_f32_bf16_b as KernelRef,
),
(
&[DType::BF16, DType::BF16, DType::F32],
matmul::matmul_bf16_bf16_f32 as KernelRef,
),
(
&[DType::BF16, DType::BF16, DType::BF16],
matmul::matmul_bf16_bf16_bf16 as KernelRef,
),
(
&[DType::F16, DType::F16, DType::F16],
matmul::matmul_f16_f16_f16 as KernelRef,
),
(
&[DType::F16, DType::F16, DType::F32],
matmul::matmul_f16_f16_f32 as KernelRef,
),
];
let mut checked = 0usize;
for (key, expected) in cases {
let alts = table.lookup_alternatives(OpKind::MatMul, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"MatMul {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
matmul contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"MatMul {key:?}: matmul family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"MatMul {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
}
assert_eq!(
checked, 6,
"all 6 (MatMul, [lhs,rhs,out]) Vulkan combos checked"
);
}
}
#[cfg(test)]
mod conv_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_conv_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let cases: &[(&[DType], KernelRef)] = &[
(
&[DType::F32, DType::F32, DType::F32],
conv2d::conv2d_f32 as KernelRef,
),
(
&[DType::BF16, DType::BF16, DType::BF16],
conv2d::conv2d_bf16 as KernelRef,
),
(
&[DType::F16, DType::F16, DType::F16],
conv2d::conv2d_f16 as KernelRef,
),
];
let mut checked = 0usize;
for (key, expected) in cases {
let alts = table.lookup_alternatives(OpKind::Conv2D, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"Conv2D {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
conv contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"Conv2D {key:?}: conv family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"Conv2D {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
}
assert_eq!(
checked, 3,
"all 3 (Conv2D, [x, weight, out]) Vulkan combos checked"
);
}
}
#[cfg(test)]
mod select_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_select_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let f32 = DType::F32;
let f16 = DType::F16;
let bf16 = DType::BF16;
let f64 = DType::F64;
let u8 = DType::U8;
let u32 = DType::U32;
let boolean = DType::Bool;
let cases: &[(OpKind, &[DType], KernelRef)] = &[
(
OpKind::IndexSelect,
&[f32, u32, f32],
indexing::index_select_f32 as KernelRef,
),
(
OpKind::IndexSelect,
&[f16, u32, f16],
indexing::index_select_f16 as KernelRef,
),
(
OpKind::IndexSelect,
&[bf16, u32, bf16],
indexing::index_select_bf16 as KernelRef,
),
(
OpKind::IndexSelect,
&[f64, u32, f64],
indexing::index_select_f64 as KernelRef,
),
(
OpKind::Gather,
&[f32, u32, f32],
gather::gather as KernelRef,
),
(
OpKind::Gather,
&[f16, u32, f16],
gather::gather as KernelRef,
),
(
OpKind::Gather,
&[bf16, u32, bf16],
gather::gather as KernelRef,
),
(
OpKind::Gather,
&[f64, u32, f64],
gather::gather as KernelRef,
),
(OpKind::Gather, &[u8, u32, u8], gather::gather as KernelRef),
(
OpKind::Gather,
&[u32, u32, u32],
gather::gather as KernelRef,
),
(
OpKind::MaskedFill,
&[f32, boolean, f32],
masked_fill::masked_fill as KernelRef,
),
(
OpKind::MaskedFill,
&[f16, boolean, f16],
masked_fill::masked_fill as KernelRef,
),
(
OpKind::MaskedFill,
&[bf16, boolean, bf16],
masked_fill::masked_fill as KernelRef,
),
(
OpKind::MaskedFill,
&[f64, boolean, f64],
masked_fill::masked_fill as KernelRef,
),
(
OpKind::MaskedFill,
&[u8, boolean, u8],
masked_fill::masked_fill as KernelRef,
),
(
OpKind::MaskedFill,
&[u32, boolean, u32],
masked_fill::masked_fill as KernelRef,
),
];
let mut checked = 0usize;
for (op, key, expected) in cases {
let alts = table.lookup_alternatives(*op, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
select contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: select family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?} {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
}
assert_eq!(
checked, 16,
"all 16 (IndexSelect/Gather/MaskedFill) select keys checked"
);
}
}
#[cfg(test)]
mod scatter_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_scatter_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let f32 = DType::F32;
let f16 = DType::F16;
let bf16 = DType::BF16;
let f64 = DType::F64;
let u32 = DType::U32;
let cases: &[(OpKind, &[DType], KernelRef)] = &[
(
OpKind::IndexAdd,
&[f32, u32, f32, f32],
index_add::index_add_f32 as KernelRef,
),
(
OpKind::IndexAdd,
&[f64, u32, f64, f64],
index_add::index_add_f64 as KernelRef,
),
(
OpKind::IndexAdd,
&[bf16, u32, bf16, bf16],
index_add::index_add_bf16 as KernelRef,
),
(
OpKind::IndexAdd,
&[f16, u32, f16, f16],
index_add::index_add_f16 as KernelRef,
),
(
OpKind::ScatterAdd,
&[f32, u32, f32, f32],
scatter_add::scatter_add_f32 as KernelRef,
),
(
OpKind::ScatterAdd,
&[f64, u32, f64, f64],
scatter_add::scatter_add_f64 as KernelRef,
),
(
OpKind::ScatterAdd,
&[bf16, u32, bf16, bf16],
scatter_add::scatter_add_bf16 as KernelRef,
),
(
OpKind::ScatterAdd,
&[f16, u32, f16, f16],
scatter_add::scatter_add_f16 as KernelRef,
),
];
let mut checked = 0usize;
for (op, key, expected) in cases {
let alts = table.lookup_alternatives(*op, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
scatter contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: scatter family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?} {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
}
assert_eq!(
checked, 8,
"all 8 (IndexAdd/ScatterAdd) scatter keys checked"
);
}
}
#[cfg(test)]
mod movement_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_movement_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let f32 = DType::F32;
let f16 = DType::F16;
let bf16 = DType::BF16;
let f64 = DType::F64;
let cases: &[(OpKind, &[DType], KernelRef)] = &[
(OpKind::Concat, &[f32, f32], concat::concat_f32 as KernelRef),
(OpKind::Concat, &[f16, f16], concat::concat_f16 as KernelRef),
(
OpKind::Concat,
&[bf16, bf16],
concat::concat_bf16 as KernelRef,
),
(OpKind::Concat, &[f64, f64], concat::concat_f64 as KernelRef),
(OpKind::CumSum, &[f32, f32], cumsum::cumsum_f32 as KernelRef),
(OpKind::CumSum, &[f64, f64], cumsum::cumsum_f64 as KernelRef),
(OpKind::CumSum, &[f16, f16], cumsum::cumsum_f16 as KernelRef),
(
OpKind::CumSum,
&[bf16, bf16],
cumsum::cumsum_bf16 as KernelRef,
),
];
let mut checked = 0usize;
for (op, key, expected) in cases {
let alts = table.lookup_alternatives(*op, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
movement contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: movement family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
entry.caps.strided_input,
"{op:?} {key:?}: caps must ride through the import truthfully (strided_input=true)",
);
checked += 1;
}
assert_eq!(checked, 8, "all 8 (Concat/CumSum) movement keys checked");
}
}
#[cfg(test)]
mod shape_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_shape_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let dts = [
DType::F32,
DType::F16,
DType::BF16,
DType::F64,
DType::I32,
DType::U32,
DType::I64,
];
let ops: &[(OpKind, KernelRef, bool)] = &[
(OpKind::Triu, triangular::triu as KernelRef, false),
(OpKind::Tril, triangular::tril as KernelRef, false),
(OpKind::Flip, flip::flip as KernelRef, true),
(OpKind::Roll, roll::roll as KernelRef, true),
];
let mut checked = 0usize;
for &dt in &dts {
for (op, expected, expect_strided) in ops {
let key = [dt, dt];
let alts = table.lookup_alternatives(*op, &key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
shape contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: shape family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert_eq!(
entry.caps.strided_input, *expect_strided,
"{op:?} {key:?}: caps must ride through the import truthfully (strided_input)",
);
checked += 1;
}
}
assert_eq!(
checked, 28,
"all 28 (Triu/Tril/Flip/Roll × 7 dtypes) shape keys checked"
);
}
}
#[cfg(test)]
mod pad_copy_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_pad_copy_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let pad_dts = [
DType::F32,
DType::F16,
DType::BF16,
DType::F64,
DType::U8,
DType::U32,
];
let copy_dts = [
DType::F32,
DType::F16,
DType::BF16,
DType::F64,
DType::U32,
DType::U8,
DType::I16,
DType::I32,
DType::I64,
];
let mut checked = 0usize;
let mut check = |op: OpKind, key: &[DType], expected: KernelRef| {
let alts = table.lookup_alternatives(op, key, vk);
let entry = alts.iter().find(|e| e.kernel as usize == expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
pad-copy contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: pad-copy family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?} {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
};
for &dt in &pad_dts {
check(OpKind::Pad, &[dt, dt], pad::pad_const as KernelRef);
check(
OpKind::PadBackward,
&[dt, dt],
pad::pad_backward as KernelRef,
);
}
for &dt in ©_dts {
check(OpKind::Copy, &[dt, dt], copy_to_cpu_vulkan as KernelRef);
}
assert_eq!(
checked, 21,
"all 21 (Pad/PadBackward/Copy) pad-copy keys checked"
);
}
}
#[cfg(test)]
mod write_slice_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_write_slice_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let cases: &[(DType, KernelRef, KernelRef)] = &[
(
DType::F32,
write_slice::write_slice_b4 as KernelRef,
write_slice_rotating::write_slice_rotating_b4 as KernelRef,
),
(
DType::I32,
write_slice::write_slice_b4 as KernelRef,
write_slice_rotating::write_slice_rotating_b4 as KernelRef,
),
(
DType::U32,
write_slice::write_slice_b4 as KernelRef,
write_slice_rotating::write_slice_rotating_b4 as KernelRef,
),
(
DType::F16,
write_slice::write_slice_b2 as KernelRef,
write_slice_rotating::write_slice_rotating_b2 as KernelRef,
),
(
DType::BF16,
write_slice::write_slice_b2 as KernelRef,
write_slice_rotating::write_slice_rotating_b2 as KernelRef,
),
(
DType::F64,
write_slice::write_slice_b8 as KernelRef,
write_slice_rotating::write_slice_rotating_b8 as KernelRef,
),
(
DType::I64,
write_slice::write_slice_b8 as KernelRef,
write_slice_rotating::write_slice_rotating_b8 as KernelRef,
),
(
DType::U8,
write_slice::write_slice_b1 as KernelRef,
write_slice_rotating::write_slice_rotating_b1 as KernelRef,
),
(
DType::I8,
write_slice::write_slice_b1 as KernelRef,
write_slice_rotating::write_slice_rotating_b1 as KernelRef,
),
];
let mut checked = 0usize;
let mut check = |op: OpKind, key: &[DType], expected: KernelRef| {
let alts = table.lookup_alternatives(op, key, vk);
let entry = alts.iter().find(|e| e.kernel as usize == expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
write-slice contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: write-slice family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?} {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
};
for (dt, ws, wsr) in cases {
check(OpKind::WriteSlice, &[*dt, *dt], *ws);
check(OpKind::WriteSliceRotating, &[*dt, *dt], *wsr);
}
assert_eq!(
checked, 18,
"all 18 (WriteSlice/WriteSliceRotating × 9 dtypes) write-slice keys checked"
);
}
}
#[cfg(test)]
mod rope_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_rope_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let f32 = DType::F32;
let f16 = DType::F16;
let bf16 = DType::BF16;
let f64 = DType::F64;
let cases: &[(&[DType], KernelRef)] = &[
(&[f32, f32, f32, f32], attention::rope_f32 as KernelRef),
(&[f16, f16, f16, f16], attention::rope_f16 as KernelRef),
(&[f64, f64, f64, f64], attention::rope_f64 as KernelRef),
(&[bf16, bf16, bf16, bf16], attention::rope_bf16 as KernelRef),
];
let mut checked = 0usize;
for (key, expected) in cases {
let alts = table.lookup_alternatives(OpKind::Rope, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"Rope {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
rope contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"Rope {key:?}: rope family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
entry.caps.strided_input,
"Rope {key:?}: caps must ride through the import truthfully (strided_input=true)",
);
checked += 1;
}
assert_eq!(checked, 4, "all 4 (RoPE × dtype) rope keys checked");
}
}
#[cfg(test)]
mod reduce_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_reduce_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let u32 = DType::U32;
let mut checked = 0usize;
let mut check = |op: OpKind, key: &[DType], expected: KernelRef| {
let alts = table.lookup_alternatives(op, key, vk);
let entry = alts.iter().find(|e| e.kernel as usize == expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
reduce contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: reduce family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?} {key:?}: contiguous-only (strided_input=false)"
);
checked += 1;
};
let val: &[(OpKind, [KernelRef; 4])] = &[
(
OpKind::SumReduce,
[
reduce::sum_f32,
reduce::sum_f16,
reduce::sum_bf16,
reduce::sum_f64,
],
),
(
OpKind::MaxReduce,
[
reduce::max_f32,
reduce::max_f16,
reduce::max_bf16,
reduce::max_f64,
],
),
(
OpKind::MinReduce,
[
reduce::min_f32,
reduce::min_f16,
reduce::min_bf16,
reduce::min_f64,
],
),
(
OpKind::MeanReduce,
[
reduce::mean_f32,
reduce::mean_f16,
reduce::mean_bf16,
reduce::mean_f64,
],
),
];
let dts = [DType::F32, DType::F16, DType::BF16, DType::F64];
for (op, wraps) in val {
for (i, &dt) in dts.iter().enumerate() {
check(*op, &[dt, dt], wraps[i]);
}
}
let arg: &[(OpKind, [KernelRef; 4])] = &[
(
OpKind::ArgMaxDim,
[
arg_reduce::argmax_f32,
arg_reduce::argmax_f16,
arg_reduce::argmax_bf16,
arg_reduce::argmax_f64,
],
),
(
OpKind::ArgMinDim,
[
arg_reduce::argmin_f32,
arg_reduce::argmin_f16,
arg_reduce::argmin_bf16,
arg_reduce::argmin_f64,
],
),
];
for (op, wraps) in arg {
for (i, &dt) in dts.iter().enumerate() {
check(*op, &[dt, u32], wraps[i]);
}
}
assert_eq!(
checked, 24,
"all 24 (Sum/Max/Min/Mean + ArgMax/ArgMin × 4 dtypes) reduce keys checked"
);
}
}
#[cfg(test)]
mod norm_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_norm_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let mut checked = 0usize;
let mut check = |op: OpKind, key: &[DType], expected: KernelRef| {
let alts = table.lookup_alternatives(op, key, vk);
let entry = alts.iter().find(|e| e.kernel as usize == expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
norm contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: norm family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?} {key:?}: contiguous-only (strided_input=false)"
);
checked += 1;
};
let dts = [DType::F32, DType::F16, DType::BF16, DType::F64];
let un: &[(OpKind, [KernelRef; 4])] = &[
(
OpKind::SoftmaxLastDim,
[
softmax::softmax_f32,
softmax::softmax_f16,
softmax::softmax_bf16,
softmax::softmax_f64,
],
),
(
OpKind::LayerNormLastDim,
[
norm::layer_norm_f32,
norm::layer_norm_f16,
norm::layer_norm_bf16,
norm::layer_norm_f64,
],
),
(
OpKind::RmsNormLastDim,
[norm::rms_f32, norm::rms_f16, norm::rms_bf16, norm::rms_f64],
),
];
for (op, wraps) in un {
for (i, &dt) in dts.iter().enumerate() {
check(*op, &[dt, dt], wraps[i]);
}
}
let bwd: &[(OpKind, [KernelRef; 4])] = &[
(
OpKind::SoftmaxLastDimBackward,
[
softmax::softmax_last_dim_backward_f32,
softmax::softmax_last_dim_backward_f16,
softmax::softmax_last_dim_backward_bf16,
softmax::softmax_last_dim_backward_f64,
],
),
(
OpKind::LayerNormLastDimBackward,
[
norm::layer_norm_backward_f32,
norm::layer_norm_backward_f16,
norm::layer_norm_backward_bf16,
norm::layer_norm_backward_f64,
],
),
];
for (op, wraps) in bwd {
for (i, &dt) in dts.iter().enumerate() {
check(*op, &[dt, dt, dt], wraps[i]);
}
}
assert_eq!(
checked, 20,
"all 20 (Softmax/SoftmaxBwd/LayerNorm/LayerNormBwd/RmsNorm × 4 dtypes) norm keys checked"
);
}
}
#[cfg(test)]
mod qmatmul_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_qmatmul_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let key: &[DType] = &[DType::F32, DType::U32, DType::F32];
let expected = qmatmul::qmatmul_vk as KernelRef;
let alts = table.lookup_alternatives(OpKind::QMatMul, key, vk);
let entry = alts.iter().find(|e| e.kernel as usize == expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"QMatMul {key:?}/Vulkan: production wrapper (qmatmul_vk) must be bound FROM the \
vulkan qmatmul contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"QMatMul {key:?}: qmatmul must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"QMatMul {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
}
}
#[cfg(test)]
mod attention_contract_tests {
use super::*;
use crate::kernel::{KernelBindingTable, KernelRef};
#[test]
fn register_vulkan_kernels_binds_flash_attn_family_from_contract() {
let mut table = KernelBindingTable::new();
register_vulkan_kernels(&mut table);
let vk = BackendId::Vulkan;
let cases: &[(OpKind, &[DType], KernelRef)] = &[
(
OpKind::FlashAttn,
&[DType::F32, DType::F32, DType::F32, DType::F32],
flash_attn::flash_attn_f32 as KernelRef,
),
(
OpKind::FlashAttn,
&[DType::F32, DType::F32, DType::F32, DType::F32, DType::F32],
flash_attn::flash_attn_f32 as KernelRef,
),
(
OpKind::FlashAttn,
&[DType::BF16, DType::BF16, DType::BF16, DType::BF16],
flash_attn::flash_attn_bf16 as KernelRef,
),
(
OpKind::FlashAttn,
&[
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
DType::BF16,
],
flash_attn::flash_attn_bf16 as KernelRef,
),
(
OpKind::FlashAttn,
&[DType::F16, DType::F16, DType::F16, DType::F16],
flash_attn::flash_attn_f16 as KernelRef,
),
(
OpKind::FlashAttn,
&[DType::F16, DType::F16, DType::F16, DType::F16, DType::F16],
flash_attn::flash_attn_f16 as KernelRef,
),
(
OpKind::FlashAttnBackwardQ,
&[DType::F32, DType::F32, DType::F32, DType::F32, DType::F32],
flash_attn::flash_attn_backward_q_f32 as KernelRef,
),
(
OpKind::FlashAttnBackwardQ,
&[
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
],
flash_attn::flash_attn_backward_q_f32 as KernelRef,
),
(
OpKind::FlashAttnBackwardK,
&[DType::F32, DType::F32, DType::F32, DType::F32, DType::F32],
flash_attn::flash_attn_backward_k_f32 as KernelRef,
),
(
OpKind::FlashAttnBackwardK,
&[
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
],
flash_attn::flash_attn_backward_k_f32 as KernelRef,
),
(
OpKind::FlashAttnBackwardV,
&[DType::F32, DType::F32, DType::F32, DType::F32, DType::F32],
flash_attn::flash_attn_backward_v_f32 as KernelRef,
),
(
OpKind::FlashAttnBackwardV,
&[
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
DType::F32,
],
flash_attn::flash_attn_backward_v_f32 as KernelRef,
),
];
let mut checked = 0usize;
for (op, key, expected) in cases {
let alts = table.lookup_alternatives(*op, key, vk);
let entry = alts
.iter()
.find(|e| e.kernel as usize == *expected as usize);
let entry = match entry {
Some(e) => e,
None => panic!(
"{op:?} {key:?}/Vulkan: production wrapper must be bound FROM the vulkan \
attention contract in register_vulkan_kernels; found {} alt(s) with sources {:?}",
alts.len(),
alts.iter().map(|e| e.kernel_source).collect::<Vec<_>>(),
),
};
assert_eq!(
entry.kernel_source, "vulkan-slang",
"{op:?} {key:?}: FlashAttn family must be contract-sourced \
(kernel_source=\"vulkan-slang\"); got {:?}",
entry.kernel_source,
);
assert!(
!entry.caps.strided_input,
"{op:?} {key:?}: caps preserved (contiguous-only, strided_input=false)",
);
checked += 1;
}
assert_eq!(
checked, 12,
"all 12 FlashAttn family bindings (6 forward + 6 backward) checked"
);
}
}