Skip to content

Commit d23e03e

Browse files
committed
[ExecuTorch][WebGPU] Dynamic resize hooks for sigmoid and select_copy
Pull Request resolved: #20578 **Make sigmoid and select_copy serve any live shape from one graph; fix select's last-token index under dynamic shapes.** **Problem:** Both ops baked their dispatch/params/output shape at `build()` for the max shape. `select_copy` was worse: a negative index (e.g. `-1` for the last token) was normalized against the build-time MAX dim, so at a smaller live S it selected a stale/zero position past the live data — producing wrong (often zero) output. **Solution:** - `sigmoid` (generic `add_unary_op`): a resize hook recomputes `num_elements`/dispatch and sets the output `cur_dims` (shape-preserving). - `select_copy`: KEEP the raw (possibly negative) index at build; a resize hook re-resolves it against the LIVE dim, recomputes the output dims (= input minus `dim`), rebuilds the out/in `TensorMeta` UBOs and the dispatch. - Both keep their uniform buffer(s) alive via `own_uniform_buffer`. **Implementation:** - The select out/in meta is rebuilt from synthetic `WebGPUTensor{dims}` via `fill_tensor_meta` (reads only `.dims`). - Mirrors Vulkan per-op `resize_*_node`. **Constraints:** Behavior-neutral on static graphs (hooks fire only when an input's live shape differs from the max). No kernel/WGSL/numerics change. Co-authored-with: Claude Code. ghstack-source-id: 399812832 @exported-using-ghexport Differential Revision: [D109906095](https://our.internmc.facebook.com/intern/diff/D109906095/)
1 parent 7b3f90d commit d23e03e

2 files changed

Lines changed: 85 additions & 12 deletions

File tree

backends/webgpu/runtime/ops/select/Select.cpp

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ int64_t read_scalar(WebGPUGraph& graph, int id, const char* what) {
3737
throw std::runtime_error(std::string("select: dynamic/unsupported ") + what);
3838
}
3939

40+
// Build a TensorMeta from live dims, write it to buf, return numel.
41+
uint32_t write_meta_from_dims(
42+
WebGPUGraph& g,
43+
WGPUBuffer buf,
44+
const std::vector<int64_t>& dims) {
45+
WebGPUTensor t;
46+
t.dims = dims;
47+
TensorMeta m;
48+
fill_tensor_meta(t, &m);
49+
wgpuQueueWriteBuffer(g.queue(), buf, 0, &m, sizeof(m));
50+
return m.numel;
51+
}
52+
4053
void select_impl(WebGPUGraph& graph, const std::vector<int>& args) {
4154
// args: [self, dim, index, out]; output rank = in rank - 1.
4255
const int in_id = args.at(0);
@@ -58,10 +71,9 @@ void select_impl(WebGPUGraph& graph, const std::vector<int>& args) {
5871
throw std::runtime_error("select: dim out of range");
5972
}
6073
const int64_t in_size = in_tensor.dims[dim];
61-
int64_t index = read_scalar(graph, args.at(2), "index");
62-
if (index < 0) {
63-
index += in_size;
64-
}
74+
// Keep the RAW index: -1 normalizes against the LIVE dim (the resize hook).
75+
const int64_t raw_index = read_scalar(graph, args.at(2), "index");
76+
int64_t index = raw_index < 0 ? raw_index + in_size : raw_index;
6577
if (index < 0 || index >= in_size) {
6678
throw std::runtime_error("select: index out of range");
6779
}
@@ -164,15 +176,56 @@ void select_impl(WebGPUGraph& graph, const std::vector<int>& args) {
164176
bg_desc.entries = bg_entries;
165177
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc);
166178

167-
graph.add_dispatch({pipeline, bind_group, workgroup_count});
179+
const size_t dispatch_idx =
180+
graph.add_dispatch({pipeline, bind_group, workgroup_count});
181+
182+
// Dynamic shapes: out = in minus `dim`; re-resolve index, meta, dispatch.
183+
graph.add_tensor_resize_hook(
184+
in_id,
185+
[in_id,
186+
out_id,
187+
dim,
188+
raw_index,
189+
out_meta_buf,
190+
in_meta_buf,
191+
params_buf,
192+
wg_size,
193+
dispatch_idx](WebGPUGraph& g) {
194+
const auto& ind = g.cur_dims(in_id);
195+
if (dim < 0 || dim >= static_cast<int>(ind.size())) {
196+
throw std::runtime_error("select(resize): dim out of range");
197+
}
198+
const int64_t live_in_size = ind[dim];
199+
int64_t idx = raw_index < 0 ? raw_index + live_in_size : raw_index;
200+
if (idx < 0 || idx >= live_in_size) {
201+
throw std::runtime_error("select(resize): index out of range");
202+
}
203+
std::vector<int64_t> od;
204+
od.reserve(ind.size() - 1);
205+
for (size_t k = 0; k < ind.size(); k++) {
206+
if (static_cast<int>(k) != dim) {
207+
od.push_back(ind[k]);
208+
}
209+
}
210+
g.set_cur_dims(out_id, od);
211+
const uint32_t out_numel = write_meta_from_dims(g, out_meta_buf, od);
212+
write_meta_from_dims(g, in_meta_buf, ind);
213+
SelectParams p = {};
214+
p.dim = static_cast<uint32_t>(dim);
215+
p.index = static_cast<uint32_t>(idx);
216+
wgpuQueueWriteBuffer(g.queue(), params_buf, 0, &p, sizeof(p));
217+
g.dispatch_at(dispatch_idx).workgroup_count_x =
218+
utils::compute_1d_workgroup_count(
219+
g.device(), out_numel, wg_size, "select(resize)");
220+
});
168221

169222
wgpuShaderModuleRelease(shader);
170223
wgpuBindGroupLayoutRelease(bgl);
171224
wgpuPipelineLayoutRelease(pipeline_layout);
172-
// Drop our refs; the bind group keeps the uniforms alive until release.
173-
wgpuBufferRelease(out_meta_buf);
174-
wgpuBufferRelease(in_meta_buf);
175-
wgpuBufferRelease(params_buf);
225+
// Graph owns them so the resize hook can rewrite them; freed in the dtor.
226+
graph.own_uniform_buffer(out_meta_buf);
227+
graph.own_uniform_buffer(in_meta_buf);
228+
graph.own_uniform_buffer(params_buf);
176229
}
177230

