ggml-webgpu(shader): support conv2d kernels. (#21964)
* ggml(webgpu): fix the busy-polls in Emscripten in the waitAny after #20618, and remove the busy webgpu log * Merge with upstream * Fix GET_ROWS packed integer NaN when using f16 as memory buffer in shader quants * Update Unary wgsl EXP and EXPM1 for f16 stability * Fix GET_ROWS IQ4_XS strcut for NaN f16 canonicalization * Fix numerical percision for unary sqrt when working with f16 * Fix NaN canonicalization for packed integers using f16 * Update err threshold for binary div ops when using f16 * backend: Keep one Dawn/WebGPU instance alive for the lifetime of the static backend * clean: uncomment existing code logs * clean: clean the unncessary debug info * Refactor and generalize dequant helpers * Remove deprecated quant structs * Refactor shader defines to reduce repetition * Remove error override for F16 type * fix: fix the accidential removal of the proper initialization of ctx * clean: clean legacy and format code * fix: did not modify tests ops * shader(conv2d): add conv2d shader kernels and pass f32 and f16 tests * shader(conv2d): fix the out of bounds memory access in the weight indexing * shader(conv2d): clean unused variables and optimize the computation * merge: use the new entries function * clean: address the formatting issues * clean: address the warning issues * clear: clean the shader editorconfig-checker issues * clear: clean the shader editorconfig-checker with utf-8 --------- Co-authored-by: Jeremy J. Hartmann <jeremy@mtion.tv>
This commit is contained in:
co-authored by
Jeremy J. Hartmann
parent
0dedb9ef7a
commit
ca7f7b7b94
@@ -0,0 +1,165 @@
|
||||
#include "common_decls.tmpl"
|
||||
enable f16;
|
||||
|
||||
@group(0) @binding(0)
|
||||
#if defined(WEIGHT_F32)
|
||||
var<storage, read_write> weights: array<f32>;
|
||||
#elif defined(WEIGHT_F16)
|
||||
var<storage, read_write> weights: array<f16>;
|
||||
#endif
|
||||
|
||||
@group(0) @binding(1)
|
||||
#if defined(INPUT_F32)
|
||||
var<storage, read_write> input: array<f32>;
|
||||
#elif defined(INPUT_F16)
|
||||
var<storage, read_write> input: array<f16>;
|
||||
#endif
|
||||
|
||||
@group(0) @binding(2)
|
||||
#if defined(OUTPUT_F32)
|
||||
var<storage, read_write> output: array<f32>;
|
||||
#elif defined(OUTPUT_F16)
|
||||
var<storage, read_write> output: array<f16>;
|
||||
#endif
|
||||
|
||||
struct Params {
|
||||
offset_w: u32,
|
||||
offset_i: u32,
|
||||
offset_o: u32,
|
||||
|
||||
// element strides
|
||||
sw0: u32, sw1: u32, sw2: u32, sw3: u32,
|
||||
si0: u32, si1: u32, si2: u32, si3: u32,
|
||||
so0: u32, so1: u32, so2: u32, so3: u32,
|
||||
|
||||
// kernel dimensions
|
||||
KW: u32, KH: u32, IC: u32,
|
||||
// input dimensions
|
||||
IW: u32, IH: u32,
|
||||
// output dimensions
|
||||
OW: u32, OH: u32, OC_out: u32, N_out: u32,
|
||||
|
||||
// stride
|
||||
s0: u32, s1: u32,
|
||||
// padding
|
||||
p0: u32, p1: u32,
|
||||
// dilation
|
||||
d0: u32, d1: u32,
|
||||
};
|
||||
|
||||
@group(0) @binding(3)
|
||||
var<uniform> params: Params;
|
||||
|
||||
fn load_weight(idx: u32) -> f32 {
|
||||
#if defined(WEIGHT_F32)
|
||||
return weights[idx];
|
||||
#elif defined(WEIGHT_F16)
|
||||
return f32(weights[idx]);
|
||||
#endif
|
||||
}
|
||||
|
||||
fn load_input(idx: u32) -> f32 {
|
||||
#if defined(INPUT_F32)
|
||||
return input[idx];
|
||||
#elif defined(INPUT_F16)
|
||||
return f32(input[idx]);
|
||||
#endif
|
||||
}
|
||||
|
||||
fn store_output(idx: u32, val: f32) {
|
||||
#if defined(OUTPUT_F32)
|
||||
output[idx] = val;
|
||||
#elif defined(OUTPUT_F16)
|
||||
output[idx] = f16(val);
|
||||
#endif
|
||||
}
|
||||
|
||||
fn ceil_div_u32(x: u32, y: u32) -> u32 {
|
||||
return (x + y - 1) / y;
|
||||
}
|
||||
|
||||
// returns the first valid kernel index k such that base + k * step >= 0
|
||||
fn first_valid_k(base: i32, step: u32) -> u32 {
|
||||
if (base >= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ceil_div_u32(u32(-base), step);
|
||||
}
|
||||
|
||||
// returns the first invalid kernel index k such that base + k * step >= limit so valid k are in [0, end_valid_k)
|
||||
fn end_valid_k(base: i32, step: u32, limit: u32, k_max: u32) -> u32 {
|
||||
let remaining = i32(limit) - base;
|
||||
if (remaining <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return min(k_max, ceil_div_u32(u32(remaining), step));
|
||||
}
|
||||
|
||||
@compute @workgroup_size(WG_SIZE)
|
||||
fn main(
|
||||
@builtin(global_invocation_id) gid: vec3<u32>,
|
||||
@builtin(num_workgroups) num_wg: vec3<u32>
|
||||
) {
|
||||
|
||||
let threads_per_group = u32(WG_SIZE);
|
||||
let i_out = gid.x + (num_wg.x * threads_per_group) * gid.y;
|
||||
let n_out = params.OW * params.OH * params.OC_out * params.N_out;
|
||||
|
||||
var sum: f32 = 0.0;
|
||||
if (i_out >= n_out) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Kernel layout: [KW, KH, IC, ..]
|
||||
// Input layout: [IW, IH, .., ..]
|
||||
// Output layout: [OW, OH, OC, N]
|
||||
|
||||
var i = i_out;
|
||||
let n = i / (params.OC_out * params.OH * params.OW);
|
||||
i = i % (params.OC_out * params.OH * params.OW);
|
||||
let oc = i / (params.OH * params.OW);
|
||||
i = i % (params.OH * params.OW);
|
||||
let oh = i / params.OW;
|
||||
let ow = i % params.OW;
|
||||
|
||||
let ow_base = i32(ow * params.s0) - i32(params.p0);
|
||||
let oh_base = i32(oh * params.s1) - i32(params.p1);
|
||||
|
||||
// clip the valid kernel window once
|
||||
let kw_begin = first_valid_k(ow_base, params.d0);
|
||||
let kw_end = end_valid_k(ow_base, params.d0, params.IW, params.KW);
|
||||
let kh_begin = first_valid_k(oh_base, params.d1);
|
||||
let kh_end = end_valid_k(oh_base, params.d1, params.IH, params.KH);
|
||||
|
||||
// entire receptive field is out of bounds
|
||||
if (kw_begin >= kw_end || kh_begin >= kh_end) {
|
||||
let out_idx = params.offset_o + ow * params.so0 + oh * params.so1 + oc * params.so2 + n * params.so3;
|
||||
store_output(out_idx, 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
let weight_oc_base = params.offset_w + oc * params.sw3;
|
||||
let input_n_base = params.offset_i + n * params.si3;
|
||||
|
||||
for (var ic: u32 = 0; ic < params.IC; ic += 1) {
|
||||
let w_base_ic = ic * params.sw2 + weight_oc_base;
|
||||
let in_base = ic * params.si2 + input_n_base;
|
||||
|
||||
for (var kh: u32 = kh_begin; kh < kh_end; kh += 1) {
|
||||
let ih = u32(oh_base + i32(kh * params.d1));
|
||||
let w_row_base = w_base_ic + kh * params.sw1;
|
||||
let in_row_base = in_base + ih * params.si1;
|
||||
for (var kw: u32 = kw_begin; kw < kw_end; kw += 1) {
|
||||
let iw = u32(ow_base + i32(kw * params.d0));
|
||||
let w_idx = w_row_base + kw * params.sw0;
|
||||
let in_idx = in_row_base + iw * params.si0;
|
||||
sum += load_weight(w_idx) * load_input(in_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let out_idx = params.offset_o + ow * params.so0 + oh * params.so1 + oc * params.so2 + n * params.so3;
|
||||
store_output(out_idx, sum);
|
||||
}
|
||||
Reference in New Issue
Block a user