Kaloscope-onnx / convert_scripts /kaloscope_pth2onnx.py
DraconicDragon's picture
Update convert_scripts/kaloscope_pth2onnx.py
ea93bd5
Raw
History Blame
6.33 kB
import importlib
import os
import sys
import onnxruntime as ort
import torch
# CONFIG SECTION
config = {
# path to .pth kaloscope lsnet file
"checkpoint": "/content/Kaloscope/best_checkpoint.pth",
# Model factory name inside model/lsnet_artist.py
# (examples: lsnet_t_artist, lsnet_s_artist, lsnet_b_artist, lsnet_xl_artist)
# lsnet_xl_artist is the one use for kaloscope initial release apparently
"model_name": "lsnet_xl_artist",
# where to save exported ONNX model
"onnx_output": "/content/lsnet_xl_artist-dynamo-opset20.onnx",
# Image input size (height, width) - derived from model repo readme
"img_size": (224, 224),
# ONNX opset version - ignore this, set it below in torch.onnx.export()
"opset": 18,
"device": "cpu",
}
# ------------------------------------------------------------------------------------
def strip_prefix_from_state_dict(state_dict, prefix="module."):
new_state = {}
for k, v in state_dict.items():
if k.startswith(prefix):
new_state[k[len(prefix) :]] = v
else:
new_state[k] = v
return new_state
def load_checkpoint(checkpoint_path, map_location="cpu"):
ck = torch.load(checkpoint_path, map_location=map_location, weights_only=False)
if isinstance(ck, dict):
for key in ("state_dict", "model_state_dict", "model"):
if key in ck:
return ck[key], ck
return ck, ck
else:
return None, ck
def main(cfg):
# module names might be wrong here but the files/modules are in lsnet folder in same dir as this file
modules_to_delete = ["lsnet.lsnet_artist", "lsnet.ska"]
for module_name in modules_to_delete:
if module_name in sys.modules:
print(f"Forcefully deleting cached module: {module_name}")
del sys.modules[module_name]
repo_root = os.getcwd()
if repo_root not in sys.path:
sys.path.insert(0, repo_root)
model_module_name = "lsnet.lsnet_artist"
try:
mod = importlib.import_module(model_module_name)
# originally intended to make google colab not cache this but doesnt work as expected
importlib.reload(mod)
if "lsnet.ska" in sys.modules:
importlib.reload(sys.modules["lsnet.ska"])
except Exception as e:
print(f"WARNING - could not import {model_module_name}: {e}")
mod = None
model = None
if mod is not None and hasattr(mod, cfg["model_name"]):
factory = getattr(mod, cfg["model_name"])
if callable(factory):
try:
# Attempt to instantiate with the number of classes from your checkpoint
model = factory(num_classes=31770)
print(f"Instantiated {cfg['model_name']}(num_classes=31770)")
except Exception as e:
print(f"Failed to instantiate model with 31770 classes: {e}")
# Fallback to the original logic if the above fails
try:
model = factory()
print(f"Instantiated model using {model_module_name}.{cfg['model_name']}()")
except TypeError:
for ncls in [1000, 100]:
try:
model = factory(num_classes=ncls)
print(f"Instantiated {cfg['model_name']}(num_classes={ncls})")
break
except Exception:
pass
if model is None:
print("Model instance not created from source; will try to load pickled model from checkpoint.")
state_dict, full_ck = load_checkpoint(cfg["checkpoint"], map_location=cfg["device"])
if state_dict is None and isinstance(full_ck, torch.nn.Module):
print("Checkpoint is a pickled model object - exporting directly.")
model = full_ck
else:
if model is None:
if isinstance(full_ck, dict):
for k in ("model", "net", "module"):
if k in full_ck and isinstance(full_ck[k], torch.nn.Module):
model = full_ck[k]
print(f'Using model found in checkpoint["{k}"]')
break
if model is not None and state_dict is not None:
state_dict = strip_prefix_from_state_dict(state_dict)
try:
model.load_state_dict(state_dict, strict=False)
print("Loaded state_dict into model (strict=False).")
except Exception as e:
print("Warning: load_state_dict failed:", e)
elif state_dict is not None and model is None:
raise RuntimeError("Found a state_dict but no model instance to load it into.")
device = torch.device(cfg["device"])
model.to(device)
model.eval()
h, w = cfg["img_size"]
dummy_input = torch.randn(1, 3, h, w, device=device)
onnx_path = cfg["onnx_output"]
print("Exporting to ONNX ->", onnx_path)
try:
torch.onnx.export(
model,
dummy_input,
onnx_path,
input_names=["input"],
output_names=["output"],
# opset_version=cfg["opset"],
opset_version=None, # leaving none for auto recommended version, see print(torch.onnx._constants.ONNX_DEFAULT_OPSET) eg torch 2.8.0 = 18 and 2.9.0 = 20 with up to 23 supported
# if you want dynamic axes, re-enable this AND set dynamo=False
# dynamic_axes={
# "input": {0: "batch_size", 2: "height", 3: "width"},
# "output": {0: "batch_size"},
# },
verbose=False,
do_constant_folding=True,
optimize=True,
# dynamo here
dynamo=True,
)
print("ONNX export completed.")
except Exception as e:
print("ONNX export failed:", e)
raise
print("Verifying ONNX model with onnxruntime...")
sess = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"])
inp_name = sess.get_inputs()[0].name
out = sess.run(None, {inp_name: dummy_input.cpu().numpy()})
print("ONNX runtime produced output shapes:", [o.shape for o in out])
print("Done. Saved:", onnx_path)
if __name__ == "__main__":
main(config)