File size: 2,622 Bytes
e53235c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/env python3
"""Convert SCRFD det_10g.onnx from FP32 to FP16.

Usage:
    pip install onnx onnxconverter-common
    python scripts/convert_scrfd_fp16.py \
        --input /path/to/det_10g.onnx \
        --output /path/to/det_10g_fp16.onnx

Design decisions:
- op_block_list=['BatchNormalization'] — epsilon 1e-5 underflows to 0 in FP16 → NaN.
  Keeping BN in FP32 prevents this while still converting ~95% of ops to FP16.
- keep_io_types=True — Input/output remain float32 for compatibility.
  No preprocessing changes needed in SCRFD pipeline.
- onnx.checker validates structural integrity after conversion.
"""

import argparse
import sys
from pathlib import Path


def convert_fp16(input_path: str, output_path: str) -> None:
    """Convert ONNX model from FP32 to FP16."""
    try:
        import onnx
        from onnxconverter_common import float16
    except ImportError:
        print("Missing dependencies. Install:")
        print("  pip install onnx onnxconverter-common")
        sys.exit(1)

    input_file = Path(input_path)
    if not input_file.exists():
        print(f"Input file not found: {input_path}")
        sys.exit(1)

    print(f"Loading {input_path} ...")
    model = onnx.load(input_path)

    input_size_mb = input_file.stat().st_size / (1024 * 1024)
    print(f"  Input size: {input_size_mb:.1f} MB")
    print(f"  Opset version: {model.opset_import[0].version}")

    # Convert to FP16 with BatchNormalization excluded
    # BN epsilon (1e-5) underflows to 0 in FP16 → division by zero → NaN
    print("Converting to FP16 (excluding BatchNormalization) ...")
    model_fp16 = float16.convert_float_to_float16(
        model,
        op_block_list=["BatchNormalization"],
        keep_io_types=True,
    )

    # Validate
    print("Validating converted model ...")
    onnx.checker.check_model(model_fp16)

    # Save
    output_file = Path(output_path)
    output_file.parent.mkdir(parents=True, exist_ok=True)
    onnx.save(model_fp16, output_path)

    output_size_mb = output_file.stat().st_size / (1024 * 1024)
    ratio = output_size_mb / input_size_mb * 100
    print(f"  Output size: {output_size_mb:.1f} MB ({ratio:.0f}% of original)")
    print(f"  Saved to: {output_path}")
    print("Done.")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Convert SCRFD det_10g.onnx FP32 → FP16")
    parser.add_argument("--input", required=True, help="Path to FP32 det_10g.onnx")
    parser.add_argument("--output", required=True, help="Output path for FP16 model")
    args = parser.parse_args()

    convert_fp16(args.input, args.output)