Skip to content

Commit 45cd058

Browse files
committed
[ExecuTorch][WebGPU] Broadcast add.Tensor (mirror mul) + un-skip 6 models
Pull Request resolved: #21233 `add.Tensor` was elementwise-only: `binary_add.wgsl` computed `output[idx] = input1[idx] + alpha * input2[idx]` over a single flat index up to the output numel, so on a broadcast it read the smaller operand out of bounds (WebGPU robustness clamps/zeros -> silently wrong, not `0x30`). This is the failure mode behind the models the WebGPU flow skips (`resnet50`, `vit_b_16`, `swin_v2_t`, `convnext_small`, `mobilenet_v3_small`, `shufflenet_v2_x1_0`) and the `bcast_first`/`bcast_second` op cases. Give `add.Tensor` NumPy broadcasting by mirroring the shipped `mul`: - `binary_add.wgsl` - copy `binary_mul`'s kernel (rank-8 `TensorMeta` layout from the rank-cap fix below it in the stack): keep the identical-shape elementwise fast path (common case stays bit-for-bit `input1[idx] + alpha * input2[idx]`), else relinearize out idx -> per-input coords (clamp size-1 dims) and add. `alpha` is a second pipeline-override constant (read once at build, never rewritten on resize), so no extra UBO. - `add/BinaryOp.cpp` - port `mul_impl`: 3 `TensorMeta` UBOs via `fill_tensor_meta_broadcast`, rank + fp32 guards, 6-entry bind group, `constantCount = 2` ({wg_size, alpha}), 2D dispatch, and `mul`'s resize hook verbatim (rebuild the 3 metas from `cur_dims`, `set_cur_dims`, rewrite UBOs + dispatch); `own_uniform_buffer` the 3 metas. Drops the old flat `AddParams` path. - `flows/webgpu.py` - un-skip the 6 broadcast-add models + the `bcast_first`/`bcast_second` op cases (kept the `float16`/`float64` dtype skips and `hardswish`/`lstm_batch_sizes`/`upsample_nearest2d`). This sits above the rank-cap fix, so the shader uses the rank-8 `array<vec4<u32>, 2>` `TensorMeta` layout. Applied identically to both `xplat/` and `fbcode/` mirrors (byte-identical). Co-authored-with: Claude Code. ghstack-source-id: 406388773 @exported-using-ghexport Differential Revision: [D113319599](https://our.internmc.facebook.com/intern/diff/D113319599/)
1 parent 9e71d0b commit 45cd058

54 files changed

Lines changed: 1570 additions & 3450 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backends/test/suite/flows/webgpu.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,9 @@ def _create_webgpu_flow() -> TestFlow:
1616
skip_patterns=[
1717
"float16",
1818
"float64", # Not supported in swiftshader
19-
# WebGPU add is elementwise-only; broadcasting add.Tensor unsupported.
20-
"bcast_first",
21-
"bcast_second",
2219
"hardswish",
2320
"lstm_batch_sizes",
2421
"upsample_nearest2d",
25-
# torchvision models with broadcasting adds; resnet50 covers wide.
26-
"mobilenet_v3_small",
27-
"shufflenet_v2_x1_0",
28-
"resnet50",
29-
"vit_b_16",
30-
"swin_v2_t",
31-
"convnext_small",
3222
],
3323
)
3424

backends/webgpu/runtime/WebGPUUtils.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,63 @@ inline ComputePipelineBundle make_compute_pipeline(
457457
return bundle;
458458
}
459459

