Voice et bot modif

This commit is contained in:
pi 2026-06-16 17:09:34 +00:00
parent 189d56026b
commit 7333a22bcd
10774 changed files with 634644 additions and 933308 deletions

View file

@ -353,6 +353,14 @@ class MinMaxCalibrater(CalibraterBase):
return opset_import.version
raise RuntimeError(f"Model does not contain a version for '{op_type}'.")
def insert_nodes(tensor_name, new_nodes):
index = next(
(i for i, x in enumerate(self.model.graph.node) if tensor_name in x.input), len(self.model.graph.node)
)
for node in new_nodes:
self.model.graph.node.insert(index, node)
index += 1
def add_reduce_min_max(tensor_name, reduce_op_name):
# When doing ReduceMax/ReduceMin, ORT can't reduce on dim with value of 0 if 'keepdims' is false.
# To make the code simple, we always let keepdims to be 1.
@ -396,7 +404,7 @@ class MinMaxCalibrater(CalibraterBase):
reduce_node.input.append(reduce_axes_name)
self.model.graph.initializer.append(reduce_axes)
self.model.graph.node.extend([reduce_node, reshape_node])
insert_nodes(tensor_name, [reduce_node, reshape_node])
self.model.graph.output.append(helper.make_tensor_value_info(reduce_output, onnx_type, [None]))
for tensor in tensors:
@ -417,7 +425,14 @@ class MinMaxCalibrater(CalibraterBase):
inputs = data_reader.get_next()
if not inputs:
break
self.intermediate_outputs.append(self.infer_session.run(None, inputs))
self.intermediate_outputs.append(
[
value if sess_o.name not in self.model_original_outputs else None
for sess_o, value in zip(
self.infer_session.get_outputs(), self.infer_session.run(None, inputs), strict=False
)
]
)
if (
self.max_intermediate_outputs is not None
and len(self.intermediate_outputs) == self.max_intermediate_outputs

View file

@ -6,15 +6,15 @@
from __future__ import annotations
import logging
import tempfile
from pathlib import Path
import onnx
from ....tools.onnx_model_utils import fix_output_shapes, make_input_shape_fixed
from ....tools.onnx_model_utils import fix_output_shapes, make_input_shape_fixed, optimize_model
from ....tools.remove_initializer_from_input import remove_initializer_from_input
from ...fusions import FusionGelu, FusionLayerNormalization
from ...onnx_model import ONNXModel
from ...quant_utils import save_and_reload_model_with_shape_infer
from .fusion_lpnorm import FusionLpNormalization
from .fusion_spacetodepth import FusionSpaceToDepth
@ -93,7 +93,7 @@ def qnn_preprocess_model(
"""
modified = False
model = model_input if isinstance(model_input, onnx.ModelProto) else onnx.load_model(model_input)
model = save_and_reload_model_with_shape_infer(model)
model = save_and_reload_optimize_model(model, shape_infer=True)
onnx_model = ONNXModel(model)
# Optionally, fix the dynamic input shapes.
@ -178,6 +178,24 @@ def qnn_preprocess_model(
return modified
def save_and_reload_optimize_model(model: onnx.ModelProto, shape_infer: bool) -> onnx.ModelProto:
with tempfile.TemporaryDirectory(prefix="ort.qnn_preproc.") as qnn_preproc_tmp_dir:
model_in_path = Path(qnn_preproc_tmp_dir).joinpath("qnn_proc_input.onnx")
onnx.save_model(model, model_in_path, save_as_external_data=True)
if shape_infer:
model_infer_path = Path(qnn_preproc_tmp_dir).joinpath("qnn_proc_infer.onnx")
onnx.shape_inference.infer_shapes_path(str(model_in_path), str(model_infer_path))
model_in_path = model_infer_path
model_out_path = Path(qnn_preproc_tmp_dir).joinpath("qnn_proc_output.onnx")
optimize_model(model_in_path, model_out_path)
ret_model = onnx.load_model(model_out_path)
ret_metaprops = {"onnx.infer": "onnxruntime.tools.qnn.preprocess"}
if ret_model.metadata_props:
ret_metaprops.update(ret_model.metadata_props)
onnx.helper.set_model_props(ret_model, ret_metaprops)
return ret_model
class InputOutputNameMap:
def __init__(
self,

View file

@ -331,23 +331,6 @@ class QnnCompatibilityOverrides:
if not self.per_channel:
self._make_static_inputs_use_default_weight_type(node)
return
has_weight_no_overrides = node.input[1] in self.initializers and node.input[1] not in self.overrides
has_bias_no_overrides = (
len(node.input) > 2
and node.input[2]
and node.input[2] in self.initializers
and node.input[2] not in self.overrides
)
if has_weight_no_overrides or has_bias_no_overrides:
# TODO: Make bias input not per-channel. QNN needs it to be per-tensor, but quantizer
# tries to makes it per-channel if the weight is also per-channel.
raise ValueError(
"get_qnn_qdq_config() does not currently support the global per_channel option with LayerNormalization."
" Please try using custom overrides that make bias per-tensor quantized."
)
def _process_sigmoid(self, node: onnx.NodeProto):
"""

View file

@ -33,6 +33,16 @@ class FusionLayerNormalization(Fusion):
| |
+-------------------------------------------------+
Or, using Mul instead of Pow:
+----------------------+
| |
| v
[Root] --> ReduceMean --> Sub --> Mul --> ReduceMean --> Add --> Sqrt --> Div --> Mul --> Add
(axis=2 or -1) | (in0=in1) (axis=2 or -1) (E-6 or E-12 or 0) ^
| |
+-------------------------------------------------+
It also handles cases of duplicated sub nodes exported from older version of PyTorch:
+----------------------+
@ -40,7 +50,7 @@ class FusionLayerNormalization(Fusion):
| +-------> Sub-----------------------------------------------+
| | |
| | v
[Root] --> ReduceMean --> Sub --> Pow --> ReduceMean --> Add --> Sqrt --> Div --> Mul --> Add
[Root] --> ReduceMean --> Sub --> (Pow or Mul) --> ReduceMean --> Add --> Sqrt --> Div --> Mul --> Add
| ^
| |
+----------------------+
@ -70,10 +80,9 @@ class FusionLayerNormalization(Fusion):
div_node,
[
(["Sqrt", "Add", "ReduceMean", "Pow", "Sub"], [1, 0, 0, 0, 0]),
(
["Sqrt", "Add", "ReduceMean", "Pow", "Cast", "Sub"],
[1, 0, 0, 0, 0, 0],
),
(["Sqrt", "Add", "ReduceMean", "Pow", "Cast", "Sub"], [1, 0, 0, 0, 0, 0]),
(["Sqrt", "Add", "ReduceMean", "Mul", "Sub"], [1, 0, 0, 0, 0]),
(["Sqrt", "Add", "ReduceMean", "Mul", "Cast", "Sub"], [1, 0, 0, 0, 0, 0]),
],
output_name_to_node,
)
@ -90,8 +99,10 @@ class FusionLayerNormalization(Fusion):
# Skip fusion since epsilon value is not expected.
return
pow_node = parent_nodes[3]
if self.find_constant_input(pow_node, 2.0) != 1:
pow_or_mul_node = parent_nodes[3]
if pow_or_mul_node.op_type == "Pow" and self.find_constant_input(pow_or_mul_node, 2.0) != 1:
return
elif pow_or_mul_node.op_type == "Mul" and pow_or_mul_node.input[0] != pow_or_mul_node.input[1]:
return
mul_node = input_name_to_nodes[div_node.output[0]][0]

View file

@ -11,12 +11,19 @@ import copy
import logging
import os
import ml_dtypes
import numpy as np
import numpy.typing as npt
import onnx
import onnx_ir as ir
from onnx.onnx_pb import GraphProto, ModelProto, NodeProto, TensorProto
from onnxruntime.capi._pybind_state import quantize_matmul_4bits, quantize_matmul_8bits, quantize_qdq_matmul_4bits
from onnxruntime.capi._pybind_state import (
quantize_matmul_2bits,
quantize_matmul_4bits,
quantize_matmul_8bits,
quantize_qdq_matmul_4bits,
)
from .calibrate import CalibrationDataReader
from .neural_compressor import gptq_quantize, rtn_quantize
@ -816,9 +823,13 @@ class DefaultWeightOnlyQuantizer:
# block wise quantization, each block comes from a single column
packed = np.zeros((cols, k_blocks, blob_size), dtype="uint8")
zero_point = np.zeros(cols * ((k_blocks + kpack - 1) // kpack), dtype="uint8")
scales = np.zeros((cols * k_blocks), dtype=fp32weight.dtype)
if qbits == 8:
zero_point = np.zeros((cols, ((k_blocks + kpack - 1) // kpack)), dtype="uint8")
scales = np.zeros((cols, k_blocks), dtype=fp32weight.dtype)
if qbits == 2:
quantize_matmul_2bits(
packed, fp32weight, scales, zero_point, block_size, cols, rows, self.config.is_symmetric
)
elif qbits == 8:
quantize_matmul_8bits(
packed, fp32weight, scales, zero_point, block_size, cols, rows, self.config.is_symmetric
)
@ -857,21 +868,27 @@ class DefaultWeightOnlyQuantizer:
logger.info("MatMul doesn't have const weight. Skip to quantize")
return [node] # only care about constant weight
b_ndarray = onnx.numpy_helper.to_array(b_tensor)
b_ndarray = ir.from_proto(b_tensor).numpy()
if len(b_ndarray.shape) != 2:
logger.info("MatMul weight is not 2D. Skip to quantize")
return [node] # can only process 2-D matrix
bfloat16 = b_ndarray.dtype == "bfloat16"
if bfloat16:
b_ndarray = b_ndarray.astype(np.float32)
packed, scales, zero_points = self.qbits_block_quant(b_ndarray)
if bfloat16:
scales = scales.astype(ml_dtypes.bfloat16)
if self.config.quant_format == QuantFormat.QOperator:
b_quant = onnx.numpy_helper.from_array(packed, b_tensor.name + f"_Q{bits}")
scales_tensor = onnx.numpy_helper.from_array(scales, b_tensor.name + "_scales")
b_quant = ir.serde.serialize_tensor(ir.Tensor(packed, name=b_tensor.name + f"_Q{bits}"))
scales_tensor = ir.serde.serialize_tensor(ir.Tensor(scales, name=b_tensor.name + "_scales"))
else:
b_quant = onnx.helper.make_tensor(
b_tensor.name + f"_DQ_Q{bits}", qtype, b_ndarray.shape, packed.tobytes(), True
)
scales_tensor = onnx.numpy_helper.from_array(scales, b_tensor.name + "_DQ_scales")
scales_tensor = ir.serde.serialize_tensor(ir.Tensor(scales, name=b_tensor.name + "_DQ_scales"))
# if QDQ, CW and SYM enabled, optimize for Intel NPU, tranpose the weight to NHWC format will increase performance
qdq_opt_for_intel_npu_enabled = (
@ -886,7 +903,7 @@ class DefaultWeightOnlyQuantizer:
b_quant = onnx.helper.make_tensor(
b_tensor.name + f"_DQ_Q{bits}", qtype, [cols, rows], packed.tobytes(), True
)
scales_tensor = onnx.numpy_helper.from_array(scales, b_tensor.name + "_DQ_scales")
scales_tensor = ir.serde.serialize_tensor(ir.Tensor(scales, name=b_tensor.name + "_DQ_scales"))
for input in b_graph.input:
if input.name == input_b:
@ -1206,7 +1223,7 @@ class MatMulNBitsQuantizer:
MatMul MatMulNBits DeQuantizeLinear -> MatMul
Gather GatherBlockQuantized Gather, Gather, Gather (optional) -> DequantizeLinear
Perform 4/8 bits quantization of constant weights for target nodes.
Perform 2/4/8 bits quantization of constant weights for target nodes.
If algo_config.quant_format is QOperator:
- nodes are replaced by the corresponding QOperator nodes.
- quantized weights are stored in the contrib ops.
@ -1224,6 +1241,7 @@ class MatMulNBitsQuantizer:
def __init__(
self,
model: ModelProto | str,
bits: int = 4, # default to 4bit
block_size: int = 128,
is_symmetric: bool = False,
accuracy_level: int | None = None,
@ -1239,6 +1257,7 @@ class MatMulNBitsQuantizer:
nodes_to_exclude = []
self.model = ONNXModel(onnx.load(model)) if isinstance(model, str) else ONNXModel(model)
self.model_path = model if isinstance(model, str) else None
self.bits = bits
self.block_size = block_size
self.is_symmetric = is_symmetric
self.accuracy_level = accuracy_level
@ -1254,13 +1273,13 @@ class MatMulNBitsQuantizer:
quant_format=quant_format,
op_types_to_quantize=op_types_to_quantize,
quant_axes=quant_axes,
bits=4, # default to 4 bits
bits=bits,
channel_wised_quantize=channel_wised_quantize,
)
self.algo_config = algo_config
if hasattr(self.algo_config, "bits"):
assert self.algo_config.bits in [4, 8], "Only support 4 or 8 bits quantization"
assert self.algo_config.bits in [2, 4, 8], "Only support 2, 4 or 8 bits quantization"
if algo_config.algorithm == "HQQ":
self.node_quantizer = HQQWeightOnlyQuantizer(self.algo_config)
@ -1609,6 +1628,7 @@ if __name__ == "__main__":
quant = MatMulNBitsQuantizer(
model=model,
bits=args.bits,
accuracy_level=args.accuracy_level,
nodes_to_exclude=args.nodes_to_exclude,
nodes_to_include=args.nodes_to_include,

View file

@ -21,12 +21,12 @@
import copy
import logging
import os
import sys
from collections import deque
from pathlib import Path
import onnx
import onnx.external_data_helper
import onnx_ir as ir
from .util import MAXIMUM_PROTOBUF, find_by_name
@ -73,26 +73,11 @@ class ONNXModel:
def check_is_large_model(self):
"""Check model > 2GB."""
init_size = 0
for init in self._model.graph.initializer:
# if initializer has external data location, return True
if init.HasField("data_location") and init.data_location == onnx.TensorProto.EXTERNAL:
self._is_large_model = True
return
# if raise error of initializer size > 2GB, return True
try:
init_bytes = init.SerializeToString()
init_size += sys.getsizeof(init_bytes)
except Exception as e:
if "exceeds maximum protobuf size of 2GB" in str(e):
self._is_large_model = True
return
else: # pragma: no cover
raise e
if init_size > MAXIMUM_PROTOBUF:
self._is_large_model = True
return
self._is_large_model = False
ir_graph = ir.from_proto(self._model.graph)
initializer_size = sum(
v.const_value.nbytes for v in ir_graph.initializers.values() if v.const_value is not None
)
self._is_large_model = initializer_size > MAXIMUM_PROTOBUF
@property
def is_large_model(self):

View file

@ -12,7 +12,6 @@ from typing import Any
import numpy as np
import onnx
import onnx.numpy_helper
from onnx import TensorProto
from onnx import onnx_pb as onnx_proto

View file

@ -14,6 +14,7 @@ from pathlib import Path
import numpy
import onnx
from ml_dtypes import float8_e4m3fn, int4, uint4
from onnx import ModelProto, TensorProto, external_data_helper
from onnx import onnx_pb as onnx_proto
from onnx.helper import make_graph, make_model, make_node, make_tensor_value_info
@ -21,19 +22,6 @@ from onnx.reference import ReferenceEvaluator
from onnxruntime import GraphOptimizationLevel, InferenceSession, SessionOptions
try:
from onnx.reference.custom_element_types import float8e4m3fn
except ImportError:
float8e4m3fn = None
# INT4 np.dtypes added in ONNX 1.16. These map to np.int8/np.uint8 because numpy
# does not support sub-byte types.
try:
from onnx.reference.custom_element_types import int4, uint4
except ImportError:
int4 = None
uint4 = None
try:
from onnx.reference.op_run import to_array_extended
except ImportError:
@ -149,9 +137,9 @@ ONNX_TYPE_TO_NP_TYPE = {
onnx_proto.TensorProto.UINT8: numpy.dtype("uint8"),
onnx_proto.TensorProto.INT16: numpy.dtype("int16"),
onnx_proto.TensorProto.UINT16: numpy.dtype("uint16"),
onnx_proto.TensorProto.FLOAT8E4M3FN: float8e4m3fn,
onnx_proto.TensorProto.INT4: int4, # base_dtype is np.int8
onnx_proto.TensorProto.UINT4: uint4, # base_dtype is np.uint8
onnx_proto.TensorProto.FLOAT8E4M3FN: float8_e4m3fn,
onnx_proto.TensorProto.INT4: int4,
onnx_proto.TensorProto.UINT4: uint4,
}
ONNX_INT_TYPE_RANGE = {
@ -164,9 +152,7 @@ ONNX_INT_TYPE_RANGE = {
}
ONNX_INT_TYPE_SYMMETRIC_RANGE = {
onnx_proto.TensorProto.UINT8: (numpy.array(0, dtype=numpy.uint8), numpy.array(254, dtype=numpy.uint8)),
onnx_proto.TensorProto.INT8: (numpy.array(-127, dtype=numpy.int8), numpy.array(127, dtype=numpy.int8)),
onnx_proto.TensorProto.UINT16: (numpy.array(0, dtype=numpy.uint16), numpy.array(65534, dtype=numpy.uint16)),
onnx_proto.TensorProto.INT16: (numpy.array(-32767, dtype=numpy.int16), numpy.array(32767, dtype=numpy.int16)),
}
@ -175,7 +161,7 @@ ONNX_INT_TYPE_REDUCED_RANGE = {
onnx_proto.TensorProto.INT8: (numpy.array(-64, dtype=numpy.int8), numpy.array(64, dtype=numpy.int8)),
onnx_proto.TensorProto.UINT16: (numpy.array(0, dtype=numpy.uint16), numpy.array(32767, dtype=numpy.uint16)),
onnx_proto.TensorProto.INT16: (numpy.array(-16384, dtype=numpy.int16), numpy.array(16384, dtype=numpy.int16)),
onnx_proto.TensorProto.UINT4: (numpy.array(0, dtype=int4), numpy.array(7, dtype=int4)),
onnx_proto.TensorProto.UINT4: (numpy.array(0, dtype=uint4), numpy.array(7, dtype=uint4)),
onnx_proto.TensorProto.INT4: (numpy.array(-4, dtype=int4), numpy.array(3, dtype=int4)),
}
@ -324,11 +310,10 @@ def compute_scale_zp_float8(element_type, std):
zp_dtype = None
if element_type not in FLOAT8_DISTRIBUTIONS:
if element_type == TensorProto.FLOAT8E4M3FN:
from onnx.numpy_helper import float8e4m3_to_float32 # noqa: PLC0415
from onnx.reference.custom_element_types import float8e4m3fn # noqa: PLC0415
from ml_dtypes import float8_e4m3fn # noqa: PLC0415
zp_dtype = float8e4m3fn
all_values = [float8e4m3_to_float32(i) for i in range(256)]
zp_dtype = float8_e4m3fn
all_values = [float(i) for i in range(256)]
values = numpy.array(
[f for f in all_values if not numpy.isnan(f) and not numpy.isinf(f)], dtype=numpy.float32
)
@ -336,9 +321,9 @@ def compute_scale_zp_float8(element_type, std):
raise ValueError(f"Quantization to element_type={element_type} not implemented.")
FLOAT8_DISTRIBUTIONS[element_type] = values
elif element_type == TensorProto.FLOAT8E4M3FN:
from onnx.reference.custom_element_types import float8e4m3fn # noqa: PLC0415
from ml_dtypes import float8_e4m3fn # noqa: PLC0415
zp_dtype = float8e4m3fn
zp_dtype = float8_e4m3fn
if zp_dtype is None:
raise TypeError(f"Unexpected element_type {element_type}.")
@ -449,7 +434,7 @@ def quantize_data(
)
if qType == TensorProto.FLOAT8E4M3FN:
quantized_data = quantize_nparray(qType, data, scale, zero_point)
if any((quantized_data.astype(numpy.uint8).ravel() & 127) == 127):
if any((quantized_data.view(numpy.uint8).ravel() & 127) == 127):
np_data = numpy.asarray(data)
raise RuntimeError(
f"One of the quantized value is NaN data in [{np_data.min()}, {np_data.max()}], "
@ -533,7 +518,7 @@ def quantize_onnx_initializer(
f"\nraw={str(q_weight_initializer)[:200]}."
)
elif quant_type in (onnx.TensorProto.INT4, onnx.TensorProto.UINT4):
if q_weight_data.dtype not in (numpy.int8, numpy.uint8):
if q_weight_data.dtype not in (int4, uint4):
raise RuntimeError(f"Quantized weights for {q_weight_name} must be 8-bit before packing as 4-bit values.")
# We do not use onnx.helper.pack_float32_to_4bit() due to performance.

View file

@ -87,6 +87,7 @@ QDQRegistry = {
"LayerNormalization": QDQNormalization,
"BatchNormalization": QDQNormalization,
"TopK": QDQDirect8BitOp,
"CumSum": QDQOperatorBase,
}

View file

@ -74,34 +74,29 @@ def quant_pre_process(
with tempfile.TemporaryDirectory(prefix="pre.quant.") as quant_tmp_dir:
temp_path = Path(quant_tmp_dir)
model = None
model = input_model if isinstance(input_model, onnx.ModelProto) else onnx.load(input_model)
# Since Upsample is deprecated after opset v10, and the model's opset will
# be upgraded to at least v11 during quantization, we need to replace Upsample
# with Resize first to avoid generating an invalid model.
ai_onnx_domain = [opset for opset in model.opset_import if not opset.domain or opset.domain == "ai.onnx"]
if len(ai_onnx_domain) == 1:
opset_version = ai_onnx_domain[0].version
if opset_version <= 10:
ReplaceUpsampleWithResize(ONNXModel(model), opset_version).apply()
model = onnx.version_converter.convert_version(model, 11)
model = save_and_reload_model_with_shape_infer(model)
if not skip_symbolic_shape:
logger.info("Performing symbolic shape inference...")
loaded_model = input_model if isinstance(input_model, onnx.ModelProto) else onnx.load(input_model)
model = SymbolicShapeInference.infer_shapes(
loaded_model,
model,
int_max,
auto_merge,
guess_output_rank,
verbose,
)
# Since Upsample is deprecated after opset v10, and the model's opset will
# be upgraded to at least v11 during quantization, we need to replace Upsample
# with Resize first to avoid generating an invalid model.
if model:
ai_onnx_domain = [opset for opset in model.opset_import if not opset.domain or opset.domain == "ai.onnx"]
if len(ai_onnx_domain) == 1:
opset_version = ai_onnx_domain[0].version
if opset_version < 10:
ReplaceUpsampleWithResize(ONNXModel(model), opset_version).apply()
model.opset_import.remove(ai_onnx_domain[0])
opset_version = 11
model.opset_import.extend([onnx.helper.make_opsetid("", opset_version)])
model = onnx.version_converter.convert_version(model, opset_version)
model = save_and_reload_model_with_shape_infer(model)
if not skip_optimization:
# Use ORT optimizers (native code) to optimize model
if not skip_symbolic_shape:

View file

@ -20,7 +20,7 @@ class OnnxModelCalibrationDataReader(CalibrationDataReader):
name2tensors = []
for data_dir in data_dirs:
name2tensor = {}
data_paths = [os.path.join(data_dir, a) for a in sorted(os.listdir(data_dir))]
data_paths = [os.path.join(data_dir, f"input_{input_idx}.pb") for input_idx in range(len(model_inputs))]
data_ndarrays = [self.read_onnx_pb_data(data_path) for data_path in data_paths]
for model_input, data_ndarray in zip(model_inputs, data_ndarrays, strict=False):
name2tensor[model_input.name] = data_ndarray