Skip to content

Commit ca7f7b7

Browse files
ggml-webgpu(shader): support conv2d kernels. (ggml-org#21964)
* ggml(webgpu): fix the busy-polls in Emscripten in the waitAny after ggml-org#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>
1 parent 0dedb9e commit ca7f7b7

3 files changed

Lines changed: 317 additions & 0 deletions

File tree

ggml/src/ggml-webgpu/ggml-webgpu-shader-lib.hpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,27 @@ struct ggml_webgpu_ssm_conv_pipeline_key {
240240
}
241241
};
242242

243+
/** CONV 2D */
244+
struct ggml_webgpu_conv2d_pipeline_key {
245+
ggml_type weight_type;
246+
ggml_type input_type;
247+
ggml_type output_type;
248+
249+
bool operator==(const ggml_webgpu_conv2d_pipeline_key & other) const {
250+
return weight_type == other.weight_type && input_type == other.input_type && output_type == other.output_type;
251+
}
252+
};
253+
254+
struct ggml_webgpu_conv2d_pipeline_key_hash {
255+
size_t operator()(const ggml_webgpu_conv2d_pipeline_key & key) const {
256+
size_t seed = 0;
257+
ggml_webgpu_hash_combine(seed, key.weight_type);
258+
ggml_webgpu_hash_combine(seed, key.input_type);
259+
ggml_webgpu_hash_combine(seed, key.output_type);
260+
return seed;
261+
}
262+
};
263+
243264
/** Gated Delta Net **/
244265
struct ggml_webgpu_gated_delta_net_pipeline_key {
245266
int type;
@@ -789,6 +810,8 @@ class ggml_webgpu_shader_lib {
789810
rope_pipelines;
790811
std::unordered_map<ggml_webgpu_soft_max_pipeline_key, webgpu_pipeline, ggml_webgpu_soft_max_pipeline_key_hash>
791812
soft_max_pipelines;
813+
std::unordered_map<ggml_webgpu_conv2d_pipeline_key, webgpu_pipeline, ggml_webgpu_conv2d_pipeline_key_hash>
814+
conv2d_pipelines;
792815

793816
public:
794817
ggml_webgpu_shader_lib(wgpu::Device device) { this->device = device; }
@@ -2382,6 +2405,46 @@ class ggml_webgpu_shader_lib {
23822405
return soft_max_pipelines[key];
23832406
}
23842407

2408+
webgpu_pipeline get_conv2d_pipeline(const ggml_webgpu_shader_lib_context & context) {
2409+
ggml_webgpu_conv2d_pipeline_key key = {};
2410+
key.weight_type = context.src0->type;
2411+
key.input_type = context.src1->type;
2412+
key.output_type = context.dst->type;
2413+
2414+
auto it = conv2d_pipelines.find(key);
2415+
if (it != conv2d_pipelines.end()) {
2416+
return it->second;
2417+
}
2418+
2419+
std::vector<std::string> defines;
2420+
std::string variant = "conv_2d";
2421+
2422+
auto push_type_defines = [&](const char * prefix, ggml_type type) {
2423+
std::string s_prefix = prefix;
2424+
if (type == GGML_TYPE_F32) {
2425+
defines.push_back(s_prefix + "_F32");
2426+
} else if (type == GGML_TYPE_F16) {
2427+
defines.push_back(s_prefix + "_F16");
2428+
} else {
2429+
GGML_ABORT("Unsupported type for CONV_2D shader");
2430+
}
2431+
};
2432+
2433+
push_type_defines("WEIGHT", key.weight_type);
2434+
push_type_defines("INPUT", key.input_type);
2435+
push_type_defines("OUTPUT", key.output_type);
2436+
2437+
defines.push_back(std::string("WG_SIZE=") + std::to_string(context.max_wg_size));
2438+
2439+
auto processed = preprocessor.preprocess(wgsl_conv2d, defines);
2440+
auto decisions = std::make_shared<ggml_webgpu_generic_shader_decisions>();
2441+
decisions->wg_size = context.max_wg_size;
2442+
webgpu_pipeline pipeline = ggml_webgpu_create_pipeline(device, processed, variant);
2443+
pipeline.context = decisions;
2444+
conv2d_pipelines[key] = pipeline;
2445+
return conv2d_pipelines[key];
2446+
}
2447+
23852448
private:
23862449
static webgpu_pipeline ggml_webgpu_create_pipeline(wgpu::Device & device,
23872450
std::string shader_code,

ggml/src/ggml-webgpu/ggml-webgpu.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "ggml-backend-impl.h"
99
#include "ggml-impl.h"
1010
#include "ggml-webgpu-shader-lib.hpp"
11+
#include "ggml.h"
1112

1213
#ifdef __EMSCRIPTEN__
1314
# include <emscripten/emscripten.h>
@@ -921,6 +922,87 @@ static webgpu_encoded_op ggml_webgpu_solve_tri(webgpu_context & ctx,
921922
return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
922923
}
923924

925+
static webgpu_encoded_op ggml_webgpu_conv_2d(webgpu_context & ctx,
926+
ggml_tensor * src0,
927+
ggml_tensor * src1,
928+
ggml_tensor * dst) {
929+
const int32_t s0 = ggml_get_op_params_i32(dst, 0);
930+
const int32_t s1 = ggml_get_op_params_i32(dst, 1);
931+
const int32_t p0 = ggml_get_op_params_i32(dst, 2);
932+
const int32_t p1 = ggml_get_op_params_i32(dst, 3);
933+
const int32_t d0 = ggml_get_op_params_i32(dst, 4);
934+
const int32_t d1 = ggml_get_op_params_i32(dst, 5);
935+
936+
std::vector<uint32_t> params = {
937+
(uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src0) / ggml_type_size(src0->type)),
938+
(uint32_t) (ggml_webgpu_tensor_misalignment(ctx, src1) / ggml_type_size(src1->type)),
939+
(uint32_t) (ggml_webgpu_tensor_misalignment(ctx, dst) / ggml_type_size(dst->type)),
940+
941+
(uint32_t) (src0->nb[0] / ggml_type_size(src0->type)),
942+
(uint32_t) (src0->nb[1] / ggml_type_size(src0->type)),
943+
(uint32_t) (src0->nb[2] / ggml_type_size(src0->type)),
944+
(uint32_t) (src0->nb[3] / ggml_type_size(src0->type)),
945+
946+
(uint32_t) (src1->nb[0] / ggml_type_size(src1->type)),
947+
(uint32_t) (src1->nb[1] / ggml_type_size(src1->type)),
948+
(uint32_t) (src1->nb[2] / ggml_type_size(src1->type)),
949+
(uint32_t) (src1->nb[3] / ggml_type_size(src1->type)),
950+
951+
(uint32_t) (dst->nb[0] / ggml_type_size(dst->type)),
952+
(uint32_t) (dst->nb[1] / ggml_type_size(dst->type)),
953+
(uint32_t) (dst->nb[2] / ggml_type_size(dst->type)),
954+
(uint32_t) (dst->nb[3] / ggml_type_size(dst->type)),
955+
956+
(uint32_t) src0->ne[0],
957+
(uint32_t) src0->ne[1],
958+
(uint32_t) src0->ne[2],
959+
960+
(uint32_t) src1->ne[0],
961+
(uint32_t) src1->ne[1],
962+
963+
(uint32_t) dst->ne[0],
964+
(uint32_t) dst->ne[1],
965+
(uint32_t) dst->ne[2],
966+
(uint32_t) dst->ne[3],
967+
968+
(uint32_t) s0,
969+
(uint32_t) s1,
970+
(uint32_t) p0,
971+
(uint32_t) p1,
972+
(uint32_t) d0,
973+
(uint32_t) d1,
974+
};
975+
976+
std::vector<wgpu::BindGroupEntry> entries = {
977+
ggml_webgpu_make_tensor_bind_group_entry(ctx, 0, src0),
978+
ggml_webgpu_make_tensor_bind_group_entry(ctx, 1, src1),
979+
ggml_webgpu_make_tensor_bind_group_entry(ctx, 2, dst),
980+
};
981+
982+
uint32_t max_wg_size =
983+
std::min((uint32_t) WEBGPU_MAX_WG_SIZE, ctx->global_ctx->capabilities.limits.maxComputeWorkgroupSizeX);
984+
uint32_t wg_size =
985+
std::min((uint32_t) ctx->global_ctx->capabilities.limits.maxComputeInvocationsPerWorkgroup, max_wg_size);
986+
987+
ggml_webgpu_shader_lib_context shader_lib_ctx = {};
988+
shader_lib_ctx.src0 = src0;
989+
shader_lib_ctx.src1 = src1;
990+
shader_lib_ctx.dst = dst;
991+
shader_lib_ctx.max_wg_size = wg_size;
992+
993+
webgpu_pipeline pipeline = ctx->shader_lib->get_conv2d_pipeline(shader_lib_ctx);
994+
995+
auto * decisions = static_cast<ggml_webgpu_generic_shader_decisions *>(pipeline.context.get());
996+
997+
uint32_t n_out = ggml_nelements(dst);
998+
uint32_t total_wg = CEIL_DIV(n_out, decisions->wg_size);
999+
uint32_t max_wg = ctx->global_ctx->capabilities.limits.maxComputeWorkgroupsPerDimension;
1000+
uint32_t wg_x = std::min(total_wg, max_wg);
1001+
uint32_t wg_y = CEIL_DIV(total_wg, wg_x);
1002+
1003+
return ggml_backend_webgpu_build(ctx, pipeline, params, entries, wg_x, wg_y);
1004+
}
1005+
9241006
static webgpu_encoded_op ggml_webgpu_ssm_conv(webgpu_context & ctx,
9251007
ggml_tensor * src0,
9261008
ggml_tensor * src1,
@@ -2477,6 +2559,8 @@ static std::optional<webgpu_encoded_op> ggml_webgpu_encode_node(webgpu_context c
24772559
case GGML_OP_SUM:
24782560
case GGML_OP_SUM_ROWS:
24792561
return ggml_webgpu_sum_rows(ctx, src0, node);
2562+
case GGML_OP_CONV_2D:
2563+
return ggml_webgpu_conv_2d(ctx, src0, src1, node);
24802564
default:
24812565
return std::nullopt;
24822566
}
@@ -3495,6 +3579,11 @@ static bool ggml_backend_webgpu_device_supports_op(ggml_backend_dev_t dev, const
34953579
case GGML_OP_SOLVE_TRI:
34963580
supports_op = op->type == GGML_TYPE_F32 && src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32;
34973581
break;
3582+
case GGML_OP_CONV_2D:
3583+
supports_op = (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) &&
3584+
(src0->type == GGML_TYPE_F32 || src0->type == GGML_TYPE_F16) &&
3585+
(src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16);
3586+
break;
34983587
case GGML_OP_SSM_CONV:
34993588
supports_op = op->type == GGML_TYPE_F32;
35003589
break;
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
#include "common_decls.tmpl"
2+
enable f16;
3+
4+
@group(0) @binding(0)
5+
#if defined(WEIGHT_F32)
6+
var<storage, read_write> weights: array<f32>;
7+
#elif defined(WEIGHT_F16)
8+
var<storage, read_write> weights: array<f16>;
9+
#endif
10+
11+
@group(0) @binding(1)
12+
#if defined(INPUT_F32)
13+
var<storage, read_write> input: array<f32>;
14+
#elif defined(INPUT_F16)
15+
var<storage, read_write> input: array<f16>;
16+
#endif
17+
18+
@group(0) @binding(2)
19+
#if defined(OUTPUT_F32)
20+
var<storage, read_write> output: array<f32>;
21+
#elif defined(OUTPUT_F16)
22+
var<storage, read_write> output: array<f16>;
23+
#endif
24+
25+
struct Params {
26+
offset_w: u32,
27+
offset_i: u32,
28+
offset_o: u32,
29+
30+
// element strides
31+
sw0: u32, sw1: u32, sw2: u32, sw3: u32,
32+
si0: u32, si1: u32, si2: u32, si3: u32,
33+
so0: u32, so1: u32, so2: u32, so3: u32,
34+
35+
// kernel dimensions
36+
KW: u32, KH: u32, IC: u32,
37+
// input dimensions
38+
IW: u32, IH: u32,
39+
// output dimensions
40+
OW: u32, OH: u32, OC_out: u32, N_out: u32,
41+
42+
// stride
43+
s0: u32, s1: u32,
44+
// padding
45+
p0: u32, p1: u32,
46+
// dilation
47+
d0: u32, d1: u32,
48+
};
49+
50+
@group(0) @binding(3)
51+
var<uniform> params: Params;
52+
53+
fn load_weight(idx: u32) -> f32 {
54+
#if defined(WEIGHT_F32)
55+
return weights[idx];
56+
#elif defined(WEIGHT_F16)
57+
return f32(weights[idx]);
58+
#endif
59+
}
60+
61+
fn load_input(idx: u32) -> f32 {
62+
#if defined(INPUT_F32)
63+
return input[idx];
64+
#elif defined(INPUT_F16)
65+
return f32(input[idx]);
66+
#endif
67+
}
68+
69+
fn store_output(idx: u32, val: f32) {
70+
#if defined(OUTPUT_F32)
71+
output[idx] = val;
72+
#elif defined(OUTPUT_F16)
73+
output[idx] = f16(val);
74+
#endif
75+
}
76+
77+
fn ceil_div_u32(x: u32, y: u32) -> u32 {
78+
return (x + y - 1) / y;
79+
}
80+
81+
// returns the first valid kernel index k such that base + k * step >= 0
82+
fn first_valid_k(base: i32, step: u32) -> u32 {
83+
if (base >= 0) {
84+
return 0;
85+
}
86+
87+
return ceil_div_u32(u32(-base), step);
88+
}
89+
90+
// returns the first invalid kernel index k such that base + k * step >= limit so valid k are in [0, end_valid_k)
91+
fn end_valid_k(base: i32, step: u32, limit: u32, k_max: u32) -> u32 {
92+
let remaining = i32(limit) - base;
93+
if (remaining <= 0) {
94+
return 0;
95+
}
96+
97+
return min(k_max, ceil_div_u32(u32(remaining), step));
98+
}
99+
100+
@compute @workgroup_size(WG_SIZE)
101+
fn main(
102+
@builtin(global_invocation_id) gid: vec3<u32>,
103+
@builtin(num_workgroups) num_wg: vec3<u32>
104+
) {
105+
106+
let threads_per_group = u32(WG_SIZE);
107+
let i_out = gid.x + (num_wg.x * threads_per_group) * gid.y;
108+
let n_out = params.OW * params.OH * params.OC_out * params.N_out;
109+
110+
var sum: f32 = 0.0;
111+
if (i_out >= n_out) {
112+
return;
113+
}
114+
115+
// Kernel layout: [KW, KH, IC, ..]
116+
// Input layout: [IW, IH, .., ..]
117+
// Output layout: [OW, OH, OC, N]
118+
119+
var i = i_out;
120+
let n = i / (params.OC_out * params.OH * params.OW);
121+
i = i % (params.OC_out * params.OH * params.OW);
122+
let oc = i / (params.OH * params.OW);
123+
i = i % (params.OH * params.OW);
124+
let oh = i / params.OW;
125+
let ow = i % params.OW;
126+
127+
let ow_base = i32(ow * params.s0) - i32(params.p0);
128+
let oh_base = i32(oh * params.s1) - i32(params.p1);
129+
130+
// clip the valid kernel window once
131+
let kw_begin = first_valid_k(ow_base, params.d0);
132+
let kw_end = end_valid_k(ow_base, params.d0, params.IW, params.KW);
133+
let kh_begin = first_valid_k(oh_base, params.d1);
134+
let kh_end = end_valid_k(oh_base, params.d1, params.IH, params.KH);
135+
136+
// entire receptive field is out of bounds
137+
if (kw_begin >= kw_end || kh_begin >= kh_end) {
138+
let out_idx = params.offset_o + ow * params.so0 + oh * params.so1 + oc * params.so2 + n * params.so3;
139+
store_output(out_idx, 0.0);
140+
return;
141+
}
142+
143+
let weight_oc_base = params.offset_w + oc * params.sw3;
144+
let input_n_base = params.offset_i + n * params.si3;
145+
146+
for (var ic: u32 = 0; ic < params.IC; ic += 1) {
147+
let w_base_ic = ic * params.sw2 + weight_oc_base;
148+
let in_base = ic * params.si2 + input_n_base;
149+
150+
for (var kh: u32 = kh_begin; kh < kh_end; kh += 1) {
151+
let ih = u32(oh_base + i32(kh * params.d1));
152+
let w_row_base = w_base_ic + kh * params.sw1;
153+
let in_row_base = in_base + ih * params.si1;
154+
for (var kw: u32 = kw_begin; kw < kw_end; kw += 1) {
155+
let iw = u32(ow_base + i32(kw * params.d0));
156+
let w_idx = w_row_base + kw * params.sw0;
157+
let in_idx = in_row_base + iw * params.si0;
158+
sum += load_weight(w_idx) * load_input(in_idx);
159+
}
160+
}
161+
}
162+
163+
let out_idx = params.offset_o + ow * params.so0 + oh * params.so1 + oc * params.so2 + n * params.so3;
164+
store_output(out_idx, sum);
165+
}

0 commit comments

Comments
 (0)