chore: import upstream snapshot with attribution
Docker Image CI / build-ubuntu2004 (push) Has been cancelled
Docker Image CI / build-ubuntu2004 (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import tensorrt as trt
|
||||
|
||||
logger = trt.Logger()
|
||||
logger.log(trt.Logger.WARNING, "Functionality provided through tensorrt.plugin module is experimental.")
|
||||
|
||||
# export.public_api() will expose things here. To make sure that happens, we just need to
|
||||
# import all the submodules so that the decorator is actually executed (__discover_modules() below).
|
||||
__all__ = []
|
||||
|
||||
def __discover_modules():
|
||||
import importlib
|
||||
import pkgutil
|
||||
|
||||
mods = [importlib.import_module(__package__)]
|
||||
while mods:
|
||||
mod = mods.pop(0)
|
||||
|
||||
yield mod
|
||||
|
||||
if hasattr(mod, "__path__"):
|
||||
mods.extend(
|
||||
[
|
||||
importlib.import_module(f"{mod.__name__}.{submod.name}")
|
||||
for submod in pkgutil.iter_modules(mod.__path__)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
_ = list(__discover_modules())
|
||||
@@ -0,0 +1,270 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import builtins
|
||||
import tensorrt as trt
|
||||
from typing import List, Iterable
|
||||
import copy
|
||||
|
||||
from ._utils import _str_to_data_type
|
||||
from ._export import public_api
|
||||
|
||||
|
||||
# "onesided" means either type or format combinations. After combinations for each are separately generated, we will combine them later.
|
||||
# e.g. io_variants = ["FP32|FP16", "FP32|FP16", "FP32*FP16"] for a plugin with 3 I/Os. i.e. I/O indices 0 and 1 are dependently either FP32/FP16 and index 2 is independently FP32/FP16.
|
||||
# There will be 2 * 2 = 4 combinations here: ["FP32", "FP32", "FP32"], ["FP16", "FP16", "FP32"], ["FP32", "FP32", "FP16"], ["FP16", "FP16", "FP16"]
|
||||
def _gen_onesided_combinations(io_variants):
|
||||
|
||||
# Algorithm:
|
||||
# (1) Ignore independent variants and count the (max) number of dependent variants `mx_poly`
|
||||
# (2) Compile initial list of #`mx_poly` combinations using the first option (option 0) for any independent variants
|
||||
# (3) For each independent variant IO index, add combinations with that index replaced by option 1, 2, ...
|
||||
|
||||
combinations = []
|
||||
mx_poly = 0 # This is the number of dependent variants
|
||||
|
||||
for io_variant in io_variants:
|
||||
io_variant_list = io_variant.split("|")
|
||||
|
||||
if len(io_variant_list) > 1:
|
||||
if "*" in io_variant:
|
||||
raise ValueError(
|
||||
f"Type/Format '{io_variant}' contains both '|' and '*'"
|
||||
)
|
||||
if mx_poly > 1:
|
||||
if mx_poly != len(io_variant_list):
|
||||
raise ValueError(
|
||||
f"Type/Format combinations {io_variants} contain illegal dependent lengths"
|
||||
)
|
||||
|
||||
mx_poly = builtins.max(mx_poly, len(io_variant_list))
|
||||
|
||||
for _ in range(mx_poly):
|
||||
combinations.append([None] * len(io_variants))
|
||||
|
||||
for j, io_variant in enumerate(io_variants):
|
||||
io_variant_list = io_variant.split("|")
|
||||
|
||||
if len(io_variant_list) == 1:
|
||||
if "*" in io_variant:
|
||||
io_variant_list = io_variant.split("*")
|
||||
for i in range(len(combinations)):
|
||||
combinations[i][j] = io_variant_list[0]
|
||||
else:
|
||||
for k in range(len(io_variant_list)):
|
||||
combinations[k][j] = io_variant_list[k]
|
||||
|
||||
for j, io_variant in enumerate(io_variants):
|
||||
new_combs = []
|
||||
if "*" in io_variant:
|
||||
io_variant_list = io_variant.split("*")
|
||||
for k in range(1, len(io_variant_list)):
|
||||
for c in combinations:
|
||||
new_c = copy.deepcopy(c)
|
||||
new_c[j] = io_variant_list[k]
|
||||
new_combs.append(new_c)
|
||||
combinations.extend(new_combs)
|
||||
|
||||
return combinations
|
||||
|
||||
|
||||
class _TypeFormatCombination:
|
||||
def __init__(self, num=0):
|
||||
self.types = [None] * num
|
||||
self.layouts = [None] * num
|
||||
self.tactics = []
|
||||
|
||||
def set_types(self, types):
|
||||
self.types = types
|
||||
|
||||
def set_layouts(self, layouts=None):
|
||||
if isinstance(layouts, List):
|
||||
self.layouts = layouts
|
||||
else:
|
||||
self.layouts = [layouts] * len(self.types)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((tuple(self.types), tuple(self.layouts)))
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, _TypeFormatCombination)
|
||||
and self.types == other.types
|
||||
and self.layouts == other.layouts
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "{" + str(self.types) + ", " + str(self.layouts) + "}"
|
||||
|
||||
|
||||
@public_api()
|
||||
class AutoTuneCombination:
|
||||
def __init__(
|
||||
self, io_types: str = None, layouts: str = None, tactics: Iterable[int] = None
|
||||
):
|
||||
"""
|
||||
Construct a set of supported type/format combinations of a plugin's I/O.
|
||||
|
||||
Any custom *tactic* s per each such type/format combination can also be advertised. A tactic is simply another way to
|
||||
calculate the output of a plugin for the same type/format combination of the I/O (e.g. if there are multiple kernels available).
|
||||
|
||||
Args:
|
||||
io_types (str, optional): A string representation of a type combination.
|
||||
|
||||
Valid format is "type0,type1,...,type#io" where 'type' is of the form "TYPE0[sep]TYPE1[sep]...".
|
||||
|
||||
TYPE is a valid string representation of a `trt.DataType`. These include "FP32" for trt.float32, "FP16" for trt.float16. The string representation of other data types is the same as their name in the trt.DataType enum.
|
||||
|
||||
|
||||
[sep] is a valid separator, which is either '|' or '*'. Only one of these separators can appear in a given `io_types`.
|
||||
|
||||
(1). '|' indicates a dependent combination: the dependence of the type of one I/O to another I/O. e.g. "FP32|FP16,FP32|FP16" indicates the IO can only be both FP32 or both FP16.
|
||||
|
||||
(2). '*' indicates an independent combination. e.g. "FP32*FP16,FP32|FP16,FP32|FP16" indicates that the first input is independently either FP32 or FP16 regardless of the rest of the IO.
|
||||
|
||||
layouts (str, optional): A string representation of a format combination.
|
||||
|
||||
Valid format is "format0,format1,...,format#io" where 'format' is of the form "FORMAT0[sep]FORMAT1[sep]...".
|
||||
|
||||
FORMAT is a valid string representation of a `trt.TensorFormat`. These are string versions for the enum values of `trt.TensorFormat`. e.g. "LINEAR" for `trt.TensorFormat.LINEAR`.
|
||||
|
||||
[sep] is a valid separator, which is either '|' or '*'. The rules are the same as for `io_types`.
|
||||
|
||||
tactics (Iterable[int], optional): Custom tactics for this type/format combination. Each custom tactic must be a positive integer. Defaults to default tactic (0).
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: For a plugin with 3 I/Os, I/O indices 0 and 1 are dependently either FP32/FP16 and index 2 is independently FP32/FP16.
|
||||
|
||||
@trtp.autotune("my::plugin")
|
||||
def autotune(inp0: trtp.TensorDesc, inp1: trtp.TensorDesc, outputs: Tuple[trtp.TensorDesc]) -> List[trtp.AutoTuneCombination]:
|
||||
# The following would result in the following type combinations:
|
||||
# [FP32, FP32, FP32], [FP16, FP16, FP32], [FP32, FP32, FP16], [FP16, FP16, FP16]
|
||||
return [trtp.AutoTuneCombination("FP32|FP16, FP32|FP16, FP32|FP16", "LINEAR", [1, 2])]
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: For a plugin with 2 I/Os, the input/output supports either LINEAR or HWC format for FP32 and LINEAR format for FP16.
|
||||
|
||||
@trtp.autotune("my::plugin")
|
||||
def autotune(inp0: trtp.TensorDesc, outputs: Tuple[trtp.TensorDesc]) -> List[trtp.AutoTuneCombination]:
|
||||
# Even though (FP16, HWC) is not a valid combination (see next example), TRT should intelligently reject those
|
||||
# and pass the following combinations to the impl function:
|
||||
# [{FP32, FP32}, {LINEAR, LINEAR}], [{FP32, FP32}, {HWC, LINEAR}], [{FP16, FP32}, {LINEAR, LINEAR}]
|
||||
return [trtp.AutoTuneCombination("FP32*FP16, FP32", "LINEAR*HWC, LINEAR", [1, 2])]
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: For a plugin with 2 I/Os, the input/output supports either LINEAR or HWC format for FP32 and LINEAR format for FP16 (second method).
|
||||
|
||||
@trtp.autotune("my::plugin")
|
||||
def autotune(inp0: trtp.TensorDesc, outputs: Tuple[trtp.TensorDesc]) -> List[trtp.AutoTuneCombination]:
|
||||
# We can use two AutoTuneCombination objects to avoid communicating illegal combinations
|
||||
return [trtp.AutoTuneCombination("FP32*FP16, FP32", "LINEAR, LINEAR", [1, 2]), trtp.AutoTuneCombination("FP32, FP32", "HWC, LINEAR", [1, 2])]
|
||||
"""
|
||||
|
||||
if io_types is not None:
|
||||
self.io_types = [s.strip() for s in io_types.split(",")]
|
||||
if layouts is None:
|
||||
layouts = "LINEAR"
|
||||
self.layouts = [s.strip() for s in layouts.split(",")]
|
||||
|
||||
if len(self.layouts) > 1:
|
||||
assert len(self.io_types) == len(self.layouts)
|
||||
|
||||
if len(self.io_types) > len(self.layouts):
|
||||
assert len(self.layouts) == 1
|
||||
self.layouts = [self.layouts[0]] * len(self.io_types)
|
||||
else:
|
||||
self.io_types = []
|
||||
self.layouts = []
|
||||
|
||||
self.combinations = []
|
||||
self._tactics = tactics
|
||||
|
||||
def pos(self, pos: Iterable[int], io_types: str, layouts: str = "LINEAR") -> None:
|
||||
"""
|
||||
Specify I/O types and formats for a specified set of I/O indices.
|
||||
|
||||
Args:
|
||||
pos (Iterable[int]): I/O indices. Input indices are [0, 1, ..., #inputs - 1] and output indices are [#inputs, #inputs + 1, ..., #inputs + #outputs - 1].
|
||||
io_types (str): Data types for these I/O indices.
|
||||
layouts (str, optional): Tensor format(s) for these I/O indices. Defaults to "LINEAR".
|
||||
Raises:
|
||||
ValueError: If types or layouts for any of these I/O indices is already specified.
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: For a plugin with 3 I/Os, I/O indices 0 and 1 are dependently either FP32/FP16 and index 2 is independently FP32/FP16.
|
||||
|
||||
@trtp.autotune("my::plugin")
|
||||
def autotune(inp0: trtp.TensorDesc, inp1: trtp.TensorDesc, outputs: Tuple[trtp.TensorDesc]) -> List[trtp.AutoTuneCombination]:
|
||||
c = trtp.AutoTuneCombination()
|
||||
c.pos([0, 1], "FP32|FP16", "LINEAR")
|
||||
c.pos(2, "FP32*FP16") # Omitting format is the same as declaring it to be LINEAR.
|
||||
c.tactics([1, 2])
|
||||
return [c]
|
||||
"""
|
||||
if max(pos) >= len(self.io_types):
|
||||
self.io_types.extend([None] * (max(pos) + 1 - len(self.io_types)))
|
||||
self.layouts.extend([None] * (max(pos) + 1 - len(self.layouts)))
|
||||
assert len(self.io_types) == len(self.layouts)
|
||||
|
||||
for p in pos:
|
||||
if self.io_types[p] is not None:
|
||||
raise ValueError(f"Type(s) for position {p} already specified")
|
||||
if self.layouts[p] is not None:
|
||||
raise ValueError(f"Layout(s) for position {p} already specified")
|
||||
self.io_types[p] = io_types
|
||||
self.layouts[p] = layouts
|
||||
|
||||
def tactics(self, tactics: Iterable[int]) -> None:
|
||||
"""
|
||||
Specify custom tactics for this type/format combination
|
||||
|
||||
Args:
|
||||
tactics (Iterable[int]): Custom tactics. These must be positive integers.
|
||||
"""
|
||||
self._tactics = tactics
|
||||
|
||||
def _generate_combinations(self):
|
||||
|
||||
self.combinations = []
|
||||
|
||||
type_combinations = _gen_onesided_combinations(self.io_types)
|
||||
layout_combinations = _gen_onesided_combinations(self.layouts)
|
||||
|
||||
for t in type_combinations:
|
||||
for l in layout_combinations:
|
||||
c = _TypeFormatCombination(len(self.io_types))
|
||||
c.types = [_str_to_data_type(tt) for tt in t]
|
||||
c.layouts = [getattr(trt.TensorFormat, ff) for ff in l]
|
||||
c.tactics = self._tactics
|
||||
self.combinations.append(c)
|
||||
|
||||
def _get_combinations(self):
|
||||
self._generate_combinations()
|
||||
return self.combinations
|
||||
|
||||
def _check(self, pos, type, layout):
|
||||
for i in range(len(self.combinations)):
|
||||
if (
|
||||
self.combinations[i].types[pos] == _str_to_data_type(type)
|
||||
and self.combinations[i].layouts[pos] == layout.name
|
||||
):
|
||||
return True
|
||||
return False
|
||||
@@ -0,0 +1,39 @@
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 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.
|
||||
#
|
||||
|
||||
import tensorrt as trt
|
||||
from types import ModuleType
|
||||
import importlib
|
||||
|
||||
def public_api(module: ModuleType = None, symbol: str = None):
|
||||
def export_impl(obj):
|
||||
nonlocal module, symbol
|
||||
|
||||
module = module or importlib.import_module(__package__)
|
||||
symbol = symbol or obj.__name__
|
||||
|
||||
if not hasattr(module, "__all__"):
|
||||
module.__all__ = []
|
||||
|
||||
module.__all__.append(symbol)
|
||||
setattr(module, symbol, obj)
|
||||
|
||||
return obj
|
||||
|
||||
return export_impl
|
||||
|
||||
IS_AOT_ENABLED = hasattr(trt, "QuickPluginCreationRequest")
|
||||
@@ -0,0 +1,695 @@
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 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.
|
||||
#
|
||||
|
||||
import tensorrt as trt
|
||||
import types
|
||||
import typing
|
||||
from typing import Callable, Tuple, List
|
||||
import numpy as np
|
||||
from ._plugin_class import _TemplateJITPlugin
|
||||
from ._export import IS_AOT_ENABLED
|
||||
if IS_AOT_ENABLED:
|
||||
from ._plugin_class import _TemplateAOTPlugin
|
||||
from ._validate import (
|
||||
_parse_register_inputs,
|
||||
_parse_register_return,
|
||||
_validate_autotune,
|
||||
_validate_impl,
|
||||
_validate_aot_impl,
|
||||
_validate_name_and_namespace,
|
||||
)
|
||||
from ._utils import (
|
||||
_built_in_to_plugin_field_type,
|
||||
_join_with,
|
||||
_numpy_to_plugin_field_type,
|
||||
_is_numpy_array,
|
||||
_infer_numpy_type,
|
||||
)
|
||||
|
||||
from ._export import public_api
|
||||
|
||||
# Namespace to which plugins are dynamically bound
|
||||
# A namespace can be thought of as a library of plugins from the same author/common objective
|
||||
class _PluginNamespace(types.ModuleType):
|
||||
def __init__(self, namespace):
|
||||
super().__init__("tensorrt.plugin.op." + namespace)
|
||||
self._namespace = namespace
|
||||
|
||||
def define(self, name, plugin_def):
|
||||
assert not hasattr(self, name)
|
||||
setattr(self, name, plugin_def)
|
||||
|
||||
def __getattr__(self, name):
|
||||
raise AttributeError(
|
||||
f"'{self.__class__.__name__}' object '{self._namespace}' has no attribute '{name}'"
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f'_PluginNamespace(namespace="{self._namespace}")'
|
||||
|
||||
|
||||
# `tensorrt.plugin.op` module to which plugin namespaces are dynamically bound
|
||||
class _Op(types.ModuleType):
|
||||
def __init__(self):
|
||||
super().__init__("tensorrt.plugin.op")
|
||||
|
||||
def define_or_get(self, namespace):
|
||||
if hasattr(self, namespace):
|
||||
return getattr(self, namespace)
|
||||
|
||||
ns = _PluginNamespace(namespace)
|
||||
setattr(self, namespace, ns)
|
||||
|
||||
return ns
|
||||
|
||||
def __getattr__(self, name):
|
||||
raise AttributeError(
|
||||
f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
||||
)
|
||||
|
||||
|
||||
op = _Op()
|
||||
public_api(symbol="op")(op)
|
||||
|
||||
QDP_CREATORS = {}
|
||||
QDP_REGISTRY = {}
|
||||
|
||||
# Contains metadata about a registered plugin and `__call__()`` that allows for a plugin instance to be created
|
||||
class PluginDef:
|
||||
def __init__(self):
|
||||
self.plugin_id = None # includes namespace (format is ns::name)
|
||||
self.register_func = None
|
||||
self.impl_func = None
|
||||
self.aot_impl_func = None
|
||||
self.autotune_func = None
|
||||
self.autotune_attr_names = None
|
||||
self.input_tensor_names = None
|
||||
self.input_attrs = None # map name -> type
|
||||
self.impl_attr_names = None
|
||||
self.aot_impl_attr_names = None
|
||||
self.num_outputs = None
|
||||
self.input_arg_schema = None
|
||||
self.expects_tactic = None
|
||||
|
||||
def __call__(
|
||||
self, *args, **kwargs
|
||||
) -> Tuple[List[trt.ITensor], List[trt.ITensor], trt.IPluginV3]:
|
||||
namespace, name = self.plugin_id.split("::")
|
||||
|
||||
input_tensors = []
|
||||
schema_chunks = []
|
||||
|
||||
for t in args:
|
||||
if not isinstance(t, trt.ITensor):
|
||||
raise ValueError(
|
||||
f"Expected trt.ITensor but got input of type {type(t)}"
|
||||
)
|
||||
|
||||
schema_chunks.append("ITensor")
|
||||
input_tensors.append(t)
|
||||
|
||||
attrs = {}
|
||||
for key, value in kwargs.items():
|
||||
if key not in self.input_attrs:
|
||||
raise ValueError(
|
||||
f"Unexpected attribute {key} provided. Expected one of {self.input_attrs.keys()}."
|
||||
)
|
||||
attrs[key] = value
|
||||
attr_annotation = self.input_attrs[key]
|
||||
if isinstance(value, np.ndarray):
|
||||
if typing.get_origin(attr_annotation) == np.ndarray:
|
||||
np_dtype = typing.get_args(typing.get_args(attr_annotation)[1])[0]
|
||||
if np.dtype(np_dtype) != np.dtype(value.dtype):
|
||||
raise ValueError(
|
||||
f"Unexpected dtype '{np.dtype(value.dtype)}' for attribute '{key}'. Expected '{np_dtype}'."
|
||||
)
|
||||
else:
|
||||
if attr_annotation is not type(value):
|
||||
raise ValueError(
|
||||
f"Unexpected type '{type(value)}' for attribute '{key}'. Expected '{attr_annotation}'."
|
||||
)
|
||||
|
||||
schema_chunks.append(key)
|
||||
|
||||
expected_schema = (
|
||||
f"({_join_with(['ITensor'] * len(self.input_tensor_names))}"
|
||||
+ _join_with(self.input_attrs.keys(), True)
|
||||
+ ")"
|
||||
)
|
||||
schema = f"({', '.join(schema_chunks)})"
|
||||
|
||||
if schema != expected_schema:
|
||||
raise ValueError(
|
||||
f"Unexpected schema {schema} received. Expected {expected_schema}."
|
||||
)
|
||||
|
||||
if self.plugin_id in QDP_CREATORS:
|
||||
plg_creator = trt.get_plugin_registry().get_creator(name, "1", namespace)
|
||||
else:
|
||||
attrs_types = {}
|
||||
for key, value in kwargs.items():
|
||||
if isinstance(value, np.ndarray):
|
||||
attrs_types[key] = (False, value.dtype) # (builtin?, type)
|
||||
else:
|
||||
attrs_types[key] = (True, type(value)) # (builtin?, type)
|
||||
|
||||
plg_creator = _register_plugin_creator(name, namespace, attrs_types)
|
||||
|
||||
fields = []
|
||||
for key, value in attrs.items():
|
||||
if isinstance(value, np.ndarray):
|
||||
np_type = np.dtype(value.dtype)
|
||||
if np_type == np.float16:
|
||||
fields.append(
|
||||
trt.PluginField(
|
||||
key, value.tobytes(), trt.PluginFieldType.UNKNOWN
|
||||
)
|
||||
)
|
||||
else:
|
||||
fields.append(
|
||||
trt.PluginField(
|
||||
key, value, _numpy_to_plugin_field_type[np_type]
|
||||
)
|
||||
)
|
||||
elif isinstance(value, str):
|
||||
fields.append(
|
||||
trt.PluginField(key, value.encode(), trt.PluginFieldType.CHAR)
|
||||
)
|
||||
elif isinstance(value, bytes):
|
||||
fields.append(trt.PluginField(key, value, trt.PluginFieldType.UNKNOWN))
|
||||
else:
|
||||
fields.append(
|
||||
trt.PluginField(
|
||||
key,
|
||||
np.array([value]),
|
||||
_built_in_to_plugin_field_type[type(value)],
|
||||
)
|
||||
)
|
||||
|
||||
def create_plugin_instance(quick_plugin_creation_request: "trt.QuickPluginCreationRequest" = None):
|
||||
if quick_plugin_creation_request is None:
|
||||
plg = plg_creator.create_plugin(
|
||||
name,
|
||||
namespace,
|
||||
trt.PluginFieldCollection(fields),
|
||||
trt.TensorRTPhase.BUILD
|
||||
)
|
||||
else:
|
||||
plg = plg_creator.create_plugin(
|
||||
name,
|
||||
namespace,
|
||||
trt.PluginFieldCollection(fields),
|
||||
trt.TensorRTPhase.BUILD,
|
||||
quick_plugin_creation_request
|
||||
)
|
||||
|
||||
return input_tensors, [], plg
|
||||
|
||||
return create_plugin_instance
|
||||
|
||||
class _TemplatePluginCreator(trt.IPluginCreatorV3Quick):
|
||||
def __init__(self, name, namespace, attrs):
|
||||
trt.IPluginCreatorV3Quick.__init__(self)
|
||||
self.name = name
|
||||
self.plugin_namespace = namespace
|
||||
self.plugin_version = "1"
|
||||
field_names = []
|
||||
for name, (builtin, type_) in attrs.items():
|
||||
if builtin:
|
||||
if type_ is str:
|
||||
field_names.append(
|
||||
trt.PluginField(name, b"", trt.PluginFieldType.CHAR)
|
||||
)
|
||||
elif type_ is bytes:
|
||||
field_names.append(
|
||||
trt.PluginField(name, b"", trt.PluginFieldType.UNKNOWN)
|
||||
)
|
||||
else:
|
||||
field_names.append(
|
||||
trt.PluginField(
|
||||
name, np.array([]), _built_in_to_plugin_field_type[type_]
|
||||
)
|
||||
)
|
||||
else:
|
||||
field_names.append(
|
||||
trt.PluginField(
|
||||
name, np.array([]), _numpy_to_plugin_field_type[np.dtype(type_)]
|
||||
)
|
||||
)
|
||||
|
||||
self.field_names = trt.PluginFieldCollection(field_names)
|
||||
|
||||
def create_plugin(self, name, namespace, fc, phase, qpcr: "trt.QuickPluginCreationRequest" = None):
|
||||
desc = QDP_REGISTRY[f"{namespace}::{name}"]
|
||||
name = name
|
||||
namespace = namespace
|
||||
|
||||
attrs = {}
|
||||
for f in fc:
|
||||
if f.name not in desc.input_attrs:
|
||||
raise AssertionError(
|
||||
f"Unexpected attribute {f.name} provided to create_plugin. Expected one of {desc.input_attrs.keys()}."
|
||||
)
|
||||
|
||||
attr_type_annot = desc.input_attrs[f.name]
|
||||
if _is_numpy_array(attr_type_annot):
|
||||
np_type = _infer_numpy_type(attr_type_annot)
|
||||
if np_type == np.float16:
|
||||
attrs[f.name] = np.frombuffer(f.data.tobytes(), dtype=np.float16)
|
||||
else:
|
||||
attrs[f.name] = f.data.astype(np_type)
|
||||
else:
|
||||
if issubclass(attr_type_annot, str):
|
||||
attrs[f.name] = f.data.tobytes().decode("utf-8")
|
||||
else:
|
||||
attrs[f.name] = attr_type_annot(f.data)
|
||||
|
||||
jit_or_aot = None # True if JIT is to be created, False if AOT. Not None will be asserted before plugin creation.
|
||||
|
||||
if qpcr is None:
|
||||
plg = _TemplateJITPlugin(name, namespace, desc.num_outputs)
|
||||
|
||||
plg.init(
|
||||
desc.register_func,
|
||||
attrs,
|
||||
desc.impl_attr_names,
|
||||
desc.impl_func,
|
||||
desc.autotune_attr_names,
|
||||
desc.autotune_func,
|
||||
desc.expects_tactic,
|
||||
)
|
||||
|
||||
return plg
|
||||
|
||||
# If there is a strict preference, that takes precedence
|
||||
if qpcr == trt.QuickPluginCreationRequest.STRICT_AOT:
|
||||
if desc.aot_impl_func is None:
|
||||
raise ValueError(f"AOT implementation requested, but not defined for '{desc.plugin_id}'. Was @trt.plugin.aot_impl defined?")
|
||||
jit_or_aot = False
|
||||
elif qpcr == trt.QuickPluginCreationRequest.STRICT_JIT:
|
||||
if desc.impl_func is None:
|
||||
raise ValueError(f"JIT implementation requested, but not defined for '{desc.plugin_id}'. Was @trt.plugin.impl defined?")
|
||||
jit_or_aot = True
|
||||
else:
|
||||
aot_defined = desc.aot_impl_func is not None
|
||||
jit_defined = desc.impl_func is not None
|
||||
|
||||
# A preferemce must be indicated if both AOT and JIT implementations are defined
|
||||
if aot_defined and jit_defined:
|
||||
if qpcr == trt.QuickPluginCreationRequest.PREFER_AOT:
|
||||
jit_or_aot = False
|
||||
elif qpcr == trt.QuickPluginCreationRequest.PREFER_JIT:
|
||||
jit_or_aot = True
|
||||
else:
|
||||
raise ValueError(f"Plugin '{desc.plugin_id}' has both AOT and JIT implementations. NetworkDefinitionCreationFlag.PREFER_AOT_PYTHON_PLUGINS or NetworkDefinitionCreationFlag.PREFER_JIT_PYTHON_PLUGINS should be specified.")
|
||||
else:
|
||||
# If only one implementation is defined, use that.
|
||||
# Any preference specified is ignored. If the preference is strong, a strict flag should have been specified.
|
||||
if aot_defined:
|
||||
jit_or_aot = False
|
||||
elif jit_defined:
|
||||
jit_or_aot = True
|
||||
else:
|
||||
raise ValueError(f"Plugin '{desc.plugin_id}' does not have either a AOT or JIT implementation.")
|
||||
|
||||
assert jit_or_aot is not None
|
||||
|
||||
if jit_or_aot:
|
||||
plg = _TemplateJITPlugin(name, namespace, desc.num_outputs)
|
||||
|
||||
plg.init(
|
||||
desc.register_func,
|
||||
attrs,
|
||||
desc.impl_attr_names,
|
||||
desc.impl_func,
|
||||
desc.autotune_attr_names,
|
||||
desc.autotune_func,
|
||||
desc.expects_tactic,
|
||||
)
|
||||
|
||||
else:
|
||||
plg = _TemplateAOTPlugin(name, namespace, desc.num_outputs)
|
||||
|
||||
plg.init(
|
||||
desc.register_func,
|
||||
attrs,
|
||||
desc.aot_impl_attr_names,
|
||||
desc.aot_impl_func,
|
||||
desc.autotune_attr_names,
|
||||
desc.autotune_func
|
||||
)
|
||||
|
||||
# the caller can determine if the created plugin is an AOT or JIT plugin by inspecting the interface info
|
||||
return plg
|
||||
|
||||
def _register_plugin_creator(name: str, namespace: str, attrs_types):
|
||||
plg_registry = trt.get_plugin_registry()
|
||||
plg_creator = _TemplatePluginCreator(name, namespace, attrs_types)
|
||||
plg_registry.register_creator(plg_creator, namespace)
|
||||
plg_creator = plg_registry.get_creator(name, "1", namespace)
|
||||
QDP_CREATORS[f"{namespace}::{name}"] = plg_creator
|
||||
return plg_creator
|
||||
|
||||
|
||||
# Decorator for `tensorrt.plugin.register`
|
||||
# By default, the plugin will be immediately registered in the TRT plugin registry
|
||||
# During plugin development/when building engine, lazy registration may be used to delay plugin registration until the plugin is explicitly instantiated using `trt.plugin.op.ns.plugin_name(...)`
|
||||
@public_api()
|
||||
def register(plugin_id: str, lazy_register: bool = False) -> Callable:
|
||||
"""
|
||||
Wraps a function to register and describe a TensorRT plugin's IO characteristics. In addition, a complete plugin at least needs an `trt.plugin.impl` function to be registered.
|
||||
|
||||
This API is only intended to be used as a decorator. The decorated function must have type hints for all inputs as well as return value.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
(inp0: TensorDesc, inp1: TensorDesc, ..., attr0: SupportedAttrType, attr1: SupportedAttrType, ...) -> Union[TensorDesc, Tuple[TensorDesc]]
|
||||
|
||||
* Input tensors are declared first, each described by a tensor descriptor TensorDesc.
|
||||
* Plugin attributes are declared next. "SupportedAttrType" must be one of:
|
||||
* Supported built-in types: int, float, str, bool, bytes (Note: Lists/tuples of these types are not supported)
|
||||
* 1-D Numpy arrays of the following types: int8, int16, int32, int64, float16, float32, float64, bool. These must be annotated with 'numpy.typing.NDArray[dtype]', where 'dtype' is the expected numpy dtype.
|
||||
* If the plugin has only one output, the return annotation could be TensorDesc. Tuple[TensorDesc] could be used for any number of outputs.
|
||||
|
||||
By default, the plugin will be immediately registered in the TRT plugin registry. Use the lazy_register argument to change this.
|
||||
|
||||
Args:
|
||||
plugin_id: An ID for the plugin in the form "{namespace}::{name}",
|
||||
e.g. "my_project::add_plugin". The namespace is used to avoid collisions
|
||||
so using your product/project name is recommended.
|
||||
|
||||
lazy_register: During plugin development/when building engine, lazy registration may be used to delay plugin registration until the plugin is explicitly instantiated using `trt.plugin.op.ns.plugin_name(...)`
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: Registration of an elementwise plugin (output has same characteristics as the input)
|
||||
|
||||
import tensorrt.plugin as trtp
|
||||
|
||||
@trtp.register("my::add_plugin")
|
||||
def add_plugin_desc(inp0: trtp.TensorDesc, block_size: int) -> Tuple[trtp.TensorDesc]:
|
||||
return inp0.like()
|
||||
|
||||
"""
|
||||
|
||||
def decorator(register_func: Callable):
|
||||
|
||||
plugin_ns, plugin_name = plugin_id.split("::")
|
||||
_validate_name_and_namespace(plugin_ns, plugin_name)
|
||||
|
||||
op_namespace = op.define_or_get(plugin_ns)
|
||||
|
||||
if hasattr(op_namespace, plugin_name):
|
||||
raise ValueError(
|
||||
f"'{op.__class__.__name__}' already has a defintion for '{plugin_name}'"
|
||||
)
|
||||
|
||||
(
|
||||
tensor_names,
|
||||
input_attrs,
|
||||
input_arg_schema,
|
||||
attrs_types,
|
||||
) = _parse_register_inputs(register_func, lazy_register)
|
||||
|
||||
plugin_def = PluginDef()
|
||||
plugin_def.plugin_id = plugin_id
|
||||
plugin_def.register_func = register_func
|
||||
plugin_def.input_tensor_names = tensor_names
|
||||
plugin_def.input_attrs = input_attrs
|
||||
plugin_def.input_arg_schema = input_arg_schema
|
||||
|
||||
num_outputs = _parse_register_return(register_func)
|
||||
|
||||
plugin_def.num_outputs = num_outputs
|
||||
QDP_REGISTRY[plugin_id] = plugin_def
|
||||
|
||||
if not lazy_register:
|
||||
_register_plugin_creator(plugin_name, plugin_ns, attrs_types)
|
||||
|
||||
op_namespace.define(plugin_name, plugin_def)
|
||||
|
||||
return register_func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# Decorator for `tensorrt.plugin.impl`
|
||||
@public_api()
|
||||
def impl(plugin_id: str) -> Callable:
|
||||
"""
|
||||
Wraps a function to define an implementation for a plugin already registered through `trt.plugin.register`.
|
||||
|
||||
This API is only intended to be used as a decorator. The decorated function is not required to have type hints for input arguments or return value;
|
||||
however, any type hints specified will be validated against the `trt.plugin.register` signature for consistency.
|
||||
|
||||
The schema for the function is as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
(inp0: Tensor, inp1: Tensor, ..., attr0: SupportedAttrType, attr1: SupportedAttrType, outputs: Tuple[Tensor], stream: int, tactic: Optional[int]) -> None
|
||||
|
||||
* Input tensors are passed first, each described by a `Tensor`.
|
||||
* Plugin attributes are declared next.
|
||||
* Not all attributes included in `trt.plugin.register` must be specified here -- they could be a subset.
|
||||
* Included attributes will be serialized to the TRT engine. Therefore, only attributes the plugin actually needs to perform inference (within the body of `trt.plugin.impl`) should be included.
|
||||
* `tactic` is an optional argument. If the plugin is using custom tactics, it must be specified to receive the tactic value to use for the current execution of the plugin.
|
||||
|
||||
Args:
|
||||
plugin_id: The ID for the plugin in the form "{namespace}::{name}", which must match that used during `trt.plugin.register`
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: Implementation of an elementwise plugin with an OpenAI Triton kernel
|
||||
|
||||
import tensorrt.plugin as trtp
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def add_kernel(x_ptr, y_ptr, n_elements, BLOCK_SIZE: tl.constexpr):
|
||||
pid = tl.program_id(0)
|
||||
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
||||
mask = offsets < n_elements
|
||||
x = tl.load(x_ptr + offsets, mask=mask)
|
||||
tl.store(y_ptr + offsets, x + 1, mask=mask)
|
||||
|
||||
@trtp.register("my::add_plugin")
|
||||
def add_plugin_desc(inp0: trtp.TensorDesc, block_size: int) -> Tuple[trtp.TensorDesc]:
|
||||
return inp0.like()
|
||||
|
||||
@trtp.impl("my::add_plugin")
|
||||
def add_plugin_impl(inp0: trtp.Tensor, block_size: int, outputs: Tuple[trtp.Tensor], stream: int) -> None:
|
||||
|
||||
n = inp0.numel()
|
||||
inp0_t = torch.as_tensor(inp0, device="cuda")
|
||||
out_t = torch.as_tensor(outputs[0], device="cuda")
|
||||
|
||||
add_kernel[(triton.cdiv(n, block_size),)](inp0_t, out_t, n, BLOCK_SIZE = block_size)
|
||||
"""
|
||||
|
||||
def decorator(impl_func: Callable):
|
||||
if plugin_id not in QDP_REGISTRY:
|
||||
raise ValueError(
|
||||
f"Plugin {plugin_id} is not registered. Did you register it with tensorrt.plugin.register API?"
|
||||
)
|
||||
|
||||
plugin_def = QDP_REGISTRY[plugin_id]
|
||||
impl_attr_names, found_tactic = _validate_impl(impl_func, plugin_def)
|
||||
|
||||
plugin_def.impl_func = impl_func
|
||||
plugin_def.impl_attr_names = impl_attr_names
|
||||
plugin_def.expects_tactic = found_tactic
|
||||
return impl_func
|
||||
|
||||
return decorator
|
||||
|
||||
# Decorator for `tensorrt.plugin.aot_impl`
|
||||
@public_api()
|
||||
def aot_impl(plugin_id: str) -> Callable:
|
||||
"""
|
||||
Wraps a function to define an Ahead-of-Time (AOT) implementation for a plugin already registered through `trt.plugin.register`.
|
||||
|
||||
This API is only intended to be used as a decorator. The decorated function is not required to have type hints for input arguments or return value;
|
||||
however, any type hints specified will be validated against the `trt.plugin.register` signature for consistency.
|
||||
|
||||
The schema for the function is as follows:
|
||||
.. code-block:: text
|
||||
|
||||
(inp0: TensorDesc, inp1: TensorDesc, ..., attr0: SupportedAttrType, attr1: SupportedAttrType, outputs: Tuple[TensorDesc], tactic: Optional[int]) -> Tuple[str, str, KernelLaunchParams, SymExprs]
|
||||
|
||||
* Input tensors are passed first, each described by a `TensorDesc`.
|
||||
* Plugin attributes are declared next.
|
||||
* Not all attributes included in `trt.plugin.register` must be specified here -- they could be a subset.
|
||||
* NOTE: Plugin attributes are not serialized into the engine when using an AOT implementation.
|
||||
* `tactic` is an optional argument. If the plugin is using custom tactics, it must be specified to receive the tactic value to use for the current execution of the plugin.
|
||||
|
||||
Args:
|
||||
plugin_id: The ID for the plugin in the form "{namespace}::{name}", which must match that used during `trt.plugin.register`
|
||||
|
||||
:returns:
|
||||
- kernel_name: The name of the kernel.
|
||||
- compiled_kernel: Compiled form of the kernel. Presently, only PTX is supported.
|
||||
- launch_params: The launch parameters for the kernel
|
||||
- extra_args: Symbolic expressions for scalar inputs to the kernel, located after the tensor inputs and before the tensor outputs
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: Implementation of an elementwise plugin with an OpenAI Triton kernel
|
||||
|
||||
import tensorrt.plugin as trtp
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
@triton.jit
|
||||
def add_kernel(x_ptr, n_elements, y_ptr, BLOCK_SIZE: tl.constexpr):
|
||||
pid = tl.program_id(0)
|
||||
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
||||
mask = offsets < n_elements
|
||||
x = tl.load(x_ptr + offsets, mask=mask)
|
||||
tl.store(y_ptr + offsets, x + 1, mask=mask)
|
||||
|
||||
@trtp.register("my::add_plugin")
|
||||
def add_plugin_desc(inp0: trtp.TensorDesc, block_size: int) -> Tuple[trtp.TensorDesc]:
|
||||
return inp0.like()
|
||||
|
||||
@trtp.aot_impl("my::elemwise_add_plugin")
|
||||
def add_plugin_aot_impl(
|
||||
inp0: trtp.TensorDesc, block_size: int, single_tactic: bool, outputs: Tuple[trtp.TensorDesc], tactic: int
|
||||
) -> Tuple[Union[str, bytes], Union[str, bytes], trtp.KernelLaunchParams, trtp.SymExprs]:
|
||||
|
||||
type_str = "fp32" if inp0.dtype == trt.float32 else "fp16"
|
||||
|
||||
src = triton.compiler.ASTSource(
|
||||
fn=add_kernel,
|
||||
signature={
|
||||
"x_ptr": f"*{type_str}",
|
||||
"n_elements": "i32",
|
||||
"y_ptr": f"*{type_str}",
|
||||
},
|
||||
constexprs={
|
||||
"BLOCK_SIZE": block_size,
|
||||
},
|
||||
)
|
||||
|
||||
compiled_kernel = triton.compile(src)
|
||||
|
||||
N = inp0.shape_expr.numel()
|
||||
launch_params = trtp.KernelLaunchParams()
|
||||
|
||||
# grid dims
|
||||
launch_params.grid_x = trtp.cdiv(N, block_size)
|
||||
# block dims
|
||||
launch_params.block_x = compiled_kernel.metadata.num_warps * 32
|
||||
# shared memory
|
||||
launch_params.shared_mem = compiled_kernel.metadata.shared
|
||||
|
||||
extra_args = trtp.SymIntExprs(1)
|
||||
extra_args[0] = trtp.SymInt32(N)
|
||||
|
||||
return compiled_kernel.metadata.name, compiled_kernel.asm["ptx"], launch_params, extra_args
|
||||
"""
|
||||
def decorator(aot_impl_func: Callable):
|
||||
if plugin_id not in QDP_REGISTRY:
|
||||
raise ValueError(
|
||||
f"Plugin {plugin_id} is not registered. Did you register it with tensorrt.plugin.register API?"
|
||||
)
|
||||
|
||||
plugin_def = QDP_REGISTRY[plugin_id]
|
||||
aot_impl_attr_names = _validate_aot_impl(aot_impl_func, plugin_def)
|
||||
|
||||
plugin_def.aot_impl_func = aot_impl_func
|
||||
plugin_def.aot_impl_attr_names = aot_impl_attr_names
|
||||
return aot_impl_func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# Decorator for `tensorrt.plugin.autotune`
|
||||
@public_api()
|
||||
def autotune(plugin_id: str) -> Callable:
|
||||
"""
|
||||
Wraps a function to define autotune logic for a plugin already registered through `trt.plugin.register`.
|
||||
|
||||
Autotuning is the process by which TensorRT executes the plugin over IO type/format combinations, and any custom tactics advertised as being supported by the plugin.
|
||||
The (type, format, tactic) combination with the lowest latency is used to execute the plugin once the engine is built.
|
||||
|
||||
.. note:: An autotune function is optional. If not specified, TensorRT will assume the plugin only supports input types specified at network creation, output types specifeid through `trt.plugin.register`, and linear formats for all I/O.
|
||||
|
||||
This API is only intended to be used as a decorator. The decorated function is not required to have type hints for input arguments or return value; however, any type hints specified will be validated against the `trt.plugin.register` signature for consistency.
|
||||
|
||||
The schema for the function is as follows:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
(inp0: TensorDesc, inp1: TensorDesc, ..., attr0: SupportedAttrType, attr1: SupportedAttrType, outputs: Tuple[TensorDesc]) -> List[AutoTuneCombination]
|
||||
|
||||
* Input tensors are passed first, each described by a :class:`TensorDesc`.
|
||||
* Plugin attributes are declared next. Not all attributes included in `trt.plugin.register` must be specified here -- they could be a subset.
|
||||
* The function should return a list of :class:`AutoTuneCombination`\s.
|
||||
|
||||
Args:
|
||||
plugin_id: The ID for the plugin in the form "{namespace}::{name}", which must match that used during `trt.plugin.register`
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: An elementwise add plugin which supports both FP32 and FP16 linear I/O and wants to be tuned over 2 custom tactics.
|
||||
|
||||
import tensorrt.plugin as trtp
|
||||
|
||||
@trtp.register("my::add_plugin")
|
||||
def add_plugin_desc(inp0: trtp.TensorDesc, block_size: int) -> Tuple[trtp.TensorDesc]:
|
||||
return inp0.like()
|
||||
|
||||
@trtp.autotune("my::add_plugin")
|
||||
def add_plugin_autotune(inp0: trtp.TensorDesc, block_size: int, outputs: Tuple[trtp.TensorDesc]) -> List[trtp.AutoTuneCombination]:
|
||||
|
||||
return [trtp.AutoTuneCombination("FP32|FP16, FP32|FP16", "LINEAR", [1, 2])]
|
||||
|
||||
.. code-block:: python
|
||||
:linenos:
|
||||
:caption: Same as above example but using index-by-index construction of an `AutoTuneCombination`
|
||||
|
||||
import tensorrt.plugin as trtp
|
||||
|
||||
@trtp.register("my::add_plugin")
|
||||
def add_plugin_desc(inp0: trtp.TensorDesc, block_size: int) -> Tuple[trtp.TensorDesc]:
|
||||
return inp0.like()
|
||||
|
||||
@trtp.autotune("my::add_plugin")
|
||||
def add_plugin_autotune(inp0: trtp.TensorDesc, block_size: int, outputs: Tuple[trtp.TensorDesc]) -> List[trtp.AutoTuneCombination]:
|
||||
c = trtp.AutoTuneCombination()
|
||||
c.pos(0, "FP32|FP16", "LINEAR")
|
||||
c.pos(1, "FP32|FP16") # index 1 is the output. Omitting format is the same as declaring it to be LINEAR.
|
||||
c.tactics([1, 2])
|
||||
return [c]
|
||||
"""
|
||||
|
||||
def decorator(autotune_func: Callable):
|
||||
if plugin_id not in QDP_REGISTRY:
|
||||
raise ValueError(
|
||||
f"Plugin {plugin_id} is not registered. Did you register it with tensorrt.plugin.register API?"
|
||||
)
|
||||
|
||||
plugin_def = QDP_REGISTRY[plugin_id]
|
||||
autotune_attr_names = _validate_autotune(autotune_func, plugin_def)
|
||||
|
||||
plugin_def.autotune_func = autotune_func
|
||||
plugin_def.autotune_attr_names = autotune_attr_names
|
||||
|
||||
return autotune_func
|
||||
|
||||
return decorator
|
||||
@@ -0,0 +1,445 @@
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 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.
|
||||
#
|
||||
import tensorrt as trt
|
||||
from typing import Tuple, Union
|
||||
|
||||
import numpy as np
|
||||
from ._utils import _numpy_to_plugin_field_type, _built_in_to_plugin_field_type
|
||||
from ._tensor import TensorDesc, Tensor, Shape, ShapeExpr, ShapeExprs, SymIntExpr, SymExprs, SymInt32
|
||||
from ._export import IS_AOT_ENABLED
|
||||
|
||||
if IS_AOT_ENABLED:
|
||||
from ._tensor import KernelLaunchParams
|
||||
from ._autotune import _TypeFormatCombination
|
||||
|
||||
from ._export import public_api
|
||||
|
||||
|
||||
class _TemplatePluginBase(
|
||||
trt.IPluginV3,
|
||||
trt.IPluginV3QuickCore,
|
||||
trt.IPluginV3QuickBuild,
|
||||
):
|
||||
def __init__(self, name, namespace, num_outputs):
|
||||
trt.IPluginV3.__init__(self)
|
||||
trt.IPluginV3QuickCore.__init__(self)
|
||||
trt.IPluginV3QuickBuild.__init__(self)
|
||||
|
||||
self.plugin_version = "1"
|
||||
self.input_types = []
|
||||
self.aliased_map = {} # output index -> input index
|
||||
|
||||
self.plugin_namespace = namespace
|
||||
self.plugin_name = name
|
||||
self.num_outputs = num_outputs
|
||||
|
||||
self.autotune_combs = []
|
||||
self.supported_combs = {}
|
||||
self.curr_comb = None
|
||||
|
||||
def get_num_outputs(self):
|
||||
return self.num_outputs
|
||||
|
||||
def get_output_data_types(self, input_types, ranks):
|
||||
self.input_types = input_types
|
||||
|
||||
input_descs = [None] * len(input_types)
|
||||
input_desc_map = {}
|
||||
for i in range(len(input_types)):
|
||||
input_descs[i] = TensorDesc()
|
||||
input_descs[i].dtype = input_types[i]
|
||||
input_descs[i].shape_expr = ShapeExprs(ranks[i], _is_dummy=True)
|
||||
input_descs[i]._immutable = True
|
||||
input_desc_map[id(input_descs[i])] = i
|
||||
|
||||
output_descs = self.register_function(*input_descs, **self.attrs)
|
||||
if not isinstance(output_descs, Tuple):
|
||||
output_descs = tuple([output_descs])
|
||||
|
||||
self.output_types = []
|
||||
|
||||
for i in range(len(output_descs)):
|
||||
self.output_types.append(output_descs[i].dtype)
|
||||
|
||||
if output_descs[i].get_aliased() is not None:
|
||||
self.aliased_map[i] = input_desc_map[id(output_descs[i].get_aliased())]
|
||||
else:
|
||||
self.aliased_map[i] = -1
|
||||
|
||||
return self.output_types
|
||||
|
||||
def get_fields_to_serialize(self):
|
||||
fields = []
|
||||
for key, value in self.attrs.items():
|
||||
if key in self.impl_attr_names:
|
||||
if isinstance(value, np.ndarray):
|
||||
if np.dtype(value.dtype) == np.float16:
|
||||
fields.append(trt.PluginField(key, value.tobytes(), trt.PluginFieldType.UNKNOWN))
|
||||
else:
|
||||
fields.append(
|
||||
trt.PluginField(
|
||||
key,
|
||||
value,
|
||||
_numpy_to_plugin_field_type[np.dtype(value.dtype)],
|
||||
)
|
||||
)
|
||||
elif isinstance(value, str):
|
||||
fields.append(trt.PluginField(key, value.encode(), trt.PluginFieldType.CHAR))
|
||||
elif isinstance(value, bytes):
|
||||
fields.append(trt.PluginField(key, value, trt.PluginFieldType.UNKNOWN))
|
||||
else:
|
||||
fields.append(
|
||||
trt.PluginField(
|
||||
key,
|
||||
np.array([value]),
|
||||
_built_in_to_plugin_field_type[type(value)],
|
||||
)
|
||||
)
|
||||
|
||||
return trt.PluginFieldCollection(fields)
|
||||
|
||||
def get_output_shapes(self, inputs, shape_inputs, exprBuilder):
|
||||
assert len(shape_inputs) == 0 # Shape inputs are not yet supported for QDPs
|
||||
SymIntExpr._exprBuilder = exprBuilder
|
||||
self.input_descs = []
|
||||
for i in range(len(inputs)):
|
||||
desc = TensorDesc()
|
||||
inp = inputs[i]
|
||||
|
||||
desc.dtype = self.input_types[i]
|
||||
desc.shape_expr = ShapeExprs(len(inp))
|
||||
for j in range(len(inp)):
|
||||
desc.shape_expr[j] = ShapeExpr(inp[j])
|
||||
desc._immutable = True
|
||||
|
||||
self.input_descs.append(desc)
|
||||
|
||||
self.output_descs = self.register_function(*self.input_descs, **self.attrs)
|
||||
if not isinstance(self.output_descs, Tuple):
|
||||
self.output_descs = tuple([self.output_descs])
|
||||
|
||||
for idx, desc in enumerate(self.output_descs):
|
||||
if desc.is_size_tensor:
|
||||
desc._set_index(idx)
|
||||
|
||||
output_exprs = []
|
||||
for i in range(len(self.output_descs)):
|
||||
exprs = trt.DimsExprs(len(self.output_descs[i].shape_expr))
|
||||
for j in range(len(exprs)):
|
||||
exprs[j] = self.output_descs[i].shape_expr[j]._expr
|
||||
|
||||
output_exprs.append(exprs)
|
||||
|
||||
SymIntExpr._exprBuilder = None
|
||||
return output_exprs
|
||||
|
||||
def configure_plugin(self, inputs, outputs):
|
||||
self.curr_comb = _TypeFormatCombination()
|
||||
self.curr_comb.types = [inp.desc.type for inp in inputs] + [out.desc.type for out in outputs]
|
||||
self.curr_comb.layouts = [inp.desc.format for inp in inputs] + [out.desc.format for out in outputs]
|
||||
|
||||
def get_supported_format_combinations(self, in_out, num_inputs):
|
||||
if self.autotune_function is not None:
|
||||
if len(self.autotune_attr_names) > 0:
|
||||
val = [self.attrs[k] for k in self.autotune_attr_names]
|
||||
else:
|
||||
val = ()
|
||||
|
||||
for i, desc in enumerate(in_out):
|
||||
if i < num_inputs:
|
||||
self.input_descs[i]._immutable = False
|
||||
self.input_descs[i].shape = Shape(desc)
|
||||
self.input_descs[i].format = desc.desc.format
|
||||
self.input_descs[i].scale = desc.desc.scale
|
||||
self.input_descs[i]._immutable = True
|
||||
else:
|
||||
self.output_descs[i - num_inputs]._immutable = False
|
||||
self.output_descs[i - num_inputs].shape = Shape(desc)
|
||||
self.output_descs[i - num_inputs].format = desc.desc.format
|
||||
self.output_descs[i - num_inputs].scale = desc.desc.scale
|
||||
self.output_descs[i - num_inputs]._immutable = True
|
||||
|
||||
self.autotune_combs = self.autotune_function(*self.input_descs, *val, self.output_descs)
|
||||
|
||||
if len(self.autotune_combs) == 0:
|
||||
default_comb = [None] * len(in_out)
|
||||
comb = _TypeFormatCombination(len(in_out))
|
||||
for j in range(len(in_out)):
|
||||
default_comb[j] = trt.PluginTensorDesc()
|
||||
default_comb[j].type = (
|
||||
self.input_types[j] if j < num_inputs else self.output_descs[j - num_inputs].dtype
|
||||
)
|
||||
default_comb[j].format = trt.TensorFormat.LINEAR
|
||||
comb.types[j] = default_comb[j].type
|
||||
comb.layouts[j] = default_comb[j].format
|
||||
|
||||
self.supported_combs[comb] = set()
|
||||
|
||||
return default_comb
|
||||
|
||||
all_combs = []
|
||||
for comb in self.autotune_combs:
|
||||
all_combs.extend(comb._get_combinations())
|
||||
|
||||
ret_supported_combs = []
|
||||
self.supported_combs = {}
|
||||
|
||||
for i, comb in enumerate(all_combs):
|
||||
value = self.supported_combs.get(comb)
|
||||
if value is not None:
|
||||
value.update(set(comb.tactics) if comb.tactics is not None else set())
|
||||
else:
|
||||
self.supported_combs[comb] = set(comb.tactics) if comb.tactics is not None else set()
|
||||
for j in range(len(in_out)):
|
||||
curr_comb = trt.PluginTensorDesc()
|
||||
curr_comb.type = comb.types[j]
|
||||
curr_comb.format = comb.layouts[j]
|
||||
ret_supported_combs.append(curr_comb)
|
||||
|
||||
return ret_supported_combs
|
||||
|
||||
def get_aliased_input(self, output_index: int):
|
||||
return self.aliased_map[output_index]
|
||||
|
||||
def get_valid_tactics(self):
|
||||
tactics = self.supported_combs.get(self.curr_comb)
|
||||
assert tactics is not None
|
||||
return list(tactics)
|
||||
|
||||
def set_tactic(self, tactic):
|
||||
self._tactic = tactic
|
||||
|
||||
|
||||
class _TemplateJITPlugin(_TemplatePluginBase, trt.IPluginV3QuickRuntime):
|
||||
def __init__(self, name, namespace, num_outputs):
|
||||
super().__init__(name, namespace, num_outputs)
|
||||
trt.IPluginV3QuickRuntime.__init__(self)
|
||||
|
||||
self.expects_tactic = False
|
||||
|
||||
def init(
|
||||
self,
|
||||
register_function,
|
||||
attrs,
|
||||
impl_attr_names,
|
||||
impl_function,
|
||||
autotune_attr_names,
|
||||
autotune_function,
|
||||
expects_tactic,
|
||||
):
|
||||
self.register_function = register_function
|
||||
self.impl_function = impl_function
|
||||
self.attrs = attrs
|
||||
self.impl_attr_names = impl_attr_names
|
||||
self.autotune_attr_names = autotune_attr_names
|
||||
self.autotune_function = autotune_function
|
||||
self.expects_tactic = expects_tactic
|
||||
|
||||
def get_capability_interface(self, type):
|
||||
return self
|
||||
|
||||
def enqueue(
|
||||
self,
|
||||
input_desc,
|
||||
output_desc,
|
||||
inputs,
|
||||
outputs,
|
||||
in_strides,
|
||||
out_strides,
|
||||
stream,
|
||||
):
|
||||
input_tensors = [None] * (len(inputs))
|
||||
aliased_input_idxs = list(self.aliased_map.values())
|
||||
|
||||
for i in range(len(inputs)):
|
||||
input_tensors[i] = Tensor()
|
||||
input_tensors[i].dtype = input_desc[i].type
|
||||
input_tensors[i].shape = Shape(input_desc[i])
|
||||
input_tensors[i].format = input_desc[i].format
|
||||
input_tensors[i].scale = input_desc[i].scale
|
||||
input_tensors[i].data_ptr = inputs[i]
|
||||
input_tensors[i]._stream = stream
|
||||
input_tensors[i]._read_only = i not in aliased_input_idxs
|
||||
input_tensors[i].strides = in_strides[i]
|
||||
|
||||
output_tensors = [None] * (len(outputs))
|
||||
for i in range(len(outputs)):
|
||||
output_tensors[i] = Tensor()
|
||||
output_tensors[i].dtype = output_desc[i].type
|
||||
output_tensors[i].shape = Shape(output_desc[i])
|
||||
output_tensors[i].format = output_desc[i].format
|
||||
output_tensors[i].scale = output_desc[i].scale
|
||||
output_tensors[i].data_ptr = outputs[i]
|
||||
output_tensors[i]._stream = stream
|
||||
output_tensors[i]._read_only = False
|
||||
output_tensors[i].strides = out_strides[i]
|
||||
|
||||
for i, j in self.aliased_map.items():
|
||||
output_tensors[i]._aliased_to = input_tensors[j]
|
||||
input_tensors[j]._aliased_to = output_tensors[i]
|
||||
|
||||
for t in input_tensors:
|
||||
t._immutable = True
|
||||
|
||||
for t in output_tensors:
|
||||
t._immutable = True
|
||||
|
||||
if len(self.impl_attr_names) > 0:
|
||||
val = [self.attrs[k] for k in self.impl_attr_names]
|
||||
else:
|
||||
val = ()
|
||||
|
||||
if self.expects_tactic:
|
||||
self.impl_function(*input_tensors, *val, output_tensors, stream, self._tactic)
|
||||
else:
|
||||
self.impl_function(*input_tensors, *val, output_tensors, stream=stream)
|
||||
|
||||
def clone(self):
|
||||
cloned_plugin = _TemplateJITPlugin(self.plugin_name, self.plugin_namespace, self.num_outputs)
|
||||
cloned_plugin.__dict__.update(self.__dict__)
|
||||
return cloned_plugin
|
||||
|
||||
|
||||
if IS_AOT_ENABLED:
|
||||
|
||||
class _TemplateAOTPlugin(
|
||||
_TemplatePluginBase,
|
||||
trt.IPluginV3QuickAOTBuild,
|
||||
):
|
||||
def __init__(self, name, namespace, num_outputs):
|
||||
_TemplatePluginBase.__init__(self, name, namespace, num_outputs)
|
||||
trt.IPluginV3QuickAOTBuild.__init__(self)
|
||||
self.kernel_map = {}
|
||||
|
||||
def set_tactic(self, tactic):
|
||||
self._tactic = tactic
|
||||
|
||||
def init(
|
||||
self,
|
||||
register_function,
|
||||
attrs,
|
||||
aot_impl_attr_names,
|
||||
aot_impl_function,
|
||||
autotune_attr_names,
|
||||
autotune_function,
|
||||
):
|
||||
self.register_function = register_function
|
||||
self.aot_impl_function = aot_impl_function
|
||||
self.attrs = attrs
|
||||
self.aot_impl_attr_names = aot_impl_attr_names
|
||||
self.autotune_attr_names = autotune_attr_names
|
||||
self.autotune_function = autotune_function
|
||||
|
||||
def get_capability_interface(self, type):
|
||||
return self
|
||||
|
||||
def get_kernel(self, inputDesc, outputDesc):
|
||||
io_types = []
|
||||
io_formats = []
|
||||
|
||||
for i, desc in enumerate(inputDesc):
|
||||
io_types.append(desc.type)
|
||||
io_formats.append(desc.format)
|
||||
|
||||
for i, desc in enumerate(outputDesc):
|
||||
io_types.append(desc.type)
|
||||
io_formats.append(desc.format)
|
||||
|
||||
key = (tuple(io_types), tuple(io_formats), self._tactic)
|
||||
|
||||
assert key in self.kernel_map, "key {} not in kernel_map".format(key)
|
||||
|
||||
kernel_name, ptx = self.kernel_map[key]
|
||||
|
||||
return kernel_name, ptx.encode() if isinstance(ptx, str) else ptx
|
||||
|
||||
def get_launch_params(self, inDimsExprs, in_out, num_inputs, launchParams, symExprSetter, exprBuilder):
|
||||
|
||||
SymIntExpr._exprBuilder = exprBuilder
|
||||
|
||||
if len(self.attrs) > 0:
|
||||
_, val = zip(*self.attrs.items())
|
||||
else:
|
||||
val = ()
|
||||
|
||||
io_types = []
|
||||
io_formats = []
|
||||
|
||||
for i, desc in enumerate(in_out):
|
||||
if i < num_inputs:
|
||||
self.input_descs[i]._immutable = False
|
||||
self.input_descs[i].shape = Shape(desc)
|
||||
self.input_descs[i].dtype = desc.desc.type
|
||||
self.input_descs[i].format = desc.desc.format
|
||||
self.input_descs[i].scale = desc.desc.scale
|
||||
io_types.append(desc.desc.type)
|
||||
io_formats.append(desc.desc.format)
|
||||
self.input_descs[i]._immutable = True
|
||||
else:
|
||||
self.output_descs[i - num_inputs]._immutable = False
|
||||
self.output_descs[i - num_inputs].shape = Shape(desc)
|
||||
self.output_descs[i - num_inputs].dtype = desc.desc.type
|
||||
self.output_descs[i - num_inputs].format = desc.desc.format
|
||||
self.output_descs[i - num_inputs].scale = desc.desc.scale
|
||||
io_types.append(desc.desc.type)
|
||||
io_formats.append(desc.desc.format)
|
||||
self.output_descs[i - num_inputs]._immutable = True
|
||||
|
||||
kernel_name, ptx, launch_params, extra_args = self.aot_impl_function(
|
||||
*self.input_descs, *val, self.output_descs, self._tactic
|
||||
)
|
||||
|
||||
if not isinstance(kernel_name, str) and not isinstance(kernel_name, bytes):
|
||||
raise TypeError(f"Kernel name must be a 'str' or 'bytes'. Got: {type(kernel_name)}.")
|
||||
|
||||
if not isinstance(ptx, str) and not isinstance(ptx, bytes):
|
||||
raise TypeError(f"PTX/CUBIN must be a 'str' or 'bytes'. Got: {type(ptx)}.")
|
||||
|
||||
if not isinstance(launch_params, KernelLaunchParams):
|
||||
raise TypeError(
|
||||
f"Launch params must be a 'tensorrt.plugin.KernelLaunchParams'. Got: {type(launch_params)}."
|
||||
)
|
||||
|
||||
if not isinstance(extra_args, SymExprs):
|
||||
raise TypeError(f"Extra args must be a 'tensorrt.plugin.SymIntExprs'. Got: {type(extra_args)}.")
|
||||
|
||||
launchParams.grid_x = launch_params.grid_x()
|
||||
launchParams.grid_y = launch_params.grid_y()
|
||||
launchParams.grid_z = launch_params.grid_z()
|
||||
launchParams.block_x = launch_params.block_x()
|
||||
launchParams.block_y = launch_params.block_y()
|
||||
launchParams.block_z = launch_params.block_z()
|
||||
launchParams.shared_mem = launch_params.shared_mem()
|
||||
|
||||
self.kernel_map[(tuple(io_types), tuple(io_formats), self._tactic)] = (kernel_name, ptx)
|
||||
|
||||
symExprSetter.nbSymExprs = len(extra_args)
|
||||
|
||||
for i, arg in enumerate(extra_args):
|
||||
if not isinstance(arg, SymInt32):
|
||||
raise TypeError(f"Extra args must be a 'tensorrt.plugin.SymInt32'. Got: {type(arg)}.")
|
||||
symExprSetter[i] = arg()
|
||||
|
||||
SymIntExpr._exprBuilder = None
|
||||
|
||||
def get_timing_cache_id(self):
|
||||
return ""
|
||||
|
||||
def clone(self):
|
||||
cloned_plugin = _TemplateAOTPlugin(self.plugin_name, self.plugin_namespace, self.num_outputs)
|
||||
cloned_plugin.__dict__.update(self.__dict__)
|
||||
return cloned_plugin
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,132 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
from typing import Union, Tuple
|
||||
import tensorrt as trt
|
||||
from ._tensor import ShapeExpr, TensorDesc, ShapeExprs, SizeTensorDesc
|
||||
from ._export import public_api
|
||||
|
||||
# Miscellaneous top-level functions accessible through `tensorrt.plugin`
|
||||
|
||||
# Performs `trt.DimensionOperation.CEIL_DIV`
|
||||
@public_api()
|
||||
def cdiv(first: Union[int, ShapeExpr], second: Union[int, ShapeExpr]) -> ShapeExpr:
|
||||
"""
|
||||
Computes symbolic ceiling division of `first` by `second`
|
||||
|
||||
Args:
|
||||
first (Union[int, ShapeExpr]): Dividend
|
||||
second (Union[int, ShapeExpr]): Divisor
|
||||
|
||||
Raises:
|
||||
ValueError: If both arguments are `int`\s or if `second` evaluates to 0
|
||||
|
||||
Returns:
|
||||
ShapeExpr: Symbolic expression for the ceiling division of `first` by `second`
|
||||
"""
|
||||
if isinstance(first, int):
|
||||
if isinstance(second, int):
|
||||
raise ValueError("Both arguments cannot be 'int's")
|
||||
first = ShapeExpr(first)
|
||||
|
||||
return first._op(trt.DimensionOperation.CEIL_DIV, second)
|
||||
|
||||
|
||||
# Performs `trt.DimensionOperation.MAX`
|
||||
@public_api()
|
||||
def max(first: Union[int, ShapeExpr], second: Union[int, ShapeExpr]) -> ShapeExpr:
|
||||
"""
|
||||
Computes the maximum of `first` and `second`
|
||||
|
||||
Args:
|
||||
first (Union[int, ShapeExpr]): First operand
|
||||
second (Union[int, ShapeExpr]): Second operand
|
||||
|
||||
Raises:
|
||||
ValueError: If both arguments are `int`\s
|
||||
|
||||
Returns:
|
||||
ShapeExpr: Symbolic expression for the maximum of `first` and `second`
|
||||
"""
|
||||
if isinstance(first, int):
|
||||
if isinstance(second, int):
|
||||
raise ValueError("Both arguments cannot be 'int's")
|
||||
first = ShapeExpr(first)
|
||||
|
||||
return first._op(trt.DimensionOperation.MAX, second)
|
||||
|
||||
|
||||
# Performs `trt.DimensionOperation.MIN`
|
||||
@public_api()
|
||||
def min(first: Union[int, ShapeExpr], second: Union[int, ShapeExpr]) -> ShapeExpr:
|
||||
"""
|
||||
Computes the minimum of `first` and `second`
|
||||
|
||||
Args:
|
||||
first (Union[int, ShapeExpr]): First operand
|
||||
second (Union[int, ShapeExpr]): Second operand
|
||||
|
||||
Raises:
|
||||
ValueError: If both arguments are `int`\s
|
||||
|
||||
Returns:
|
||||
ShapeExpr: Symbolic expression for the minimum of `first` and `second`
|
||||
"""
|
||||
if isinstance(first, int):
|
||||
if isinstance(second, int):
|
||||
raise ValueError("Both arguments cannot be 'int's")
|
||||
first = ShapeExpr(first)
|
||||
|
||||
return first._op(trt.DimensionOperation.MIN, second)
|
||||
|
||||
|
||||
# Declare a size tensor descriptor with the specified autotune shape expression `opt` and `upper-bound` shape expression
|
||||
@public_api()
|
||||
def size_tensor(opt: ShapeExpr, upper_bound: ShapeExpr) -> SizeTensorDesc:
|
||||
"""
|
||||
Constructs a size tensor with the specified autotune shape expression `opt` and `upper_bound`
|
||||
|
||||
Args:
|
||||
opt (ShapeExpr): Symbolic expression for the extent of this size tensor to use in the autotune process of the engine build
|
||||
upper_bound (ShapeExpr): Symbolic expression for the upper-bound of this size tensor
|
||||
|
||||
Returns:
|
||||
SizeTensorDesc: A tensor descriptor for a size tensor with the specified autotune extent and upper-bound
|
||||
"""
|
||||
return SizeTensorDesc(opt, upper_bound)
|
||||
|
||||
# Create a TensorDesc using shape expressions and a dtype
|
||||
@public_api()
|
||||
def from_shape_expr(shape_expr: Union[Tuple[Union[ShapeExpr, int]], ShapeExprs], dtype: trt.DataType) -> TensorDesc:
|
||||
"""
|
||||
Constructs a tensor descriptor with the specified shape expression and data type
|
||||
|
||||
Args:
|
||||
shape_expr (Union[Tuple[Union[ShapeExpr, int]], ShapeExprs]): Expressions or constants denoting the shape of the tensor
|
||||
dtype (trt.DataType): Data type of the tensor
|
||||
|
||||
Returns:
|
||||
TensorDesc: Tensor descriptor with the specified shape expression and data type
|
||||
"""
|
||||
if isinstance(shape_expr, tuple):
|
||||
shape_expr_ = ShapeExprs.from_tuple(shape_expr)
|
||||
else:
|
||||
shape_expr_ = shape_expr
|
||||
|
||||
return TensorDesc(shape_expr_, dtype)
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
import tensorrt as trt
|
||||
import numpy as np
|
||||
import typing
|
||||
|
||||
_numpy_to_plugin_field_type = {
|
||||
np.dtype('int32'): trt.PluginFieldType.INT32,
|
||||
np.dtype('int16'): trt.PluginFieldType.INT16,
|
||||
np.dtype('int8'): trt.PluginFieldType.INT8,
|
||||
np.dtype('bool'): trt.PluginFieldType.INT8,
|
||||
np.dtype('int64'): trt.PluginFieldType.INT64,
|
||||
np.dtype('float32'): trt.PluginFieldType.FLOAT32,
|
||||
np.dtype('float64'): trt.PluginFieldType.FLOAT64,
|
||||
np.dtype('float16'): trt.PluginFieldType.FLOAT16
|
||||
}
|
||||
|
||||
_built_in_to_plugin_field_type = {
|
||||
int: trt.PluginFieldType.INT64,
|
||||
float: trt.PluginFieldType.FLOAT64,
|
||||
bool: trt.PluginFieldType.INT8,
|
||||
# str is handled separately, so not needed here
|
||||
}
|
||||
|
||||
def _str_to_data_type(dtype: str) -> trt.DataType:
|
||||
if dtype == "FP32":
|
||||
return trt.DataType.FLOAT
|
||||
if dtype == "FP16":
|
||||
return trt.DataType.HALF
|
||||
try:
|
||||
return getattr(trt.DataType, dtype)
|
||||
except KeyError:
|
||||
raise ValueError(f"Unknown data type string '{dtype}'") from None
|
||||
|
||||
|
||||
def _join_with(lst, middle = False, delim = ", "):
|
||||
if len(lst) == 0:
|
||||
return ""
|
||||
|
||||
ret = ""
|
||||
if middle:
|
||||
ret += ", "
|
||||
|
||||
ret += delim.join(lst)
|
||||
|
||||
return ret
|
||||
|
||||
def _is_npt_ndarray(annotation):
|
||||
return (typing.get_origin(annotation) == np.ndarray) or (hasattr(annotation, "__origin__") and annotation.__origin__ == np.ndarray)
|
||||
|
||||
def _is_numpy_array(annotation):
|
||||
return (annotation == np.ndarray) or _is_npt_ndarray(annotation)
|
||||
|
||||
def _infer_numpy_type(annotation):
|
||||
assert _is_npt_ndarray(annotation)
|
||||
annot_args = typing.get_args(annotation) or annotation.__args__
|
||||
if len(annot_args) >= 2:
|
||||
np_type = typing.get_args(annot_args[1]) or annot_args[1].__args__
|
||||
if len(np_type) >= 1:
|
||||
return np_type[0]
|
||||
|
||||
raise AttributeError("Improper annotation for numpy array. Annotate numpy array attributes using 'numpy.typing.NDArray[dtype]', where 'dtype' is the expected numpy dtype of the array.")
|
||||
@@ -0,0 +1,475 @@
|
||||
#
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 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.
|
||||
#
|
||||
|
||||
import inspect
|
||||
import numpy as np
|
||||
import typing
|
||||
import types
|
||||
|
||||
from ._utils import _is_numpy_array, _join_with, _infer_numpy_type, _is_npt_ndarray
|
||||
from ._tensor import TensorDesc, Tensor, SymExprs
|
||||
from ._export import IS_AOT_ENABLED
|
||||
if IS_AOT_ENABLED:
|
||||
from ._tensor import KernelLaunchParams
|
||||
from ._autotune import AutoTuneCombination
|
||||
|
||||
SERIALIZABLE_BUILTIN_TYPES = (int, float, bytes, bool, str)
|
||||
SERIALIZABLE_NP_DTYPES = (
|
||||
np.int8,
|
||||
np.int16,
|
||||
np.int32,
|
||||
np.int64,
|
||||
np.float16,
|
||||
np.float32,
|
||||
np.float64,
|
||||
bool,
|
||||
np.bool_,
|
||||
)
|
||||
|
||||
# Reserve some namespaces for future use/avoid confusion
|
||||
RESERVED_NAMESPACES = {
|
||||
"",
|
||||
"trt",
|
||||
"tensorrt",
|
||||
"std",
|
||||
}
|
||||
|
||||
DISALLOWED_ATTR_NAMES = {
|
||||
"outputs",
|
||||
"stream",
|
||||
"tactic",
|
||||
}
|
||||
|
||||
def _validate_name_and_namespace(ns: str, name: str):
|
||||
if "." in ns:
|
||||
raise ValueError(
|
||||
f"Provided namespace {ns} cannot have any '.' in trt.plugin.register(\"{ns}::{name}\", ...)"
|
||||
)
|
||||
|
||||
if "." in name:
|
||||
raise ValueError(
|
||||
f"Provided name {name} cannot have any '.' in trt.plugin.register(\"{ns}::{name}\", ...)"
|
||||
)
|
||||
|
||||
if ns in RESERVED_NAMESPACES:
|
||||
raise ValueError(
|
||||
f"Provided namespace {ns} is a reserved namespace"
|
||||
)
|
||||
|
||||
|
||||
# Parse `tensorrt.plugin.register` schema
|
||||
def _parse_register_inputs(register_func, lazy_register):
|
||||
tensor_names = []
|
||||
input_attrs = (
|
||||
dict()
|
||||
) # order is important here but for Python >= 3.7, dict respects key order
|
||||
|
||||
schema_chunks = []
|
||||
|
||||
# TensorDescs and attribute args cannot be interspersed, so remember when we saw the first attribute arg
|
||||
saw_first_attr = False
|
||||
|
||||
# Map of (attr_name: str) -> (is_builtin_type?: bool, type annotation: str)
|
||||
attrs_types = {}
|
||||
|
||||
sig = inspect.signature(register_func)
|
||||
|
||||
for idx, (name, param) in enumerate(sig.parameters.items()):
|
||||
|
||||
if param.kind not in (
|
||||
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
||||
inspect.Parameter.KEYWORD_ONLY,
|
||||
):
|
||||
raise ValueError(
|
||||
f"Argument {name} is not a positional-or-keyword or keyword-only arg"
|
||||
)
|
||||
|
||||
# Type annotations are manadatory for `tensorrt.plugin.register` args
|
||||
if param.annotation == inspect.Parameter.empty:
|
||||
raise ValueError(
|
||||
f"Argument {name} does not have a type annotation. Please mark as TensorDesc or one of the serializable attribute types."
|
||||
)
|
||||
|
||||
# Presently, we do not support default values for attributes
|
||||
if param.default is not inspect.Parameter.empty:
|
||||
raise ValueError(
|
||||
f"Argument {name} has a default value. Default values are not supported yet."
|
||||
)
|
||||
|
||||
|
||||
if issubclass(param.annotation, TensorDesc):
|
||||
if saw_first_attr:
|
||||
raise ValueError(
|
||||
f"TensorDescs args and attribute args cannot be interspersed. Received function with signature {sig}."
|
||||
)
|
||||
|
||||
tensor_names.append(name)
|
||||
schema_chunks.append(f"TensorDesc {name}")
|
||||
# At this point, we don't validate attribute types since we only care about the types of serializable attributes
|
||||
# However, we memorize name and type so that we may validate that the autotune function maintains consistency
|
||||
else:
|
||||
if idx == 0:
|
||||
raise ValueError(
|
||||
f"TensorDescs args should come first, followed by attributes. Received function with signature {sig}."
|
||||
)
|
||||
|
||||
if name in DISALLOWED_ATTR_NAMES:
|
||||
raise ValueError(
|
||||
f"'{name}' is not allowed as a plugin attribute name."
|
||||
)
|
||||
|
||||
if param.annotation not in SERIALIZABLE_BUILTIN_TYPES:
|
||||
if _is_numpy_array(param.annotation):
|
||||
if not lazy_register:
|
||||
if param.annotation == np.ndarray:
|
||||
raise ValueError(
|
||||
"If using non-lazy registration, annotate numpy array attributes using 'numpy.typing.NDArray[dtype]', where 'dtype' is the expected numpy dtype of the array."
|
||||
)
|
||||
|
||||
if _is_npt_ndarray(param.annotation):
|
||||
np_dtype = _infer_numpy_type(param.annotation)
|
||||
if np_dtype not in SERIALIZABLE_NP_DTYPES:
|
||||
raise ValueError(
|
||||
f"Attribute '{name}' is not a supported numpy array type. Supported numpy arrays type are {SERIALIZABLE_NP_DTYPES}."
|
||||
)
|
||||
attrs_types[name] = (False, np_dtype)
|
||||
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Attribute '{name}' of type {param.annotation} is not a supported serializable type. Supported types are {SERIALIZABLE_BUILTIN_TYPES} or numpy arrays of type {SERIALIZABLE_NP_DTYPES}."
|
||||
)
|
||||
else:
|
||||
attrs_types[name] = (True, param.annotation)
|
||||
|
||||
saw_first_attr = True
|
||||
|
||||
schema_chunks.append(f"{param.annotation} {name}")
|
||||
input_attrs[name] = param.annotation
|
||||
|
||||
return (
|
||||
tensor_names,
|
||||
input_attrs,
|
||||
f"({_join_with(schema_chunks)})",
|
||||
attrs_types,
|
||||
)
|
||||
|
||||
|
||||
def _parse_register_return(register_func):
|
||||
sig = inspect.signature(register_func)
|
||||
|
||||
ret_annotation = sig.return_annotation
|
||||
|
||||
if ret_annotation == inspect.Parameter.empty:
|
||||
raise ValueError(
|
||||
f"No return annotation found for register function. Received signature {sig}."
|
||||
)
|
||||
|
||||
if typing.get_origin(ret_annotation) is not tuple:
|
||||
if not inspect.isclass(ret_annotation) or not issubclass(
|
||||
ret_annotation, TensorDesc
|
||||
):
|
||||
raise ValueError(
|
||||
f"Return argument is of type {ret_annotation}. Return types can only be TensorDesc or Tuple[TensorDesc]."
|
||||
)
|
||||
|
||||
num_outputs = 1
|
||||
else:
|
||||
args = typing.get_args(ret_annotation)
|
||||
|
||||
for arg in args:
|
||||
if not issubclass(arg, TensorDesc):
|
||||
raise ValueError(
|
||||
f"Return argument is of type {ret_annotation}. Return types can only be TensorDesc or Tuple[TensorDesc]."
|
||||
)
|
||||
|
||||
num_outputs = len(args)
|
||||
|
||||
return num_outputs
|
||||
|
||||
|
||||
def _validate_impl(impl_func, plugin_def):
|
||||
impl_attr_names = []
|
||||
found_tactic = False
|
||||
|
||||
sig = inspect.signature(impl_func)
|
||||
registered_attr_names = plugin_def.input_attrs.keys()
|
||||
|
||||
# input arg annotations are optional, but we will validate if provided
|
||||
for name, param in sig.parameters.items():
|
||||
# tactic arg is optional in impl function. If specified, remember so that we can pass it during enqueue.
|
||||
if name == "tactic":
|
||||
found_tactic = True
|
||||
if param.annotation != inspect.Parameter.empty:
|
||||
if name == "outputs":
|
||||
if typing.get_origin(param.annotation) is not tuple:
|
||||
raise ValueError(
|
||||
f"'outputs' should be of type Tuple[Tensor]. Received {param.annotation}."
|
||||
)
|
||||
args = typing.get_args(param.annotation)
|
||||
for arg in args:
|
||||
if not issubclass(arg, Tensor):
|
||||
raise ValueError(
|
||||
f"Argument for receiving output Tensor, '{name}' contains a {param.annotation}. '{name}' should be a Tuple[Tensor]."
|
||||
)
|
||||
elif name == "stream":
|
||||
if not issubclass(param.annotation, int):
|
||||
raise ValueError("'stream' input argument should be an int")
|
||||
elif name == "tactic":
|
||||
if not issubclass(param.annotation, int):
|
||||
raise ValueError("'tactic' input argument should be an int")
|
||||
elif issubclass(param.annotation, Tensor):
|
||||
if name not in plugin_def.input_tensor_names:
|
||||
raise ValueError(
|
||||
f"Unexpected tensor '{name}' specified in autotune function. Expected one of {plugin_def.input_tensor_names}."
|
||||
)
|
||||
else:
|
||||
if name not in plugin_def.input_attrs:
|
||||
raise ValueError(
|
||||
f"Unexpected attribute '{name}' specified in impl function. Expected one of {list(registered_attr_names)}."
|
||||
)
|
||||
|
||||
if param.annotation != plugin_def.input_attrs[name]:
|
||||
raise ValueError(
|
||||
f"Attribute '{name}' has a type annotation different from the one specified at registration. Expected '{plugin_def.input_attrs[name]}'."
|
||||
)
|
||||
|
||||
impl_attr_names.append(name)
|
||||
else:
|
||||
if name in plugin_def.input_attrs:
|
||||
impl_attr_names.append(name)
|
||||
|
||||
# Expected attribute schema should be constructed in the order they appeared in the register function
|
||||
expected_attr_schema_chunks = [
|
||||
n for n in registered_attr_names if n in impl_attr_names
|
||||
]
|
||||
|
||||
expected_schema = (
|
||||
"("
|
||||
+ _join_with(plugin_def.input_tensor_names)
|
||||
+ _join_with(expected_attr_schema_chunks, True)
|
||||
+ ", outputs, stream"
|
||||
)
|
||||
if found_tactic:
|
||||
expected_schema += ", tactic)"
|
||||
else:
|
||||
expected_schema += ")"
|
||||
|
||||
if f"({', '.join(sig.parameters.keys())})" != expected_schema:
|
||||
raise ValueError(
|
||||
f"Signature of the impl function '{sig}' does not match the expected input arg schema: {expected_schema}"
|
||||
)
|
||||
|
||||
# Return annotation is optional, but we will validate if one is specified
|
||||
if sig.return_annotation != inspect.Parameter.empty and sig.return_annotation is not None:
|
||||
raise ValueError("Return annotation should be None.")
|
||||
|
||||
return impl_attr_names, found_tactic
|
||||
|
||||
def _validate_aot_impl(aot_impl_func, plugin_def):
|
||||
aot_impl_attr_names = []
|
||||
|
||||
sig = inspect.signature(aot_impl_func)
|
||||
registered_attr_names = plugin_def.input_attrs.keys()
|
||||
|
||||
# input arg annotations are optional, but we will validate if provided
|
||||
for name, param in sig.parameters.items():
|
||||
if param.annotation != inspect.Parameter.empty:
|
||||
if name == "outputs":
|
||||
if typing.get_origin(param.annotation) is not tuple:
|
||||
raise ValueError(
|
||||
f"'outputs' should be of type Tuple[TensorDesc]. Received {param.annotation}."
|
||||
)
|
||||
args = typing.get_args(param.annotation)
|
||||
for arg in args:
|
||||
if not issubclass(arg, TensorDesc):
|
||||
raise ValueError(
|
||||
f"Argument for receiving output TensorDesc, '{name}' contains a {param.annotation}. '{name}' should be a Tuple[TensorDesc]."
|
||||
)
|
||||
elif name == "tactic":
|
||||
if not issubclass(param.annotation, int):
|
||||
raise ValueError("'tactic' input argument should be an int")
|
||||
elif issubclass(param.annotation, TensorDesc):
|
||||
if name not in plugin_def.input_tensor_names:
|
||||
raise ValueError(
|
||||
f"Unexpected tensor '{name}' specified in autotune function. Expected one of {plugin_def.input_tensor_names}."
|
||||
)
|
||||
else:
|
||||
if name not in plugin_def.input_attrs:
|
||||
raise ValueError(
|
||||
f"Unexpected attribute '{name}' specified in aot_impl function. Expected one of {list(registered_attr_names)}."
|
||||
)
|
||||
|
||||
if param.annotation != plugin_def.input_attrs[name]:
|
||||
raise ValueError(
|
||||
f"Attribute '{name}' has a type annotation different from the one specified at registration. Expected '{plugin_def.input_attrs[name]}'."
|
||||
)
|
||||
|
||||
aot_impl_attr_names.append(name)
|
||||
else:
|
||||
if name in plugin_def.input_attrs:
|
||||
aot_impl_attr_names.append(name)
|
||||
|
||||
# Expected attribute schema should be constructed in the order they appeared in the register function
|
||||
expected_attr_schema_chunks = [
|
||||
n for n in registered_attr_names if n in aot_impl_attr_names
|
||||
]
|
||||
|
||||
expected_schema = (
|
||||
"("
|
||||
+ _join_with(plugin_def.input_tensor_names)
|
||||
+ _join_with(expected_attr_schema_chunks, True)
|
||||
+ ", outputs, tactic)"
|
||||
)
|
||||
|
||||
if f"({', '.join(sig.parameters.keys())})" != expected_schema:
|
||||
raise ValueError(
|
||||
f"Signature of the aot_impl function '{sig}' does not match the expected input arg schema: {expected_schema}"
|
||||
)
|
||||
|
||||
ret_annotation = sig.return_annotation
|
||||
|
||||
if ret_annotation == inspect.Parameter.empty:
|
||||
raise ValueError(
|
||||
f"No return annotation found for aot_impl function. Received signature {sig}."
|
||||
)
|
||||
|
||||
expected_return_schema = "tuple[str | bytes, str | bytes, tensorrt.plugin.KernelLaunchParams, tensorrt.plugin.SymIntExprs]"
|
||||
|
||||
# Return annotation is optional, but we will validate if one is specified
|
||||
if ret_annotation != inspect.Parameter.empty:
|
||||
if typing.get_origin(ret_annotation) is not tuple:
|
||||
raise ValueError(
|
||||
f"Return annotation is {ret_annotation}. Expected {expected_return_schema}."
|
||||
)
|
||||
else:
|
||||
args = typing.get_args(ret_annotation)
|
||||
|
||||
if len(args) != 4:
|
||||
raise ValueError(
|
||||
f"Return annotation is {ret_annotation}. Expected {expected_return_schema}."
|
||||
)
|
||||
|
||||
def validate_union_str_or_bytes(index):
|
||||
def validate_str_or_bytes(arg_):
|
||||
if (arg_ is not str) and (arg_ is not bytes):
|
||||
raise ValueError(
|
||||
f"Return annotation for argument at {index} is '{arg_}'. Expected 'str' or 'bytes'."
|
||||
)
|
||||
|
||||
orig = typing.get_origin(args[index])
|
||||
# orig is `typing.Union` when annotation uses typing module (e.g, Union[str, bytes])
|
||||
# orig is `types.UnionType` when annotation is of the new (3.10+) native syntax (e.g, str | bytes)
|
||||
if orig is typing.Union or orig is types.UnionType:
|
||||
for a in typing.get_args(args[index]):
|
||||
validate_str_or_bytes(a)
|
||||
else:
|
||||
# when annoted with `str` or `bytes`
|
||||
validate_str_or_bytes(args[index])
|
||||
|
||||
# kernel name should be str or bytes encoding
|
||||
validate_union_str_or_bytes(0)
|
||||
# kernel PTX should be str or bytes encoding
|
||||
validate_union_str_or_bytes(1)
|
||||
|
||||
if not issubclass(args[2], KernelLaunchParams):
|
||||
raise ValueError(f"Argument at index 2 of return annotation is '{args[2]}'. Expected 'tensorrt.plugin.KernelLaunchParams'.")
|
||||
|
||||
if not issubclass(args[3], SymExprs):
|
||||
raise ValueError(f"Argument at index 3 of return annotation is '{args[3]}'. Expected a descendent of tensorrt.plugin.SymExprs.")
|
||||
|
||||
return aot_impl_attr_names
|
||||
|
||||
|
||||
def _validate_autotune(autotune_func, plugin_def):
|
||||
|
||||
sig = inspect.signature(autotune_func)
|
||||
registered_attr_names = plugin_def.input_attrs.keys()
|
||||
|
||||
autotune_attr_names = []
|
||||
|
||||
# input arg annotations are optional, but we will validate if provided
|
||||
for name, param in sig.parameters.items():
|
||||
if param.annotation != inspect.Parameter.empty:
|
||||
if name == "outputs":
|
||||
if typing.get_origin(param.annotation) is not tuple:
|
||||
raise ValueError(
|
||||
f"'outputs' should be of type Tuple[TensorDesc]. Received {param.annotation}."
|
||||
)
|
||||
args = typing.get_args(param.annotation)
|
||||
for arg in args:
|
||||
if not issubclass(arg, TensorDesc):
|
||||
raise ValueError(
|
||||
f"Argument for receiving output TensorDescs, '{name}' contains a {param.annotation}. '{name}' should be a Tuple[TensorDesc]."
|
||||
)
|
||||
elif issubclass(param.annotation, TensorDesc):
|
||||
if name not in plugin_def.input_tensor_names:
|
||||
raise ValueError(
|
||||
f"Unexpected tensor '{name}' specified in autotune function. Expected one of {plugin_def.input_tensor_names}."
|
||||
)
|
||||
else:
|
||||
if name not in plugin_def.input_attrs:
|
||||
raise ValueError(
|
||||
f"Unexpected attribute '{name}' specified in autotune function. Expected one of {list(registered_attr_names)}."
|
||||
)
|
||||
if param.annotation != plugin_def.input_attrs[name]:
|
||||
raise ValueError(
|
||||
f"Attribute '{name}' has a type annotation different from the one specified at registration. Expected '{plugin_def.input_attrs[name]}'."
|
||||
)
|
||||
|
||||
autotune_attr_names.append(name)
|
||||
else:
|
||||
if name in plugin_def.input_attrs:
|
||||
autotune_attr_names.append(name)
|
||||
|
||||
# Expected attribute schema should be constructed in the order they appeared in the register function
|
||||
expected_attr_schema_chunks = [
|
||||
n for n in registered_attr_names if n in autotune_attr_names
|
||||
]
|
||||
|
||||
expected_schema = (
|
||||
"("
|
||||
+ _join_with(plugin_def.input_tensor_names)
|
||||
+ _join_with(expected_attr_schema_chunks, True)
|
||||
+ ", outputs)"
|
||||
)
|
||||
|
||||
if f"({', '.join(sig.parameters.keys())})" != expected_schema:
|
||||
raise ValueError(
|
||||
f"Specified autotune function signature {sig} is not consistent with the expected input arg schema {expected_schema}."
|
||||
)
|
||||
|
||||
ret_annotation = sig.return_annotation
|
||||
|
||||
# Return annotation is optional, but we will validate if one is specified
|
||||
if ret_annotation != inspect.Parameter.empty:
|
||||
if typing.get_origin(ret_annotation) is not list:
|
||||
if not inspect.isclass(ret_annotation) or not issubclass(
|
||||
ret_annotation, AutoTuneCombination
|
||||
):
|
||||
raise ValueError(
|
||||
f"Return argument is of type {ret_annotation}. Return types can only be AutoTuneCombination or List[AutoTuneCombination]."
|
||||
)
|
||||
else:
|
||||
args = typing.get_args(ret_annotation)
|
||||
|
||||
for arg in args:
|
||||
if not issubclass(arg, AutoTuneCombination):
|
||||
raise ValueError(
|
||||
f"Return argument is of type {ret_annotation}. Return types can only be AutoTuneCombination or List[AutoTuneCombination]."
|
||||
)
|
||||
|
||||
return autotune_attr_names
|
||||
Reference in New Issue
Block a user