Files
remove-ai-watermarks/scripts/export_photo_classify_onnx.py
T
Victor KuznetsovandClaude Opus 5.5 06c0cb1bbe Add Codex plugin manifest, stdlib MCP server, SHA-pinned Actions
- .codex-plugin/plugin.json and assets/icon.svg package the existing skill
  tree for Codex plugin catalogs; skill version 1.0.12 across all manifests
- `remove-ai-watermarks mcp` serves identify, remove and batch over stdio,
  one CLI command per tool call, standard library only; server.json and
  glama.json for the MCP registry and Glama
- Pin every GitHub Action to a commit SHA
- Call module.train(False) instead of module.eval(), which plugin scanners
  read as dynamic code execution

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 14:13:04 -07:00

86 lines
2.8 KiB
Python

#!/usr/bin/env python3
"""Export the frozen CLIP-L vision tower as a static FP32 ONNX graph.
Run from the repository root with the ONNX exporter installed temporarily:
uv run --with onnx python scripts/export_photo_classify_onnx.py \
--checkpoint /path/to/clip-l-ft.pt \
--out /path/to/clip-l-ft-vision-fp32.onnx
"""
from __future__ import annotations
import argparse
import hashlib
import inspect
import logging
import sys
from pathlib import Path
from typing import Any
import torch
from torch import nn
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
log = logging.getLogger(__name__)
class VisionEmbedding(nn.Module):
"""Only the vision modules used by the classifier's embedding path."""
def __init__(self, headed: Any) -> None:
super().__init__()
self.vision_model = headed.clip.vision_model
self.visual_projection = headed.clip.visual_projection
def forward(self, pixel_values: Any) -> Any:
pooled = self.vision_model(pixel_values=pixel_values).pooler_output
return nn.functional.normalize(self.visual_projection(pooled), dim=-1)
def export_onnx(checkpoint: Path, output: Path) -> None:
"""Write the static batch-one graph consumed by the ONNX runtime backend."""
from remove_ai_watermarks._internal.clip_l_ft import SIZE, load_headed_clip
checkpoint = checkpoint.expanduser().resolve()
output = output.expanduser().resolve()
if not checkpoint.is_file():
raise SystemExit(f"missing checkpoint: {checkpoint}")
if output.exists():
raise SystemExit(f"refusing to overwrite existing output: {output}")
output.parent.mkdir(parents=True, exist_ok=True)
model = VisionEmbedding(load_headed_clip(checkpoint, torch.device("cpu"))).train(False)
export_options: dict[str, Any] = {}
if "dynamo" in inspect.signature(torch.onnx.export).parameters:
export_options["dynamo"] = False
with torch.inference_mode():
torch.onnx.export(
model,
(torch.zeros(1, 3, SIZE, SIZE, dtype=torch.float32),),
output,
input_names=["pixel_values"],
output_names=["embedding"],
opset_version=17,
do_constant_folding=True,
**export_options,
)
with output.open("rb") as handle:
digest = hashlib.file_digest(handle, "sha256").hexdigest()
log.info("wrote %s bytes=%d sha256=%s", output, output.stat().st_size, digest)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--checkpoint", type=Path, required=True)
parser.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
logging.basicConfig(level=logging.INFO, format="%(message)s")
export_onnx(args.checkpoint, args.out)
return 0
if __name__ == "__main__":
raise SystemExit(main())