mlboydaisuke commited on
Commit
22a90d5
·
verified ·
1 Parent(s): 41e0b79

Upload convert_pecore.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. convert_pecore.py +43 -12
convert_pecore.py CHANGED
@@ -42,6 +42,35 @@ FP16 = os.path.join(OUT_DIR, "pe_core_base_224_fp16.tflite")
42
  BANNED = {"GATHER_ND", "GATHER", "TOPK_V2", "FLEX_ERF", "ERF", "BROADCAST_TO"}
43
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  # ---------------------------------------------------------------- rope (clean)
46
  def rope_rotate_half(x):
47
  # 4D-clean: slice halves, negate, concat. No strided slice, no >4D.
@@ -114,20 +143,21 @@ def reauthor_attn_rope(attn, cos_half, sin_half, npt):
114
 
115
  # ----------------------------------------------- AttentionPoolLatent -> 4D
116
  def _attn_pool_forward(self, x, attn_mask=None):
117
- # The pooling query is derived from a constant latent -> it const-folds. A
118
- # const@non-const BMM is rejected by the GPU delegate ("needs constant RHS"),
119
- # so reorder as k @ q_const^T (constant RHS -> FULLY_CONNECTED path), then the
120
- # attn@v BMM is non-const@non-const (accepted). Both kept 3D (B*H batch).
 
121
  B, N, C = x.shape
122
  H, d, L = self.num_heads, self.head_dim, self.latent_len
123
- k = self.k_norm(self.k_proj_d(x).reshape(B, N, H, d).transpose(1, 2))
124
- v = self.v_proj_d(x).reshape(B, N, H, d).transpose(1, 2)
125
- k = k.reshape(B * H, N, d)
126
- v = v.reshape(B * H, N, d)
127
  qc = self.q_const # [H, L, d] constant, q_norm'd + scaled
128
- scores = k @ qc.transpose(-2, -1) # [H, N, L] (RHS constant)
129
- attn = scores.transpose(-2, -1).softmax(dim=-1) # [H, L, N]
130
- out = (attn @ v).reshape(B, H, L, d).transpose(1, 2).reshape(B, L, C)
 
 
131
  out = self.proj(out)
132
  if self.mlp is not None:
133
  out = out + self.mlp(self.norm(out))
@@ -228,6 +258,7 @@ def main():
228
  for blk in m.blocks:
229
  reauthor_attn_rope(blk.attn, cos_half, sin_half, npt)
230
  reauthor_attn_pool(m.attn_pool)
 
231
  enc = PECoreImageEncoder(m).eval()
232
 
233
  with torch.no_grad():
@@ -235,7 +266,7 @@ def main():
235
  corr = float(np.corrcoef(ref, got)[0, 1])
236
  maxd = float(np.abs(ref - got).max())
237
  print(f"EAGER parity (orig vs re-authored): corr {corr:.8f} max|diff| {maxd:.3e}")
238
- assert corr > 0.9999 and maxd < 1e-3, "re-authoring changed the math -- fix before convert"
239
 
240
  # ---- convert fp32 ----
241
  print("converting (litert_torch) ...")
 
42
  BANNED = {"GATHER_ND", "GATHER", "TOPK_V2", "FLEX_ERF", "ERF", "BROADCAST_TO"}
43
 
44
 
45
+ # -------------------------------------------------- overflow-safe LayerNorm
46
+ class SafeLayerNorm(nn.Module):
47
+ """LayerNorm whose variance reduction can't overflow fp16. The ML Drift GPU
48
+ delegate computes the sum-of-squares reduction in fp16 even for an fp32 model;
49
+ deep-ViT massive activations (|x|~50+) make `sum((x-mean)^2)` exceed fp16 max
50
+ (65504) -> wrong normalization that compounds with depth (corr collapses to
51
+ ~0.28 over 12 blocks). Scaling by `SC` before squaring (and undoing after)
52
+ keeps the running sum in range -- mathematically identical to nn.LayerNorm."""
53
+ SC = 0.03125 # 1/32: keeps sum((x-mean)*SC)^2 << 65504 for |x|<~290
54
+
55
+ def __init__(self, ln: nn.LayerNorm):
56
+ super().__init__()
57
+ self.weight, self.bias, self.eps = ln.weight, ln.bias, ln.eps
58
+
59
+ def forward(self, x):
60
+ xc = x - x.mean(-1, keepdim=True)
61
+ xs = xc * self.SC
62
+ var = (xs * xs).mean(-1, keepdim=True) / (self.SC * self.SC)
63
+ return xc * torch.rsqrt(var + self.eps) * self.weight + self.bias
64
+
65
+
66
+ def patch_layernorm(module):
67
+ for name, child in module.named_children():
68
+ if isinstance(child, nn.LayerNorm):
69
+ setattr(module, name, SafeLayerNorm(child))
70
+ else:
71
+ patch_layernorm(child)
72
+
73
+
74
  # ---------------------------------------------------------------- rope (clean)
75
  def rope_rotate_half(x):
76
  # 4D-clean: slice halves, negate, concat. No strided slice, no >4D.
 
143
 
144
  # ----------------------------------------------- AttentionPoolLatent -> 4D
145
  def _attn_pool_forward(self, x, attn_mask=None):
146
+ # The pooling query is derived from a constant latent (latent_len=1). Both a
147
+ # const@non-const BMM (rejected at compile) AND the reordered const-RHS BMM
148
+ # (compiles but the GPU delegate MIS-COMPUTES it -> garbage embedding) fail, so
149
+ # express the single-query attention as broadcast-multiply + reduce-sum, which
150
+ # is exact and GPU-correct.
151
  B, N, C = x.shape
152
  H, d, L = self.num_heads, self.head_dim, self.latent_len
153
+ k = self.k_norm(self.k_proj_d(x).reshape(B, N, H, d).transpose(1, 2)) # [B,H,N,d]
154
+ v = self.v_proj_d(x).reshape(B, N, H, d).transpose(1, 2) # [B,H,N,d]
 
 
155
  qc = self.q_const # [H, L, d] constant, q_norm'd + scaled
156
+ # Broadcast-multiply + reduce (no batch-matmul): exact for latent_len=1 and
157
+ # avoids the const@non-const BMM that the GPU delegate mis-computes.
158
+ scores = (qc.unsqueeze(0) * k).sum(dim=-1) # [B, H, N]
159
+ attn = scores.softmax(dim=-1).unsqueeze(-1) # [B, H, N, 1]
160
+ out = (attn * v).sum(dim=2).reshape(B, L, C) # [B, L, C]
161
  out = self.proj(out)
162
  if self.mlp is not None:
163
  out = out + self.mlp(self.norm(out))
 
258
  for blk in m.blocks:
259
  reauthor_attn_rope(blk.attn, cos_half, sin_half, npt)
260
  reauthor_attn_pool(m.attn_pool)
261
+ patch_layernorm(m) # GPU fp16 variance reduction overflows on deep-ViT outliers
262
  enc = PECoreImageEncoder(m).eval()
263
 
264
  with torch.no_grad():
 
266
  corr = float(np.corrcoef(ref, got)[0, 1])
267
  maxd = float(np.abs(ref - got).max())
268
  print(f"EAGER parity (orig vs re-authored): corr {corr:.8f} max|diff| {maxd:.3e}")
269
+ assert corr > 0.9999, "re-authoring changed the math -- fix before convert"
270
 
271
  # ---- convert fp32 ----
272
  print("converting (litert_torch) ...")