Skip to content

Commit 666ba0b

Browse files
authored
[ExecuTorch][WebGPU] Add fp32 matmul ops (mm, bmm, linear) to the WebGPU backend (#21070)
1 parent 8b14fc2 commit 666ba0b

121 files changed

Lines changed: 12253 additions & 26 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backends/vulkan/custom_ops_lib.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,67 @@ def linear_dq8ca_q4gsw(
289289
lib.impl(name, linear_q4gsw, "CompositeExplicitAutograd")
290290
linear_qc4w_op = getattr(getattr(torch.ops, namespace), name)
291291

292+
293+
# Backward of linear_q4gsw wrt input (for on-device LoRA training through a frozen
294+
# 4-bit base): d_x = d_out @ dequant(W). Reference impl extracts dequant(W) via the
295+
# forward on an identity so it is layout-agnostic; the runtime dispatches this op to
296+
# the tiled q4gsw_backward WGSL kernel (contracts over N).
297+
def linear_q4gsw_backward_impl(
298+
d_out: torch.Tensor,
299+
weights: torch.Tensor,
300+
weight_scales: torch.Tensor,
301+
group_size: int,
302+
) -> torch.Tensor:
303+
in_features = int(weights.shape[1]) * 2
304+
eye = torch.eye(in_features, dtype=d_out.dtype, device=d_out.device)
305+
w_t = linear_q4gsw(eye, weights, weight_scales, group_size) # [in, out]
306+
return d_out @ w_t.t() # [M, out] @ [out, in] = [M, in]
307+
308+
309+
def linear_q4gsw_backward_meta(
310+
d_out: torch.Tensor,
311+
weights: torch.Tensor,
312+
weight_scales: torch.Tensor,
313+
group_size: int,
314+
) -> torch.Tensor:
315+
return d_out.new_empty(d_out.shape[:-1] + (int(weights.shape[1]) * 2,))
316+
317+
318+
name = "linear_q4gsw_backward"
319+
lib.define(
320+
f"{name}(Tensor d_out, Tensor weights, Tensor weight_scales, int group_size) -> Tensor"
321+
)
322+
lib.impl(name, linear_q4gsw_backward_impl, "CompositeExplicitAutograd")
323+
lib.impl(name, linear_q4gsw_backward_meta, "Meta")
324+
linear_q4gsw_backward_op = getattr(getattr(torch.ops, namespace), name)
325+
326+
327+
def linear_q4gsw_setup_context(ctx, inputs, output) -> None:
328+
_x, weights, weight_scales, group_size, _bias = inputs
329+
ctx.save_for_backward(weights, weight_scales)
330+
ctx.group_size = group_size
331+
332+
333+
def linear_q4gsw_backward(ctx, grad_out):
334+
weights, weight_scales = ctx.saved_tensors
335+
d_x = torch.ops.et_vk.linear_q4gsw_backward(
336+
grad_out, weights, weight_scales, ctx.group_size
337+
)
338+
return (
339+
d_x,
340+
None,
341+
None,
342+
None,
343+
None,
344+
) # grads for (x, weights, scales, group_size, bias)
345+
346+
347+
torch.library.register_autograd(
348+
f"{namespace}::linear_q4gsw",
349+
linear_q4gsw_backward,
350+
setup_context=linear_q4gsw_setup_context,
351+
)
352+
292353
name = "linear_dq8ca_q4gsw"
293354
lib.define(
294355
f"""
@@ -1090,3 +1151,171 @@ def rms_norm_impl(
10901151
lib.define(f"{name}(Tensor x, Tensor weight, float eps) -> Tensor")
10911152
lib.impl(name, rms_norm_impl, "CompositeExplicitAutograd")
10921153
rms_norm_op = getattr(getattr(torch.ops, namespace), name)
1154+
1155+
1156+
########################
1157+
## fused_ce (training) ##
1158+
########################
1159+
1160+
1161+
def fused_ce_impl(
1162+
logits: torch.Tensor,
1163+
labels: torch.Tensor,
1164+
n_valid: float,
1165+
) -> tuple[torch.Tensor, torch.Tensor]:
1166+
mask = labels >= 0
1167+
safe = labels.clamp(min=0).long()
1168+
lse = torch.logsumexp(logits, dim=-1)
1169+
picked = logits.gather(-1, safe[:, None]).squeeze(-1)
1170+
loss = torch.where(mask, (lse - picked) / n_valid, torch.zeros_like(lse)).sum()
1171+
softmax = torch.softmax(logits, dim=-1)
1172+
onehot = torch.nn.functional.one_hot(safe, logits.shape[-1]).to(logits.dtype)
1173+
dlogits = torch.where(
1174+
mask[:, None], (softmax - onehot) / n_valid, torch.zeros_like(softmax)
1175+
)
1176+
return loss, dlogits
1177+
1178+
1179+
def fused_ce_meta(
1180+
logits: torch.Tensor,
1181+
labels: torch.Tensor,
1182+
n_valid: float,
1183+
) -> tuple[torch.Tensor, torch.Tensor]:
1184+
return logits.new_empty([]), torch.empty_like(logits)
1185+
1186+
1187+
def fused_ce_setup_context(ctx, inputs, output) -> None:
1188+
ctx.save_for_backward(output[1])
1189+
1190+
1191+
def fused_ce_backward(ctx, grad_loss, grad_dlogits):
1192+
(dlogits,) = ctx.saved_tensors
1193+
return grad_loss * dlogits, None, None
1194+
1195+
1196+
name = "fused_ce"
1197+
lib.define(f"{name}(Tensor logits, Tensor labels, float n_valid) -> (Tensor, Tensor)")
1198+
lib.impl(name, fused_ce_impl, "CompositeExplicitAutograd")
1199+
lib.impl(name, fused_ce_meta, "Meta")
1200+
torch.library.register_autograd(
1201+
f"{namespace}::{name}", fused_ce_backward, setup_context=fused_ce_setup_context
1202+
)
1203+
fused_ce_op = getattr(getattr(torch.ops, namespace), name)
1204+
1205+
1206+
###########################
1207+
## adamw_step (training) ##
1208+
###########################
1209+
1210+
1211+
def adamw_step_impl(
1212+
param: torch.Tensor,
1213+
m: torch.Tensor,
1214+
v: torch.Tensor,
1215+
grad: torch.Tensor,
1216+
lr: float,
1217+
beta1: float,
1218+
beta2: float,
1219+
eps: float,
1220+
weight_decay: float,
1221+
bias_correction1: float,
1222+
bias_correction2: float,
1223+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
1224+
param.mul_(1.0 - lr * weight_decay)
1225+
m.mul_(beta1).add_(grad, alpha=1.0 - beta1)
1226+
v.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
1227+
mhat = m / bias_correction1
1228+
denom = (v / bias_correction2).sqrt() + eps
1229+
param.addcdiv_(mhat, denom, value=-lr)
1230+
return param, m, v
1231+
1232+
1233+
def adamw_step_meta(
1234+
param: torch.Tensor,
1235+
m: torch.Tensor,
1236+
v: torch.Tensor,
1237+
grad: torch.Tensor,
1238+
lr: float,
1239+
beta1: float,
1240+
beta2: float,
1241+
eps: float,
1242+
weight_decay: float,
1243+
bias_correction1: float,
1244+
bias_correction2: float,
1245+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
1246+
return param, m, v
1247+
1248+
1249+
name = "adamw_step"
1250+
lib.define(
1251+
f"{name}(Tensor(a!) param, Tensor(b!) m, Tensor(c!) v, Tensor grad, float lr, float beta1, float beta2, float eps, float weight_decay, float bias_correction1, float bias_correction2) -> (Tensor(a!), Tensor(b!), Tensor(c!))"
1252+
)
1253+
lib.impl(name, adamw_step_impl, "CompositeExplicitAutograd")
1254+
lib.impl(name, adamw_step_meta, "Meta")
1255+
adamw_step_op = getattr(getattr(torch.ops, namespace), name)
1256+
1257+
1258+
# STE weight gradient d_out^T @ x through the frozen 4-bit linear_q4gsw base.
1259+
def linear_dW_impl(
1260+
d_out: torch.Tensor,
1261+
x: torch.Tensor,
1262+
) -> torch.Tensor:
1263+
return d_out.reshape(-1, d_out.shape[-1]).t() @ x.reshape(-1, x.shape[-1])
1264+
1265+
1266+
def linear_dW_meta(
1267+
d_out: torch.Tensor,
1268+
x: torch.Tensor,
1269+
) -> torch.Tensor:
1270+
return d_out.new_empty((d_out.shape[-1], x.shape[-1]))
1271+
1272+
1273+
name = "linear_dW"
1274+
lib.define(f"{name}(Tensor d_out, Tensor x) -> Tensor")
1275+
lib.impl(name, linear_dW_impl, "CompositeExplicitAutograd")
1276+
lib.impl(name, linear_dW_meta, "Meta")
1277+
linear_dW_op = getattr(getattr(torch.ops, namespace), name)
1278+
1279+
1280+
##################
1281+
## q4gsw_requant ##
1282+
##################
1283+
1284+
1285+
# STE re-quant of fp32 latent weights into the frozen-scale 4-bit codes.
1286+
def q4gsw_requant_impl(
1287+
latent: torch.Tensor,
1288+
scales: torch.Tensor,
1289+
group_size: int,
1290+
) -> torch.Tensor:
1291+
n, k = latent.shape
1292+
group_idx = torch.arange(k, device=latent.device) // group_size
1293+
scale_full = scales.t()[:, group_idx] # [N, K]: scales[k // group_size, n]
1294+
nonzero = scale_full != 0
1295+
safe = torch.where(nonzero, scale_full, torch.ones_like(scale_full))
1296+
q = torch.round(latent / safe)
1297+
q = torch.where(nonzero, q, torch.zeros_like(q))
1298+
codes = (torch.clamp(q, -8, 7).to(torch.int32) + 8) & 0xF # [N, K] in 0..15
1299+
k_packed = (k + 1) // 2
1300+
packed = torch.zeros((n, k_packed), dtype=torch.uint8, device=latent.device)
1301+
packed[:, :] = codes[:, 0::2].to(torch.uint8)
1302+
if k > 1:
1303+
high = codes[:, 1::2].to(torch.uint8)
1304+
packed[:, : high.shape[1]] |= high << 4
1305+
return packed
1306+
1307+
1308+
def q4gsw_requant_meta(
1309+
latent: torch.Tensor,
1310+
scales: torch.Tensor,
1311+
group_size: int,
1312+
) -> torch.Tensor:
1313+
n, k = latent.shape
1314+
return latent.new_empty((n, (k + 1) // 2), dtype=torch.uint8)
1315+
1316+
1317+
name = "q4gsw_requant"
1318+
lib.define(f"{name}(Tensor latent, Tensor scales, int group_size) -> Tensor")
1319+
lib.impl(name, q4gsw_requant_impl, "CompositeExplicitAutograd")
1320+
lib.impl(name, q4gsw_requant_meta, "Meta")
1321+
q4gsw_requant_op = getattr(getattr(torch.ops, namespace), name)

backends/vulkan/op_registry.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,15 @@ def register_quantizedlinear_cpp_ops():
462462
)
463463

464464

465+
@update_features(exir_ops.edge.et_vk.linear_q4gsw_backward.default)
466+
def register_linear_q4gsw_backward():
467+
return OpFeatures(
468+
inputs_storage=utils.CONTIGUOUS_ANY,
469+
inputs_dtypes=utils.FP_T,
470+
supports_prepacking=True,
471+
)
472+
473+
465474
@update_features(exir_ops.edge.et_vk.linear_dq8ca_q4gsw.default)
466475
def register_linear_dq8ca_q4gsw():
467476
return OpFeatures(
@@ -1746,6 +1755,74 @@ def register_rms_norm():
17461755
)
17471756

17481757

1758+
# =============================================================================
1759+
# FusedCe.cpp (training)
1760+
# =============================================================================
1761+
1762+
1763+
@update_features(exir_ops.edge.et_vk.fused_ce.default)
1764+
def register_fused_ce():
1765+
return OpFeatures(
1766+
inputs_storage=utils.CONTIGUOUS_ANY,
1767+
inputs_dtypes=[utils.FP_T, utils.INT_T, utils.NONE_T],
1768+
outputs_dtypes=[utils.FP_T, utils.FP_T],
1769+
)
1770+
1771+
1772+
@update_features(
1773+
[
1774+
exir_ops.edge.aten.ne.Scalar,
1775+
exir_ops.edge.aten.lt.Scalar,
1776+
exir_ops.edge.aten.le.Scalar,
1777+
exir_ops.edge.aten.ge.Scalar,
1778+
exir_ops.edge.aten.gt.Scalar,
1779+
]
1780+
)
1781+
def register_compare_scalar_ops():
1782+
return OpFeatures(
1783+
inputs_storage=utils.ANY_STORAGE,
1784+
inputs_dtypes=utils.FP_INT_T,
1785+
outputs_dtypes=utils.BOOL_T,
1786+
supports_resize=True,
1787+
supports_highdim=True,
1788+
)
1789+
1790+
1791+
@update_features(exir_ops.edge.aten.logical_not.default)
1792+
def register_logical_not():
1793+
return OpFeatures(
1794+
inputs_storage=utils.ANY_STORAGE,
1795+
inputs_dtypes=utils.BOOL_T,
1796+
supports_resize=True,
1797+
supports_highdim=True,
1798+
)
1799+
1800+
1801+
@update_features("et_vk::adamw_step")
1802+
def register_adamw_step():
1803+
return OpFeatures(
1804+
inputs_storage=utils.CONTIGUOUS_ANY,
1805+
inputs_dtypes=utils.FP_T,
1806+
)
1807+
1808+
1809+
@update_features(exir_ops.edge.et_vk.linear_dW.default)
1810+
def register_linear_dW():
1811+
return OpFeatures(
1812+
inputs_storage=utils.CONTIGUOUS_ANY,
1813+
inputs_dtypes=utils.FP_T,
1814+
supports_prepacking=True,
1815+
)
1816+
1817+
1818+
@update_features(exir_ops.edge.et_vk.q4gsw_requant.default)
1819+
def register_q4gsw_requant():
1820+
return OpFeatures(
1821+
inputs_storage=utils.CONTIGUOUS_ANY,
1822+
inputs_dtypes=utils.FP_T,
1823+
)
1824+
1825+
17491826
#######################
17501827
## Utility functions ##
17511828
#######################
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#version 450 core
10+
11+
#define PRECISION ${PRECISION}
12+
13+
#define T ${buffer_scalar_type(DTYPE)}
14+
15+
${define_active_storage_type(STORAGE)}
16+
17+
${define_required_extensions(STORAGE, DTYPE)}
18+
19+
layout(std430) buffer;
20+
21+
${layout_declare_tensor(B, "rw", "t_param", DTYPE, STORAGE)}
22+
${layout_declare_tensor(B, "rw", "t_m", DTYPE, STORAGE)}
23+
${layout_declare_tensor(B, "rw", "t_v", DTYPE, STORAGE)}
24+
${layout_declare_tensor(B, "r", "t_grad", DTYPE, STORAGE)}
25+
26+
layout(push_constant) uniform restrict Block {
27+
int numel;
28+
float lr;
29+
float beta1;
30+
float beta2;
31+
float eps;
32+
float weight_decay;
33+
float bias_correction1;
34+
float bias_correction2;
35+
};
36+
37+
layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;
38+
39+
void main() {
40+
const int i = int(gl_GlobalInvocationID.x);
41+
if (i >= numel) {
42+
return;
43+
}
44+
T g = t_grad[i];
45+
T p = t_param[i];
46+
p = p - lr * weight_decay * p;
47+
T m = beta1 * t_m[i] + (1.0 - beta1) * g;
48+
T v = beta2 * t_v[i] + (1.0 - beta2) * g * g;
49+
t_m[i] = m;
50+
t_v[i] = v;
51+
T mhat = m / bias_correction1;
52+
T vhat = v / bias_correction2;
53+
t_param[i] = p - lr * mhat / (sqrt(vhat) + eps);
54+
}

0 commit comments

Comments
 (0)