@@ -289,6 +289,61 @@ 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 d_x , None , None , None , None # grads for (x, weights, scales, group_size, bias)
339+
340+
341+ torch .library .register_autograd (
342+ f"{ namespace } ::linear_q4gsw" ,
343+ linear_q4gsw_backward ,
344+ setup_context = linear_q4gsw_setup_context ,
345+ )
346+
292347name = "linear_dq8ca_q4gsw"
293348lib .define (
294349 f"""
@@ -1090,3 +1145,120 @@ def rms_norm_impl(
10901145lib .define (f"{ name } (Tensor x, Tensor weight, float eps) -> Tensor" )
10911146lib .impl (name , rms_norm_impl , "CompositeExplicitAutograd" )
10921147rms_norm_op = getattr (getattr (torch .ops , namespace ), name )
1148+
1149+
1150+ ########################
1151+ ## fused_ce (training) ##
1152+ ########################
1153+
1154+
1155+ def fused_ce_impl (
1156+ logits : torch .Tensor ,
1157+ labels : torch .Tensor ,
1158+ n_valid : float ,
1159+ ) -> tuple [torch .Tensor , torch .Tensor ]:
1160+ mask = labels >= 0
1161+ safe = labels .clamp (min = 0 ).long ()
1162+ lse = torch .logsumexp (logits , dim = - 1 )
1163+ picked = logits .gather (- 1 , safe [:, None ]).squeeze (- 1 )
1164+ loss = torch .where (mask , (lse - picked ) / n_valid , torch .zeros_like (lse )).sum ()
1165+ softmax = torch .softmax (logits , dim = - 1 )
1166+ onehot = torch .nn .functional .one_hot (safe , logits .shape [- 1 ]).to (logits .dtype )
1167+ dlogits = torch .where (
1168+ mask [:, None ], (softmax - onehot ) / n_valid , torch .zeros_like (softmax )
1169+ )
1170+ return loss , dlogits
1171+
1172+
1173+ def fused_ce_meta (
1174+ logits : torch .Tensor ,
1175+ labels : torch .Tensor ,
1176+ n_valid : float ,
1177+ ) -> tuple [torch .Tensor , torch .Tensor ]:
1178+ return logits .new_empty ([]), torch .empty_like (logits )
1179+
1180+
1181+ def fused_ce_setup_context (ctx , inputs , output ) -> None :
1182+ ctx .save_for_backward (output [1 ])
1183+
1184+
1185+ def fused_ce_backward (ctx , grad_loss , grad_dlogits ):
1186+ (dlogits ,) = ctx .saved_tensors
1187+ return grad_loss * dlogits , None , None
1188+
1189+
1190+ name = "fused_ce"
1191+ lib .define (f"{ name } (Tensor logits, Tensor labels, float n_valid) -> (Tensor, Tensor)" )
1192+ lib .impl (name , fused_ce_impl , "CompositeExplicitAutograd" )
1193+ lib .impl (name , fused_ce_meta , "Meta" )
1194+ torch .library .register_autograd (
1195+ f"{ namespace } ::{ name } " , fused_ce_backward , setup_context = fused_ce_setup_context
1196+ )
1197+ fused_ce_op = getattr (getattr (torch .ops , namespace ), name )
1198+
1199+
1200+ # STE weight gradient d_out^T @ x through the frozen 4-bit linear_q4gsw base.
1201+ def linear_q4gsw_dw_impl (
1202+ d_out : torch .Tensor ,
1203+ x : torch .Tensor ,
1204+ ) -> torch .Tensor :
1205+ return d_out .reshape (- 1 , d_out .shape [- 1 ]).t () @ x .reshape (- 1 , x .shape [- 1 ])
1206+
1207+
1208+ def linear_q4gsw_dw_meta (
1209+ d_out : torch .Tensor ,
1210+ x : torch .Tensor ,
1211+ ) -> torch .Tensor :
1212+ return d_out .new_empty ((d_out .shape [- 1 ], x .shape [- 1 ]))
1213+
1214+
1215+ name = "linear_q4gsw_dw"
1216+ lib .define (f"{ name } (Tensor d_out, Tensor x) -> Tensor" )
1217+ lib .impl (name , linear_q4gsw_dw_impl , "CompositeExplicitAutograd" )
1218+ lib .impl (name , linear_q4gsw_dw_meta , "Meta" )
1219+ linear_q4gsw_dw_op = getattr (getattr (torch .ops , namespace ), name )
1220+
1221+
1222+
1223+ ##################
1224+ ## q4gsw_requant ##
1225+ ##################
1226+
1227+
1228+ # STE re-quant of fp32 latent weights into the frozen-scale 4-bit codes.
1229+ def q4gsw_requant_impl (
1230+ latent : torch .Tensor ,
1231+ scales : torch .Tensor ,
1232+ group_size : int ,
1233+ ) -> torch .Tensor :
1234+ n , k = latent .shape
1235+ group_idx = torch .arange (k , device = latent .device ) // group_size
1236+ scale_full = scales .t ()[:, group_idx ] # [N, K]: scales[k // group_size, n]
1237+ nonzero = scale_full != 0
1238+ safe = torch .where (nonzero , scale_full , torch .ones_like (scale_full ))
1239+ q = torch .round (latent / safe )
1240+ q = torch .where (nonzero , q , torch .zeros_like (q ))
1241+ codes = (torch .clamp (q , - 8 , 7 ).to (torch .int32 ) + 8 ) & 0xF # [N, K] in 0..15
1242+ k_packed = (k + 1 ) // 2
1243+ packed = torch .zeros ((n , k_packed ), dtype = torch .uint8 , device = latent .device )
1244+ packed [:, :] = codes [:, 0 ::2 ].to (torch .uint8 )
1245+ if k > 1 :
1246+ high = codes [:, 1 ::2 ].to (torch .uint8 )
1247+ packed [:, : high .shape [1 ]] |= high << 4
1248+ return packed
1249+
1250+
1251+ def q4gsw_requant_meta (
1252+ latent : torch .Tensor ,
1253+ scales : torch .Tensor ,
1254+ group_size : int ,
1255+ ) -> torch .Tensor :
1256+ n , k = latent .shape
1257+ return latent .new_empty ((n , (k + 1 ) // 2 ), dtype = torch .uint8 )
1258+
1259+
1260+ name = "q4gsw_requant"
1261+ lib .define (f"{ name } (Tensor latent, Tensor scales, int group_size) -> Tensor" )
1262+ lib .impl (name , q4gsw_requant_impl , "CompositeExplicitAutograd" )
1263+ lib .impl (name , q4gsw_requant_meta , "Meta" )
1264+ q4gsw_requant_op = getattr (getattr (torch .ops , namespace ), name )
0 commit comments