#!/usr/bin/env python3 """Convert a Qwen3-VL HF safetensors checkpoint into a sd.cpp-loadable form. The HF dump prefixes text-tower keys with ``model.language_model.`` and vision-tower keys with ``model.visual.``. sd.cpp expects ``model.`` for the text side; the vision side is converted by sd.cpp's own ``convert_qwen3_vl_vision_name`` and is left as-is here. Operates on raw safetensors bytes so any dtype (BF16/F16/F32) is preserved. Usage: python3 script/convert_qwen3_vl.py """ import argparse import json import os import struct import sys def rewrite_key(key: str) -> str: if key.startswith("model.language_model."): return "model." + key[len("model.language_model."):] return key def read_safetensors_header(path: str): with open(path, "rb") as f: hdr_len = struct.unpack(" 0: chunk = src.read(min(8 * 1024 * 1024, remaining)) if not chunk: raise IOError(f"Truncated tensor in {shard_path}") out.write(chunk) remaining -= len(chunk) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("input", help="HF Qwen3-VL directory or single safetensors file") parser.add_argument("output", help="Output single safetensors path") args = parser.parse_args() entries = stage_tensors(args.input) print(f"Tensors: {len(entries)}") print(f"Writing -> {args.output}") os.makedirs(os.path.dirname(args.output) or ".", exist_ok=True) write_consolidated(args.output, entries) print(f"Done. Output size: {os.path.getsize(args.output) / 1e9:.2f} GB") if __name__ == "__main__": main()