1 # Copyright (c) 2019-2020 NVIDIA CORPORATION. All rights reserved.
3 @page dwx_dnn_plugins DNN Plugins
5 @note SW Release Applicability: This tutorial is applicable to modules in both **NVIDIA DriveWorks** and **NVIDIA DRIVE Software** releases.
7 @section plugin_overview DNN Plugins Overview
9 The DNN Plugins module enables DNN models that are composed of layers that are not supported by TensorRT to benefit from the efficiency of TensorRT.
11 Integrating such models to DriveWorks can be divided into the following steps:
13 -# @ref plugin_implementation "Implement custom layers as dwDNNPlugin and compile as a dynamic library"
14 -# @ref model_generation "Generate TensorRT model with plugins using tensorRT_optimization tool"
15 -# @ref model_runtime "Load the model and the corresponding plugins at initialization"
17 @section plugin_implementation Implementing Custom DNN Layers as Plugins
19 @note Dynamic libraries representing DNN layers must depend on the same TensorRT, CuDNN and CUDA version as DriveWorks if applicable.
21 A DNN plugin in DriveWorks requires implementing a set of pre-defined functions. The declarations of these functions are located in `dw/dnn/plugins/DNNPlugin.h`.
23 These functions have `_dwDNNPluginHandle_t` in common. This is a custom variable that can be constructed at initialization and used or modified during the lifetime of the plugin.
25 In this tutorial, we are going to implement and integrate a fully-connected layer (FCPlugin) for an MNIST network model. See \ref dwx_dnn_plugin_sample for the corresponding sample.
27 \anchor ip2_description The fully-connected layer in the MNIST network model looks like this:
53 In FCPlugin example, we shall implement a class to store the members that must be available throughout the lifetime of the network model as well as to provide the methods that are required by dwDNNPlugin. Two of these members are cuBLAS and CuDNN handles, which we shall use to perform the inference.
54 This class will be the custom variable that will be passed to each function.
59 FCPlugin(const dwDNNPluginWeights *weights, int32_t numWeights,
60 int32_t numOutputChannels);
62 FCPlugin(const void* data, size_t length);
69 int32_t getNbOutputs() const;
71 dwBlobSize getOutputDimensions(int32_t index, const dwBlobSize* inputs, int32_t numInputDims);
73 bool supportsFormat(dwPrecision precision, dwDNNPluginLayout layout);
75 void configureWithFormat(dwPrecision precision, dwDNNPluginLayout layout);
77 size_t getWorkspaceSize(int32_t) const;
79 int32_t enqueue(int32_t batchSize, const void* const* inputs, void** outputs, void*, cudaStream_t stream);
81 size_t getSerializationSize();
83 void serialize(void* buffer);
86 size_t type2size(dwPrecision precision);
88 template<typename T> void write(char*& buffer, const T& val);
90 template<typename T> void read(const char*& buffer, T& val);
92 void* copyToDevice(const void* data, size_t count);
94 void convertAndCopyToDevice(void*& deviceWeights, const dwDNNPluginWeights& weights);
96 void convertAndCopyToBuffer(char*& buffer, const dwDNNPluginWeights& weights);
98 void deserializeToDevice(const char*& hostBuffer, void*& deviceWeights, size_t size);
100 // Number of input and output channels of FC layer
101 int32_t m_numOutputChannels, m_numInputChannels;
102 // Kernel and bias weights of FC layer
103 dwDNNPluginWeights m_kernelWeights, m_biasWeights;
104 // Precision of the network model
105 dwPrecision m_precision{DW_PRECISION_FP32};
107 // Actual storage on the device for kernel and bias weights
108 void* m_deviceKernel{nullptr};
109 void* m_deviceBias{nullptr};
111 // CuDNN and cuBLAS handles that are needed for the execution of the layer
112 cudnnHandle_t m_cudnn;
113 cublasHandle_t m_cublas;
114 cudnnTensorDescriptor_t m_srcDescriptor, m_dstDescriptor;
120 A plugin can be initialized either directly from a structure `dwDNNPluginWeights`:
122 dwStatus _dwDNNPlugin_initializeFromWeights(_dwDNNPluginHandle_t* handle, const char* layerName,
123 const dwDNNPluginWeights* weights, int32_t numWeights);
126 or it can be initialized from serialized data:
128 dwStatus _dwDNNPlugin_initialize(_dwDNNPluginHandle_t* handle, const char* layerName,
129 const void* data, size_t length);
132 Both of these functions shall define how the plugin should be initialized from given input data.
134 In FCPlugin example, we shall implement the backend for both these initialization functions as constructors of the class and implement both of the initialization functions mentioned above to create an instance of FCPlugin class. Additionally, we shall define helper methods to be able to deserialize layer from data.
136 The first constructor implements initializing the plugin class from `dwDNNPluginWeight`.
138 // Construction from dwDNNPluginWeights
139 FCPlugin::FCPlugin(const dwDNNPluginWeights *weights, int32_t numWeights, int32_t numOutputChannels)
140 : m_numOutputChannels(numOutputChannels)
142 m_kernelWeights = weights[0];
143 m_biasWeights = weights[1];
144 m_kernelWeights.values = malloc(m_kernelWeights.count * type2size(m_kernelWeights.precision));
146 memcpy(const_cast<void*>(m_kernelWeights.values), weights[0].values, m_kernelWeights.count * type2size(m_kernelWeights.precision));
147 m_biasWeights.values = malloc(m_biasWeights.count*type2size(m_biasWeights.precision));
148 memcpy(const_cast<void*>(m_biasWeights.values), weights[1].values, m_biasWeights.count*type2size(m_biasWeights.precision));
150 m_numInputChannels = int(weights[0].count / numOutputChannels);
155 This constructor allocates kernel and bias weights, and copies them from the given input. `numOutputChannels` is a configurable parameter of the layer which is defined in \ref ip2_description "the model's description".
157 The second constructor implements initializing the plugin class from binary data.
159 // Construction from data
160 FCPlugin::FCPlugin(const void* data, size_t length)
162 const char* d = static_cast<const char*>(data);
163 read(d, m_numInputChannels);
164 read(d, m_numOutputChannels);
166 m_kernelWeights.count = m_numInputChannels * m_numOutputChannels;
167 m_kernelWeights.values = nullptr;
169 read(d, m_biasWeights.count);
170 m_biasWeights.values = nullptr;
172 read(d, m_precision);
174 deserializeToDevice(d, m_deviceKernel, m_kernelWeights.count*type2size(m_precision));
175 deserializeToDevice(d, m_deviceBias, m_biasWeights.count*type2size(m_precision));
179 The deserialization of the data is dependent on the way the data is \ref serialization "serialized". Firstly, this constructor deserializes the dimensions. Then, it allocates member variables accordingly and deserializes the weights.
181 Initialize function is used by both constructors to create CuDNN and cuBLAS handles, and to perform the operations that are common to both constructors.
183 // Initialize common members to both constructors
184 void FCPlugin::initialize()
186 CHECK_CUDA_ERROR(cudnnCreate(&m_cudnn));// initialize cudnn and cublas
187 CHECK_CUDA_ERROR(cublasCreate(&m_cublas));
188 CHECK_CUDA_ERROR(cudnnCreateTensorDescriptor(&m_srcDescriptor));// create cudnn tensor descriptors we need for bias addition
189 CHECK_CUDA_ERROR(cudnnCreateTensorDescriptor(&m_dstDescriptor));
190 if (m_kernelWeights.values)
191 convertAndCopyToDevice(m_deviceKernel, m_kernelWeights);
192 if (m_biasWeights.values)
193 convertAndCopyToDevice(m_deviceBias, m_biasWeights);
197 Deserialization, in this case, is performed by simply copying the binary data from host to device.
200 void FCPlugin::deserializeToDevice(const char*& hostBuffer, void*& deviceWeights, size_t size)
202 deviceWeights = copyToDevice(hostBuffer, size);
207 The following function facilitates allocating data on device and copying data to device.
209 // Allocate and copy given data to device memory
210 void* FCPlugin::copyToDevice(const void* data, size_t count)
213 CHECK_CUDA_ERROR(cudaMalloc(&deviceData, count));
214 CHECK_CUDA_ERROR(cudaMemcpy(deviceData, data, count, cudaMemcpyHostToDevice));
219 The following function performs precision conversion if necessary before copying data to device.
221 // Convert weights based on loaded precision and copy to device
222 void FCPlugin::convertAndCopyToDevice(void*& deviceWeights, const dwDNNPluginWeights& weights)
224 if (weights.precision != m_precision) // Weights are converted in host memory first, if the type does not match
226 size_t size = weights.count* type2size(m_precision);
227 void* buffer = malloc(size);
228 for (int64_t v = 0; v < weights.count; ++v)
229 if (m_precision == DW_PRECISION_FP32)
230 static_cast<float32_t*>(buffer)[v] = __half2float(static_cast<const __half*>(weights.values)[v]);
232 static_cast<__half*>(buffer)[v] = __float2half(static_cast<const float32_t*>(weights.values)[v]);
234 deviceWeights = copyToDevice(buffer, size);
238 deviceWeights = copyToDevice(weights.values, weights.count * type2size(m_precision));
242 The initialization functions required by dwDNNPlugin can now be implemented as follows:
244 dwStatus _dwDNNPlugin_initializeFromWeights(_dwDNNPluginHandle_t* handle, const char*,
245 const dwDNNPluginWeights* weights, int32_t numWeights)
247 std::unique_ptr<FCPlugin> fcPlugin(new FCPlugin(weights, numWeights, 10));
248 *handle = reinterpret_cast<_dwDNNPluginHandle_t>(fcPlugin.release());
252 dwStatus _dwDNNPlugin_initialize(_dwDNNPluginHandle_t* handle, const char*,
253 const void* data, size_t length)
255 std::unique_ptr<FCPlugin> fcPlugin(new FCPlugin(data, length));
256 *handle = reinterpret_cast<_dwDNNPluginHandle_t>(fcPlugin.release());
263 The release function must destroy everything that was created by the plugin at initialization.
265 dwStatus _dwDNNPlugin_release(_dwDNNPluginHandle_t handle);
268 In FCPlugin example, we are going to destroy everything in the destructor.
271 FCPlugin::~FCPlugin()
273 if (m_kernelWeights.values)
275 free(const_cast<void*>(m_kernelWeights.values));
276 m_kernelWeights.values = nullptr;
278 if (m_biasWeights.values)
280 free(const_cast<void*>(m_biasWeights.values));
281 m_biasWeights.values = nullptr;
287 void FCPlugin::terminate()
289 CHECK_CUDA_ERROR(cublasDestroy(m_cublas));
290 CHECK_CUDA_ERROR(cudnnDestroy(m_cudnn));
293 cudaFree(m_deviceKernel);
294 m_deviceKernel = nullptr;
298 cudaFree(m_deviceBias);
299 m_deviceBias = nullptr;
303 dwStatus _dwDNNPlugin_release(_dwDNNPluginHandle_t handle)
305 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
311 ## \anchor serialization Serialization
313 A plugin must provide functions to enable serialization from a buffer.
316 dwStatus _dwDNNPlugin_getSerializationSize(size_t *serializationSize, _dwDNNPluginHandle_t handle);
318 dwStatus _dwDNNPlugin_serialize(void* buffer, _dwDNNPluginHandle_t handle);
321 In FCPlugin example, we store the number of input channels, the number of output channels, the number of bias weights and precision. In addition,
322 we store the kernel and bias weights. Therefore:
325 size_t FCPlugin::getSerializationSize()
327 return sizeof(m_numInputChannels) + sizeof(m_numOutputChannels) + sizeof(m_biasWeights.count) + sizeof(m_precision) +
328 (m_kernelWeights.count + m_biasWeights.count) * type2size(m_precision);
331 void FCPlugin::serialize(void* buffer)
333 char* d = static_cast<char*>(buffer);
335 write(d, m_numInputChannels);
336 write(d, m_numOutputChannels);
337 write(d, m_biasWeights.count);
338 write(d, m_precision);
339 convertAndCopyToBuffer(d, m_kernelWeights);
340 convertAndCopyToBuffer(d, m_biasWeights);
343 void FCPlugin::convertAndCopyToBuffer(char*& buffer, const dwDNNPluginWeights& weights)
345 if (weights.precision != m_precision)
346 for (int64_t v = 0; v < weights.count; ++v)
347 if (m_precision == DW_PRECISION_FP32)
348 reinterpret_cast<float32_t*>(buffer)[v] = __half2float(static_cast<const __half*>(weights.values)[v]);
350 reinterpret_cast<__half*>(buffer)[v] = __float2half(static_cast<const float32_t*>(weights.values)[v]);
352 memcpy(buffer, weights.values, weights.count * type2size(m_precision));
353 buffer += weights.count * type2size(m_precision);
357 The interface functions can be defined as:
360 dwStatus _dwDNNPlugin_getSerializationSize(size_t *serializationSize, _dwDNNPluginHandle_t handle)
362 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
363 *serializationSize = plugin->getSerializationSize();
367 dwStatus _dwDNNPlugin_serialize(void* buffer, _dwDNNPluginHandle_t handle)
369 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
370 plugin->serialize(buffer);
377 There are multiple getter functions that are required to be implemented by plugin.
379 A plugin must provide a function for returning how many outputs it has:
382 dwStatus _dwDNNPlugin_getNumOutputs(int32_t* numOutputs, _dwDNNPluginHandle_t handle);
385 In addition, it must provide a function for returning dimensions for each of the outputs based on the input dimensions:
388 dwStatus _dwDNNPlugin_getOutputDimensions(dwBlobSize *outputDimensions,
389 int32_t outputIndex, const dwBlobSize* inputDimensions,
390 int32_t numInputs, _dwDNNPluginHandle_t handle);
393 Finally, it must provide a function to return temporary workspace size required by this layer.
396 dwStatus _dwDNNPlugin_getWorkspaceSize(size_t* workspaceSize, int32_t maxBatchSize,
397 _dwDNNPluginHandle_t handle);
400 In FCPlugin example, we know that there is only one output of the layer:
403 int32_t FCPlugin::getNbOutputs() const
409 The dimensions of the output is expected to be NxCx1x1 where N is the batch size of the input, and C is the number of output channels.
411 dwBlobSize FCPlugin::getOutputDimensions(int32_t index, const dwBlobSize* inputs, int32_t numInputDims)
413 return dwBlobSize{inputs[0].batchsize, m_numOutputChannels, 1, 1};
418 FCPlugin does not need any temporary workspace during the generation of the model.
420 size_t FCPlugin::getWorkspaceSize(int32_t) const
426 Finally, the interface functions can be defined as:
428 dwStatus _dwDNNPlugin_getNumOutputs(int32_t* numOutputs, _dwDNNPluginHandle_t handle)
430 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
431 *numOutputs = plugin->getNbOutputs();
435 dwStatus _dwDNNPlugin_getOutputDimensions(dwBlobSize *outputDimensions,
436 int32_t outputIndex, const dwBlobSize* inputDimensions,
437 int32_t numInputs, _dwDNNPluginHandle_t handle)
439 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
440 *outputDimensions = plugin->getOutputDimensions(outputIndex, inputDimensions, numInputs);
444 dwStatus _dwDNNPlugin_getWorkspaceSize(size_t* workspaceSize, int32_t maxBatchSize,
445 _dwDNNPluginHandle_t handle)
447 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
448 *workspaceSize = plugin->getWorkspaceSize(maxBatchSize);
455 A plugin must implement a function to indicate whether a given format is supported:
458 dwStatus _dwDNNPlugin_supportsFormat(bool* res, dwPrecision precision,
459 dwDNNPluginLayout pluginLayout, _dwDNNPluginHandle_t handle);
462 Furthermore, it must allow configuring the format:
465 dwStatus _dwDNNPlugin_configureWithFormat(const dwBlobSize* inputDimensions,
466 int32_t numInputs, const dwBlobSize* outputDimensions,
467 int32_t numOutputs, dwPrecision precision,
468 dwDNNPluginLayout layout, int32_t maxBatchSize,
469 _dwDNNPluginHandle_t handle);
472 In FCPlugin sample, we support single and half precision and only planar layout:
475 bool FCPlugin::supportsFormat(dwPrecision precision, dwDNNPluginLayout layout) const
477 return (precision == DW_PRECISION_FP32 || precision == DW_PRECISION_FP16) && layout == DW_DNN_PLUGIN_LAYOUT_NCHW;
481 Since there is only one layout supported, precision is the only configurable parameter:
484 void FCPlugin::configureWithFormat(dwPrecision precision, dwDNNPluginLayout layout)
486 m_precision = precision;
490 Hence, the implementation of interface functions look like this:
492 dwStatus _dwDNNPlugin_supportsFormat(bool* res, dwPrecision precision,
493 dwDNNPluginLayout pluginLayout, _dwDNNPluginHandle_t handle)
495 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
496 *res = plugin->supportsFormat(precision, pluginLayout);
500 dwStatus _dwDNNPlugin_configureWithFormat(const dwBlobSize* inputDimensions,
501 int32_t numInputs, const dwBlobSize* outputDimensions,
502 int32_t numOutputs, dwPrecision precision,
503 dwDNNPluginLayout layout, int32_t maxBatchSize,
504 _dwDNNPluginHandle_t handle)
506 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
507 plugin->configureWithFormat(precision, layout);
514 A plugin must implement a function to perform inference:
517 dwStatus _dwDNNPlugin_enqueue(int32_t batchSize, const void* const* inputs, void** outputs,
518 void* workspace, cudaStream_t stream, _dwDNNPluginHandle_t handle)
521 In FCPlugin example, we shall use CuDNN and cuBLAS to perform fully-connected layer.
522 We shall use cuBLAS for matrix multiplication and CuDNN for adding bias.
525 int32_t FCPlugin::enqueue(int32_t batchSize, const void*const * inputs, void** outputs, void*, cudaStream_t stream)
527 float32_t onef{1.0f}, zerof{0.0f};
528 __half oneh = __float2half(1.0f), zeroh = __float2half(0.0f);
530 cublasSetStream(m_cublas, stream);
531 cudnnSetStream(m_cudnn, stream);
533 if (m_precision == DW_PRECISION_FP32)
535 CHECK_CUDA_ERROR(cublasSgemm(m_cublas, CUBLAS_OP_T, CUBLAS_OP_N, m_numOutputChannels, batchSize, m_numInputChannels, &onef,
536 reinterpret_cast<const float32_t*>(m_deviceKernel), m_numInputChannels,
537 reinterpret_cast<const float32_t*>(inputs[0]), m_numInputChannels, &zerof,
538 reinterpret_cast<float32_t*>(outputs[0]), m_numOutputChannels));
542 CHECK_CUDA_ERROR(cublasHgemm(m_cublas, CUBLAS_OP_T, CUBLAS_OP_N, m_numOutputChannels, batchSize, m_numInputChannels, &oneh,
543 reinterpret_cast<const __half*>(m_deviceKernel), m_numInputChannels,
544 reinterpret_cast<const __half*>(inputs[0]), m_numInputChannels, &zeroh,
545 reinterpret_cast<__half*>(outputs[0]), m_numOutputChannels));
548 if (m_biasWeights.count)
550 cudnnDataType_t cudnnDT = m_precision == DW_PRECISION_FP32 ? CUDNN_DATA_FLOAT : CUDNN_DATA_HALF;
551 CHECK_CUDA_ERROR(cudnnSetTensor4dDescriptor(m_srcDescriptor, CUDNN_TENSOR_NCHW, cudnnDT, 1, m_numOutputChannels, 1, 1));
552 CHECK_CUDA_ERROR(cudnnSetTensor4dDescriptor(m_dstDescriptor, CUDNN_TENSOR_NCHW, cudnnDT, batchSize, m_numOutputChannels, 1, 1));
553 CHECK_CUDA_ERROR(cudnnAddTensor(m_cudnn, &onef, m_srcDescriptor, m_deviceBias, &onef, m_dstDescriptor, outputs[0]));
560 Finally, the implementation of the interface function is:
563 dwStatus _dwDNNPlugin_enqueue(int32_t batchSize, const void* const* inputs, void** outputs,
564 void* workspace, cudaStream_t stream, _dwDNNPluginHandle_t handle)
566 FCPlugin *plugin = reinterpret_cast<FCPlugin*>(handle);
567 plugin->enqueue(batchSize, inputs, outputs, workspace, stream);
572 ## Building plugin as a dynamic library
574 The plugin code must be compiled into a dynamic library for each processor architecture that are to be supported.
576 In FCPlugin example, we shall add plugin to the samples to simplify the building process.
577 Firstly, we shall create a folder under `samples` called `fcplugin` and create a `CMakeLists.txt` under this folder:
580 project(fc_plugin C CXX)
582 #-------------------------------------------------------------------------------
584 #-------------------------------------------------------------------------------
591 ${DriveWorks_LIBRARIES}
595 #-------------------------------------------------------------------------------
597 #-------------------------------------------------------------------------------
598 cuda_add_library(${PROJECT_NAME} SHARED ${SOURCES})
599 CUDA_ADD_CUBLAS_TO_TARGET(${PROJECT_NAME})
600 target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBRARIES})
602 #-------------------------------------------------------------------------------
604 #-------------------------------------------------------------------------------
605 sdk_add_sample(${PROJECT_NAME})
608 Append `fcplugin` to the `SAMPLES` list in `CMakeLists.txt` under `samples`:
610 set(SAMPLES framework;egomotion;sensors;features;rig;visualization;sfm;dnn;laneDetection;colorcorrection;rectifier;ipc;hello_world;image;stereo;freespaceperception;drivenet;maps;template;icp;lidar_accumulator;cameraBlindness;calibration;vehicleio;dataspeedBridge;waitcondition;pointcloudprocessor;fcplugin)
613 Finally, run `cmake` and the preferred build system to compile the library. This will generate a `libfc_plugin.so`.
615 @section model_generation Generating TensorRT Model with Plugins
617 In order to execute a DNN model with plugins into DriveWorks, like any other DNN model, it has to be converted to a TensorRT model.
618 This is achieved by running @ref dwx_tensorRT_tool with an extra parameter to configure plugins.
620 Plugin configuration is provided in a json file. This file is expected to provide a path to a plugin as dynamic library for each custom layer:
623 "__comment" : "Please list here all the plugin paths (absolute path or relative to tensorRT_optimization tool) and the layers they apply to.",
624 "plugin1_path" : ["layer1", "layer2", "layer3"],
625 "plugin2_path" : ["layer4", "layer5"],
626 "plugin3_path" : ["layer6"]
630 \anchor plugin_json In FCPlugin example, we have only one custom layer that requires a plugin. The name of the layer is `ip2`, and path of the plugin is `libfc_plugin.so`, assuming that it is located in the same folder as @ref dwx_tensorRT_tool . Therefore, we can create a `plugin.json` file as follows:
634 "libfc_plugin.so" : ["ip2"],
635 "plugin2_path" : ["layer4", "layer5"],
636 "plugin3_path" : ["layer6"]
640 Finally, we can generate the model by:
642 ./tensorRT_optimization --modelType=caffe --prototxt=mnist.prototxt --caffemodel=mnist.caffemodel --outputBlobs=prob --pluginConfig=plugin.json --out=mnist_with_plugin.dnn
645 @section model_runtime Loading TensorRT Model with Plugins
647 Loading a TensorRT model with plugin generated via @ref dwx_tensorRT_tool requires plugin configuration to be defined at initialization of `::dwDNNHandle_t`.
649 The plugin configuration structures look like this:
653 const dwDNNCustomLayer* customLayers; /**< Array of custom layers. */
654 size_t numCustomLayers; /**< Number of custom layers */
655 } dwDNNPluginConfiguration;
659 const char* pluginLibraryPath; /**< Path to a plugin shared object. Path must be either absolute path or path relative to DW lib folder. */
660 const char* layerName; /**< Name of the custom layer. */
664 Just like the json file used at model generation (see \ref plugin_json "plugin json"), for each layer, the layer name and path to the plugin that implements the layer must be provided.
668 dwDNNPluginConfiguration pluginConf{};
669 pluginConf.numCustomLayers = 1;
670 dwDNNCustomLayer customLayer{};
671 customLayer.layerName = "ip2";
673 // Assuming libfc_plugin.so is in the same folder as libdriveworks.so
674 std::string pluginPath = "libfc_plugin.so";
675 customLayer.pluginLibraryPath = pluginPath.c_str();
676 pluginConf.customLayers = &customLayer;
678 // Initialize DNN from a TensorRT file with plugin configuration
680 CHECK_DW_ERROR(dwDNN_initializeTensorRTFromFile(&dnn, "mnist_with_plugin.dnn", &pluginConf, DW_PROCESSOR_TYPE_GPU, sdk));
683 See \ref dwx_dnn_plugin_sample for more details.