@@ -289,6 +289,67 @@ def linear_dq8ca_q4gsw(
289289lib .impl (name , linear_q4gsw , "CompositeExplicitAutograd" )
290290linear_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+
292353name = "linear_dq8ca_q4gsw"
293354lib .define (
294355 f"""
@@ -1090,3 +1151,171 @@ def rms_norm_impl(
10901151lib .define (f"{ name } (Tensor x, Tensor weight, float eps) -> Tensor" )
10911152lib .impl (name , rms_norm_impl , "CompositeExplicitAutograd" )
10921153rms_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 )
0 commit comments