use super::{DType, DeviceTensor, TensorDesc, TensorOps};
use crate::{
Error, Result,
loom::Specialization,
model::{Command, Dispatch, ModelFragment, ModelSession},
};
impl TensorOps {
pub fn linear(
&self,
input: &DeviceTensor,
weight: &DeviceTensor,
output: DType,
) -> Result<DeviceTensor> {
Ok(self
.linear_many(input, std::slice::from_ref(weight), output)?
.remove(0))
}
pub fn linear_many(
&self,
input: &DeviceTensor,
weights: &[DeviceTensor],
output: DType,
) -> Result<Vec<DeviceTensor>> {
self.context.validate(input)?;
for weight in weights {
self.context.validate(weight)?;
}
let descriptions: Vec<_> = weights.iter().map(|w| w.desc().clone()).collect();
let key = (input.desc().clone(), descriptions.clone(), output);
let plan = self.linear.get_or_prepare(key, || {
self.linear_fragment(input.desc(), &descriptions, output)?
.prepare(3)
})?;
let mut bindings = vec![input.clone()];
bindings.extend_from_slice(weights);
Ok(plan.submit(&bindings)?.outputs().to_vec())
}
pub fn linear_fragment(
&self,
input: &TensorDesc,
weights: &[TensorDesc],
output: DType,
) -> Result<ModelFragment> {
let valid = input.dtype() == DType::BF16
&& input.is_contiguous()
&& input.shape().len() == 2
&& input.shape()[0] == 1
&& input.shape()[1] > 0
&& input.shape()[1].is_multiple_of(128)
&& (1..=3).contains(&weights.len())
&& matches!(output, DType::BF16 | DType::F32)
&& weights.iter().all(|w| {
w.dtype() == DType::BF16
&& w.is_contiguous()
&& w.shape().len() == 2
&& w.shape()[0] > 0
&& w.shape()[1] == input.shape()[1]
&& w.elements() <= 1073741824
});
if !valid {
return Err(Error::Message("linear requires BF16 [1,K], one to three contiguous BF16 [N,K] weights, K divisible by 128, and BF16/F32 output".into()));
}
let k = input.shape()[1];
let grid = weights
.iter()
.map(|w| w.shape()[0].div_ceil(8))
.max()
.unwrap();
let mut model = ModelSession::in_context(&self.context)?;
let x = model.allocate(input.bytes())?;
let mut inputs = vec![(x, input.clone())];
let mut outputs = Vec::new();
let mut bindings = vec![x.read()];
let mut source = "amdgpu.target<gfx11-generic> @target {subgroup_size = 32}\n".to_owned();
let mut spec = Specialization::new("hrx_linear");
for (key, value) in [("k", k), ("grid_x", grid), ("grid_y", weights.len())] {
source += &format!(
"config.decl @hrx.linear.{key} : %value: index where [range(%value, 1, 1073741824)]\n"
);
spec.set_config(format!("hrx.linear.{key}"), value.to_string());
}
for (i, w) in weights.iter().enumerate() {
for (key, value) in [
(format!("n{i}"), w.shape()[0]),
(format!("wn{i}"), w.elements()),
] {
source += &format!(
"config.decl @hrx.linear.{key} : %value: index where [range(%value, 1, 1073741824)]\n"
);
spec.set_config(format!("hrx.linear.{key}"), value.to_string());
}
let weight = model.allocate(w.bytes())?;
let desc = TensorDesc::new(output, vec![1, w.shape()[0]])?;
let out = model.allocate(desc.bytes())?;
inputs.push((weight, w.clone()));
outputs.push((out, desc));
bindings.extend([weight.read(), out.write()]);
}
source += r#"kernel.def target(@target) export("hrx_linear") @hrx_linear(%count: index) {
%one = index.constant 1 : index
%threads = index.constant 256 : index
%gx = config.get @hrx.linear.grid_x : index
%gy = config.get @hrx.linear.grid_y : index
kernel.launch.config workgroups(%gx, %gy, %one) workgroup_size(%threads, %one, %one) : index
} launch(%count: index, %x: buffer, "#;
source += &(0..weights.len())
.map(|i| format!("%w{i}: buffer, %out{i}: buffer"))
.collect::<Vec<_>>()
.join(", ");
source += ") {\n %projection = kernel.workgroup.id<y> : index\n";
let (dtype, convert) = if output == DType::BF16 {
("bf16", "scalar.fptrunc %sum1 : f32 to bf16")
} else {
("f32", "scalar.addf %sum1, %zf : f32")
};
for i in 0..weights.len() {
source += &format!(
" %p{i} = index.constant {i} : index\n %is{i} = index.cmp eq, %projection, %p{i} : index\n scf.if %is{i} {{\n"
);
source += &include_str!("linear.loom")
.replace("@P@", &i.to_string())
.replace("@TYPE@", dtype)
.replace("@CONVERT@", convert);
source += "\n }\n";
}
source += " kernel.return\n}\n";
let kernel = unsafe { model.compile(&[(&source, spec)])? }[0];
unsafe {
model.freeze(&self.context)?.fragment(
&[Command::Dispatch(Dispatch::indices(
kernel,
[1],
[grid as u32, weights.len() as u32, 1],
bindings,
))],
&inputs,
&outputs,
)
}
}
}