Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions backends/vulkan/op_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -1746,6 +1746,34 @@ def register_rms_norm():
)


@update_features(
[
exir_ops.edge.aten.ne.Scalar,
exir_ops.edge.aten.lt.Scalar,
exir_ops.edge.aten.le.Scalar,
exir_ops.edge.aten.ge.Scalar,
]
)
def register_compare_scalar_ops():
return OpFeatures(
inputs_storage=utils.ANY_STORAGE,
inputs_dtypes=utils.FP_INT_T,
outputs_dtypes=utils.BOOL_T,
supports_resize=True,
supports_highdim=True,
)


@update_features(exir_ops.edge.aten.logical_not.default)
def register_logical_not():
return OpFeatures(
inputs_storage=utils.ANY_STORAGE,
inputs_dtypes=utils.BOOL_T,
supports_resize=True,
supports_highdim=True,
)


#######################
## Utility functions ##
#######################
Expand Down
3 changes: 3 additions & 0 deletions backends/webgpu/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ set(WEBGPU_SRCS
runtime/ops/reduce/Reduce.cpp
runtime/ops/div/BinaryOp.cpp
runtime/ops/sub/BinaryOp.cpp
runtime/ops/where/Where.cpp
runtime/ops/compare/Compare.cpp
runtime/ops/linear/Linear.cpp
runtime/ops/logical_not/LogicalNot.cpp
)

add_library(webgpu_backend ${WEBGPU_SRCS})
Expand Down
192 changes: 192 additions & 0 deletions backends/webgpu/runtime/ops/compare/Compare.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#include <executorch/backends/webgpu/runtime/WebGPUGraph.h>
#include <executorch/backends/webgpu/runtime/WebGPUUtils.h>
#include <executorch/backends/webgpu/runtime/ops/OperatorRegistry.h>
#include <executorch/backends/webgpu/runtime/ops/compare/compare_wgsl.h>

#include <webgpu/webgpu.h>

#include <cstdint>
#include <stdexcept>
#include <string>
#include <vector>

