Download source_current/baselines/AltDiff_my.py from SabaPivot/repro-a-fully-first-order-layer-for-differentiable-optimization: direct link, hf CLI and curl.
- Browser
- Download file 10.7 kB
-
https://huggingface.co/spaces/SabaPivot/repro-a-fully-first-order-layer-for-differentiable-optimization/resolve/main/source_current/baselines/AltDiff_my.py
- Command line
-
hf download hf://spaces/SabaPivot/repro-a-fully-first-order-layer-for-differentiable-optimization/source_current/baselines/AltDiff_my.py
-
curl -L -o AltDiff_my.py https://huggingface.co/spaces/SabaPivot/repro-a-fully-first-order-layer-for-differentiable-optimization/resolve/main/source_current/baselines/AltDiff_my.py
10.7 kB
| import torch | |
| from torch.autograd import Function | |
| def alt_qp_solve_batched( | |
| P, q, A, b, G, h, | |
| rho=1.0, eps=1e-6, max_iter=5000, reg=1e-7 | |
| ): | |
| device, dtype = q.device, q.dtype | |
| B, n = q.shape | |
| rho_t = torch.as_tensor(rho, device=device, dtype=dtype) | |
| m = A.shape[1] # A: (B,m,n) | |
| p = G.shape[1] # G: (B,p,n) | |
| x = torch.zeros((B, n), device=device, dtype=dtype) | |
| s = torch.zeros((B, p), device=device, dtype=dtype) | |
| lam = torch.zeros((B, m), device=device, dtype=dtype) | |
| nu = torch.zeros((B, p), device=device, dtype=dtype) | |
| I = torch.eye(n, device=device, dtype=dtype).expand(B, n, n) | |
| if m > 0: | |
| AtA = A.transpose(-1, -2) @ A | |
| Atb = (A.transpose(-1, -2) @ b.unsqueeze(-1)).squeeze(-1) | |
| else: | |
| AtA = torch.zeros((B, n, n), device=device, dtype=dtype) | |
| Atb = torch.zeros((B, n), device=device, dtype=dtype) | |
| if p > 0: | |
| GtG = G.transpose(-1, -2) @ G | |
| else: | |
| GtG = torch.zeros((B, n, n), device=device, dtype=dtype) | |
| H = P + rho_t * (AtA + GtG) + reg * I | |
| L = torch.linalg.cholesky(H) | |
| def solve_H(rhs_2d): # (B,n) | |
| return torch.cholesky_solve(rhs_2d.unsqueeze(-1), L).squeeze(-1) | |
| alive = torch.ones((B,), device=device, dtype=torch.bool) | |
| for _ in range(max_iter): | |
| if not alive.any(): | |
| break | |
| x0 = x | |
| s0 = s | |
| rhs = q.clone() | |
| if m > 0: | |
| rhs = rhs + (A.transpose(-1, -2) @ lam.unsqueeze(-1)).squeeze(-1) - rho_t * Atb | |
| if p > 0: | |
| rhs = rhs + (G.transpose(-1, -2) @ nu.unsqueeze(-1)).squeeze(-1) | |
| rhs = rhs + rho_t * (G.transpose(-1, -2) @ (s - h).unsqueeze(-1)).squeeze(-1) | |
| x = solve_H(-rhs) | |
| if m > 0: | |
| ax = (A @ x.unsqueeze(-1)).squeeze(-1) # (B,m) | |
| lam = lam + rho_t * (ax - b) | |
| else: | |
| ax = None | |
| if p > 0: | |
| gx = (G @ x.unsqueeze(-1)).squeeze(-1) # (B,p) | |
| s = torch.relu(-(nu / rho_t) - (gx - h)) | |
| nu = nu + rho_t * (gx + s - h) | |
| else: | |
| gx = None | |
| dx = torch.linalg.norm(x - x0, dim=-1) | |
| tol_x = eps * (1.0 + torch.linalg.norm(x0, dim=-1)) | |
| conv_x = dx <= tol_x | |
| conv_pri = torch.ones((B,), device=device, dtype=torch.bool) | |
| if m > 0: | |
| r_eq = torch.linalg.norm(ax - b, dim=-1) | |
| axn = torch.linalg.norm(ax, dim=-1) | |
| bn = torch.linalg.norm(b, dim=-1) | |
| tol_eq = eps * (1.0 + torch.maximum(axn, bn)) | |
| conv_pri = conv_pri & (r_eq <= tol_eq) | |
| if p > 0: | |
| r_in = torch.linalg.norm(gx + s - h, dim=-1) | |
| gsn = torch.linalg.norm(gx + s, dim=-1) | |
| hn = torch.linalg.norm(h, dim=-1) | |
| tol_in = eps * (1.0 + torch.maximum(gsn, hn)) | |
| conv_pri = conv_pri & (r_in <= tol_in) | |
| ds = torch.linalg.norm(s - s0, dim=-1) | |
| tol_s = eps * (1.0 + torch.linalg.norm(s0, dim=-1)) | |
| conv_s = ds <= tol_s | |
| else: | |
| conv_s = torch.ones((B,), device=device, dtype=torch.bool) | |
| conv = conv_x & conv_pri & conv_s | |
| alive = alive & (~conv) | |
| return x, lam, nu, s | |
| def _adjoint_and_param_grads_single( | |
| P, q, A, b, G, h, x, lam, nu, active_mask, g, reg=1e-9, sym_P=True | |
| ): | |
| device, dtype = g.device, g.dtype | |
| n = P.shape[0] | |
| m = A.shape[0] | |
| p = G.shape[0] | |
| if p == 0 or active_mask.numel() == 0 or active_mask.sum() == 0: | |
| Ga = G.new_zeros((0, n)) | |
| nu_a = nu.new_zeros((0,)) | |
| ha = h.new_zeros((0,)) | |
| else: | |
| Ga = G[active_mask] | |
| nu_a = nu[active_mask] | |
| ha = h[active_mask] | |
| ka = Ga.shape[0] | |
| K = torch.zeros((n + m + ka, n + m + ka), device=device, dtype=dtype) | |
| K[:n, :n] = P + reg * torch.eye(n, device=device, dtype=dtype) | |
| if m > 0: | |
| K[:n, n:n+m] = A.T | |
| K[n:n+m, :n] = A | |
| if ka > 0: | |
| K[:n, n+m:] = Ga.T | |
| K[n+m:, :n] = Ga | |
| rhs = torch.zeros((n + m + ka,), device=device, dtype=dtype) | |
| rhs[:n] = g | |
| sol = torch.linalg.solve(K, rhs) | |
| u = sol[:n] | |
| v = sol[n:n+m] if m > 0 else sol.new_zeros((0,)) | |
| w = sol[n+m:] if ka > 0 else sol.new_zeros((0,)) | |
| grad_q = -u | |
| grad_P = -torch.outer(u, x) | |
| if sym_P: | |
| grad_P = 0.5 * (grad_P + grad_P.T) | |
| if m > 0: | |
| grad_b = v | |
| grad_A = -(torch.outer(v, x) + torch.outer(lam, u)) | |
| else: | |
| grad_b = None | |
| grad_A = None | |
| if p > 0: | |
| grad_h = h.new_zeros((p,)) | |
| grad_G = G.new_zeros((p, n)) | |
| if ka > 0: | |
| grad_h[active_mask] = w | |
| grad_Ga = -(torch.outer(w, x) + torch.outer(nu_a, u)) | |
| grad_G[active_mask] = grad_Ga | |
| else: | |
| grad_h = None | |
| grad_G = None | |
| return grad_P, grad_q, grad_G, grad_h, grad_A, grad_b | |
| def AltDiffLayer( | |
| eps=1e-5, | |
| max_iter=2500, | |
| rho=1.0, | |
| reg_fwd=1e-5, | |
| active_tol=1e-6, | |
| reg_bwd=1e-5, | |
| sym_P=True, | |
| ): | |
| class L(Function): | |
| def forward(ctx, P, q, G=None, h=None, A=None, b=None): | |
| unbatched = (q.dim() == 1) | |
| P_in, q_in, A_in, b_in, G_in, h_in = P, q, A, b, G, h | |
| def ensure_batch(x, want_dim): | |
| if x is None: | |
| return None | |
| return x.unsqueeze(0) if x.dim() == want_dim else x | |
| P = ensure_batch(P, 2) | |
| q = ensure_batch(q, 1) | |
| B, n = q.shape | |
| A = ensure_batch(A, 2) | |
| b = ensure_batch(b, 1) | |
| G = ensure_batch(G, 2) | |
| h = ensure_batch(h, 1) | |
| if A is None or A.numel() == 0: | |
| A = torch.empty((1, 0, n), device=q.device, dtype=q.dtype) | |
| if b is None or b.numel() == 0: | |
| b = torch.empty((1, 0), device=q.device, dtype=q.dtype) | |
| if G is None or G.numel() == 0: | |
| G = torch.empty((1, 0, n), device=q.device, dtype=q.dtype) | |
| if h is None or h.numel() == 0: | |
| h = torch.empty((1, 0), device=q.device, dtype=q.dtype) | |
| P_shared = (P.shape[0] == 1 and B > 1) | |
| A_shared = (A.shape[0] == 1 and B > 1) | |
| b_shared = (b.shape[0] == 1 and B > 1) | |
| G_shared = (G.shape[0] == 1 and B > 1) | |
| h_shared = (h.shape[0] == 1 and B > 1) | |
| if P_shared: P = P.expand(B, -1, -1) | |
| if A_shared: A = A.expand(B, -1, -1) | |
| if b_shared: b = b.expand(B, -1) | |
| if G_shared: G = G.expand(B, -1, -1) | |
| if h_shared: h = h.expand(B, -1) | |
| P_d, q_d, A_d, b_d, G_d, h_d = P.detach(), q.detach(), A.detach(), b.detach(), G.detach(), h.detach() | |
| x, lam, nu, s = alt_qp_solve_batched( | |
| P_d, q_d, A_d, b_d, G_d, h_d, | |
| rho=rho, eps=eps, max_iter=max_iter, reg=reg_fwd | |
| ) | |
| if s.numel() == 0: | |
| active = torch.zeros((B, 0), device=q.device, dtype=torch.bool) | |
| else: | |
| active = (s <= active_tol) | |
| ctx.save_for_backward(P_d, q_d, A_d, b_d, G_d, h_d, x, lam, nu, active) | |
| ctx.unbatched = unbatched | |
| ctx.shared_flags = (P_shared, A_shared, b_shared, G_shared, h_shared) | |
| ctx.orig_none = (P_in is None, q_in is None, G_in is None, h_in is None, A_in is None, b_in is None) | |
| ctx.orig_shapes = ( | |
| getattr(P_in, "shape", None), | |
| getattr(q_in, "shape", None), | |
| getattr(G_in, "shape", None), | |
| getattr(h_in, "shape", None), | |
| getattr(A_in, "shape", None), | |
| getattr(b_in, "shape", None), | |
| ) | |
| ctx.sym_P = sym_P | |
| return x[0] if unbatched else x | |
| def backward(ctx, grad_output): | |
| P, q, A, b, G, h, x, lam, nu, active = ctx.saved_tensors | |
| P_shared, A_shared, b_shared, G_shared, h_shared = ctx.shared_flags | |
| unbatched = ctx.unbatched | |
| sym_P = ctx.sym_P | |
| g = grad_output.unsqueeze(0) if grad_output.dim() == 1 else grad_output | |
| B, n = g.shape | |
| grad_P = torch.zeros_like(P) | |
| grad_q = torch.zeros_like(q) | |
| grad_A = torch.zeros_like(A) if A.numel() > 0 else None | |
| grad_b = torch.zeros_like(b) if b.numel() > 0 else None | |
| grad_G = torch.zeros_like(G) if G.numel() > 0 else None | |
| grad_h = torch.zeros_like(h) if h.numel() > 0 else None | |
| for i in range(B): | |
| act_i = active[i] if active.numel() > 0 else active.new_zeros((0,), dtype=torch.bool) | |
| Pi = P[i] | |
| qi = q[i] | |
| Ai = A[i] if A.numel() > 0 else A.new_zeros((0, n)) | |
| bi = b[i] if b.numel() > 0 else b.new_zeros((0,)) | |
| Gi = G[i] if G.numel() > 0 else G.new_zeros((0, n)) | |
| hi = h[i] if h.numel() > 0 else h.new_zeros((0,)) | |
| xi = x[i] | |
| lami = lam[i] if lam.numel() > 0 else lam.new_zeros((0,)) | |
| nui = nu[i] if nu.numel() > 0 else nu.new_zeros((0,)) | |
| dP_i, dq_i, dG_i, dh_i, dA_i, db_i = _adjoint_and_param_grads_single( | |
| Pi, qi, Ai, bi, Gi, hi, xi, lami, nui, act_i, g[i], | |
| reg=reg_bwd, sym_P=sym_P | |
| ) | |
| grad_P[i] = dP_i | |
| grad_q[i] = dq_i | |
| if grad_A is not None and dA_i is not None: | |
| grad_A[i] = dA_i | |
| if grad_b is not None and db_i is not None: | |
| grad_b[i] = db_i | |
| if grad_G is not None and dG_i is not None: | |
| grad_G[i] = dG_i | |
| if grad_h is not None and dh_i is not None: | |
| grad_h[i] = dh_i | |
| if P_shared: | |
| grad_P = grad_P.sum(dim=0, keepdim=True) | |
| if A_shared and grad_A is not None: | |
| grad_A = grad_A.sum(dim=0, keepdim=True) | |
| if b_shared and grad_b is not None: | |
| grad_b = grad_b.sum(dim=0, keepdim=True) | |
| if G_shared and grad_G is not None: | |
| grad_G = grad_G.sum(dim=0, keepdim=True) | |
| if h_shared and grad_h is not None: | |
| grad_h = grad_h.sum(dim=0, keepdim=True) | |
| if unbatched: | |
| grad_P = grad_P[0] | |
| grad_q = grad_q[0] | |
| if grad_A is not None: grad_A = grad_A[0] | |
| if grad_b is not None: grad_b = grad_b[0] | |
| if grad_G is not None: grad_G = grad_G[0] | |
| if grad_h is not None: grad_h = grad_h[0] | |
| return grad_P, grad_q, grad_G, grad_h, grad_A, grad_b | |
| return L.apply | |