Instructions to use kernels-community/gpt-oss-metal-kernels with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Kernels
How to use kernels-community/gpt-oss-metal-kernels with Kernels:
# !pip install kernels from kernels import get_kernel kernel = get_kernel("kernels-community/gpt-oss-metal-kernels") - Notebooks
- Google Colab
- Kaggle
| namespace { | |
| class MetalBuffer { | |
| public: | |
| MetalBuffer() = default; | |
| MetalBuffer(const MetalBuffer&) = delete; | |
| MetalBuffer& operator=(const MetalBuffer&) = delete; | |
| MetalBuffer(MetalBuffer&& other) noexcept | |
| : buffer_(other.buffer_), has_value_(other.has_value_) { | |
| other.buffer_ = {}; | |
| other.has_value_ = false; | |
| } | |
| MetalBuffer& operator=(MetalBuffer&& other) noexcept { | |
| if (this != &other) { | |
| reset(); | |
| buffer_ = other.buffer_; | |
| has_value_ = other.has_value_; | |
| other.buffer_ = {}; | |
| other.has_value_ = false; | |
| } | |
| return *this; | |
| } | |
| ~MetalBuffer() { | |
| reset(); | |
| } | |
| gptoss_metal_buffer* get() { | |
| return &buffer_; | |
| } | |
| const gptoss_metal_buffer* get() const { | |
| return &buffer_; | |
| } | |
| void* ptr() const { | |
| return buffer_.ptr; | |
| } | |
| size_t size_bytes() const { | |
| return buffer_.size; | |
| } | |
| bool valid() const { | |
| return has_value_; | |
| } | |
| void wrap(const gptoss_metal_device* device, size_t size, const void* data) { | |
| reset(); | |
| TORCH_CHECK(gptoss_metal_buffer_wrap(device, size, data, &buffer_) == gptoss_status_success, | |
| "metal_buffer_wrap failed"); | |
| has_value_ = true; | |
| } | |
| void wrap_mps_tensor(const gptoss_metal_device* device, const at::Tensor& tensor) { | |
| reset(); | |
| TORCH_CHECK(tensor.is_contiguous(), "tensor must be contiguous for MPS wrap"); | |
| const size_t size_bytes = static_cast<size_t>(tensor.numel()) * tensor.element_size(); | |
| const size_t byte_offset = static_cast<size_t>(tensor.storage_offset()) * tensor.element_size(); | |
| const void* buffer_handle = tensor.storage().data(); | |
| TORCH_CHECK( | |
| gptoss_metal_buffer_wrap_mtlbuffer(device, buffer_handle, size_bytes, byte_offset, &buffer_) | |
| == gptoss_status_success, | |
| "metal_buffer_wrap_mtlbuffer failed"); | |
| has_value_ = true; | |
| } | |
| void create(const gptoss_metal_device* device, size_t size, const void* data = nullptr) { | |
| reset(); | |
| TORCH_CHECK(gptoss_metal_buffer_create(device, size, data, &buffer_) == gptoss_status_success, | |
| "metal_buffer_create failed"); | |
| has_value_ = true; | |
| } | |
| void reset() { | |
| if (has_value_) { | |
| (void) gptoss_metal_buffer_release(&buffer_); | |
| buffer_ = {}; | |
| has_value_ = false; | |
| } | |
| } | |
| private: | |
| gptoss_metal_buffer buffer_{}; | |
| bool has_value_ = false; | |
| }; | |
| template <typename EncodeFn> | |
| void run_metal_kernel(const char* kernel_symbol, EncodeFn&& encode_fn) { | |
| gptoss_metal_device device{}; | |
| gptoss_metal_library library{}; | |
| gptoss_metal_function fn{}; | |
| gptoss_metal_command_queue cq{}; | |
| gptoss_metal_command_buffer cb{}; | |
| bool borrowed_queue = false; | |
| bool borrowed_buffer = false; | |
| at::mps::MPSStream* stream = nullptr; | |
| auto cleanup = [&]() { | |
| if (!borrowed_buffer) { | |
| (void) gptoss_metal_command_buffer_release(&cb); | |
| } else { | |
| cb = {}; | |
| } | |
| if (!borrowed_queue) { | |
| (void) gptoss_metal_command_queue_release(&cq); | |
| } else { | |
| cq = {}; | |
| } | |
| (void) gptoss_metal_function_release(&fn); | |
| (void) gptoss_metal_library_release(&library); | |
| (void) gptoss_metal_device_release(&device); | |
| }; | |
| TORCH_CHECK(gptoss_metal_device_create_system_default(&device) == gptoss_status_success, | |
| "device_create failed"); | |
| try { | |
| TORCH_CHECK(gptoss_metal_library_create_default(&device, &library) == gptoss_status_success, | |
| "library_create failed"); | |
| TORCH_CHECK(gptoss_metal_function_create(&library, kernel_symbol, &fn) == gptoss_status_success, | |
| "function_create failed"); | |
| stream = at::mps::getCurrentMPSStream(); | |
| if (stream != nullptr) { | |
| // stream->synchronize(at::mps::SyncType::COMMIT_AND_WAIT); | |
| stream->endKernelCoalescing(); | |
| void* command_queue_obj = stream->commandQueue(); | |
| if (command_queue_obj != nullptr) { | |
| TORCH_CHECK( | |
| gptoss_metal_command_queue_wrap_existing(command_queue_obj, &cq) == gptoss_status_success, | |
| "wrap_command_queue failed"); | |
| borrowed_queue = true; | |
| void* command_buffer_obj = stream->commandBuffer(); | |
| if (command_buffer_obj != nullptr) { | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_wrap_existing(command_buffer_obj, &cb) == gptoss_status_success, | |
| "wrap_command_buffer failed"); | |
| borrowed_buffer = true; | |
| } | |
| } | |
| } | |
| if (!borrowed_queue) { | |
| TORCH_CHECK(gptoss_metal_command_queue_create(&device, &cq) == gptoss_status_success, | |
| "cq_create failed"); | |
| } | |
| if (!borrowed_buffer) { | |
| TORCH_CHECK(gptoss_metal_command_buffer_create(&cq, &cb) == gptoss_status_success, | |
| "cb_create failed"); | |
| } | |
| encode_fn(device, fn, cb); | |
| if (borrowed_buffer) { | |
| TORCH_CHECK(stream != nullptr, "stream must be valid when using borrowed command buffer"); | |
| stream->synchronize(at::mps::SyncType::COMMIT); | |
| } else { | |
| TORCH_CHECK(gptoss_metal_command_buffer_commit(&cb) == gptoss_status_success, | |
| "commit failed"); | |
| TORCH_CHECK(gptoss_metal_command_buffer_wait_completion(&cb, nullptr) == gptoss_status_success, | |
| "wait failed"); | |
| } | |
| } catch (...) { | |
| cleanup(); | |
| throw; | |
| } | |
| cleanup(); | |
| } | |
| at::Tensor to_cpu_contiguous(const at::Tensor& tensor) { | |
| if (tensor.device().is_cpu() && tensor.is_contiguous()) { | |
| return tensor; | |
| } | |
| return tensor.contiguous().to(at::kCPU); | |
| } | |
| at::Tensor empty_cpu_like(const at::Tensor& tensor) { | |
| return at::empty_like(tensor, tensor.options().device(at::kCPU)).contiguous(); | |
| } | |
| void copy_back(at::Tensor& dst, const at::Tensor& src_cpu) { | |
| dst.copy_(src_cpu.to(dst.device(), /*non_blocking=*/false, /*copy=*/true)); | |
| } | |
| void create_control_buffer(const gptoss_metal_device* device, MetalBuffer& buffer) { | |
| struct gptoss_control ctrl {0}; | |
| buffer.create(device, sizeof(ctrl), &ctrl); | |
| } | |
| template <typename LaunchFn> | |
| void run_dense_matmul_bf16(const char* kernel_symbol, | |
| LaunchFn&& launch_fn, | |
| const at::Tensor& input, | |
| const at::Tensor& weight_bf16, | |
| const at::Tensor& bias_bf16, | |
| at::Tensor& output) | |
| { | |
| TORCH_CHECK(input.dtype() == at::kFloat, "input must be float32"); | |
| TORCH_CHECK(weight_bf16.dtype() == at::kBFloat16, "weight must be bfloat16"); | |
| TORCH_CHECK(bias_bf16.dtype() == at::kBFloat16, "bias must be bfloat16"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(input.dim() == 2, "input must be 2D"); | |
| TORCH_CHECK(weight_bf16.dim() == 2, "weight must be 2D"); | |
| TORCH_CHECK(bias_bf16.dim() == 1, "bias must be 1D"); | |
| TORCH_CHECK(output.dim() == 2, "output must be 2D"); | |
| const int64_t num_tokens = input.size(0); | |
| const int64_t num_cols = input.size(1); | |
| const int64_t num_rows = output.size(1); | |
| TORCH_CHECK(output.size(0) == num_tokens, | |
| "output first dimension must match number of tokens"); | |
| TORCH_CHECK(weight_bf16.size(0) == num_cols && weight_bf16.size(1) == num_rows, | |
| "weight shape must be [num_cols, num_rows]"); | |
| TORCH_CHECK(bias_bf16.size(0) == num_rows, | |
| "bias length must equal number of rows"); | |
| const bool use_mps = input.device().is_mps() && weight_bf16.device().is_mps() && | |
| bias_bf16.device().is_mps() && output.device().is_mps(); | |
| at::Tensor input_cpu; | |
| at::Tensor weight_cpu; | |
| at::Tensor bias_cpu; | |
| at::Tensor out_cpu; | |
| size_t in_bytes = 0; | |
| size_t weight_bytes = 0; | |
| size_t bias_bytes = 0; | |
| size_t out_bytes = 0; | |
| at::Tensor input_mps; | |
| at::Tensor weight_mps; | |
| at::Tensor bias_mps; | |
| at::Tensor output_mps; | |
| if (use_mps) { | |
| input_mps = input.contiguous(); | |
| weight_mps = weight_bf16.transpose(0, 1).contiguous(); | |
| bias_mps = bias_bf16.contiguous(); | |
| output_mps = output.contiguous(); | |
| } else { | |
| input_cpu = to_cpu_contiguous(input); | |
| weight_cpu = weight_bf16.transpose(0, 1).contiguous().to(at::kCPU); | |
| bias_cpu = to_cpu_contiguous(bias_bf16); | |
| out_cpu = empty_cpu_like(output); | |
| in_bytes = static_cast<size_t>(input_cpu.numel()) * input_cpu.element_size(); | |
| weight_bytes = static_cast<size_t>(weight_cpu.numel()) * weight_cpu.element_size(); | |
| bias_bytes = static_cast<size_t>(bias_cpu.numel()) * bias_cpu.element_size(); | |
| out_bytes = static_cast<size_t>(out_cpu.numel()) * out_cpu.element_size(); | |
| } | |
| MetalBuffer input_buf; | |
| MetalBuffer weight_buf; | |
| MetalBuffer bias_buf; | |
| MetalBuffer out_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel(kernel_symbol, [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| input_buf.wrap_mps_tensor(&device, input_mps); | |
| weight_buf.wrap_mps_tensor(&device, weight_mps); | |
| bias_buf.wrap_mps_tensor(&device, bias_mps); | |
| out_buf.wrap_mps_tensor(&device, output_mps); | |
| } else { | |
| input_buf.wrap(&device, in_bytes, input_cpu.data_ptr()); | |
| weight_buf.wrap(&device, weight_bytes, weight_cpu.data_ptr()); | |
| bias_buf.wrap(&device, bias_bytes, bias_cpu.data_ptr()); | |
| out_buf.create(&device, out_bytes, nullptr); | |
| } | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| launch_fn( | |
| &cb, &fn, | |
| input_buf.get(), 0, | |
| weight_buf.get(), 0, | |
| bias_buf.get(), 0, | |
| out_buf.get(), 0, | |
| control_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_cols), | |
| static_cast<uint32_t>(num_rows)) == gptoss_status_success, | |
| "encode dense matmul failed"); | |
| }); | |
| if (use_mps) { | |
| if (!output_mps.is_alias_of(output)) { | |
| output.copy_(output_mps); | |
| } | |
| } else { | |
| std::memcpy(out_cpu.data_ptr(), out_buf.ptr(), out_bytes); | |
| copy_back(output, out_cpu); | |
| } | |
| } | |
| } // namespace | |
| void f32_bf16w_matmul_torch(const at::Tensor &input, | |
| const at::Tensor &weight_bf16, | |
| const at::Tensor &bias_bf16, | |
| at::Tensor &output, | |
| int64_t num_tokens, int64_t num_cols, int64_t num_rows, int64_t threadgroup_size) | |
| { | |
| TORCH_CHECK(input.dtype() == at::kFloat, "input must be float32"); | |
| TORCH_CHECK(weight_bf16.dtype() == at::kBFloat16, "weight must be bfloat16"); | |
| TORCH_CHECK(bias_bf16.dtype() == at::kBFloat16, "bias must be bfloat16"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(input.dim() == 2, "input must be 2D"); | |
| TORCH_CHECK(weight_bf16.dim() == 2, "weight must be 2D"); | |
| TORCH_CHECK(bias_bf16.dim() == 1, "bias must be 1D"); | |
| TORCH_CHECK(output.dim() == 2, "output must be 2D"); | |
| TORCH_CHECK(input.size(0) == num_tokens && input.size(1) == num_cols, | |
| "input shape must be [num_tokens, num_cols]"); | |
| TORCH_CHECK(weight_bf16.size(0) == num_cols && weight_bf16.size(1) == num_rows, | |
| "weight shape must be [num_cols, num_rows]"); | |
| TORCH_CHECK(bias_bf16.size(0) == num_rows, "bias length must be num_rows"); | |
| TORCH_CHECK(output.size(0) == num_tokens && output.size(1) == num_rows, | |
| "output shape must be [num_tokens, num_rows]"); | |
| const bool use_mps = input.device().is_mps() && weight_bf16.device().is_mps() && | |
| bias_bf16.device().is_mps() && output.device().is_mps(); | |
| at::Tensor input_cpu; | |
| at::Tensor weight_cpu; | |
| at::Tensor bias_cpu; | |
| at::Tensor out_cpu; | |
| size_t in_bytes = 0; | |
| size_t wt_bytes = 0; | |
| size_t bs_bytes = 0; | |
| size_t out_bytes = 0; | |
| at::Tensor input_mps; | |
| at::Tensor weight_mps; | |
| at::Tensor bias_mps; | |
| at::Tensor output_mps; | |
| if (use_mps) { | |
| input_mps = input.contiguous(); | |
| weight_mps = weight_bf16.transpose(0, 1).contiguous(); | |
| bias_mps = bias_bf16.contiguous(); | |
| output_mps = output.contiguous(); | |
| } else { | |
| input_cpu = to_cpu_contiguous(input); | |
| weight_cpu = weight_bf16.transpose(0, 1).contiguous().to(at::kCPU); | |
| bias_cpu = to_cpu_contiguous(bias_bf16); | |
| out_cpu = empty_cpu_like(output); | |
| in_bytes = static_cast<size_t>(num_tokens) * static_cast<size_t>(num_cols) * sizeof(float); | |
| wt_bytes = static_cast<size_t>(num_rows) * static_cast<size_t>(num_cols) * sizeof(uint16_t); | |
| bs_bytes = static_cast<size_t>(num_rows) * sizeof(uint16_t); | |
| out_bytes = static_cast<size_t>(num_tokens) * static_cast<size_t>(num_rows) * sizeof(float); | |
| } | |
| MetalBuffer in_buf; | |
| MetalBuffer wt_buf; | |
| MetalBuffer bs_buf; | |
| MetalBuffer out_buf; | |
| MetalBuffer ctrl_buf; | |
| run_metal_kernel("gptoss_f32_bf16w_matmul", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| in_buf.wrap_mps_tensor(&device, input_mps); | |
| wt_buf.wrap_mps_tensor(&device, weight_mps); | |
| bs_buf.wrap_mps_tensor(&device, bias_mps); | |
| out_buf.wrap_mps_tensor(&device, output_mps); | |
| } else { | |
| in_buf.wrap(&device, in_bytes, input_cpu.data_ptr()); | |
| wt_buf.wrap(&device, wt_bytes, weight_cpu.data_ptr()); | |
| bs_buf.wrap(&device, bs_bytes, bias_cpu.data_ptr()); | |
| out_buf.create(&device, out_bytes, nullptr); | |
| } | |
| create_control_buffer(&device, ctrl_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_bf16w_matmul( | |
| &cb, &fn, static_cast<size_t>(threadgroup_size), | |
| in_buf.get(), 0, | |
| wt_buf.get(), 0, | |
| bs_buf.get(), 0, | |
| out_buf.get(), 0, | |
| ctrl_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_cols), | |
| static_cast<uint32_t>(num_rows)) == gptoss_status_success, | |
| "encode failed"); | |
| }); | |
| if (use_mps) { | |
| if (!output_mps.is_alias_of(output)) { | |
| output.copy_(output_mps); | |
| } | |
| } else { | |
| std::memcpy(out_cpu.data_ptr(), out_buf.ptr(), out_bytes); | |
| copy_back(output, out_cpu); | |
| } | |
| } | |
| void bf16_f32_embeddings_torch(const at::Tensor& token_ids, | |
| const at::Tensor& weight_bf16, | |
| at::Tensor& output, | |
| int64_t threadgroup_size) | |
| { | |
| TORCH_CHECK(token_ids.dtype() == at::kInt || token_ids.dtype() == at::kLong, | |
| "token_ids must be int32 or int64"); | |
| TORCH_CHECK(weight_bf16.dtype() == at::kBFloat16, "weight must be bfloat16"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(token_ids.dim() == 1, "token_ids must be 1D"); | |
| TORCH_CHECK(weight_bf16.dim() == 2, "weight must be 2D"); | |
| TORCH_CHECK(output.dim() == 2, "output must be 2D"); | |
| const int64_t num_tokens = token_ids.size(0); | |
| TORCH_CHECK(output.size(0) == num_tokens, "output first dimension must match num_tokens"); | |
| const int64_t num_channels = output.size(1); | |
| TORCH_CHECK(num_channels % 4 == 0, "num_channels must be divisible by 4"); | |
| TORCH_CHECK(weight_bf16.size(1) == num_channels, | |
| "weight second dimension must equal embedding dimension (num_channels)"); | |
| TORCH_CHECK(threadgroup_size >= 0, "threadgroup_size must be non-negative"); | |
| const bool use_mps = token_ids.device().is_mps() && weight_bf16.device().is_mps() && | |
| output.device().is_mps(); | |
| at::Tensor tokens_cpu; | |
| at::Tensor weight_cpu; | |
| at::Tensor out_cpu; | |
| size_t token_bytes = 0; | |
| size_t weight_bytes = 0; | |
| size_t out_bytes = 0; | |
| at::Tensor tokens_mps; | |
| at::Tensor weight_mps; | |
| at::Tensor output_mps; | |
| if (use_mps) { | |
| tokens_mps = (token_ids.dtype() == at::kInt ? token_ids : token_ids.to(at::kInt)).contiguous(); | |
| weight_mps = weight_bf16.contiguous(); | |
| output_mps = output.contiguous(); | |
| } else { | |
| tokens_cpu = token_ids.dtype() == at::kInt | |
| ? to_cpu_contiguous(token_ids) | |
| : token_ids.to(at::kInt).contiguous().to(at::kCPU); | |
| weight_cpu = to_cpu_contiguous(weight_bf16); | |
| out_cpu = empty_cpu_like(output); | |
| token_bytes = static_cast<size_t>(num_tokens) * sizeof(uint32_t); | |
| weight_bytes = static_cast<size_t>(weight_cpu.numel()) * weight_cpu.element_size(); | |
| out_bytes = static_cast<size_t>(out_cpu.numel()) * out_cpu.element_size(); | |
| } | |
| MetalBuffer tokens_buf; | |
| MetalBuffer weight_buf; | |
| MetalBuffer out_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel("gptoss_bf16_f32_embeddings", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| tokens_buf.wrap_mps_tensor(&device, tokens_mps); | |
| weight_buf.wrap_mps_tensor(&device, weight_mps); | |
| out_buf.wrap_mps_tensor(&device, output_mps); | |
| } else { | |
| tokens_buf.wrap(&device, token_bytes, tokens_cpu.data_ptr()); | |
| weight_buf.wrap(&device, weight_bytes, weight_cpu.data_ptr()); | |
| out_buf.create(&device, out_bytes, nullptr); | |
| } | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_bf16_f32_embeddings( | |
| &cb, &fn, static_cast<size_t>(threadgroup_size), | |
| tokens_buf.get(), 0, | |
| weight_buf.get(), 0, | |
| out_buf.get(), 0, | |
| control_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_channels)) == gptoss_status_success, | |
| "encode embeddings failed"); | |
| }); | |
| if (use_mps) { | |
| if (!output_mps.is_alias_of(output)) { | |
| output.copy_(output_mps); | |
| } | |
| } else { | |
| std::memcpy(out_cpu.data_ptr(), out_buf.ptr(), out_bytes); | |
| copy_back(output, out_cpu); | |
| } | |
| } | |
| void f32_bf16w_rmsnorm_torch(const at::Tensor& input, | |
| const at::Tensor& weight_bf16, | |
| at::Tensor& output, | |
| double epsilon) | |
| { | |
| TORCH_CHECK(input.dtype() == at::kFloat, "input must be float32"); | |
| TORCH_CHECK(weight_bf16.dtype() == at::kBFloat16, "weight must be bfloat16"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(input.dim() == 2, "input must be 2D"); | |
| TORCH_CHECK(weight_bf16.dim() == 1, "weight must be 1D"); | |
| TORCH_CHECK(output.dim() == 2, "output must be 2D"); | |
| const int64_t num_tokens = input.size(0); | |
| const int64_t num_channels = input.size(1); | |
| TORCH_CHECK(output.size(0) == num_tokens && output.size(1) == num_channels, | |
| "output shape must match input shape"); | |
| TORCH_CHECK(weight_bf16.size(0) == num_channels, | |
| "weight length must equal number of channels"); | |
| TORCH_CHECK(num_channels % 4 == 0, "num_channels must be divisible by 4"); | |
| const bool use_mps = input.device().is_mps(); | |
| at::Tensor input_cpu; | |
| at::Tensor weight_cpu; | |
| at::Tensor out_cpu; | |
| size_t in_bytes = 0; | |
| size_t weight_bytes = 0; | |
| size_t out_bytes = 0; | |
| at::Tensor input_mps; | |
| at::Tensor weight_mps; | |
| at::Tensor output_mps; | |
| if (use_mps) { | |
| input_mps = input.contiguous(); | |
| weight_mps = weight_bf16.contiguous(); | |
| output_mps = output.contiguous(); | |
| } else { | |
| input_cpu = to_cpu_contiguous(input); | |
| weight_cpu = to_cpu_contiguous(weight_bf16); | |
| out_cpu = empty_cpu_like(output); | |
| in_bytes = static_cast<size_t>(input_cpu.numel()) * input_cpu.element_size(); | |
| weight_bytes = static_cast<size_t>(weight_cpu.numel()) * weight_cpu.element_size(); | |
| out_bytes = static_cast<size_t>(out_cpu.numel()) * out_cpu.element_size(); | |
| } | |
| MetalBuffer input_buf; | |
| MetalBuffer weight_buf; | |
| MetalBuffer out_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel("gptoss_f32_bf16w_rmsnorm", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| input_buf.wrap_mps_tensor(&device, input_mps); | |
| weight_buf.wrap_mps_tensor(&device, weight_mps); | |
| out_buf.wrap_mps_tensor(&device, output_mps); | |
| } else { | |
| input_buf.wrap(&device, in_bytes, input_cpu.data_ptr()); | |
| weight_buf.wrap(&device, weight_bytes, weight_cpu.data_ptr()); | |
| out_buf.create(&device, out_bytes, nullptr); | |
| } | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_bf16w_rmsnorm( | |
| &cb, &fn, | |
| input_buf.get(), 0, | |
| weight_buf.get(), 0, | |
| out_buf.get(), 0, | |
| control_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_channels), | |
| static_cast<float>(epsilon)) == gptoss_status_success, | |
| "encode rmsnorm failed"); | |
| }); | |
| if (use_mps) { | |
| if (!output_mps.is_alias_of(output)) { | |
| output.copy_(output_mps); | |
| } | |
| } else { | |
| std::memcpy(out_cpu.data_ptr(), out_buf.ptr(), out_bytes); | |
| copy_back(output, out_cpu); | |
| } | |
| } | |
| void f32_bf16w_dense_matmul_qkv_torch(const at::Tensor& input, | |
| const at::Tensor& weight_bf16, | |
| const at::Tensor& bias_bf16, | |
| at::Tensor& output) | |
| { | |
| run_dense_matmul_bf16( | |
| "gptoss_f32_bf16w_dense_matmul_qkv", | |
| gptoss_metal_command_buffer_encode_launch_f32_bf16w_dense_matmul_qkv, | |
| input, weight_bf16, bias_bf16, output); | |
| } | |
| void f32_bf16w_dense_matmul_attn_output_torch(const at::Tensor& input, | |
| const at::Tensor& weight_bf16, | |
| const at::Tensor& bias_bf16, | |
| at::Tensor& output) | |
| { | |
| run_dense_matmul_bf16( | |
| "gptoss_f32_bf16w_dense_matmul_attn_output", | |
| gptoss_metal_command_buffer_encode_launch_f32_bf16w_dense_matmul_attn_output, | |
| input, weight_bf16, bias_bf16, output); | |
| } | |
| void f32_bf16w_dense_matmul_mlp_gate_torch(const at::Tensor& input, | |
| const at::Tensor& weight_bf16, | |
| const at::Tensor& bias_bf16, | |
| at::Tensor& output) | |
| { | |
| run_dense_matmul_bf16( | |
| "gptoss_f32_bf16w_dense_matmul_mlp_gate", | |
| gptoss_metal_command_buffer_encode_launch_f32_bf16w_dense_matmul_mlp_gate, | |
| input, weight_bf16, bias_bf16, output); | |
| } | |
| void f32_rope_torch(at::Tensor& activations, | |
| double rope_base, | |
| double interpolation_scale, | |
| double yarn_offset, | |
| double yarn_scale, | |
| double yarn_multiplier, | |
| int64_t num_tokens, | |
| int64_t num_q_heads, | |
| int64_t num_kv_heads, | |
| int64_t attn_head_dim, | |
| int64_t token_offset, | |
| int64_t threadgroup_size) | |
| { | |
| TORCH_CHECK(activations.dtype() == at::kFloat, "activations must be float32"); | |
| TORCH_CHECK(num_tokens >= 0 && num_q_heads >= 0 && num_kv_heads >= 0 && attn_head_dim >= 0, | |
| "shape parameters must be non-negative"); | |
| TORCH_CHECK(threadgroup_size >= 0, "threadgroup_size must be non-negative"); | |
| const bool use_mps = activations.device().is_mps(); | |
| at::Tensor activations_cpu; | |
| at::Tensor activations_mps; | |
| size_t activations_bytes = 0; | |
| if (use_mps) { | |
| activations_mps = activations.contiguous(); | |
| activations_bytes = static_cast<size_t>(activations_mps.numel()) * activations_mps.element_size(); | |
| } else { | |
| activations_cpu = to_cpu_contiguous(activations); | |
| activations_bytes = static_cast<size_t>(activations_cpu.numel()) * activations_cpu.element_size(); | |
| } | |
| MetalBuffer activations_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel("gptoss_f32_rope", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| activations_buf.wrap_mps_tensor(&device, activations_mps); | |
| } else { | |
| activations_buf.wrap(&device, activations_bytes, activations_cpu.data_ptr()); | |
| } | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_rope( | |
| &cb, &fn, | |
| static_cast<size_t>(threadgroup_size), | |
| activations_buf.get(), 0, | |
| control_buf.get(), 0, | |
| static_cast<float>(rope_base), | |
| static_cast<float>(interpolation_scale), | |
| static_cast<float>(yarn_offset), | |
| static_cast<float>(yarn_scale), | |
| static_cast<float>(yarn_multiplier), | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_q_heads), | |
| static_cast<uint32_t>(num_kv_heads), | |
| static_cast<uint32_t>(attn_head_dim), | |
| static_cast<uint32_t>(token_offset)) == gptoss_status_success, | |
| "encode rope failed"); | |
| }); | |
| if (use_mps) { | |
| if (!activations_mps.is_alias_of(activations)) { | |
| activations.copy_(activations_mps); | |
| } | |
| } else { | |
| copy_back(activations, activations_cpu); | |
| } | |
| } | |
| void f32_bf16w_matmul_qkv_torch(const at::Tensor& input, | |
| const at::Tensor& weight_bf16, | |
| const at::Tensor& bias_bf16, | |
| at::Tensor& output, | |
| at::Tensor& kv_cache, | |
| int64_t kv_cache_offset_bytes, | |
| int64_t num_tokens, | |
| int64_t num_cols, | |
| int64_t num_q_heads, | |
| int64_t num_kv_heads, | |
| int64_t attn_head_dim, | |
| int64_t token_offset, | |
| int64_t max_tokens, | |
| double rope_base, | |
| double interpolation_scale, | |
| double yarn_offset, | |
| double yarn_scale, | |
| double yarn_multiplier, | |
| int64_t threadgroup_size) | |
| { | |
| TORCH_CHECK(input.dtype() == at::kFloat, "input must be float32"); | |
| TORCH_CHECK(weight_bf16.dtype() == at::kBFloat16, "weight must be bfloat16"); | |
| TORCH_CHECK(bias_bf16.dtype() == at::kBFloat16, "bias must be bfloat16"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(kv_cache.dtype() == at::kFloat, "kv_cache must be float32"); | |
| TORCH_CHECK(input.dim() == 2, "input must be 2D"); | |
| TORCH_CHECK(weight_bf16.dim() == 2, "weight must be 2D"); | |
| TORCH_CHECK(bias_bf16.dim() == 1, "bias must be 1D"); | |
| TORCH_CHECK(output.dim() == 2, "output must be 2D"); | |
| TORCH_CHECK(num_tokens >= 0 && num_cols >= 0 && num_q_heads >= 0 && num_kv_heads >= 0 && attn_head_dim >= 0 && max_tokens >= 0, | |
| "shape parameters must be non-negative"); | |
| TORCH_CHECK(threadgroup_size >= 0, "threadgroup_size must be non-negative"); | |
| TORCH_CHECK(kv_cache_offset_bytes >= 0, "kv_cache_offset_bytes must be non-negative"); | |
| TORCH_CHECK(input.size(0) == num_tokens && input.size(1) == num_cols, | |
| "input shape must be [num_tokens, num_cols]"); | |
| const int64_t num_rows = (num_q_heads + 2 * num_kv_heads) * attn_head_dim; | |
| TORCH_CHECK(output.size(0) == num_tokens && output.size(1) == num_rows, | |
| "output shape must be [num_tokens, (num_q_heads + 2 * num_kv_heads) * attn_head_dim]"); | |
| TORCH_CHECK(weight_bf16.size(0) == num_cols && weight_bf16.size(1) == num_rows, | |
| "weight shape must be [num_cols, (num_q_heads + 2 * num_kv_heads) * attn_head_dim]"); | |
| TORCH_CHECK(bias_bf16.size(0) == num_rows, | |
| "bias length must equal output feature dimension"); | |
| const bool use_mps = input.device().is_mps() && weight_bf16.device().is_mps() && | |
| bias_bf16.device().is_mps() && output.device().is_mps() && kv_cache.device().is_mps(); | |
| at::Tensor input_cpu; | |
| at::Tensor weight_cpu; | |
| at::Tensor bias_cpu; | |
| at::Tensor out_cpu; | |
| at::Tensor kv_cpu; | |
| size_t in_bytes = 0; | |
| size_t weight_bytes = 0; | |
| size_t bias_bytes = 0; | |
| size_t out_bytes = 0; | |
| size_t kv_bytes = 0; | |
| at::Tensor input_mps; | |
| at::Tensor weight_mps; | |
| at::Tensor bias_mps; | |
| at::Tensor out_mps; | |
| at::Tensor kv_mps; | |
| if (use_mps) { | |
| input_mps = input.contiguous(); | |
| weight_mps = weight_bf16.transpose(0, 1).contiguous(); | |
| bias_mps = bias_bf16.contiguous(); | |
| out_mps = output.contiguous(); | |
| kv_mps = kv_cache.contiguous(); | |
| } else { | |
| input_cpu = to_cpu_contiguous(input); | |
| weight_cpu = weight_bf16.transpose(0, 1).contiguous().to(at::kCPU); | |
| bias_cpu = to_cpu_contiguous(bias_bf16); | |
| out_cpu = empty_cpu_like(output); | |
| kv_cpu = to_cpu_contiguous(kv_cache); | |
| in_bytes = static_cast<size_t>(input_cpu.numel()) * input_cpu.element_size(); | |
| weight_bytes = static_cast<size_t>(weight_cpu.numel()) * weight_cpu.element_size(); | |
| bias_bytes = static_cast<size_t>(bias_cpu.numel()) * bias_cpu.element_size(); | |
| out_bytes = static_cast<size_t>(out_cpu.numel()) * out_cpu.element_size(); | |
| kv_bytes = static_cast<size_t>(kv_cpu.numel()) * kv_cpu.element_size(); | |
| } | |
| MetalBuffer input_buf; | |
| MetalBuffer weight_buf; | |
| MetalBuffer bias_buf; | |
| MetalBuffer out_buf; | |
| MetalBuffer kv_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel("gptoss_f32_bf16w_matmul_qkv", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| input_buf.wrap_mps_tensor(&device, input_mps); | |
| weight_buf.wrap_mps_tensor(&device, weight_mps); | |
| bias_buf.wrap_mps_tensor(&device, bias_mps); | |
| out_buf.wrap_mps_tensor(&device, out_mps); | |
| kv_buf.wrap_mps_tensor(&device, kv_mps); | |
| } else { | |
| input_buf.wrap(&device, in_bytes, input_cpu.data_ptr()); | |
| weight_buf.wrap(&device, weight_bytes, weight_cpu.data_ptr()); | |
| bias_buf.wrap(&device, bias_bytes, bias_cpu.data_ptr()); | |
| out_buf.create(&device, out_bytes, nullptr); | |
| kv_buf.wrap(&device, kv_bytes, kv_cpu.data_ptr()); | |
| } | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_bf16w_matmul_qkv( | |
| &cb, &fn, | |
| static_cast<size_t>(threadgroup_size), | |
| input_buf.get(), 0, | |
| weight_buf.get(), 0, | |
| bias_buf.get(), 0, | |
| out_buf.get(), 0, | |
| kv_buf.get(), static_cast<size_t>(kv_cache_offset_bytes), | |
| control_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_cols), | |
| static_cast<uint32_t>(num_q_heads), | |
| static_cast<uint32_t>(num_kv_heads), | |
| static_cast<uint32_t>(attn_head_dim), | |
| static_cast<uint32_t>(token_offset), | |
| static_cast<uint32_t>(max_tokens), | |
| static_cast<float>(rope_base), | |
| static_cast<float>(interpolation_scale), | |
| static_cast<float>(yarn_offset), | |
| static_cast<float>(yarn_scale), | |
| static_cast<float>(yarn_multiplier)) == gptoss_status_success, | |
| "encode matmul_qkv failed"); | |
| }); | |
| if (use_mps) { | |
| if (!out_mps.is_alias_of(output)) { | |
| output.copy_(out_mps); | |
| } | |
| if (!kv_mps.is_alias_of(kv_cache)) { | |
| kv_cache.copy_(kv_mps); | |
| } | |
| } else { | |
| std::memcpy(out_cpu.data_ptr(), out_buf.ptr(), out_bytes); | |
| copy_back(output, out_cpu); | |
| copy_back(kv_cache, kv_cpu); | |
| } | |
| } | |
| void f32_sdpa_torch(const at::Tensor& q, | |
| int64_t q_offset_bytes, | |
| const at::Tensor& kv, | |
| int64_t kv_offset_bytes, | |
| const at::Tensor& s_bf16, | |
| int64_t s_offset_bytes, | |
| at::Tensor& output, | |
| int64_t output_offset_bytes, | |
| int64_t window, | |
| int64_t kv_stride, | |
| int64_t num_q_tokens, | |
| int64_t num_kv_tokens, | |
| int64_t num_q_heads, | |
| int64_t num_kv_heads, | |
| int64_t head_dim) | |
| { | |
| TORCH_CHECK(q.dtype() == at::kFloat, "q must be float32"); | |
| TORCH_CHECK(kv.dtype() == at::kFloat, "kv must be float32"); | |
| TORCH_CHECK(s_bf16.dtype() == at::kBFloat16, "s must be bfloat16"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(q_offset_bytes >= 0 && kv_offset_bytes >= 0 && s_offset_bytes >= 0 && output_offset_bytes >= 0, | |
| "offsets must be non-negative"); | |
| TORCH_CHECK(window >= 0 && kv_stride >= 0 && num_q_tokens >= 0 && num_kv_tokens >= 0 && num_q_heads >= 0 && num_kv_heads >= 0 && head_dim >= 0, | |
| "shape parameters must be non-negative"); | |
| const bool use_mps = q.device().is_mps() && kv.device().is_mps() && | |
| s_bf16.device().is_mps() && output.device().is_mps(); | |
| at::Tensor q_cpu; | |
| at::Tensor kv_cpu; | |
| at::Tensor s_cpu; | |
| at::Tensor out_cpu; | |
| size_t q_bytes = 0; | |
| size_t kv_bytes = 0; | |
| size_t s_bytes = 0; | |
| size_t out_bytes = 0; | |
| at::Tensor q_mps; | |
| at::Tensor kv_mps; | |
| at::Tensor s_mps; | |
| at::Tensor out_mps; | |
| if (use_mps) { | |
| q_mps = q.contiguous(); | |
| kv_mps = kv.contiguous(); | |
| s_mps = s_bf16.contiguous(); | |
| out_mps = output.contiguous(); | |
| } else { | |
| q_cpu = to_cpu_contiguous(q); | |
| kv_cpu = to_cpu_contiguous(kv); | |
| s_cpu = to_cpu_contiguous(s_bf16); | |
| out_cpu = empty_cpu_like(output); | |
| q_bytes = static_cast<size_t>(q_cpu.numel()) * q_cpu.element_size(); | |
| kv_bytes = static_cast<size_t>(kv_cpu.numel()) * kv_cpu.element_size(); | |
| s_bytes = static_cast<size_t>(s_cpu.numel()) * s_cpu.element_size(); | |
| out_bytes = static_cast<size_t>(out_cpu.numel()) * out_cpu.element_size(); | |
| } | |
| MetalBuffer q_buf; | |
| MetalBuffer kv_buf; | |
| MetalBuffer s_buf; | |
| MetalBuffer out_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel("gptoss_f32_sdpa_q8_d64", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| q_buf.wrap_mps_tensor(&device, q_mps); | |
| kv_buf.wrap_mps_tensor(&device, kv_mps); | |
| s_buf.wrap_mps_tensor(&device, s_mps); | |
| out_buf.wrap_mps_tensor(&device, out_mps); | |
| } else { | |
| q_buf.wrap(&device, q_bytes, q_cpu.data_ptr()); | |
| kv_buf.wrap(&device, kv_bytes, kv_cpu.data_ptr()); | |
| s_buf.wrap(&device, s_bytes, s_cpu.data_ptr()); | |
| out_buf.create(&device, out_bytes, nullptr); | |
| } | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_sdpa( | |
| &cb, &fn, | |
| q_buf.get(), static_cast<size_t>(q_offset_bytes), | |
| kv_buf.get(), static_cast<size_t>(kv_offset_bytes), | |
| s_buf.get(), static_cast<size_t>(s_offset_bytes), | |
| out_buf.get(), static_cast<size_t>(output_offset_bytes), | |
| control_buf.get(), 0, | |
| static_cast<uint32_t>(window), | |
| static_cast<uint32_t>(kv_stride), | |
| static_cast<uint32_t>(num_q_tokens), | |
| static_cast<uint32_t>(num_kv_tokens), | |
| static_cast<uint32_t>(num_q_heads), | |
| static_cast<uint32_t>(num_kv_heads), | |
| static_cast<uint32_t>(head_dim)) == gptoss_status_success, | |
| "encode sdpa failed"); | |
| }); | |
| if (use_mps) { | |
| if (!out_mps.is_alias_of(output)) { | |
| output.copy_(out_mps); | |
| } | |
| } else { | |
| std::memcpy(out_cpu.data_ptr(), out_buf.ptr(), out_bytes); | |
| copy_back(output, out_cpu); | |
| } | |
| } | |
| void f32_topk_torch(const at::Tensor& scores, | |
| at::Tensor& expert_ids, | |
| at::Tensor& expert_scores, | |
| int64_t num_tokens, | |
| int64_t num_experts, | |
| int64_t num_active_experts) | |
| { | |
| TORCH_CHECK(scores.dtype() == at::kFloat, "scores must be float32"); | |
| TORCH_CHECK(expert_ids.dtype() == at::kInt, "expert_ids must be int32"); | |
| TORCH_CHECK(expert_scores.dtype() == at::kFloat, "expert_scores must be float32"); | |
| TORCH_CHECK(num_tokens >= 0 && num_experts >= 0 && num_active_experts >= 0, | |
| "shape parameters must be non-negative"); | |
| TORCH_CHECK(scores.size(0) == num_tokens, | |
| "scores first dimension must match num_tokens"); | |
| TORCH_CHECK(scores.numel() == num_tokens * num_experts, | |
| "scores must have num_tokens * num_experts elements"); | |
| TORCH_CHECK(expert_ids.numel() == num_tokens * num_active_experts, | |
| "expert_ids must have num_tokens * num_active_experts elements"); | |
| TORCH_CHECK(expert_scores.numel() == num_tokens * num_active_experts, | |
| "expert_scores must have num_tokens * num_active_experts elements"); | |
| const bool scores_use_mps = scores.device().is_mps(); | |
| at::Tensor scores_cpu; | |
| at::Tensor scores_mps; | |
| size_t score_bytes = 0; | |
| if (scores_use_mps) { | |
| scores_mps = scores.contiguous(); | |
| } else { | |
| scores_cpu = to_cpu_contiguous(scores); | |
| score_bytes = static_cast<size_t>(scores_cpu.numel()) * scores_cpu.element_size(); | |
| } | |
| std::vector<gptoss_expert_prediction> predictions(static_cast<size_t>(num_tokens) * static_cast<size_t>(num_active_experts)); | |
| const size_t pred_bytes = predictions.size() * sizeof(gptoss_expert_prediction); | |
| MetalBuffer score_buf; | |
| MetalBuffer pred_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel("gptoss_f32_topk_softmax_e128_k4", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (scores_use_mps) { | |
| score_buf.wrap_mps_tensor(&device, scores_mps); | |
| } else { | |
| score_buf.wrap(&device, score_bytes, scores_cpu.data_ptr()); | |
| } | |
| pred_buf.wrap(&device, pred_bytes, predictions.data()); | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_topk( | |
| &cb, &fn, | |
| score_buf.get(), 0, | |
| pred_buf.get(), 0, | |
| control_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_experts), | |
| static_cast<uint32_t>(num_active_experts)) == gptoss_status_success, | |
| "encode topk failed"); | |
| }); | |
| auto ids_cpu = expert_ids.to(at::kCPU).contiguous(); | |
| auto scores_out_cpu = expert_scores.to(at::kCPU).contiguous(); | |
| auto* ids_ptr = ids_cpu.data_ptr<int32_t>(); | |
| auto* scores_ptr = scores_out_cpu.data_ptr<float>(); | |
| const size_t total = predictions.size(); | |
| for (size_t i = 0; i < total; ++i) { | |
| ids_ptr[i] = static_cast<int32_t>(predictions[i].expert_id); | |
| scores_ptr[i] = predictions[i].score; | |
| } | |
| copy_back(expert_ids, ids_cpu); | |
| copy_back(expert_scores, scores_out_cpu); | |
| } | |
| void expert_routing_metadata_torch(const at::Tensor& expert_ids, | |
| const at::Tensor& expert_scores, | |
| at::Tensor& expert_offsets, | |
| at::Tensor& intra_expert_offsets, | |
| int64_t num_tokens, | |
| int64_t num_experts) | |
| { | |
| TORCH_CHECK(expert_ids.dtype() == at::kInt, "expert_ids must be int32"); | |
| TORCH_CHECK(expert_scores.dtype() == at::kFloat, "expert_scores must be float32"); | |
| TORCH_CHECK(expert_offsets.dtype() == at::kInt, "expert_offsets must be int32"); | |
| TORCH_CHECK(intra_expert_offsets.dtype() == at::kInt, "intra_expert_offsets must be int32"); | |
| TORCH_CHECK(num_tokens >= 0 && num_experts >= 0, "shape parameters must be non-negative"); | |
| TORCH_CHECK(expert_ids.numel() == num_tokens, | |
| "expert_ids must have num_tokens elements"); | |
| TORCH_CHECK(expert_scores.numel() == num_tokens, | |
| "expert_scores must have num_tokens elements"); | |
| TORCH_CHECK(intra_expert_offsets.numel() == num_tokens, | |
| "intra_expert_offsets must have num_tokens elements"); | |
| TORCH_CHECK(expert_offsets.numel() == num_experts + 1, | |
| "expert_offsets must have num_experts + 1 elements"); | |
| auto ids_cpu = to_cpu_contiguous(expert_ids); | |
| auto scores_cpu = to_cpu_contiguous(expert_scores); | |
| const bool use_mps = expert_offsets.device().is_mps() && intra_expert_offsets.device().is_mps(); | |
| at::Tensor offsets_cpu; | |
| at::Tensor intra_offsets_cpu; | |
| at::Tensor offsets_mps; | |
| at::Tensor intra_offsets_mps; | |
| size_t offsets_bytes = 0; | |
| size_t intra_bytes = 0; | |
| if (use_mps) { | |
| offsets_mps = expert_offsets.contiguous(); | |
| intra_offsets_mps = intra_expert_offsets.contiguous(); | |
| } else { | |
| offsets_cpu = to_cpu_contiguous(expert_offsets); | |
| intra_offsets_cpu = to_cpu_contiguous(intra_expert_offsets); | |
| offsets_bytes = static_cast<size_t>(offsets_cpu.numel()) * offsets_cpu.element_size(); | |
| intra_bytes = static_cast<size_t>(intra_offsets_cpu.numel()) * intra_offsets_cpu.element_size(); | |
| } | |
| std::vector<gptoss_expert_prediction> predictions(static_cast<size_t>(num_tokens)); | |
| const auto* ids_ptr = ids_cpu.data_ptr<int32_t>(); | |
| const auto* scores_ptr = scores_cpu.data_ptr<float>(); | |
| for (int64_t i = 0; i < num_tokens; ++i) { | |
| predictions[static_cast<size_t>(i)] = gptoss_expert_prediction { | |
| .expert_id = static_cast<uint32_t>(ids_ptr[i]), | |
| .score = scores_ptr[i], | |
| }; | |
| } | |
| const size_t pred_bytes = predictions.size() * sizeof(gptoss_expert_prediction); | |
| MetalBuffer pred_buf; | |
| MetalBuffer offsets_buf; | |
| MetalBuffer intra_offsets_buf; | |
| run_metal_kernel("gptoss_f32_expert_routing_metadata", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| pred_buf.wrap(&device, pred_bytes, predictions.data()); | |
| if (use_mps) { | |
| offsets_buf.wrap_mps_tensor(&device, offsets_mps); | |
| intra_offsets_buf.wrap_mps_tensor(&device, intra_offsets_mps); | |
| } else { | |
| offsets_buf.wrap(&device, offsets_bytes, offsets_cpu.data_ptr()); | |
| intra_offsets_buf.wrap(&device, intra_bytes, intra_offsets_cpu.data_ptr()); | |
| } | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_expert_routing_metadata( | |
| &cb, &fn, | |
| pred_buf.get(), 0, | |
| offsets_buf.get(), 0, | |
| intra_offsets_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_experts)) == gptoss_status_success, | |
| "encode expert_routing_metadata failed"); | |
| }); | |
| if (use_mps) { | |
| if (!offsets_mps.is_alias_of(expert_offsets)) { | |
| expert_offsets.copy_(offsets_mps); | |
| } | |
| if (!intra_offsets_mps.is_alias_of(intra_expert_offsets)) { | |
| intra_expert_offsets.copy_(intra_offsets_mps); | |
| } | |
| } else { | |
| copy_back(expert_offsets, offsets_cpu); | |
| copy_back(intra_expert_offsets, intra_offsets_cpu); | |
| } | |
| } | |
| void f32_scatter_torch(const at::Tensor& input, | |
| const at::Tensor& expert_ids, | |
| const at::Tensor& expert_scores, | |
| const at::Tensor& expert_offsets, | |
| const at::Tensor& intra_expert_offsets, | |
| at::Tensor& output, | |
| int64_t num_channels, | |
| int64_t num_tokens, | |
| int64_t num_active_experts) | |
| { | |
| TORCH_CHECK(input.dtype() == at::kFloat, "input must be float32"); | |
| TORCH_CHECK(expert_ids.dtype() == at::kInt, "expert_ids must be int32"); | |
| TORCH_CHECK(expert_scores.dtype() == at::kFloat, "expert_scores must be float32"); | |
| TORCH_CHECK(expert_offsets.dtype() == at::kInt, "expert_offsets must be int32"); | |
| TORCH_CHECK(intra_expert_offsets.dtype() == at::kInt, "intra_expert_offsets must be int32"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(num_channels >= 0 && num_tokens >= 0 && num_active_experts >= 0, | |
| "shape parameters must be non-negative"); | |
| TORCH_CHECK(input.numel() == static_cast<int64_t>(num_tokens / num_active_experts) * num_channels, | |
| "input size mismatch"); | |
| TORCH_CHECK(expert_ids.numel() == num_tokens, | |
| "expert_ids must have num_tokens elements"); | |
| TORCH_CHECK(expert_scores.numel() == num_tokens, | |
| "expert_scores must have num_tokens elements"); | |
| TORCH_CHECK(intra_expert_offsets.numel() == num_tokens, | |
| "intra_expert_offsets must have num_tokens elements"); | |
| TORCH_CHECK(output.numel() == num_tokens * num_channels / num_active_experts, | |
| "output size mismatch"); | |
| const bool use_mps = input.device().is_mps() && expert_offsets.device().is_mps() && | |
| intra_expert_offsets.device().is_mps() && output.device().is_mps(); | |
| at::Tensor input_cpu; | |
| at::Tensor expert_offsets_cpu; | |
| at::Tensor intra_offsets_cpu; | |
| at::Tensor output_cpu; | |
| size_t input_bytes = 0; | |
| size_t offsets_bytes = 0; | |
| size_t intra_bytes = 0; | |
| size_t output_bytes = 0; | |
| at::Tensor input_mps; | |
| at::Tensor expert_offsets_mps; | |
| at::Tensor intra_offsets_mps; | |
| at::Tensor output_mps; | |
| if (use_mps) { | |
| input_mps = input.contiguous(); | |
| expert_offsets_mps = expert_offsets.contiguous(); | |
| intra_offsets_mps = intra_expert_offsets.contiguous(); | |
| output_mps = output.contiguous(); | |
| } else { | |
| input_cpu = to_cpu_contiguous(input); | |
| expert_offsets_cpu = to_cpu_contiguous(expert_offsets); | |
| intra_offsets_cpu = to_cpu_contiguous(intra_expert_offsets); | |
| output_cpu = empty_cpu_like(output); | |
| input_bytes = static_cast<size_t>(input_cpu.numel()) * input_cpu.element_size(); | |
| offsets_bytes = static_cast<size_t>(expert_offsets_cpu.numel()) * expert_offsets_cpu.element_size(); | |
| intra_bytes = static_cast<size_t>(intra_offsets_cpu.numel()) * intra_offsets_cpu.element_size(); | |
| output_bytes = static_cast<size_t>(output_cpu.numel()) * output_cpu.element_size(); | |
| } | |
| std::vector<gptoss_expert_prediction> predictions(static_cast<size_t>(num_tokens)); | |
| const auto* ids_ptr = expert_ids.to(at::kCPU).contiguous().data_ptr<int32_t>(); | |
| const auto* scores_ptr = expert_scores.to(at::kCPU).contiguous().data_ptr<float>(); | |
| for (int64_t i = 0; i < num_tokens; ++i) { | |
| predictions[static_cast<size_t>(i)] = gptoss_expert_prediction { | |
| .expert_id = static_cast<uint32_t>(ids_ptr[i]), | |
| .score = scores_ptr[i], | |
| }; | |
| } | |
| const size_t pred_bytes = predictions.size() * sizeof(gptoss_expert_prediction); | |
| MetalBuffer input_buf; | |
| MetalBuffer pred_buf; | |
| MetalBuffer offsets_buf; | |
| MetalBuffer intra_offsets_buf; | |
| MetalBuffer output_buf; | |
| run_metal_kernel("gptoss_f32_scatter_e4", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| input_buf.wrap_mps_tensor(&device, input_mps); | |
| offsets_buf.wrap_mps_tensor(&device, expert_offsets_mps); | |
| intra_offsets_buf.wrap_mps_tensor(&device, intra_offsets_mps); | |
| output_buf.wrap_mps_tensor(&device, output_mps); | |
| } else { | |
| input_buf.wrap(&device, input_bytes, input_cpu.data_ptr()); | |
| offsets_buf.wrap(&device, offsets_bytes, expert_offsets_cpu.data_ptr()); | |
| intra_offsets_buf.wrap(&device, intra_bytes, intra_offsets_cpu.data_ptr()); | |
| output_buf.create(&device, output_bytes, nullptr); | |
| } | |
| pred_buf.wrap(&device, pred_bytes, predictions.data()); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_scatter( | |
| &cb, &fn, | |
| input_buf.get(), 0, | |
| pred_buf.get(), 0, | |
| offsets_buf.get(), 0, | |
| intra_offsets_buf.get(), 0, | |
| output_buf.get(), 0, | |
| static_cast<uint32_t>(num_channels), | |
| static_cast<uint32_t>(num_tokens / num_active_experts), | |
| static_cast<uint32_t>(num_active_experts)) == gptoss_status_success, | |
| "encode scatter failed"); | |
| }); | |
| if (use_mps) { | |
| if (!output_mps.is_alias_of(output)) { | |
| output.copy_(output_mps); | |
| } | |
| } else { | |
| std::memcpy(output_cpu.data_ptr(), output_buf.ptr(), output_bytes); | |
| copy_back(output, output_cpu); | |
| } | |
| } | |
| void f32_bf16w_matmul_add_torch(const at::Tensor& input, | |
| const at::Tensor& weight_bf16, | |
| const at::Tensor& bias_bf16, | |
| at::Tensor& output, | |
| int64_t num_tokens, | |
| int64_t num_cols, | |
| int64_t num_rows, | |
| int64_t threadgroup_size) | |
| { | |
| TORCH_CHECK(input.dtype() == at::kFloat, "input must be float32"); | |
| TORCH_CHECK(weight_bf16.dtype() == at::kBFloat16, "weight must be bfloat16"); | |
| TORCH_CHECK(bias_bf16.dtype() == at::kBFloat16, "bias must be bfloat16"); | |
| TORCH_CHECK(output.dtype() == at::kFloat, "output must be float32"); | |
| TORCH_CHECK(input.dim() == 2, "input must be 2D"); | |
| TORCH_CHECK(weight_bf16.dim() == 2, "weight must be 2D"); | |
| TORCH_CHECK(bias_bf16.dim() == 1, "bias must be 1D"); | |
| TORCH_CHECK(output.dim() == 2, "output must be 2D"); | |
| TORCH_CHECK(input.size(0) == num_tokens && input.size(1) == num_cols, | |
| "input shape must be [num_tokens, num_cols]"); | |
| TORCH_CHECK(weight_bf16.size(0) == num_cols && weight_bf16.size(1) == num_rows, | |
| "weight shape must be [num_cols, num_rows]"); | |
| TORCH_CHECK(bias_bf16.size(0) == num_rows, | |
| "bias length must equal num_rows"); | |
| TORCH_CHECK(output.size(0) == num_tokens && output.size(1) == num_rows, | |
| "output shape must be [num_tokens, num_rows]"); | |
| const bool use_mps = input.device().is_mps() && weight_bf16.device().is_mps() && | |
| bias_bf16.device().is_mps() && output.device().is_mps(); | |
| at::Tensor input_cpu; | |
| at::Tensor weight_cpu; | |
| at::Tensor bias_cpu; | |
| at::Tensor out_cpu; | |
| size_t in_bytes = 0; | |
| size_t weight_bytes = 0; | |
| size_t bias_bytes = 0; | |
| size_t out_bytes = 0; | |
| at::Tensor input_mps; | |
| at::Tensor weight_mps; | |
| at::Tensor bias_mps; | |
| at::Tensor out_mps; | |
| if (use_mps) { | |
| input_mps = input.contiguous(); | |
| weight_mps = weight_bf16.transpose(0, 1).contiguous(); | |
| bias_mps = bias_bf16.contiguous(); | |
| out_mps = output.contiguous(); | |
| } else { | |
| input_cpu = to_cpu_contiguous(input); | |
| weight_cpu = weight_bf16.transpose(0, 1).contiguous().to(at::kCPU); | |
| bias_cpu = to_cpu_contiguous(bias_bf16); | |
| out_cpu = to_cpu_contiguous(output); | |
| in_bytes = static_cast<size_t>(input_cpu.numel()) * input_cpu.element_size(); | |
| weight_bytes = static_cast<size_t>(weight_cpu.numel()) * weight_cpu.element_size(); | |
| bias_bytes = static_cast<size_t>(bias_cpu.numel()) * bias_cpu.element_size(); | |
| out_bytes = static_cast<size_t>(out_cpu.numel()) * out_cpu.element_size(); | |
| } | |
| MetalBuffer input_buf; | |
| MetalBuffer weight_buf; | |
| MetalBuffer bias_buf; | |
| MetalBuffer out_buf; | |
| MetalBuffer control_buf; | |
| run_metal_kernel("gptoss_f32_bf16w_matmul", [&](const gptoss_metal_device& device, | |
| const gptoss_metal_function& fn, | |
| gptoss_metal_command_buffer& cb) { | |
| if (use_mps) { | |
| input_buf.wrap_mps_tensor(&device, input_mps); | |
| weight_buf.wrap_mps_tensor(&device, weight_mps); | |
| bias_buf.wrap_mps_tensor(&device, bias_mps); | |
| out_buf.wrap_mps_tensor(&device, out_mps); | |
| } else { | |
| input_buf.wrap(&device, in_bytes, input_cpu.data_ptr()); | |
| weight_buf.wrap(&device, weight_bytes, weight_cpu.data_ptr()); | |
| bias_buf.wrap(&device, bias_bytes, bias_cpu.data_ptr()); | |
| out_buf.create(&device, out_bytes, nullptr); | |
| std::memcpy(out_buf.ptr(), out_cpu.data_ptr(), out_bytes); | |
| } | |
| create_control_buffer(&device, control_buf); | |
| TORCH_CHECK( | |
| gptoss_metal_command_buffer_encode_launch_f32_bf16w_matmul_add( | |
| &cb, &fn, | |
| static_cast<size_t>(threadgroup_size), | |
| input_buf.get(), 0, | |
| weight_buf.get(), 0, | |
| bias_buf.get(), 0, | |
| out_buf.get(), 0, | |
| control_buf.get(), 0, | |
| static_cast<uint32_t>(num_tokens), | |
| static_cast<uint32_t>(num_cols), | |
| static_cast<uint32_t>(num_rows)) == gptoss_status_success, | |
| "encode matmul_add failed"); | |
| }); | |
| if (use_mps) { | |
| if (!out_mps.is_alias_of(output)) { | |
| output.copy_(out_mps); | |
| } | |
| } else { | |
| std::memcpy(out_cpu.data_ptr(), out_buf.ptr(), out_bytes); | |
| copy_back(output, out_cpu); | |
| } | |
| } |