namespace executorch::backends::webgpu {

namespace {

struct CompareParams {
uint32_t num_elements;
uint32_t mode;
float scalar;
uint32_t _pad;
};

float read_scalar(WebGPUGraph& graph, int id, const char* op_name) {
if (graph.get_value_type(id) == WebGPUGraph::ValueType::Double) {
return static_cast<float>(graph.get_double(id));
}
if (graph.get_value_type(id) == WebGPUGraph::ValueType::Int) {
return static_cast<float>(graph.get_int(id));
}
throw std::runtime_error(std::string(op_name) + ": scalar is not int/double");
}

// cmp(self[i], scalar) -> byte-packed bool; one u32 word packs 4 elems.
void compare_impl(
WebGPUGraph& graph,
const std::vector<int>& args,
uint32_t mode,
const char* op_name) {
const int self_id = args.at(0);
const int out_id = args.at(args.size() - 1);
const float scalar = read_scalar(graph, args.at(1), op_name);

WGPUDevice device = graph.device();
const auto& self_tensor = graph.get_tensor(self_id);
const auto& out_tensor = graph.get_tensor(out_id);

if (self_tensor.buffer == nullptr || out_tensor.buffer == nullptr) {
throw std::runtime_error(std::string(op_name) + ": null buffer binding");
}
if (self_tensor.nbytes % sizeof(float) != 0) {
throw std::runtime_error(std::string(op_name) + ": self is not fp32");
}
const uint32_t numel =
static_cast<uint32_t>(self_tensor.nbytes / sizeof(float));
if (out_tensor.nbytes != static_cast<size_t>(numel)) {
throw std::runtime_error(
std::string(op_name) + ": out is not a 1-byte (bool) tensor");
}

const size_t out_bind_size = (out_tensor.nbytes + 3) & ~size_t(3);
const uint32_t n_words = (numel + 3u) / 4u;

uint32_t wg_size =
utils::clamp_workgroup_size(device, kCompareWorkgroupSizeX);
uint32_t workgroup_count =
utils::compute_1d_workgroup_count(device, n_words, wg_size, op_name);

WGPUConstantEntry wg_size_constant = {};
wg_size_constant.key = {"wg_size", WGPU_STRLEN};
wg_size_constant.value = static_cast<double>(wg_size);

CompareParams params = {numel, mode, scalar, 0u};
WGPUBuffer params_buf =
utils::make_uniform(device, &params, sizeof(CompareParams));
graph.add_uniform_buffer_bytes(sizeof(CompareParams));

WGPUShaderSourceWGSL wgsl_desc = {};
wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
wgsl_desc.code = {kCompareWGSL, WGPU_STRLEN};
WGPUShaderModuleDescriptor shader_desc = {};
shader_desc.nextInChain = &wgsl_desc.chain;
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &shader_desc);

WGPUBindGroupLayoutEntry entries[3] = {};
entries[0].buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
entries[1].buffer.type = WGPUBufferBindingType_Storage;
entries[2].buffer.type = WGPUBufferBindingType_Uniform;
for (uint32_t i = 0; i < 3; i++) {
entries[i].binding = i;
entries[i].visibility = WGPUShaderStage_Compute;
}

WGPUBindGroupLayoutDescriptor bgl_desc = {};
bgl_desc.entryCount = 3;
bgl_desc.entries = entries;
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(device, &bgl_desc);

WGPUPipelineLayoutDescriptor pl_desc = {};
pl_desc.bindGroupLayoutCount = 1;
pl_desc.bindGroupLayouts = &bgl;
WGPUPipelineLayout pipeline_layout =
wgpuDeviceCreatePipelineLayout(device, &pl_desc);

WGPUComputePipelineDescriptor pipeline_desc = {};
pipeline_desc.layout = pipeline_layout;
pipeline_desc.compute.module = shader;
pipeline_desc.compute.entryPoint = {"main", WGPU_STRLEN};
pipeline_desc.compute.constantCount = 1;
pipeline_desc.compute.constants = &wg_size_constant;
WGPUComputePipeline pipeline =
wgpuDeviceCreateComputePipeline(device, &pipeline_desc);

WGPUBindGroupEntry bg_entries[3] = {};
bg_entries[0].binding = 0;
bg_entries[0].buffer = self_tensor.buffer;
bg_entries[0].size = self_tensor.nbytes;
bg_entries[1].binding = 1;
bg_entries[1].buffer = out_tensor.buffer;
bg_entries[1].size = out_bind_size;
bg_entries[2].binding = 2;
bg_entries[2].buffer = params_buf;
bg_entries[2].size = sizeof(CompareParams);

WGPUBindGroupDescriptor bg_desc = {};
bg_desc.layout = bgl;
bg_desc.entryCount = 3;
bg_desc.entries = bg_entries;
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc);

const size_t dispatch_idx =
graph.add_dispatch({pipeline, bind_group, workgroup_count});

WGPUBuffer p_buf = params_buf;
auto cmp_resize =
[self_id, out_id, mode, scalar, wg_size, dispatch_idx, p_buf, op_name](
WebGPUGraph& g) {
const auto& d = g.cur_dims(self_id);
uint32_t n = 1u;
for (auto x : d) {
n *= static_cast<uint32_t>(x);
}
g.set_cur_dims(out_id, d);
CompareParams p = {n, mode, scalar, 0u};
wgpuQueueWriteBuffer(g.queue(), p_buf, 0, &p, sizeof(p));
const uint32_t nw = (n + 3u) / 4u;
g.dispatch_at(dispatch_idx).workgroup_count_x =
utils::compute_1d_workgroup_count(g.device(), nw, wg_size, op_name);
};
graph.add_tensor_resize_hook(self_id, cmp_resize);

wgpuShaderModuleRelease(shader);
wgpuBindGroupLayoutRelease(bgl);
wgpuPipelineLayoutRelease(pipeline_layout);
graph.own_uniform_buffer(params_buf);
}

void eq_scalar_impl(WebGPUGraph& graph, const std::vector<int>& args) {
compare_impl(graph, args, 0u, "eq.Scalar");
}
void ne_scalar_impl(WebGPUGraph& graph, const std::vector<int>& args) {
compare_impl(graph, args, 1u, "ne.Scalar");
}
void le_scalar_impl(WebGPUGraph& graph, const std::vector<int>& args) {
compare_impl(graph, args, 2u, "le.Scalar");
}
void ge_scalar_impl(WebGPUGraph& graph, const std::vector<int>& args) {
compare_impl(graph, args, 3u, "ge.Scalar");
}
void lt_scalar_impl(WebGPUGraph& graph, const std::vector<int>& args) {
compare_impl(graph, args, 4u, "lt.Scalar");
}

} // namespace

