mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-10-02 03:14:52 +08:00
Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
616 lines
23 KiB
Python
616 lines
23 KiB
Python
# Adapted from https://github.com/pytorch/pytorch/blob/e48ee2cf50d86d87ef7c7d0839267dbed4903ebf/torch/onnx/symbolic_opset14.py#L137-L207
|
|
|
|
# From PyTorch:
|
|
|
|
# Copyright (c) 2016- Facebook, Inc (Adam Paszke)
|
|
# Copyright (c) 2014- Facebook, Inc (Soumith Chintala)
|
|
# Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
|
|
# Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
|
|
# Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
|
|
# Copyright (c) 2011-2013 NYU (Clement Farabet)
|
|
# Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston)
|
|
# Copyright (c) 2006 Idiap Research Institute (Samy Bengio)
|
|
# Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz)
|
|
|
|
# From Caffe2:
|
|
|
|
# Copyright (c) 2016-present, Facebook Inc. All rights reserved.
|
|
|
|
# All contributions by Facebook:
|
|
# Copyright (c) 2016 Facebook Inc.
|
|
|
|
# All contributions by Google:
|
|
# Copyright (c) 2015 Google Inc.
|
|
# All rights reserved.
|
|
|
|
# All contributions by Yangqing Jia:
|
|
# Copyright (c) 2015 Yangqing Jia
|
|
# All rights reserved.
|
|
|
|
# All contributions by Kakao Brain:
|
|
# Copyright 2019-2020 Kakao Brain
|
|
|
|
# All contributions by Cruise LLC:
|
|
# Copyright (c) 2022 Cruise LLC.
|
|
# All rights reserved.
|
|
|
|
# All contributions by Tri Dao:
|
|
# Copyright (c) 2024 Tri Dao.
|
|
# All rights reserved.
|
|
|
|
# All contributions by Arm:
|
|
# Copyright (c) 2021, 2023-2024 Arm Limited and/or its affiliates
|
|
|
|
# All contributions from Caffe:
|
|
# Copyright(c) 2013, 2014, 2015, the respective contributors
|
|
# All rights reserved.
|
|
|
|
# All other contributions:
|
|
# Copyright(c) 2015, 2016 the respective contributors
|
|
# All rights reserved.
|
|
|
|
# Caffe2 uses a copyright model similar to Caffe: each contributor holds
|
|
# copyright over their contributions to Caffe2. The project versioning records
|
|
# all such contribution and copyright details. If a contributor wants to further
|
|
# mark their specific copyright on a particular contribution, they should
|
|
# indicate their copyright solely in the commit message of the change when it is
|
|
# committed.
|
|
|
|
# All rights reserved.
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
|
|
# 3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories America
|
|
# and IDIAP Research Institute nor the names of its contributors may be
|
|
# used to endorse or promote products derived from this software without
|
|
# specific prior written permission.
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Utility to export a quantized torch model to quantized ONNX."""
|
|
|
|
import contextlib
|
|
|
|
import onnx
|
|
import torch
|
|
from torch.onnx import _type_utils, symbolic_helper
|
|
from torch.onnx import symbolic_helper as sym_help
|
|
from torch.onnx._internal import jit_utils
|
|
from torch.onnx.symbolic_opset14 import _attention_scale, _causal_attention_mask
|
|
|
|
onnx_dtype_map = {
|
|
"BFloat16": onnx.TensorProto.BFLOAT16,
|
|
"Float": onnx.TensorProto.FLOAT,
|
|
"Float8": onnx.TensorProto.FLOAT8E4M3FN,
|
|
"Half": onnx.TensorProto.FLOAT16,
|
|
"INT8": onnx.TensorProto.INT8,
|
|
"UINT8": onnx.TensorProto.UINT8,
|
|
}
|
|
mha_valid_precisions = {"Half", "BFloat16"}
|
|
|
|
torch_dtype_map = {"Float": torch.float32, "Half": torch.float16, "BFloat16": torch.bfloat16}
|
|
|
|
|
|
def export_int8(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
amax: torch.Tensor,
|
|
num_bits: int,
|
|
unsigned: bool,
|
|
narrow_range: bool,
|
|
trt_high_precision_dtype: str | None,
|
|
):
|
|
"""Export quantized model to INT8 ONNX."""
|
|
assert num_bits == 8, "Only INT8 ONNX export is supported for now."
|
|
output_shape = sym_help._get_tensor_sizes(inputs)
|
|
maxbound = (1 << (num_bits - 1 + int(unsigned))) - 1
|
|
|
|
input_type = inputs.type().scalarType()
|
|
if trt_high_precision_dtype is None:
|
|
trt_high_precision_dtype = input_type
|
|
|
|
if amax.numel() == 1:
|
|
zero_point, axis = torch.tensor(0.0, device=amax.device), None
|
|
else:
|
|
amax_init_shape = amax.shape
|
|
amax = amax.squeeze().data
|
|
assert len(amax.shape) == 1, "ONNX does not support multi-axis quantization."
|
|
zero_point = torch.zeros_like(amax, dtype=torch.int32).data
|
|
axis = list(amax_init_shape).index(next(iter(amax.shape)))
|
|
|
|
zero_point = g.op("Constant", value_t=zero_point.to(torch_dtype_map[trt_high_precision_dtype]))
|
|
|
|
if not unsigned:
|
|
assert not narrow_range, "ONNX does not support unsigned narrow range INT8."
|
|
zero_point = g.op("Cast", zero_point, to_i=onnx_dtype_map["INT8"])
|
|
else:
|
|
zero_point = g.op("Cast", zero_point, to_i=onnx_dtype_map["UINT8"])
|
|
|
|
amax = amax.to(torch_dtype_map[trt_high_precision_dtype])
|
|
scale = amax / maxbound
|
|
scale.masked_fill_(scale == 0, 1.0)
|
|
scale = g.op("Constant", value_t=scale)
|
|
|
|
assert trt_high_precision_dtype in (input_type, "Float"), (
|
|
"TRT StronglyType requires both weights and amax to be in the BF16/FP16, or the QDQ in Float."
|
|
)
|
|
|
|
# custom ops, so cast the input if needed.
|
|
if trt_high_precision_dtype != input_type:
|
|
inputs = g.op("Cast", inputs, to_i=onnx_dtype_map[trt_high_precision_dtype])
|
|
quantized = g.op("QuantizeLinear", inputs, scale, zero_point, axis_i=axis)
|
|
out = g.op("DequantizeLinear", quantized, scale, zero_point, axis_i=axis).setType(
|
|
inputs.type().with_dtype(torch_dtype_map[trt_high_precision_dtype]).with_sizes(output_shape)
|
|
)
|
|
|
|
# custom ops, so cast the output if needed.
|
|
if trt_high_precision_dtype != input_type:
|
|
inputs = g.op("Cast", inputs, to_i=onnx_dtype_map[input_type])
|
|
|
|
return out
|
|
|
|
|
|
def _fp8_quantize(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
scale_inv: float,
|
|
trt_high_precision_dtype: str,
|
|
):
|
|
"""Helper Function for Quantization."""
|
|
output_shape = sym_help._get_tensor_sizes(inputs)
|
|
|
|
# TRT StronglyType only supports FP16 QDQs
|
|
# custom ops, so cast the input if needed.
|
|
input_type = inputs.type().scalarType()
|
|
assert trt_high_precision_dtype in (input_type, "Float"), (
|
|
"TRT StronglyType requires both weights and amax to be in the BF16/FP16, or the QDQ in Float."
|
|
)
|
|
if trt_high_precision_dtype != input_type:
|
|
inputs = g.op("Cast", inputs, to_i=onnx_dtype_map[trt_high_precision_dtype])
|
|
|
|
scale = g.op(
|
|
"Constant",
|
|
value_t=torch.tensor(scale_inv).to(torch_dtype_map[trt_high_precision_dtype]),
|
|
)
|
|
q_op = g.op("trt::TRT_FP8QuantizeLinear", inputs, scale).setType(
|
|
inputs.type().with_dtype(torch.uint8).with_sizes(output_shape)
|
|
)
|
|
return q_op
|
|
|
|
|
|
def _fp8_dequantize(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
scale_inv: float,
|
|
trt_high_precision_dtype: str,
|
|
otype: str | None = None,
|
|
):
|
|
"""Helper Function for Dequantization."""
|
|
output_shape = sym_help._get_tensor_sizes(inputs)
|
|
assert trt_high_precision_dtype in (otype, "Float"), (
|
|
"TRT StronglyType requires both weights and amax to be in the BF16/FP16, or the QDQ in Float."
|
|
)
|
|
scale = g.op(
|
|
"Constant",
|
|
value_t=torch.tensor(scale_inv, dtype=torch_dtype_map[otype]), # type: ignore[index]
|
|
)
|
|
out = g.op("trt::TRT_FP8DequantizeLinear", inputs, scale).setType(
|
|
inputs.type().with_dtype(torch_dtype_map[trt_high_precision_dtype]).with_sizes(output_shape)
|
|
)
|
|
|
|
# DQ outputs are currently constrained to FP32 due to a similar limitation in ORT
|
|
# custom ops, so cast the output if needed.
|
|
if trt_high_precision_dtype != otype:
|
|
out = g.op("Cast", out, to_i=onnx_dtype_map[otype]) # type: ignore[index]
|
|
return out
|
|
|
|
|
|
def export_fp8(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
amax: float,
|
|
trt_high_precision_dtype: str | None,
|
|
):
|
|
"""Export quantized model to FP8 ONNX."""
|
|
scale = 1.0 if amax is None else 448.0 / float(amax)
|
|
otype = inputs.type().scalarType()
|
|
if trt_high_precision_dtype is None:
|
|
trt_high_precision_dtype = otype
|
|
|
|
q_tensor = _fp8_quantize(g, inputs, 1.0 / scale, trt_high_precision_dtype)
|
|
return _fp8_dequantize(g, q_tensor, 1.0 / scale, trt_high_precision_dtype, otype)
|
|
|
|
|
|
def scaled_dot_product_attention(
|
|
g: jit_utils.GraphContext,
|
|
query: torch._C.Value,
|
|
key: torch._C.Value,
|
|
value: torch._C.Value,
|
|
attn_mask: torch._C.Value | None = None,
|
|
dropout_p: float = 0.0,
|
|
is_causal: bool = False,
|
|
scale: torch._C.Value | None = None,
|
|
enable_gqa: bool = False,
|
|
):
|
|
"""Perform scaled dot product attention."""
|
|
assert (not is_causal) or (is_causal and symbolic_helper._is_none(attn_mask)), (
|
|
"is_causal and attn_mask cannot be set at the same time"
|
|
)
|
|
assert not enable_gqa, (
|
|
"conversion of scaled_dot_product_attention not implemented if enable_gqa is True"
|
|
)
|
|
|
|
if symbolic_helper._is_none(scale):
|
|
scale = _attention_scale(g, query)
|
|
|
|
if is_causal:
|
|
attn_mask = _causal_attention_mask(g, query, key)
|
|
|
|
# Swap the last two axes of key
|
|
# NOTE: onnx-script has different logic here, because the attribute perms in
|
|
# transpose needs list of ints
|
|
key_shape_builtin = symbolic_helper._get_tensor_rank(key)
|
|
key_transposed_axes = list(range(key_shape_builtin))
|
|
key_transposed_axes[-1], key_transposed_axes[-2] = (
|
|
key_transposed_axes[-2],
|
|
key_transposed_axes[-1],
|
|
)
|
|
key_transposed = g.op("Transpose", key, perm_i=key_transposed_axes)
|
|
|
|
# https://github.com/pytorch/pytorch/blob/12da0c70378b5be9135c6fda62a9863bce4a4818/aten/src/ATen/native/transformers/attention.cpp#L653
|
|
# Scale q, k before matmul for stability see https://tinyurl.com/sudb9s96 for math
|
|
query_scaled = g.op("Mul", query, g.op("Sqrt", scale))
|
|
key_transposed_scaled = g.op("Mul", key_transposed, g.op("Sqrt", scale))
|
|
mul_qk = g.op("MatMul", query_scaled, key_transposed_scaled)
|
|
|
|
if symbolic_helper._is_none(attn_mask):
|
|
mul_qk_add = mul_qk
|
|
elif _type_utils.JitScalarType.from_value(attn_mask) == _type_utils.JitScalarType.BOOL:
|
|
# Turn the Boolean mask to float: attn_mask.masked_fill(not attn_mask, -float('inf'))
|
|
const_zero = g.op("Constant", value_t=torch.tensor([0.0]))
|
|
const_neg_inf = g.op("Constant", value_t=torch.tensor([-float("inf")]))
|
|
attn_mask = g.op("Where", attn_mask, const_zero, const_neg_inf)
|
|
mul_qk_add = g.op("Add", mul_qk, attn_mask)
|
|
elif _type_utils.JitScalarType.from_value(attn_mask) in (
|
|
_type_utils.JitScalarType.FLOAT,
|
|
_type_utils.JitScalarType.HALF,
|
|
_type_utils.JitScalarType.BFLOAT16,
|
|
):
|
|
mul_qk_add = g.op("Add", mul_qk, attn_mask)
|
|
else:
|
|
raise ValueError(
|
|
f"Unsupported type for attn_mask: {_type_utils.JitScalarType.from_value(attn_mask)}"
|
|
)
|
|
|
|
attn_weight = g.op("Softmax", mul_qk_add, axis_i=-1)
|
|
|
|
if dropout_p != 0:
|
|
attn_weight = g.op(
|
|
"Dropout",
|
|
attn_weight,
|
|
g.op("Constant", value_t=torch.tensor(dropout_p, dtype=torch.float)),
|
|
)
|
|
|
|
return g.op("MatMul", attn_weight, value)
|
|
|
|
|
|
def export_fp8_mha(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
query: torch._C.Value,
|
|
key: torch._C.Value,
|
|
value: torch._C.Value,
|
|
attn_mask: torch._C.Value | None = None,
|
|
dropout_p: float = 0.0,
|
|
is_causal: bool = False,
|
|
scale: torch._C.Value | None = None,
|
|
q_quantized_scale: float = 1.0,
|
|
k_quantized_scale: float = 1.0,
|
|
v_quantized_scale: float = 1.0,
|
|
high_precision_flag: str = "Half",
|
|
disable_fp8_mha: bool = True,
|
|
):
|
|
r"""Export quantized fMHA to FP8 ONNX.
|
|
|
|
FP8 ONNX graph::
|
|
|
|
Q K V
|
|
| | |
|
|
\ / |
|
|
QDQ QDQ |
|
|
\ / |
|
|
Cast Cast |
|
|
\ / |
|
|
BMM1 |
|
|
\ |
|
|
Cast QDQ
|
|
\ |
|
|
SoftMax |
|
|
| |
|
|
QDQ |
|
|
\ |
|
|
Cast Cast
|
|
\ /
|
|
BMM2
|
|
|
|
|
Cast
|
|
"""
|
|
from torch.onnx.symbolic_opset14 import _attention_scale, _causal_attention_mask
|
|
|
|
# Pass all arguments, including x, to the custom ONNX operator
|
|
assert (not is_causal) or (is_causal and sym_help._is_none(attn_mask)), (
|
|
"is_causal and attn_mask cannot be set at the same time"
|
|
)
|
|
|
|
scale = sym_help._maybe_get_const(scale, "f")
|
|
if sym_help._is_none(scale):
|
|
scale = _attention_scale(g, query)
|
|
|
|
if is_causal:
|
|
attn_mask = _causal_attention_mask(g, query, key)
|
|
|
|
# Swap the last two axes of key
|
|
# NOTE: onnx-script has different logic here, because the attribute perms in
|
|
# transpose needs list of ints
|
|
key_shape_builtin = sym_help._get_tensor_rank(key)
|
|
key_transposed_axes = list(range(key_shape_builtin))
|
|
key_transposed_axes[-1], key_transposed_axes[-2] = (
|
|
key_transposed_axes[-2],
|
|
key_transposed_axes[-1],
|
|
)
|
|
key_transposed = g.op("Transpose", key, perm_i=key_transposed_axes)
|
|
|
|
# https://github.com/pytorch/pytorch/blob/12da0c70378b5be9135c6fda62a9863bce4a4818/aten/src/ATen/native/transformers/attention.cpp#L653
|
|
# Scale q, k before matmul for stability see https://tinyurl.com/sudb9s96 for math
|
|
query_scaled = g.op("Mul", query, g.op("Sqrt", scale))
|
|
key_transposed_scaled = g.op("Mul", key_transposed, g.op("Sqrt", scale))
|
|
if not disable_fp8_mha:
|
|
if high_precision_flag not in mha_valid_precisions:
|
|
raise ValueError(
|
|
"The Quantized config setting doesn't match TRT's fusion pattern; the qdqs must be in 16 bits."
|
|
)
|
|
q_input_dtype = query.type().scalarType()
|
|
k_input_dtype = key.type().scalarType()
|
|
v_input_dtype = value.type().scalarType()
|
|
if {q_input_dtype, k_input_dtype, v_input_dtype} != {high_precision_flag}:
|
|
raise ValueError("The quantized MHA must have 16-bit inputs.")
|
|
query_scaled = export_fp8(g, query_scaled, q_quantized_scale, high_precision_flag)
|
|
query_scaled = g.op("Cast", query_scaled, to_i=onnx_dtype_map["Float"])
|
|
key_transposed_scaled = export_fp8(
|
|
g, key_transposed_scaled, k_quantized_scale, high_precision_flag
|
|
)
|
|
key_transposed_scaled = g.op("Cast", key_transposed_scaled, to_i=onnx_dtype_map["Float"])
|
|
mul_qk = g.op("MatMul", query_scaled, key_transposed_scaled)
|
|
if not disable_fp8_mha:
|
|
mul_qk = g.op("Cast", mul_qk, to_i=onnx_dtype_map[high_precision_flag])
|
|
|
|
if sym_help._is_none(attn_mask):
|
|
mul_qk_add = mul_qk
|
|
elif _type_utils.JitScalarType.from_value(attn_mask) == _type_utils.JitScalarType.BOOL:
|
|
# Turn the Boolean mask to float: attn_mask.masked_fill(not attn_mask, -float('inf'))
|
|
const_zero = g.op("Constant", value_t=torch.tensor([0.0]))
|
|
const_neg_inf = g.op("Constant", value_t=torch.tensor([-float("inf")]))
|
|
attn_mask = g.op("Where", attn_mask, const_zero, const_neg_inf)
|
|
mul_qk_add = g.op("Add", mul_qk, attn_mask)
|
|
elif _type_utils.JitScalarType.from_value(attn_mask) in (
|
|
_type_utils.JitScalarType.FLOAT,
|
|
_type_utils.JitScalarType.HALF,
|
|
_type_utils.JitScalarType.BFLOAT16,
|
|
):
|
|
mul_qk_add = g.op("Add", mul_qk, attn_mask)
|
|
else:
|
|
raise ValueError(
|
|
f"Unsupported type for attn_mask: {_type_utils.JitScalarType.from_value(attn_mask)}"
|
|
)
|
|
|
|
attn_weight = g.op("Softmax", mul_qk_add, axis_i=-1)
|
|
|
|
if not disable_fp8_mha:
|
|
# Softmax's output scale is hard coded to 1.0
|
|
attn_weight = export_fp8(g, attn_weight, 1.0, high_precision_flag)
|
|
attn_weight = g.op("Cast", attn_weight, to_i=onnx_dtype_map["Float"])
|
|
|
|
if dropout_p != 0:
|
|
attn_weight = g.op(
|
|
"Dropout",
|
|
attn_weight,
|
|
g.op("Constant", value_t=torch.tensor(dropout_p, dtype=torch.float)),
|
|
)
|
|
if not disable_fp8_mha:
|
|
value = export_fp8(g, value, v_quantized_scale, high_precision_flag)
|
|
value = g.op("Cast", value, to_i=onnx_dtype_map["Float"])
|
|
return g.op(
|
|
"Cast",
|
|
g.op("MatMul", attn_weight, value),
|
|
to_i=onnx_dtype_map[high_precision_flag],
|
|
)
|
|
else:
|
|
return g.op("MatMul", attn_weight, value)
|
|
|
|
|
|
def _fp4_dynamic_quantize(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
scale: float,
|
|
trt_high_precision_dtype: str | None,
|
|
block_size: int,
|
|
axis: int = -1,
|
|
scale_type: int = onnx_dtype_map["Float8"],
|
|
):
|
|
"""Helper Function for Dynamic Quantization."""
|
|
# TRT StronglyType only supports FP16 QDQ ops, so cast the input if needed.
|
|
input_type = inputs.type().scalarType()
|
|
if trt_high_precision_dtype is None:
|
|
trt_high_precision_dtype = input_type
|
|
|
|
if trt_high_precision_dtype != input_type:
|
|
inputs = g.op("Cast", inputs, to_i=onnx_dtype_map[trt_high_precision_dtype])
|
|
|
|
scale = g.op(
|
|
"Constant",
|
|
value_t=torch.tensor(scale).to(torch_dtype_map["Float"]),
|
|
)
|
|
# This is a TensorRT local function, it dynamically quantizes the input tensor to FP4.
|
|
xf4, sx_f8 = g.op(
|
|
"trt::TRT_FP4DynamicQuantize",
|
|
inputs,
|
|
scale,
|
|
axis_i=axis,
|
|
block_size_i=block_size,
|
|
scale_type_i=scale_type,
|
|
outputs=2,
|
|
)
|
|
|
|
return xf4, sx_f8
|
|
|
|
|
|
def _fp4_dequantize(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
scale: float | torch.Value,
|
|
trt_high_precision_dtype: str | None,
|
|
):
|
|
"""Helper Function for Dequantization."""
|
|
if isinstance(scale, float):
|
|
scale = g.op(
|
|
"Constant",
|
|
value_t=torch.tensor(scale, dtype=torch_dtype_map["Float"]),
|
|
)
|
|
return g.op("trt::DequantizeLinear", inputs, scale)
|
|
|
|
|
|
def _fp4_dequantize_2(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
dyn_scale: torch.Value,
|
|
block_size: int,
|
|
axis: int = -1,
|
|
):
|
|
"""Helper Function for Dequantization."""
|
|
return g.op("trt::DequantizeLinear", inputs, dyn_scale, axis_i=axis, block_size_i=block_size)
|
|
|
|
|
|
def _mxfp8_dynamic_quantize(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
block_size: int,
|
|
axis: int = -1,
|
|
):
|
|
x_f8, sx_ui8 = g.op(
|
|
"trt::TRT_MXFP8DynamicQuantize",
|
|
inputs,
|
|
axis_i=axis,
|
|
block_size_i=block_size,
|
|
outputs=2,
|
|
output_dtype_i=onnx_dtype_map["Float8"],
|
|
)
|
|
|
|
return x_f8, sx_ui8
|
|
|
|
|
|
def _mxfp8_dequantize(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
scale: torch.Value,
|
|
block_size: int,
|
|
axis: int = -1,
|
|
input_dtype: str = "Half",
|
|
):
|
|
return g.op(
|
|
"trt::TRT_MXFP8DequantizeLinear",
|
|
inputs,
|
|
scale,
|
|
axis_i=axis,
|
|
block_size_i=block_size,
|
|
output_dtype_i=onnx_dtype_map[input_dtype],
|
|
)
|
|
|
|
|
|
def export_mxfp8(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Tensor,
|
|
onnx_quantizer_type: str,
|
|
block_size: int,
|
|
axis: int = -1,
|
|
):
|
|
"""Export quantized model to MXFP8 ONNX."""
|
|
input_dtype = inputs.type().scalarType()
|
|
if onnx_quantizer_type == "dynamic":
|
|
x_f8, sx_ui8 = _mxfp8_dynamic_quantize(g, inputs, block_size, axis=axis)
|
|
|
|
return _mxfp8_dequantize(g, x_f8, sx_ui8, block_size, axis=axis, input_dtype=input_dtype)
|
|
else:
|
|
scale = torch.tensor(1.0, dtype=torch_dtype_map[input_dtype])
|
|
return _mxfp8_dequantize(g, inputs, scale, block_size, axis=axis, input_dtype=input_dtype)
|
|
|
|
|
|
def export_fp4(
|
|
g: torch.onnx._internal.jit_utils.GraphContext,
|
|
inputs: torch.Value,
|
|
block_size: int,
|
|
amax: torch.Value,
|
|
num_bits: tuple[int, int],
|
|
trt_high_precision_dtype: str | None,
|
|
onnx_quantizer_type: str,
|
|
):
|
|
"""Export quantized model to FP4 ONNX."""
|
|
if onnx_quantizer_type == "dynamic":
|
|
amax = sym_help._get_const(amax, "f", "amax")
|
|
if amax is None or amax == 0:
|
|
sx_f32_per_tensor = 1.0
|
|
else:
|
|
assert num_bits == (2, 1)
|
|
sx_f32_per_tensor = float(amax) / 6.0 / 448.0
|
|
|
|
x_f4, sx_f8 = _fp4_dynamic_quantize(
|
|
g, inputs, sx_f32_per_tensor, trt_high_precision_dtype, block_size
|
|
)
|
|
dq_scale = _fp4_dequantize(g, sx_f8, sx_f32_per_tensor, trt_high_precision_dtype)
|
|
return _fp4_dequantize_2(g, x_f4, dq_scale, block_size)
|
|
else:
|
|
# This is a dummy custom op, we post-process the exported ONNX model to replace this node
|
|
# with two DQ nodes as described in the static double quantization recipe.
|
|
return g.op("trt::TRT_FP4QDQ", inputs, block_size_i=block_size)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def configure_linear_module_onnx_quantizers(model):
|
|
"""Sets the onnx export attributes for the given model."""
|
|
for _, module in model.named_modules():
|
|
if isinstance(module, torch.nn.Linear):
|
|
module.input_quantizer._onnx_quantizer_type = "dynamic"
|
|
module.weight_quantizer._onnx_quantizer_type = "static"
|
|
yield
|