@@ -244,6 +244,11 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) {
244244 const uint32_t wg_size =
245245 utils::clamp_workgroup_size (device, kQ4gswLinearWorkgroupSizeX );
246246 const bool use_gemv = (M == 1u && K % 8u == 0u && gs % 8u == 0u );
247+ // GEMV (bicol) is a pow2 tree reduction; compute its size only when used.
248+ const uint32_t gemv_wg_size = use_gemv
249+ ? utils::clamp_workgroup_size_pow2 (
250+ device, kQ4gswLinearCoop4BicolWorkgroupSizeX )
251+ : 0u ;
247252 // steel (256-thread) is the preferred M>1 prefill GEMM; 0 count = ineligible.
248253 const bool use_steel = !use_gemv && steel_supported (device) &&
249254 steel_workgroup_count (device, M, N, K) > 0u ;
@@ -362,16 +367,17 @@ void q4gsw_linear_impl(WebGPUGraph& graph, const std::vector<int>& args) {
362367 WGPUPipelineLayout pipeline_layout =
363368 wgpuDeviceCreatePipelineLayout (device, &pl_desc);
364369
370+ // GEMV/tiled wire an override wg_size; steel (256) + shmem (64) are fixed.
371+ const bool fixed_wg = use_steel || use_shmem_gemm;
365372 WGPUConstantEntry wg_size_constant = {};
366373 wg_size_constant.key = {" wg_size" , WGPU_STRLEN };
367- wg_size_constant.value = static_cast <double >(wg_size);
374+ wg_size_constant.value =
375+ static_cast <double >(use_gemv ? gemv_wg_size : wg_size);
368376
369377 WGPUComputePipelineDescriptor pipeline_desc = {};
370378 pipeline_desc.layout = pipeline_layout;
371379 pipeline_desc.compute .module = shader;
372380 pipeline_desc.compute .entryPoint = {" main" , WGPU_STRLEN };
373- // Only tiled GEMM overrides wg_size; GEMV/shmem (64) + steel (256) are fixed.
374- const bool fixed_wg = use_gemv || use_steel || use_shmem_gemm;
375381 pipeline_desc.compute .constantCount = fixed_wg ? 0u : 1u ;
376382 pipeline_desc.compute .constants = fixed_wg ? nullptr : &wg_size_constant;
377383 WGPUComputePipeline pipeline =
0 commit comments