Image Feature Extraction
LiteRT
LiteRT
PerceptionEncoder
on-device
android
gpu
clip
image-encoder
vit
rope
Instructions to use litert-community/PE-Core-base-patch16-224 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/PE-Core-base-patch16-224 with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- PerceptionEncoder
How to use litert-community/PE-Core-base-patch16-224 with PerceptionEncoder:
# Use PE-Core models as CLIP models import core.vision_encoder.pe as pe model = pe.CLIP.from_config("litert-community/PE-Core-base-patch16-224", pretrained=True)# Use any PE model as a vision encoder import core.vision_encoder.pe as pe model = pe.VisionTransformer.from_config("litert-community/PE-Core-base-patch16-224", pretrained=True) - Notebooks
- Google Colab
- Kaggle
Upload convert_pecore.py with huggingface_hub
Browse files- 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
|
| 118 |
-
# const@non-const BMM
|
| 119 |
-
#
|
| 120 |
-
#
|
|
|
|
| 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 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
| 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
|
| 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) ...")
|