Mask Generation
LiteRT
LiteRT
sam2
segment-anything
mask-decoder
interactive-segmentation
on-device
gpu
Instructions to use litert-community/SAM2.1-Hiera-Tiny-Mask-Decoder with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/SAM2.1-Hiera-Tiny-Mask-Decoder 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
- sam2
How to use litert-community/SAM2.1-Hiera-Tiny-Mask-Decoder with sam2:
# Use SAM2 with images import torch from sam2.sam2_image_predictor import SAM2ImagePredictor predictor = SAM2ImagePredictor.from_pretrained(litert-community/SAM2.1-Hiera-Tiny-Mask-Decoder) with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16): predictor.set_image(<your_image>) masks, _, _ = predictor.predict(<input_prompts>)# Use SAM2 with videos import torch from sam2.sam2_video_predictor import SAM2VideoPredictor predictor = SAM2VideoPredictor.from_pretrained(litert-community/SAM2.1-Hiera-Tiny-Mask-Decoder) with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16): state = predictor.init_state(<your_video>) # add new prompts and instantly get the output on the same frame frame_idx, object_ids, masks = predictor.add_new_points(state, <your_prompts>): # propagate the prompts to get masklets throughout the video for frame_idx, object_ids, masks in predictor.propagate_in_video(state): ... - Notebooks
- Google Colab
- Kaggle
Mirror the conversion script
Browse files- convert_sam2_decoder.py +264 -0
convert_sam2_decoder.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SAM 2.1 (hiera-tiny) mask decoder -> LiteRT GPU-clean .tflite (Bucket 1: model-side re-authoring only)
|
| 3 |
+
|
| 4 |
+
Phase-2 companion to convert_sam2.py (the image encoder). This converts the prompt-conditioned
|
| 5 |
+
mask DECODER. The tiny prompt-encoder (point -> sparse tokens, sin/cos) is done HOST-SIDE in Kotlin
|
| 6 |
+
(see emit at the end) so the GPU graph stays sin/cos-free; the decoder takes `sparse` as an input.
|
| 7 |
+
|
| 8 |
+
Walls re-authored (all model-side; no converter patch):
|
| 9 |
+
1. Sam2Attention (7x: 2 blocks x 3 + 1 final) : 4D fused attn -> 3D batched SDPA [heads, N, d]
|
| 10 |
+
2. ConvTranspose2d (upscale_conv1/2) : -> ZeroStuffConvT (exact zero-stuff + Conv2d), TRANSPOSE_CONV-free
|
| 11 |
+
3. mask head (hyper_in @ upscaled) : kept <=4D (no [1,1,4,256,256] 5D tensor); collapse batch==1
|
| 12 |
+
4. LayerNorm (9x) : SafeLayerNorm (scale-before-square), fp16-overflow-safe, exact
|
| 13 |
+
5. image_positional_embeddings + no-mask dense : baked CONSTANT buffers (host doesn't supply them)
|
| 14 |
+
6. multimask_output=True path : static slice [1:], no dynamic-stability argmax/gather/where
|
| 15 |
+
|
| 16 |
+
Decoder I/O (single point prompt):
|
| 17 |
+
inputs : image_embeddings [1,256,64,64], sparse [1,2,256], feat_s1 [1,64,128,128], feat_s0 [1,32,256,256]
|
| 18 |
+
outputs: pred_masks [1,3,256,256] (logits, 3 multimask), iou_scores [1,3]
|
| 19 |
+
|
| 20 |
+
Run:
|
| 21 |
+
python convert_sam2_decoder.py # eager parity vs transformers reference (correctness gate)
|
| 22 |
+
python convert_sam2_decoder.py --convert # + litert_torch convert + op-gate + fp16
|
| 23 |
+
"""
|
| 24 |
+
import sys, types, argparse, math
|
| 25 |
+
import torch
|
| 26 |
+
import torch.nn as nn
|
| 27 |
+
import torch.nn.functional as F
|
| 28 |
+
|
| 29 |
+
# macOS scipy stub (same as convert_sam2.py)
|
| 30 |
+
_svdp = types.ModuleType("scipy.sparse.linalg._svdp"); _svdp._svdp = lambda *a, **k: None
|
| 31 |
+
sys.modules["scipy.sparse.linalg._svdp"] = _svdp
|
| 32 |
+
_opt = types.ModuleType("scipy.optimize"); _opt.linear_sum_assignment = lambda *a, **k: (None, None)
|
| 33 |
+
sys.modules["scipy.optimize"] = _opt
|
| 34 |
+
|
| 35 |
+
from transformers import Sam2Model
|
| 36 |
+
|
| 37 |
+
MODEL_ID = "facebook/sam2.1-hiera-tiny"
|
| 38 |
+
SCRATCH = "/private/tmp/claude-501/-Users-majimadaisuke-Downloads-meeting/4ab9d785-6580-4aef-9d43-30f02ad9879b/scratchpad/sam2"
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# ----- LayerNorm. SafeLayerNorm (scale-before-square) protects the encoder's huge deep-stage
|
| 42 |
+
# activations from fp16 variance overflow, but the decoder's activations are normal-scale, where
|
| 43 |
+
# the down-scaling instead HURTS GPU fp16 (device A/B: SafeLN decoder masks the background).
|
| 44 |
+
# PLAIN_LN=1 uses stock LayerNorm for the decoder. -----
|
| 45 |
+
import os
|
| 46 |
+
_PLAIN_LN = os.environ.get("PLAIN_LN") == "1"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def safe_ln(x, weight, bias, eps, sc=0.03125):
|
| 50 |
+
if _PLAIN_LN:
|
| 51 |
+
xc = x - x.mean(-1, keepdim=True)
|
| 52 |
+
var = (xc * xc).mean(-1, keepdim=True)
|
| 53 |
+
return xc * torch.rsqrt(var + eps) * weight + bias
|
| 54 |
+
xc = x - x.mean(-1, keepdim=True)
|
| 55 |
+
xs = xc * sc
|
| 56 |
+
var = (xs * xs).mean(-1, keepdim=True) / (sc * sc)
|
| 57 |
+
return xc * torch.rsqrt(var + eps) * weight + bias
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# ----- ZeroStuffConvT: ConvTranspose2d(k=s,stride=s) == zero-stuff(nearest x top-left mask) + Conv2d(flipped w) -----
|
| 61 |
+
class ZeroStuffConvT(nn.Module):
|
| 62 |
+
def __init__(self, ct, H, W):
|
| 63 |
+
super().__init__()
|
| 64 |
+
self.s = ct.stride[0]; self.k = ct.kernel_size[0]
|
| 65 |
+
self.register_buffer("w", ct.weight.flip(2, 3).transpose(0, 1).contiguous())
|
| 66 |
+
self.register_buffer("b", ct.bias.detach().clone() if ct.bias is not None else torch.zeros(ct.out_channels))
|
| 67 |
+
s = self.s
|
| 68 |
+
mk = torch.zeros(H * s, W * s)
|
| 69 |
+
mk[::s, ::s] = 1.0
|
| 70 |
+
self.register_buffer("mask", mk[None, None])
|
| 71 |
+
|
| 72 |
+
def forward(self, x):
|
| 73 |
+
H, W = x.shape[-2], x.shape[-1]
|
| 74 |
+
s, k = self.s, self.k
|
| 75 |
+
xn = F.interpolate(x, size=(H * s, W * s), mode="nearest")
|
| 76 |
+
y = F.conv2d(xn * self.mask, self.w, bias=self.b, padding=k - 1)
|
| 77 |
+
return y[:, :, :H * s, :W * s]
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class CleanMaskDecoder(nn.Module):
|
| 81 |
+
"""Static single-point SAM2 mask decoder, GPU-clean. batch==1, point_batch==1 collapsed away."""
|
| 82 |
+
def __init__(self, model: Sam2Model):
|
| 83 |
+
super().__init__()
|
| 84 |
+
dec = model.mask_decoder
|
| 85 |
+
self.dec = dec
|
| 86 |
+
self.layers = dec.transformer.layers
|
| 87 |
+
self.final_attn = dec.transformer.final_attn_token_to_image
|
| 88 |
+
self.ln_final = dec.transformer.layer_norm_final_attn
|
| 89 |
+
self.mlps = dec.output_hypernetworks_mlps
|
| 90 |
+
self.iou_head = dec.iou_prediction_head
|
| 91 |
+
self.act = dec.activation
|
| 92 |
+
self.upscale_ln = dec.upscale_layer_norm # Sam2LayerNorm channels_first (32ch)
|
| 93 |
+
|
| 94 |
+
# ConvTranspose2d -> ZeroStuffConvT (input sizes are static: 64x64 -> 128 -> 256)
|
| 95 |
+
self.upscale_conv1 = ZeroStuffConvT(dec.upscale_conv1, 64, 64) # 256->64, 64x64 -> 128x128
|
| 96 |
+
self.upscale_conv2 = ZeroStuffConvT(dec.upscale_conv2, 128, 128) # 64->32, 128x128 -> 256x256
|
| 97 |
+
|
| 98 |
+
# baked constants
|
| 99 |
+
with torch.no_grad():
|
| 100 |
+
image_pos = model.get_image_wide_positional_embeddings() # [1,256,64,64]
|
| 101 |
+
self.register_buffer("image_pos_flat", image_pos.flatten(2).transpose(1, 2)[0].contiguous()) # [4096,256]
|
| 102 |
+
dense = model.prompt_encoder.no_mask_embed.weight.reshape(1, -1, 1, 1).expand(1, 256, 64, 64).contiguous()
|
| 103 |
+
self.register_buffer("dense", dense) # [1,256,64,64]
|
| 104 |
+
out_tokens = torch.cat([dec.obj_score_token.weight, dec.iou_token.weight, dec.mask_tokens.weight], 0)
|
| 105 |
+
self.register_buffer("output_tokens", out_tokens.contiguous()) # [6,256]
|
| 106 |
+
|
| 107 |
+
def _ln(self, ln_module, x):
|
| 108 |
+
return safe_ln(x, ln_module.weight, ln_module.bias, ln_module.eps)
|
| 109 |
+
|
| 110 |
+
def _attn(self, mod, query, key, value):
|
| 111 |
+
"""3D batched SDPA. query [Nq,C], key/value [Nk,C] -> [Nq,C]."""
|
| 112 |
+
Nq, Nk = query.shape[0], key.shape[0]
|
| 113 |
+
H, hd = mod.num_attention_heads, mod.head_dim
|
| 114 |
+
q = mod.q_proj(query).reshape(Nq, H, hd).transpose(0, 1) # [H,Nq,hd]
|
| 115 |
+
k = mod.k_proj(key).reshape(Nk, H, hd).transpose(0, 1) # [H,Nk,hd]
|
| 116 |
+
v = mod.v_proj(value).reshape(Nk, H, hd).transpose(0, 1) # [H,Nk,hd]
|
| 117 |
+
o = F.scaled_dot_product_attention(q, k, v, scale=mod.scaling) # [H,Nq,hd]
|
| 118 |
+
o = o.transpose(0, 1).reshape(Nq, H * hd) # [Nq, internal]
|
| 119 |
+
return mod.o_proj(o) # [Nq, C]
|
| 120 |
+
|
| 121 |
+
def _block(self, layer, queries, keys, qpe, kpe, skip):
|
| 122 |
+
if skip:
|
| 123 |
+
queries = self._attn(layer.self_attn, queries, queries, queries)
|
| 124 |
+
else:
|
| 125 |
+
qq = queries + qpe
|
| 126 |
+
queries = queries + self._attn(layer.self_attn, qq, qq, queries)
|
| 127 |
+
queries = self._ln(layer.layer_norm1, queries)
|
| 128 |
+
qq = queries + qpe; kk = keys + kpe
|
| 129 |
+
queries = queries + self._attn(layer.cross_attn_token_to_image, qq, kk, keys)
|
| 130 |
+
queries = self._ln(layer.layer_norm2, queries)
|
| 131 |
+
queries = queries + layer.mlp(queries)
|
| 132 |
+
queries = self._ln(layer.layer_norm3, queries)
|
| 133 |
+
qq = queries + qpe; kk = keys + kpe
|
| 134 |
+
keys = keys + self._attn(layer.cross_attn_image_to_token, kk, qq, queries)
|
| 135 |
+
keys = self._ln(layer.layer_norm4, keys)
|
| 136 |
+
return queries, keys
|
| 137 |
+
|
| 138 |
+
def forward(self, image_embeddings, sparse, feat_s1, feat_s0):
|
| 139 |
+
keys = (image_embeddings + self.dense).flatten(2).transpose(1, 2)[0] # [4096,256]
|
| 140 |
+
kpe = self.image_pos_flat # [4096,256]
|
| 141 |
+
queries = torch.cat([self.output_tokens, sparse[0]], 0) # [8,256]
|
| 142 |
+
qpe = queries # query_point_embedding (constant across layers)
|
| 143 |
+
|
| 144 |
+
q, k = queries, keys
|
| 145 |
+
q, k = self._block(self.layers[0], q, k, qpe, kpe, skip=True)
|
| 146 |
+
q, k = self._block(self.layers[1], q, k, qpe, kpe, skip=False)
|
| 147 |
+
fq, fk = q + qpe, k + kpe
|
| 148 |
+
q = q + self._attn(self.final_attn, fq, fk, k)
|
| 149 |
+
q = self._ln(self.ln_final, q)
|
| 150 |
+
|
| 151 |
+
iou_tok = q[1:2] # [1,256]
|
| 152 |
+
mask_toks = q[2:6] # [4,256]
|
| 153 |
+
|
| 154 |
+
img = k.transpose(0, 1).reshape(1, 256, 64, 64) # [1,256,64,64]
|
| 155 |
+
u = self.upscale_conv1(img) + feat_s1 # [1,64,128,128]
|
| 156 |
+
# upscale_layer_norm: channels_first SafeLN over the 64 channels
|
| 157 |
+
u = u.permute(0, 2, 3, 1)
|
| 158 |
+
u = safe_ln(u, self.upscale_ln.weight, self.upscale_ln.bias, self.upscale_ln.eps)
|
| 159 |
+
u = u.permute(0, 3, 1, 2)
|
| 160 |
+
u = self.act(u)
|
| 161 |
+
u = self.act(self.upscale_conv2(u) + feat_s0) # [1,32,256,256]
|
| 162 |
+
|
| 163 |
+
hyper = torch.cat([self.mlps[j](mask_toks[j:j + 1]) for j in range(4)], 0) # [4,32]
|
| 164 |
+
uf = u.reshape(32, 256 * 256) # [32,65536]
|
| 165 |
+
masks = (hyper @ uf).reshape(4, 256, 256)[1:].unsqueeze(0) # [1,3,256,256]
|
| 166 |
+
iou = self.iou_head(iou_tok)[:, 1:] # [1,3]
|
| 167 |
+
return masks, iou
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def main():
|
| 171 |
+
ap = argparse.ArgumentParser()
|
| 172 |
+
ap.add_argument("--convert", action="store_true")
|
| 173 |
+
args = ap.parse_args()
|
| 174 |
+
|
| 175 |
+
m = Sam2Model.from_pretrained(MODEL_ID).eval()
|
| 176 |
+
ref = torch.load(f"{SCRATCH}/ref_decoder.pt")
|
| 177 |
+
image_embeddings = ref["image_embeddings"] # list of 3
|
| 178 |
+
img_emb = image_embeddings[-1] # [1,256,64,64]
|
| 179 |
+
feat_s0, feat_s1 = image_embeddings[0], image_embeddings[1]
|
| 180 |
+
sparse = ref["sparse"][0] # [1,1,2,256] -> [1,2,256]
|
| 181 |
+
ref_masks, ref_iou = ref["masks"], ref["iou"] # [1,1,3,256,256], [1,1,3]
|
| 182 |
+
|
| 183 |
+
net = CleanMaskDecoder(m).eval()
|
| 184 |
+
with torch.no_grad():
|
| 185 |
+
masks, iou = net(img_emb, sparse, feat_s1, feat_s0)
|
| 186 |
+
|
| 187 |
+
rm = ref_masks.reshape(3, -1)
|
| 188 |
+
gm = masks.reshape(3, -1)
|
| 189 |
+
cos = F.cosine_similarity(gm.flatten(), rm.flatten(), dim=0).item()
|
| 190 |
+
mae = (gm - rm).abs().mean().item()
|
| 191 |
+
# mask agreement (binary IoU at threshold 0)
|
| 192 |
+
inter = ((gm > 0) & (rm > 0)).float().sum().item()
|
| 193 |
+
union = ((gm > 0) | (rm > 0)).float().sum().item()
|
| 194 |
+
iou_mask = inter / max(union, 1.0)
|
| 195 |
+
print(f"[eager] masks cos={cos:.6f} mae={mae:.3e} | binary-IoU(thr0)={iou_mask:.5f}")
|
| 196 |
+
print(f"[eager] iou ref={ref_iou.flatten().tolist()}")
|
| 197 |
+
print(f"[eager] iou got={iou.flatten().tolist()}")
|
| 198 |
+
assert cos > 0.9999, f"re-authoring changed the math! cos={cos}"
|
| 199 |
+
print(" -> re-authoring is numerically exact ✓")
|
| 200 |
+
|
| 201 |
+
if args.convert:
|
| 202 |
+
import os, collections, numpy as np, litert_torch
|
| 203 |
+
from ai_edge_litert.interpreter import Interpreter
|
| 204 |
+
BANNED = {"GATHER_ND", "GATHER", "TOPK_V2", "FLEX_ERF", "ERF", "BROADCAST_TO", "TRANSPOSE_CONV"}
|
| 205 |
+
FP32 = f"{SCRATCH}/sam2_tiny_dec_fp32.tflite"
|
| 206 |
+
FP16 = f"{SCRATCH}/sam2_tiny_mask_decoder_fp16.tflite"
|
| 207 |
+
ex = (img_emb, sparse, feat_s1, feat_s0)
|
| 208 |
+
|
| 209 |
+
with torch.no_grad():
|
| 210 |
+
ref_out = [t.detach().numpy().astype("float64").reshape(-1) for t in net(*ex)]
|
| 211 |
+
|
| 212 |
+
print("converting (litert_torch) ...")
|
| 213 |
+
litert_torch.convert(net, ex).export(FP32)
|
| 214 |
+
|
| 215 |
+
def gate(path, tag):
|
| 216 |
+
it = Interpreter(model_path=path); it.allocate_tensors()
|
| 217 |
+
hist = collections.Counter(d["op_name"] for d in it._get_ops_details())
|
| 218 |
+
over4d = sum(1 for d in it.get_tensor_details() if len(d.get("shape", [])) > 4)
|
| 219 |
+
bad = {k: v for k, v in hist.items() if k in BANNED}
|
| 220 |
+
print(f"[{tag}] ops: {dict(sorted(hist.items(), key=lambda kv: -kv[1]))}")
|
| 221 |
+
print(f"[{tag}] banned: {bad or 'NONE'} | >4D tensors: {over4d}")
|
| 222 |
+
return it, bad, over4d
|
| 223 |
+
|
| 224 |
+
def parity(it, tag):
|
| 225 |
+
ins = it.get_input_details()
|
| 226 |
+
order = [img_emb, sparse, feat_s1, feat_s0]
|
| 227 |
+
# match each model input slot to our tensors by shape
|
| 228 |
+
for d in ins:
|
| 229 |
+
want = next(t for t in order if tuple(t.shape) == tuple(d["shape"]))
|
| 230 |
+
it.set_tensor(d["index"], want.numpy().astype(d["dtype"]))
|
| 231 |
+
it.invoke()
|
| 232 |
+
outs = [it.get_tensor(o["index"]).astype("float64").reshape(-1) for o in it.get_output_details()]
|
| 233 |
+
for ro in ref_out:
|
| 234 |
+
cand = [o for o in outs if o.size == ro.size]
|
| 235 |
+
if cand:
|
| 236 |
+
c = max(np.corrcoef(ro, o)[0, 1] for o in cand)
|
| 237 |
+
print(f"[{tag}] parity corr={c:.6f} (len {ro.size})")
|
| 238 |
+
|
| 239 |
+
it32, bad, over4d = gate(FP32, "FP32")
|
| 240 |
+
parity(it32, "FP32")
|
| 241 |
+
|
| 242 |
+
print("quantizing fp16 (FLOAT_CASTING) ...")
|
| 243 |
+
from ai_edge_quantizer import quantizer, recipe_manager
|
| 244 |
+
from ai_edge_quantizer.recipe import AlgorithmName, qtyping
|
| 245 |
+
rmgr = recipe_manager.RecipeManager()
|
| 246 |
+
rmgr.add_quantization_config(
|
| 247 |
+
regex=".*", operation_name=qtyping.TFLOperationName.ALL_SUPPORTED,
|
| 248 |
+
op_config=qtyping.OpQuantizationConfig(
|
| 249 |
+
weight_tensor_config=qtyping.TensorQuantizationConfig(num_bits=16, dtype=qtyping.TensorDataType.FLOAT),
|
| 250 |
+
compute_precision=qtyping.ComputePrecision.FLOAT),
|
| 251 |
+
algorithm_key=AlgorithmName.FLOAT_CASTING)
|
| 252 |
+
if os.path.exists(FP16):
|
| 253 |
+
os.remove(FP16)
|
| 254 |
+
qt = quantizer.Quantizer(float_model=FP32)
|
| 255 |
+
qt.load_quantization_recipe(rmgr.get_quantization_recipe())
|
| 256 |
+
qt.quantize().export_model(FP16)
|
| 257 |
+
print(f"SIZE fp32 {os.path.getsize(FP32)/1e6:.1f} MB -> fp16 {os.path.getsize(FP16)/1e6:.1f} MB")
|
| 258 |
+
it16, bad16, over4d16 = gate(FP16, "FP16")
|
| 259 |
+
parity(it16, "FP16")
|
| 260 |
+
print(f"\n{'OK -> GPU-clean' if not bad16 and over4d16 == 0 else 'BLOCKERS REMAIN'}: {FP16}")
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
if __name__ == "__main__":
|
| 264 |
+
main()
|