Adding cast + binary kernels.

This commit is contained in:
Nicolas Patry
2023-11-07 23:45:53 +01:00
parent 0c24a885a6
commit 480a3e22e6
7 changed files with 601 additions and 84 deletions

View File

@ -113,11 +113,11 @@ impl BackendStorage for MetalStorage {
debug!("{shape:?} {el:?} {:?}", layout.stride()); debug!("{shape:?} {el:?} {:?}", layout.stride());
let output_buffer = device.new_buffer(el, self.dtype); let output_buffer = device.new_buffer(el, self.dtype);
// return Ok(Self { return Ok(Self {
// buffer: output_buffer, buffer: output_buffer,
// device: device.clone(), device: device.clone(),
// dtype, dtype,
// }); });
let function = self let function = self
.device .device
.kernels .kernels
@ -185,9 +185,9 @@ impl BackendStorage for MetalStorage {
start.elapsed() start.elapsed()
); );
let capture = metal::CaptureManager::shared(); // let capture = metal::CaptureManager::shared();
capture.stop_capture(); // capture.stop_capture();
panic!("Done"); // panic!("Done");
Ok(Self { Ok(Self {
buffer: output_buffer, buffer: output_buffer,
@ -283,7 +283,58 @@ impl BackendStorage for MetalStorage {
} }
fn to_dtype(&self, layout: &Layout, dtype: DType) -> Result<Self> { fn to_dtype(&self, layout: &Layout, dtype: DType) -> Result<Self> {
todo!("Implement {:?} {layout:?} - {dtype:?}", self.dtype) let device = self.device();
let shape = layout.shape();
let dims = shape.dims();
let el_count = shape.elem_count();
let mut buffer = device.new_buffer(el_count, dtype);
// TODO remove
// return Ok(Self {
// buffer,
// device: device.clone(),
// dtype,
// });
let command_buffer = device.command_queue.new_command_buffer();
if layout.is_contiguous() {
use candle_metal_kernels::unary::contiguous;
let kernel_name = match (self.dtype, dtype) {
(DType::U32, DType::F32) => "cast_u32_f32",
(left, right) => todo!("to dtype {left:?} - {right:?}"),
};
candle_metal_kernels::call_cast_contiguous(
&device.device,
&command_buffer,
&device.kernels,
kernel_name,
el_count,
&self.buffer,
&mut buffer,
)
.map_err(MetalError::from)?;
} else {
todo!(
"TODO Implement the kernel calling cast {:?}-{:?}",
self.dtype,
dtype
);
}
let start = std::time::Instant::now();
command_buffer.commit();
// command_buffer.wait_until_scheduled();
debug!(
"cast {:?} - {:?} - {:?} - {:?}",
dtype,
start.elapsed(),
self.buffer.length(),
buffer.length()
);
Ok(Self {
buffer,
device: device.clone(),
dtype,
})
} }
fn unary_impl<B: UnaryOpT>(&self, layout: &Layout) -> Result<Self> { fn unary_impl<B: UnaryOpT>(&self, layout: &Layout) -> Result<Self> {
@ -294,11 +345,11 @@ impl BackendStorage for MetalStorage {
let el_count = shape.elem_count(); let el_count = shape.elem_count();
let mut buffer = device.new_buffer(el_count, dtype); let mut buffer = device.new_buffer(el_count, dtype);
// TODO remove // TODO remove
return Ok(Self { // return Ok(Self {
buffer, // buffer,
device: device.clone(), // device: device.clone(),
dtype, // dtype,
}); // });
let command_buffer = device.command_queue.new_command_buffer(); let command_buffer = device.command_queue.new_command_buffer();
if layout.is_contiguous() { if layout.is_contiguous() {
use candle_metal_kernels::unary::contiguous; use candle_metal_kernels::unary::contiguous;
@ -328,7 +379,7 @@ impl BackendStorage for MetalStorage {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
command_buffer.commit(); command_buffer.commit();
command_buffer.wait_until_completed(); // command_buffer.wait_until_scheduled();
debug!( debug!(
"Unary {:?} - {:?} - {:?} - {:?}", "Unary {:?} - {:?} - {:?} - {:?}",
B::KERNEL, B::KERNEL,
@ -344,10 +395,87 @@ impl BackendStorage for MetalStorage {
}) })
} }
fn binary_impl<B: BinaryOpT>(&self, _: &Self, _: &Layout, _: &Layout) -> Result<Self> { fn binary_impl<B: BinaryOpT>(
debug!("TODO Binary {:?}", B::NAME); &self,
Ok(self.clone()) rhs: &Self,
// todo!() lhs_l: &Layout,
rhs_l: &Layout,
) -> Result<Self> {
let device = self.device();
let dtype = self.dtype;
let shape = lhs_l.shape();
let dims = shape.dims();
let el_count = shape.elem_count();
let mut buffer = device.new_buffer(el_count, dtype);
let command_buffer = device.command_queue.new_command_buffer();
if lhs_l.is_contiguous() && rhs_l.is_contiguous() {
use candle_metal_kernels::binary::contiguous;
let kernel_name = match (B::KERNEL, dtype) {
("add", DType::F32) => contiguous::add::FLOAT,
("badd", DType::F32) => contiguous::add::FLOAT,
("sub", DType::F32) => contiguous::sub::FLOAT,
("bsub", DType::F32) => contiguous::sub::FLOAT,
("mul", DType::F32) => contiguous::mul::FLOAT,
("bmul", DType::F32) => contiguous::mul::FLOAT,
("div", DType::F32) => contiguous::div::FLOAT,
("bdiv", DType::F32) => contiguous::div::FLOAT,
(name, dtype) => todo!("Match {name} - {dtype:?}"),
};
candle_metal_kernels::call_binary_contiguous(
&device.device,
&command_buffer,
&device.kernels,
kernel_name,
el_count,
&self.buffer,
&rhs.buffer,
&mut buffer,
)
.map_err(MetalError::from)?;
} else {
use candle_metal_kernels::binary::strided;
let kernel_name = match (B::KERNEL, dtype) {
("badd", DType::F32) => strided::add::FLOAT,
("bsub", DType::F32) => strided::sub::FLOAT,
("bmul", DType::F32) => strided::mul::FLOAT,
("bdiv", DType::F32) => strided::div::FLOAT,
(name, dtype) => todo!("Match {name} - {dtype:?}"),
};
candle_metal_kernels::call_binary_strided(
&device.device,
&command_buffer,
&device.kernels,
kernel_name,
lhs_l.dims(),
&self.buffer,
&lhs_l.stride(),
lhs_l.start_offset(),
&rhs.buffer,
&rhs_l.stride(),
rhs_l.start_offset(),
&mut buffer,
)
.map_err(MetalError::from)?;
}
let start = std::time::Instant::now();
command_buffer.commit();
// command_buffer.wait_until_scheduled();
debug!(
"Binary {:?} - {:?} - {:?} - {:?}",
B::KERNEL,
start.elapsed(),
self.buffer.length(),
buffer.length()
);
Ok(Self {
buffer,
device: device.clone(),
dtype,
})
} }
fn where_cond(&self, _: &Layout, rhs: &Self, _: &Layout, _: &Self, _: &Layout) -> Result<Self> { fn where_cond(&self, _: &Layout, rhs: &Self, _: &Layout, _: &Self, _: &Layout) -> Result<Self> {
@ -546,25 +674,25 @@ impl MetalStorage {
} }
debug!("GEMM"); debug!("GEMM");
let command_buffer = self.device.command_queue.new_command_buffer(); // let command_buffer = self.device.command_queue.new_command_buffer();
encode_gemm::<Float32, Float32, Float32>( // encode_gemm::<Float32, Float32, Float32>(
&self.device, // &self.device,
&command_buffer, // &command_buffer,
transpose_left, // transpose_left,
transpose_right, // transpose_right,
&self.buffer, // &self.buffer,
&rhs.buffer, // &rhs.buffer,
&mut out_buffer, // &mut out_buffer,
m as NSUInteger, // m as NSUInteger,
n as NSUInteger, // n as NSUInteger,
k as NSUInteger, // k as NSUInteger,
alpha, // alpha,
beta, // beta,
) // )
.map_err(MetalError::from)?; // .map_err(MetalError::from)?;
command_buffer.commit(); // command_buffer.commit();
command_buffer.wait_until_scheduled(); // command_buffer.wait_until_scheduled();
// println!("lhs {:?} {m} {k}", self.buffer.length()); // println!("lhs {:?} {m} {k}", self.buffer.length());
// println!("rhs {:?} {k} {n}", rhs.buffer.length()); // println!("rhs {:?} {k} {n}", rhs.buffer.length());
@ -588,18 +716,18 @@ impl BackendDevice for MetalDevice {
fn new(ordinal: usize) -> Result<Self> { fn new(ordinal: usize) -> Result<Self> {
let device = metal::Device::all().swap_remove(ordinal); let device = metal::Device::all().swap_remove(ordinal);
let capture = metal::CaptureManager::shared(); // let capture = metal::CaptureManager::shared();
let descriptor = metal::CaptureDescriptor::new(); // let descriptor = metal::CaptureDescriptor::new();
descriptor.set_destination(metal::MTLCaptureDestination::GpuTraceDocument); // descriptor.set_destination(metal::MTLCaptureDestination::GpuTraceDocument);
println!("{:?}", std::env::current_dir()?); // println!("{:?}", std::env::current_dir()?);
descriptor.set_capture_device(&device); // descriptor.set_capture_device(&device);
let mut dir = std::env::current_dir()?; // let mut dir = std::env::current_dir()?;
dir.push("out.gputrace"); // dir.push("out.gputrace");
descriptor.set_output_url(dir); // descriptor.set_output_url(dir);
capture // capture
.start_capture(&descriptor) // .start_capture(&descriptor)
.map_err(MetalError::from)?; // .map_err(MetalError::from)?;
let command_queue = device.new_command_queue(); let command_queue = device.new_command_queue();
// let command_buffer = _command_queue.new_owned_command_buffer(); // let command_buffer = _command_queue.new_owned_command_buffer();
let kernels = Arc::new(Kernels::new()); let kernels = Arc::new(Kernels::new());

View File

@ -239,15 +239,13 @@ fn main() -> anyhow::Result<()> {
Some(args.temperature) Some(args.temperature)
}; };
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
// let _guard = if args.tracing { let _guard = if args.tracing {
// // let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); let (chrome_layer, guard) = ChromeLayerBuilder::new().build();
// // tracing_subscriber::registry().with(chrome_layer).init(); tracing_subscriber::registry().with(chrome_layer).init();
// tracing_subscriber::fmt::init(); Some(guard)
// None } else {
// // Some(guard) None
// } else { };
// None
// };
println!( println!(
"avx: {}, neon: {}, simd128: {}, f16c: {}", "avx: {}, neon: {}, simd128: {}, f16c: {}",
@ -375,7 +373,8 @@ fn main() -> anyhow::Result<()> {
let logits = logits.squeeze(0)?; let logits = logits.squeeze(0)?;
// TODO Remove this once implementation is finished. // TODO Remove this once implementation is finished.
let logits = logits.ones_like()?; let logits = logits.ones_like()?;
logits_processor.sample(&logits)? // logits_processor.sample(&logits)?
15043
}; };
let prompt_dt = start_prompt_processing.elapsed(); let prompt_dt = start_prompt_processing.elapsed();
all_tokens.push(next_token); all_tokens.push(next_token);
@ -399,8 +398,9 @@ fn main() -> anyhow::Result<()> {
)? )?
}; };
// TODO Remove this once implementation is finished. // TODO Remove this once implementation is finished.
let logits = logits.ones_like()?; // let logits = logits.ones_like()?;
next_token = logits_processor.sample(&logits)?; // next_token = logits_processor.sample(&logits)?;
let next_token = 15043;
all_tokens.push(next_token); all_tokens.push(next_token);
print_token(next_token, &tokenizer); print_token(next_token, &tokenizer);
if next_token == eos_token { if next_token == eos_token {

View File

@ -0,0 +1,78 @@
#include <metal_stdlib>
METAL_FUNC uint get_strided_index(
uint idx,
constant size_t &num_dims,
constant size_t *dims,
constant size_t *strides
) {
uint strided_i = 0;
for (uint d = 0; d < num_dims; d++) {
uint dim_idx = num_dims - 1 - d;
strided_i += (idx % dims[dim_idx]) * strides[dim_idx];
idx /= dims[dim_idx];
}
return strided_i;
}
using namespace metal;
#define BINARY(FN, TYPENAME, OUT_TYPENAME, FN_NAME, FN_NAME_STRIDED) \
kernel void FN_NAME( \
constant size_t &dim, \
device const TYPENAME *left, \
device const TYPENAME *right, \
device TYPENAME *output, \
uint threadgroup_size [[threads_per_threadgroup]], \
uint thread_index [[thread_index_in_threadgroup]] \
) { \
const size_t length = (dim + threadgroup_size - 1) / threadgroup_size; \
const size_t start = thread_index * length; \
const size_t stop = min(start + length, dim); \
for (size_t i = start; i < stop; i++){ \
TYPENAME x = left[i]; \
TYPENAME y = right[i]; \
output[i] = OUT_TYPENAME(FN); \
} \
}\
kernel void FN_NAME_STRIDED( \
constant size_t &dim, \
constant size_t &num_dims, \
constant size_t *dims, \
constant size_t *left_strides, \
constant size_t *right_strides, \
device const TYPENAME *left, \
device const TYPENAME *right, \
device TYPENAME *output, \
uint threadgroup_size [[threads_per_threadgroup]], \
uint thread_index [[thread_index_in_threadgroup]] \
) { \
const size_t length = (dim + threadgroup_size - 1) / threadgroup_size; \
const size_t start = thread_index * length; \
const size_t stop = min(start + length, dim); \
for (size_t i = start; i < stop; i++){ \
TYPENAME x = left[get_strided_index(i, num_dims, dims, left_strides)]; \
TYPENAME y = left[get_strided_index(i, num_dims, dims, right_strides)]; \
output[i] = OUT_TYPENAME(FN); \
} \
}
#define BINARY_OP(FN, NAME) \
BINARY(FN, float, float, NAME##_float, NAME##_float_strided); \
BINARY(FN, half, half, NAME##_half, NAME##_half_strided);
#define BFLOAT_BINARY_OP(FN, NAME) \
BINARY(NAME, bfloat, bfloat, NAME##_bfloat, NAME##_bfloat_strided);
BINARY_OP(x + y, add)
BINARY_OP(x - y, sub)
BINARY_OP(x * y, mul)
BINARY_OP(x / y, div)
#if __METAL_VERSION__ >= 310
BFLOAT_BINARY_OP(x + y, badd)
BFLOAT_BINARY_OP(x - y, bsub)
BFLOAT_BINARY_OP(x * y, bmul)
BFLOAT_BINARY_OP(x / y, bdiv)
#endif

View File

@ -0,0 +1,58 @@
#include <metal_stdlib>
METAL_FUNC uint get_strided_index(
uint idx,
constant size_t &num_dims,
constant size_t *dims,
constant size_t *strides
) {
uint strided_i = 0;
for (uint d = 0; d < num_dims; d++) {
uint dim_idx = num_dims - 1 - d;
strided_i += (idx % dims[dim_idx]) * strides[dim_idx];
idx /= dims[dim_idx];
}
return strided_i;
}
using namespace metal;
#define CAST(FN_NAME, FN_NAME_STRIDED, LEFT_TYPENAME, RIGHT_TYPENAME) \
kernel void FN_NAME( \
constant size_t &dim, \
device const LEFT_TYPENAME *input, \
device RIGHT_TYPENAME *output, \
uint threadgroup_size [[threads_per_threadgroup]], \
uint thread_index [[thread_index_in_threadgroup]] \
) { \
const size_t length = (dim + threadgroup_size - 1) / threadgroup_size; \
const size_t start = thread_index * length; \
const size_t stop = min(start + length, dim); \
for (size_t i = start; i < stop; i++){ \
output[i] = RIGHT_TYPENAME(input[i]); \
} \
} \
kernel void FN_NAME_STRIDED( \
constant size_t &dim, \
constant size_t &num_dims, \
constant size_t *dims, \
constant size_t *strides, \
device const LEFT_TYPENAME *input, \
device RIGHT_TYPENAME *output, \
uint threadgroup_size [[threads_per_threadgroup]], \
uint thread_index [[thread_index_in_threadgroup]] \
) { \
const size_t length = (dim + threadgroup_size - 1) / threadgroup_size; \
const size_t start = thread_index * length; \
const size_t stop = min(start + length, dim); \
for (size_t i = start; i < stop; i++){ \
output[i] = RIGHT_TYPENAME(input[get_strided_index(i, num_dims, dims, strides)]); \
} \
}
CAST(cast_u32_f32, cast_u32_f32_strided, int32_t, float)
#if __METAL_VERSION__ >= 310
#endif

View File

@ -9,15 +9,19 @@ use std::sync::RwLock;
const AFFINE: &str = include_str!("affine.metal"); const AFFINE: &str = include_str!("affine.metal");
const INDEXING: &str = include_str!("indexing.metal"); const INDEXING: &str = include_str!("indexing.metal");
const UNARY: &str = include_str!("unary.metal"); const UNARY: &str = include_str!("unary.metal");
const BINARY: &str = include_str!("binary.metal");
const CAST: &str = include_str!("cast.metal");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Source { pub enum Source {
Affine, Affine,
Indexing, Indexing,
Unary, Unary,
Binary,
Cast,
} }
macro_rules! unary{ macro_rules! ops{
($($name:ident),+) => { ($($name:ident),+) => {
pub mod contiguous { pub mod contiguous {
@ -47,7 +51,10 @@ macro_rules! unary{
} }
pub mod unary { pub mod unary {
unary!(cos, sin, exp, sqr, sqrt, neg); ops!(cos, sin, exp, sqr, sqrt, neg);
}
pub mod binary {
ops!(add, sub, mul, div);
} }
// static LIBRARY_SOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| { // static LIBRARY_SOURCES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
@ -109,7 +116,9 @@ impl Kernels {
match source { match source {
Source::Affine => AFFINE, Source::Affine => AFFINE,
Source::Unary => UNARY, Source::Unary => UNARY,
Source::Binary => BINARY,
Source::Indexing => INDEXING, Source::Indexing => INDEXING,
Source::Cast => CAST,
} }
} }
@ -234,10 +243,9 @@ pub fn call_unary_strided(
(strides.len() * std::mem::size_of::<usize>()) as u64, (strides.len() * std::mem::size_of::<usize>()) as u64,
strides.as_ptr() as *const c_void, strides.as_ptr() as *const c_void,
); );
encoder.set_bytes(4, std::mem::size_of::<usize>() as u64, void_ptr(&offset));
encoder.set_buffer(5, Some(&input), 0); encoder.set_buffer(4, Some(&input), offset as u64);
encoder.set_buffer(6, Some(&output), 0); encoder.set_buffer(5, Some(&output), 0);
let width = output.length(); let width = output.length();
@ -258,6 +266,170 @@ pub fn call_unary_strided(
Ok(()) Ok(())
} }
pub fn call_binary_contiguous(
device: &Device,
command_buffer: &CommandBufferRef,
kernels: &Kernels,
kernel_name: binary::contiguous::Kernel,
length: usize,
left: &Buffer,
right: &Buffer,
output: &mut Buffer,
) -> Result<(), MetalKernelError> {
// println!("Kernel {:?}", kernel_name.0);
// assert_eq!(input.length(), output.length());
let func = kernels.load_function(device, Source::Binary, kernel_name.0)?;
let pipeline_state_descriptor = ComputePipelineDescriptor::new();
pipeline_state_descriptor.set_compute_function(Some(&func));
let pipeline = device
.new_compute_pipeline_state_with_function(
pipeline_state_descriptor.compute_function().unwrap(),
)
.unwrap();
let encoder = command_buffer.new_compute_command_encoder();
encoder.set_compute_pipeline_state(&pipeline);
encoder.set_bytes(0, 4, void_ptr(&length));
encoder.set_buffer(1, Some(&left), 0);
encoder.set_buffer(2, Some(&right), 0);
encoder.set_buffer(3, Some(&output), 0);
let thread_group_count = MTLSize {
width: 1,
height: 1,
depth: 1,
};
let width = std::cmp::min(pipeline.max_total_threads_per_threadgroup(), length as u64);
let thread_group_size = MTLSize {
width,
height: 1,
depth: 1,
};
encoder.dispatch_thread_groups(thread_group_count, thread_group_size);
encoder.end_encoding();
Ok(())
}
pub fn call_binary_strided(
device: &Device,
command_buffer: &CommandBufferRef,
kernels: &Kernels,
name: binary::strided::Kernel,
shape: &[usize],
left_input: &Buffer,
left_strides: &[usize],
left_offset: usize,
right_input: &Buffer,
right_strides: &[usize],
right_offset: usize,
output: &mut Buffer,
) -> Result<(), MetalKernelError> {
let func = kernels.load_function(device, Source::Binary, name.0)?;
let pipeline_state_descriptor = ComputePipelineDescriptor::new();
pipeline_state_descriptor.set_compute_function(Some(&func));
let pipeline = device
.new_compute_pipeline_state_with_function(
pipeline_state_descriptor.compute_function().unwrap(),
)
.unwrap();
let num_dims: usize = shape.len() as usize;
let encoder = command_buffer.new_compute_command_encoder();
encoder.set_compute_pipeline_state(&pipeline);
let length: usize = shape.iter().product();
encoder.set_bytes(0, std::mem::size_of::<usize>() as u64, void_ptr(&length));
encoder.set_bytes(1, std::mem::size_of::<usize>() as u64, void_ptr(&num_dims));
encoder.set_bytes(
2,
(shape.len() * std::mem::size_of::<usize>()) as u64,
shape.as_ptr() as *const c_void,
);
encoder.set_bytes(
3,
(left_strides.len() * std::mem::size_of::<usize>()) as u64,
left_strides.as_ptr() as *const c_void,
);
encoder.set_bytes(
4,
(right_strides.len() * std::mem::size_of::<usize>()) as u64,
right_strides.as_ptr() as *const c_void,
);
encoder.set_buffer(5, Some(&left_input), left_offset as u64);
encoder.set_buffer(6, Some(&right_input), right_offset as u64);
encoder.set_buffer(7, Some(&output), 0);
let width = output.length();
let thread_group_count = MTLSize {
width: 1,
height: 1,
depth: 1,
};
let thread_group_size = MTLSize {
width: std::cmp::min(pipeline.max_total_threads_per_threadgroup(), width),
height: 1,
depth: 1,
};
encoder.dispatch_thread_groups(thread_group_count, thread_group_size);
encoder.end_encoding();
Ok(())
}
pub fn call_cast_contiguous(
device: &Device,
command_buffer: &CommandBufferRef,
kernels: &Kernels,
kernel_name: &'static str,
length: usize,
input: &Buffer,
output: &mut Buffer,
) -> Result<(), MetalKernelError> {
// println!("Kernel {:?}", kernel_name.0);
// assert_eq!(input.length(), output.length());
let func = kernels.load_function(device, Source::Cast, kernel_name)?;
let pipeline_state_descriptor = ComputePipelineDescriptor::new();
pipeline_state_descriptor.set_compute_function(Some(&func));
let pipeline = device
.new_compute_pipeline_state_with_function(
pipeline_state_descriptor.compute_function().unwrap(),
)
.unwrap();
let encoder = command_buffer.new_compute_command_encoder();
encoder.set_compute_pipeline_state(&pipeline);
encoder.set_bytes(0, 4, void_ptr(&length));
encoder.set_buffer(1, Some(&input), 0);
encoder.set_buffer(2, Some(&output), 0);
let thread_group_count = MTLSize {
width: 1,
height: 1,
depth: 1,
};
let width = std::cmp::min(pipeline.max_total_threads_per_threadgroup(), length as u64);
let thread_group_size = MTLSize {
width,
height: 1,
depth: 1,
};
encoder.dispatch_thread_groups(thread_group_count, thread_group_size);
encoder.end_encoding();
Ok(())
}
pub fn void_ptr<T>(v: &T) -> *const c_void { pub fn void_ptr<T>(v: &T) -> *const c_void {
(v as *const T).cast() (v as *const T).cast()
} }
@ -310,6 +482,39 @@ mod tests {
output.read_to_vec::<T>(v.len()) output.read_to_vec::<T>(v.len())
} }
fn run_binary<T: Clone>(x: &[T], y: &[T], name: binary::contiguous::Kernel) -> Vec<T> {
let device = device();
let kernels = Kernels::new();
let command_queue = device.new_command_queue();
let command_buffer = command_queue.new_command_buffer();
let options = MTLResourceOptions::StorageModeManaged;
let left = device.new_buffer_with_data(
x.as_ptr() as *const core::ffi::c_void,
(x.len() * core::mem::size_of::<T>()) as u64,
options,
);
let right = device.new_buffer_with_data(
y.as_ptr() as *const core::ffi::c_void,
(y.len() * core::mem::size_of::<T>()) as u64,
options,
);
let mut output = device.new_buffer((x.len() * core::mem::size_of::<T>()) as u64, options);
call_binary_contiguous(
&device,
&command_buffer,
&kernels,
name,
x.len(),
&left,
&right,
&mut output,
)
.unwrap();
command_buffer.commit();
command_buffer.wait_until_completed();
output.read_to_vec::<T>(x.len())
}
fn run_strided<T: Clone>( fn run_strided<T: Clone>(
v: &[T], v: &[T],
kernel: unary::strided::Kernel, kernel: unary::strided::Kernel,
@ -421,6 +626,62 @@ mod tests {
assert_eq!(approx(expected, 4), vec![0.5403; 10_000]); assert_eq!(approx(expected, 4), vec![0.5403; 10_000]);
} }
#[test]
fn binary_add_f32() {
let left = vec![1.0f32, 2.0, 3.0];
let right = vec![2.0f32, 3.1, 4.2];
let results = run_binary(&left, &right, binary::contiguous::add::FLOAT);
let expected: Vec<_> = left
.iter()
.zip(right.iter())
.map(|(&x, &y)| x + y)
.collect();
assert_eq!(approx(results, 4), vec![3.0f32, 5.1, 7.2]);
assert_eq!(approx(expected, 4), vec![3.0f32, 5.1, 7.2]);
}
fn cast<T: Clone, U: Clone>(v: &[T], name: &'static str) -> Vec<U> {
let device = device();
let kernels = Kernels::new();
let command_queue = device.new_command_queue();
let command_buffer = command_queue.new_command_buffer();
let options = MTLResourceOptions::StorageModeManaged;
let input = device.new_buffer_with_data(
v.as_ptr() as *const core::ffi::c_void,
(v.len() * core::mem::size_of::<T>()) as u64,
options,
);
let mut output = device.new_buffer((v.len() * core::mem::size_of::<U>()) as u64, options);
call_cast_contiguous(
&device,
&command_buffer,
&kernels,
name,
v.len(),
&input,
&mut output,
)
.unwrap();
command_buffer.commit();
command_buffer.wait_until_completed();
output.read_to_vec::<U>(v.len())
}
#[test]
fn cast_u32_f32() {
let v = vec![1u32, 2, 3];
let results = cast(&v, "cast_u32_f32");
let expected: Vec<_> = v.iter().map(|&v| v as f32).collect();
assert_eq!(approx(results, 4), vec![1.0f32, 2.0, 3.0]);
assert_eq!(approx(expected, 4), vec![1.0f32, 2.0, 3.0]);
let v = vec![1.0f32; 10_000];
let results = run(&v, unary::contiguous::cos::FLOAT);
let expected: Vec<_> = v.iter().map(|v| v.cos()).collect();
assert_eq!(approx(results, 4), vec![0.5403; 10_000]);
assert_eq!(approx(expected, 4), vec![0.5403; 10_000]);
}
#[test] #[test]
fn affine() { fn affine() {
let device = device(); let device = device();

View File

@ -1,19 +1,12 @@
#include <metal_stdlib> #include <metal_stdlib>
struct Info{
device size_t &num_dims;
device size_t *dims;
device size_t *strides;
};
METAL_FUNC uint get_strided_index( METAL_FUNC uint get_strided_index(
uint idx, uint idx,
constant size_t &num_dims, constant size_t &num_dims,
constant size_t *dims, constant size_t *dims,
constant size_t *strides, constant size_t *strides
constant size_t &offset
) { ) {
uint strided_i = offset; uint strided_i = 0;
for (uint d = 0; d < num_dims; d++) { for (uint d = 0; d < num_dims; d++) {
uint dim_idx = num_dims - 1 - d; uint dim_idx = num_dims - 1 - d;
strided_i += (idx % dims[dim_idx]) * strides[dim_idx]; strided_i += (idx % dims[dim_idx]) * strides[dim_idx];
@ -48,7 +41,6 @@ kernel void FN_NAME_STRIDED( \
constant size_t &num_dims, \ constant size_t &num_dims, \
constant size_t *dims, \ constant size_t *dims, \
constant size_t *strides, \ constant size_t *strides, \
constant size_t &offset, \
device const TYPENAME *input, \ device const TYPENAME *input, \
device TYPENAME *output, \ device TYPENAME *output, \
uint threadgroup_size [[threads_per_threadgroup]], \ uint threadgroup_size [[threads_per_threadgroup]], \
@ -58,7 +50,7 @@ kernel void FN_NAME_STRIDED( \
const size_t start = thread_index * length; \ const size_t start = thread_index * length; \
const size_t stop = min(start + length, dim); \ const size_t stop = min(start + length, dim); \
for (size_t i = start; i < stop; i++){ \ for (size_t i = start; i < stop; i++){ \
output[i] = TYPENAME(FN(input[get_strided_index(i, num_dims, dims, strides, offset)])); \ output[i] = TYPENAME(FN(input[get_strided_index(i, num_dims, dims, strides)])); \
} \ } \
} }

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use candle::quantized::QTensor; use candle::quantized::QTensor;
use candle::quantized::{ggml_file, gguf_file}; use candle::quantized::{ggml_file, gguf_file};
use candle::{Device, IndexOp, Result, Tensor, D}; use candle::{DType, Device, IndexOp, Result, Tensor, D};
use candle_nn::{Embedding, Module}; use candle_nn::{Embedding, Module};
pub const MAX_SEQ_LEN: usize = 4096; pub const MAX_SEQ_LEN: usize = 4096;
@ -196,15 +196,15 @@ fn precomput_freqs_cis(
.collect(); .collect();
let theta = Tensor::new(theta.as_slice(), device)?; let theta = Tensor::new(theta.as_slice(), device)?;
let range: Vec<f32> = (0..MAX_SEQ_LEN).map(|r| r as f32).collect(); let range: Vec<f32> = (0..MAX_SEQ_LEN).map(|r| r as f32).collect();
let idx_theta = Tensor::new(range.as_slice(), device)? // let idx_theta = Tensor::new(range.as_slice(), device)?
.reshape((MAX_SEQ_LEN, 1))?
.matmul(&theta.reshape((1, theta.elem_count()))?)?;
// TODO This change avoids allocating on Metal and then casting since allocating directly on
// CPU as f32 seems just as fast
// let idx_theta = Tensor::arange(0, MAX_SEQ_LEN as u32, device)?
// .to_dtype(DType::F32)?
// .reshape((MAX_SEQ_LEN, 1))? // .reshape((MAX_SEQ_LEN, 1))?
// .matmul(&theta.reshape((1, theta.elem_count()))?)?; // .matmul(&theta.reshape((1, theta.elem_count()))?)?;
// TODO This change avoids allocating on Metal and then casting since allocating directly on
// CPU as f32 seems just as fast
let idx_theta = Tensor::arange(0, MAX_SEQ_LEN as u32, device)?
.to_dtype(DType::F32)?
.reshape((MAX_SEQ_LEN, 1))?
.matmul(&theta.reshape((1, theta.elem_count()))?)?;
let cos = idx_theta.cos()?; let cos = idx_theta.cos()?;
let sin = idx_theta.sin()?; let sin = idx_theta.sin()?;
Ok((cos, sin)) Ok((cos, sin))