use metal::foreign_types::ForeignType;
use metal::MTLSize;
use crate::buffer::MlxBuffer;
use crate::dtypes::DType;
use crate::encoder::CommandEncoder;
use crate::error::{MlxError, Result};
use crate::kernel_registry::KernelRegistry;
pub static FWHT_STANDALONE_SHADER_SOURCE: &str =
include_str!("../shaders/fwht_standalone.metal");
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
struct GpuFwhtParams {
head_dim: u32,
num_heads: u32,
}
pub fn dispatch_fwht_f32(
encoder: &mut CommandEncoder,
registry: &mut KernelRegistry,
device: &metal::DeviceRef,
data: &MlxBuffer,
num_heads: u32,
head_dim: u32,
) -> Result<()> {
let kernel_name = match head_dim {
256 => "fwht_standalone_f32_d256",
512 => "fwht_standalone_f32_d512",
_ => return Err(MlxError::InvalidArgument(
format!("fwht_standalone: unsupported head_dim={}", head_dim),
)),
};
let pipeline = registry.get_pipeline(kernel_name, device)?;
let params = GpuFwhtParams { head_dim, num_heads };
let threadgroups = MTLSize::new(num_heads as u64, 1, 1);
let threads_per_tg = MTLSize::new(32, 1, 1);
use crate::ops::encode_helpers::{as_bytes, KernelArg};
encoder.encode_threadgroups_with_args(
pipeline,
&[
(0, KernelArg::Buffer(data)),
(1, KernelArg::Bytes(as_bytes(¶ms))),
],
threadgroups,
threads_per_tg,
);
Ok(())
}
pub fn dispatch_fwht_sign_premult_f32(
encoder: &mut CommandEncoder,
registry: &mut KernelRegistry,
device: &metal::DeviceRef,
data: &MlxBuffer,
num_heads: u32,
head_dim: u32,
) -> Result<()> {
let kernel_name = match head_dim {
256 => "fwht_sign_premult_f32_d256",
512 => "fwht_sign_premult_f32_d512",
_ => return Err(MlxError::InvalidArgument(
format!("fwht_sign_premult: unsupported head_dim={}", head_dim),
)),
};
let pipeline = registry.get_pipeline(kernel_name, device)?;
let params = GpuFwhtParams { head_dim, num_heads };
let threadgroups = MTLSize::new(num_heads as u64, 1, 1);
let threads_per_tg = MTLSize::new(32, 1, 1);
use crate::ops::encode_helpers::{as_bytes, KernelArg};
encoder.encode_threadgroups_with_args(
pipeline,
&[
(0, KernelArg::Buffer(data)),
(1, KernelArg::Bytes(as_bytes(¶ms))),
],
threadgroups,
threads_per_tg,
);
Ok(())
}
pub fn dispatch_fwht_sign_undo_f32(
encoder: &mut CommandEncoder,
registry: &mut KernelRegistry,
device: &metal::DeviceRef,
data: &MlxBuffer,
num_heads: u32,
head_dim: u32,
) -> Result<()> {
let kernel_name = match head_dim {
256 => "fwht_sign_undo_f32_d256",
512 => "fwht_sign_undo_f32_d512",
_ => return Err(MlxError::InvalidArgument(
format!("fwht_sign_undo: unsupported head_dim={}", head_dim),
)),
};
let pipeline = registry.get_pipeline(kernel_name, device)?;
let params = GpuFwhtParams { head_dim, num_heads };
let threadgroups = MTLSize::new(num_heads as u64, 1, 1);
let threads_per_tg = MTLSize::new(32, 1, 1);
use crate::ops::encode_helpers::{as_bytes, KernelArg};
encoder.encode_threadgroups_with_args(
pipeline,
&[
(0, KernelArg::Buffer(data)),
(1, KernelArg::Bytes(as_bytes(¶ms))),
],
threadgroups,
threads_per_tg,
);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn dispatch_fwht_sign_undo_sigmoid_mul_f32(
encoder: &mut CommandEncoder,
registry: &mut KernelRegistry,
device: &metal::DeviceRef,
data: &MlxBuffer,
gate: &MlxBuffer,
output: &MlxBuffer,
num_heads: u32,
head_dim: u32,
) -> Result<()> {
const OP: &str = "fwht_sign_undo_sigmoid_mul";
let kernel_name = match head_dim {
256 => "fwht_sign_undo_sigmoid_mul_f32_d256",
_ => {
return Err(MlxError::InvalidArgument(format!(
"{OP}: unsupported head_dim={head_dim}"
)))
}
};
let elements = num_heads as usize * head_dim as usize;
for (name, buffer) in [("data", data), ("gate", gate), ("output", output)] {
if buffer.dtype() != DType::F32 || buffer.element_count() < elements {
return Err(MlxError::InvalidArgument(format!(
"{OP}: {name} must be F32 with at least {elements} elements"
)));
}
}
let bytes = elements as u64 * 4;
let range = |buffer: &MlxBuffer| {
let start = buffer.byte_offset();
(buffer.metal_buffer().as_ptr() as usize, start, start + bytes)
};
let out = range(output);
for (name, other) in [("data", range(data)), ("gate", range(gate))] {
if out.0 == other.0 && out.1 < other.2 && other.1 < out.2 {
return Err(MlxError::InvalidArgument(format!(
"{OP}: output overlaps {name}"
)));
}
}
let pipeline = registry.get_pipeline(kernel_name, device)?;
let params = GpuFwhtParams { head_dim, num_heads };
use crate::ops::encode_helpers::{as_bytes, KernelArg};
encoder.encode_threadgroups_with_args(
pipeline,
&[
(0, KernelArg::Buffer(data)),
(1, KernelArg::Buffer(gate)),
(2, KernelArg::Buffer(output)),
(3, KernelArg::Bytes(as_bytes(¶ms))),
],
MTLSize::new(num_heads as u64, 1, 1),
MTLSize::new(32, 1, 1),
);
Ok(())
}