460+
// Builds another pipeline + bind group from resources owned by an earlier
461+
// bundle. The binding indices and types must match the shared bind-group
462+
// layout. This preserves shared-shader/layout multi-pipeline construction
463+
// without transferring ownership of those resources to the returned bundle.
464+
inline ComputePipelineBundle make_compute_pipeline(
465+
WGPUDevice device,
466+
const ComputePipelineBundle& shared_resources,
467+
const std::vector<BindingSpec>& bindings,
468+
const WGPUConstantEntry* constants = nullptr,
469+
size_t constant_count = 0,
470+
const char* entry_point = "main") {
471+
if (shared_resources.shader == nullptr ||
472+
shared_resources.bind_group_layout == nullptr ||
473+
shared_resources.pipeline_layout == nullptr) {
474+
throw std::runtime_error(
475+
"make_compute_pipeline: shared resources are not available");
476+
}
477+
478+
ComputePipelineBundle bundle;
479+
480+
std::vector<WGPUBindGroupEntry> bind_entries(bindings.size());
481+
for (size_t i = 0; i < bindings.size(); i++) {
482+
bind_entries[i] = {};
483+
bind_entries[i].binding = bindings[i].binding;
484+
bind_entries[i].buffer = bindings[i].buffer;
485+
bind_entries[i].size = bindings[i].size;
486+
}
487+
488+
WGPUComputePipelineDescriptor pipeline_desc = {};
489+
pipeline_desc.layout = shared_resources.pipeline_layout;
490+
pipeline_desc.compute.module = shared_resources.shader;
491+
pipeline_desc.compute.entryPoint = {entry_point, WGPU_STRLEN};
492+
pipeline_desc.compute.constantCount = constant_count;
493+
pipeline_desc.compute.constants = constants;
494+
WGPUComputePipeline pipeline =
495+
wgpuDeviceCreateComputePipeline(device, &pipeline_desc);
496+
if (pipeline == nullptr) {
497+
throw std::runtime_error(
498+
"make_compute_pipeline: compute pipeline creation failed");
499+
}
500+
501+
WGPUBindGroupDescriptor bg_desc = {};
502+
bg_desc.layout = shared_resources.bind_group_layout;
503+
bg_desc.entryCount = bind_entries.size();
504+
bg_desc.entries = bind_entries.data();
505+
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc);
506+
if (bind_group == nullptr) {
507+
wgpuComputePipelineRelease(pipeline);
508+
throw std::runtime_error(
509+
"make_compute_pipeline: bind group creation failed");
510+
}
511+
512+
bundle.pipeline = pipeline;
513+
bundle.bind_group = bind_group;
514+
return bundle;
515+
}
516+
460517
// The {wg_size, stride_x} override-constant pair every 2D-spill dispatch
461518
// builds from its DispatchGrid; was hand-rolled identically at 7 call sites.
462519
inline std::array<WGPUConstantEntry, 2> make_grid_constants(

backends/webgpu/runtime/ops/adamw/AdamwStep.cpp

Lines changed: 16 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -97,78 +97,26 @@ void adamw_step_impl(WebGPUGraph& graph, const std::vector<int>& args) {
9797
utils::make_uniform(device, &params, sizeof(params));
9898
graph.add_uniform_buffer_bytes(sizeof(params));
9999

100-
WGPUShaderSourceWGSL wgsl_desc = {};
101-
wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL;
102-
wgsl_desc.code = {kAdamwStepWGSL, WGPU_STRLEN};
103-
WGPUShaderModuleDescriptor shader_desc = {};
104-
shader_desc.nextInChain = &wgsl_desc.chain;
105-
WGPUShaderModule shader = wgpuDeviceCreateShaderModule(device, &shader_desc);
106-
107-
WGPUBindGroupLayoutEntry entries[5] = {};
108-
for (uint32_t i = 0; i <= 2; i++) {
109-
entries[i].binding = i;
110-
entries[i].visibility = WGPUShaderStage_Compute;
111-
entries[i].buffer.type = WGPUBufferBindingType_Storage;
112-
}
113-
entries[3].binding = 3;
114-
entries[3].visibility = WGPUShaderStage_Compute;
115-
entries[3].buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
116-
entries[4].binding = 4;
117-
entries[4].visibility = WGPUShaderStage_Compute;
118-
entries[4].buffer.type = WGPUBufferBindingType_Uniform;
119-
120-
WGPUBindGroupLayoutDescriptor bgl_desc = {};
121-
bgl_desc.entryCount = 5;
122-
bgl_desc.entries = entries;
123-
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(device, &bgl_desc);
124-
125-
WGPUPipelineLayoutDescriptor pl_desc = {};
126-
pl_desc.bindGroupLayoutCount = 1;
127-
pl_desc.bindGroupLayouts = &bgl;
128-
WGPUPipelineLayout pipeline_layout =
129-
wgpuDeviceCreatePipelineLayout(device, &pl_desc);
130-
131100
WGPUConstantEntry wg_size_constant = {};
132101
wg_size_constant.key = {"wg_size", WGPU_STRLEN};
133102
wg_size_constant.value = static_cast<double>(wg_size);
134103

135-
WGPUComputePipelineDescriptor pipeline_desc = {};
136-
pipeline_desc.layout = pipeline_layout;
137-
pipeline_desc.compute.module = shader;
138-
pipeline_desc.compute.entryPoint = {"main", WGPU_STRLEN};
139-
pipeline_desc.compute.constantCount = 1;
140-
pipeline_desc.compute.constants = &wg_size_constant;
141-
WGPUComputePipeline pipeline =
142-
wgpuDeviceCreateComputePipeline(device, &pipeline_desc);
143-
144-
WGPUBindGroupEntry bg_entries[5] = {};
145-
bg_entries[0].binding = 0;
146-
bg_entries[0].buffer = param.buffer;
147-
bg_entries[0].size = param.nbytes;
148-
bg_entries[1].binding = 1;
149-
bg_entries[1].buffer = m.buffer;
150-
bg_entries[1].size = m.nbytes;
151-
bg_entries[2].binding = 2;
152-
bg_entries[2].buffer = v.buffer;
153-
bg_entries[2].size = v.nbytes;
154-
bg_entries[3].binding = 3;
155-
bg_entries[3].buffer = grad.buffer;
156-
bg_entries[3].size = grad.nbytes;
157-
bg_entries[4].binding = 4;
158-
bg_entries[4].buffer = uniform_buffer;
159-
bg_entries[4].size = sizeof(params);
160-
161-
WGPUBindGroupDescriptor bg_desc = {};
162-
bg_desc.layout = bgl;
163-
bg_desc.entryCount = 5;
164-
bg_desc.entries = bg_entries;
165-
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc);
166-
167-
graph.add_dispatch({pipeline, bind_group, workgroup_count, "adamw_step"});
168-
169-
wgpuShaderModuleRelease(shader);
170-
wgpuBindGroupLayoutRelease(bgl);
171-
wgpuPipelineLayoutRelease(pipeline_layout);
104+
utils::ComputePipelineBundle bundle = utils::make_compute_pipeline(
105+
device,
106+
kAdamwStepWGSL,
107+
{
108+
{0, WGPUBufferBindingType_Storage, param.buffer, param.nbytes},
109+
{1, WGPUBufferBindingType_Storage, m.buffer, m.nbytes},
110+
{2, WGPUBufferBindingType_Storage, v.buffer, v.nbytes},
111+
{3, WGPUBufferBindingType_ReadOnlyStorage, grad.buffer, grad.nbytes},
112+
{4, WGPUBufferBindingType_Uniform, uniform_buffer, sizeof(params)},
113+
},
114+
&wg_size_constant,
115+
1);
116+
117+
graph.add_dispatch(
118+
{bundle.pipeline, bundle.bind_group, workgroup_count, "adamw_step"});
119+
172120
graph.own_uniform_buffer(uniform_buffer);
173121
}
174122

0 commit comments

Comments
 (0)