Repository: microsoft/onnxruntime
File: onnxruntime/core/providers/webgpu/nn/conv.cc (C++)

Code before:
namespace webgpu {

Status TransposeKernel(ComputeContext& context, const Tensor* kernel, const TensorShape& kernel_shape, Tensor* transposed_kernel, const InlinedVector<size_t>& perm) {
  // Transpose weights
  auto rank = kernel_shape.NumDimensions();
  TensorShapeVector transposed_kernel_shape_vector(rank);
  for (size_t i = 0; i < rank; ++i) {
    transposed_kernel_shape_vector[i] = kernel_shape[perm[i]];
  }
  uint32_t output_size = onnxruntime::narrow<uint32_t>(kernel_shape.Size());

  uint32_t dispatch_x = ceil_div(output_size, 64);
  uint32_t dispatch_y = 1;
  uint32_t dispatch_z = 1;

  // This temporary workaround addresses a significant performance bottleneck
  // (10x slower) for the shape (3, 3, 2560, 1280) due to an issue with Intel's
  // GPU drivers. We manually normalize the dispatch group size to restore
  // performance.
  //
  // TODO: Revert this change once the driver issue is fixed.
  if (context.AdapterInfo().vendor == std::string_view{"intel"}) {
    ORT_ENFORCE(rank == static_cast<size_t>(4), "Input tensor must have rank 4.");
    dispatch_x = ceil_div(transposed_kernel_shape_vector[0] * transposed_kernel_shape_vector[1], 2);
    dispatch_y = ceil_div(transposed_kernel_shape_vector[2], 4);
    dispatch_z = ceil_div(transposed_kernel_shape_vector[3], 8);
  }

  TensorShape transposed_kernel_shape(transposed_kernel_shape_vector);
  *transposed_kernel = context.CreateGPUTensor(kernel->DataType(), transposed_kernel_shape);
  bool use_shared = false;
  TransposeProgram program{perm, use_shared};
  program
      .CacheHint(absl::StrJoin(perm, "-"))
      .AddInput({kernel, ProgramTensorMetadataDependency::TypeAndRank, kernel_shape, 1})
      .AddOutput({transposed_kernel, ProgramTensorMetadataDependency::TypeAndRank})
      .AddUniformVariable({output_size})
      .SetWorkgroupSize(64)
      .SetDispatchGroupSize(dispatch_x, dispatch_y, dispatch_z);
  return context.RunProgram(program);
}

template <bool is_channels_last, bool is_fused>
Status Conv<is_channels_last, is_fused>::ComputeInternal(ComputeContext& context) const {
  bool has_bias = context.InputCount() > 2;
  const auto* input = context.Input<Tensor>(0);
  const auto* kernel = context.Input<Tensor>(1);
  const auto* bias = has_bias ? context.Input<Tensor>(2) : nullptr;
  TensorShape input_shape = input->Shape();
  TensorShape kernel_shape = kernel->Shape();
  ConvAttributes::ConvPadVector local_pads(conv_attrs_.pads.begin(), conv_attrs_.pads.end());

Review comment (performance):
I am wondering what kind of pattern of the dispatched workgroups will trigger the driver slow path. Does a dispatch group size of (679, 679, 1) resulting in extremely slow performance on LNL apply for any compute shader's dispatch or only for transpose? If it's not transpose specific, should we put the workaround in a common place for intel device? 

Code after:
namespace webgpu {

Status TransposeKernel(ComputeContext& context, const Tensor* kernel, const TensorShape& kernel_shape, Tensor* transposed_kernel, const InlinedVector<size_t>& perm) {
  // Transpose weights
  auto rank = kernel_shape.NumDimensions();
  TensorShapeVector transposed_kernel_shape_vector(rank);
  for (size_t i = 0; i < rank; ++i) {
    transposed_kernel_shape_vector[i] = kernel_shape[perm[i]];
  }
  uint32_t output_size = onnxruntime::narrow<uint32_t>(kernel_shape.Size());

  uint32_t dispatch_x = ceil_div(output_size, 64);
  uint32_t dispatch_y = 1;
  uint32_t dispatch_z = 1;

  // This temporary workaround addresses a significant performance bottleneck
  // (~12x slower) for the shape (3, 3, 2560, 1280) due to an issue with Intel's
  // GPU drivers. We manually normalize the dispatch group size to restore
  // performance.
  //
  // TODO: Revert this change once the driver issue is fixed.
  if (context.AdapterInfo().vendor == std::string_view{"intel"}) {
    ORT_ENFORCE(rank == static_cast<size_t>(4), "Input tensor must have rank 4.");
    dispatch_x = ceil_div(transposed_kernel_shape_vector[0] * transposed_kernel_shape_vector[1], 2);
    dispatch_y = ceil_div(transposed_kernel_shape_vector[2], 4);
    dispatch_z = ceil_div(transposed_kernel_shape_vector[3], 8);
  }

  TensorShape transposed_kernel_shape(transposed_kernel_shape_vector);
  *transposed_kernel = context.CreateGPUTensor(kernel->DataType(), transposed_kernel_shape);
  bool use_shared = false;
  TransposeProgram program{perm, use_shared};
  program
      .CacheHint(absl::StrJoin(perm, "-"))
      .AddInput({kernel, ProgramTensorMetadataDependency::TypeAndRank, kernel_shape, 1})
      .AddOutput({transposed_kernel, ProgramTensorMetadataDependency::TypeAndRank})
      .AddUniformVariable({output_size})
      .SetWorkgroupSize(64)
      .SetDispatchGroupSize(dispatch_x, dispatch_y, dispatch_z);
  return context.RunProgram(program);
}

template <bool is_channels_last, bool is_fused>
Status Conv<is_channels_last, is_fused>::ComputeInternal(ComputeContext& context) const {
  bool has_bias = context.InputCount() > 2;
  const auto* input = context.Input<Tensor>(0);
  const auto* kernel = context.Input<Tensor>(1);
  const auto* bias = has_bias ? context.Input<Tensor>(2) : nullptr;
  TensorShape input_shape = input->Shape();
  TensorShape kernel_shape = kernel->Shape();
  ConvAttributes::ConvPadVector local_pads(conv_attrs_.pads.begin(), conv_attrs_.pads.end());