|
18 | 18 | #include <cstdint> |
19 | 19 | #include <cstring> |
20 | 20 | #include <stdexcept> |
| 21 | +#include <string> |
| 22 | +#include <vector> |
21 | 23 |
|
22 | 24 | namespace executorch::backends::webgpu { |
23 | 25 |
|
@@ -50,6 +52,62 @@ constexpr int64_t kQ4gswShmemTileN = 32; |
50 | 52 | constexpr uint32_t kQ4gswShmemMinDim = 4096u; |
51 | 53 | constexpr uint32_t kQ4gswShmemNMinDim = 2048u; |
52 | 54 |
|
| 55 | +// Workgroup count for a linear_q4gsw dispatch (bicol GEMV / shmem GEMM / tiled |
| 56 | +// GEMM), with the range/limit guards shared by the build-time path and the |
| 57 | +// resize hook. use_gemv/use_shmem_gemm are the build-time routing decision (the |
| 58 | +// shader/pipeline is fixed at build); the resize hook re-runs this with live m. |
| 59 | +uint32_t compute_q4gsw_workgroup_count( |
| 60 | + WGPUDevice device, |
| 61 | + bool use_gemv, |
| 62 | + bool use_shmem_gemm, |
| 63 | + uint32_t m, |
| 64 | + uint32_t n, |
| 65 | + uint32_t wg_size, |
| 66 | + const char* op_name) { |
| 67 | + if (use_gemv) { |
| 68 | + // bicol: fixed 64 lanes, 2 output columns/workgroup, grid-strided over |
| 69 | + // ceil(N/2) column-pairs (M == 1 on this decode path). |
| 70 | + const uint64_t pairs = (static_cast<uint64_t>(n) + 1u) / 2u; |
| 71 | + if (pairs == 0u || pairs > UINT32_MAX) { |
| 72 | + throw std::runtime_error( |
| 73 | + std::string("WebGPU ") + op_name + ": N/2 out of range"); |
| 74 | + } |
| 75 | + const uint32_t wgc = |
| 76 | + utils::clamp_workgroup_count(device, static_cast<uint32_t>(pairs)); |
| 77 | + if (wgc == 0u) { |
| 78 | + throw std::runtime_error( |
| 79 | + std::string("WebGPU ") + op_name + ": zero GEMV dispatch"); |
| 80 | + } |
| 81 | + return wgc; |
| 82 | + } |
| 83 | + if (use_shmem_gemm) { |
| 84 | + // shmem GEMM: one workgroup per tile, no grid-stride -> throw over limit. |
| 85 | + const int64_t total_wgs = utils::div_up<int64_t>(m, kQ4gswShmemTileM) * |
| 86 | + utils::div_up<int64_t>(n, kQ4gswShmemTileN); |
| 87 | + WGPULimits limits = {}; |
| 88 | + const uint32_t max_wgs = |
| 89 | + wgpuDeviceGetLimits(device, &limits) == WGPUStatus_Success && |
| 90 | + limits.maxComputeWorkgroupsPerDimension > 0 |
| 91 | + ? limits.maxComputeWorkgroupsPerDimension |
| 92 | + : 65535u; |
| 93 | + if (total_wgs > static_cast<int64_t>(max_wgs)) { |
| 94 | + throw std::runtime_error( |
| 95 | + std::string("WebGPU ") + op_name + |
| 96 | + ": shmem GEMM tile count exceeds the 1D dispatch limit"); |
| 97 | + } |
| 98 | + return static_cast<uint32_t>(total_wgs); |
| 99 | + } |
| 100 | + const int64_t total_tiles = utils::div_up<int64_t>(m, kQ4gswTileM) * |
| 101 | + utils::div_up<int64_t>(n, kQ4gswTileN); |
| 102 | + if (total_tiles > static_cast<int64_t>(UINT32_MAX)) { |
| 103 | + throw std::runtime_error( |
| 104 | + std::string("WebGPU ") + op_name + |
| 105 | + ": tile count exceeds the 1D dispatch limit"); |
| 106 | + } |
| 107 | + return utils::compute_1d_workgroup_count( |
| 108 | + device, static_cast<uint32_t>(total_tiles), wg_size, op_name); |
| 109 | +} |
| 110 | + |
53 | 111 | // et_vk.linear_q4gsw args: [in, weight, scales, group_size, bias, out]. |
54 | 112 | void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) { |
55 | 113 | const int in_id = args.at(0); |
@@ -137,44 +195,8 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) { |
137 | 195 | const char* shader_src = use_gemv ? kQ4gswLinearCoop4BicolWGSL |
138 | 196 | : use_shmem_gemm ? kQ4gswLinearGemmShmemWGSL |
139 | 197 | : kQ4gswLinearWGSL; |
140 | | - uint32_t workgroup_count; |
141 | | - if (use_gemv) { |
142 | | - // bicol: fixed 64 lanes, 2 output columns/workgroup, grid-strided over |
143 | | - // ceil(N/2) column-pairs (M == 1 on this decode path). |
144 | | - const uint64_t pairs = (static_cast<uint64_t>(N) + 1u) / 2u; |
145 | | - if (pairs == 0u || pairs > UINT32_MAX) { |
146 | | - throw std::runtime_error("WebGPU linear_q4gsw: N/2 out of range"); |
147 | | - } |
148 | | - workgroup_count = |
149 | | - utils::clamp_workgroup_count(device, static_cast<uint32_t>(pairs)); |
150 | | - if (workgroup_count == 0u) { |
151 | | - throw std::runtime_error("WebGPU linear_q4gsw: zero GEMV dispatch"); |
152 | | - } |
153 | | - } else if (use_shmem_gemm) { |
154 | | - // shmem GEMM: one workgroup per tile, no grid-stride -> throw over limit. |
155 | | - const int64_t total_wgs = utils::div_up<int64_t>(M, kQ4gswShmemTileM) * |
156 | | - utils::div_up<int64_t>(N, kQ4gswShmemTileN); |
157 | | - WGPULimits limits = {}; |
158 | | - const uint32_t max_wgs = |
159 | | - wgpuDeviceGetLimits(device, &limits) == WGPUStatus_Success && |
160 | | - limits.maxComputeWorkgroupsPerDimension > 0 |
161 | | - ? limits.maxComputeWorkgroupsPerDimension |
162 | | - : 65535u; |
163 | | - if (total_wgs > static_cast<int64_t>(max_wgs)) { |
164 | | - throw std::runtime_error( |
165 | | - "WebGPU linear_q4gsw: shmem GEMM tile count exceeds the 1D dispatch limit"); |
166 | | - } |
167 | | - workgroup_count = static_cast<uint32_t>(total_wgs); |
168 | | - } else { |
169 | | - const int64_t total_tiles = utils::div_up<int64_t>(M, kQ4gswTileM) * |
170 | | - utils::div_up<int64_t>(N, kQ4gswTileN); |
171 | | - if (total_tiles > static_cast<int64_t>(UINT32_MAX)) { |
172 | | - throw std::runtime_error( |
173 | | - "WebGPU linear_q4gsw: tile count exceeds the 1D dispatch limit"); |
174 | | - } |
175 | | - workgroup_count = utils::compute_1d_workgroup_count( |
176 | | - device, static_cast<uint32_t>(total_tiles), wg_size, "linear_q4gsw"); |
177 | | - } |
| 198 | + const uint32_t workgroup_count = compute_q4gsw_workgroup_count( |
| 199 | + device, use_gemv, use_shmem_gemm, M, N, wg_size, "linear_q4gsw"); |
178 | 200 |
|
179 | 201 | // Optional bias: real buffer if present, else a dummy for the fixed layout. |
180 | 202 | uint32_t has_bias = 0; |
@@ -287,12 +309,78 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) { |
287 | 309 | bg_desc.entries = bg_entries; |
288 | 310 | WGPUBindGroup bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc); |
289 | 311 |
|
290 | | - graph.add_dispatch({pipeline, bind_group, workgroup_count, "linear_q4gsw"}); |
| 312 | + const size_t dispatch_idx = graph.add_dispatch( |
| 313 | + {pipeline, bind_group, workgroup_count, "linear_q4gsw"}); |
| 314 | + |
| 315 | + // Dynamic shapes: recompute dispatch + params.M for the live M. use_gemv and |
| 316 | + // use_shmem_gemm are captured (routing is fixed at build); the helper re-runs |
| 317 | + // the same path's workgroup-count formula with the live m. |
| 318 | + graph.add_tensor_resize_hook( |
| 319 | + in_id, |
| 320 | + [in_id, |
| 321 | + out_id, |
| 322 | + M, |
| 323 | + K, |
| 324 | + N, |
| 325 | + K_packed, |
| 326 | + gs, |
| 327 | + padded_N, |
| 328 | + has_bias, |
| 329 | + wg_size, |
| 330 | + use_gemv, |
| 331 | + use_shmem_gemm, |
| 332 | + dispatch_idx, |
| 333 | + uniform_buffer](WebGPUGraph& g) { |
| 334 | + const auto& d = g.cur_dims(in_id); |
| 335 | + if (d.empty()) { |
| 336 | + throw std::runtime_error( |
| 337 | + "WebGPU linear_q4gsw(resize): empty input dims"); |
| 338 | + } |
| 339 | + const uint64_t numel = utils::numel_of(d); |
| 340 | + if (numel % static_cast<uint64_t>(K) != 0u) { |
| 341 | + throw std::runtime_error( |
| 342 | + "WebGPU linear_q4gsw(resize): live input numel not a multiple " |
| 343 | + "of K"); |
| 344 | + } |
| 345 | + const uint32_t m = |
| 346 | + static_cast<uint32_t>(numel / static_cast<uint64_t>(K)); |
| 347 | + if (m == 0u) { |
| 348 | + throw std::runtime_error("WebGPU linear_q4gsw(resize): live M == 0"); |
| 349 | + } |
| 350 | + // Buffers/bind-groups were sized for the build-time max M; a larger |
| 351 | + // live M would write out of bounds. |
| 352 | + if (m > M) { |
| 353 | + throw std::runtime_error( |
| 354 | + "WebGPU linear_q4gsw(resize): live M exceeds the build-time max"); |
| 355 | + } |
| 356 | + const uint32_t wgc = compute_q4gsw_workgroup_count( |
| 357 | + g.device(), |
| 358 | + use_gemv, |
| 359 | + use_shmem_gemm, |
| 360 | + m, |
| 361 | + N, |
| 362 | + wg_size, |
| 363 | + "linear_q4gsw(resize)"); |
| 364 | + Q4gswParams p = {}; |
| 365 | + p.M = m; |
| 366 | + p.N = N; |
| 367 | + p.K = K; |
| 368 | + p.K_packed = K_packed; |
| 369 | + p.group_size = gs; |
| 370 | + p.padded_N = padded_N; |
| 371 | + p.has_bias = has_bias; |
| 372 | + wgpuQueueWriteBuffer(g.queue(), uniform_buffer, 0, &p, sizeof(p)); |
| 373 | + g.dispatch_at(dispatch_idx).workgroup_count_x = wgc; |
| 374 | + std::vector<int64_t> od(d.begin(), d.end()); |
| 375 | + od.back() = static_cast<int64_t>(N); |
| 376 | + g.set_cur_dims(out_id, od); |
| 377 | + }); |
291 | 378 |
|
292 | 379 | wgpuShaderModuleRelease(shader); |
293 | 380 | wgpuBindGroupLayoutRelease(bgl); |
294 | 381 | wgpuPipelineLayoutRelease(pipeline_layout); |
295 | | - wgpuBufferRelease(uniform_buffer); |
| 382 | + // Graph owns it so the resize hook can rewrite it; freed in the dtor. |
| 383 | + graph.own_uniform_buffer(uniform_buffer); |
296 | 384 | } |
297 | 385 |
|
298 | 386 | } // namespace |
|
0 commit comments