99#include < executorch/backends/webgpu/runtime/WebGPUGraph.h>
1010#include < executorch/backends/webgpu/runtime/WebGPUUtils.h>
1111#include < executorch/backends/webgpu/runtime/ops/OperatorRegistry.h>
12+ #include < executorch/backends/webgpu/runtime/ops/quantized_linear/q4gsw_linear_coop4_wgsl.h>
1213#include < executorch/backends/webgpu/runtime/ops/quantized_linear/q4gsw_linear_wgsl.h>
1314
1415#include < webgpu/webgpu.h>
@@ -34,6 +35,10 @@ struct Q4gswParams {
3435};
3536static_assert (sizeof (Q4gswParams) == 32 , " Q4gswParams must be 32 bytes" );
3637
38+ // Register-tile dims; MUST match TM/TN in q4gsw_linear.wgsl.
39+ constexpr int64_t kQ4gswTileM = 4 ;
40+ constexpr int64_t kQ4gswTileN = 4 ;
41+
3742// et_vk.linear_q4gsw args: [in, weight, scales, group_size, bias, out].
3843void q4gsw_linear_impl (WebGPUGraph& graph, const std::vector<int >& args) {
3944 const int in_id = args.at (0 );
@@ -85,10 +90,6 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) {
8590 " WebGPU linear_q4gsw: N*K_packed must be a multiple of 4 (u32-packed)" );
8691 }
8792
88- // One workgroup per output row (M); validate dispatch before any alloc.
89- const uint32_t workgroup_count =
90- utils::compute_1d_workgroup_count (device, M, 1 , " linear_q4gsw" );
91-
9293 // fp32-only byte-size guards (no runtime dtype); fp16 scales -> bail.
9394 const uint64_t scales_numel =
9495 static_cast <uint64_t >(num_groups) * static_cast <uint64_t >(padded_N);
@@ -116,6 +117,35 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) {
116117 " WebGPU linear_q4gsw: scales dims too small for K/N" );
117118 }
118119
120+ // M==1 decode -> coop4 GEMV (needs K%8==0 && gs%8==0); else tiled GEMM.
121+ const uint32_t wg_size =
122+ utils::clamp_workgroup_size (device, kQ4gswLinearWorkgroupSizeX );
123+ const bool use_gemv = (M == 1u && K % 8u == 0u && gs % 8u == 0u );
124+ const char * shader_src = use_gemv ? kQ4gswLinearCoop4WGSL : kQ4gswLinearWGSL ;
125+ uint32_t workgroup_count;
126+ if (use_gemv) {
127+ // coop4: fixed 64 lanes, 1 workgroup per output, grid-strided over M*N.
128+ const uint64_t outputs =
129+ static_cast <uint64_t >(M) * static_cast <uint64_t >(N);
130+ if (outputs == 0u || outputs > UINT32_MAX ) {
131+ throw std::runtime_error (" WebGPU linear_q4gsw: M*N out of range" );
132+ }
133+ workgroup_count =
134+ utils::clamp_workgroup_count (device, static_cast <uint32_t >(outputs));
135+ if (workgroup_count == 0u ) {
136+ throw std::runtime_error (" WebGPU linear_q4gsw: zero GEMV dispatch" );
137+ }
138+ } else {
139+ const int64_t total_tiles = utils::div_up<int64_t >(M, kQ4gswTileM ) *
140+ utils::div_up<int64_t >(N, kQ4gswTileN );
141+ if (total_tiles > static_cast <int64_t >(UINT32_MAX )) {
142+ throw std::runtime_error (
143+ " WebGPU linear_q4gsw: tile count exceeds the 1D dispatch limit" );
144+ }
145+ workgroup_count = utils::compute_1d_workgroup_count (
146+ device, static_cast <uint32_t >(total_tiles), wg_size, " linear_q4gsw" );
147+ }
148+
119149 // Optional bias: real buffer if present, else a dummy for the fixed layout.
120150 uint32_t has_bias = 0 ;
121151 WGPUBuffer bias_buffer = nullptr ;
@@ -156,7 +186,7 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) {
156186
157187 WGPUShaderSourceWGSL wgsl_desc = {};
158188 wgsl_desc.chain .sType = WGPUSType_ShaderSourceWGSL;
159- wgsl_desc.code = {kQ4gswLinearWGSL , WGPU_STRLEN };
189+ wgsl_desc.code = {shader_src , WGPU_STRLEN };
160190 WGPUShaderModuleDescriptor shader_desc = {};
161191 shader_desc.nextInChain = &wgsl_desc.chain ;
162192 WGPUShaderModule shader = wgpuDeviceCreateShaderModule (device, &shader_desc);
@@ -186,8 +216,6 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) {
186216 WGPUPipelineLayout pipeline_layout =
187217 wgpuDeviceCreatePipelineLayout (device, &pl_desc);
188218
189- const uint32_t wg_size =
190- utils::clamp_workgroup_size (device, kQ4gswLinearWorkgroupSizeX );
191219 WGPUConstantEntry wg_size_constant = {};
192220 wg_size_constant.key = {" wg_size" , WGPU_STRLEN };
193221 wg_size_constant.value = static_cast <double >(wg_size);
@@ -196,8 +224,9 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) {
196224 pipeline_desc.layout = pipeline_layout;
197225 pipeline_desc.compute .module = shader;
198226 pipeline_desc.compute .entryPoint = {" main" , WGPU_STRLEN };
199- pipeline_desc.compute .constantCount = 1 ;
200- pipeline_desc.compute .constants = &wg_size_constant;
227+ // coop4 GEMV uses fixed @workgroup_size(64); only the GEMM has an override.
228+ pipeline_desc.compute .constantCount = use_gemv ? 0u : 1u ;
229+ pipeline_desc.compute .constants = use_gemv ? nullptr : &wg_size_constant;
201230 WGPUComputePipeline pipeline =
202231 wgpuDeviceCreateComputePipeline (device, &pipeline_desc);
203232
0 commit comments