WEBGPU_REGISTER_OPERATORS {
WEBGPU_REGISTER_OP(aten.eq.Scalar, eq_scalar_impl);
WEBGPU_REGISTER_OP(aten.ne.Scalar, ne_scalar_impl);
WEBGPU_REGISTER_OP(aten.le.Scalar, le_scalar_impl);
WEBGPU_REGISTER_OP(aten.ge.Scalar, ge_scalar_impl);
WEBGPU_REGISTER_OP(aten.lt.Scalar, lt_scalar_impl);
}

} // namespace executorch::backends::webgpu
45 changes: 45 additions & 0 deletions backends/webgpu/runtime/ops/compare/compare.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
@group(0) @binding(0) var<storage, read> input: array<f32>;
@group(0) @binding(1) var<storage, read_write> output: array<u32>;

struct Params {
num_elements: u32,
mode: u32,
scalar: f32,
_pad: u32,
}
@group(0) @binding(2) var<uniform> params: Params;

override wg_size: u32 = 64u;

// One thread per output u32 word packs 4 bool bytes -> no inter-thread race.
@compute @workgroup_size(wg_size, 1, 1)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let word_idx = gid.x;
let n_words = (params.num_elements + 3u) / 4u;
if (word_idx >= n_words) {
return;
}
var packed: u32 = 0u;
for (var j: u32 = 0u; j < 4u; j = j + 1u) {
let i = word_idx * 4u + j;
if (i < params.num_elements) {
let v = input[i];
var r: bool;
if (params.mode == 0u) {
r = v == params.scalar;
} else if (params.mode == 1u) {
r = v != params.scalar;
} else if (params.mode == 2u) {
r = v <= params.scalar;
} else if (params.mode == 3u) {
r = v >= params.scalar;
} else {
r = v < params.scalar;
}
if (r) {
packed = packed | (1u << (j * 8u));
}
}
}
output[word_idx] = packed;
}
69 changes: 69 additions & 0 deletions backends/webgpu/runtime/ops/compare/compare_wgsl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <cstdint>

namespace executorch::backends::webgpu {

// @generated from compare.wgsl - DO NOT EDIT.
// wgsl-sha256: f13da085195696aa6975cae62b4d0b2f5837fc02584d95b0a46fd06dc418c4a4
inline constexpr const char* kCompareWGSL = R"(
@group(0) @binding(0) var<storage, read> input: array<f32>;
@group(0) @binding(1) var<storage, read_write> output: array<u32>;

struct Params {
num_elements: u32,
mode: u32,
scalar: f32,
_pad: u32,
}
@group(0) @binding(2) var<uniform> params: Params;

override wg_size: u32 = 64u;

// One thread per output u32 word packs 4 bool bytes -> no inter-thread race.
@compute @workgroup_size(wg_size, 1, 1)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let word_idx = gid.x;
let n_words = (params.num_elements + 3u) / 4u;
if (word_idx >= n_words) {
return;
}
var packed: u32 = 0u;
for (var j: u32 = 0u; j < 4u; j = j + 1u) {
let i = word_idx * 4u + j;
if (i < params.num_elements) {
let v = input[i];
var r: bool;
if (params.mode == 0u) {
r = v == params.scalar;
} else if (params.mode == 1u) {
r = v != params.scalar;
} else if (params.mode == 2u) {
r = v <= params.scalar;
} else if (params.mode == 3u) {
r = v >= params.scalar;
} else {
r = v < params.scalar;
}
if (r) {
packed = packed | (1u << (j * 8u));
}
}
}
output[word_idx] = packed;
}
)";

inline constexpr uint32_t kCompareWorkgroupSizeX = 64;
inline constexpr uint32_t kCompareWorkgroupSizeY = 1;
inline constexpr uint32_t kCompareWorkgroupSizeZ = 1;

} // namespace executorch::backends::webgpu
Loading
Loading