178231
} // namespace

backends/webgpu/runtime/ops/sigmoid/UnaryOp.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,34 @@ void add_unary_op(
135135
bg_desc.entries = bg_entries;
136136
WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc);
137137

138-
graph.add_dispatch({pipeline, bind_group, workgroup_count});
138+
const size_t dispatch_idx =
139+
graph.add_dispatch({pipeline, bind_group, workgroup_count});
140+
141+
// Dynamic shapes: recompute num_elements/dispatch for the live shape.
142+
WGPUBuffer params_buf = uniform_buffer;
143+
graph.add_tensor_resize_hook(
144+
in_id,
145+
[in_id, out_id, wg_size, dispatch_idx, params_buf](WebGPUGraph& g) {
146+
const auto& d = g.cur_dims(in_id);
147+
const uint64_t numel = utils::numel_of(d);
148+
g.set_cur_dims(out_id, d);
149+
UnaryParams p = {};
150+
p.num_elements = static_cast<uint32_t>(numel);
151+
wgpuQueueWriteBuffer(g.queue(), params_buf, 0, &p, sizeof(p));
152+
g.dispatch_at(dispatch_idx).workgroup_count_x =
153+
utils::compute_1d_workgroup_count(
154+
g.device(),
155+
static_cast<uint32_t>(numel),
156+
wg_size,
157+
"unary(resize)");
158+
});
139159

140160
// Release intermediates (pipeline + bind_group are kept by dispatch).
141161
wgpuShaderModuleRelease(shader);
142162
wgpuBindGroupLayoutRelease(bgl);
143163
wgpuPipelineLayoutRelease(pipeline_layout);
144-
// Drop our ref; the bind group keeps the uniform buffer alive until release.
145-
wgpuBufferRelease(uniform_buffer);
164+
// Graph owns it so the resize hook can rewrite it; freed in the dtor.
165+
graph.own_uniform_buffer(uniform_buffer);
146166
}
147167

148168
void sigmoid_impl(WebGPUGraph& graph, const std::vector<int>& args) {

0 commit comments

Comments
 (0)