Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
75617e5
[ExecuTorch][WebGPU] Tests for the binary WebGPU ops
JCNTH Jul 21, 2026
675b948
[ExecuTorch][WebGPU] Add reduce op (sum/mean) to the WebGPU backend
JCNTH Jul 21, 2026
0670221
[ExecuTorch][WebGPU] Tests for the reduce WebGPU ops
JCNTH Jul 21, 2026
6573297
[ExecuTorch][WebGPU] Add select/boolean ops (where, scalar compares, …
JCNTH Jul 21, 2026
018f0b2
[ExecuTorch][WebGPU] Tests for the select_bool WebGPU ops
JCNTH Jul 21, 2026
674a8c7
[ExecuTorch][WebGPU] Add gather and embedding ops to the WebGPU backend
JCNTH Jul 21, 2026
cef3680
[ExecuTorch][WebGPU] Tests for the gather WebGPU ops
JCNTH Jul 21, 2026
440ae3c
[ExecuTorch][WebGPU] Add shape/copy ops (dim_order, expand_copy, fill…
JCNTH Jul 21, 2026
5d87312
[ExecuTorch][WebGPU] Tests for the shape_copy WebGPU ops
JCNTH Jul 21, 2026
a085baf
[ExecuTorch][WebGPU] Add q4gsw training kernels (backward, weight-gra…
JCNTH Jul 21, 2026
2b6be36
[ExecuTorch][WebGPU] Tests for the q4gsw_train WebGPU ops
JCNTH Jul 21, 2026
c8a19f0
[ExecuTorch][WebGPU] Add et_vk.fused_ce (fused cross-entropy) to the …
JCNTH Jul 21, 2026
4ef0400
[ExecuTorch][WebGPU] Tests for the fused_ce WebGPU ops
JCNTH Jul 21, 2026
113ccd9
[ExecuTorch][WebGPU] Add et_vk.adamw_step (on-GPU AdamW optimizer ste…
JCNTH Jul 21, 2026
b7002fd
[ExecuTorch][WebGPU] Tests for the adamw WebGPU ops
JCNTH Jul 21, 2026
f1f2aa5
[ExecuTorch][Vulkan] Add et_vk.adamw_step kernel (on-GPU AdamW optimi…
JCNTH Jul 21, 2026
78b9f96
[ExecuTorch][Vulkan] Tests for et_vk.adamw_step
JCNTH Jul 21, 2026
d7456bb
[ExecuTorch][Vulkan] Add et_vk.linear_dW kernel (dense fp32 weight-gr…
JCNTH Jul 21, 2026
318d33c
[ExecuTorch][Vulkan] Tests for et_vk.linear_dW
JCNTH Jul 21, 2026
2d05a3c
[ExecuTorch][Vulkan] Add et_vk.fused_ce kernels (fused cross-entropy …
JCNTH Jul 21, 2026
8240983
[ExecuTorch][Vulkan] Tests for et_vk.fused_ce
JCNTH Jul 21, 2026
23c8347
[ExecuTorch][Vulkan] Add et_vk.linear_q4gsw_backward kernel (4-bit in…
JCNTH Jul 21, 2026
8452fd1
[ExecuTorch][Vulkan] Tests for et_vk.linear_q4gsw_backward
JCNTH Jul 21, 2026
e25c752
[ExecuTorch][Vulkan] Add et_vk.q4gsw_requant kernel (STE re-quant to …
JCNTH Jul 21, 2026
d32206f
[ExecuTorch][Vulkan] Tests for et_vk.q4gsw_requant
JCNTH Jul 21, 2026
c0519f6
[ExecuTorch][WebGPU] Glob runtime/ops sources in CMakeLists
JCNTH Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions backends/vulkan/custom_ops_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,67 @@ def linear_dq8ca_q4gsw(
lib.impl(name, linear_q4gsw, "CompositeExplicitAutograd")
linear_qc4w_op = getattr(getattr(torch.ops, namespace), name)


# Backward of linear_q4gsw wrt input (for on-device LoRA training through a frozen
# 4-bit base): d_x = d_out @ dequant(W). Reference impl extracts dequant(W) via the
# forward on an identity so it is layout-agnostic; the runtime dispatches this op to
# the tiled q4gsw_backward WGSL kernel (contracts over N).
def linear_q4gsw_backward_impl(
d_out: torch.Tensor,
weights: torch.Tensor,
weight_scales: torch.Tensor,
group_size: int,
) -> torch.Tensor:
in_features = int(weights.shape[1]) * 2
eye = torch.eye(in_features, dtype=d_out.dtype, device=d_out.device)
w_t = linear_q4gsw(eye, weights, weight_scales, group_size) # [in, out]
return d_out @ w_t.t() # [M, out] @ [out, in] = [M, in]


def linear_q4gsw_backward_meta(
d_out: torch.Tensor,
weights: torch.Tensor,
weight_scales: torch.Tensor,
group_size: int,
) -> torch.Tensor:
return d_out.new_empty(d_out.shape[:-1] + (int(weights.shape[1]) * 2,))


name = "linear_q4gsw_backward"
lib.define(
f"{name}(Tensor d_out, Tensor weights, Tensor weight_scales, int group_size) -> Tensor"
)
lib.impl(name, linear_q4gsw_backward_impl, "CompositeExplicitAutograd")
lib.impl(name, linear_q4gsw_backward_meta, "Meta")
linear_q4gsw_backward_op = getattr(getattr(torch.ops, namespace), name)


def linear_q4gsw_setup_context(ctx, inputs, output) -> None:
_x, weights, weight_scales, group_size, _bias = inputs
ctx.save_for_backward(weights, weight_scales)
ctx.group_size = group_size


def linear_q4gsw_backward(ctx, grad_out):
weights, weight_scales = ctx.saved_tensors
d_x = torch.ops.et_vk.linear_q4gsw_backward(
grad_out, weights, weight_scales, ctx.group_size
)
return (
d_x,
None,
None,
None,
None,
) # grads for (x, weights, scales, group_size, bias)


torch.library.register_autograd(
f"{namespace}::linear_q4gsw",
linear_q4gsw_backward,
setup_context=linear_q4gsw_setup_context,
)

name = "linear_dq8ca_q4gsw"
lib.define(
f"""
Expand Down Expand Up @@ -1090,3 +1151,171 @@ def rms_norm_impl(
lib.define(f"{name}(Tensor x, Tensor weight, float eps) -> Tensor")
lib.impl(name, rms_norm_impl, "CompositeExplicitAutograd")
rms_norm_op = getattr(getattr(torch.ops, namespace), name)


########################
## fused_ce (training) ##
########################


def fused_ce_impl(
logits: torch.Tensor,
labels: torch.Tensor,
n_valid: float,
) -> tuple[torch.Tensor, torch.Tensor]:
mask = labels >= 0
safe = labels.clamp(min=0).long()
lse = torch.logsumexp(logits, dim=-1)
picked = logits.gather(-1, safe[:, None]).squeeze(-1)
loss = torch.where(mask, (lse - picked) / n_valid, torch.zeros_like(lse)).sum()
softmax = torch.softmax(logits, dim=-1)
onehot = torch.nn.functional.one_hot(safe, logits.shape[-1]).to(logits.dtype)
dlogits = torch.where(
mask[:, None], (softmax - onehot) / n_valid, torch.zeros_like(softmax)
)
return loss, dlogits


def fused_ce_meta(
logits: torch.Tensor,
labels: torch.Tensor,
n_valid: float,
) -> tuple[torch.Tensor, torch.Tensor]:
return logits.new_empty([]), torch.empty_like(logits)


def fused_ce_setup_context(ctx, inputs, output) -> None:
ctx.save_for_backward(output[1])


def fused_ce_backward(ctx, grad_loss, grad_dlogits):
(dlogits,) = ctx.saved_tensors
return grad_loss * dlogits, None, None


name = "fused_ce"
lib.define(f"{name}(Tensor logits, Tensor labels, float n_valid) -> (Tensor, Tensor)")
lib.impl(name, fused_ce_impl, "CompositeExplicitAutograd")
lib.impl(name, fused_ce_meta, "Meta")
torch.library.register_autograd(
f"{namespace}::{name}", fused_ce_backward, setup_context=fused_ce_setup_context
)
fused_ce_op = getattr(getattr(torch.ops, namespace), name)


###########################
## adamw_step (training) ##
###########################


def adamw_step_impl(
param: torch.Tensor,
m: torch.Tensor,
v: torch.Tensor,
grad: torch.Tensor,
lr: float,
beta1: float,
beta2: float,
eps: float,
weight_decay: float,
bias_correction1: float,
bias_correction2: float,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
param.mul_(1.0 - lr * weight_decay)
m.mul_(beta1).add_(grad, alpha=1.0 - beta1)
v.mul_(beta2).addcmul_(grad, grad, value=1.0 - beta2)
mhat = m / bias_correction1
denom = (v / bias_correction2).sqrt() + eps
param.addcdiv_(mhat, denom, value=-lr)
return param, m, v


def adamw_step_meta(
param: torch.Tensor,
m: torch.Tensor,
v: torch.Tensor,
grad: torch.Tensor,
lr: float,
beta1: float,
beta2: float,
eps: float,
weight_decay: float,
bias_correction1: float,
bias_correction2: float,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
return param, m, v


name = "adamw_step"
lib.define(
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!))"
)
lib.impl(name, adamw_step_impl, "CompositeExplicitAutograd")
lib.impl(name, adamw_step_meta, "Meta")
adamw_step_op = getattr(getattr(torch.ops, namespace), name)


# STE weight gradient d_out^T @ x through the frozen 4-bit linear_q4gsw base.
def linear_dW_impl(
d_out: torch.Tensor,
x: torch.Tensor,
) -> torch.Tensor:
return d_out.reshape(-1, d_out.shape[-1]).t() @ x.reshape(-1, x.shape[-1])


def linear_dW_meta(
d_out: torch.Tensor,
x: torch.Tensor,
) -> torch.Tensor:
return d_out.new_empty((d_out.shape[-1], x.shape[-1]))


name = "linear_dW"
lib.define(f"{name}(Tensor d_out, Tensor x) -> Tensor")
lib.impl(name, linear_dW_impl, "CompositeExplicitAutograd")
lib.impl(name, linear_dW_meta, "Meta")
linear_dW_op = getattr(getattr(torch.ops, namespace), name)


##################
## q4gsw_requant ##
##################


# STE re-quant of fp32 latent weights into the frozen-scale 4-bit codes.
def q4gsw_requant_impl(
latent: torch.Tensor,
scales: torch.Tensor,
group_size: int,
) -> torch.Tensor:
n, k = latent.shape
group_idx = torch.arange(k, device=latent.device) // group_size
scale_full = scales.t()[:, group_idx] # [N, K]: scales[k // group_size, n]
nonzero = scale_full != 0
safe = torch.where(nonzero, scale_full, torch.ones_like(scale_full))
q = torch.round(latent / safe)
q = torch.where(nonzero, q, torch.zeros_like(q))
codes = (torch.clamp(q, -8, 7).to(torch.int32) + 8) & 0xF # [N, K] in 0..15
k_packed = (k + 1) // 2
packed = torch.zeros((n, k_packed), dtype=torch.uint8, device=latent.device)
packed[:, :] = codes[:, 0::2].to(torch.uint8)
if k > 1:
high = codes[:, 1::2].to(torch.uint8)
packed[:, : high.shape[1]] |= high << 4
return packed


def q4gsw_requant_meta(
latent: torch.Tensor,
scales: torch.Tensor,
group_size: int,
) -> torch.Tensor:
n, k = latent.shape
return latent.new_empty((n, (k + 1) // 2), dtype=torch.uint8)


name = "q4gsw_requant"
lib.define(f"{name}(Tensor latent, Tensor scales, int group_size) -> Tensor")
lib.impl(name, q4gsw_requant_impl, "CompositeExplicitAutograd")
lib.impl(name, q4gsw_requant_meta, "Meta")
q4gsw_requant_op = getattr(getattr(torch.ops, namespace), name)
77 changes: 77 additions & 0 deletions backends/vulkan/op_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,15 @@ def register_quantizedlinear_cpp_ops():
)


@update_features(exir_ops.edge.et_vk.linear_q4gsw_backward.default)
def register_linear_q4gsw_backward():
return OpFeatures(
inputs_storage=utils.CONTIGUOUS_ANY,
inputs_dtypes=utils.FP_T,
supports_prepacking=True,
)


@update_features(exir_ops.edge.et_vk.linear_dq8ca_q4gsw.default)
def register_linear_dq8ca_q4gsw():
return OpFeatures(
Expand Down Expand Up @@ -1746,6 +1755,74 @@ def register_rms_norm():
)


# =============================================================================
# FusedCe.cpp (training)
# =============================================================================


@update_features(exir_ops.edge.et_vk.fused_ce.default)
def register_fused_ce():
return OpFeatures(
inputs_storage=utils.CONTIGUOUS_ANY,
inputs_dtypes=[utils.FP_T, utils.INT_T, utils.NONE_T],
outputs_dtypes=[utils.FP_T, utils.FP_T],
)


@update_features(
[
exir_ops.edge.aten.ne.Scalar,
exir_ops.edge.aten.lt.Scalar,
exir_ops.edge.aten.le.Scalar,
exir_ops.edge.aten.ge.Scalar,
exir_ops.edge.aten.gt.Scalar,
]
)
def register_compare_scalar_ops():
return OpFeatures(
inputs_storage=utils.ANY_STORAGE,
inputs_dtypes=utils.FP_INT_T,
outputs_dtypes=utils.BOOL_T,
supports_resize=True,
supports_highdim=True,
)


@update_features(exir_ops.edge.aten.logical_not.default)
def register_logical_not():
return OpFeatures(
inputs_storage=utils.ANY_STORAGE,
inputs_dtypes=utils.BOOL_T,
supports_resize=True,
supports_highdim=True,
)


@update_features("et_vk::adamw_step")
def register_adamw_step():
return OpFeatures(
inputs_storage=utils.CONTIGUOUS_ANY,
inputs_dtypes=utils.FP_T,
)


@update_features(exir_ops.edge.et_vk.linear_dW.default)
def register_linear_dW():
return OpFeatures(
inputs_storage=utils.CONTIGUOUS_ANY,
inputs_dtypes=utils.FP_T,
supports_prepacking=True,
)


@update_features(exir_ops.edge.et_vk.q4gsw_requant.default)
def register_q4gsw_requant():
return OpFeatures(
inputs_storage=utils.CONTIGUOUS_ANY,
inputs_dtypes=utils.FP_T,
)


#######################
## Utility functions ##
#######################
Expand Down
54 changes: 54 additions & 0 deletions backends/vulkan/runtime/graph/ops/glsl/adamw_step.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#version 450 core

#define PRECISION ${PRECISION}

#define T ${buffer_scalar_type(DTYPE)}

${define_active_storage_type(STORAGE)}

${define_required_extensions(STORAGE, DTYPE)}

layout(std430) buffer;

${layout_declare_tensor(B, "rw", "t_param", DTYPE, STORAGE)}
${layout_declare_tensor(B, "rw", "t_m", DTYPE, STORAGE)}
${layout_declare_tensor(B, "rw", "t_v", DTYPE, STORAGE)}
${layout_declare_tensor(B, "r", "t_grad", DTYPE, STORAGE)}

layout(push_constant) uniform restrict Block {
int numel;
float lr;
float beta1;
float beta2;
float eps;
float weight_decay;
float bias_correction1;
float bias_correction2;
};

layout(local_size_x_id = 0, local_size_y_id = 1, local_size_z_id = 2) in;

void main() {
const int i = int(gl_GlobalInvocationID.x);
if (i >= numel) {
return;
}
T g = t_grad[i];
T p = t_param[i];
p = p - lr * weight_decay * p;
T m = beta1 * t_m[i] + (1.0 - beta1) * g;
T v = beta2 * t_v[i] + (1.0 - beta2) * g * g;
t_m[i] = m;
t_v[i] = v;
T mhat = m / bias_correction1;
T vhat = v / bias_correction2;
t_param[i] = p - lr * mhat / (sqrt(vhat) + eps);
}
Loading
Loading