@@ -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,173 @@ 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+
1201+ ###########################
1202+ ## adamw_step (training) ##
1203+ ###########################
1204+
1205+
1206+ def adamw_step_impl (
1207+ param : torch .Tensor ,
1208+ m : torch .Tensor ,
1209+ v : torch .Tensor ,
1210+ grad : torch .Tensor ,
1211+ lr : float ,
1212+ beta1 : float ,
1213+ beta2 : float ,
1214+ eps : float ,
1215+ weight_decay : float ,
1216+ bias_correction1 : float ,
1217+ bias_correction2 : float ,
1218+ ) -> tuple [torch .Tensor , torch .Tensor , torch .Tensor ]:
1219+ param .mul_ (1.0 - lr * weight_decay )
1220+ m .mul_ (beta1 ).add_ (grad , alpha = 1.0 - beta1 )
1221+ v .mul_ (beta2 ).addcmul_ (grad , grad , value = 1.0 - beta2 )
1222+ mhat = m / bias_correction1
1223+ denom = (v / bias_correction2 ).sqrt () + eps
1224+ param .addcdiv_ (mhat , denom , value = - lr )
1225+ return param , m , v
1226+
1227+
1228+ def adamw_step_meta (
1229+ param : torch .Tensor ,
1230+ m : torch .Tensor ,
1231+ v : torch .Tensor ,
1232+ grad : torch .Tensor ,
1233+ lr : float ,
1234+ beta1 : float ,
1235+ beta2 : float ,
1236+ eps : float ,
1237+ weight_decay : float ,
1238+ bias_correction1 : float ,
1239+ bias_correction2 : float ,
1240+ ) -> tuple [torch .Tensor , torch .Tensor , torch .Tensor ]:
1241+ return param , m , v
1242+
1243+
1244+ name = "adamw_step"
1245+ lib .define (
1246+ 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!))"
1247+ )
1248+ lib .impl (name , adamw_step_impl , "CompositeExplicitAutograd" )
1249+ lib .impl (name , adamw_step_meta , "Meta" )
1250+ adamw_step_op = getattr (getattr (torch .ops , namespace ), name )
1251+
1252+
1253+ # STE weight gradient d_out^T @ x through the frozen 4-bit linear_q4gsw base.
1254+ def linear_q4gsw_dw_impl (
1255+ d_out : torch .Tensor ,
1256+ x : torch .Tensor ,
1257+ ) -> torch .Tensor :
1258+ return d_out .reshape (- 1 , d_out .shape [- 1 ]).t () @ x .reshape (- 1 , x .shape [- 1 ])
1259+
1260+
1261+ def linear_q4gsw_dw_meta (
1262+ d_out : torch .Tensor ,
1263+ x : torch .Tensor ,
1264+ ) -> torch .Tensor :
1265+ return d_out .new_empty ((d_out .shape [- 1 ], x .shape [- 1 ]))
1266+
1267+
1268+ name = "linear_q4gsw_dw"
1269+ lib .define (f"{ name } (Tensor d_out, Tensor x) -> Tensor" )
1270+ lib .impl (name , linear_q4gsw_dw_impl , "CompositeExplicitAutograd" )
1271+ lib .impl (name , linear_q4gsw_dw_meta , "Meta" )
1272+ linear_q4gsw_dw_op = getattr (getattr (torch .ops , namespace ), name )
1273+
1274+
1275+
1276+ ##################
1277+ ## q4gsw_requant ##
1278+ ##################
1279+
1280+
1281+ # STE re-quant of fp32 latent weights into the frozen-scale 4-bit codes.
1282+ def q4gsw_requant_impl (
1283+ latent : torch .Tensor ,
1284+ scales : torch .Tensor ,
1285+ group_size : int ,
1286+ ) -> torch .Tensor :
1287+ n , k = latent .shape
1288+ group_idx = torch .arange (k , device = latent .device ) // group_size
1289+ scale_full = scales .t ()[:, group_idx ] # [N, K]: scales[k // group_size, n]
1290+ nonzero = scale_full != 0
1291+ safe = torch .where (nonzero , scale_full , torch .ones_like (scale_full ))
1292+ q = torch .round (latent / safe )
1293+ q = torch .where (nonzero , q , torch .zeros_like (q ))
1294+ codes = (torch .clamp (q , - 8 , 7 ).to (torch .int32 ) + 8 ) & 0xF # [N, K] in 0..15
1295+ k_packed = (k + 1 ) // 2
1296+ packed = torch .zeros ((n , k_packed ), dtype = torch .uint8 , device = latent .device )
1297+ packed [:, :] = codes [:, 0 ::2 ].to (torch .uint8 )
1298+ if k > 1 :
1299+ high = codes [:, 1 ::2 ].to (torch .uint8 )
1300+ packed [:, : high .shape [1 ]] |= high << 4
1301+ return packed
1302+
1303+
1304+ def q4gsw_requant_meta (
1305+ latent : torch .Tensor ,
1306+ scales : torch .Tensor ,
1307+ group_size : int ,
1308+ ) -> torch .Tensor :
1309+ n , k = latent .shape
1310+ return latent .new_empty ((n , (k + 1 ) // 2 ), dtype = torch .uint8 )
1311+
1312+
1313+ name = "q4gsw_requant"
1314+ lib .define (f"{ name } (Tensor latent, Tensor scales, int group_size) -> Tensor" )
1315+ lib .impl (name , q4gsw_requant_impl , "CompositeExplicitAutograd" )
1316+ lib .impl (name , q4gsw_requant_meta , "Meta" )
1317+ q4gsw_requant_op = getattr (getattr (torch .ops , namespace ), name )
0 commit comments