Fix TFLite INT8 quant bug (#13082)
This commit is contained in:
parent
cb99f71728
commit
11623eeb00
4 changed files with 32 additions and 37 deletions
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
|
|
@ -164,7 +164,7 @@ jobs:
|
||||||
|
|
||||||
Tests:
|
Tests:
|
||||||
if: github.event_name != 'workflow_dispatch' || github.event.inputs.tests == 'true'
|
if: github.event_name != 'workflow_dispatch' || github.event.inputs.tests == 'true'
|
||||||
timeout-minutes: 60
|
timeout-minutes: 120
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
@ -241,7 +241,7 @@ jobs:
|
||||||
|
|
||||||
RaspberryPi:
|
RaspberryPi:
|
||||||
if: github.repository == 'ultralytics/ultralytics' && (github.event_name == 'schedule' || github.event.inputs.raspberrypi == 'true')
|
if: github.repository == 'ultralytics/ultralytics' && (github.event_name == 'schedule' || github.event.inputs.raspberrypi == 'true')
|
||||||
timeout-minutes: 60
|
timeout-minutes: 120
|
||||||
runs-on: raspberry-pi
|
runs-on: raspberry-pi
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
@ -253,7 +253,7 @@ jobs:
|
||||||
- name: Install requirements
|
- name: Install requirements
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip wheel
|
python -m pip install --upgrade pip wheel
|
||||||
pip install -e ".[export]" pytest mlflow pycocotools "ray[tune]"
|
pip install -e ".[export]" pytest
|
||||||
- name: Check environment
|
- name: Check environment
|
||||||
run: |
|
run: |
|
||||||
yolo checks
|
yolo checks
|
||||||
|
|
|
||||||
|
|
@ -23,22 +23,22 @@ from tests import MODEL, SOURCE
|
||||||
|
|
||||||
def test_export_torchscript():
|
def test_export_torchscript():
|
||||||
"""Test YOLO exports to TorchScript format."""
|
"""Test YOLO exports to TorchScript format."""
|
||||||
f = YOLO(MODEL).export(format="torchscript", optimize=False, imgsz=32)
|
file = YOLO(MODEL).export(format="torchscript", optimize=False, imgsz=32)
|
||||||
YOLO(f)(SOURCE, imgsz=32) # exported model inference
|
YOLO(file)(SOURCE, imgsz=32) # exported model inference
|
||||||
|
|
||||||
|
|
||||||
def test_export_onnx():
|
def test_export_onnx():
|
||||||
"""Test YOLO exports to ONNX format."""
|
"""Test YOLO exports to ONNX format."""
|
||||||
f = YOLO(MODEL).export(format="onnx", dynamic=True, imgsz=32)
|
file = YOLO(MODEL).export(format="onnx", dynamic=True, imgsz=32)
|
||||||
YOLO(f)(SOURCE, imgsz=32) # exported model inference
|
YOLO(file)(SOURCE, imgsz=32) # exported model inference
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(checks.IS_PYTHON_3_12, reason="OpenVINO not supported in Python 3.12")
|
@pytest.mark.skipif(checks.IS_PYTHON_3_12, reason="OpenVINO not supported in Python 3.12")
|
||||||
@pytest.mark.skipif(not TORCH_1_13, reason="OpenVINO requires torch>=1.13")
|
@pytest.mark.skipif(not TORCH_1_13, reason="OpenVINO requires torch>=1.13")
|
||||||
def test_export_openvino():
|
def test_export_openvino():
|
||||||
"""Test YOLO exports to OpenVINO format."""
|
"""Test YOLO exports to OpenVINO format."""
|
||||||
f = YOLO(MODEL).export(format="openvino", imgsz=32)
|
file = YOLO(MODEL).export(format="openvino", imgsz=32)
|
||||||
YOLO(f)(SOURCE, imgsz=32) # exported model inference
|
YOLO(file)(SOURCE, imgsz=32) # exported model inference
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
|
|
@ -118,7 +118,7 @@ def test_export_torchscript_matrix(task, dynamic, int8, half, batch):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_export_coreml_matrix(task, dynamic, int8, half, batch):
|
def test_export_coreml_matrix(task, dynamic, int8, half, batch):
|
||||||
"""Test YOLO exports to TorchScript format."""
|
"""Test YOLO exports to CoreML format."""
|
||||||
file = YOLO(TASK2MODEL[task]).export(
|
file = YOLO(TASK2MODEL[task]).export(
|
||||||
format="coreml",
|
format="coreml",
|
||||||
imgsz=32,
|
imgsz=32,
|
||||||
|
|
@ -138,8 +138,8 @@ def test_export_coreml_matrix(task, dynamic, int8, half, batch):
|
||||||
def test_export_coreml():
|
def test_export_coreml():
|
||||||
"""Test YOLO exports to CoreML format."""
|
"""Test YOLO exports to CoreML format."""
|
||||||
if MACOS:
|
if MACOS:
|
||||||
f = YOLO(MODEL).export(format="coreml", imgsz=32)
|
file = YOLO(MODEL).export(format="coreml", imgsz=32)
|
||||||
YOLO(f)(SOURCE, imgsz=32) # model prediction only supported on macOS for nms=False models
|
YOLO(file)(SOURCE, imgsz=32) # model prediction only supported on macOS for nms=False models
|
||||||
else:
|
else:
|
||||||
YOLO(MODEL).export(format="coreml", nms=True, imgsz=32)
|
YOLO(MODEL).export(format="coreml", nms=True, imgsz=32)
|
||||||
|
|
||||||
|
|
@ -152,8 +152,8 @@ def test_export_tflite():
|
||||||
Note TF suffers from install conflicts on Windows and macOS.
|
Note TF suffers from install conflicts on Windows and macOS.
|
||||||
"""
|
"""
|
||||||
model = YOLO(MODEL)
|
model = YOLO(MODEL)
|
||||||
f = model.export(format="tflite", imgsz=32)
|
file = model.export(format="tflite", imgsz=32)
|
||||||
YOLO(f)(SOURCE, imgsz=32)
|
YOLO(file)(SOURCE, imgsz=32)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason="Test disabled")
|
@pytest.mark.skipif(True, reason="Test disabled")
|
||||||
|
|
@ -165,8 +165,8 @@ def test_export_pb():
|
||||||
Note TF suffers from install conflicts on Windows and macOS.
|
Note TF suffers from install conflicts on Windows and macOS.
|
||||||
"""
|
"""
|
||||||
model = YOLO(MODEL)
|
model = YOLO(MODEL)
|
||||||
f = model.export(format="pb", imgsz=32)
|
file = model.export(format="pb", imgsz=32)
|
||||||
YOLO(f)(SOURCE, imgsz=32)
|
YOLO(file)(SOURCE, imgsz=32)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(True, reason="Test disabled as Paddle protobuf and ONNX protobuf requirementsk conflict.")
|
@pytest.mark.skipif(True, reason="Test disabled as Paddle protobuf and ONNX protobuf requirementsk conflict.")
|
||||||
|
|
@ -182,5 +182,5 @@ def test_export_paddle():
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_export_ncnn():
|
def test_export_ncnn():
|
||||||
"""Test YOLO exports to NCNN format."""
|
"""Test YOLO exports to NCNN format."""
|
||||||
f = YOLO(MODEL).export(format="ncnn", imgsz=32)
|
file = YOLO(MODEL).export(format="ncnn", imgsz=32)
|
||||||
YOLO(f)(SOURCE, imgsz=32) # exported model inference
|
YOLO(file)(SOURCE, imgsz=32) # exported model inference
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ from ultralytics.utils import (
|
||||||
WINDOWS,
|
WINDOWS,
|
||||||
__version__,
|
__version__,
|
||||||
callbacks,
|
callbacks,
|
||||||
|
checks,
|
||||||
colorstr,
|
colorstr,
|
||||||
get_default_args,
|
get_default_args,
|
||||||
yaml_save,
|
yaml_save,
|
||||||
|
|
@ -184,6 +185,7 @@ class Exporter:
|
||||||
if sum(flags) != 1:
|
if sum(flags) != 1:
|
||||||
raise ValueError(f"Invalid export format='{fmt}'. Valid formats are {fmts}")
|
raise ValueError(f"Invalid export format='{fmt}'. Valid formats are {fmts}")
|
||||||
jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle, ncnn = flags # export booleans
|
jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle, ncnn = flags # export booleans
|
||||||
|
is_tf_format = any((saved_model, pb, tflite, edgetpu, tfjs))
|
||||||
|
|
||||||
# Device
|
# Device
|
||||||
if fmt == "engine" and self.args.device is None:
|
if fmt == "engine" and self.args.device is None:
|
||||||
|
|
@ -243,7 +245,7 @@ class Exporter:
|
||||||
m.dynamic = self.args.dynamic
|
m.dynamic = self.args.dynamic
|
||||||
m.export = True
|
m.export = True
|
||||||
m.format = self.args.format
|
m.format = self.args.format
|
||||||
elif isinstance(m, C2f) and not any((saved_model, pb, tflite, edgetpu, tfjs)):
|
elif isinstance(m, C2f) and not is_tf_format:
|
||||||
# EdgeTPU does not support FlexSplitV while split provides cleaner ONNX graph
|
# EdgeTPU does not support FlexSplitV while split provides cleaner ONNX graph
|
||||||
m.forward = m.forward_split
|
m.forward = m.forward_split
|
||||||
|
|
||||||
|
|
@ -303,7 +305,7 @@ class Exporter:
|
||||||
f[3], _ = self.export_openvino()
|
f[3], _ = self.export_openvino()
|
||||||
if coreml: # CoreML
|
if coreml: # CoreML
|
||||||
f[4], _ = self.export_coreml()
|
f[4], _ = self.export_coreml()
|
||||||
if any((saved_model, pb, tflite, edgetpu, tfjs)): # TensorFlow formats
|
if is_tf_format: # TensorFlow formats
|
||||||
self.args.int8 |= edgetpu
|
self.args.int8 |= edgetpu
|
||||||
f[5], keras_model = self.export_saved_model()
|
f[5], keras_model = self.export_saved_model()
|
||||||
if pb or tfjs: # pb prerequisite to tfjs
|
if pb or tfjs: # pb prerequisite to tfjs
|
||||||
|
|
@ -777,11 +779,10 @@ class Exporter:
|
||||||
_ = self.cache.write_bytes(cache)
|
_ = self.cache.write_bytes(cache)
|
||||||
|
|
||||||
# Load dataset w/ builder (for batching) and calibrate
|
# Load dataset w/ builder (for batching) and calibrate
|
||||||
dataset = self.get_int8_calibration_dataloader(prefix)
|
|
||||||
config.int8_calibrator = EngineCalibrator(
|
config.int8_calibrator = EngineCalibrator(
|
||||||
dataset=dataset,
|
dataset=self.get_int8_calibration_dataloader(prefix),
|
||||||
batch=2 * self.args.batch,
|
batch=2 * self.args.batch,
|
||||||
cache=self.file.with_suffix(".cache"),
|
cache=str(self.file.with_suffix(".cache")),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif half:
|
elif half:
|
||||||
|
|
@ -813,7 +814,7 @@ class Exporter:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
suffix = "-macos" if MACOS else "-aarch64" if ARM64 else "" if cuda else "-cpu"
|
suffix = "-macos" if MACOS else "-aarch64" if ARM64 else "" if cuda else "-cpu"
|
||||||
version = "" if ARM64 else "<=2.13.1"
|
version = "" if ARM64 else "<=2.13.1"
|
||||||
check_requirements(f"tensorflow{suffix}{version}")
|
check_requirements((f"tensorflow{suffix}{version}", "keras"))
|
||||||
import tensorflow as tf # noqa
|
import tensorflow as tf # noqa
|
||||||
if ARM64:
|
if ARM64:
|
||||||
check_requirements("cmake") # 'cmake' is needed to build onnxsim on aarch64
|
check_requirements("cmake") # 'cmake' is needed to build onnxsim on aarch64
|
||||||
|
|
@ -855,24 +856,17 @@ class Exporter:
|
||||||
f_onnx, _ = self.export_onnx()
|
f_onnx, _ = self.export_onnx()
|
||||||
|
|
||||||
# Export to TF
|
# Export to TF
|
||||||
tmp_file = f / "tmp_tflite_int8_calibration_images.npy" # int8 calibration images file
|
|
||||||
np_data = None
|
np_data = None
|
||||||
if self.args.int8:
|
if self.args.int8:
|
||||||
|
tmp_file = f / "tmp_tflite_int8_calibration_images.npy" # int8 calibration images file
|
||||||
verbosity = "info"
|
verbosity = "info"
|
||||||
if self.args.data:
|
if self.args.data:
|
||||||
# Generate calibration data for integer quantization
|
|
||||||
dataloader = self.get_int8_calibration_dataloader(prefix)
|
|
||||||
images = []
|
|
||||||
for i, batch in enumerate(dataloader):
|
|
||||||
if i >= 100: # maximum number of calibration images
|
|
||||||
break
|
|
||||||
im = batch["img"].permute(1, 2, 0)[None] # list to nparray, CHW to BHWC
|
|
||||||
images.append(im)
|
|
||||||
f.mkdir()
|
f.mkdir()
|
||||||
|
images = [batch["img"].permute(0, 2, 3, 1) for batch in self.get_int8_calibration_dataloader(prefix)]
|
||||||
images = torch.cat(images, 0).float()
|
images = torch.cat(images, 0).float()
|
||||||
# mean = images.view(-1, 3).mean(0) # imagenet mean [123.675, 116.28, 103.53]
|
# mean = images.view(-1, 3).mean(0) # imagenet mean [123.675, 116.28, 103.53]
|
||||||
# std = images.view(-1, 3).std(0) # imagenet std [58.395, 57.12, 57.375]
|
# std = images.view(-1, 3).std(0) # imagenet std [58.395, 57.12, 57.375]
|
||||||
np.save(str(tmp_file), images.numpy()) # BHWC
|
np.save(str(tmp_file), images.numpy().astype(np.float32)) # BHWC
|
||||||
np_data = [["images", tmp_file, [[[[0, 0, 0]]]], [[[[255, 255, 255]]]]]]
|
np_data = [["images", tmp_file, [[[[0, 0, 0]]]], [[[[255, 255, 255]]]]]]
|
||||||
else:
|
else:
|
||||||
verbosity = "error"
|
verbosity = "error"
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ from ultralytics.utils import (
|
||||||
ASSETS,
|
ASSETS,
|
||||||
AUTOINSTALL,
|
AUTOINSTALL,
|
||||||
IS_COLAB,
|
IS_COLAB,
|
||||||
IS_DOCKER,
|
|
||||||
IS_JUPYTER,
|
IS_JUPYTER,
|
||||||
IS_KAGGLE,
|
IS_KAGGLE,
|
||||||
IS_PIP_PACKAGE,
|
IS_PIP_PACKAGE,
|
||||||
|
|
@ -322,17 +321,18 @@ def check_font(font="Arial.ttf"):
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
|
||||||
def check_python(minimum: str = "3.8.0") -> bool:
|
def check_python(minimum: str = "3.8.0", hard: bool = True) -> bool:
|
||||||
"""
|
"""
|
||||||
Check current python version against the required minimum version.
|
Check current python version against the required minimum version.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
minimum (str): Required minimum version of python.
|
minimum (str): Required minimum version of python.
|
||||||
|
hard (bool, optional): If True, raise an AssertionError if the requirement is not met.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(bool): Whether the installed Python version meets the minimum constraints.
|
(bool): Whether the installed Python version meets the minimum constraints.
|
||||||
"""
|
"""
|
||||||
return check_version(PYTHON_VERSION, minimum, name="Python ", hard=True)
|
return check_version(PYTHON_VERSION, minimum, name="Python", hard=hard)
|
||||||
|
|
||||||
|
|
||||||
@TryExcept()
|
@TryExcept()
|
||||||
|
|
@ -735,4 +735,5 @@ def cuda_is_available() -> bool:
|
||||||
|
|
||||||
|
|
||||||
# Define constants
|
# Define constants
|
||||||
|
IS_PYTHON_MINIMUM_3_10 = check_python("3.10", hard=False)
|
||||||
IS_PYTHON_3_12 = PYTHON_VERSION.startswith("3.12")
|
IS_PYTHON_3_12 = PYTHON_VERSION.startswith("3.12")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue