chore: import upstream snapshot with attribution
Docker Image CI / build-ubuntu2004 (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:55 +08:00
commit c8a779b1bb
1887 changed files with 3245738 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
# Python-based NonZero Plugin for TensorRT using IPluginV3
## Description
This sample, `non_zero_plugin`, implements a Python-based plugin for the NonZero operation, configurable to use a `CUDA Python` or `PyTorch` backend.
NonZero is an operation where the non-zero indices of the input tensor is found.
## How does this sample work?
This sample creates and runs a TensorRT engine built from a network containing a single NonZeroPlugin node. It demonstrates how
custom layers with data-dependent output shapes can be implemented and added to a TensorRT network using Python.
### Implementing a NonZero plugin using IPluginV3 interface
Until `IPluginV3` (and associated interfaces), TensorRT plugins could not have outputs whose shapes depended on the input values (they could only depend
on input shapes). `IPluginV3OneBuild` which exposes a build capability for `IPluginV3`, provides support for such data-dependent output shapes.
`NonZeroPlugin` in this sample is written to handle 2-D input tensors of shape $R \times C$. Assume that the tensor contains $K$ non-zero elements and that the
non-zero indices are required in a row ordering (each set of indices in its own row). Then the output shape would be $K \times 2$.
The output shapes are expressed to the TensorRT builder through the `IPluginV3OneBuild.get_output_shapes()` API. Expressing the second dimension of the output is
straightforward:
```
# output_dims[0] = trt.DimsExprs(2)
output_dims[0][1] = exprBuilder.constant(2)
```
The extent of each data-dependent dimension in the plugin must be expressed in terms of a *_size tensor_*. A size tensor is a scalar output of type
`trt.int32` or `trt.int64` that must be added as one of the plugin outputs. In this case, it is sufficient to declare one size tensor to denote the extent of the
first dimension of the non-zero indices output. To declare a size tensor, one must provide an upper-bound and optimum value for its extent as `IDimensionExpr`s. These can be formed through the `IExprBuilder` argument passed to the `IPluginV3OneBuild.get_output_shapes()` method.
- For unknown inputs, the upper-bound is the total number of elements in the input
```
upper_bound = exprBuilder.operation(trt.DimensionOperation.PROD, inputs[0][0], inputs[0][1])
```
- A good estimate for the optimum is that half of the elements are non-zero
```
opt_value = exprBuilder.operation(trt.DimensionOperation.FLOOR_DIV, upper_bound, exprBuilder.constant(2))
```
Now we can declare the size tensor using the `IExprBuilder.declare_size_tensor()` method, which also requires the specification of the output index at which the size tensor would reside. Let us place it after the non-zero indices output:
```
num_non_zero_size_tensor = exprBuilder.declare_size_tensor(1, opt_value, upper_bound)
```
Now we are ready to specify the extent of the first dimension of the non-zero indices output:
```
# output_dims[0] = trt.DimsExprs(0)
output_dims[0][0] = num_non_zero_size_tensor
```
Note that the size tensor is declared to be a scalar (0-D):
### Creating network and building the engine
To add the plugin to the network, the `INetworkDefinition::add_plugin_v3()` method must be used.
Similar to `IPluginCreator` used for V2 plugins, V3 plugins must be accompanied by the registration of a plugin creator implementing the `IPluginCreatorV3One` interface.
## Running the sample
1. Run the sample to create a TensorRT inference engine and run inference:
`python3 non_zero_plugin.py [-h] [--precision {fp32,fp16}] [--backend {cuda_python,torch}] [--net_type {onnx,inetdef}]`
2. Verify that the sample ran successfully. If the sample runs successfully you should see the following message:
```
Inference result correct!
```
### Sample `--help` options
To see the full list of available options and their descriptions, use the `-h` or `--help` command line option.
# Additional resources
The following resources provide a deeper understanding about the V3 TensorRT plugins and the NonZero operation:
**NonZero**
- [ONNX: NonZero](https://onnx.ai/onnx/operators/onnx__NonZero.html)
**C++-based NonZero Plugin sample**
- [NonZero C++ Plugin](../../sampleNonZeroPlugin/)
**TensorRT plugins**
- [Extending TensorRT with Custom Layers](https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#extending)
- [TensorRT Python-based Plugins](https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/#add_custom_layer_python)
**Other documentation**
- [Introduction To NVIDIAs TensorRT Samples](https://docs.nvidia.com/deeplearning/sdk/tensorrt-sample-support-guide/index.html#samples)
- [Working With TensorRT Using The Python API](https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/#python_topics)
- [NVIDIAs TensorRT Documentation Library](https://docs.nvidia.com/deeplearning/sdk/tensorrt-archived/index.html)
# License
For terms and conditions for use, reproduction, and distribution, see the [TensorRT Software License Agreement](https://docs.nvidia.com/deeplearning/sdk/tensorrt-sla/index.html) documentation.
# Changelog
October 2025
Migrate to strongly typed APIs.
August 2025
Removed support for Python versions < 3.10.
April 2024
This is the first version of this `README.md` file.
# Known issues
There are no known issues in this sample.
@@ -0,0 +1,352 @@
#
# 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 onnx_graphsurgeon as gs
import numpy as np
import onnx
import os
import sys
import tensorrt as trt
from polygraphy.backend.trt import (
CreateConfig,
EngineFromNetwork,
NetworkFromOnnxPath,
TrtRunner,
create_network,
engine_from_network,
)
import argparse
from polygraphy import mod
sys.path.insert(1, os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
from plugin_utils import cuda_call, KernelHelper, UnownedMemory, volume
cuda = mod.lazy_import("cuda.bindings.driver")
cudart = mod.lazy_import("cuda.bindings.runtime")
nvrtc = mod.lazy_import("cuda.bindings.nvrtc")
torch = mod.lazy_import("torch")
cp = mod.lazy_import("cupy")
non_zero_half_kernel = r'''
#include <cuda_fp16.h>
extern "C" __global__
void find_non_zero_indices_half(
half const* X, int* indices, unsigned long long* count, int R, int C)
{
static_assert(sizeof(unsigned long long) == 8U, "unsigned long long must be 8 bytes in NVCC");
int row = blockIdx.x * blockDim.x + threadIdx.x;
// Check if the row index is within bounds
if (row < R)
{
for (int col = 0; col < C; ++col)
{
half const z = static_cast<half>(0.F);
if (X[col + C * row] != z)
{
// Increment count atomically and get the previous value
unsigned long long index = atomicAdd(count, 1ULL);
indices[2 * index] = row;
indices[2 * index + 1] = col;
}
}
}
}
'''
non_zero_float_kernel = r'''
extern "C" __global__
void find_non_zero_indices_float(
float const* X, int* indices, unsigned long long* count, int R, int C)
{
static_assert(sizeof(unsigned long long) == 8U, "unsigned long long must be 8 bytes in NVCC");
int row = blockIdx.x * blockDim.x + threadIdx.x;
// Check if the row index is within bounds
if (row < R)
{
for (int col = 0; col < C; ++col)
{
if (X[col + C * row] != 0.F)
{
// Increment count atomically and get the previous value
unsigned long long index = atomicAdd(count, 1ULL);
indices[2 * index] = row;
indices[2 * index + 1] = col;
}
}
}
}
'''
class NonZeroPlugin(trt.IPluginV3, trt.IPluginV3OneCore, trt.IPluginV3OneBuild, trt.IPluginV3OneRuntime):
def __init__(self, backend = None):
trt.IPluginV3.__init__(self)
trt.IPluginV3OneCore.__init__(self)
trt.IPluginV3OneBuild.__init__(self)
trt.IPluginV3OneRuntime.__init__(self)
self.num_outputs = 2
self.plugin_namespace = ""
self.plugin_name = "NonZeroPlugin"
self.plugin_version = "1"
if backend is not None:
self.backend = backend.tobytes().decode("utf-8")
else:
self.backend = "cuda_python"
self.cuDevice = None
def get_capability_interface(self, type):
return self
def get_output_data_types(self, input_types):
return [trt.DataType.INT32, trt.DataType.INT64]
def get_output_shapes(self, inputs, shape_inputs, exprBuilder):
# First output is 2-D
# Second output is a size tensor, which must be declared a scalar (0-D)
output_dims = [trt.DimsExprs(2), trt.DimsExprs(0)]
upper_bound = exprBuilder.operation(trt.DimensionOperation.PROD, inputs[0][0], inputs[0][1])
opt_value = exprBuilder.operation(trt.DimensionOperation.FLOOR_DIV, upper_bound, exprBuilder.constant(2))
num_non_zero_size_tensor = exprBuilder.declare_size_tensor(1, opt_value, upper_bound)
output_dims[0][0] = num_non_zero_size_tensor
output_dims[0][1] = exprBuilder.constant(2)
return output_dims
def get_fields_to_serialize(self):
return trt.PluginFieldCollection(
[
trt.PluginField(
"backend", self.backend.encode(), trt.PluginFieldType.CHAR
)
]
)
def configure_plugin(self, inp, out):
if self.backend == "cuda_python":
self.cuDevice = cuda_call(cuda.cuDeviceGet(0))
def on_shape_change(self, inp, out):
if self.backend == "cuda_python":
self.cuDevice = cuda_call(cuda.cuDeviceGet(0))
def supports_format_combination(self, pos, in_out, num_inputs):
assert num_inputs == 1
assert pos < len(in_out)
type_ok = False
# first input should be float16 or float32
if pos == 0:
type_ok = in_out[0].desc.type == trt.DataType.FLOAT or in_out[0].desc.type == trt.DataType.HALF
elif pos == 1:
type_ok = in_out[1].desc.type == trt.DataType.INT32
else: # pos == 2
# size tensor outputs must be NCHW INT64
type_ok = in_out[2].desc.type == trt.DataType.INT64
return in_out[pos].desc.format == trt.TensorFormat.LINEAR and type_ok
def enqueue(self, input_desc, output_desc, inputs, outputs, workspace, stream):
inp_dtype = trt.nptype(input_desc[0].type)
if self.backend == "cuda_python":
R = input_desc[0].dims[0]
C = input_desc[0].dims[1]
blockSize = 256
numBlocks = int((C + blockSize - 1) // blockSize)
d_in = np.array([inputs[0]], dtype=np.uint64)
d_out_0 = np.array([outputs[0]], dtype=np.uint64)
d_out_1 = np.array([outputs[1]], dtype=np.uint64)
args = [d_in, d_out_0, d_out_1, np.array(R, dtype=np.uint32), np.array(C, dtype=np.uint32)]
kernelArgs = np.array([arg.ctypes.data for arg in args], dtype=np.uint64)
stream_ptr = np.array([stream], dtype=np.uint64)
if inp_dtype == np.float32:
kernelHelper = KernelHelper(non_zero_float_kernel, int(self.cuDevice))
_non_zero_float_kernel = kernelHelper.getFunction(b'find_non_zero_indices_float')
cuda_call(cuda.cuLaunchKernel(_non_zero_float_kernel,
numBlocks, 1, 1,
blockSize, 1, 1,
0,
stream_ptr,
kernelArgs, 0))
elif inp_dtype == np.float16:
kernelHelper = KernelHelper(non_zero_half_kernel, int(self.cuDevice))
_non_zero_half_kernel = kernelHelper.getFunction(b'find_non_zero_indices_half')
cuda_call(cuda.cuLaunchKernel(_non_zero_half_kernel,
numBlocks, 1, 1,
blockSize, 1, 1,
0,
stream_ptr,
kernelArgs, 0))
else:
raise ValueError("inp_dtype not valid")
elif self.backend == "torch":
inp_mem = UnownedMemory(inputs[0], input_desc[0].dims, inp_dtype)
out_mem = UnownedMemory(
outputs[0], 2 * volume(input_desc[0].dims), np.int32
)
out_1_mem = UnownedMemory(outputs[1], 1, np.int64)
a_t = torch.as_tensor(inp_mem.d, device="cuda")
out = torch.nonzero(a_t)
out_mem.d[: volume(out.shape)] = cp.reshape(cp.asarray(out), (-1,))
cp.copyto(out_1_mem.d, cp.reshape(cp.asarray([out.shape[0]]), (-1,)))
else:
raise ValueError(f"backend not valid: {self.backend}")
def attach_to_context(self, context):
return self.clone()
def set_tactic(self, tactic):
pass
def clone(self):
cloned_plugin = NonZeroPlugin()
cloned_plugin.__dict__.update(self.__dict__)
return cloned_plugin
#
# The following defaults take effect since the respective methods are not overriden
#
# def get_valid_tactics(self):
# return []
# def get_workspace_size(self, input_desc, output_desc):
# return 0
# def destroy(self):
# pass
class NonZeroPluginCreator(trt.IPluginCreatorV3One):
def __init__(self):
trt.IPluginCreatorV3One.__init__(self)
self.name = "NonZeroPlugin"
self.plugin_namespace = ""
self.plugin_version = "1"
self.field_names = trt.PluginFieldCollection(
[trt.PluginField("backend", np.array([]), trt.PluginFieldType.CHAR)]
)
def create_plugin(self, name, fc, phase):
backend = None
for f in fc:
if f.name == "backend":
backend = f.data[:-1] if f.data[-1] == 0 else f.data
return NonZeroPlugin(backend)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--precision', type=str, default="fp32", choices=["fp32", "fp16"])
parser.add_argument("--backend", type=str, default="torch", choices=["cuda_python", "torch"])
parser.add_argument('--net_type', type=str, default="onnx", choices=["onnx", "inetdef"])
args = parser.parse_args()
if args.backend == "cuda_python":
# Initialize CUDA and create default context
cuda_call(cudart.cudaFree(0))
elif args.backend == "torch":
# Initialize CUDA and create default context
torch.cuda.init()
precision = np.float32 if args.precision == "fp32" else np.float16
inp_shape = (128, 128)
X = np.random.normal(size=inp_shape).astype(precision)
# Zero out a random set of indices
indices = np.random.choice(np.prod(inp_shape), replace=False, size=np.random.randint(0, np.prod(inp_shape) + 1))
X[np.unravel_index(indices, inp_shape)] = 0
# Register plugin creator
plg_registry = trt.get_plugin_registry()
my_plugin_creator = NonZeroPluginCreator()
plg_registry.register_creator(my_plugin_creator, "")
if args.net_type == "onnx":
# create ONNX model
onnx_path = "test_NonZeroPlugin.onnx"
inputX = gs.Variable(name="X", shape=inp_shape, dtype=precision)
Y = gs.Variable(name="Y", dtype=np.int32)
Y_num = gs.Variable(name="Y_num", dtype=np.int64)
nonZeroPluginNode = gs.Node(
name="NonZeroPlugin",
op="NonZeroPlugin",
inputs=[inputX],
outputs=[Y, Y_num],
attrs={"backend": args.backend.encode()},
)
graph = gs.Graph(nodes=[nonZeroPluginNode], inputs=[inputX], outputs=[Y], opset=16)
onnx.save(gs.export_onnx(graph), onnx_path)
# build engine
build_engine = EngineFromNetwork(
NetworkFromOnnxPath(onnx_path, strongly_typed=True), CreateConfig()
)
else:
# Create plugin object
builder, network = create_network(strongly_typed=True)
plg_creator = plg_registry.get_creator("NonZeroPlugin", "1", "")
plugin_fields_list = [
trt.PluginField("backend", args.backend.encode(), trt.PluginFieldType.CHAR)
]
pfc = trt.PluginFieldCollection(plugin_fields_list)
plugin = plg_creator.create_plugin("NonZeroPlugin", pfc, trt.TensorRTPhase.BUILD)
# Populate network
inputX = network.add_input(name="X", dtype=trt.float32 if precision==np.float32 else trt.float16, shape=inp_shape)
out = network.add_plugin_v3([inputX], [], plugin)
out.get_output(0).name = "Y"
network.mark_output(tensor=out.get_output(0))
build_engine = engine_from_network((builder, network), CreateConfig())
# Compare against Numpy's nonzero
Y_ref = np.transpose(np.nonzero(X))
# Run
with TrtRunner(build_engine, "trt_runner") as runner:
outputs = runner.infer({"X": X})
Y = outputs["Y"]
Y = Y[np.lexsort(np.fliplr(Y).T)]
if np.allclose(Y, Y_ref):
print("Inference result correct!")
else:
print("Inference result incorrect!")
@@ -0,0 +1,13 @@
cuda-python==12.9.0
cupy-cuda12x
torch
--extra-index-url https://pypi.ngc.nvidia.com
polygraphy>=0.50.1
colored
numpy==1.26.4
--extra-index-url https://pypi.ngc.nvidia.com
onnx-graphsurgeon
pywin32; platform_system == "Windows"
pyyaml==6.0.3
requests==2.32.4
tqdm==4.66.4