Files
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

333 lines
12 KiB
Python

#
# SPDX-FileCopyrightText: Copyright (c) 1993-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 logging
import numpy as np
import onnx_graphsurgeon as gs
logging.basicConfig(level=logging.INFO)
logging.getLogger("ModelHelper").setLevel(logging.INFO)
log = logging.getLogger("ModelHelper")
@gs.Graph.register()
def op_with_const(self, op, name, input, value):
"""
Add an operation with constant to the graph which will operate on the input tensor with the value(s) given.
:param op: The ONNX operation to perform, i.e. "Add" or "Mul".
:param input: The tensor to operate on.
:param value: The value array to operate with.
:param name: The name to use for the node.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created {} node '{}': {}".format(op, name, value.squeeze()))
const = gs.Constant(name="{}_value:0".format(name), values=value)
return self.layer(
name=name, op=op, inputs=[input_tensor, const], outputs=[name + ":0"]
)
@gs.Graph.register()
def matmul(self, name, input, value):
"""
Add MatMul operation to the graph which will operate on the input tensor with the value(s) given.
:param input: The tensor to operate on.
:param value: The linear transformation matrix to operate with.
:param name: The name to use for the node.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created {} node '{}': {}".format("MatMul", name, value.squeeze()))
const = gs.Constant(name="{}_value:0".format(name), values=value)
return self.layer(
name=name, op="MatMul", inputs=[input_tensor, const], outputs=[name + ":0"]
)
@gs.Graph.register()
def clip(self, name, input, clip_min, clip_max):
"""
Add Clip operation to the graph which will operate on the input tensor with the value(s) given.
:param input: The tensor to operate on.
:param name: The name to use for the node.
:param clip_min: Minimum value to include, less is clipped.
:param clip_max: Maximum value to include, more is clipped.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created {} node '{}".format("Clip", name))
const_min = gs.Constant(
name="{}_value:0".format(name), values=np.asarray([clip_min], dtype=np.float32)
)
const_max = gs.Constant(
name="{}_value:1".format(name), values=np.asarray([clip_max], dtype=np.float32)
)
return self.layer(
name=name,
op="Clip",
inputs=[input_tensor, const_min, const_max],
outputs=[name + ":0"],
)
@gs.Graph.register()
def slice(self, name, input, starts, ends, axes):
"""
Add Slice operation to the graph which will operate on the input tensor with the value(s) given.
:param op: The ONNX operation to perform, i.e. "Add" or "Mul".
:param input: The tensor to operate on.
:param name: The name to use for the node.
:param starts: Value at which Slice starts.
:param ends: Value at which Slice ends.
:param axes: Axes on which Slice operation should be performed.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created {} node '{}".format("Slice", name))
const_start = gs.Constant(
name="{}_value:0".format(name), values=np.asarray([starts], dtype=np.int64)
)
const_end = gs.Constant(
name="{}_value:1".format(name), values=np.asarray([ends], dtype=np.int64)
)
const_axes = gs.Constant(
name="{}_value:2".format(name), values=np.asarray([axes], dtype=np.int64)
)
return self.layer(
name=name,
op="Slice",
inputs=[input_tensor, const_start, const_end, const_axes],
outputs=[name + ":0"],
)
@gs.Graph.register()
def unsqueeze(self, name, input, axes=[3]):
"""
Adds to the graph an Unsqueeze node for the given axes and to the given input.
:param self: The gs.Graph object being extended.
:param name: The name to use for the node.
:param input: The tensor to be "unsqueezed".
:param axes: A list of axes on which to add the new dimension(s).
:return: The first output tensor, to allow chained graph construction.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created Unsqueeze node '{}': {}".format(name, axes))
return self.layer(
name=name,
op="Unsqueeze",
inputs=[input_tensor],
outputs=[name + ":0"],
attrs={"axes": axes},
)
@gs.Graph.register()
def squeeze(self, name, input, axes=[2]):
"""
Adds to the graph an Squeeze node for the given axes and to the given input.
:param self: The gs.Graph object being extended.
:param name: The name to use for the node.
:param input: The tensor to be "squeezed".
:param axes: A list of axes on which to remove a dimension(s).
:return: The first output tensor, to allow chained graph construction.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created Squeeze node '{}': {}".format(name, axes))
return self.layer(
name=name,
op="Squeeze",
inputs=[input_tensor],
outputs=[name + ":0"],
attrs={"axes": axes},
)
@gs.Graph.register()
def gather(self, name, data, indices, axes=0):
"""
Adds to the graph a Gather node for the given axes and to the given input.
:param self: The gs.Graph object being extended.
:param name: The name to use for the node.
:param data: Data from which to gather specific tensors.
:param indices: Indices by which to gather data tensors.
:param axes: A list of axes on which to perform gather operation
"""
data_tensor = data if type(data) is gs.Variable else data[0]
indices_tensor = indices if type(indices) is gs.Variable else indices[0]
log.debug("Created Gather node '{}': {}".format(name, axes))
return self.layer(
name=name,
op="Gather",
inputs=[data_tensor, indices_tensor],
outputs=[name + ":0"],
attrs={"axes": axes},
)
@gs.Graph.register()
def transpose(self, name, input, perm):
"""
Adds to the graph a Transpose node for the given axes permutation and to the given input.
:param self: The gs.Graph object being extended.
:param name: The name to use for the node.
:param input: The tensor to be transposed.
:param perm: A list of axes defining their order after transposing occurs.
:return: The first output tensor, to allow chained graph construction.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created Transpose node '{}': {}".format(name, perm))
return self.layer(
name=name,
op="Transpose",
inputs=[input_tensor],
outputs=[name + ":0"],
attrs={"perm": perm},
)
@gs.Graph.register()
def sigmoid(self, name, input):
"""
Adds to the graph a Sigmoid node for the given input.
:param self: The gs.Graph object being extended.
:param name: The name to use for the node.
:param input: The tensor to be applied to.
:return: The first output tensor, to allow chained graph construction.
"""
input_tensor = input if type(input) is gs.Variable else input[0]
log.debug("Created Sigmoid node '{}'".format(name))
return self.layer(
name=name, op="Sigmoid", inputs=[input_tensor], outputs=[name + ":0"]
)
@gs.Graph.register()
def plugin(self, op, name, inputs: list, outputs: list, attrs):
"""
Adds to the graph a TensorRT plugin node with the given name, inputs and outputs. The attrs dictionary holds
attributes to be added to the plugin node.
:param self: The gs.Graph object being extended.
:param op: The registered name for the TensorRT plugin.
:param name: The name to use for the node.
:param inputs: The list of tensors to use an inputs.
:param outputs: The list of tensors to use as outputs.
:param attrs: The dictionary to use as attributes.
:return: The first output tensor, to allow chained graph construction.
"""
log.debug("Created TRT Plugin node '{}': {}".format(name, attrs))
return self.layer(op=op, name=name, inputs=inputs, outputs=outputs, attrs=attrs)
@gs.Graph.register()
def find_node_by_op(self, op):
"""
Finds the first node in the graph with the given operation name.
:param self: The gs.Graph object being extended.
:param op: The operation name to search for.
:return: The first node matching that performs that op.
"""
for node in self.nodes:
if node.op == op:
return node
return None
@gs.Graph.register()
def find_node_by_op_name(self, op, name):
"""
Finds the first node in the graph with the given operation name.
:param self: The gs.Graph object being extended.
:param op: The operation name to search for.
:param name: Selected node name.
:return: The first node matching that performs that op.
"""
for node in self.nodes:
if node.op == op and node.name == name:
return node
return None
@gs.Graph.register()
def find_node_by_op_input_output_name(
self, op, input_name, output_name, input_pos=0, output_pos=0
):
"""
Finds the first node in the graph with the given operation name.
:param self: The gs.Graph object being extended.
:param op: The operation name to search for.
:param input_pos: Which input to consider, default is 0.
:param output_pos: Which output to consider, default is 0.
:param input_name: Selected input's name.
:param output_name: Selected output's name.
:return: The first node matching that performs that op.
"""
for node in self.nodes:
if (
node.op == op
and node.inputs[input_pos].name == input_name
and node.outputs[output_pos].name == output_name
):
return node
return None
@gs.Graph.register()
def find_descendant_by_op(self, node, op, depth=10):
"""
Starting from the given node, finds a node lower in the graph matching the given operation name.
This is not an exhaustive graph search.
In order to graph search bfs is used, so runtime complexity is O(V+E).
:param self: The gs.Graph object being extended.
:param node: The node to start searching from.
:param op: The operation name to search for.
:param depth: Stop searching after traversing these many nodes.
:return: The first descendant node matching that performs that op.
"""
queue = []
for i in range(depth):
queue.append(node.o())
while queue:
node = queue.pop(0)
if node.op == op:
return node
for child in node.outputs[0].outputs:
queue.append(child)
return None
@gs.Graph.register()
def find_ancestor_by_op(self, node, op, depth=10):
"""
Starting from the given node, finds a node higher in the graph matching the given operation name.
This is not an exhaustive graph search.
In order to graph search bfs is used, so runtime complexity is O(V+E).
:param self: The gs.Graph object being extended.
:param node: The node to start searching from.
:param op: The operation name to search for.
:param depth: Stop searching after traversing these many nodes.
:return: The first ancestor node matching that performs that op.
"""
queue = []
for i in range(depth):
queue.append(node.i())
while queue:
node = queue.pop(0)
if node.op == op:
return node
for child in node.inputs[-1].inputs:
queue.append(child)
return None