Download export/_sam2.py from executorch-community/SAM2-Hiera-Tiny-ExecuTorch-WebGPU: direct link, hf CLI and curl.
- Browser
- Download file 9.9 kB
-
https://huggingface.co/executorch-community/SAM2-Hiera-Tiny-ExecuTorch-WebGPU/resolve/main/export/_sam2.py
- Command line
-
hf download hf://executorch-community/SAM2-Hiera-Tiny-ExecuTorch-WebGPU/export/_sam2.py
-
curl -L -o _sam2.py https://huggingface.co/executorch-community/SAM2-Hiera-Tiny-ExecuTorch-WebGPU/resolve/main/export/_sam2.py
9.9 kB
| #!/usr/bin/env python3 | |
| """Inspection helpers for the SAM 2 recipe (sam2-hiera-tiny.py). CPU only, read-only on inputs. | |
| _sam2.py ops A.pte [B.pte] every delegated op call with its argument types/shapes; with B, | |
| the op-signature set difference (ops/arity/ranks in A not in B) | |
| _sam2.py consts A.pte B.pte how many delegate constants of A are byte-identical in B | |
| _sam2.py io A.pte method names, program inputs/outputs, non-delegated kernels | |
| """ | |
| import collections | |
| import hashlib | |
| import json | |
| import sys | |
| def _program(data): | |
| from executorch.exir._serialize._program import deserialize_pte_binary | |
| pte = deserialize_pte_binary(data) | |
| return pte, (pte.program if hasattr(pte, "program") else pte) | |
| def _blobs(data): | |
| pte, prog = _program(data) | |
| seg_base = int.from_bytes(data[24:32], "little") | |
| out = [] | |
| for plan in prog.execution_plan: | |
| for d in plan.delegates: | |
| proc = d.processed | |
| if proc.location.name == "INLINE": | |
| out.append( | |
| ( | |
| plan.name, | |
| d.id, | |
| bytes(prog.backend_delegate_data[proc.index].data), | |
| ) | |
| ) | |
| else: | |
| seg = prog.segments[proc.index] | |
| out.append( | |
| ( | |
| plan.name, | |
| d.id, | |
| data[seg_base + seg.offset : seg_base + seg.offset + seg.size], | |
| ) | |
| ) | |
| nd = getattr(pte, "named_data", None) | |
| resolve = ( | |
| ( | |
| lambda k: bytes(nd.buffers[nd.pte_data[k].buffer_index]) | |
| if k in nd.pte_data | |
| else None | |
| ) | |
| if nd | |
| else (lambda k: None) | |
| ) | |
| return out, resolve | |
| def vk_graph(blob): | |
| from executorch.backends.vulkan.serialization.vulkan_graph_serialize import ( | |
| extract_vk_flatbuffer, | |
| flatbuffer_to_vk_graph, | |
| ) | |
| return flatbuffer_to_vk_graph(extract_vk_flatbuffer(blob)) | |
| def describe(g, vid): | |
| v = g.values[vid].value | |
| n = type(v).__name__ | |
| if n == "VkTensor": | |
| kind = "const" if v.constant_id >= 0 else "tensor" | |
| return f"{kind}{list(v.dims)}:{v.datatype.name if hasattr(v.datatype, 'name') else v.datatype}" | |
| if n == "Int": | |
| return f"Int={v.int_val}" | |
| if n == "Double": | |
| return f"Double={v.double_val:g}" | |
| if n == "Bool": | |
| return f"Bool={v.bool_val}" | |
| if n in ("IntList", "DoubleList", "BoolList"): | |
| return f"{n}={list(v.items)}" | |
| if n == "ValueList": | |
| return "ValueList[" + ", ".join(describe(g, i) for i in v.items) + "]" | |
| if n == "String": | |
| return f"String={v.string_val!r}" | |
| return n | |
| def ops(path): | |
| data = open(path, "rb").read() | |
| blobs, _ = _blobs(data) | |
| calls = [] | |
| for plan, did, blob in blobs: | |
| g = vk_graph(blob) | |
| for c in g.chain: | |
| calls.append( | |
| {"plan": plan, "op": c.name, "args": [describe(g, a) for a in c.args]} | |
| ) | |
| return calls | |
| def signature(call): | |
| """Op name + arity + per-argument kind/rank (shape-agnostic) for set comparisons.""" | |
| parts = [] | |
| for a in call["args"]: | |
| if a.startswith(("tensor[", "const[")): | |
| dims = json.loads(a[a.index("[") : a.index("]") + 1]) | |
| parts.append(f"{a.split('[')[0]}{len(dims)}d:{a.rsplit(':', 1)[1]}") | |
| else: | |
| parts.append(a.split("=")[0]) | |
| return f"{call['op']}({', '.join(parts)})" | |
| def consts(path): | |
| from executorch.backends.vulkan.serialization.vulkan_graph_serialize import ( | |
| VulkanDelegateHeader, | |
| ) | |
| data = open(path, "rb").read() | |
| blobs, resolve = _blobs(data) | |
| out = [] | |
| for plan, did, blob in blobs: | |
| h = VulkanDelegateHeader.from_bytes( | |
| blob[: VulkanDelegateHeader.EXPECTED_LENGTH] | |
| ) | |
| g = vk_graph(blob) | |
| raw = blob[h.bytes_offset : h.bytes_offset + h.bytes_size] | |
| for v in g.values: | |
| t = v.value | |
| if type(t).__name__ != "VkTensor" or t.constant_id < 0: | |
| continue | |
| c = g.constants[t.constant_id] | |
| b = ( | |
| resolve(c.named_key) | |
| if c.named_key | |
| else raw[c.offset : c.offset + c.length] | |
| ) | |
| out.append( | |
| { | |
| "dims": list(t.dims), | |
| "sha": hashlib.sha256(b).hexdigest(), | |
| "bytes": len(b), | |
| } | |
| ) | |
| return out | |
| def io(path): | |
| data = open(path, "rb").read() | |
| _, prog = _program(data) | |
| res = {"file": path, "bytes": len(data), "methods": {}} | |
| for plan in prog.execution_plan: | |
| def d(i): | |
| v = plan.values[i].val | |
| return ( | |
| f"{list(v.sizes)}:{v.scalar_type.name}" | |
| if hasattr(v, "sizes") | |
| else type(v).__name__ | |
| ) | |
| names = [f"{o.name}.{o.overload}" for o in plan.operators] | |
| kernels = collections.Counter( | |
| names[ins.instr_args.op_index] | |
| for ch in plan.chains | |
| for ins in ch.instructions | |
| if type(ins.instr_args).__name__ == "KernelCall" | |
| ) | |
| res["methods"][plan.name] = { | |
| "inputs": [d(i) for i in plan.inputs], | |
| "outputs": [d(i) for i in plan.outputs], | |
| "delegates": [x.id for x in plan.delegates], | |
| "non_delegated_kernels": dict(kernels), | |
| } | |
| return res | |
| OFFICIAL = { | |
| "sam2.1": ("facebook/sam2.1-hiera-tiny", "de431c4043854a71d8101e17995dfe596bf101a5", "sam2.1_hiera_tiny.pt", | |
| "7402e0d864fa82708a20fbd15bc84245c2f26dff0eb43a4b5b93452deb34be69"), | |
| } | |
| def verify_official(weights="sam2.1"): | |
| import importlib.util | |
| import pathlib | |
| import tempfile | |
| import torch | |
| from huggingface_hub import hf_hub_download | |
| from safetensors.torch import load_file | |
| here = pathlib.Path(__file__).resolve().parent | |
| spec = importlib.util.spec_from_file_location("sam2_recipe", here / "sam2-hiera-tiny.py") | |
| recipe = importlib.util.module_from_spec(spec) | |
| spec.loader.exec_module(recipe) | |
| snap, digest = recipe.snapshot(weights) | |
| hf = load_file(str(snap / "model.safetensors")) | |
| repo, rev, name, want = OFFICIAL[weights] | |
| with tempfile.TemporaryDirectory() as tmp: | |
| p = hf_hub_download(repo, name, revision=rev, local_dir=tmp) | |
| got = hashlib.sha256(open(p, "rb").read()).hexdigest() | |
| if got != want: | |
| sys.exit(f"{name}: sha256 {got} != {want}") | |
| orig = torch.load(p, map_location="cpu", weights_only=True)["model"] | |
| h = lambda t: hashlib.sha256(t.detach().contiguous().numpy().tobytes()).hexdigest() # noqa: E731 | |
| pool = {h(v): k for k, v in orig.items()} | |
| matched, unmatched = 0, [] | |
| for k, v in hf.items(): | |
| if h(v) in pool: | |
| matched += 1 | |
| else: | |
| unmatched.append(k) | |
| notes = {} | |
| if "prompt_encoder.point_embed.weight" in unmatched: | |
| pe = hf["prompt_encoder.point_embed.weight"] | |
| if all(torch.equal(pe[i], orig[f"sam_prompt_encoder.point_embeddings.{i}.weight"][0]) for i in range(pe.shape[0])): | |
| unmatched.remove("prompt_encoder.point_embed.weight") | |
| matched += 1 | |
| notes["prompt_encoder.point_embed.weight"] = "rows == sam_prompt_encoder.point_embeddings.{0..3}.weight" | |
| return {"hf": f"{repo}@{rev}/model.safetensors", "hf_sha256": digest, "official": f"{repo}@{rev}/{name}", | |
| "official_sha256": want, "hf_tensors": len(hf), "official_tensors": len(orig), | |
| "hf_tensors_bit_identical_to_official": matched, "unmatched": unmatched, "notes": notes} | |
| def main(): | |
| cmd, args = sys.argv[1], sys.argv[2:] | |
| if cmd == "io": | |
| print(json.dumps(io(args[0]), indent=1)) | |
| elif cmd == "ops": | |
| a = ops(args[0]) | |
| if len(args) == 1: | |
| for c in a: | |
| print(c["op"], ", ".join(c["args"])) | |
| return | |
| b = ops(args[1]) | |
| sa, sb = ( | |
| collections.Counter(map(signature, a)), | |
| collections.Counter(map(signature, b)), | |
| ) | |
| print( | |
| json.dumps( | |
| { | |
| "ops_count": [sum(sa.values()), sum(sb.values())], | |
| "op_types_only_in_a": sorted( | |
| {c["op"] for c in a} - {c["op"] for c in b} | |
| ), | |
| "op_types_only_in_b": sorted( | |
| {c["op"] for c in b} - {c["op"] for c in a} | |
| ), | |
| "signatures_only_in_a": { | |
| k: v for k, v in sorted(sa.items()) if k not in sb | |
| }, | |
| "signatures_only_in_b": { | |
| k: v for k, v in sorted(sb.items()) if k not in sa | |
| }, | |
| }, | |
| indent=1, | |
| ) | |
| ) | |
| elif cmd == "verify-official": | |
| print(json.dumps(verify_official(*(args or ["sam2.1"])), indent=1)) | |
| elif cmd == "consts": | |
| a, b = consts(args[0]), consts(args[1]) | |
| hb, ha = ( | |
| collections.Counter(c["sha"] for c in b), | |
| collections.Counter(c["sha"] for c in a), | |
| ) | |
| print( | |
| json.dumps( | |
| { | |
| "a_constants": len(a), | |
| "b_constants": len(b), | |
| "a_found_in_b": sum(c["sha"] in hb for c in a), | |
| "b_found_in_a": sum(c["sha"] in ha for c in b), | |
| "a_missing": [c["dims"] for c in a if c["sha"] not in hb], | |
| "b_missing": [c["dims"] for c in b if c["sha"] not in ha], | |
| }, | |
| indent=1, | |
| ) | |
| ) | |
| else: | |
| sys.exit(__doc__) | |
| if __name__ == "__main__": | |
| main() | |