From d3107c885ae40085dca4543e75da72bbe18d824c Mon Sep 17 00:00:00 2001 From: Asfiya Baig Date: Thu, 25 Apr 2024 16:01:48 -0700 Subject: [PATCH] TensorRT 10.0 GA Release Signed-off-by: Asfiya Baig --- CHANGELOG.md | 19 +- CMakeLists.txt | 32 +- README.md | 54 +- VERSION | 2 +- .../modules/find_library_create_target.cmake | 7 +- cmake/modules/set_ifndef.cmake | 2 +- .../cmake_aarch64-android.toolchain | 2 +- .../toolchains/cmake_aarch64-native.toolchain | 2 +- cmake/toolchains/cmake_aarch64.toolchain | 14 +- .../toolchains/cmake_aarch64_cross.toolchain | 2 +- cmake/toolchains/cmake_ppc64le.toolchain | 2 +- cmake/toolchains/cmake_qnx.toolchain | 2 +- cmake/toolchains/cmake_x64_win.toolchain | 2 +- cmake/toolchains/cmake_x86_64.toolchain | 2 +- .../cmake_x86_64_agnostic.toolchain | 2 +- demo/BERT/CMakeLists.txt | 2 +- demo/BERT/README.md | 6 +- demo/BERT/builder.py | 4 +- demo/BERT/builder_utils.py | 2 +- demo/BERT/builder_varseqlen.py | 4 +- demo/BERT/helpers/calibrator.py | 2 +- demo/BERT/helpers/data_processing.py | 2 +- demo/BERT/helpers/tokenization.py | 2 +- demo/BERT/infer_c/bert_infer.h | 18 +- demo/BERT/infer_c/common.h | 4 +- demo/BERT/infer_c/infer_c.cpp | 2 +- demo/BERT/infer_c/logging.cpp | 2 +- demo/BERT/infer_c/logging.h | 2 +- demo/BERT/infer_c/perf.cpp | 2 +- demo/BERT/inference.py | 4 +- demo/BERT/inference_c.py | 2 +- demo/BERT/inference_varseqlen.py | 4 +- demo/BERT/perf.py | 2 +- demo/BERT/perf_varseqlen.py | 2 +- demo/BERT/squad/evaluate-v1.1.py | 2 +- demo/BERT/squad/evaluate-v2.0.py | 2 +- demo/DeBERTa/deberta_onnx_modify.py | 2 +- demo/DeBERTa/deberta_ort_inference.py | 2 +- demo/DeBERTa/deberta_pytorch2onnx.py | 2 +- demo/DeBERTa/deberta_tensorrt_inference.py | 2 +- demo/DeBERTa/requirements.txt | 2 +- demo/Diffusion/README.md | 31 +- demo/Diffusion/calibration.py | 177 --- demo/Diffusion/demo_img2img.py | 2 +- demo/Diffusion/demo_inpaint.py | 2 +- demo/Diffusion/demo_txt2img.py | 2 +- demo/Diffusion/demo_txt2img_xl.py | 2 +- demo/Diffusion/models.py | 27 +- demo/Diffusion/requirements.txt | 10 +- demo/Diffusion/stable_diffusion_pipeline.py | 105 +- demo/Diffusion/utilities.py | 64 +- demo/Diffusion/utils_ammo.py | 160 ++ demo/Jasper/README.md | 3 - demo/Tacotron2/README.md | 113 -- demo/Tacotron2/common/audio_processing.py | 110 -- demo/Tacotron2/common/layers.py | 96 -- demo/Tacotron2/common/stft.py | 159 -- demo/Tacotron2/common/utils.py | 72 - demo/Tacotron2/config.json | 11 - demo/Tacotron2/data_functions.py | 58 - demo/Tacotron2/inference.py | 266 ---- demo/Tacotron2/inference_perf.py | 117 -- demo/Tacotron2/main.py | 43 - demo/Tacotron2/models.py | 137 -- demo/Tacotron2/multiproc.py | 75 - demo/Tacotron2/phrases/phrase.txt | 1 - demo/Tacotron2/phrases/phrase_1_128.txt | 1 - demo/Tacotron2/phrases/phrase_1_256.txt | 2 - demo/Tacotron2/phrases/phrase_1_64.txt | 1 - demo/Tacotron2/phrases/phrase_4_256.txt | 4 - demo/Tacotron2/phrases/phrase_4_64.txt | 4 - demo/Tacotron2/phrases/phrase_8_256.txt | 8 - demo/Tacotron2/phrases/phrase_8_64.txt | 8 - demo/Tacotron2/preprocess_audio2mel.py | 81 -- demo/Tacotron2/requirements.txt | 12 - demo/Tacotron2/run_latency_tests.sh | 27 - .../Tacotron2/scripts/download_checkpoints.sh | 31 - demo/Tacotron2/scripts/inference_benchmark.sh | 21 - .../scripts/install_prerequisites.sh | 25 - demo/Tacotron2/scripts/prepare_dataset.sh | 31 - demo/Tacotron2/scripts/prepare_mels.sh | 36 - demo/Tacotron2/tacotron2/arg_parser.py | 98 -- demo/Tacotron2/tacotron2/data_function.py | 145 -- demo/Tacotron2/tacotron2/loss_function.py | 36 - demo/Tacotron2/tacotron2/model.py | 681 --------- demo/Tacotron2/tacotron2/text/LICENCE | 19 - demo/Tacotron2/tacotron2/text/__init__.py | 74 - demo/Tacotron2/tacotron2/text/cleaners.py | 106 -- demo/Tacotron2/tacotron2/text/cmudict.py | 81 -- demo/Tacotron2/tacotron2/text/numbers.py | 87 -- demo/Tacotron2/tacotron2/text/symbols.py | 34 - demo/Tacotron2/tensorrt/convert_onnx2trt.py | 168 --- .../tensorrt/convert_tacotron22onnx.py | 418 ------ .../tensorrt/convert_waveglow2onnx.py | 167 --- demo/Tacotron2/tensorrt/generate_decoder.py | 212 --- demo/Tacotron2/tensorrt/inference_trt.py | 491 ------- .../tensorrt/run_latency_tests_trt.sh | 17 - demo/Tacotron2/tensorrt/test_infer_trt.py | 230 --- demo/Tacotron2/tensorrt/trt_utils.py | 154 -- demo/Tacotron2/test_infer.py | 198 --- demo/Tacotron2/test_infer.sh | 126 -- demo/Tacotron2/train.py | 535 ------- demo/Tacotron2/waveglow/arg_parser.py | 55 - demo/Tacotron2/waveglow/data_function.py | 78 - demo/Tacotron2/waveglow/denoiser.py | 53 - demo/Tacotron2/waveglow/loss_function.py | 38 - demo/Tacotron2/waveglow/model.py | 343 ----- .../HuggingFace-Diffusers/README.md | 36 - .../TensorRT-diffusers-txt2img.ipynb | 1290 ----------------- docker/build.sh | 2 +- docker/launch.sh | 2 +- docker/rockylinux8.Dockerfile | 105 ++ docker/rockylinux9.Dockerfile | 104 ++ docker/ubuntu-20.04.Dockerfile | 20 +- docker/ubuntu-22.04-aarch64.Dockerfile | 112 ++ docker/ubuntu-22.04.Dockerfile | 22 +- docker/ubuntu-cross-aarch64.Dockerfile | 134 ++ include/NvInfer.h | 65 +- include/NvInferConsistency.h | 2 + include/NvInferLegacyDims.h | 4 +- include/NvInferRuntimeBase.h | 22 +- include/NvInferRuntimeCommon.h | 2 + include/NvInferRuntimePlugin.h | 2 + include/NvInferSafeRuntime.h | 2 + include/NvInferVersion.h | 4 +- parsers/CMakeLists.txt | 2 +- parsers/common/half.h | 2 +- parsers/common/ieee_half.h | 2 +- parsers/common/parserUtils.h | 2 +- parsers/onnx | 2 +- plugin/CMakeLists.txt | 22 +- plugin/batchTilePlugin/CMakeLists.txt | 2 +- plugin/batchTilePlugin/batchTilePlugin.cpp | 2 +- plugin/batchTilePlugin/batchTilePlugin.h | 2 +- plugin/batchedNMSPlugin/CMakeLists.txt | 2 +- .../batchedNMSPlugin/batchedNMSInference.cu | 2 +- plugin/batchedNMSPlugin/batchedNMSPlugin.cpp | 2 +- plugin/batchedNMSPlugin/batchedNMSPlugin.h | 2 +- plugin/batchedNMSPlugin/gatherNMSOutputs.h | 2 +- plugin/bertQKVToContextPlugin/CMakeLists.txt | 2 +- .../fused_multihead_attention/CMakeLists.txt | 2 +- .../include/fused_multihead_attention.h | 463 +++--- .../fused_multihead_attention_common.h | 2 +- ...head_attention_fp16_128_64_kernel.sm75.cpp | 2 +- ...head_attention_fp16_128_64_kernel.sm80.cpp | 2 +- ...head_attention_fp16_128_64_kernel.sm87.cpp | 2 +- ...head_attention_fp16_128_64_kernel.sm90.cpp | 2 +- ...head_attention_fp16_384_64_kernel.sm75.cpp | 2 +- ...head_attention_fp16_384_64_kernel.sm80.cpp | 2 +- ...head_attention_fp16_384_64_kernel.sm86.cpp | 2 +- ...head_attention_fp16_384_64_kernel.sm87.cpp | 2 +- ...head_attention_fp16_384_64_kernel.sm90.cpp | 2 +- ...head_attention_fp16_512_64_kernel.sm90.cpp | 2 +- ...ihead_attention_fp16_64_64_kernel.sm75.cpp | 2 +- ...ihead_attention_fp16_64_64_kernel.sm80.cpp | 2 +- ...ihead_attention_fp16_64_64_kernel.sm87.cpp | 2 +- ...ihead_attention_fp16_64_64_kernel.sm90.cpp | 2 +- ...ihead_attention_fp16_96_64_kernel.sm75.cpp | 2 +- ...ihead_attention_fp16_96_64_kernel.sm80.cpp | 2 +- ...ihead_attention_fp16_96_64_kernel.sm87.cpp | 2 +- ...ihead_attention_fp16_96_64_kernel.sm90.cpp | 2 +- ...head_attention_int8_128_64_kernel.sm75.cpp | 2 +- ...head_attention_int8_128_64_kernel.sm80.cpp | 2 +- ...head_attention_int8_128_64_kernel.sm87.cpp | 2 +- ...head_attention_int8_128_64_kernel.sm90.cpp | 2 +- ...head_attention_int8_384_64_kernel.sm75.cpp | 2 +- ...head_attention_int8_384_64_kernel.sm80.cpp | 2 +- ...head_attention_int8_384_64_kernel.sm87.cpp | 2 +- ...head_attention_int8_384_64_kernel.sm90.cpp | 2 +- ...head_attention_int8_512_64_kernel.sm90.cpp | 2 +- ...ihead_attention_int8_64_64_kernel.sm80.cpp | 2 +- ...ihead_attention_int8_96_64_kernel.sm80.cpp | 2 +- .../CMakeLists.txt | 2 +- .../include/fused_multihead_attention_v2.h | 10 +- ...d_attention_v2_fp16_128_32_kernel.sm75.cpp | 2 +- ...d_attention_v2_fp16_128_32_kernel.sm80.cpp | 2 +- ...d_attention_v2_fp16_128_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_fp16_128_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_fp16_128_64_kernel.sm86.cpp | 2 +- ...d_attention_v2_fp16_128_64_kernel.sm87.cpp | 2 +- ...d_attention_v2_fp16_128_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_fp16_256_32_kernel.sm75.cpp | 2 +- ...d_attention_v2_fp16_256_32_kernel.sm80.cpp | 2 +- ...d_attention_v2_fp16_256_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_fp16_256_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_fp16_256_64_kernel.sm86.cpp | 2 +- ...d_attention_v2_fp16_256_64_kernel.sm87.cpp | 2 +- ...d_attention_v2_fp16_256_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_fp16_384_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_fp16_384_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_fp16_384_64_kernel.sm86.cpp | 2 +- ...d_attention_v2_fp16_384_64_kernel.sm87.cpp | 2 +- ...d_attention_v2_fp16_384_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_fp16_512_32_kernel.sm75.cpp | 2 +- ...d_attention_v2_fp16_512_32_kernel.sm80.cpp | 2 +- ...d_attention_v2_fp16_512_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_fp16_512_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_fp16_512_64_kernel.sm90.cpp | 2 +- ...ad_attention_v2_fp16_64_64_kernel.sm75.cpp | 2 +- ...ad_attention_v2_fp16_64_64_kernel.sm80.cpp | 2 +- ...ad_attention_v2_fp16_64_64_kernel.sm86.cpp | 2 +- ...ad_attention_v2_fp16_64_64_kernel.sm87.cpp | 2 +- ...ad_attention_v2_fp16_64_64_kernel.sm90.cpp | 2 +- ...ad_attention_v2_fp16_96_64_kernel.sm75.cpp | 2 +- ...ad_attention_v2_fp16_96_64_kernel.sm80.cpp | 2 +- ...ad_attention_v2_fp16_96_64_kernel.sm86.cpp | 2 +- ...ad_attention_v2_fp16_96_64_kernel.sm87.cpp | 2 +- ...ad_attention_v2_fp16_96_64_kernel.sm90.cpp | 2 +- ...ttention_v2_il_int8_128_32_kernel.sm80.cpp | 2 +- ...ttention_v2_il_int8_128_64_kernel.sm87.cpp | 2 +- ...ttention_v2_il_int8_128_64_kernel.sm90.cpp | 2 +- ...ttention_v2_il_int8_192_64_kernel.sm87.cpp | 2 +- ...ttention_v2_il_int8_192_64_kernel.sm90.cpp | 2 +- ...ttention_v2_il_int8_256_64_kernel.sm87.cpp | 2 +- ...ttention_v2_il_int8_256_64_kernel.sm90.cpp | 2 +- ...ttention_v2_il_int8_384_64_kernel.sm87.cpp | 2 +- ...ttention_v2_il_int8_384_64_kernel.sm90.cpp | 2 +- ...attention_v2_il_int8_64_64_kernel.sm80.cpp | 2 +- ...attention_v2_il_int8_64_64_kernel.sm87.cpp | 2 +- ...attention_v2_il_int8_64_64_kernel.sm90.cpp | 2 +- ...attention_v2_il_int8_96_64_kernel.sm80.cpp | 2 +- ...attention_v2_il_int8_96_64_kernel.sm87.cpp | 2 +- ...attention_v2_il_int8_96_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_int8_128_32_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_128_32_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_128_64_kernel.sm72.cpp | 2 +- ...d_attention_v2_int8_128_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_128_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_128_64_kernel.sm86.cpp | 2 +- ...d_attention_v2_int8_128_64_kernel.sm87.cpp | 2 +- ...d_attention_v2_int8_128_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_int8_192_64_kernel.sm72.cpp | 2 +- ...d_attention_v2_int8_192_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_192_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_192_64_kernel.sm86.cpp | 2 +- ...d_attention_v2_int8_192_64_kernel.sm87.cpp | 2 +- ...d_attention_v2_int8_192_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_int8_256_32_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_256_32_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_256_64_kernel.sm72.cpp | 2 +- ...d_attention_v2_int8_256_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_256_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_256_64_kernel.sm86.cpp | 2 +- ...d_attention_v2_int8_256_64_kernel.sm87.cpp | 2 +- ...d_attention_v2_int8_256_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_int8_384_64_kernel.sm72.cpp | 2 +- ...d_attention_v2_int8_384_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_384_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_384_64_kernel.sm86.cpp | 2 +- ...d_attention_v2_int8_384_64_kernel.sm87.cpp | 2 +- ...d_attention_v2_int8_384_64_kernel.sm90.cpp | 2 +- ...d_attention_v2_int8_512_32_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_512_32_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_512_64_kernel.sm75.cpp | 2 +- ...d_attention_v2_int8_512_64_kernel.sm80.cpp | 2 +- ...d_attention_v2_int8_512_64_kernel.sm90.cpp | 2 +- ...ad_attention_v2_int8_64_64_kernel.sm80.cpp | 2 +- ...ad_attention_v2_int8_64_64_kernel.sm87.cpp | 2 +- ...ad_attention_v2_int8_64_64_kernel.sm90.cpp | 2 +- ...ad_attention_v2_int8_96_64_kernel.sm80.cpp | 2 +- ...ad_attention_v2_int8_96_64_kernel.sm87.cpp | 2 +- ...ad_attention_v2_int8_96_64_kernel.sm90.cpp | 2 +- .../qkvToContextInt8InterleavedPlugin.cpp | 2 +- .../bertQKVToContextPlugin/zeroPadding2d.cu | 2 +- plugin/bertQKVToContextPlugin/zeroPadding2d.h | 2 +- plugin/clipPlugin/CMakeLists.txt | 2 +- plugin/clipPlugin/clip.cu | 2 +- plugin/clipPlugin/clip.h | 2 +- plugin/common/CMakeLists.txt | 2 +- plugin/common/bboxUtils.h | 2 +- plugin/common/bertCommon.h | 24 +- plugin/common/cub_helper.h | 2 +- plugin/common/cudaDriverWrapper.cpp | 2 +- plugin/common/cudaDriverWrapper.h | 2 +- plugin/common/dimsHelpers.h | 2 +- plugin/common/half.h | 2 +- plugin/common/kernels/CMakeLists.txt | 2 +- plugin/common/kernels/bboxDeltas2Proposals.cu | 2 +- plugin/common/kernels/cropAndResizeKernel.cu | 2 +- plugin/common/kernels/decodeBbox3DKernels.cu | 2 +- plugin/common/kernels/detectionForward.cu | 2 +- plugin/common/kernels/extractFgScores.cu | 2 +- plugin/common/kernels/generateAnchors.cu | 2 +- plugin/common/kernels/gridAnchorLayer.cu | 2 +- plugin/common/kernels/kernel.cpp | 2 +- plugin/common/kernels/lReLU.cu | 2 +- plugin/common/kernels/maskRCNNKernels.cu | 2 +- plugin/common/kernels/maskRCNNKernels.h | 2 +- plugin/common/kernels/nmsLayer.cu | 2 +- plugin/common/kernels/permuteData.cu | 2 +- plugin/common/kernels/pillarScatterKernels.cu | 2 +- plugin/common/kernels/priorBoxLayer.cu | 2 +- plugin/common/kernels/proposalKernel.cu | 2 +- plugin/common/kernels/proposalsForward.cu | 2 +- plugin/common/kernels/reducedMathPlugin.h | 2 +- plugin/common/kernels/regionForward.cu | 2 +- plugin/common/kernels/reorgForward.cu | 2 +- plugin/common/kernels/roiPooling.cu | 2 +- plugin/common/kernels/rproiInferenceFused.cu | 2 +- plugin/common/kernels/sortScoresPerClass.cu | 2 +- plugin/common/kernels/sortScoresPerImage.cu | 2 +- .../common/kernels/voxelGeneratorKernels.cu | 2 +- plugin/common/mrcnn_config.h | 2 +- plugin/common/nmsUtils.h | 2 +- plugin/common/reducedMathPlugin.cpp | 2 +- plugin/common/serialize.hpp | 2 +- plugin/common/templates.h | 2 +- plugin/common/vfcCommon.cpp | 2 +- plugin/common/vfcCommon.h | 2 +- plugin/coordConvACPlugin/CMakeLists.txt | 2 +- .../coordConvACPlugin/coordConvACPlugin.cpp | 2 +- plugin/coordConvACPlugin/coordConvACPlugin.h | 2 +- .../coordConvACPluginKernels.cu | 2 +- plugin/cropAndResizePlugin/CMakeLists.txt | 2 +- .../cropAndResizePlugin.cpp | 2 +- .../cropAndResizePlugin/cropAndResizePlugin.h | 2 +- plugin/decodeBbox3DPlugin/CMakeLists.txt | 2 +- plugin/decodeBbox3DPlugin/decodeBbox3D.cpp | 2 +- plugin/decodeBbox3DPlugin/decodeBbox3D.h | 2 +- plugin/detectionLayerPlugin/CMakeLists.txt | 2 +- .../detectionLayerPlugin.cpp | 2 +- .../detectionLayerPlugin.h | 2 +- .../CMakeLists.txt | 2 +- .../disentangledAttentionPlugin.cpp | 2 +- .../disentangledAttentionPlugin.h | 2 +- .../disentangledKernel.cu | 2 +- plugin/efficientNMSPlugin/CMakeLists.txt | 2 +- .../efficientNMSInference.cu | 2 +- .../efficientNMSInference.cuh | 2 +- .../efficientNMSInference.h | 2 +- .../efficientNMSParameters.h | 2 +- .../efficientNMSPlugin/efficientNMSPlugin.cpp | 2 +- .../efficientNMSPlugin/efficientNMSPlugin.h | 2 +- .../efficientNMSPlugin/tftrt/CMakeLists.txt | 2 +- .../tftrt/efficientNMSExplicitTFTRTPlugin.cpp | 2 +- .../tftrt/efficientNMSExplicitTFTRTPlugin.h | 2 +- .../tftrt/efficientNMSImplicitTFTRTPlugin.cpp | 2 +- .../tftrt/efficientNMSImplicitTFTRTPlugin.h | 2 +- plugin/embLayerNormPlugin/CMakeLists.txt | 2 +- .../embLayerNormPlugin/embLayerNormKernel.cu | 2 +- .../embLayerNormPlugin/embLayerNormPlugin.cpp | 2 +- .../embLayerNormPlugin/embLayerNormPlugin.h | 2 +- .../embLayerNormVarSeqlenKernelHFace.cu | 2 +- .../embLayerNormVarSeqlenKernelMTron.cu | 2 +- .../embLayerNormVarSeqlenPlugin.cpp | 2 +- .../embLayerNormVarSeqlenPlugin.h | 2 +- plugin/exports-vfc_plugin.def | 4 +- plugin/exports-vfc_plugin.map | 2 +- plugin/exports.def | 4 +- plugin/exports.map | 2 +- plugin/fcPlugin/CMakeLists.txt | 2 +- plugin/fcPlugin/fcPlugin.cpp | 19 +- plugin/fcPlugin/fcPlugin.h | 121 +- plugin/flattenConcat/CMakeLists.txt | 2 +- plugin/geluPlugin/CMakeLists.txt | 2 +- plugin/geluPlugin/geluKernel.cu | 2 +- plugin/geluPlugin/geluPlugin.cpp | 2 +- plugin/geluPlugin/geluPlugin.h | 2 +- plugin/generateDetectionPlugin/CMakeLists.txt | 2 +- .../generateDetectionPlugin.cpp | 2 +- .../generateDetectionPlugin.h | 2 +- plugin/gridAnchorPlugin/CMakeLists.txt | 2 +- .../groupNormalizationPlugin/CMakeLists.txt | 2 +- .../groupNormalizationKernel.cu | 2 +- .../CMakeLists.txt | 2 +- .../instanceNormCommon.h | 2 +- .../instanceNormFwd.h | 2 +- .../instanceNormFwdImpl.cu | 2 +- plugin/leakyReluPlugin/CMakeLists.txt | 2 +- plugin/leakyReluPlugin/lReluPlugin.cpp | 2 +- plugin/leakyReluPlugin/lReluPlugin.h | 2 +- .../modulatedDeformConvPlugin/CMakeLists.txt | 2 +- .../commonCudaHelper.h | 2 +- .../CMakeLists.txt | 2 +- .../multilevelCropAndResizePlugin.cpp | 2 +- .../multilevelCropAndResizePlugin.h | 2 +- plugin/multilevelProposeROI/CMakeLists.txt | 2 +- .../multilevelProposeROIPlugin.cpp | 2 +- .../multilevelProposeROIPlugin.h | 2 +- .../multilevelProposeROI/tlt_mrcnn_config.h | 2 +- .../CMakeLists.txt | 2 +- .../multiscaleDeformableAttn.cu | 2 +- .../multiscaleDeformableAttn.h | 2 +- .../multiscaleDeformableAttnPlugin.cpp | 2 +- .../multiscaleDeformableIm2ColCuda.cuh | 2 +- plugin/nmsPlugin/CMakeLists.txt | 2 +- plugin/nmsPlugin/nmsPlugin.cpp | 2 +- plugin/nmsPlugin/nmsPlugin.h | 2 +- plugin/normalizePlugin/CMakeLists.txt | 2 +- plugin/nvFasterRCNN/CMakeLists.txt | 2 +- plugin/pillarScatterPlugin/CMakeLists.txt | 2 +- plugin/pillarScatterPlugin/pillarScatter.cpp | 2 +- plugin/pillarScatterPlugin/pillarScatter.h | 2 +- plugin/priorBoxPlugin/CMakeLists.txt | 2 +- plugin/proposalLayerPlugin/CMakeLists.txt | 2 +- .../proposalLayerPlugin.cpp | 2 +- .../proposalLayerPlugin/proposalLayerPlugin.h | 2 +- plugin/proposalPlugin/CMakeLists.txt | 2 +- plugin/proposalPlugin/proposalPlugin.cpp | 6 +- plugin/proposalPlugin/proposalPlugin.h | 2 +- plugin/pyramidROIAlignPlugin/CMakeLists.txt | 2 +- .../pyramidROIAlignPlugin.cpp | 2 +- .../pyramidROIAlignPlugin.h | 2 +- plugin/regionPlugin/CMakeLists.txt | 2 +- plugin/regionPlugin/regionPlugin.cpp | 2 +- plugin/regionPlugin/regionPlugin.h | 2 +- plugin/reorgPlugin/CMakeLists.txt | 2 +- plugin/reorgPlugin/reorgPlugin.cpp | 2 +- plugin/reorgPlugin/reorgPlugin.h | 2 +- plugin/resizeNearestPlugin/CMakeLists.txt | 2 +- .../resizeNearestPlugin.cpp | 2 +- .../resizeNearestPlugin/resizeNearestPlugin.h | 2 +- plugin/roiAlignPlugin/CMakeLists.txt | 2 +- plugin/roiAlignPlugin/roiAlignKernel.h | 2 +- plugin/roiAlignPlugin/roiAlignPlugin.cpp | 2 +- plugin/roiAlignPlugin/roiAlignPlugin.h | 2 +- plugin/scatterElementsPlugin/CMakeLists.txt | 2 +- plugin/scatterElementsPlugin/TensorInfo.cuh | 2 +- plugin/scatterElementsPlugin/atomics.cuh | 2 +- plugin/scatterElementsPlugin/reducer.cuh | 2 +- .../scatterElementsPlugin.cpp | 2 +- .../scatterElementsPlugin.h | 2 +- .../scatterElementsPluginKernel.cu | 2 +- .../scatterElementsPluginKernel.h | 2 +- plugin/scatterPlugin/CMakeLists.txt | 2 +- plugin/scatterPlugin/scatterLayer.cu | 2 +- plugin/skipLayerNormPlugin/CMakeLists.txt | 2 +- ...skipLayerNormInt8InterleavedKernelHFace.cu | 2 +- ...skipLayerNormInt8InterleavedKernelMTron.cu | 2 +- .../skipLayerNormInt8InterleavedPlugin.cpp | 2 +- .../skipLayerNormInt8InterleavedPlugin.h | 2 +- .../skipLayerNormKernel.cu | 2 +- .../skipLayerNormPlugin.cpp | 2 +- .../skipLayerNormPlugin/skipLayerNormPlugin.h | 2 +- plugin/specialSlicePlugin/CMakeLists.txt | 2 +- .../specialSlicePlugin/specialSlicePlugin.cpp | 2 +- .../specialSlicePlugin/specialSlicePlugin.h | 2 +- plugin/splitPlugin/CMakeLists.txt | 2 +- plugin/splitPlugin/split.cu | 2 +- plugin/splitPlugin/split.h | 2 +- plugin/voxelGeneratorPlugin/CMakeLists.txt | 2 +- .../voxelGeneratorPlugin/voxelGenerator.cpp | 2 +- plugin/voxelGeneratorPlugin/voxelGenerator.h | 2 +- python/CMakeLists.txt | 34 +- .../docstrings/infer/pyAlgorithmSelectorDoc.h | 10 +- python/docstrings/infer/pyCoreDoc.h | 31 +- .../docstrings/infer/pyFoundationalTypesDoc.h | 8 +- python/docstrings/infer/pyGraphDoc.h | 9 +- python/docstrings/infer/pyInt8Doc.h | 2 +- python/docstrings/infer/pyPluginDoc.h | 96 +- python/docstrings/parsers/pyOnnxDoc.h | 2 +- python/docstrings/pyTensorRTDoc.h | 2 +- python/include/ForwardDeclarations.h | 2 +- python/include/utils.h | 4 +- python/packaging/bindings_wheel/setup.py | 2 +- .../bindings_wheel/tensorrt/__init__.py | 12 +- python/packaging/frontend_sdist/setup.py | 12 +- .../frontend_sdist/tensorrt/__init__.py | 2 +- python/packaging/libs_wheel/setup.py | 2 +- .../libs_wheel/tensorrt_libs/__init__.py | 9 +- python/packaging/metapackage/setup.py | 2 +- python/src/infer/pyAlgorithmSelector.cpp | 12 +- python/src/infer/pyCore.cpp | 53 +- python/src/infer/pyFoundationalTypes.cpp | 14 +- python/src/infer/pyGraph.cpp | 2 +- python/src/infer/pyInt8.cpp | 36 +- python/src/infer/pyPlugin.cpp | 291 ++-- python/src/parsers/pyOnnx.cpp | 2 +- python/src/pyTensorRT.cpp | 2 +- python/src/utils.cpp | 4 +- .../Additional Examples/helper.py | 2 +- quickstart/IntroNotebooks/helper.py | 2 +- quickstart/IntroNotebooks/onnx_helper.py | 2 +- quickstart/Makefile | 2 +- quickstart/Makefile.config | 2 +- quickstart/SemanticSegmentation/Makefile | 2 +- quickstart/SemanticSegmentation/export.py | 2 +- .../SemanticSegmentation/tutorial-runtime.cpp | 2 +- quickstart/common/logger.cpp | 2 +- quickstart/common/logger.h | 2 +- quickstart/common/logging.h | 2 +- quickstart/common/util.cpp | 2 +- quickstart/common/util.h | 2 +- quickstart/deploy_to_triton/config.pbtxt | 2 +- .../deploy_to_triton/export_resnet_to_onnx.py | 2 +- quickstart/deploy_to_triton/triton_client.py | 2 +- samples/CMakeLists.txt | 2 +- samples/CMakeSamplesTemplate.txt | 16 +- samples/common/BatchStream.h | 2 +- samples/common/EntropyCalibrator.h | 2 +- samples/common/ErrorRecorder.h | 4 +- samples/common/argsParser.h | 8 +- samples/common/bfloat16.cpp | 2 +- samples/common/bfloat16.h | 2 +- samples/common/buffers.h | 2 +- samples/common/common.h | 2 +- samples/common/dumpTFWts.py | 2 +- samples/common/getOptions.cpp | 2 +- samples/common/getOptions.h | 2 +- samples/common/getoptWin.h | 2 +- samples/common/half.h | 2 +- samples/common/logger.cpp | 2 +- samples/common/logger.h | 2 +- samples/common/logging.h | 4 +- samples/common/parserOnnxConfig.h | 2 +- samples/common/safeCommon.h | 4 +- samples/common/sampleConfig.h | 2 +- samples/common/sampleDevice.cpp | 2 +- samples/common/sampleDevice.h | 2 +- samples/common/sampleEngines.cpp | 12 +- samples/common/sampleEngines.h | 2 +- samples/common/sampleEntrypoints.h | 2 +- samples/common/sampleInference.cpp | 19 +- samples/common/sampleInference.h | 2 +- samples/common/sampleOptions.cpp | 2 +- samples/common/sampleOptions.h | 2 +- samples/common/sampleReporting.cpp | 2 +- samples/common/sampleReporting.h | 2 +- samples/common/sampleUtils.cpp | 2 +- samples/common/sampleUtils.h | 2 +- samples/common/streamReader.h | 2 +- samples/python/common.py | 31 +- samples/python/detectron2/build_engine.py | 133 +- samples/python/detectron2/create_onnx.py | 557 +++++-- samples/python/detectron2/eval_coco.py | 72 +- samples/python/detectron2/image_batcher.py | 36 +- samples/python/detectron2/infer.py | 194 ++- samples/python/detectron2/onnx_utils.py | 105 +- samples/python/detectron2/visualize.py | 231 ++- samples/python/downloader.py | 35 +- samples/python/efficientdet/build_engine.py | 193 ++- samples/python/efficientdet/compare_tf.py | 128 +- samples/python/efficientdet/create_onnx.py | 304 +++- samples/python/efficientdet/eval_coco.py | 52 +- samples/python/efficientdet/image_batcher.py | 27 +- samples/python/efficientdet/infer.py | 99 +- samples/python/efficientdet/infer_tf.py | 113 +- samples/python/efficientdet/onnx_utils.py | 30 +- samples/python/efficientdet/visualize.py | 22 +- samples/python/efficientnet/build_engine.py | 50 +- samples/python/efficientnet/compare_tf.py | 45 +- samples/python/efficientnet/create_onnx.py | 38 +- samples/python/efficientnet/eval_gt.py | 24 +- samples/python/efficientnet/image_batcher.py | 22 +- samples/python/efficientnet/infer.py | 20 +- .../build_and_refit_engine.py | 205 ++- .../data_processing.py | 6 +- .../engine_refit_onnx_bidaf/prepare_model.py | 6 +- .../onnx_resnet50.py | 12 +- .../python/network_api_pytorch_mnist/model.py | 21 +- .../network_api_pytorch_mnist/sample.py | 41 +- .../python/onnx_custom_plugin/CMakeLists.txt | 2 +- .../onnx_custom_plugin/load_plugin_lib.py | 11 +- samples/python/onnx_custom_plugin/model.py | 31 +- samples/python/onnx_custom_plugin/sample.py | 51 +- .../test_custom_hardmax_plugin.py | 32 +- .../python/onnx_packnet/convert_to_onnx.py | 22 +- .../python/onnx_packnet/post_processing.py | 19 +- samples/python/python_plugin/CMakeLists.txt | 2 +- .../python_plugin/circ_pad_plugin_cpp.py | 29 +- .../circ_pad_plugin_cuda_python.py | 112 +- .../python_plugin/circ_pad_plugin_cupy.py | 61 +- .../circ_pad_plugin_inetdef_cuda_python.py | 126 +- .../python_plugin/circ_pad_plugin_numba.py | 17 +- .../python_plugin/circ_pad_plugin_torch.py | 18 +- .../python_plugin/circ_pad_plugin_triton.py | 68 +- .../circ_plugin_cpp/circ_pad_plugin.cu | 5 +- samples/python/python_plugin/utils.py | 75 +- samples/python/scripts/download_mnist_data.sh | 2 +- samples/python/scripts/download_mnist_pgms.py | 2 +- .../simple_progress_monitor.py | 59 +- .../build_engine.py | 132 +- .../compare_tf.py | 289 ++-- .../create_onnx.py | 675 +++++++-- .../eval_coco.py | 119 +- .../image_batcher.py | 38 +- .../tensorflow_object_detection_api/infer.py | 137 +- .../onnx_utils.py | 87 +- .../visualize.py | 237 ++- samples/python/yolov3_onnx/data_processing.py | 27 +- .../python/yolov3_onnx/onnx_to_tensorrt.py | 69 +- samples/python/yolov3_onnx/yolov3_to_onnx.py | 112 +- .../sampleAlgorithmSelector/CMakeLists.txt | 2 +- .../sampleAlgorithmSelector.cpp | 2 +- samples/sampleCharRNN/CMakeLists.txt | 2 +- samples/sampleCharRNN/sampleCharRNN.cpp | 2 +- samples/sampleDynamicReshape/CMakeLists.txt | 2 +- .../sampleDynamicReshape.cpp | 2 +- samples/sampleINT8API/CMakeLists.txt | 2 +- samples/sampleINT8API/sampleINT8API.cpp | 2 +- samples/sampleIOFormats/CMakeLists.txt | 7 +- samples/sampleIOFormats/sampleIOFormats.cpp | 307 ++-- samples/sampleNamedDimensions/CMakeLists.txt | 2 +- samples/sampleNamedDimensions/create_model.py | 2 +- .../sampleNamedDimensions.cpp | 2 +- samples/sampleNonZeroPlugin/README.md | 8 +- samples/sampleNonZeroPlugin/nonZeroKernel.cu | 40 +- samples/sampleNonZeroPlugin/nonZeroKernel.h | 8 +- .../sampleNonZeroPlugin.cpp | 79 +- samples/sampleOnnxMNIST/CMakeLists.txt | 2 +- samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp | 2 +- .../sampleOnnxMnistCoordConvAC/CMakeLists.txt | 2 +- .../sampleOnnxMnistCoordConvAC/coord_conv.py | 2 +- .../mnist_coord_conv_train.py | 2 +- .../modify_onnx_ac.py | 2 +- .../sampleOnnxMnistCoordConvAC.cpp | 2 +- samples/sampleProgressMonitor/CMakeLists.txt | 2 +- .../sampleProgressMonitor.cpp | 2 +- samples/trtexec/CMakeLists.txt | 2 +- samples/trtexec/prn_utils.py | 2 +- samples/trtexec/profiler.py | 2 +- samples/trtexec/tracer.py | 2 +- samples/trtexec/trtexec.cpp | 12 +- samples/utils/fileLock.cpp | 2 +- samples/utils/fileLock.h | 2 +- samples/utils/timingCache.cpp | 2 +- samples/utils/timingCache.h | 2 +- scripts/convert_te_onnx_to_trt_onnx.py | 2 +- scripts/copyright-scan.py | 2 +- scripts/stubify.sh | 2 +- third_party/ieee/half.h | 2 +- third_party/protobuf.cmake | 9 +- tools/Polygraphy/CHANGELOG.md | 9 + tools/Polygraphy/Makefile | 2 +- tools/Polygraphy/docs/conf.py | 19 +- tools/Polygraphy/docs/requirements.txt | 6 +- .../build_and_run.py | 10 +- .../load_and_run.py | 2 +- .../api/01_comparing_frameworks/example.py | 8 +- .../api/02_validating_on_a_dataset/example.py | 4 +- .../example.py | 10 +- .../example.py | 13 +- .../05_using_tensorrt_network_api/example.py | 14 +- .../06_immediate_eval_api/build_and_run.py | 10 +- .../api/06_immediate_eval_api/load_and_run.py | 2 +- .../07_tensorrt_and_dynamic_shapes/example.py | 17 +- .../example.py | 2 +- .../example.py | 13 +- .../data_loader.py | 6 +- .../01_match_and_replace_plugin/README.md | 15 +- .../plugins/toyPlugin/__init__.py | 0 .../plugins/toyPlugin/pattern.py | 29 +- ...hing_toy_plugin.onnx => toy_subgraph.onnx} | Bin .../create_config.py | 2 +- .../define_network.py | 3 +- .../data_loader.py | 7 +- .../generate_data.py | 2 +- .../add_constraints.py | 2 +- .../constrained_network.py | 2 +- .../polygraphy_reshape_destroyer/__init__.py | 2 +- .../args/__init__.py | 2 +- .../args/loader.py | 14 +- .../args/runner.py | 10 +- .../backend/__init__.py | 2 +- .../backend/loader.py | 6 +- .../backend/runner.py | 6 +- .../polygraphy_reshape_destroyer/export.py | 3 +- .../extension_module/setup.py | 2 +- tools/Polygraphy/polygraphy/__init__.py | 2 +- .../polygraphy/backend/base/loader.py | 2 +- .../polygraphy/backend/base/runner.py | 19 +- .../polygraphy/backend/base/util.py | 6 +- .../polygraphy/backend/common/loader.py | 2 +- .../polygraphy/backend/onnx/loader.py | 128 +- .../polygraphy/backend/onnx/util.py | 78 +- .../polygraphy/backend/onnxrt/loader.py | 6 +- .../polygraphy/backend/onnxrt/runner.py | 2 +- .../backend/pluginref/references.py | 6 +- .../polygraphy/backend/pluginref/runner.py | 10 +- .../polygraphy/backend/pyt/runner.py | 6 +- .../polygraphy/backend/tf/loader.py | 54 +- .../polygraphy/backend/tf/runner.py | 7 +- .../Polygraphy/polygraphy/backend/tf/util.py | 30 +- .../polygraphy/backend/trt/__init__.py | 1 + .../backend/trt/algorithm_selector.py | 2 +- .../polygraphy/backend/trt/calibrator.py | 55 +- .../polygraphy/backend/trt/config.py | 2 +- .../polygraphy/backend/trt/file_reader.py | 80 + .../polygraphy/backend/trt/loader.py | 174 ++- .../polygraphy/backend/trt/profile.py | 14 +- .../polygraphy/backend/trt/runner.py | 46 +- .../Polygraphy/polygraphy/backend/trt/util.py | 10 +- .../Polygraphy/polygraphy/common/interface.py | 2 +- tools/Polygraphy/polygraphy/common/struct.py | 10 +- .../polygraphy/comparator/comparator.py | 66 +- .../polygraphy/comparator/compare.py | 2 +- .../polygraphy/comparator/data_loader.py | 16 +- .../polygraphy/comparator/postprocess.py | 2 +- .../polygraphy/comparator/struct.py | 23 +- .../Polygraphy/polygraphy/comparator/util.py | 39 +- tools/Polygraphy/polygraphy/config.py | 14 +- tools/Polygraphy/polygraphy/constants.py | 2 +- tools/Polygraphy/polygraphy/cuda/cuda.py | 38 +- .../polygraphy/datatype/datatype.py | 2 +- tools/Polygraphy/polygraphy/datatype/numpy.py | 8 +- tools/Polygraphy/polygraphy/datatype/onnx.py | 14 +- .../Polygraphy/polygraphy/datatype/onnxrt.py | 8 +- .../polygraphy/datatype/tensorrt.py | 2 +- tools/Polygraphy/polygraphy/datatype/torch.py | 8 +- .../polygraphy/exception/exception.py | 2 +- tools/Polygraphy/polygraphy/func/func.py | 19 +- tools/Polygraphy/polygraphy/json/serde.py | 12 +- tools/Polygraphy/polygraphy/logger/logger.py | 10 +- tools/Polygraphy/polygraphy/mod/exporter.py | 69 +- tools/Polygraphy/polygraphy/mod/importer.py | 5 +- tools/Polygraphy/polygraphy/mod/util.py | 2 +- .../tools/args/backend/onnx/loader.py | 149 +- .../tools/args/backend/onnxrt/loader.py | 11 +- .../tools/args/backend/onnxrt/runner.py | 8 +- .../tools/args/backend/pluginref/runner.py | 10 +- .../tools/args/backend/runner_select.py | 6 +- .../tools/args/backend/tf/config.py | 16 +- .../tools/args/backend/tf/loader.py | 28 +- .../tools/args/backend/tf/runner.py | 9 +- .../tools/args/backend/trt/config.py | 197 ++- .../tools/args/backend/trt/loader.py | 195 ++- .../tools/args/backend/trt/runner.py | 25 +- .../Polygraphy/polygraphy/tools/args/base.py | 6 +- .../tools/args/comparator/comparator.py | 79 +- .../tools/args/comparator/compare.py | 23 +- .../tools/args/comparator/data_loader.py | 66 +- .../tools/args/comparator/postprocess.py | 10 +- .../polygraphy/tools/args/logger/logger.py | 14 +- .../Polygraphy/polygraphy/tools/args/model.py | 30 +- .../polygraphy/tools/args/util/util.py | 41 +- .../Polygraphy/polygraphy/tools/base/tool.py | 18 +- .../polygraphy/tools/check/check.py | 2 +- .../polygraphy/tools/check/subtool/lint.py | 113 +- .../polygraphy/tools/convert/convert.py | 22 +- .../Polygraphy/polygraphy/tools/data/data.py | 2 +- .../polygraphy/tools/data/subtool/to_input.py | 12 +- .../polygraphy/tools/debug/debug.py | 2 +- .../polygraphy/tools/debug/subtool/base.py | 31 +- .../polygraphy/tools/debug/subtool/build.py | 2 +- .../debug/subtool/iterative_debug_args.py | 76 +- .../tools/debug/subtool/precision.py | 66 +- .../polygraphy/tools/debug/subtool/reduce.py | 106 +- .../polygraphy/tools/debug/subtool/repeat.py | 14 +- .../polygraphy/tools/inspect/inspect.py | 11 +- .../tools/inspect/subtool/capability.py | 137 +- .../polygraphy/tools/inspect/subtool/data.py | 22 +- .../tools/inspect/subtool/diff_tactics.py | 18 +- .../polygraphy/tools/inspect/subtool/model.py | 37 +- .../tools/inspect/subtool/sparsity.py | 14 +- .../tools/inspect/subtool/tactics.py | 2 +- .../polygraphy/tools/plugin/plugin.py | 2 +- .../tools/plugin/subtool/list_plugins.py | 10 +- .../polygraphy/tools/plugin/subtool/match.py | 7 +- .../tools/plugin/subtool/plugin_base.py | 131 +- .../tools/plugin/subtool/replace.py | 113 +- tools/Polygraphy/polygraphy/tools/registry.py | 14 +- tools/Polygraphy/polygraphy/tools/run/run.py | 19 +- tools/Polygraphy/polygraphy/tools/script.py | 59 +- tools/Polygraphy/polygraphy/tools/sparse.py | 35 +- .../polygraphy/tools/surgeon/subtool/base.py | 2 +- .../tools/surgeon/subtool/extract.py | 51 +- .../tools/surgeon/subtool/insert.py | 35 +- .../polygraphy/tools/surgeon/subtool/prune.py | 14 +- .../tools/surgeon/subtool/sanitize.py | 31 +- .../polygraphy/tools/surgeon/surgeon.py | 11 +- .../polygraphy/tools/template/subtool/base.py | 2 +- .../tools/template/subtool/onnx_gs.py | 10 +- .../tools/template/subtool/trt_config.py | 7 +- .../tools/template/subtool/trt_network.py | 11 +- .../polygraphy/tools/template/template.py | 2 +- tools/Polygraphy/polygraphy/tools/util.py | 6 +- tools/Polygraphy/polygraphy/util/array.py | 8 +- tools/Polygraphy/polygraphy/util/util.py | 72 +- tools/Polygraphy/setup.py | 2 +- .../tests/backend/base/test_loader.py | 2 +- .../tests/backend/base/test_runner.py | 2 +- .../tests/backend/common/test_loader.py | 6 +- .../tests/backend/onnx/test_loader.py | 36 +- .../tests/backend/onnx/test_util.py | 10 +- .../tests/backend/onnxrt/test_loader.py | 7 +- .../tests/backend/onnxrt/test_runner.py | 9 +- .../tests/backend/pluginref/test_runner.py | 21 +- .../tests/backend/tf/test_loader.py | 17 +- .../tests/backend/tf/test_runner.py | 23 +- .../backend/trt/test_algorithm_selector.py | 2 +- .../tests/backend/trt/test_calibrator.py | 16 +- .../tests/backend/trt/test_loader.py | 63 +- .../tests/backend/trt/test_profile.py | 20 +- .../tests/backend/trt/test_runner.py | 149 +- .../Polygraphy/tests/backend/trt/test_util.py | 2 +- .../Polygraphy/tests/common/test_datatype.py | 2 +- .../Polygraphy/tests/common/test_interface.py | 2 +- tools/Polygraphy/tests/common/test_struct.py | 3 +- .../tests/comparator/test_comparator.py | 43 +- .../tests/comparator/test_compare.py | 2 +- .../tests/comparator/test_data_loader.py | 2 +- .../tests/comparator/test_postprocess.py | 3 +- .../tests/comparator/test_struct.py | 10 +- tools/Polygraphy/tests/conftest.py | 24 +- tools/Polygraphy/tests/cuda/test_cuda.py | 2 +- tools/Polygraphy/tests/func/test_func.py | 13 +- tools/Polygraphy/tests/helper.py | 11 +- tools/Polygraphy/tests/logger/test_logger.py | 16 +- tools/Polygraphy/tests/mod/conftest.py | 2 +- .../Polygraphy/tests/mod/test_dependencies.py | 16 +- tools/Polygraphy/tests/mod/test_exporter.py | 11 +- tools/Polygraphy/tests/mod/test_importer.py | 39 +- tools/Polygraphy/tests/mod/test_util.py | 2 +- tools/Polygraphy/tests/models/make_models.py | 220 ++- tools/Polygraphy/tests/models/meta.py | 274 +++- .../tests/models/plugins/toyPlugin/pattern.py | 29 +- ...hing_toy_plugin.onnx => toy_subgraph.onnx} | Bin .../tests/test_deprecated_aliases.py | 2 +- tools/Polygraphy/tests/test_examples.py | 5 +- tools/Polygraphy/tests/test_packaging.py | 14 +- tools/Polygraphy/tests/test_tests.py | 10 +- tools/Polygraphy/tests/test_ux.py | 6 +- .../tools/args/backend/onnx/test_loader.py | 97 +- .../tools/args/backend/onnxrt/test_loader.py | 9 +- .../tools/args/backend/test_runner_select.py | 8 +- .../tools/args/backend/tf/test_loader.py | 2 +- .../tools/args/backend/trt/test_config.py | 10 +- .../tools/args/backend/trt/test_loader.py | 12 +- .../tools/args/backend/trt/test_runner.py | 6 +- .../tools/args/comparator/test_comparator.py | 12 +- .../tools/args/comparator/test_compare.py | 40 +- .../tools/args/comparator/test_data_loader.py | 59 +- tools/Polygraphy/tests/tools/args/helper.py | 2 +- .../tests/tools/args/logger/test_logger.py | 14 +- .../tests/tools/args/test_docstrings.py | 22 +- .../Polygraphy/tests/tools/args/test_model.py | 36 +- .../tests/tools/args/util/test_util.py | 6 +- tools/Polygraphy/tests/tools/conftest.py | 3 +- .../tests/tools/fake_reduce_checker.py | 16 +- tools/Polygraphy/tests/tools/test_check.py | 78 +- tools/Polygraphy/tests/tools/test_convert.py | 63 +- tools/Polygraphy/tests/tools/test_data.py | 11 +- tools/Polygraphy/tests/tools/test_debug.py | 43 +- .../Polygraphy/tests/tools/test_deprecated.py | 2 +- tools/Polygraphy/tests/tools/test_inspect.py | 22 +- tools/Polygraphy/tests/tools/test_plugin.py | 8 +- .../Polygraphy/tests/tools/test_polygraphy.py | 2 +- tools/Polygraphy/tests/tools/test_run.py | 301 +++- tools/Polygraphy/tests/tools/test_script.py | 13 +- tools/Polygraphy/tests/tools/test_surgeon.py | 207 ++- tools/Polygraphy/tests/tools/test_template.py | 6 +- tools/Polygraphy/tests/util/test_array.py | 12 +- tools/Polygraphy/tests/util/test_serde.py | 2 +- tools/Polygraphy/tests/util/test_util.py | 73 +- .../trt-engine-explorer/trex/graphing.py | 1 + tools/onnx-graphsurgeon/CHANGELOG.md | 4 + .../examples/12_using_bf16/README.md | 26 + .../examples/12_using_bf16/generate.py | 29 +- .../examples/resources/12_bf16.onnx.png | Bin 0 -> 36504 bytes .../exporters/onnx_exporter.py | 69 +- .../onnx_graphsurgeon/ir/tensor.py | 54 +- .../onnx-graphsurgeon/tests/test_examples.py | 3 + tools/onnx-graphsurgeon/tests/test_ir.py | 10 +- 853 files changed, 11169 insertions(+), 12121 deletions(-) delete mode 100644 demo/Diffusion/calibration.py create mode 100644 demo/Diffusion/utils_ammo.py delete mode 100644 demo/Jasper/README.md delete mode 100644 demo/Tacotron2/README.md delete mode 100644 demo/Tacotron2/common/audio_processing.py delete mode 100644 demo/Tacotron2/common/layers.py delete mode 100644 demo/Tacotron2/common/stft.py delete mode 100644 demo/Tacotron2/common/utils.py delete mode 100644 demo/Tacotron2/config.json delete mode 100644 demo/Tacotron2/data_functions.py delete mode 100644 demo/Tacotron2/inference.py delete mode 100644 demo/Tacotron2/inference_perf.py delete mode 100644 demo/Tacotron2/main.py delete mode 100644 demo/Tacotron2/models.py delete mode 100644 demo/Tacotron2/multiproc.py delete mode 100644 demo/Tacotron2/phrases/phrase.txt delete mode 100644 demo/Tacotron2/phrases/phrase_1_128.txt delete mode 100644 demo/Tacotron2/phrases/phrase_1_256.txt delete mode 100644 demo/Tacotron2/phrases/phrase_1_64.txt delete mode 100644 demo/Tacotron2/phrases/phrase_4_256.txt delete mode 100644 demo/Tacotron2/phrases/phrase_4_64.txt delete mode 100644 demo/Tacotron2/phrases/phrase_8_256.txt delete mode 100644 demo/Tacotron2/phrases/phrase_8_64.txt delete mode 100644 demo/Tacotron2/preprocess_audio2mel.py delete mode 100644 demo/Tacotron2/requirements.txt delete mode 100644 demo/Tacotron2/run_latency_tests.sh delete mode 100755 demo/Tacotron2/scripts/download_checkpoints.sh delete mode 100755 demo/Tacotron2/scripts/inference_benchmark.sh delete mode 100755 demo/Tacotron2/scripts/install_prerequisites.sh delete mode 100755 demo/Tacotron2/scripts/prepare_dataset.sh delete mode 100644 demo/Tacotron2/scripts/prepare_mels.sh delete mode 100644 demo/Tacotron2/tacotron2/arg_parser.py delete mode 100644 demo/Tacotron2/tacotron2/data_function.py delete mode 100644 demo/Tacotron2/tacotron2/loss_function.py delete mode 100644 demo/Tacotron2/tacotron2/model.py delete mode 100644 demo/Tacotron2/tacotron2/text/LICENCE delete mode 100644 demo/Tacotron2/tacotron2/text/__init__.py delete mode 100644 demo/Tacotron2/tacotron2/text/cleaners.py delete mode 100644 demo/Tacotron2/tacotron2/text/cmudict.py delete mode 100644 demo/Tacotron2/tacotron2/text/numbers.py delete mode 100644 demo/Tacotron2/tacotron2/text/symbols.py delete mode 100644 demo/Tacotron2/tensorrt/convert_onnx2trt.py delete mode 100644 demo/Tacotron2/tensorrt/convert_tacotron22onnx.py delete mode 100644 demo/Tacotron2/tensorrt/convert_waveglow2onnx.py delete mode 100644 demo/Tacotron2/tensorrt/generate_decoder.py delete mode 100644 demo/Tacotron2/tensorrt/inference_trt.py delete mode 100644 demo/Tacotron2/tensorrt/run_latency_tests_trt.sh delete mode 100644 demo/Tacotron2/tensorrt/test_infer_trt.py delete mode 100644 demo/Tacotron2/tensorrt/trt_utils.py delete mode 100644 demo/Tacotron2/test_infer.py delete mode 100644 demo/Tacotron2/test_infer.sh delete mode 100644 demo/Tacotron2/train.py delete mode 100644 demo/Tacotron2/waveglow/arg_parser.py delete mode 100644 demo/Tacotron2/waveglow/data_function.py delete mode 100644 demo/Tacotron2/waveglow/denoiser.py delete mode 100644 demo/Tacotron2/waveglow/loss_function.py delete mode 100644 demo/Tacotron2/waveglow/model.py delete mode 100644 demo/experimental/HuggingFace-Diffusers/README.md delete mode 100644 demo/experimental/HuggingFace-Diffusers/TensorRT-diffusers-txt2img.ipynb create mode 100644 docker/rockylinux8.Dockerfile create mode 100644 docker/rockylinux9.Dockerfile create mode 100644 docker/ubuntu-22.04-aarch64.Dockerfile create mode 100644 docker/ubuntu-cross-aarch64.Dockerfile delete mode 100644 tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/__init__.py rename tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/{graph_with_subgraph_matching_toy_plugin.onnx => toy_subgraph.onnx} (100%) create mode 100644 tools/Polygraphy/polygraphy/backend/trt/file_reader.py rename tools/Polygraphy/tests/models/{graph_with_subgraph_matching_toy_plugin.onnx => toy_subgraph.onnx} (100%) create mode 100644 tools/onnx-graphsurgeon/examples/12_using_bf16/README.md rename demo/Tacotron2/loss_functions.py => tools/onnx-graphsurgeon/examples/12_using_bf16/generate.py (52%) create mode 100644 tools/onnx-graphsurgeon/examples/resources/12_bf16.onnx.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 66139b50..0ef9e135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # TensorRT OSS Release Changelog -## 10.0.0 EA - 2024-04-02 +## 10.0.1 GA - 2024-04-30 + +Key Features and Updates: + + - Parser changes + - Added support for building with `protobuf-lite`. + - Fixed issue when parsing and refitting models with nested `BatchNormalization` nodes. + - Added support for empty inputs in custom plugin nodes. + - Demo changes + - The following demos have been removed: Jasper, Tacotron2, HuggingFace Diffusers notebook + - Updated tooling + - Polygraphy v0.49.10 + - ONNX-GraphSurgeon v0.5.2 + - Build Containers + - Updated default cuda versions to `12.4.0`. + - Added Rocky Linux 8 and Rocky Linux 9 build containers + +## 10.0.0 EA - 2024-03-27 Key Features and Updates: diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d29b78e..a1f072a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -143,7 +143,20 @@ if(BUILD_PARSERS) configure_protobuf(${PROTOBUF_VERSION}) endif() -find_library_create_target(nvinfer nvinfer SHARED ${TRT_LIB_DIR}) +# Windows library names have major version appended. +if (MSVC) + set(nvinfer_lib_name "nvinfer_${TRT_SOVERSION}") + set(nvinfer_plugin_lib_name "nvinfer_plugin_${TRT_SOVERSION}") + set(nvinfer_vc_plugin_lib_name "nvinfer_vc_plugin_${TRT_SOVERSION}") + set(nvonnxparser_lib_name "nvonnxparser_${TRT_SOVERSION}") +else() + set(nvinfer_lib_name "nvinfer") + set(nvinfer_plugin_lib_name "nvinfer_plugin") + set(nvinfer_vc_plugin_lib_name "nvinfer_vc_plugin") + set(nvonnxparser_lib_name "nvonnxparser") +endif() + +find_library_create_target(nvinfer ${nvinfer_lib_name} SHARED ${TRT_LIB_DIR}) find_library(CUDART_LIB cudart_static HINTS ${CUDA_TOOLKIT_ROOT_DIR} PATH_SUFFIXES lib lib/x64 lib64) @@ -165,7 +178,16 @@ else() 75 ) - string(REGEX MATCH "aarch64" IS_ARM "${TRT_PLATFORM_ID}") + find_file(IS_L4T_NATIVE nv_tegra_release PATHS /env/) + set (IS_L4T_CROSS "False") + if (DEFINED ENV{IS_L4T_CROSS}) + set(IS_L4T_CROSS $ENV{IS_L4T_CROSS}) + endif() + + if (IS_L4T_NATIVE OR ${IS_L4T_CROSS} STREQUAL "True") + # Only Orin (SM87) supported + list(APPEND GPU_ARCHS 87) + endif() if (CUDA_VERSION VERSION_GREATER_EQUAL 11.0) # Ampere GPU (SM80) support is only available in CUDA versions > 11.0 @@ -206,13 +228,13 @@ endif() if(BUILD_PLUGINS) add_subdirectory(plugin) else() - find_library_create_target(nvinfer_plugin nvinfer_plugin SHARED ${TRT_OUT_DIR} ${TRT_LIB_DIR}) + find_library_create_target(nvinfer_plugin ${nvinfer_plugin_lib_name} SHARED ${TRT_OUT_DIR} ${TRT_LIB_DIR}) endif() if(BUILD_PARSERS) add_subdirectory(parsers) else() - find_library_create_target(nvonnxparser nvonnxparser SHARED ${TRT_OUT_DIR} ${TRT_LIB_DIR}) + find_library_create_target(nvonnxparser ${nvonnxparser_lib_name} SHARED ${TRT_OUT_DIR} ${TRT_LIB_DIR}) endif() if(BUILD_SAMPLES) diff --git a/README.md b/README.md index 28a3edba..9e2bf7b9 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ You can skip the **Build** section to enjoy TensorRT with Python. To build the TensorRT-OSS components, you will first need the following software packages. **TensorRT GA build** -* TensorRT v10.0.0.6 +* TensorRT v10.0.1.6 * Available from direct download links listed below **System Packages** @@ -73,16 +73,16 @@ To build the TensorRT-OSS components, you will first need the following software If using the TensorRT OSS build container, TensorRT libraries are preinstalled under `/usr/lib/x86_64-linux-gnu` and you may skip this step. Else download and extract the TensorRT GA build from [NVIDIA Developer Zone](https://developer.nvidia.com) with the direct links below: - - [TensorRT 10.0.0.6 for CUDA 11.8, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.0/tars/TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-11.8.tar.gz) - - [TensorRT 10.0.0.6 for CUDA 12.4, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.0/tars/TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-12.4.tar.gz) + - [TensorRT 10.0.1.6 for CUDA 11.8, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz) + - [TensorRT 10.0.1.6 for CUDA 12.4, Linux x86_64](https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz) **Example: Ubuntu 20.04 on x86-64 with cuda-12.4** ```bash cd ~/Downloads - tar -xvzf TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-12.4.tar.gz - export TRT_LIBPATH=`pwd`/TensorRT-10.0.0.6 + tar -xvzf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz + export TRT_LIBPATH=`pwd`/TensorRT-10.0.1.6 ``` ## Setting Up The Build Environment @@ -92,16 +92,27 @@ For Linux platforms, we recommend that you generate a docker container for build 1. #### Generate the TensorRT-OSS build container. The TensorRT-OSS build container can be generated using the supplied Dockerfiles and build scripts. The build containers are configured for building TensorRT OSS out-of-the-box. - **Example: Ubuntu 20.04 on x86-64 with cuda-12.3.2 (default)** + **Example: Ubuntu 20.04 on x86-64 with cuda-12.4 (default)** ```bash - ./docker/build.sh --file docker/ubuntu-20.04.Dockerfile --tag tensorrt-ubuntu20.04-cuda12.3.2 + ./docker/build.sh --file docker/ubuntu-20.04.Dockerfile --tag tensorrt-ubuntu20.04-cuda12.4 + ``` + **Example: Rockylinux8 on x86-64 with cuda-12.4** + ```bash + ./docker/build.sh --file docker/rockylinux8.Dockerfile --tag tensorrt-rockylinux8-cuda12.4 + ``` + **Example: Ubuntu 22.04 cross-compile for Jetson (aarch64) with cuda-12.4 (JetPack SDK)** + ```bash + ./docker/build.sh --file docker/ubuntu-cross-aarch64.Dockerfile --tag tensorrt-jetpack-cuda12.4 + ``` + **Example: Ubuntu 22.04 on aarch64 with cuda-12.4** + ```bash + ./docker/build.sh --file docker/ubuntu-22.04-aarch64.Dockerfile --tag tensorrt-aarch64-ubuntu22.04-cuda12.4 ``` - 2. #### Launch the TensorRT-OSS build container. **Example: Ubuntu 20.04 build container** ```bash - ./docker/launch.sh --tag tensorrt-ubuntu20.04-cuda12.3.2 --gpus all + ./docker/launch.sh --tag tensorrt-ubuntu20.04-cuda12.4 --gpus all ``` > NOTE:
1. Use the `--tag` corresponding to build container generated in Step 1. @@ -112,13 +123,36 @@ For Linux platforms, we recommend that you generate a docker container for build ## Building TensorRT-OSS * Generate Makefiles and build. - **Example: Linux (x86-64) build with default cuda-12.3.2** + **Example: Linux (x86-64) build with default cuda-12.4** ```bash cd $TRT_OSSPATH mkdir -p build && cd build cmake .. -DTRT_LIB_DIR=$TRT_LIBPATH -DTRT_OUT_DIR=`pwd`/out make -j$(nproc) ``` + **Example: Linux (aarch64) build with default cuda-12.4** + ```bash + cd $TRT_OSSPATH + mkdir -p build && cd build + cmake .. -DTRT_LIB_DIR=$TRT_LIBPATH -DTRT_OUT_DIR=`pwd`/out -DCMAKE_TOOLCHAIN_FILE=$TRT_OSSPATH/cmake/toolchains/cmake_aarch64-native.toolchain + make -j$(nproc) + ``` + **Example: Native build on Jetson (aarch64) with cuda-12.4** + ```bash + cd $TRT_OSSPATH + mkdir -p build && cd build + cmake .. -DTRT_LIB_DIR=$TRT_LIBPATH -DTRT_OUT_DIR=`pwd`/out -DTRT_PLATFORM_ID=aarch64 -DCUDA_VERSION=12.4 + CC=/usr/bin/gcc make -j$(nproc) + ``` + > NOTE: C compiler must be explicitly specified via CC= for native aarch64 builds of protobuf. + + **Example: Ubuntu 22.04 Cross-Compile for Jetson (aarch64) with cuda-12.4 (JetPack)** + ```bash + cd $TRT_OSSPATH + mkdir -p build && cd build + cmake .. -DCMAKE_TOOLCHAIN_FILE=$TRT_OSSPATH/cmake/toolchains/cmake_aarch64.toolchain -DCUDA_VERSION=12.4 -DCUDNN_LIB=/pdk_files/cudnn/usr/lib/aarch64-linux-gnu/libcudnn.so -DCUBLAS_LIB=/usr/local/cuda-12.4/targets/aarch64-linux/lib/stubs/libcublas.so -DCUBLASLT_LIB=/usr/local/cuda-12.4/targets/aarch64-linux/lib/stubs/libcublasLt.so -DTRT_LIB_DIR=/pdk_files/tensorrt/lib + make -j$(nproc) + ``` > NOTE:
1. The default CUDA version used by CMake is 12.2.0. To override this, for example to 11.8, append `-DCUDA_VERSION=11.8` to the cmake command. diff --git a/VERSION b/VERSION index efdce495..db243822 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.0.0.6 +10.0.1.6 diff --git a/cmake/modules/find_library_create_target.cmake b/cmake/modules/find_library_create_target.cmake index a1d29efb..49441847 100644 --- a/cmake/modules/find_library_create_target.cmake +++ b/cmake/modules/find_library_create_target.cmake @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -25,9 +25,6 @@ macro(find_library_create_target target_name lib libtype hints) find_library(${lib}_LIB_PATH ${lib}) message(STATUS "Library that was found ${${lib}_LIB_PATH}") add_library(${target_name} ${libtype} IMPORTED) - set_property(TARGET ${target_name} PROPERTY IMPORTED_LOCATION ${${lib}_LIB_PATH}) # This should be .so or .dll file, currently its .a or .lib. - if (WIN32) - set_property(TARGET ${target_name} PROPERTY IMPORTED_IMPLIB ${${lib}_LIB_PATH}) # This should be a .lib file - endif() + set_property(TARGET ${target_name} PROPERTY IMPORTED_LOCATION ${${lib}_LIB_PATH}) message(STATUS "==========================================================================================") endmacro() diff --git a/cmake/modules/set_ifndef.cmake b/cmake/modules/set_ifndef.cmake index fbdc9be1..85d769e9 100644 --- a/cmake/modules/set_ifndef.cmake +++ b/cmake/modules/set_ifndef.cmake @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_aarch64-android.toolchain b/cmake/toolchains/cmake_aarch64-android.toolchain index 87e490f6..ec768aa4 100644 --- a/cmake/toolchains/cmake_aarch64-android.toolchain +++ b/cmake/toolchains/cmake_aarch64-android.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_aarch64-native.toolchain b/cmake/toolchains/cmake_aarch64-native.toolchain index fd4e30cc..bd49c9bb 100644 --- a/cmake/toolchains/cmake_aarch64-native.toolchain +++ b/cmake/toolchains/cmake_aarch64-native.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_aarch64.toolchain b/cmake/toolchains/cmake_aarch64.toolchain index 3c87fd65..020a1066 100644 --- a/cmake/toolchains/cmake_aarch64.toolchain +++ b/cmake/toolchains/cmake_aarch64.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -19,6 +19,8 @@ set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(TRT_PLATFORM_ID "aarch64") +set(CMAKE_FIND_LIBRARY_PREFIXES "lib") +set(CMAKE_FIND_LIBRARY_SUFFIXES .so) if("$ENV{ARMSERVER}" AND "${CUDA_VERSION}" VERSION_GREATER_EQUAL 11.0) set(CUDA_PLATFORM_ID "sbsa-linux") @@ -46,10 +48,18 @@ set(BUILD_LIBRARY_ONLY 1) set(CUDA_TOOLKIT_ROOT_DIR ${CUDA_ROOT}) set(CUDA_INCLUDE_DIRS ${CUDA_ROOT}/include) +set(CMAKE_THREAD_LIBS_INIT "-lpthread") +set(CMAKE_HAVE_THREADS_LIBRARY 1) +set(CMAKE_USE_WIN32_THREADS_INIT 0) +set(CMAKE_USE_PTHREADS_INIT 1) + find_library(RT_LIB rt PATHS /usr/aarch64-linux-gnu/lib /usr/lib/aarch64-linux-gnu) if(NOT RT_LIB) - message(WARNING "librt.so not found in default paths") + find_file(RT_LIB librt.so PATHS /usr/aarch64-linux-gnu/lib /usr/lib/aarch64-linux-gnu) + if(NOT RT_LIB) + message(WARNING "librt.so not found in default paths") + endif() endif() message("RT_LIB: ${RT_LIB}") diff --git a/cmake/toolchains/cmake_aarch64_cross.toolchain b/cmake/toolchains/cmake_aarch64_cross.toolchain index 177a82f9..844fdd89 100644 --- a/cmake/toolchains/cmake_aarch64_cross.toolchain +++ b/cmake/toolchains/cmake_aarch64_cross.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_ppc64le.toolchain b/cmake/toolchains/cmake_ppc64le.toolchain index 074c3fb0..2d6272f5 100644 --- a/cmake/toolchains/cmake_ppc64le.toolchain +++ b/cmake/toolchains/cmake_ppc64le.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_qnx.toolchain b/cmake/toolchains/cmake_qnx.toolchain index 95f337a8..60b36163 100644 --- a/cmake/toolchains/cmake_qnx.toolchain +++ b/cmake/toolchains/cmake_qnx.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_x64_win.toolchain b/cmake/toolchains/cmake_x64_win.toolchain index 5dad0ce7..87b04f5f 100644 --- a/cmake/toolchains/cmake_x64_win.toolchain +++ b/cmake/toolchains/cmake_x64_win.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_x86_64.toolchain b/cmake/toolchains/cmake_x86_64.toolchain index 8d452945..daf336ef 100644 --- a/cmake/toolchains/cmake_x86_64.toolchain +++ b/cmake/toolchains/cmake_x86_64.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/cmake/toolchains/cmake_x86_64_agnostic.toolchain b/cmake/toolchains/cmake_x86_64_agnostic.toolchain index 8253d8f1..91c03095 100644 --- a/cmake/toolchains/cmake_x86_64_agnostic.toolchain +++ b/cmake/toolchains/cmake_x86_64_agnostic.toolchain @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/CMakeLists.txt b/demo/BERT/CMakeLists.txt index cc2c8fc9..94639130 100644 --- a/demo/BERT/CMakeLists.txt +++ b/demo/BERT/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/README.md b/demo/BERT/README.md index f867a321..27d141f5 100755 --- a/demo/BERT/README.md +++ b/demo/BERT/README.md @@ -73,9 +73,9 @@ The following software version configuration has been tested: |Software|Version| |--------|-------| -|Python|>=3.6| -|TensorRT|8.5.1| -|CUDA|11.6| +|Python|>=3.8| +|TensorRT|10.0.1.6| +|CUDA|12.4| ## Setup diff --git a/demo/BERT/builder.py b/demo/BERT/builder.py index 5eafe367..c5f21b0a 100755 --- a/demo/BERT/builder.py +++ b/demo/BERT/builder.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -43,7 +43,7 @@ trt_version = [n for n in trt.__version__.split('.')] # Import necessary plugins for demoBERT -plugin_lib_name = "nvinfer_plugin.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" +plugin_lib_name = "nvinfer_plugin_10.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" env_name_to_add_path = "PATH" if sys.platform == "win32" else "LD_LIBRARY_PATH" handle = ctypes.CDLL(plugin_lib_name, mode=ctypes.RTLD_GLOBAL) if not handle: diff --git a/demo/BERT/builder_utils.py b/demo/BERT/builder_utils.py index 248bee80..abf0f514 100644 --- a/demo/BERT/builder_utils.py +++ b/demo/BERT/builder_utils.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/builder_varseqlen.py b/demo/BERT/builder_varseqlen.py index ad25ef0c..66a9d571 100755 --- a/demo/BERT/builder_varseqlen.py +++ b/demo/BERT/builder_varseqlen.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -42,7 +42,7 @@ trt_version = [n for n in trt.__version__.split('.')] # Import necessary plugins for demoBERT -plugin_lib_name = "nvinfer_plugin.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" +plugin_lib_name = "nvinfer_plugin_10.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" env_name_to_add_path = "PATH" if sys.platform == "win32" else "LD_LIBRARY_PATH" handle = ctypes.CDLL(plugin_lib_name, mode=ctypes.RTLD_GLOBAL) if not handle: diff --git a/demo/BERT/helpers/calibrator.py b/demo/BERT/helpers/calibrator.py index beacc625..09e6014b 100644 --- a/demo/BERT/helpers/calibrator.py +++ b/demo/BERT/helpers/calibrator.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/helpers/data_processing.py b/demo/BERT/helpers/data_processing.py index 88459ebf..e7deae31 100644 --- a/demo/BERT/helpers/data_processing.py +++ b/demo/BERT/helpers/data_processing.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/helpers/tokenization.py b/demo/BERT/helpers/tokenization.py index 434f411d..9d3cb22d 100644 --- a/demo/BERT/helpers/tokenization.py +++ b/demo/BERT/helpers/tokenization.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/infer_c/bert_infer.h b/demo/BERT/infer_c/bert_infer.h index 2f72102a..d049877e 100644 --- a/demo/BERT/infer_c/bert_infer.h +++ b/demo/BERT/infer_c/bert_infer.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -152,15 +152,12 @@ struct BertInference mDeviceBuffers.emplace_back(devBuf); mHostOutput.resize(numOutputItems); - mBindings.resize(mEngine->getNbIOTensors() * mEngine->getNbOptimizationProfiles()); } void prepare(int profIdx, int batchSize) { - mContext->setOptimizationProfile(profIdx); - const int bindingIdxOffset = profIdx * mEngine->getNbIOTensors(); - std::copy(mDeviceBuffers.begin(), mDeviceBuffers.end(), mBindings.begin() + bindingIdxOffset); + mContext->setOptimizationProfileAsync(profIdx, mStream); if (mEnableVariableLen) { @@ -191,13 +188,13 @@ struct BertInference for (int32_t i = 0; i < mEngine->getNbIOTensors(); i++) { auto const& name = mEngine->getIOTensorName(i); - context->setTensorAddress(name, mBindings[i + bindingIdxOffset]); + mContext->setTensorAddress(name, mDeviceBuffers[i]); } cudaGraph_t graph; cudaGraphExec_t exec; // warm up and let mContext do cublas initialization - bool status = mContext->enqueueV3(mStream, nullptr); + bool status = mContext->enqueueV3(mStream); if (!status) { gLogError << "Enqueue failed\n"; @@ -206,7 +203,7 @@ struct BertInference gLogVerbose << "Capturing graph\n"; gpuErrChk(cudaStreamBeginCapture(mStream, cudaStreamCaptureModeRelaxed)); - status = mContext->enqueueV3(mStream, nullptr); + status = mContext->enqueueV3(mStream); if (!status) { gLogError << "Enqueue failed\n"; @@ -240,7 +237,7 @@ struct BertInference } else { - bool status = mContext->enqueueV3(mStream, nullptr); + bool status = mContext->enqueueV3(mStream); if (!status) { gLogError << "Enqueue failed\n"; @@ -265,7 +262,7 @@ struct BertInference } else { - bool status = mContext->enqueueV3(mStream, nullptr); + bool status = mContext->enqueueV3(mStream); if (!status) { gLogError << "Enqueue failed\n"; @@ -347,7 +344,6 @@ struct BertInference TrtUniquePtr mRuntime{nullptr}; TrtUniquePtr mEngine{nullptr}; TrtUniquePtr mContext{nullptr}; - std::vector mBindings; bool mEnableVariableLen; std::vector mCuSeqlens; diff --git a/demo/BERT/infer_c/common.h b/demo/BERT/infer_c/common.h index b5280e2a..da29944c 100644 --- a/demo/BERT/infer_c/common.h +++ b/demo/BERT/infer_c/common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -169,7 +169,7 @@ struct TrtDestroyer { void operator()(T* t) { - t->destroy(); + delete t; } }; diff --git a/demo/BERT/infer_c/infer_c.cpp b/demo/BERT/infer_c/infer_c.cpp index b868a661..946ce663 100644 --- a/demo/BERT/infer_c/infer_c.cpp +++ b/demo/BERT/infer_c/infer_c.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/demo/BERT/infer_c/logging.cpp b/demo/BERT/infer_c/logging.cpp index b6b14298..f651155c 100644 --- a/demo/BERT/infer_c/logging.cpp +++ b/demo/BERT/infer_c/logging.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/demo/BERT/infer_c/logging.h b/demo/BERT/infer_c/logging.h index 2c36d039..2c137465 100644 --- a/demo/BERT/infer_c/logging.h +++ b/demo/BERT/infer_c/logging.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/demo/BERT/infer_c/perf.cpp b/demo/BERT/infer_c/perf.cpp index bbc6de76..0208f2eb 100644 --- a/demo/BERT/infer_c/perf.cpp +++ b/demo/BERT/infer_c/perf.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/demo/BERT/inference.py b/demo/BERT/inference.py index dc172181..aa0d0dd7 100644 --- a/demo/BERT/inference.py +++ b/demo/BERT/inference.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -121,7 +121,7 @@ def question_features(tokens, question): return dp.convert_example_to_features(tokens, question, tokenizer, max_seq_length, doc_stride, args.max_query_length) # Import necessary plugins for demoBERT - plugin_lib_name = "nvinfer_plugin.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" + plugin_lib_name = "nvinfer_plugin_10.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" env_name_to_add_path = "PATH" if sys.platform == "win32" else "LD_LIBRARY_PATH" handle = ctypes.CDLL(plugin_lib_name, mode=ctypes.RTLD_GLOBAL) if not handle: diff --git a/demo/BERT/inference_c.py b/demo/BERT/inference_c.py index e2bda9af..b10127bd 100644 --- a/demo/BERT/inference_c.py +++ b/demo/BERT/inference_c.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/inference_varseqlen.py b/demo/BERT/inference_varseqlen.py index 7eb87012..700ddcce 100644 --- a/demo/BERT/inference_varseqlen.py +++ b/demo/BERT/inference_varseqlen.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -120,7 +120,7 @@ def question_features(tokens, question): return dp.convert_example_to_features(tokens, question, tokenizer, max_seq_length, doc_stride, args.max_query_length) # Import necessary plugins for demoBERT - plugin_lib_name = "nvinfer_plugin.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" + plugin_lib_name = "nvinfer_plugin_10.dll" if sys.platform == "win32" else "libnvinfer_plugin.so" env_name_to_add_path = "PATH" if sys.platform == "win32" else "LD_LIBRARY_PATH" handle = ctypes.CDLL(plugin_lib_name, mode=ctypes.RTLD_GLOBAL) if not handle: diff --git a/demo/BERT/perf.py b/demo/BERT/perf.py index 7b4e9da9..f3d2ab74 100644 --- a/demo/BERT/perf.py +++ b/demo/BERT/perf.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/perf_varseqlen.py b/demo/BERT/perf_varseqlen.py index 853201a4..6708f989 100644 --- a/demo/BERT/perf_varseqlen.py +++ b/demo/BERT/perf_varseqlen.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/squad/evaluate-v1.1.py b/demo/BERT/squad/evaluate-v1.1.py index c73db423..bde41564 100644 --- a/demo/BERT/squad/evaluate-v1.1.py +++ b/demo/BERT/squad/evaluate-v1.1.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/BERT/squad/evaluate-v2.0.py b/demo/BERT/squad/evaluate-v2.0.py index e36d3e9f..67518e3c 100644 --- a/demo/BERT/squad/evaluate-v2.0.py +++ b/demo/BERT/squad/evaluate-v2.0.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/DeBERTa/deberta_onnx_modify.py b/demo/DeBERTa/deberta_onnx_modify.py index 234c4659..f8fe61f5 100644 --- a/demo/DeBERTa/deberta_onnx_modify.py +++ b/demo/DeBERTa/deberta_onnx_modify.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/DeBERTa/deberta_ort_inference.py b/demo/DeBERTa/deberta_ort_inference.py index 17378989..05741733 100644 --- a/demo/DeBERTa/deberta_ort_inference.py +++ b/demo/DeBERTa/deberta_ort_inference.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/DeBERTa/deberta_pytorch2onnx.py b/demo/DeBERTa/deberta_pytorch2onnx.py index 51546b29..7745f0dc 100644 --- a/demo/DeBERTa/deberta_pytorch2onnx.py +++ b/demo/DeBERTa/deberta_pytorch2onnx.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/DeBERTa/deberta_tensorrt_inference.py b/demo/DeBERTa/deberta_tensorrt_inference.py index 378a5953..355ad7cf 100644 --- a/demo/DeBERTa/deberta_tensorrt_inference.py +++ b/demo/DeBERTa/deberta_tensorrt_inference.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/DeBERTa/requirements.txt b/demo/DeBERTa/requirements.txt index 59b63433..c52dd08a 100644 --- a/demo/DeBERTa/requirements.txt +++ b/demo/DeBERTa/requirements.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/Diffusion/README.md b/demo/Diffusion/README.md index d550c83b..42949381 100644 --- a/demo/Diffusion/README.md +++ b/demo/Diffusion/README.md @@ -19,15 +19,15 @@ Install nvidia-docker using [these intructions](https://docs.nvidia.com/datacent docker run --rm -it --gpus all -v $PWD:/workspace nvcr.io/nvidia/pytorch:24.01-py3 /bin/bash ``` +NOTE: The demo supports CUDA>=11.8 + ### Install latest TensorRT release ```bash python3 -m pip install --upgrade pip -python3 -m pip install --pre --upgrade --extra-index-url https://pypi.nvidia.com tensorrt +pip install --pre tensorrt-cu12 ``` -> NOTE: TensorRT 10.x is only available as a pre-release - Check your installed version using: `python3 -c 'import tensorrt;print(tensorrt.__version__)'` @@ -39,27 +39,24 @@ Check your installed version using: export TRT_OSSPATH=/workspace cd $TRT_OSSPATH/demo/Diffusion pip3 install -r requirements.txt - ``` -> NOTE: demoDiffusion has been tested on systems with NVIDIA A100, RTX3090, and RTX4090 GPUs, and the following software configuration. +> NOTE: demoDiffusion has been tested on systems with NVIDIA H100, A100, L40, T4, and RTX4090 GPUs, and the following software configuration. ``` diffusers 0.26.3 onnx 1.15.0 -onnx-graphsurgeon 0.3.27 -onnxruntime 1.17.0 -polygraphy 0.49.7 -tensorrt 10.0.0.6 +onnx-graphsurgeon 0.5.2 +onnxruntime 1.16.3 +polygraphy 0.49.9 +tensorrt 10.0.1.6 tokenizers 0.13.3 -torch 2.1.0 -transformers 4.31.0 +torch 2.2.0 +transformers 4.33.1 controlnet-aux 0.0.6 -nvidia-ammo 0.7.0 +nvidia-ammo 0.9.4 ``` - > NOTE: optionally install HuggingFace [accelerate](https://pypi.org/project/accelerate/) package for faster and less memory-intense model loading. - # Running demoDiffusion ### Review usage instructions for the supported pipelines @@ -75,6 +72,7 @@ python3 demo_txt2img_xl.py --help ### HuggingFace user access token To download model checkpoints for the Stable Diffusion pipelines, obtain a `read` access token to HuggingFace Hub. See [instructions](https://huggingface.co/docs/hub/security-tokens). +> NOTE: This step isn't required for many models now. ```bash export HF_TOKEN= @@ -144,10 +142,9 @@ python3 demo_txt2img_xl.py "Picture of a rustic Italian village with Olive trees ### Faster Text-to-image using SDXL & INT8 quantization using AMMO ```bash -python3 demo_txt2img_xl.py "a photo of an astronaut riding a horse on mars" --version xl-1.0 --onnx-dir onnx-sdxl --engine-dir engine-sdxl --int8 --quantization-level 3 +python3 demo_txt2img_xl.py "a photo of an astronaut riding a horse on mars" --version xl-1.0 --onnx-dir onnx-sdxl --engine-dir engine-sdxl --int8 ``` - -Note that the calibration process can be quite time-consuming, and will be repeated if `--quantization-level`, `--denoising-steps`, or `--onnx-dir` is changed. +> Note that INT8 quantization is only supported for SDXL, and won't work with LoRA weights. Some prompts may produce better inputs with fewer denoising steps (e.g. `--denoising-steps 20`) but this will repeat the calibration, ONNX export, and engine building processes for the U-Net. ### Faster Text-to-Image using SDXL + LCM (Latent Consistency Model) LoRA weights [LCM-LoRA](https://arxiv.org/abs/2311.05556) produces good quality images in 4 to 8 denoising steps instead of 30+ needed base model. Note that we use LCM scheduler and disable classifier-free-guidance by setting `--guidance-scale` to 0. diff --git a/demo/Diffusion/calibration.py b/demo/Diffusion/calibration.py deleted file mode 100644 index 98adb6d3..00000000 --- a/demo/Diffusion/calibration.py +++ /dev/null @@ -1,177 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 types -from typing import Callable, Optional, Union - -import numpy as np -import torch -import torch.distributed as dist -import torch.nn as nn -from torch.distributed import ReduceOp -from utilities import PercentileAmaxes - -from ammo.torch.quantization.model_calib import ( - enable_stats_collection, - finish_stats_collection, - max_calibrate, -) -from ammo.torch.quantization.utils import is_quantized_linear - - -def precentile_calib_mode(base_unet, quant_config={}): - def compute_amax(self, all_reduce=True): - """Return the absolute max of all tensors collected.""" - if ( - self._calib_amax is not None - and all_reduce - and dist.is_available() - and dist.is_initialized() - and dist.get_world_size() > 1 - ): - tmp_amax = self._calib_amax.clone() - dist.all_reduce(tmp_amax, op=ReduceOp.MAX) - self._calib_amax.copy_(tmp_amax) - if self._track_amax: - up_lim = int(self._amaxs.total_step * self._amaxs.percentile) - if up_lim <= 0: - up_lim = 1 - amaxs_values = [self._amaxs.data[i] for i in range(0, up_lim)] - act_amax = ( - torch.tensor(np.vstack(amaxs_values).min(axis=0)) - .float() - .squeeze(0) - .to(self._calib_amax.device) - .to(self._calib_amax.dtype) - ) - return act_amax - return self._calib_amax - - for _, module in base_unet.named_modules(): - if isinstance(module, (nn.Linear, nn.Conv2d)): - module.input_quantizer._calibrator._track_amax = True - module.input_quantizer._calibrator._amaxs = PercentileAmaxes( - total_step=quant_config["base-step"], percentile=quant_config["percentile"] - ) - module.input_quantizer._calibrator.compute_amax = types.MethodType( - compute_amax, module.input_quantizer._calibrator - ) - - -@torch.no_grad() -def smoothquant(model, forward_loop=None): - """ - Rewrite the original SmoothQuant method - """ - assert forward_loop is not None, "forward_loop must be provided for smoothquant" - max_calibrate(model, forward_loop) - - smoothed_modules = 0 - for name, module in model.named_modules(): - if is_quantized_linear(module): - if not hasattr(module.input_quantizer, "_amax"): - print(f"Warning: {name} is not calibrated, skip smoothing") - continue - if module.input_quantizer.num_bits != 8 or module.weight_quantizer.num_bits != 8: - print(f"Warning: only int8 smoothing is supported, skip {name}") - continue - if module.input_quantizer.axis != -1: - print(f"Warning: only per-channel smoothing is supported, skip {name}") - continue - - alpha = 1.0 - if hasattr(module, "alpha"): - alpha = module.alpha - assert ( - module.input_quantizer._amax.numel() > 1 - ), f"Error: {name} has only one channel to smooth" - - # It is important to keep scaling math in fp32 to be numerically safe - act_amax = module.input_quantizer.amax.float() - - act_device = act_amax.device - - # If model is split across devices, this tensor may be on wrong one - act_amax = act_amax.to(module.weight.device) - - weight_scale = module.weight.abs().max(dim=0, keepdim=True)[0] - scale_a = (weight_scale.pow(1 - alpha) / act_amax.pow(alpha)).squeeze() - - # Some channel could have 0 amax which causes scale_a to overflow. Explicitly mask them out here - epsilon = 1.0 / (1 << 31) - if act_amax.min() <= epsilon: - zero_mask = act_amax <= epsilon - scale_a[zero_mask] = 1 - inv_scale_a = 1.0 / scale_a - inv_scale_a = inv_scale_a.squeeze()[None, :] - - # Use per-tensor quantization for activation, add a pre-quantization scale vector - module.input_quantizer.pre_quant_scale = scale_a.to(module.weight.dtype).to(act_device) - module.input_quantizer._axis = None - delattr(module.input_quantizer, "_amax") - module.input_quantizer.amax = torch.tensor( - (act_amax * scale_a).max().item(), - dtype=module.weight.dtype, - device=module.weight.device, - ) - - # Multiply weight by inv_scale_a and recalibrate - module.weight.detach().copy_( - (module.weight.float() * inv_scale_a).to(module.weight.dtype) - ) - - enable_stats_collection(module.weight_quantizer) - module.weight_quantizer(module.weight) - finish_stats_collection(module.weight_quantizer) - - smoothed_modules += 1 - print(f"Smoothed {smoothed_modules} modules") - - -def calibrate( - model: nn.Module, - algorithm: Union[str, dict, None] = "max", - forward_loop: Optional[Callable] = None, -) -> None: - if algorithm is None: - return - - if isinstance(algorithm, str): - kwargs = {} - elif isinstance(algorithm, dict): - kwargs = algorithm.copy() - algorithm = kwargs.pop("method") - else: - raise TypeError(f"Unsupported type for algorithm: {type(algorithm)}") - - if algorithm == "smoothquant": - smoothquant(model, forward_loop) - elif algorithm == "max": - max_calibrate(model, forward_loop) - else: - raise ValueError(f"Unsupported calibration algorithm: {algorithm}") - - -def reg_alpha_qkv(base_unet, alpha): - """ - Only apply alpha to QKV layers - """ - for name, module in base_unet.named_modules(): - if isinstance(module, torch.nn.Linear): - if "to_q" in name or "to_k" in name or "to_v" in name: - module.alpha = alpha - diff --git a/demo/Diffusion/demo_img2img.py b/demo/Diffusion/demo_img2img.py index bf56f6a9..74ec90ad 100755 --- a/demo/Diffusion/demo_img2img.py +++ b/demo/Diffusion/demo_img2img.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/Diffusion/demo_inpaint.py b/demo/Diffusion/demo_inpaint.py index af635df0..29ca0ce2 100755 --- a/demo/Diffusion/demo_inpaint.py +++ b/demo/Diffusion/demo_inpaint.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/Diffusion/demo_txt2img.py b/demo/Diffusion/demo_txt2img.py index 3e33838f..84c9e164 100644 --- a/demo/Diffusion/demo_txt2img.py +++ b/demo/Diffusion/demo_txt2img.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/Diffusion/demo_txt2img_xl.py b/demo/Diffusion/demo_txt2img_xl.py index ea579279..96910756 100644 --- a/demo/Diffusion/demo_txt2img_xl.py +++ b/demo/Diffusion/demo_txt2img_xl.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/demo/Diffusion/models.py b/demo/Diffusion/models.py index b1a196aa..b48028ff 100644 --- a/demo/Diffusion/models.py +++ b/demo/Diffusion/models.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -22,7 +22,6 @@ ControlNetModel, UNet2DConditionModel ) -from diffusers.utils import convert_state_dict_to_diffusers import json import numpy as np import onnx @@ -159,13 +158,13 @@ def fuse_mha_qkv_int8_sq(self): del tensors[k] removed += 1 print(f"Removed {removed} QDQ nodes") - return removed + return removed # expected 72 for L2.5 def get_path(version, pipeline, controlnets=None): if controlnets is not None: return ["lllyasviel/sd-controlnet-" + modality for modality in controlnets] - + if version == "1.4": if pipeline.is_inpaint(): return "runwayml/stable-diffusion-inpainting" @@ -647,7 +646,7 @@ def __init__(self, unet, controlnets) -> None: super().__init__() self.unet = unet self.controlnets = controlnets - + def forward(self, sample, timestep, encoder_hidden_states, images, controlnet_scales): for i, (image, conditioning_scale, controlnet) in enumerate(zip(images, controlnet_scales, self.controlnets)): down_samples, mid_sample = controlnet( @@ -663,7 +662,7 @@ def forward(self, sample, timestep, encoder_hidden_states, images, controlnet_sc for down_sample in down_samples ] mid_sample *= conditioning_scale - + # merge samples if i == 0: down_block_res_samples, mid_block_res_sample = down_samples, mid_sample @@ -673,7 +672,7 @@ def forward(self, sample, timestep, encoder_hidden_states, images, controlnet_sc for samples_prev, samples_curr in zip(down_block_res_samples, down_samples) ] mid_block_res_sample += mid_sample - + noise_pred = self.unet( sample, timestep, @@ -744,7 +743,7 @@ def get_model(self, torch_inference=''): def get_input_names(self): if self.controlnets is None: return ['sample', 'timestep', 'encoder_hidden_states'] - else: + else: return ['sample', 'timestep', 'encoder_hidden_states', 'images', 'controlnet_scales'] def get_output_names(self): @@ -820,14 +819,14 @@ def get_sample_input(self, batch_size, image_height, image_width, static_shape): dtype = torch.float16 if self.fp16 else torch.float32 if self.controlnets is None: return ( - torch.randn(batch_size, self.unet_dim, latent_height, latent_width, dtype=torch.float32, device=self.device), - torch.tensor([1.], dtype=torch.float32, device=self.device), + torch.randn(batch_size, self.unet_dim, latent_height, latent_width, dtype=dtype, device=self.device), + torch.tensor([1.], dtype=dtype, device=self.device), torch.randn(batch_size, self.text_maxlen, self.embedding_dim, dtype=dtype, device=self.device) ) else: return ( - torch.randn(batch_size, self.unet_dim, latent_height, latent_width, dtype=torch.float32, device=self.device), - torch.tensor(999, dtype=torch.float32, device=self.device), + torch.randn(batch_size, self.unet_dim, latent_height, latent_width, dtype=dtype, device=self.device), + torch.tensor(999, dtype=dtype, device=self.device), torch.randn(batch_size, self.text_maxlen, self.embedding_dim, dtype=dtype, device=self.device), torch.randn(len(self.controlnets), batch_size, 3, image_height, image_width, dtype=dtype, device=self.device), torch.randn(len(self.controlnets), dtype=dtype, device=self.device) @@ -931,8 +930,8 @@ def get_sample_input(self, batch_size, image_height, image_width, static_shape): latent_height, latent_width = self.check_dims(batch_size, image_height, image_width) dtype = torch.float16 if self.fp16 else torch.float32 return ( - torch.randn(self.xB*batch_size, self.unet_dim, latent_height, latent_width, dtype=torch.float32, device=self.device), - torch.tensor([1.], dtype=torch.float32, device=self.device), + torch.randn(self.xB*batch_size, self.unet_dim, latent_height, latent_width, dtype=dtype, device=self.device), + torch.tensor([1.], dtype=dtype, device=self.device), torch.randn(self.xB*batch_size, self.text_maxlen, self.embedding_dim, dtype=dtype, device=self.device), { 'added_cond_kwargs': { diff --git a/demo/Diffusion/requirements.txt b/demo/Diffusion/requirements.txt index 4de26381..5fa939ec 100644 --- a/demo/Diffusion/requirements.txt +++ b/demo/Diffusion/requirements.txt @@ -1,4 +1,3 @@ -accelerate colored controlnet_aux==0.0.6 cuda-python @@ -7,11 +6,10 @@ ftfy matplotlib nvtx onnx==1.15.0 -onnxruntime==1.17.0 +onnxruntime==1.16.3 opencv-python==4.8.0.74 scipy -transformers==4.31.0 ---extra-index-url https://pypi.nvidia.com -nvidia-ammo==0.7.0 +transformers==4.33.1 +nvidia-ammo==0.9.4 onnx-graphsurgeon -polygraphy +polygraphy==0.49.9 diff --git a/demo/Diffusion/stable_diffusion_pipeline.py b/demo/Diffusion/stable_diffusion_pipeline.py index 13bd4156..10b7f57e 100755 --- a/demo/Diffusion/stable_diffusion_pipeline.py +++ b/demo/Diffusion/stable_diffusion_pipeline.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -15,8 +15,8 @@ # limitations under the License. # +import ammo.torch.opt as ato import ammo.torch.quantization as atq -import calibration from cuda import cudart from diffusers import ( DDIMScheduler, @@ -44,7 +44,6 @@ import numpy as np import nvtx import json -import onnx import os import pathlib import tensorrt as trt @@ -55,17 +54,18 @@ PIPELINE_TYPE, TRT_LOGGER, Engine, - filter_func, - get_smoothquant_config, get_refit_weights, load_calib_prompts, merge_loras, prepare_mask_and_masked_image, - quantize_lvl, - replace_lora_layers, save_image, unload_model ) +from utils_ammo import ( + filter_func, + quantize_lvl, + get_int8_config, +) class StableDiffusionPipeline: """ @@ -76,7 +76,7 @@ def __init__( version='1.5', pipeline_type=PIPELINE_TYPE.TXT2IMG, max_batch_size=16, - denoising_steps=50, + denoising_steps=30, scheduler=None, guidance_scale=7.5, device='cuda', @@ -216,6 +216,11 @@ def makeScheduler(cls, subfolder="scheduler", **kwargs): if self.pipeline_type.is_sd_xl(): self.config['clip_hidden_states'] = True self.torch_inference = torch_inference + if self.torch_inference: + torch._inductor.config.conv_1x1_as_mm = True + torch._inductor.config.coordinate_descent_tuning = True + torch._inductor.config.epilogue_fusion = False + torch._inductor.config.coordinate_descent_check_all_directions = True self.use_cuda_graph = use_cuda_graph # initialized in loadEngines() @@ -315,10 +320,11 @@ def loadEngines( timing_cache=None, int8=False, quantization_level=2.5, - quantization_percentile=0.4, - quantization_alpha=0.6, - calibration_steps=384, - denoising_steps=50, + quantization_percentile=1.0, + quantization_alpha=0.8, + calibration_size=32, + calib_batch_size=2, + denoising_steps=30, ): """ Build and load engines for TensorRT accelerated inference. @@ -349,6 +355,24 @@ def loadEngines( Enable all tactic sources during TensorRT engine builds. timing_cache (str): Path to the timing cache to speed up TensorRT build. + int8 (bool): + Whether to quantize to int8 format or not (SDXL only). + quantization_level (float): + Controls which layers to quantize. 1: CNN, 2: CNN+FFN, 2.5: CNN+FFN+QKV, 3: CNN+FC + quantization_percentile (float): + Control quantization scaling factors (amax) collecting range, where the minimum amax in + range(n_steps * percentile) will be collected. Recommendation: 1.0 + quantization_alpha (float): + The alpha parameter for SmoothQuant quantization used for linear layers. + Recommendation: 0.8 for SDXL + calibration_size (int): + The number of steps to use for calibrating the model for quantization. + Recommendation: 32, 64, 128 for SDXL + calib_batch_size (int): + The batch size to use for calibration. Defaults to 2. + denoising_steps (int): + The number of denoising steps. + More denoising steps usually lead to a higher quality image at the expense of slower inference. """ # Create directories if missing for directory in [engine_dir, onnx_dir]: @@ -411,7 +435,7 @@ def loadEngines( if int8: assert self.pipeline_type.is_sd_xl(), "int8 quantization only supported for SDXL pipeline" use_int8['unetxl'] = True - model_suffix['unetxl'] += f"-int8.l{quantization_level}.bs2.s{denoising_steps}.c{calibration_steps}.p{quantization_percentile}.a{quantization_alpha}" + model_suffix['unetxl'] += f"-int8.l{quantization_level}.bs2.s{denoising_steps}.c{calibration_size}.p{quantization_percentile}.a{quantization_alpha}" onnx_path = dict(zip(model_names, [self.getOnnxPath(model_name, onnx_dir, opt=False, suffix=model_suffix[model_name]) for model_name in model_names])) onnx_opt_path = dict(zip(model_names, [self.getOnnxPath(model_name, onnx_dir, suffix=model_suffix[model_name]) for model_name in model_names])) engine_path = dict(zip(model_names, [self.getEnginePath(model_name, engine_dir, do_engine_refit[model_name], suffix=model_suffix[model_name]) for model_name in model_names])) @@ -433,22 +457,16 @@ def loadEngines( print(f"[I] Calibrated weights not found, generating {state_dict_path}") pipeline = obj.get_pipeline() model = pipeline.unet - replace_lora_layers(model) calibration_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'calibration-prompts.txt') - # Use batch_size = 2 for UNet calibration - calibration_prompts = load_calib_prompts(2, calibration_file) - # TODO check size > calibration_steps - quant_config = get_smoothquant_config(model, quantization_level) - if quantization_percentile is not None: - quant_config["percentile"] = quantization_percentile - quant_config["base-step"] = int(denoising_steps) - - atq.replace_quant_module(model) - atq.set_quantizer_by_cfg(model, quant_config["quant_cfg"]) - if quantization_percentile is not None: - calibration.precentile_calib_mode(base_unet=model, quant_config=quant_config) - if quantization_alpha is not None: - calibration.reg_alpha_qkv(base_unet=model, alpha=quantization_alpha) + calibration_prompts = load_calib_prompts(calib_batch_size, calibration_file) + # TODO check size > calibration_size + quant_config = get_int8_config( + model, + quantization_level, + quantization_alpha, + quantization_percentile, + denoising_steps + ) def do_calibrate(base, calibration_prompts, **kwargs): for i_th, prompts in enumerate(calibration_prompts): @@ -462,34 +480,35 @@ def do_calibrate(base, calibration_prompts, **kwargs): ] * len(prompts), ).images - - def calibration_loop(): + + def calibration_loop(unet): + pipeline.model = unet do_calibrate( base=pipeline, calibration_prompts=calibration_prompts, - calib_size=calibration_steps, + calib_size=calibration_size // calib_batch_size, n_steps=denoising_steps, ) - print(f"[I] Performing int8 calibration for {calibration_steps} steps. This can take a long time.") - calibration.calibrate(model, quant_config["algorithm"], forward_loop=calibration_loop) - torch.save(model.state_dict(), state_dict_path) + print(f"[I] Performing int8 calibration for {calibration_size} steps.") + atq.quantize(model, quant_config, forward_loop=calibration_loop) + ato.save(model, state_dict_path) - print(f"[I] Generaing quantized ONNX model: {onnx_opt_path[model_name]}") + print(f"[I] Generating quantized ONNX model: {onnx_opt_path[model_name]}") if not os.path.exists(onnx_path[model_name]): model = obj.get_model() - replace_lora_layers(model) - atq.replace_quant_module(model) - quant_config = atq.INT8_DEFAULT_CFG - atq.set_quantizer_by_cfg(model, quant_config["quant_cfg"]) - model.load_state_dict(torch.load(state_dict_path), strict=True) - quantize_lvl(model, quantization_level) + ato.restore(model, state_dict_path) + quantize_lvl(model, quantization_level) atq.disable_quantizer(model, filter_func) - model.to(torch.float32) # QDQ needs to be in FP32 + model.to(torch.float32).to("cpu") # QDQ needs to be in FP32 + # WAR to enable ONNX export of quantized UNet + obj.device="cpu" + obj.fp16=False else: model = None obj.export_onnx(onnx_path[model_name], onnx_opt_path[model_name], onnx_opset, opt_image_height, opt_image_width, custom_model=model) - + obj.fp16=True # Part of WAR, UNET obj.fp16 defaults to True so it is safe to reset this way + # FIXME do_export_weights_map needs ONNX graph if do_export_weights_map: print(f"[I] Saving weights map: {weights_map_path[model_name]}") diff --git a/demo/Diffusion/utilities.py b/demo/Diffusion/utilities.py index 62d582f5..11f36807 100644 --- a/demo/Diffusion/utilities.py +++ b/demo/Diffusion/utilities.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -25,7 +25,6 @@ import numpy as np import onnx from onnx import numpy_helper -import onnx_graphsurgeon as gs import os from PIL import Image from polygraphy.backend.common import bytes_from_path @@ -40,9 +39,7 @@ ) from polygraphy.logger import G_LOGGER import random -import re import requests -from scipy import integrate import tensorrt as trt import torch import types @@ -406,63 +403,6 @@ def load_calib_prompts(batch_size, calib_data_path): lst = [line.rstrip("\n") for line in file] return [lst[i : i + batch_size] for i in range(0, len(lst), batch_size)] -def filter_func(name): - pattern = re.compile( - r".*(time_emb_proj|time_embedding|conv_in|conv_out|conv_shortcut|add_embedding).*" - ) - return pattern.match(name) is not None - -def quantize_lvl(unet, quant_level=2.5): - """ - We should disable the unwanted quantizer when exporting the onnx - Because in the current ammo setting, it will load the quantizer amax for all the layers even - if we didn't add that unwanted layer into the config during the calibration - """ - for name, module in unet.named_modules(): - if isinstance(module, torch.nn.Conv2d): - module.input_quantizer.enable() - module.weight_quantizer.enable() - elif isinstance(module, torch.nn.Linear): - if ( - (quant_level >= 2 and "ff.net" in name) - or (quant_level >= 2.5 and ("to_q" in name or "to_k" in name or "to_v" in name)) - or quant_level == 3 - ): - module.input_quantizer.enable() - module.weight_quantizer.enable() - else: - module.input_quantizer.disable() - module.weight_quantizer.disable() - -def get_smoothquant_config(model, quant_level=3): - quant_config = { - "quant_cfg": {}, - "algorithm": "smoothquant", - } - for name, module in model.named_modules(): - w_name = f"{name}*weight_quantizer" - i_name = f"{name}*input_quantizer" - - if ( - w_name in quant_config["quant_cfg"].keys() # type: ignore - or i_name in quant_config["quant_cfg"].keys() # type: ignore - ): - continue - if filter_func(name): - continue - if isinstance(module, torch.nn.Linear): - if ( - (quant_level >= 2 and "ff.net" in name) - or (quant_level >= 2.5 and ("to_q" in name or "to_k" in name or "to_v" in name)) - or quant_level == 3 - ): - quant_config["quant_cfg"][w_name] = {"num_bits": 8, "axis": 0} # type: ignore - quant_config["quant_cfg"][i_name] = {"num_bits": 8, "axis": -1} # type: ignore - elif isinstance(module, torch.nn.Conv2d): - quant_config["quant_cfg"][w_name] = {"num_bits": 8, "axis": 0} # type: ignore - quant_config["quant_cfg"][i_name] = {"num_bits": 8, "axis": None} # type: ignore - return quant_config - class PercentileAmaxes: def __init__(self, total_step, percentile) -> None: self.data = {} @@ -503,7 +443,7 @@ def add_arguments(parser): # TensorRT engine build parser.add_argument('--engine-dir', default='engine', help="Output directory for TensorRT engines") parser.add_argument('--int8', action='store_true', help="Apply int8 quantization.") - parser.add_argument('--quantization-level', type=float, default=3.0, choices=range(1,4), help="int8/fp8 quantization level, 1: CNN, 2: CNN+FFN, 2.5: CNN+FFN+QKV, 3: CNN+FC") + parser.add_argument('--quantization-level', type=float, default=2.5, choices=[1.0, 2.0, 2.5, 3.0], help="int8/fp8 quantization level, 1: CNN, 2: CNN+FFN, 2.5: CNN+FFN+QKV, 3: CNN+FC") parser.add_argument('--build-static-batch', action='store_true', help="Build TensorRT engines with fixed batch size.") parser.add_argument('--build-dynamic-shape', action='store_true', help="Build TensorRT engines with dynamic image shapes.") parser.add_argument('--build-enable-refit', action='store_true', help="Enable Refit option in TensorRT engines during build.") diff --git a/demo/Diffusion/utils_ammo.py b/demo/Diffusion/utils_ammo.py new file mode 100644 index 00000000..8bfe44b8 --- /dev/null +++ b/demo/Diffusion/utils_ammo.py @@ -0,0 +1,160 @@ +# +# 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 re +import torch + +from ammo.torch.quantization import utils as quant_utils +from ammo.torch.quantization.calib.max import MaxCalibrator + +from diffusers.models.lora import LoRACompatibleConv, LoRACompatibleLinear + + +class PercentileCalibrator(MaxCalibrator): + def __init__(self, num_bits=8, axis=None, unsigned=False, track_amax=False, **kwargs): + super().__init__(num_bits, axis, unsigned, track_amax) + self.percentile = kwargs["percentile"] + self.total_step = kwargs["total_step"] + self.global_min = kwargs["global_min"] + self.data = {} + self.i = 0 + + def collect(self, x): + """Tracks the absolute max of all tensors. + + Args: + x: A tensor + + Raises: + RuntimeError: If amax shape changes + """ + # Swap axis to reduce. + axis = self._axis if isinstance(self._axis, (list, tuple)) else [self._axis] + # Handle negative axis. + axis = [x.dim() + i if isinstance(i, int) and i < 0 else i for i in axis] + reduce_axis = [] + for i in range(x.dim()): + if i not in axis: + reduce_axis.append(i) + local_amax = quant_utils.reduce_amax(x, axis=reduce_axis).detach() + _cur_step = self.i % self.total_step + if _cur_step not in self.data.keys(): + self.data[_cur_step] = local_amax + else: + if self.global_min: + self.data[_cur_step] = torch.min(self.data[_cur_step], local_amax) + else: + self.data[_cur_step] += local_amax + if self._track_amax: + raise NotImplementedError + self.i += 1 + + def compute_amax(self): + """Return the absolute max of all tensors collected.""" + up_lim = int(self.total_step * self.percentile) + amaxs_values = [self.data[i] / self.total_step for i in range(0, up_lim)] + act_amax = torch.vstack(amaxs_values).min(axis=0)[0] + self._calib_amax = act_amax + return self._calib_amax + + def __str__(self): + s = "PercentileCalibrator" + return s.format(**self.__dict__) + + def __repr__(self): + s = "PercentileCalibrator(" + s += super(MaxCalibrator, self).__repr__() + s += " calib_amax={_calib_amax}" + if self._track_amax: + s += " amaxs={_amaxs}" + s += ")" + return s.format(**self.__dict__) + +def filter_func(name): + pattern = re.compile( + r".*(time_emb_proj|time_embedding|conv_in|conv_out|conv_shortcut|add_embedding).*" + ) + return pattern.match(name) is not None + + +def quantize_lvl(unet, quant_level=2.5): + """ + We should disable the unwanted quantizer when exporting the onnx + Because in the current ammo setting, it will load the quantizer amax for all the layers even + if we didn't add that unwanted layer into the config during the calibration + """ + for name, module in unet.named_modules(): + if isinstance(module, (torch.nn.Conv2d, LoRACompatibleConv)): + module.input_quantizer.enable() + module.weight_quantizer.enable() + elif isinstance(module, (torch.nn.Linear, LoRACompatibleLinear)): + if ( + (quant_level >= 2 and "ff.net" in name) + or (quant_level >= 2.5 and ("to_q" in name or "to_k" in name or "to_v" in name)) + or quant_level == 3 + ): + module.input_quantizer.enable() + module.weight_quantizer.enable() + else: + module.input_quantizer.disable() + module.weight_quantizer.disable() + +def get_int8_config( + model, quant_level=2.5, alpha=0.8, percentile=1.0, num_inference_steps=20, global_min=False +): + quant_config = { + "quant_cfg": { + "*lm_head*": {"enable": False}, + "*output_layer*": {"enable": False}, + "default": {"num_bits": 8, "axis": None}, + }, + "algorithm": {"method": "smoothquant", "alpha": alpha}, + } + for name, module in model.named_modules(): + w_name = f"{name}*weight_quantizer" + i_name = f"{name}*input_quantizer" + + if w_name in quant_config["quant_cfg"].keys() or i_name in quant_config["quant_cfg"].keys(): + continue + if filter_func(name): + continue + if isinstance(module, (torch.nn.Linear, LoRACompatibleLinear)): + if ( + (quant_level >= 2 and "ff.net" in name) + or (quant_level >= 2.5 and ("to_q" in name or "to_k" in name or "to_v" in name)) + or quant_level == 3 + ): + quant_config["quant_cfg"][w_name] = {"num_bits": 8, "axis": 0} + quant_config["quant_cfg"][i_name] = {"num_bits": 8, "axis": -1} + elif isinstance(module, (torch.nn.Conv2d, LoRACompatibleConv)): + quant_config["quant_cfg"][w_name] = {"num_bits": 8, "axis": 0} + quant_config["quant_cfg"][i_name] = { + "num_bits": 8, + "axis": None, + "calibrator": ( + PercentileCalibrator, + (), + { + "num_bits": 8, + "axis": None, + "percentile": percentile, + "total_step": num_inference_steps, + "global_min": global_min, + }, + ), + } + return quant_config diff --git a/demo/Jasper/README.md b/demo/Jasper/README.md deleted file mode 100644 index f8988c08..00000000 --- a/demo/Jasper/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Jasper Inference Using TensorRT - -[Jupyter Notebook](https://github.com/NVIDIA/DeepLearningExamples/blob/master/PyTorch/SpeechRecognition/Jasper/notebooks/) diff --git a/demo/Tacotron2/README.md b/demo/Tacotron2/README.md deleted file mode 100644 index c687c5ee..00000000 --- a/demo/Tacotron2/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Tacotron 2 and WaveGlow Inference with TensorRT - -The Tacotron2 and WaveGlow models form a text-to-speech (TTS) system that enables users to synthesize natural sounding speech from raw transcripts without any additional information such as patterns and/or rhythms of speech. This is an implementation of Tacotron2 for PyTorch, tested and maintained by NVIDIA, and provides scripts to perform high-performance inference using NVIDIA TensorRT. More information about the TTS system and its training can be found in the -[NVIDIA DeepLearningExamples](https://github.com/NVIDIA/DeepLearningExamples/tree/master/PyTorch/SpeechSynthesis/Tacotron2). - -NVIDIA TensorRT is a platform for high-performance deep learning inference. It includes a deep learning inference optimizer and runtime that delivers low latency and high-throughput for deep learning inference applications. After optimizing the compute-intensive acoustic model with NVIDIA TensorRT, inference throughput increased by up to 1.4x over native PyTorch in mixed precision. - -### Software Versions - -|Software|Version| -|--------|-------| -|Python|3.8.10| -|CUDA|12.2| -|Apex|0.1| -|TensorRT|9.0| -|PyTorch|2.0.1| - - -## Quick Start Guide - -1. Build and launch the container as described in [TensorRT OSS README](https://github.com/NVIDIA/TensorRT/blob/master/README.md). - - **Note:** After this point, all commands should be run from within the container. - -2. Verify TensorRT installation by printing the version: - ```bash - python3 -c "import tensorrt as trt; print(trt.__version__)" - ``` - -3. Install prerequisite software for TTS sample: - ```bash - cd $TRT_OSSPATH/demo/Tacotron2 - bash ./scripts/install_prerequisites.sh - ``` -4. Download pretrained checkpoints from [NGC](https://ngc.nvidia.com/catalog/models) into the `./checkpoints` directory: - -- [Tacotron2 checkpoint](https://ngc.nvidia.com/models/nvidia:tacotron2pyt_fp16) -- [WaveGlow checkpoint](https://ngc.nvidia.com/models/nvidia:waveglow256pyt_fp16) - - ```bash - bash ./scripts/download_checkpoints.sh - ``` - -5. Export the models to ONNX intermediate representation (ONNX IR). - Export Tacotron 2 to three ONNX parts: Encoder, Decoder, and Postnet: - - ```bash - mkdir -p output - python3 tensorrt/convert_tacotron22onnx.py --tacotron2 checkpoints/tacotron2_pyt_ckpt_amp_v19.09.0/nvidia_tacotron2pyt_fp16_20190427 -o output/ --fp16 - ``` - - Convert WaveGlow to ONNX IR: - - ```bash - python3 tensorrt/convert_waveglow2onnx.py --waveglow ./checkpoints/waveglow_ckpt_amp_256_v19.10.0/nvidia_waveglow256pyt_fp16 --config-file config.json --wn-channels 256 -o output/ --fp16 - ``` - - The above commands store the generated ONNX files under the `./output/` directory: - `encoder.onnx`, `decoder_iter.onnx`, `postnet.onnx`, `waveglow.onnx`, `loop_body_fp16.onnx`, and `decoder.onnx` (on TensorRT 8.0+ if `--no-loop` option is not specified). - -6. Export the ONNX IRs to TensorRT engines with fp16 mode enabled: - - ```bash - python3 tensorrt/convert_onnx2trt.py --encoder output/encoder.onnx --decoder output/decoder.onnx --postnet output/postnet.onnx --waveglow output/waveglow.onnx -o output/ --fp16 - ``` - - After running the command, there should be four new engine files in `./output/` directory: - `encoder_fp16.engine`, `decoder_with_outer_loop_fp16.engine`, `postnet_fp16.engine`, and `waveglow_fp16.engine`. On TensorRT <8.0 or if `--no-loop` option is specified, `decoder_iter_fp16.engine` is generated instead. - -7. Run TTS inference pipeline with fp16: - - - ```bash - python3 tensorrt/inference_trt.py -i phrases/phrase.txt --encoder output/encoder_fp16.engine --decoder output/decoder_with_outer_loop_fp16.engine --postnet output/postnet_fp16.engine --waveglow output/waveglow_fp16.engine -o output/ --fp16 - ``` - - On TensorRT <8.0 use `decoder_iter_fp16.engine` for the decoder instead. - -## Performance - -### Benchmarking - -The following section shows how to benchmark the TensorRT inference performance for our Tacotron2 + Waveglow TTS. - -#### TensorRT inference benchmark - -Before running the benchmark script, please download the checkpoints and build the TensorRT engines for the Tacotron2 and Waveglow models as prescribed in the [Quick Start Guide](#quick-start-guide) above. - -The inference benchmark is performed on a single GPU by the `inference_benchmark.sh` script, which runs 3 warm-up iterations then runs timed inference for 1000 iterations. - -```bash -bash scripts/inference_benchmark.sh -``` - -*Note*: For benchmarking we use WaveGlow with 256 residual channels, and Tacotron2 decoder with outer loop for TensorRT inference. - -### Results - -> Note: Results last updated for TensorRT 8.0.1.6 release. - -#### Inference performance: NVIDIA T4 (16GB) - -|Framework|Batch size|Input length|Precision|Avg latency (s)|Latency std (s)|Latency confidence interval 90% (s)|Latency confidence interval 95% (s)|Latency confidence interval 99% (s)|Throughput (samples/sec)|Speed-up PyT+TRT/TRT|Avg mels generated (81 mels=1 sec of speech)| Avg audio length (s)| Avg RTF| -|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| -|PyT+TRT|1| 128| FP16| 0.1662 | 0.0036 | 0.1705 | 0.1717 | 0.1736 | 871,568 | 7.64 | 566 | 6.99 | 42.03 | -|PyT |1| 128| FP16| 1.27 | 0.07 | 1.36 | 1.38 | 1.44 | 121,184 | 1.00 | 601 | 7.42 | 5.84 | - -#### Inference performance: NVIDIA V100 (16GB) - -|Framework|Batch size|Input length|Precision|Avg latency (s)|Latency std (s)|Latency confidence interval 90% (s)|Latency confidence interval 95% (s)|Latency confidence interval 99% (s)|Throughput (samples/sec)|Speed-up PyT+TRT/TRT|Avg mels generated (81 mels=1 sec of speech)| Avg audio length (s)| Avg RTF| -|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| -|PyT+TRT|1| 128| FP16| 0.1641 | 0.0046 | 0.1694 | 0.1707 | 0.1731 | 900,884 | 6.52 | 577 | 7.13 | 43.44 | -|PyT |1| 128| FP16| 1.07 | 0.06 | 1.14 | 1.17 | 1.23 | 144,668 | 1.00 | 602 | 7.42 | 6.95 | diff --git a/demo/Tacotron2/common/audio_processing.py b/demo/Tacotron2/common/audio_processing.py deleted file mode 100644 index 7b261cec..00000000 --- a/demo/Tacotron2/common/audio_processing.py +++ /dev/null @@ -1,110 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch -import numpy as np -from scipy.signal import get_window -import librosa.util as librosa_util - - -def window_sumsquare(window, n_frames, hop_length=200, win_length=800, - n_fft=800, dtype=np.float32, norm=None): - """ - # from librosa 0.6 - Compute the sum-square envelope of a window function at a given hop length. - - This is used to estimate modulation effects induced by windowing - observations in short-time fourier transforms. - - Parameters - ---------- - window : string, tuple, number, callable, or list-like - Window specification, as in `get_window` - - n_frames : int > 0 - The number of analysis frames - - hop_length : int > 0 - The number of samples to advance between frames - - win_length : [optional] - The length of the window function. By default, this matches `n_fft`. - - n_fft : int > 0 - The length of each analysis frame. - - dtype : np.dtype - The data type of the output - - Returns - ------- - wss : np.ndarray, shape=`(n_fft + hop_length * (n_frames - 1))` - The sum-squared envelope of the window function - """ - if win_length is None: - win_length = n_fft - - n = n_fft + hop_length * (n_frames - 1) - x = np.zeros(n, dtype=dtype) - - # Compute the squared window at the desired length - win_sq = get_window(window, win_length, fftbins=True) - win_sq = librosa_util.normalize(win_sq, norm=norm)**2 - win_sq = librosa_util.pad_center(win_sq, size=n_fft) - - # Fill the envelope - for i in range(n_frames): - sample = i * hop_length - x[sample:min(n, sample + n_fft)] += win_sq[:max(0, min(n_fft, n - sample))] - return x - - -def griffin_lim(magnitudes, stft_fn, n_iters=30): - """ - PARAMS - ------ - magnitudes: spectrogram magnitudes - stft_fn: STFT class with transform (STFT) and inverse (ISTFT) methods - """ - - angles = np.angle(np.exp(2j * np.pi * np.random.rand(*magnitudes.size()))) - angles = angles.astype(np.float32) - angles = torch.autograd.Variable(torch.from_numpy(angles)) - signal = stft_fn.inverse(magnitudes, angles).squeeze(1) - - for i in range(n_iters): - _, angles = stft_fn.transform(signal) - signal = stft_fn.inverse(magnitudes, angles).squeeze(1) - return signal - - -def dynamic_range_compression(x, C=1, clip_val=1e-5): - """ - PARAMS - ------ - C: compression factor - """ - return torch.log(torch.clamp(x, min=clip_val) * C) - - -def dynamic_range_decompression(x, C=1): - """ - PARAMS - ------ - C: compression factor used to compress - """ - return torch.exp(x) / C diff --git a/demo/Tacotron2/common/layers.py b/demo/Tacotron2/common/layers.py deleted file mode 100644 index cbeb4910..00000000 --- a/demo/Tacotron2/common/layers.py +++ /dev/null @@ -1,96 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch -from librosa.filters import mel as librosa_mel_fn -from common.audio_processing import dynamic_range_compression, dynamic_range_decompression -from common.stft import STFT - - -class LinearNorm(torch.nn.Module): - def __init__(self, in_dim, out_dim, bias=True, w_init_gain='linear'): - super(LinearNorm, self).__init__() - self.linear_layer = torch.nn.Linear(in_dim, out_dim, bias=bias) - - torch.nn.init.xavier_uniform_( - self.linear_layer.weight, - gain=torch.nn.init.calculate_gain(w_init_gain)) - - def forward(self, x): - return self.linear_layer(x) - - -class ConvNorm(torch.nn.Module): - def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, - padding=None, dilation=1, bias=True, w_init_gain='linear'): - super(ConvNorm, self).__init__() - if padding is None: - assert(kernel_size % 2 == 1) - padding = int(dilation * (kernel_size - 1) / 2) - - self.conv = torch.nn.Conv1d(in_channels, out_channels, - kernel_size=kernel_size, stride=stride, - padding=padding, dilation=dilation, - bias=bias) - - torch.nn.init.xavier_uniform_( - self.conv.weight, - gain=torch.nn.init.calculate_gain(w_init_gain)) - - def forward(self, signal): - return self.conv(signal) - - -class TacotronSTFT(torch.nn.Module): - def __init__(self, filter_length=1024, hop_length=256, win_length=1024, - n_mel_channels=80, sampling_rate=22050, mel_fmin=0.0, - mel_fmax=8000.0): - super(TacotronSTFT, self).__init__() - self.n_mel_channels = n_mel_channels - self.sampling_rate = sampling_rate - self.stft_fn = STFT(filter_length, hop_length, win_length) - mel_basis = librosa_mel_fn( - sampling_rate, filter_length, n_mel_channels, mel_fmin, mel_fmax) - mel_basis = torch.from_numpy(mel_basis).float() - self.register_buffer('mel_basis', mel_basis) - - def spectral_normalize(self, magnitudes): - output = dynamic_range_compression(magnitudes) - return output - - def spectral_de_normalize(self, magnitudes): - output = dynamic_range_decompression(magnitudes) - return output - - def mel_spectrogram(self, y): - """Computes mel-spectrograms from a batch of waves - PARAMS - ------ - y: Variable(torch.FloatTensor) with shape (B, T) in range [-1, 1] - - RETURNS - ------- - mel_output: torch.FloatTensor of shape (B, n_mel_channels, T) - """ - assert(torch.min(y.data) >= -1) - assert(torch.max(y.data) <= 1) - - magnitudes, phases = self.stft_fn.transform(y) - magnitudes = magnitudes.data - mel_output = torch.matmul(self.mel_basis, magnitudes) - mel_output = self.spectral_normalize(mel_output) - return mel_output diff --git a/demo/Tacotron2/common/stft.py b/demo/Tacotron2/common/stft.py deleted file mode 100644 index 0341d60e..00000000 --- a/demo/Tacotron2/common/stft.py +++ /dev/null @@ -1,159 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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. -# - -""" -BSD 3-Clause License - -Copyright (c) 2017, Prem Seetharaman -All rights reserved. - -* Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" - -import torch -import numpy as np -import torch.nn.functional as F -from torch.autograd import Variable -from scipy.signal import get_window -from librosa.util import pad_center, tiny -from common.audio_processing import window_sumsquare - - -class STFT(torch.nn.Module): - """adapted from Prem Seetharaman's https://github.com/pseeth/pytorch-stft""" - def __init__(self, filter_length=800, hop_length=200, win_length=800, - window='hann'): - super(STFT, self).__init__() - self.filter_length = filter_length - self.hop_length = hop_length - self.win_length = win_length - self.window = window - self.forward_transform = None - scale = self.filter_length / self.hop_length - fourier_basis = np.fft.fft(np.eye(self.filter_length)) - - cutoff = int((self.filter_length / 2 + 1)) - fourier_basis = np.vstack([np.real(fourier_basis[:cutoff, :]), - np.imag(fourier_basis[:cutoff, :])]) - - forward_basis = torch.FloatTensor(fourier_basis[:, None, :]) - inverse_basis = torch.FloatTensor( - np.linalg.pinv(scale * fourier_basis).T[:, None, :].astype(np.float32)) - - if window is not None: - assert(filter_length >= win_length) - # get window and zero center pad it to filter_length - fft_window = get_window(window, win_length, fftbins=True) - fft_window = pad_center(fft_window, size=filter_length) - fft_window = torch.from_numpy(fft_window).float() - - # window the bases - forward_basis *= fft_window - inverse_basis *= fft_window - - self.register_buffer('forward_basis', forward_basis.float()) - self.register_buffer('inverse_basis', inverse_basis.float()) - - def transform(self, input_data): - num_batches = input_data.size(0) - num_samples = input_data.size(1) - - self.num_samples = num_samples - - # similar to librosa, reflect-pad the input - input_data = input_data.view(num_batches, 1, num_samples) - input_data = F.pad( - input_data.unsqueeze(1), - (int(self.filter_length / 2), int(self.filter_length / 2), 0, 0), - mode='reflect') - input_data = input_data.squeeze(1) - - forward_transform = F.conv1d( - input_data, - Variable(self.forward_basis, requires_grad=False), - stride=self.hop_length, - padding=0) - - cutoff = int((self.filter_length / 2) + 1) - real_part = forward_transform[:, :cutoff, :] - imag_part = forward_transform[:, cutoff:, :] - - magnitude = torch.sqrt(real_part**2 + imag_part**2) - phase = torch.autograd.Variable( - torch.atan2(imag_part.data, real_part.data)) - - return magnitude, phase - - def inverse(self, magnitude, phase): - recombine_magnitude_phase = torch.cat( - [magnitude*torch.cos(phase), magnitude*torch.sin(phase)], dim=1) - - inverse_transform = F.conv_transpose2d( - recombine_magnitude_phase.unsqueeze(-1), - Variable(self.inverse_basis.unsqueeze(-1), requires_grad=False), - stride=(self.hop_length,1), - padding=(0,0)) - inverse_transform = inverse_transform.squeeze(-1) - - if self.window is not None: - window_sum = window_sumsquare( - self.window, magnitude.size(-1), hop_length=self.hop_length, - win_length=self.win_length, n_fft=self.filter_length, - dtype=np.float32) - # remove modulation effects - approx_nonzero_indices = torch.from_numpy( - np.where(window_sum > tiny(window_sum))[0]) - window_sum = torch.autograd.Variable( - torch.from_numpy(window_sum), requires_grad=False) - window_sum = window_sum.cuda() if magnitude.is_cuda else window_sum - inverse_transform[:, :, approx_nonzero_indices] /= window_sum[approx_nonzero_indices] - - # scale by hop ratio - inverse_transform *= float(self.filter_length) / self.hop_length - - inverse_transform = inverse_transform[:, :, int(self.filter_length/2):] - inverse_transform = inverse_transform[:, :, :-int(self.filter_length/2):] - - return inverse_transform - - def forward(self, input_data): - self.magnitude, self.phase = self.transform(input_data) - reconstruction = self.inverse(self.magnitude, self.phase) - return reconstruction diff --git a/demo/Tacotron2/common/utils.py b/demo/Tacotron2/common/utils.py deleted file mode 100644 index 6cccbf22..00000000 --- a/demo/Tacotron2/common/utils.py +++ /dev/null @@ -1,72 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 numpy as np -from scipy.io.wavfile import read -import torch -import os - -import argparse -import json - -class ParseFromConfigFile(argparse.Action): - - def __init__(self, option_strings, type, dest, help=None, required=False): - super(ParseFromConfigFile, self).__init__(option_strings=option_strings, type=type, dest=dest, help=help, required=required) - - def __call__(self, parser, namespace, values, option_string): - with open(values, 'r') as f: - data = json.load(f) - - for group in data.keys(): - for k,v in data[group].items(): - underscore_k = k.replace('-', '_') - setattr(namespace, underscore_k, v) - -def get_mask_from_lengths(lengths): - max_len = torch.max(lengths).item() - ids = torch.arange(0, max_len, device=lengths.device, dtype=lengths.dtype) - mask = (ids < lengths.unsqueeze(1)).byte() - mask = torch.le(mask, 0) - return mask - - -def load_wav_to_torch(full_path): - sampling_rate, data = read(full_path) - return torch.FloatTensor(data.astype(np.float32)), sampling_rate - - -def load_filepaths_and_text(dataset_path, filename, split="|"): - with open(filename, encoding='utf-8') as f: - def split_line(root, line): - parts = line.strip().split(split) - if len(parts) > 2: - raise Exception( - "incorrect line format for file: {}".format(filename)) - path = os.path.join(root, parts[0]) - text = parts[1] - return path,text - filepaths_and_text = [split_line(dataset_path, line) for line in f] - return filepaths_and_text - - -def to_gpu(x): - x = x.contiguous() - - if torch.cuda.is_available(): - x = x.cuda(non_blocking=True) - return x diff --git a/demo/Tacotron2/config.json b/demo/Tacotron2/config.json deleted file mode 100644 index 07ab289e..00000000 --- a/demo/Tacotron2/config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "audio": { - "max-wav-value": 32768.0, - "sampling-rate": 22050, - "filter-length": 1024, - "hop-length": 256, - "win-length": 1024, - "mel-fmin": 0.0, - "mel-fmax": 7000.0 - } -} diff --git a/demo/Tacotron2/data_functions.py b/demo/Tacotron2/data_functions.py deleted file mode 100644 index 623e5af6..00000000 --- a/demo/Tacotron2/data_functions.py +++ /dev/null @@ -1,58 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch -from tacotron2.data_function import TextMelCollate -from tacotron2.data_function import TextMelLoader -from waveglow.data_function import MelAudioLoader -from tacotron2.data_function import batch_to_gpu as batch_to_gpu_tacotron2 -from waveglow.data_function import batch_to_gpu as batch_to_gpu_waveglow - - -def get_collate_function(model_name, n_frames_per_step): - if model_name == 'Tacotron2': - collate_fn = TextMelCollate(n_frames_per_step) - elif model_name == 'WaveGlow': - collate_fn = torch.utils.data.dataloader.default_collate - else: - raise NotImplementedError( - "unknown collate function requested: {}".format(model_name)) - - return collate_fn - - -def get_data_loader(model_name, dataset_path, audiopaths_and_text, args): - if model_name == 'Tacotron2': - data_loader = TextMelLoader(dataset_path, audiopaths_and_text, args) - elif model_name == 'WaveGlow': - data_loader = MelAudioLoader(dataset_path, audiopaths_and_text, args) - else: - raise NotImplementedError( - "unknown data loader requested: {}".format(model_name)) - - return data_loader - - -def get_batch_to_gpu(model_name): - if model_name == 'Tacotron2': - batch_to_gpu = batch_to_gpu_tacotron2 - elif model_name == 'WaveGlow': - batch_to_gpu = batch_to_gpu_waveglow - else: - raise NotImplementedError( - "unknown batch_to_gpu requested: {}".format(model_name)) - return batch_to_gpu diff --git a/demo/Tacotron2/inference.py b/demo/Tacotron2/inference.py deleted file mode 100644 index 77bbccc1..00000000 --- a/demo/Tacotron2/inference.py +++ /dev/null @@ -1,266 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 tacotron2.text import text_to_sequence -import models -import torch -import argparse -import numpy as np -from scipy.io.wavfile import write -import matplotlib -import matplotlib.pyplot as plt - -import sys - -import time -import dllogger as DLLogger -from dllogger import StdOutBackend, JSONStreamBackend, Verbosity - -from waveglow.denoiser import Denoiser - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('-i', '--input', type=str, required=True, - help='Full path to the input text (phareses separated by new line)') - parser.add_argument('-o', '--output', required=True, - help='Output folder to save audio (file per phrase)') - parser.add_argument('--suffix', type=str, default="", - help="Output filename suffix") - parser.add_argument('--tacotron2', type=str, - help='Full path to the Tacotron2 model checkpoint file') - parser.add_argument('--waveglow', type=str, - help='Full path to the WaveGlow model checkpoint file') - parser.add_argument('-s', '--sigma-infer', default=0.9, type=float, - help='Standard deviation of the Gaussian distribution') - parser.add_argument('-d', '--denoising-strength', default=0.01, type=float, - help='Denoising strength for removing model bias') - parser.add_argument('-sr', '--sampling-rate', default=22050, type=int, - help='Sampling rate') - - run_mode = parser.add_mutually_exclusive_group() - run_mode.add_argument('--fp16', action='store_true', - help='Run inference with mixed precision') - run_mode.add_argument('--cpu', action='store_true', - help='Run inference on CPU') - - parser.add_argument('--log-file', type=str, default='nvlog.json', - help='Filename for logging') - parser.add_argument('--include-warmup', action='store_true', - help='Include warmup') - parser.add_argument('--stft-hop-length', type=int, default=256, - help='STFT hop length for estimating audio length from mel size') - - return parser - - -def checkpoint_from_distributed(state_dict): - """ - Checks whether checkpoint was generated by DistributedDataParallel. DDP - wraps model in additional "module.", it needs to be unwrapped for single - GPU inference. - :param state_dict: model's state dict - """ - ret = False - for key, _ in state_dict.items(): - if key.find('module.') != -1: - ret = True - break - return ret - - -def unwrap_distributed(state_dict): - """ - Unwraps model from DistributedDataParallel. - DDP wraps model in additional "module.", it needs to be removed for single - GPU inference. - :param state_dict: model's state dict - """ - new_state_dict = {} - for key, value in state_dict.items(): - new_key = key.replace('module.', '') - new_state_dict[new_key] = value - return new_state_dict - - -def load_and_setup_model(model_name, parser, checkpoint, fp16_run, cpu_run, forward_is_infer=False): - model_parser = models.parse_model_args(model_name, parser, add_help=False) - model_args, _ = model_parser.parse_known_args() - - model_config = models.get_model_config(model_name, model_args) - model = models.get_model(model_name, model_config, to_cuda=(not cpu_run), - forward_is_infer=forward_is_infer) - - if checkpoint is not None: - if cpu_run: - state_dict = torch.load(checkpoint, map_location=torch.device('cpu'))['state_dict'] - else: - state_dict = torch.load(checkpoint)['state_dict'] - if checkpoint_from_distributed(state_dict): - state_dict = unwrap_distributed(state_dict) - - model.load_state_dict(state_dict) - - if model_name == "WaveGlow": - model = model.remove_weightnorm(model) - - model.eval() - - if fp16_run: - model.half() - - return model - - -# taken from tacotron2/data_function.py:TextMelCollate.__call__ -def pad_sequences(batch): - # Right zero-pad all one-hot text sequences to max input length - input_lengths, ids_sorted_decreasing = torch.sort( - torch.LongTensor([len(x) for x in batch]), - dim=0, descending=True) - max_input_len = input_lengths[0] - - text_padded = torch.LongTensor(len(batch), max_input_len) - text_padded.zero_() - for i in range(len(ids_sorted_decreasing)): - text = batch[ids_sorted_decreasing[i]] - text_padded[i, :text.size(0)] = text - - return text_padded, input_lengths - - -def prepare_input_sequence(texts, cpu_run=False): - - d = [] - for i,text in enumerate(texts): - d.append(torch.IntTensor( - text_to_sequence(text, ['english_cleaners'])[:])) - - text_padded, input_lengths = pad_sequences(d) - if not cpu_run: - text_padded = text_padded.cuda().long() - input_lengths = input_lengths.cuda().long() - else: - text_padded = text_padded.long() - input_lengths = input_lengths.long() - - return text_padded, input_lengths - - -class MeasureTime(): - def __init__(self, measurements, key, cpu_run=False): - self.measurements = measurements - self.key = key - self.cpu_run = cpu_run - - def __enter__(self): - if not self.cpu_run: - torch.cuda.synchronize() - self.t0 = time.perf_counter() - - def __exit__(self, exc_type, exc_value, exc_traceback): - if not self.cpu_run: - torch.cuda.synchronize() - self.measurements[self.key] = time.perf_counter() - self.t0 - - -def main(): - """ - Launches text to speech (inference). - Inference is executed on a single GPU or CPU. - """ - parser = argparse.ArgumentParser( - description='PyTorch Tacotron 2 Inference') - parser = parse_args(parser) - args, _ = parser.parse_known_args() - - DLLogger.init(backends=[JSONStreamBackend(Verbosity.DEFAULT, - args.output+'/'+args.log_file), - StdOutBackend(Verbosity.VERBOSE)]) - for k,v in vars(args).items(): - DLLogger.log(step="PARAMETER", data={k:v}) - DLLogger.log(step="PARAMETER", data={'model_name':'Tacotron2_PyT'}) - - tacotron2 = load_and_setup_model('Tacotron2', parser, args.tacotron2, - args.fp16, args.cpu, forward_is_infer=True) - waveglow = load_and_setup_model('WaveGlow', parser, args.waveglow, - args.fp16, args.cpu, forward_is_infer=True) - denoiser = Denoiser(waveglow) - if not args.cpu: - denoiser.cuda() - - jitted_tacotron2 = torch.jit.script(tacotron2) - - texts = [] - try: - f = open(args.input, 'r') - texts = f.readlines() - except: - print("Could not read file") - sys.exit(1) - - if args.include_warmup: - sequence = torch.randint(low=0, high=148, size=(1,50)).long() - input_lengths = torch.IntTensor([sequence.size(1)]).long() - if not args.cpu: - sequence = sequence.cuda() - input_lengths = input_lengths.cuda() - for i in range(3): - with torch.no_grad(): - mel, mel_lengths, _ = jitted_tacotron2(sequence, input_lengths) - _ = waveglow(mel) - - measurements = {} - - sequences_padded, input_lengths = prepare_input_sequence(texts, args.cpu) - - with torch.no_grad(), MeasureTime(measurements, "tacotron2_time", args.cpu): - mel, mel_lengths, alignments = jitted_tacotron2(sequences_padded, input_lengths) - - with torch.no_grad(), MeasureTime(measurements, "waveglow_time", args.cpu): - audios = waveglow(mel, sigma=args.sigma_infer) - audios = audios.float() - with torch.no_grad(), MeasureTime(measurements, "denoiser_time", args.cpu): - audios = denoiser(audios, strength=args.denoising_strength).squeeze(1) - - print("Stopping after",mel.size(2),"decoder steps") - tacotron2_infer_perf = mel.size(0)*mel.size(2)/measurements['tacotron2_time'] - waveglow_infer_perf = audios.size(0)*audios.size(1)/measurements['waveglow_time'] - - DLLogger.log(step=0, data={"tacotron2_items_per_sec": tacotron2_infer_perf}) - DLLogger.log(step=0, data={"tacotron2_latency": measurements['tacotron2_time']}) - DLLogger.log(step=0, data={"waveglow_items_per_sec": waveglow_infer_perf}) - DLLogger.log(step=0, data={"waveglow_latency": measurements['waveglow_time']}) - DLLogger.log(step=0, data={"denoiser_latency": measurements['denoiser_time']}) - DLLogger.log(step=0, data={"latency": (measurements['tacotron2_time']+measurements['waveglow_time']+measurements['denoiser_time'])}) - - for i, audio in enumerate(audios): - - plt.imshow(alignments[i].float().data.cpu().numpy().T, aspect="auto", origin="lower") - figure_path = args.output+"alignment_"+str(i)+"_"+args.suffix+".png" - plt.savefig(figure_path) - - audio = audio[:mel_lengths[i]*args.stft_hop_length] - audio = audio/torch.max(torch.abs(audio)) - audio_path = args.output+"audio_"+str(i)+"_"+args.suffix+".wav" - write(audio_path, args.sampling_rate, audio.cpu().numpy()) - - DLLogger.flush() - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/inference_perf.py b/demo/Tacotron2/inference_perf.py deleted file mode 100644 index cb13463e..00000000 --- a/demo/Tacotron2/inference_perf.py +++ /dev/null @@ -1,117 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 models -import torch -import argparse -import numpy as np -import json -import time - -from inference import checkpoint_from_distributed, unwrap_distributed, load_and_setup_model, MeasureTime - -import dllogger as DLLogger -from dllogger import StdOutBackend, JSONStreamBackend, Verbosity - -from apex import amp - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('-m', '--model-name', type=str, default='', required=True, - help='Model to train') - parser.add_argument('-sr', '--sampling-rate', default=22050, type=int, - help='Sampling rate') - parser.add_argument('--amp-run', action='store_true', - help='Inference with Automatic Mixed Precision') - parser.add_argument('-bs', '--batch-size', type=int, default=1, - help='Batch size') - parser.add_argument('-o', '--output', type=str, required=True, - help='Directory to save results') - parser.add_argument('--log-file', type=str, default='nvlog.json', - help='Filename for logging') - - return parser - - -def main(): - """ - Launches inference benchmark. - Inference is executed on a single GPU. - """ - parser = argparse.ArgumentParser( - description='PyTorch Tacotron 2 Inference') - parser = parse_args(parser) - args, _ = parser.parse_known_args() - - log_file = args.log_file - - DLLogger.init(backends=[JSONStreamBackend(Verbosity.DEFAULT, - args.output+'/'+args.log_file), - StdOutBackend(Verbosity.VERBOSE)]) - for k,v in vars(args).items(): - DLLogger.log(step="PARAMETER", data={k:v}) - DLLogger.log(step="PARAMETER", data={'model_name':'Tacotron2_PyT'}) - - model = load_and_setup_model(args.model_name, parser, None, args.amp_run, - forward_is_infer=True) - - if args.model_name == "Tacotron2": - model = torch.jit.script(model) - - warmup_iters = 3 - num_iters = 1+warmup_iters - - for i in range(num_iters): - - measurements = {} - - if args.model_name == 'Tacotron2': - text_padded = torch.randint(low=0, high=148, size=(args.batch_size, 140), - dtype=torch.long).cuda() - input_lengths = torch.IntTensor([text_padded.size(1)]*args.batch_size).cuda().long() - with torch.no_grad(), MeasureTime(measurements, "inference_time"): - mels, _, _ = model(text_padded, input_lengths) - num_items = mels.size(0)*mels.size(2) - - if args.model_name == 'WaveGlow': - n_mel_channels = model.upsample.in_channels - num_mels = 895 - mel_padded = torch.zeros(args.batch_size, n_mel_channels, - num_mels).normal_(-5.62, 1.98).cuda() - if args.amp_run: - mel_padded = mel_padded.half() - - with torch.no_grad(), MeasureTime(measurements, "inference_time"): - audios = model(mel_padded) - audios = audios.float() - num_items = audios.size(0)*audios.size(1) - - if i >= warmup_iters: - DLLogger.log(step=(i-warmup_iters,), data={"latency": measurements['inference_time']}) - DLLogger.log(step=(i-warmup_iters,), data={"items_per_sec": num_items/measurements['inference_time']}) - - DLLogger.log(step=tuple(), - data={'infer_latency': measurements['inference_time']}) - DLLogger.log(step=tuple(), - data={'infer_items_per_sec': num_items/measurements['inference_time']}) - - DLLogger.flush() - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/main.py b/demo/Tacotron2/main.py deleted file mode 100644 index 2fee8563..00000000 --- a/demo/Tacotron2/main.py +++ /dev/null @@ -1,43 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 argparse -from train import main as main_train -from inference_perf import main as main_infer - -def parse_args(parser): - """ - Parse commandline arguments. - """ - - parser.add_argument('--bench-class', type=str, choices=['train', 'perf-infer', 'perf-train'], required=True, help='Choose test class') - - return parser - -def main(): - - parser = argparse.ArgumentParser(description='PyTorch Tacotron 2 Testing') - parser = parse_args(parser) - args, unknown_args = parser.parse_known_args() - - if "train" in args.bench_class: - main_train() - else: - main_infer() - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/models.py b/demo/Tacotron2/models.py deleted file mode 100644 index fad8af46..00000000 --- a/demo/Tacotron2/models.py +++ /dev/null @@ -1,137 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 sys -from os.path import abspath, dirname -# enabling modules discovery from global entrypoint -sys.path.append(abspath(dirname(__file__)+'/')) -from tacotron2.model import Tacotron2 -from waveglow.model import WaveGlow -import torch - - -def parse_model_args(model_name, parser, add_help=False): - if model_name == 'Tacotron2': - from tacotron2.arg_parser import parse_tacotron2_args - return parse_tacotron2_args(parser, add_help) - if model_name == 'WaveGlow': - from waveglow.arg_parser import parse_waveglow_args - return parse_waveglow_args(parser, add_help) - else: - raise NotImplementedError(model_name) - - -def batchnorm_to_float(module): - """Converts batch norm to FP32""" - if isinstance(module, torch.nn.modules.batchnorm._BatchNorm): - module.float() - for child in module.children(): - batchnorm_to_float(child) - return module - - -def init_bn(module): - if isinstance(module, torch.nn.modules.batchnorm._BatchNorm): - if module.affine: - module.weight.data.uniform_() - for child in module.children(): - init_bn(child) - - -def get_model(model_name, model_config, to_cuda, - uniform_initialize_bn_weight=False, forward_is_infer=False): - """ Code chooses a model based on name""" - model = None - if model_name == 'Tacotron2': - if forward_is_infer: - class Tacotron2__forward_is_infer(Tacotron2): - def forward(self, inputs, input_lengths): - return self.infer(inputs, input_lengths) - model = Tacotron2__forward_is_infer(**model_config) - else: - model = Tacotron2(**model_config) - elif model_name == 'WaveGlow': - if forward_is_infer: - class WaveGlow__forward_is_infer(WaveGlow): - def forward(self, spect, sigma=1.0): - return self.infer(spect, sigma) - model = WaveGlow__forward_is_infer(**model_config) - else: - model = WaveGlow(**model_config) - else: - raise NotImplementedError(model_name) - - if uniform_initialize_bn_weight: - init_bn(model) - - if to_cuda: - model = model.cuda() - return model - - -def get_model_config(model_name, args): - """ Code chooses a model based on name""" - if model_name == 'Tacotron2': - model_config = dict( - # optimization - mask_padding=args.mask_padding, - # audio - n_mel_channels=args.n_mel_channels, - # symbols - n_symbols=args.n_symbols, - symbols_embedding_dim=args.symbols_embedding_dim, - # encoder - encoder_kernel_size=args.encoder_kernel_size, - encoder_n_convolutions=args.encoder_n_convolutions, - encoder_embedding_dim=args.encoder_embedding_dim, - # attention - attention_rnn_dim=args.attention_rnn_dim, - attention_dim=args.attention_dim, - # attention location - attention_location_n_filters=args.attention_location_n_filters, - attention_location_kernel_size=args.attention_location_kernel_size, - # decoder - n_frames_per_step=args.n_frames_per_step, - decoder_rnn_dim=args.decoder_rnn_dim, - prenet_dim=args.prenet_dim, - max_decoder_steps=args.max_decoder_steps, - gate_threshold=args.gate_threshold, - p_attention_dropout=args.p_attention_dropout, - p_decoder_dropout=args.p_decoder_dropout, - # postnet - postnet_embedding_dim=args.postnet_embedding_dim, - postnet_kernel_size=args.postnet_kernel_size, - postnet_n_convolutions=args.postnet_n_convolutions, - decoder_no_early_stopping=args.decoder_no_early_stopping - ) - return model_config - elif model_name == 'WaveGlow': - model_config = dict( - n_mel_channels=args.n_mel_channels, - n_flows=args.flows, - n_group=args.groups, - n_early_every=args.early_every, - n_early_size=args.early_size, - WN_config=dict( - n_layers=args.wn_layers, - kernel_size=args.wn_kernel_size, - n_channels=args.wn_channels - ) - ) - return model_config - else: - raise NotImplementedError(model_name) diff --git a/demo/Tacotron2/multiproc.py b/demo/Tacotron2/multiproc.py deleted file mode 100644 index d3eb63ad..00000000 --- a/demo/Tacotron2/multiproc.py +++ /dev/null @@ -1,75 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 sys -import subprocess - -import torch - - -def main(): - argslist = list(sys.argv)[1:] - world_size = torch.cuda.device_count() - - if '--world-size' in argslist: - argslist[argslist.index('--world-size') + 1] = str(world_size) - else: - argslist.append('--world-size') - argslist.append(str(world_size)) - - workers = [] - - for i in range(world_size): - if '--rank' in argslist: - argslist[argslist.index('--rank') + 1] = str(i) - else: - argslist.append('--rank') - argslist.append(str(i)) - stdout = None if i == 0 else subprocess.DEVNULL - worker = subprocess.Popen( - [str(sys.executable)] + argslist, stdout=stdout) - workers.append(worker) - - returncode = 0 - try: - pending = len(workers) - while pending > 0: - for worker in workers: - try: - worker_returncode = worker.wait(1) - except subprocess.TimeoutExpired: - continue - pending -= 1 - if worker_returncode != 0: - if returncode != 1: - for worker in workers: - worker.terminate() - returncode = 1 - - except KeyboardInterrupt: - print('Pressed CTRL-C, TERMINATING') - for worker in workers: - worker.terminate() - for worker in workers: - worker.wait() - raise - - sys.exit(returncode) - - -if __name__ == "__main__": - main() diff --git a/demo/Tacotron2/phrases/phrase.txt b/demo/Tacotron2/phrases/phrase.txt deleted file mode 100644 index 8999934d..00000000 --- a/demo/Tacotron2/phrases/phrase.txt +++ /dev/null @@ -1 +0,0 @@ -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves. diff --git a/demo/Tacotron2/phrases/phrase_1_128.txt b/demo/Tacotron2/phrases/phrase_1_128.txt deleted file mode 100644 index 2bd87ff0..00000000 --- a/demo/Tacotron2/phrases/phrase_1_128.txt +++ /dev/null @@ -1 +0,0 @@ -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the diff --git a/demo/Tacotron2/phrases/phrase_1_256.txt b/demo/Tacotron2/phrases/phrase_1_256.txt deleted file mode 100644 index 8286058e..00000000 --- a/demo/Tacotron2/phrases/phrase_1_256.txt +++ /dev/null @@ -1,2 +0,0 @@ -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. - diff --git a/demo/Tacotron2/phrases/phrase_1_64.txt b/demo/Tacotron2/phrases/phrase_1_64.txt deleted file mode 100644 index 817a8a60..00000000 --- a/demo/Tacotron2/phrases/phrase_1_64.txt +++ /dev/null @@ -1 +0,0 @@ -She sells seashells by the seashore, shells she sells are great diff --git a/demo/Tacotron2/phrases/phrase_4_256.txt b/demo/Tacotron2/phrases/phrase_4_256.txt deleted file mode 100644 index 84de94bc..00000000 --- a/demo/Tacotron2/phrases/phrase_4_256.txt +++ /dev/null @@ -1,4 +0,0 @@ -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. diff --git a/demo/Tacotron2/phrases/phrase_4_64.txt b/demo/Tacotron2/phrases/phrase_4_64.txt deleted file mode 100644 index cd1d75b5..00000000 --- a/demo/Tacotron2/phrases/phrase_4_64.txt +++ /dev/null @@ -1,4 +0,0 @@ -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great diff --git a/demo/Tacotron2/phrases/phrase_8_256.txt b/demo/Tacotron2/phrases/phrase_8_256.txt deleted file mode 100644 index eace2b8e..00000000 --- a/demo/Tacotron2/phrases/phrase_8_256.txt +++ /dev/null @@ -1,8 +0,0 @@ -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. -The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves and the form of printed letters should be beautiful, and that their arrangement on pages. diff --git a/demo/Tacotron2/phrases/phrase_8_64.txt b/demo/Tacotron2/phrases/phrase_8_64.txt deleted file mode 100644 index e3a97a5c..00000000 --- a/demo/Tacotron2/phrases/phrase_8_64.txt +++ /dev/null @@ -1,8 +0,0 @@ -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great -She sells seashells by the seashore, shells she sells are great diff --git a/demo/Tacotron2/preprocess_audio2mel.py b/demo/Tacotron2/preprocess_audio2mel.py deleted file mode 100644 index 32026325..00000000 --- a/demo/Tacotron2/preprocess_audio2mel.py +++ /dev/null @@ -1,81 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 argparse -import torch - -from tacotron2.data_function import TextMelLoader -from common.utils import load_filepaths_and_text - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('-d', '--dataset-path', type=str, - default='./', help='Path to dataset') - parser.add_argument('--wav-files', required=True, - type=str, help='Path to filelist with audio paths and text') - parser.add_argument('--mel-files', required=True, - type=str, help='Path to filelist with mel paths and text') - parser.add_argument('--text-cleaners', nargs='*', - default=['english_cleaners'], type=str, - help='Type of text cleaners for input text') - parser.add_argument('--max-wav-value', default=32768.0, type=float, - help='Maximum audiowave value') - parser.add_argument('--sampling-rate', default=22050, type=int, - help='Sampling rate') - parser.add_argument('--filter-length', default=1024, type=int, - help='Filter length') - parser.add_argument('--hop-length', default=256, type=int, - help='Hop (stride) length') - parser.add_argument('--win-length', default=1024, type=int, - help='Window length') - parser.add_argument('--mel-fmin', default=0.0, type=float, - help='Minimum mel frequency') - parser.add_argument('--mel-fmax', default=8000.0, type=float, - help='Maximum mel frequency') - parser.add_argument('--n-mel-channels', default=80, type=int, - help='Number of bins in mel-spectrograms') - - return parser - - -def audio2mel(dataset_path, audiopaths_and_text, melpaths_and_text, args): - - melpaths_and_text_list = load_filepaths_and_text(dataset_path, melpaths_and_text) - audiopaths_and_text_list = load_filepaths_and_text(dataset_path, audiopaths_and_text) - - data_loader = TextMelLoader(dataset_path, audiopaths_and_text, args) - - for i in range(len(melpaths_and_text_list)): - if i%100 == 0: - print("done", i, "/", len(melpaths_and_text_list)) - - mel = data_loader.get_mel(audiopaths_and_text_list[i][0]) - torch.save(mel, melpaths_and_text_list[i][0]) - -def main(): - - parser = argparse.ArgumentParser(description='PyTorch Tacotron 2 Training') - parser = parse_args(parser) - args = parser.parse_args() - args.load_mel_from_disk = False - - audio2mel(args.dataset_path, args.wav_files, args.mel_files, args) - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/requirements.txt b/demo/Tacotron2/requirements.txt deleted file mode 100644 index b6eb26de..00000000 --- a/demo/Tacotron2/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -numba>=0.48 -resampy>=0.3.1 -torch==2.0.1 -matplotlib -numpy -inflect -librosa>=0.10.0 -scipy -Unidecode -git+https://github.com/NVIDIA/dllogger#egg=dllogger ---extra-index-url https://pypi.ngc.nvidia.com -onnx-graphsurgeon diff --git a/demo/Tacotron2/run_latency_tests.sh b/demo/Tacotron2/run_latency_tests.sh deleted file mode 100644 index 85e5f0f8..00000000 --- a/demo/Tacotron2/run_latency_tests.sh +++ /dev/null @@ -1,27 +0,0 @@ -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -unset CUDA_VISIBLE_DEVICES -bash test_infer.sh -bs 1 -il 128 --fp16 --num-iters 1003 --tacotron2 ./checkpoints/tacotron2_1032590_6000_amp --waveglow ./checkpoints/waveglow_1076430_14000_amp --wn-channels 256 -bash test_infer.sh -bs 4 -il 128 --fp16 --num-iters 1003 --tacotron2 ./checkpoints/tacotron2_1032590_6000_amp --waveglow ./checkpoints/waveglow_1076430_14000_amp --wn-channels 256 -bash test_infer.sh -bs 1 -il 128 --num-iters 1003 --tacotron2 ./checkpoints/tacotron2_1032590_6000_amp --waveglow ./checkpoints/waveglow_1076430_14000_amp --wn-channels 256 -bash test_infer.sh -bs 4 -il 128 --num-iters 1003 --tacotron2 ./checkpoints/tacotron2_1032590_6000_amp --waveglow ./checkpoints/waveglow_1076430_14000_amp --wn-channels 256 -export CUDA_VISIBLE_DEVICES= -export OMP_NUM_THREADS=6 -export KMP_BLOCKTIME=0 -export KMP_AFFINITY=granularity=fine,compact,1,0 -bash test_infer.sh -bs 1 -il 128 --cpu --num-iters 1003 --tacotron2 ./checkpoints/tacotron2_1032590_6000_amp --waveglow ./checkpoints/waveglow_1076430_14000_amp --wn-channels 256 -bash test_infer.sh -bs 4 -il 128 --cpu --num-iters 1003 --tacotron2 ./checkpoints/tacotron2_1032590_6000_amp --waveglow ./checkpoints/waveglow_1076430_14000_amp --wn-channels 256 diff --git a/demo/Tacotron2/scripts/download_checkpoints.sh b/demo/Tacotron2/scripts/download_checkpoints.sh deleted file mode 100755 index 0d23f2d3..00000000 --- a/demo/Tacotron2/scripts/download_checkpoints.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -# Prepare the download directory -mkdir -p checkpoints && cd checkpoints - -# Download the Tacotron2 and Waveglow checkpoints -if [ ! -f "checkpoints/tacotron2_pyt_ckpt_amp_v19.09.0/nvidia_tacotron2pyt_fp16_20190427" ]; then - echo "Downloading Tacotron2 checkpoint from NGC" - ngc registry model download-version nvidia/tacotron2_pyt_ckpt_amp:19.09.0 -fi; -if [ ! -f "checkpoints/waveglow_ckpt_amp_256_v19.10.0/nvidia_waveglow256pyt_fp16" ]; then - echo "Downloading Waveglow checkpoint from NGC" - ngc registry model download-version nvidia/waveglow_ckpt_amp_256:19.10.0 -fi; - -cd - diff --git a/demo/Tacotron2/scripts/inference_benchmark.sh b/demo/Tacotron2/scripts/inference_benchmark.sh deleted file mode 100755 index 86200557..00000000 --- a/demo/Tacotron2/scripts/inference_benchmark.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -echo "TensorRT BS=1, S=128" -bash test_infer.sh --test tensorrt/test_infer_trt.py -bs 1 -il 128 --fp16 --num-iters 103 --encoder ./output/encoder_fp16.engine --decoder ./output/decoder_with_outer_loop_fp16.engine --postnet ./output/postnet_fp16.engine --waveglow ./output/waveglow_fp16.engine --wn-channels 256 -echo "PyTorch (GPU) BS=1, S=128" -bash test_infer.sh -bs 1 -il 128 --fp16 --num-iters 103 --tacotron2 ./checkpoints/tacotron2_pyt_ckpt_amp_v19.09.0/nvidia_tacotron2pyt_fp16_20190427 --waveglow ./checkpoints/waveglow_ckpt_amp_256_v19.10.0/nvidia_waveglow256pyt_fp16 --wn-channels 256 diff --git a/demo/Tacotron2/scripts/install_prerequisites.sh b/demo/Tacotron2/scripts/install_prerequisites.sh deleted file mode 100755 index 5a16d392..00000000 --- a/demo/Tacotron2/scripts/install_prerequisites.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -pip3 install -r requirements.txt -echo "nvidia" | sudo -S apt-get install -y libsndfile1 - -pushd /tmp -git clone https://github.com/NVIDIA/apex -cd apex -pip3 install -v --disable-pip-version-check --no-build-isolation --no-cache-dir ./ -popd diff --git a/demo/Tacotron2/scripts/prepare_dataset.sh b/demo/Tacotron2/scripts/prepare_dataset.sh deleted file mode 100755 index d38be817..00000000 --- a/demo/Tacotron2/scripts/prepare_dataset.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -set -e - -DATADIR="LJSpeech-1.1" -BZ2ARCHIVE="${DATADIR}.tar.bz2" -ENDPOINT="http://data.keithito.com/data/speech/$BZ2ARCHIVE" - -if [ ! -d "$DATADIR" ]; then - echo "dataset is missing, unpacking ..." - if [ ! -f "$BZ2ARCHIVE" ]; then - echo "dataset archive is missing, downloading ..." - wget "$ENDPOINT" - fi - tar jxvf "$BZ2ARCHIVE" -fi diff --git a/demo/Tacotron2/scripts/prepare_mels.sh b/demo/Tacotron2/scripts/prepare_mels.sh deleted file mode 100644 index b3843a26..00000000 --- a/demo/Tacotron2/scripts/prepare_mels.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -set -e - -DATADIR="LJSpeech-1.1" -FILELISTSDIR="filelists" - -TESTLIST="$FILELISTSDIR/ljs_audio_text_test_filelist.txt" -TRAINLIST="$FILELISTSDIR/ljs_audio_text_train_filelist.txt" -VALLIST="$FILELISTSDIR/ljs_audio_text_val_filelist.txt" - -TESTLIST_MEL="$FILELISTSDIR/ljs_mel_text_test_filelist.txt" -TRAINLIST_MEL="$FILELISTSDIR/ljs_mel_text_train_filelist.txt" -VALLIST_MEL="$FILELISTSDIR/ljs_mel_text_val_filelist.txt" - -mkdir -p "$DATADIR/mels" -if [ $(ls $DATADIR/mels | wc -l) -ne 13100 ]; then - python3 preprocess_audio2mel.py --wav-files "$TRAINLIST" --mel-files "$TRAINLIST_MEL" - python3 preprocess_audio2mel.py --wav-files "$TESTLIST" --mel-files "$TESTLIST_MEL" - python3 preprocess_audio2mel.py --wav-files "$VALLIST" --mel-files "$VALLIST_MEL" -fi diff --git a/demo/Tacotron2/tacotron2/arg_parser.py b/demo/Tacotron2/tacotron2/arg_parser.py deleted file mode 100644 index 2a450ef6..00000000 --- a/demo/Tacotron2/tacotron2/arg_parser.py +++ /dev/null @@ -1,98 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 argparse - -from tacotron2.text import symbols - - -def parse_tacotron2_args(parent, add_help=False): - """ - Parse commandline arguments. - """ - parser = argparse.ArgumentParser(parents=[parent], add_help=add_help) - - # misc parameters - parser.add_argument('--mask-padding', default=False, type=bool, - help='Use mask padding') - parser.add_argument('--n-mel-channels', default=80, type=int, - help='Number of bins in mel-spectrograms') - - # symbols parameters - global symbols - len_symbols = len(symbols) - symbols = parser.add_argument_group('symbols parameters') - symbols.add_argument('--n-symbols', default=len_symbols, type=int, - help='Number of symbols in dictionary') - symbols.add_argument('--symbols-embedding-dim', default=512, type=int, - help='Input embedding dimension') - - # encoder parameters - encoder = parser.add_argument_group('encoder parameters') - encoder.add_argument('--encoder-kernel-size', default=5, type=int, - help='Encoder kernel size') - encoder.add_argument('--encoder-n-convolutions', default=3, type=int, - help='Number of encoder convolutions') - encoder.add_argument('--encoder-embedding-dim', default=512, type=int, - help='Encoder embedding dimension') - - # decoder parameters - decoder = parser.add_argument_group('decoder parameters') - decoder.add_argument('--n-frames-per-step', default=1, - type=int, - help='Number of frames processed per step') # currently only 1 is supported - decoder.add_argument('--decoder-rnn-dim', default=1024, type=int, - help='Number of units in decoder LSTM') - decoder.add_argument('--prenet-dim', default=256, type=int, - help='Number of ReLU units in prenet layers') - decoder.add_argument('--max-decoder-steps', default=2000, type=int, - help='Maximum number of output mel spectrograms') - decoder.add_argument('--gate-threshold', default=0.5, type=float, - help='Probability threshold for stop token') - decoder.add_argument('--p-attention-dropout', default=0.1, type=float, - help='Dropout probability for attention LSTM') - decoder.add_argument('--p-decoder-dropout', default=0.1, type=float, - help='Dropout probability for decoder LSTM') - decoder.add_argument('--decoder-no-early-stopping', action='store_true', - help='Stop decoding once all samples are finished') - - # attention parameters - attention = parser.add_argument_group('attention parameters') - attention.add_argument('--attention-rnn-dim', default=1024, type=int, - help='Number of units in attention LSTM') - attention.add_argument('--attention-dim', default=128, type=int, - help='Dimension of attention hidden representation') - - # location layer parameters - location = parser.add_argument_group('location parameters') - location.add_argument( - '--attention-location-n-filters', default=32, type=int, - help='Number of filters for location-sensitive attention') - location.add_argument( - '--attention-location-kernel-size', default=31, type=int, - help='Kernel size for location-sensitive attention') - - # Mel-post processing network parameters - postnet = parser.add_argument_group('postnet parameters') - postnet.add_argument('--postnet-embedding-dim', default=512, type=int, - help='Postnet embedding dimension') - postnet.add_argument('--postnet-kernel-size', default=5, type=int, - help='Postnet kernel size') - postnet.add_argument('--postnet-n-convolutions', default=5, type=int, - help='Number of postnet convolutions') - - return parser diff --git a/demo/Tacotron2/tacotron2/data_function.py b/demo/Tacotron2/tacotron2/data_function.py deleted file mode 100644 index 5d2c0064..00000000 --- a/demo/Tacotron2/tacotron2/data_function.py +++ /dev/null @@ -1,145 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 random -import numpy as np -import torch -import torch.utils.data - -import common.layers as layers -from common.utils import load_wav_to_torch, load_filepaths_and_text, to_gpu -from tacotron2.text import text_to_sequence - -class TextMelLoader(torch.utils.data.Dataset): - """ - 1) loads audio,text pairs - 2) normalizes text and converts them to sequences of one-hot vectors - 3) computes mel-spectrograms from audio files. - """ - def __init__(self, dataset_path, audiopaths_and_text, args): - self.audiopaths_and_text = load_filepaths_and_text(dataset_path, audiopaths_and_text) - self.text_cleaners = args.text_cleaners - self.max_wav_value = args.max_wav_value - self.sampling_rate = args.sampling_rate - self.load_mel_from_disk = args.load_mel_from_disk - self.stft = layers.TacotronSTFT( - args.filter_length, args.hop_length, args.win_length, - args.n_mel_channels, args.sampling_rate, args.mel_fmin, - args.mel_fmax) - random.seed(1234) - random.shuffle(self.audiopaths_and_text) - - def get_mel_text_pair(self, audiopath_and_text): - # separate filename and text - audiopath, text = audiopath_and_text[0], audiopath_and_text[1] - len_text = len(text) - text = self.get_text(text) - mel = self.get_mel(audiopath) - return (text, mel, len_text) - - def get_mel(self, filename): - if not self.load_mel_from_disk: - audio, sampling_rate = load_wav_to_torch(filename) - if sampling_rate != self.stft.sampling_rate: - raise ValueError("{} {} SR doesn't match target {} SR".format( - sampling_rate, self.stft.sampling_rate)) - audio_norm = audio / self.max_wav_value - audio_norm = audio_norm.unsqueeze(0) - audio_norm = torch.autograd.Variable(audio_norm, requires_grad=False) - melspec = self.stft.mel_spectrogram(audio_norm) - melspec = torch.squeeze(melspec, 0) - else: - melspec = torch.load(filename) - assert melspec.size(0) == self.stft.n_mel_channels, ( - 'Mel dimension mismatch: given {}, expected {}'.format( - melspec.size(0), self.stft.n_mel_channels)) - - return melspec - - def get_text(self, text): - text_norm = torch.IntTensor(text_to_sequence(text, self.text_cleaners)) - return text_norm - - def __getitem__(self, index): - return self.get_mel_text_pair(self.audiopaths_and_text[index]) - - def __len__(self): - return len(self.audiopaths_and_text) - - -class TextMelCollate(): - """ Zero-pads model inputs and targets based on number of frames per setep - """ - def __init__(self, n_frames_per_step): - self.n_frames_per_step = n_frames_per_step - - def __call__(self, batch): - """Collate's training batch from normalized text and mel-spectrogram - PARAMS - ------ - batch: [text_normalized, mel_normalized] - """ - # Right zero-pad all one-hot text sequences to max input length - input_lengths, ids_sorted_decreasing = torch.sort( - torch.LongTensor([len(x[0]) for x in batch]), - dim=0, descending=True) - max_input_len = input_lengths[0] - - text_padded = torch.LongTensor(len(batch), max_input_len) - text_padded.zero_() - for i in range(len(ids_sorted_decreasing)): - text = batch[ids_sorted_decreasing[i]][0] - text_padded[i, :text.size(0)] = text - - # Right zero-pad mel-spec - num_mels = batch[0][1].size(0) - max_target_len = max([x[1].size(1) for x in batch]) - if max_target_len % self.n_frames_per_step != 0: - max_target_len += self.n_frames_per_step - max_target_len % self.n_frames_per_step - assert max_target_len % self.n_frames_per_step == 0 - - # include mel padded and gate padded - mel_padded = torch.FloatTensor(len(batch), num_mels, max_target_len) - mel_padded.zero_() - gate_padded = torch.FloatTensor(len(batch), max_target_len) - gate_padded.zero_() - output_lengths = torch.LongTensor(len(batch)) - for i in range(len(ids_sorted_decreasing)): - mel = batch[ids_sorted_decreasing[i]][1] - mel_padded[i, :, :mel.size(1)] = mel - gate_padded[i, mel.size(1)-1:] = 1 - output_lengths[i] = mel.size(1) - - # count number of items - characters in text - len_x = [x[2] for x in batch] - len_x = torch.Tensor(len_x) - return text_padded, input_lengths, mel_padded, gate_padded, \ - output_lengths, len_x - -def batch_to_gpu(batch): - text_padded, input_lengths, mel_padded, gate_padded, \ - output_lengths, len_x = batch - text_padded = to_gpu(text_padded).long() - input_lengths = to_gpu(input_lengths).long() - max_len = torch.max(input_lengths.data).item() - mel_padded = to_gpu(mel_padded).float() - gate_padded = to_gpu(gate_padded).float() - output_lengths = to_gpu(output_lengths).long() - x = (text_padded, input_lengths, mel_padded, max_len, output_lengths) - y = (mel_padded, gate_padded) - len_x = torch.sum(output_lengths) - return (x, y, len_x) diff --git a/demo/Tacotron2/tacotron2/loss_function.py b/demo/Tacotron2/tacotron2/loss_function.py deleted file mode 100644 index 07b3610e..00000000 --- a/demo/Tacotron2/tacotron2/loss_function.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch import nn - - -class Tacotron2Loss(nn.Module): - def __init__(self): - super(Tacotron2Loss, self).__init__() - - def forward(self, model_output, targets): - mel_target, gate_target = targets[0], targets[1] - mel_target.requires_grad = False - gate_target.requires_grad = False - gate_target = gate_target.view(-1, 1) - - mel_out, mel_out_postnet, gate_out, _ = model_output - gate_out = gate_out.view(-1, 1) - mel_loss = nn.MSELoss()(mel_out, mel_target) + \ - nn.MSELoss()(mel_out_postnet, mel_target) - gate_loss = nn.BCEWithLogitsLoss()(gate_out, gate_target) - return mel_loss + gate_loss diff --git a/demo/Tacotron2/tacotron2/model.py b/demo/Tacotron2/tacotron2/model.py deleted file mode 100644 index c8ba9f96..00000000 --- a/demo/Tacotron2/tacotron2/model.py +++ /dev/null @@ -1,681 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 math import sqrt -import torch -from torch import nn -from torch.nn import functional as F -import sys -from os.path import abspath, dirname -# enabling modules discovery from global entrypoint -sys.path.append(abspath(dirname(__file__)+'/../')) -from common.layers import ConvNorm, LinearNorm -from common.utils import to_gpu, get_mask_from_lengths - - -class LocationLayer(nn.Module): - def __init__(self, attention_n_filters, attention_kernel_size, - attention_dim): - super(LocationLayer, self).__init__() - padding = int((attention_kernel_size - 1) / 2) - self.location_conv = ConvNorm(2, attention_n_filters, - kernel_size=attention_kernel_size, - padding=padding, bias=False, stride=1, - dilation=1) - self.location_dense = LinearNorm(attention_n_filters, attention_dim, - bias=False, w_init_gain='tanh') - - def forward(self, attention_weights_cat): - processed_attention = self.location_conv(attention_weights_cat) - processed_attention = processed_attention.transpose(1, 2) - processed_attention = self.location_dense(processed_attention) - return processed_attention - - -class Attention(nn.Module): - def __init__(self, attention_rnn_dim, embedding_dim, - attention_dim, attention_location_n_filters, - attention_location_kernel_size): - super(Attention, self).__init__() - self.query_layer = LinearNorm(attention_rnn_dim, attention_dim, - bias=False, w_init_gain='tanh') - self.memory_layer = LinearNorm(embedding_dim, attention_dim, bias=False, - w_init_gain='tanh') - self.v = LinearNorm(attention_dim, 1, bias=False) - self.location_layer = LocationLayer(attention_location_n_filters, - attention_location_kernel_size, - attention_dim) - self.score_mask_value = -float("inf") - - def get_alignment_energies(self, query, processed_memory, - attention_weights_cat): - """ - PARAMS - ------ - query: decoder output (batch, n_mel_channels * n_frames_per_step) - processed_memory: processed encoder outputs (B, T_in, attention_dim) - attention_weights_cat: cumulative and prev. att weights (B, 2, max_time) - - RETURNS - ------- - alignment (batch, max_time) - """ - - processed_query = self.query_layer(query.unsqueeze(1)) - processed_attention_weights = self.location_layer(attention_weights_cat) - energies = self.v(torch.tanh( - processed_query + processed_attention_weights + processed_memory)) - - energies = energies.squeeze(2) - return energies - - def forward(self, attention_hidden_state, memory, processed_memory, - attention_weights_cat, mask): - """ - PARAMS - ------ - attention_hidden_state: attention rnn last output - memory: encoder outputs - processed_memory: processed encoder outputs - attention_weights_cat: previous and cummulative attention weights - mask: binary mask for padded data - """ - alignment = self.get_alignment_energies( - attention_hidden_state, processed_memory, attention_weights_cat) - - alignment = alignment.masked_fill(mask, self.score_mask_value) - - attention_weights = F.softmax(alignment, dim=1) - attention_context = torch.bmm(attention_weights.unsqueeze(1), memory) - attention_context = attention_context.squeeze(1) - - return attention_context, attention_weights - - -class Prenet(nn.Module): - def __init__(self, in_dim, sizes): - super(Prenet, self).__init__() - in_sizes = [in_dim] + sizes[:-1] - self.layers = nn.ModuleList( - [LinearNorm(in_size, out_size, bias=False) - for (in_size, out_size) in zip(in_sizes, sizes)]) - - def forward(self, x): - for linear in self.layers: - x = F.dropout(F.relu(linear(x)), p=0.5, training=True) - return x - - -class Postnet(nn.Module): - """Postnet - - Five 1-d convolution with 512 channels and kernel size 5 - """ - - def __init__(self, n_mel_channels, postnet_embedding_dim, - postnet_kernel_size, postnet_n_convolutions): - super(Postnet, self).__init__() - self.convolutions = nn.ModuleList() - - self.convolutions.append( - nn.Sequential( - ConvNorm(n_mel_channels, postnet_embedding_dim, - kernel_size=postnet_kernel_size, stride=1, - padding=int((postnet_kernel_size - 1) / 2), - dilation=1, w_init_gain='tanh'), - nn.BatchNorm1d(postnet_embedding_dim)) - ) - - for i in range(1, postnet_n_convolutions - 1): - self.convolutions.append( - nn.Sequential( - ConvNorm(postnet_embedding_dim, - postnet_embedding_dim, - kernel_size=postnet_kernel_size, stride=1, - padding=int((postnet_kernel_size - 1) / 2), - dilation=1, w_init_gain='tanh'), - nn.BatchNorm1d(postnet_embedding_dim)) - ) - - self.convolutions.append( - nn.Sequential( - ConvNorm(postnet_embedding_dim, n_mel_channels, - kernel_size=postnet_kernel_size, stride=1, - padding=int((postnet_kernel_size - 1) / 2), - dilation=1, w_init_gain='linear'), - nn.BatchNorm1d(n_mel_channels)) - ) - self.n_convs = len(self.convolutions) - - def forward(self, x): - i = 0 - for conv in self.convolutions: - if i < self.n_convs - 1: - x = F.dropout(torch.tanh(conv(x)), 0.5, training=self.training) - else: - x = F.dropout(conv(x), 0.5, training=self.training) - i += 1 - - return x - - -class Encoder(nn.Module): - """Encoder module: - - Three 1-d convolution banks - - Bidirectional LSTM - """ - def __init__(self, encoder_n_convolutions, - encoder_embedding_dim, encoder_kernel_size): - super(Encoder, self).__init__() - - convolutions = [] - for _ in range(encoder_n_convolutions): - conv_layer = nn.Sequential( - ConvNorm(encoder_embedding_dim, - encoder_embedding_dim, - kernel_size=encoder_kernel_size, stride=1, - padding=int((encoder_kernel_size - 1) / 2), - dilation=1, w_init_gain='relu'), - nn.BatchNorm1d(encoder_embedding_dim)) - convolutions.append(conv_layer) - self.convolutions = nn.ModuleList(convolutions) - - self.lstm = nn.LSTM(encoder_embedding_dim, - int(encoder_embedding_dim / 2), 1, - batch_first=True, bidirectional=True) - - @torch.jit.ignore - def forward(self, x, input_lengths): - for conv in self.convolutions: - x = F.dropout(F.relu(conv(x)), 0.5, self.training) - - x = x.transpose(1, 2) - - # pytorch tensor are not reversible, hence the conversion - input_lengths = input_lengths.cpu().numpy() - x = nn.utils.rnn.pack_padded_sequence( - x, input_lengths, batch_first=True) - - self.lstm.flatten_parameters() - outputs, _ = self.lstm(x) - - outputs, _ = nn.utils.rnn.pad_packed_sequence( - outputs, batch_first=True) - - return outputs - - @torch.jit.export - def infer(self, x, input_lengths): - device = x.device - for conv in self.convolutions: - x = F.dropout(F.relu(conv(x.to(device))), 0.5, self.training) - - x = x.transpose(1, 2) - - input_lengths = input_lengths.cpu() - x = nn.utils.rnn.pack_padded_sequence( - x, input_lengths, batch_first=True) - - outputs, _ = self.lstm(x) - - outputs, _ = nn.utils.rnn.pad_packed_sequence( - outputs, batch_first=True) - - return outputs - - -class Decoder(nn.Module): - def __init__(self, n_mel_channels, n_frames_per_step, - encoder_embedding_dim, attention_dim, - attention_location_n_filters, - attention_location_kernel_size, - attention_rnn_dim, decoder_rnn_dim, - prenet_dim, max_decoder_steps, gate_threshold, - p_attention_dropout, p_decoder_dropout, - early_stopping): - super(Decoder, self).__init__() - self.n_mel_channels = n_mel_channels - self.n_frames_per_step = n_frames_per_step - self.encoder_embedding_dim = encoder_embedding_dim - self.attention_rnn_dim = attention_rnn_dim - self.decoder_rnn_dim = decoder_rnn_dim - self.prenet_dim = prenet_dim - self.max_decoder_steps = max_decoder_steps - self.gate_threshold = gate_threshold - self.p_attention_dropout = p_attention_dropout - self.p_decoder_dropout = p_decoder_dropout - self.early_stopping = early_stopping - - self.prenet = Prenet( - n_mel_channels * n_frames_per_step, - [prenet_dim, prenet_dim]) - - self.attention_rnn = nn.LSTMCell( - prenet_dim + encoder_embedding_dim, - attention_rnn_dim) - - self.attention_layer = Attention( - attention_rnn_dim, encoder_embedding_dim, - attention_dim, attention_location_n_filters, - attention_location_kernel_size) - - self.decoder_rnn = nn.LSTMCell( - attention_rnn_dim + encoder_embedding_dim, - decoder_rnn_dim, 1) - - self.linear_projection = LinearNorm( - decoder_rnn_dim + encoder_embedding_dim, - n_mel_channels * n_frames_per_step) - - self.gate_layer = LinearNorm( - decoder_rnn_dim + encoder_embedding_dim, 1, - bias=True, w_init_gain='sigmoid') - - def get_go_frame(self, memory): - """ Gets all zeros frames to use as first decoder input - PARAMS - ------ - memory: decoder outputs - - RETURNS - ------- - decoder_input: all zeros frames - """ - B = memory.size(0) - dtype = memory.dtype - device = memory.device - decoder_input = torch.zeros( - B, self.n_mel_channels*self.n_frames_per_step, - dtype=dtype, device=device) - return decoder_input - - def initialize_decoder_states(self, memory): - """ Initializes attention rnn states, decoder rnn states, attention - weights, attention cumulative weights, attention context, stores memory - and stores processed memory - PARAMS - ------ - memory: Encoder outputs - mask: Mask for padded data if training, expects None for inference - """ - B = memory.size(0) - MAX_TIME = memory.size(1) - dtype = memory.dtype - device = memory.device - - attention_hidden = torch.zeros( - B, self.attention_rnn_dim, dtype=dtype, device=device) - attention_cell = torch.zeros( - B, self.attention_rnn_dim, dtype=dtype, device=device) - - decoder_hidden = torch.zeros( - B, self.decoder_rnn_dim, dtype=dtype, device=device) - decoder_cell = torch.zeros( - B, self.decoder_rnn_dim, dtype=dtype, device=device) - - attention_weights = torch.zeros( - B, MAX_TIME, dtype=dtype, device=device) - attention_weights_cum = torch.zeros( - B, MAX_TIME, dtype=dtype, device=device) - attention_context = torch.zeros( - B, self.encoder_embedding_dim, dtype=dtype, device=device) - - processed_memory = self.attention_layer.memory_layer(memory) - - return (attention_hidden, attention_cell, decoder_hidden, - decoder_cell, attention_weights, attention_weights_cum, - attention_context, processed_memory) - - def parse_decoder_inputs(self, decoder_inputs): - """ Prepares decoder inputs, i.e. mel outputs - PARAMS - ------ - decoder_inputs: inputs used for teacher-forced training, i.e. mel-specs - - RETURNS - ------- - inputs: processed decoder inputs - - """ - # (B, n_mel_channels, T_out) -> (B, T_out, n_mel_channels) - decoder_inputs = decoder_inputs.transpose(1, 2) - decoder_inputs = decoder_inputs.view( - decoder_inputs.size(0), - int(decoder_inputs.size(1)/self.n_frames_per_step), -1) - # (B, T_out, n_mel_channels) -> (T_out, B, n_mel_channels) - decoder_inputs = decoder_inputs.transpose(0, 1) - return decoder_inputs - - def parse_decoder_outputs(self, mel_outputs, gate_outputs, alignments): - """ Prepares decoder outputs for output - PARAMS - ------ - mel_outputs: - gate_outputs: gate output energies - alignments: - - RETURNS - ------- - mel_outputs: - gate_outpust: gate output energies - alignments: - """ - # (T_out, B) -> (B, T_out) - alignments = alignments.transpose(0, 1).contiguous() - # (T_out, B) -> (B, T_out) - gate_outputs = gate_outputs.transpose(0, 1).contiguous() - # (T_out, B, n_mel_channels) -> (B, T_out, n_mel_channels) - mel_outputs = mel_outputs.transpose(0, 1).contiguous() - # decouple frames per step - shape = (mel_outputs.shape[0], -1, self.n_mel_channels) - mel_outputs = mel_outputs.view(*shape) - # (B, T_out, n_mel_channels) -> (B, n_mel_channels, T_out) - mel_outputs = mel_outputs.transpose(1, 2) - - return mel_outputs, gate_outputs, alignments - - def decode(self, decoder_input, attention_hidden, attention_cell, - decoder_hidden, decoder_cell, attention_weights, - attention_weights_cum, attention_context, memory, - processed_memory, mask): - """ Decoder step using stored states, attention and memory - PARAMS - ------ - decoder_input: previous mel output - - RETURNS - ------- - mel_output: - gate_output: gate output energies - attention_weights: - """ - cell_input = torch.cat((decoder_input, attention_context), -1) - - attention_hidden, attention_cell = self.attention_rnn( - cell_input, (attention_hidden, attention_cell)) - attention_hidden = F.dropout( - attention_hidden, self.p_attention_dropout, self.training) - - attention_weights_cat = torch.cat( - (attention_weights.unsqueeze(1), - attention_weights_cum.unsqueeze(1)), dim=1) - attention_context, attention_weights = self.attention_layer( - attention_hidden, memory, processed_memory, - attention_weights_cat, mask) - - attention_weights_cum += attention_weights - decoder_input = torch.cat( - (attention_hidden, attention_context), -1) - - decoder_hidden, decoder_cell = self.decoder_rnn( - decoder_input, (decoder_hidden, decoder_cell)) - decoder_hidden = F.dropout( - decoder_hidden, self.p_decoder_dropout, self.training) - - decoder_hidden_attention_context = torch.cat( - (decoder_hidden, attention_context), dim=1) - decoder_output = self.linear_projection( - decoder_hidden_attention_context) - - gate_prediction = self.gate_layer(decoder_hidden_attention_context) - - return (decoder_output, gate_prediction, attention_hidden, - attention_cell, decoder_hidden, decoder_cell, attention_weights, - attention_weights_cum, attention_context) - - @torch.jit.ignore - def forward(self, memory, decoder_inputs, memory_lengths): - """ Decoder forward pass for training - PARAMS - ------ - memory: Encoder outputs - decoder_inputs: Decoder inputs for teacher forcing. i.e. mel-specs - memory_lengths: Encoder output lengths for attention masking. - - RETURNS - ------- - mel_outputs: mel outputs from the decoder - gate_outputs: gate outputs from the decoder - alignments: sequence of attention weights from the decoder - """ - - decoder_input = self.get_go_frame(memory).unsqueeze(0) - decoder_inputs = self.parse_decoder_inputs(decoder_inputs) - decoder_inputs = torch.cat((decoder_input, decoder_inputs), dim=0) - decoder_inputs = self.prenet(decoder_inputs) - - mask = get_mask_from_lengths(memory_lengths) - (attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - processed_memory) = self.initialize_decoder_states(memory) - - mel_outputs, gate_outputs, alignments = [], [], [] - while len(mel_outputs) < decoder_inputs.size(0) - 1: - decoder_input = decoder_inputs[len(mel_outputs)] - (mel_output, - gate_output, - attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context) = self.decode(decoder_input, - attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - memory, - processed_memory, - mask) - - mel_outputs += [mel_output.squeeze(1)] - gate_outputs += [gate_output.squeeze()] - alignments += [attention_weights] - - mel_outputs, gate_outputs, alignments = self.parse_decoder_outputs( - torch.stack(mel_outputs), - torch.stack(gate_outputs), - torch.stack(alignments)) - - return mel_outputs, gate_outputs, alignments - - @torch.jit.export - def infer(self, memory, memory_lengths): - """ Decoder inference - PARAMS - ------ - memory: Encoder outputs - - RETURNS - ------- - mel_outputs: mel outputs from the decoder - gate_outputs: gate outputs from the decoder - alignments: sequence of attention weights from the decoder - """ - decoder_input = self.get_go_frame(memory) - - mask = get_mask_from_lengths(memory_lengths) - (attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - processed_memory) = self.initialize_decoder_states(memory) - - mel_lengths = torch.zeros([memory.size(0)], dtype=torch.int32, device=memory.device) - not_finished = torch.ones([memory.size(0)], dtype=torch.int32, device=memory.device) - - mel_outputs, gate_outputs, alignments = ( - torch.zeros(1), torch.zeros(1), torch.zeros(1)) - first_iter = True - while True: - decoder_input = self.prenet(decoder_input) - (mel_output, - gate_output, - attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context) = self.decode(decoder_input, - attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - memory, - processed_memory, - mask) - - if first_iter: - mel_outputs = mel_output.unsqueeze(0) - gate_outputs = gate_output - alignments = attention_weights - first_iter = False - else: - mel_outputs = torch.cat( - (mel_outputs, mel_output.unsqueeze(0)), dim=0) - gate_outputs = torch.cat((gate_outputs, gate_output), dim=0) - alignments = torch.cat((alignments, attention_weights), dim=0) - - dec = torch.le(torch.sigmoid(gate_output), - self.gate_threshold).to(torch.int32).squeeze(1) - - not_finished = not_finished*dec - mel_lengths += not_finished - - if self.early_stopping and torch.sum(not_finished) == 0: - break - if len(mel_outputs) == self.max_decoder_steps: - print("Warning! Reached max decoder steps") - break - - decoder_input = mel_output - - mel_outputs, gate_outputs, alignments = self.parse_decoder_outputs( - mel_outputs, gate_outputs, alignments) - - return mel_outputs, gate_outputs, alignments, mel_lengths - - -class Tacotron2(nn.Module): - def __init__(self, mask_padding, n_mel_channels, - n_symbols, symbols_embedding_dim, encoder_kernel_size, - encoder_n_convolutions, encoder_embedding_dim, - attention_rnn_dim, attention_dim, attention_location_n_filters, - attention_location_kernel_size, n_frames_per_step, - decoder_rnn_dim, prenet_dim, max_decoder_steps, gate_threshold, - p_attention_dropout, p_decoder_dropout, - postnet_embedding_dim, postnet_kernel_size, - postnet_n_convolutions, decoder_no_early_stopping): - super(Tacotron2, self).__init__() - self.mask_padding = mask_padding - self.n_mel_channels = n_mel_channels - self.n_frames_per_step = n_frames_per_step - self.embedding = nn.Embedding(n_symbols, symbols_embedding_dim) - std = sqrt(2.0 / (n_symbols + symbols_embedding_dim)) - val = sqrt(3.0) * std # uniform bounds for std - self.embedding.weight.data.uniform_(-val, val) - self.encoder = Encoder(encoder_n_convolutions, - encoder_embedding_dim, - encoder_kernel_size) - self.decoder = Decoder(n_mel_channels, n_frames_per_step, - encoder_embedding_dim, attention_dim, - attention_location_n_filters, - attention_location_kernel_size, - attention_rnn_dim, decoder_rnn_dim, - prenet_dim, max_decoder_steps, - gate_threshold, p_attention_dropout, - p_decoder_dropout, - not decoder_no_early_stopping) - self.postnet = Postnet(n_mel_channels, postnet_embedding_dim, - postnet_kernel_size, - postnet_n_convolutions) - - def parse_batch(self, batch): - text_padded, input_lengths, mel_padded, gate_padded, \ - output_lengths = batch - text_padded = to_gpu(text_padded).long() - input_lengths = to_gpu(input_lengths).long() - max_len = torch.max(input_lengths.data).item() - mel_padded = to_gpu(mel_padded).float() - gate_padded = to_gpu(gate_padded).float() - output_lengths = to_gpu(output_lengths).long() - - return ( - (text_padded, input_lengths, mel_padded, max_len, output_lengths), - (mel_padded, gate_padded)) - - def parse_output(self, outputs, output_lengths): - # type: (List[Tensor], Tensor) -> List[Tensor] - if self.mask_padding and output_lengths is not None: - mask = get_mask_from_lengths(output_lengths) - mask = mask.expand(self.n_mel_channels, mask.size(0), mask.size(1)) - mask = mask.permute(1, 0, 2) - - outputs[0].masked_fill_(mask, 0.0) - outputs[1].masked_fill_(mask, 0.0) - outputs[2].masked_fill_(mask[:, 0, :], 1e3) # gate energies - - return outputs - - def forward(self, inputs): - inputs, input_lengths, targets, max_len, output_lengths = inputs - input_lengths, output_lengths = input_lengths.data, output_lengths.data - - embedded_inputs = self.embedding(inputs).transpose(1, 2) - - encoder_outputs = self.encoder(embedded_inputs, input_lengths) - - mel_outputs, gate_outputs, alignments = self.decoder( - encoder_outputs, targets, memory_lengths=input_lengths) - - mel_outputs_postnet = self.postnet(mel_outputs) - mel_outputs_postnet = mel_outputs + mel_outputs_postnet - - return self.parse_output( - [mel_outputs, mel_outputs_postnet, gate_outputs, alignments], - output_lengths) - - - def infer(self, inputs, input_lengths): - - embedded_inputs = self.embedding(inputs).transpose(1, 2) - encoder_outputs = self.encoder.infer(embedded_inputs, input_lengths) - mel_outputs, gate_outputs, alignments, mel_lengths = self.decoder.infer( - encoder_outputs, input_lengths) - - mel_outputs_postnet = self.postnet(mel_outputs) - mel_outputs_postnet = mel_outputs + mel_outputs_postnet - - BS = mel_outputs_postnet.size(0) - alignments = alignments.unfold(1, BS, BS).transpose(0,2) - - return mel_outputs_postnet, mel_lengths, alignments diff --git a/demo/Tacotron2/tacotron2/text/LICENCE b/demo/Tacotron2/tacotron2/text/LICENCE deleted file mode 100644 index 8ac1abf2..00000000 --- a/demo/Tacotron2/tacotron2/text/LICENCE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2017 Keith Ito - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/demo/Tacotron2/tacotron2/text/__init__.py b/demo/Tacotron2/tacotron2/text/__init__.py deleted file mode 100644 index f81bab41..00000000 --- a/demo/Tacotron2/tacotron2/text/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -""" from https://github.com/keithito/tacotron """ -import re -from tacotron2.text import cleaners -from tacotron2.text.symbols import symbols - - -# Mappings from symbol to numeric ID and vice versa: -_symbol_to_id = {s: i for i, s in enumerate(symbols)} -_id_to_symbol = {i: s for i, s in enumerate(symbols)} - -# Regular expression matching text enclosed in curly braces: -_curly_re = re.compile(r'(.*?)\{(.+?)\}(.*)') - - -def text_to_sequence(text, cleaner_names): - '''Converts a string of text to a sequence of IDs corresponding to the symbols in the text. - - The text can optionally have ARPAbet sequences enclosed in curly braces embedded - in it. For example, "Turn left on {HH AW1 S S T AH0 N} Street." - - Args: - text: string to convert to a sequence - cleaner_names: names of the cleaner functions to run the text through - - Returns: - List of integers corresponding to the symbols in the text - ''' - sequence = [] - - # Check for curly braces and treat their contents as ARPAbet: - while len(text): - m = _curly_re.match(text) - if not m: - sequence += _symbols_to_sequence(_clean_text(text, cleaner_names)) - break - sequence += _symbols_to_sequence(_clean_text(m.group(1), cleaner_names)) - sequence += _arpabet_to_sequence(m.group(2)) - text = m.group(3) - - return sequence - - -def sequence_to_text(sequence): - '''Converts a sequence of IDs back to a string''' - result = '' - for symbol_id in sequence: - if symbol_id in _id_to_symbol: - s = _id_to_symbol[symbol_id] - # Enclose ARPAbet back in curly braces: - if len(s) > 1 and s[0] == '@': - s = '{%s}' % s[1:] - result += s - return result.replace('}{', ' ') - - -def _clean_text(text, cleaner_names): - for name in cleaner_names: - cleaner = getattr(cleaners, name) - if not cleaner: - raise Exception('Unknown cleaner: %s' % name) - text = cleaner(text) - return text - - -def _symbols_to_sequence(symbols): - return [_symbol_to_id[s] for s in symbols if _should_keep_symbol(s)] - - -def _arpabet_to_sequence(text): - return _symbols_to_sequence(['@' + s for s in text.split()]) - - -def _should_keep_symbol(s): - return s in _symbol_to_id and s is not '_' and s is not '~' diff --git a/demo/Tacotron2/tacotron2/text/cleaners.py b/demo/Tacotron2/tacotron2/text/cleaners.py deleted file mode 100644 index 4cbcb015..00000000 --- a/demo/Tacotron2/tacotron2/text/cleaners.py +++ /dev/null @@ -1,106 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 https://github.com/keithito/tacotron """ - -''' -Cleaners are transformations that run over the input text at both training and eval time. - -Cleaners can be selected by passing a comma-delimited list of cleaner names as the "cleaners" -hyperparameter. Some cleaners are English-specific. You'll typically want to use: - 1. "english_cleaners" for English text - 2. "transliteration_cleaners" for non-English text that can be transliterated to ASCII using - the Unidecode library (https://pypi.python.org/pypi/Unidecode) - 3. "basic_cleaners" if you do not want to transliterate (in this case, you should also update - the symbols in symbols.py to match your data). -''' - -import re -from unidecode import unidecode -from .numbers import normalize_numbers - - -# Regular expression matching whitespace: -_whitespace_re = re.compile(r'\s+') - -# List of (regular expression, replacement) pairs for abbreviations: -_abbreviations = [(re.compile('\\b%s\\.' % x[0], re.IGNORECASE), x[1]) for x in [ - ('mrs', 'misess'), - ('mr', 'mister'), - ('dr', 'doctor'), - ('st', 'saint'), - ('co', 'company'), - ('jr', 'junior'), - ('maj', 'major'), - ('gen', 'general'), - ('drs', 'doctors'), - ('rev', 'reverend'), - ('lt', 'lieutenant'), - ('hon', 'honorable'), - ('sgt', 'sergeant'), - ('capt', 'captain'), - ('esq', 'esquire'), - ('ltd', 'limited'), - ('col', 'colonel'), - ('ft', 'fort'), -]] - - -def expand_abbreviations(text): - for regex, replacement in _abbreviations: - text = re.sub(regex, replacement, text) - return text - - -def expand_numbers(text): - return normalize_numbers(text) - - -def lowercase(text): - return text.lower() - - -def collapse_whitespace(text): - return re.sub(_whitespace_re, ' ', text) - - -def convert_to_ascii(text): - return unidecode(text) - - -def basic_cleaners(text): - '''Basic pipeline that lowercases and collapses whitespace without transliteration.''' - text = lowercase(text) - text = collapse_whitespace(text) - return text - - -def transliteration_cleaners(text): - '''Pipeline for non-English text that transliterates to ASCII.''' - text = convert_to_ascii(text) - text = lowercase(text) - text = collapse_whitespace(text) - return text - - -def english_cleaners(text): - '''Pipeline for English text, including number and abbreviation expansion.''' - text = convert_to_ascii(text) - text = lowercase(text) - text = expand_numbers(text) - text = expand_abbreviations(text) - text = collapse_whitespace(text) - return text diff --git a/demo/Tacotron2/tacotron2/text/cmudict.py b/demo/Tacotron2/tacotron2/text/cmudict.py deleted file mode 100644 index b359b235..00000000 --- a/demo/Tacotron2/tacotron2/text/cmudict.py +++ /dev/null @@ -1,81 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 https://github.com/keithito/tacotron """ - -import re - - -valid_symbols = [ - 'AA', 'AA0', 'AA1', 'AA2', 'AE', 'AE0', 'AE1', 'AE2', 'AH', 'AH0', 'AH1', 'AH2', - 'AO', 'AO0', 'AO1', 'AO2', 'AW', 'AW0', 'AW1', 'AW2', 'AY', 'AY0', 'AY1', 'AY2', - 'B', 'CH', 'D', 'DH', 'EH', 'EH0', 'EH1', 'EH2', 'ER', 'ER0', 'ER1', 'ER2', 'EY', - 'EY0', 'EY1', 'EY2', 'F', 'G', 'HH', 'IH', 'IH0', 'IH1', 'IH2', 'IY', 'IY0', 'IY1', - 'IY2', 'JH', 'K', 'L', 'M', 'N', 'NG', 'OW', 'OW0', 'OW1', 'OW2', 'OY', 'OY0', - 'OY1', 'OY2', 'P', 'R', 'S', 'SH', 'T', 'TH', 'UH', 'UH0', 'UH1', 'UH2', 'UW', - 'UW0', 'UW1', 'UW2', 'V', 'W', 'Y', 'Z', 'ZH' -] - -_valid_symbol_set = set(valid_symbols) - - -class CMUDict: - '''Thin wrapper around CMUDict data. http://www.speech.cs.cmu.edu/cgi-bin/cmudict''' - def __init__(self, file_or_path, keep_ambiguous=True): - if isinstance(file_or_path, str): - with open(file_or_path, encoding='latin-1') as f: - entries = _parse_cmudict(f) - else: - entries = _parse_cmudict(file_or_path) - if not keep_ambiguous: - entries = {word: pron for word, pron in entries.items() if len(pron) == 1} - self._entries = entries - - - def __len__(self): - return len(self._entries) - - - def lookup(self, word): - '''Returns list of ARPAbet pronunciations of the given word.''' - return self._entries.get(word.upper()) - - - -_alt_re = re.compile(r'\([0-9]+\)') - - -def _parse_cmudict(file): - cmudict = {} - for line in file: - if len(line) and (line[0] >= 'A' and line[0] <= 'Z' or line[0] == "'"): - parts = line.split(' ') - word = re.sub(_alt_re, '', parts[0]) - pronunciation = _get_pronunciation(parts[1]) - if pronunciation: - if word in cmudict: - cmudict[word].append(pronunciation) - else: - cmudict[word] = [pronunciation] - return cmudict - - -def _get_pronunciation(s): - parts = s.strip().split(' ') - for part in parts: - if part not in _valid_symbol_set: - return None - return ' '.join(parts) diff --git a/demo/Tacotron2/tacotron2/text/numbers.py b/demo/Tacotron2/tacotron2/text/numbers.py deleted file mode 100644 index 43df588d..00000000 --- a/demo/Tacotron2/tacotron2/text/numbers.py +++ /dev/null @@ -1,87 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 https://github.com/keithito/tacotron """ - -import inflect -import re - - -_inflect = inflect.engine() -_comma_number_re = re.compile(r'([0-9][0-9\,]+[0-9])') -_decimal_number_re = re.compile(r'([0-9]+\.[0-9]+)') -_pounds_re = re.compile(r'£([0-9\,]*[0-9]+)') -_dollars_re = re.compile(r'\$([0-9\.\,]*[0-9]+)') -_ordinal_re = re.compile(r'[0-9]+(st|nd|rd|th)') -_number_re = re.compile(r'[0-9]+') - - -def _remove_commas(m): - return m.group(1).replace(',', '') - - -def _expand_decimal_point(m): - return m.group(1).replace('.', ' point ') - - -def _expand_dollars(m): - match = m.group(1) - parts = match.split('.') - if len(parts) > 2: - return match + ' dollars' # Unexpected format - dollars = int(parts[0]) if parts[0] else 0 - cents = int(parts[1]) if len(parts) > 1 and parts[1] else 0 - if dollars and cents: - dollar_unit = 'dollar' if dollars == 1 else 'dollars' - cent_unit = 'cent' if cents == 1 else 'cents' - return '%s %s, %s %s' % (dollars, dollar_unit, cents, cent_unit) - elif dollars: - dollar_unit = 'dollar' if dollars == 1 else 'dollars' - return '%s %s' % (dollars, dollar_unit) - elif cents: - cent_unit = 'cent' if cents == 1 else 'cents' - return '%s %s' % (cents, cent_unit) - else: - return 'zero dollars' - - -def _expand_ordinal(m): - return _inflect.number_to_words(m.group(0)) - - -def _expand_number(m): - num = int(m.group(0)) - if num > 1000 and num < 3000: - if num == 2000: - return 'two thousand' - elif num > 2000 and num < 2010: - return 'two thousand ' + _inflect.number_to_words(num % 100) - elif num % 100 == 0: - return _inflect.number_to_words(num // 100) + ' hundred' - else: - return _inflect.number_to_words(num, andword='', zero='oh', group=2).replace(', ', ' ') - else: - return _inflect.number_to_words(num, andword='') - - -def normalize_numbers(text): - text = re.sub(_comma_number_re, _remove_commas, text) - text = re.sub(_pounds_re, r'\1 pounds', text) - text = re.sub(_dollars_re, _expand_dollars, text) - text = re.sub(_decimal_number_re, _expand_decimal_point, text) - text = re.sub(_ordinal_re, _expand_ordinal, text) - text = re.sub(_number_re, _expand_number, text) - return text diff --git a/demo/Tacotron2/tacotron2/text/symbols.py b/demo/Tacotron2/tacotron2/text/symbols.py deleted file mode 100644 index 604626ec..00000000 --- a/demo/Tacotron2/tacotron2/text/symbols.py +++ /dev/null @@ -1,34 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 https://github.com/keithito/tacotron """ - -''' -Defines the set of symbols used in text input to the model. - -The default is a set of ASCII characters that works well for English or text that has been run through Unidecode. For other data, you can modify _characters. See TRAINING_DATA.md for details. ''' -from tacotron2.text import cmudict - -_pad = '_' -_punctuation = '!\'(),.:;? ' -_special = '-' -_letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' - -# Prepend "@" to ARPAbet symbols to ensure uniqueness (some are the same as uppercase letters): -_arpabet = ['@' + s for s in cmudict.valid_symbols] - -# Export all symbols: -symbols = [_pad] + list(_special) + list(_punctuation) + list(_letters) + _arpabet diff --git a/demo/Tacotron2/tensorrt/convert_onnx2trt.py b/demo/Tacotron2/tensorrt/convert_onnx2trt.py deleted file mode 100644 index dd24c801..00000000 --- a/demo/Tacotron2/tensorrt/convert_onnx2trt.py +++ /dev/null @@ -1,168 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 argparse -import sys -import tensorrt as trt -from os.path import join - -from trt_utils import build_engine, parse_dynamic_size - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('-o', '--output', required=True, - help='output folder to save audio (file per phrase)') - parser.add_argument('--encoder', type=str, default="", - help='full path to the Encoder ONNX') - parser.add_argument('--decoder', type=str, default="", - help='full path to the Decoder or DecoderIter ONNX.') - parser.add_argument('--postnet', type=str, default="", - help='full path to the Postnet ONNX') - parser.add_argument('--waveglow', type=str, default="", - help='full path to the WaveGlow ONNX') - parser.add_argument('--encoder_out', type=str, - help='Filename of the exported encoder engine') - parser.add_argument('--decoder_out', type=str, - help='Filename of the exported decoder engine') - parser.add_argument('--postnet_out', type=str, - help='Filename of the exported postnet engine') - parser.add_argument('--waveglow_out', type=str, - help='Filename of the exported waveglow engine') - parser.add_argument('--fp16', action='store_true', - help='inference with FP16') - parser.add_argument('-bs', '--batch-size', type=str, default="1", - help='One or three comma separated integers specifying the batch size. Specify "min,opt,max" for dynamic shape') - parser.add_argument('--mel-size', type=str, default="32,768,1664", - help='One or three comma separated integers specifying the mels size for waveglow.') - parser.add_argument('--z-size', type=str, default="1024,24576,53248", - help='One or three comma separated integers specifying the z size for waveglow.') - parser.add_argument('--loop', dest='loop', action='store_true', - help='Includes the outer decoder loop in the ONNX model. Enabled by default and only supported on TensorRT 8.0 or later.') - parser.add_argument('--no-loop', dest='loop', action='store_false', - help='Excludes outer decoder loop from decoder ONNX model. Default behavior and necessary for TensorRT 7.2 or earlier.') - parser.add_argument("-tcf", "--timing-cache-file", default=None, type=str, - help="Path to tensorrt build timeing cache file, only available for tensorrt 8.0 and later. The cache file is assumed to be used exclusively. It's the users' responsibility to create file lock to prevent accessing conflict.", - required=False) - parser.set_defaults(loop=int(trt.__version__[0]) >= 8) - return parser - - -def main(): - - parser = argparse.ArgumentParser( - description='Export from ONNX to TensorRT for Tacotron 2 and WaveGlow') - parser = parse_args(parser) - args = parser.parse_args() - - precision = "fp16" if args.fp16 else "fp32" - encoder_path = join(args.output, args.encoder_out if args.encoder_out else f"encoder_{precision}.engine") - decoder_path = join(args.output, args.decoder_out if args.decoder_out else f"decoder_with_outer_loop_{precision}.engine" if args.loop else f"decoder_iter_{precision}.engine") - postnet_path = join(args.output, args.postnet_out if args.postnet_out else f"postnet_{precision}.engine") - waveglow_path = join(args.output, args.waveglow_out if args.waveglow_out else f"waveglow_{precision}.engine") - - bs_min, bs_opt, bs_max = parse_dynamic_size(args.batch_size) - mel_min, mel_opt, mel_max = parse_dynamic_size(args.mel_size) - z_min, z_opt, z_max = parse_dynamic_size(args.z_size) - - # Encoder - shapes=[{"name": "sequences", "min": (bs_min,4), "opt": (bs_opt,128), "max": (bs_max,256)}, - {"name": "sequence_lengths", "min": (bs_min,), "opt": (bs_opt,), "max": (bs_max,)}] - if args.encoder != "": - print("Building Encoder ...") - encoder_engine = build_engine(args.encoder, shapes=shapes, fp16=args.fp16, timing_cache=args.timing_cache_file) - if encoder_engine is not None: - with open(encoder_path, 'wb') as f: - f.write(encoder_engine) - else: - print("Failed to build engine from", args.encoder) - sys.exit(1) - - if args.loop: - # Decoder - shapes=[{"name": "decoder_input_0", "min": (bs_min,80), "opt": (bs_opt,80), "max": (bs_max,80)}, - {"name": "attention_hidden_0", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "attention_cell_0", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "decoder_hidden_0", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "decoder_cell_0", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "attention_weights_0", "min": (bs_min,4), "opt": (bs_opt,128), "max": (bs_max,256)}, - {"name": "attention_weights_cum_0", "min": (bs_min,4), "opt": (bs_opt,128), "max": (bs_max,256)}, - {"name": "attention_context_0", "min": (bs_min,512), "opt": (bs_opt,512), "max": (bs_max,512)}, - {"name": "memory", "min": (bs_min,4,512), "opt": (bs_opt,128,512), "max": (bs_max,256,512)}, - {"name": "processed_memory", "min": (bs_min,4,128), "opt": (bs_opt,128,128), "max": (bs_max,256,128)}, - {"name": "mask", "min": (bs_min,4), "opt": (bs_opt,128), "max": (bs_max,256)}] - if args.decoder != "": - print("Building Decoder with loop...") - decoder_engine = build_engine(args.decoder, shapes=shapes, fp16=args.fp16, timing_cache=args.timing_cache_file) - if decoder_engine is not None: - with open(decoder_path, 'wb') as f: - f.write(decoder_engine) - else: - print("Failed to build engine from", args.decoder) - sys.exit(1) - else: - # DecoderIter - shapes=[{"name": "decoder_input", "min": (bs_min,80), "opt": (bs_opt,80), "max": (bs_max,80)}, - {"name": "attention_hidden", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "attention_cell", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "decoder_hidden", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "decoder_cell", "min": (bs_min,1024), "opt": (bs_opt,1024), "max": (bs_max,1024)}, - {"name": "attention_weights", "min": (bs_min,4), "opt": (bs_opt,128), "max": (bs_max,256)}, - {"name": "attention_weights_cum", "min": (bs_min,4), "opt": (bs_opt,128), "max": (bs_max,256)}, - {"name": "attention_context", "min": (bs_min,512), "opt": (bs_opt,512), "max": (bs_max,512)}, - {"name": "memory", "min": (bs_min,4,512), "opt": (bs_opt,128,512), "max": (bs_max,256,512)}, - {"name": "processed_memory", "min": (bs_min,4,128), "opt": (bs_opt,128,128), "max": (bs_max,256,128)}, - {"name": "mask", "min": (bs_min,4), "opt": (bs_opt,128), "max": (bs_max,256)}] - if args.decoder != "": - print("Building Decoder ...") - decoder_iter_engine = build_engine(args.decoder, shapes=shapes, fp16=args.fp16, timing_cache=args.timing_cache_file) - if decoder_iter_engine is not None: - with open(decoder_path, 'wb') as f: - f.write(decoder_iter_engine) - else: - print("Failed to build engine from", args.decoder) - sys.exit(1) - - # Postnet - shapes=[{"name": "mel_outputs", "min": (bs_min,80,32), "opt": (bs_opt,80,768), "max": (bs_max,80,1664)}] - if args.postnet != "": - print("Building Postnet ...") - postnet_engine = build_engine(args.postnet, shapes=shapes, fp16=args.fp16, timing_cache=args.timing_cache_file) - if postnet_engine is not None: - with open(postnet_path, 'wb') as f: - f.write(postnet_engine) - else: - print("Failed to build engine from", args.postnet) - sys.exit(1) - - # WaveGlow - shapes=[{"name": "mel", "min": (bs_min,80,mel_min,1), "opt": (bs_opt,80,mel_opt,1), "max": (bs_max,80,mel_max,1)}, - {"name": "z", "min": (bs_min,8,z_min,1), "opt": (bs_opt,8,z_opt,1), "max": (bs_max,8,z_max,1)}] - if args.waveglow != "": - print("Building WaveGlow ...") - waveglow_engine = build_engine(args.waveglow, shapes=shapes, fp16=args.fp16, timing_cache=args.timing_cache_file) - if waveglow_engine is not None: - with open(waveglow_path, 'wb') as f: - f.write(waveglow_engine) - else: - print("Failed to build engine from", args.waveglow) - sys.exit(1) - - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/tensorrt/convert_tacotron22onnx.py b/demo/Tacotron2/tensorrt/convert_tacotron22onnx.py deleted file mode 100644 index 361a2221..00000000 --- a/demo/Tacotron2/tensorrt/convert_tacotron22onnx.py +++ /dev/null @@ -1,418 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 -import torch -from torch import nn -from torch.nn import functional as F -import argparse - -import sys -import os -from pathlib import Path -sys.path.append(str(Path(__file__).parents[1])) - -import models -from inference import checkpoint_from_distributed, unwrap_distributed, load_and_setup_model, prepare_input_sequence -from common.utils import to_gpu, get_mask_from_lengths - -torch.backends.cudnn.enabled = True -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('--tacotron2', type=str, required=True, - help='Full path to the Tacotron2 model checkpoint file') - parser.add_argument('-o', '--output', type=str, required=True, - help='Directory for the exported Tacotron2 ONNX models') - parser.add_argument('-e', '--encoder', type=str, required=False, default="encoder.onnx", - help='Filename for exported encoder ONNX model') - parser.add_argument('-d', '--decoder', type=str, required=False, default="decoder_iter.onnx", - help='Filename for exported decoder ONNX model') - parser.add_argument('-p', '--postnet', type=str, required=False, default="postnet.onnx", - help='Filename for exported postnet ONNX model') - parser.add_argument('--fp16', action='store_true', - help='Export with half precision to ONNX') - parser.add_argument('--loop', dest='loop', action='store_true', - help='Includes the outer decoder loop in the ONNX model. Enabled by default and only supported on TensorRT 8.0 or later.') - parser.add_argument('--no-loop', dest='loop', action='store_false', - help='Excludes outer decoder loop from decoder ONNX model. Default behavior and necessary for TensorRT 7.2 or earlier.') - parser.set_defaults(loop=int(tensorrt.__version__[0]) >= 8) - - return parser - - -def encoder_infer(self, x, input_lengths): - device = x.device - for conv in self.convolutions: - x = F.dropout(F.relu(conv(x.to(device))), 0.5, False) - - x = x.transpose(1, 2) - - x = nn.utils.rnn.pack_padded_sequence( - x, input_lengths, batch_first=True) - - outputs, _ = self.lstm(x) - - outputs, _ = nn.utils.rnn.pad_packed_sequence( - outputs, batch_first=True) - - lens = input_lengths*2 - - return outputs, lens - - -class Encoder(torch.nn.Module): - def __init__(self, tacotron2): - super(Encoder, self).__init__() - self.tacotron2 = tacotron2 - self.tacotron2.encoder.lstm.flatten_parameters() - self.infer = encoder_infer - - def forward(self, sequence, sequence_lengths): - embedded_inputs = self.tacotron2.embedding(sequence).transpose(1, 2) - memory, lens = self.infer(self.tacotron2.encoder, embedded_inputs, sequence_lengths) - processed_memory = self.tacotron2.decoder.attention_layer.memory_layer(memory) - return memory, processed_memory, lens - -class Postnet(torch.nn.Module): - def __init__(self, tacotron2): - super(Postnet, self).__init__() - self.tacotron2 = tacotron2 - - def forward(self, mel_outputs): - mel_outputs_postnet = self.tacotron2.postnet(mel_outputs) - return mel_outputs + mel_outputs_postnet - -def lstmcell2lstm_params(lstm_mod, lstmcell_mod): - lstm_mod.weight_ih_l0 = torch.nn.Parameter(lstmcell_mod.weight_ih) - lstm_mod.weight_hh_l0 = torch.nn.Parameter(lstmcell_mod.weight_hh) - lstm_mod.bias_ih_l0 = torch.nn.Parameter(lstmcell_mod.bias_ih) - lstm_mod.bias_hh_l0 = torch.nn.Parameter(lstmcell_mod.bias_hh) - - -def prenet_infer(self, x): - x1 = x[:] - for linear in self.layers: - x1 = F.relu(linear(x1)) - x0 = x1[0].unsqueeze(0) - mask = torch.le(torch.rand(256, device='cuda').to(x.dtype), 0.5).to(x.dtype) - mask = mask.expand(x1.size(0), x1.size(1)) - x1 = x1*mask*2.0 - - return x1 - -class DecoderIter(torch.nn.Module): - def __init__(self, tacotron2): - super(DecoderIter, self).__init__() - - self.tacotron2 = tacotron2 - dec = tacotron2.decoder - - self.p_attention_dropout = dec.p_attention_dropout - self.p_decoder_dropout = dec.p_decoder_dropout - self.prenet = dec.prenet - - self.prenet.infer = prenet_infer - - self.attention_rnn = nn.LSTM(dec.prenet_dim + dec.encoder_embedding_dim, - dec.attention_rnn_dim, 1) - lstmcell2lstm_params(self.attention_rnn, dec.attention_rnn) - self.attention_rnn.flatten_parameters() - - self.attention_layer = dec.attention_layer - - self.decoder_rnn = nn.LSTM(dec.attention_rnn_dim + dec.encoder_embedding_dim, - dec.decoder_rnn_dim, 1) - lstmcell2lstm_params(self.decoder_rnn, dec.decoder_rnn) - self.decoder_rnn.flatten_parameters() - - self.linear_projection = dec.linear_projection - self.gate_layer = dec.gate_layer - - - def decode(self, decoder_input, in_attention_hidden, in_attention_cell, - in_decoder_hidden, in_decoder_cell, in_attention_weights, - in_attention_weights_cum, in_attention_context, memory, - processed_memory, mask): - - cell_input = torch.cat((decoder_input, in_attention_context), -1) - - _, (out_attention_hidden, out_attention_cell) = self.attention_rnn( - cell_input.unsqueeze(0), (in_attention_hidden.unsqueeze(0), - in_attention_cell.unsqueeze(0))) - out_attention_hidden = out_attention_hidden.squeeze(0) - out_attention_cell = out_attention_cell.squeeze(0) - - out_attention_hidden = F.dropout( - out_attention_hidden, self.p_attention_dropout, False) - - attention_weights_cat = torch.cat( - (in_attention_weights.unsqueeze(1), - in_attention_weights_cum.unsqueeze(1)), dim=1) - out_attention_context, out_attention_weights = self.attention_layer( - out_attention_hidden, memory, processed_memory, - attention_weights_cat, mask) - - out_attention_weights_cum = in_attention_weights_cum + out_attention_weights - decoder_input_tmp = torch.cat( - (out_attention_hidden, out_attention_context), -1) - - _, (out_decoder_hidden, out_decoder_cell) = self.decoder_rnn( - decoder_input_tmp.unsqueeze(0), (in_decoder_hidden.unsqueeze(0), - in_decoder_cell.unsqueeze(0))) - out_decoder_hidden = out_decoder_hidden.squeeze(0) - out_decoder_cell = out_decoder_cell.squeeze(0) - - out_decoder_hidden = F.dropout( - out_decoder_hidden, self.p_decoder_dropout, False) - - decoder_hidden_attention_context = torch.cat( - (out_decoder_hidden, out_attention_context), 1) - - decoder_output = self.linear_projection( - decoder_hidden_attention_context) - - gate_prediction = self.gate_layer(decoder_hidden_attention_context) - - return (decoder_output, gate_prediction, out_attention_hidden, - out_attention_cell, out_decoder_hidden, out_decoder_cell, - out_attention_weights, out_attention_weights_cum, out_attention_context) - - # @torch.jit.script - def forward(self, - decoder_input, - attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - memory, - processed_memory, - mask): - decoder_input1 = self.prenet.infer(self.prenet, decoder_input) - outputs = self.decode(decoder_input1, - attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - memory, - processed_memory, - mask) - return outputs - - -def test_inference(encoder, decoder_iter, postnet): - - encoder.eval() - decoder_iter.eval() - postnet.eval() - - sys.path.append('./tensorrt') - from inference_trt import init_decoder_inputs - - texts = ["Hello World, good day."] - sequences, sequence_lengths = prepare_input_sequence(texts) - - measurements = {} - - print("Running Tacotron2 Encoder") - with torch.no_grad(): - memory, processed_memory, lens = encoder(sequences, sequence_lengths) - - print("Running Tacotron2 Decoder") - device = memory.device - dtype = memory.dtype - mel_lengths = torch.zeros([memory.size(0)], dtype=torch.int32, device = device) - not_finished = torch.ones([memory.size(0)], dtype=torch.int32, device = device) - mel_outputs, gate_outputs, alignments = (torch.zeros(1), torch.zeros(1), torch.zeros(1)) - gate_threshold = 0.6 - max_decoder_steps = 1000 - first_iter = True - - (decoder_input, attention_hidden, attention_cell, decoder_hidden, - decoder_cell, attention_weights, attention_weights_cum, - attention_context, memory, processed_memory, - mask) = init_decoder_inputs(memory, processed_memory, sequence_lengths) - - while True: - with torch.no_grad(): - (mel_output, gate_output, - attention_hidden, attention_cell, - decoder_hidden, decoder_cell, - attention_weights, attention_weights_cum, - attention_context) = decoder_iter(decoder_input, attention_hidden, attention_cell, decoder_hidden, - decoder_cell, attention_weights, attention_weights_cum, - attention_context, memory, processed_memory, mask) - - if first_iter: - mel_outputs = torch.unsqueeze(mel_output, 2) - gate_outputs = torch.unsqueeze(gate_output, 2) - alignments = torch.unsqueeze(attention_weights, 2) - first_iter = False - else: - mel_outputs = torch.cat((mel_outputs, torch.unsqueeze(mel_output, 2)), 2) - gate_outputs = torch.cat((gate_outputs, torch.unsqueeze(gate_output, 2)), 2) - alignments = torch.cat((alignments, torch.unsqueeze(attention_weights, 2)), 2) - - dec = torch.le(torch.sigmoid(gate_output), gate_threshold).to(torch.int32).squeeze(1) - not_finished = not_finished*dec - mel_lengths += not_finished - - if torch.sum(not_finished) == 0: - print("Stopping after ",mel_outputs.size(2)," decoder steps") - break - if mel_outputs.size(2) == max_decoder_steps: - print("Warning! Reached max decoder steps") - break - - decoder_input = mel_output - - - print("Running Tacotron2 PostNet") - with torch.no_grad(): - mel_outputs_postnet = postnet(mel_outputs) - - return mel_outputs_postnet - -def main(): - - parser = argparse.ArgumentParser( - description='PyTorch Tacotron 2 export to TRT') - parser = parse_args(parser) - args, _ = parser.parse_known_args() - - args.encoder = os.path.join(args.output, args.encoder) - args.decoder = os.path.join(args.output, args.decoder) - args.postnet = os.path.join(args.output, args.postnet) - - tacotron2 = load_and_setup_model('Tacotron2', parser, args.tacotron2, - fp16_run=args.fp16, cpu_run=False) - - opset_version = 10 - - sequences = torch.randint(low=0, high=148, size=(1,50), - dtype=torch.long).cuda() - sequence_lengths = torch.IntTensor([sequences.size(1)]) - dummy_input = (sequences, sequence_lengths) - - encoder = Encoder(tacotron2) - encoder.eval() - with torch.no_grad(): - encoder(*dummy_input) - - torch.onnx.export(encoder, dummy_input, args.encoder, - opset_version=opset_version, - do_constant_folding=True, - input_names=["sequences", "sequence_lengths"], - output_names=["memory", "processed_memory", "lens"], - dynamic_axes={"sequences": {0: "batch_size", 1: "text_seq"}, - "sequence_lengths": {0: "batch_size"}, - "memory": {0: "batch_size", 1: "mem_seq"}, - "processed_memory": {0: "batch_size", 1: "mem_seq"}, - "lens": {0: "batch_size"} - }) - - decoder_iter = DecoderIter(tacotron2) - memory = torch.randn((1,sequence_lengths[0],512)).cuda() #encoder_outputs - if args.fp16: - memory = memory.half() - memory_lengths = sequence_lengths.cuda() - # initialize decoder states for dummy_input - decoder_input = tacotron2.decoder.get_go_frame(memory) - mask = get_mask_from_lengths(memory_lengths) - (attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - processed_memory) = tacotron2.decoder.initialize_decoder_states(memory) - dummy_input = (decoder_input, - attention_hidden, - attention_cell, - decoder_hidden, - decoder_cell, - attention_weights, - attention_weights_cum, - attention_context, - memory, - processed_memory, - mask) - - decoder_iter = DecoderIter(tacotron2) - decoder_iter.eval() - with torch.no_grad(): - decoder_iter(*dummy_input) - - torch.onnx.export(decoder_iter, dummy_input, args.decoder, - opset_version=opset_version, - do_constant_folding=True, - input_names=["decoder_input", - "attention_hidden", - "attention_cell", - "decoder_hidden", - "decoder_cell", - "attention_weights", - "attention_weights_cum", - "attention_context", - "memory", - "processed_memory", - "mask"], - output_names=["decoder_output", - "gate_prediction", - "out_attention_hidden", - "out_attention_cell", - "out_decoder_hidden", - "out_decoder_cell", - "out_attention_weights", - "out_attention_weights_cum", - "out_attention_context"], - dynamic_axes={"attention_weights" : {0: "batch_size", 1: "seq_len"}, - "attention_weights_cum" : {0: "batch_size", 1: "seq_len"}, - "memory" : {0: "batch_size", 1: "seq_len"}, - "processed_memory" : {0: "batch_size", 1: "seq_len"}, - "mask" : {0: "batch_size", 1: "seq_len"}, - "out_attention_weights" : {0: "batch_size", 1: "seq_len"}, - "out_attention_weights_cum" : {0: "batch_size", 1: "seq_len"} - }) - - if args.loop: - from generate_decoder import insert_decoder_loop - decoder_dir = os.path.dirname(os.path.abspath(args.decoder)) - insert_decoder_loop(args.decoder, decoder_dir, os.path.basename(args.decoder).replace("_iter", ""), args.fp16) - - postnet = Postnet(tacotron2) - dummy_input = torch.randn((1,80,620)).cuda() - if args.fp16: - dummy_input = dummy_input.half() - torch.onnx.export(postnet, dummy_input, args.postnet, - opset_version=opset_version, - do_constant_folding=True, - input_names=["mel_outputs"], - output_names=["mel_outputs_postnet"], - dynamic_axes={"mel_outputs": {0: "batch_size", 2: "mel_seq"}, - "mel_outputs_postnet": {0: "batch_size", 2: "mel_seq"}}) - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/tensorrt/convert_waveglow2onnx.py b/demo/Tacotron2/tensorrt/convert_waveglow2onnx.py deleted file mode 100644 index 4b9aecbc..00000000 --- a/demo/Tacotron2/tensorrt/convert_waveglow2onnx.py +++ /dev/null @@ -1,167 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch -import argparse -import os -import sys -from pathlib import Path -sys.path.append(str(Path(__file__).parents[1])) - -from common.utils import ParseFromConfigFile -from inference import load_and_setup_model - -def convert_convinv_1d_to_2d(convinv): - """ - Takes an invertible 1x1 1-d convolution and returns a 2-d convolution that does - the inverse - """ - conv2d = torch.nn.Conv2d(convinv.W_inverse.size(1), - convinv.W_inverse.size(0), - 1, bias=False) - conv2d.weight.data[:,:,:,0] = convinv.W_inverse.data - return conv2d - - -def convert_conv_1d_to_2d(conv1d): - conv2d = torch.nn.Conv2d(conv1d.weight.size(1), - conv1d.weight.size(0), - (conv1d.weight.size(2), 1), - stride=(conv1d.stride[0], 1), - dilation=(conv1d.dilation[0], 1), - padding=(conv1d.padding[0], 0)) - conv2d.weight.data[:,:,:,0] = conv1d.weight.data - conv2d.bias.data = conv1d.bias.data - return conv2d - - -def convert_WN_1d_to_2d_(WN): - """ - Modifies the WaveNet like affine coupling layer in-place to use 2-d convolutions - """ - WN.start = convert_conv_1d_to_2d(WN.start) - WN.end = convert_conv_1d_to_2d(WN.end) - - for i in range(len(WN.in_layers)): - WN.in_layers[i] = convert_conv_1d_to_2d(WN.in_layers[i]) - - for i in range(len(WN.res_skip_layers)): - WN.res_skip_layers[i] = convert_conv_1d_to_2d(WN.res_skip_layers[i]) - - for i in range(len(WN.res_skip_layers)): - WN.cond_layers[i] = convert_conv_1d_to_2d(WN.cond_layers[i]) - - -def convert_1d_to_2d_(glow): - """ - Caffe2 and TensorRT don't seem to support 1-d convolutions or properly - convert ONNX exports with 1d convolutions to 2d convolutions yet, so we - do the conversion to 2-d convolutions before ONNX export - """ - # Convert upsample to 2d - upsample = torch.nn.ConvTranspose2d(glow.upsample.weight.size(0), - glow.upsample.weight.size(1), - (glow.upsample.weight.size(2), 1), - stride=(glow.upsample.stride[0], 1)) - upsample.weight.data[:,:,:,0] = glow.upsample.weight.data - upsample.bias.data = glow.upsample.bias.data - glow.upsample = upsample.cuda() - - # Convert WN to 2d - for WN in glow.WN: - convert_WN_1d_to_2d_(WN) - - # Convert invertible conv to 2d - for i in range(len(glow.convinv)): - glow.convinv[i] = convert_convinv_1d_to_2d(glow.convinv[i]) - - glow.cuda() - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('--waveglow', type=str, required=True, - help='full path to the WaveGlow model checkpoint file') - parser.add_argument('-o', '--output', type=str, required=True, - help='Directory or file name for the exported WaveGlow ONNX model') - parser.add_argument('--fp16', action='store_true', - help='inference with AMP') - parser.add_argument('-s', '--sigma-infer', default=0.6, type=float) - - parser.add_argument('--config-file', action=ParseFromConfigFile, - type=str, help='Path to configuration file') - - return parser - - -def export_onnx(parser, args): - - waveglow = load_and_setup_model('WaveGlow', parser, args.waveglow, - fp16_run=args.fp16, cpu_run=False, - forward_is_infer=False) - - # 80 mel channels, 620 mel spectrograms ~ 7 seconds of speech - mel = torch.randn(1, 80, 620).cuda() - stride = 256 # value from waveglow upsample - n_group = 8 - z_size2 = (mel.size(2)*stride)//n_group - z = torch.randn(1, n_group, z_size2, 1).cuda() - - if args.fp16: - mel = mel.half() - z = z.half() - with torch.no_grad(): - # run inference to force calculation of inverses - waveglow.infer(mel, sigma=args.sigma_infer) - - convert_1d_to_2d_(waveglow) - mel = mel.unsqueeze(3) - - # export to ONNX - if args.fp16: - waveglow = waveglow.half() - - waveglow.forward = waveglow.infer_onnx - - opset_version = 11 - - if os.path.isdir(args.output): - output_path = os.path.join(args.output, "waveglow.onnx") - else: - output_path = args.output - - torch.onnx.export(waveglow, (mel, z), output_path, - opset_version=opset_version, - do_constant_folding=True, - input_names=["mel", "z"], - output_names=["audio"], - dynamic_axes={"mel": {0: "batch_size", 2: "mel_seq"}, - "z": {0: "batch_size", 2: "z_seq"}, - "audio": {0: "batch_size", 1: "audio_seq"}}) - - -def main(): - parser = argparse.ArgumentParser( - description='PyTorch Tacotron 2 Inference') - parser = parse_args(parser) - args, _ = parser.parse_known_args() - - export_onnx(parser, args) - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/tensorrt/generate_decoder.py b/demo/Tacotron2/tensorrt/generate_decoder.py deleted file mode 100644 index 62f8b04e..00000000 --- a/demo/Tacotron2/tensorrt/generate_decoder.py +++ /dev/null @@ -1,212 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 onnx -import sys -import os -import numpy as np -import argparse - -def insert_decoder_loop(decoder_iter_onnx_path, output_dir, decoder_out_name, fp16): - float_prec = np.float16 if fp16 else np.float32 - - # Modify loop body so that it has 2+N inputs: (iteration_num, condition, loop carried dependencies...) - # and 1+N+K outputs: (condition, loop carried dependencies..., scan_outputs...) - - # In this case, the loop carried dependencies include the following IN ORDER - # - decoder_output/decoder_input - # - attention_hidden - # - attention_cell - # - decoder_hidden - # - decoder_cell - # - attention_weights - # - attention_weights_cum - # - attention_context - # - not_finished (bool tensor, initialized to all True) - # - mel_lengths - - # The following are NOT loop carried dependencies (they remain constant through the loop), and must be moved to be inputs outside of the loop body - # - memory - # - processed_memory - # - mask - - # The scan outputs are - # - mel_outputs (which scans across decoder_output) - # - gate_outputs (scans across gate_prediction) - # - alignments (scans across attention_weights) - - - loop_body = gs.import_onnx(onnx.load(decoder_iter_onnx_path)) - loop_tensors = loop_body.tensors() - - iteration_num = gs.Variable("iteration_num", dtype=np.int64, shape=()) - cond_in = gs.Variable("cond_in", dtype=bool, shape=()) - cond_out = gs.Variable("cond_out", dtype=bool, shape=()) - not_finished_in = gs.Variable("not_finished_in", shape=('batch_size', 1), dtype=bool) - not_finished_out = gs.Variable("not_finished_out", shape=('batch_size', 1), dtype=bool) - mel_lengths_in = gs.Variable("mel_lengths_in", shape=('batch_size', 1), dtype=np.int32) - mel_lengths_out = gs.Variable("mel_lengths_out", shape=('batch_size', 1), dtype=np.int32) - - - # Set loop body inputs in the correct order - loop_body.inputs = [iteration_num, cond_in, loop_tensors["decoder_input"], loop_tensors["attention_hidden"], loop_tensors["attention_cell"], loop_tensors["decoder_hidden"], loop_tensors["decoder_cell"], loop_tensors["attention_weights"], loop_tensors["attention_weights_cum"], loop_tensors["attention_context"], not_finished_in, mel_lengths_in] - - # Set loop body outputs in the correct order - loop_body.outputs = [cond_out, loop_tensors["decoder_output"], loop_tensors["out_attention_hidden"], loop_tensors["out_attention_cell"], loop_tensors["out_decoder_hidden"], loop_tensors["out_decoder_cell"], loop_tensors["out_attention_weights"], loop_tensors["out_attention_weights_cum"], loop_tensors["out_attention_context"], not_finished_out, mel_lengths_out, loop_tensors["decoder_output"], loop_tensors["gate_prediction"], loop_tensors["out_attention_weights"]] - - # The loop stop condition is given by the following lines in PyTorch - # dec = torch.le(torch.sigmoid(decoder_outputs[8]), gate_threshold).to(torch.int32).squeeze(1) - # not_finished = not_finished*dec - # if torch.sum(not_finished) == 0: - # break - - # To compute cond_out, we can essentially follow the same steps. Using Less instead of Greater+Not for now - - gate_threshold = gs.Constant("gate_threshold", np.array([0.5], dtype=float_prec)) - gate_sigmoid = gs.Variable("gate_sigmoid", dtype=float_prec, shape=()) - sigmoid = loop_body.nodes.append(gs.Node(op="Sigmoid", inputs=[loop_tensors["gate_prediction"]], outputs=[gate_sigmoid])) - - leq_output = gs.Variable("leq_output", dtype=bool) - leq = loop_body.nodes.append(gs.Node(op="Less", inputs=[gate_sigmoid, gate_threshold], outputs=[leq_output])) - - loop_body.nodes.append(gs.Node(op="And", inputs=[not_finished_in, leq_output], outputs=[not_finished_out])) - - cast_output = gs.Variable("cast_output", dtype=np.int32) - loop_body.nodes.append(gs.Node(op="Cast", inputs=[not_finished_out], outputs=[cast_output], attrs={"to": 6})) # int32 - - reduce_output = gs.Variable("reduce_output", dtype=np.int32) - loop_body.nodes.append( gs.Node(op="ReduceSum", inputs=[cast_output], outputs=[reduce_output], attrs={"axes": [0], "keepdims": 0})) - - unsqueezed_cond_out = gs.Variable("unsqueezed_cond_out", dtype=bool) - loop_body.nodes.append(gs.Node(op="Equal", inputs=[reduce_output, gs.Constant("zero", np.array(0, dtype=np.int32))], outputs=[unsqueezed_cond_out])) - - squeezed_cond_out = gs.Variable("squeezed_cond_out", dtype=bool) - loop_body.nodes.append(gs.Node(op="Squeeze", inputs=[unsqueezed_cond_out], outputs=[squeezed_cond_out], attrs={"axes": [0]})) - - loop_body.nodes.append(gs.Node(op="Not", inputs=[squeezed_cond_out], outputs=[cond_out])) - - # Compute mel_lengths - # from PyTorch: mel_lengths += not_finished - - loop_body.nodes.append(gs.Node(op="Add", inputs=[mel_lengths_in, cast_output], outputs=[mel_lengths_out])) - - memory = gs.Variable("memory", dtype=float_prec, shape=('batch_size', 'seq_len', 512)) - processed_memory = gs.Variable("processed_memory", dtype=float_prec, shape=('batch_size', 'seq_len', 128)) - mask = gs.Variable("mask", dtype=bool, shape=('batch_size', 'seq_len')) - - loop_body.toposort() - onnx.save(gs.export_onnx(loop_body), os.path.join(output_dir, "loop_body_{prec}.onnx".format(prec="fp16" if float_prec == np.float16 else "fp32"))) - - # Create outer graph - - # Inputs to outer graph are the following (suffixed with _0 to signify initial states) - # - decoder_input_0 - # - attention_hidden_0 - # - attention_cell_0 - # - decoder_hidden_0 - # - decoder_cell_0 - # - attention_weights_0 - # - attention_weights_cum_0 - # - attention_context_0 - # - memory - # - processed_memory - # - mask - - # Outputs are the following - # - mel_outputs - # - mel_lengths - - # Note: alignments and gate_outputs are scan outputs, but don't seem to be used later in the PyTorch implementation. For now, we will make them intermediate tensors that are not outputted - - graph = gs.Graph() - - decoder_input_0 = gs.Variable("decoder_input_0", dtype=float_prec, shape=('batch_size', 80)) - attention_hidden_0 = gs.Variable("attention_hidden_0", dtype=float_prec, shape=('batch_size', 1024)) - attention_cell_0 = gs.Variable("attention_cell_0", dtype=float_prec, shape=('batch_size', 1024)) - decoder_hidden_0 = gs.Variable("decoder_hidden_0", dtype=float_prec, shape=('batch_size', 1024)) - decoder_cell_0 = gs.Variable("decoder_cell_0", dtype=float_prec, shape=('batch_size', 1024)) - attention_weights_0 = gs.Variable("attention_weights_0", dtype=float_prec, shape=('batch_size', 'seq_len')) - attention_weights_cum_0 = gs.Variable("attention_weights_cum_0", dtype=float_prec, shape=('batch_size', 'seq_len')) - attention_context_0 = gs.Variable("attention_context_0", dtype=float_prec, shape=('batch_size', 512)) - not_finished_0 = gs.Variable("not_finished_0", dtype=bool) - mel_lengths_0 = gs.Variable("mel_lengths_0", dtype=np.int32) - - # For not_finished, we need to generate a tensor of shape (batch_size) that is all 1s - # We can use the ONNX ConstantOfShape op to do this - not_finished_shape = gs.Variable("not_finished_shape", dtype=np.int64) - reduced = gs.Variable("reduced", dtype=float_prec) - graph.nodes.append(gs.Node(op="ReduceSum", inputs=[decoder_input_0], outputs=[reduced], attrs={"axes":[1], "keepdims": 1})) - graph.nodes.append(gs.Node(op="Shape", inputs=[reduced], outputs=[not_finished_shape])) - before_cast = gs.Variable("before_cast", dtype=np.int32) - graph.nodes.append(gs.Node(op="ConstantOfShape", inputs=[not_finished_shape], outputs=[before_cast], attrs={"value":gs.Constant("one", np.array([1], dtype=np.int32))})) - graph.nodes.append(gs.Node(op="Cast", inputs=[before_cast], outputs=[not_finished_0], attrs={"to": 9})) - - # Same thing for mel_lengths, but we need all 0s - graph.nodes.append(gs.Node(op="ConstantOfShape", inputs=[not_finished_shape], outputs=[mel_lengths_0], attrs={"value":gs.Constant("zero", np.array([0], dtype=np.int32))})) - - # Loop carried dependecies at the end of the loop - decoder_input_t = gs.Variable("decoder_input_t", dtype=float_prec, shape=('batch_size', 80)) - attention_hidden_t = gs.Variable("attention_hidden_t", dtype=float_prec, shape=('batch_size', 1024)) - attention_cell_t = gs.Variable("attention_cell_t", dtype=float_prec, shape=('batch_size', 1024)) - decoder_hidden_t = gs.Variable("decoder_hidden_t", dtype=float_prec, shape=('batch_size', 1024)) - decoder_cell_t = gs.Variable("decoder_cell_t", dtype=float_prec, shape=('batch_size', 1024)) - attention_weights_t = gs.Variable("attention_weights_t", dtype=float_prec, shape=('batch_size', 'seq_len')) - attention_weights_cum_t = gs.Variable("attention_weights_cum_t", dtype=float_prec, shape=('batch_size', 'seq_len')) - attention_context_t = gs.Variable("attention_context_t", dtype=float_prec, shape=('batch_size', 512)) - not_finished_t = gs.Variable("not_finished_t", dtype=bool) - mel_lengths_t = gs.Variable("mel_lengths_t", dtype=np.int32, shape=('batch_size', 1)) - - # Scan outputs - mel_outputs_raw = gs.Variable("mel_outputs_raw", dtype=float_prec, shape=(-1, 'batch_size', 80)) - gate_outputs = gs.Variable("gate_outputs", dtype=float_prec, shape=(-1, 'batch_size', 1)) - alignments = gs.Variable("alignments", dtype=float_prec, shape=(-1, 1, 'seq_len')) - - mel_outputs = gs.Variable("mel_outputs", dtype=float_prec, shape=('batch_size', 80, -1)) - - graph.inputs = [decoder_input_0, attention_hidden_0, attention_cell_0, decoder_hidden_0, decoder_cell_0, attention_weights_0, attention_weights_cum_0, attention_context_0, memory, processed_memory, mask] - graph.outputs = [mel_outputs, mel_lengths_t] - - trip_count = gs.Constant("trip_count", np.array(0, dtype=np.int64)) # In ONNX, this is an optional parameter, but I don't think ONNX-GS supports optional inputs. To fix this, after we export the ONNX ModelProto from GS, we replace this input with "" - initial_cond = gs.Constant("initial_cond", np.array(True, dtype=bool)) - loop_inputs = [trip_count, initial_cond, decoder_input_0, attention_hidden_0, attention_cell_0, decoder_hidden_0, decoder_cell_0, attention_weights_0, attention_weights_cum_0, attention_context_0, not_finished_0, mel_lengths_0] - loop_outputs = [decoder_input_t, attention_hidden_t, attention_cell_t, decoder_hidden_t, decoder_cell_t, attention_weights_t, attention_weights_cum_t, attention_context_t, not_finished_t, mel_lengths_t, mel_outputs_raw, gate_outputs, alignments] - decoder_loop = gs.Node(op="Loop", name="decoder_loop", inputs=loop_inputs, outputs=loop_outputs, attrs={"body": loop_body}) - graph.nodes.append(decoder_loop) - - graph.nodes.append(gs.Node(op="Transpose", inputs=[mel_outputs_raw], outputs=[mel_outputs], attrs={"perm": [1, 2, 0]})) # Output needs to have loop dimension as inner-most dim - - graph.toposort() - exported_graph = gs.export_onnx(graph) - [x for x in exported_graph.graph.node if x.name == "decoder_loop"][0].input[0] = "" # Remove trip count input - - onnx.save(exported_graph, os.path.join(output_dir, decoder_out_name)) - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('model_path', type=str, - help='path to original decoder_iter ONNX model') - parser.add_argument('-o', '--output_dir', type=str, default='.', help='Output directory') - parser.add_argument('--decoder_out', type=str, help='Filename of the exported decoder with outer loop') - parser.add_argument('--fp16', action='store_true') - - args = parser.parse_args() - - if args.decoder_out == None: - args.decoder_out = "decoder_with_outer_loop_{}.onnx".format("fp16" if args.fp16 else "fp32") - - insert_decoder_loop(args.model_path, args.output_dir, args.decoder_out, args.fp16) diff --git a/demo/Tacotron2/tensorrt/inference_trt.py b/demo/Tacotron2/tensorrt/inference_trt.py deleted file mode 100644 index d1a6dabd..00000000 --- a/demo/Tacotron2/tensorrt/inference_trt.py +++ /dev/null @@ -1,491 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 -from scipy.io.wavfile import write -import time -import torch -import argparse -import os.path as path - -import sys -from pathlib import Path -sys.path.append(str(Path(__file__).parents[1])) - -from common.utils import to_gpu, get_mask_from_lengths -from tacotron2.text import text_to_sequence -from inference import MeasureTime, prepare_input_sequence, load_and_setup_model -import dllogger as DLLogger -from dllogger import StdOutBackend, JSONStreamBackend, Verbosity -from trt_utils import load_engine, run_trt_engine - -from waveglow.denoiser import Denoiser - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('-i', '--input', type=str, required=True, - help='full path to the input text (phareses separated by new line)') - parser.add_argument('-o', '--output', required=True, - help='output folder to save audio (file per phrase)') - parser.add_argument('--encoder', type=str, required=True, - help='full path to the Encoder engine') - parser.add_argument('--decoder', type=str, required=True, - help='full path to the DecoderIter engine') - parser.add_argument('--postnet', type=str, required=True, - help='full path to the Postnet engine') - parser.add_argument('--waveglow', type=str, required=True, - help='full path to the WaveGlow engine') - parser.add_argument('--waveglow-ckpt', type=str, default="", - help='full path to the WaveGlow model checkpoint file') - parser.add_argument('--log-file', type=str, default='nvlog.json', - help='Filename for logging') - parser.add_argument('-d', '--denoising-strength', default=0.01, type=float) - parser.add_argument('-sr', '--sampling-rate', default=22050, type=int, - help='Sampling rate') - parser.add_argument('--stft-hop-length', type=int, default=256, - help='STFT hop length for estimating audio length from mel size') - parser.add_argument('--fp16', action='store_true', - help='inference with FP16') - parser.add_argument('--loop', dest='loop', action='store_true', - help='Includes the outer decoder loop in the ONNX model. Enabled by default and only supported on TensorRT 8.0 or later.') - parser.add_argument('--no-loop', dest='loop', action='store_false', - help='Excludes outer decoder loop from decoder ONNX model. Default behavior and necessary for TensorRT 7.2 or earlier.') - parser.set_defaults(loop=int(trt.__version__[0]) >= 8) - parser.add_argument('--waveglow-onnxruntime', action='store_true', - help='Specify this option to use ONNX runtime instead of TRT for running Waveglow') - parser.add_argument('--decoder-onnxruntime', action='store_true', - help='Specify this option to use ONNX runtime instead of TRT for running the TT2 Decoder with loop. When using this option, pass the decoder ONNX model to the --decoder argument') - return parser - - -def init_decoder_inputs(memory, processed_memory, memory_lengths): - - device = memory.device - dtype = memory.dtype - bs = memory.size(0) - seq_len = memory.size(1) - attention_rnn_dim = 1024 - decoder_rnn_dim = 1024 - encoder_embedding_dim = 512 - n_mel_channels = 80 - - attention_hidden = torch.zeros(bs, attention_rnn_dim, device=device, dtype=dtype) - attention_cell = torch.zeros(bs, attention_rnn_dim, device=device, dtype=dtype) - decoder_hidden = torch.zeros(bs, decoder_rnn_dim, device=device, dtype=dtype) - decoder_cell = torch.zeros(bs, decoder_rnn_dim, device=device, dtype=dtype) - attention_weights = torch.zeros(bs, seq_len, device=device, dtype=dtype) - attention_weights_cum = torch.zeros(bs, seq_len, device=device, dtype=dtype) - attention_context = torch.zeros(bs, encoder_embedding_dim, device=device, dtype=dtype) - mask = get_mask_from_lengths(memory_lengths).to(device) - decoder_input = torch.zeros(bs, n_mel_channels, device=device, dtype=dtype) - - return (decoder_input, attention_hidden, attention_cell, decoder_hidden, - decoder_cell, attention_weights, attention_weights_cum, - attention_context, memory, processed_memory, mask) - -def init_decoder_outputs(memory, memory_lengths): - - device = memory.device - dtype = memory.dtype - bs = memory.size(0) - seq_len = memory.size(1) - attention_rnn_dim = 1024 - decoder_rnn_dim = 1024 - encoder_embedding_dim = 512 - n_mel_channels = 80 - - attention_hidden = torch.zeros(bs, attention_rnn_dim, device=device, dtype=dtype) - attention_cell = torch.zeros(bs, attention_rnn_dim, device=device, dtype=dtype) - decoder_hidden = torch.zeros(bs, decoder_rnn_dim, device=device, dtype=dtype) - decoder_cell = torch.zeros(bs, decoder_rnn_dim, device=device, dtype=dtype) - attention_weights = torch.zeros(bs, seq_len, device=device, dtype=dtype) - attention_weights_cum = torch.zeros(bs, seq_len, device=device, dtype=dtype) - attention_context = torch.zeros(bs, encoder_embedding_dim, device=device, dtype=dtype) - decoder_output = torch.zeros(bs, n_mel_channels, device=device, dtype=dtype) - gate_prediction = torch.zeros(bs, 1, device=device, dtype=dtype) - - return (attention_hidden, attention_cell, decoder_hidden, - decoder_cell, attention_weights, attention_weights_cum, - attention_context, decoder_output, gate_prediction) - -def init_decoder_tensors(decoder_inputs, decoder_outputs): - - decoder_tensors = { - "inputs" : { - 'decoder_input': decoder_inputs[0], - 'attention_hidden': decoder_inputs[1], - 'attention_cell': decoder_inputs[2], - 'decoder_hidden': decoder_inputs[3], - 'decoder_cell': decoder_inputs[4], - 'attention_weights': decoder_inputs[5], - 'attention_weights_cum': decoder_inputs[6], - 'attention_context': decoder_inputs[7], - 'memory': decoder_inputs[8], - 'processed_memory': decoder_inputs[9], - 'mask': decoder_inputs[10] - }, - "outputs" : { - 'out_attention_hidden': decoder_outputs[0], - 'out_attention_cell': decoder_outputs[1], - 'out_decoder_hidden': decoder_outputs[2], - 'out_decoder_cell': decoder_outputs[3], - 'out_attention_weights': decoder_outputs[4], - 'out_attention_weights_cum': decoder_outputs[5], - 'out_attention_context': decoder_outputs[6], - 'decoder_output': decoder_outputs[7], - 'gate_prediction': decoder_outputs[8] - } - } - return decoder_tensors - -def swap_inputs_outputs(decoder_inputs, decoder_outputs): - - new_decoder_inputs = (decoder_outputs[7], # decoder_output - decoder_outputs[0], # attention_hidden - decoder_outputs[1], # attention_cell - decoder_outputs[2], # decoder_hidden - decoder_outputs[3], # decoder_cell - decoder_outputs[4], # attention_weights - decoder_outputs[5], # attention_weights_cum - decoder_outputs[6], # attention_context - decoder_inputs[8], # memory - decoder_inputs[9], # processed_memory - decoder_inputs[10]) # mask - - new_decoder_outputs = (decoder_inputs[1], # attention_hidden - decoder_inputs[2], # attention_cell - decoder_inputs[3], # decoder_hidden - decoder_inputs[4], # decoder_cell - decoder_inputs[5], # attention_weights - decoder_inputs[6], # attention_weights_cum - decoder_inputs[7], # attention_context - decoder_inputs[0], # decoder_input - decoder_outputs[8])# gate_output - - return new_decoder_inputs, new_decoder_outputs - - -def infer_tacotron2_trt(encoder, decoder_iter, postnet, - encoder_context, decoder_context, postnet_context, - sequences, sequence_lengths, measurements, fp16, loop): - - batch_size = len(sequence_lengths) - max_sequence_len = sequence_lengths[0] - memory = torch.zeros((batch_size, max_sequence_len, 512)).cuda() - if fp16: - memory = memory.half() - device = memory.device - dtype = memory.dtype - - processed_memory = torch.zeros((batch_size, max_sequence_len, 128), device=device, dtype=dtype) - lens = torch.zeros_like(sequence_lengths) - print(f"batch_size: {batch_size}, max sequence length: {max_sequence_len}") - - encoder_tensors = { - "inputs" : - {'sequences': sequences, 'sequence_lengths': sequence_lengths}, - "outputs" : - {'memory': memory, 'lens': lens, 'processed_memory': processed_memory} - } - - print("Running Tacotron2 Encoder") - with MeasureTime(measurements, "tacotron2_encoder_time"): - run_trt_engine(encoder_context, encoder, encoder_tensors) - max_decoder_steps = 1024 - device = memory.device - mel_lengths = torch.zeros([memory.size(0)], dtype=torch.int32, device = device) - not_finished = torch.ones([memory.size(0)], dtype=torch.int32, device = device) - mel_outputs = torch.ones((batch_size, 80, max_decoder_steps), device = device, dtype=dtype).cuda() - gate_threshold = 0.5 - first_iter = True - - decoder_inputs = init_decoder_inputs(memory, processed_memory, sequence_lengths) - decoder_outputs = init_decoder_outputs(memory, sequence_lengths) - - if loop: - if decoder_context is None: - print("Running Tacotron2 Decoder with loop with ONNX-RT") - decoder_inputs_onnxrt = [x.cpu().numpy().copy() for x in decoder_inputs] - import onnx - import onnxruntime - sess = onnxruntime.InferenceSession(decoder_iter) - - with MeasureTime(measurements, "tacotron2_decoder_time"): - result = sess.run(["mel_outputs", "mel_lengths_t"], { - 'decoder_input_0': decoder_inputs_onnxrt[0], - 'attention_hidden_0': decoder_inputs_onnxrt[1], - 'attention_cell_0': decoder_inputs_onnxrt[2], - 'decoder_hidden_0': decoder_inputs_onnxrt[3], - 'decoder_cell_0': decoder_inputs_onnxrt[4], - 'attention_weights_0': decoder_inputs_onnxrt[5], - 'attention_weights_cum_0': decoder_inputs_onnxrt[6], - 'attention_context_0': decoder_inputs_onnxrt[7], - 'memory': decoder_inputs_onnxrt[8], - 'processed_memory': decoder_inputs_onnxrt[9], - 'mask': decoder_inputs_onnxrt[10] - }) - - mel_outputs = torch.tensor(result[0], device=device) - mel_lengths = torch.tensor(result[1], device=device) - else: - print("Running Tacotron2 Decoder with loop") - decoder_tensors = { - "inputs" : - { - 'decoder_input_0': decoder_inputs[0], - 'attention_hidden_0': decoder_inputs[1], - 'attention_cell_0': decoder_inputs[2], - 'decoder_hidden_0': decoder_inputs[3], - 'decoder_cell_0': decoder_inputs[4], - 'attention_weights_0': decoder_inputs[5], - 'attention_weights_cum_0': decoder_inputs[6], - 'attention_context_0': decoder_inputs[7], - 'memory': decoder_inputs[8], - 'processed_memory': decoder_inputs[9], - 'mask': decoder_inputs[10] - }, - "outputs" : - {'mel_outputs': mel_outputs, 'mel_lengths_t': mel_lengths} - } - - with MeasureTime(measurements, "tacotron2_decoder_time"): - run_trt_engine(decoder_context, decoder_iter, decoder_tensors) - mel_outputs = mel_outputs[:,:,:torch.max(mel_lengths)] - - else: - print("Running Tacotron2 Decoder") - measurements_decoder = {} - while True: - decoder_tensors = init_decoder_tensors(decoder_inputs, decoder_outputs) - with MeasureTime(measurements_decoder, "step"): - run_trt_engine(decoder_context, decoder_iter, decoder_tensors) - - if first_iter: - mel_outputs = torch.unsqueeze(decoder_outputs[7], 2) - gate_outputs = torch.unsqueeze(decoder_outputs[8], 2) - alignments = torch.unsqueeze(decoder_outputs[4], 2) - measurements['tacotron2_decoder_time'] = measurements_decoder['step'] - first_iter = False - else: - mel_outputs = torch.cat((mel_outputs, torch.unsqueeze(decoder_outputs[7], 2)), 2) - gate_outputs = torch.cat((gate_outputs, torch.unsqueeze(decoder_outputs[8], 2)), 2) - alignments = torch.cat((alignments, torch.unsqueeze(decoder_outputs[4], 2)), 2) - measurements['tacotron2_decoder_time'] += measurements_decoder['step'] - - dec = torch.le(torch.sigmoid(decoder_outputs[8]), gate_threshold).to(torch.int32).squeeze(1) - not_finished = not_finished*dec - mel_lengths += not_finished - - if torch.sum(not_finished) == 0: - print("Stopping after",mel_outputs.size(2),"decoder steps") - break - if mel_outputs.size(2) == max_decoder_steps: - print("Warning! Reached max decoder steps") - break - - decoder_inputs, decoder_outputs = swap_inputs_outputs(decoder_inputs, decoder_outputs) - - mel_outputs = mel_outputs.clone().detach() - mel_outputs_postnet = torch.zeros_like(mel_outputs, device=device, dtype=dtype) - - postnet_tensors = { - "inputs" : - {'mel_outputs': mel_outputs}, - "outputs" : - {'mel_outputs_postnet': mel_outputs_postnet} - } - print("Running Tacotron2 Postnet") - with MeasureTime(measurements, "tacotron2_postnet_time"): - run_trt_engine(postnet_context, postnet, postnet_tensors) - - print("Tacotron2 Postnet done") - - return mel_outputs_postnet, mel_lengths - - -def infer_waveglow_trt(waveglow, waveglow_context, mel, measurements, fp16): - - mel_size = mel.size(2) - batch_size = mel.size(0) - stride = 256 - n_group = 8 - z_size = mel_size*stride - z_size = z_size//n_group - z = torch.randn(batch_size, n_group, z_size).cuda() - audios = torch.zeros(batch_size, mel_size*stride).cuda() - - mel = mel.unsqueeze(3) - z = z.unsqueeze(3) - - if fp16: - z = z.half() - mel = mel.half() - audios = audios.half() - - waveglow_tensors = { - "inputs" : {'mel': mel, 'z': z}, - "outputs" : {'audio': audios} - } - - print("Running WaveGlow with TensorRT") - with MeasureTime(measurements, "waveglow_time"): - run_trt_engine(waveglow_context, waveglow, waveglow_tensors) - - return audios - -def infer_waveglow_onnx(waveglow_path, mel, measurements, fp16): - import onnx - import onnxruntime - sess = onnxruntime.InferenceSession(waveglow_path) - - device=mel.device - mel_size = mel.size(2) - batch_size = mel.size(0) - stride = 256 - n_group = 8 - z_size = mel_size*stride - z_size = z_size//n_group - z = torch.randn(batch_size, n_group, z_size).cuda() - - mel = mel.unsqueeze(3) - z = z.unsqueeze(3) - - if fp16: - z = z.half() - mel = mel.half() - - mel = mel.cpu().numpy().copy() - z = z.cpu().numpy().copy() - - print("Running WaveGlow with ONNX Runtime") - with MeasureTime(measurements, "waveglow_time"): - result = sess.run(["audio"], { - 'mel': mel, - 'z': z - }) - audios = torch.tensor(result[0], device=device) - return audios - -def main(): - - parser = argparse.ArgumentParser( - description='TensorRT Tacotron 2 Inference') - parser = parse_args(parser) - args, _ = parser.parse_known_args() - - # initialize CUDA state - torch.cuda.init() - - TRT_LOGGER = trt.Logger(trt.Logger.WARNING) - encoder = load_engine(args.encoder, TRT_LOGGER) - postnet = load_engine(args.postnet, TRT_LOGGER) - - if args.waveglow_ckpt != "": - # setup denoiser using WaveGlow PyTorch checkpoint - waveglow_ckpt = load_and_setup_model('WaveGlow', parser, args.waveglow_ckpt, - True, forward_is_infer=True) - denoiser = Denoiser(waveglow_ckpt).cuda() - # after initialization, we don't need WaveGlow PyTorch checkpoint - # anymore - deleting - del waveglow_ckpt - torch.cuda.empty_cache() - - # create TRT contexts for each engine - encoder_context = encoder.create_execution_context() - decoder_context = None - if not args.decoder_onnxruntime: - decoder_iter = load_engine(args.decoder, TRT_LOGGER) - decoder_context = decoder_iter.create_execution_context() - else: - decoder_iter = args.decoder - postnet_context = postnet.create_execution_context() - - waveglow_context = None - if not args.waveglow_onnxruntime: - waveglow = load_engine(args.waveglow, TRT_LOGGER) - waveglow_context = waveglow.create_execution_context() - - DLLogger.init(backends=[JSONStreamBackend(Verbosity.DEFAULT, - path.join(args.output, args.log_file)), - StdOutBackend(Verbosity.VERBOSE)]) - - texts = [] - try: - f = open(args.input, 'r') - texts = f.readlines() - except: - print("Could not read file") - sys.exit(1) - - measurements = {} - - sequences, sequence_lengths = prepare_input_sequence(texts) - dt = encoder.get_tensor_dtype("sequences") - sequences = sequences.to(torch.int64 if dt == trt.DataType.INT64 else torch.int32) - sequence_lengths = sequence_lengths.to(torch.int32) - - with MeasureTime(measurements, "latency"): - mel, mel_lengths = infer_tacotron2_trt(encoder, decoder_iter, postnet, - encoder_context, decoder_context, postnet_context, - sequences, sequence_lengths, measurements, args.fp16, args.loop) - audios = infer_waveglow_onnx(args.waveglow, mel, measurements, args.fp16) if args.waveglow_onnxruntime else \ - infer_waveglow_trt(waveglow, waveglow_context, mel, measurements, args.fp16) - - with encoder_context, postnet_context: - pass - - if decoder_context is not None: - with decoder_context: pass - - if waveglow_context is not None: - with waveglow_context: pass - - audios = audios.float() - if args.waveglow_ckpt != "": - with MeasureTime(measurements, "denoiser"): - audios = denoiser(audios, strength=args.denoising_strength).squeeze(1) - - for i, audio in enumerate(audios): - audio = audio[:mel_lengths[i]*args.stft_hop_length] - audio = audio/torch.max(torch.abs(audio)) - audio_path = path.join(args.output, f"audio_{i}_trt.wav") - write(audio_path, args.sampling_rate, audio.cpu().numpy()) - - - DLLogger.log(step=0, data={"tacotron2_encoder_latency": measurements['tacotron2_encoder_time']}) - DLLogger.log(step=0, data={"tacotron2_decoder_latency": measurements['tacotron2_decoder_time']}) - DLLogger.log(step=0, data={"tacotron2_postnet_latency": measurements['tacotron2_postnet_time']}) - DLLogger.log(step=0, data={"waveglow_latency": measurements['waveglow_time']}) - DLLogger.log(step=0, data={"latency": measurements['latency']}) - - if args.waveglow_ckpt != "": - DLLogger.log(step=0, data={"denoiser": measurements['denoiser']}) - DLLogger.flush() - - prec = "fp16" if args.fp16 else "fp32" - latency = measurements['latency'] - throughput = audios.size(1)/latency - log_data = f"1,{sequence_lengths[0].item()},{prec},{latency},{throughput},{mel_lengths[0].item()}\n" - log_file = path.join(args.output, f"log_bs1_{prec}.log") - with open(log_file, 'a') as f: - f.write(log_data) - -if __name__ == "__main__": - main() diff --git a/demo/Tacotron2/tensorrt/run_latency_tests_trt.sh b/demo/Tacotron2/tensorrt/run_latency_tests_trt.sh deleted file mode 100644 index a289cf63..00000000 --- a/demo/Tacotron2/tensorrt/run_latency_tests_trt.sh +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -bash test_infer.sh --test tensorrt/test_infer_trt.py -bs 1 -il 128 --fp16 --num-iters 1003 --encoder ./output/encoder_fp16.engine --decoder ./output/decoder_with_outer_loop_fp16.engine --postnet ./output/postnet_fp16.engine --waveglow ./output/waveglow_fp16.engine --wn-channels 256 diff --git a/demo/Tacotron2/tensorrt/test_infer_trt.py b/demo/Tacotron2/tensorrt/test_infer_trt.py deleted file mode 100644 index 7023f02f..00000000 --- a/demo/Tacotron2/tensorrt/test_infer_trt.py +++ /dev/null @@ -1,230 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 sys -sys.path.append('./') -from tacotron2.text import text_to_sequence -import models -import tensorrt as trt -import torch -import argparse -import numpy as np -from scipy.io.wavfile import write - -from inference import checkpoint_from_distributed, unwrap_distributed, MeasureTime, prepare_input_sequence, load_and_setup_model -from inference_trt import infer_tacotron2_trt, infer_waveglow_trt - -from trt_utils import load_engine - -import time -import dllogger as DLLogger -from dllogger import StdOutBackend, JSONStreamBackend, Verbosity - -# from apex import amp - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('--encoder', type=str, required=True, - help='full path to the Encoder engine') - parser.add_argument('--decoder', type=str, required=True, - help='full path to the DecoderIter engine') - parser.add_argument('--postnet', type=str, required=True, - help='full path to the Postnet engine') - parser.add_argument('--waveglow', type=str, required=True, - help='full path to the WaveGlow engine') - parser.add_argument('--waveglow-ckpt', type=str, default="", - help='full path to the WaveGlow model checkpoint file') - parser.add_argument('-s', '--sigma-infer', default=0.6, type=float) - parser.add_argument('-sr', '--sampling-rate', default=22050, type=int, - help='Sampling rate') - parser.add_argument('--fp16', action='store_true', - help='inference with FP16') - parser.add_argument('--log-file', type=str, default='nvlog.json', - help='Filename for logging') - parser.add_argument('--stft-hop-length', type=int, default=256, - help='STFT hop length for estimating audio length from mel size') - parser.add_argument('--num-iters', type=int, default=10, - help='Number of iterations') - parser.add_argument('-il', '--input-length', type=int, default=64, - help='Input length') - parser.add_argument('-bs', '--batch-size', type=int, default=1, - help='Batch size') - - return parser - - -def print_stats(measurements_all): - - print(np.mean(measurements_all['latency'][1:]), - np.mean(measurements_all['throughput'][1:]), - np.mean(measurements_all['pre_processing'][1:]), - np.mean(measurements_all['type_conversion'][1:])+ - np.mean(measurements_all['storage'][1:])+ - np.mean(measurements_all['data_transfer'][1:]), - np.mean(measurements_all['num_mels_per_audio'][1:])) - - throughput = measurements_all['throughput'] - preprocessing = measurements_all['pre_processing'] - type_conversion = measurements_all['type_conversion'] - storage = measurements_all['storage'] - data_transfer = measurements_all['data_transfer'] - postprocessing = [sum(p) for p in zip(type_conversion,storage,data_transfer)] - latency = measurements_all['latency'] - num_mels_per_audio = measurements_all['num_mels_per_audio'] - - latency.sort() - - cf_50 = max(latency[:int(len(latency)*0.50)]) - cf_90 = max(latency[:int(len(latency)*0.90)]) - cf_95 = max(latency[:int(len(latency)*0.95)]) - cf_99 = max(latency[:int(len(latency)*0.99)]) - cf_100 = max(latency[:int(len(latency)*1.0)]) - - print("Throughput average (samples/sec) = {:.4f}".format(np.mean(throughput))) - print("Preprocessing average (seconds) = {:.4f}".format(np.mean(preprocessing))) - print("Postprocessing average (seconds) = {:.4f}".format(np.mean(postprocessing))) - print("Number of mels per audio average = {}".format(np.mean(num_mels_per_audio))) # - print("Latency average (seconds) = {:.4f}".format(np.mean(latency))) - print("Latency std (seconds) = {:.4f}".format(np.std(latency))) - print("Latency cl 50 (seconds) = {:.4f}".format(cf_50)) - print("Latency cl 90 (seconds) = {:.4f}".format(cf_90)) - print("Latency cl 95 (seconds) = {:.4f}".format(cf_95)) - print("Latency cl 99 (seconds) = {:.4f}".format(cf_99)) - print("Latency cl 100 (seconds) = {:.4f}".format(cf_100)) - - -def main(): - """ - Launches text to speech (inference). - Inference is executed on a single GPU. - """ - parser = argparse.ArgumentParser( - description='PyTorch Tacotron 2 Inference') - parser = parse_args(parser) - args, unknown_args = parser.parse_known_args() - - DLLogger.init(backends=[JSONStreamBackend(Verbosity.DEFAULT, args.log_file), - StdOutBackend(Verbosity.VERBOSE)]) - for k,v in vars(args).items(): - DLLogger.log(step="PARAMETER", data={k:v}) - DLLogger.log(step="PARAMETER", data={'model_name':'Tacotron2_PyT'}) - - measurements_all = {"pre_processing": [], - "tacotron2_encoder_time": [], - "tacotron2_decoder_time": [], - "tacotron2_postnet_time": [], - "tacotron2_latency": [], - "waveglow_latency": [], - "latency": [], - "type_conversion": [], - "data_transfer": [], - "storage": [], - "tacotron2_items_per_sec": [], - "waveglow_items_per_sec": [], - "num_mels_per_audio": [], - "throughput": []} - - print("args:", args, unknown_args) - - torch.cuda.init() - - TRT_LOGGER = trt.Logger(trt.Logger.WARNING) - encoder = load_engine(args.encoder, TRT_LOGGER) - decoder_iter = load_engine(args.decoder, TRT_LOGGER) - postnet = load_engine(args.postnet, TRT_LOGGER) - waveglow = load_engine(args.waveglow, TRT_LOGGER) - - if args.waveglow_ckpt != "": - # setup denoiser using WaveGlow PyTorch checkpoint - waveglow_ckpt = load_and_setup_model('WaveGlow', parser, - args.waveglow_ckpt, - fp16_run=args.fp16, - cpu_run=False, - forward_is_infer=True) - denoiser = Denoiser(waveglow_ckpt).cuda() - # after initialization, we don't need WaveGlow PyTorch checkpoint - # anymore - deleting - del waveglow_ckpt - torch.cuda.empty_cache() - - # create TRT contexts for each engine - encoder_context = encoder.create_execution_context() - decoder_context = decoder_iter.create_execution_context() - postnet_context = postnet.create_execution_context() - waveglow_context = waveglow.create_execution_context() - - - texts = ["The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves. The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves."] - texts = [texts[0][:args.input_length]] - texts = texts*args.batch_size - - warmup_iters = 3 - - for iter in range(args.num_iters): - - measurements = {} - - with MeasureTime(measurements, "pre_processing"): - sequences_padded, input_lengths = prepare_input_sequence(texts) - sequences_padded = sequences_padded.to(torch.int32) - input_lengths = input_lengths.to(torch.int32) - - with torch.no_grad(): - with MeasureTime(measurements, "latency"): - with MeasureTime(measurements, "tacotron2_latency"): - mel, mel_lengths = infer_tacotron2_trt(encoder, decoder_iter, postnet, - encoder_context, decoder_context, postnet_context, - sequences_padded, input_lengths, measurements, args.fp16, True) - - with MeasureTime(measurements, "waveglow_latency"): - audios = infer_waveglow_trt(waveglow, waveglow_context, mel, measurements, args.fp16) - - num_mels = mel.size(0)*mel.size(2) - num_samples = audios.size(0)*audios.size(1) - - with MeasureTime(measurements, "type_conversion"): - audios = audios.float() - - with MeasureTime(measurements, "data_transfer"): - audios = audios.cpu() - - with MeasureTime(measurements, "storage"): - audios = audios.numpy() - for i, audio in enumerate(audios): - audio_path = "audio_"+str(i)+".wav" - write(audio_path, args.sampling_rate, - audio[:mel_lengths[i]*args.stft_hop_length]) - - measurements['tacotron2_items_per_sec'] = num_mels/measurements['tacotron2_latency'] - measurements['waveglow_items_per_sec'] = num_samples/measurements['waveglow_latency'] - measurements['num_mels_per_audio'] = mel.size(2) - measurements['throughput'] = num_samples/measurements['latency'] - - if iter >= warmup_iters: - for k,v in measurements.items(): - if k in measurements_all.keys(): - measurements_all[k].append(v) - DLLogger.log(step=(iter-warmup_iters), data={k: v}) - - DLLogger.flush() - - print_stats(measurements_all) - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/tensorrt/trt_utils.py b/demo/Tacotron2/tensorrt/trt_utils.py deleted file mode 100644 index e150983f..00000000 --- a/demo/Tacotron2/tensorrt/trt_utils.py +++ /dev/null @@ -1,154 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 os -import sys -import tensorrt as trt - -# For a single dimension this will return the min, opt, and max size when given -# input of either one or three (comma delimited) values -# dim="1" or dim=1 returns (1, 1, 1) -# dim="1,4,5" returns (1, 4, 5) -def parse_dynamic_size(dim): - split = str(dim).split(',') - assert len(split) in (1,3) , "Dynamic size input must be either 1 or 3 comma-separated integers" - ints = [int(i) for i in split] - - if len(ints) == 1: - ints *= 3 - - assert ints[0] <= ints[1] <= ints[2] - return tuple(ints) - - -def is_dimension_dynamic(dim): - return dim is None or dim <= 0 - - -def is_shape_dynamic(shape): - return any([is_dimension_dynamic(dim) for dim in shape]) - - -def run_trt_engine(context, engine, tensors): - - bindings = [0] * engine.num_io_tensors - - for i in range(engine.num_io_tensors): - tensor_name = engine.get_tensor_name(i) - if engine.get_tensor_mode(tensor_name) == trt.TensorIOMode.INPUT: - tensor = tensors['inputs'][tensor_name] - bindings[i] = tensor.data_ptr() - if is_shape_dynamic(engine.get_tensor_shape(tensor_name)): - context.set_input_shape(tensor_name, tensor.shape) - elif engine.get_tensor_mode(tensor_name) == trt.TensorIOMode.OUTPUT: - tensor = tensors['outputs'][tensor_name] - bindings[i] = tensor.data_ptr() - - context.execute_v2(bindings=bindings) - - -def load_engine(engine_filepath, trt_logger): - with open(engine_filepath, "rb") as f, trt.Runtime(trt_logger) as runtime: - engine = runtime.deserialize_cuda_engine(f.read()) - return engine - - -def engine_info(engine_filepath): - - TRT_LOGGER = trt.Logger(trt.Logger.WARNING) - engine = load_engine(engine_filepath, TRT_LOGGER) - - binding_template = r""" -{btype} {{ - name: "{bname}" - data_type: {dtype} - dims: {dims} -}}""" - type_mapping = {"DataType.HALF": "TYPE_FP16", - "DataType.FLOAT": "TYPE_FP32", - "DataType.INT32": "TYPE_INT32", - "DataType.BOOL" : "TYPE_BOOL"} - - print("engine name", engine.name) - start_dim = 1 - print("num_optimization_profiles", engine.num_optimization_profiles) - print("device_memory_size:", engine.device_memory_size) - print("max_workspace_size:", engine.get_memory_pool_limit(trt.MemoryPoolType.WORKSPACE)) - print("num_layers:", engine.num_layers) - - for i in range(engine.num_io_tensors): - tensor_name = engine.get_tensor_name(i) - btype = "input" if engine.get_tensor_mode(tensor_name) == trt.TensorIOMode.INPUT else "output" - dtype = engine.get_tensor_dtype(tensor_name) - bdims = engine.get_tensor_shape(tensor_name) - config_values = { - "btype": btype, - "bname": tensor_name, - "dtype": type_mapping[str(dtype)], - "dims": list(bdims[start_dim:]) - } - final_binding_str = binding_template.format_map(config_values) - print(final_binding_str) - - -def build_engine(model_file, shapes, max_ws=512*1024*1024, fp16=False, timing_cache=None): - - TRT_LOGGER = trt.Logger(trt.Logger.WARNING) - builder = trt.Builder(TRT_LOGGER) - - config = builder.create_builder_config() - config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, max_ws) - if fp16: - config.flags |= 1 << int(trt.BuilderFlag.FP16) - profile = builder.create_optimization_profile() - for s in shapes: - profile.set_shape(s['name'], min=s['min'], opt=s['opt'], max=s['max']) - config.add_optimization_profile(profile) - - timing_cache_available = int(trt.__version__[0]) >= 8 and timing_cache != None - # load global timing cache - if timing_cache_available: - if os.path.exists(timing_cache): - with open(timing_cache, "rb") as f: - cache = config.create_timing_cache(f.read()) - config.set_timing_cache(cache, ignore_mismatch = False) - else: - cache = config.create_timing_cache(b"") - config.set_timing_cache(cache, ignore_mismatch = False) - - network_creation_flag = 0 - if "EXPLICIT_BATCH" in trt.NetworkDefinitionCreationFlag.__members__.keys(): - network_creation_flag = 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) - network = builder.create_network(network_creation_flag) - - with trt.OnnxParser(network, TRT_LOGGER) as parser: - with open(model_file, 'rb') as model: - parsed = parser.parse(model.read()) - for i in range(parser.num_errors): - print("TensorRT ONNX parser error:", parser.get_error(i)) - engine = builder.build_serialized_network(network, config=config) - - # save global timing cache - if timing_cache_available: - cache = config.get_timing_cache() - with cache.serialize() as buffer: - with open(timing_cache, "wb") as f: - f.write(buffer) - f.flush() - os.fsync(f) - - return engine diff --git a/demo/Tacotron2/test_infer.py b/demo/Tacotron2/test_infer.py deleted file mode 100644 index 23816da9..00000000 --- a/demo/Tacotron2/test_infer.py +++ /dev/null @@ -1,198 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch -import argparse -import numpy as np -from scipy.io.wavfile import write - -from inference import MeasureTime, prepare_input_sequence, load_and_setup_model - -import dllogger as DLLogger -from dllogger import StdOutBackend, JSONStreamBackend, Verbosity - -from waveglow.denoiser import Denoiser - -def parse_args(parser): - """ - Parse commandline arguments. - """ - parser.add_argument('--tacotron2', type=str, - help='Full path to the Tacotron2 model checkpoint file') - parser.add_argument('--waveglow', type=str, - help='Full path to the WaveGlow model checkpoint file') - parser.add_argument('-s', '--sigma-infer', default=0.6, type=float, - help='Standard deviation of the Gaussian distribution') - parser.add_argument('-d', '--denoising-strength', default=0.01, type=float, - help='Denoising strength for removing model bias') - parser.add_argument('-sr', '--sampling-rate', default=22050, type=int, - help='Sampling rate') - - run_mode = parser.add_mutually_exclusive_group() - run_mode.add_argument('--fp16', action='store_true', - help='Run inference with FP16') - run_mode.add_argument('--cpu', action='store_true', - help='Run inference on CPU') - - parser.add_argument('--log-file', type=str, default='nvlog.json', - help='Filename for logging') - parser.add_argument('--stft-hop-length', type=int, default=256, - help='STFT hop length for estimating audio length from mel size') - parser.add_argument('--num-iters', type=int, default=10, - help='Number of iterations') - parser.add_argument('-il', '--input-length', type=int, default=64, - help='Input length') - parser.add_argument('-bs', '--batch-size', type=int, default=1, - help='Batch size') - - - return parser - - -def print_stats(measurements_all): - - throughput = measurements_all['throughput'] - preprocessing = measurements_all['pre_processing'] - type_conversion = measurements_all['type_conversion'] - storage = measurements_all['storage'] - data_transfer = measurements_all['data_transfer'] - postprocessing = [sum(p) for p in zip(type_conversion,storage,data_transfer)] - latency = measurements_all['latency'] - waveglow_latency = measurements_all['waveglow_latency'] - tacotron2_latency = measurements_all['tacotron2_latency'] - denoiser_latency = measurements_all['denoiser_latency'] - num_mels_per_audio = measurements_all['num_mels_per_audio'] - - latency.sort() - - cf_50 = max(latency[:int(len(latency)*0.50)]) - cf_90 = max(latency[:int(len(latency)*0.90)]) - cf_95 = max(latency[:int(len(latency)*0.95)]) - cf_99 = max(latency[:int(len(latency)*0.99)]) - cf_100 = max(latency[:int(len(latency)*1.0)]) - - print("Throughput average (samples/sec) = {:.0f}".format(np.mean(throughput))) - print("Preprocessing average (seconds) = {:.4f}".format(np.mean(preprocessing))) - print("Postprocessing average (seconds) = {:.4f}".format(np.mean(postprocessing))) - print("Number of mels per audio average = {:.0f}".format(np.mean(num_mels_per_audio))) - print("Tacotron2 latency average (seconds) = {:.2f}".format(np.mean(tacotron2_latency))) - print("WaveGlow latency average (seconds) = {:.2f}".format(np.mean(waveglow_latency))) - print("Denoiser latency average (seconds) = {:.4f}".format(np.mean(denoiser_latency))) - print("Latency average (seconds) = {:.2f}".format(np.mean(latency))) - print("Latency std (seconds) = {:.2f}".format(np.std(latency))) - print("Latency cl 50 (seconds) = {:.2f}".format(cf_50)) - print("Latency cl 90 (seconds) = {:.2f}".format(cf_90)) - print("Latency cl 95 (seconds) = {:.2f}".format(cf_95)) - print("Latency cl 99 (seconds) = {:.2f}".format(cf_99)) - print("Latency cl 100 (seconds) = {:.2f}".format(cf_100)) - - -def main(): - """ - Launches text to speech (inference). - Inference is executed on a single GPU or CPU. - """ - parser = argparse.ArgumentParser( - description='PyTorch Tacotron 2 Inference') - parser = parse_args(parser) - args, unknown_args = parser.parse_known_args() - - DLLogger.init(backends=[JSONStreamBackend(Verbosity.DEFAULT, args.log_file), - StdOutBackend(Verbosity.VERBOSE)]) - for k,v in vars(args).items(): - DLLogger.log(step="PARAMETER", data={k:v}) - DLLogger.log(step="PARAMETER", data={'model_name':'Tacotron2_PyT'}) - - measurements_all = {"pre_processing": [], - "tacotron2_latency": [], - "waveglow_latency": [], - "denoiser_latency": [], - "latency": [], - "type_conversion": [], - "data_transfer": [], - "storage": [], - "tacotron2_items_per_sec": [], - "waveglow_items_per_sec": [], - "num_mels_per_audio": [], - "throughput": []} - - print("args:", args, unknown_args) - - tacotron2 = load_and_setup_model('Tacotron2', parser, args.tacotron2, - args.fp16, args.cpu, forward_is_infer=True) - waveglow = load_and_setup_model('WaveGlow', parser, args.waveglow, - args.fp16, args.cpu, forward_is_infer=True) - denoiser = Denoiser(waveglow) - if not args.cpu: - denoiser.cuda() - - texts = ["The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves. The forms of printed letters should be beautiful, and that their arrangement on the page should be reasonable and a help to the shapeliness of the letters themselves."] - texts = [texts[0][:args.input_length]] - texts = texts*args.batch_size - - warmup_iters = 3 - - for iter in range(args.num_iters): - - measurements = {} - - with MeasureTime(measurements, "pre_processing", args.cpu): - sequences_padded, input_lengths = prepare_input_sequence(texts, args.cpu) - - with torch.no_grad(): - with MeasureTime(measurements, "latency", args.cpu): - with MeasureTime(measurements, "tacotron2_latency", args.cpu): - mel, mel_lengths, _ = tacotron2.infer(sequences_padded, input_lengths) - - with MeasureTime(measurements, "waveglow_latency", args.cpu): - audios = waveglow.infer(mel, sigma=args.sigma_infer) - - num_mels = mel.size(0)*mel.size(2) - num_samples = audios.size(0)*audios.size(1) - - with MeasureTime(measurements, "type_conversion", args.cpu): - audios = audios.float() - - with torch.no_grad(), MeasureTime(measurements, "denoiser_latency", args.cpu): - audios = denoiser(audios, strength=args.denoising_strength).squeeze(1) - - with MeasureTime(measurements, "data_transfer", args.cpu): - audios = audios.cpu() - - with MeasureTime(measurements, "storage", args.cpu): - audios = audios.numpy() - for i, audio in enumerate(audios): - audio_path = "audio_"+str(i)+".wav" - write(audio_path, args.sampling_rate, - audio[:mel_lengths[i]*args.stft_hop_length]) - - measurements['tacotron2_items_per_sec'] = num_mels/measurements['tacotron2_latency'] - measurements['waveglow_items_per_sec'] = num_samples/measurements['waveglow_latency'] - measurements['num_mels_per_audio'] = mel.size(2) - measurements['throughput'] = num_samples/measurements['latency'] - - if iter >= warmup_iters: - for k,v in measurements.items(): - measurements_all[k].append(v) - DLLogger.log(step=(iter-warmup_iters), data={k: v}) - - DLLogger.flush() - - print_stats(measurements_all) - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/test_infer.sh b/demo/Tacotron2/test_infer.sh deleted file mode 100644 index 103fb941..00000000 --- a/demo/Tacotron2/test_infer.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# 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. -# - -BATCH_SIZE=1 -INPUT_LENGTH=128 -NUM_ITERS=1003 # extra 3 iterations for warmup -TACOTRON2_CKPT="nvidia_tacotron2pyt_fp16_20190427" -WAVEGLOW_CKPT="nvidia_waveglow256pyt_fp16" -RUN_MODE="" # = fp32 -LOG_RUN_MODE="gpu_fp32" -TEST_PROGRAM="test_infer.py" -WN_CHANNELS=512 -LOG_SUFFIX_ADD="" #additional info, e.g., GPU type - -while [ -n "$1" ] -do - case "$1" in - -bs|--batch-size) - BATCH_SIZE="$2" - shift - ;; - -il|--input-length) - INPUT_LENGTH="$2" - shift - ;; - --num-iters) - NUM_ITERS="$2" - shift - ;; - --test) - TEST_PROGRAM="$2" - shift - ;; - --tacotron2) - TACOTRON2_CKPT="$2" - shift - ;; - --encoder) - ENCODER_CKPT="$2" - shift - ;; - --decoder) - DECODER_CKPT="$2" - shift - ;; - --postnet) - POSTNET_CKPT="$2" - shift - ;; - --waveglow) - WAVEGLOW_CKPT="$2" - shift - ;; - --wn-channels) - WN_CHANNELS="$2" - shift - ;; - --cpu) - RUN_MODE="--cpu" - LOG_RUN_MODE="cpu_fp32" - ;; - --fp16) - RUN_MODE="--fp16" - LOG_RUN_MODE="gpu_fp16" - ;; - --log-suffix) - LOG_SUFFIX_ADD="$2" - shift - ;; - *) - echo "Option $1 not recognized" - esac - shift -done - -LOG_SUFFIX=bs${BATCH_SIZE}_il${INPUT_LENGTH}_${LOG_RUN_MODE}_wn${WN_CHANNELS}_${LOG_SUFFIX_ADD} -NVLOG_FILE=nvlog_${LOG_SUFFIX}.json -TMP_LOGFILE=tmp_log_${LOG_SUFFIX}.log -LOGFILE=log_${LOG_SUFFIX}.log - - -if [ "$TEST_PROGRAM" = "tensorrt/test_infer_trt.py" ] -then - TACOTRON2_PARAMS="--encoder $ENCODER_CKPT --decoder $DECODER_CKPT --postnet $POSTNET_CKPT" -else - TACOTRON2_PARAMS="--tacotron2 $TACOTRON2_CKPT" -fi - -set -x -python3 $TEST_PROGRAM \ - $TACOTRON2_PARAMS \ - --waveglow $WAVEGLOW_CKPT \ - --batch-size $BATCH_SIZE \ - --input-length $INPUT_LENGTH \ - --log-file $NVLOG_FILE \ - --num-iters $NUM_ITERS \ - --wn-channels $WN_CHANNELS \ - $RUN_MODE \ - |& tee $TMP_LOGFILE -set +x - - -PERF=$(cat $TMP_LOGFILE | grep -F 'Throughput average (samples/sec)' | awk -F'= ' '{print $2}') -NUM_MELS=$(cat $TMP_LOGFILE | grep -F 'Number of mels per audio average' | awk -F'= ' '{print $2}') -LATENCY=$(cat $TMP_LOGFILE | grep -F 'Latency average (seconds)' | awk -F'= ' '{print $2}') -LATENCYSTD=$(cat $TMP_LOGFILE | grep -F 'Latency std (seconds)' | awk -F'= ' '{print $2}') -LATENCY50=$(cat $TMP_LOGFILE | grep -F 'Latency cl 50 (seconds)' | awk -F'= ' '{print $2}') -LATENCY90=$(cat $TMP_LOGFILE | grep -F 'Latency cl 90 (seconds)' | awk -F'= ' '{print $2}') -LATENCY95=$(cat $TMP_LOGFILE | grep -F 'Latency cl 95 (seconds)' | awk -F'= ' '{print $2}') -LATENCY99=$(cat $TMP_LOGFILE | grep -F 'Latency cl 99 (seconds)' | awk -F'= ' '{print $2}') - -echo "$BATCH_SIZE,$INPUT_LENGTH,$LOG_RUN_MODE,$NUM_ITERS,$LATENCY,$LATENCYSTD,$LATENCY50,$LATENCY90,$LATENCY95,$LATENCY99,$PERF,$NUM_MELS" | tee $LOGFILE diff --git a/demo/Tacotron2/train.py b/demo/Tacotron2/train.py deleted file mode 100644 index 55a9e56f..00000000 --- a/demo/Tacotron2/train.py +++ /dev/null @@ -1,535 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 os -import time -import argparse -import numpy as np -from contextlib import contextmanager - -import torch -from torch.utils.data import DataLoader -from torch.autograd import Variable -from torch.nn.parameter import Parameter - -import torch.distributed as dist -from torch.utils.data.distributed import DistributedSampler - -from apex.parallel import DistributedDataParallel as DDP - -import models -import loss_functions -import data_functions - -import dllogger as DLLogger -from dllogger import StdOutBackend, JSONStreamBackend, Verbosity - -from scipy.io.wavfile import write as write_wav - -from apex import amp -amp.lists.functional_overrides.FP32_FUNCS.remove('softmax') -amp.lists.functional_overrides.FP16_FUNCS.append('softmax') - - -def parse_args(parser): - """ - Parse commandline arguments. - """ - - parser.add_argument('-o', '--output', type=str, required=True, - help='Directory to save checkpoints') - parser.add_argument('-d', '--dataset-path', type=str, - default='./', help='Path to dataset') - parser.add_argument('-m', '--model-name', type=str, default='', required=True, - help='Model to train') - parser.add_argument('--log-file', type=str, default='nvlog.json', - help='Filename for logging') - parser.add_argument('--anneal-steps', nargs='*', - help='Epochs after which decrease learning rate') - parser.add_argument('--anneal-factor', type=float, choices=[0.1, 0.3], default=0.1, - help='Factor for annealing learning rate') - - # training - training = parser.add_argument_group('training setup') - training.add_argument('--epochs', type=int, required=True, - help='Number of total epochs to run') - training.add_argument('--epochs-per-checkpoint', type=int, default=50, - help='Number of epochs per checkpoint') - training.add_argument('--checkpoint-path', type=str, default='', - help='Checkpoint path to resume training') - training.add_argument('--resume-from-last', action='store_true', - help='Resumes training from the last checkpoint; uses the directory provided with \'--output\' option to search for the checkpoint \"checkpoint__last.pt\"') - training.add_argument('--dynamic-loss-scaling', type=bool, default=True, - help='Enable dynamic loss scaling') - training.add_argument('--amp', action='store_true', - help='Enable AMP') - training.add_argument('--cudnn-enabled', action='store_true', - help='Enable cudnn') - training.add_argument('--cudnn-benchmark', action='store_true', - help='Run cudnn benchmark') - training.add_argument('--disable-uniform-initialize-bn-weight', action='store_true', - help='disable uniform initialization of batchnorm layer weight') - - optimization = parser.add_argument_group('optimization setup') - optimization.add_argument( - '--use-saved-learning-rate', default=False, type=bool) - optimization.add_argument('-lr', '--learning-rate', type=float, required=True, - help='Learing rate') - optimization.add_argument('--weight-decay', default=1e-6, type=float, - help='Weight decay') - optimization.add_argument('--grad-clip-thresh', default=1.0, type=float, - help='Clip threshold for gradients') - optimization.add_argument('-bs', '--batch-size', type=int, required=True, - help='Batch size per GPU') - optimization.add_argument('--grad-clip', default=5.0, type=float, - help='Enables gradient clipping and sets maximum gradient norm value') - - # dataset parameters - dataset = parser.add_argument_group('dataset parameters') - dataset.add_argument('--load-mel-from-disk', action='store_true', - help='Loads mel spectrograms from disk instead of computing them on the fly') - dataset.add_argument('--training-files', - default='filelists/ljs_audio_text_train_filelist.txt', - type=str, help='Path to training filelist') - dataset.add_argument('--validation-files', - default='filelists/ljs_audio_text_val_filelist.txt', - type=str, help='Path to validation filelist') - dataset.add_argument('--text-cleaners', nargs='*', - default=['english_cleaners'], type=str, - help='Type of text cleaners for input text') - - # audio parameters - audio = parser.add_argument_group('audio parameters') - audio.add_argument('--max-wav-value', default=32768.0, type=float, - help='Maximum audiowave value') - audio.add_argument('--sampling-rate', default=22050, type=int, - help='Sampling rate') - audio.add_argument('--filter-length', default=1024, type=int, - help='Filter length') - audio.add_argument('--hop-length', default=256, type=int, - help='Hop (stride) length') - audio.add_argument('--win-length', default=1024, type=int, - help='Window length') - audio.add_argument('--mel-fmin', default=0.0, type=float, - help='Minimum mel frequency') - audio.add_argument('--mel-fmax', default=8000.0, type=float, - help='Maximum mel frequency') - - distributed = parser.add_argument_group('distributed setup') - # distributed.add_argument('--distributed-run', default=True, type=bool, - # help='enable distributed run') - distributed.add_argument('--rank', default=0, type=int, - help='Rank of the process, do not set! Done by multiproc module') - distributed.add_argument('--world-size', default=1, type=int, - help='Number of processes, do not set! Done by multiproc module') - distributed.add_argument('--dist-url', type=str, default='tcp://localhost:23456', - help='Url used to set up distributed training') - distributed.add_argument('--group-name', type=str, default='group_name', - required=False, help='Distributed group name') - distributed.add_argument('--dist-backend', default='nccl', type=str, choices={'nccl'}, - help='Distributed run backend') - - benchmark = parser.add_argument_group('benchmark') - benchmark.add_argument('--bench-class', type=str, default='') - - return parser - - -def reduce_tensor(tensor, num_gpus): - rt = tensor.clone() - dist.all_reduce(rt, op=dist.reduce_op.SUM) - rt /= num_gpus - return rt - - -def init_distributed(args, world_size, rank, group_name): - assert torch.cuda.is_available(), "Distributed mode requires CUDA." - print("Initializing Distributed") - - # Set cuda device so everything is done on the right GPU. - torch.cuda.set_device(rank % torch.cuda.device_count()) - - # Initialize distributed communication - dist.init_process_group( - backend=args.dist_backend, init_method=args.dist_url, - world_size=world_size, rank=rank, group_name=group_name) - - print("Done initializing distributed") - - -def save_checkpoint(model, optimizer, epoch, config, amp_run, output_dir, model_name, - local_rank, world_size): - - random_rng_state = torch.random.get_rng_state().cuda() - cuda_rng_state = torch.cuda.get_rng_state(local_rank).cuda() - - random_rng_states_all = [torch.empty_like(random_rng_state) for _ in range(world_size)] - cuda_rng_states_all = [torch.empty_like(cuda_rng_state) for _ in range(world_size)] - - if world_size > 1: - dist.all_gather(random_rng_states_all, random_rng_state) - dist.all_gather(cuda_rng_states_all, cuda_rng_state) - else: - random_rng_states_all = [random_rng_state] - cuda_rng_states_all = [cuda_rng_state] - - random_rng_states_all = torch.stack(random_rng_states_all).cpu() - cuda_rng_states_all = torch.stack(cuda_rng_states_all).cpu() - - if local_rank == 0: - checkpoint = {'epoch': epoch, - 'cuda_rng_state_all': cuda_rng_states_all, - 'random_rng_states_all': random_rng_states_all, - 'config': config, - 'state_dict': model.state_dict(), - 'optimizer': optimizer.state_dict()} - if amp_run: - checkpoint['amp'] = amp.state_dict() - - checkpoint_filename = "checkpoint_{}_{}.pt".format(model_name, epoch) - checkpoint_path = os.path.join( - output_dir, checkpoint_filename) - print("Saving model and optimizer state at epoch {} to {}".format( - epoch, checkpoint_path)) - torch.save(checkpoint, checkpoint_path) - - symlink_src = checkpoint_filename - symlink_dst = os.path.join( - output_dir, "checkpoint_{}_last.pt".format(model_name)) - if os.path.exists(symlink_dst) and os.path.islink(symlink_dst): - print("|||| Updating symlink", symlink_dst, "to point to", symlink_src) - os.remove(symlink_dst) - - os.symlink(symlink_src, symlink_dst) - - -def get_last_checkpoint_filename(output_dir, model_name): - symlink = os.path.join(output_dir, "checkpoint_{}_last.pt".format(model_name)) - if os.path.exists(symlink): - print("|||| Loading checkpoint from symlink", symlink) - return os.path.join(output_dir, os.readlink(symlink)) - else: - print("|||| No last checkpoint available - starting from epoch 0 ") - return "" - - -def load_checkpoint(model, optimizer, epoch, config, amp_run, filepath, local_rank): - - checkpoint = torch.load(filepath, map_location='cpu') - - epoch[0] = checkpoint['epoch']+1 - device_id = local_rank % torch.cuda.device_count() - torch.cuda.set_rng_state(checkpoint['cuda_rng_state_all'][device_id]) - torch.random.set_rng_state(checkpoint['random_rng_states_all'][device_id]) - config = checkpoint['config'] - model.load_state_dict(checkpoint['state_dict']) - optimizer.load_state_dict(checkpoint['optimizer']) - - if amp_run: - amp.load_state_dict(checkpoint['amp']) - - -# adapted from: https://discuss.pytorch.org/t/opinion-eval-should-be-a-context-manager/18998/3 -# Following snippet is licensed under MIT license - -@contextmanager -def evaluating(model): - '''Temporarily switch to evaluation mode.''' - istrain = model.training - try: - model.eval() - yield model - finally: - if istrain: - model.train() - - -def validate(model, criterion, valset, epoch, batch_iter, batch_size, - world_size, collate_fn, distributed_run, rank, batch_to_gpu): - """Handles all the validation scoring and printing""" - with evaluating(model), torch.no_grad(): - val_sampler = DistributedSampler(valset) if distributed_run else None - val_loader = DataLoader(valset, num_workers=1, shuffle=False, - sampler=val_sampler, - batch_size=batch_size, pin_memory=False, - collate_fn=collate_fn) - - val_loss = 0.0 - num_iters = 0 - val_items_per_sec = 0.0 - for i, batch in enumerate(val_loader): - torch.cuda.synchronize() - iter_start_time = time.perf_counter() - - x, y, num_items = batch_to_gpu(batch) - y_pred = model(x) - loss = criterion(y_pred, y) - if distributed_run: - reduced_val_loss = reduce_tensor(loss.data, world_size).item() - reduced_num_items = reduce_tensor(num_items.data, 1).item() - else: # - reduced_val_loss = loss.item() - reduced_num_items = num_items.item() - val_loss += reduced_val_loss - - torch.cuda.synchronize() - iter_stop_time = time.perf_counter() - iter_time = iter_stop_time - iter_start_time - - items_per_sec = reduced_num_items/iter_time - DLLogger.log(step=(epoch, batch_iter, i), data={'val_items_per_sec': items_per_sec}) - val_items_per_sec += items_per_sec - num_iters += 1 - - val_loss = val_loss/(i + 1) - - DLLogger.log(step=(epoch,), data={'val_loss': val_loss}) - DLLogger.log(step=(epoch,), data={'val_items_per_sec': - (val_items_per_sec/num_iters if num_iters > 0 else 0.0)}) - - return val_loss - -def adjust_learning_rate(iteration, epoch, optimizer, learning_rate, - anneal_steps, anneal_factor, rank): - - p = 0 - if anneal_steps is not None: - for i, a_step in enumerate(anneal_steps): - if epoch >= int(a_step): - p = p+1 - - if anneal_factor == 0.3: - lr = learning_rate*((0.1 ** (p//2))*(1.0 if p % 2 == 0 else 0.3)) - else: - lr = learning_rate*(anneal_factor ** p) - - if optimizer.param_groups[0]['lr'] != lr: - DLLogger.log(step=(epoch, iteration), data={'learning_rate changed': str(optimizer.param_groups[0]['lr'])+" -> "+str(lr)}) - - for param_group in optimizer.param_groups: - param_group['lr'] = lr - - -def main(): - - parser = argparse.ArgumentParser(description='PyTorch Tacotron 2 Training') - parser = parse_args(parser) - args, _ = parser.parse_known_args() - - if 'LOCAL_RANK' in os.environ and 'WORLD_SIZE' in os.environ: - local_rank = int(os.environ['LOCAL_RANK']) - world_size = int(os.environ['WORLD_SIZE']) - else: - local_rank = args.rank - world_size = args.world_size - - distributed_run = world_size > 1 - - if local_rank == 0: - DLLogger.init(backends=[JSONStreamBackend(Verbosity.DEFAULT, - args.output+'/'+args.log_file), - StdOutBackend(Verbosity.VERBOSE)]) - else: - DLLogger.init(backends=[]) - - for k,v in vars(args).items(): - DLLogger.log(step="PARAMETER", data={k:v}) - DLLogger.log(step="PARAMETER", data={'model_name':'Tacotron2_PyT'}) - - model_name = args.model_name - parser = models.parse_model_args(model_name, parser) - args, _ = parser.parse_known_args() - - torch.backends.cudnn.enabled = args.cudnn_enabled - torch.backends.cudnn.benchmark = args.cudnn_benchmark - - if distributed_run: - init_distributed(args, world_size, local_rank, args.group_name) - - torch.cuda.synchronize() - run_start_time = time.perf_counter() - - model_config = models.get_model_config(model_name, args) - model = models.get_model(model_name, model_config, - to_cuda=True, - uniform_initialize_bn_weight=not args.disable_uniform_initialize_bn_weight) - - if not args.amp and distributed_run: - model = DDP(model) - - optimizer = torch.optim.Adam(model.parameters(), lr=args.learning_rate, - weight_decay=args.weight_decay) - - if args.amp: - model, optimizer = amp.initialize(model, optimizer, opt_level="O1") - if distributed_run: - model = DDP(model) - - try: - sigma = args.sigma - except AttributeError: - sigma = None - - start_epoch = [0] - - if args.resume_from_last: - args.checkpoint_path = get_last_checkpoint_filename(args.output, model_name) - - if args.checkpoint_path is not "": - load_checkpoint(model, optimizer, start_epoch, model_config, - args.amp, args.checkpoint_path, local_rank) - - start_epoch = start_epoch[0] - - criterion = loss_functions.get_loss_function(model_name, sigma) - - try: - n_frames_per_step = args.n_frames_per_step - except AttributeError: - n_frames_per_step = None - - collate_fn = data_functions.get_collate_function( - model_name, n_frames_per_step) - trainset = data_functions.get_data_loader( - model_name, args.dataset_path, args.training_files, args) - if distributed_run: - train_sampler = DistributedSampler(trainset) - shuffle = False - else: - train_sampler = None - shuffle = True - - train_loader = DataLoader(trainset, num_workers=1, shuffle=shuffle, - sampler=train_sampler, - batch_size=args.batch_size, pin_memory=False, - drop_last=True, collate_fn=collate_fn) - - valset = data_functions.get_data_loader( - model_name, args.dataset_path, args.validation_files, args) - - batch_to_gpu = data_functions.get_batch_to_gpu(model_name) - - iteration = 0 - train_epoch_items_per_sec = 0.0 - val_loss = 0.0 - num_iters = 0 - - model.train() - - for epoch in range(start_epoch, args.epochs): - torch.cuda.synchronize() - epoch_start_time = time.perf_counter() - # used to calculate avg items/sec over epoch - reduced_num_items_epoch = 0 - - train_epoch_items_per_sec = 0.0 - - num_iters = 0 - reduced_loss = 0 - - # if overflow at the last iteration then do not save checkpoint - overflow = False - - if distributed_run: - train_loader.sampler.set_epoch(epoch) - - for i, batch in enumerate(train_loader): - torch.cuda.synchronize() - iter_start_time = time.perf_counter() - DLLogger.log(step=(epoch, i), - data={'glob_iter/iters_per_epoch': str(iteration)+"/"+str(len(train_loader))}) - - adjust_learning_rate(iteration, epoch, optimizer, args.learning_rate, - args.anneal_steps, args.anneal_factor, local_rank) - - model.zero_grad() - x, y, num_items = batch_to_gpu(batch) - - y_pred = model(x) - loss = criterion(y_pred, y) - - if distributed_run: - reduced_loss = reduce_tensor(loss.data, world_size).item() - reduced_num_items = reduce_tensor(num_items.data, 1).item() - else: - reduced_loss = loss.item() - reduced_num_items = num_items.item() - if np.isnan(reduced_loss): - raise Exception("loss is NaN") - - DLLogger.log(step=(epoch,i), data={'train_loss': reduced_loss}) - - num_iters += 1 - - # accumulate number of items processed in this epoch - reduced_num_items_epoch += reduced_num_items - - if args.amp: - with amp.scale_loss(loss, optimizer) as scaled_loss: - scaled_loss.backward() - grad_norm = torch.nn.utils.clip_grad_norm_( - amp.master_params(optimizer), args.grad_clip_thresh) - else: - loss.backward() - grad_norm = torch.nn.utils.clip_grad_norm_( - model.parameters(), args.grad_clip_thresh) - - optimizer.step() - - torch.cuda.synchronize() - iter_stop_time = time.perf_counter() - iter_time = iter_stop_time - iter_start_time - items_per_sec = reduced_num_items/iter_time - train_epoch_items_per_sec += items_per_sec - - DLLogger.log(step=(epoch, i), data={'train_items_per_sec': items_per_sec}) - DLLogger.log(step=(epoch, i), data={'train_iter_time': iter_time}) - iteration += 1 - - torch.cuda.synchronize() - epoch_stop_time = time.perf_counter() - epoch_time = epoch_stop_time - epoch_start_time - - DLLogger.log(step=(epoch,), data={'train_items_per_sec': - (train_epoch_items_per_sec/num_iters if num_iters > 0 else 0.0)}) - DLLogger.log(step=(epoch,), data={'train_loss': reduced_loss}) - DLLogger.log(step=(epoch,), data={'train_epoch_time': epoch_time}) - - val_loss = validate(model, criterion, valset, epoch, iteration, - args.batch_size, world_size, collate_fn, - distributed_run, local_rank, batch_to_gpu) - - if (epoch % args.epochs_per_checkpoint == 0) and args.bench_class == "": - save_checkpoint(model, optimizer, epoch, model_config, - args.amp, args.output, args.model_name, - local_rank, world_size) - if local_rank == 0: - DLLogger.flush() - - torch.cuda.synchronize() - run_stop_time = time.perf_counter() - run_time = run_stop_time - run_start_time - DLLogger.log(step=tuple(), data={'run_time': run_time}) - DLLogger.log(step=tuple(), data={'val_loss': val_loss}) - DLLogger.log(step=tuple(), data={'train_items_per_sec': - (train_epoch_items_per_sec/num_iters if num_iters > 0 else 0.0)}) - - if local_rank == 0: - DLLogger.flush() - -if __name__ == '__main__': - main() diff --git a/demo/Tacotron2/waveglow/arg_parser.py b/demo/Tacotron2/waveglow/arg_parser.py deleted file mode 100644 index 7002bf6d..00000000 --- a/demo/Tacotron2/waveglow/arg_parser.py +++ /dev/null @@ -1,55 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 argparse - -def parse_waveglow_args(parent, add_help=False): - """ - Parse commandline arguments. - """ - parser = argparse.ArgumentParser(parents=[parent], add_help=add_help) - - # misc parameters - parser.add_argument('--n-mel-channels', default=80, type=int, - help='Number of bins in mel-spectrograms') - - # glow parameters - parser.add_argument('--flows', default=12, type=int, - help='Number of steps of flow') - parser.add_argument('--groups', default=8, type=int, - help='Number of samples in a group processed by the steps of flow') - parser.add_argument('--early-every', default=4, type=int, - help='Determines how often (i.e., after how many coupling layers) \ - a number of channels (defined by --early-size parameter) are output\ - to the loss function') - parser.add_argument('--early-size', default=2, type=int, - help='Number of channels output to the loss function') - parser.add_argument('--sigma', default=1.0, type=float, - help='Standard deviation used for sampling from Gaussian') - parser.add_argument('--segment-length', default=4000, type=int, - help='Segment length (audio samples) processed per iteration') - - # wavenet parameters - wavenet = parser.add_argument_group('WaveNet parameters') - wavenet.add_argument('--wn-kernel-size', default=3, type=int, - help='Kernel size for dialted convolution in the affine coupling layer (WN)') - wavenet.add_argument('--wn-channels', default=512, type=int, - help='Number of channels in WN') - wavenet.add_argument('--wn-layers', default=8, type=int, - help='Number of layers in WN') - - return parser diff --git a/demo/Tacotron2/waveglow/data_function.py b/demo/Tacotron2/waveglow/data_function.py deleted file mode 100644 index 62076eba..00000000 --- a/demo/Tacotron2/waveglow/data_function.py +++ /dev/null @@ -1,78 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch -import random -import common.layers as layers -from common.utils import load_wav_to_torch, load_filepaths_and_text, to_gpu - - -class MelAudioLoader(torch.utils.data.Dataset): - """ - 1) loads audio,text pairs - 2) computes mel-spectrograms from audio files. - """ - - def __init__(self, dataset_path, audiopaths_and_text, args): - self.audiopaths_and_text = load_filepaths_and_text(dataset_path, audiopaths_and_text) - self.max_wav_value = args.max_wav_value - self.sampling_rate = args.sampling_rate - self.stft = layers.TacotronSTFT( - args.filter_length, args.hop_length, args.win_length, - args.n_mel_channels, args.sampling_rate, args.mel_fmin, - args.mel_fmax) - self.segment_length = args.segment_length - random.seed(1234) - random.shuffle(self.audiopaths_and_text) - - def get_mel_audio_pair(self, filename): - audio, sampling_rate = load_wav_to_torch(filename) - - if sampling_rate != self.stft.sampling_rate: - raise ValueError("{} {} SR doesn't match target {} SR".format( - sampling_rate, self.stft.sampling_rate)) - - # Take segment - if audio.size(0) >= self.segment_length: - max_audio_start = audio.size(0) - self.segment_length - audio_start = random.randint(0, max_audio_start) - audio = audio[audio_start:audio_start+self.segment_length] - else: - audio = torch.nn.functional.pad( - audio, (0, self.segment_length - audio.size(0)), 'constant').data - - audio = audio / self.max_wav_value - audio_norm = audio.unsqueeze(0) - audio_norm = torch.autograd.Variable(audio_norm, requires_grad=False) - melspec = self.stft.mel_spectrogram(audio_norm) - melspec = melspec.squeeze(0) - - return (melspec, audio, len(audio)) - - def __getitem__(self, index): - return self.get_mel_audio_pair(self.audiopaths_and_text[index][0]) - - def __len__(self): - return len(self.audiopaths_and_text) - - -def batch_to_gpu(batch): - x, y, len_y = batch - x = to_gpu(x).float() - y = to_gpu(y).float() - len_y = to_gpu(torch.sum(len_y)) - return ((x, y), y, len_y) diff --git a/demo/Tacotron2/waveglow/denoiser.py b/demo/Tacotron2/waveglow/denoiser.py deleted file mode 100644 index 5dc2d789..00000000 --- a/demo/Tacotron2/waveglow/denoiser.py +++ /dev/null @@ -1,53 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 sys -sys.path.append('tacotron2') -import torch -from common.layers import STFT - - -class Denoiser(torch.nn.Module): - """ Removes model bias from audio produced with waveglow """ - - def __init__(self, waveglow, filter_length=1024, n_overlap=4, - win_length=1024, mode='zeros'): - super(Denoiser, self).__init__() - device = waveglow.upsample.weight.device - dtype = waveglow.upsample.weight.dtype - self.stft = STFT(filter_length=filter_length, - hop_length=int(filter_length/n_overlap), - win_length=win_length).to(device) - if mode == 'zeros': - mel_input = torch.zeros((1, 80, 88), dtype=dtype, device=device) - elif mode == 'normal': - mel_input = torch.randn((1, 80, 88), dtype=dtype, device=device) - else: - raise Exception("Mode {} if not supported".format(mode)) - - with torch.no_grad(): - bias_audio = waveglow.infer(mel_input, sigma=0.0).float() - bias_spec, _ = self.stft.transform(bias_audio) - - self.register_buffer('bias_spec', bias_spec[:, :, 0][:, :, None]) - - def forward(self, audio, strength=0.1): - audio_spec, audio_angles = self.stft.transform(audio) - audio_spec_denoised = audio_spec - self.bias_spec * strength - audio_spec_denoised = torch.clamp(audio_spec_denoised, 0.0) - audio_denoised = self.stft.inverse(audio_spec_denoised, audio_angles) - return audio_denoised diff --git a/demo/Tacotron2/waveglow/loss_function.py b/demo/Tacotron2/waveglow/loss_function.py deleted file mode 100644 index 75620df9..00000000 --- a/demo/Tacotron2/waveglow/loss_function.py +++ /dev/null @@ -1,38 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch - -class WaveGlowLoss(torch.nn.Module): - def __init__(self, sigma=1.0): - super(WaveGlowLoss, self).__init__() - self.sigma = sigma - - def forward(self, model_output, clean_audio): - # clean_audio is unused; - z, log_s_list, log_det_W_list = model_output - for i, log_s in enumerate(log_s_list): - if i == 0: - log_s_total = torch.sum(log_s) - log_det_W_total = log_det_W_list[i] - else: - log_s_total = log_s_total + torch.sum(log_s) - log_det_W_total += log_det_W_list[i] - - loss = torch.sum( - z * z) / (2 * self.sigma * self.sigma) - log_s_total - log_det_W_total # noqa: E501 - return loss / (z.size(0) * z.size(1) * z.size(2)) diff --git a/demo/Tacotron2/waveglow/model.py b/demo/Tacotron2/waveglow/model.py deleted file mode 100644 index 00a26421..00000000 --- a/demo/Tacotron2/waveglow/model.py +++ /dev/null @@ -1,343 +0,0 @@ -# -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 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 torch -from torch.autograd import Variable -import torch.nn.functional as F -import numpy as np - - -@torch.jit.script -def fused_add_tanh_sigmoid_multiply(input_a, input_b, n_channels): - n_channels_int = n_channels[0] - in_act = input_a + input_b - t_act = torch.tanh(in_act[:, :n_channels_int, :]) - s_act = torch.sigmoid(in_act[:, n_channels_int:, :]) - acts = t_act * s_act - return acts - - -class Invertible1x1Conv(torch.nn.Module): - """ - The layer outputs both the convolution, and the log determinant - of its weight matrix. If reverse=True it does convolution with - inverse - """ - - def __init__(self, c): - super(Invertible1x1Conv, self).__init__() - self.conv = torch.nn.Conv1d(c, c, kernel_size=1, stride=1, padding=0, - bias=False) - - # Sample a random orthonormal matrix to initialize weights - W = torch.qr(torch.FloatTensor(c, c).normal_())[0] - - # Ensure determinant is 1.0 not -1.0 - if torch.det(W) < 0: - W[:, 0] = -1 * W[:, 0] - W = W.view(c, c, 1) - W = W.contiguous() - self.conv.weight.data = W - - def forward(self, z): - # shape - batch_size, group_size, n_of_groups = z.size() - - W = self.conv.weight.squeeze() - - # Forward computation - log_det_W = batch_size * n_of_groups * torch.logdet(W.unsqueeze(0).float()).squeeze() - z = self.conv(z) - return z, log_det_W - - - def infer(self, z): - # shape - batch_size, group_size, n_of_groups = z.size() - - W = self.conv.weight.squeeze() - - if not hasattr(self, 'W_inverse'): - # Reverse computation - W_inverse = W.float().inverse() - W_inverse = Variable(W_inverse[..., None]) - if z.type() == 'torch.cuda.HalfTensor' or z.type() == 'torch.HalfTensor': - W_inverse = W_inverse.half() - self.W_inverse = W_inverse - z = F.conv1d(z, self.W_inverse, bias=None, stride=1, padding=0) - return z - - -class WN(torch.nn.Module): - """ - This is the WaveNet like layer for the affine coupling. The primary - difference from WaveNet is the convolutions need not be causal. There is - also no dilation size reset. The dilation only doubles on each layer - """ - - def __init__(self, n_in_channels, n_mel_channels, n_layers, n_channels, - kernel_size): - super(WN, self).__init__() - assert(kernel_size % 2 == 1) - assert(n_channels % 2 == 0) - self.n_layers = n_layers - self.n_channels = n_channels - self.in_layers = torch.nn.ModuleList() - self.res_skip_layers = torch.nn.ModuleList() - self.cond_layers = torch.nn.ModuleList() - - start = torch.nn.Conv1d(n_in_channels, n_channels, 1) - start = torch.nn.utils.weight_norm(start, name='weight') - self.start = start - - # Initializing last layer to 0 makes the affine coupling layers - # do nothing at first. This helps with training stability - end = torch.nn.Conv1d(n_channels, 2 * n_in_channels, 1) - end.weight.data.zero_() - end.bias.data.zero_() - self.end = end - - for i in range(n_layers): - dilation = 2 ** i - padding = int((kernel_size * dilation - dilation) / 2) - in_layer = torch.nn.Conv1d(n_channels, 2 * n_channels, kernel_size, - dilation=dilation, padding=padding) - in_layer = torch.nn.utils.weight_norm(in_layer, name='weight') - self.in_layers.append(in_layer) - - cond_layer = torch.nn.Conv1d(n_mel_channels, 2 * n_channels, 1) - cond_layer = torch.nn.utils.weight_norm(cond_layer, name='weight') - self.cond_layers.append(cond_layer) - - # last one is not necessary - if i < n_layers - 1: - res_skip_channels = 2 * n_channels - else: - res_skip_channels = n_channels - res_skip_layer = torch.nn.Conv1d(n_channels, res_skip_channels, 1) - res_skip_layer = torch.nn.utils.weight_norm( - res_skip_layer, name='weight') - self.res_skip_layers.append(res_skip_layer) - - def forward(self, forward_input): - audio, spect = forward_input - audio = self.start(audio) - - for i in range(self.n_layers): - acts = fused_add_tanh_sigmoid_multiply( - self.in_layers[i](audio), - self.cond_layers[i](spect), - torch.IntTensor([self.n_channels])) - - res_skip_acts = self.res_skip_layers[i](acts) - if i < self.n_layers - 1: - audio = res_skip_acts[:, :self.n_channels, :] + audio - skip_acts = res_skip_acts[:, self.n_channels:, :] - else: - skip_acts = res_skip_acts - - if i == 0: - output = skip_acts - else: - output = skip_acts + output - return self.end(output) - - -class WaveGlow(torch.nn.Module): - def __init__(self, n_mel_channels, n_flows, n_group, n_early_every, - n_early_size, WN_config): - super(WaveGlow, self).__init__() - - self.upsample = torch.nn.ConvTranspose1d(n_mel_channels, - n_mel_channels, - 1024, stride=256) - assert(n_group % 2 == 0) - self.n_flows = n_flows - self.n_group = n_group - self.n_early_every = n_early_every - self.n_early_size = n_early_size - self.WN = torch.nn.ModuleList() - self.convinv = torch.nn.ModuleList() - - n_half = int(n_group / 2) - - # Set up layers with the right sizes based on how many dimensions - # have been output already - n_remaining_channels = n_group - for k in range(n_flows): - if k % self.n_early_every == 0 and k > 0: - n_half = n_half - int(self.n_early_size / 2) - n_remaining_channels = n_remaining_channels - self.n_early_size - self.convinv.append(Invertible1x1Conv(n_remaining_channels)) - self.WN.append(WN(n_half, n_mel_channels * n_group, **WN_config)) - self.n_remaining_channels = n_remaining_channels - - def forward(self, forward_input): - """ - forward_input[0] = mel_spectrogram: batch x n_mel_channels x frames - forward_input[1] = audio: batch x time - """ - spect, audio = forward_input - - # Upsample spectrogram to size of audio - spect = self.upsample(spect) - assert(spect.size(2) >= audio.size(1)) - if spect.size(2) > audio.size(1): - spect = spect[:, :, :audio.size(1)] - - spect = spect.unfold(2, self.n_group, self.n_group).permute(0, 2, 1, 3) - spect = spect.contiguous().view(spect.size(0), spect.size(1), -1) - spect = spect.permute(0, 2, 1) - - audio = audio.unfold(1, self.n_group, self.n_group).permute(0, 2, 1) - output_audio = [] - log_s_list = [] - log_det_W_list = [] - - for k in range(self.n_flows): - if k % self.n_early_every == 0 and k > 0: - output_audio.append(audio[:, :self.n_early_size, :]) - audio = audio[:, self.n_early_size:, :] - - audio, log_det_W = self.convinv[k](audio) - log_det_W_list.append(log_det_W) - - n_half = int(audio.size(1) / 2) - audio_0 = audio[:, :n_half, :] - audio_1 = audio[:, n_half:, :] - - output = self.WN[k]((audio_0, spect)) - log_s = output[:, n_half:, :] - b = output[:, :n_half, :] - audio_1 = torch.exp(log_s) * audio_1 + b - log_s_list.append(log_s) - - audio = torch.cat([audio_0, audio_1], 1) - - output_audio.append(audio) - return torch.cat(output_audio, 1), log_s_list, log_det_W_list - - def infer(self, spect, sigma=1.0): - - spect = self.upsample(spect) - # trim conv artifacts. maybe pad spec to kernel multiple - time_cutoff = self.upsample.kernel_size[0] - self.upsample.stride[0] - spect = spect[:, :, :-time_cutoff] - - spect = spect.unfold(2, self.n_group, self.n_group).permute(0, 2, 1, 3) - spect = spect.contiguous().view(spect.size(0), spect.size(1), -1) - spect = spect.permute(0, 2, 1) - - audio = torch.randn(spect.size(0), - self.n_remaining_channels, - spect.size(2), device=spect.device).to(spect.dtype) - - audio = torch.autograd.Variable(sigma * audio) - - for k in reversed(range(self.n_flows)): - n_half = int(audio.size(1) / 2) - audio_0 = audio[:, :n_half, :] - audio_1 = audio[:, n_half:, :] - - output = self.WN[k]((audio_0, spect)) - s = output[:, n_half:, :] - b = output[:, :n_half, :] - audio_1 = (audio_1 - b) / torch.exp(s) - audio = torch.cat([audio_0, audio_1], 1) - - audio = self.convinv[k].infer(audio) - - if k % self.n_early_every == 0 and k > 0: - z = torch.randn(spect.size(0), self.n_early_size, spect.size( - 2), device=spect.device).to(spect.dtype) - audio = torch.cat((sigma * z, audio), 1) - - audio = audio.permute( - 0, 2, 1).contiguous().view( - audio.size(0), -1).data - return audio - - - def infer_onnx(self, spect, z, sigma=0.9): - - spect = self.upsample(spect) - # trim conv artifacts. maybe pad spec to kernel multiple - time_cutoff = self.upsample.kernel_size[0] - self.upsample.stride[0] - spect = spect[:, :, :-time_cutoff] - - length_spect_group = spect.size(2)//8 - mel_dim = 80 - batch_size = spect.size(0) - - spect = torch.squeeze(spect, 3) - spect = spect.view((batch_size, mel_dim, length_spect_group, self.n_group)) - spect = spect.permute(0, 2, 1, 3) - spect = spect.contiguous() - spect = spect.view((batch_size, length_spect_group, self.n_group*mel_dim)) - spect = spect.permute(0, 2, 1) - spect = torch.unsqueeze(spect, 3) - spect = spect.contiguous() - - audio = z[:, :self.n_remaining_channels, :, :] - z = z[:, self.n_remaining_channels:self.n_group, :, :] - - # Convert sigma to a torch tensor to ensure constant is exported properly - if audio.type() == 'torch.cuda.HalfTensor' or audio.type() == 'torch.HalfTensor': - sigma = torch.tensor(np.float16(sigma)) - else: - sigma = torch.tensor(np.float32(sigma)) - audio = sigma * audio - - for k in reversed(range(self.n_flows)): - n_half = int(audio.size(1) // 2) - audio_0 = audio[:, :n_half, :, :] - audio_1 = audio[:, n_half:(n_half+n_half), :, :] - - output = self.WN[k]((audio_0, spect)) - s = output[:, n_half:(n_half+n_half), :, :] - b = output[:, :n_half, :, :] - audio_1 = (audio_1 - b) / torch.exp(s) - audio = torch.cat([audio_0, audio_1], 1) - audio = self.convinv[k](audio) - - if k % self.n_early_every == 0 and k > 0: - audio = torch.cat((z[:, :self.n_early_size, :, :], audio), 1) - z = z[:, self.n_early_size:self.n_group, :, :] - - audio = torch.squeeze(audio, 3) - audio = audio.permute(0,2,1).contiguous().view(batch_size, (length_spect_group * self.n_group)) - - return audio - - - @staticmethod - def remove_weightnorm(model): - waveglow = model - for WN in waveglow.WN: - WN.start = torch.nn.utils.remove_weight_norm(WN.start) - WN.in_layers = remove(WN.in_layers) - WN.cond_layers = remove(WN.cond_layers) - WN.res_skip_layers = remove(WN.res_skip_layers) - return waveglow - - -def remove(conv_list): - new_conv_list = torch.nn.ModuleList() - for old_conv in conv_list: - old_conv = torch.nn.utils.remove_weight_norm(old_conv) - new_conv_list.append(old_conv) - return new_conv_list diff --git a/demo/experimental/HuggingFace-Diffusers/README.md b/demo/experimental/HuggingFace-Diffusers/README.md deleted file mode 100644 index d0e4e563..00000000 --- a/demo/experimental/HuggingFace-Diffusers/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Introduction - -This demo notebook showcases the acceleration of Stable Diffusion pipeline using TensorRT through HuggingFace pipelines. - -# Setup - -### Clone the TensorRT OSS repository - -```bash -git clone git@github.com:NVIDIA/TensorRT.git -b release/9.3 --single-branch -cd TensorRT/demo/experimental/HuggingFace-Diffusers -``` - -### Launch TensorRT NGC container - -Install nvidia-docker using [these intructions](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker). Launch the docker container with the following command: - -```bash -docker run --rm -it --gpus all -p 8888:8888 -v $PWD:/workspace nvcr.io/nvidia/tensorrt:23.04-py3 /bin/bash -``` - -### Run Jupyter Notebook - -Install `jupyter` with: - -```bash -pip install jupyter -``` - -Launch the notebook within the container with: - -```bash -jupyter notebook --ip 0.0.0.0 TensorRT-diffusers-txt2img.ipynb --allow-root --no-browser -``` - -Follow the console output for the link to run the notebook on your host machine. diff --git a/demo/experimental/HuggingFace-Diffusers/TensorRT-diffusers-txt2img.ipynb b/demo/experimental/HuggingFace-Diffusers/TensorRT-diffusers-txt2img.ipynb deleted file mode 100644 index 23eb1492..00000000 --- a/demo/experimental/HuggingFace-Diffusers/TensorRT-diffusers-txt2img.ipynb +++ /dev/null @@ -1,1290 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "14941611", - "metadata": {}, - "source": [ - "# Stable Diffusion acceleration with TensorRT" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "47c80a60", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 NVIDIA Corporation. All Rights Reserved.\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# http://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License.\n", - "# ==============================================================================" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7a9c6d74", - "metadata": {}, - "source": [ - "# Install Prerequisites" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "b32d847b", - "metadata": {}, - "outputs": [], - "source": [ - "# Disable warnings if pip is run as root.\n", - "import os\n", - "os.environ['PIP_ROOT_USER_ACTION']='ignore'" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "cd9e73ba", - "metadata": {}, - "outputs": [], - "source": [ - "!python -m pip install --upgrade --quiet pip" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d0214ad4", - "metadata": {}, - "source": [ - "### Check NVIDIA GPU availability\n", - "\n", - "TensorRT acceleration for Diffusion models is available for NVIDIA Turing, Ampere, Ada Lovelace, and Hopper GPUs.\n", - "\n", - "For the following illustration we are using an A100 40GB GPU." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "362193c2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Wed May 3 04:32:55 2023 \n", - "+-----------------------------------------------------------------------------+\n", - "| NVIDIA-SMI 515.44 Driver Version: 515.44 CUDA Version: 12.0 |\n", - "|-------------------------------+----------------------+----------------------+\n", - "| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n", - "| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n", - "| | | MIG M. |\n", - "|===============================+======================+======================|\n", - "| 0 NVIDIA Graphics... Off | 00000000:01:00.0 Off | 0 |\n", - "| 65% 64C P0 81W / 200W | 86MiB / 40960MiB | 0% Default |\n", - "| | | Disabled |\n", - "+-------------------------------+----------------------+----------------------+\n", - " \n", - "+-----------------------------------------------------------------------------+\n", - "| Processes: |\n", - "| GPU GI CI PID Type Process name GPU Memory |\n", - "| ID ID Usage |\n", - "|=============================================================================|\n", - "+-----------------------------------------------------------------------------+\n" - ] - } - ], - "source": [ - "!nvidia-smi" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "c79b497a", - "metadata": {}, - "source": [ - "### Install PyTorch 1.x\n", - "\n", - "NOTE: this is a temporary workaround for ONNX export issues observed in PyTorch 2.0," - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "cabe1586", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install --upgrade --quiet \"torch <2.0.0\"" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "f07ee31c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PyTorch version: 1.14.0a0+44dac51\n" - ] - } - ], - "source": [ - "import torch\n", - "print(f\"PyTorch version: {torch.__version__}\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "f26c0286", - "metadata": {}, - "source": [ - "### Install NVIDIA TensorRT\n", - "\n", - "TensorRT 8.6+ includes Stable Diffusion model optimizations out of the box." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "1e5b96f2", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install --upgrade --quiet \"tensorrt>=8.6\"" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "34a83eb3", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "TensorRT version: 8.6.1\n" - ] - } - ], - "source": [ - "import tensorrt\n", - "print(f\"TensorRT version: {tensorrt.__version__}\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "3a14e192", - "metadata": {}, - "source": [ - "### Install TensorRT Utilities\n", - "\n", - "The TensorRT pipeline implementation in diffusers uses `polygraphy` API to reduce boilerplate code and simplify deployment of ONNX models in TensorRT.\n", - "\n", - "The pipeline also uses `onnx-graphsurgeon` and `onnxruntime` to sanitize (constant folding & shape inference) the exported ONNX models for deployment." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "465c891a", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install --extra-index-url https://pypi.ngc.nvidia.com --upgrade --quiet \"onnx-graphsurgeon\" \"onnxruntime\" \"polygraphy\"" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "3d157e2d", - "metadata": {}, - "source": [ - "### Install HuggingFace libraries\n", - "\n", - "HuggingFace `diffusers` library provides an implementation of the Stable Diffusion pipeline, including the constituent models. TensorRT txt2img pipeline was added in `diffusers` v0.16.0, which is a minimum requirement for the following illustration.\n", - "\n", - "The OpenAI CLIP text encoder and tokenizer models are obtained from HuggingFace `transformers` package." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "2c8f24c9", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install --upgrade --quiet \"accelerate\" \"diffusers>=0.16\" \"transformers\"" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "eef75c7f", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "diffusers version: 0.16.1\n" - ] - } - ], - "source": [ - "import diffusers\n", - "print(f\"diffusers version: {diffusers.__version__}\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "7ee62e33", - "metadata": {}, - "source": [ - "# Run Stable Diffusion\n", - "\n", - "The Stable Diffusion text2image pipeline takes a text prompt as an input and generates an image. A latent seed is used generate an initial random latent of size 64×64 and the text prompt is transformed to text embeddings of size 77×768 by a CLIP text encoder.\n", - "\n", - "Next the U-Net iteratively denoises the random latent representation over a user-specified number of steps while being conditioned on the text embeddings. The output of the U-Net in each iteration is a noise residual which is transformed into denoised latent image representation via a scheduler algorithm.\n", - "\n", - "For more information, see this [blog post](https://huggingface.co/blog/stable_diffusion)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "6892fdee", - "metadata": {}, - "source": [ - "### Import SD pipeline from diffusers\n", - "\n", - "`StableDiffusionPipeline` contains all models required for inference - a tokenizer, `CLIPTextModel` (text encoder), `UNet2DConditionModel` (denoising UNet), and `AutoencoderKL` (VAE decoder)." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "7d3abfe8", - "metadata": {}, - "outputs": [], - "source": [ - "from diffusers.pipelines.stable_diffusion import StableDiffusionPipeline" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "d68630b1", - "metadata": {}, - "source": [ - "### Initialize DDIM scheduler\n", - "\n", - "A custom noise scheduler can be specified by the user. In our example we use [DDIM](https://huggingface.co/docs/diffusers/main/en/api/schedulers/ddim)." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "8c0df48e", - "metadata": {}, - "outputs": [], - "source": [ - "from diffusers import DDIMScheduler\n", - "scheduler = DDIMScheduler.from_pretrained(\"stabilityai/stable-diffusion-2-1\", subfolder=\"scheduler\")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "12fbcdc7", - "metadata": {}, - "source": [ - "### Initialize native txt2img pipeline" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "0e81860f", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e84d2ea17a5247fea357a7499fbc9cc3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Fetching 11 files: 0%| | 0/11 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "prompt = \"a beautiful photograph of Mt. Fuji during cherry blossom\"\n", - "image = pipe(prompt).images[0]\n", - "display(image)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "1b22dd3c", - "metadata": {}, - "source": [ - "# Run Stable Diffusion with TensorRT" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "709ed5d5", - "metadata": {}, - "source": [ - "### Initialize TensorRT txt2img pipeline\n", - "\n", - "TensorRT pipeline initialization is similar to the native pipeline, with a single extra option to specify the path to a [python file containing the TensorRT implementation](https://github.com/huggingface/diffusers/blob/main/examples/community/stable_diffusion_tensorrt_txt2img.py) in diffusers.\n", - "`custom_pipeline=\"stable_diffusion_tensorrt_txt2img\"`" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "fbd7f7a8", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.8/dist-packages/huggingface_hub/file_download.py:649: FutureWarning: 'cached_download' is the legacy way to download files from the HF hub, please consider upgrading to 'hf_hub_download'\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "pipe_trt = StableDiffusionPipeline.from_pretrained(\n", - " \"stabilityai/stable-diffusion-2-1\",\n", - " custom_pipeline=\"stable_diffusion_tensorrt_txt2img\",\n", - " revision='fp16',\n", - " torch_dtype=torch.float16,\n", - " scheduler=scheduler,\n", - " image_height=512,\n", - " image_width=512)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "4e7e6c2e", - "metadata": {}, - "source": [ - "### Specify cache folder name\n", - "\n", - "The ONNX models and TensorRT engines generated during the first inference run will be cached in this folder to speed up subsequent runs." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "9d018680", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b0caad71f89a45ceb6e6f790bfc28f71", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Fetching 16 files: 0%| | 0/16 [00:00= 64:\n", - "/usr/local/lib/python3.8/dist-packages/diffusers/models/unet_2d_condition.py:793: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n", - " if not return_dict:\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "========== Diagnostic Run torch.onnx.export version 1.14.0a0+44dac51 ===========\n", - "verbose: False, log level: Level.ERROR\n", - "======================= 0 NONE 0 NOTE 0 WARNING 0 ERROR ========================\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Generating optimizing model: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/onnx/unet.opt.onnx\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Folding Constants | Pass 1\n", - "[I] Total Nodes | Original: 7757, After Folding: 5379 | 2378 Nodes Folded\n", - "[I] Folding Constants | Pass 2\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2023-05-03 04:35:05.063804462 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.063835442 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.063851254 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/Unsqueeze_6\n", - "2023-05-03 04:35:05.063860151 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/Unsqueeze_2\n", - "2023-05-03 04:35:05.063874574 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.063885250 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.063899247 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/Unsqueeze_6\n", - "2023-05-03 04:35:05.063907147 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/Unsqueeze_2\n", - "2023-05-03 04:35:05.063921177 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.063931750 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.063945862 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/Unsqueeze_6\n", - "2023-05-03 04:35:05.063953915 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/Unsqueeze_2\n", - "2023-05-03 04:35:05.063968177 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.063979539 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.063993087 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/Unsqueeze_6\n", - "2023-05-03 04:35:05.064000857 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/Unsqueeze_2\n", - "2023-05-03 04:35:05.064014245 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064024708 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064038411 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/Unsqueeze_6\n", - "2023-05-03 04:35:05.064046432 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/Unsqueeze_2\n", - "2023-05-03 04:35:05.064060263 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064070926 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064084326 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/Unsqueeze_6\n", - "2023-05-03 04:35:05.064092365 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/Unsqueeze_2\n", - "2023-05-03 04:35:05.064105810 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064116164 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064132918 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/Unsqueeze_6\n", - "2023-05-03 04:35:05.064140796 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/Unsqueeze_2\n", - "2023-05-03 04:35:05.064154446 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064165905 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064179209 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/Unsqueeze_6\n", - "2023-05-03 04:35:05.064187342 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/Unsqueeze_2\n", - "2023-05-03 04:35:05.064201199 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064211740 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064225424 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/Unsqueeze_6\n", - "2023-05-03 04:35:05.064233327 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/Unsqueeze_2\n", - "2023-05-03 04:35:05.064247287 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064257595 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064270903 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/Unsqueeze_6\n", - "2023-05-03 04:35:05.064279133 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/Unsqueeze_2\n", - "2023-05-03 04:35:05.064293283 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064303776 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064317285 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/Unsqueeze_6\n", - "2023-05-03 04:35:05.064325039 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/Unsqueeze_2\n", - "2023-05-03 04:35:05.064339012 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064349129 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064361976 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/Unsqueeze_6\n", - "2023-05-03 04:35:05.064369583 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/Unsqueeze_2\n", - "2023-05-03 04:35:05.064383610 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064394922 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064407734 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/Unsqueeze_6\n", - "2023-05-03 04:35:05.064415531 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/Unsqueeze_2\n", - "2023-05-03 04:35:05.064429270 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064439251 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064452187 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/Unsqueeze_6\n", - "2023-05-03 04:35:05.064459693 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/Unsqueeze_2\n", - "2023-05-03 04:35:05.064473401 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064483399 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064495818 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/Unsqueeze_6\n", - "2023-05-03 04:35:05.064505291 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/Unsqueeze_2\n", - "2023-05-03 04:35:05.064519225 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/transformer_blocks.0/attn2/Unsqueeze_23\n", - "2023-05-03 04:35:05.064529037 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/transformer_blocks.0/attn1/Unsqueeze_23\n", - "2023-05-03 04:35:05.064541710 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/Unsqueeze_6\n", - "2023-05-03 04:35:05.064549728 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/Unsqueeze_2\n", - "2023-05-03 04:35:05.064556692 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064563536 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064570257 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064576986 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064583887 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064590714 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064597349 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064605340 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064612284 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064619173 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064626141 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064633046 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064639818 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064646608 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064653372 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064660184 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/transformer_blocks.0/attn2/Unsqueeze_20\n", - "2023-05-03 04:35:05.064719945 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064730107 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064737080 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064744647 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.2/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064751229 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064758661 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064765408 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064772857 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.1/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064779651 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064787086 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064793967 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064801286 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.3/attentions.0/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064808977 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064816235 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064822982 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064830202 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.2/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064836835 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064844097 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064850828 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064858220 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064864754 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064872009 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064878486 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064885621 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064892385 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064899754 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064906377 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064913714 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.2/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064920540 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064927841 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064934645 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064941859 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064948485 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064956392 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064963043 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064970411 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /up_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.064977125 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.064984371 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.064990976 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.064998100 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /mid_block/attentions.0/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.065005010 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.065012308 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.065018874 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.065026019 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.1/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.065032681 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.065039872 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.065046731 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.065086682 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.2/attentions.0/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.065095119 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.065102513 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.065109193 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.065116469 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.1/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.065123211 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.065130426 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.065138081 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.065145644 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.1/attentions.0/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.065152308 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.065159575 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.065166147 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.065173277 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.1/transformer_blocks.0/attn2/Unsqueeze_7\n", - "2023-05-03 04:35:05.065179792 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/transformer_blocks.0/attn2/Unsqueeze_16\n", - "2023-05-03 04:35:05.065187044 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/transformer_blocks.0/attn2/Unsqueeze_13\n", - "2023-05-03 04:35:05.065193654 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/transformer_blocks.0/attn2/Unsqueeze_10\n", - "2023-05-03 04:35:05.065200911 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /down_blocks.0/attentions.0/transformer_blocks.0/attn2/Unsqueeze_7\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Total Nodes | Original: 5379, After Folding: 4208 | 1171 Nodes Folded\n", - "[I] Folding Constants | Pass 3\n", - "[I] Total Nodes | Original: 4208, After Folding: 4208 | 0 Nodes Folded\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Building Engines...\n", - "Engine build can take a while to complete\n", - "Exporting model: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/onnx/vae.onnx\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "========== Diagnostic Run torch.onnx.export version 1.14.0a0+44dac51 ===========\n", - "verbose: False, log level: Level.ERROR\n", - "======================= 0 NONE 0 NOTE 0 WARNING 0 ERROR ========================\n", - "\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Generating optimizing model: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/onnx/vae.opt.onnx\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Folding Constants | Pass 1\n", - "[I] Total Nodes | Original: 671, After Folding: 500 | 171 Nodes Folded\n", - "[I] Folding Constants | Pass 2\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2023-05-03 04:35:36.443555280 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /decoder/mid_block/attentions.0/Unsqueeze_29\n", - "2023-05-03 04:35:36.443582656 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /decoder/mid_block/attentions.0/Unsqueeze_26\n", - "2023-05-03 04:35:36.443597966 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /decoder/mid_block/attentions.0/Unsqueeze_31\n", - "2023-05-03 04:35:36.443606789 [W:onnxruntime:, unsqueeze_elimination.cc:20 Apply] UnsqueezeElimination cannot remove node /decoder/mid_block/attentions.0/Unsqueeze_1\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Total Nodes | Original: 500, After Folding: 471 | 29 Nodes Folded\n", - "[I] Folding Constants | Pass 3\n", - "[I] Total Nodes | Original: 471, After Folding: 471 | 0 Nodes Folded\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Building TensorRT engine for /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/onnx/clip.opt.onnx: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/clip.plan\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:604] Reading dangerously large protocol message. If the message turns out to be larger than 2147483647 bytes, parsing will be halted for security reasons. To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:81] The total number of bytes read was 681566094\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:604] Reading dangerously large protocol message. If the message turns out to be larger than 2147483647 bytes, parsing will be halted for security reasons. To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:81] The total number of bytes read was 681566094\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[W] onnx2trt_utils.cpp:374: Your ONNX model has been generated with INT64 weights, while TensorRT does not natively support INT64. Attempting to cast down to INT32.\n", - "[I] Configuring with profiles: [Profile().add('input_ids', min=(1, 77), opt=(1, 77), max=(4, 77))]\n", - "[I] Loading tactic timing cache from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/timing_cache\n", - "[W] Timing cache file /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/timing_cache not found, falling back to empty timing cache.\n", - "[I] Building engine with configuration:\n", - " Flags | [FP16]\n", - " Engine Capability | EngineCapability.DEFAULT\n", - " Memory Pools | [WORKSPACE: 40535.88 MiB, TACTIC_DRAM: 40535.88 MiB]\n", - " Tactic Sources | []\n", - " Profiling Verbosity | ProfilingVerbosity.DETAILED\n", - " Preview Features | [DISABLE_EXTERNAL_TACTIC_SOURCES_FOR_CORE_0805]\n", - "[W] kFASTER_DYNAMIC_SHAPES_0805 preview feature is disabled.\n", - "[W] TensorRT encountered issues when converting weights between types and that could affect accuracy.\n", - "[W] If this is not the desired behavior, please modify the weights or retrain with regularization to adjust the magnitude of the weights.\n", - "[W] Check verbose logs for the list of affected weights.\n", - "[W] - 225 weights are affected by this issue: Detected subnormal FP16 values.\n", - "[I] Finished engine building in 146.532 seconds\n", - "[I] Saving tactic timing cache to /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/timing_cache\n", - "[I] Saving engine to /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/clip.plan\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Building TensorRT engine for /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/onnx/unet.opt.onnx: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/unet.plan\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:604] Reading dangerously large protocol message. If the message turns out to be larger than 2147483647 bytes, parsing will be halted for security reasons. To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:81] The total number of bytes read was 1733934759\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:604] Reading dangerously large protocol message. If the message turns out to be larger than 2147483647 bytes, parsing will be halted for security reasons. To increase the limit (or to disable these warnings), see CodedInputStream::SetTotalBytesLimit() in google/protobuf/io/coded_stream.h.\n", - "[libprotobuf WARNING google/protobuf/io/coded_stream.cc:81] The total number of bytes read was 1733934759\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[W] onnx2trt_utils.cpp:400: One or more weights outside the range of INT32 was clamped\n", - "[I] Configuring with profiles: [Profile().add('sample', min=(2, 4, 96, 96), opt=(2, 4, 96, 96), max=(8, 4, 96, 96)).add('encoder_hidden_states', min=(2, 77, 1024), opt=(2, 77, 1024), max=(8, 77, 1024)).add('timestep', min=[1], opt=[1], max=[1])]\n", - "[I] Loading tactic timing cache from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/timing_cache\n", - "[I] Building engine with configuration:\n", - " Flags | [FP16]\n", - " Engine Capability | EngineCapability.DEFAULT\n", - " Memory Pools | [WORKSPACE: 40535.88 MiB, TACTIC_DRAM: 40535.88 MiB]\n", - " Tactic Sources | []\n", - " Profiling Verbosity | ProfilingVerbosity.DETAILED\n", - " Preview Features | [DISABLE_EXTERNAL_TACTIC_SOURCES_FOR_CORE_0805]\n", - "[W] - 272 weights are affected by this issue: Detected subnormal FP16 values.\n", - "[I] Finished engine building in 1032.233 seconds\n", - "[I] Saving tactic timing cache to /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/timing_cache\n", - "[I] Saving engine to /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/unet.plan\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Building TensorRT engine for /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/onnx/vae.opt.onnx: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/vae.plan\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Configuring with profiles: [Profile().add('latent', min=(1, 4, 96, 96), opt=(1, 4, 96, 96), max=(4, 4, 96, 96))]\n", - "[I] Loading tactic timing cache from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/timing_cache\n", - "[I] Building engine with configuration:\n", - " Flags | [FP16]\n", - " Engine Capability | EngineCapability.DEFAULT\n", - " Memory Pools | [WORKSPACE: 40535.88 MiB, TACTIC_DRAM: 40535.88 MiB]\n", - " Tactic Sources | []\n", - " Profiling Verbosity | ProfilingVerbosity.DETAILED\n", - " Preview Features | [DISABLE_EXTERNAL_TACTIC_SOURCES_FOR_CORE_0805]\n", - "[W] - 4 weights are affected by this issue: Detected subnormal FP16 values.\n", - "[I] Finished engine building in 204.808 seconds\n", - "[I] Saving tactic timing cache to /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/timing_cache\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Loading TensorRT engine: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/clip.plan\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Saving engine to /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/vae.plan\n", - "[I] Loading bytes from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/clip.plan\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Loading TensorRT engine: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/unet.plan\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Loading bytes from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/unet.plan\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Loading TensorRT engine: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/vae.plan\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Loading bytes from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/vae.plan\n" - ] - } - ], - "source": [ - "pipe_trt = pipe_trt.to(\"cuda\")" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "c7defb86", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Running inference on device: cuda:0\n", - "Loading TensorRT engine: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/clip.plan\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Loading bytes from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/clip.plan\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Loading TensorRT engine: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/unet.plan\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Loading bytes from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/unet.plan\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Loading TensorRT engine: /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/vae.plan\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[I] Loading bytes from /root/.cache/huggingface/hub/models--stabilityai--stable-diffusion-2-1/snapshots/f7f33030acc57428be85fbec092c37a78231d75a/engine/vae.plan\n" - ] - } - ], - "source": [ - "pipe_trt = pipe_trt.to(\"cuda\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2bdd0eaa", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/root/.cache/huggingface/modules/diffusers_modules/git/stable_diffusion_tensorrt_txt2img.py:907: FutureWarning: Accessing config attribute `in_channels` directly via 'UNet2DConditionModel' object attribute is deprecated. Please access 'in_channels' over 'UNet2DConditionModel's config object instead, e.g. 'unet.config.in_channels'.\n", - " num_channels_latents = self.unet.in_channels\n" - ] - }, - { - "data": { - "image/png": "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YwkNlflwKwKKPtQEStYez93ueMoptXXrfWWMlJL7YSglI+B6s0KEFHPmDIDWueViM8UBUdAYI6Aq0zimiX/z61/+r3/9vyjrhw8fPn/5dOr6x+fnlLMSOu+stamwsQxERTkPU9NULGCtI5MtqhYshYd+MEiq0rYNIhAZY22MyQWbUmSBUAdAAtBQV21Vdee+H0fn3eXFxaubV4RkkOSa+25waC6222bREtDT5wfVosKEcnN9uV61f/rxh7tPn6eh68/naRjczXWOJfYDMEPhYRz78TT24zh1jQmvXr//27/5T2Ocum60WARtaBboA1jbnyMQW4O5TLE/n54f+uc9jRk69ptSe1PKYAz4KhjnEYOvl0jUNKssam3jqxVRKDnVi0212BhnrW/qxZqFkYyrGnRuc3W92e831zery4vQtDaEmzht15dTiqvV5ub2tWSeNX03d3fhWF1eXYaqaZfrZrEkY2obXt/cvHl3a4293z0B6DROWeT7nz7+4+9+F3y9CNU4pcfn5/V6S9a8fv9OWb58fqjqeoylaldvm2XXTw/3Dw+P9zmXy8120zbXF5dpTM+PjylOq9XKGROqsN6uq7oSZhCMcWraan25UdTVclFy6c7nlCOLGGctmWZROesR1FeWyDPnw/N+mCYB7Yfx8mobgu/HIae8XNe2cudufN4dq7pqF+00pa7rlusVevvw6f7D/R1LORwP5+6US5rG6e7xKZVcgUoup/FsJw9ki2axZG2oqwYRfRWw5PPzPo6TU+ucm+XCgIBZloulr0LOWQsX0SoY29RS0rHvhNkugv74gVV+/PDpOPSncexTSipFXkjcc3wz5cIiTgGVrPEIWkpOudDM48CX3f+cLp0pawQoIIAEVhKopiwE7XZJaepztNaTAVIthRXRks1SYi5FBFCd8xa0iAirCCuDIzDWZEEVTXGinErmzIyEzjuyRlimJIbIej+cE0lBZGEQQWMNITFozlwKG8KcEAEtoff+OPSPv/td++P3whCnUoqWIt6a4L2CDkN31vz9x08i5XzslqvF0KfMOaf83//193/+6dNms729uV4/PxsyKFpXrTFIRIDUD/1zN6hIijkMNe0eVSWV3HV9KSlPQyzxcD7207h7PuxPx/3hrIToKGVOXOq2tsY6cFjmhzsbxOvt9vrmxtrKe5+mdNo/G5BAdHl9+f7tX//q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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "prompt = \"a beautiful photograph of Mt. Fuji during cherry blossom\"\n", - "\n", - "# warm up runs to stabilize performance benchmarking\n", - "num_warm_up_steps=5\n", - "for _ in range(num_warm_up_steps):\n", - " _ = pipe_trt(prompt)\n", - "\n", - "image = pipe_trt(prompt).images[0]\n", - "display(image)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docker/build.sh b/docker/build.sh index b24029ae..33f52f55 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/docker/launch.sh b/docker/launch.sh index 2fe9d299..c1b5d05d 100755 --- a/docker/launch.sh +++ b/docker/launch.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/docker/rockylinux8.Dockerfile b/docker/rockylinux8.Dockerfile new file mode 100644 index 00000000..dca7208c --- /dev/null +++ b/docker/rockylinux8.Dockerfile @@ -0,0 +1,105 @@ +# +# 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. +# + +ARG CUDA_VERSION=12.4.0 + +FROM nvidia/cuda:${CUDA_VERSION}-devel-rockylinux8 +LABEL maintainer="NVIDIA CORPORATION" + +ENV CUDA_VERSION_MAJOR_MINOR=12.2 +ENV NV_CUDNN_VERSION 8.9.6.50-1 +ENV NV_CUDNN_PACKAGE libcudnn8-${NV_CUDNN_VERSION}.cuda12.2 +ENV NV_CUDNN_PACKAGE_DEV libcudnn8-devel-${NV_CUDNN_VERSION}.cuda12.2 + +ENV TRT_VERSION 10.0.1.6 +SHELL ["/bin/bash", "-c"] + +RUN dnf install -y \ + ${NV_CUDNN_PACKAGE} \ + ${NV_CUDNN_PACKAGE_DEV} \ + && dnf clean all \ + && rm -rf /var/cache/dnf/* + +# Setup user account +ARG uid=1000 +ARG gid=1000 +RUN groupadd -r -f -g ${gid} trtuser && useradd -o -r -l -u ${uid} -g ${gid} -ms /bin/bash trtuser +RUN usermod -aG wheel trtuser +RUN echo 'trtuser:nvidia' | chpasswd +RUN mkdir -p /workspace && chown trtuser /workspace + +# Install requried packages +RUN dnf -y groupinstall "Development Tools" +RUN dnf -y install \ + openssl-devel \ + bzip2-devel \ + libffi-devel \ + wget \ + perl-core \ + git \ + pkg-config \ + unzip \ + sudo + +# Install python3 +RUN dnf install -y python38 python38-devel &&\ + cd /usr/bin && ln -s /usr/bin/pip3.8 pip; + + +# Install TensorRT +RUN if [ "${CUDA_VERSION:0:2}" = "11" ]; then \ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib64 \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp38-none-linux_x86_64.whl ;\ +elif [ "${CUDA_VERSION:0:2}" = "12" ]; then \ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib64 \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp38-none-linux_x86_64.whl ;\ +else \ + echo "Invalid CUDA_VERSION"; \ + exit 1; \ +fi + +# Install PyPI packages +RUN pip install --upgrade pip +RUN pip install setuptools>=41.0.0 +RUN pip install numpy +RUN pip install jupyter jupyterlab + +# Install Cmake +RUN cd /tmp && \ + wget https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.sh && \ + chmod +x cmake-3.14.4-Linux-x86_64.sh && \ + ./cmake-3.14.4-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir --skip-license && \ + rm ./cmake-3.14.4-Linux-x86_64.sh + +# Download NGC client +RUN cd /usr/local/bin && wget https://ngc.nvidia.com/downloads/ngccli_cat_linux.zip && unzip ngccli_cat_linux.zip && chmod u+x ngc-cli/ngc && rm ngccli_cat_linux.zip ngc-cli.md5 && echo "no-apikey\nascii\n" | ngc-cli/ngc config set + +RUN ln -s /usr/bin/python3 /usr/bin/python + +# Set environment and working directory +ENV TRT_LIBPATH /usr/lib64 +ENV TRT_OSSPATH /workspace/TensorRT +ENV PATH="${PATH}:/usr/local/bin/ngc-cli" +ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${TRT_OSSPATH}/build/out:${TRT_LIBPATH}" +WORKDIR /workspace + +USER trtuser +RUN ["/bin/bash"] diff --git a/docker/rockylinux9.Dockerfile b/docker/rockylinux9.Dockerfile new file mode 100644 index 00000000..ff00512a --- /dev/null +++ b/docker/rockylinux9.Dockerfile @@ -0,0 +1,104 @@ +# +# 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. +# + +ARG CUDA_VERSION=12.4.0 + +FROM nvidia/cuda:${CUDA_VERSION}-devel-rockylinux9 +LABEL maintainer="NVIDIA CORPORATION" + +ENV CUDA_VERSION_MAJOR_MINOR=12.2 +ENV NV_CUDNN_VERSION 8.9.6.50-1 +ENV NV_CUDNN_PACKAGE libcudnn8-${NV_CUDNN_VERSION}.cuda12.2 +ENV NV_CUDNN_PACKAGE_DEV libcudnn8-devel-${NV_CUDNN_VERSION}.cuda12.2 + +ENV TRT_VERSION 10.0.1.6 +SHELL ["/bin/bash", "-c"] + +RUN dnf install -y \ + ${NV_CUDNN_PACKAGE} \ + ${NV_CUDNN_PACKAGE_DEV} \ + && dnf clean all \ + && rm -rf /var/cache/dnf/* + +# Setup user account +ARG uid=1000 +ARG gid=1000 +RUN groupadd -r -f -g ${gid} trtuser && useradd -o -r -l -u ${uid} -g ${gid} -ms /bin/bash trtuser +RUN usermod -aG wheel trtuser +RUN echo 'trtuser:nvidia' | chpasswd +RUN mkdir -p /workspace && chown trtuser /workspace + +# Install python3 +RUN dnf install -y python39 python3-devel && \ + cd /usr/bin && rm pip && ln -s /usr/bin/pip3.9 pip; + +# Install PyPI packages +RUN pip install --upgrade pip +RUN pip install setuptools>=41.0.0 +RUN pip install numpy +RUN pip install jupyter jupyterlab + +# Install requried packages +RUN dnf -y groupinstall "Development Tools" +RUN dnf -y install \ + openssl-devel \ + bzip2-devel \ + libffi-devel \ + wget \ + perl-core \ + git \ + pkg-config \ + unzip \ + sudo + +# Install TensorRT +RUN if [ "${CUDA_VERSION:0:2}" = "11" ]; then \ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib64 \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp39-none-linux_x86_64.whl ;\ +elif [ "${CUDA_VERSION:0:2}" = "12" ]; then \ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib64 \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp39-none-linux_x86_64.whl ;\ +else \ + echo "Invalid CUDA_VERSION"; \ + exit 1; \ +fi + +# Install Cmake +RUN cd /tmp && \ + wget https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.sh && \ + chmod +x cmake-3.14.4-Linux-x86_64.sh && \ + ./cmake-3.14.4-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir --skip-license && \ + rm ./cmake-3.14.4-Linux-x86_64.sh + +# Download NGC client +RUN cd /usr/local/bin && wget https://ngc.nvidia.com/downloads/ngccli_cat_linux.zip && unzip ngccli_cat_linux.zip && chmod u+x ngc-cli/ngc && rm ngccli_cat_linux.zip ngc-cli.md5 && echo "no-apikey\nascii\n" | ngc-cli/ngc config set + +RUN ln -s /usr/bin/python3 /usr/bin/python + +# Set environment and working directory +ENV TRT_LIBPATH /usr/lib64 +ENV TRT_OSSPATH /workspace/TensorRT +ENV PATH="${PATH}:/usr/local/bin/ngc-cli" +ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${TRT_OSSPATH}/build/out:${TRT_LIBPATH}" +WORKDIR /workspace + +USER trtuser +RUN ["/bin/bash"] diff --git a/docker/ubuntu-20.04.Dockerfile b/docker/ubuntu-20.04.Dockerfile index 0049d4c2..7498c124 100644 --- a/docker/ubuntu-20.04.Dockerfile +++ b/docker/ubuntu-20.04.Dockerfile @@ -15,7 +15,7 @@ # limitations under the License. # -ARG CUDA_VERSION=12.3.2 +ARG CUDA_VERSION=12.4.0 FROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu20.04 LABEL maintainer="NVIDIA CORPORATION" @@ -28,7 +28,7 @@ ENV CUDA_VERSION_MAJOR_MINOR=12.2 ENV NV_CUDNN_PACKAGE "libcudnn8=$NV_CUDNN_VERSION-1+cuda${CUDA_VERSION_MAJOR_MINOR}" ENV NV_CUDNN_PACKAGE_DEV "libcudnn8-dev=$NV_CUDNN_VERSION-1+cuda${CUDA_VERSION_MAJOR_MINOR}" -ENV TRT_VERSION 10.0.0.6 +ENV TRT_VERSION 10.0.1.6 SHELL ["/bin/bash", "-c"] RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -84,15 +84,15 @@ RUN apt-get install -y --no-install-recommends \ # Install TensorRT RUN if [ "${CUDA_VERSION:0:2}" = "11" ]; then \ - wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.0/tars/TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ - && tar -xf TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ - && cp -a TensorRT-10.0.0.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ - && pip install TensorRT-10.0.0.6/python/tensorrt-10.0.0b6-cp38-none-linux_x86_64.whl ;\ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp38-none-linux_x86_64.whl ;\ elif [ "${CUDA_VERSION:0:2}" = "12" ]; then \ - wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.0/tars/TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ - && tar -xf TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ - && cp -a TensorRT-10.0.0.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ - && pip install TensorRT-10.0.0.6/python/tensorrt-10.0.0b6-cp38-none-linux_x86_64.whl ;\ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp38-none-linux_x86_64.whl ;\ else \ echo "Invalid CUDA_VERSION"; \ exit 1; \ diff --git a/docker/ubuntu-22.04-aarch64.Dockerfile b/docker/ubuntu-22.04-aarch64.Dockerfile new file mode 100644 index 00000000..ebac9297 --- /dev/null +++ b/docker/ubuntu-22.04-aarch64.Dockerfile @@ -0,0 +1,112 @@ +# +# 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. +# + +ARG CUDA_VERSION=12.4.0 + +# Multi-arch container support available in non-cudnn containers. +FROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu22.04 + +ENV TRT_VERSION 10.0.1.6 +SHELL ["/bin/bash", "-c"] + +# Setup user account +ARG uid=1000 +ARG gid=1000 +RUN groupadd -r -f -g ${gid} trtuser && useradd -o -r -l -u ${uid} -g ${gid} -ms /bin/bash trtuser +RUN usermod -aG sudo trtuser +RUN echo 'trtuser:nvidia' | chpasswd +RUN mkdir -p /workspace && chown trtuser /workspace + +# Required to build Ubuntu 20.04 without user prompts with DLFW container +ENV DEBIAN_FRONTEND=noninteractive + +# Update CUDA signing key +RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/3bf863cc.pub + +# Install requried libraries +RUN apt-get update && apt-get install -y software-properties-common +RUN add-apt-repository ppa:ubuntu-toolchain-r/test +RUN apt-get update && apt-get install -y --no-install-recommends \ + libcurl4-openssl-dev \ + wget \ + git \ + pkg-config \ + sudo \ + ssh \ + libssl-dev \ + pbzip2 \ + pv \ + bzip2 \ + unzip \ + devscripts \ + lintian \ + fakeroot \ + dh-make \ + build-essential + +# Install python3 +RUN apt-get install -y --no-install-recommends \ + python3 \ + python3-pip \ + python3-dev \ + python3-wheel &&\ + cd /usr/local/bin &&\ + ln -s /usr/bin/python3 python &&\ + ln -s /usr/bin/pip3 pip; + +# Install TensorRT. This will also pull in CUDNN +RUN ver="${CUDA_VERSION%.*}" &&\ + if [ "${ver%.*}" = "12" ] ; then \ + ver="12.4"; \ + fi &&\ + v="${TRT_VERSION}-1+cuda${ver}" &&\ + apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/3bf863cc.pub &&\ + apt-get update &&\ + sudo apt-get -y install libnvinfer10=${v} libnvonnxparsers10=${v} libnvinfer-plugin10=${v} \ + libnvinfer-dev=${v} libnvonnxparsers-dev=${v} libnvinfer-plugin-dev=${v} \ + python3-libnvinfer=${v} libnvinfer-dispatch10=${v} libnvinfer-dispatch-dev=${v} libnvinfer-lean10=${v} \ + libnvinfer-lean-dev=${v} libnvinfer-vc-plugin10=${v} libnvinfer-vc-plugin-dev=${v} \ + libnvinfer-headers-dev=${v} libnvinfer-headers-plugin-dev=${v}; + +# Install Cmake +RUN cd /tmp && \ + wget https://github.com/Kitware/CMake/releases/download/v3.21.4/cmake-3.21.4-linux-aarch64.sh && \ + chmod +x cmake-3.21.4-linux-aarch64.sh && \ + ./cmake-3.21.4-linux-aarch64.sh --prefix=/usr/local --exclude-subdir --skip-license && \ + rm ./cmake-3.21.4-linux-aarch64.sh + +# Install PyPI packages +RUN pip3 install --upgrade pip +RUN pip3 install setuptools>=41.0.0 +COPY requirements.txt /tmp/requirements.txt +RUN pip3 install -r /tmp/requirements.txt +RUN pip3 install jupyter jupyterlab +# Workaround to remove numpy installed with tensorflow +RUN pip3 install --upgrade numpy + +# Download NGC client +RUN cd /usr/local/bin && wget https://ngc.nvidia.com/downloads/ngccli_arm64.zip && unzip ngccli_arm64.zip && chmod u+x ngc-cli/ngc && rm ngccli_arm64.zip ngc-cli.md5 && echo "no-apikey\nascii\n" | ngc-cli/ngc config set + +# Set environment and working directory +ENV TRT_LIBPATH /usr/lib/aarch64-linux-gnu/ +ENV TRT_OSSPATH /workspace/TensorRT +ENV PATH="${PATH}:/usr/local/bin/ngc-cli" +ENV LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${TRT_OSSPATH}/build/out:${TRT_LIBPATH}" +WORKDIR /workspace + +USER trtuser +RUN ["/bin/bash"] diff --git a/docker/ubuntu-22.04.Dockerfile b/docker/ubuntu-22.04.Dockerfile index ebe90f71..a7e0d6a1 100644 --- a/docker/ubuntu-22.04.Dockerfile +++ b/docker/ubuntu-22.04.Dockerfile @@ -15,7 +15,7 @@ # limitations under the License. # -ARG CUDA_VERSION=12.3.2 +ARG CUDA_VERSION=12.4.0 FROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu22.04 LABEL maintainer="NVIDIA CORPORATION" @@ -28,7 +28,7 @@ ENV CUDA_VERSION_MAJOR_MINOR=12.2 ENV NV_CUDNN_PACKAGE "libcudnn8=$NV_CUDNN_VERSION-1+cuda${CUDA_VERSION_MAJOR_MINOR}" ENV NV_CUDNN_PACKAGE_DEV "libcudnn8-dev=$NV_CUDNN_VERSION-1+cuda${CUDA_VERSION_MAJOR_MINOR}" -ENV TRT_VERSION 10.0.0.6 +ENV TRT_VERSION 10.0.1.6 SHELL ["/bin/bash", "-c"] RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -49,7 +49,7 @@ RUN mkdir -p /workspace && chown trtuser /workspace ENV DEBIAN_FRONTEND=noninteractive # Update CUDA signing key -RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/3bf863cc.pub +RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/3bf863cc.pub # Install requried libraries RUN apt-get update && apt-get install -y software-properties-common @@ -84,15 +84,15 @@ RUN apt-get install -y --no-install-recommends \ # Install TensorRT RUN if [ "${CUDA_VERSION:0:2}" = "11" ]; then \ - wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.0/tars/TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ - && tar -xf TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ - && cp -a TensorRT-10.0.0.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ - && pip install TensorRT-10.0.0.6/python/tensorrt-10.0.0b6-cp310-none-linux_x86_64.whl ;\ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-11.8.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp310-none-linux_x86_64.whl ;\ elif [ "${CUDA_VERSION:0:2}" = "12" ]; then \ - wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.0/tars/TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ - && tar -xf TensorRT-10.0.0.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ - && cp -a TensorRT-10.0.0.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ - && pip install TensorRT-10.0.0.6/python/tensorrt-10.0.0b6-cp310-none-linux_x86_64.whl ;\ + wget https://developer.nvidia.com/downloads/compute/machine-learning/tensorrt/10.0.1/tars/TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && tar -xf TensorRT-10.0.1.6.Linux.x86_64-gnu.cuda-12.4.tar.gz \ + && cp -a TensorRT-10.0.1.6/lib/*.so* /usr/lib/x86_64-linux-gnu \ + && pip install TensorRT-10.0.1.6/python/tensorrt-10.0.1-cp310-none-linux_x86_64.whl ;\ else \ echo "Invalid CUDA_VERSION"; \ exit 1; \ diff --git a/docker/ubuntu-cross-aarch64.Dockerfile b/docker/ubuntu-cross-aarch64.Dockerfile new file mode 100644 index 00000000..eb2e100b --- /dev/null +++ b/docker/ubuntu-cross-aarch64.Dockerfile @@ -0,0 +1,134 @@ +# +# 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. +# + +ARG CUDA_VERSION=12.4.0 +ARG OS_VERSION=22.04 + +FROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu${OS_VERSION} +LABEL maintainer="NVIDIA CORPORATION" + +ENV TRT_VERSION 10.0.1.6 +ENV DEBIAN_FRONTEND=noninteractive + +ARG uid=1000 +ARG gid=1000 +RUN groupadd -r -f -g ${gid} trtuser && useradd -o -r -l -u ${uid} -g ${gid} -ms /bin/bash trtuser +RUN usermod -aG sudo trtuser +RUN echo 'trtuser:nvidia' | chpasswd +RUN mkdir -p /workspace && chown trtuser /workspace + +# Install requried libraries +RUN apt-get update && apt-get install -y software-properties-common +RUN add-apt-repository ppa:ubuntu-toolchain-r/test +RUN apt-get update && apt-get install -y --no-install-recommends \ + libcurl4-openssl-dev \ + wget \ + git \ + pkg-config \ + python3 \ + python3-pip \ + python3-dev \ + python3-wheel \ + sudo \ + ssh \ + pbzip2 \ + pv \ + bzip2 \ + unzip \ + build-essential + +RUN cd /usr/local/bin &&\ + ln -s /usr/bin/python3 python &&\ + ln -s /usr/bin/pip3 pip +RUN pip3 install --upgrade pip +RUN pip3 install setuptools>=41.0.0 + +# Install Cmake +RUN cd /tmp && \ + wget https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.sh && \ + chmod +x cmake-3.14.4-Linux-x86_64.sh && \ + ./cmake-3.14.4-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir --skip-license && \ + rm ./cmake-3.14.4-Linux-x86_64.sh + +# Skip installing PyPI packages and NGC client on cross-build container + +COPY docker/jetpack_files /pdk_files +COPY scripts/stubify.sh /pdk_files + +# Update CUDA signing keys +RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/3bf863cc.pub + +# Install CUDA cross compile toolchain +RUN dpkg -i /pdk_files/cuda-repo-cross-aarch64*.deb /pdk_files/cuda-repo-ubuntu*_amd64.deb \ + && sudo cp /var/cuda-repo-cross-aarch64*/cuda-*keyring.gpg /usr/share/keyrings/ \ + && sudo cp /var/cuda-repo-ubuntu2204*/cuda-*keyring.gpg /usr/share/keyrings/ \ + && apt-get update \ + && apt-get install -y cuda-cross-aarch64 \ + && rm -rf /var/lib/apt/lists/* + +# Unpack cudnn +RUN dpkg -x /pdk_files/cudnn-local*.deb /pdk_files/cudnn_extract \ + && dpkg -x /pdk_files/cudnn_extract/var/cudnn-local*/libcudnn8_*.deb /pdk_files/cudnn \ + && dpkg -x /pdk_files/cudnn_extract/var/cudnn-local*/libcudnn8-dev*.deb /pdk_files/cudnn \ + && cd /pdk_files/cudnn/usr/lib/aarch64-linux-gnu \ + && cd /pdk_files/cudnn \ + && ln -s usr/include/aarch64-linux-gnu include \ + && ln -s usr/lib/aarch64-linux-gnu lib \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_adv_infer_v[7-9].h /usr/include/cudnn_adv_infer.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_adv_train_v[7-9].h /usr/include/cudnn_adv_train.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_backend_v[7-9].h /usr/include/cudnn_backend.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_cnn_infer_v[7-9].h /usr/include/cudnn_cnn_infer.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_cnn_train_v[7-9].h /usr/include/cudnn_cnn_train.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_ops_infer_v[7-9].h /usr/include/cudnn_ops_infer.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_ops_train_v[7-9].h /usr/include/cudnn_ops_train.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_v[7-9].h /usr/include/cudnn.h \ + && ln -s /pdk_files/cudnn/usr/include/aarch64-linux-gnu/cudnn_version_v[7-9].h /usr/include/cudnn_version.h + +# Unpack libnvinfer +RUN dpkg -x /pdk_files/libnvinfer10_*-1+cuda12.[0-9]_arm64.deb /pdk_files/tensorrt \ + && dpkg -x /pdk_files/libnvinfer-dev_*-1+cuda12.[0-9]_arm64.deb /pdk_files/tensorrt \ + && dpkg -x /pdk_files/libnvinfer-plugin10_*-1+cuda12.[0-9]_arm64.deb /pdk_files/tensorrt \ + && dpkg -x /pdk_files/libnvinfer-plugin-dev_*-1+cuda12.[0-9]_arm64.deb /pdk_files/tensorrt \ + && dpkg -x /pdk_files/libnvonnxparsers10_*-1+cuda12.[0-9]_arm64.deb /pdk_files/tensorrt \ + && dpkg -x /pdk_files/libnvonnxparsers-dev_*-1+cuda12.[0-9]_arm64.deb /pdk_files/tensorrt + +# Clean up debs +RUN rm -rf /pdk_files/*.deb + +# set up librt.so symlink +RUN ln -sf /usr/aarch64-linux-gnu/lib/librt.so.1 /usr/aarch64-linux-gnu/lib/librt.so +RUN ln -sf /usr/lib/aarch64-linux-gnu/librt.so.1 /usr/lib/aarch64-linux-gnu/librt.so + +# create stub libraries +RUN cd /pdk_files/tensorrt \ + && ln -s usr/include/aarch64-linux-gnu include \ + && ln -s usr/lib/aarch64-linux-gnu lib \ + && cd lib \ + && mkdir stubs \ + && for x in nvinfer nvparsers nvinfer_plugin nvonnxparser; \ + do \ + CC=aarch64-linux-gnu-gcc /pdk_files/stubify.sh lib${x}.so stubs/lib${x}.so \ + ; done + +# Set environment and working directory +ENV TRT_LIBPATH /pdk_files/tensorrt/lib +ENV TRT_OSSPATH /workspace/TensorRT +ENV IS_L4T_CROSS True +WORKDIR /workspace + +USER trtuser +RUN ["/bin/bash"] diff --git a/include/NvInfer.h b/include/NvInfer.h index 7fff86b1..c921ede0 100644 --- a/include/NvInfer.h +++ b/include/NvInfer.h @@ -1282,7 +1282,7 @@ class IConvolutionLayer : public ILayer //! //! If executing this layer on DLA, only support 2D padding, both height and width must be in the range [1,32]. //! - //! \see getDilation() + //! \see getDilationNd() //! void setDilationNd(Dims const& dilation) noexcept { @@ -1292,7 +1292,7 @@ class IConvolutionLayer : public ILayer //! //! \brief Get the multi-dimension dilation of the convolution. //! - //! \see setDilation() + //! \see setDilationNd() //! Dims getDilationNd() const noexcept { @@ -3716,10 +3716,9 @@ class IRaggedSoftMaxLayer : public ILayer //! Two types are compatible if they are identical, or are both in {kFLOAT, kHALF}. //! Implicit conversion between incompatible types, i.e. without using setOutputType, //! is recognized as incorrect as of TensorRT 8.4, but is retained for API compatibility -//! within TensorRT 8.x releases. In a future major release the behavior will change -//! to record an error if the network output tensor type is incompatible with the layer -//! output type. E.g., implicit conversion from kFLOAT to kINT32 will not be allowed, -//! and instead such a conversion will require calling setOutputType(DataType::kINT32). +//! within TensorRT 8.x releases. TensorRT 10.0 onwards it is an error if the network output tensor type is incompatible +//! with the layer output type. E.g., implicit conversion from kFLOAT to kINT32 is not allowed, Use +//! setOutputType(DataType::kINT32) to explict convert kFLOAT to kINT32. //! //! \warning Do not inherit from this class, as doing so will break forward-compatibility of the API and ABI. //! @@ -4343,6 +4342,14 @@ class ILoop; //! //! \brief This is a base class for Loop boundary layers. //! +//! The loop boundary layers are used to define loops within a network, enabling the implementation +//! of recurrences. The boundary layers for a loop are created by class ILoop. +//! +//! There are four kinds of boundary layers. +//! * ITripLimitLayer: controls the number of loop iterations. +//! * IIterationLayer: iterates over an input tensor. +//! * IRecurrenceLayer: returns an initial value or value from the previous loop iteration. +//! * ILoopOutputLayer: generates an output tensor from the loop iterations. class ILoopBoundaryLayer : public ILayer { public: @@ -4526,6 +4533,8 @@ class IIfConditional : public INoCopy //! //! \brief A recurrence layer in a network definition. //! +//! The recurrence layer allows a loop iteration to compute a result from a value computed in the previous iteration. +//! class IRecurrenceLayer : public ILoopBoundaryLayer { public: @@ -4641,6 +4650,12 @@ class ILoopOutputLayer : public ILoopBoundaryLayer //! //! \brief A layer that represents a trip-count limiter. //! +//! The trip limit layer sets the execution condition for loops, using kCOUNT to define the number of iterations or +//! kWHILE for a conditional loop. A loop can have one of each kind of limit, in which case the loop exits when +//! the trip count is reached or the condition becomes false. +//! +//! See INetworkDefinition::addTripLimit(). +//! class ITripLimitLayer : public ILoopBoundaryLayer { public: @@ -4662,6 +4677,11 @@ class ITripLimitLayer : public ILoopBoundaryLayer //! //! \brief A layer to do iterations. //! +//! The iterator layer iterates over a tensor along the given axis and in the given direction. +//! It enables each loop iteration to inspect a different slice of the tensor. +//! +//! \see ILoop::addIterator() +//! class IIteratorLayer : public ILoopBoundaryLayer { public: @@ -4715,6 +4735,10 @@ class IIteratorLayer : public ILoopBoundaryLayer //! //! \brief Helper for creating a recurrent subgraph. //! +//! An ILoop defines a loop within a network. It supports the implementation of recurrences, +//! which are crucial for iterative computations, such as RNNs for natural language processing and +//! time-series analysis. +//! class ILoop : public INoCopy { public: @@ -4809,7 +4833,12 @@ class ILoop : public INoCopy //! //! \class ISelectLayer //! -//! \brief A select layer in a network definition. +//! \brief Select elements from two data tensors based on a condition tensor. +//! +//! The select layer makes elementwise selections from two data tensors based on a condition tensor, +//! behaving similarly to the numpy.where function with three parameters. +//! The three input tensors must share the same rank. Multidirectional broadcasting is supported. +//! The output tensor has the dimensions of the inputs AFTER applying the broadcast rule. //! //! \warning Do not inherit from this class, as doing so will break forward-compatibility of the API and ABI. //! @@ -8361,13 +8390,16 @@ enum class MemoryPoolType : int32_t kTACTIC_DRAM = 4, //! - //! kTACTIC_SHARED_MEMORY defines the maximum shared memory size utilized for executing - //! the backend CUDA kernel implementation. Adjust this value to restrict tactics that exceed - //! the specified threshold en masse. The default value is device max capability. This value must + //! kTACTIC_SHARED_MEMORY defines the maximum sum of shared memory reserved by the driver and + //! used for executing CUDA kernels. Adjust this value to restrict tactics that exceed the + //! specified threshold en masse. The default value is device max capability. This value must //! be less than 1GiB. //! + //! The driver reserved shared memory can be queried from cuDeviceGetAttribute(&reservedShmem, + //! CU_DEVICE_ATTRIBUTE_RESERVED_SHARED_MEMORY_PER_BLOCK). + //! //! Updating this flag will override the shared memory limit set by \ref HardwareCompatibilityLevel, - //! which defaults to 48KiB. + //! which defaults to 48KiB - reservedShmem. //! kTACTIC_SHARED_MEMORY = 5, }; @@ -8430,10 +8462,15 @@ enum class HardwareCompatibilityLevel : int32_t //! built. kNONE = 0, - //! Require that the engine is compatible with Ampere and newer GPUs. This will limit the max shared memory usage to - //! 48KiB, may reduce the number of available tactics for each layer, and may prevent some fusions from occurring. - //! Thus this can decrease the performance, especially for tf32 models. + //! Require that the engine is compatible with Ampere and newer GPUs. This will limit the combined usage of driver + //! reserved and backend kernel max shared memory to 48KiB, may reduce the number of available tactics for each + //! layer, and may prevent some fusions from occurring. Thus this can decrease the performance, especially for tf32 + //! models. //! This option will disable cuDNN, cuBLAS, and cuBLAS LT as tactic sources. + //! + //! The driver reserved shared memory can be queried from cuDeviceGetAttribute(&reservedShmem, + //! CU_DEVICE_ATTRIBUTE_RESERVED_SHARED_MEMORY_PER_BLOCK). + //! kAMPERE_PLUS = 1, }; diff --git a/include/NvInferConsistency.h b/include/NvInferConsistency.h index 5096c3f4..32bca28b 100644 --- a/include/NvInferConsistency.h +++ b/include/NvInferConsistency.h @@ -19,7 +19,9 @@ #define NV_INFER_CONSISTENCY_H #include "NvInferConsistencyImpl.h" +#define NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE 1 #include "NvInferRuntimeBase.h" +#undef NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE #include "NvInferRuntimePlugin.h" //! diff --git a/include/NvInferLegacyDims.h b/include/NvInferLegacyDims.h index 204d17a8..2725d184 100644 --- a/include/NvInferLegacyDims.h +++ b/include/NvInferLegacyDims.h @@ -18,7 +18,9 @@ #ifndef NV_INFER_LEGACY_DIMS_H #define NV_INFER_LEGACY_DIMS_H -#include "NvInferRuntimeCommon.h" +#define NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE 1 +#include "NvInferRuntimeBase.h" +#undef NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE //! //! \file NvInferLegacyDims.h diff --git a/include/NvInferRuntimeBase.h b/include/NvInferRuntimeBase.h index 60006e6c..3624706c 100644 --- a/include/NvInferRuntimeBase.h +++ b/include/NvInferRuntimeBase.h @@ -64,9 +64,15 @@ //! //! This file contains common definitions, data structures and interfaces shared between the standard and safe runtime. //! -//! \warning Do not directly include this file. Instead include either NvInferRuntime.h (for the standard runtime) or -//! NvInferSafeRuntime.h (for the safety runtime). -//! +//! \warning Do not directly include this file. Instead include one of: +//! * NvInferRuntime.h (for the standard runtime) +//! * NvInferSafeRuntime.h (for the safety runtime) +//! * NvInferConsistency.h (for consistency checker) +//! * NvInferPluginUtils.h (for plugin utilities) +//! +#if !defined(NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE) && !defined(TRT_VCAST_SAFE) +static_assert(false, "Do not directly include this file. Include NvInferRuntime.h or NvInferSafeRuntime.h or NvInferConsistency.h or NvInferPluginUtils.h"); +#endif //! Forward declare some CUDA types to avoid an include dependency. @@ -864,6 +870,8 @@ class IErrorRecorder : public IVersionedInterface //! //! \brief The length limit for an error description in bytes, excluding the '\0' string terminator. + //! Only applicable to safe runtime. + //! General error recorder implementation can use any size appropriate for the use case. //! static constexpr size_t kMAX_DESC_LENGTH{127U}; @@ -982,10 +990,10 @@ class IErrorRecorder : public IVersionedInterface //! //! \brief Report an error to the error recorder with the corresponding enum and description. //! - //! \param val The error code enum that is being reported. - //! \param desc The string description of the error, which will be a NULL-terminated string of kMAX_DESC_LENGTH - //! bytes or less (excluding the NULL terminator). Descriptions that exceed this limit will be silently - //! truncated. + //! \param val The error code enum that is being reported. + //! \param desc The string description of the error, which will be a NULL-terminated string. + //! For safety use cases its length is limited to kMAX_DESC_LENGTH bytes + //! (excluding the NULL terminator) and descriptions that exceed this limit will be silently truncated. //! //! Report an error to the user that has a given value and human readable description. The function returns false //! if processing can continue, which implies that the reported error is not fatal. This does not guarantee that diff --git a/include/NvInferRuntimeCommon.h b/include/NvInferRuntimeCommon.h index 65a3c220..13e42f4f 100644 --- a/include/NvInferRuntimeCommon.h +++ b/include/NvInferRuntimeCommon.h @@ -28,7 +28,9 @@ //! //! \warning Do not directly include this file. Instead include NvInferRuntime.h //! +#define NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE 1 #include "NvInferRuntimeBase.h" +#undef NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE #include "NvInferRuntimePlugin.h" namespace nvinfer1 diff --git a/include/NvInferRuntimePlugin.h b/include/NvInferRuntimePlugin.h index ecae2ce9..5f97f4a5 100644 --- a/include/NvInferRuntimePlugin.h +++ b/include/NvInferRuntimePlugin.h @@ -18,7 +18,9 @@ #ifndef NV_INFER_RUNTIME_PLUGIN_H #define NV_INFER_RUNTIME_PLUGIN_H +#define NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE 1 #include "NvInferRuntimeBase.h" +#undef NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE //! //! \file NvInferRuntimePlugin.h diff --git a/include/NvInferSafeRuntime.h b/include/NvInferSafeRuntime.h index 1c322c4e..6dc503e0 100644 --- a/include/NvInferSafeRuntime.h +++ b/include/NvInferSafeRuntime.h @@ -18,7 +18,9 @@ #ifndef NV_INFER_SAFE_RUNTIME_H #define NV_INFER_SAFE_RUNTIME_H +#define NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE 1 #include "NvInferRuntimeBase.h" +#undef NV_INFER_INTERNAL_INCLUDE_RUNTIME_BASE #include "NvInferRuntimePlugin.h" #include #include diff --git a/include/NvInferVersion.h b/include/NvInferVersion.h index 8c99bea7..13861a12 100644 --- a/include/NvInferVersion.h +++ b/include/NvInferVersion.h @@ -25,7 +25,7 @@ #define NV_TENSORRT_MAJOR 10 //!< TensorRT major version. #define NV_TENSORRT_MINOR 0 //!< TensorRT minor version. -#define NV_TENSORRT_PATCH 0 //!< TensorRT patch version. +#define NV_TENSORRT_PATCH 1 //!< TensorRT patch version. #define NV_TENSORRT_BUILD 6 //!< TensorRT build number. #define NV_TENSORRT_LWS_MAJOR 0 //!< TensorRT LWS major version. @@ -36,6 +36,6 @@ #define NV_TENSORRT_RELEASE_TYPE_RELEASE_CANDIDATE 1 //!< A release candidate #define NV_TENSORRT_RELEASE_TYPE_GENERAL_AVAILABILITY 2 //!< A final release -#define NV_TENSORRT_RELEASE_TYPE NV_TENSORRT_RELEASE_TYPE_EARLY_ACCESS //!< TensorRT release type +#define NV_TENSORRT_RELEASE_TYPE NV_TENSORRT_RELEASE_TYPE_GENERAL_AVAILABILITY //!< TensorRT release type #endif // NV_INFER_VERSION_H diff --git a/parsers/CMakeLists.txt b/parsers/CMakeLists.txt index 750942e6..6b4858ba 100644 --- a/parsers/CMakeLists.txt +++ b/parsers/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/parsers/common/half.h b/parsers/common/half.h index 7497459a..a66c197c 100644 --- a/parsers/common/half.h +++ b/parsers/common/half.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/parsers/common/ieee_half.h b/parsers/common/ieee_half.h index 071aee09..ac78fd6b 100644 --- a/parsers/common/ieee_half.h +++ b/parsers/common/ieee_half.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/parsers/common/parserUtils.h b/parsers/common/parserUtils.h index 115a2efa..eeb14724 100644 --- a/parsers/common/parserUtils.h +++ b/parsers/common/parserUtils.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/parsers/onnx b/parsers/onnx index 973d68d0..eb43908b 160000 --- a/parsers/onnx +++ b/parsers/onnx @@ -1 +1 @@ -Subproject commit 973d68d06f671998ddcc0c504b9a2fdfcfc85a62 +Subproject commit eb43908b02a296ea0594432f06e9d3fac288d672 diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 2e708d3a..2007b7ed 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,10 +16,10 @@ # add_custom_target(plugin) -set(TARGET_NAME nvinfer_plugin) +set(TARGET_NAME ${nvinfer_plugin_lib_name}) set(SHARED_TARGET ${TARGET_NAME}) set(STATIC_TARGET ${TARGET_NAME}_static) -set(VFC_TARGET_NAME nvinfer_vc_plugin) +set(VFC_TARGET_NAME ${nvinfer_vc_plugin_lib_name}) set(VFC_SHARED_TARGET ${VFC_TARGET_NAME}) set(TARGET_DIR ${CMAKE_CURRENT_SOURCE_DIR}) @@ -143,10 +143,6 @@ else() set_target_properties(${SHARED_TARGET} PROPERTIES LINK_FLAGS "-Wl,--exclude-libs,ALL -Wl,-Bsymbolic -Wl,--version-script=${PLUGIN_EXPORT_MAP} -Wl,--no-undefined") endif() -if (ADDITIONAL_PLATFORM_LIB_FLAGS) - set_target_properties(${SHARED_TARGET} PROPERTIES LINK_FLAGS ${ADDITIONAL_PLATFORM_LIB_FLAGS}) -endif() - set_target_properties(${SHARED_TARGET} PROPERTIES DEBUG_POSTFIX ${TRT_DEBUG_POSTFIX}) set_target_properties(${SHARED_TARGET} PROPERTIES VERSION ${TRT_VERSION} SOVERSION ${TRT_SOVERSION} ) @@ -155,7 +151,7 @@ set_property(TARGET ${SHARED_TARGET} PROPERTY CUDA_STANDARD 14) target_link_libraries(${SHARED_TARGET} ${CUDART_LIB} - ${nvinfer_LIB_PATH} + ${${nvinfer_lib_name}_LIB_PATH} ${CMAKE_DL_LIBS} ) @@ -189,10 +185,6 @@ set_target_properties(${STATIC_TARGET} PROPERTIES set_target_properties(${STATIC_TARGET} PROPERTIES LINK_FLAGS "-Wl,--exclude-libs,ALL") -if (ADDITIONAL_PLATFORM_LIB_FLAGS) - set_target_properties(${STATIC_TARGET} PROPERTIES LINK_FLAGS ${ADDITIONAL_PLATFORM_LIB_FLAGS}) -endif() - set_target_properties(${STATIC_TARGET} PROPERTIES DEBUG_POSTFIX ${TRT_DEBUG_POSTFIX}) set_target_properties(${STATIC_TARGET} PROPERTIES VERSION ${TRT_VERSION} SOVERSION ${TRT_SOVERSION} ) @@ -230,10 +222,6 @@ else() set_target_properties(${VFC_SHARED_TARGET} PROPERTIES LINK_FLAGS "-Wl,--exclude-libs,ALL -Wl,-Bsymbolic -Wl,--version-script=${VFC_PLUGIN_EXPORT_MAP} -Wl,--no-undefined") endif() -if (ADDITIONAL_PLATFORM_LIB_FLAGS) - set_target_properties(${VFC_SHARED_TARGET} PROPERTIES LINK_FLAGS ${ADDITIONAL_PLATFORM_LIB_FLAGS}) -endif() - set_target_properties(${VFC_SHARED_TARGET} PROPERTIES DEBUG_POSTFIX ${TRT_DEBUG_POSTFIX}) set_target_properties(${VFC_SHARED_TARGET} PROPERTIES VERSION ${TRT_VERSION} SOVERSION ${TRT_SOVERSION} ) @@ -242,7 +230,7 @@ set_property(TARGET ${VFC_SHARED_TARGET} PROPERTY CUDA_STANDARD 14) target_link_libraries(${VFC_SHARED_TARGET} ${CUDART_LIB} - ${nvinfer_LIB_PATH} + ${${nvinfer_lib_name}_LIB_PATH} ${CMAKE_DL_LIBS} ) diff --git a/plugin/batchTilePlugin/CMakeLists.txt b/plugin/batchTilePlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/batchTilePlugin/CMakeLists.txt +++ b/plugin/batchTilePlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/batchTilePlugin/batchTilePlugin.cpp b/plugin/batchTilePlugin/batchTilePlugin.cpp index 7b99d578..1e98ac6e 100644 --- a/plugin/batchTilePlugin/batchTilePlugin.cpp +++ b/plugin/batchTilePlugin/batchTilePlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/batchTilePlugin/batchTilePlugin.h b/plugin/batchTilePlugin/batchTilePlugin.h index 0ff85bb0..fe1ce902 100644 --- a/plugin/batchTilePlugin/batchTilePlugin.h +++ b/plugin/batchTilePlugin/batchTilePlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/batchedNMSPlugin/CMakeLists.txt b/plugin/batchedNMSPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/batchedNMSPlugin/CMakeLists.txt +++ b/plugin/batchedNMSPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/batchedNMSPlugin/batchedNMSInference.cu b/plugin/batchedNMSPlugin/batchedNMSInference.cu index 9d01f5b8..2a0ceff3 100644 --- a/plugin/batchedNMSPlugin/batchedNMSInference.cu +++ b/plugin/batchedNMSPlugin/batchedNMSInference.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/batchedNMSPlugin/batchedNMSPlugin.cpp b/plugin/batchedNMSPlugin/batchedNMSPlugin.cpp index 40ff8671..428db1ad 100644 --- a/plugin/batchedNMSPlugin/batchedNMSPlugin.cpp +++ b/plugin/batchedNMSPlugin/batchedNMSPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/batchedNMSPlugin/batchedNMSPlugin.h b/plugin/batchedNMSPlugin/batchedNMSPlugin.h index 418333e8..4c6c749f 100644 --- a/plugin/batchedNMSPlugin/batchedNMSPlugin.h +++ b/plugin/batchedNMSPlugin/batchedNMSPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/batchedNMSPlugin/gatherNMSOutputs.h b/plugin/batchedNMSPlugin/gatherNMSOutputs.h index f245eb93..0e9b78e4 100644 --- a/plugin/batchedNMSPlugin/gatherNMSOutputs.h +++ b/plugin/batchedNMSPlugin/gatherNMSOutputs.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/CMakeLists.txt b/plugin/bertQKVToContextPlugin/CMakeLists.txt index 6bdff6d7..da805cd2 100644 --- a/plugin/bertQKVToContextPlugin/CMakeLists.txt +++ b/plugin/bertQKVToContextPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/CMakeLists.txt b/plugin/bertQKVToContextPlugin/fused_multihead_attention/CMakeLists.txt index 1d53970e..91e05d03 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/CMakeLists.txt +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention.h b/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention.h index d59e8a73..e1b51b9d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention.h +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -32,6 +32,236 @@ namespace nvinfer1 { + +namespace pluginInternal +{ +template +class TFusedMultiHeadAttentionXMMAKernel +{ +public: + using KernelMeta = TKernelMeta; + using KernelParam = TKernelParam; + inline uint64_t hashID(uint32_t s, uint32_t d) const + { + return (uint64_t) s << 32 | d; + } + virtual uint64_t hashID(const KernelMeta& kernelMeta) const + { + return hashID(kernelMeta.mS, kernelMeta.mD); + } + + TFusedMultiHeadAttentionXMMAKernel( + const TKernelMeta* pMetaStart, uint32_t nMetaCount, plugin::bert::Data_type type, uint32_t sm) + : mDataType(type) + , mKernelMeta(pMetaStart) + , mKernelMetaCount(nMetaCount) + , mSM(sm) + { + PLUGIN_ASSERT(mKernelMetaCount && "No kernels were loaded correctly."); + } + + void loadXMMAKernels(uint32_t smVersion) + { + for (uint32_t i = 0; i < mKernelMetaCount; ++i) + { + const auto& kernelMeta = mKernelMeta[i]; + const auto kernelKey = hashID(kernelMeta); + if (kernelMeta.mSM == smVersion && kernelMeta.mDataType == mDataType + && mFunctions.find(kernelKey) == mFunctions.end()) + { + const uint32_t DEFAULT_SMEM_SIZE{48 * 1024}; + if (kernelMeta.mSharedMemBytes >= DEFAULT_SMEM_SIZE) + { + int32_t deviceID{0}; + cudaGetDevice(&deviceID); + int32_t sharedMemPerMultiprocessor{0}; + if (cudaDeviceGetAttribute( + &sharedMemPerMultiprocessor, cudaDevAttrMaxSharedMemoryPerBlockOptin, deviceID) + != cudaSuccess + || sharedMemPerMultiprocessor < static_cast(kernelMeta.mSharedMemBytes)) + { + // skip load function because not enough shared memory to launch the kernel + continue; + } + } + + CUmodule hmod{0}; + auto findModuleIter = mModules.find(kernelMeta.mCubin); + if (findModuleIter != mModules.end()) + { + hmod = findModuleIter->second; + } + else + { + cuErrCheck(mDriver.cuModuleLoadData(&hmod, kernelMeta.mCubin), mDriver); + mModules.insert(std::make_pair(kernelMeta.mCubin, hmod)); + } + + FusedMultiHeadAttentionKernelInfo funcInfo; + funcInfo.mMetaInfoIndex = i; + cuErrCheck(mDriver.cuModuleGetFunction(&funcInfo.mDeviceFunction, hmod, kernelMeta.mFuncName), mDriver); + if (kernelMeta.mSharedMemBytes >= DEFAULT_SMEM_SIZE) + { + if (mDriver.cuFuncSetAttribute(funcInfo.mDeviceFunction, + CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, kernelMeta.mSharedMemBytes) + != CUDA_SUCCESS) + { + // some chip may not have enough shared memory to launch the kernel + continue; + } + } + mFunctions.insert({kernelKey, funcInfo}); + uint64_t const s = kernelMeta.mS; + uint64_t const headSize = kernelMeta.mD; + uint64_t key = (headSize << 32 | s); + if (mValidSequences.find(key) == mValidSequences.end()) + { + mValidSequences.insert(key); + } + } + } + } + + void loadXMMAKernels() + { + if (!mFunctions.empty()) + { + return; + } + + loadXMMAKernels(mSM); + + // sm_86 chips prefer sm_86 sass, but can also use sm_80 sass if sm_86 not exist. + // sm_87 cannot run sm_80 sass + if (mSM == kSM_86) + { + loadXMMAKernels(kSM_80); + } + + // sm_89 will reuse sm_80 and sm_86 kernels + if (mSM == kSM_89) + { + loadXMMAKernels(kSM_86); + loadXMMAKernels(kSM_80); + } + } + + bool isValid(int32_t headSize, int32_t s) const + { + uint64_t key = (static_cast(headSize) << 32 | static_cast(s)); + return (mValidSequences.find(key) != mValidSequences.end()); + } + + virtual void run(TKernelParam& params, cudaStream_t ss) const + { + const auto findIter = mFunctions.find(hashID(params.s, params.d)); + std::stringstream errMsg; + errMsg << "Could not find kernel for:\n" + << "\t s: " << params.s << "\n" + << "\t d: " << params.d << "\n" + << "Was the plugin compiled on a compatible CUDA and SM version?\n" + << "\t Compiled on CUDA " << CUDA_VERSION << "\n" + << "\t Current SM version: " << mSM << "\n" + << "\t SM versions enabled during compilation: " +#if defined(ENABLE_SM72) + << "72 " +#endif +#if defined(ENABLE_SM75) + << "75 " +#endif +#if defined(ENABLE_SM80) + << "80 " +#endif +#if defined(ENABLE_SM86) + << "86 " +#endif +#if defined(ENABLE_SM87) + << "87 " +#endif +#if defined(ENABLE_SM89) + << "89 " +#endif +#if defined(ENABLE_SM90) + << "90 " +#endif + << "\n"; + PLUGIN_VALIDATE(findIter != mFunctions.end(), errMsg.str().c_str()); + + const auto& kernelMeta = mKernelMeta[findIter->second.mMetaInfoIndex]; + const CUfunction func = findIter->second.mDeviceFunction; + + void* kernelParams[] = {¶ms, nullptr}; + cuErrCheck(mDriver.cuLaunchKernel(func, params.h, params.b, 1, kernelMeta.mThreadsPerCTA, 1, 1, + kernelMeta.mSharedMemBytes, ss, kernelParams, nullptr), + mDriver); + } + + virtual ~TFusedMultiHeadAttentionXMMAKernel() = default; + +protected: + nvinfer1::CUDADriverWrapper mDriver; + + plugin::bert::Data_type mDataType; + const TKernelMeta* mKernelMeta; + uint32_t mKernelMetaCount; + uint32_t mSM; + std::unordered_map mModules; + struct FusedMultiHeadAttentionKernelInfo + { + uint32_t mMetaInfoIndex; + CUfunction mDeviceFunction; + }; + std::unordered_map mFunctions; + // Set of valid sequence and head size combination. We use (headSize << 32 | sequence) as key here. + std::unordered_set mValidSequences; +}; +template +class TFusedMHAKernelFactory +{ +public: + const TFusedMHAKernelList* getXMMAKernels(const typename TFusedMHAKernelList::KernelMeta* pKernelList, + uint32_t nbKernels, plugin::bert::Data_type type, uint32_t sm) + { + static std::mutex s_mutex; + std::lock_guard lg(s_mutex); + + const auto id = hashID(type, sm); + const auto findIter = mKernels.find(id); + if (findIter == mKernels.end()) + { + TFusedMHAKernelList* newKernel = new TFusedMHAKernelList{pKernelList, nbKernels, type, sm}; + newKernel->loadXMMAKernels(); + mKernels.insert(std::make_pair(id, std::unique_ptr(newKernel))); + return newKernel; + } + return findIter->second.get(); + } + + static TFusedMHAKernelFactory& Get() + { + static TFusedMHAKernelFactory s_factory; + return s_factory; + } + +private: + TFusedMHAKernelFactory() = default; + + inline uint64_t hashID(plugin::bert::Data_type type, uint32_t sm) const + { + // use deviceID in hasID for multi GPU support before driver support context-less loading of cubin + int32_t deviceID{0}; + CSC(cudaGetDevice(&deviceID), STATUS_FAILURE); + + PLUGIN_ASSERT((deviceID & 0xFFFF) == deviceID); + PLUGIN_ASSERT((type & 0xFFFF) == type); + PLUGIN_ASSERT((sm & 0xFFFFFFFF) == sm); + return (uint64_t) type << 48 | (uint64_t) deviceID << 32 | sm; + } + + std::unordered_map> mKernels; +}; +} // namespace pluginInternal + namespace plugin { namespace bert @@ -324,235 +554,10 @@ static const struct FusedMultiHeadAttentionKernelMetaInfoV1 #endif // defined(ENABLE_SM90) }; -template -class TFusedMultiHeadAttentionXMMAKernel -{ -public: - using KernelMeta = TKernelMeta; - using KernelParam = TKernelParam; - inline uint64_t hashID(uint32_t s, uint32_t d) const - { - return (uint64_t) s << 32 | d; - } - virtual uint64_t hashID(const KernelMeta& kernelMeta) const - { - return hashID(kernelMeta.mS, kernelMeta.mD); - } - - TFusedMultiHeadAttentionXMMAKernel(const TKernelMeta* pMetaStart, uint32_t nMetaCount, Data_type type, uint32_t sm) - : mDataType(type) - , mKernelMeta(pMetaStart) - , mKernelMetaCount(nMetaCount) - , mSM(sm) - { - PLUGIN_ASSERT(mKernelMetaCount && "No kernels were loaded correctly."); - } - - void loadXMMAKernels(uint32_t smVersion) - { - for (uint32_t i = 0; i < mKernelMetaCount; ++i) - { - const auto& kernelMeta = mKernelMeta[i]; - const auto kernelKey = hashID(kernelMeta); - if (kernelMeta.mSM == smVersion && kernelMeta.mDataType == mDataType - && mFunctions.find(kernelKey) == mFunctions.end()) - { - const uint32_t DEFAULT_SMEM_SIZE{48 * 1024}; - if (kernelMeta.mSharedMemBytes >= DEFAULT_SMEM_SIZE) - { - int32_t deviceID{0}; - cudaGetDevice(&deviceID); - int32_t sharedMemPerMultiprocessor{0}; - if (cudaDeviceGetAttribute( - &sharedMemPerMultiprocessor, cudaDevAttrMaxSharedMemoryPerBlockOptin, deviceID) - != cudaSuccess - || sharedMemPerMultiprocessor < static_cast(kernelMeta.mSharedMemBytes)) - { - // skip load function because not enough shared memory to launch the kernel - continue; - } - } - - CUmodule hmod{0}; - auto findModuleIter = mModules.find(kernelMeta.mCubin); - if (findModuleIter != mModules.end()) - { - hmod = findModuleIter->second; - } - else - { - cuErrCheck(mDriver.cuModuleLoadData(&hmod, kernelMeta.mCubin), mDriver); - mModules.insert(std::make_pair(kernelMeta.mCubin, hmod)); - } - - FusedMultiHeadAttentionKernelInfo funcInfo; - funcInfo.mMetaInfoIndex = i; - cuErrCheck(mDriver.cuModuleGetFunction(&funcInfo.mDeviceFunction, hmod, kernelMeta.mFuncName), mDriver); - if (kernelMeta.mSharedMemBytes >= DEFAULT_SMEM_SIZE) - { - if (mDriver.cuFuncSetAttribute(funcInfo.mDeviceFunction, - CU_FUNC_ATTRIBUTE_MAX_DYNAMIC_SHARED_SIZE_BYTES, kernelMeta.mSharedMemBytes) - != CUDA_SUCCESS) - { - // some chip may not have enough shared memory to launch the kernel - continue; - } - } - mFunctions.insert({kernelKey, funcInfo}); - uint64_t const s = kernelMeta.mS; - uint64_t const headSize = kernelMeta.mD; - uint64_t key = (headSize << 32 | s); - if (mValidSequences.find(key) == mValidSequences.end()) - { - mValidSequences.insert(key); - } - } - } - } - - void loadXMMAKernels() - { - if (!mFunctions.empty()) - { - return; - } - - loadXMMAKernels(mSM); - - // sm_86 chips prefer sm_86 sass, but can also use sm_80 sass if sm_86 not exist. - // sm_87 cannot run sm_80 sass - if (mSM == kSM_86) - { - loadXMMAKernels(kSM_80); - } - - // sm_89 will reuse sm_80 and sm_86 kernels - if (mSM == kSM_89) - { - loadXMMAKernels(kSM_86); - loadXMMAKernels(kSM_80); - } - } - - bool isValid(int32_t headSize, int32_t s) const - { - uint64_t key = (static_cast(headSize) << 32 | static_cast(s)); - return (mValidSequences.find(key) != mValidSequences.end()); - } - - virtual void run(TKernelParam& params, cudaStream_t ss) const - { - const auto findIter = mFunctions.find(hashID(params.s, params.d)); - std::stringstream errMsg; - errMsg << "Could not find kernel for:\n" - << "\t s: " << params.s << "\n" - << "\t d: " << params.d << "\n" - << "Was the plugin compiled on a compatible CUDA and SM version?\n" - << "\t Compiled on CUDA " << CUDA_VERSION << "\n" - << "\t Current SM version: " << mSM << "\n" - << "\t SM versions enabled during compilation: " -#if defined(ENABLE_SM72) - << "72 " -#endif -#if defined(ENABLE_SM75) - << "75 " -#endif -#if defined(ENABLE_SM80) - << "80 " -#endif -#if defined(ENABLE_SM86) - << "86 " -#endif -#if defined(ENABLE_SM87) - << "87 " -#endif -#if defined(ENABLE_SM89) - << "89 " -#endif -#if defined(ENABLE_SM90) - << "90 " -#endif - << "\n"; - PLUGIN_VALIDATE(findIter != mFunctions.end(), errMsg.str().c_str()); - - const auto& kernelMeta = mKernelMeta[findIter->second.mMetaInfoIndex]; - const CUfunction func = findIter->second.mDeviceFunction; - - void* kernelParams[] = {¶ms, nullptr}; - cuErrCheck(mDriver.cuLaunchKernel(func, params.h, params.b, 1, kernelMeta.mThreadsPerCTA, 1, 1, - kernelMeta.mSharedMemBytes, ss, kernelParams, nullptr), - mDriver); - } - - virtual ~TFusedMultiHeadAttentionXMMAKernel() = default; - -protected: - nvinfer1::CUDADriverWrapper mDriver; - - Data_type mDataType; - const TKernelMeta* mKernelMeta; - uint32_t mKernelMetaCount; - uint32_t mSM; - std::unordered_map mModules; - struct FusedMultiHeadAttentionKernelInfo - { - uint32_t mMetaInfoIndex; - CUfunction mDeviceFunction; - }; - std::unordered_map mFunctions; - // Set of valid sequence and head size combination. We use (headSize << 32 | sequence) as key here. - std::unordered_set mValidSequences; -}; - -template -class TFusedMHAKernelFactory -{ -public: - const TFusedMHAKernelList* getXMMAKernels( - const typename TFusedMHAKernelList::KernelMeta* pKernelList, uint32_t nbKernels, Data_type type, uint32_t sm) - { - static std::mutex s_mutex; - std::lock_guard lg(s_mutex); - - const auto id = hashID(type, sm); - const auto findIter = mKernels.find(id); - if (findIter == mKernels.end()) - { - TFusedMHAKernelList* newKernel = new TFusedMHAKernelList{pKernelList, nbKernels, type, sm}; - newKernel->loadXMMAKernels(); - mKernels.insert(std::make_pair(id, std::unique_ptr(newKernel))); - return newKernel; - } - return findIter->second.get(); - } - - static TFusedMHAKernelFactory& Get() - { - static TFusedMHAKernelFactory s_factory; - return s_factory; - } - -private: - TFusedMHAKernelFactory() = default; - - inline uint64_t hashID(Data_type type, uint32_t sm) const - { - // use deviceID in hasID for multi GPU support before driver support context-less loading of cubin - int32_t deviceID{0}; - CSC(cudaGetDevice(&deviceID), STATUS_FAILURE); - - PLUGIN_ASSERT((deviceID & 0xFFFF) == deviceID); - PLUGIN_ASSERT((type & 0xFFFF) == type); - PLUGIN_ASSERT((sm & 0xFFFFFFFF) == sm); - return (uint64_t) type << 48 | (uint64_t) deviceID << 32 | sm; - } - - std::unordered_map> mKernels; -}; - using FusedMultiHeadAttentionXMMAKernel - = TFusedMultiHeadAttentionXMMAKernel; -using FusedMHAKernelFactory = TFusedMHAKernelFactory; + = pluginInternal::TFusedMultiHeadAttentionXMMAKernel; +using FusedMHAKernelFactory = pluginInternal::TFusedMHAKernelFactory; inline const FusedMultiHeadAttentionXMMAKernel* getXMMAKernels(Data_type type, uint32_t sm) { diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention_common.h b/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention_common.h index 11d4b954..e1fe7d40 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention_common.h +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/include/fused_multihead_attention_common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm75.cpp index af45426d..9ae4c46d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm80.cpp index 3e5031b1..aef4ae47 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm87.cpp index 0d0a6ed7..6846143d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm90.cpp index a5134aaf..41bd15fa 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_128_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm75.cpp index e2604633..59cadd97 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm80.cpp index 035270eb..ab54f6b9 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm86.cpp index 81f7a887..9189749c 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm87.cpp index 929c0a4b..92e6811e 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm90.cpp index a9592f3f..a2a10d10 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_384_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_512_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_512_64_kernel.sm90.cpp index a5a19772..690e6f42 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_512_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_512_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm75.cpp index 9dc6ffa6..6d8c23da 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm80.cpp index 588d5dc8..34eba769 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm87.cpp index 4d6308d3..9268ddc3 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm90.cpp index fd292683..43b2bd85 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_64_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm75.cpp index 238e9fbd..f345e66c 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm80.cpp index a2eb24f7..c61eb87a 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm87.cpp index 5b39da95..29d128ef 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm90.cpp index 1af3e96a..18e389ca 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_fp16_96_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm75.cpp index a18e4874..26ca9b77 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm80.cpp index 0c079b17..ffb0d50d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm87.cpp index b88a696d..26b7460f 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm90.cpp index 457af2b6..eb18694d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_128_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm75.cpp index 22611907..941996d1 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm80.cpp index bf716793..5fe88e45 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm87.cpp index c4376f86..0d23c4a1 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm90.cpp index 44f159a7..576b0e17 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_384_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_512_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_512_64_kernel.sm90.cpp index fd51119e..6cef65c5 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_512_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_512_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_64_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_64_64_kernel.sm80.cpp index 062ce999..6211cf87 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_64_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_64_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_96_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_96_64_kernel.sm80.cpp index 017f6862..b94a6a7b 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_96_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention/src/fused_multihead_attention_int8_96_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/CMakeLists.txt b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/CMakeLists.txt index 1d53970e..91e05d03 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/CMakeLists.txt +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/include/fused_multihead_attention_v2.h b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/include/fused_multihead_attention_v2.h index bb729359..ecc3684d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/include/fused_multihead_attention_v2.h +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/include/fused_multihead_attention_v2.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -832,14 +832,14 @@ static const struct FusedMultiHeadAttentionKernelMetaInfoV2 }; class FusedMultiHeadAttentionXMMAKernelV2 - : public TFusedMultiHeadAttentionXMMAKernel { public: FusedMultiHeadAttentionXMMAKernelV2( const FusedMultiHeadAttentionKernelMetaInfoV2* pMetaStart, uint32_t nMetaCount, Data_type type, uint32_t sm) - : TFusedMultiHeadAttentionXMMAKernel(pMetaStart, nMetaCount, type, sm) + : pluginInternal::TFusedMultiHeadAttentionXMMAKernel(pMetaStart, nMetaCount, type, sm) { } @@ -988,7 +988,7 @@ class FusedMultiHeadAttentionXMMAKernelV2 } }; -using FusedMHAKernelFactoryV2 = TFusedMHAKernelFactory; +using FusedMHAKernelFactoryV2 = pluginInternal::TFusedMHAKernelFactory; inline const FusedMultiHeadAttentionXMMAKernelV2* getXMMAKernelsV2(Data_type type, uint32_t sm) { diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm75.cpp index 373f496a..d82cc0cb 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm80.cpp index 1e3ff7c6..3f992060 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_32_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm75.cpp index ece2d0eb..c146aa40 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm80.cpp index dbc34090..6ae22e4a 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm86.cpp index ff794f09..f8a98908 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm87.cpp index d957a175..6f3b27e3 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm90.cpp index 910c2772..d56ece44 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_128_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm75.cpp index f466437c..05ffdb23 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm80.cpp index 643f3abe..0d6a6c53 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_32_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm75.cpp index b193aac5..d549443c 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm80.cpp index eedf762f..e8b7ec1d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm86.cpp index 17cdf962..1d7791b3 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm87.cpp index 3943f07e..a03a9a39 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm90.cpp index 8aebf6e4..6e04b4e7 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_256_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm75.cpp index 47d6f8b4..b9f264c2 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm80.cpp index 2c0141c6..1b5e752a 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm86.cpp index 007b0ca5..320a9e88 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm87.cpp index e47a0eb5..e264c016 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm90.cpp index 71047e0d..f9ce8e34 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_384_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm75.cpp index e424fd93..8f766536 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm80.cpp index f3b2aec9..b22936ed 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_32_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm75.cpp index 6706f1e1..624e6e0b 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm80.cpp index 57d31338..c8a9c2f9 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm90.cpp index d9bbd955..a03160a2 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_512_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm75.cpp index a93f1f80..4642ab1a 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm80.cpp index fc6e825e..fae19400 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm86.cpp index dc64aaf1..c5dc1be8 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm87.cpp index 17394f7b..b93318b7 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm90.cpp index 30a6a139..192047d0 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_64_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm75.cpp index 75826861..a4dd7851 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm80.cpp index a5a9db91..9f8557f6 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm86.cpp index 5c0e4792..e45804f5 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm87.cpp index 75cca5b0..f9fd241e 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm90.cpp index 05ed3a7d..93e21e59 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_fp16_96_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_32_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_32_kernel.sm80.cpp index 7377bb87..cf253602 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_32_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_32_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm87.cpp index c486ba74..a446bdc9 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm90.cpp index ff8b71b7..52dd640b 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_128_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm87.cpp index b55a9b29..5f51d0f9 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm90.cpp index a486db0f..2b0aec86 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_192_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm87.cpp index dcac39f3..3cd2a96d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm90.cpp index 9826a2c2..5a3744f1 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_256_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm87.cpp index b6659f16..10b61245 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm90.cpp index bbb5eeeb..b52902cb 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_384_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm80.cpp index f9fd6183..84db4d9b 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm87.cpp index 6441c74a..abdc3f80 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm90.cpp index df8cda25..dc88c038 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_64_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm80.cpp index e62d93aa..014442ea 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm87.cpp index 590c0df4..6d830826 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm90.cpp index be698b64..b345aad7 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_il_int8_96_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm75.cpp index ce3baa27..310bd7b3 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm80.cpp index 0abcf4e3..754f1117 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_32_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm72.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm72.cpp index fbf16481..e8d90371 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm72.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm72.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm75.cpp index 56cb1930..208f99b1 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm80.cpp index f7b86091..28063c3a 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm86.cpp index fe49aaa3..9073a280 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm87.cpp index b84b0dc8..a7c7067b 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm90.cpp index 6f889451..2db0cf89 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_128_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm72.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm72.cpp index 3c3735d1..81f815ab 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm72.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm72.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm75.cpp index dfe6d8ce..c2725c28 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm80.cpp index 8a1d2d2c..9e310f3e 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm86.cpp index 31dd3150..d7c891c6 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm87.cpp index aa2a81c9..a1a73aed 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm90.cpp index a5e4c65e..e2ba6e02 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_192_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm75.cpp index 2a729502..6b74c2fe 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm80.cpp index aeac0ebd..ecb4e343 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_32_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm72.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm72.cpp index a62c2cf9..248e3096 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm72.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm72.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm75.cpp index 3fa33ae5..c9a585ee 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm80.cpp index f597a37e..fe195f5e 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm86.cpp index 24d31716..6afbe0c8 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm87.cpp index b70f696d..f8e37cfb 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm90.cpp index 07f7b870..d170e2f1 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_256_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm72.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm72.cpp index 2d62254b..cb17f7ab 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm72.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm72.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm75.cpp index b373a064..9fbd6434 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm80.cpp index 86517581..d8c78ccd 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm86.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm86.cpp index c9196880..aeac0b9e 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm86.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm86.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm87.cpp index 70e699f8..044654af 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm90.cpp index 848c68be..6028ed75 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_384_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm75.cpp index baaf7441..36ece8b7 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm80.cpp index 68204bf6..590cbecb 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_32_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm75.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm75.cpp index 8ee4ced0..15312cff 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm75.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm75.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm80.cpp index e9bd8613..0cd60732 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm90.cpp index 48644b36..58e28091 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_512_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm80.cpp index 77ccb240..23019c5a 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm87.cpp index 2eb5c132..35635613 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm90.cpp index 2280de3b..4161dcd5 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_64_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm80.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm80.cpp index b7a7f1db..f9056c6d 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm80.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm80.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm87.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm87.cpp index c2e6aca4..e5689381 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm87.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm87.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm90.cpp b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm90.cpp index a4516a2d..427ab9f8 100644 --- a/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm90.cpp +++ b/plugin/bertQKVToContextPlugin/fused_multihead_attention_v2/src/fused_multihead_attention_v2_int8_96_64_kernel.sm90.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/qkvToContextInt8InterleavedPlugin.cpp b/plugin/bertQKVToContextPlugin/qkvToContextInt8InterleavedPlugin.cpp index f62f2c9a..40a42af0 100644 --- a/plugin/bertQKVToContextPlugin/qkvToContextInt8InterleavedPlugin.cpp +++ b/plugin/bertQKVToContextPlugin/qkvToContextInt8InterleavedPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/zeroPadding2d.cu b/plugin/bertQKVToContextPlugin/zeroPadding2d.cu index aa8a70c9..f8135ada 100644 --- a/plugin/bertQKVToContextPlugin/zeroPadding2d.cu +++ b/plugin/bertQKVToContextPlugin/zeroPadding2d.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/bertQKVToContextPlugin/zeroPadding2d.h b/plugin/bertQKVToContextPlugin/zeroPadding2d.h index bc1409a2..faa85ebe 100644 --- a/plugin/bertQKVToContextPlugin/zeroPadding2d.h +++ b/plugin/bertQKVToContextPlugin/zeroPadding2d.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/clipPlugin/CMakeLists.txt b/plugin/clipPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/clipPlugin/CMakeLists.txt +++ b/plugin/clipPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/clipPlugin/clip.cu b/plugin/clipPlugin/clip.cu index f407ebbc..44bc1f73 100644 --- a/plugin/clipPlugin/clip.cu +++ b/plugin/clipPlugin/clip.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/clipPlugin/clip.h b/plugin/clipPlugin/clip.h index 70a53143..e21e8b43 100644 --- a/plugin/clipPlugin/clip.h +++ b/plugin/clipPlugin/clip.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/CMakeLists.txt b/plugin/common/CMakeLists.txt index 12ab940b..af59d7f7 100644 --- a/plugin/common/CMakeLists.txt +++ b/plugin/common/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/common/bboxUtils.h b/plugin/common/bboxUtils.h index 028eeb81..6419611d 100644 --- a/plugin/common/bboxUtils.h +++ b/plugin/common/bboxUtils.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/bertCommon.h b/plugin/common/bertCommon.h index e34e954f..4cb33551 100644 --- a/plugin/common/bertCommon.h +++ b/plugin/common/bertCommon.h @@ -86,6 +86,17 @@ constexpr size_t packedMaskSize384 = xmmasM384 * threadsPerCta384; namespace nvinfer1 { +namespace pluginInternal +{ +template +struct CudaDeleter +{ + void operator()(T* buf) + { + PLUGIN_CUASSERT(cudaFree(buf)); + } +}; +} // namespace pluginInternal namespace plugin { namespace bert @@ -308,16 +319,7 @@ struct CublasConfigHelper }; template -struct CudaDeleter -{ - void operator()(T* buf) - { - PLUGIN_CUASSERT(cudaFree(buf)); - } -}; - -template -using cuda_unique_ptr = std::unique_ptr>; +using cuda_unique_ptr = std::unique_ptr>; template using cuda_shared_ptr = std::shared_ptr; @@ -325,7 +327,7 @@ using cuda_shared_ptr = std::shared_ptr; template void make_cuda_shared(cuda_shared_ptr& ptr, void* cudaMem) { - ptr.reset(static_cast(cudaMem), bert::CudaDeleter()); + ptr.reset(static_cast(cudaMem), pluginInternal::CudaDeleter()); } struct WeightsWithOwnership : public nvinfer1::Weights diff --git a/plugin/common/cub_helper.h b/plugin/common/cub_helper.h index ee8402c4..7cc35848 100644 --- a/plugin/common/cub_helper.h +++ b/plugin/common/cub_helper.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/cudaDriverWrapper.cpp b/plugin/common/cudaDriverWrapper.cpp index 5e317564..fa83866c 100644 --- a/plugin/common/cudaDriverWrapper.cpp +++ b/plugin/common/cudaDriverWrapper.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/cudaDriverWrapper.h b/plugin/common/cudaDriverWrapper.h index b105e3c2..209ed3f8 100644 --- a/plugin/common/cudaDriverWrapper.h +++ b/plugin/common/cudaDriverWrapper.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/dimsHelpers.h b/plugin/common/dimsHelpers.h index 8198590b..239a63ac 100644 --- a/plugin/common/dimsHelpers.h +++ b/plugin/common/dimsHelpers.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/half.h b/plugin/common/half.h index 28825bb1..af49356a 100644 --- a/plugin/common/half.h +++ b/plugin/common/half.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/CMakeLists.txt b/plugin/common/kernels/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/common/kernels/CMakeLists.txt +++ b/plugin/common/kernels/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/common/kernels/bboxDeltas2Proposals.cu b/plugin/common/kernels/bboxDeltas2Proposals.cu index 945d3bc5..0be5e90d 100644 --- a/plugin/common/kernels/bboxDeltas2Proposals.cu +++ b/plugin/common/kernels/bboxDeltas2Proposals.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/cropAndResizeKernel.cu b/plugin/common/kernels/cropAndResizeKernel.cu index aa1bec14..fdae167b 100644 --- a/plugin/common/kernels/cropAndResizeKernel.cu +++ b/plugin/common/kernels/cropAndResizeKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/decodeBbox3DKernels.cu b/plugin/common/kernels/decodeBbox3DKernels.cu index ac53c098..f1592e49 100644 --- a/plugin/common/kernels/decodeBbox3DKernels.cu +++ b/plugin/common/kernels/decodeBbox3DKernels.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/detectionForward.cu b/plugin/common/kernels/detectionForward.cu index 09cba7dd..6f28c15a 100644 --- a/plugin/common/kernels/detectionForward.cu +++ b/plugin/common/kernels/detectionForward.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/extractFgScores.cu b/plugin/common/kernels/extractFgScores.cu index f087e012..1785bf0a 100644 --- a/plugin/common/kernels/extractFgScores.cu +++ b/plugin/common/kernels/extractFgScores.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/generateAnchors.cu b/plugin/common/kernels/generateAnchors.cu index 398cf1b7..b80383f7 100644 --- a/plugin/common/kernels/generateAnchors.cu +++ b/plugin/common/kernels/generateAnchors.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/gridAnchorLayer.cu b/plugin/common/kernels/gridAnchorLayer.cu index 666997c5..2475a943 100644 --- a/plugin/common/kernels/gridAnchorLayer.cu +++ b/plugin/common/kernels/gridAnchorLayer.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/kernel.cpp b/plugin/common/kernels/kernel.cpp index 7f8a00dc..d5c0966a 100644 --- a/plugin/common/kernels/kernel.cpp +++ b/plugin/common/kernels/kernel.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/lReLU.cu b/plugin/common/kernels/lReLU.cu index 8a720ff1..87c42724 100644 --- a/plugin/common/kernels/lReLU.cu +++ b/plugin/common/kernels/lReLU.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/maskRCNNKernels.cu b/plugin/common/kernels/maskRCNNKernels.cu index b79d55e0..0a9d8083 100644 --- a/plugin/common/kernels/maskRCNNKernels.cu +++ b/plugin/common/kernels/maskRCNNKernels.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/maskRCNNKernels.h b/plugin/common/kernels/maskRCNNKernels.h index 71ed0784..433d7ca2 100644 --- a/plugin/common/kernels/maskRCNNKernels.h +++ b/plugin/common/kernels/maskRCNNKernels.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/nmsLayer.cu b/plugin/common/kernels/nmsLayer.cu index 0fdcdf39..8ce2a8f2 100644 --- a/plugin/common/kernels/nmsLayer.cu +++ b/plugin/common/kernels/nmsLayer.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/permuteData.cu b/plugin/common/kernels/permuteData.cu index dd43f04c..185e4c53 100644 --- a/plugin/common/kernels/permuteData.cu +++ b/plugin/common/kernels/permuteData.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/pillarScatterKernels.cu b/plugin/common/kernels/pillarScatterKernels.cu index 528a2665..6ee3c3e8 100644 --- a/plugin/common/kernels/pillarScatterKernels.cu +++ b/plugin/common/kernels/pillarScatterKernels.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/priorBoxLayer.cu b/plugin/common/kernels/priorBoxLayer.cu index 3c6e160b..af17af22 100644 --- a/plugin/common/kernels/priorBoxLayer.cu +++ b/plugin/common/kernels/priorBoxLayer.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/proposalKernel.cu b/plugin/common/kernels/proposalKernel.cu index 8fcaab14..82f2db9b 100644 --- a/plugin/common/kernels/proposalKernel.cu +++ b/plugin/common/kernels/proposalKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/proposalsForward.cu b/plugin/common/kernels/proposalsForward.cu index cab00063..2be3a087 100644 --- a/plugin/common/kernels/proposalsForward.cu +++ b/plugin/common/kernels/proposalsForward.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/reducedMathPlugin.h b/plugin/common/kernels/reducedMathPlugin.h index 777a5e51..d7c17f92 100644 --- a/plugin/common/kernels/reducedMathPlugin.h +++ b/plugin/common/kernels/reducedMathPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/regionForward.cu b/plugin/common/kernels/regionForward.cu index a948dc4f..b33b9b3f 100644 --- a/plugin/common/kernels/regionForward.cu +++ b/plugin/common/kernels/regionForward.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/reorgForward.cu b/plugin/common/kernels/reorgForward.cu index becc87a7..ef5fdb7a 100644 --- a/plugin/common/kernels/reorgForward.cu +++ b/plugin/common/kernels/reorgForward.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/roiPooling.cu b/plugin/common/kernels/roiPooling.cu index abac39a2..353173cc 100644 --- a/plugin/common/kernels/roiPooling.cu +++ b/plugin/common/kernels/roiPooling.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/rproiInferenceFused.cu b/plugin/common/kernels/rproiInferenceFused.cu index 46d0243b..db1161bb 100644 --- a/plugin/common/kernels/rproiInferenceFused.cu +++ b/plugin/common/kernels/rproiInferenceFused.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/sortScoresPerClass.cu b/plugin/common/kernels/sortScoresPerClass.cu index 1ac96086..cd62df64 100644 --- a/plugin/common/kernels/sortScoresPerClass.cu +++ b/plugin/common/kernels/sortScoresPerClass.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/sortScoresPerImage.cu b/plugin/common/kernels/sortScoresPerImage.cu index 2137bc09..99749c53 100644 --- a/plugin/common/kernels/sortScoresPerImage.cu +++ b/plugin/common/kernels/sortScoresPerImage.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/kernels/voxelGeneratorKernels.cu b/plugin/common/kernels/voxelGeneratorKernels.cu index 785a7e63..57b71798 100644 --- a/plugin/common/kernels/voxelGeneratorKernels.cu +++ b/plugin/common/kernels/voxelGeneratorKernels.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/mrcnn_config.h b/plugin/common/mrcnn_config.h index 5b3673ca..88added0 100644 --- a/plugin/common/mrcnn_config.h +++ b/plugin/common/mrcnn_config.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/nmsUtils.h b/plugin/common/nmsUtils.h index 28a4aa7e..8dbd03ff 100644 --- a/plugin/common/nmsUtils.h +++ b/plugin/common/nmsUtils.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/reducedMathPlugin.cpp b/plugin/common/reducedMathPlugin.cpp index 4e33680a..bedd8d2b 100644 --- a/plugin/common/reducedMathPlugin.cpp +++ b/plugin/common/reducedMathPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/serialize.hpp b/plugin/common/serialize.hpp index 8a29dd46..8fcef07f 100644 --- a/plugin/common/serialize.hpp +++ b/plugin/common/serialize.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/templates.h b/plugin/common/templates.h index 298bb8c2..2870bfd6 100644 --- a/plugin/common/templates.h +++ b/plugin/common/templates.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/vfcCommon.cpp b/plugin/common/vfcCommon.cpp index 7122d0d4..8664ab56 100644 --- a/plugin/common/vfcCommon.cpp +++ b/plugin/common/vfcCommon.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/common/vfcCommon.h b/plugin/common/vfcCommon.h index ee84dc97..7b7db007 100644 --- a/plugin/common/vfcCommon.h +++ b/plugin/common/vfcCommon.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/coordConvACPlugin/CMakeLists.txt b/plugin/coordConvACPlugin/CMakeLists.txt index df2f2da8..0e7b1e6e 100644 --- a/plugin/coordConvACPlugin/CMakeLists.txt +++ b/plugin/coordConvACPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/coordConvACPlugin/coordConvACPlugin.cpp b/plugin/coordConvACPlugin/coordConvACPlugin.cpp index 63462fcd..671e06ee 100644 --- a/plugin/coordConvACPlugin/coordConvACPlugin.cpp +++ b/plugin/coordConvACPlugin/coordConvACPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/coordConvACPlugin/coordConvACPlugin.h b/plugin/coordConvACPlugin/coordConvACPlugin.h index 1776d6f7..0df045ce 100644 --- a/plugin/coordConvACPlugin/coordConvACPlugin.h +++ b/plugin/coordConvACPlugin/coordConvACPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/coordConvACPlugin/coordConvACPluginKernels.cu b/plugin/coordConvACPlugin/coordConvACPluginKernels.cu index a0130a16..8f32aa87 100644 --- a/plugin/coordConvACPlugin/coordConvACPluginKernels.cu +++ b/plugin/coordConvACPlugin/coordConvACPluginKernels.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/cropAndResizePlugin/CMakeLists.txt b/plugin/cropAndResizePlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/cropAndResizePlugin/CMakeLists.txt +++ b/plugin/cropAndResizePlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/cropAndResizePlugin/cropAndResizePlugin.cpp b/plugin/cropAndResizePlugin/cropAndResizePlugin.cpp index a0b19fc4..f8d5a731 100644 --- a/plugin/cropAndResizePlugin/cropAndResizePlugin.cpp +++ b/plugin/cropAndResizePlugin/cropAndResizePlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/cropAndResizePlugin/cropAndResizePlugin.h b/plugin/cropAndResizePlugin/cropAndResizePlugin.h index 54c8f16b..c0f9d33d 100644 --- a/plugin/cropAndResizePlugin/cropAndResizePlugin.h +++ b/plugin/cropAndResizePlugin/cropAndResizePlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/decodeBbox3DPlugin/CMakeLists.txt b/plugin/decodeBbox3DPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/decodeBbox3DPlugin/CMakeLists.txt +++ b/plugin/decodeBbox3DPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/decodeBbox3DPlugin/decodeBbox3D.cpp b/plugin/decodeBbox3DPlugin/decodeBbox3D.cpp index f9e9faa5..96884a5b 100644 --- a/plugin/decodeBbox3DPlugin/decodeBbox3D.cpp +++ b/plugin/decodeBbox3DPlugin/decodeBbox3D.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/decodeBbox3DPlugin/decodeBbox3D.h b/plugin/decodeBbox3DPlugin/decodeBbox3D.h index ea85785a..65fbb5ae 100644 --- a/plugin/decodeBbox3DPlugin/decodeBbox3D.h +++ b/plugin/decodeBbox3DPlugin/decodeBbox3D.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/detectionLayerPlugin/CMakeLists.txt b/plugin/detectionLayerPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/detectionLayerPlugin/CMakeLists.txt +++ b/plugin/detectionLayerPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/detectionLayerPlugin/detectionLayerPlugin.cpp b/plugin/detectionLayerPlugin/detectionLayerPlugin.cpp index 840156cd..cd243c11 100644 --- a/plugin/detectionLayerPlugin/detectionLayerPlugin.cpp +++ b/plugin/detectionLayerPlugin/detectionLayerPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/detectionLayerPlugin/detectionLayerPlugin.h b/plugin/detectionLayerPlugin/detectionLayerPlugin.h index adbf535d..88ac12f5 100644 --- a/plugin/detectionLayerPlugin/detectionLayerPlugin.h +++ b/plugin/detectionLayerPlugin/detectionLayerPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/disentangledAttentionPlugin/CMakeLists.txt b/plugin/disentangledAttentionPlugin/CMakeLists.txt index df2f2da8..0e7b1e6e 100644 --- a/plugin/disentangledAttentionPlugin/CMakeLists.txt +++ b/plugin/disentangledAttentionPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.cpp b/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.cpp index c79096a5..d9bf788f 100644 --- a/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.cpp +++ b/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.h b/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.h index 7d77a514..f9d01a4c 100644 --- a/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.h +++ b/plugin/disentangledAttentionPlugin/disentangledAttentionPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/disentangledAttentionPlugin/disentangledKernel.cu b/plugin/disentangledAttentionPlugin/disentangledKernel.cu index f90a98e6..2636fd8f 100644 --- a/plugin/disentangledAttentionPlugin/disentangledKernel.cu +++ b/plugin/disentangledAttentionPlugin/disentangledKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/CMakeLists.txt b/plugin/efficientNMSPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/efficientNMSPlugin/CMakeLists.txt +++ b/plugin/efficientNMSPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/efficientNMSPlugin/efficientNMSInference.cu b/plugin/efficientNMSPlugin/efficientNMSInference.cu index ba99cb56..f3eee1a3 100644 --- a/plugin/efficientNMSPlugin/efficientNMSInference.cu +++ b/plugin/efficientNMSPlugin/efficientNMSInference.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/efficientNMSInference.cuh b/plugin/efficientNMSPlugin/efficientNMSInference.cuh index bf12c359..c16bdb40 100644 --- a/plugin/efficientNMSPlugin/efficientNMSInference.cuh +++ b/plugin/efficientNMSPlugin/efficientNMSInference.cuh @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/efficientNMSInference.h b/plugin/efficientNMSPlugin/efficientNMSInference.h index d9ec3192..fa4749bd 100644 --- a/plugin/efficientNMSPlugin/efficientNMSInference.h +++ b/plugin/efficientNMSPlugin/efficientNMSInference.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/efficientNMSParameters.h b/plugin/efficientNMSPlugin/efficientNMSParameters.h index 89829089..c4b6dc51 100644 --- a/plugin/efficientNMSPlugin/efficientNMSParameters.h +++ b/plugin/efficientNMSPlugin/efficientNMSParameters.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/efficientNMSPlugin.cpp b/plugin/efficientNMSPlugin/efficientNMSPlugin.cpp index 1a8692ae..71836943 100644 --- a/plugin/efficientNMSPlugin/efficientNMSPlugin.cpp +++ b/plugin/efficientNMSPlugin/efficientNMSPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/efficientNMSPlugin.h b/plugin/efficientNMSPlugin/efficientNMSPlugin.h index afceec01..c7248d91 100644 --- a/plugin/efficientNMSPlugin/efficientNMSPlugin.h +++ b/plugin/efficientNMSPlugin/efficientNMSPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/tftrt/CMakeLists.txt b/plugin/efficientNMSPlugin/tftrt/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/efficientNMSPlugin/tftrt/CMakeLists.txt +++ b/plugin/efficientNMSPlugin/tftrt/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.cpp b/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.cpp index f5c86365..3aef2fe6 100644 --- a/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.cpp +++ b/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.h b/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.h index e1e98052..2ad7a2f0 100644 --- a/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.h +++ b/plugin/efficientNMSPlugin/tftrt/efficientNMSExplicitTFTRTPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.cpp b/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.cpp index 25c8e0ef..af75d75d 100644 --- a/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.cpp +++ b/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.h b/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.h index 51b09148..58e07289 100644 --- a/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.h +++ b/plugin/efficientNMSPlugin/tftrt/efficientNMSImplicitTFTRTPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/embLayerNormPlugin/CMakeLists.txt b/plugin/embLayerNormPlugin/CMakeLists.txt index f49d60bd..0fbe405b 100644 --- a/plugin/embLayerNormPlugin/CMakeLists.txt +++ b/plugin/embLayerNormPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/embLayerNormPlugin/embLayerNormKernel.cu b/plugin/embLayerNormPlugin/embLayerNormKernel.cu index a32d14e5..6e6707d7 100644 --- a/plugin/embLayerNormPlugin/embLayerNormKernel.cu +++ b/plugin/embLayerNormPlugin/embLayerNormKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/embLayerNormPlugin/embLayerNormPlugin.cpp b/plugin/embLayerNormPlugin/embLayerNormPlugin.cpp index 8e392b82..ab523971 100644 --- a/plugin/embLayerNormPlugin/embLayerNormPlugin.cpp +++ b/plugin/embLayerNormPlugin/embLayerNormPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/embLayerNormPlugin/embLayerNormPlugin.h b/plugin/embLayerNormPlugin/embLayerNormPlugin.h index eb21d268..5eb40958 100644 --- a/plugin/embLayerNormPlugin/embLayerNormPlugin.h +++ b/plugin/embLayerNormPlugin/embLayerNormPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelHFace.cu b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelHFace.cu index db8f6b06..a23f3326 100644 --- a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelHFace.cu +++ b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelHFace.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelMTron.cu b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelMTron.cu index 95e45820..2fddfe02 100644 --- a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelMTron.cu +++ b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenKernelMTron.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.cpp b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.cpp index 4b6bd72d..4313faa7 100644 --- a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.cpp +++ b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.h b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.h index 80a0cc57..d3141a6b 100644 --- a/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.h +++ b/plugin/embLayerNormPlugin/embLayerNormVarSeqlenPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/exports-vfc_plugin.def b/plugin/exports-vfc_plugin.def index d47954b3..28a79242 100644 --- a/plugin/exports-vfc_plugin.def +++ b/plugin/exports-vfc_plugin.def @@ -1,4 +1,4 @@ -; SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +; 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"); @@ -13,7 +13,7 @@ ; See the License for the specific language governing permissions and ; limitations under the License. -LIBRARY nvinfer_vc_plugin +LIBRARY nvinfer_vc_plugin_10 EXPORTS setLoggerFinder getPluginCreators diff --git a/plugin/exports-vfc_plugin.map b/plugin/exports-vfc_plugin.map index b90d58ce..7171544b 100644 --- a/plugin/exports-vfc_plugin.map +++ b/plugin/exports-vfc_plugin.map @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/exports.def b/plugin/exports.def index 6dac36fe..20503473 100644 --- a/plugin/exports.def +++ b/plugin/exports.def @@ -1,4 +1,4 @@ -; SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +; 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"); @@ -13,7 +13,7 @@ ; See the License for the specific language governing permissions and ; limitations under the License. -LIBRARY nvinfer_plugin +LIBRARY nvinfer_plugin_10 EXPORTS getInferLibVersion getPluginRegistry diff --git a/plugin/exports.map b/plugin/exports.map index 64de08ba..b68b1d16 100644 --- a/plugin/exports.map +++ b/plugin/exports.map @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/fcPlugin/CMakeLists.txt b/plugin/fcPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/fcPlugin/CMakeLists.txt +++ b/plugin/fcPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/fcPlugin/fcPlugin.cpp b/plugin/fcPlugin/fcPlugin.cpp index c98ae433..fd0c1339 100644 --- a/plugin/fcPlugin/fcPlugin.cpp +++ b/plugin/fcPlugin/fcPlugin.cpp @@ -140,7 +140,7 @@ void nvinfer1::plugin::bert::LtGemmSearch(cublasLtHandle_t ltHandle, cublasOpera void const* A, int32_t const& lda, void const* B, int32_t const& ldb, void const* beta, // host pointer void* C, int32_t const& ldc, void* workSpace, size_t workSpaceSize, cublasComputeType_t computeType, cudaDataType_t scaleType, cudaDataType_t Atype, cudaDataType_t Btype, cudaDataType_t Ctype, - std::vector& perfResults) + std::vector& perfResults, cudaStream_t stream) { cublasStatus_t status = CUBLAS_STATUS_SUCCESS; @@ -153,7 +153,6 @@ void nvinfer1::plugin::bert::LtGemmSearch(cublasLtHandle_t ltHandle, cublasOpera cudaEvent_t startEvent = nullptr; cudaEvent_t stopEvent = nullptr; - cudaStream_t stream = nullptr; CublasLtWrapper& cublasLtWrapper = getCublasLtWrapper(); @@ -520,13 +519,20 @@ void FCPluginDynamic::configurePlugin(DynamicPluginTensorDesc const* inputs, int if (mAlgo.data[0] == 0 && memcmp(mAlgo.data, mAlgo.data + 1, sizeof(mAlgo.data) - sizeof(mAlgo.data[0])) == 0) { gLogVerbose << "FCPluginDynamic gemmSearch\n"; + if (mSharedStream == nullptr) + { + SharedStream ss{}; + mSharedStream = static_cast( + getPluginRegistry()->acquirePluginResource(kFCPLUGIN_SHARED_STREAM_KEY, &ss)) + ->mStream; + } if (mType == DataType::kFLOAT) { - mAlgo = gemmSearch(mOutDim, mNmax, mK, kMAX_WORKSPACE_BYTES, actualWorkspace); + mAlgo = gemmSearch(mOutDim, mNmax, mK, kMAX_WORKSPACE_BYTES, actualWorkspace, mSharedStream); } else if (mType == DataType::kHALF) { - mAlgo = gemmSearch(mOutDim, mNmax, mK, kMAX_WORKSPACE_BYTES, actualWorkspace); + mAlgo = gemmSearch(mOutDim, mNmax, mK, kMAX_WORKSPACE_BYTES, actualWorkspace, mSharedStream); } } @@ -656,6 +662,11 @@ int32_t FCPluginDynamic::initialize() noexcept void FCPluginDynamic::terminate() noexcept { gLogVerbose << "FCPluginDynamic terminate\n"; + if (mSharedStream) + { + TRT_UNUSED(getPluginRegistry()->releasePluginResource(kFCPLUGIN_SHARED_STREAM_KEY)); + mSharedStream = nullptr; + } } size_t FCPluginDynamic::getSerializationSize() const noexcept diff --git a/plugin/fcPlugin/fcPlugin.h b/plugin/fcPlugin/fcPlugin.h index 1ba56f7b..855ce96d 100644 --- a/plugin/fcPlugin/fcPlugin.h +++ b/plugin/fcPlugin/fcPlugin.h @@ -31,6 +31,67 @@ namespace nvinfer1 { + +namespace pluginInternal +{ +class SharedStream : public IPluginResource +{ +public: + SharedStream(bool init = false) + { + if (init) + { + PLUGIN_CUASSERT(cudaStreamCreate(&mStream)); + } + } + + void free() + { + if (mStream != nullptr) + { + PLUGIN_CUASSERT(cudaStreamDestroy(mStream)); + mStream = nullptr; + } + } + + int32_t release() noexcept override + { + try + { + free(); + } + catch (std::exception const& e) + { + return -1; + } + return 0; + } + + IPluginResource* clone() noexcept override + { + std::unique_ptr cloned{}; + try + { + cloned = std::make_unique(/* init */ true); + } + catch (std::exception const& e) + { + return nullptr; + } + return cloned.release(); + } + + ~SharedStream() override + { + if (mStream) + { + free(); + } + } + + cudaStream_t mStream{nullptr}; +}; +} // namespace pluginInternal namespace plugin { namespace bert @@ -41,6 +102,8 @@ struct GemmTypes { }; +char const* const kFCPLUGIN_SHARED_STREAM_KEY{"fcPlugin_timing_key"}; + template <> struct GemmTypes { @@ -174,11 +237,12 @@ void LtGemmSearch(nvinfer1::pluginInternal::cublasLtHandle_t ltHandle, cudaDataType_t Atype, cudaDataType_t Btype, cudaDataType_t Ctype, - std::vector &perfResults); + std::vector &perfResults, + cudaStream_t stream); // clang-format on template void LtGemmSearch(nvinfer1::pluginInternal::cublasLtHandle_t ltHandle, Gemm const& g, void* workSpace, - size_t workSpaceSize, std::vector& perfResults) + size_t workSpaceSize, std::vector& perfResults, cudaStream_t stream) { // clang-format off LtGemmSearch( @@ -203,7 +267,8 @@ void LtGemmSearch(nvinfer1::pluginInternal::cublasLtHandle_t ltHandle, Gemm c Gemm::Types::cudaTypeI, Gemm::Types::cudaTypeI, Gemm::Types::cudaTypeO, - perfResults + perfResults, + stream ); // clang-format on } @@ -380,29 +445,30 @@ struct AlgoProps }; template -nvinfer1::pluginInternal::cublasLtMatmulAlgo_t gemmSearch( - int32_t const m, int32_t const n, int32_t const k, size_t const workspaceSize, size_t& actualWorkspace) +nvinfer1::pluginInternal::cublasLtMatmulAlgo_t gemmSearch(int32_t const m, int32_t const n, int32_t const k, + size_t const workspaceSize, size_t& actualWorkspace, cudaStream_t& stream) { Gemm g(m, n, k, false, false); std::vector perfResults(kNB_ALGO_COMBINATIONS); - PLUGIN_CUASSERT(cudaMalloc(reinterpret_cast(&g.A), g.bytesA)); - PLUGIN_CUASSERT(cudaMalloc(reinterpret_cast(&g.B), g.bytesB)); - PLUGIN_CUASSERT(cudaMalloc(reinterpret_cast(&g.C), g.bytesC)); + PLUGIN_CUASSERT(cudaMallocAsync(reinterpret_cast(&g.A), g.bytesA, stream)); + PLUGIN_CUASSERT(cudaMallocAsync(reinterpret_cast(&g.B), g.bytesB, stream)); + PLUGIN_CUASSERT(cudaMallocAsync(reinterpret_cast(&g.C), g.bytesC, stream)); void* workspace; - PLUGIN_CUASSERT(cudaMalloc(&workspace, workspaceSize)); + PLUGIN_CUASSERT(cudaMallocAsync(&workspace, workspaceSize, stream)); nvinfer1::pluginInternal::cublasLtHandle_t lt; nvinfer1::pluginInternal::CublasLtWrapper& cublasLtWrapper = nvinfer1::pluginInternal::getCublasLtWrapper(); PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtCreate(<)); - LtGemmSearch(lt, g, workspace, workspaceSize, perfResults); - PLUGIN_CUASSERT(cudaDeviceSynchronize()); + + LtGemmSearch(lt, g, workspace, workspaceSize, perfResults, stream); + PLUGIN_CUASSERT(cudaStreamSynchronize(stream)); PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtDestroy(lt)); - PLUGIN_CUASSERT(cudaFree(workspace)); + PLUGIN_CUASSERT(cudaFreeAsync(workspace, stream)); - PLUGIN_CUASSERT(cudaFree(g.A)); - PLUGIN_CUASSERT(cudaFree(g.B)); - PLUGIN_CUASSERT(cudaFree(g.C)); + PLUGIN_CUASSERT(cudaFreeAsync(g.A, stream)); + PLUGIN_CUASSERT(cudaFreeAsync(g.B, stream)); + PLUGIN_CUASSERT(cudaFreeAsync(g.C, stream)); actualWorkspace = perfResults[0].workspaceSize; return perfResults[0].algo; @@ -410,27 +476,28 @@ nvinfer1::pluginInternal::cublasLtMatmulAlgo_t gemmSearch( template nvinfer1::pluginInternal::cublasLtMatmulAlgo_t gemmSearch( - Gemm& g, size_t const workspaceSize, size_t& actualWorkspace) + Gemm& g, size_t const workspaceSize, size_t& actualWorkspace, cudaStream_t& stream) { std::vector perfResults(kNB_ALGO_COMBINATIONS); - PLUGIN_CUASSERT(cudaMalloc(&g.A, g.bytesA)); - PLUGIN_CUASSERT(cudaMalloc(&g.B, g.bytesB)); - PLUGIN_CUASSERT(cudaMalloc(&g.C, g.bytesC)); + PLUGIN_CUASSERT(cudaMallocAsync(&g.A, g.bytesA, stream)); + PLUGIN_CUASSERT(cudaMallocAsync(&g.B, g.bytesB, stream)); + PLUGIN_CUASSERT(cudaMallocAsync(&g.C, g.bytesC, stream)); void* workspace; - PLUGIN_CUASSERT(cudaMalloc(&workspace, workspaceSize)); + PLUGIN_CUASSERT(cudaMallocAsync(&workspace, workspaceSize, stream)); nvinfer1::pluginInternal::cublasLtHandle_t lt; nvinfer1::pluginInternal::CublasLtWrapper& cublasLtWrapper = nvinfer1::pluginInternal::getCublasLtWrapper(); PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtCreate(<)); - LtGemmSearch(lt, g, workspace, workspaceSize, perfResults); - PLUGIN_CUASSERT(cudaDeviceSynchronize()); + + LtGemmSearch(lt, g, workspace, workspaceSize, perfResults, stream); + PLUGIN_CUASSERT(cudaStreamSynchronize(stream)); PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtDestroy(lt)); - PLUGIN_CUASSERT(cudaFree(workspace)); + PLUGIN_CUASSERT(cudaFreeAsync(workspace, stream)); - PLUGIN_CUASSERT(cudaFree(g.A)); - PLUGIN_CUASSERT(cudaFree(g.B)); - PLUGIN_CUASSERT(cudaFree(g.C)); + PLUGIN_CUASSERT(cudaFreeAsync(g.A, stream)); + PLUGIN_CUASSERT(cudaFreeAsync(g.B, stream)); + PLUGIN_CUASSERT(cudaFreeAsync(g.C, stream)); actualWorkspace = perfResults[0].workspaceSize; return perfResults[0].algo; @@ -500,6 +567,7 @@ class FCPluginDynamic : public nvinfer1::IPluginV2DynamicExt bert::cuda_unique_ptr mWdev; LtContext mLtContext; + cudaStream_t mSharedStream{nullptr}; }; class FCPluginDynamicCreator : public nvinfer1::IPluginCreator @@ -527,6 +595,7 @@ class FCPluginDynamicCreator : public nvinfer1::IPluginCreator static std::vector mPluginAttributes; std::string mNamespace; }; + } // namespace bert } // namespace plugin } // namespace nvinfer1 diff --git a/plugin/flattenConcat/CMakeLists.txt b/plugin/flattenConcat/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/flattenConcat/CMakeLists.txt +++ b/plugin/flattenConcat/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/geluPlugin/CMakeLists.txt b/plugin/geluPlugin/CMakeLists.txt index f49d60bd..0fbe405b 100644 --- a/plugin/geluPlugin/CMakeLists.txt +++ b/plugin/geluPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/geluPlugin/geluKernel.cu b/plugin/geluPlugin/geluKernel.cu index 823ae803..fd7f8d54 100644 --- a/plugin/geluPlugin/geluKernel.cu +++ b/plugin/geluPlugin/geluKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/geluPlugin/geluPlugin.cpp b/plugin/geluPlugin/geluPlugin.cpp index ca0d775f..dc6d48f8 100644 --- a/plugin/geluPlugin/geluPlugin.cpp +++ b/plugin/geluPlugin/geluPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/geluPlugin/geluPlugin.h b/plugin/geluPlugin/geluPlugin.h index 14bc0f6a..724d4ee8 100644 --- a/plugin/geluPlugin/geluPlugin.h +++ b/plugin/geluPlugin/geluPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/generateDetectionPlugin/CMakeLists.txt b/plugin/generateDetectionPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/generateDetectionPlugin/CMakeLists.txt +++ b/plugin/generateDetectionPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/generateDetectionPlugin/generateDetectionPlugin.cpp b/plugin/generateDetectionPlugin/generateDetectionPlugin.cpp index 574f2ba2..7c7f5f82 100644 --- a/plugin/generateDetectionPlugin/generateDetectionPlugin.cpp +++ b/plugin/generateDetectionPlugin/generateDetectionPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/generateDetectionPlugin/generateDetectionPlugin.h b/plugin/generateDetectionPlugin/generateDetectionPlugin.h index 75dd50f3..f888f8a7 100644 --- a/plugin/generateDetectionPlugin/generateDetectionPlugin.h +++ b/plugin/generateDetectionPlugin/generateDetectionPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/gridAnchorPlugin/CMakeLists.txt b/plugin/gridAnchorPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/gridAnchorPlugin/CMakeLists.txt +++ b/plugin/gridAnchorPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/groupNormalizationPlugin/CMakeLists.txt b/plugin/groupNormalizationPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/groupNormalizationPlugin/CMakeLists.txt +++ b/plugin/groupNormalizationPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/groupNormalizationPlugin/groupNormalizationKernel.cu b/plugin/groupNormalizationPlugin/groupNormalizationKernel.cu index fc051e7f..4ab6dd12 100644 --- a/plugin/groupNormalizationPlugin/groupNormalizationKernel.cu +++ b/plugin/groupNormalizationPlugin/groupNormalizationKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/instanceNormalizationPlugin/CMakeLists.txt b/plugin/instanceNormalizationPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/instanceNormalizationPlugin/CMakeLists.txt +++ b/plugin/instanceNormalizationPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/instanceNormalizationPlugin/instanceNormCommon.h b/plugin/instanceNormalizationPlugin/instanceNormCommon.h index fb6f5bd0..938ed2cf 100644 --- a/plugin/instanceNormalizationPlugin/instanceNormCommon.h +++ b/plugin/instanceNormalizationPlugin/instanceNormCommon.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/instanceNormalizationPlugin/instanceNormFwd.h b/plugin/instanceNormalizationPlugin/instanceNormFwd.h index 5f5901bb..1836eb41 100644 --- a/plugin/instanceNormalizationPlugin/instanceNormFwd.h +++ b/plugin/instanceNormalizationPlugin/instanceNormFwd.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/instanceNormalizationPlugin/instanceNormFwdImpl.cu b/plugin/instanceNormalizationPlugin/instanceNormFwdImpl.cu index b79436e7..3bf35f6b 100644 --- a/plugin/instanceNormalizationPlugin/instanceNormFwdImpl.cu +++ b/plugin/instanceNormalizationPlugin/instanceNormFwdImpl.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/leakyReluPlugin/CMakeLists.txt b/plugin/leakyReluPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/leakyReluPlugin/CMakeLists.txt +++ b/plugin/leakyReluPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/leakyReluPlugin/lReluPlugin.cpp b/plugin/leakyReluPlugin/lReluPlugin.cpp index 28148c8b..3acf8f39 100644 --- a/plugin/leakyReluPlugin/lReluPlugin.cpp +++ b/plugin/leakyReluPlugin/lReluPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/leakyReluPlugin/lReluPlugin.h b/plugin/leakyReluPlugin/lReluPlugin.h index 60d81029..087b0a0b 100644 --- a/plugin/leakyReluPlugin/lReluPlugin.h +++ b/plugin/leakyReluPlugin/lReluPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/modulatedDeformConvPlugin/CMakeLists.txt b/plugin/modulatedDeformConvPlugin/CMakeLists.txt index df2f2da8..0e7b1e6e 100644 --- a/plugin/modulatedDeformConvPlugin/CMakeLists.txt +++ b/plugin/modulatedDeformConvPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/modulatedDeformConvPlugin/commonCudaHelper.h b/plugin/modulatedDeformConvPlugin/commonCudaHelper.h index 5466817b..336867cd 100644 --- a/plugin/modulatedDeformConvPlugin/commonCudaHelper.h +++ b/plugin/modulatedDeformConvPlugin/commonCudaHelper.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multilevelCropAndResizePlugin/CMakeLists.txt b/plugin/multilevelCropAndResizePlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/multilevelCropAndResizePlugin/CMakeLists.txt +++ b/plugin/multilevelCropAndResizePlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.cpp b/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.cpp index 8b8c57f0..6ae3186d 100644 --- a/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.cpp +++ b/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.h b/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.h index c2f615b4..d30df9cf 100644 --- a/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.h +++ b/plugin/multilevelCropAndResizePlugin/multilevelCropAndResizePlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multilevelProposeROI/CMakeLists.txt b/plugin/multilevelProposeROI/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/multilevelProposeROI/CMakeLists.txt +++ b/plugin/multilevelProposeROI/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/multilevelProposeROI/multilevelProposeROIPlugin.cpp b/plugin/multilevelProposeROI/multilevelProposeROIPlugin.cpp index d9ad8add..48d3a359 100644 --- a/plugin/multilevelProposeROI/multilevelProposeROIPlugin.cpp +++ b/plugin/multilevelProposeROI/multilevelProposeROIPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multilevelProposeROI/multilevelProposeROIPlugin.h b/plugin/multilevelProposeROI/multilevelProposeROIPlugin.h index 653958e6..e384556f 100644 --- a/plugin/multilevelProposeROI/multilevelProposeROIPlugin.h +++ b/plugin/multilevelProposeROI/multilevelProposeROIPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multilevelProposeROI/tlt_mrcnn_config.h b/plugin/multilevelProposeROI/tlt_mrcnn_config.h index 13c8abfe..d85cc9fd 100644 --- a/plugin/multilevelProposeROI/tlt_mrcnn_config.h +++ b/plugin/multilevelProposeROI/tlt_mrcnn_config.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multiscaleDeformableAttnPlugin/CMakeLists.txt b/plugin/multiscaleDeformableAttnPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/multiscaleDeformableAttnPlugin/CMakeLists.txt +++ b/plugin/multiscaleDeformableAttnPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.cu b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.cu index d6843c64..648c83fb 100644 --- a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.cu +++ b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.h b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.h index ba29c0bb..50336389 100644 --- a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.h +++ b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttn.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttnPlugin.cpp b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttnPlugin.cpp index 18b763ba..1a87adb0 100644 --- a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttnPlugin.cpp +++ b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableAttnPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableIm2ColCuda.cuh b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableIm2ColCuda.cuh index 370c4cd1..454b9f03 100644 --- a/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableIm2ColCuda.cuh +++ b/plugin/multiscaleDeformableAttnPlugin/multiscaleDeformableIm2ColCuda.cuh @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/nmsPlugin/CMakeLists.txt b/plugin/nmsPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/nmsPlugin/CMakeLists.txt +++ b/plugin/nmsPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/nmsPlugin/nmsPlugin.cpp b/plugin/nmsPlugin/nmsPlugin.cpp index 458c184e..e567f8b9 100644 --- a/plugin/nmsPlugin/nmsPlugin.cpp +++ b/plugin/nmsPlugin/nmsPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/nmsPlugin/nmsPlugin.h b/plugin/nmsPlugin/nmsPlugin.h index eccefc37..dc70f2b0 100644 --- a/plugin/nmsPlugin/nmsPlugin.h +++ b/plugin/nmsPlugin/nmsPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/normalizePlugin/CMakeLists.txt b/plugin/normalizePlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/normalizePlugin/CMakeLists.txt +++ b/plugin/normalizePlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/nvFasterRCNN/CMakeLists.txt b/plugin/nvFasterRCNN/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/nvFasterRCNN/CMakeLists.txt +++ b/plugin/nvFasterRCNN/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/pillarScatterPlugin/CMakeLists.txt b/plugin/pillarScatterPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/pillarScatterPlugin/CMakeLists.txt +++ b/plugin/pillarScatterPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/pillarScatterPlugin/pillarScatter.cpp b/plugin/pillarScatterPlugin/pillarScatter.cpp index b520347f..fe47b4c0 100644 --- a/plugin/pillarScatterPlugin/pillarScatter.cpp +++ b/plugin/pillarScatterPlugin/pillarScatter.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/pillarScatterPlugin/pillarScatter.h b/plugin/pillarScatterPlugin/pillarScatter.h index cdaf0454..6a968b08 100644 --- a/plugin/pillarScatterPlugin/pillarScatter.h +++ b/plugin/pillarScatterPlugin/pillarScatter.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/priorBoxPlugin/CMakeLists.txt b/plugin/priorBoxPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/priorBoxPlugin/CMakeLists.txt +++ b/plugin/priorBoxPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/proposalLayerPlugin/CMakeLists.txt b/plugin/proposalLayerPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/proposalLayerPlugin/CMakeLists.txt +++ b/plugin/proposalLayerPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/proposalLayerPlugin/proposalLayerPlugin.cpp b/plugin/proposalLayerPlugin/proposalLayerPlugin.cpp index 1335ea66..b9847495 100644 --- a/plugin/proposalLayerPlugin/proposalLayerPlugin.cpp +++ b/plugin/proposalLayerPlugin/proposalLayerPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/proposalLayerPlugin/proposalLayerPlugin.h b/plugin/proposalLayerPlugin/proposalLayerPlugin.h index 68a0d136..d612db29 100644 --- a/plugin/proposalLayerPlugin/proposalLayerPlugin.h +++ b/plugin/proposalLayerPlugin/proposalLayerPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/proposalPlugin/CMakeLists.txt b/plugin/proposalPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/proposalPlugin/CMakeLists.txt +++ b/plugin/proposalPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/proposalPlugin/proposalPlugin.cpp b/plugin/proposalPlugin/proposalPlugin.cpp index e1bd677b..6a6e48c0 100644 --- a/plugin/proposalPlugin/proposalPlugin.cpp +++ b/plugin/proposalPlugin/proposalPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -514,7 +514,7 @@ void ProposalPlugin::setPluginNamespace(char const* libNamespace) noexcept { try { - PLUGIN_VALIDATE(libNamespace != nullptr); + PLUGIN_VALIDATE(libNamespace == nullptr); mNamespace = libNamespace; } catch (std::exception const& e) @@ -527,7 +527,7 @@ void ProposalDynamicPlugin::setPluginNamespace(char const* libNamespace) noexcep { try { - PLUGIN_VALIDATE(libNamespace != nullptr); + PLUGIN_VALIDATE(libNamespace == nullptr); mNamespace = libNamespace; } catch (std::exception const& e) diff --git a/plugin/proposalPlugin/proposalPlugin.h b/plugin/proposalPlugin/proposalPlugin.h index 05e9508f..025f1dee 100644 --- a/plugin/proposalPlugin/proposalPlugin.h +++ b/plugin/proposalPlugin/proposalPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/pyramidROIAlignPlugin/CMakeLists.txt b/plugin/pyramidROIAlignPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/pyramidROIAlignPlugin/CMakeLists.txt +++ b/plugin/pyramidROIAlignPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.cpp b/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.cpp index 141339f1..e1cf5749 100644 --- a/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.cpp +++ b/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.h b/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.h index dde6a309..9d2cf26e 100644 --- a/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.h +++ b/plugin/pyramidROIAlignPlugin/pyramidROIAlignPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/regionPlugin/CMakeLists.txt b/plugin/regionPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/regionPlugin/CMakeLists.txt +++ b/plugin/regionPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/regionPlugin/regionPlugin.cpp b/plugin/regionPlugin/regionPlugin.cpp index c6f709eb..6a140556 100644 --- a/plugin/regionPlugin/regionPlugin.cpp +++ b/plugin/regionPlugin/regionPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/regionPlugin/regionPlugin.h b/plugin/regionPlugin/regionPlugin.h index 7af234f1..66913fc0 100644 --- a/plugin/regionPlugin/regionPlugin.h +++ b/plugin/regionPlugin/regionPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/reorgPlugin/CMakeLists.txt b/plugin/reorgPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/reorgPlugin/CMakeLists.txt +++ b/plugin/reorgPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/reorgPlugin/reorgPlugin.cpp b/plugin/reorgPlugin/reorgPlugin.cpp index 0154580a..227c59d9 100644 --- a/plugin/reorgPlugin/reorgPlugin.cpp +++ b/plugin/reorgPlugin/reorgPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/reorgPlugin/reorgPlugin.h b/plugin/reorgPlugin/reorgPlugin.h index 5971e028..f0e4b2e6 100644 --- a/plugin/reorgPlugin/reorgPlugin.h +++ b/plugin/reorgPlugin/reorgPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/resizeNearestPlugin/CMakeLists.txt b/plugin/resizeNearestPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/resizeNearestPlugin/CMakeLists.txt +++ b/plugin/resizeNearestPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/resizeNearestPlugin/resizeNearestPlugin.cpp b/plugin/resizeNearestPlugin/resizeNearestPlugin.cpp index d60c91fa..75f0b73a 100644 --- a/plugin/resizeNearestPlugin/resizeNearestPlugin.cpp +++ b/plugin/resizeNearestPlugin/resizeNearestPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/resizeNearestPlugin/resizeNearestPlugin.h b/plugin/resizeNearestPlugin/resizeNearestPlugin.h index 5db5fc49..3f9f7e3e 100644 --- a/plugin/resizeNearestPlugin/resizeNearestPlugin.h +++ b/plugin/resizeNearestPlugin/resizeNearestPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/roiAlignPlugin/CMakeLists.txt b/plugin/roiAlignPlugin/CMakeLists.txt index bd8066f0..a2ac13d7 100644 --- a/plugin/roiAlignPlugin/CMakeLists.txt +++ b/plugin/roiAlignPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/roiAlignPlugin/roiAlignKernel.h b/plugin/roiAlignPlugin/roiAlignKernel.h index 890a9822..3be3faaa 100644 --- a/plugin/roiAlignPlugin/roiAlignKernel.h +++ b/plugin/roiAlignPlugin/roiAlignKernel.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/roiAlignPlugin/roiAlignPlugin.cpp b/plugin/roiAlignPlugin/roiAlignPlugin.cpp index d5e51638..5681eff5 100644 --- a/plugin/roiAlignPlugin/roiAlignPlugin.cpp +++ b/plugin/roiAlignPlugin/roiAlignPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/roiAlignPlugin/roiAlignPlugin.h b/plugin/roiAlignPlugin/roiAlignPlugin.h index f1246c83..e22d2571 100644 --- a/plugin/roiAlignPlugin/roiAlignPlugin.h +++ b/plugin/roiAlignPlugin/roiAlignPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterElementsPlugin/CMakeLists.txt b/plugin/scatterElementsPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/scatterElementsPlugin/CMakeLists.txt +++ b/plugin/scatterElementsPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/scatterElementsPlugin/TensorInfo.cuh b/plugin/scatterElementsPlugin/TensorInfo.cuh index 0656756c..fd6dd69d 100644 --- a/plugin/scatterElementsPlugin/TensorInfo.cuh +++ b/plugin/scatterElementsPlugin/TensorInfo.cuh @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterElementsPlugin/atomics.cuh b/plugin/scatterElementsPlugin/atomics.cuh index 90094c22..19c43e48 100644 --- a/plugin/scatterElementsPlugin/atomics.cuh +++ b/plugin/scatterElementsPlugin/atomics.cuh @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterElementsPlugin/reducer.cuh b/plugin/scatterElementsPlugin/reducer.cuh index 7143aa9f..baa13d92 100644 --- a/plugin/scatterElementsPlugin/reducer.cuh +++ b/plugin/scatterElementsPlugin/reducer.cuh @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterElementsPlugin/scatterElementsPlugin.cpp b/plugin/scatterElementsPlugin/scatterElementsPlugin.cpp index 7910ad55..babbaecc 100644 --- a/plugin/scatterElementsPlugin/scatterElementsPlugin.cpp +++ b/plugin/scatterElementsPlugin/scatterElementsPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterElementsPlugin/scatterElementsPlugin.h b/plugin/scatterElementsPlugin/scatterElementsPlugin.h index a49c4448..01c2a73d 100644 --- a/plugin/scatterElementsPlugin/scatterElementsPlugin.h +++ b/plugin/scatterElementsPlugin/scatterElementsPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterElementsPlugin/scatterElementsPluginKernel.cu b/plugin/scatterElementsPlugin/scatterElementsPluginKernel.cu index 7f487725..b09db5ae 100644 --- a/plugin/scatterElementsPlugin/scatterElementsPluginKernel.cu +++ b/plugin/scatterElementsPlugin/scatterElementsPluginKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterElementsPlugin/scatterElementsPluginKernel.h b/plugin/scatterElementsPlugin/scatterElementsPluginKernel.h index d7fa1f5a..307ef355 100644 --- a/plugin/scatterElementsPlugin/scatterElementsPluginKernel.h +++ b/plugin/scatterElementsPlugin/scatterElementsPluginKernel.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/scatterPlugin/CMakeLists.txt b/plugin/scatterPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/scatterPlugin/CMakeLists.txt +++ b/plugin/scatterPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/scatterPlugin/scatterLayer.cu b/plugin/scatterPlugin/scatterLayer.cu index b7409156..55fdef1f 100644 --- a/plugin/scatterPlugin/scatterLayer.cu +++ b/plugin/scatterPlugin/scatterLayer.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/skipLayerNormPlugin/CMakeLists.txt b/plugin/skipLayerNormPlugin/CMakeLists.txt index f49d60bd..0fbe405b 100644 --- a/plugin/skipLayerNormPlugin/CMakeLists.txt +++ b/plugin/skipLayerNormPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelHFace.cu b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelHFace.cu index 428c7483..b915dfb2 100644 --- a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelHFace.cu +++ b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelHFace.cu @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & + * 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"); diff --git a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelMTron.cu b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelMTron.cu index f4c2a39c..7858f3e3 100644 --- a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelMTron.cu +++ b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedKernelMTron.cu @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & + * 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"); diff --git a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.cpp b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.cpp index 72061613..1b74f944 100644 --- a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.cpp +++ b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & + * 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"); diff --git a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.h b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.h index f12cdda8..e858919b 100644 --- a/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.h +++ b/plugin/skipLayerNormPlugin/skipLayerNormInt8InterleavedPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & + * 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"); diff --git a/plugin/skipLayerNormPlugin/skipLayerNormKernel.cu b/plugin/skipLayerNormPlugin/skipLayerNormKernel.cu index 5d52c249..da0cee19 100644 --- a/plugin/skipLayerNormPlugin/skipLayerNormKernel.cu +++ b/plugin/skipLayerNormPlugin/skipLayerNormKernel.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & + * 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"); diff --git a/plugin/skipLayerNormPlugin/skipLayerNormPlugin.cpp b/plugin/skipLayerNormPlugin/skipLayerNormPlugin.cpp index 04ed5885..c792486b 100644 --- a/plugin/skipLayerNormPlugin/skipLayerNormPlugin.cpp +++ b/plugin/skipLayerNormPlugin/skipLayerNormPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & + * 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"); diff --git a/plugin/skipLayerNormPlugin/skipLayerNormPlugin.h b/plugin/skipLayerNormPlugin/skipLayerNormPlugin.h index b9fb8c50..9b1a783a 100644 --- a/plugin/skipLayerNormPlugin/skipLayerNormPlugin.h +++ b/plugin/skipLayerNormPlugin/skipLayerNormPlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & + * 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"); diff --git a/plugin/specialSlicePlugin/CMakeLists.txt b/plugin/specialSlicePlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/specialSlicePlugin/CMakeLists.txt +++ b/plugin/specialSlicePlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/specialSlicePlugin/specialSlicePlugin.cpp b/plugin/specialSlicePlugin/specialSlicePlugin.cpp index f4bdb04c..3730cfcc 100644 --- a/plugin/specialSlicePlugin/specialSlicePlugin.cpp +++ b/plugin/specialSlicePlugin/specialSlicePlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/specialSlicePlugin/specialSlicePlugin.h b/plugin/specialSlicePlugin/specialSlicePlugin.h index 0837682f..710bb8b4 100644 --- a/plugin/specialSlicePlugin/specialSlicePlugin.h +++ b/plugin/specialSlicePlugin/specialSlicePlugin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/splitPlugin/CMakeLists.txt b/plugin/splitPlugin/CMakeLists.txt index 1f1d4169..f1f6081b 100644 --- a/plugin/splitPlugin/CMakeLists.txt +++ b/plugin/splitPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/splitPlugin/split.cu b/plugin/splitPlugin/split.cu index 771e9cba..0afec432 100644 --- a/plugin/splitPlugin/split.cu +++ b/plugin/splitPlugin/split.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/splitPlugin/split.h b/plugin/splitPlugin/split.h index cc1916bf..2d7a9bd5 100644 --- a/plugin/splitPlugin/split.h +++ b/plugin/splitPlugin/split.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/voxelGeneratorPlugin/CMakeLists.txt b/plugin/voxelGeneratorPlugin/CMakeLists.txt index a240519a..657bfadc 100644 --- a/plugin/voxelGeneratorPlugin/CMakeLists.txt +++ b/plugin/voxelGeneratorPlugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/plugin/voxelGeneratorPlugin/voxelGenerator.cpp b/plugin/voxelGeneratorPlugin/voxelGenerator.cpp index c27d5193..2fff10cc 100644 --- a/plugin/voxelGeneratorPlugin/voxelGenerator.cpp +++ b/plugin/voxelGeneratorPlugin/voxelGenerator.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/plugin/voxelGeneratorPlugin/voxelGenerator.h b/plugin/voxelGeneratorPlugin/voxelGenerator.h index fea96877..9bb4f471 100644 --- a/plugin/voxelGeneratorPlugin/voxelGenerator.h +++ b/plugin/voxelGeneratorPlugin/voxelGenerator.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 1494c1fd..66034f8b 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -91,7 +91,7 @@ if (MSVC) find_path(PY_LIB_DIR ${PYTHON_LIB_NAME}.lib HINTS ${WIN_EXTERNALS}/${PYTHON} ${EXT_PATH}/${PYTHON} PATH_SUFFIXES lib) message(STATUS "PY_LIB_DIR: ${PY_LIB_DIR}") else() - find_path(PY_INCLUDE Python.h HINTS ${EXT_PATH}/${PYTHON} PATH_SUFFIXES include) + find_path(PY_INCLUDE Python.h HINTS ${EXT_PATH}/${PYTHON} /usr/include/${PYTHON} PATH_SUFFIXES include) endif() message(STATUS "PY_INCLUDE: ${PY_INCLUDE}") @@ -133,16 +133,6 @@ else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GLIBCXX_USE_CXX11_ABI_FLAG} -fvisibility=hidden -std=c++${CPP_STANDARD} -Wno-deprecated-declarations") endif() -# remove md -# Add the flags to enable MD-TRT. -if ("${ENABLE_MDTRT}" STREQUAL "1") - include_directories(${TENSORRT_ROOT}/optimizer) - include_directories(${TENSORRT_ROOT}/runtime) - include_directories(${TENSORRT_ROOT}/common) - include_directories(${TENSORRT_ROOT}/safety) - add_compile_definitions(ENABLE_MDTRT=1) -endif() - # Update linker if(${NV_TARGET_OS} MATCHES "wddm2") if(DEFINED W10_LINKER) @@ -159,12 +149,26 @@ else() set(vfc_suffix "") endif() +if (MSVC) + set(nvinfer_lib_name "nvinfer_${TENSORRT_MAJOR_VERSION}") + set(nvinfer_plugin_lib_name "nvinfer_plugin_${TENSORRT_MAJOR_VERSION}") + set(nvonnxparser_lib_name "nvonnxparser_${TENSORRT_MAJOR_VERSION}") + set(nvinfer_lean_lib_name "nvinfer_lean_${TENSORRT_MAJOR_VERSION}${vfc_suffix}") + set(nvinfer_dispatch_lib_name "nvinfer_dispatch_${TENSORRT_MAJOR_VERSION}${vfc_suffix}") +else() + set(nvinfer_lib_name "nvinfer") + set(nvinfer_plugin_lib_name "nvinfer_plugin") + set(nvonnxparser_lib_name "nvonnxparser") + set(nvinfer_lean_lib_name "nvinfer_lean${vfc_suffix}") + set(nvinfer_dispatch_lib_name "nvinfer_dispatch${vfc_suffix}") +endif() + if (${TENSORRT_MODULE} STREQUAL "tensorrt") - set(TRT_LIBS nvinfer nvonnxparser nvinfer_plugin) + set(TRT_LIBS ${nvinfer_lib_name} ${nvonnxparser_lib_name} ${nvinfer_plugin_lib_name}) elseif (${TENSORRT_MODULE} STREQUAL "tensorrt_lean") - set(TRT_LIBS "nvinfer_lean${vfc_suffix}") + set(TRT_LIBS ${nvinfer_lean_lib_name}) elseif (${TENSORRT_MODULE} STREQUAL "tensorrt_dispatch") - set(TRT_LIBS "nvinfer_dispatch${vfc_suffix}") + set(TRT_LIBS ${nvinfer_dispatch_lib_name}) else() message(FATAL_ERROR "Unknown TensorRT module " ${TENSORRT_MODULE}) endif() diff --git a/python/docstrings/infer/pyAlgorithmSelectorDoc.h b/python/docstrings/infer/pyAlgorithmSelectorDoc.h index f5814474..78ba454c 100644 --- a/python/docstrings/infer/pyAlgorithmSelectorDoc.h +++ b/python/docstrings/infer/pyAlgorithmSelectorDoc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -60,13 +60,7 @@ constexpr const char* descr = R"trtdoc( :ivar num_inputs: :class:`int` number of inputs of the algorithm. :ivar num_outputs: :class:`int` number of outputs of the algorithm. )trtdoc" -// remove md -#if ENABLE_MDTRT - R"trtdoc( - :ivar instance_id: Read-only. The multi-device instance ID. -)trtdoc" -#endif // ENABLE_MDTRT - ; + ; constexpr const char* get_shape = R"trtdoc( Get the minimum / optimum / maximum dimensions for a dynamic input tensor. diff --git a/python/docstrings/infer/pyCoreDoc.h b/python/docstrings/infer/pyCoreDoc.h index 3586fd9f..d59d6ac0 100644 --- a/python/docstrings/infer/pyCoreDoc.h +++ b/python/docstrings/infer/pyCoreDoc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -407,7 +407,7 @@ constexpr char const* descr = R"trtdoc( :ivar nvtx_verbosity: The NVTX verbosity of the execution context. Building with DETAILED verbosity will generally increase latency in enqueueV3(). Call this method to select NVTX verbosity in this execution context at runtime. The default is the verbosity with which the engine was built, and the verbosity may not be raised above that level. This function does not affect how IEngineInspector interacts with the engine. :ivar temporary_allocator: :class:`IGpuAllocator` The GPU allocator used for internal temporary storage. :ivar weight_streaming_budget: Set and get the current weight streaming budget for inference. The budget may be set to -1 disabling weight streaming at runtime, 0 (default) enabling TRT to choose to weight stream or not, or a positive value in the inclusive range [minimum_weight_streaming_budget, streamable_weights_size - 1]. - :ivar minimum_weight_streaming_budget: Returns the minimum weight streaming budget in bytes required to run the network successfully. The engine must have been built with kWEIGHT_STREAMING. + :ivar minimum_weight_streaming_budget: Returns the minimum weight streaming budget in bytes required to run the network successfully. The engine must have been built with kWEIGHT_STREAMING. :ivar streamable_weights_size: Returns the size of the streamable weights in the engine. This may not include all the weights. )trtdoc"; @@ -731,15 +731,6 @@ constexpr char const* create_execution_context_without_device_memory = R"trtdoc( :returns: An :class:`IExecutionContext` without device memory allocated. )trtdoc"; -constexpr char const* get_profile_shape = R"trtdoc( - Get the minimum/optimum/maximum dimensions for a particular binding under an optimization profile. - - :arg profile_index: The index of the profile. - :arg binding: The binding index or name. - - :returns: A ``List[Dims]`` of length 3, containing the minimum, optimum, and maximum shapes, in that order. -)trtdoc"; - constexpr char const* get_tensor_profile_values = R"trtdoc( Get minimum/optimum/maximum values for an input shape binding under an optimization profile. If the specified binding is not an input shape binding, an exception is raised. @@ -882,7 +873,7 @@ To implement a custom output allocator, ensure that you explicitly instantiate t def reallocate_output_async(self, tensor_name, memory, size, alignment, stream): ... # Your implementation here - + def notify_shape(self, tensor_name, shape): ... # Your implementation here @@ -936,7 +927,7 @@ To implement a custom stream reader, ensure that you explicitly instantiate the def __init__(self): trt.IStreamReader.__init__(self) - def read(self, memory, size): + def read(self, size: int) -> bytes: ... # Your implementation here )trtdoc"; @@ -1032,7 +1023,7 @@ constexpr char const* TACTIC_DRAM = R"trtdoc( cudaGetDeviceProperties.embedded is true, and 100% otherwise. )trtdoc"; constexpr char const* TACTIC_SHARED_MEMORY = R"trtdoc( - TACTIC_SHARED_MEMORY defines the maximum shared memory size utilized for executing + TACTIC_SHARED_MEMORY defines the maximum shared memory size utilized for driver reserved and executing the backend CUDA kernel implementation. Adjust this value to restrict tactics that exceed the specified threshold en masse. The default value is device max capability. This value must be less than 1GiB. @@ -1074,7 +1065,7 @@ constexpr char const* NONE = R"trtdoc( Do not require hardware compatibility with GPU architectures other than that of the GPU on which the engine was built. )trtdoc"; constexpr char const* AMPERE_PLUS = R"trtdoc( - Require that the engine is compatible with Ampere and newer GPUs. This will limit the max shared memory usage to + Require that the engine is compatible with Ampere and newer GPUs. This will limit the combined usage of driver reserved and backend kernel max shared memory to 48KiB, may reduce the number of available tactics for each layer, and may prevent some fusions from occurring. Thus this can decrease the performance, especially for tf32 models. This option will disable cuDNN, cuBLAS, and cuBLAS LT as tactic sources. @@ -1624,7 +1615,7 @@ constexpr char const* deserialize_cuda_engine = R"trtdoc( constexpr char const* deserialize_cuda_engine_reader = R"trtdoc( Deserialize an :class:`ICudaEngine` from a stream reader. - :arg stream_reader: The :class:`PyStreamReader` that will read the serialized :class:`ICudaEngine`. This enables deserialization from a file directly. + :arg stream_reader: The :class:`PyStreamReader` that will read the serialized :class:`ICudaEngine`. This enables deserialization from a file directly. :returns: The :class:`ICudaEngine`, or None if it could not be deserialized. )trtdoc"; @@ -1794,9 +1785,9 @@ constexpr char const* get_weights_prototype = R"trtdoc( The dtype and size of weights prototype is the same as weights used for engine building. The size of the weights prototype is -1 when the name of the weights is None or does not correspond to any refittable weights. - + :arg weights_name: The name of the weights to be refitted. - + :returns: weights prototype associated with the given name. )trtdoc"; @@ -2033,7 +2024,7 @@ Note that all methods below (allocate, reallocate, deallocate, allocate_async, r constexpr char const* allocate = R"trtdoc( [DEPRECATED] Deprecated in TensorRT 10.0. Please use allocate_async instead. A callback implemented by the application to handle acquisition of GPU memory. - This is just a wrapper around a syncronous method allocate_async passing the default stream. + This is just a wrapper around a synchronous method allocate_async passing the default stream. If an allocation request of size 0 is made, ``None`` should be returned. @@ -2052,7 +2043,7 @@ constexpr char const* allocate = R"trtdoc( constexpr char const* deallocate = R"trtdoc( [DEPRECATED] Deprecated in TensorRT 10.0. Please use deallocate_async instead. A callback implemented by the application to handle release of GPU memory. - This is just a wrapper around a syncronous method deallocate_async passing the default stream. + This is just a wrapper around a synchronous method deallocate_async passing the default stream. TensorRT may pass a 0 to this function if it was previously returned by ``allocate()``. diff --git a/python/docstrings/infer/pyFoundationalTypesDoc.h b/python/docstrings/infer/pyFoundationalTypesDoc.h index 0e404631..39ffd53f 100644 --- a/python/docstrings/infer/pyFoundationalTypesDoc.h +++ b/python/docstrings/infer/pyFoundationalTypesDoc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -94,8 +94,8 @@ constexpr const char* init_type = R"trtdoc( constexpr const char* init_ptr = R"trtdoc( Initializes a Weights object with the specified data. - :type: A type to initialize the weights with. - :ptr: A pointer to the data. + :type: A type to initialize the weights with. + :ptr: A pointer to the data. :count: The number of weights. )trtdoc"; @@ -108,7 +108,7 @@ constexpr const char* numpy = R"trtdoc( Create a numpy array using the underlying buffer of this weights object. The resulting array is just a view over the existing data, i.e. no deep copy is made. - If the weights cannot be converted to NumPy (e.g. due to unsupported data type), the original weights are returned. + If the weights cannot be converted to NumPy (e.g. due to unsupported data type), the original weights are returned. :returns: The NumPy array or the original weights. )trtdoc"; diff --git a/python/docstrings/infer/pyGraphDoc.h b/python/docstrings/infer/pyGraphDoc.h index 1581ad9c..e9913210 100644 --- a/python/docstrings/infer/pyGraphDoc.h +++ b/python/docstrings/infer/pyGraphDoc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -1341,8 +1341,10 @@ constexpr const char* descr = R"trtdoc( Enumerates bounding box data formats used for the Boxes input tensor in the NMS layer. )trtdoc"; -constexpr const char* CORNER_PAIRS = R"trtdoc((x1, y1, x2, y2) where (x1, y1) and (x2, y2) are any pair of diagonal corners)trtdoc"; -constexpr const char* CENTER_SIZES = R"trtdoc((x_center, y_center, width, height) where (x_center, y_center) is the center point of the box)trtdoc"; +constexpr const char* CORNER_PAIRS + = R"trtdoc((x1, y1, x2, y2) where (x1, y1) and (x2, y2) are any pair of diagonal corners)trtdoc"; +constexpr const char* CENTER_SIZES + = R"trtdoc((x_center, y_center, width, height) where (x_center, y_center) is the center point of the box)trtdoc"; } // namespace BoundingBoxFormatDoc @@ -1422,7 +1424,6 @@ constexpr const char* set_input = R"trtdoc( } // namespace INMSLayerDoc - namespace FillOperationDoc { constexpr const char* descr = R"trtdoc(The tensor fill operations that may performed by an Fill layer.)trtdoc"; diff --git a/python/docstrings/infer/pyInt8Doc.h b/python/docstrings/infer/pyInt8Doc.h index 91b635fe..013c6c75 100644 --- a/python/docstrings/infer/pyInt8Doc.h +++ b/python/docstrings/infer/pyInt8Doc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/docstrings/infer/pyPluginDoc.h b/python/docstrings/infer/pyPluginDoc.h index 5df97568..f541a281 100644 --- a/python/docstrings/infer/pyPluginDoc.h +++ b/python/docstrings/infer/pyPluginDoc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -183,7 +183,6 @@ constexpr const char* detach_from_context = R"trtdoc( )trtdoc"; } // namespace IPluginV2ExtDoc - namespace IPluginV2DynamicExtDoc { constexpr const char* descr = R"trtdoc( @@ -194,7 +193,7 @@ constexpr const char* descr = R"trtdoc( Similar to `IPluginV2Ext` (including capability to support different output data types), but with support for dynamic shapes. This class is made available for the purpose of implementing `IPluginV2DynamicExt` plugins with Python. Inherited - Python->C++ bindings from `IPluginV2` and `IPluginV2Ext` will continue to work on C++-based `IPluginV2DynamicExt` plugins. + Python->C++ bindings from `IPluginV2` and `IPluginV2Ext` will continue to work on C++-based `IPluginV2DynamicExt` plugins. .. note:: Every attribute except `tensorrt_version` must be explicitly initialized on Python-based plugins. Except `plugin_namespace`, @@ -212,22 +211,22 @@ constexpr const char* initialize = R"trtdoc( Initialize the plugin for execution. This is called when the engine is created. .. note:: - When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `pass`. + When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `pass`. .. warning:: In contrast to the C++ API for `initialize()`, this method must not return an error code. The expected behavior is to throw an appropriate exception - if an error occurs. + if an error occurs. .. warning:: This `initialize()` method is not available to be called from Python on C++-based plugins. - + )trtdoc"; constexpr const char* terminate = R"trtdoc( Release resources acquired during plugin layer initialization. This is called when the engine is destroyed. .. note:: - When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `pass`. + When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `pass`. )trtdoc"; @@ -238,7 +237,7 @@ constexpr const char* get_output_dimensions = R"trtdoc( This function is called by the implementations of `IBuilder` during analysis of the network. .. warning:: - This `get_output_dimensions()` method is not available to be called from Python on C++-based plugins + This `get_output_dimensions()` method is not available to be called from Python on C++-based plugins :arg output_index: The index of the output tensor :arg inputs: Expressions for dimensions of the input tensors @@ -269,7 +268,7 @@ constexpr const char* configure_plugin = R"trtdoc( Execution phase: `configure_plugin()` is called when a plugin is being prepared for executing the plugin for specific dimensions. This provides an opportunity for the plugin to change algorithmic choices based on the explicit input dimensions stored in `desc.dims` field. .. warning:: - This `configure_plugin()` method is not available to be called from Python on C++-based plugins + This `configure_plugin()` method is not available to be called from Python on C++-based plugins :arg in: The input tensors attributes that are used for configuration. :arg out: The output tensors attributes that are used for configuration. @@ -299,10 +298,10 @@ constexpr const char* get_workspace_size = R"trtdoc( This function is called after the plugin is configured, and possibly during execution. The result should be a sufficient workspace size to deal with inputs and outputs of the given size or any smaller problem. .. note:: - When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `return 0`. + When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `return 0`. .. warning:: - This `get_workspace_size()` method is not available to be called from Python on C++-based plugins + This `get_workspace_size()` method is not available to be called from Python on C++-based plugins :arg input_desc: How to interpret the memory for the input tensors. :arg output_desc: How to interpret the memory for the output tensors. @@ -314,7 +313,7 @@ constexpr const char* destroy = R"trtdoc( Destroy the plugin object. This will be called when the :class:`INetworkDefinition` , :class:`Builder` or :class:`ICudaEngine` is destroyed. .. note:: - When implementing a Python-based plugin, implementing this method is optional. The default behavior is a `pass`. + When implementing a Python-based plugin, implementing this method is optional. The default behavior is a `pass`. )trtdoc"; @@ -322,13 +321,13 @@ constexpr const char* enqueue = R"trtdoc( Execute the layer. `inputs` and `outputs` contains pointers to the corresponding input and output device buffers as their `intptr_t` casts. `stream` also represents an `intptr_t` cast of the CUDA stream in which enqueue should be executed. - + .. warning:: Since input, output, and workspace buffers are created and owned by TRT, care must be taken when writing to them from the Python side. .. warning:: In contrast to the C++ API for `enqueue()`, this method must not return an error code. The expected behavior is to throw an appropriate exception. - if an error occurs. + if an error occurs. .. warning:: This `enqueue()` method is not available to be called from Python on C++-based plugins. @@ -345,7 +344,7 @@ constexpr const char* enqueue = R"trtdoc( constexpr const char* clone = R"trtdoc( Clone the plugin object. This copies over internal plugin parameters as well and returns a new plugin object with these parameters. - If the source plugin is pre-configured with `configure_plugin()`, the returned object should also be pre-configured. + If the source plugin is pre-configured with `configure_plugin()`, the returned object should also be pre-configured. Cloned plugin objects can share the same per-engine immutable resource (e.g. weights) with the source object to avoid duplication. )trtdoc"; @@ -353,7 +352,7 @@ constexpr const char* get_serialization_size = R"trtdoc( Return the serialization size (in bytes) required by the plugin. .. note:: - When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `return len(serialize())`. + When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `return len(serialize())`. )trtdoc"; @@ -392,7 +391,7 @@ constexpr const char* ipluginv3_descr = R"trtdoc( constexpr const char* iplugincapability_descr = R"trtdoc( Base class for plugin capability interfaces - + IPluginCapability represents a split in TensorRT V3 plugins to sub-objects that expose different types of capabilites a plugin may have, as opposed to a single interface which defines all capabilities and behaviors of a plugin. )trtdoc"; @@ -411,7 +410,7 @@ constexpr const char* ipluginv3onecore_descr = R"trtdoc( constexpr const char* ipluginv3onebuild_descr = R"trtdoc( A plugin capability interface that enables the build capability (PluginCapabilityType.BUILD). - + Exposes methods that allow the expression of the build time properties and behavior of a plugin. .. note:: @@ -423,7 +422,7 @@ constexpr const char* ipluginv3onebuild_descr = R"trtdoc( constexpr const char* ipluginv3oneruntime_descr = R"trtdoc( A plugin capability interface that enables the runtime capability (PluginCapabilityType.RUNTIME). - + Exposes methods that allow the expression of the runtime properties and behavior of a plugin. )trtdoc"; @@ -434,7 +433,7 @@ constexpr const char* get_output_shapes = R"trtdoc( This function is called by the implementations of `IBuilder` during analysis of the network. .. warning:: - This `get_output_shapes()` method is not available to be called from Python on C++-based plugins + This get_output_shapes() method is not available to be called from Python on C++-based plugins :arg inputs: Expressions for shapes of the input tensors :arg shape_inputs: Expressions for shapes of the shape inputs @@ -445,9 +444,9 @@ constexpr const char* get_output_shapes = R"trtdoc( constexpr const char* get_output_data_types = R"trtdoc( - Return `DataType`s of the plugin outputs. + Return `DataType` s of the plugin outputs. - Provide `DataType.FLOAT`s if the layer has no inputs. The data type for any size tensor outputs must be + Provide `DataType.FLOAT` s if the layer has no inputs. The data type for any size tensor outputs must be `DataType.INT32`. The returned data types must each have a format that is supported by the plugin. :arg input_types: Data types of the inputs. @@ -458,7 +457,7 @@ constexpr const char* get_output_data_types = R"trtdoc( constexpr const char* configure_plugin = R"trtdoc( Configure the plugin. - This function can be called multiple times in the build phase during creation of an engine by IBuilder. + This function can be called multiple times in the build phase during creation of an engine by IBuilder. Build phase: `configure_plugin()` is called when a plugin is being prepared for profiling but not for any specific input size. This provides an opportunity for the plugin to make algorithmic choices on the basis of input and output formats, along with the bound of possible dimensions. The min, opt and max value of the `DynamicPluginTensorDesc` correspond to the `MIN`, `OPT` and `MAX` value of the current profile that the plugin is @@ -467,31 +466,28 @@ constexpr const char* configure_plugin = R"trtdoc( .. warning:: In contrast to the C++ API for `configurePlugin()`, this method must not return an error code. The expected behavior is to throw an appropriate exception - if an error occurs. + if an error occurs. .. warning:: - This `configure_plugin()` method is not available to be called from Python on C++-based plugins + This `configure_plugin()` method is not available to be called from Python on C++-based plugins :arg in: The input tensors attributes that are used for configuration. :arg out: The output tensors attributes that are used for configuration. )trtdoc"; constexpr const char* on_shape_change = R"trtdoc( - Called when a plugin is being prepared for execution for specific dimensions. This could happen multiple times in the execution phase, both during creation of an engine by IBuilder and execution of an - engine by IExecutionContext. + Called when a plugin is being prepared for execution for specific dimensions. This could happen multiple times in the execution phase, both during creation of an engine by IBuilder and execution of an + engine by IExecutionContext. - * IBuilder will call this function once per profile, with `in` resolved to the values specified by the - kOPT field of the current profile. - * IExecutionContext will call this during the next subsequent instance of enqueue_v2() or execute_v3() if: - - The optimization profile is changed. - - An input binding is changed. + * IBuilder will call this function once per profile, with `in` resolved to the values specified by the kOPT field of the current profile. + * IExecutionContext will call this during the next subsequent instance of enqueue_v2() or execute_v3() if: (1) The optimization profile is changed (2). An input binding is changed. .. warning:: In contrast to the C++ API for `onShapeChange()`, this method must not return an error code. The expected behavior is to throw an appropriate exception - if an error occurs. + if an error occurs. .. warning:: - This `on_shape_change()` method is not available to be called from Python on C++-based plugins + This `on_shape_change()` method is not available to be called from Python on C++-based plugins :arg in: The input tensors attributes that are used for configuration. :arg out: The output tensors attributes that are used for configuration. @@ -521,10 +517,10 @@ constexpr const char* get_workspace_size = R"trtdoc( This function is called after the plugin is configured, and possibly during execution. The result should be a sufficient workspace size to deal with inputs and outputs of the given size or any smaller problem. .. note:: - When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `return 0`. + When implementing a Python-based plugin, implementing this method is optional. The default behavior is equivalent to `return 0`. .. warning:: - This `get_workspace_size()` method is not available to be called from Python on C++-based plugins + This `get_workspace_size()` method is not available to be called from Python on C++-based plugins :arg input_desc: How to interpret the memory for the input tensors. :arg output_desc: How to interpret the memory for the output tensors. @@ -539,7 +535,7 @@ constexpr const char* destroy = R"trtdoc( There is no direct equivalent to this method in the C++ API. .. note:: - Implementing this method is optional. The default behavior is a `pass`. + Implementing this method is optional. The default behavior is a `pass`. )trtdoc"; @@ -547,13 +543,13 @@ constexpr const char* enqueue = R"trtdoc( Execute the layer. `inputs` and `outputs` contains pointers to the corresponding input and output device buffers as their `intptr_t` casts. `stream` also represents an `intptr_t` cast of the CUDA stream in which enqueue should be executed. - + .. warning:: Since input, output, and workspace buffers are created and owned by TRT, care must be taken when writing to them from the Python side. .. warning:: In contrast to the C++ API for `enqueue()`, this method must not return an error code. The expected behavior is to throw an appropriate exception. - if an error occurs. + if an error occurs. .. warning:: This `enqueue()` method is not available to be called from Python on C++-based plugins. @@ -580,7 +576,7 @@ constexpr const char* get_capability_interface = R"trtdoc( constexpr const char* clone = R"trtdoc( Clone the plugin object. This copies over internal plugin parameters as well and returns a new plugin object with these parameters. - If the source plugin is pre-configured with `configure_plugin()`, the returned object should also be pre-configured. + If the source plugin is pre-configured with `configure_plugin()`, the returned object should also be pre-configured. Cloned plugin objects can share the same per-engine immutable resource (e.g. weights) with the source object to avoid duplication. )trtdoc"; @@ -602,7 +598,7 @@ constexpr const char* set_tactic = R"trtdoc( .. warning:: In contrast to the C++ API for `setTactic()`, this method must not return an error code. The expected behavior is to throw an appropriate exception - if an error occurs. + if an error occurs. .. warning:: This `set_tactic()` method is not available to be called from Python on C++-based plugins. @@ -611,7 +607,7 @@ constexpr const char* set_tactic = R"trtdoc( constexpr const char* get_valid_tactics = R"trtdoc( Return any custom tactics that the plugin intends to use. - + .. note:: The provided tactic values must be unique and positive @@ -626,9 +622,9 @@ constexpr const char* attach_to_context = R"trtdoc( This function is called automatically for each plugin when a new execution context is created. The plugin may use resources provided by the resource_context until the plugin is deleted by TensorRT. - + :arg resource_context: A resource context that exposes methods to get access to execution context specific resources. A different resource context is guaranteed for each different execution context to which the plugin is attached. - + .. note:: This method should clone the entire IPluginV3 object, not just the runtime interface @@ -660,7 +656,7 @@ constexpr const char* release = R"trtdoc( constexpr const char* clone = R"trtdoc( Resource initialization (if any) may be skipped for non-cloned objects since only clones will be registered by TensorRT. - + )trtdoc"; } // namespace IPluginResourceDoc @@ -703,7 +699,7 @@ namespace IDimensionExprDoc { constexpr const char* descr = R"trtdoc( An `IDimensionExpr` represents an integer expression constructed from constants, input dimensions, and binary operations. - + These expressions are can be used in overrides of `IPluginV2DynamicExt::get_output_dimensions()` to define output dimensions in terms of input dimensions. )trtdoc"; @@ -787,7 +783,7 @@ namespace IPluginResourceContextDoc { constexpr const char* descr = R"trtdoc( Interface for plugins to access per context resources provided by TensorRT - + There is no public way to construct an IPluginResourceContext. It appears as an argument to trt.IPluginV3OneRuntime.attach_to_context(). )trtdoc"; } // namespace IPluginResourceContextDoc @@ -953,7 +949,7 @@ constexpr const char* get_plugin_creator = R"trtdoc( Return plugin creator based on type, version and namespace .. warning:: - Returns None if a plugin creator with matching name, version, and namespace is found, but is not a + Returns None if a plugin creator with matching name, version, and namespace is found, but is not a descendent of IPluginCreator :arg type: The type of the plugin. @@ -998,12 +994,12 @@ constexpr const char* deregister_library = R"trtdoc( constexpr const char* acquire_plugin_resource = R"trtdoc( Get a handle to a plugin resource registered against the provided key. - :arg: key: Key for identifying the resource. + :arg: key: Key for identifying the resource. :arg: resource: A plugin resource object. The object will only need to be valid until this method returns, as only a clone of this object will be registered by TRT. Cannot be null. )trtdoc"; constexpr const char* release_plugin_resource = R"trtdoc( - Decrement reference count for the resource with this key. If reference count goes to zero after decrement, release() will be invoked on the resource, + Decrement reference count for the resource with this key. If reference count goes to zero after decrement, release() will be invoked on the resource, and the key will be deregistered. :arg: key: Key that was used to register the resource. diff --git a/python/docstrings/parsers/pyOnnxDoc.h b/python/docstrings/parsers/pyOnnxDoc.h index 7099a207..17656d27 100644 --- a/python/docstrings/parsers/pyOnnxDoc.h +++ b/python/docstrings/parsers/pyOnnxDoc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/docstrings/pyTensorRTDoc.h b/python/docstrings/pyTensorRTDoc.h index 2ebb0a82..3594d387 100644 --- a/python/docstrings/pyTensorRTDoc.h +++ b/python/docstrings/pyTensorRTDoc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/include/ForwardDeclarations.h b/python/include/ForwardDeclarations.h index c377bf66..d4bed446 100644 --- a/python/include/ForwardDeclarations.h +++ b/python/include/ForwardDeclarations.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/include/utils.h b/python/include/utils.h index 0b46743a..2f0d5bdc 100644 --- a/python/include/utils.h +++ b/python/include/utils.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -162,7 +162,7 @@ void throwPyError(PyObject* type, std::string const& message = "python error"); { \ utils::throwPyError(PyExc_IndexError, "Out of bounds"); \ } \ - }while(false) + } while (false) #define PY_ASSERT_VALUE_ERROR(assertion, msg) \ do \ diff --git a/python/packaging/bindings_wheel/setup.py b/python/packaging/bindings_wheel/setup.py index 7bd97517..32b9a730 100644 --- a/python/packaging/bindings_wheel/setup.py +++ b/python/packaging/bindings_wheel/setup.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/python/packaging/bindings_wheel/tensorrt/__init__.py b/python/packaging/bindings_wheel/tensorrt/__init__.py index 01e49480..e82ee1ec 100644 --- a/python/packaging/bindings_wheel/tensorrt/__init__.py +++ b/python/packaging/bindings_wheel/tensorrt/__init__.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -51,18 +51,18 @@ def find_lib(name): # Order matters here because of dependencies LIBRARIES = { "tensorrt": [ - "nvinfer.dll", + "nvinfer_##TENSORRT_MAJOR##.dll", "cublas64_##CUDA_MAJOR##.dll", "cublasLt64_##CUDA_MAJOR##.dll", "cudnn64_##CUDNN_MAJOR##.dll", - "nvinfer_plugin.dll", - "nvonnxparser.dll", + "nvinfer_plugin_##TENSORRT_MAJOR##.dll", + "nvonnxparser_##TENSORRT_MAJOR##.dll", ], "tensorrt_dispatch": [ - "nvinfer_dispatch.dll", + "nvinfer_dispatch_##TENSORRT_MAJOR##.dll", ], "tensorrt_lean": [ - "nvinfer_lean.dll", + "nvinfer_lean_##TENSORRT_MAJOR##.dll", ], }["##TENSORRT_MODULE##"] diff --git a/python/packaging/frontend_sdist/setup.py b/python/packaging/frontend_sdist/setup.py index b593e52c..8c050d20 100644 --- a/python/packaging/frontend_sdist/setup.py +++ b/python/packaging/frontend_sdist/setup.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -104,14 +104,20 @@ def parent_command_line(): pass # fall back to shell try: - return subprocess.check_output(["ps", "-p", str(pid), "-o", "command", "--no-headers"]).decode() + return subprocess.check_output( + ["ps", "-p", str(pid), "-o", "command", "--no-headers"] + ).decode() except: return "" # use pip-inside-pip hack only if the nvidia index is not set in the environment install_requires = [] -if disable_internal_pip or nvidia_pip_index_url in parent_command_line() or nvidia_pip_index_url in pip_config_list(): +if ( + disable_internal_pip + or nvidia_pip_index_url in parent_command_line() + or nvidia_pip_index_url in pip_config_list() +): install_requires.extend(tensorrt_submodules) cmdclass = {} else: diff --git a/python/packaging/frontend_sdist/tensorrt/__init__.py b/python/packaging/frontend_sdist/tensorrt/__init__.py index d15c89d7..5b7038fd 100644 --- a/python/packaging/frontend_sdist/tensorrt/__init__.py +++ b/python/packaging/frontend_sdist/tensorrt/__init__.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/python/packaging/libs_wheel/setup.py b/python/packaging/libs_wheel/setup.py index b6060e0b..b9f7af76 100644 --- a/python/packaging/libs_wheel/setup.py +++ b/python/packaging/libs_wheel/setup.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/python/packaging/libs_wheel/tensorrt_libs/__init__.py b/python/packaging/libs_wheel/tensorrt_libs/__init__.py index a7d9e91a..0335c921 100644 --- a/python/packaging/libs_wheel/tensorrt_libs/__init__.py +++ b/python/packaging/libs_wheel/tensorrt_libs/__init__.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -43,7 +43,12 @@ def try_load_libs_from_dir(path): ] for dep_path in DEPENDENCY_PATHS: try_load_libs_from_dir( - os.path.join(CURDIR, os.path.pardir, dep_path, "bin" if sys.platform.startswith("win") else "lib") + os.path.join( + CURDIR, + os.path.pardir, + dep_path, + "bin" if sys.platform.startswith("win") else "lib", + ) ) diff --git a/python/packaging/metapackage/setup.py b/python/packaging/metapackage/setup.py index b5f8452f..bd673247 100644 --- a/python/packaging/metapackage/setup.py +++ b/python/packaging/metapackage/setup.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/python/src/infer/pyAlgorithmSelector.cpp b/python/src/infer/pyAlgorithmSelector.cpp index 75fe97d2..81984930 100644 --- a/python/src/infer/pyAlgorithmSelector.cpp +++ b/python/src/infer/pyAlgorithmSelector.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -19,10 +19,6 @@ #include "ForwardDeclarations.h" #include "utils.h" #include -// remove md -#if ENABLE_MDTRT -#include "api/internal.h" -#endif // ENABLE_MDTRT #include "infer/pyAlgorithmSelectorDoc.h" #include #include @@ -167,11 +163,7 @@ void bindAlgorithm(py::module& m) .def("get_shape", lambdas::get_shape, "index"_a, IAlgorithmContextDoc::get_shape) .def_property_readonly("num_inputs", &IAlgorithmContext::getNbInputs) .def_property_readonly("num_outputs", &IAlgorithmContext::getNbOutputs) -// remove md -#if ENABLE_MDTRT - .def_property_readonly("instance_id", &nvinfer1AlgorithmGetInstanceID) -#endif // ENABLE_MDTRT - ; + ; // IAlgorithm py::class_>( diff --git a/python/src/infer/pyCore.cpp b/python/src/infer/pyCore.cpp index e2d95473..4d6f72e0 100644 --- a/python/src/infer/pyCore.cpp +++ b/python/src/infer/pyCore.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -55,16 +55,16 @@ static const auto opt_profile_get_shape return shapes; }; -static const auto opt_profile_set_shape_input - = [](IOptimizationProfile& self, std::string const& inputName, std::vector const& min, - std::vector const& opt, std::vector const& max) { - PY_ASSERT_RUNTIME_ERROR(self.setShapeValues(inputName.c_str(), OptProfileSelector::kMIN, min.data(), min.size()), - "min input provided for shape tensor is inconsistent with other inputs."); - PY_ASSERT_RUNTIME_ERROR(self.setShapeValues(inputName.c_str(), OptProfileSelector::kOPT, opt.data(), opt.size()), - "opt input provided for shape tensor is inconsistent with other inputs."); - PY_ASSERT_RUNTIME_ERROR(self.setShapeValues(inputName.c_str(), OptProfileSelector::kMAX, max.data(), max.size()), - "max input provided for shape tensor is inconsistent with other inputs."); - }; +static const auto opt_profile_set_shape_input = [](IOptimizationProfile& self, std::string const& inputName, + std::vector const& min, std::vector const& opt, + std::vector const& max) { + PY_ASSERT_RUNTIME_ERROR(self.setShapeValues(inputName.c_str(), OptProfileSelector::kMIN, min.data(), min.size()), + "min input provided for shape tensor is inconsistent with other inputs."); + PY_ASSERT_RUNTIME_ERROR(self.setShapeValues(inputName.c_str(), OptProfileSelector::kOPT, opt.data(), opt.size()), + "opt input provided for shape tensor is inconsistent with other inputs."); + PY_ASSERT_RUNTIME_ERROR(self.setShapeValues(inputName.c_str(), OptProfileSelector::kMAX, max.data(), max.size()), + "max input provided for shape tensor is inconsistent with other inputs."); +}; static const auto opt_profile_get_shape_input = [](IOptimizationProfile& self, std::string const& inputName) -> std::vector> { @@ -144,7 +144,8 @@ Dims castDimsFromPyIterable(PyIterable& in) int32_t const maxDims{static_cast(Dims::MAX_DIMS)}; Dims dims{}; dims.nbDims = py::len(in); - PY_ASSERT_RUNTIME_ERROR(dims.nbDims <= maxDims, "The number of input dims exceeds the maximum allowed number of dimensions"); + PY_ASSERT_RUNTIME_ERROR( + dims.nbDims <= maxDims, "The number of input dims exceeds the maximum allowed number of dimensions"); for (int32_t i = 0; i < dims.nbDims; ++i) { dims.d[i] = in[i].template cast(); @@ -182,21 +183,6 @@ std::vector get_tensor_profile_shape(ICudaEngine& self, std::string const& return shapes; }; -std::vector engine_get_profile_shape(ICudaEngine& self, int32_t profileIndex, int32_t bindingIndex) -{ - std::vector shapes{}; - auto const tensorName = self.getIOTensorName(bindingIndex); - shapes.emplace_back(self.getProfileShape(tensorName, profileIndex, OptProfileSelector::kMIN)); - shapes.emplace_back(self.getProfileShape(tensorName, profileIndex, OptProfileSelector::kOPT)); - shapes.emplace_back(self.getProfileShape(tensorName, profileIndex, OptProfileSelector::kMAX)); - return shapes; -}; -// Overload to allow using binding names instead of indices. -std::vector engine_get_profile_shape_str(ICudaEngine& self, int32_t profileIndex, std::string const& bindingName) -{ - return get_tensor_profile_shape(self, bindingName, profileIndex); -}; - std::vector> get_tensor_profile_values( ICudaEngine& self, int32_t profileIndex, std::string const& tensorName) { @@ -618,8 +604,11 @@ class PyStreamReader : public IStreamReader return 0; } - py::object bytesRead = pyFunc(reinterpret_cast(destination), size); - return bytesRead.cast(); + py::buffer data = pyFunc(size); + py::buffer_info info = data.request(); + int64_t bytesRead = info.size * info.itemsize; + std::memcpy(destination, info.ptr, std::min(bytesRead, size)); + return bytesRead; } catch (std::exception const& e) { @@ -1180,10 +1169,6 @@ void bindCore(py::module& m) .def_property_readonly("name", &ICudaEngine::getName) .def_property_readonly("num_optimization_profiles", &ICudaEngine::getNbOptimizationProfiles) .def_property_readonly("engine_capability", &ICudaEngine::getEngineCapability) - .def("get_profile_shape", utils::deprecate(lambdas::engine_get_profile_shape, "get_tensor_profile_shape"), - "profile_index"_a, "binding"_a, ICudaEngineDoc::get_profile_shape) - .def("get_profile_shape", utils::deprecate(lambdas::engine_get_profile_shape_str, "get_tensor_profile_shape"), - "profile_index"_a, "binding"_a, ICudaEngineDoc::get_profile_shape) // Start of enqueueV3 related APIs. .def_property_readonly("num_io_tensors", &ICudaEngine::getNbIOTensors) .def("get_tensor_name", &ICudaEngine::getIOTensorName, "index"_a, ICudaEngineDoc::get_tensor_name) @@ -1278,7 +1263,7 @@ void bindCore(py::module& m) .def_property_readonly("minimum_weight_streaming_budget", &ICudaEngine::getMinimumWeightStreamingBudget) .def_property_readonly("streamable_weights_size", &ICudaEngine::getStreamableWeightsSize) .def("is_debug_tensor", &ICudaEngine::isDebugTensor, "name"_a, ICudaEngineDoc::is_debug_tensor) - .def("__del__", &utils::doNothingDel); + .def("__del__", &utils::doNothingDel); py::enum_(m, "AllocatorFlag", py::arithmetic{}, AllocatorFlagDoc::descr, py::module_local()) .value("RESIZABLE", AllocatorFlag::kRESIZABLE, AllocatorFlagDoc::RESIZABLE); diff --git a/python/src/infer/pyFoundationalTypes.cpp b/python/src/infer/pyFoundationalTypes.cpp index e89e020a..6f64f7d4 100644 --- a/python/src/infer/pyFoundationalTypes.cpp +++ b/python/src/infer/pyFoundationalTypes.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -40,8 +40,8 @@ static const auto weights_pointer_constructor = [](DataType const& type, size_t static const auto weights_numpy_constructor = [](py::array& arr) { arr = py::array::ensure(arr); // In order to construct a weights object, we must have a contiguous C-style array. - PY_ASSERT_VALUE_ERROR(arr, - "Could not convert NumPy array to Weights. Is it using a data type supported by TensorRT?"); + PY_ASSERT_VALUE_ERROR( + arr, "Could not convert NumPy array to Weights. Is it using a data type supported by TensorRT?"); PY_ASSERT_VALUE_ERROR((arr.flags() & py::array::c_style), "Could not convert non-contiguous NumPy array to Weights. Please use numpy.ascontiguousarray() to fix this."); return new Weights{utils::type(arr.dtype()), arr.data(), arr.size()}; @@ -105,8 +105,8 @@ static const auto dims_getter = [](Dims const& self, int32_t const pyIndex) -> i static const auto dims_getter_slice = [](Dims const& self, py::slice slice) { size_t start, stop, step, slicelength; - PY_ASSERT_VALUE_ERROR(slice.compute(self.nbDims, &start, &stop, &step, &slicelength), - "Incorrect getter slice dims"); + PY_ASSERT_VALUE_ERROR( + slice.compute(self.nbDims, &start, &stop, &step, &slicelength), "Incorrect getter slice dims"); // Disallow out-of-bounds things. PY_ASSERT_INDEX_ERROR(stop <= self.nbDims); @@ -124,8 +124,8 @@ static const auto dims_setter = [](Dims& self, int32_t const pyIndex, int64_t co static const auto dims_setter_slice = [](Dims& self, py::slice slice, Dims const& other) { size_t start, stop, step, slicelength; - PY_ASSERT_VALUE_ERROR(slice.compute(self.nbDims, &start, &stop, &step, &slicelength), - "Incorrect setter slice dims"); + PY_ASSERT_VALUE_ERROR( + slice.compute(self.nbDims, &start, &stop, &step, &slicelength), "Incorrect setter slice dims"); // Disallow out-of-bounds things. PY_ASSERT_INDEX_ERROR(stop < self.nbDims); diff --git a/python/src/infer/pyGraph.cpp b/python/src/infer/pyGraph.cpp index 730481ae..ddca1e9d 100644 --- a/python/src/infer/pyGraph.cpp +++ b/python/src/infer/pyGraph.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/src/infer/pyInt8.cpp b/python/src/infer/pyInt8.cpp index 5639bcd1..9052f796 100644 --- a/python/src/infer/pyInt8.cpp +++ b/python/src/infer/pyInt8.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -82,7 +82,8 @@ class pyCalibratorTrampoline : public Derived { py::gil_scoped_acquire gil{}; - py::function pyReadCalibrationCache = utils::getOverride(static_cast(this), "read_calibration_cache"); + py::function pyReadCalibrationCache + = utils::getOverride(static_cast(this), "read_calibration_cache"); // Cannot cast `None` to py::buffer. auto cacheRaw = pyReadCalibrationCache(); @@ -118,7 +119,7 @@ class pyCalibratorTrampoline : public Derived py::function pyWriteCalibrationCache = utils::getOverride(static_cast(this), "write_calibration_cache"); - #if PYBIND11_VERSION_MAJOR < 2 || PYBIND11_VERSION_MAJOR == 2 && PYBIND11_VERSION_MINOR < 6 +#if PYBIND11_VERSION_MAJOR < 2 || PYBIND11_VERSION_MAJOR == 2 && PYBIND11_VERSION_MINOR < 6 py::buffer_info info{ const_cast(ptr), /* Pointer to buffer */ sizeof(uint8_t), /* Size of one scalar */ @@ -128,10 +129,10 @@ class pyCalibratorTrampoline : public Derived { sizeof(uint8_t) } /* Strides (in bytes) for each index */ }; py::memoryview cache{info}; - #else +#else py::memoryview cache{ py::memoryview::from_buffer(static_cast(ptr), {length}, {sizeof(uint8_t)})}; - #endif +#endif pyWriteCalibrationCache(cache); } catch (std::exception const& e) @@ -284,7 +285,10 @@ void bindInt8(py::module& m) py::class_(m, "IInt8Calibrator", IInt8CalibratorDoc::descr, py::module_local()) .def(py::init<>()) - .def("get_batch_size", utils::deprecateMember(&IInt8Calibrator::getBatchSize, "Implicit batch dimensions support has been removed"), IInt8CalibratorDoc::get_batch_size) + .def("get_batch_size", + utils::deprecateMember( + &IInt8Calibrator::getBatchSize, "Implicit batch dimensions support has been removed"), + IInt8CalibratorDoc::get_batch_size) .def("get_algorithm", &IInt8Calibrator::getAlgorithm, IInt8CalibratorDoc::get_algorithm) // For documentation purposes only .def("get_batch", docGetBatch, "names"_a, IInt8CalibratorDoc::get_batch) @@ -296,7 +300,10 @@ void bindInt8(py::module& m) py::class_( m, "IInt8LegacyCalibrator", IInt8LegacyCalibratorDoc::descr, py::module_local()) .def(py::init<>()) - .def("get_batch_size", utils::deprecateMember(&IInt8LegacyCalibrator::getBatchSize, "Implicit batch dimensions support has been removed"), IInt8CalibratorDoc::get_batch_size) + .def("get_batch_size", + utils::deprecateMember( + &IInt8LegacyCalibrator::getBatchSize, "Implicit batch dimensions support has been removed"), + IInt8CalibratorDoc::get_batch_size) .def("get_algorithm", &IInt8LegacyCalibrator::getAlgorithm, IInt8LegacyCalibratorDoc::get_algorithm) // For documentation purposes only .def("get_batch", docGetBatch, "names"_a, IInt8CalibratorDoc::get_batch) @@ -308,7 +315,10 @@ void bindInt8(py::module& m) py::class_>( m, "IInt8EntropyCalibrator", IInt8EntropyCalibratorDoc::descr, py::module_local()) .def(py::init<>()) - .def("get_batch_size", utils::deprecateMember(&IInt8EntropyCalibrator::getBatchSize, "Implicit batch dimensions support has been removed"), IInt8CalibratorDoc::get_batch_size) + .def("get_batch_size", + utils::deprecateMember( + &IInt8EntropyCalibrator::getBatchSize, "Implicit batch dimensions support has been removed"), + IInt8CalibratorDoc::get_batch_size) .def("get_algorithm", &IInt8EntropyCalibrator::getAlgorithm, IInt8EntropyCalibratorDoc::get_algorithm) // For documentation purposes only .def("get_batch", docGetBatch, "names"_a, IInt8CalibratorDoc::get_batch) @@ -320,7 +330,10 @@ void bindInt8(py::module& m) py::class_>( m, "IInt8EntropyCalibrator2", IInt8EntropyCalibrator2Doc::descr, py::module_local()) .def(py::init<>()) - .def("get_batch_size", utils::deprecateMember(&IInt8EntropyCalibrator2::getBatchSize, "Implicit batch dimensions support has been removed"), IInt8CalibratorDoc::get_batch_size) + .def("get_batch_size", + utils::deprecateMember( + &IInt8EntropyCalibrator2::getBatchSize, "Implicit batch dimensions support has been removed"), + IInt8CalibratorDoc::get_batch_size) .def("get_algorithm", &IInt8EntropyCalibrator2::getAlgorithm, IInt8EntropyCalibrator2Doc::get_algorithm) // For documentation purposes only .def("get_batch", docGetBatch, "names"_a, IInt8CalibratorDoc::get_batch) @@ -332,7 +345,10 @@ void bindInt8(py::module& m) py::class_>( m, "IInt8MinMaxCalibrator", IInt8MinMaxCalibratorDoc::descr, py::module_local()) .def(py::init<>()) - .def("get_batch_size", utils::deprecateMember(&IInt8MinMaxCalibrator::getBatchSize, "Implicit batch dimensions support has been removed"), IInt8CalibratorDoc::get_batch_size) + .def("get_batch_size", + utils::deprecateMember( + &IInt8MinMaxCalibrator::getBatchSize, "Implicit batch dimensions support has been removed"), + IInt8CalibratorDoc::get_batch_size) .def("get_algorithm", &IInt8MinMaxCalibrator::getAlgorithm, IInt8MinMaxCalibratorDoc::get_algorithm) // For documentation purposes only .def("get_batch", docGetBatch, "names"_a, IInt8CalibratorDoc::get_batch) diff --git a/python/src/infer/pyPlugin.cpp b/python/src/infer/pyPlugin.cpp index d87a42ec..9fc1b901 100644 --- a/python/src/infer/pyPlugin.cpp +++ b/python/src/infer/pyPlugin.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -87,14 +87,14 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt public: using PyIPluginV2DynamicExt::PyIPluginV2DynamicExt; PyIPluginV2DynamicExtImpl() = default; - PyIPluginV2DynamicExtImpl(const PyIPluginV2DynamicExt& a) {}; + PyIPluginV2DynamicExtImpl(const PyIPluginV2DynamicExt& a){}; int32_t getNbOutputs() const noexcept override { try { py::gil_scoped_acquire gil{}; - if(!mIsNbOutputsInitialized) + if (!mIsNbOutputsInitialized) { utils::throwPyError(PyExc_AttributeError, "num_outputs not initialized"); } @@ -104,7 +104,8 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt return -1; } - bool supportsFormatCombination(int32_t pos, PluginTensorDesc const* inOut, int32_t nbInputs, int32_t nbOutputs) noexcept override + bool supportsFormatCombination( + int32_t pos, PluginTensorDesc const* inOut, int32_t nbInputs, int32_t nbOutputs) noexcept override { try { @@ -118,7 +119,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt } std::vector inOutVector; - for(int32_t idx = 0; idx < nbInputs + nbOutputs; ++idx) + for (int32_t idx = 0; idx < nbInputs + nbOutputs; ++idx) { inOutVector.push_back(*(inOut + idx)); } @@ -151,10 +152,11 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt return 0; } - try{ + try + { py::object pyResult = pyInitialize(); } - catch (py::error_already_set &e) + catch (py::error_already_set& e) { std::cerr << "[ERROR] Exception thrown from initialize() " << e.what() << std::endl; return -1; @@ -165,7 +167,8 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt return -1; } - void terminate() noexcept override { + void terminate() noexcept override + { try { py::gil_scoped_acquire gil{}; @@ -173,7 +176,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt py::function pyTerminate = py::get_override(static_cast(this), "terminate"); // if no implementation is provided for terminate(), it is defaulted to `pass` - if(pyTerminate) + if (pyTerminate) { pyTerminate(); } @@ -181,7 +184,8 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt PLUGIN_API_CATCH("terminate") } - int32_t enqueue(PluginTensorDesc const* inputDesc, PluginTensorDesc const* outputDesc, void const* const* inputs, void* const* outputs, void* workspace, cudaStream_t stream) noexcept override + int32_t enqueue(PluginTensorDesc const* inputDesc, PluginTensorDesc const* outputDesc, void const* const* inputs, + void* const* outputs, void* workspace, cudaStream_t stream) noexcept override { try { @@ -194,12 +198,12 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt } std::vector inVector; - for(int32_t idx = 0; idx < mNbInputs; ++idx) + for (int32_t idx = 0; idx < mNbInputs; ++idx) { inVector.push_back(*(inputDesc + idx)); } std::vector outVector; - for(int32_t idx = 0; idx < mNbOutputs; ++idx) + for (int32_t idx = 0; idx < mNbOutputs; ++idx) { outVector.push_back(*(outputDesc + idx)); } @@ -218,10 +222,11 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt intptr_t workspacePtr = reinterpret_cast(workspace); intptr_t cudaStreamPtr = reinterpret_cast(stream); - try{ + try + { pyEnqueue(inVector, outVector, inPtrs, outPtrs, workspacePtr, cudaStreamPtr); } - catch (py::error_already_set &e) + catch (py::error_already_set& e) { std::cerr << "[ERROR] Exception thrown from enqueue() " << e.what() << std::endl; return -1; @@ -283,8 +288,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt { py::gil_scoped_acquire gil{}; - py::function pySerialize - = utils::getOverride(static_cast(this), "serialize"); + py::function pySerialize = utils::getOverride(static_cast(this), "serialize"); if (!pySerialize) { utils::throwPyError(PyExc_RuntimeError, "no implementation provided for serialize()"); @@ -307,7 +311,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt try { py::gil_scoped_acquire gil{}; - if(!mIsPluginTypeInitialized) + if (!mIsPluginTypeInitialized) { utils::throwPyError(PyExc_AttributeError, "plugin_type not initialized"); } @@ -322,7 +326,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt try { py::gil_scoped_acquire gil{}; - if(!mIsPluginVersionInitialized) + if (!mIsPluginVersionInitialized) { utils::throwPyError(PyExc_AttributeError, "plugin_version not initialized"); } @@ -374,7 +378,6 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt // Remove reference to the Python plugin object so that it could be garbage-collected pyObjVec[this].dec_ref(); - } PLUGIN_API_CATCH("destroy") } @@ -393,7 +396,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt { py::gil_scoped_acquire gil{}; // getPluginNamespace() is not passed through to the Python side - if(!mIsNamespaceInitialized) + if (!mIsNamespaceInitialized) { utils::throwPyError(PyExc_AttributeError, "plugin_namespace not initialized"); } @@ -417,7 +420,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt } std::vector inVector; - for(int32_t idx = 0; idx < nbInputs; ++idx) + for (int32_t idx = 0; idx < nbInputs; ++idx) { inVector.push_back(*(inputTypes + idx)); } @@ -436,8 +439,8 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt return DataType{}; } - - DimsExprs getOutputDimensions(int32_t outputIndex, DimsExprs const* inputs, int32_t nbInputs, IExprBuilder& exprBuilder) noexcept override + DimsExprs getOutputDimensions( + int32_t outputIndex, DimsExprs const* inputs, int32_t nbInputs, IExprBuilder& exprBuilder) noexcept override { try { @@ -451,7 +454,7 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt } std::vector inVector; - for(int32_t idx = 0; idx < nbInputs; ++idx) + for (int32_t idx = 0; idx < nbInputs; ++idx) { inVector.push_back(*(inputs + idx)); } @@ -470,7 +473,8 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt return DimsExprs{}; } - void configurePlugin(DynamicPluginTensorDesc const* in, int32_t nbInputs, DynamicPluginTensorDesc const* out, int32_t nbOutputs) noexcept override + void configurePlugin(DynamicPluginTensorDesc const* in, int32_t nbInputs, DynamicPluginTensorDesc const* out, + int32_t nbOutputs) noexcept override { try { @@ -486,13 +490,13 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt } std::vector inVector; - for(int32_t idx = 0; idx < nbInputs; ++idx) + for (int32_t idx = 0; idx < nbInputs; ++idx) { inVector.push_back(*(in + idx)); } std::vector outVector; - for(int32_t idx = 0; idx < nbOutputs; ++idx) + for (int32_t idx = 0; idx < nbOutputs; ++idx) { outVector.push_back(*(out + idx)); } @@ -502,13 +506,15 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt PLUGIN_API_CATCH("configure_plugin") } - size_t getWorkspaceSize(PluginTensorDesc const* inputs, int32_t nbInputs, PluginTensorDesc const* outputs, int32_t nbOutputs) const noexcept override + size_t getWorkspaceSize(PluginTensorDesc const* inputs, int32_t nbInputs, PluginTensorDesc const* outputs, + int32_t nbOutputs) const noexcept override { try { py::gil_scoped_acquire gil{}; - py::function pyGetWorkspaceSize = py::get_override(static_cast(this), "get_workspace_size"); + py::function pyGetWorkspaceSize + = py::get_override(static_cast(this), "get_workspace_size"); if (!pyGetWorkspaceSize) { @@ -517,13 +523,13 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt } std::vector inVector; - for(int32_t idx = 0; idx < nbInputs; ++idx) + for (int32_t idx = 0; idx < nbInputs; ++idx) { inVector.push_back(*(inputs + idx)); } std::vector outVector; - for(int32_t idx = 0; idx < nbOutputs; ++idx) + for (int32_t idx = 0; idx < nbOutputs; ++idx) { outVector.push_back(*(outputs + idx)); } @@ -559,23 +565,24 @@ class PyIPluginV2DynamicExtImpl : public PyIPluginV2DynamicExt mPluginVersion = std::move(pluginVersion); mIsPluginVersionInitialized = true; } - private: - int32_t getTensorRTVersion() const noexcept override - { + +private: + int32_t getTensorRTVersion() const noexcept override + { return static_cast((static_cast(PluginVersion::kV2_DYNAMICEXT_PYTHON) << 24U) | (static_cast(NV_TENSORRT_VERSION) & 0xFFFFFFU)); - } + } - int32_t mNbInputs{}; - int32_t mNbOutputs{}; - std::string mNamespace; - std::string mPluginType; - std::string mPluginVersion; + int32_t mNbInputs{}; + int32_t mNbOutputs{}; + std::string mNamespace; + std::string mPluginType; + std::string mPluginVersion; - bool mIsNbOutputsInitialized{false}; - bool mIsNamespaceInitialized{false}; - bool mIsPluginTypeInitialized{false}; - bool mIsPluginVersionInitialized{false}; + bool mIsNbOutputsInitialized{false}; + bool mIsNamespaceInitialized{false}; + bool mIsPluginTypeInitialized{false}; + bool mIsPluginVersionInitialized{false}; }; class IPluginCreatorImpl : public IPluginCreator @@ -593,7 +600,7 @@ class IPluginCreatorImpl : public IPluginCreator try { py::gil_scoped_acquire gil{}; - if(!mIsNameInitialized) + if (!mIsNameInitialized) { utils::throwPyError(PyExc_AttributeError, "name not initialized"); } @@ -608,7 +615,7 @@ class IPluginCreatorImpl : public IPluginCreator try { py::gil_scoped_acquire gil{}; - if(!mIsPluginVersionInitialized) + if (!mIsPluginVersionInitialized) { utils::throwPyError(PyExc_AttributeError, "plugin_version not initialized"); } @@ -623,7 +630,7 @@ class IPluginCreatorImpl : public IPluginCreator try { py::gil_scoped_acquire gil{}; - if(!mIsFCInitialized) + if (!mIsFCInitialized) { utils::throwPyError(PyExc_AttributeError, "field_names not initialized"); } @@ -661,8 +668,7 @@ class IPluginCreatorImpl : public IPluginCreator return nullptr; } - IPluginV2* deserializePlugin( - const char* name, const void* serialData, size_t serialLength) noexcept override + IPluginV2* deserializePlugin(const char* name, const void* serialData, size_t serialLength) noexcept override { try { @@ -677,7 +683,9 @@ class IPluginCreatorImpl : public IPluginCreator std::string nameString{name}; - py::handle handle = pyDeserializePlugin(nameString, py::bytes(static_cast(serialData), serialLength)).release(); + py::handle handle + = pyDeserializePlugin(nameString, py::bytes(static_cast(serialData), serialLength)) + .release(); try { auto result = handle.cast(); @@ -703,7 +711,7 @@ class IPluginCreatorImpl : public IPluginCreator try { py::gil_scoped_acquire gil{}; - if(!mIsNamespaceInitialized) + if (!mIsNamespaceInitialized) { utils::throwPyError(PyExc_AttributeError, "plugin_namespace not initialized"); } @@ -1755,9 +1763,10 @@ bool isPython(IVersionedInterface const& versionedInterface) namespace lambdas { // For IPluginV2 -static const auto IPluginV2_get_output_shape = [](IPluginV2& self, int32_t const index, std::vector const& inputShapes) { - return self.getOutputDimensions(index, inputShapes.data(), inputShapes.size()); -}; +static const auto IPluginV2_get_output_shape + = [](IPluginV2& self, int32_t const index, std::vector const& inputShapes) { + return self.getOutputDimensions(index, inputShapes.data(), inputShapes.size()); + }; static const auto IPluginV2_configure_with_format = [](IPluginV2& self, std::vector const& inputShapes, std::vector const& outputShapes, DataType dtype, @@ -1789,13 +1798,14 @@ static const auto IPluginV2_serialize = [](IPluginV2& self) { }; // `const vector::data()` corresponds to `const void* const*` (pointer to const-pointer to const void) -static const auto IPluginV2_execute_async = [](IPluginV2& self, int32_t batchSize, const std::vector& inputs, - std::vector& outputs, void* workspace, long stream) { +static const auto IPluginV2_execute_async = [](IPluginV2& self, int32_t batchSize, + const std::vector& inputs, std::vector& outputs, + void* workspace, long stream) { return self.enqueue(batchSize, inputs.data(), outputs.data(), workspace, reinterpret_cast(stream)); }; static const auto IPluginV2_set_num_outputs = [](IPluginV2& self, int32_t numOutputs) { - if(getPluginVersion(self.getTensorRTVersion()) == PluginVersion::kV2_DYNAMICEXT_PYTHON) + if (getPluginVersion(self.getTensorRTVersion()) == PluginVersion::kV2_DYNAMICEXT_PYTHON) { auto plugin = static_cast(&self); plugin->setNbOutputs(numOutputs); @@ -1805,7 +1815,7 @@ static const auto IPluginV2_set_num_outputs = [](IPluginV2& self, int32_t numOut }; static const auto IPluginV2_set_plugin_type = [](IPluginV2& self, std::string pluginType) { - if(getPluginVersion(self.getTensorRTVersion()) == PluginVersion::kV2_DYNAMICEXT_PYTHON) + if (getPluginVersion(self.getTensorRTVersion()) == PluginVersion::kV2_DYNAMICEXT_PYTHON) { auto plugin = reinterpret_cast(&self); plugin->setPluginType(std::move(pluginType)); @@ -1815,7 +1825,7 @@ static const auto IPluginV2_set_plugin_type = [](IPluginV2& self, std::string pl }; static const auto IPluginV2_set_plugin_version = [](IPluginV2& self, std::string pluginVersion) { - if(getPluginVersion(self.getTensorRTVersion()) == PluginVersion::kV2_DYNAMICEXT_PYTHON) + if (getPluginVersion(self.getTensorRTVersion()) == PluginVersion::kV2_DYNAMICEXT_PYTHON) { auto plugin = reinterpret_cast(&self); plugin->setPluginVersion(std::move(pluginVersion)); @@ -1841,8 +1851,8 @@ static std::unique_ptr makeBoolArray(std::vector const& v) static const auto configure_plugin = [](IPluginV2Ext& self, std::vector const& inputShapes, std::vector const& outputShapes, std::vector const& inputTypes, std::vector const& outputTypes, - std::vector const& inputIsBroadcasted, std::vector const& outputIsBroadcasted, TensorFormat format, - int32_t maxBatchSize) { + std::vector const& inputIsBroadcasted, std::vector const& outputIsBroadcasted, + TensorFormat format, int32_t maxBatchSize) { auto inputBroadcast = makeBoolArray(inputIsBroadcasted); auto outputBroadcast = makeBoolArray(outputIsBroadcasted); return self.configurePlugin(inputShapes.data(), inputShapes.size(), outputShapes.data(), outputShapes.size(), @@ -1984,7 +1994,7 @@ static const auto dimsexprs_vector_constructor = [](std::vector(Dims::MAX_DIMS)}; PY_ASSERT_VALUE_ERROR(in.size() <= maxDims, - "Input length " + std::to_string(in.size()) + ". Max expected length is " + std::to_string(maxDims)); + "Input length " + std::to_string(in.size()) + ". Max expected length is " + std::to_string(maxDims)); // Create the Dims object. DimsExprs* self = new DimsExprs{}; @@ -2300,6 +2310,80 @@ void bindPlugin(py::module& m) .def_readwrite("opt", &DynamicPluginTensorDesc::opt) .def_readwrite("max", &DynamicPluginTensorDesc::max); + py::enum_(m, "PluginFieldType", PluginFieldTypeDoc::descr, py::module_local()) + .value("FLOAT16", PluginFieldType::kFLOAT16) + .value("FLOAT32", PluginFieldType::kFLOAT32) + .value("FLOAT64", PluginFieldType::kFLOAT64) + .value("INT8", PluginFieldType::kINT8) + .value("INT16", PluginFieldType::kINT16) + .value("INT32", PluginFieldType::kINT32) + .value("CHAR", PluginFieldType::kCHAR) + .value("DIMS", PluginFieldType::kDIMS) + .value("UNKNOWN", PluginFieldType::kUNKNOWN) + .value("BF16", PluginFieldType::kBF16) + .value("INT64", PluginFieldType::kINT64) + .value("FP8", PluginFieldType::kFP8); + + py::class_(m, "PluginField", PluginFieldDoc::descr, py::module_local()) + .def(py::init(lambdas::plugin_field_default_constructor), "name"_a = "", py::keep_alive<1, 2>{}) + .def(py::init(lambdas::plugin_field_constructor), "name"_a, "data"_a, + "type"_a = nvinfer1::PluginFieldType::kUNKNOWN, py::keep_alive<1, 2>{}, py::keep_alive<1, 3>{}) + .def_property( + "name", [](PluginField& self) { return self.name; }, + py::cpp_function( + [](PluginField& self, FallbackString& name) { self.name = name.c_str(); }, py::keep_alive<1, 2>{})) + .def_property( + "data", + [](PluginField& self) { + switch (self.type) + { + case PluginFieldType::kINT32: + return py::array(self.length, static_cast(self.data)); + break; + case PluginFieldType::kINT8: + return py::array(self.length, static_cast(self.data)); + break; + case PluginFieldType::kINT16: + return py::array(self.length, static_cast(self.data)); + break; + case PluginFieldType::kFLOAT16: + // TODO: Figure out how to handle float16 correctly here + return py::array(self.length, static_cast(self.data)); + break; + case PluginFieldType::kFLOAT32: + return py::array(self.length, static_cast(self.data)); + break; + case PluginFieldType::kFLOAT64: + return py::array(self.length, static_cast(self.data)); + break; + case PluginFieldType::kCHAR: return py::array(self.length, static_cast(self.data)); break; + default: assert(false && "No known conversion for returning data from PluginField"); break; + } + // should not reach this line + return py::array(); + }, + py::cpp_function( + [](PluginField& self, py::buffer& buffer) { + py::buffer_info info = buffer.request(); + self.data = info.ptr; + }, + py::keep_alive<1, 2>{})) + .def_readwrite("type", &PluginField::type) + .def_readwrite("size", &PluginField::length); + + // PluginFieldCollection behaves like an iterable, and can be constructed from iterables. + py::class_(m, "PluginFieldCollection_", PluginFieldCollectionDoc::descr, py::module_local()) + .def(py::init<>(lambdas::plugin_field_collection_constructor), py::keep_alive<1, 2>{}) + .def("__len__", [](PluginFieldCollection& self) { return self.nbFields; }) + .def("__getitem__", [](PluginFieldCollection& self, int32_t const index) { + PY_ASSERT_INDEX_ERROR(index < self.nbFields); + return self.fields[index]; + }); + + // Creating a trt.PluginFieldCollection in Python will actually construct a vector, + // which can then be converted to an actual C++ PluginFieldCollection. + py::implicitly_convertible, PluginFieldCollection>(); + py::class_(m, "IPluginV2", IPluginV2Doc::descr, py::module_local()) .def_property("num_outputs", &IPluginV2::getNbOutputs, lambdas::IPluginV2_set_num_outputs) .def_property_readonly("tensorrt_version", &IPluginV2::getTensorRTVersion) @@ -2337,7 +2421,8 @@ void bindPlugin(py::module& m) .def("clone", &IPluginV2Ext::clone, IPluginV2ExtDoc::clone); ; - py::class_>(m, "IPluginV2DynamicExtBase", py::module_local()); + py::class_>( + m, "IPluginV2DynamicExtBase", py::module_local()); py::class_>( @@ -2366,6 +2451,9 @@ void bindPlugin(py::module& m) "stream"_a, IPluginV2DynamicExtDoc::enqueue) .def("clone", &pluginDoc::clone, IPluginV2DynamicExtDoc::clone); + py::class_>( + m, "IPluginCapability", IPluginV3Doc::iplugincapability_descr, py::module_local()); + py::class_>( m, "IPluginV3", IPluginV3Doc::ipluginv3_descr, py::module_local()) .def(py::init<>()) @@ -2375,9 +2463,6 @@ void bindPlugin(py::module& m) .def("clone", &pluginDoc::cloneV3, IPluginV3Doc::clone) .def("destroy", &pluginDoc::destroyV3, IPluginV3Doc::destroy); - py::class_>( - m, "IPluginCapability", IPluginV3Doc::iplugincapability_descr, py::module_local()); - py::class_>( m, "IPluginV3OneCore", IPluginV3Doc::ipluginv3onecore_descr, py::module_local()) @@ -2430,80 +2515,6 @@ void bindPlugin(py::module& m) "stream"_a, IPluginV3Doc::enqueue) .def("attach_to_context", &pluginDoc::attachToContext, "resource_context"_a, IPluginV3Doc::attach_to_context); - py::enum_(m, "PluginFieldType", PluginFieldTypeDoc::descr, py::module_local()) - .value("FLOAT16", PluginFieldType::kFLOAT16) - .value("FLOAT32", PluginFieldType::kFLOAT32) - .value("FLOAT64", PluginFieldType::kFLOAT64) - .value("INT8", PluginFieldType::kINT8) - .value("INT16", PluginFieldType::kINT16) - .value("INT32", PluginFieldType::kINT32) - .value("CHAR", PluginFieldType::kCHAR) - .value("DIMS", PluginFieldType::kDIMS) - .value("UNKNOWN", PluginFieldType::kUNKNOWN) - .value("BF16", PluginFieldType::kBF16) - .value("INT64", PluginFieldType::kINT64) - .value("FP8", PluginFieldType::kFP8); - - py::class_(m, "PluginField", PluginFieldDoc::descr, py::module_local()) - .def(py::init(lambdas::plugin_field_default_constructor), "name"_a = "", py::keep_alive<1, 2>{}) - .def(py::init(lambdas::plugin_field_constructor), "name"_a, "data"_a, - "type"_a = nvinfer1::PluginFieldType::kUNKNOWN, py::keep_alive<1, 2>{}, py::keep_alive<1, 3>{}) - .def_property( - "name", [](PluginField& self) { return self.name; }, - py::cpp_function( - [](PluginField& self, FallbackString& name) { self.name = name.c_str(); }, py::keep_alive<1, 2>{})) - .def_property( - "data", - [](PluginField& self) { - switch (self.type) - { - case PluginFieldType::kINT32: - return py::array(self.length, static_cast(self.data)); - break; - case PluginFieldType::kINT8: - return py::array(self.length, static_cast(self.data)); - break; - case PluginFieldType::kINT16: - return py::array(self.length, static_cast(self.data)); - break; - case PluginFieldType::kFLOAT16: - // TODO: Figure out how to handle float16 correctly here - return py::array(self.length, static_cast(self.data)); - break; - case PluginFieldType::kFLOAT32: - return py::array(self.length, static_cast(self.data)); - break; - case PluginFieldType::kFLOAT64: - return py::array(self.length, static_cast(self.data)); - break; - case PluginFieldType::kCHAR: return py::array(self.length, static_cast(self.data)); break; - default: assert(false && "No known conversion for returning data from PluginField"); break; - } - // should not reach this line - return py::array(); - }, - py::cpp_function( - [](PluginField& self, py::buffer& buffer) { - py::buffer_info info = buffer.request(); - self.data = info.ptr; - }, - py::keep_alive<1, 2>{})) - .def_readwrite("type", &PluginField::type) - .def_readwrite("size", &PluginField::length); - - // PluginFieldCollection behaves like an iterable, and can be constructed from iterables. - py::class_(m, "PluginFieldCollection_", PluginFieldCollectionDoc::descr, py::module_local()) - .def(py::init<>(lambdas::plugin_field_collection_constructor), py::keep_alive<1, 2>{}) - .def("__len__", [](PluginFieldCollection& self) { return self.nbFields; }) - .def("__getitem__", [](PluginFieldCollection& self, int32_t const index) { - PY_ASSERT_INDEX_ERROR(index < self.nbFields); - return self.fields[index]; - }); - - // Creating a trt.PluginFieldCollection in Python will actually construct a vector, - // which can then be converted to an actual C++ PluginFieldCollection. - py::implicitly_convertible, PluginFieldCollection>(); - py::class_( m, "IPluginCreatorInterface", IPluginCreatorInterfaceDoc::descr, py::module_local()); diff --git a/python/src/parsers/pyOnnx.cpp b/python/src/parsers/pyOnnx.cpp index 122fc219..9059a3b7 100644 --- a/python/src/parsers/pyOnnx.cpp +++ b/python/src/parsers/pyOnnx.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/src/pyTensorRT.cpp b/python/src/pyTensorRT.cpp index a7fe0017..c562703a 100644 --- a/python/src/pyTensorRT.cpp +++ b/python/src/pyTensorRT.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/python/src/utils.cpp b/python/src/utils.cpp index 46e8b3ba..de601542 100644 --- a/python/src/utils.cpp +++ b/python/src/utils.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -44,7 +44,7 @@ size_t size(nvinfer1::DataType type) case nvinfer1::DataType::kUINT8: return 1; case nvinfer1::DataType::kFP8: return 1; case nvinfer1::DataType::kBF16: return 2; - case nvinfer1::DataType::kINT4: break; //TRT-22011 - need to address sub-byte element size + case nvinfer1::DataType::kINT4: break; // TRT-22011 - need to address sub-byte element size } return -1; } diff --git a/quickstart/IntroNotebooks/Additional Examples/helper.py b/quickstart/IntroNotebooks/Additional Examples/helper.py index 66c4e006..c00ed985 100644 --- a/quickstart/IntroNotebooks/Additional Examples/helper.py +++ b/quickstart/IntroNotebooks/Additional Examples/helper.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/IntroNotebooks/helper.py b/quickstart/IntroNotebooks/helper.py index 66c4e006..c00ed985 100644 --- a/quickstart/IntroNotebooks/helper.py +++ b/quickstart/IntroNotebooks/helper.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/IntroNotebooks/onnx_helper.py b/quickstart/IntroNotebooks/onnx_helper.py index 6bea97dd..2f3d6767 100644 --- a/quickstart/IntroNotebooks/onnx_helper.py +++ b/quickstart/IntroNotebooks/onnx_helper.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/Makefile b/quickstart/Makefile index bf728ff4..1e700e3d 100644 --- a/quickstart/Makefile +++ b/quickstart/Makefile @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/Makefile.config b/quickstart/Makefile.config index d81f325d..0d290ea5 100644 --- a/quickstart/Makefile.config +++ b/quickstart/Makefile.config @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/SemanticSegmentation/Makefile b/quickstart/SemanticSegmentation/Makefile index 5c1bdea3..3c1f68d0 100644 --- a/quickstart/SemanticSegmentation/Makefile +++ b/quickstart/SemanticSegmentation/Makefile @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/SemanticSegmentation/export.py b/quickstart/SemanticSegmentation/export.py index e5168aaa..560e233e 100644 --- a/quickstart/SemanticSegmentation/export.py +++ b/quickstart/SemanticSegmentation/export.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/SemanticSegmentation/tutorial-runtime.cpp b/quickstart/SemanticSegmentation/tutorial-runtime.cpp index 7f0854a3..c1f09197 100644 --- a/quickstart/SemanticSegmentation/tutorial-runtime.cpp +++ b/quickstart/SemanticSegmentation/tutorial-runtime.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/quickstart/common/logger.cpp b/quickstart/common/logger.cpp index 2eaccd54..9d07754c 100644 --- a/quickstart/common/logger.cpp +++ b/quickstart/common/logger.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/quickstart/common/logger.h b/quickstart/common/logger.h index 513275c2..35cbf367 100644 --- a/quickstart/common/logger.h +++ b/quickstart/common/logger.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/quickstart/common/logging.h b/quickstart/common/logging.h index f323d22b..d891e168 100644 --- a/quickstart/common/logging.h +++ b/quickstart/common/logging.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/quickstart/common/util.cpp b/quickstart/common/util.cpp index 717b63aa..55ccd630 100644 --- a/quickstart/common/util.cpp +++ b/quickstart/common/util.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/quickstart/common/util.h b/quickstart/common/util.h index 50455e97..55457969 100644 --- a/quickstart/common/util.h +++ b/quickstart/common/util.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/quickstart/deploy_to_triton/config.pbtxt b/quickstart/deploy_to_triton/config.pbtxt index 63046c8d..f65a9c55 100644 --- a/quickstart/deploy_to_triton/config.pbtxt +++ b/quickstart/deploy_to_triton/config.pbtxt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/deploy_to_triton/export_resnet_to_onnx.py b/quickstart/deploy_to_triton/export_resnet_to_onnx.py index fba1550a..64d6b137 100644 --- a/quickstart/deploy_to_triton/export_resnet_to_onnx.py +++ b/quickstart/deploy_to_triton/export_resnet_to_onnx.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/quickstart/deploy_to_triton/triton_client.py b/quickstart/deploy_to_triton/triton_client.py index a6e7553d..1575e208 100644 --- a/quickstart/deploy_to_triton/triton_client.py +++ b/quickstart/deploy_to_triton/triton_client.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 1c26cc38..513810d9 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/CMakeSamplesTemplate.txt b/samples/CMakeSamplesTemplate.txt index d4f78ae5..285e3f99 100644 --- a/samples/CMakeSamplesTemplate.txt +++ b/samples/CMakeSamplesTemplate.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -62,11 +62,11 @@ add_executable(${TARGET_NAME} set(DEPS_LIST "") if(BUILD_PLUGINS) - list(APPEND DEPS_LIST nvinfer_plugin) + list(APPEND DEPS_LIST ${nvinfer_plugin_lib_name}) endif() if(BUILD_PARSERS) - list(APPEND DEPS_LIST nvonnxparser) + list(APPEND DEPS_LIST ${nvonnxparser_lib_name}) endif() if(BUILD_PLUGINS OR BUILD_PARSERS) @@ -93,7 +93,7 @@ target_compile_options(${TARGET_NAME} PUBLIC set(SAMPLE_DEP_LIBS ${CUDART_LIB} - ${nvinfer_LIB_PATH} + ${${nvinfer_lib_name}_LIB_PATH} ${RT_LIB} ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} @@ -104,17 +104,17 @@ if (NOT MSVC) endif() if(${PLUGINS_NEEDED}) - list(APPEND SAMPLE_DEP_LIBS nvinfer_plugin) + list(APPEND SAMPLE_DEP_LIBS ${nvinfer_plugin_lib_name}) endif() if("onnx" IN_LIST SAMPLE_PARSERS) - list(APPEND SAMPLE_DEP_LIBS nvonnxparser) + list(APPEND SAMPLE_DEP_LIBS ${nvonnxparser_lib_name}) endif() -# Necessary to link nvinfer_plugin library. +# Necessary to link nvinfer_plugin library. Add unresolved symbols flag for non-Windows platforms. target_link_libraries(${TARGET_NAME} ${SAMPLE_DEP_LIBS} - -Wl,--unresolved-symbols=ignore-in-shared-libs + $<$>:-Wl,--unresolved-symbols=ignore-in-shared-libs> ) set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "-Wl,--exclude-libs,ALL") diff --git a/samples/common/BatchStream.h b/samples/common/BatchStream.h index f6da8d70..c4ab9de0 100644 --- a/samples/common/BatchStream.h +++ b/samples/common/BatchStream.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/EntropyCalibrator.h b/samples/common/EntropyCalibrator.h index 936d10e0..67a0130e 100644 --- a/samples/common/EntropyCalibrator.h +++ b/samples/common/EntropyCalibrator.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/ErrorRecorder.h b/samples/common/ErrorRecorder.h index cd00f745..bfb857c5 100644 --- a/samples/common/ErrorRecorder.h +++ b/samples/common/ErrorRecorder.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -17,7 +17,7 @@ #ifndef ERROR_RECORDER_H #define ERROR_RECORDER_H -#include "NvInferRuntimeBase.h" +#include "NvInferRuntime.h" #include "logger.h" #include #include diff --git a/samples/common/argsParser.h b/samples/common/argsParser.h index 745070d9..b302dc47 100644 --- a/samples/common/argsParser.h +++ b/samples/common/argsParser.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -68,7 +68,7 @@ struct Args std::vector dataDirs; std::string saveEngine; std::string loadEngine; - bool rowMajor{true}; + bool rowOrder{true}; }; //! @@ -85,7 +85,7 @@ inline bool parseArgs(Args& args, int32_t argc, char* argv[]) int32_t arg; static struct option long_options[] = {{"help", no_argument, 0, 'h'}, {"datadir", required_argument, 0, 'd'}, {"int8", no_argument, 0, 'i'}, {"fp16", no_argument, 0, 'f'}, {"bf16", no_argument, 0, 'z'}, - {"columnMajor", no_argument, 0, 'c'}, {"saveEngine", required_argument, 0, 's'}, + {"columnOrder", no_argument, 0, 'c'}, {"saveEngine", required_argument, 0, 's'}, {"loadEngine", required_argument, 0, 'o'}, {"useDLACore", required_argument, 0, 'u'}, {"batch", required_argument, 0, 'b'}, {nullptr, 0, nullptr, 0}}; int32_t option_index = 0; @@ -124,7 +124,7 @@ inline bool parseArgs(Args& args, int32_t argc, char* argv[]) case 'i': args.runInInt8 = true; break; case 'f': args.runInFp16 = true; break; case 'z': args.runInBf16 = true; break; - case 'c': args.rowMajor = false; break; + case 'c': args.rowOrder = false; break; case 'u': if (optarg) { diff --git a/samples/common/bfloat16.cpp b/samples/common/bfloat16.cpp index a9944789..8222826a 100644 --- a/samples/common/bfloat16.cpp +++ b/samples/common/bfloat16.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/bfloat16.h b/samples/common/bfloat16.h index 90b77421..0d0ab922 100644 --- a/samples/common/bfloat16.h +++ b/samples/common/bfloat16.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/buffers.h b/samples/common/buffers.h index bf40dc9c..e58f2f5c 100644 --- a/samples/common/buffers.h +++ b/samples/common/buffers.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/common.h b/samples/common/common.h index 557bd169..0324d2fb 100644 --- a/samples/common/common.h +++ b/samples/common/common.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/dumpTFWts.py b/samples/common/dumpTFWts.py index 0b7a0123..70770fbd 100644 --- a/samples/common/dumpTFWts.py +++ b/samples/common/dumpTFWts.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/common/getOptions.cpp b/samples/common/getOptions.cpp index 8bcf7958..19cd3281 100644 --- a/samples/common/getOptions.cpp +++ b/samples/common/getOptions.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/getOptions.h b/samples/common/getOptions.h index e8460513..4bbf9e27 100644 --- a/samples/common/getOptions.h +++ b/samples/common/getOptions.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/getoptWin.h b/samples/common/getoptWin.h index 7e1cf1ba..a1dc6ffa 100644 --- a/samples/common/getoptWin.h +++ b/samples/common/getoptWin.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/half.h b/samples/common/half.h index c5ebdb1a..b997e7db 100644 --- a/samples/common/half.h +++ b/samples/common/half.h @@ -16,7 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/logger.cpp b/samples/common/logger.cpp index 0592db2c..909ec0bb 100644 --- a/samples/common/logger.cpp +++ b/samples/common/logger.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/logger.h b/samples/common/logger.h index ff59bfa9..8205e457 100644 --- a/samples/common/logger.h +++ b/samples/common/logger.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/logging.h b/samples/common/logging.h index e61b3687..d2c571d9 100644 --- a/samples/common/logging.h +++ b/samples/common/logging.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -18,7 +18,7 @@ #ifndef TENSORRT_LOGGING_H #define TENSORRT_LOGGING_H -#include "NvInferRuntimeBase.h" +#include "NvInferRuntime.h" #include "sampleOptions.h" #include #include diff --git a/samples/common/parserOnnxConfig.h b/samples/common/parserOnnxConfig.h index ed0a9b55..67ee6c71 100644 --- a/samples/common/parserOnnxConfig.h +++ b/samples/common/parserOnnxConfig.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/safeCommon.h b/samples/common/safeCommon.h index fc9f28b0..4cc87a70 100644 --- a/samples/common/safeCommon.h +++ b/samples/common/safeCommon.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -18,7 +18,7 @@ #ifndef TENSORRT_SAFE_COMMON_H #define TENSORRT_SAFE_COMMON_H -#include "NvInferRuntimeBase.h" +#include "NvInferSafeRuntime.h" #include "cuda_runtime.h" #include "sampleEntrypoints.h" #include diff --git a/samples/common/sampleConfig.h b/samples/common/sampleConfig.h index f60ed363..801a268a 100644 --- a/samples/common/sampleConfig.h +++ b/samples/common/sampleConfig.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleDevice.cpp b/samples/common/sampleDevice.cpp index f504fa69..235ad9f0 100644 --- a/samples/common/sampleDevice.cpp +++ b/samples/common/sampleDevice.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleDevice.h b/samples/common/sampleDevice.h index ad122180..5e62f6d0 100644 --- a/samples/common/sampleDevice.h +++ b/samples/common/sampleDevice.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleEngines.cpp b/samples/common/sampleEngines.cpp index bea07a53..b39d513b 100644 --- a/samples/common/sampleEngines.cpp +++ b/samples/common/sampleEngines.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -654,7 +654,15 @@ void setMemoryPoolLimits(IBuilderConfig& config, BuildOptions const& build) } if (build.tacticSharedMem >= 0) { - config.setMemoryPoolLimit(MemoryPoolType::kTACTIC_SHARED_MEMORY, roundToBytes(build.tacticSharedMem)); + if (build.tacticSharedMem >= 0.046 && build.tacticSharedMem <= 0.047) + { + // 48KB is a common use case but user might not type the exact number 0.046875MB. + config.setMemoryPoolLimit(MemoryPoolType::kTACTIC_SHARED_MEMORY, 48 << 10); + } + else + { + config.setMemoryPoolLimit(MemoryPoolType::kTACTIC_SHARED_MEMORY, roundToBytes(build.tacticSharedMem)); + } } } diff --git a/samples/common/sampleEngines.h b/samples/common/sampleEngines.h index f6cff080..4c4272b7 100644 --- a/samples/common/sampleEngines.h +++ b/samples/common/sampleEngines.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleEntrypoints.h b/samples/common/sampleEntrypoints.h index 70f45dde..cc8bf1b9 100644 --- a/samples/common/sampleEntrypoints.h +++ b/samples/common/sampleEntrypoints.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleInference.cpp b/samples/common/sampleInference.cpp index dfc76708..024dd6f6 100644 --- a/samples/common/sampleInference.cpp +++ b/samples/common/sampleInference.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -620,8 +620,10 @@ class EnqueueExplicit : private Enqueue try { bool const result = mContext.enqueueV3(stream.get()); - // Collecting layer timing info from current profile index of execution context - if (mContext.getProfiler() && !mContext.getEnqueueEmitsProfile() && !mContext.reportToProfiler()) + // Collecting layer timing info from current profile index of execution context, except under capturing + // mode. + if (!isStreamCapturing(stream) && mContext.getProfiler() && !mContext.getEnqueueEmitsProfile() + && !mContext.reportToProfiler()) { gLogWarning << "Failed to collect layer timing info from previous enqueueV3()" << std::endl; } @@ -635,6 +637,14 @@ class EnqueueExplicit : private Enqueue } private: + // Helper function to check if a stream is in capturing mode. + bool isStreamCapturing(TrtCudaStream& stream) const + { + cudaStreamCaptureStatus status{cudaStreamCaptureStatusNone}; + cudaCheck(cudaStreamIsCapturing(stream.get(), &status)); + return status != cudaStreamCaptureStatusNone; + } + Bindings const& mBindings; }; @@ -931,6 +941,8 @@ class Iteration mEnqueue = EnqueueFunction(EnqueueExplicit(context, mBindings)); if (inference.graph) { + sample::gLogInfo << "Capturing CUDA graph for the current execution context" << std::endl; + TrtCudaStream& stream = getStream(StreamType::kCOMPUTE); // Avoid capturing initialization calls by executing the enqueue function at least // once before starting CUDA graph capture. @@ -948,6 +960,7 @@ class Iteration { mGraph.endCapture(stream); mEnqueue = EnqueueFunction(EnqueueGraph(context, mGraph)); + sample::gLogInfo << "Successfully captured CUDA graph for the current execution context" << std::endl; } else { diff --git a/samples/common/sampleInference.h b/samples/common/sampleInference.h index e726cb31..e8e53bb7 100644 --- a/samples/common/sampleInference.h +++ b/samples/common/sampleInference.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleOptions.cpp b/samples/common/sampleOptions.cpp index 575668e1..7f2bd9f1 100644 --- a/samples/common/sampleOptions.cpp +++ b/samples/common/sampleOptions.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleOptions.h b/samples/common/sampleOptions.h index 00e8b15d..cddbc60d 100644 --- a/samples/common/sampleOptions.h +++ b/samples/common/sampleOptions.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleReporting.cpp b/samples/common/sampleReporting.cpp index 3c8efab0..1d3e2ca5 100644 --- a/samples/common/sampleReporting.cpp +++ b/samples/common/sampleReporting.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleReporting.h b/samples/common/sampleReporting.h index 8cab62ba..c6813fe6 100644 --- a/samples/common/sampleReporting.h +++ b/samples/common/sampleReporting.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleUtils.cpp b/samples/common/sampleUtils.cpp index 7f827bc8..522cde65 100644 --- a/samples/common/sampleUtils.cpp +++ b/samples/common/sampleUtils.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/sampleUtils.h b/samples/common/sampleUtils.h index 32d5f1b0..6cd4280b 100644 --- a/samples/common/sampleUtils.h +++ b/samples/common/sampleUtils.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/common/streamReader.h b/samples/common/streamReader.h index 657e35b8..7d4aa1c6 100644 --- a/samples/common/streamReader.h +++ b/samples/common/streamReader.h @@ -18,7 +18,7 @@ #ifndef STREAM_READER_H #define STREAM_READER_H -#include "NvInferRuntimeBase.h" +#include "NvInferRuntime.h" #include "sampleUtils.h" #include diff --git a/samples/python/common.py b/samples/python/common.py index f289c366..10b2c323 100644 --- a/samples/python/common.py +++ b/samples/python/common.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,16 +27,21 @@ except NameError: FileNotFoundError = IOError + def GiB(val): return val * 1 << 30 def add_help(description): - parser = argparse.ArgumentParser(description=description, formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) args, _ = parser.parse_known_args() -def find_sample_data(description="Runs a TensorRT Python sample", subfolder="", find_files=[], err_msg=""): +def find_sample_data( + description="Runs a TensorRT Python sample", subfolder="", find_files=[], err_msg="" +): """ Parses sample arguments. @@ -51,7 +56,9 @@ def find_sample_data(description="Runs a TensorRT Python sample", subfolder="", # Standard command-line arguments for all samples. kDEFAULT_DATA_ROOT = os.path.join(os.sep, "usr", "src", "tensorrt", "data") - parser = argparse.ArgumentParser(description=description, formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) parser.add_argument( "-d", "--datadir", @@ -66,7 +73,13 @@ def get_data_path(data_dir): data_path = os.path.join(data_dir, subfolder) if not os.path.exists(data_path): if data_dir != kDEFAULT_DATA_ROOT: - print("WARNING: " + data_path + " does not exist. Trying " + data_dir + " instead.") + print( + "WARNING: " + + data_path + + " does not exist. Trying " + + data_dir + + " instead." + ) data_path = data_dir # Make sure data directory exists. if not (os.path.exists(data_path)) and data_dir != kDEFAULT_DATA_ROOT: @@ -109,10 +122,13 @@ def locate_files(data_paths, filenames, err_msg=""): for f, filename in zip(found_files, filenames): if not f or not os.path.exists(f): raise FileNotFoundError( - "Could not find {:}. Searched in data paths: {:}\n{:}".format(filename, data_paths, err_msg) + "Could not find {:}. Searched in data paths: {:}\n{:}".format( + filename, data_paths, err_msg + ) ) return found_files + # Sets up the builder to use the timing cache file, and creates it if it does not already exist def setup_timing_cache(config: trt.IBuilderConfig, timing_cache_path: os.PathLike): buffer = b"" @@ -122,8 +138,9 @@ def setup_timing_cache(config: trt.IBuilderConfig, timing_cache_path: os.PathLik timing_cache: trt.ITimingCache = config.create_timing_cache(buffer) config.set_timing_cache(timing_cache, True) + # Saves the config's timing cache to file def save_timing_cache(config: trt.IBuilderConfig, timing_cache_path: os.PathLike): timing_cache: trt.ITimingCache = config.get_timing_cache() - with open(timing_cache_path, 'wb') as timing_cache_file: + with open(timing_cache_path, "wb") as timing_cache_file: timing_cache_file.write(memoryview(timing_cache.serialize())) diff --git a/samples/python/detectron2/build_engine.py b/samples/python/detectron2/build_engine.py index aa6f5795..c62b941c 100644 --- a/samples/python/detectron2/build_engine.py +++ b/samples/python/detectron2/build_engine.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -33,6 +33,7 @@ sys.path.insert(1, os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) import common + class EngineCalibrator(trt.IInt8MinMaxCalibrator): """ Implements the INT8 MinMax Calibrator. @@ -55,7 +56,10 @@ def set_image_batcher(self, image_batcher: ImageBatcher): :param image_batcher: The ImageBatcher object """ self.image_batcher = image_batcher - self.size = int(np.dtype(self.image_batcher.dtype).itemsize * np.prod(self.image_batcher.shape)) + self.size = int( + np.dtype(self.image_batcher.dtype).itemsize + * np.prod(self.image_batcher.shape) + ) self.batch_allocation = common.cuda_call(cudart.cudaMalloc(self.size)) self.batch_generator = self.image_batcher.get_batch() @@ -80,8 +84,14 @@ def get_batch(self, names): return None try: batch, _, _ = next(self.batch_generator) - log.info("Calibrating image {} / {}".format(self.image_batcher.image_index, self.image_batcher.num_images)) - common.memcpy_host_to_device(self.batch_allocation, np.ascontiguousarray(batch)) + log.info( + "Calibrating image {} / {}".format( + self.image_batcher.image_index, self.image_batcher.num_images + ) + ) + common.memcpy_host_to_device( + self.batch_allocation, np.ascontiguousarray(batch) + ) return [int(self.batch_allocation)] except StopIteration: @@ -130,7 +140,9 @@ def __init__(self, verbose=False, workspace=8): self.builder = trt.Builder(self.trt_logger) self.config = self.builder.create_builder_config() - self.config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace * (2 ** 30)) + self.config.set_memory_pool_limit( + trt.MemoryPoolType.WORKSPACE, workspace * (2**30) + ) self.batch_size = None self.network = None @@ -158,13 +170,29 @@ def create_network(self, onnx_path): log.info("Network Description") for input in inputs: self.batch_size = input.shape[0] - log.info("Input '{}' with shape {} and dtype {}".format(input.name, input.shape, input.dtype)) + log.info( + "Input '{}' with shape {} and dtype {}".format( + input.name, input.shape, input.dtype + ) + ) for output in outputs: - log.info("Output '{}' with shape {} and dtype {}".format(output.name, output.shape, output.dtype)) + log.info( + "Output '{}' with shape {} and dtype {}".format( + output.name, output.shape, output.dtype + ) + ) assert self.batch_size > 0 - def create_engine(self, engine_path, precision, config_file, calib_input=None, calib_cache=None, calib_num_images=5000, - calib_batch_size=8): + def create_engine( + self, + engine_path, + precision, + config_file, + calib_input=None, + calib_cache=None, + calib_num_images=5000, + calib_batch_size=8, + ): """ Build the TensorRT engine and serialize it to disk. :param engine_path: The path where to serialize the engine to. @@ -194,8 +222,15 @@ def create_engine(self, engine_path, precision, config_file, calib_input=None, c calib_shape = [calib_batch_size] + list(inputs[0].shape[1:]) calib_dtype = trt.nptype(inputs[0].dtype) self.config.int8_calibrator.set_image_batcher( - ImageBatcher(calib_input, calib_shape, calib_dtype, max_num_images=calib_num_images, - exact_batches=True, config_file=config_file)) + ImageBatcher( + calib_input, + calib_shape, + calib_dtype, + max_num_images=calib_num_images, + exact_batches=True, + config_file=config_file, + ) + ) engine_bytes = self.builder.build_serialized_network(self.network, self.config) if engine_bytes is None: @@ -210,34 +245,76 @@ def create_engine(self, engine_path, precision, config_file, calib_input=None, c def main(args): builder = EngineBuilder(args.verbose, args.workspace) builder.create_network(args.onnx) - builder.create_engine(args.engine, args.precision, args.det2_config, args.calib_input, args.calib_cache, args.calib_num_images, - args.calib_batch_size) + builder.create_engine( + args.engine, + args.precision, + args.det2_config, + args.calib_input, + args.calib_cache, + args.calib_num_images, + args.calib_batch_size, + ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-o", "--onnx", help="The input ONNX model file to load") parser.add_argument("-e", "--engine", help="The output path for the TRT engine") - parser.add_argument("-c", "--det2_config", default=None, help="The Detectron 2 config file (.yaml) for the model", type=str) - parser.add_argument("-p", "--precision", default="fp16", choices=["fp32", "fp16", "int8"], - help="The precision mode to build in, either fp32/fp16/int8, default: 'fp16'") - parser.add_argument("-v", "--verbose", action="store_true", help="Enable more verbose log output") - parser.add_argument("-w", "--workspace", default=1, type=int, help="The max memory workspace size to allow in Gb, " - "default: 1") - parser.add_argument("--calib_input", help="The directory holding images to use for calibration") - parser.add_argument("--calib_cache", default="./calibration.cache", - help="The file path for INT8 calibration cache to use, default: ./calibration.cache") - parser.add_argument("--calib_num_images", default=5000, type=int, - help="The maximum number of images to use for calibration, default: 5000") - parser.add_argument("--calib_batch_size", default=8, type=int, - help="The batch size for the calibration process, default: 8") + parser.add_argument( + "-c", + "--det2_config", + default=None, + help="The Detectron 2 config file (.yaml) for the model", + type=str, + ) + parser.add_argument( + "-p", + "--precision", + default="fp16", + choices=["fp32", "fp16", "int8"], + help="The precision mode to build in, either fp32/fp16/int8, default: 'fp16'", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable more verbose log output" + ) + parser.add_argument( + "-w", + "--workspace", + default=1, + type=int, + help="The max memory workspace size to allow in Gb, " "default: 1", + ) + parser.add_argument( + "--calib_input", help="The directory holding images to use for calibration" + ) + parser.add_argument( + "--calib_cache", + default="./calibration.cache", + help="The file path for INT8 calibration cache to use, default: ./calibration.cache", + ) + parser.add_argument( + "--calib_num_images", + default=5000, + type=int, + help="The maximum number of images to use for calibration, default: 5000", + ) + parser.add_argument( + "--calib_batch_size", + default=8, + type=int, + help="The batch size for the calibration process, default: 8", + ) args = parser.parse_args() if not all([args.onnx, args.engine]): parser.print_help() log.error("These arguments are required: --onnx and --engine") sys.exit(1) - if args.precision in ["int8"] and not (args.calib_input or os.path.exists(args.calib_cache)): + if args.precision in ["int8"] and not ( + args.calib_input or os.path.exists(args.calib_cache) + ): parser.print_help() - log.error("When building in int8 precision, --calib_input or an existing --calib_cache file is required") + log.error( + "When building in int8 precision, --calib_input or an existing --calib_cache file is required" + ) sys.exit(1) main(args) diff --git a/samples/python/detectron2/create_onnx.py b/samples/python/detectron2/create_onnx.py index 38538464..478ead75 100644 --- a/samples/python/detectron2/create_onnx.py +++ b/samples/python/detectron2/create_onnx.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -34,7 +34,9 @@ from detectron2.structures import ImageList except ImportError: print("Could not import Detectron 2 modules. Maybe you did not install Detectron 2") - print("Please install Detectron 2, check https://github.com/facebookresearch/detectron2/blob/main/INSTALL.md") + print( + "Please install Detectron 2, check https://github.com/facebookresearch/detectron2/blob/main/INSTALL.md" + ) sys.exit(1) import onnx_utils @@ -81,14 +83,24 @@ def det2_setup(config_file, weights): self.first_NMS_max_proposals = self.det2_cfg.MODEL.RPN.POST_NMS_TOPK_TEST self.first_NMS_iou_threshold = self.det2_cfg.MODEL.RPN.NMS_THRESH self.first_NMS_score_threshold = 0.01 - self.first_ROIAlign_pooled_size = self.det2_cfg.MODEL.ROI_BOX_HEAD.POOLER_RESOLUTION - self.first_ROIAlign_sampling_ratio = self.det2_cfg.MODEL.ROI_BOX_HEAD.POOLER_SAMPLING_RATIO + self.first_ROIAlign_pooled_size = ( + self.det2_cfg.MODEL.ROI_BOX_HEAD.POOLER_RESOLUTION + ) + self.first_ROIAlign_sampling_ratio = ( + self.det2_cfg.MODEL.ROI_BOX_HEAD.POOLER_SAMPLING_RATIO + ) self.first_ROIAlign_type = self.det2_cfg.MODEL.ROI_BOX_HEAD.POOLER_TYPE self.second_NMS_max_proposals = self.det2_cfg.TEST.DETECTIONS_PER_IMAGE self.second_NMS_iou_threshold = self.det2_cfg.MODEL.ROI_HEADS.NMS_THRESH_TEST - self.second_NMS_score_threshold = self.det2_cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST - self.second_ROIAlign_pooled_size = self.det2_cfg.MODEL.ROI_MASK_HEAD.POOLER_RESOLUTION - self.second_ROIAlign_sampling_ratio = self.det2_cfg.MODEL.ROI_MASK_HEAD.POOLER_SAMPLING_RATIO + self.second_NMS_score_threshold = ( + self.det2_cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST + ) + self.second_ROIAlign_pooled_size = ( + self.det2_cfg.MODEL.ROI_MASK_HEAD.POOLER_RESOLUTION + ) + self.second_ROIAlign_sampling_ratio = ( + self.det2_cfg.MODEL.ROI_MASK_HEAD.POOLER_SAMPLING_RATIO + ) self.second_ROIAlign_type = self.det2_cfg.MODEL.ROI_MASK_HEAD.POOLER_TYPE self.mask_out_res = 28 @@ -97,17 +109,37 @@ def det2_setup(config_file, weights): log.info("Number of classes is {}".format(self.num_classes)) log.info("First NMS max proposals is {}".format(self.first_NMS_max_proposals)) log.info("First NMS iou threshold is {}".format(self.first_NMS_iou_threshold)) - log.info("First NMS score threshold is {}".format(self.first_NMS_score_threshold)) + log.info( + "First NMS score threshold is {}".format(self.first_NMS_score_threshold) + ) log.info("First ROIAlign type is {}".format(self.first_ROIAlign_type)) - log.info("First ROIAlign pooled size is {}".format(self.first_ROIAlign_pooled_size)) - log.info("First ROIAlign sampling ratio is {}".format(self.first_ROIAlign_sampling_ratio)) + log.info( + "First ROIAlign pooled size is {}".format(self.first_ROIAlign_pooled_size) + ) + log.info( + "First ROIAlign sampling ratio is {}".format( + self.first_ROIAlign_sampling_ratio + ) + ) log.info("Second NMS max proposals is {}".format(self.second_NMS_max_proposals)) log.info("Second NMS iou threshold is {}".format(self.second_NMS_iou_threshold)) - log.info("Second NMS score threshold is {}".format(self.second_NMS_score_threshold)) + log.info( + "Second NMS score threshold is {}".format(self.second_NMS_score_threshold) + ) log.info("Second ROIAlign type is {}".format(self.second_ROIAlign_type)) - log.info("Second ROIAlign pooled size is {}".format(self.second_ROIAlign_pooled_size)) - log.info("Second ROIAlign sampling ratio is {}".format(self.second_ROIAlign_sampling_ratio)) - log.info("Individual mask output resolution is {}x{}".format(self.mask_out_res, self.mask_out_res)) + log.info( + "Second ROIAlign pooled size is {}".format(self.second_ROIAlign_pooled_size) + ) + log.info( + "Second ROIAlign sampling ratio is {}".format( + self.second_ROIAlign_sampling_ratio + ) + ) + log.info( + "Individual mask output resolution is {}x{}".format( + self.mask_out_res, self.mask_out_res + ) + ) self.batch_size = None @@ -128,12 +160,16 @@ def sanitize(self): model = shape_inference.infer_shapes(model) self.graph = gs.import_onnx(model) except Exception as e: - log.info("Shape inference could not be performed at this time:\n{}".format(e)) + log.info( + "Shape inference could not be performed at this time:\n{}".format(e) + ) try: self.graph.fold_constants(fold_shapes=True) except TypeError as e: - log.error("This version of ONNX GraphSurgeon does not support folding shapes, please upgrade your " - "onnx_graphsurgeon module. Error:\n{}".format(e)) + log.error( + "This version of ONNX GraphSurgeon does not support folding shapes, please upgrade your " + "onnx_graphsurgeon module. Error:\n{}".format(e) + ) raise count_after = len(self.graph.nodes) @@ -182,7 +218,9 @@ def get_anchors(self, sample_image): p4_anchors = det2_anchors[2].tensor.detach().cpu().numpy() p5_anchors = det2_anchors[3].tensor.detach().cpu().numpy() p6_anchors = det2_anchors[4].tensor.detach().cpu().numpy() - final_anchors = np.concatenate((p2_anchors,p3_anchors,p4_anchors,p5_anchors,p6_anchors)) + final_anchors = np.concatenate( + (p2_anchors, p3_anchors, p4_anchors, p5_anchors, p6_anchors) + ) return final_anchors @@ -214,18 +252,29 @@ def update_preprocessor(self, batch_size): self.graph.inputs[0].name = "input_tensor" self.sanitize() - log.info("ONNX graph input shape: {} [NCHW format set]".format(self.graph.inputs[0].shape)) + log.info( + "ONNX graph input shape: {} [NCHW format set]".format( + self.graph.inputs[0].shape + ) + ) # Find the initial nodes of the graph, whatever the input is first connected to, and disconnect them. - for node in [node for node in self.graph.nodes if self.graph.inputs[0] in node.inputs]: + for node in [ + node for node in self.graph.nodes if self.graph.inputs[0] in node.inputs + ]: node.inputs.clear() # Get input tensor. input_tensor = self.graph.inputs[0] # Create preprocessing Sub node and connect input tensor to it. - sub_const = np.expand_dims(np.asarray([255 * 0.406, 255 * 0.456, 255 * 0.485], dtype=np.float32), axis=(1, 2)) - sub_out = self.graph.op_with_const("Sub", "preprocessor/mean", input_tensor, sub_const) + sub_const = np.expand_dims( + np.asarray([255 * 0.406, 255 * 0.456, 255 * 0.485], dtype=np.float32), + axis=(1, 2), + ) + sub_out = self.graph.op_with_const( + "Sub", "preprocessor/mean", input_tensor, sub_const + ) # Find first Div node and connect to output of Sub node. div_node = self.graph.find_node_by_op("Div") @@ -242,7 +291,19 @@ def update_preprocessor(self, batch_size): if type(node.inputs[1]) == gs.Constant and node.inputs[1].values[0] == 1: node.inputs[1].values[0] = self.batch_size - def NMS(self, boxes, scores, anchors, background_class, score_activation, max_proposals, iou_threshold, nms_score_threshold, user_threshold, nms_name=None): + def NMS( + self, + boxes, + scores, + anchors, + background_class, + score_activation, + max_proposals, + iou_threshold, + nms_score_threshold, + user_threshold, + nms_name=None, + ): # Helper function to create the NMS Plugin node with the selected inputs. # EfficientNMS_TRT TensorRT Plugin is suitable for our use case. # :param boxes: The box predictions from the Box Net. @@ -263,41 +324,71 @@ def NMS(self, boxes, scores, anchors, background_class, score_activation, max_pr nms_name = "_" + nms_name # Set score threshold. - score_threshold = nms_score_threshold if user_threshold is None else user_threshold + score_threshold = ( + nms_score_threshold if user_threshold is None else user_threshold + ) # NMS Outputs. - nms_output_num_detections = gs.Variable(name="num_detections"+nms_name, dtype=np.int32, shape=[self.batch_size, 1]) - nms_output_boxes = gs.Variable(name="detection_boxes"+nms_name, dtype=np.float32, - shape=[self.batch_size, max_proposals, 4]) - nms_output_scores = gs.Variable(name="detection_scores"+nms_name, dtype=np.float32, - shape=[self.batch_size, max_proposals]) - nms_output_classes = gs.Variable(name="detection_classes"+nms_name, dtype=np.int32, - shape=[self.batch_size, max_proposals]) + nms_output_num_detections = gs.Variable( + name="num_detections" + nms_name, dtype=np.int32, shape=[self.batch_size, 1] + ) + nms_output_boxes = gs.Variable( + name="detection_boxes" + nms_name, + dtype=np.float32, + shape=[self.batch_size, max_proposals, 4], + ) + nms_output_scores = gs.Variable( + name="detection_scores" + nms_name, + dtype=np.float32, + shape=[self.batch_size, max_proposals], + ) + nms_output_classes = gs.Variable( + name="detection_classes" + nms_name, + dtype=np.int32, + shape=[self.batch_size, max_proposals], + ) - nms_outputs = [nms_output_num_detections, nms_output_boxes, nms_output_scores, nms_output_classes] + nms_outputs = [ + nms_output_num_detections, + nms_output_boxes, + nms_output_scores, + nms_output_classes, + ] # Plugin. self.graph.plugin( op="EfficientNMS_TRT", - name="nms"+nms_name, + name="nms" + nms_name, inputs=[boxes, scores, anchors], outputs=nms_outputs, attrs={ - 'plugin_version': "1", - 'background_class': background_class, - 'max_output_boxes': max_proposals, - 'score_threshold': max(0.01, score_threshold), - 'iou_threshold': iou_threshold, - 'score_activation': score_activation, - 'class_agnostic': False, - 'box_coding': 1, - } + "plugin_version": "1", + "background_class": background_class, + "max_output_boxes": max_proposals, + "score_threshold": max(0.01, score_threshold), + "iou_threshold": iou_threshold, + "score_activation": score_activation, + "class_agnostic": False, + "box_coding": 1, + }, ) log.info("Created nms{} with EfficientNMS_TRT plugin".format(nms_name)) return nms_outputs - def ROIAlign(self, rois, p2, p3, p4, p5, pooled_size, sampling_ratio, roi_align_type, num_rois, ra_name): + def ROIAlign( + self, + rois, + p2, + p3, + p4, + p5, + pooled_size, + sampling_ratio, + roi_align_type, + num_rois, + ra_name, + ): # Helper function to create the ROIAlign Plugin node with the selected inputs. # PyramidROIAlign_TRT TensorRT Plugin is suitable for our use case. # :param rois: Regions of interest/detection boxes outputs from preceding NMS node. @@ -318,31 +409,42 @@ def ROIAlign(self, rois, p2, p3, p4, p5, pooled_size, sampling_ratio, roi_align_ roi_coords_transform = 0 # ROIAlign outputs. - roi_align_output = gs.Variable(name="roi_align/output_"+ra_name, dtype=np.float32, - shape=[self.batch_size, num_rois, self.fpn_out_channels, pooled_size, pooled_size]) + roi_align_output = gs.Variable( + name="roi_align/output_" + ra_name, + dtype=np.float32, + shape=[ + self.batch_size, + num_rois, + self.fpn_out_channels, + pooled_size, + pooled_size, + ], + ) # Plugin. self.graph.plugin( op="PyramidROIAlign_TRT", - name="roi_align_"+ra_name, + name="roi_align_" + ra_name, inputs=[rois, p2, p3, p4, p5], outputs=[roi_align_output], attrs={ - 'plugin_version': "1", - 'fpn_scale': 224, - 'pooled_size': pooled_size, - 'image_size': [self.height, self.width], - 'roi_coords_absolute': 0, - 'roi_coords_swap': 0, - 'roi_coords_transform': roi_coords_transform, - 'sampling_ratio': sampling_ratio, - } + "plugin_version": "1", + "fpn_scale": 224, + "pooled_size": pooled_size, + "image_size": [self.height, self.width], + "roi_coords_absolute": 0, + "roi_coords_swap": 0, + "roi_coords_transform": roi_coords_transform, + "sampling_ratio": sampling_ratio, + }, ) log.info("Created {} with PyramidROIAlign_TRT plugin".format(ra_name)) return roi_align_output - def process_graph(self, anchors, first_nms_threshold=None, second_nms_threshold=None): + def process_graph( + self, anchors, first_nms_threshold=None, second_nms_threshold=None + ): """ Processes the graph to replace the GenerateProposals and BoxWithNMSLimit operations with EfficientNMS_TRT TensorRT plugin nodes and ROIAlign operations with PyramidROIAlign_TRT plugin nodes. @@ -351,6 +453,7 @@ def process_graph(self, anchors, first_nms_threshold=None, second_nms_threshold= :param first_nms_threshold: Override the 1st NMS score threshold value. If set to None, use the value in the graph. :param second_nms_threshold: Override the 2nd NMS score threshold value. If set to None, use the value in the graph. """ + def backbone(): """ Updates the graph to replace all ResizeNearest ops with ResizeNearest plugins in backbone. @@ -361,7 +464,6 @@ def backbone(): p4 = self.graph.find_node_by_op_name("Conv", "/backbone/fpn_output4/Conv") p5 = self.graph.find_node_by_op_name("Conv", "/backbone/fpn_output5/Conv") - return p2.outputs[0], p3.outputs[0], p4.outputs[0], p5.outputs[0] def proposal_generator(anchors, first_nms_threshold): @@ -372,38 +474,101 @@ def proposal_generator(anchors, first_nms_threshold): :param first_nms_threshold: Override the 1st NMS score threshold value. If set to None, use the value in the graph. """ # Get nodes containing final objectness logits. - p2_logits = self.graph.find_node_by_op_name("Flatten", "/proposal_generator/Flatten") - p3_logits = self.graph.find_node_by_op_name("Flatten", "/proposal_generator/Flatten_1") - p4_logits = self.graph.find_node_by_op_name("Flatten", "/proposal_generator/Flatten_2") - p5_logits = self.graph.find_node_by_op_name("Flatten", "/proposal_generator/Flatten_3") - p6_logits = self.graph.find_node_by_op_name("Flatten", "/proposal_generator/Flatten_4") + p2_logits = self.graph.find_node_by_op_name( + "Flatten", "/proposal_generator/Flatten" + ) + p3_logits = self.graph.find_node_by_op_name( + "Flatten", "/proposal_generator/Flatten_1" + ) + p4_logits = self.graph.find_node_by_op_name( + "Flatten", "/proposal_generator/Flatten_2" + ) + p5_logits = self.graph.find_node_by_op_name( + "Flatten", "/proposal_generator/Flatten_3" + ) + p6_logits = self.graph.find_node_by_op_name( + "Flatten", "/proposal_generator/Flatten_4" + ) # Get nodes containing final anchor_deltas. - p2_anchors = self.graph.find_node_by_op_name("Reshape", "/proposal_generator/Reshape_1") - p3_anchors = self.graph.find_node_by_op_name("Reshape", "/proposal_generator/Reshape_3") - p4_anchors = self.graph.find_node_by_op_name("Reshape", "/proposal_generator/Reshape_5") - p5_anchors = self.graph.find_node_by_op_name("Reshape", "/proposal_generator/Reshape_7") - p6_anchors = self.graph.find_node_by_op_name("Reshape", "/proposal_generator/Reshape_9") + p2_anchors = self.graph.find_node_by_op_name( + "Reshape", "/proposal_generator/Reshape_1" + ) + p3_anchors = self.graph.find_node_by_op_name( + "Reshape", "/proposal_generator/Reshape_3" + ) + p4_anchors = self.graph.find_node_by_op_name( + "Reshape", "/proposal_generator/Reshape_5" + ) + p5_anchors = self.graph.find_node_by_op_name( + "Reshape", "/proposal_generator/Reshape_7" + ) + p6_anchors = self.graph.find_node_by_op_name( + "Reshape", "/proposal_generator/Reshape_9" + ) # Concatenate all objectness logits/scores data. - scores_inputs = [p2_logits.outputs[0], p3_logits.outputs[0], p4_logits.outputs[0], p5_logits.outputs[0], p6_logits.outputs[0]] - scores_tensor = self.graph.layer(name="scores", op="Concat", inputs=scores_inputs, outputs=['scores'], attrs={'axis': 1})[0] + scores_inputs = [ + p2_logits.outputs[0], + p3_logits.outputs[0], + p4_logits.outputs[0], + p5_logits.outputs[0], + p6_logits.outputs[0], + ] + scores_tensor = self.graph.layer( + name="scores", + op="Concat", + inputs=scores_inputs, + outputs=["scores"], + attrs={"axis": 1}, + )[0] # Unsqueeze to add 3rd dimension of 1 to match tensor dimensions of boxes tensor. scores = self.graph.unsqueeze("scores_unsqueeze", scores_tensor, [2])[0] # Concatenate all boxes/anchor_delta data. - boxes_inputs = [p2_anchors.outputs[0], p3_anchors.outputs[0], p4_anchors.outputs[0], p5_anchors.outputs[0], p6_anchors.outputs[0]] - boxes = self.graph.layer(name="boxes", op="Concat", inputs=boxes_inputs, outputs=['anchors'], attrs={'axis': 1})[0] + boxes_inputs = [ + p2_anchors.outputs[0], + p3_anchors.outputs[0], + p4_anchors.outputs[0], + p5_anchors.outputs[0], + p6_anchors.outputs[0], + ] + boxes = self.graph.layer( + name="boxes", + op="Concat", + inputs=boxes_inputs, + outputs=["anchors"], + attrs={"axis": 1}, + )[0] # Convert the anchors from Corners to CenterSize encoding. - anchors = np.matmul(anchors, [[0.5, 0, -1, 0], [0, 0.5, 0, -1], [0.5, 0, 1, 0], [0, 0.5, 0, 1]]) - anchors = anchors / [self.width, self.height, self.width, self.height] # Normalize anchors to [0-1] range + anchors = np.matmul( + anchors, + [[0.5, 0, -1, 0], [0, 0.5, 0, -1], [0.5, 0, 1, 0], [0, 0.5, 0, 1]], + ) + anchors = anchors / [ + self.width, + self.height, + self.width, + self.height, + ] # Normalize anchors to [0-1] range anchors = np.expand_dims(anchors, axis=0) anchors = anchors.astype(np.float32) anchors = gs.Constant(name="default_anchors", values=anchors) # Create NMS node. - nms_outputs = self.NMS(boxes, scores, anchors, -1, False, self.first_NMS_max_proposals, self.first_NMS_iou_threshold, self.first_NMS_score_threshold, first_nms_threshold, 'rpn') + nms_outputs = self.NMS( + boxes, + scores, + anchors, + -1, + False, + self.first_NMS_max_proposals, + self.first_NMS_iou_threshold, + self.first_NMS_score_threshold, + first_nms_threshold, + "rpn", + ) return nms_outputs @@ -422,63 +587,149 @@ def roi_heads(rpn_outputs, p2, p3, p4, p5, second_nms_threshold): :param second_nms_threshold: Override the 2nd NMS score threshold value. If set to None, use the value in the graph. """ # Create ROIAlign node. - box_pooler_output = self.ROIAlign(rpn_outputs[1], p2, p3, p4, p5, self.first_ROIAlign_pooled_size, self.first_ROIAlign_sampling_ratio, self.first_ROIAlign_type, self.first_NMS_max_proposals, 'box_pooler') + box_pooler_output = self.ROIAlign( + rpn_outputs[1], + p2, + p3, + p4, + p5, + self.first_ROIAlign_pooled_size, + self.first_ROIAlign_sampling_ratio, + self.first_ROIAlign_type, + self.first_NMS_max_proposals, + "box_pooler", + ) # Reshape node that prepares ROIAlign/box pooler output for Gemm node that comes next. - box_pooler_shape = np.asarray([-1, self.fpn_out_channels*self.first_ROIAlign_pooled_size*self.first_ROIAlign_pooled_size], dtype=np.int64) - box_pooler_reshape = self.graph.op_with_const("Reshape", "box_pooler/reshape", box_pooler_output, box_pooler_shape) + box_pooler_shape = np.asarray( + [ + -1, + self.fpn_out_channels + * self.first_ROIAlign_pooled_size + * self.first_ROIAlign_pooled_size, + ], + dtype=np.int64, + ) + box_pooler_reshape = self.graph.op_with_const( + "Reshape", "box_pooler/reshape", box_pooler_output, box_pooler_shape + ) # Get first Gemm op of box head and connect box pooler to it. - first_box_head_gemm = self.graph.find_node_by_op_name("Gemm", "/roi_heads/box_head/fc1/Gemm") + first_box_head_gemm = self.graph.find_node_by_op_name( + "Gemm", "/roi_heads/box_head/fc1/Gemm" + ) first_box_head_gemm.inputs[0] = box_pooler_reshape[0] # Get final two nodes of box predictor. Softmax op for cls_score, Gemm op for bbox_pred. cls_score = self.graph.find_node_by_op_name("Softmax", "/roi_heads/Softmax") - bbox_pred = self.graph.find_node_by_op_name("Gemm", "/roi_heads/box_predictor/bbox_pred/Gemm") + bbox_pred = self.graph.find_node_by_op_name( + "Gemm", "/roi_heads/box_predictor/bbox_pred/Gemm" + ) # Linear transformation to convert box coordinates from (TopLeft, BottomRight) Corner encoding # to CenterSize encoding. 1st NMS boxes are multiplied by transformation matrix in order to # encode it into CenterSize format. - matmul_const = np.matrix('0.5 0 -1 0; 0 0.5 0 -1; 0.5 0 1 0; 0 0.5 0 1', dtype=np.float32) - matmul_out = self.graph.matmul("RPN_NMS/detection_boxes_conversion", rpn_outputs[1], matmul_const) + matmul_const = np.matrix( + "0.5 0 -1 0; 0 0.5 0 -1; 0.5 0 1 0; 0 0.5 0 1", dtype=np.float32 + ) + matmul_out = self.graph.matmul( + "RPN_NMS/detection_boxes_conversion", rpn_outputs[1], matmul_const + ) # Reshape node that prepares bbox_pred for scaling and second NMS. - bbox_pred_shape = np.asarray([self.batch_size, self.first_NMS_max_proposals, self.num_classes, 4], dtype=np.int64) - bbox_pred_reshape = self.graph.op_with_const("Reshape", "bbox_pred/reshape", bbox_pred.outputs[0], bbox_pred_shape) + bbox_pred_shape = np.asarray( + [self.batch_size, self.first_NMS_max_proposals, self.num_classes, 4], + dtype=np.int64, + ) + bbox_pred_reshape = self.graph.op_with_const( + "Reshape", "bbox_pred/reshape", bbox_pred.outputs[0], bbox_pred_shape + ) # 0.1, 0.1, 0.2, 0.2 are localization head variance numbers, they scale bbox_pred_reshape, in order to get accurate coordinates. - scale_adj = np.expand_dims(np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1)) - final_bbox_pred = self.graph.op_with_const("Mul", "bbox_pred/scale", bbox_pred_reshape[0], scale_adj) + scale_adj = np.expand_dims( + np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1) + ) + final_bbox_pred = self.graph.op_with_const( + "Mul", "bbox_pred/scale", bbox_pred_reshape[0], scale_adj + ) # Reshape node that prepares cls_score for slicing and second NMS. - cls_score_shape = np.array([self.batch_size, self.first_NMS_max_proposals, self.num_classes+1], dtype=np.int64) - cls_score_reshape = self.graph.op_with_const("Reshape", "cls_score/reshape", cls_score.outputs[0], cls_score_shape) + cls_score_shape = np.array( + [self.batch_size, self.first_NMS_max_proposals, self.num_classes + 1], + dtype=np.int64, + ) + cls_score_reshape = self.graph.op_with_const( + "Reshape", "cls_score/reshape", cls_score.outputs[0], cls_score_shape + ) # Slice operation to adjust third dimension of cls_score tensor, deletion of background class (81 in Detectron 2). - final_cls_score = self.graph.slice("cls_score/slicer", cls_score_reshape[0], 0, self.num_classes, 2) + final_cls_score = self.graph.slice( + "cls_score/slicer", cls_score_reshape[0], 0, self.num_classes, 2 + ) # Create NMS node. - nms_outputs = self.NMS(final_bbox_pred[0], final_cls_score[0], matmul_out[0], -1, False, self.second_NMS_max_proposals, self.second_NMS_iou_threshold, self.second_NMS_score_threshold, second_nms_threshold, 'box_outputs') + nms_outputs = self.NMS( + final_bbox_pred[0], + final_cls_score[0], + matmul_out[0], + -1, + False, + self.second_NMS_max_proposals, + self.second_NMS_iou_threshold, + self.second_NMS_score_threshold, + second_nms_threshold, + "box_outputs", + ) # Create ROIAlign node. - mask_pooler_output = self.ROIAlign(nms_outputs[1], p2, p3, p4, p5, self.second_ROIAlign_pooled_size, self.second_ROIAlign_sampling_ratio, self.second_ROIAlign_type, self.second_NMS_max_proposals, 'mask_pooler') + mask_pooler_output = self.ROIAlign( + nms_outputs[1], + p2, + p3, + p4, + p5, + self.second_ROIAlign_pooled_size, + self.second_ROIAlign_sampling_ratio, + self.second_ROIAlign_type, + self.second_NMS_max_proposals, + "mask_pooler", + ) # Reshape mask pooler output. - mask_pooler_shape = np.asarray([self.second_NMS_max_proposals*self.batch_size, self.fpn_out_channels, self.second_ROIAlign_pooled_size, self.second_ROIAlign_pooled_size], dtype=np.int64) - mask_pooler_reshape_node = self.graph.op_with_const("Reshape", "mask_pooler/reshape", mask_pooler_output, mask_pooler_shape) + mask_pooler_shape = np.asarray( + [ + self.second_NMS_max_proposals * self.batch_size, + self.fpn_out_channels, + self.second_ROIAlign_pooled_size, + self.second_ROIAlign_pooled_size, + ], + dtype=np.int64, + ) + mask_pooler_reshape_node = self.graph.op_with_const( + "Reshape", "mask_pooler/reshape", mask_pooler_output, mask_pooler_shape + ) # Get first Conv op in mask head and connect ROIAlign's squeezed output to it. - mask_head_conv = self.graph.find_node_by_op_name("Conv", "/roi_heads/mask_head/mask_fcn1/Conv") + mask_head_conv = self.graph.find_node_by_op_name( + "Conv", "/roi_heads/mask_head/mask_fcn1/Conv" + ) mask_head_conv.inputs[0] = mask_pooler_reshape_node[0] # Reshape node that is preparing 2nd NMS class outputs for Add node that comes next. - classes_reshape_shape = np.asarray([self.second_NMS_max_proposals*self.batch_size], dtype=np.int64) - classes_reshape_node = self.graph.op_with_const("Reshape", "box_outputs/reshape_classes", nms_outputs[3], classes_reshape_shape) + classes_reshape_shape = np.asarray( + [self.second_NMS_max_proposals * self.batch_size], dtype=np.int64 + ) + classes_reshape_node = self.graph.op_with_const( + "Reshape", + "box_outputs/reshape_classes", + nms_outputs[3], + classes_reshape_shape, + ) # This loop will generate an array used in Add node, which eventually will help Gather node to pick the single # class of interest per bounding box, instead of creating 80 masks for every single bounding box. add_array = [] - for i in range(self.second_NMS_max_proposals*self.batch_size): + for i in range(self.second_NMS_max_proposals * self.batch_size): if i == 0: start_pos = 0 else: @@ -488,23 +739,59 @@ def roi_heads(rpn_outputs, p2, p3, p4, p5, second_nms_threshold): # This Add node is one of the Gather node inputs, Gather node performs gather on 0th axis of data tensor # and requires indices that set tensors to be withing bounds, this Add node provides the bounds for Gather. add_array = np.asarray(add_array, dtype=np.int32) - classes_add_node = self.graph.op_with_const("Add", "box_outputs/add", classes_reshape_node[0], add_array) + classes_add_node = self.graph.op_with_const( + "Add", "box_outputs/add", classes_reshape_node[0], add_array + ) # Get the last Conv op in mask head and reshape it to correctly gather class of interest's masks. - last_conv = self.graph.find_node_by_op_name("Conv", "/roi_heads/mask_head/predictor/Conv") - last_conv_reshape_shape = np.asarray([self.second_NMS_max_proposals*self.num_classes*self.batch_size, self.mask_out_res, self.mask_out_res], dtype=np.int64) - last_conv_reshape_node = self.graph.op_with_const("Reshape", "mask_head/reshape_all_masks", last_conv.outputs[0], last_conv_reshape_shape) + last_conv = self.graph.find_node_by_op_name( + "Conv", "/roi_heads/mask_head/predictor/Conv" + ) + last_conv_reshape_shape = np.asarray( + [ + self.second_NMS_max_proposals * self.num_classes * self.batch_size, + self.mask_out_res, + self.mask_out_res, + ], + dtype=np.int64, + ) + last_conv_reshape_node = self.graph.op_with_const( + "Reshape", + "mask_head/reshape_all_masks", + last_conv.outputs[0], + last_conv_reshape_shape, + ) # Gather node that selects only masks belonging to detected class, 79 other masks are discarded. - final_gather = self.graph.gather("mask_head/final_gather", last_conv_reshape_node[0], classes_add_node[0], 0) + final_gather = self.graph.gather( + "mask_head/final_gather", + last_conv_reshape_node[0], + classes_add_node[0], + 0, + ) # Get last Sigmoid node and connect Gather node to it. - mask_head_sigmoid = self.graph.find_node_by_op_name("Sigmoid", "/roi_heads/mask_head/Sigmoid") + mask_head_sigmoid = self.graph.find_node_by_op_name( + "Sigmoid", "/roi_heads/mask_head/Sigmoid" + ) mask_head_sigmoid.inputs[0] = final_gather[0] # Final Reshape node, reshapes output of Sigmoid, important for various batch_size support (not tested yet). - final_graph_reshape_shape = np.asarray([self.batch_size, self.second_NMS_max_proposals, self.mask_out_res, self.mask_out_res], dtype=np.int64) - final_graph_reshape_node = self.graph.op_with_const("Reshape", "mask_head/final_reshape", mask_head_sigmoid.outputs[0], final_graph_reshape_shape) + final_graph_reshape_shape = np.asarray( + [ + self.batch_size, + self.second_NMS_max_proposals, + self.mask_out_res, + self.mask_out_res, + ], + dtype=np.int64, + ) + final_graph_reshape_node = self.graph.op_with_const( + "Reshape", + "mask_head/final_reshape", + mask_head_sigmoid.outputs[0], + final_graph_reshape_shape, + ) final_graph_reshape_node[0].dtype = np.float32 final_graph_reshape_node[0].name = "detection_masks" @@ -513,7 +800,9 @@ def roi_heads(rpn_outputs, p2, p3, p4, p5, second_nms_threshold): # Only Detectron 2's Mask-RCNN R50-FPN 3x is supported currently. p2, p3, p4, p5 = backbone() rpn_outputs = proposal_generator(anchors, first_nms_threshold) - box_head_outputs, mask_head_output = roi_heads(rpn_outputs, p2, p3, p4, p5, second_nms_threshold) + box_head_outputs, mask_head_output = roi_heads( + rpn_outputs, p2, p3, p4, p5, second_nms_threshold + ) # Append segmentation head output. box_head_outputs.append(mask_head_output) # Set graph outputs, both bbox and segmentation heads. @@ -531,17 +820,55 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-i", "--exported_onnx", help="The exported to ONNX Detectron 2 Mask R-CNN", type=str) - parser.add_argument("-o", "--onnx", help="The output ONNX model file to write", type=str) - parser.add_argument("-c", "--det2_config", help="The Detectron 2 config file (.yaml) for the model", type=str) - parser.add_argument("-w", "--det2_weights", help="The Detectron 2 model weights (.pkl)", type=str) - parser.add_argument("-s", "--sample_image", help="Sample image for anchors generation", type=str) - parser.add_argument("-b", "--batch_size", help="Batch size for the model", type=int, default=1) - parser.add_argument("-t1", "--first_nms_threshold", help="Override the score threshold for the 1st NMS operation", type=float) - parser.add_argument("-t2", "--second_nms_threshold", help="Override the score threshold for the 2nd NMS operation", type=float) + parser.add_argument( + "-i", + "--exported_onnx", + help="The exported to ONNX Detectron 2 Mask R-CNN", + type=str, + ) + parser.add_argument( + "-o", "--onnx", help="The output ONNX model file to write", type=str + ) + parser.add_argument( + "-c", + "--det2_config", + help="The Detectron 2 config file (.yaml) for the model", + type=str, + ) + parser.add_argument( + "-w", "--det2_weights", help="The Detectron 2 model weights (.pkl)", type=str + ) + parser.add_argument( + "-s", "--sample_image", help="Sample image for anchors generation", type=str + ) + parser.add_argument( + "-b", "--batch_size", help="Batch size for the model", type=int, default=1 + ) + parser.add_argument( + "-t1", + "--first_nms_threshold", + help="Override the score threshold for the 1st NMS operation", + type=float, + ) + parser.add_argument( + "-t2", + "--second_nms_threshold", + help="Override the score threshold for the 2nd NMS operation", + type=float, + ) args = parser.parse_args() - if not all([args.exported_onnx, args.onnx, args.det2_config, args.det2_weights, args.sample_image]): + if not all( + [ + args.exported_onnx, + args.onnx, + args.det2_config, + args.det2_weights, + args.sample_image, + ] + ): parser.print_help() - print("\nThese arguments are required: --exported_onnx --onnx --det2_config --det2_weights and --sample_image") + print( + "\nThese arguments are required: --exported_onnx --onnx --det2_config --det2_weights and --sample_image" + ) sys.exit(1) main(args) diff --git a/samples/python/detectron2/eval_coco.py b/samples/python/detectron2/eval_coco.py index 828413d4..7afb6116 100644 --- a/samples/python/detectron2/eval_coco.py +++ b/samples/python/detectron2/eval_coco.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,9 +31,12 @@ from detectron2.structures import Instances, Boxes, ROIMasks except ImportError: print("Could not import Detectron 2 modules. Maybe you did not install Detectron 2") - print("Please install Detectron 2, check https://github.com/facebookresearch/detectron2/blob/main/INSTALL.md") + print( + "Please install Detectron 2, check https://github.com/facebookresearch/detectron2/blob/main/INSTALL.md" + ) sys.exit(1) + def build_evaluator(dataset_name): """ Create evaluator for a COCO dataset. @@ -45,6 +48,7 @@ def build_evaluator(dataset_name): else: raise NotImplementedError("Evaluator type is not supported") + def setup(config_file, weights): """ Create config and perform basic setup. @@ -55,6 +59,7 @@ def setup(config_file, weights): cfg.freeze() return cfg + def main(args): # Set up Detectron 2 config and build evaluator. cfg = setup(args.det2_config, args.det2_weights) @@ -63,10 +68,15 @@ def main(args): evaluator.reset() trt_infer = TensorRTInfer(args.engine) - batcher = ImageBatcher(args.input, *trt_infer.input_spec(), config_file=args.det2_config) + batcher = ImageBatcher( + args.input, *trt_infer.input_spec(), config_file=args.det2_config + ) for batch, images, scales in batcher.get_batch(): - print("Processing Image {} / {}".format(batcher.image_index, batcher.num_images), end="\r") + print( + "Processing Image {} / {}".format(batcher.image_index, batcher.num_images), + end="\r", + ) detections = trt_infer.infer(batch, scales, args.nms_threshold) for i in range(len(images)): # Get inference image resolution. @@ -85,13 +95,13 @@ def main(args): for n in range(num_instances): det = detections[i][n] # Append box coordinates data. - pred_boxes.append([det['ymin'], det['xmin'], det['ymax'], det['xmax']]) + pred_boxes.append([det["ymin"], det["xmin"], det["ymax"], det["xmax"]]) # Append score. - scores.append(det['score']) + scores.append(det["score"]) # Append class. - pred_classes.append(det['class']) + pred_classes.append(det["class"]) # Append mask. - pred_masks[n] = det['mask'] + pred_masks[n] = det["mask"] # Create new Instances object required for Detectron 2 evalutions and add: # boxes, scores, pred_classes, pred_masks. image_shape = (im_height, im_width) @@ -100,10 +110,12 @@ def main(args): instances.scores = torch.tensor(scores) instances.pred_classes = torch.tensor(pred_classes) roi_masks = ROIMasks(torch.tensor(pred_masks)) - instances.pred_masks = roi_masks.to_bitmasks(instances.pred_boxes, im_height, im_width, args.iou_threshold).tensor + instances.pred_masks = roi_masks.to_bitmasks( + instances.pred_boxes, im_height, im_width, args.iou_threshold + ).tensor # Process evaluations per image. - image_dict = [{'instances': instances}] - input_dict = [{'image_id': source_id}] + image_dict = [{"instances": instances}] + input_dict = [{"image_id": source_id}] evaluator.process(input_dict, image_dict) # Final evaluations, generation of mAP accuracy performance. @@ -113,17 +125,37 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with.") - parser.add_argument("-i", "--input", - help="The input to infer, either a single image path, or a directory of images.") - parser.add_argument("-c", "--det2_config", help="The Detectron 2 config file (.yaml) for the model", type=str) - parser.add_argument("-w", "--det2_weights", help="The Detectron 2 model weights (.pkl)", type=str) - parser.add_argument("-t", "--nms_threshold", type=float, - help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.") - parser.add_argument("--iou_threshold", default=0.5, type=float, - help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0.") + parser.add_argument( + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images.", + ) + parser.add_argument( + "-c", + "--det2_config", + help="The Detectron 2 config file (.yaml) for the model", + type=str, + ) + parser.add_argument( + "-w", "--det2_weights", help="The Detectron 2 model weights (.pkl)", type=str + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.", + ) + parser.add_argument( + "--iou_threshold", + default=0.5, + type=float, + help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0.", + ) args = parser.parse_args() if not all([args.engine, args.input, args.det2_config, args.det2_weights]): parser.print_help() - print("\nThese arguments are required: --engine --input --det2_config and --det2_weights") + print( + "\nThese arguments are required: --engine --input --det2_config and --det2_weights" + ) sys.exit(1) main(args) diff --git a/samples/python/detectron2/image_batcher.py b/samples/python/detectron2/image_batcher.py index 228798ad..0fb1d90a 100644 --- a/samples/python/detectron2/image_batcher.py +++ b/samples/python/detectron2/image_batcher.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,15 +24,26 @@ from detectron2.config import get_cfg except ImportError: print("Could not import Detectron 2 modules. Maybe you did not install Detectron 2") - print("Please install Detectron 2, check https://github.com/facebookresearch/detectron2/blob/main/INSTALL.md") + print( + "Please install Detectron 2, check https://github.com/facebookresearch/detectron2/blob/main/INSTALL.md" + ) sys.exit(1) + class ImageBatcher: """ Creates batches of pre-processed images. """ - def __init__(self, input, shape, dtype, max_num_images=None, exact_batches=False, config_file=None): + def __init__( + self, + input, + shape, + dtype, + max_num_images=None, + exact_batches=False, + config_file=None, + ): """ :param input: The input directory to read images from. :param shape: The tensor shape of the batch to prepare, either in NCHW or NHWC format. @@ -68,10 +79,16 @@ def det2_setup(config_file): extensions = [".jpg", ".jpeg", ".png", ".bmp", ".ppm"] def is_image(path): - return os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + return ( + os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + ) if os.path.isdir(input): - self.images = [os.path.join(input, f) for f in os.listdir(input) if is_image(os.path.join(input, f))] + self.images = [ + os.path.join(input, f) + for f in os.listdir(input) + if is_image(os.path.join(input, f)) + ] self.images.sort() elif os.path.isfile(input): if is_image(input): @@ -108,7 +125,7 @@ def is_image(path): if self.num_images < 1: print("Not enough images to create batches") sys.exit(1) - self.images = self.images[0:self.num_images] + self.images = self.images[0 : self.num_images] # Subdivide the list of images into batches. self.num_batches = 1 + int((self.num_images - 1) / self.batch_size) @@ -122,7 +139,6 @@ def is_image(path): self.image_index = 0 self.batch_index = 0 - def preprocess_image(self, image_path): """ The image preprocessor loads an image from disk and prepares it as needed for batching. This includes padding, @@ -165,7 +181,7 @@ def resize_pad(image, pad_color=(0, 0, 0)): newh = int(newh + 0.5) # Scaling factor for normalized box coordinates scaling in post-processing. - scaling = max(newh/height, neww/width) + scaling = max(newh / height, neww / width) # Padding. image = image.resize((neww, newh), resample=Image.BILINEAR) @@ -176,7 +192,7 @@ def resize_pad(image, pad_color=(0, 0, 0)): scale = None image = Image.open(image_path) - image = image.convert(mode='RGB') + image = image.convert(mode="RGB") # Pad with mean values of COCO dataset, since padding is applied before actual model's # preprocessor steps (Sub, Div ops), we need to pad with mean values in order to reverse # the effects of Sub and Div, so that padding after model's preprocessor will be with actual 0s. @@ -185,7 +201,7 @@ def resize_pad(image, pad_color=(0, 0, 0)): # Change HWC -> CHW. image = np.transpose(image, (2, 0, 1)) # Change RGB -> BGR. - return image[[2,1,0]], scale + return image[[2, 1, 0]], scale def get_batch(self): """ diff --git a/samples/python/detectron2/infer.py b/samples/python/detectron2/infer.py index db7c83b6..d086fb76 100644 --- a/samples/python/detectron2/infer.py +++ b/samples/python/detectron2/infer.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,6 +27,7 @@ sys.path.insert(1, os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) import common + class TensorRTInfer: """ Implements inference for the Model TensorRT engine. @@ -65,12 +66,12 @@ def __init__(self, engine_path): size *= s allocation = common.cuda_call(cudart.cudaMalloc(size)) binding = { - 'index': i, - 'name': name, - 'dtype': np.dtype(trt.nptype(dtype)), - 'shape': list(shape), - 'allocation': allocation, - 'size': size + "index": i, + "name": name, + "dtype": np.dtype(trt.nptype(dtype)), + "shape": list(shape), + "allocation": allocation, + "size": size, } self.allocations.append(allocation) if is_input: @@ -88,7 +89,7 @@ def input_spec(self): Get the specs for the input tensor of the network. Useful to prepare memory allocations. :return: Two items, the shape of the input tensor and its (numpy) datatype. """ - return self.inputs[0]['shape'], self.inputs[0]['dtype'] + return self.inputs[0]["shape"], self.inputs[0]["dtype"] def output_spec(self): """ @@ -97,7 +98,7 @@ def output_spec(self): """ specs = [] for o in self.outputs: - specs.append((o['shape'], o['dtype'])) + specs.append((o["shape"], o["dtype"])) return specs def infer(self, batch, scales=None, nms_threshold=None): @@ -115,11 +116,13 @@ def infer(self, batch, scales=None, nms_threshold=None): outputs.append(np.zeros(shape, dtype)) # Process I/O and execute the network. - common.memcpy_host_to_device(self.inputs[0]['allocation'], np.ascontiguousarray(batch)) + common.memcpy_host_to_device( + self.inputs[0]["allocation"], np.ascontiguousarray(batch) + ) self.context.execute_v2(self.allocations) for o in range(len(outputs)): - common.memcpy_device_to_host(outputs[o], self.outputs[o]['allocation']) + common.memcpy_device_to_host(outputs[o], self.outputs[o]["allocation"]) # Process the results. nums = outputs[0] @@ -136,7 +139,7 @@ def infer(self, batch, scales=None, nms_threshold=None): mask = masks[i][n] # Calculate scaling values for bboxes. - scale = self.inputs[0]['shape'][2] + scale = self.inputs[0]["shape"][2] scale /= scales[i] scale_y = scale scale_x = scale @@ -144,15 +147,17 @@ def infer(self, batch, scales=None, nms_threshold=None): if nms_threshold and scores[i][n] < nms_threshold: continue # Append to detections - detections[i].append({ - 'ymin': boxes[i][n][0] * scale_y, - 'xmin': boxes[i][n][1] * scale_x, - 'ymax': boxes[i][n][2] * scale_y, - 'xmax': boxes[i][n][3] * scale_x, - 'score': scores[i][n], - 'class': int(pred_classes[i][n]), - 'mask': mask, - }) + detections[i].append( + { + "ymin": boxes[i][n][0] * scale_y, + "xmin": boxes[i][n][1] * scale_x, + "ymax": boxes[i][n][2] * scale_y, + "xmax": boxes[i][n][3] * scale_x, + "score": scores[i][n], + "class": int(pred_classes[i][n]), + "mask": mask, + } + ) return detections @@ -160,22 +165,117 @@ def main(args): output_dir = os.path.realpath(args.output) os.makedirs(output_dir, exist_ok=True) - labels = ["person","bicycle","car","motorcycle","airplane","bus","train","truck","boat","traffic light","fire hydrant","stop sign","parking meter","bench","bird","cat","dog","horse","sheep","cow","elephant","bear","zebra","giraffe","backpack","umbrella","handbag","tie","suitcase","frisbee","skis","snowboard","sports ball","kite","baseball bat","baseball glove","skateboard","surfboard","tennis racket","bottle","wine glass","cup","fork","knife","spoon","bowl","banana","apple","sandwich","orange","broccoli","carrot","hot dog","pizza","donut","cake","chair","couch","potted plant","bed","dining table","toilet","tv","laptop","mouse","remote","keyboard","cell phone","microwave","oven","toaster","sink","refrigerator","book","clock","vase","scissors","teddy bear","hair drier", "toothbrush"] + labels = [ + "person", + "bicycle", + "car", + "motorcycle", + "airplane", + "bus", + "train", + "truck", + "boat", + "traffic light", + "fire hydrant", + "stop sign", + "parking meter", + "bench", + "bird", + "cat", + "dog", + "horse", + "sheep", + "cow", + "elephant", + "bear", + "zebra", + "giraffe", + "backpack", + "umbrella", + "handbag", + "tie", + "suitcase", + "frisbee", + "skis", + "snowboard", + "sports ball", + "kite", + "baseball bat", + "baseball glove", + "skateboard", + "surfboard", + "tennis racket", + "bottle", + "wine glass", + "cup", + "fork", + "knife", + "spoon", + "bowl", + "banana", + "apple", + "sandwich", + "orange", + "broccoli", + "carrot", + "hot dog", + "pizza", + "donut", + "cake", + "chair", + "couch", + "potted plant", + "bed", + "dining table", + "toilet", + "tv", + "laptop", + "mouse", + "remote", + "keyboard", + "cell phone", + "microwave", + "oven", + "toaster", + "sink", + "refrigerator", + "book", + "clock", + "vase", + "scissors", + "teddy bear", + "hair drier", + "toothbrush", + ] trt_infer = TensorRTInfer(args.engine) - batcher = ImageBatcher(args.input, *trt_infer.input_spec(), config_file=args.det2_config) + batcher = ImageBatcher( + args.input, *trt_infer.input_spec(), config_file=args.det2_config + ) for batch, images, scales in batcher.get_batch(): - print("Processing Image {} / {}".format(batcher.image_index, batcher.num_images), end="\r") + print( + "Processing Image {} / {}".format(batcher.image_index, batcher.num_images), + end="\r", + ) detections = trt_infer.infer(batch, scales, args.nms_threshold) for i in range(len(images)): basename = os.path.splitext(os.path.basename(images[i]))[0] # Image Visualizations output_path = os.path.join(output_dir, "{}.png".format(basename)) - visualize_detections(images[i], output_path, detections[i], labels, args.iou_threshold) + visualize_detections( + images[i], output_path, detections[i], labels, args.iou_threshold + ) # Text Results output_results = "" for d in detections[i]: - line = [d['xmin'], d['ymin'], d['xmax'], d['ymax'], d['score'], d['class']] + line = [ + d["xmin"], + d["ymin"], + d["xmax"], + d["ymax"], + d["score"], + d["class"], + ] output_results += "\t".join([str(f) for f in line]) + "\n" with open(os.path.join(args.output, "{}.txt".format(basename)), "w") as f: f.write(output_results) @@ -185,17 +285,41 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-e", "--engine", default=None, help="The serialized TensorRT engine") - parser.add_argument("-i", "--input", default=None, help="Path to the image or directory to process") - parser.add_argument("-c", "--det2_config", help="The Detectron 2 config file (.yaml) for the model", type=str) - parser.add_argument("-o", "--output", default=None, help="Directory where to save the visualization results") - parser.add_argument("-t", "--nms_threshold", type=float, - help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.") - parser.add_argument("--iou_threshold", default=0.5, type=float, - help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0") + parser.add_argument( + "-e", "--engine", default=None, help="The serialized TensorRT engine" + ) + parser.add_argument( + "-i", "--input", default=None, help="Path to the image or directory to process" + ) + parser.add_argument( + "-c", + "--det2_config", + help="The Detectron 2 config file (.yaml) for the model", + type=str, + ) + parser.add_argument( + "-o", + "--output", + default=None, + help="Directory where to save the visualization results", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.", + ) + parser.add_argument( + "--iou_threshold", + default=0.5, + type=float, + help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0", + ) args = parser.parse_args() if not all([args.engine, args.input, args.output, args.det2_config]): parser.print_help() - print("\nThese arguments are required: --engine --input --output and --det2_config") + print( + "\nThese arguments are required: --engine --input --output and --det2_config" + ) sys.exit(1) main(args) diff --git a/samples/python/detectron2/onnx_utils.py b/samples/python/detectron2/onnx_utils.py index 56d280fa..2144fea0 100644 --- a/samples/python/detectron2/onnx_utils.py +++ b/samples/python/detectron2/onnx_utils.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,6 +23,7 @@ logging.getLogger("ModelHelper").setLevel(logging.INFO) log = logging.getLogger("ModelHelper") + @gs.Graph.register() def op_with_const(self, op, name, input, value): """ @@ -35,7 +36,10 @@ def op_with_const(self, op, name, input, value): 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"]) + return self.layer( + name=name, op=op, inputs=[input_tensor, const], outputs=[name + ":0"] + ) + @gs.Graph.register() def matmul(self, name, input, value): @@ -48,7 +52,10 @@ def matmul(self, name, input, value): 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"]) + 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): @@ -61,9 +68,19 @@ def clip(self, name, input, clip_min, clip_max): """ 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"]) + 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): @@ -79,10 +96,22 @@ def slice(self, name, input, starts, ends, axes): 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"]) + 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]): @@ -96,7 +125,14 @@ def unsqueeze(self, name, input, axes=[3]): """ 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}) + 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]): @@ -110,7 +146,14 @@ def squeeze(self, name, input, axes=[2]): """ 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}) + 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): @@ -125,7 +168,14 @@ def gather(self, name, data, indices, axes=0): 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}) + 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): @@ -139,7 +189,14 @@ def transpose(self, name, input, perm): """ 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}) + return self.layer( + name=name, + op="Transpose", + inputs=[input_tensor], + outputs=[name + ":0"], + attrs={"perm": perm}, + ) + @gs.Graph.register() def sigmoid(self, name, input): @@ -152,7 +209,10 @@ def sigmoid(self, name, input): """ 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"]) + 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): @@ -170,6 +230,7 @@ def plugin(self, op, name, inputs: list, outputs: list, attrs): 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): """ @@ -183,6 +244,7 @@ def find_node_by_op(self, op): return node return None + @gs.Graph.register() def find_node_by_op_name(self, op, name): """ @@ -197,8 +259,11 @@ def find_node_by_op_name(self, op, 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): +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. @@ -210,10 +275,15 @@ def find_node_by_op_input_output_name(self, op, input_name, output_name, input_p :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: + 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): """ @@ -237,6 +307,7 @@ def find_descendant_by_op(self, node, op, depth=10): queue.append(child) return None + @gs.Graph.register() def find_ancestor_by_op(self, node, op, depth=10): """ diff --git a/samples/python/detectron2/visualize.py b/samples/python/detectron2/visualize.py index dd8b6ead..00e930f1 100644 --- a/samples/python/detectron2/visualize.py +++ b/samples/python/detectron2/visualize.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -22,55 +22,177 @@ import PIL.ImageFilter as ImageFilter -COLORS = ['GoldenRod', 'MediumTurquoise', 'GreenYellow', 'SteelBlue', 'DarkSeaGreen', 'SeaShell', 'LightGrey', - 'IndianRed', 'DarkKhaki', 'LawnGreen', 'WhiteSmoke', 'Peru', 'LightCoral', 'FireBrick', 'OldLace', - 'LightBlue', 'SlateGray', 'OliveDrab', 'NavajoWhite', 'PaleVioletRed', 'SpringGreen', 'AliceBlue', 'Violet', - 'DeepSkyBlue', 'Red', 'MediumVioletRed', 'PaleTurquoise', 'Tomato', 'Azure', 'Yellow', 'Cornsilk', - 'Aquamarine', 'CadetBlue', 'CornflowerBlue', 'DodgerBlue', 'Olive', 'Orchid', 'LemonChiffon', 'Sienna', - 'OrangeRed', 'Orange', 'DarkSalmon', 'Magenta', 'Wheat', 'Lime', 'GhostWhite', 'SlateBlue', 'Aqua', - 'MediumAquaMarine', 'LightSlateGrey', 'MediumSeaGreen', 'SandyBrown', 'YellowGreen', 'Plum', 'FloralWhite', - 'LightPink', 'Thistle', 'DarkViolet', 'Pink', 'Crimson', 'Chocolate', 'DarkGrey', 'Ivory', 'PaleGreen', - 'DarkGoldenRod', 'LavenderBlush', 'SlateGrey', 'DeepPink', 'Gold', 'Cyan', 'LightSteelBlue', 'MediumPurple', - 'ForestGreen', 'DarkOrange', 'Tan', 'Salmon', 'PaleGoldenRod', 'LightGreen', 'LightSlateGray', 'HoneyDew', - 'Fuchsia', 'LightSeaGreen', 'DarkOrchid', 'Green', 'Chartreuse', 'LimeGreen', 'AntiqueWhite', 'Beige', - 'Gainsboro', 'Bisque', 'SaddleBrown', 'Silver', 'Lavender', 'Teal', 'LightCyan', 'PapayaWhip', 'Purple', - 'Coral', 'BurlyWood', 'LightGray', 'Snow', 'MistyRose', 'PowderBlue', 'DarkCyan', 'White', 'Turquoise', - 'MediumSlateBlue', 'PeachPuff', 'Moccasin', 'LightSalmon', 'SkyBlue', 'Khaki', 'MediumSpringGreen', - 'BlueViolet', 'MintCream', 'Linen', 'SeaGreen', 'HotPink', 'LightYellow', 'BlanchedAlmond', 'RoyalBlue', - 'RosyBrown', 'MediumOrchid', 'DarkTurquoise', 'LightGoldenRodYellow', 'LightSkyBlue'] +COLORS = [ + "GoldenRod", + "MediumTurquoise", + "GreenYellow", + "SteelBlue", + "DarkSeaGreen", + "SeaShell", + "LightGrey", + "IndianRed", + "DarkKhaki", + "LawnGreen", + "WhiteSmoke", + "Peru", + "LightCoral", + "FireBrick", + "OldLace", + "LightBlue", + "SlateGray", + "OliveDrab", + "NavajoWhite", + "PaleVioletRed", + "SpringGreen", + "AliceBlue", + "Violet", + "DeepSkyBlue", + "Red", + "MediumVioletRed", + "PaleTurquoise", + "Tomato", + "Azure", + "Yellow", + "Cornsilk", + "Aquamarine", + "CadetBlue", + "CornflowerBlue", + "DodgerBlue", + "Olive", + "Orchid", + "LemonChiffon", + "Sienna", + "OrangeRed", + "Orange", + "DarkSalmon", + "Magenta", + "Wheat", + "Lime", + "GhostWhite", + "SlateBlue", + "Aqua", + "MediumAquaMarine", + "LightSlateGrey", + "MediumSeaGreen", + "SandyBrown", + "YellowGreen", + "Plum", + "FloralWhite", + "LightPink", + "Thistle", + "DarkViolet", + "Pink", + "Crimson", + "Chocolate", + "DarkGrey", + "Ivory", + "PaleGreen", + "DarkGoldenRod", + "LavenderBlush", + "SlateGrey", + "DeepPink", + "Gold", + "Cyan", + "LightSteelBlue", + "MediumPurple", + "ForestGreen", + "DarkOrange", + "Tan", + "Salmon", + "PaleGoldenRod", + "LightGreen", + "LightSlateGray", + "HoneyDew", + "Fuchsia", + "LightSeaGreen", + "DarkOrchid", + "Green", + "Chartreuse", + "LimeGreen", + "AntiqueWhite", + "Beige", + "Gainsboro", + "Bisque", + "SaddleBrown", + "Silver", + "Lavender", + "Teal", + "LightCyan", + "PapayaWhip", + "Purple", + "Coral", + "BurlyWood", + "LightGray", + "Snow", + "MistyRose", + "PowderBlue", + "DarkCyan", + "White", + "Turquoise", + "MediumSlateBlue", + "PeachPuff", + "Moccasin", + "LightSalmon", + "SkyBlue", + "Khaki", + "MediumSpringGreen", + "BlueViolet", + "MintCream", + "Linen", + "SeaGreen", + "HotPink", + "LightYellow", + "BlanchedAlmond", + "RoyalBlue", + "RosyBrown", + "MediumOrchid", + "DarkTurquoise", + "LightGoldenRodYellow", + "LightSkyBlue", +] -#Overlay mask with transparency on top of the image. +# Overlay mask with transparency on top of the image. def overlay(image, mask, color, alpha_transparency=0.5): for channel in range(3): - image[:, :, channel] = np.where(mask == 1, - image[:, :, channel] * - (1 - alpha_transparency) + alpha_transparency * color[channel] * 255, - image[:, :, channel]) + image[:, :, channel] = np.where( + mask == 1, + image[:, :, channel] * (1 - alpha_transparency) + + alpha_transparency * color[channel] * 255, + image[:, :, channel], + ) return image -def visualize_detections(image_path, output_path, detections, labels=[], iou_threshold=0.5): - image = Image.open(image_path).convert(mode='RGB') + +def visualize_detections( + image_path, output_path, detections, labels=[], iou_threshold=0.5 +): + image = Image.open(image_path).convert(mode="RGB") # Get image dimensions. im_width, im_height = image.size line_width = 2 font = ImageFont.load_default() for d in detections: - color = COLORS[d['class'] % len(COLORS)] + color = COLORS[d["class"] % len(COLORS)] # Dynamically convert PIL color into RGB numpy array. - pixel_color = Image.new("RGB",(1, 1), color) + pixel_color = Image.new("RGB", (1, 1), color) # Normalize. - np_color = (np.asarray(pixel_color)[0][0])/255 + np_color = (np.asarray(pixel_color)[0][0]) / 255 # TRT instance segmentation masks. - if isinstance(d['mask'], np.ndarray) and d['mask'].shape == (28, 28): + if isinstance(d["mask"], np.ndarray) and d["mask"].shape == (28, 28): # PyTorch uses [x1,y1,x2,y2] format instead of regular [y1,x1,y2,x2]. - d['ymin'], d['xmin'], d['ymax'], d['xmax'] = d['xmin'], d['ymin'], d['xmax'], d['ymax'] + d["ymin"], d["xmin"], d["ymax"], d["xmax"] = ( + d["xmin"], + d["ymin"], + d["xmax"], + d["ymax"], + ) # Get detection bbox resolution. - det_width = round(d['xmax'] - d['xmin']) - det_height = round(d['ymax'] - d['ymin']) + det_width = round(d["xmax"] - d["xmin"]) + det_height = round(d["ymax"] - d["ymin"]) # Slight scaling, to get binary masks after float32 -> uint8 # conversion, if not scaled all pixels are zero. - mask = d['mask'] > iou_threshold + mask = d["mask"] > iou_threshold # Convert float32 -> uint8. mask = mask.astype(np.uint8) # Create an image out of predicted mask array. @@ -80,10 +202,10 @@ def visualize_detections(image_path, output_path, detections, labels=[], iou_thr # Create an original image sized template for correct mask placement. pad = Image.new("L", (im_width, im_height)) # Place your mask according to detection bbox placement. - pad.paste(mask, (round(d['xmin']), (round(d['ymin'])))) + pad.paste(mask, (round(d["xmin"]), (round(d["ymin"])))) # Reconvert mask into numpy array for evaluation. padded_mask = np.array(pad) - #Creat np.array from original image, copy in order to modify. + # Creat np.array from original image, copy in order to modify. image_copy = np.asarray(image).copy() # Image with overlaid mask. masked_image = overlay(image_copy, padded_mask, np_color) @@ -92,23 +214,42 @@ def visualize_detections(image_path, output_path, detections, labels=[], iou_thr # Bbox lines. draw = ImageDraw.Draw(image) - draw.line([(d['xmin'], d['ymin']), (d['xmin'], d['ymax']), (d['xmax'], d['ymax']), (d['xmax'], d['ymin']), - (d['xmin'], d['ymin'])], width=line_width, fill=color) - label = "Class {}".format(d['class']) - if d['class'] < len(labels): - label = "{}".format(labels[d['class']]) - score = d['score'] + draw.line( + [ + (d["xmin"], d["ymin"]), + (d["xmin"], d["ymax"]), + (d["xmax"], d["ymax"]), + (d["xmax"], d["ymin"]), + (d["xmin"], d["ymin"]), + ], + width=line_width, + fill=color, + ) + label = "Class {}".format(d["class"]) + if d["class"] < len(labels): + label = "{}".format(labels[d["class"]]) + score = d["score"] text = "{}: {}%".format(label, int(100 * score)) if score < 0: text = label left, top, right, bottom = font.getbbox(text) text_width, text_height = right - left, bottom - top - text_bottom = max(text_height, d['ymin']) - text_left = d['xmin'] + text_bottom = max(text_height, d["ymin"]) + text_left = d["xmin"] margin = np.ceil(0.05 * text_height) - draw.rectangle([(text_left, text_bottom - text_height - 2 * margin), (text_left + text_width, text_bottom)], - fill=color) - draw.text((text_left + margin, text_bottom - text_height - margin), text, fill='black', font=font) + draw.rectangle( + [ + (text_left, text_bottom - text_height - 2 * margin), + (text_left + text_width, text_bottom), + ], + fill=color, + ) + draw.text( + (text_left + margin, text_bottom - text_height - margin), + text, + fill="black", + font=font, + ) if output_path is None: return image image.save(output_path) diff --git a/samples/python/downloader.py b/samples/python/downloader.py index b4a436e2..5e8be202 100755 --- a/samples/python/downloader.py +++ b/samples/python/downloader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -87,8 +87,13 @@ def download(data_dir, yaml_path, overwrite=False): def _downloadFile(path, url): logger.info("Downloading %s from %s", path, url) import requests + from requests.adapters import HTTPAdapter, Retry + + session = requests.Session() + retries = Retry(total=5, backoff_factor=0.5) + session.mount("http://", HTTPAdapter(max_retries=retries)) + r = session.get(url, stream=True, timeout=10) - r = requests.get(url, stream=True, timeout=5) size = int(r.headers.get("content-length", 0)) from tqdm import tqdm @@ -124,7 +129,9 @@ def _downloadFile(path, url): def _parseArgs(): - parser = argparse.ArgumentParser(description="Downloader of TensorRT sample data files.") + parser = argparse.ArgumentParser( + description="Downloader of TensorRT sample data files." + ) parser.add_argument( "-d", "--data", @@ -137,7 +144,11 @@ def _parseArgs(): default="download.yml", ) parser.add_argument( - "-o", "--overwrite", help="Force to overwrite if MD5 check failed", action="store_true", default=False + "-o", + "--overwrite", + help="Force to overwrite if MD5 check failed", + action="store_true", + default=False, ) parser.add_argument( "-v", @@ -150,7 +161,9 @@ def _parseArgs(): args, _ = parser.parse_known_args() data = os.environ.get("TRT_DATA_DIR", None) if args.data is None else args.data if data is None: - raise ValueError("Data directory must be specified by either `-d $DATA` or environment variable $TRT_DATA_DIR.") + raise ValueError( + "Data directory must be specified by either `-d $DATA` or environment variable $TRT_DATA_DIR." + ) return data, args @@ -209,16 +222,22 @@ def getFilePath(path): """ global TRT_DATA_DIR if not TRT_DATA_DIR: - parser = argparse.ArgumentParser(description="Helper of data file download tool") + parser = argparse.ArgumentParser( + description="Helper of data file download tool" + ) parser.add_argument( "-d", "--data", help="Specify the data directory where it is saved in. $TRT_DATA_DIR will be overwritten by this argument.", ) args, _ = parser.parse_known_args() - TRT_DATA_DIR = os.environ.get("TRT_DATA_DIR", None) if args.data is None else args.data + TRT_DATA_DIR = ( + os.environ.get("TRT_DATA_DIR", None) if args.data is None else args.data + ) if TRT_DATA_DIR is None: - raise ValueError("Data directory must be specified by either `-d $DATA` or environment variable $TRT_DATA_DIR.") + raise ValueError( + "Data directory must be specified by either `-d $DATA` or environment variable $TRT_DATA_DIR." + ) fullpath = os.path.join(TRT_DATA_DIR, path) if not os.path.exists(fullpath): diff --git a/samples/python/efficientdet/build_engine.py b/samples/python/efficientdet/build_engine.py index 77143aad..58dd6d5c 100644 --- a/samples/python/efficientdet/build_engine.py +++ b/samples/python/efficientdet/build_engine.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -56,7 +56,10 @@ def set_image_batcher(self, image_batcher: ImageBatcher): :param image_batcher: The ImageBatcher object """ self.image_batcher = image_batcher - size = int(np.dtype(self.image_batcher.dtype).itemsize * np.prod(self.image_batcher.shape)) + size = int( + np.dtype(self.image_batcher.dtype).itemsize + * np.prod(self.image_batcher.shape) + ) self.batch_allocation = common.cuda_call(cudart.cudaMalloc(size)) self.batch_generator = self.image_batcher.get_batch() @@ -81,8 +84,14 @@ def get_batch(self, names): return None try: batch, _, _ = next(self.batch_generator) - log.info("Calibrating image {} / {}".format(self.image_batcher.image_index, self.image_batcher.num_images)) - common.memcpy_host_to_device(self.batch_allocation, np.ascontiguousarray(batch)) + log.info( + "Calibrating image {} / {}".format( + self.image_batcher.image_index, self.image_batcher.num_images + ) + ) + common.memcpy_host_to_device( + self.batch_allocation, np.ascontiguousarray(batch) + ) return [int(self.batch_allocation)] except StopIteration: log.info("Finished calibration batches") @@ -130,7 +139,9 @@ def __init__(self, verbose=False, workspace=8): self.builder = trt.Builder(self.trt_logger) self.config = self.builder.create_builder_config() - self.config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace * (2 ** 30)) + self.config.set_memory_pool_limit( + trt.MemoryPoolType.WORKSPACE, workspace * (2**30) + ) self.network = None self.parser = None @@ -161,29 +172,46 @@ def create_network(self, onnx_path, batch_size, dynamic_batch_size=None): profile = self.builder.create_optimization_profile() dynamic_inputs = False for input in inputs: - log.info("Input '{}' with shape {} and dtype {}".format(input.name, input.shape, input.dtype)) + log.info( + "Input '{}' with shape {} and dtype {}".format( + input.name, input.shape, input.dtype + ) + ) if input.shape[0] == -1: dynamic_inputs = True if dynamic_batch_size: if type(dynamic_batch_size) is str: - dynamic_batch_size = [int(v) for v in dynamic_batch_size.split(",")] + dynamic_batch_size = [ + int(v) for v in dynamic_batch_size.split(",") + ] assert len(dynamic_batch_size) == 3 min_shape = [dynamic_batch_size[0]] + list(input.shape[1:]) opt_shape = [dynamic_batch_size[1]] + list(input.shape[1:]) max_shape = [dynamic_batch_size[2]] + list(input.shape[1:]) profile.set_shape(input.name, min_shape, opt_shape, max_shape) - log.info("Input '{}' Optimization Profile with shape MIN {} / OPT {} / MAX {}".format( - input.name, min_shape, opt_shape, max_shape)) + log.info( + "Input '{}' Optimization Profile with shape MIN {} / OPT {} / MAX {}".format( + input.name, min_shape, opt_shape, max_shape + ) + ) else: shape = [batch_size] + list(input.shape[1:]) profile.set_shape(input.name, shape, shape, shape) - log.info("Input '{}' Optimization Profile with shape {}".format(input.name, shape)) + log.info( + "Input '{}' Optimization Profile with shape {}".format( + input.name, shape + ) + ) if dynamic_inputs: self.config.add_optimization_profile(profile) outputs = [self.network.get_output(i) for i in range(self.network.num_outputs)] for output in outputs: - log.info("Output '{}' with shape {} and dtype {}".format(output.name, output.shape, output.dtype)) + log.info( + "Output '{}' with shape {} and dtype {}".format( + output.name, output.shape, output.dtype + ) + ) def set_mixed_precision(self): """ @@ -202,7 +230,8 @@ def set_mixed_precision(self): # add or remove blocks. for i in range(self.network.num_layers): layer = self.network.get_layer(i) - if layer.type == trt.LayerType.CONVOLUTION and any([ + if layer.type == trt.LayerType.CONVOLUTION and any( + [ # AutoML Layer Names: "/stem/" in layer.name, "/blocks_0/" in layer.name, @@ -213,12 +242,24 @@ def set_mixed_precision(self): "/stack_0/block_0/" in layer.name, "/stack_1/block_0/" in layer.name, "/stack_1/block_1/" in layer.name, - ]): + ] + ): self.network.get_layer(i).precision = trt.DataType.HALF - log.info("Mixed-Precision Layer {} set to HALF STRICT data type".format(layer.name)) - - def create_engine(self, engine_path, precision, calib_input=None, calib_cache=None, calib_num_images=5000, - calib_batch_size=8): + log.info( + "Mixed-Precision Layer {} set to HALF STRICT data type".format( + layer.name + ) + ) + + def create_engine( + self, + engine_path, + precision, + calib_input=None, + calib_cache=None, + calib_num_images=5000, + calib_batch_size=8, + ): """ Build the TensorRT engine and serialize it to disk. :param engine_path: The path where to serialize the engine to. @@ -251,8 +292,15 @@ def create_engine(self, engine_path, precision, calib_input=None, calib_cache=No calib_shape = [calib_batch_size] + list(inputs[0].shape[1:]) calib_dtype = trt.nptype(inputs[0].dtype) self.config.int8_calibrator.set_image_batcher( - ImageBatcher(calib_input, calib_shape, calib_dtype, max_num_images=calib_num_images, - exact_batches=True, shuffle_files=True)) + ImageBatcher( + calib_input, + calib_shape, + calib_dtype, + max_num_images=calib_num_images, + exact_batches=True, + shuffle_files=True, + ) + ) engine_bytes = self.builder.build_serialized_network(self.network, self.config) if engine_bytes is None: @@ -272,41 +320,88 @@ def main(args): builder.create_network(args.onnx, args.batch_size, args.dynamic_batch_size) if args.precision == "mixed": builder.set_mixed_precision() - builder.create_engine(args.engine, args.precision, args.calib_input, args.calib_cache, args.calib_num_images, - args.calib_batch_size) + builder.create_engine( + args.engine, + args.precision, + args.calib_input, + args.calib_cache, + args.calib_num_images, + args.calib_batch_size, + ) if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-o", "--onnx", required=True, - help="The input ONNX model file to load") - parser.add_argument("-e", "--engine", required=True, - help="The output path for the TRT engine") - parser.add_argument("-b", "--batch_size", default=1, type=int, - help="The static batch size to build the engine with, default: 1") - parser.add_argument("-d", "--dynamic_batch_size", default=None, - help="Enable dynamic batch size by providing a comma-separated MIN,OPT,MAX batch size, " - "if this option is set, --batch_size is ignored, example: -d 1,16,32, " - "default: None, build static engine") - parser.add_argument("-p", "--precision", default="fp16", choices=["fp32", "fp16", "int8", "mixed"], - help="The precision mode to build in, either fp32/fp16/int8/mixed, default: fp16") - parser.add_argument("-v", "--verbose", action="store_true", - help="Enable more verbose log output") - parser.add_argument("-w", "--workspace", default=8, type=int, - help="The max memory workspace size to allow in Gb, default: 8") - parser.add_argument("--calib_input", - help="The directory holding images to use for calibration") - parser.add_argument("--calib_cache", default=None, - help="The file path for INT8 calibration cache to use, default: ./calibration.cache") - parser.add_argument("--calib_num_images", default=5000, type=int, - help="The maximum number of images to use for calibration, default: 5000") - parser.add_argument("--calib_batch_size", default=8, type=int, - help="The batch size for the calibration process, default: 8") - parser.add_argument("--timing_cache", default="./timing.cache", - help="The file path for timing cache, default: ./timing.cache") + parser.add_argument( + "-o", "--onnx", required=True, help="The input ONNX model file to load" + ) + parser.add_argument( + "-e", "--engine", required=True, help="The output path for the TRT engine" + ) + parser.add_argument( + "-b", + "--batch_size", + default=1, + type=int, + help="The static batch size to build the engine with, default: 1", + ) + parser.add_argument( + "-d", + "--dynamic_batch_size", + default=None, + help="Enable dynamic batch size by providing a comma-separated MIN,OPT,MAX batch size, " + "if this option is set, --batch_size is ignored, example: -d 1,16,32, " + "default: None, build static engine", + ) + parser.add_argument( + "-p", + "--precision", + default="fp16", + choices=["fp32", "fp16", "int8", "mixed"], + help="The precision mode to build in, either fp32/fp16/int8/mixed, default: fp16", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable more verbose log output" + ) + parser.add_argument( + "-w", + "--workspace", + default=8, + type=int, + help="The max memory workspace size to allow in Gb, default: 8", + ) + parser.add_argument( + "--calib_input", help="The directory holding images to use for calibration" + ) + parser.add_argument( + "--calib_cache", + default=None, + help="The file path for INT8 calibration cache to use, default: ./calibration.cache", + ) + parser.add_argument( + "--calib_num_images", + default=5000, + type=int, + help="The maximum number of images to use for calibration, default: 5000", + ) + parser.add_argument( + "--calib_batch_size", + default=8, + type=int, + help="The batch size for the calibration process, default: 8", + ) + parser.add_argument( + "--timing_cache", + default="./timing.cache", + help="The file path for timing cache, default: ./timing.cache", + ) args = parser.parse_args() - if args.precision in ["int8", "mixed"] and not (args.calib_input or os.path.exists(args.calib_cache)): + if args.precision in ["int8", "mixed"] and not ( + args.calib_input or os.path.exists(args.calib_cache) + ): parser.print_help() - log.error("When building in int8 or mixed precision, --calib_input or an existing --calib_cache file is required") + log.error( + "When building in int8 or mixed precision, --calib_input or an existing --calib_cache file is required" + ) sys.exit(1) main(args) diff --git a/samples/python/efficientdet/compare_tf.py b/samples/python/efficientdet/compare_tf.py index 54c356cd..4e4b91fc 100644 --- a/samples/python/efficientdet/compare_tf.py +++ b/samples/python/efficientdet/compare_tf.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -35,7 +35,12 @@ def run(batcher, inferer, framework, nms_threshold=None): for batch, images, scales in batcher.get_batch(): res_detections += inferer.process(batch, scales, nms_threshold) res_images += images - print("Processing {} / {} images ({})".format(batcher.image_index, batcher.num_images, framework), end="\r") + print( + "Processing {} / {} images ({})".format( + batcher.image_index, batcher.num_images, framework + ), + end="\r", + ) print() return res_images, res_detections @@ -62,7 +67,15 @@ def parse_annotations(annotations_path): return annotations -def compare_images(tf_images, tf_detections, trt_images, trt_detections, output_dir, annotations_path, labels_path): +def compare_images( + tf_images, + tf_detections, + trt_images, + trt_detections, + output_dir, + annotations_path, + labels_path, +): labels = [] if labels_path and os.path.exists(labels_path): with open(labels_path) as f: @@ -72,7 +85,9 @@ def compare_images(tf_images, tf_detections, trt_images, trt_detections, output_ annotations = parse_annotations(annotations_path) count = 1 - for tf_img, tf_det, trt_img, trt_det in zip(tf_images, tf_detections, trt_images, trt_detections): + for tf_img, tf_det, trt_img, trt_det in zip( + tf_images, tf_detections, trt_images, trt_detections + ): vis = [] names = [] colors = [] @@ -90,18 +105,27 @@ def compare_images(tf_images, tf_detections, trt_images, trt_detections, output_ if img_id.isnumeric(): img_id = int(img_id) if img_id in annotations.keys(): - vis.append(visualize_detections(trt_img, None, annotations[img_id], labels)) + vis.append( + visualize_detections(trt_img, None, annotations[img_id], labels) + ) names.append("Ground Truth") colors.append("RoyalBlue") else: - print("Image {} does not have a COCO annotation, skipping ground truth visualization".format(trt_img)) + print( + "Image {} does not have a COCO annotation, skipping ground truth visualization".format( + trt_img + ) + ) basename = os.path.splitext(os.path.basename(tf_img))[0] output_path = os.path.join(output_dir, "{}.compare.png".format(basename)) os.makedirs(output_dir, exist_ok=True) concat_visualizations(vis, names, colors, output_path) - print("Processing {} / {} images (Visualization)".format(count, len(tf_images)), end="\r") + print( + "Processing {} / {} images (Visualization)".format(count, len(tf_images)), + end="\r", + ) count += 1 print() @@ -110,32 +134,80 @@ def main(args): tf_infer = TensorFlowInfer(args.saved_model) trt_infer = TensorRTInfer(args.engine) - trt_batcher = ImageBatcher(args.input, *trt_infer.input_spec(), max_num_images=args.num_images) - tf_infer.override_input_shape(0, [1, trt_batcher.height, trt_batcher.width, 3]) # Same size input in TF as TRT - tf_batcher = ImageBatcher(args.input, *tf_infer.input_spec(), max_num_images=args.num_images) - - tf_images, tf_detections = run(tf_batcher, tf_infer, "TensorFlow", args.nms_threshold) - trt_images, trt_detections = run(trt_batcher, trt_infer, "TensorRT", args.nms_threshold) - - compare_images(tf_images, tf_detections, trt_images, trt_detections, args.output, args.annotations, args.labels) + trt_batcher = ImageBatcher( + args.input, *trt_infer.input_spec(), max_num_images=args.num_images + ) + tf_infer.override_input_shape( + 0, [1, trt_batcher.height, trt_batcher.width, 3] + ) # Same size input in TF as TRT + tf_batcher = ImageBatcher( + args.input, *tf_infer.input_spec(), max_num_images=args.num_images + ) + + tf_images, tf_detections = run( + tf_batcher, tf_infer, "TensorFlow", args.nms_threshold + ) + trt_images, trt_detections = run( + trt_batcher, trt_infer, "TensorRT", args.nms_threshold + ) + + compare_images( + tf_images, + tf_detections, + trt_images, + trt_detections, + args.output, + args.annotations, + args.labels, + ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with") - parser.add_argument("-m", "--saved_model", help="The TensorFlow saved model path to validate against") - parser.add_argument("-i", "--input", - help="The input to infer, either a single image path, or a directory of images") - parser.add_argument("-o", "--output", default=None, help="Directory where to save the visualization results") - parser.add_argument("-l", "--labels", default="./labels_coco.txt", - help="File to use for reading the class labels from, default: ./labels_coco.txt") - parser.add_argument("-a", "--annotations", default=None, - help="Set the path to the 'instances_val2017.json' file to use for COCO annotations, in which " - "case --input should point to the COCO val2017 dataset, default: not used") - parser.add_argument("-n", "--num_images", default=100, type=int, - help="The maximum number of images to visualize, default: 100") - parser.add_argument("-t", "--nms_threshold", type=float, help="Override the score threshold for the NMS operation, " - "if higher than the threshold in the model/engine.") + parser.add_argument( + "-m", + "--saved_model", + help="The TensorFlow saved model path to validate against", + ) + parser.add_argument( + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images", + ) + parser.add_argument( + "-o", + "--output", + default=None, + help="Directory where to save the visualization results", + ) + parser.add_argument( + "-l", + "--labels", + default="./labels_coco.txt", + help="File to use for reading the class labels from, default: ./labels_coco.txt", + ) + parser.add_argument( + "-a", + "--annotations", + default=None, + help="Set the path to the 'instances_val2017.json' file to use for COCO annotations, in which " + "case --input should point to the COCO val2017 dataset, default: not used", + ) + parser.add_argument( + "-n", + "--num_images", + default=100, + type=int, + help="The maximum number of images to visualize, default: 100", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, " + "if higher than the threshold in the model/engine.", + ) args = parser.parse_args() if not all([args.engine, args.saved_model, args.input, args.output]): parser.print_help() diff --git a/samples/python/efficientdet/create_onnx.py b/samples/python/efficientdet/create_onnx.py index 17fee5f6..ffe83094 100644 --- a/samples/python/efficientdet/create_onnx.py +++ b/samples/python/efficientdet/create_onnx.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -52,8 +52,12 @@ def __init__(self, saved_model_path): with tf.Graph().as_default() as tf_graph: tf.import_graph_def(graph_def, name="") with tf_loader.tf_session(graph=tf_graph): - onnx_graph = tfonnx.process_tf_graph(tf_graph, input_names=inputs, output_names=outputs, opset=11) - onnx_model = optimizer.optimize_graph(onnx_graph).make_model("Converted from {}".format(saved_model_path)) + onnx_graph = tfonnx.process_tf_graph( + tf_graph, input_names=inputs, output_names=outputs, opset=11 + ) + onnx_model = optimizer.optimize_graph(onnx_graph).make_model( + "Converted from {}".format(saved_model_path) + ) self.graph = gs.import_onnx(onnx_model) assert self.graph log.info("TF2ONNX graph created successfully") @@ -65,7 +69,16 @@ def __init__(self, saved_model_path): self.api = None if len([node for node in self.graph.nodes if "class_net/" in node.name]) > 0: self.api = "AutoML" - elif len([node for node in self.graph.nodes if "/WeightSharedConvolutionalClassHead/" in node.name]) > 0: + elif ( + len( + [ + node + for node in self.graph.nodes + if "/WeightSharedConvolutionalClassHead/" in node.name + ] + ) + > 0 + ): self.api = "TFOD" assert self.api log.info("Graph was detected as {}".format(self.api)) @@ -87,7 +100,9 @@ def sanitize(self): model = shape_inference.infer_shapes(model) self.graph = gs.import_onnx(model) except Exception as e: - log.info("Shape inference could not be performed at this time:\n{}".format(e)) + log.info( + "Shape inference could not be performed at this time:\n{}".format(e) + ) try: self.graph.fold_constants(fold_shapes=True) except TypeError as e: @@ -130,41 +145,63 @@ def update_preprocessor(self, input_format, input_size, preprocessor="imagenet") assert input_size[i] >= 1 assert input_format in ["NCHW", "NHWC"] if input_format == "NCHW": - self.graph.inputs[0].shape = ['N', 3, input_size[0], input_size[1]] + self.graph.inputs[0].shape = ["N", 3, input_size[0], input_size[1]] if input_format == "NHWC": - self.graph.inputs[0].shape = ['N', input_size[0], input_size[1], 3] + self.graph.inputs[0].shape = ["N", input_size[0], input_size[1], 3] self.graph.inputs[0].dtype = np.float32 self.graph.inputs[0].name = "input" - log.info("ONNX graph input shape: {} [{} format]".format(self.graph.inputs[0].shape, input_format)) + log.info( + "ONNX graph input shape: {} [{} format]".format( + self.graph.inputs[0].shape, input_format + ) + ) self.sanitize() # Find the initial nodes of the graph, whatever the input is first connected to, and disconnect them - for node in [node for node in self.graph.nodes if self.graph.inputs[0] in node.inputs]: + for node in [ + node for node in self.graph.nodes if self.graph.inputs[0] in node.inputs + ]: node.inputs.clear() # Convert to NCHW format if needed input_tensor = self.graph.inputs[0] if input_format == "NHWC": - input_tensor = self.graph.transpose("preprocessor/transpose", input_tensor, [0, 3, 1, 2]) + input_tensor = self.graph.transpose( + "preprocessor/transpose", input_tensor, [0, 3, 1, 2] + ) assert preprocessor in ["imagenet", "scale_range"] preprocessed_tensor = None if preprocessor == "imagenet": # RGB Normalizers. The per-channel values are given with shape [1, 3, 1, 1] for proper NCHW shape broadcasting scale_val = 1 / np.asarray([255], dtype=np.float32) - mean_val = -1 * np.expand_dims(np.asarray([0.485, 0.456, 0.406], dtype=np.float32), axis=(0, 2, 3)) - stddev_val = 1 / np.expand_dims(np.asarray([0.229, 0.224, 0.225], dtype=np.float32), axis=(0, 2, 3)) + mean_val = -1 * np.expand_dims( + np.asarray([0.485, 0.456, 0.406], dtype=np.float32), axis=(0, 2, 3) + ) + stddev_val = 1 / np.expand_dims( + np.asarray([0.229, 0.224, 0.225], dtype=np.float32), axis=(0, 2, 3) + ) # y = (x * scale + mean) * stddev --> y = x * scale * stddev + mean * stddev - scale_out = self.graph.elt_const("Mul", "preprocessor/scale", input_tensor, scale_val * stddev_val) - mean_out = self.graph.elt_const("Add", "preprocessor/mean", scale_out, mean_val * stddev_val) + scale_out = self.graph.elt_const( + "Mul", "preprocessor/scale", input_tensor, scale_val * stddev_val + ) + mean_out = self.graph.elt_const( + "Add", "preprocessor/mean", scale_out, mean_val * stddev_val + ) preprocessed_tensor = mean_out[0] if preprocessor == "scale_range": # RGB Normalizers. The per-channel values are given with shape [1, 3, 1, 1] for proper NCHW shape broadcasting scale_val = 2 / np.asarray([255], dtype=np.float32) - offset_val = np.expand_dims(np.asarray([-1, -1, -1], dtype=np.float32), axis=(0, 2, 3)) + offset_val = np.expand_dims( + np.asarray([-1, -1, -1], dtype=np.float32), axis=(0, 2, 3) + ) # y = (x * scale + mean) * stddev --> y = x * scale * stddev + mean * stddev - scale_out = self.graph.elt_const("Mul", "preprocessor/scale", input_tensor, scale_val) - range_out = self.graph.elt_const("Add", "preprocessor/range", scale_out, offset_val) + scale_out = self.graph.elt_const( + "Mul", "preprocessor/scale", input_tensor, scale_val + ) + range_out = self.graph.elt_const( + "Add", "preprocessor/range", scale_out, offset_val + ) preprocessed_tensor = range_out[0] # Find the first stem conv node of the graph, and connect the normalizer directly to it @@ -173,7 +210,11 @@ def update_preprocessor(self, input_format, input_size, preprocessor="imagenet") stem_name = "/stem/" if self.api == "TFOD": stem_name = "/stem_conv2d/" - stem = [node for node in self.graph.nodes if node.op == "Conv" and stem_name in node.name][0] + stem = [ + node + for node in self.graph.nodes + if node.op == "Conv" and stem_name in node.name + ][0] log.info("Found {} node '{}' as stem entry".format(stem.op, stem.name)) stem.inputs[0] = preprocessed_tensor @@ -184,7 +225,10 @@ def update_shapes(self): # Output-Head reshapes use [1, -1, C], corrected reshape value should be [-1, V, C] for node in [node for node in self.graph.nodes if node.op == "Reshape"]: shape_in = node.inputs[0].shape - if shape_in is None or len(shape_in) not in [4,5]: # TFOD graphs have 5-dim inputs on this Reshape + if shape_in is None or len(shape_in) not in [ + 4, + 5, + ]: # TFOD graphs have 5-dim inputs on this Reshape continue if type(node.inputs[1]) != gs.Constant: continue @@ -195,15 +239,29 @@ def update_shapes(self): if len(shape_in) == 5: volume *= shape_in[4] shape_corrected = np.asarray([-1, volume, shape_out[2]], dtype=np.int64) - node.inputs[1] = gs.Constant("{}_shape".format(node.name), values=shape_corrected) - log.info("Updating Output-Head Reshape node {} to {}".format(node.name, node.inputs[1].values)) + node.inputs[1] = gs.Constant( + "{}_shape".format(node.name), values=shape_corrected + ) + log.info( + "Updating Output-Head Reshape node {} to {}".format( + node.name, node.inputs[1].values + ) + ) # Other Reshapes only need to change the first dim to -1, as long as there are no -1's already for node in [node for node in self.graph.nodes if node.op == "Reshape"]: - if type(node.inputs[1]) != gs.Constant or node.inputs[1].values[0] != 1 or -1 in node.inputs[1].values: + if ( + type(node.inputs[1]) != gs.Constant + or node.inputs[1].values[0] != 1 + or -1 in node.inputs[1].values + ): continue node.inputs[1].values[0] = -1 - log.info("Updating Reshape node {} to {}".format(node.name, node.inputs[1].values)) + log.info( + "Updating Reshape node {} to {}".format( + node.name, node.inputs[1].values + ) + ) # Resize nodes try to calculate the output shape dynamically, it's more optimal to pre-compute the shape if self.api == "AutoML": @@ -223,13 +281,18 @@ def update_shapes(self): concat = node.i(3) if concat.op != "Concat": continue - if type(concat.inputs[1]) != gs.Constant or len(concat.inputs[1].values) != 2: + if ( + type(concat.inputs[1]) != gs.Constant + or len(concat.inputs[1].values) != 2 + ): continue scale_h = concat.inputs[1].values[0] / node.inputs[0].shape[2] scale_w = concat.inputs[1].values[1] / node.inputs[0].shape[3] scales = np.asarray([1, 1, scale_h, scale_w], dtype=np.float32) del node.inputs[3] - node.inputs[2] = gs.Constant(name="{}_scales".format(node.name), values=scales) + node.inputs[2] = gs.Constant( + name="{}_scales".format(node.name), values=scales + ) log.info("Updating Resize node {} to {}".format(node.name, scales)) self.sanitize() @@ -241,7 +304,9 @@ def update_network(self): """ if self.api == "TFOD": - for reduce in [node for node in self.graph.nodes if node.op == "ReduceMean"]: + for reduce in [ + node for node in self.graph.nodes if node.op == "ReduceMean" + ]: # TFOD models have their ReduceMean nodes applied with some redundant transposes that can be # optimized away for better performance # Make sure the correct subgraph is being replaced, basically search for this: @@ -249,19 +314,30 @@ def update_network(self): # And change to this: # X > ReduceMean (2,3) > Conv > Y transpose = reduce.i() - if transpose.op != "Transpose" or transpose.attrs['perm'] != [0, 2, 3, 1]: + if transpose.op != "Transpose" or transpose.attrs["perm"] != [ + 0, + 2, + 3, + 1, + ]: continue - if len(reduce.attrs['axes']) != 2 or reduce.attrs['axes'] != [1, 2]: + if len(reduce.attrs["axes"]) != 2 or reduce.attrs["axes"] != [1, 2]: continue reshape1 = reduce.o() if reshape1.op != "Reshape" or len(reshape1.inputs[1].values) != 4: continue - if reshape1.inputs[1].values[1] != 1 or reshape1.inputs[1].values[2] != 1: + if ( + reshape1.inputs[1].values[1] != 1 + or reshape1.inputs[1].values[2] != 1 + ): continue reshape2 = reshape1.o() if reshape2.op != "Reshape" or len(reshape2.inputs[1].values) != 4: continue - if reshape2.inputs[1].values[2] != 1 or reshape2.inputs[1].values[3] != 1: + if ( + reshape2.inputs[1].values[2] != 1 + or reshape2.inputs[1].values[3] != 1 + ): continue conv = reshape2.o() if conv.op != "Conv": @@ -269,12 +345,21 @@ def update_network(self): # If all the checks above pass, then this node sequence can be optimized by just the ReduceMean itself # operating on a different set of axes input_tensor = transpose.inputs[0] # Input tensor to the Transpose - reduce.inputs[0] = input_tensor # Forward the Transpose input to the ReduceMean node + reduce.inputs[0] = ( + input_tensor # Forward the Transpose input to the ReduceMean node + ) output_tensor = reduce.outputs[0] # Output tensor of the ReduceMean - conv.inputs[0] = output_tensor # Forward the ReduceMean output to the Conv node - reduce.attrs["axes"] = [2, 3] # Update the axes that ReduceMean operates on + conv.inputs[0] = ( + output_tensor # Forward the ReduceMean output to the Conv node + ) + reduce.attrs["axes"] = [ + 2, + 3, + ] # Update the axes that ReduceMean operates on reduce.attrs["keepdims"] = 1 # Keep the reduced dimensions - log.info("Optimized subgraph around ReduceMean node '{}'".format(reduce.name)) + log.info( + "Optimized subgraph around ReduceMean node '{}'".format(reduce.name) + ) def update_nms(self, threshold=None, detections=None): """ @@ -290,10 +375,18 @@ def find_head_concat(name_scope): # and the concatenated Box Net node has the shape [batch_size, num_anchors, 4]. # These concatenation nodes can be be found by searching for all Concat's and checking if the node two # steps above in the graph has a name that begins with either "box_net/..." or "class_net/...". - for node in [node for node in self.graph.nodes if node.op == "Transpose" and name_scope in node.name]: + for node in [ + node + for node in self.graph.nodes + if node.op == "Transpose" and name_scope in node.name + ]: concat = self.graph.find_descendant_by_op(node, "Concat") assert concat and len(concat.inputs) == 5 - log.info("Found {} node '{}' as the tip of {}".format(concat.op, concat.name, name_scope)) + log.info( + "Found {} node '{}' as the tip of {}".format( + concat.op, concat.name, name_scope + ) + ) return concat def extract_anchors_tensor(split): @@ -319,7 +412,9 @@ def get_anchor_np(output_idx, op): anchors_x = get_anchor_np(1, "Add") anchors_h = get_anchor_np(2, "Mul") anchors_w = get_anchor_np(3, "Mul") - anchors = np.concatenate([anchors_y, anchors_x, anchors_h, anchors_w], axis=2) + anchors = np.concatenate( + [anchors_y, anchors_x, anchors_h, anchors_w], axis=2 + ) return gs.Constant(name="nms/anchors:0", values=anchors) self.sanitize() @@ -328,7 +423,10 @@ def get_anchor_np(output_idx, op): if self.api == "AutoML": head_names = ["class_net/", "box_net/"] if self.api == "TFOD": - head_names = ["/WeightSharedConvolutionalClassHead/", "/WeightSharedConvolutionalBoxHead/"] + head_names = [ + "/WeightSharedConvolutionalClassHead/", + "/WeightSharedConvolutionalBoxHead/", + ] # There are five nodes at the bottom of the graph that provide important connection points: @@ -353,9 +451,13 @@ def get_anchor_np(output_idx, op): nms_node = self.graph.find_node_by_op("NonMaxSuppression") # Extract NMS Configuration - num_detections = int(nms_node.inputs[2].values) if detections is None else detections + num_detections = ( + int(nms_node.inputs[2].values) if detections is None else detections + ) iou_threshold = float(nms_node.inputs[3].values) - score_threshold = float(nms_node.inputs[4].values) if threshold is None else threshold + score_threshold = ( + float(nms_node.inputs[4].values) if threshold is None else threshold + ) num_classes = class_net.i().inputs[1].values[-1] normalized = True if self.api == "TFOD" else False @@ -380,27 +482,41 @@ def get_anchor_np(output_idx, op): nms_inputs = [box_net_tensor, class_net_tensor, anchors_tensor] nms_op = "EfficientNMS_TRT" nms_attrs = { - 'plugin_version': "1", - 'background_class': -1, - 'max_output_boxes': num_detections, - 'score_threshold': max(0.01, score_threshold), # Keep threshold to at least 0.01 for better efficiency - 'iou_threshold': iou_threshold, - 'score_activation': True, - 'class_agnostic': False, - 'box_coding': 1, + "plugin_version": "1", + "background_class": -1, + "max_output_boxes": num_detections, + "score_threshold": max( + 0.01, score_threshold + ), # Keep threshold to at least 0.01 for better efficiency + "iou_threshold": iou_threshold, + "score_activation": True, + "class_agnostic": False, + "box_coding": 1, } nms_output_classes_dtype = np.int32 # NMS Outputs - nms_output_num_detections = gs.Variable(name="num_detections", dtype=np.int32, shape=['N', 1]) - nms_output_boxes = gs.Variable(name="detection_boxes", dtype=np.float32, - shape=['N', num_detections, 4]) - nms_output_scores = gs.Variable(name="detection_scores", dtype=np.float32, - shape=['N', num_detections]) - nms_output_classes = gs.Variable(name="detection_classes", dtype=nms_output_classes_dtype, - shape=['N', num_detections]) + nms_output_num_detections = gs.Variable( + name="num_detections", dtype=np.int32, shape=["N", 1] + ) + nms_output_boxes = gs.Variable( + name="detection_boxes", dtype=np.float32, shape=["N", num_detections, 4] + ) + nms_output_scores = gs.Variable( + name="detection_scores", dtype=np.float32, shape=["N", num_detections] + ) + nms_output_classes = gs.Variable( + name="detection_classes", + dtype=nms_output_classes_dtype, + shape=["N", num_detections], + ) - nms_outputs = [nms_output_num_detections, nms_output_boxes, nms_output_scores, nms_output_classes] + nms_outputs = [ + nms_output_num_detections, + nms_output_boxes, + nms_output_scores, + nms_output_classes, + ] # Create the NMS Plugin node with the selected inputs. The outputs of the node will also become the final # outputs of the graph. @@ -409,8 +525,11 @@ def get_anchor_np(output_idx, op): name="nms/non_maximum_suppression", inputs=nms_inputs, outputs=nms_outputs, - attrs=nms_attrs) - log.info("Created NMS plugin '{}' with attributes: {}".format(nms_op, nms_attrs)) + attrs=nms_attrs, + ) + log.info( + "Created NMS plugin '{}' with attributes: {}".format(nms_op, nms_attrs) + ) self.graph.outputs = nms_outputs @@ -430,25 +549,54 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-m", "--saved_model", required=True, - help="The TensorFlow saved model directory to load") - parser.add_argument("-o", "--onnx", required=True, - help="The output ONNX model file to write") - parser.add_argument("-f", "--input_format", default="NHWC", choices=["NHWC", "NCHW"], - help="Set the input data format of the graph, either NCHW or NHWC, default: NHWC") - parser.add_argument("-i", "--input_size", default="512,512", - help="Set the input shape of the graph, as a comma-separated dimensions in H,W format, " - "default: 512,512") - parser.add_argument("-p", "--preprocessor", default="imagenet", choices=["imagenet", "scale_range"], - help="Set the preprocessor to apply on the graph, either 'imagenet' for standard mean " - "subtraction and stdev normalization, or 'scale_range' for uniform [-1,+1] " - "normalization as is used in the AdvProp models, default: imagenet") - parser.add_argument("-t", "--nms_threshold", type=float, - help="Override the NMS score threshold, default: use the original value in the model") - parser.add_argument("-d", "--nms_detections", type=int, - help="Override the NMS max detections, default: use the original value in the model") - parser.add_argument("--tf2onnx", - help="The path where to save the intermediate ONNX graph generated by tf2onnx, useful" - "for graph debugging purposes, default: not saved") + parser.add_argument( + "-m", + "--saved_model", + required=True, + help="The TensorFlow saved model directory to load", + ) + parser.add_argument( + "-o", "--onnx", required=True, help="The output ONNX model file to write" + ) + parser.add_argument( + "-f", + "--input_format", + default="NHWC", + choices=["NHWC", "NCHW"], + help="Set the input data format of the graph, either NCHW or NHWC, default: NHWC", + ) + parser.add_argument( + "-i", + "--input_size", + default="512,512", + help="Set the input shape of the graph, as a comma-separated dimensions in H,W format, " + "default: 512,512", + ) + parser.add_argument( + "-p", + "--preprocessor", + default="imagenet", + choices=["imagenet", "scale_range"], + help="Set the preprocessor to apply on the graph, either 'imagenet' for standard mean " + "subtraction and stdev normalization, or 'scale_range' for uniform [-1,+1] " + "normalization as is used in the AdvProp models, default: imagenet", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the NMS score threshold, default: use the original value in the model", + ) + parser.add_argument( + "-d", + "--nms_detections", + type=int, + help="Override the NMS max detections, default: use the original value in the model", + ) + parser.add_argument( + "--tf2onnx", + help="The path where to save the intermediate ONNX graph generated by tf2onnx, useful" + "for graph debugging purposes, default: not saved", + ) args = parser.parse_args() main(args) diff --git a/samples/python/efficientdet/eval_coco.py b/samples/python/efficientdet/eval_coco.py index 966f49be..d6796ac0 100644 --- a/samples/python/efficientdet/eval_coco.py +++ b/samples/python/efficientdet/eval_coco.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,15 +31,24 @@ def main(args): try: import coco_metric except ImportError: - print("Could not import the 'coco_metric' module from AutoML. Searching in: {}".format(automl_path)) - print("Please clone the repository https://github.com/google/automl and provide its path with --automl_path.") + print( + "Could not import the 'coco_metric' module from AutoML. Searching in: {}".format( + automl_path + ) + ) + print( + "Please clone the repository https://github.com/google/automl and provide its path with --automl_path." + ) sys.exit(1) trt_infer = TensorRTInfer(args.engine) batcher = ImageBatcher(args.input, *trt_infer.input_spec()) evaluator = coco_metric.EvaluationMetric(filename=args.annotations) for batch, images, scales in batcher.get_batch(): - print("Processing Image {} / {}".format(batcher.image_index, batcher.num_images), end="\r") + print( + "Processing Image {} / {}".format(batcher.image_index, batcher.num_images), + end="\r", + ) detections = trt_infer.process(batch, scales, args.nms_threshold) coco_det = np.zeros((len(images), max([len(d) for d in detections]), 7)) coco_det[:, :, -1] = -1 @@ -54,7 +63,8 @@ def main(args): det["xmax"] - det["xmin"], det["ymax"] - det["ymin"], det["score"], - det["class"] + 1, # The COCO evaluator expects class 0 to be background, so offset by 1 + det["class"] + + 1, # The COCO evaluator expects class 0 to be background, so offset by 1 ] evaluator.update_state(None, coco_det) print() @@ -64,14 +74,30 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with") - parser.add_argument("-i", "--input", - help="The input to infer, either a single image path, or a directory of images") - parser.add_argument("-a", "--annotations", help="Set the path to the COCO 'instances_val2017.json' file") - parser.add_argument("-p", "--automl_path", default="./automl", - help="Set the path where to find the AutoML repository, from " - "https://github.com/google/automl. Default: ./automl") - parser.add_argument("-t", "--nms_threshold", type=float, help="Override the score threshold for the NMS operation, " - "if higher than the threshold in the engine.") + parser.add_argument( + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images", + ) + parser.add_argument( + "-a", + "--annotations", + help="Set the path to the COCO 'instances_val2017.json' file", + ) + parser.add_argument( + "-p", + "--automl_path", + default="./automl", + help="Set the path where to find the AutoML repository, from " + "https://github.com/google/automl. Default: ./automl", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, " + "if higher than the threshold in the engine.", + ) args = parser.parse_args() if not all([args.engine, args.input, args.annotations]): parser.print_help() diff --git a/samples/python/efficientdet/image_batcher.py b/samples/python/efficientdet/image_batcher.py index e519a5db..11b94c24 100644 --- a/samples/python/efficientdet/image_batcher.py +++ b/samples/python/efficientdet/image_batcher.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -28,7 +28,16 @@ class ImageBatcher: Creates batches of pre-processed images. """ - def __init__(self, input, shape, dtype, max_num_images=None, exact_batches=False, preprocessor="EfficientDet", shuffle_files=False): + def __init__( + self, + input, + shape, + dtype, + max_num_images=None, + exact_batches=False, + preprocessor="EfficientDet", + shuffle_files=False, + ): """ :param input: The input directory to read images from. :param shape: The tensor shape of the batch to prepare, either in NCHW or NHWC format. @@ -47,10 +56,16 @@ def __init__(self, input, shape, dtype, max_num_images=None, exact_batches=False extensions = [".jpg", ".jpeg", ".png", ".bmp"] def is_image(path): - return os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + return ( + os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + ) if os.path.isdir(input): - self.images = [os.path.join(input, f) for f in os.listdir(input) if is_image(os.path.join(input, f))] + self.images = [ + os.path.join(input, f) + for f in os.listdir(input) + if is_image(os.path.join(input, f)) + ] self.images.sort() if shuffle_files: random.seed(47) @@ -129,7 +144,9 @@ def resize_pad(image, pad_color=(0, 0, 0)): width_scale = width / self.width height_scale = height / self.height scale = 1.0 / max(width_scale, height_scale) - image = image.resize((round(width * scale), round(height * scale)), resample=Image.BILINEAR) + image = image.resize( + (round(width * scale), round(height * scale)), resample=Image.BILINEAR + ) pad = Image.new("RGB", (self.width, self.height)) pad.paste(pad_color, [0, 0, self.width, self.height]) pad.paste(image) diff --git a/samples/python/efficientdet/infer.py b/samples/python/efficientdet/infer.py index 25bd28de..5308cf47 100644 --- a/samples/python/efficientdet/infer.py +++ b/samples/python/efficientdet/infer.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -62,7 +62,7 @@ def __init__(self, engine_path): shape = self.context.get_tensor_shape(name) if is_input and shape[0] < 0: assert self.engine.num_optimization_profiles > 0 - profile_shape = self.engine.get_profile_shape(0, name) + profile_shape = self.engine.get_tensor_profile_shape(name, 0) assert len(profile_shape) == 3 # min,opt,max # Set the *max* profile as binding shape self.context.set_input_shape(name, profile_shape[2]) @@ -87,9 +87,14 @@ def __init__(self, engine_path): self.inputs.append(binding) else: self.outputs.append(binding) - print("{} '{}' with shape {} and dtype {}".format( - "Input" if is_input else "Output", - binding['name'], binding['shape'], binding['dtype'])) + print( + "{} '{}' with shape {} and dtype {}".format( + "Input" if is_input else "Output", + binding["name"], + binding["shape"], + binding["dtype"], + ) + ) assert self.batch_size > 0 assert len(self.inputs) > 0 @@ -101,7 +106,7 @@ def input_spec(self): Get the specs for the input tensor of the network. Useful to prepare memory allocations. :return: Two items, the shape of the input tensor and its (numpy) datatype. """ - return self.inputs[0]['shape'], self.inputs[0]['dtype'] + return self.inputs[0]["shape"], self.inputs[0]["dtype"] def output_spec(self): """ @@ -110,7 +115,7 @@ def output_spec(self): """ specs = [] for o in self.outputs: - specs.append((o['shape'], o['dtype'])) + specs.append((o["shape"], o["dtype"])) return specs def infer(self, batch): @@ -120,11 +125,13 @@ def infer(self, batch): :return A list of outputs as numpy arrays. """ # Copy I/O and Execute - common.memcpy_host_to_device(self.inputs[0]['allocation'], batch) + common.memcpy_host_to_device(self.inputs[0]["allocation"], batch) self.context.execute_v2(self.allocations) for o in range(len(self.outputs)): - common.memcpy_device_to_host(self.outputs[o]['host_allocation'], self.outputs[o]['allocation']) - return [o['host_allocation'] for o in self.outputs] + common.memcpy_device_to_host( + self.outputs[o]["host_allocation"], self.outputs[o]["allocation"] + ) + return [o["host_allocation"] for o in self.outputs] def process(self, batch, scales=None, nms_threshold=None): """ @@ -143,11 +150,11 @@ def process(self, batch, scales=None, nms_threshold=None): scores = outputs[2] classes = outputs[3] detections = [] - normalized = (np.max(boxes) < 2.0) + normalized = np.max(boxes) < 2.0 for i in range(self.batch_size): detections.append([]) for n in range(int(nums[i])): - scale = self.inputs[0]['shape'][2] if normalized else 1.0 + scale = self.inputs[0]["shape"][2] if normalized else 1.0 if scales and i < len(scales): scale /= scales[i] if nms_threshold and scores[i][n] < nms_threshold: @@ -181,7 +188,12 @@ def main(args): print("Inferring data in {}".format(args.input)) batcher = ImageBatcher(args.input, *trt_infer.input_spec()) for batch, images, scales in batcher.get_batch(): - print("Processing Image {} / {}".format(batcher.image_index, batcher.num_images), end="\r") + print( + "Processing Image {} / {}".format( + batcher.image_index, batcher.num_images + ), + end="\r", + ) detections = trt_infer.process(batch, scales, args.nms_threshold) if args.output: for i in range(len(images)): @@ -192,9 +204,18 @@ def main(args): # Text Results output_results = "" for d in detections[i]: - line = [d['xmin'], d['ymin'], d['xmax'], d['ymax'], d['score'], d['class']] + line = [ + d["xmin"], + d["ymin"], + d["xmax"], + d["ymax"], + d["score"], + d["class"], + ] output_results += "\t".join([str(f) for f in line]) + "\n" - with open(os.path.join(output_dir, "{}.txt".format(basename)), "w") as f: + with open( + os.path.join(output_dir, "{}.txt".format(basename)), "w" + ) as f: f.write(output_results) else: print("No input provided, running in benchmark mode") @@ -210,10 +231,12 @@ def main(args): times.append(time.time() - start) print("Iteration {} / {}".format(i + 1, iterations), end="\r") print("Benchmark results include time for H2D and D2H memory copies") - print("Average Latency: {:.3f} ms".format( - 1000 * np.average(times))) - print("Average Throughput: {:.1f} ips".format( - trt_infer.batch_size / np.average(times))) + print("Average Latency: {:.3f} ms".format(1000 * np.average(times))) + print( + "Average Throughput: {:.1f} ips".format( + trt_infer.batch_size / np.average(times) + ) + ) print() print("Finished Processing") @@ -221,15 +244,33 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-e", "--engine", default=None, required=True, - help="The serialized TensorRT engine") - parser.add_argument("-i", "--input", default=None, - help="Path to the image or directory to process") - parser.add_argument("-o", "--output", default=None, - help="Directory where to save the visualization results") - parser.add_argument("-l", "--labels", default="./labels_coco.txt", - help="File to use for reading the class labels from, default: ./labels_coco.txt") - parser.add_argument("-t", "--nms_threshold", type=float, - help="Override the score threshold for the NMS operation, if higher than the built-in threshold") + parser.add_argument( + "-e", + "--engine", + default=None, + required=True, + help="The serialized TensorRT engine", + ) + parser.add_argument( + "-i", "--input", default=None, help="Path to the image or directory to process" + ) + parser.add_argument( + "-o", + "--output", + default=None, + help="Directory where to save the visualization results", + ) + parser.add_argument( + "-l", + "--labels", + default="./labels_coco.txt", + help="File to use for reading the class labels from, default: ./labels_coco.txt", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, if higher than the built-in threshold", + ) args = parser.parse_args() main(args) diff --git a/samples/python/efficientdet/infer_tf.py b/samples/python/efficientdet/infer_tf.py index a02f87ee..a2ecbd93 100644 --- a/samples/python/efficientdet/infer_tf.py +++ b/samples/python/efficientdet/infer_tf.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -30,47 +30,51 @@ class TensorFlowInfer: """ def __init__(self, saved_model_path): - gpus = tf.config.experimental.list_physical_devices('GPU') + gpus = tf.config.experimental.list_physical_devices("GPU") for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) self.model = tf.saved_model.load(saved_model_path) - self.pred_fn = self.model.signatures['serving_default'] + self.pred_fn = self.model.signatures["serving_default"] # Setup I/O bindings self.batch_size = 1 self.inputs = [] fn_inputs = self.pred_fn.structured_input_signature[1] for i, input in enumerate(list(fn_inputs.values())): - self.inputs.append({ - 'index': i, - 'name': input.name, - 'dtype': np.dtype(input.dtype.as_numpy_dtype()), - 'shape': [1, 512, 512, 3], # This can be overridden later - }) + self.inputs.append( + { + "index": i, + "name": input.name, + "dtype": np.dtype(input.dtype.as_numpy_dtype()), + "shape": [1, 512, 512, 3], # This can be overridden later + } + ) self.outputs = [] fn_outputs = self.pred_fn.structured_outputs for i, output in enumerate(list(fn_outputs.values())): - self.outputs.append({ - 'index': i, - 'name': output.name, - 'dtype': np.dtype(output.dtype.as_numpy_dtype()), - 'shape': output.shape.as_list(), - }) + self.outputs.append( + { + "index": i, + "name": output.name, + "dtype": np.dtype(output.dtype.as_numpy_dtype()), + "shape": output.shape.as_list(), + } + ) def override_input_shape(self, input, shape): - self.inputs[input]['shape'] = shape + self.inputs[input]["shape"] = shape self.batch_size = shape[0] def input_spec(self): - return self.inputs[0]['shape'], self.inputs[0]['dtype'] + return self.inputs[0]["shape"], self.inputs[0]["dtype"] def output_spec(self): - return self.outputs[0]['shape'], self.outputs[0]['dtype'] + return self.outputs[0]["shape"], self.outputs[0]["dtype"] def infer(self, batch): # Process I/O and execute the network - input = {self.inputs[0]['name']: tf.convert_to_tensor(batch)} + input = {self.inputs[0]["name"]: tf.convert_to_tensor(batch)} output = self.pred_fn(**input) return output @@ -84,38 +88,42 @@ def process(self, batch, scales=None, nms_threshold=None): classes = None if len(self.outputs) == 1: # Detected as AutoML Saved Model - assert len(self.outputs[0]['shape']) == 3 and self.outputs[0]['shape'][2] == 7 - results = output[self.outputs[0]['name']].numpy() + assert ( + len(self.outputs[0]["shape"]) == 3 and self.outputs[0]["shape"][2] == 7 + ) + results = output[self.outputs[0]["name"]].numpy() boxes = results[:, :, 1:5] scores = results[:, :, 5] classes = results[:, :, 6].astype(np.int32) elif len(self.outputs) >= 4: # Detected as TFOD Saved Model - assert output['num_detections'] - num = int(output['num_detections'].numpy().flatten()[0]) - boxes = output['detection_boxes'].numpy()[:, 0:num, :] - scores = output['detection_scores'].numpy()[:, 0:num] - classes = output['detection_classes'].numpy()[:, 0:num] + assert output["num_detections"] + num = int(output["num_detections"].numpy().flatten()[0]) + boxes = output["detection_boxes"].numpy()[:, 0:num, :] + scores = output["detection_scores"].numpy()[:, 0:num] + classes = output["detection_classes"].numpy()[:, 0:num] # Process the results detections = [[]] - normalized = (np.max(boxes) < 2.0) + normalized = np.max(boxes) < 2.0 for n in range(scores.shape[1]): if scores[0][n] == 0.0: break - scale = self.inputs[0]['shape'][2] if normalized else 1.0 + scale = self.inputs[0]["shape"][2] if normalized else 1.0 if scales: scale /= scales[0] if nms_threshold and scores[0][n] < nms_threshold: continue - detections[0].append({ - 'ymin': boxes[0][n][0] * scale, - 'xmin': boxes[0][n][1] * scale, - 'ymax': boxes[0][n][2] * scale, - 'xmax': boxes[0][n][3] * scale, - 'score': scores[0][n], - 'class': int(classes[0][n]) - 1, - }) + detections[0].append( + { + "ymin": boxes[0][n][0] * scale, + "xmin": boxes[0][n][1] * scale, + "ymax": boxes[0][n][2] * scale, + "xmax": boxes[0][n][3] * scale, + "score": scores[0][n], + "class": int(classes[0][n]) - 1, + } + ) return detections @@ -137,10 +145,10 @@ def main(args): times.append(time.time() - start) print("Iteration {} / {}".format(i + 1, iterations), end="\r") print("Benchmark results include TensorFlow host overhead") - print("Average Latency: {:.3f} ms".format( - 1000 * np.average(times))) - print("Average Throughput: {:.1f} ips".format( - tf_infer.batch_size / np.average(times))) + print("Average Latency: {:.3f} ms".format(1000 * np.average(times))) + print( + "Average Throughput: {:.1f} ips".format(tf_infer.batch_size / np.average(times)) + ) print() print("Finished Processing") @@ -148,11 +156,24 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-m", "--saved_model", required=True, - help="The TensorFlow saved model path to validate against") - parser.add_argument("-i", "--input_size", default="512,512", - help="The input size to run the model with, in HEIGHT,WIDTH format") - parser.add_argument("-b", "--batch_size", default=1, type=int, - help="The batch size to run the model with") + parser.add_argument( + "-m", + "--saved_model", + required=True, + help="The TensorFlow saved model path to validate against", + ) + parser.add_argument( + "-i", + "--input_size", + default="512,512", + help="The input size to run the model with, in HEIGHT,WIDTH format", + ) + parser.add_argument( + "-b", + "--batch_size", + default=1, + type=int, + help="The batch size to run the model with", + ) args = parser.parse_args() main(args) diff --git a/samples/python/efficientdet/onnx_utils.py b/samples/python/efficientdet/onnx_utils.py index e55f7e11..a98c3a7c 100644 --- a/samples/python/efficientdet/onnx_utils.py +++ b/samples/python/efficientdet/onnx_utils.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -36,7 +36,9 @@ def elt_const(self, op, name, input, value): 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"]) + return self.layer( + name=name, op=op, inputs=[input_tensor, const], outputs=[name + ":0"] + ) @gs.Graph.register() @@ -51,7 +53,13 @@ def unsqueeze(self, name, input, axes=[-1]): """ 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}) + return self.layer( + name=name, + op="Unsqueeze", + inputs=[input_tensor], + outputs=[name + ":0"], + attrs={"axes": axes}, + ) @gs.Graph.register() @@ -66,7 +74,13 @@ def transpose(self, name, input, perm): """ 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}) + return self.layer( + name=name, + op="Transpose", + inputs=[input_tensor], + outputs=[name + ":0"], + attrs={"perm": perm}, + ) @gs.Graph.register() @@ -80,7 +94,9 @@ def sigmoid(self, name, input): """ 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"]) + return self.layer( + name=name, op="Sigmoid", inputs=[input_tensor], outputs=[name + ":0"] + ) @gs.Graph.register() @@ -98,7 +114,9 @@ def plugin(self, op, name, inputs, outputs, attrs): """ input_tensors = inputs if type(inputs) is list else [inputs] log.debug("Created TRT Plugin node '{}': {}".format(name, attrs)) - return self.layer(op=op, name=name, inputs=input_tensors, outputs=outputs, attrs=attrs) + return self.layer( + op=op, name=name, inputs=input_tensors, outputs=outputs, attrs=attrs + ) @gs.Graph.register() diff --git a/samples/python/efficientdet/visualize.py b/samples/python/efficientdet/visualize.py index 4366f9e0..3fb982ef 100644 --- a/samples/python/efficientdet/visualize.py +++ b/samples/python/efficientdet/visualize.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -182,9 +182,18 @@ def visualize_detections(image_path, output_path, detections, labels=[]): text_left = d["xmin"] margin = np.ceil(0.05 * text_height) draw.rectangle( - [(text_left, text_bottom - text_height - 2 * margin), (text_left + text_width, text_bottom)], fill=color + [ + (text_left, text_bottom - text_height - 2 * margin), + (text_left + text_width, text_bottom), + ], + fill=color, + ) + draw.text( + (text_left + margin, text_bottom - text_height - margin), + text, + fill="black", + font=font, ) - draw.text((text_left + margin, text_bottom - text_height - margin), text, fill="black", font=font) if output_path is None: return image image.save(output_path) @@ -195,7 +204,12 @@ def draw_text(draw, font, text, width, bar_height, offset, color): left, top, right, bottom = font.getbbox(text) text_width, text_height = right - left, bottom - top draw.rectangle([(offset, 0), (offset + width, bar_height)], fill=color) - draw.text((offset + (width - text_width) / 2, text_height - text_height / 2), text, fill="black", font=font) + draw.text( + (offset + (width - text_width) / 2, text_height - text_height / 2), + text, + fill="black", + font=font, + ) bar_height = 18 width = 0 diff --git a/samples/python/efficientnet/build_engine.py b/samples/python/efficientnet/build_engine.py index a4d75552..683c567c 100644 --- a/samples/python/efficientnet/build_engine.py +++ b/samples/python/efficientnet/build_engine.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -56,7 +56,10 @@ def set_image_batcher(self, image_batcher: ImageBatcher): :param image_batcher: The ImageBatcher object """ self.image_batcher = image_batcher - size = int(np.dtype(self.image_batcher.dtype).itemsize * np.prod(self.image_batcher.shape)) + size = int( + np.dtype(self.image_batcher.dtype).itemsize + * np.prod(self.image_batcher.shape) + ) self.batch_allocation = common.cuda_call(cudart.cudaMalloc(size)) self.batch_generator = self.image_batcher.get_batch() @@ -81,8 +84,14 @@ def get_batch(self, names): return None try: batch, _ = next(self.batch_generator) - log.info("Calibrating image {} / {}".format(self.image_batcher.image_index, self.image_batcher.num_images)) - common.memcpy_host_to_device(self.batch_allocation, np.ascontiguousarray(batch)) + log.info( + "Calibrating image {} / {}".format( + self.image_batcher.image_index, self.image_batcher.num_images + ) + ) + common.memcpy_host_to_device( + self.batch_allocation, np.ascontiguousarray(batch) + ) return [int(self.batch_allocation)] except StopIteration: log.info("Finished calibration batches") @@ -127,7 +136,9 @@ def __init__(self, verbose=False): self.builder = trt.Builder(self.trt_logger) self.config = self.builder.create_builder_config() - self.config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 8 * (2 ** 30)) # 8 GB + self.config.set_memory_pool_limit( + trt.MemoryPoolType.WORKSPACE, 8 * (2**30) + ) # 8 GB self.batch_size = None self.network = None @@ -156,9 +167,17 @@ def create_network(self, onnx_path): log.info("Network Description") for input in inputs: self.batch_size = input.shape[0] - log.info("Input '{}' with shape {} and dtype {}".format(input.name, input.shape, input.dtype)) + log.info( + "Input '{}' with shape {} and dtype {}".format( + input.name, input.shape, input.dtype + ) + ) for output in outputs: - log.info("Output '{}' with shape {} and dtype {}".format(output.name, output.shape, output.dtype)) + log.info( + "Output '{}' with shape {} and dtype {}".format( + output.name, output.shape, output.dtype + ) + ) assert self.batch_size > 0 def create_engine( @@ -254,8 +273,12 @@ def main(args): choices=["fp32", "fp16", "int8"], help="The precision mode to build in, either 'fp32', 'fp16' or 'int8', default: 'fp16'", ) - parser.add_argument("-v", "--verbose", action="store_true", help="Enable more verbose log output") - parser.add_argument("--calib_input", help="The directory holding images to use for calibration") + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable more verbose log output" + ) + parser.add_argument( + "--calib_input", help="The directory holding images to use for calibration" + ) parser.add_argument( "--calib_cache", default="./calibration.cache", @@ -268,7 +291,10 @@ def main(args): help="The maximum number of images to use for calibration, default: 25000", ) parser.add_argument( - "--calib_batch_size", default=8, type=int, help="The batch size for the calibration process, default: 1" + "--calib_batch_size", + default=8, + type=int, + help="The batch size for the calibration process, default: 1", ) parser.add_argument( "--calib_preprocessor", @@ -288,6 +314,8 @@ def main(args): sys.exit(1) if args.precision == "int8" and not any([args.calib_input, args.calib_cache]): parser.print_help() - log.error("When building in int8 precision, either --calib_input or --calib_cache are required") + log.error( + "When building in int8 precision, either --calib_input or --calib_cache are required" + ) sys.exit(1) main(args) diff --git a/samples/python/efficientnet/compare_tf.py b/samples/python/efficientnet/compare_tf.py index 6d9ad88f..2671572e 100644 --- a/samples/python/efficientnet/compare_tf.py +++ b/samples/python/efficientnet/compare_tf.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -91,7 +91,10 @@ def main(args): trt_infer = TensorRTInfer(args.engine) batcher = ImageBatcher( - args.input, *trt_infer.input_spec(), max_num_images=args.num_images, preprocessor=args.preprocessor + args.input, + *trt_infer.input_spec(), + max_num_images=args.num_images, + preprocessor=args.preprocessor ) # Make sure both systems use the same input spec, so we can use the exact same image batches with both @@ -101,14 +104,20 @@ def main(args): print("Input datatype does not match") print("TRT Engine Input Dtype: {} {}".format(trt_dtype)) print("TF Saved Model Input Dtype: {} {}".format(tf_dtype)) - print("Please use the same TensorFlow saved model that the TensorRT engine was built with") + print( + "Please use the same TensorFlow saved model that the TensorRT engine was built with" + ) sys.exit(1) - if (tf_shape[1] and trt_shape[1] != tf_shape[1]) or (tf_shape[2] and trt_shape[2] != tf_shape[2]): + if (tf_shape[1] and trt_shape[1] != tf_shape[1]) or ( + tf_shape[2] and trt_shape[2] != tf_shape[2] + ): print("Input shapes do not match") print("TRT Engine Input Shape: {} {}".format(trt_shape[1:])) print("TF Saved Model Input Shape: {} {}".format(tf_shape[1:])) - print("Please use the same TensorFlow saved model that the TensorRT engine was built with") + print( + "Please use the same TensorFlow saved model that the TensorRT engine was built with" + ) sys.exit(1) match = 0 @@ -131,24 +140,40 @@ def main(args): print( "Processing {} / {} images: {:.2f}% match ".format( - batcher.image_index, batcher.num_images, (100 * (match / batcher.image_index)) + batcher.image_index, + batcher.num_images, + (100 * (match / batcher.image_index)), ), end="\r", ) print() pc = 100 * (match / batcher.num_images) - print("Matching Top-1 class predictions for {} out of {} images: {:.2f}%".format(match, batcher.num_images, pc)) + print( + "Matching Top-1 class predictions for {} out of {} images: {:.2f}%".format( + match, batcher.num_images, pc + ) + ) avgerror = np.sqrt(error / batcher.num_images) - print("RMSE between TensorFlow and TensorRT confidence scores: {:.3f}".format(avgerror)) + print( + "RMSE between TensorFlow and TensorRT confidence scores: {:.3f}".format( + avgerror + ) + ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with") - parser.add_argument("-m", "--saved_model", help="The TensorFlow saved model path to validate against") parser.add_argument( - "-i", "--input", help="The input to infer, either a single image path, or a directory of images" + "-m", + "--saved_model", + help="The TensorFlow saved model path to validate against", + ) + parser.add_argument( + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images", ) parser.add_argument( "-n", diff --git a/samples/python/efficientnet/create_onnx.py b/samples/python/efficientnet/create_onnx.py index b98fd137..c0e7d109 100644 --- a/samples/python/efficientnet/create_onnx.py +++ b/samples/python/efficientnet/create_onnx.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -32,12 +32,18 @@ def main(args): # Load saved model saved_model_path = os.path.realpath(args.saved_model) assert os.path.isdir(saved_model_path) - graph_def, inputs, outputs = tf_loader.from_saved_model(saved_model_path, None, None, "serve", ["serving_default"]) + graph_def, inputs, outputs = tf_loader.from_saved_model( + saved_model_path, None, None, "serve", ["serving_default"] + ) with tf.Graph().as_default() as tf_graph: tf.import_graph_def(graph_def, name="") with tf_loader.tf_session(graph=tf_graph): - onnx_graph = tfonnx.process_tf_graph(tf_graph, input_names=inputs, output_names=outputs, opset=11) - onnx_model = optimizer.optimize_graph(onnx_graph).make_model("Converted from {}".format(saved_model_path)) + onnx_graph = tfonnx.process_tf_graph( + tf_graph, input_names=inputs, output_names=outputs, opset=11 + ) + onnx_model = optimizer.optimize_graph(onnx_graph).make_model( + "Converted from {}".format(saved_model_path) + ) graph = gs.import_onnx(onnx_model) assert graph print() @@ -55,11 +61,21 @@ def main(args): # Format NCHW graph.inputs[0].shape[2] = args.input_size graph.inputs[0].shape[3] = args.input_size - print("ONNX input named '{}' with shape {}".format(graph.inputs[0].name, graph.inputs[0].shape)) - print("ONNX output named '{}' with shape {}".format(graph.outputs[0].name, graph.outputs[0].shape)) + print( + "ONNX input named '{}' with shape {}".format( + graph.inputs[0].name, graph.inputs[0].shape + ) + ) + print( + "ONNX output named '{}' with shape {}".format( + graph.outputs[0].name, graph.outputs[0].shape + ) + ) for i in range(4): if type(graph.inputs[0].shape[i]) != int or graph.inputs[0].shape[i] <= 0: - print("The input shape of the graph is invalid, try overriding it by giving a fixed size with --input_size") + print( + "The input shape of the graph is invalid, try overriding it by giving a fixed size with --input_size" + ) sys.exit(1) # Fix Clip Nodes (ReLU6) @@ -85,9 +101,13 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-m", "--saved_model", help="The TensorFlow saved model directory to load") + parser.add_argument( + "-m", "--saved_model", help="The TensorFlow saved model directory to load" + ) parser.add_argument("-o", "--onnx", help="The output ONNX model file to write") - parser.add_argument("-b", "--batch_size", type=int, default=1, help="Set the batch size, default: 1") + parser.add_argument( + "-b", "--batch_size", type=int, default=1, help="Set the batch size, default: 1" + ) parser.add_argument( "-i", "--input_size", diff --git a/samples/python/efficientnet/eval_gt.py b/samples/python/efficientnet/eval_gt.py index 14d5a8d1..9f57aaa5 100644 --- a/samples/python/efficientnet/eval_gt.py +++ b/samples/python/efficientnet/eval_gt.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,17 +24,25 @@ from infer import TensorRTInfer from image_batcher import ImageBatcher + def main(args): annotations = {} for line in open(args.annotations, "r"): line = line.strip().split(args.separator) if len(line) < 2 or not line[1].isnumeric(): - print("Could not parse the annotations file correctly, make sure the correct separator is used") + print( + "Could not parse the annotations file correctly, make sure the correct separator is used" + ) sys.exit(1) annotations[os.path.basename(line[0])] = int(line[1]) trt_infer = TensorRTInfer(args.engine) - batcher = ImageBatcher(args.input, *trt_infer.input_spec(), max_num_images=args.num_images, preprocessor=args.preprocessor) + batcher = ImageBatcher( + args.input, + *trt_infer.input_spec(), + max_num_images=args.num_images, + preprocessor=args.preprocessor + ) top1 = 0 top5 = 0 total = 0 @@ -70,9 +78,15 @@ def main(args): parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with") parser.add_argument( - "-i", "--input", help="The input to infer, either a single image path, or a directory of images" + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images", + ) + parser.add_argument( + "-a", + "--annotations", + help="Set the file to use for classification ground truth annotations", ) - parser.add_argument("-a", "--annotations", help="Set the file to use for classification ground truth annotations") parser.add_argument( "-s", "--separator", diff --git a/samples/python/efficientnet/image_batcher.py b/samples/python/efficientnet/image_batcher.py index 996a72a3..63d37784 100644 --- a/samples/python/efficientnet/image_batcher.py +++ b/samples/python/efficientnet/image_batcher.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,7 +27,15 @@ class ImageBatcher: Creates batches of pre-processed images. """ - def __init__(self, input, shape, dtype, max_num_images=None, exact_batches=False, preprocessor="V2"): + def __init__( + self, + input, + shape, + dtype, + max_num_images=None, + exact_batches=False, + preprocessor="V2", + ): """ :param input: The input directory to read images from. :param shape: The tensor shape of the batch to prepare, either in NCHW or NHWC format. @@ -45,10 +53,16 @@ def __init__(self, input, shape, dtype, max_num_images=None, exact_batches=False extensions = [".jpg", ".jpeg", ".png", ".bmp"] def is_image(path): - return os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + return ( + os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + ) if os.path.isdir(input): - self.images = [os.path.join(input, f) for f in os.listdir(input) if is_image(os.path.join(input, f))] + self.images = [ + os.path.join(input, f) + for f in os.listdir(input) + if is_image(os.path.join(input, f)) + ] self.images.sort() elif os.path.isfile(input): if is_image(input): diff --git a/samples/python/efficientnet/infer.py b/samples/python/efficientnet/infer.py index 2c70b14e..cc18e1c8 100644 --- a/samples/python/efficientnet/infer.py +++ b/samples/python/efficientnet/infer.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -110,7 +110,9 @@ def infer(self, batch, top=1): output = np.zeros(*self.output_spec()) # Process I/O and execute the network - common.memcpy_host_to_device(self.inputs[0]["allocation"], np.ascontiguousarray(batch)) + common.memcpy_host_to_device( + self.inputs[0]["allocation"], np.ascontiguousarray(batch) + ) self.context.execute_v2(self.allocations) common.memcpy_device_to_host(output, self.outputs[0]["allocation"]) @@ -126,7 +128,9 @@ def infer(self, batch, top=1): def main(args): trt_infer = TensorRTInfer(args.engine) - batcher = ImageBatcher(args.input, *trt_infer.input_spec(), preprocessor=args.preprocessor) + batcher = ImageBatcher( + args.input, *trt_infer.input_spec(), preprocessor=args.preprocessor + ) for batch, images in batcher.get_batch(): classes, scores, top = trt_infer.infer(batch) for i in range(len(images)): @@ -146,10 +150,16 @@ def main(args): parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with") parser.add_argument( - "-i", "--input", help="The input to infer, either a single image path, or a directory of images" + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images", ) parser.add_argument( - "-t", "--top", default=1, type=int, help="The amount of top classes and scores to output per image, default: 1" + "-t", + "--top", + default=1, + type=int, + help="The amount of top classes and scores to output per image, default: 1", ) parser.add_argument( "-s", diff --git a/samples/python/engine_refit_onnx_bidaf/build_and_refit_engine.py b/samples/python/engine_refit_onnx_bidaf/build_and_refit_engine.py index 268a5cf5..240f1295 100644 --- a/samples/python/engine_refit_onnx_bidaf/build_and_refit_engine.py +++ b/samples/python/engine_refit_onnx_bidaf/build_and_refit_engine.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -20,25 +20,25 @@ import sys import numpy as np - +import argparse import tensorrt as trt -from data_processing import get_inputs, preprocess sys.path.insert(1, os.path.join(sys.path[0], "..")) -import common +from cuda import cudart TRT_LOGGER = trt.Logger() -def get_engine(onnx_file_path, engine_file_path): +def get_plan(onnx_file_path, engine_file_path, version_compatible): """Attempts to load a serialized engine if available, otherwise builds a new TensorRT engine and saves it.""" - def build_engine(): + def build_plan(): """Takes an ONNX file and creates a TensorRT engine to run inference with""" + import tensorrt as trt + builder = trt.Builder(TRT_LOGGER) - network = builder.create_network(common.EXPLICIT_BATCH) + network = builder.create_network(0) parser = trt.OnnxParser(network, TRT_LOGGER) - runtime = trt.Runtime(TRT_LOGGER) # Parse model file print("Loading ONNX file from path {}...".format(onnx_file_path)) @@ -59,8 +59,8 @@ def build_engine(): config = builder.create_builder_config() config.set_flag(trt.BuilderFlag.REFIT) - config.max_workspace_size = 1 << 28 # 256MiB - + if version_compatible: + config.set_flag(trt.BuilderFlag.VERSION_COMPATIBLE) for opt in [6, 10]: profile = builder.create_optimization_profile() @@ -68,47 +68,119 @@ def build_engine(): input0_min = (1, 1) input0_opt = (opt, 1) input0_max = (15, 1) - profile.set_shape(network.get_input(0).name, min=input0_min, opt=input0_opt, max=input0_max) + profile.set_shape( + network.get_input(0).name, + min=input0_min, + opt=input0_opt, + max=input0_max, + ) input1_min = (1, 1, 1, 16) input1_opt = (opt, 1, 1, 16) input1_max = (15, 1, 1, 16) - profile.set_shape(network.get_input(1).name, min=input1_min, opt=input1_opt, max=input1_max) + profile.set_shape( + network.get_input(1).name, + min=input1_min, + opt=input1_opt, + max=input1_max, + ) input2_min = (1, 1) input2_opt = (opt, 1) input2_max = (15, 1) - profile.set_shape(network.get_input(2).name, min=input2_min, opt=input2_opt, max=input2_max) + profile.set_shape( + network.get_input(2).name, + min=input2_min, + opt=input2_opt, + max=input2_max, + ) input3_min = (1, 1, 1, 16) input3_opt = (opt, 1, 1, 16) input3_max = (15, 1, 1, 16) - profile.set_shape(network.get_input(3).name, min=input3_min, opt=input3_opt, max=input3_max) + profile.set_shape( + network.get_input(3).name, + min=input3_min, + opt=input3_opt, + max=input3_max, + ) config.add_optimization_profile(profile) - print("Building an engine from file {}; this may take a while...".format(onnx_file_path)) + print( + "Building an engine from file {}; this may take a while...".format( + onnx_file_path + ) + ) plan = builder.build_serialized_network(network, config) - engine = runtime.deserialize_cuda_engine(plan) print("Completed creating Engine") with open(engine_file_path, "wb") as f: f.write(plan) - return engine + return plan if os.path.exists(engine_file_path): # If a serialized engine exists, use it instead of building an engine. - print("Reading engine from file {}".format(engine_file_path)) - with open(engine_file_path, "rb") as f: - runtime = trt.Runtime(TRT_LOGGER) - return runtime.deserialize_cuda_engine(f.read()) - else: - return build_engine() + print("Reading engine from file {}...".format(engine_file_path)) + f = open(engine_file_path, "rb") + return f.read() + return build_plan() def main(): + global trt + global TRT_LOGGER + + parser = argparse.ArgumentParser() + parser.add_argument( + "-l", + "--weights-location", + dest="weights_location", + default="GPU", + choices=["GPU", "CPU"], + help="The location for weights passed to refitter, either GPU/CPU, default: GPU", + ) + parser.add_argument( + "--version-compatible", + dest="version_compatible", + action="store_true", + help="Build a version compatible engine for refitting", + ) + args = parser.parse_args() + onnx_file_path = "bidaf-modified.onnx" - engine_file_path = "bidaf.trt" + engine_file_path = "bidaf{}.trt".format("-vc" if args.version_compatible else "") + + plan = get_plan(onnx_file_path, engine_file_path, args.version_compatible) + + if args.version_compatible: + # Try using dispatch runtime for refitting and inference. If failed, fallback to full runtime. + try: + del sys.modules["tensorrt"] + sys.modules["tensorrt"] = __import__("tensorrt_dispatch") + sys.modules["trt"] = sys.modules["tensorrt"] + import tensorrt_dispatch as trt + + print( + "Importing tensorrt_dispatch instead of full tensorrt for refitting and running vc engines." + ) + except: + print( + "Failed to import tensorrt_dispatch for refitting and running vc engines. Please install the package first!" + ) + sys.modules["tensorrt"] = __import__("tensorrt") + TRT_LOGGER = trt.Logger() + + engine = None + with open(engine_file_path, "rb") as f: + runtime = trt.Runtime(TRT_LOGGER) + if args.version_compatible: + runtime.engine_host_code_allowed = True + engine = runtime.deserialize_cuda_engine(plan) + + # should be after get_engine + from data_processing import get_inputs, preprocess + import common_runtime as common # input context = "A quick brown fox jumps over the lazy dog." @@ -119,50 +191,93 @@ def main(): # Do inference with TensorRT weights_names = ["Parameter576_B_0", "W_0"] - refit_weights_dict = {name: np.load("{}.npy".format(name)) for name in weights_names} - fake_weights_dict = {name: np.ones_like(weights) for name, weights in refit_weights_dict.items()} - engine = get_engine(onnx_file_path, engine_file_path) + refit_weights_dict = { + name: np.load("{}.npy".format(name)) for name in weights_names + } + fake_weights_dict = { + name: np.ones_like(weights) for name, weights in refit_weights_dict.items() + } + device_mem_dict = {} + if args.weights_location == "GPU": + for name, weights in refit_weights_dict.items(): + nbytes = weights.size * weights.itemsize + device_mem_dict[name] = common.cuda_call(cudart.cudaMalloc(nbytes)) + + execution_context = engine.create_execution_context() refitter = trt.Refitter(engine, TRT_LOGGER) - for weights_dict, answer_correct in [(fake_weights_dict, False), (refit_weights_dict, True)]: - print("Refitting engine...") - # To get a list of all refittable weights' names - # in the network, use refitter.get_all_weights(). - + # Skip weights validation since we are confident that the new weights are similar to the weights used to build engine. + refitter.weights_validation = False + # To get a list of all refittable weights' names + # in the network, use refitter.get_all_weights(). + + if args.weights_location == "GPU": + for name, device_mem in device_mem_dict.items(): + device_weights = trt.Weights( + trt.DataType.FLOAT, device_mem, refit_weights_dict[name].size + ) + weights_prototype = refitter.get_weights_prototype(name) + assert device_weights.dtype == weights_prototype.dtype + assert device_weights.size == weights_prototype.size + refitter.set_named_weights(name, device_weights, trt.TensorLocation.DEVICE) + + for weights_dict, answer_correct in [ + (fake_weights_dict, False), + (refit_weights_dict, True), + ]: + import time + + T1 = time.perf_counter() + device_mem_list = [] # Refit named weights via set_named_weights for name in weights_names: - refitter.set_named_weights(name, weights_dict[name]) - - # Get missing weights names. This should return empty - # lists in this case. + host_weights = weights_dict[name] + if args.weights_location == "CPU": + weights = host_weights + location = trt.TensorLocation.HOST + refitter.set_named_weights(name, weights, location) + else: + common.memcpy_host_to_device(device_mem_dict[name], host_weights) + + # Get missing weights names. This should return empty lists in this case. missing_weights = refitter.get_missing_weights() assert ( len(missing_weights) == 0 ), "Refitter found missing weights. Call set_named_weights() or set_weights() for all missing weights" - # Refit the engine with the new weights. This will return True if - # the refit operation succeeded. + + print(f"Refitting engine from {args.weights_location} weights...") + # Refit the engine with the new weights. This will return True if the refit operation succeeded. assert refitter.refit_cuda_engine() + T2 = time.perf_counter() + print("Engine refitted in {:.2f} ms.".format((T2 - T1) * 1000)) + for profile_idx in range(engine.num_optimization_profiles): print("Doing inference...") # Do inference - inputs, outputs, bindings, stream = common.allocate_buffers(engine, profile_idx) + inputs, outputs, bindings, stream = common.allocate_buffers( + engine, profile_idx + ) padding_bindings = [0] * (len(bindings) * profile_idx) new_bindings = padding_bindings + bindings - # Set host input. The common.do_inference_v2 function will copy the input to the GPU before executing. + # Set host input. The common.do_inference function will copy the input to the GPU before executing. inputs[0].host = cw inputs[1].host = cc inputs[2].host = qw inputs[3].host = qc - execution_context = engine.create_execution_context() execution_context.set_optimization_profile_async(profile_idx, stream) execution_context.set_input_shape("CategoryMapper_4", (10, 1)) execution_context.set_input_shape("CategoryMapper_5", (10, 1, 1, 16)) execution_context.set_input_shape("CategoryMapper_6", (6, 1)) execution_context.set_input_shape("CategoryMapper_7", (6, 1, 1, 16)) - trt_outputs = common.do_inference_v2( - execution_context, bindings=new_bindings, inputs=inputs, outputs=outputs, stream=stream + trt_outputs = common.do_inference( + execution_context, + engine=engine, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream, ) start = trt_outputs[0].item() @@ -170,6 +285,10 @@ def main(): answer = [w.encode() for w in cw_str[start : end + 1].reshape(-1)] assert answer_correct == (answer == [b"brown"]), answer common.free_buffers(inputs, outputs, stream) + + for _, device_mem in device_mem_dict.items(): + common.cuda_call(cudart.cudaFree(device_mem)) + print("Passed") diff --git a/samples/python/engine_refit_onnx_bidaf/data_processing.py b/samples/python/engine_refit_onnx_bidaf/data_processing.py index 6eb90fa0..f6740bc5 100644 --- a/samples/python/engine_refit_onnx_bidaf/data_processing.py +++ b/samples/python/engine_refit_onnx_bidaf/data_processing.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -40,7 +40,9 @@ def preprocess(text): def get_map_func(filepath): file = open(filepath) category_map = json.load(file) - category_mapper = dict(zip(category_map["cats_strings"], category_map["cats_int64s"])) + category_mapper = dict( + zip(category_map["cats_strings"], category_map["cats_int64s"]) + ) default_int64 = category_map["default_int64"] func = lambda s: category_mapper.get(s, default_int64) return np.vectorize(func) diff --git a/samples/python/engine_refit_onnx_bidaf/prepare_model.py b/samples/python/engine_refit_onnx_bidaf/prepare_model.py index cbeb6a92..eb45226e 100644 --- a/samples/python/engine_refit_onnx_bidaf/prepare_model.py +++ b/samples/python/engine_refit_onnx_bidaf/prepare_model.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -82,7 +82,9 @@ def save_weights_for_refitting(graph): def main(): - org_model_file_path = getFilePath("samples/python/engine_refit_onnx_bidaf/bidaf-original.onnx") + org_model_file_path = getFilePath( + "samples/python/engine_refit_onnx_bidaf/bidaf-original.onnx" + ) print("Modifying the ONNX model ...") original_model = onnx.load(org_model_file_path) diff --git a/samples/python/introductory_parser_samples/onnx_resnet50.py b/samples/python/introductory_parser_samples/onnx_resnet50.py index f07e99ff..fd69cc48 100644 --- a/samples/python/introductory_parser_samples/onnx_resnet50.py +++ b/samples/python/introductory_parser_samples/onnx_resnet50.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -40,6 +40,7 @@ class ModelData(object): # You can set the logger severity higher to suppress messages (or lower to display more messages). TRT_LOGGER = trt.Logger(trt.Logger.WARNING) + # The Onnx path is used for Onnx models. def build_engine_onnx(model_file): builder = trt.Builder(TRT_LOGGER) @@ -111,7 +112,14 @@ def main(): test_case = load_normalized_test_case(test_image, inputs[0].host) # Run the engine. The output will be a 1D tensor of length 1000, where each value represents the # probability that the image corresponds to that label - trt_outputs = common.do_inference(context, engine=engine, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) + trt_outputs = common.do_inference( + context, + engine=engine, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream, + ) # We use the highest probability as our prediction. Its index corresponds to the predicted label. pred = labels[np.argmax(trt_outputs[0])] common.free_buffers(inputs, outputs, stream) diff --git a/samples/python/network_api_pytorch_mnist/model.py b/samples/python/network_api_pytorch_mnist/model.py index 3f1a4fe4..53371989 100644 --- a/samples/python/network_api_pytorch_mnist/model.py +++ b/samples/python/network_api_pytorch_mnist/model.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -59,7 +59,9 @@ def __init__(self): "/tmp/mnist/data", train=True, download=True, - transform=transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]), + transform=transforms.Compose( + [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))] + ), ), batch_size=self.batch_size, shuffle=True, @@ -70,7 +72,9 @@ def __init__(self): datasets.MNIST( "/tmp/mnist/data", train=False, - transform=transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]), + transform=transforms.Compose( + [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))] + ), ), batch_size=self.test_batch_size, shuffle=True, @@ -86,7 +90,11 @@ def learn(self, num_epochs=2): # Train the network for a single epoch def train(epoch): self.network.train() - optimizer = optim.SGD(self.network.parameters(), lr=self.learning_rate, momentum=self.sgd_momentum) + optimizer = optim.SGD( + self.network.parameters(), + lr=self.learning_rate, + momentum=self.sgd_momentum, + ) for batch, (data, target) in enumerate(self.train_loader): if torch.cuda.is_available(): data = data.to("cuda") @@ -126,7 +134,10 @@ def test(epoch): test_loss /= len(self.test_loader) print( "\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n".format( - test_loss, correct, len(self.test_loader.dataset), 100.0 * correct / len(self.test_loader.dataset) + test_loss, + correct, + len(self.test_loader.dataset), + 100.0 * correct / len(self.test_loader.dataset), ) ) diff --git a/samples/python/network_api_pytorch_mnist/sample.py b/samples/python/network_api_pytorch_mnist/sample.py index 1f634443..a695ee9a 100644 --- a/samples/python/network_api_pytorch_mnist/sample.py +++ b/samples/python/network_api_pytorch_mnist/sample.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -41,7 +41,9 @@ class ModelData(object): def populate_network(network, weights): # Configure the network layers based on the weights provided. - input_tensor = network.add_input(name=ModelData.INPUT_NAME, dtype=ModelData.DTYPE, shape=ModelData.INPUT_SHAPE) + input_tensor = network.add_input( + name=ModelData.INPUT_NAME, dtype=ModelData.DTYPE, shape=ModelData.INPUT_SHAPE + ) def add_matmul_as_fc(net, input, outputs, w, b): assert len(input.shape) >= 3 @@ -64,7 +66,9 @@ def add_matmul_as_fc(net, input, outputs, w, b): ) bias_const = net.add_constant(trt.Dims2(1, n), b) - bias_add = net.add_elementwise(mm.get_output(0), bias_const.get_output(0), trt.ElementWiseOperation.SUM) + bias_add = net.add_elementwise( + mm.get_output(0), bias_const.get_output(0), trt.ElementWiseOperation.SUM + ) output_reshape = net.add_shuffle(bias_add.get_output(0)) output_reshape.reshape_dims = trt.Dims4(m, n, 1, 1) @@ -73,16 +77,24 @@ def add_matmul_as_fc(net, input, outputs, w, b): conv1_w = weights["conv1.weight"].cpu().numpy() conv1_b = weights["conv1.bias"].cpu().numpy() conv1 = network.add_convolution_nd( - input=input_tensor, num_output_maps=20, kernel_shape=(5, 5), kernel=conv1_w, bias=conv1_b + input=input_tensor, + num_output_maps=20, + kernel_shape=(5, 5), + kernel=conv1_w, + bias=conv1_b, ) conv1.stride_nd = (1, 1) - pool1 = network.add_pooling_nd(input=conv1.get_output(0), type=trt.PoolingType.MAX, window_size=(2, 2)) + pool1 = network.add_pooling_nd( + input=conv1.get_output(0), type=trt.PoolingType.MAX, window_size=(2, 2) + ) pool1.stride_nd = trt.Dims2(2, 2) conv2_w = weights["conv2.weight"].cpu().numpy() conv2_b = weights["conv2.bias"].cpu().numpy() - conv2 = network.add_convolution_nd(pool1.get_output(0), 50, (5, 5), conv2_w, conv2_b) + conv2 = network.add_convolution_nd( + pool1.get_output(0), 50, (5, 5), conv2_w, conv2_b + ) conv2.stride_nd = (1, 1) pool2 = network.add_pooling_nd(conv2.get_output(0), trt.PoolingType.MAX, (2, 2)) @@ -92,11 +104,15 @@ def add_matmul_as_fc(net, input, outputs, w, b): fc1_b = weights["fc1.bias"].cpu().numpy() fc1 = add_matmul_as_fc(network, pool2.get_output(0), 500, fc1_w, fc1_b) - relu1 = network.add_activation(input=fc1.get_output(0), type=trt.ActivationType.RELU) + relu1 = network.add_activation( + input=fc1.get_output(0), type=trt.ActivationType.RELU + ) fc2_w = weights["fc2.weight"].cpu().numpy() fc2_b = weights["fc2.bias"].cpu().numpy() - fc2 = add_matmul_as_fc(network, relu1.get_output(0), ModelData.OUTPUT_SIZE, fc2_w, fc2_b) + fc2 = add_matmul_as_fc( + network, relu1.get_output(0), ModelData.OUTPUT_SIZE, fc2_w, fc2_b + ) fc2.get_output(0).name = ModelData.OUTPUT_NAME network.mark_output(tensor=fc2.get_output(0)) @@ -143,7 +159,14 @@ def main(): case_num = load_random_test_case(mnist_model, pagelocked_buffer=inputs[0].host) # For more information on performing inference, refer to the introductory samples. # The common.do_inference function will return a list of outputs - we only have one in this case. - [output] = common.do_inference(context, engine=engine, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) + [output] = common.do_inference( + context, + engine=engine, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream, + ) pred = np.argmax(output) common.free_buffers(inputs, outputs, stream) print("Test Case: " + str(case_num)) diff --git a/samples/python/onnx_custom_plugin/CMakeLists.txt b/samples/python/onnx_custom_plugin/CMakeLists.txt index 75f69af4..f00bcd31 100644 --- a/samples/python/onnx_custom_plugin/CMakeLists.txt +++ b/samples/python/onnx_custom_plugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/python/onnx_custom_plugin/load_plugin_lib.py b/samples/python/onnx_custom_plugin/load_plugin_lib.py index 0a85f18e..a3feaa37 100644 --- a/samples/python/onnx_custom_plugin/load_plugin_lib.py +++ b/samples/python/onnx_custom_plugin/load_plugin_lib.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -18,7 +18,9 @@ import os import ctypes -WORKING_DIR = os.environ.get("TRT_WORKING_DIR") or os.path.dirname(os.path.realpath(__file__)) +WORKING_DIR = os.environ.get("TRT_WORKING_DIR") or os.path.dirname( + os.path.realpath(__file__) +) IS_WINDOWS = os.name == "nt" if IS_WINDOWS: HARDMAX_PLUGIN_LIBRARY_NAME = "customHardmaxPlugin.dll" @@ -28,7 +30,10 @@ ] else: HARDMAX_PLUGIN_LIBRARY_NAME = "libcustomHardmaxPlugin.so" - HARDMAX_PLUGIN_LIBRARY = [os.path.join(WORKING_DIR, "build", HARDMAX_PLUGIN_LIBRARY_NAME)] + HARDMAX_PLUGIN_LIBRARY = [ + os.path.join(WORKING_DIR, "build", HARDMAX_PLUGIN_LIBRARY_NAME) + ] + def load_plugin_lib(): for plugin_lib in HARDMAX_PLUGIN_LIBRARY: diff --git a/samples/python/onnx_custom_plugin/model.py b/samples/python/onnx_custom_plugin/model.py index cde029c5..53b2a96e 100644 --- a/samples/python/onnx_custom_plugin/model.py +++ b/samples/python/onnx_custom_plugin/model.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,18 +24,21 @@ MODEL_URL = "https://github.com/onnx/models/raw/e77240a62df68ed13e3138a5812553a552b857bb/text/machine_comprehension/bidirectional_attention_flow/model/bidaf-9.onnx" -WORKING_DIR = os.environ.get("TRT_WORKING_DIR") or os.path.dirname(os.path.realpath(__file__)) -MODEL_DIR = os.path.join(WORKING_DIR, "models") +WORKING_DIR = os.environ.get("TRT_WORKING_DIR") or os.path.dirname( + os.path.realpath(__file__) +) +MODEL_DIR = os.path.join(WORKING_DIR, "models") RAW_MODEL_PATH = os.path.join(MODEL_DIR, "bidaf-9.onnx") TRT_MODEL_PATH = os.path.join(MODEL_DIR, "bidaf-9-trt.onnx") + def _do_graph_surgery(raw_model_path, trt_model_path): graph = gs.import_onnx(onnx.load(raw_model_path)) # Replace unsupported Hardmax with our CustomHardmax op for node in graph.nodes: - if node.op == 'Hardmax': - node.op = 'CustomHardmax' + if node.op == "Hardmax": + node.op = "CustomHardmax" hardmax_node = node # The original onnx model also uses another unsupported op called "Compress". @@ -47,16 +50,16 @@ def _do_graph_surgery(raw_model_path, trt_model_path): # # So, we will replace the subgraph Compress(Transpose_29, Cast(Reshape(Hardmax))) # with the subgraph Einsum(Transpose_29, Hardmax) where the equation in Einsum takes the dot product. - node_by_name = {node.name : node for node in graph.nodes} - transpose_node = node_by_name['Transpose_29'] - compress_node = node_by_name['Compress_31'] + node_by_name = {node.name: node for node in graph.nodes} + transpose_node = node_by_name["Transpose_29"] + compress_node = node_by_name["Compress_31"] einsum_node = gs.Node( - 'Einsum', - 'Dot_of_Hardmax_and_Transpose', - attrs={'equation': 'ij,ij->i'}, # "Dot product" of 2d tensors + "Einsum", + "Dot_of_Hardmax_and_Transpose", + attrs={"equation": "ij,ij->i"}, # "Dot product" of 2d tensors inputs=[hardmax_node.outputs[0], transpose_node.outputs[0]], - outputs=[compress_node.outputs[0]] + outputs=[compress_node.outputs[0]], ) graph.nodes.append(einsum_node) @@ -80,7 +83,9 @@ def _do_graph_surgery(raw_model_path, trt_model_path): # # Later we will feed the model the integer tokens directly. # Note: list conversion is necessary because we modify graph.nodes in the for loop. - category_mapper_nodes = [node for node in graph.nodes if node.op == 'CategoryMapper'] + category_mapper_nodes = [ + node for node in graph.nodes if node.op == "CategoryMapper" + ] for node in category_mapper_nodes: # Remove CategoryMapper node from onnx graph graph.nodes.remove(node) diff --git a/samples/python/onnx_custom_plugin/sample.py b/samples/python/onnx_custom_plugin/sample.py index 7026f0e5..25d4ca36 100644 --- a/samples/python/onnx_custom_plugin/sample.py +++ b/samples/python/onnx_custom_plugin/sample.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -30,7 +30,7 @@ # Reuse some BiDAF-specific methods # ../engine_refit_onnx_bidaf/data_processing.py -sys.path.insert(1, os.path.join(parent_dir, 'engine_refit_onnx_bidaf')) +sys.path.insert(1, os.path.join(parent_dir, "engine_refit_onnx_bidaf")) from engine_refit_onnx_bidaf.data_processing import preprocess, get_inputs # Maxmimum number of words in context or query text. @@ -38,10 +38,12 @@ # Adjustable. MAX_TEXT_LENGTH = 64 -WORKING_DIR = os.environ.get("TRT_WORKING_DIR") or os.path.dirname(os.path.realpath(__file__)) +WORKING_DIR = os.environ.get("TRT_WORKING_DIR") or os.path.dirname( + os.path.realpath(__file__) +) # Path to which trained model will be saved (check README.md) -ENGINE_FILE_PATH = os.path.join(WORKING_DIR, 'bidaf.trt') +ENGINE_FILE_PATH = os.path.join(WORKING_DIR, "bidaf.trt") # Define global logger object (it should be a singleton, # available for TensorRT from anywhere in code). @@ -49,13 +51,16 @@ # (or lower to display more messages) TRT_LOGGER = trt.Logger(trt.Logger.WARNING) + # Builds TensorRT Engine def build_engine(model_path): builder = trt.Builder(TRT_LOGGER) network = builder.create_network(0) config = builder.create_builder_config() - config.set_tactic_sources(config.get_tactic_sources() | 1 << int(trt.TacticSource.CUBLAS)) + config.set_tactic_sources( + config.get_tactic_sources() | 1 << int(trt.TacticSource.CUBLAS) + ) parser = trt.OnnxParser(network, TRT_LOGGER) runtime = trt.Runtime(TRT_LOGGER) @@ -90,17 +95,20 @@ def build_engine(model_path): f.write(plan) return engine + def load_test_case(inputs, context_text, query_text, trt_context): # Part 1: Specify Input shapes cw, cc = preprocess(context_text) qw, qc = preprocess(query_text) for arr in (cw, cc, qw, qc): - assert arr.shape[0] <= MAX_TEXT_LENGTH, "Input context or query is too long! " + \ - "Either decrease the input length or increase MAX_TEXT_LENGTH" - trt_context.set_input_shape('CategoryMapper_4', cw.shape) - trt_context.set_input_shape('CategoryMapper_5', cc.shape) - trt_context.set_input_shape('CategoryMapper_6', qw.shape) - trt_context.set_input_shape('CategoryMapper_7', qc.shape) + assert arr.shape[0] <= MAX_TEXT_LENGTH, ( + "Input context or query is too long! " + + "Either decrease the input length or increase MAX_TEXT_LENGTH" + ) + trt_context.set_input_shape("CategoryMapper_4", cw.shape) + trt_context.set_input_shape("CategoryMapper_5", cc.shape) + trt_context.set_input_shape("CategoryMapper_6", qw.shape) + trt_context.set_input_shape("CategoryMapper_7", qc.shape) # Part 2: load input data cw_flat, cc_flat, qw_flat, qc_flat = get_inputs(context_text, query_text) @@ -138,20 +146,23 @@ def main(): inputs, outputs, bindings, stream = common.allocate_buffers(engine, profile_idx=0) testcases = [ - ('Garry the lion is 5 years old. He lives in the savanna.', 'Where does the lion live?'), - ('A quick brown fox jumps over the lazy dog.', 'What color is the fox?') + ( + "Garry the lion is 5 years old. He lives in the savanna.", + "Where does the lion live?", + ), + ("A quick brown fox jumps over the lazy dog.", "What color is the fox?"), ] print("\n=== Testing ===") - interactive = '--interactive' in sys.argv + interactive = "--interactive" in sys.argv if interactive: context_text = input("Enter context: ") query_text = input("Enter query: ") testcases = [(context_text, query_text)] trt_context = engine.create_execution_context() - for (context_text, query_text) in testcases: + for context_text, query_text in testcases: context_words, _ = preprocess(context_text) @@ -159,7 +170,14 @@ def main(): if not interactive: print(f"Input context: {context_text}") print(f"Input query: {query_text}") - trt_outputs = common.do_inference(trt_context, engine=engine, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) + trt_outputs = common.do_inference( + trt_context, + engine=engine, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream, + ) start = trt_outputs[1].item() end = trt_outputs[0].item() answer = context_words[start : end + 1].flatten() @@ -168,5 +186,6 @@ def main(): common.free_buffers(inputs, outputs, stream) print("Passed") + if __name__ == "__main__": main() diff --git a/samples/python/onnx_custom_plugin/test_custom_hardmax_plugin.py b/samples/python/onnx_custom_plugin/test_custom_hardmax_plugin.py index c99b78d1..59b08b06 100644 --- a/samples/python/onnx_custom_plugin/test_custom_hardmax_plugin.py +++ b/samples/python/onnx_custom_plugin/test_custom_hardmax_plugin.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -29,27 +29,35 @@ TRT_LOGGER = trt.Logger(trt.Logger.ERROR) + def hardmax_reference_impl(arr, axis): one_hot = np.zeros(arr.shape, dtype=arr.dtype) argmax = np.expand_dims(np.argmax(arr, axis), axis) - np.put_along_axis(one_hot,argmax,1,axis=axis) + np.put_along_axis(one_hot, argmax, 1, axis=axis) return one_hot + def make_trt_network_and_engine(input_shape, axis): registry = trt.get_plugin_registry() plugin_creator = registry.get_plugin_creator("CustomHardmax", "1") axis_buffer = np.array([axis]) axis_attr = trt.PluginField("axis", axis_buffer, type=trt.PluginFieldType.INT32) field_collection = trt.PluginFieldCollection([axis_attr]) - plugin = plugin_creator.create_plugin(name="CustomHardmax", field_collection=field_collection) + plugin = plugin_creator.create_plugin( + name="CustomHardmax", field_collection=field_collection + ) builder = trt.Builder(TRT_LOGGER) network = builder.create_network(0) config = builder.create_builder_config() - config.set_tactic_sources(config.get_tactic_sources() | 1 << int(trt.TacticSource.CUBLAS)) + config.set_tactic_sources( + config.get_tactic_sources() | 1 << int(trt.TacticSource.CUBLAS) + ) runtime = trt.Runtime(TRT_LOGGER) - input_layer = network.add_input(name="input_layer", dtype=trt.float32, shape=input_shape) + input_layer = network.add_input( + name="input_layer", dtype=trt.float32, shape=input_shape + ) hardmax = network.add_plugin_v2(inputs=[input_layer], plugin=plugin) network.mark_output(hardmax.get_output(0)) @@ -58,15 +66,24 @@ def make_trt_network_and_engine(input_shape, axis): return engine + def custom_plugin_impl(input_arr, engine): inputs, outputs, bindings, stream = common.allocate_buffers(engine) context = engine.create_execution_context() inputs[0].host = input_arr.astype(trt.nptype(trt.float32)) - trt_outputs = common.do_inference(context, engine=engine, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) + trt_outputs = common.do_inference( + context, + engine=engine, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream, + ) output = trt_outputs[0].copy() common.free_buffers(inputs, outputs, stream) return output + def main(): load_plugin_lib() for num_dims in range(1, 8): @@ -80,5 +97,6 @@ def main(): assert np.all(res1 == res2), f"Test failed for shape={shape}, axis={axis}" print("Passed") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/samples/python/onnx_packnet/convert_to_onnx.py b/samples/python/onnx_packnet/convert_to_onnx.py index df604f96..72c31b72 100644 --- a/samples/python/onnx_packnet/convert_to_onnx.py +++ b/samples/python/onnx_packnet/convert_to_onnx.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -63,17 +63,29 @@ def build_packnet(model_file, args): model_pyt = PackNet01(version="1A") # Convert the model into ONNX - torch.onnx.export(model_pyt, input_pyt, model_file, verbose=args.verbose, opset_version=args.opset) + torch.onnx.export( + model_pyt, input_pyt, model_file, verbose=args.verbose, opset_version=args.opset + ) def main(): parser = argparse.ArgumentParser( description="Exports PackNet01 to ONNX, and post-processes it to insert TensorRT plugins" ) - parser.add_argument("-o", "--output", help="Path to save the generated ONNX model", default="model.onnx") - parser.add_argument("-op", "--opset", type=int, help="ONNX opset to use", default=11) parser.add_argument( - "-v", "--verbose", action="store_true", help="Flag to enable verbose logging for torch.onnx.export" + "-o", + "--output", + help="Path to save the generated ONNX model", + default="model.onnx", + ) + parser.add_argument( + "-op", "--opset", type=int, help="ONNX opset to use", default=11 + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="Flag to enable verbose logging for torch.onnx.export", ) args = parser.parse_args() diff --git a/samples/python/onnx_packnet/post_processing.py b/samples/python/onnx_packnet/post_processing.py index 887834c7..33adcf1d 100644 --- a/samples/python/onnx_packnet/post_processing.py +++ b/samples/python/onnx_packnet/post_processing.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -22,6 +22,7 @@ import numpy as np import torch + # Pad layer subgraph structure in ONNX (specific to opset 11): # Constant # | @@ -65,7 +66,9 @@ def process_pad_nodes(graph): def fold_pad_inputs(node, graph): # Gather the amount of padding in each dimension from pytorch graph. if torch.__version__ < "1.5.0": - pad_values_pyt = node.i(1).i(0).i(0).i(0).i(0).i(0).i(0).i(0).attrs["value"].values + pad_values_pyt = ( + node.i(1).i(0).i(0).i(0).i(0).i(0).i(0).i(0).attrs["value"].values + ) elif torch.__version__ < "2.0.0": pad_values_pyt = node.i(1).i(0).i(0).i(0).i(0).i(0).inputs[0].values else: @@ -80,7 +83,9 @@ def fold_pad_inputs(node, graph): j -= 1 # Change the existing pad tensor to the new onnx_pad values tensor - pads_folded_tensor = gs.Constant(name=node.inputs[1].name, values=np.array(onnx_pad_values)) + pads_folded_tensor = gs.Constant( + name=node.inputs[1].name, values=np.array(onnx_pad_values) + ) node.inputs[1] = pads_folded_tensor @@ -134,7 +139,9 @@ def fold_upsample_inputs(upsample, graph, opset=11): if opset == 9: # Gather the scale factor from mul op in the upsample input subgraph - scale_factor = upsample.i(1).i(1).i(0).i(0).i(0).i(0).i(0).i(0).i(1).attrs["value"].values + scale_factor = ( + upsample.i(1).i(1).i(0).i(0).i(0).i(0).i(0).i(0).i(1).attrs["value"].values + ) # Create the new scales tensor scales = np.array([1.0, 1.0, scale_factor, scale_factor], dtype=np.float32) @@ -148,7 +155,9 @@ def fold_upsample_inputs(upsample, graph, opset=11): sizes_tensor_name = upsample.inputs[3].name # Create the new scales tensor - scale_factor = upsample.i(3).i(1).i().i().i().i().i(0).i(1).attrs["value"].values + scale_factor = ( + upsample.i(3).i(1).i().i().i().i().i(0).i(1).attrs["value"].values + ) scales = np.array([1.0, 1.0, scale_factor, scale_factor], dtype=np.float32) scale_tensor = gs.Constant(name=sizes_tensor_name, values=scales) diff --git a/samples/python/python_plugin/CMakeLists.txt b/samples/python/python_plugin/CMakeLists.txt index 3b8fc1f3..6338ea50 100644 --- a/samples/python/python_plugin/CMakeLists.txt +++ b/samples/python/python_plugin/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/python/python_plugin/circ_pad_plugin_cpp.py b/samples/python/python_plugin/circ_pad_plugin_cpp.py index a820399f..a7cb8d2f 100644 --- a/samples/python/python_plugin/circ_pad_plugin_cpp.py +++ b/samples/python/python_plugin/circ_pad_plugin_cpp.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -29,14 +29,29 @@ TrtRunner, ) + def parseArgs(): - parser = argparse.ArgumentParser(description="Options for Circular Padding plugin C++ example") + parser = argparse.ArgumentParser( + description="Options for Circular Padding plugin C++ example" + ) - parser.add_argument('--precision', type=str, default="fp32", choices=["fp32", "fp16"], help="Precision to use for plugin") - parser.add_argument('--plugin-lib', type=str, help="Path to the Circular Padding plugin lib", required=True) + parser.add_argument( + "--precision", + type=str, + default="fp32", + choices=["fp32", "fp16"], + help="Precision to use for plugin", + ) + parser.add_argument( + "--plugin-lib", + type=str, + help="Path to the Circular Padding plugin lib", + required=True, + ) return parser.parse_args() + if __name__ == "__main__": args = parseArgs() @@ -67,15 +82,15 @@ def parseArgs(): # build engine build_engine = EngineFromNetwork( - NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision==np.float16) + NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision == np.float16) ) Y_ref = np.pad(X, [[0, 0], [0, 0], [pads[0], pads[1]], [pads[2], pads[3]]], "wrap") # Run - with TrtRunner(build_engine, "trt_runner")as runner: + with TrtRunner(build_engine, "trt_runner") as runner: outputs = runner.infer({"X": X}) Y = outputs["Y"] - + if np.allclose(Y, Y_ref): print("Inference result correct!") else: diff --git a/samples/python/python_plugin/circ_pad_plugin_cuda_python.py b/samples/python/python_plugin/circ_pad_plugin_cuda_python.py index 88ad1ff7..212e3e74 100644 --- a/samples/python/python_plugin/circ_pad_plugin_cuda_python.py +++ b/samples/python/python_plugin/circ_pad_plugin_cuda_python.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,14 +24,14 @@ CreateConfig, EngineFromNetwork, NetworkFromOnnxPath, - TrtRunner + TrtRunner, ) from polygraphy.json import to_json, from_json from utils import checkCudaErrors, KernelHelper, parseArgs, CudaCtxManager from cuda import cuda -circ_pad_half_kernel = r''' +circ_pad_half_kernel = r""" #include extern "C" __global__ void circ_pad_half(half const* X, int const* all_pads, int const* orig_dims, half* Y, int const* Y_shape, int Y_len) { @@ -58,9 +58,9 @@ ]; } } -''' +""" -circ_pad_float_kernel = r''' +circ_pad_float_kernel = r""" extern "C" __global__ void circ_pad_float(float const* X, int const* all_pads, int const* orig_dims, float* Y, int const* Y_shape, int Y_len) { int index = blockIdx.x * blockDim.x + threadIdx.x; @@ -86,7 +86,8 @@ ]; } } -''' +""" + class CircPadPlugin(trt.IPluginV2DynamicExt): def __init__(self, fc=None): @@ -107,7 +108,9 @@ def __init__(self, fc=None): self.cuDevice = None if fc is not None: - assert set([f.name for f in fc]) == set(["pads", "N"]), "Field collection invalid" + assert set([f.name for f in fc]) == set( + ["pads", "N"] + ), "Field collection invalid" for f in fc: if f.name == "pads": self.pads = f.data @@ -116,11 +119,17 @@ def __init__(self, fc=None): def initialize(self): err, self.cuDevice = cuda.cuDeviceGet(0) - trt.get_plugin_registry().acquire_plugin_resource("cuda_ctx", CudaCtxManager(self.cuDevice)) - self.all_pads_d = checkCudaErrors(cuda.cuMemAlloc(np.int32().itemsize * self.N * 2)) - self.orig_dims_d = checkCudaErrors(cuda.cuMemAlloc(np.int32().itemsize * self.N)) + trt.get_plugin_registry().acquire_plugin_resource( + "cuda_ctx", CudaCtxManager(self.cuDevice) + ) + self.all_pads_d = checkCudaErrors( + cuda.cuMemAlloc(np.int32().itemsize * self.N * 2) + ) + self.orig_dims_d = checkCudaErrors( + cuda.cuMemAlloc(np.int32().itemsize * self.N) + ) self.Y_shape_d = checkCudaErrors(cuda.cuMemAlloc(np.int32().itemsize * self.N)) - + def get_output_datatype(self, index, input_types): return input_types[0] @@ -157,11 +166,17 @@ def configure_plugin(self, inp, out): # Copy vectors from host memory to device memory if self.all_pads_d: - checkCudaErrors(cuda.cuMemcpyHtoD(self.all_pads_d, all_pads, all_pads.nbytes)) + checkCudaErrors( + cuda.cuMemcpyHtoD(self.all_pads_d, all_pads, all_pads.nbytes) + ) if self.orig_dims_d: - checkCudaErrors(cuda.cuMemcpyHtoD(self.orig_dims_d, orig_dims, orig_dims.nbytes)) + checkCudaErrors( + cuda.cuMemcpyHtoD(self.orig_dims_d, orig_dims, orig_dims.nbytes) + ) if self.Y_shape_d: - checkCudaErrors(cuda.cuMemcpyHtoD(self.Y_shape_d, out_dims, out_dims.nbytes)) + checkCudaErrors( + cuda.cuMemcpyHtoD(self.Y_shape_d, out_dims, out_dims.nbytes) + ) self.Y_len_d = np.prod(out_dims) @@ -205,25 +220,43 @@ def enqueue(self, input_desc, output_desc, inputs, outputs, workspace, stream): if inp_dtype == np.float32: kernelHelper = KernelHelper(circ_pad_float_kernel, int(self.cuDevice)) - _circ_pad_float_kernel = kernelHelper.getFunction(b'circ_pad_float') - checkCudaErrors(cuda.cuLaunchKernel(_circ_pad_float_kernel, - numBlocks, 1, 1, - blockSize, 1, 1, - 0, - stream_ptr, - kernelArgs, 0)) + _circ_pad_float_kernel = kernelHelper.getFunction(b"circ_pad_float") + checkCudaErrors( + cuda.cuLaunchKernel( + _circ_pad_float_kernel, + numBlocks, + 1, + 1, + blockSize, + 1, + 1, + 0, + stream_ptr, + kernelArgs, + 0, + ) + ) elif inp_dtype == np.float16: kernelHelper = KernelHelper(circ_pad_half_kernel, int(self.cuDevice)) - _circ_pad_half_kernel = kernelHelper.getFunction(b'circ_pad_half') - checkCudaErrors(cuda.cuLaunchKernel(_circ_pad_half_kernel, - numBlocks, 1, 1, - blockSize, 1, 1, - 0, - stream_ptr, - kernelArgs, 0)) + _circ_pad_half_kernel = kernelHelper.getFunction(b"circ_pad_half") + checkCudaErrors( + cuda.cuLaunchKernel( + _circ_pad_half_kernel, + numBlocks, + 1, + 1, + blockSize, + 1, + 1, + 0, + stream_ptr, + kernelArgs, + 0, + ) + ) else: raise ValueError("inp_dtype not valid") - + def clone(self): cloned_plugin = CircPadPlugin() cloned_plugin.__dict__.update(self.__dict__) @@ -239,7 +272,7 @@ def terminate(self): trt.get_plugin_registry().release_plugin_resource("cuda_ctx") - # + # # The following defaults take effect since the respective methods are not overriden # @@ -248,7 +281,7 @@ def terminate(self): # def get_workspace_size(self, input_desc, output_desc): # return 0 - + # def destroy(self): # pass @@ -259,10 +292,12 @@ def __init__(self): self.name = "CircPadPlugin" self.plugin_namespace = "" self.plugin_version = "1" - self.field_names = trt.PluginFieldCollection([ - trt.PluginField("pads", np.array([]), trt.PluginFieldType.INT32), - trt.PluginField("N", np.array([]), trt.PluginFieldType.INT32) - ]) + self.field_names = trt.PluginFieldCollection( + [ + trt.PluginField("pads", np.array([]), trt.PluginFieldType.INT32), + trt.PluginField("N", np.array([]), trt.PluginFieldType.INT32), + ] + ) def create_plugin(self, name, fc): return CircPadPlugin(fc) @@ -273,12 +308,13 @@ def deserialize_plugin(self, name, data): deserialized.__dict__.update(j) return deserialized + if __name__ == "__main__": args = parseArgs() # Initialize CUDA Driver API - err, = cuda.cuInit(0) + (err,) = cuda.cuInit(0) # Retrieve handle for device 0 err, cuDevice = cuda.cuDeviceGet(0) @@ -319,12 +355,12 @@ def deserialize_plugin(self, name, data): # build engine build_engine = EngineFromNetwork( - NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision==np.float16) + NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision == np.float16) ) Y_ref = np.pad(X, [[0, 0], [0, 0], [pads[0], pads[1]], [pads[2], pads[3]]], "wrap") # Run - with TrtRunner(build_engine, "trt_runner")as runner: + with TrtRunner(build_engine, "trt_runner") as runner: outputs = runner.infer({"X": X}) Y = outputs["Y"] diff --git a/samples/python/python_plugin/circ_pad_plugin_cupy.py b/samples/python/python_plugin/circ_pad_plugin_cupy.py index 82b271cc..19545a11 100644 --- a/samples/python/python_plugin/circ_pad_plugin_cupy.py +++ b/samples/python/python_plugin/circ_pad_plugin_cupy.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,14 +27,15 @@ CreateConfig, EngineFromNetwork, NetworkFromOnnxPath, - TrtRunner + TrtRunner, ) from polygraphy.json import to_json, from_json from utils import volume, parseArgs -circ_pad_half_kernel = cp.RawKernel(r''' +circ_pad_half_kernel = cp.RawKernel( + r""" #include extern "C" __global__ void circ_pad_half(half const* X, int const* all_pads, int const* orig_dims, half* Y, int const* Y_shape, int const* Y_len) { @@ -61,9 +62,12 @@ ]; } } -''', 'circ_pad_half') +""", + "circ_pad_half", +) -circ_pad_float_kernel = cp.RawKernel(r''' +circ_pad_float_kernel = cp.RawKernel( + r""" extern "C" __global__ void circ_pad_float(float const* X, int const* all_pads, int const* orig_dims, float* Y, int const* Y_shape, int const* Y_len) { int index = blockIdx.x * blockDim.x + threadIdx.x; @@ -89,14 +93,17 @@ ]; } } -''', 'circ_pad_float') +""", + "circ_pad_float", +) + class CircPadPlugin(trt.IPluginV2DynamicExt): def __init__(self, fc=None): trt.IPluginV2DynamicExt.__init__(self) self.pads = [] self.X_shape = [] - + self.num_outputs = 1 self.plugin_namespace = "" self.plugin_type = "CircPadPlugin" @@ -190,9 +197,31 @@ def enqueue(self, input_desc, output_desc, inputs, outputs, workspace, stream): with cuda_stream: if inp_dtype == np.float32: - circ_pad_float_kernel((numBlocks,), (blockSize,), (a, self.all_pads_d, self.orig_dims_d, c, self.Y_shape_d, self.Y_len_d)) + circ_pad_float_kernel( + (numBlocks,), + (blockSize,), + ( + a, + self.all_pads_d, + self.orig_dims_d, + c, + self.Y_shape_d, + self.Y_len_d, + ), + ) elif inp_dtype == np.float16: - circ_pad_half_kernel((numBlocks,), (blockSize,), (a, self.all_pads_d, self.orig_dims_d, c, self.Y_shape_d, self.Y_len_d)) + circ_pad_half_kernel( + (numBlocks,), + (blockSize,), + ( + a, + self.all_pads_d, + self.orig_dims_d, + c, + self.Y_shape_d, + self.Y_len_d, + ), + ) else: raise ValueError("inp_dtype not valid") @@ -201,7 +230,7 @@ def clone(self): cloned_plugin.__dict__.update(self.__dict__) return cloned_plugin - # + # # The following defaults take effect since the respective methods are not overriden # @@ -213,17 +242,18 @@ def clone(self): # def get_workspace_size(self, input_desc, output_desc): # return 0 - + # def destroy(self): # pass # def terminate(self): # pass + class CircPadPluginCreator(trt.IPluginCreator): def __init__(self): trt.IPluginCreator.__init__(self) - + self.name = "CircPadPlugin" self.plugin_namespace = "" self.plugin_version = "1" @@ -233,13 +263,14 @@ def __init__(self): def create_plugin(self, name, fc): return CircPadPlugin(fc) - + def deserialize_plugin(self, name, data): j = dict(from_json(data.decode("utf-8"))) deserialized = CircPadPlugin() deserialized.__dict__.update(j) return deserialized + if __name__ == "__main__": args = parseArgs() @@ -275,12 +306,12 @@ def deserialize_plugin(self, name, data): # build engine build_engine = EngineFromNetwork( - NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision==np.float16) + NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision == np.float16) ) Y_ref = np.pad(X, [[0, 0], [0, 0], [pads[0], pads[1]], [pads[2], pads[3]]], "wrap") # Run - with TrtRunner(build_engine, "trt_runner")as runner: + with TrtRunner(build_engine, "trt_runner") as runner: outputs = runner.infer({"X": X}) Y = outputs["Y"] diff --git a/samples/python/python_plugin/circ_pad_plugin_inetdef_cuda_python.py b/samples/python/python_plugin/circ_pad_plugin_inetdef_cuda_python.py index 60208ab3..6abf526f 100644 --- a/samples/python/python_plugin/circ_pad_plugin_inetdef_cuda_python.py +++ b/samples/python/python_plugin/circ_pad_plugin_inetdef_cuda_python.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,7 +23,7 @@ CreateConfig, TrtRunner, create_network, - engine_from_network + engine_from_network, ) from polygraphy.json import to_json, from_json @@ -31,7 +31,7 @@ from utils import checkCudaErrors, KernelHelper, parseArgs, CudaCtxManager from cuda import cuda -circ_pad_half_kernel = r''' +circ_pad_half_kernel = r""" #include extern "C" __global__ void circ_pad_half(half const* X, int const* all_pads, int const* orig_dims, half* Y, int const* Y_shape, int Y_len) { @@ -58,9 +58,9 @@ ]; } } -''' +""" -circ_pad_float_kernel = r''' +circ_pad_float_kernel = r""" extern "C" __global__ void circ_pad_float(float const* X, int const* all_pads, int const* orig_dims, float* Y, int const* Y_shape, int Y_len) { int index = blockIdx.x * blockDim.x + threadIdx.x; @@ -86,7 +86,8 @@ ]; } } -''' +""" + class CircPadPlugin(trt.IPluginV2DynamicExt): def __init__(self, fc=None): @@ -107,7 +108,9 @@ def __init__(self, fc=None): self.cuDevice = None if fc is not None: - assert set([f.name for f in fc]) == set(["pads", "N"]), "Field collection invalid" + assert set([f.name for f in fc]) == set( + ["pads", "N"] + ), "Field collection invalid" for f in fc: if f.name == "pads": self.pads = f.data @@ -116,11 +119,17 @@ def __init__(self, fc=None): def initialize(self): err, self.cuDevice = cuda.cuDeviceGet(0) - trt.get_plugin_registry().acquire_plugin_resource("cuda_ctx", CudaCtxManager(self.cuDevice)) - self.all_pads_d = checkCudaErrors(cuda.cuMemAlloc(np.int32().itemsize * self.N * 2)) - self.orig_dims_d = checkCudaErrors(cuda.cuMemAlloc(np.int32().itemsize * self.N)) + trt.get_plugin_registry().acquire_plugin_resource( + "cuda_ctx", CudaCtxManager(self.cuDevice) + ) + self.all_pads_d = checkCudaErrors( + cuda.cuMemAlloc(np.int32().itemsize * self.N * 2) + ) + self.orig_dims_d = checkCudaErrors( + cuda.cuMemAlloc(np.int32().itemsize * self.N) + ) self.Y_shape_d = checkCudaErrors(cuda.cuMemAlloc(np.int32().itemsize * self.N)) - + def get_output_datatype(self, index, input_types): return input_types[0] @@ -157,11 +166,17 @@ def configure_plugin(self, inp, out): # Copy vectors from host memory to device memory if self.all_pads_d: - checkCudaErrors(cuda.cuMemcpyHtoD(self.all_pads_d, all_pads, all_pads.nbytes)) + checkCudaErrors( + cuda.cuMemcpyHtoD(self.all_pads_d, all_pads, all_pads.nbytes) + ) if self.orig_dims_d: - checkCudaErrors(cuda.cuMemcpyHtoD(self.orig_dims_d, orig_dims, orig_dims.nbytes)) + checkCudaErrors( + cuda.cuMemcpyHtoD(self.orig_dims_d, orig_dims, orig_dims.nbytes) + ) if self.Y_shape_d: - checkCudaErrors(cuda.cuMemcpyHtoD(self.Y_shape_d, out_dims, out_dims.nbytes)) + checkCudaErrors( + cuda.cuMemcpyHtoD(self.Y_shape_d, out_dims, out_dims.nbytes) + ) self.Y_len_d = np.prod(out_dims) @@ -205,25 +220,43 @@ def enqueue(self, input_desc, output_desc, inputs, outputs, workspace, stream): if inp_dtype == np.float32: kernelHelper = KernelHelper(circ_pad_float_kernel, int(self.cuDevice)) - _circ_pad_float_kernel = kernelHelper.getFunction(b'circ_pad_float') - checkCudaErrors(cuda.cuLaunchKernel(_circ_pad_float_kernel, - numBlocks, 1, 1, - blockSize, 1, 1, - 0, - stream_ptr, - kernelArgs, 0)) + _circ_pad_float_kernel = kernelHelper.getFunction(b"circ_pad_float") + checkCudaErrors( + cuda.cuLaunchKernel( + _circ_pad_float_kernel, + numBlocks, + 1, + 1, + blockSize, + 1, + 1, + 0, + stream_ptr, + kernelArgs, + 0, + ) + ) elif inp_dtype == np.float16: kernelHelper = KernelHelper(circ_pad_half_kernel, int(self.cuDevice)) - _circ_pad_half_kernel = kernelHelper.getFunction(b'circ_pad_half') - checkCudaErrors(cuda.cuLaunchKernel(_circ_pad_half_kernel, - numBlocks, 1, 1, - blockSize, 1, 1, - 0, - stream_ptr, - kernelArgs, 0)) + _circ_pad_half_kernel = kernelHelper.getFunction(b"circ_pad_half") + checkCudaErrors( + cuda.cuLaunchKernel( + _circ_pad_half_kernel, + numBlocks, + 1, + 1, + blockSize, + 1, + 1, + 0, + stream_ptr, + kernelArgs, + 0, + ) + ) else: raise ValueError("inp_dtype not valid") - + def clone(self): cloned_plugin = CircPadPlugin() cloned_plugin.__dict__.update(self.__dict__) @@ -239,7 +272,7 @@ def terminate(self): plg_registry.release_plugin_resource("cuda_ctx") - # + # # The following defaults take effect since the respective methods are not overriden # @@ -248,7 +281,7 @@ def terminate(self): # def get_workspace_size(self, input_desc, output_desc): # return 0 - + # def destroy(self): # pass @@ -259,10 +292,12 @@ def __init__(self): self.name = "CircPadPlugin" self.plugin_namespace = "" self.plugin_version = "1" - self.field_names = trt.PluginFieldCollection([ - trt.PluginField("pads", np.array([]), trt.PluginFieldType.INT32), - trt.PluginField("N", np.array([]), trt.PluginFieldType.INT32) - ]) + self.field_names = trt.PluginFieldCollection( + [ + trt.PluginField("pads", np.array([]), trt.PluginFieldType.INT32), + trt.PluginField("N", np.array([]), trt.PluginFieldType.INT32), + ] + ) def create_plugin(self, name, fc): return CircPadPlugin(fc) @@ -273,13 +308,14 @@ def deserialize_plugin(self, name, data): deserialized.__dict__.update(j) return deserialized + if __name__ == "__main__": args = parseArgs() precision = np.float32 if args.precision == "fp32" else np.float16 # Initialize CUDA Driver API - err, = cuda.cuInit(0) + (err,) = cuda.cuInit(0) # Retrieve handle for device 0 err, cuDevice = cuda.cuDeviceGet(0) @@ -306,28 +342,36 @@ def deserialize_plugin(self, name, data): builder, network = create_network() plg_creator = plg_registry.get_plugin_creator("CircPadPlugin", "1", "") plugin_fields_list = [ - trt.PluginField("pads", np.array(pads, dtype=np.int32), trt.PluginFieldType.INT32), + trt.PluginField( + "pads", np.array(pads, dtype=np.int32), trt.PluginFieldType.INT32 + ), trt.PluginField("N", np.array([4], dtype=np.int32), trt.PluginFieldType.INT32), ] pfc = trt.PluginFieldCollection(plugin_fields_list) plugin = plg_creator.create_plugin("CircPadPlugin", pfc) # Populate network - input_X = network.add_input(name="X", dtype=trt.float32 if precision==np.float32 else trt.float16, shape=X.shape) + input_X = network.add_input( + name="X", + dtype=trt.float32 if precision == np.float32 else trt.float16, + shape=X.shape, + ) out = network.add_plugin_v2([input_X], plugin) out.get_output(0).name = "Y" network.mark_output(tensor=out.get_output(0)) # Build engine config = builder.create_builder_config() - engine = engine_from_network((builder, network), CreateConfig(fp16=precision==trt.float16)) + engine = engine_from_network( + (builder, network), CreateConfig(fp16=precision == trt.float16) + ) Y_ref = np.pad(X, [[0, 0], [0, 0], [pads[0], pads[1]], [pads[2], pads[3]]], "wrap") # Run - with TrtRunner(engine, "trt_runner")as runner: + with TrtRunner(engine, "trt_runner") as runner: outputs = runner.infer({"X": X}) Y = outputs["Y"] - + if np.allclose(Y, Y_ref): print("Inference result correct!") else: diff --git a/samples/python/python_plugin/circ_pad_plugin_numba.py b/samples/python/python_plugin/circ_pad_plugin_numba.py index 2cc0bfab..d568419d 100644 --- a/samples/python/python_plugin/circ_pad_plugin_numba.py +++ b/samples/python/python_plugin/circ_pad_plugin_numba.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -32,6 +32,7 @@ from polygraphy.json import to_json, from_json from utils import volume, parseArgs + @cuda.jit def circ_pad(X, all_pads, orig_dims, Y, Y_shape, Y_len): index = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x @@ -57,6 +58,7 @@ def circ_pad(X, all_pads, orig_dims, Y, Y_shape, Y_len): ) ] + class CircPadPlugin(trt.IPluginV2DynamicExt): def __init__(self, fc=None): trt.IPluginV2DynamicExt.__init__(self) @@ -76,7 +78,7 @@ def get_output_datatype(self, index, input_types): return input_types[0] def get_output_dimensions(self, output_index, inputs, exprBuilder): - + output_dims = trt.DimsExprs(inputs[0]) for i in range(np.size(self.pads) // 2): @@ -163,8 +165,8 @@ def clone(self): cloned_plugin = CircPadPlugin() cloned_plugin.__dict__.update(self.__dict__) return cloned_plugin - - # + + # # The following defaults take effect since the respective methods are not overriden # @@ -176,7 +178,7 @@ def clone(self): # def get_workspace_size(self, input_desc, output_desc): # return 0 - + # def destroy(self): # pass @@ -203,6 +205,7 @@ def deserialize_plugin(self, name, data): deserialized.__dict__.update(j) return deserialized + if __name__ == "__main__": args = parseArgs() @@ -234,12 +237,12 @@ def deserialize_plugin(self, name, data): # build engine build_engine = EngineFromNetwork( - NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision==np.float16) + NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision == np.float16) ) Y_ref = np.pad(X, [[0, 0], [0, 0], [pads[0], pads[1]], [pads[2], pads[3]]], "wrap") # Run - with TrtRunner(build_engine, "trt_runner")as runner: + with TrtRunner(build_engine, "trt_runner") as runner: outputs = runner.infer({"X": X}) Y = outputs["Y"] diff --git a/samples/python/python_plugin/circ_pad_plugin_torch.py b/samples/python/python_plugin/circ_pad_plugin_torch.py index 8b036469..76e8cc41 100644 --- a/samples/python/python_plugin/circ_pad_plugin_torch.py +++ b/samples/python/python_plugin/circ_pad_plugin_torch.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -33,12 +33,13 @@ from utils import volume, parseArgs + class CircPadPlugin(trt.IPluginV2DynamicExt): def __init__(self, fc=None): trt.IPluginV2DynamicExt.__init__(self) self.pads = [] self.X_shape = [] - + self.num_outputs = 1 self.plugin_namespace = "" self.plugin_type = "CircPadPlugin" @@ -110,10 +111,10 @@ def enqueue(self, input_desc, output_desc, inputs, outputs, workspace, stream): a_d = cp.ndarray(tuple(input_desc[0].dims), dtype=inp_dtype, memptr=a_ptr) c_d = cp.ndarray((volume(output_desc[0].dims)), dtype=inp_dtype, memptr=c_ptr) - a_t = torch.as_tensor(a_d, device='cuda') + a_t = torch.as_tensor(a_d, device="cuda") # Use PyTorch functional op - no need to write kernel - out = torch.nn.functional.pad(a_t, self.pads.tolist(), mode='circular') + out = torch.nn.functional.pad(a_t, self.pads.tolist(), mode="circular") cp.copyto(c_d, cp.reshape(cp.asarray(out), (-1,))) return 0 @@ -123,7 +124,7 @@ def clone(self): cloned_plugin.__dict__.update(self.__dict__) return cloned_plugin - # + # # The following defaults take effect since the respective methods are not overriden # @@ -135,7 +136,7 @@ def clone(self): # def get_workspace_size(self, input_desc, output_desc): # return 0 - + # def destroy(self): # pass @@ -162,6 +163,7 @@ def deserialize_plugin(self, name, data): deserialized.__dict__.update(j) return deserialized + if __name__ == "__main__": args = parseArgs() @@ -193,12 +195,12 @@ def deserialize_plugin(self, name, data): # build engine build_engine = EngineFromNetwork( - NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision==np.float16) + NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision == np.float16) ) Y_ref = np.pad(X, [[0, 0], [0, 0], [pads[0], pads[1]], [pads[2], pads[3]]], "wrap") # Run - with TrtRunner(build_engine, "trt_runner")as runner: + with TrtRunner(build_engine, "trt_runner") as runner: outputs = runner.infer({"X": X}) Y = outputs["Y"] diff --git a/samples/python/python_plugin/circ_pad_plugin_triton.py b/samples/python/python_plugin/circ_pad_plugin_triton.py index 93b5f0fd..686d4e5c 100644 --- a/samples/python/python_plugin/circ_pad_plugin_triton.py +++ b/samples/python/python_plugin/circ_pad_plugin_triton.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -36,13 +36,26 @@ from utils import volume, parseArgs + @triton.jit -def circ_pad(X, - all_pads_0, all_pads_2, all_pads_4, all_pads_6, - orig_dims_0, orig_dims_1, orig_dims_2, orig_dims_3, - Y, - Y_shape_1, Y_shape_2, Y_shape_3, - X_len, Y_len, BLOCK_SIZE: tl.constexpr,): +def circ_pad( + X, + all_pads_0, + all_pads_2, + all_pads_4, + all_pads_6, + orig_dims_0, + orig_dims_1, + orig_dims_2, + orig_dims_3, + Y, + Y_shape_1, + Y_shape_2, + Y_shape_3, + X_len, + Y_len, + BLOCK_SIZE: tl.constexpr, +): pid = tl.program_id(0) i = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) @@ -58,7 +71,12 @@ def circ_pad(X, j2 = (i2 - all_pads_4 + orig_dims_2) % orig_dims_2 j3 = (i3 - all_pads_6 + orig_dims_3) % orig_dims_3 - load_idx = orig_dims_3 * orig_dims_2 * orig_dims_1 * j0 + orig_dims_3 * orig_dims_2 * j1 + orig_dims_3 * j2 + j3 + load_idx = ( + orig_dims_3 * orig_dims_2 * orig_dims_1 * j0 + + orig_dims_3 * orig_dims_2 * j1 + + orig_dims_3 * j2 + + j3 + ) mask_x = load_idx < X_len x = tl.load(X + load_idx, mask=mask_x) @@ -143,8 +161,8 @@ def enqueue(self, input_desc, output_desc, inputs, outputs, workspace, stream): a_d = cp.ndarray((volume(input_desc[0].dims)), dtype=inp_dtype, memptr=a_ptr) c_d = cp.ndarray((volume(output_desc[0].dims)), dtype=inp_dtype, memptr=c_ptr) - a_t = torch.as_tensor(a_d, device='cuda') - c_t = torch.as_tensor(c_d, device='cuda') + a_t = torch.as_tensor(a_d, device="cuda") + c_t = torch.as_tensor(c_d, device="cuda") N = len(self.X_shape) all_pads = np.zeros((N * 2,), dtype=np.int32) @@ -163,12 +181,23 @@ def enqueue(self, input_desc, output_desc, inputs, outputs, workspace, stream): blockSize = 256 numBlocks = (int((np.prod(out_dims) + blockSize - 1) // blockSize),) - circ_pad[numBlocks](a_t, - all_pads[0], all_pads[2], all_pads[4], all_pads[6], - orig_dims[0], orig_dims[1], orig_dims[2], orig_dims[3], + circ_pad[numBlocks]( + a_t, + all_pads[0], + all_pads[2], + all_pads[4], + all_pads[6], + orig_dims[0], + orig_dims[1], + orig_dims[2], + orig_dims[3], c_t, - out_dims[1], out_dims[2], out_dims[3], - int(np.prod(orig_dims)), int(np.prod(out_dims)), BLOCK_SIZE=256 + out_dims[1], + out_dims[2], + out_dims[3], + int(np.prod(orig_dims)), + int(np.prod(out_dims)), + BLOCK_SIZE=256, ) return 0 @@ -178,7 +207,7 @@ def clone(self): cloned_plugin.__dict__.update(self.__dict__) return cloned_plugin - # + # # The following defaults take effect since the respective methods are not overriden # @@ -190,7 +219,7 @@ def clone(self): # def get_workspace_size(self, input_desc, output_desc): # return 0 - + # def destroy(self): # pass @@ -217,6 +246,7 @@ def deserialize_plugin(self, name, data): deserialized.__dict__.update(j) return deserialized + if __name__ == "__main__": args = parseArgs() @@ -248,12 +278,12 @@ def deserialize_plugin(self, name, data): # build engine build_engine = EngineFromNetwork( - NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision==np.float16) + NetworkFromOnnxPath(onnx_path), CreateConfig(fp16=precision == np.float16) ) Y_ref = np.pad(X, [[0, 0], [0, 0], [pads[0], pads[1]], [pads[2], pads[3]]], "wrap") # Run - with TrtRunner(build_engine, "trt_runner")as runner: + with TrtRunner(build_engine, "trt_runner") as runner: outputs = runner.infer({"X": X}) Y = outputs["Y"] diff --git a/samples/python/python_plugin/circ_plugin_cpp/circ_pad_plugin.cu b/samples/python/python_plugin/circ_plugin_cpp/circ_pad_plugin.cu index 8e06a025..0bcffd56 100644 --- a/samples/python/python_plugin/circ_plugin_cpp/circ_pad_plugin.cu +++ b/samples/python/python_plugin/circ_plugin_cpp/circ_pad_plugin.cu @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -109,8 +109,7 @@ __global__ void circPadKernel( int32_t j2 = (i2 - allPads[4] + origDims[2]) % origDims[2]; int32_t j3 = (i3 - allPads[6] + origDims[3]) % origDims[3]; - y[i] = x[origDims[3] * origDims[2] * origDims[1] * j0 + origDims[3] * origDims[2] * j1 + origDims[3] * j2 - + j3]; + y[i] = x[origDims[3] * origDims[2] * origDims[1] * j0 + origDims[3] * origDims[2] * j1 + origDims[3] * j2 + j3]; } } diff --git a/samples/python/python_plugin/utils.py b/samples/python/python_plugin/utils.py index 4015b72c..1a1aa16c 100644 --- a/samples/python/python_plugin/utils.py +++ b/samples/python/python_plugin/utils.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,15 +23,26 @@ import tensorrt as trt + def parseArgs(): - parser = argparse.ArgumentParser(description="Options for Circular Padding plugin C++ example") - parser.add_argument('--precision', type=str, default="fp32", choices=["fp32", "fp16"], help="Precision to use for plugin") + parser = argparse.ArgumentParser( + description="Options for Circular Padding plugin C++ example" + ) + parser.add_argument( + "--precision", + type=str, + default="fp32", + choices=["fp32", "fp16"], + help="Precision to use for plugin", + ) return parser.parse_args() + def volume(d): return np.prod(d) + # Taken from https://github.com/NVIDIA/cuda-python/blob/main/examples/common/helper_cuda.py def checkCudaErrors(result): def _cudaGetErrorEnum(error): @@ -43,9 +54,14 @@ def _cudaGetErrorEnum(error): elif isinstance(error, nvrtc.nvrtcResult): return nvrtc.nvrtcGetErrorString(error)[1] else: - raise RuntimeError('Unknown error type: {}'.format(error)) + raise RuntimeError("Unknown error type: {}".format(error)) + if result[0].value: - raise RuntimeError("CUDA error code={}({})".format(result[0].value, _cudaGetErrorEnum(result[0]))) + raise RuntimeError( + "CUDA error code={}({})".format( + result[0].value, _cudaGetErrorEnum(result[0]) + ) + ) if len(result) == 1: return None elif len(result) == 2: @@ -53,34 +69,50 @@ def _cudaGetErrorEnum(error): else: return result[1:] + # Taken from https://github.com/NVIDIA/cuda-python/blob/main/examples/common/common.py class KernelHelper: def __init__(self, code, devID): - prog = checkCudaErrors(nvrtc.nvrtcCreateProgram(str.encode(code), b'sourceCode.cu', 0, [], [])) - CUDA_HOME = os.getenv('CUDA_HOME') + prog = checkCudaErrors( + nvrtc.nvrtcCreateProgram(str.encode(code), b"sourceCode.cu", 0, [], []) + ) + CUDA_HOME = os.getenv("CUDA_HOME") if CUDA_HOME == None: - CUDA_HOME = os.getenv('CUDA_PATH') + CUDA_HOME = os.getenv("CUDA_PATH") if CUDA_HOME == None: - raise RuntimeError('Environment variable CUDA_HOME or CUDA_PATH is not set') - include_dirs = os.path.join(CUDA_HOME, 'include') + raise RuntimeError("Environment variable CUDA_HOME or CUDA_PATH is not set") + include_dirs = os.path.join(CUDA_HOME, "include") # Initialize CUDA checkCudaErrors(cudart.cudaFree(0)) - major = checkCudaErrors(cudart.cudaDeviceGetAttribute(cudart.cudaDeviceAttr.cudaDevAttrComputeCapabilityMajor, devID)) - minor = checkCudaErrors(cudart.cudaDeviceGetAttribute(cudart.cudaDeviceAttr.cudaDevAttrComputeCapabilityMinor, devID)) + major = checkCudaErrors( + cudart.cudaDeviceGetAttribute( + cudart.cudaDeviceAttr.cudaDevAttrComputeCapabilityMajor, devID + ) + ) + minor = checkCudaErrors( + cudart.cudaDeviceGetAttribute( + cudart.cudaDeviceAttr.cudaDevAttrComputeCapabilityMinor, devID + ) + ) _, nvrtc_minor = checkCudaErrors(nvrtc.nvrtcVersion()) - use_cubin = (nvrtc_minor >= 1) - prefix = 'sm' if use_cubin else 'compute' - arch_arg = bytes(f'--gpu-architecture={prefix}_{major}{minor}', 'ascii') + use_cubin = nvrtc_minor >= 1 + prefix = "sm" if use_cubin else "compute" + arch_arg = bytes(f"--gpu-architecture={prefix}_{major}{minor}", "ascii") try: - opts = [b'--fmad=true', arch_arg, '--include-path={}'.format(include_dirs).encode('UTF-8'), - b'--std=c++11', b'-default-device'] + opts = [ + b"--fmad=true", + arch_arg, + "--include-path={}".format(include_dirs).encode("UTF-8"), + b"--std=c++11", + b"-default-device", + ] checkCudaErrors(nvrtc.nvrtcCompileProgram(prog, len(opts), opts)) except RuntimeError as err: logSize = checkCudaErrors(nvrtc.nvrtcGetProgramLogSize(prog)) - log = b' ' * logSize + log = b" " * logSize checkCudaErrors(nvrtc.nvrtcGetProgramLog(prog, log)) print(log.decode()) print(err) @@ -88,11 +120,11 @@ def __init__(self, code, devID): if use_cubin: dataSize = checkCudaErrors(nvrtc.nvrtcGetCUBINSize(prog)) - data = b' ' * dataSize + data = b" " * dataSize checkCudaErrors(nvrtc.nvrtcGetCUBIN(prog, data)) else: dataSize = checkCudaErrors(nvrtc.nvrtcGetPTXSize(prog)) - data = b' ' * dataSize + data = b" " * dataSize checkCudaErrors(nvrtc.nvrtcGetPTX(prog, data)) self.module = checkCudaErrors(cuda.cuModuleLoadData(np.char.array(data))) @@ -100,8 +132,9 @@ def __init__(self, code, devID): def getFunction(self, name): return checkCudaErrors(cuda.cuModuleGetFunction(self.module, name)) + class CudaCtxManager(trt.IPluginResource): - def __init__(self, device = None): + def __init__(self, device=None): trt.IPluginResource.__init__(self) self.device = device self.cuda_ctx = None diff --git a/samples/python/scripts/download_mnist_data.sh b/samples/python/scripts/download_mnist_data.sh index 809bcbc9..196ddd4e 100755 --- a/samples/python/scripts/download_mnist_data.sh +++ b/samples/python/scripts/download_mnist_data.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/python/scripts/download_mnist_pgms.py b/samples/python/scripts/download_mnist_pgms.py index a1ee0cba..dee877fe 100644 --- a/samples/python/scripts/download_mnist_pgms.py +++ b/samples/python/scripts/download_mnist_pgms.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/python/simple_progress_monitor/simple_progress_monitor.py b/samples/python/simple_progress_monitor/simple_progress_monitor.py index 9ed6c6ba..fe54f720 100644 --- a/samples/python/simple_progress_monitor/simple_progress_monitor.py +++ b/samples/python/simple_progress_monitor/simple_progress_monitor.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -36,6 +36,7 @@ class ModelData(object): # We can convert TensorRT data types to numpy types with trt.nptype(). DTYPE = trt.float32 + # This is a simple ASCII-art progress monitor comparable to the C++ version in sample_progress_monitor. class SimpleProgressMonitor(trt.IProgressMonitor): def __init__(self): @@ -46,10 +47,15 @@ def __init__(self): def phase_start(self, phase_name, parent_phase, num_steps): try: if parent_phase is not None: - nbIndents = 1 + self._active_phases[parent_phase]['nbIndents'] + nbIndents = 1 + self._active_phases[parent_phase]["nbIndents"] else: nbIndents = 0 - self._active_phases[phase_name] = { 'title': phase_name, 'steps': 0, 'num_steps': num_steps, 'nbIndents': nbIndents } + self._active_phases[phase_name] = { + "title": phase_name, + "steps": 0, + "num_steps": num_steps, + "nbIndents": nbIndents, + } self._redraw() except KeyboardInterrupt: # The phase_start callback cannot directly cancel the build, so request the cancellation from within step_complete. @@ -58,13 +64,13 @@ def phase_start(self, phase_name, parent_phase, num_steps): def phase_finish(self, phase_name): try: del self._active_phases[phase_name] - self._redraw(blank_lines=1) # Clear the removed phase. + self._redraw(blank_lines=1) # Clear the removed phase. except KeyboardInterrupt: _step_result = False def step_complete(self, phase_name, step): try: - self._active_phases[phase_name]['steps'] = step + self._active_phases[phase_name]["steps"] = step self._redraw() return self._step_result except KeyboardInterrupt: @@ -75,32 +81,35 @@ def _redraw(self, *, blank_lines=0): # The Python curses module is not widely available on Windows platforms. # Instead, this function uses raw terminal escape sequences. See the sample documentation for references. def clear_line(): - print('\x1B[2K', end='') + print("\x1B[2K", end="") + def move_to_start_of_line(): - print('\x1B[0G', end='') + print("\x1B[0G", end="") + def move_cursor_up(lines): - print('\x1B[{}A'.format(lines), end='') + print("\x1B[{}A".format(lines), end="") def progress_bar(steps, num_steps): INNER_WIDTH = 10 completed_bar_chars = int(INNER_WIDTH * steps / float(num_steps)) - return '[{}{}]'.format( - '=' * completed_bar_chars, - '-' * (INNER_WIDTH - completed_bar_chars)) + return "[{}{}]".format( + "=" * completed_bar_chars, "-" * (INNER_WIDTH - completed_bar_chars) + ) # Set max_cols to a default of 200 if not run in interactive mode. max_cols = os.get_terminal_size().columns if sys.stdout.isatty() else 200 move_to_start_of_line() for phase in self._active_phases.values(): - phase_prefix = '{indent}{bar} {title}'.format( - indent = ' ' * phase['nbIndents'], - bar = progress_bar(phase['steps'], phase['num_steps']), - title = phase['title']) - phase_suffix = '{steps}/{num_steps}'.format(**phase) + phase_prefix = "{indent}{bar} {title}".format( + indent=" " * phase["nbIndents"], + bar=progress_bar(phase["steps"], phase["num_steps"]), + title=phase["title"], + ) + phase_suffix = "{steps}/{num_steps}".format(**phase) allowable_prefix_chars = max_cols - len(phase_suffix) - 2 if allowable_prefix_chars < len(phase_prefix): - phase_prefix = phase_prefix[0:allowable_prefix_chars-3] + '...' + phase_prefix = phase_prefix[0 : allowable_prefix_chars - 3] + "..." clear_line() print(phase_prefix, phase_suffix) for line in range(blank_lines): @@ -109,16 +118,20 @@ def progress_bar(steps, num_steps): move_cursor_up(len(self._active_phases) + blank_lines) sys.stdout.flush() + # You can set the logger severity higher to suppress messages (or lower to display more messages). TRT_LOGGER = trt.Logger(trt.Logger.WARNING) + # The Onnx path is used for Onnx models. def build_engine_onnx(model_file): builder = trt.Builder(TRT_LOGGER) network = builder.create_network(0) config = builder.create_builder_config() if not sys.stdout.isatty(): - print("Warning: This sample should be run from an interactive terminal in order to showcase the progress monitor correctly.") + print( + "Warning: This sample should be run from an interactive terminal in order to showcase the progress monitor correctly." + ) config.progress_monitor = SimpleProgressMonitor() parser = trt.OnnxParser(network, TRT_LOGGER) @@ -186,7 +199,14 @@ def main(): test_case = load_normalized_test_case(test_image, inputs[0].host) # Run the engine. The output will be a 1D tensor of length 1000, where each value represents the # probability that the image corresponds to that label - trt_outputs = common.do_inference(context, engine=engine, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) + trt_outputs = common.do_inference( + context, + engine=engine, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream, + ) # We use the highest probability as our prediction. Its index corresponds to the predicted label. pred = labels[np.argmax(trt_outputs[0])] common.free_buffers(inputs, outputs, stream) @@ -195,5 +215,6 @@ def main(): else: print("Incorrectly recognized " + test_case + " as " + pred) + if __name__ == "__main__": main() diff --git a/samples/python/tensorflow_object_detection_api/build_engine.py b/samples/python/tensorflow_object_detection_api/build_engine.py index 0a0d6238..9bbf5f7c 100644 --- a/samples/python/tensorflow_object_detection_api/build_engine.py +++ b/samples/python/tensorflow_object_detection_api/build_engine.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -56,7 +56,10 @@ def set_image_batcher(self, image_batcher: ImageBatcher): :param image_batcher: The ImageBatcher object """ self.image_batcher = image_batcher - size = int(np.dtype(self.image_batcher.dtype).itemsize * np.prod(self.image_batcher.shape)) + size = int( + np.dtype(self.image_batcher.dtype).itemsize + * np.prod(self.image_batcher.shape) + ) self.batch_allocation = common.cuda_call(cudart.cudaMalloc(size)) self.batch_generator = self.image_batcher.get_batch() @@ -81,8 +84,14 @@ def get_batch(self, names): return None try: batch, _, _ = next(self.batch_generator) - log.info("Calibrating image {} / {}".format(self.image_batcher.image_index, self.image_batcher.num_images)) - common.memcpy_host_to_device(self.batch_allocation, np.ascontiguousarray(batch)) + log.info( + "Calibrating image {} / {}".format( + self.image_batcher.image_index, self.image_batcher.num_images + ) + ) + common.memcpy_host_to_device( + self.batch_allocation, np.ascontiguousarray(batch) + ) return [int(self.batch_allocation)] except StopIteration: log.info("Finished calibration batches") @@ -128,7 +137,9 @@ def __init__(self, verbose=False, workspace=8): self.builder = trt.Builder(self.trt_logger) self.config = self.builder.create_builder_config() - self.config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, workspace * (2 ** 30)) + self.config.set_memory_pool_limit( + trt.MemoryPoolType.WORKSPACE, workspace * (2**30) + ) self.batch_size = None self.network = None @@ -157,9 +168,17 @@ def create_network(self, onnx_path): log.info("Network Description") for input in inputs: self.batch_size = input.shape[0] - log.info("Input '{}' with shape {} and dtype {}".format(input.name, input.shape, input.dtype)) + log.info( + "Input '{}' with shape {} and dtype {}".format( + input.name, input.shape, input.dtype + ) + ) for output in outputs: - log.info("Output '{}' with shape {} and dtype {}".format(output.name, output.shape, output.dtype)) + log.info( + "Output '{}' with shape {} and dtype {}".format( + output.name, output.shape, output.dtype + ) + ) assert self.batch_size > 0 # TODO: These overrides are to improve fp16/int8 performance on FRCNN models @@ -167,17 +186,25 @@ def create_network(self, onnx_path): # type on the two NMS plugins. To be determined. for i in range(self.network.num_layers): if self.network.get_layer(i).name in [ - "FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor/squeeze", - "FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor/scale_value:0", - "FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor/scale", - "nms/anchors:0"]: + "FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor/squeeze", + "FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor/scale_value:0", + "FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor/scale", + "nms/anchors:0", + ]: self.network.get_layer(i).precision = trt.DataType.FLOAT - self.network.get_layer(i-1).precision = trt.DataType.FLOAT + self.network.get_layer(i - 1).precision = trt.DataType.FLOAT if self.network.get_layer(i).name == "FirstNMS/detection_boxes_conversion": self.network.get_layer(i).precision = trt.DataType.FLOAT - def create_engine(self, engine_path, precision, calib_input=None, calib_cache=None, calib_num_images=5000, - calib_batch_size=8): + def create_engine( + self, + engine_path, + precision, + calib_input=None, + calib_cache=None, + calib_num_images=5000, + calib_batch_size=8, + ): """ Build the TensorRT engine and serialize it to disk. :param engine_path: The path where to serialize the engine to. @@ -218,8 +245,14 @@ def create_engine(self, engine_path, precision, calib_input=None, calib_cache=No calib_shape = [calib_batch_size] + list(inputs[0].shape[1:]) calib_dtype = trt.nptype(inputs[0].dtype) self.config.int8_calibrator.set_image_batcher( - ImageBatcher(calib_input, calib_shape, calib_dtype, max_num_images=calib_num_images, - exact_batches=True)) + ImageBatcher( + calib_input, + calib_shape, + calib_dtype, + max_num_images=calib_num_images, + exact_batches=True, + ) + ) engine_bytes = self.builder.build_serialized_network(self.network, self.config) if engine_bytes is None: @@ -234,33 +267,68 @@ def create_engine(self, engine_path, precision, calib_input=None, calib_cache=No def main(args): builder = EngineBuilder(args.verbose, args.workspace) builder.create_network(args.onnx) - builder.create_engine(args.engine, args.precision, args.calib_input, args.calib_cache, args.calib_num_images, - args.calib_batch_size) + builder.create_engine( + args.engine, + args.precision, + args.calib_input, + args.calib_cache, + args.calib_num_images, + args.calib_batch_size, + ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-o", "--onnx", help="The input ONNX model file to load") parser.add_argument("-e", "--engine", help="The output path for the TRT engine") - parser.add_argument("-p", "--precision", default="fp16", choices=["fp32", "fp16", "int8"], - help="The precision mode to build in, either 'fp32', 'fp16' or 'int8', default: 'fp16'") - parser.add_argument("-v", "--verbose", action="store_true", help="Enable more verbose log output") - parser.add_argument("-w", "--workspace", default=1, type=int, help="The max memory workspace size to allow in Gb, " - "default: 1") - parser.add_argument("--calib_input", help="The directory holding images to use for calibration") - parser.add_argument("--calib_cache", default="./calibration.cache", - help="The file path for INT8 calibration cache to use, default: ./calibration.cache") - parser.add_argument("--calib_num_images", default=5000, type=int, - help="The maximum number of images to use for calibration, default: 5000") - parser.add_argument("--calib_batch_size", default=8, type=int, - help="The batch size for the calibration process, default: 8") + parser.add_argument( + "-p", + "--precision", + default="fp16", + choices=["fp32", "fp16", "int8"], + help="The precision mode to build in, either 'fp32', 'fp16' or 'int8', default: 'fp16'", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable more verbose log output" + ) + parser.add_argument( + "-w", + "--workspace", + default=1, + type=int, + help="The max memory workspace size to allow in Gb, " "default: 1", + ) + parser.add_argument( + "--calib_input", help="The directory holding images to use for calibration" + ) + parser.add_argument( + "--calib_cache", + default="./calibration.cache", + help="The file path for INT8 calibration cache to use, default: ./calibration.cache", + ) + parser.add_argument( + "--calib_num_images", + default=5000, + type=int, + help="The maximum number of images to use for calibration, default: 5000", + ) + parser.add_argument( + "--calib_batch_size", + default=8, + type=int, + help="The batch size for the calibration process, default: 8", + ) args = parser.parse_args() if not all([args.onnx, args.engine]): parser.print_help() log.error("These arguments are required: --onnx and --engine") sys.exit(1) - if args.precision == "int8" and not (args.calib_input or os.path.exists(args.calib_cache)): + if args.precision == "int8" and not ( + args.calib_input or os.path.exists(args.calib_cache) + ): parser.print_help() - log.error("When building in int8 precision, --calib_input or an existing --calib_cache file is required") + log.error( + "When building in int8 precision, --calib_input or an existing --calib_cache file is required" + ) sys.exit(1) main(args) diff --git a/samples/python/tensorflow_object_detection_api/compare_tf.py b/samples/python/tensorflow_object_detection_api/compare_tf.py index 409aec6b..ae5168eb 100644 --- a/samples/python/tensorflow_object_detection_api/compare_tf.py +++ b/samples/python/tensorflow_object_detection_api/compare_tf.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,6 +27,7 @@ from image_batcher import ImageBatcher from visualize import visualize_detections, concat_visualizations + class TensorFlowInfer: """ Implements TensorFlow inference of a saved model, following the same API as the TensorRTInfer class. @@ -36,45 +37,49 @@ def __init__(self, saved_model_path, preprocessor, detection_type, iou_threshold self.preprocessor = preprocessor self.detection_type = detection_type self.iou_threshold = iou_threshold - gpus = tf.config.experimental.list_physical_devices('GPU') + gpus = tf.config.experimental.list_physical_devices("GPU") for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) self.model = tf.saved_model.load(saved_model_path) - self.pred_fn = self.model.signatures['serving_default'] + self.pred_fn = self.model.signatures["serving_default"] # Setup I/O bindings self.inputs = [] fn_inputs = self.pred_fn.structured_input_signature[1] for i, input in enumerate(list(fn_inputs.values())): - self.inputs.append({ - 'index': i, - 'name': input.name, - 'dtype': np.dtype(input.dtype.as_numpy_dtype()), - 'shape': [1, 512, 512, 3], # This can be overridden later - }) + self.inputs.append( + { + "index": i, + "name": input.name, + "dtype": np.dtype(input.dtype.as_numpy_dtype()), + "shape": [1, 512, 512, 3], # This can be overridden later + } + ) self.outputs = [] fn_outputs = self.pred_fn.structured_outputs for i, output in enumerate(list(fn_outputs.values())): - self.outputs.append({ - 'index': i, - 'name': output.name, - 'dtype': np.dtype(output.dtype.as_numpy_dtype()), - 'shape': output.shape.as_list(), - }) + self.outputs.append( + { + "index": i, + "name": output.name, + "dtype": np.dtype(output.dtype.as_numpy_dtype()), + "shape": output.shape.as_list(), + } + ) def override_input_shape(self, input, shape): - self.inputs[input]['shape'] = shape + self.inputs[input]["shape"] = shape def input_spec(self): - return self.inputs[0]['shape'], self.inputs[0]['dtype'] + return self.inputs[0]["shape"], self.inputs[0]["dtype"] def output_spec(self): - return self.outputs[0]['shape'], self.outputs[0]['dtype'] + return self.outputs[0]["shape"], self.outputs[0]["dtype"] def infer(self, batch, scales=None, nms_threshold=None): # Process I/O and execute the network - input = {self.inputs[0]['name']: tf.convert_to_tensor(batch)} + input = {self.inputs[0]["name"]: tf.convert_to_tensor(batch)} output = self.pred_fn(**input) # Extract the results depending on what kind of saved model this is @@ -82,24 +87,24 @@ def infer(self, batch, scales=None, nms_threshold=None): scores = None classes = None - assert output['num_detections'] - num = int(output['num_detections'].numpy().flatten()[0]) - boxes = output['detection_boxes'].numpy()[:, 0:num, :] - scores = output['detection_scores'].numpy()[:, 0:num] - classes = output['detection_classes'].numpy()[:, 0:num] + assert output["num_detections"] + num = int(output["num_detections"].numpy().flatten()[0]) + boxes = output["detection_boxes"].numpy()[:, 0:num, :] + scores = output["detection_scores"].numpy()[:, 0:num] + classes = output["detection_classes"].numpy()[:, 0:num] # One additional output for segmentation masks if "detection_masks" in output: - masks = output['detection_masks'].numpy()[:, 0:num] + masks = output["detection_masks"].numpy()[:, 0:num] # Process the results detections = [[]] - normalized = (np.max(boxes) < 2.0) + normalized = np.max(boxes) < 2.0 for n in range(scores.shape[1]): # Depending on preprocessor, box scaling will be slightly different. if self.preprocessor == "fixed_shape_resizer": if scores[0][n] == 0.0: break - scale_x = self.inputs[0]['shape'][1] if normalized else 1.0 - scale_y = self.inputs[0]['shape'][2] if normalized else 1.0 + scale_x = self.inputs[0]["shape"][1] if normalized else 1.0 + scale_y = self.inputs[0]["shape"][2] if normalized else 1.0 if scales: scale_x /= scales[0][0] @@ -107,11 +112,11 @@ def infer(self, batch, scales=None, nms_threshold=None): if nms_threshold and scores[0][n] < nms_threshold: continue # Depending on detection type you need slightly different data. - if self.detection_type == 'bbox': + if self.detection_type == "bbox": mask = None # Segmentation is only supported with Mask R-CNN, which has # fixed_shape_resizer as image_resizer (lookup pipeline.config) - elif self.detection_type == 'segmentation': + elif self.detection_type == "segmentation": # Select a mask mask = masks[0][n] # Slight scaling, to get binary masks after float32 -> uint8 @@ -124,7 +129,7 @@ def infer(self, batch, scales=None, nms_threshold=None): mask = None if scores[0][n] == 0.0: break - scale = self.inputs[0]['shape'][2] if normalized else 1.0 + scale = self.inputs[0]["shape"][2] if normalized else 1.0 if scales: scale /= scales[0] scale_y = scale @@ -132,15 +137,17 @@ def infer(self, batch, scales=None, nms_threshold=None): if nms_threshold and scores[0][n] < nms_threshold: continue # Append to detections - detections[0].append({ - 'ymin': boxes[0][n][0] * scale_y, - 'xmin': boxes[0][n][1] * scale_x, - 'ymax': boxes[0][n][2] * scale_y, - 'xmax': boxes[0][n][3] * scale_x, - 'score': scores[0][n], - 'class': int(classes[0][n]) - 1, - 'mask': mask, - }) + detections[0].append( + { + "ymin": boxes[0][n][0] * scale_y, + "xmin": boxes[0][n][1] * scale_x, + "ymax": boxes[0][n][2] * scale_y, + "xmax": boxes[0][n][3] * scale_x, + "score": scores[0][n], + "class": int(classes[0][n]) - 1, + "mask": mask, + } + ) return detections @@ -150,7 +157,12 @@ def run(batcher, inferer, framework, nms_threshold=None): for batch, images, scales in batcher.get_batch(): res_detections += inferer.infer(batch, scales, nms_threshold) res_images += images - print("Processing {} / {} images ({})".format(batcher.image_index, batcher.num_images, framework), end="\r") + print( + "Processing {} / {} images ({})".format( + batcher.image_index, batcher.num_images, framework + ), + end="\r", + ) print() return res_images, res_detections @@ -159,34 +171,45 @@ def parse_annotations(annotations_path, detection_type): annotations = {} if annotations_path and os.path.exists(annotations_path): # Load annotations as coco, to extract segmentation masks - coco=COCO(annotations_path) + coco = COCO(annotations_path) with open(annotations_path) as f: ann_json = json.load(f) - for ann in ann_json['annotations']: - img_id = ann['image_id'] + for ann in ann_json["annotations"]: + img_id = ann["image_id"] if img_id not in annotations.keys(): annotations[img_id] = [] # Depending on detection type you need slightly different data. - if detection_type == 'bbox': + if detection_type == "bbox": mask = None # Segmentation is only supported with Mask R-CNN, which has # fixed_shape_resizer as image_resizer (lookup pipeline.config) - elif detection_type == 'segmentation': + elif detection_type == "segmentation": # Get np.array segmentation mask from annotation mask = coco.annToMask(ann) - annotations[img_id].append({ - 'ymin': ann['bbox'][1], - 'xmin': ann['bbox'][0], - 'ymax': ann['bbox'][1] + ann['bbox'][3], - 'xmax': ann['bbox'][0] + ann['bbox'][2], - 'score': -1, - 'class': ann['category_id'] - 1, - 'mask': mask, - }) + annotations[img_id].append( + { + "ymin": ann["bbox"][1], + "xmin": ann["bbox"][0], + "ymax": ann["bbox"][1] + ann["bbox"][3], + "xmax": ann["bbox"][0] + ann["bbox"][2], + "score": -1, + "class": ann["category_id"] - 1, + "mask": mask, + } + ) return annotations -def compare_images(tf_images, tf_detections, trt_images, trt_detections, output_dir, annotations_path, labels_path, detection_type): +def compare_images( + tf_images, + tf_detections, + trt_images, + trt_detections, + output_dir, + annotations_path, + labels_path, + detection_type, +): labels = [] if labels_path and os.path.exists(labels_path): with open(labels_path) as f: @@ -196,7 +219,9 @@ def compare_images(tf_images, tf_detections, trt_images, trt_detections, output_ annotations = parse_annotations(annotations_path, detection_type) count = 1 - for tf_img, tf_det, trt_img, trt_det in zip(tf_images, tf_detections, trt_images, trt_detections): + for tf_img, tf_det, trt_img, trt_det in zip( + tf_images, tf_detections, trt_images, trt_detections + ): vis = [] names = [] colors = [] @@ -214,60 +239,142 @@ def compare_images(tf_images, tf_detections, trt_images, trt_detections, output_ if img_id.isnumeric(): img_id = int(img_id) if img_id in annotations.keys(): - vis.append(visualize_detections(trt_img, None, annotations[img_id], labels)) + vis.append( + visualize_detections(trt_img, None, annotations[img_id], labels) + ) names.append("Ground Truth") colors.append("RoyalBlue") else: - print("Image {} does not have a COCO annotation, skipping ground truth visualization".format(trt_img)) + print( + "Image {} does not have a COCO annotation, skipping ground truth visualization".format( + trt_img + ) + ) basename = os.path.splitext(os.path.basename(tf_img))[0] output_path = os.path.join(output_dir, "{}.compare.png".format(basename)) os.makedirs(output_dir, exist_ok=True) concat_visualizations(vis, names, colors, output_path) - print("Processing {} / {} images (Visualization)".format(count, len(tf_images)), end="\r") + print( + "Processing {} / {} images (Visualization)".format(count, len(tf_images)), + end="\r", + ) count += 1 print() def main(args): - tf_infer = TensorFlowInfer(args.saved_model, args.preprocessor, args.detection_type, args.iou_threshold) - trt_infer = TensorRTInfer(args.engine, args.preprocessor, args.detection_type, args.iou_threshold) - - trt_batcher = ImageBatcher(args.input, *trt_infer.input_spec(), max_num_images=args.num_images, preprocessor=args.preprocessor) - tf_infer.override_input_shape(0, [1, trt_batcher.height, trt_batcher.width, 3]) # Same size input in TF as TRT - tf_batcher = ImageBatcher(args.input, *tf_infer.input_spec(), max_num_images=args.num_images, preprocessor=args.preprocessor) - - tf_images, tf_detections = run(tf_batcher, tf_infer, "TensorFlow", args.nms_threshold) - trt_images, trt_detections = run(trt_batcher, trt_infer, "TensorRT", args.nms_threshold) - - compare_images(tf_images, tf_detections, trt_images, trt_detections, args.output, args.annotations, args.labels, args.detection_type) + tf_infer = TensorFlowInfer( + args.saved_model, args.preprocessor, args.detection_type, args.iou_threshold + ) + trt_infer = TensorRTInfer( + args.engine, args.preprocessor, args.detection_type, args.iou_threshold + ) + + trt_batcher = ImageBatcher( + args.input, + *trt_infer.input_spec(), + max_num_images=args.num_images, + preprocessor=args.preprocessor + ) + tf_infer.override_input_shape( + 0, [1, trt_batcher.height, trt_batcher.width, 3] + ) # Same size input in TF as TRT + tf_batcher = ImageBatcher( + args.input, + *tf_infer.input_spec(), + max_num_images=args.num_images, + preprocessor=args.preprocessor + ) + + tf_images, tf_detections = run( + tf_batcher, tf_infer, "TensorFlow", args.nms_threshold + ) + trt_images, trt_detections = run( + trt_batcher, trt_infer, "TensorRT", args.nms_threshold + ) + + compare_images( + tf_images, + tf_detections, + trt_images, + trt_detections, + args.output, + args.annotations, + args.labels, + args.detection_type, + ) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with") - parser.add_argument("-m", "--saved_model", help="The TensorFlow saved model path to validate against") - parser.add_argument("-i", "--input", - help="The input to infer, either a single image path, or a directory of images") - parser.add_argument("-o", "--output", default=None, help="Directory where to save the visualization results") - parser.add_argument("-l", "--labels", default="./labels_coco.txt", - help="File to use for reading the class labels from, default: ./labels_coco.txt") - parser.add_argument("-a", "--annotations", default=None, - help="Set the path to the 'instances_val2017.json' file to use for COCO annotations, in which " - "case --input should point to the COCO val2017 dataset, default: not used") - parser.add_argument("-n", "--num_images", default=100, type=int, - help="The maximum number of images to visualize, default: 100") - parser.add_argument("-t", "--nms_threshold", type=float, - help="Override the score threshold for the NMS operation, if higher than the threshold in the model/engine.") - parser.add_argument("--iou_threshold", default=0.5, type=float, - help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0") - parser.add_argument("-d", "--detection_type", default="bbox", choices=["bbox", "segmentation"], - help="Detection type for COCO, either bbox or if you are using Mask R-CNN's instance segmentation - segmentation") - parser.add_argument("--preprocessor", default="fixed_shape_resizer", choices=["fixed_shape_resizer", "keep_aspect_ratio_resizer"], - help="Select the image preprocessor to use based on your pipeline.config, either 'fixed_shape_resizer' or 'keep_aspect_ratio_resizer', default: fixed_shape_resizer") + parser.add_argument( + "-m", + "--saved_model", + help="The TensorFlow saved model path to validate against", + ) + parser.add_argument( + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images", + ) + parser.add_argument( + "-o", + "--output", + default=None, + help="Directory where to save the visualization results", + ) + parser.add_argument( + "-l", + "--labels", + default="./labels_coco.txt", + help="File to use for reading the class labels from, default: ./labels_coco.txt", + ) + parser.add_argument( + "-a", + "--annotations", + default=None, + help="Set the path to the 'instances_val2017.json' file to use for COCO annotations, in which " + "case --input should point to the COCO val2017 dataset, default: not used", + ) + parser.add_argument( + "-n", + "--num_images", + default=100, + type=int, + help="The maximum number of images to visualize, default: 100", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, if higher than the threshold in the model/engine.", + ) + parser.add_argument( + "--iou_threshold", + default=0.5, + type=float, + help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0", + ) + parser.add_argument( + "-d", + "--detection_type", + default="bbox", + choices=["bbox", "segmentation"], + help="Detection type for COCO, either bbox or if you are using Mask R-CNN's instance segmentation - segmentation", + ) + parser.add_argument( + "--preprocessor", + default="fixed_shape_resizer", + choices=["fixed_shape_resizer", "keep_aspect_ratio_resizer"], + help="Select the image preprocessor to use based on your pipeline.config, either 'fixed_shape_resizer' or 'keep_aspect_ratio_resizer', default: fixed_shape_resizer", + ) args = parser.parse_args() - if not all([args.engine, args.saved_model, args.input, args.output, args.preprocessor]): + if not all( + [args.engine, args.saved_model, args.input, args.output, args.preprocessor] + ): parser.print_help() sys.exit(1) main(args) diff --git a/samples/python/tensorflow_object_detection_api/create_onnx.py b/samples/python/tensorflow_object_detection_api/create_onnx.py index 919cc8e6..fc75fa17 100644 --- a/samples/python/tensorflow_object_detection_api/create_onnx.py +++ b/samples/python/tensorflow_object_detection_api/create_onnx.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -32,7 +32,9 @@ from object_detection.utils import config_util except ImportError: print("Could not import TFOD modules. Maybe you did not install TFOD API") - print("Please install TensorFlow 2 Object Detection API, check https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2.md") + print( + "Please install TensorFlow 2 Object Detection API, check https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf2.md" + ) sys.exit(1) import onnx_utils @@ -55,14 +57,19 @@ def __init__(self, saved_model_path, pipeline_config_path): assert os.path.exists(saved_model_path) # Use tf2onnx to convert saved model to an initial ONNX graph. - graph_def, inputs, outputs = tf_loader.from_saved_model(saved_model_path, None, None, "serve", - ["serving_default"]) + graph_def, inputs, outputs = tf_loader.from_saved_model( + saved_model_path, None, None, "serve", ["serving_default"] + ) log.info("Loaded saved model from {}".format(saved_model_path)) with tf.Graph().as_default() as tf_graph: tf.import_graph_def(graph_def, name="") with tf_loader.tf_session(graph=tf_graph): - onnx_graph = tfonnx.process_tf_graph(tf_graph, input_names=inputs, output_names=outputs, opset=11) - onnx_model = optimizer.optimize_graph(onnx_graph).make_model("Converted from {}".format(saved_model_path)) + onnx_graph = tfonnx.process_tf_graph( + tf_graph, input_names=inputs, output_names=outputs, opset=11 + ) + onnx_model = optimizer.optimize_graph(onnx_graph).make_model( + "Converted from {}".format(saved_model_path) + ) self.graph = gs.import_onnx(onnx_model) assert self.graph log.info("TF2ONNX graph created successfully") @@ -71,61 +78,140 @@ def __init__(self, saved_model_path, pipeline_config_path): self.graph.fold_constants() # Pipeline config parsing. - pipeline_config = config_util.get_configs_from_pipeline_file(pipeline_config_path) + pipeline_config = config_util.get_configs_from_pipeline_file( + pipeline_config_path + ) # Get input resolution. - self.height, self.width = config_util.get_spatial_image_size(config_util.get_image_resizer_config(pipeline_config["model"])) + self.height, self.width = config_util.get_spatial_image_size( + config_util.get_image_resizer_config(pipeline_config["model"]) + ) # If your model is SSD, get characteristics accordingly from pipeline.config file. if pipeline_config["model"].HasField("ssd"): # Getting model characteristics. self.model = str(pipeline_config["model"].ssd.feature_extractor.type) - self.first_stage_nms_score_threshold = float(pipeline_config["model"].ssd.post_processing.batch_non_max_suppression.score_threshold) - self.first_stage_nms_iou_threshold = float(pipeline_config["model"].ssd.post_processing.batch_non_max_suppression.iou_threshold) - self.first_stage_max_proposals = int(pipeline_config["model"].ssd.post_processing.batch_non_max_suppression.max_detections_per_class) + self.first_stage_nms_score_threshold = float( + pipeline_config[ + "model" + ].ssd.post_processing.batch_non_max_suppression.score_threshold + ) + self.first_stage_nms_iou_threshold = float( + pipeline_config[ + "model" + ].ssd.post_processing.batch_non_max_suppression.iou_threshold + ) + self.first_stage_max_proposals = int( + pipeline_config[ + "model" + ].ssd.post_processing.batch_non_max_suppression.max_detections_per_class + ) # If your model is Faster R-CNN get it's characteristics from pipeline.config file. elif pipeline_config["model"].HasField("faster_rcnn"): # Getting model characteristics. - self.model = str(pipeline_config["model"].faster_rcnn.feature_extractor.type) + self.model = str( + pipeline_config["model"].faster_rcnn.feature_extractor.type + ) self.num_classes = pipeline_config["model"].faster_rcnn.num_classes - self.first_stage_nms_score_threshold = float(pipeline_config["model"].faster_rcnn.first_stage_nms_score_threshold) - self.first_stage_nms_iou_threshold = float(pipeline_config["model"].faster_rcnn.first_stage_nms_iou_threshold) - self.first_stage_max_proposals = int(pipeline_config["model"].faster_rcnn.first_stage_max_proposals) - self.first_stage_crop_size = int(pipeline_config["model"].faster_rcnn.initial_crop_size) - self.second_stage_nms_score_threshold = float(pipeline_config["model"].faster_rcnn.second_stage_post_processing.batch_non_max_suppression.score_threshold) - self.second_stage_iou_threshold = float(pipeline_config["model"].faster_rcnn.second_stage_post_processing.batch_non_max_suppression.iou_threshold) + self.first_stage_nms_score_threshold = float( + pipeline_config["model"].faster_rcnn.first_stage_nms_score_threshold + ) + self.first_stage_nms_iou_threshold = float( + pipeline_config["model"].faster_rcnn.first_stage_nms_iou_threshold + ) + self.first_stage_max_proposals = int( + pipeline_config["model"].faster_rcnn.first_stage_max_proposals + ) + self.first_stage_crop_size = int( + pipeline_config["model"].faster_rcnn.initial_crop_size + ) + self.second_stage_nms_score_threshold = float( + pipeline_config[ + "model" + ].faster_rcnn.second_stage_post_processing.batch_non_max_suppression.score_threshold + ) + self.second_stage_iou_threshold = float( + pipeline_config[ + "model" + ].faster_rcnn.second_stage_post_processing.batch_non_max_suppression.iou_threshold + ) self.mask_height = None self.mask_width = None self.matmul_crop_and_resize = False # Check what kind of Crop and Resize operation is used - if pipeline_config["model"].faster_rcnn.HasField("use_matmul_crop_and_resize"): - self.matmul_crop_and_resize = pipeline_config["model"].faster_rcnn.use_matmul_crop_and_resize + if pipeline_config["model"].faster_rcnn.HasField( + "use_matmul_crop_and_resize" + ): + self.matmul_crop_and_resize = pipeline_config[ + "model" + ].faster_rcnn.use_matmul_crop_and_resize # If model is Mask R-CNN, get final instance segmentation masks resolution. - if pipeline_config["model"].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.HasField("mask_height") and pipeline_config["model"].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.HasField("mask_width"): - self.mask_height = int(pipeline_config["model"].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.mask_height) - self.mask_width = int(pipeline_config["model"].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.mask_width) + if pipeline_config[ + "model" + ].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.HasField( + "mask_height" + ) and pipeline_config[ + "model" + ].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.HasField( + "mask_width" + ): + self.mask_height = int( + pipeline_config[ + "model" + ].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.mask_height + ) + self.mask_width = int( + pipeline_config[ + "model" + ].faster_rcnn.second_stage_box_predictor.mask_rcnn_box_predictor.mask_width + ) else: log.info("Given Model type is not supported") sys.exit(1) # List of supported models. - supported_models = ["ssd_mobilenet_v2_keras", "ssd_mobilenet_v1_fpn_keras", "ssd_mobilenet_v2_fpn_keras", "ssd_resnet50_v1_fpn_keras", - "ssd_resnet101_v1_fpn_keras", "ssd_resnet152_v1_fpn_keras", "faster_rcnn_resnet50_keras", "faster_rcnn_resnet101_keras", - "faster_rcnn_resnet152_keras", "faster_rcnn_inception_resnet_v2_keras"] + supported_models = [ + "ssd_mobilenet_v2_keras", + "ssd_mobilenet_v1_fpn_keras", + "ssd_mobilenet_v2_fpn_keras", + "ssd_resnet50_v1_fpn_keras", + "ssd_resnet101_v1_fpn_keras", + "ssd_resnet152_v1_fpn_keras", + "faster_rcnn_resnet50_keras", + "faster_rcnn_resnet101_keras", + "faster_rcnn_resnet152_keras", + "faster_rcnn_inception_resnet_v2_keras", + ] assert self.model in supported_models # Model characteristics. log.info("Model is {}".format(self.model)) log.info("Height is {}".format(self.height)) log.info("Width is {}".format(self.width)) - log.info("First NMS score threshold is {}".format(self.first_stage_nms_score_threshold)) - log.info("First NMS iou threshold is {}".format(self.first_stage_nms_iou_threshold)) + log.info( + "First NMS score threshold is {}".format( + self.first_stage_nms_score_threshold + ) + ) + log.info( + "First NMS iou threshold is {}".format(self.first_stage_nms_iou_threshold) + ) log.info("First NMS max proposals is {}".format(self.first_stage_max_proposals)) if "faster_rcnn" in self.model: log.info("Number of classes is {}".format(self.num_classes)) - log.info("Crop and Resize output size is {}".format(self.first_stage_crop_size)) - log.info("Second NMS score threshold is {}".format(self.second_stage_nms_score_threshold)) - log.info("Second NMS iou threshold is {}".format(self.second_stage_iou_threshold)) - log.info("Using MatMul Crop and Resize: {}".format(self.matmul_crop_and_resize)) + log.info( + "Crop and Resize output size is {}".format(self.first_stage_crop_size) + ) + log.info( + "Second NMS score threshold is {}".format( + self.second_stage_nms_score_threshold + ) + ) + log.info( + "Second NMS iou threshold is {}".format(self.second_stage_iou_threshold) + ) + log.info( + "Using MatMul Crop and Resize: {}".format(self.matmul_crop_and_resize) + ) if not (self.mask_height is None and self.mask_width is None): log.info("Mask height is {}".format(self.mask_height)) log.info("Mask width is {}".format(self.mask_width)) @@ -155,12 +241,16 @@ def sanitize(self): model = shape_inference.infer_shapes(model) self.graph = gs.import_onnx(model) except Exception as e: - log.info("Shape inference could not be performed at this time:\n{}".format(e)) + log.info( + "Shape inference could not be performed at this time:\n{}".format(e) + ) try: self.graph.fold_constants(fold_shapes=True) except TypeError as e: - log.error("This version of ONNX GraphSurgeon does not support folding shapes, please upgrade your " - "onnx_graphsurgeon module. Error:\n{}".format(e)) + log.error( + "This version of ONNX GraphSurgeon does not support folding shapes, please upgrade your " + "onnx_graphsurgeon module. Error:\n{}".format(e) + ) raise count_after = len(self.graph.nodes) @@ -189,11 +279,22 @@ def add_debug_output(self, debug): for n, name in enumerate(debug): if name not in tensors: log.warning("Could not find tensor '{}'".format(name)) - debug_tensor = gs.Variable(name="debug:{}".format(n), dtype=tensors[name].dtype) - debug_node = gs.Node(op="Identity", name="debug_{}".format(n), inputs=[tensors[name]], outputs=[debug_tensor]) + debug_tensor = gs.Variable( + name="debug:{}".format(n), dtype=tensors[name].dtype + ) + debug_node = gs.Node( + op="Identity", + name="debug_{}".format(n), + inputs=[tensors[name]], + outputs=[debug_tensor], + ) self.graph.nodes.append(debug_node) self.graph.outputs.append(debug_tensor) - log.info("Adding debug output '{}' for graph tensor '{}'".format(debug_tensor.name, name)) + log.info( + "Adding debug output '{}' for graph tensor '{}'".format( + debug_tensor.name, name + ) + ) def update_preprocessor(self, batch_size, input_format): """ @@ -208,46 +309,71 @@ def update_preprocessor(self, batch_size, input_format): assert input_format in ["NCHW", "NHWC"] input_shape = [None] * 4 if input_format == "NHWC": - input_shape = [self.batch_size, self.height, self.width, 3] + input_shape = [self.batch_size, self.height, self.width, 3] if input_format == "NCHW": - input_shape = [self.batch_size, 3, self.height, self.width] + input_shape = [self.batch_size, 3, self.height, self.width] self.graph.inputs[0].shape = input_shape self.graph.inputs[0].dtype = np.float32 self.graph.inputs[0].name = "input_tensor" self.sanitize() - log.info("ONNX graph input shape: {} [NCHW format set]".format(self.graph.inputs[0].shape)) + log.info( + "ONNX graph input shape: {} [NCHW format set]".format( + self.graph.inputs[0].shape + ) + ) # Find the initial nodes of the graph, whatever the input is first connected to, and disconnect them. - for node in [node for node in self.graph.nodes if self.graph.inputs[0] in node.inputs]: + for node in [ + node for node in self.graph.nodes if self.graph.inputs[0] in node.inputs + ]: node.inputs.clear() # Get input tensor. # Convert to NCHW format if needed. input_tensor = self.graph.inputs[0] if input_format == "NHWC": - input_tensor = self.graph.transpose("preprocessor/transpose", input_tensor, [0, 3, 1, 2]) + input_tensor = self.graph.transpose( + "preprocessor/transpose", input_tensor, [0, 3, 1, 2] + ) # Mobilenets' and inception's backbones preprocessor. - if 'mobilenet' in self.model or 'inception_resnet' in self.model: - mul_const = np.expand_dims(np.asarray([2 / 255], dtype=np.float32), axis=(0, 2, 3)) - sub_const = np.expand_dims(np.asarray([1], dtype=np.float32), axis=(0, 2, 3)) - mul_out = self.graph.op_with_const("Mul", "preprocessor/scale", input_tensor, mul_const) - sub_out = self.graph.op_with_const("Sub", "preprocessor/mean", mul_out, sub_const) + if "mobilenet" in self.model or "inception_resnet" in self.model: + mul_const = np.expand_dims( + np.asarray([2 / 255], dtype=np.float32), axis=(0, 2, 3) + ) + sub_const = np.expand_dims( + np.asarray([1], dtype=np.float32), axis=(0, 2, 3) + ) + mul_out = self.graph.op_with_const( + "Mul", "preprocessor/scale", input_tensor, mul_const + ) + sub_out = self.graph.op_with_const( + "Sub", "preprocessor/mean", mul_out, sub_const + ) # Resnet backbones' preprocessor. - elif 'resnet' in self.model: - sub_const = np.expand_dims(np.asarray([255 * 0.485, 255 * 0.456, 255 * 0.406], dtype=np.float32), axis=(0, 2, 3)) - sub_out = self.graph.op_with_const("Sub", "preprocessor/mean", input_tensor, sub_const) + elif "resnet" in self.model: + sub_const = np.expand_dims( + np.asarray([255 * 0.485, 255 * 0.456, 255 * 0.406], dtype=np.float32), + axis=(0, 2, 3), + ) + sub_out = self.graph.op_with_const( + "Sub", "preprocessor/mean", input_tensor, sub_const + ) # Backbone is not supported. else: - log.info("Given model's backbone is not supported, pre-processor algorithm can't be generated") + log.info( + "Given model's backbone is not supported, pre-processor algorithm can't be generated" + ) sys.exit(1) # Find first Conv node and connect preprocessor directly to it. conv_node = self.graph.find_node_by_op("Conv") - log.info("Found {} node '{}' as stem entry".format(conv_node.op, conv_node.name)) + log.info( + "Found {} node '{}' as stem entry".format(conv_node.op, conv_node.name) + ) conv_node.inputs[0] = sub_out[0] # Disconnect the last node in one of the preprocessing branches with first TensorListStack parent node. @@ -275,9 +401,17 @@ def find_head_end(self, head_name, descendant, end_op): # and the Box Net end node has the shape [batch_size, num_anchors, 4]. # These end nodes can be be found by searching for all end_op's operation nodes and checking if the node two # steps above in the graph has a name that begins with one of head_names for Class Net and Box Net respectively. - for node in [node for node in self.graph.nodes if node.op == descendant and head_name in node.name]: + for node in [ + node + for node in self.graph.nodes + if node.op == descendant and head_name in node.name + ]: target_node = self.graph.find_descendant_by_op(node, end_op) - log.info("Found {} node '{}' as the tip of {}".format(target_node.op, target_node.name, head_name)) + log.info( + "Found {} node '{}' as the tip of {}".format( + target_node.op, target_node.name, head_name + ) + ) return target_node def extract_anchors_tensor(self, split): @@ -314,14 +448,27 @@ def get_anchor(output_idx, op, depth=5): anchors_h = get_anchor(2, "Mul") anchors_w = get_anchor(3, "Mul") - batched_anchors = np.concatenate([anchors_y, anchors_x, anchors_h, anchors_w], axis=2) + batched_anchors = np.concatenate( + [anchors_y, anchors_x, anchors_h, anchors_w], axis=2 + ) # Identify num of anchors without repetitions. - num_anchors = int(batched_anchors.shape[1]/self.batch_size) + num_anchors = int(batched_anchors.shape[1] / self.batch_size) # Trim total number of anchors in order to not have copies introduced by growing number of batch_size. - anchors = batched_anchors[0:num_anchors,0:num_anchors] + anchors = batched_anchors[0:num_anchors, 0:num_anchors] return gs.Constant(name="nms/anchors:0", values=anchors) - def NMS(self, box_net_tensor, class_net_tensor, anchors_tensor, background_class, score_activation, iou_threshold, nms_score_threshold, user_threshold, nms_name=None): + def NMS( + self, + box_net_tensor, + class_net_tensor, + anchors_tensor, + background_class, + score_activation, + iou_threshold, + nms_score_threshold, + user_threshold, + nms_name=None, + ): # Helper function to create the NMS Plugin node with the selected inputs. # EfficientNMS_TRT TensorRT Plugin is suitable for our use case. # :param box_net_tensor: The box predictions from the Box Net. @@ -341,35 +488,53 @@ def NMS(self, box_net_tensor, class_net_tensor, anchors_tensor, background_class nms_name = "_" + nms_name # Set score threshold. - score_threshold = nms_score_threshold if user_threshold is None else user_threshold + score_threshold = ( + nms_score_threshold if user_threshold is None else user_threshold + ) # NMS Outputs. - nms_output_num_detections = gs.Variable(name="num_detections"+nms_name, dtype=np.int32, shape=[self.batch_size, 1]) - nms_output_boxes = gs.Variable(name="detection_boxes"+nms_name, dtype=np.float32, - shape=[self.batch_size, self.first_stage_max_proposals, 4]) - nms_output_scores = gs.Variable(name="detection_scores"+nms_name, dtype=np.float32, - shape=[self.batch_size, self.first_stage_max_proposals]) - nms_output_classes = gs.Variable(name="detection_classes"+nms_name, dtype=np.int32, - shape=[self.batch_size, self.first_stage_max_proposals]) + nms_output_num_detections = gs.Variable( + name="num_detections" + nms_name, dtype=np.int32, shape=[self.batch_size, 1] + ) + nms_output_boxes = gs.Variable( + name="detection_boxes" + nms_name, + dtype=np.float32, + shape=[self.batch_size, self.first_stage_max_proposals, 4], + ) + nms_output_scores = gs.Variable( + name="detection_scores" + nms_name, + dtype=np.float32, + shape=[self.batch_size, self.first_stage_max_proposals], + ) + nms_output_classes = gs.Variable( + name="detection_classes" + nms_name, + dtype=np.int32, + shape=[self.batch_size, self.first_stage_max_proposals], + ) - nms_outputs = [nms_output_num_detections, nms_output_boxes, nms_output_scores, nms_output_classes] + nms_outputs = [ + nms_output_num_detections, + nms_output_boxes, + nms_output_scores, + nms_output_classes, + ] # Plugin. self.graph.plugin( op="EfficientNMS_TRT", - name="nms/non_maximum_suppression"+nms_name, + name="nms/non_maximum_suppression" + nms_name, inputs=[box_net_tensor, class_net_tensor, anchors_tensor], outputs=nms_outputs, attrs={ - 'plugin_version': "1", - 'background_class': background_class, - 'max_output_boxes': self.first_stage_max_proposals, - 'score_threshold': max(0.01, score_threshold), - 'iou_threshold': iou_threshold, - 'score_activation': score_activation, - 'class_agnostic': False, - 'box_coding': 1, - } + "plugin_version": "1", + "background_class": background_class, + "max_output_boxes": self.first_stage_max_proposals, + "score_threshold": max(0.01, score_threshold), + "iou_threshold": iou_threshold, + "score_activation": score_activation, + "class_agnostic": False, + "box_coding": 1, + }, ) log.info("Created 'nms/non_maximum_suppression{}' NMS plugin".format(nms_name)) @@ -384,15 +549,26 @@ def CropAndResize(self, unsqeeze_input, relu_node_outputs, cnr_num): # CropAndResizePlugin requires 4th dimension of 1: [N, B, 4, 1], so # we need to add unsqeeze node to make tensor 4 dimensional. - unsqueeze_node = self.graph.unsqueeze("CNR/detection_boxes_unsqueeze_"+cnr_num, unsqeeze_input) + unsqueeze_node = self.graph.unsqueeze( + "CNR/detection_boxes_unsqueeze_" + cnr_num, unsqeeze_input + ) # CropAndResizePlugin's inputs feature_maps = relu_node_outputs rois = unsqueeze_node[0] # CropAndResize Outputs. - cnr_pfmap = gs.Variable(name="cnr/pfmap_"+cnr_num, dtype=np.float32, - shape=[self.batch_size, self.first_stage_max_proposals, feature_maps.shape[1], self.first_stage_crop_size, self.first_stage_crop_size]) + cnr_pfmap = gs.Variable( + name="cnr/pfmap_" + cnr_num, + dtype=np.float32, + shape=[ + self.batch_size, + self.first_stage_max_proposals, + feature_maps.shape[1], + self.first_stage_crop_size, + self.first_stage_crop_size, + ], + ) # Create the CropandResize Plugin node with the selected inputs. # Two inputs are given to the CropAndResize TensorRT node: @@ -400,19 +576,29 @@ def CropAndResize(self, unsqeeze_input, relu_node_outputs, cnr_num): # - The rois (clipped and normalized detection boxes resulting from NMS): [batch_size, featuremap, 4, 1] self.graph.plugin( op="CropAndResize", - name="cnr/crop_and_resize_"+cnr_num, + name="cnr/crop_and_resize_" + cnr_num, inputs=[feature_maps, rois], outputs=[cnr_pfmap], attrs={ - 'crop_width': self.first_stage_crop_size, - 'crop_height': self.first_stage_crop_size, - } + "crop_width": self.first_stage_crop_size, + "crop_height": self.first_stage_crop_size, + }, ) log.info("Created {} CropAndResize plugin".format(cnr_num)) # Reshape node that is preparing CropAndResize's pfmap output shape for MaxPool node that comes next. - reshape_shape = np.asarray([self.first_stage_max_proposals*self.batch_size, feature_maps.shape[1], self.first_stage_crop_size, self.first_stage_crop_size], dtype=np.int64) - reshape_node = self.graph.op_with_const("Reshape", "cnr/reshape_"+cnr_num, cnr_pfmap, reshape_shape) + reshape_shape = np.asarray( + [ + self.first_stage_max_proposals * self.batch_size, + feature_maps.shape[1], + self.first_stage_crop_size, + self.first_stage_crop_size, + ], + dtype=np.int64, + ) + reshape_node = self.graph.op_with_const( + "Reshape", "cnr/reshape_" + cnr_num, cnr_pfmap, reshape_shape + ) return reshape_node[0] @@ -423,7 +609,10 @@ def process_graph(self, first_nms_threshold=None, second_nms_threshold=None): :param first_nms_threshold: Override the 1st NMS score threshold value. If set to None, use the value in the graph. :param second_nms_threshold: Override the 2nd NMS score threshold value. If set to None, use the value in the graph. """ - def first_nms(background_class, score_activation, first_nms_threshold, nms_name=None): + + def first_nms( + background_class, score_activation, first_nms_threshold, nms_name=None + ): """ Updates the graph to replace the 1st NMS op by EfficientNMS_TRT TensorRT plugin node. :param background_class: Set EfficientNMS_TRT's background_class atribute. @@ -432,35 +621,67 @@ def first_nms(background_class, score_activation, first_nms_threshold, nms_name= :param nms_name: Set the NMS node name. """ # Supported models - ssd_models = ['ssd_mobilenet_v1_fpn_keras', 'ssd_mobilenet_v2_fpn_keras', 'ssd_resnet50_v1_fpn_keras', 'ssd_resnet101_v1_fpn_keras', 'ssd_resnet152_v1_fpn_keras'] - frcnn_models = ['faster_rcnn_resnet50_keras', 'faster_rcnn_resnet101_keras', 'faster_rcnn_resnet152_keras', 'faster_rcnn_inception_resnet_v2_keras'] + ssd_models = [ + "ssd_mobilenet_v1_fpn_keras", + "ssd_mobilenet_v2_fpn_keras", + "ssd_resnet50_v1_fpn_keras", + "ssd_resnet101_v1_fpn_keras", + "ssd_resnet152_v1_fpn_keras", + ] + frcnn_models = [ + "faster_rcnn_resnet50_keras", + "faster_rcnn_resnet101_keras", + "faster_rcnn_resnet152_keras", + "faster_rcnn_inception_resnet_v2_keras", + ] # Getting SSD's Class and Box Nets final tensors. if "ssd" in self.model: # Find the concat node at the end of the class net (multi-scale class predictor). - class_net_head_name = 'BoxPredictor/ConvolutionalClassHead_' if self.model == 'ssd_mobilenet_v2_keras' else 'WeightSharedConvolutionalBoxPredictor/WeightSharedConvolutionalClassHead' - class_net = self.find_head_end(class_net_head_name, "Transpose", "Concat") + class_net_head_name = ( + "BoxPredictor/ConvolutionalClassHead_" + if self.model == "ssd_mobilenet_v2_keras" + else "WeightSharedConvolutionalBoxPredictor/WeightSharedConvolutionalClassHead" + ) + class_net = self.find_head_end( + class_net_head_name, "Transpose", "Concat" + ) # Final Class Net tensor - class_net_tensor = self.graph.slice(class_net_head_name+"/slicer", class_net.outputs[0], 1, 91, 2)[0] # Remove background class + class_net_tensor = self.graph.slice( + class_net_head_name + "/slicer", class_net.outputs[0], 1, 91, 2 + )[ + 0 + ] # Remove background class # Find the concat or squeeze node at the end of the box net (multi-scale localization predictor). - if self.model == 'ssd_mobilenet_v2_keras': - box_net_head_name = 'BoxPredictor/ConvolutionalBoxHead_' - box_net = self.find_head_end(box_net_head_name, "Transpose", "Squeeze") + if self.model == "ssd_mobilenet_v2_keras": + box_net_head_name = "BoxPredictor/ConvolutionalBoxHead_" + box_net = self.find_head_end( + box_net_head_name, "Transpose", "Squeeze" + ) else: - box_net_head_name = 'WeightSharedConvolutionalBoxPredictor/WeightSharedConvolutionalBoxHead' - box_net = self.find_head_end(box_net_head_name, "Transpose", "Concat") + box_net_head_name = "WeightSharedConvolutionalBoxPredictor/WeightSharedConvolutionalBoxHead" + box_net = self.find_head_end( + box_net_head_name, "Transpose", "Concat" + ) box_net_output = box_net.outputs[0] # 0.1, 0.1, 0.2, 0.2 are localization head variance numbers, they scale box_net_output in order to get accurate coordinates. - variance_adj = np.expand_dims(np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1)) + variance_adj = np.expand_dims( + np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1) + ) # Final Box Net tensor. - box_net_tensor = self.graph.op_with_const("Mul", box_net_head_name+"/scale", box_net_output, variance_adj)[0] + box_net_tensor = self.graph.op_with_const( + "Mul", box_net_head_name + "/scale", box_net_output, variance_adj + )[0] # Getting Faster R-CNN's 1st Class and Box Nets tensors. elif "faster_rcnn" in self.model: # Identify Class Net and Box Net head names - head_names = ['FirstStageBoxPredictor/ConvolutionalClassHead_0/ClassPredictor','FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor'] + head_names = [ + "FirstStageBoxPredictor/ConvolutionalClassHead_0/ClassPredictor", + "FirstStageBoxPredictor/ConvolutionalBoxHead_0/BoxEncodingPredictor", + ] # Find the softmax node at the end of the class net (multi-scale class predictor). class_net = self.find_head_end(head_names[0], "Transpose", "Softmax") @@ -472,12 +693,18 @@ def first_nms(background_class, score_activation, first_nms_threshold, nms_name= # Final Box Net tensor. box_net_output = box_net.outputs[0] - #Insert a squeeze node - squeeze_node = self.graph.squeeze(head_names[1]+"/squeeze", box_net_output) + # Insert a squeeze node + squeeze_node = self.graph.squeeze( + head_names[1] + "/squeeze", box_net_output + ) # 0.1, 0.1, 0.2, 0.2 are localization head variance numbers, they scale box_net_output, in order to get accurate coordinates. - variance_adj = np.expand_dims(np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1)) + variance_adj = np.expand_dims( + np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1) + ) # Final Box Net tensor. - box_net_tensor = self.graph.op_with_const("Mul", head_names[1]+"/scale", squeeze_node, variance_adj)[0] + box_net_tensor = self.graph.op_with_const( + "Mul", head_names[1] + "/scale", squeeze_node, variance_adj + )[0] # Find the split node that separates the box net coordinates and feeds them into the box decoder. box_net_split = self.graph.find_descendant_by_op(box_net, "Split") @@ -487,7 +714,17 @@ def first_nms(background_class, score_activation, first_nms_threshold, nms_name= anchors_tensor = self.extract_anchors_tensor(box_net_split) # Create NMS node. - nms_outputs = self.NMS(box_net_tensor, class_net_tensor, anchors_tensor, background_class, score_activation, self.first_stage_nms_iou_threshold, self.first_stage_nms_score_threshold, first_nms_threshold, nms_name) + nms_outputs = self.NMS( + box_net_tensor, + class_net_tensor, + anchors_tensor, + background_class, + score_activation, + self.first_stage_nms_iou_threshold, + self.first_stage_nms_score_threshold, + first_nms_threshold, + nms_name, + ) # Return NMS's outputs. return nms_outputs @@ -501,26 +738,47 @@ def first_cnr(input): # Locate the last Relu node of the first backbone (pre 1st NMS). Relu node contains feature maps # necessary for CropAndResize plugin. relu_name = "StatefulPartitionedCall/model/" - relu_node = [node for node in self.graph.nodes if node.op == "Relu" and relu_name in node.name][-1] + relu_node = [ + node + for node in self.graph.nodes + if node.op == "Relu" and relu_name in node.name + ][-1] # Before passing 1st NMS's detection boxes (rois) to CropAndResize, we need to clip and normalize them. # Clipping happens for coordinates that are less than 0 and more than self.height. # Normalization is just divison of every coordinate by self.height. - clip_out = self.graph.clip("FirstNMS/detection_boxes_clipper", input, 0, self.height) - div_const = np.expand_dims(np.asarray([self.height, self.width, self.height, self.width], dtype=np.float32), axis=(0, 1)) - div_out = self.graph.op_with_const("Div", "FirstNMS/detection_boxes_normalizer", clip_out[0], div_const) + clip_out = self.graph.clip( + "FirstNMS/detection_boxes_clipper", input, 0, self.height + ) + div_const = np.expand_dims( + np.asarray( + [self.height, self.width, self.height, self.width], dtype=np.float32 + ), + axis=(0, 1), + ) + div_out = self.graph.op_with_const( + "Div", "FirstNMS/detection_boxes_normalizer", clip_out[0], div_const + ) # Linear transformation to convert box coordinates from (TopLeft, BottomRight) Corner encoding # to CenterSize encoding. 1st NMS boxes are multiplied by transformation matrix in order to # encode it into CenterSize format. - matmul_const = np.matrix('0.5 0 -1 0; 0 0.5 0 -1; 0.5 0 1 0; 0 0.5 0 1', dtype=np.float32) - matmul_out = self.graph.matmul("FirstNMS/detection_boxes_conversion", div_out[0], matmul_const) + matmul_const = np.matrix( + "0.5 0 -1 0; 0 0.5 0 -1; 0.5 0 1 0; 0 0.5 0 1", dtype=np.float32 + ) + matmul_out = self.graph.matmul( + "FirstNMS/detection_boxes_conversion", div_out[0], matmul_const + ) # Create Crop and Resize node. cnr_output = self.CropAndResize(div_out, relu_node.outputs[0], "first") # Find MaxPool node that summarizes CropAndResize structure. - maxpool_node = [node for node in self.graph.nodes if node.op == "MaxPool" and "MaxPool2D/MaxPool" in node.name][0] + maxpool_node = [ + node + for node in self.graph.nodes + if node.op == "MaxPool" and "MaxPool2D/MaxPool" in node.name + ][0] maxpool_node.inputs[0] = cnr_output # Return linear transformation node, it will be located between 1st and 2nd NMS, @@ -528,7 +786,13 @@ def first_cnr(input): # In case you are converting Mask R-CNN, feature maps are required for 2nd CropAndResize. return matmul_out[0], relu_node.outputs[0] - def second_nms(background_class, score_activation, encoded_boxes, second_nms_threshold, nms_name=None): + def second_nms( + background_class, + score_activation, + encoded_boxes, + second_nms_threshold, + nms_name=None, + ): """ Updates the graph to replace the 2nd (or final) NMS op by EfficientNMS_TRT TensorRT plugin node. :param background_class: Set EfficientNMS_TRT's background_class atribute. @@ -539,14 +803,20 @@ def second_nms(background_class, score_activation, encoded_boxes, second_nms_thr """ # Identify Class Net and Box Net head names. - second_head_names = ['StatefulPartitionedCall/mask_rcnn_keras_box_predictor/mask_rcnn_class_head/ClassPredictor_dense', - 'StatefulPartitionedCall/mask_rcnn_keras_box_predictor/mask_rcnn_box_head/BoxEncodingPredictor_dense'] + second_head_names = [ + "StatefulPartitionedCall/mask_rcnn_keras_box_predictor/mask_rcnn_class_head/ClassPredictor_dense", + "StatefulPartitionedCall/mask_rcnn_keras_box_predictor/mask_rcnn_box_head/BoxEncodingPredictor_dense", + ] # Find the softmax node at the end of the 2nd class net (multi-scale class predictor). - second_class_net = self.find_head_end(second_head_names[0], "MatMul", "Softmax") + second_class_net = self.find_head_end( + second_head_names[0], "MatMul", "Softmax" + ) # Faster R-CNN's slice operation to adjust third dimension of Class Net's last node tensor (adjusting class values). - slice_out = self.graph.slice(second_head_names[0]+"/slicer", second_class_net.outputs[0], 1, 91, 2) + slice_out = self.graph.slice( + second_head_names[0] + "/slicer", second_class_net.outputs[0], 1, 91, 2 + ) # Final Class Net tensor. second_class_net_tensor = slice_out[0] @@ -561,19 +831,56 @@ def second_nms(background_class, score_activation, encoded_boxes, second_nms_thr # If use_matmul_crop_and_resize in pipeline.config is set to True, expect: [batch_size, first_stage_max_proposals, 4]. # Else use_matmul_crop_and_resize is either False or absent, expect: [batch_size, first_stage_max_proposals, num_classes, 4] if self.matmul_crop_and_resize: - reshape_shape_second = np.asarray([self.batch_size, self.first_stage_max_proposals, second_box_net.outputs[0].shape[1]], dtype=np.int64) + reshape_shape_second = np.asarray( + [ + self.batch_size, + self.first_stage_max_proposals, + second_box_net.outputs[0].shape[1], + ], + dtype=np.int64, + ) else: - reshape_shape_second = np.asarray([self.batch_size, self.first_stage_max_proposals, self.num_classes, second_box_net.outputs[0].shape[1]/self.num_classes], dtype=np.int64) - reshape_node_second = self.graph.op_with_const("Reshape", second_head_names[1]+"/reshape", second_box_net_output, reshape_shape_second) + reshape_shape_second = np.asarray( + [ + self.batch_size, + self.first_stage_max_proposals, + self.num_classes, + second_box_net.outputs[0].shape[1] / self.num_classes, + ], + dtype=np.int64, + ) + reshape_node_second = self.graph.op_with_const( + "Reshape", + second_head_names[1] + "/reshape", + second_box_net_output, + reshape_shape_second, + ) # 0.1, 0.1, 0.2, 0.2 are localization head variance numbers, they scale second_box_net_output, in order to get accurate coordinates. - second_scale_adj = np.expand_dims(np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1)) - second_scale_out = self.graph.op_with_const("Mul", second_head_names[1]+"/scale_second", reshape_node_second[0], second_scale_adj) + second_scale_adj = np.expand_dims( + np.asarray([0.1, 0.1, 0.2, 0.2], dtype=np.float32), axis=(0, 1) + ) + second_scale_out = self.graph.op_with_const( + "Mul", + second_head_names[1] + "/scale_second", + reshape_node_second[0], + second_scale_adj, + ) # Final Box Net tensor. second_box_net_tensor = second_scale_out[0] # Create NMS node. - nms_outputs = self.NMS(second_box_net_tensor, second_class_net_tensor, encoded_boxes, background_class, score_activation, self.second_stage_iou_threshold, self.second_stage_nms_score_threshold, second_nms_threshold, nms_name) + nms_outputs = self.NMS( + second_box_net_tensor, + second_class_net_tensor, + encoded_boxes, + background_class, + score_activation, + self.second_stage_iou_threshold, + self.second_stage_nms_score_threshold, + second_nms_threshold, + nms_name, + ) return nms_outputs @@ -585,24 +892,36 @@ def second_cnr(feature_maps, second_nms_outputs): # Before passing 2nd NMS's detection boxes (rois) to second CropAndResize, we need to clip them. # Clipping happens for coordinates that are less than 0 and more than 1 (binary). - clip_out = self.graph.clip("SecondNMS/detection_boxes_clipper", second_nms_outputs[1], 0, 1) + clip_out = self.graph.clip( + "SecondNMS/detection_boxes_clipper", second_nms_outputs[1], 0, 1 + ) # Create Crop and Resize node. cnr_output = self.CropAndResize(clip_out, feature_maps, "second") # Find MaxPool node that summarizes CropAndResize structure - maxpool_node = [node for node in self.graph.nodes if node.op == "MaxPool" and "MaxPool2D/MaxPool_1" in node.name][0] + maxpool_node = [ + node + for node in self.graph.nodes + if node.op == "MaxPool" and "MaxPool2D/MaxPool_1" in node.name + ][0] maxpool_node.inputs[0] = cnr_output # Reshape node that is preparing 2nd NMS class outputs for Add node that comes next. # [self.batch_size, self.first_stage_max_proposals] -> [self.first_stage_max_proposals*self.batch_size] - class_reshape_shape = np.asarray([self.first_stage_max_proposals*self.batch_size], dtype=np.int64) - class_reshape_node = self.graph.op_with_const("Reshape", "Reshape_Class", second_nms_outputs[3], class_reshape_shape) + class_reshape_shape = np.asarray( + [self.first_stage_max_proposals * self.batch_size], dtype=np.int64 + ) + class_reshape_node = self.graph.op_with_const( + "Reshape", "Reshape_Class", second_nms_outputs[3], class_reshape_shape + ) # Find sigmoid node in the end of the network, applies sigmoid to get instance segmentation masks - last_sigmoid_node = self.graph.find_descendant_by_op(maxpool_node, "Sigmoid", 40) + last_sigmoid_node = self.graph.find_descendant_by_op( + maxpool_node, "Sigmoid", 40 + ) - if (self.num_classes > 1): + if self.num_classes > 1: # Find first ancestor of Sigmoid of operation type Add. This Add node is one of the Gather node inputs, # Gather node performs gather on 0th axis of data tensor and requires indices that set tesnors to be withing bounds, # this Add node provides the bounds for Gather. @@ -610,8 +929,21 @@ def second_cnr(feature_maps, second_nms_outputs): add_node.inputs[1] = class_reshape_node[0] # Final Reshape node, reshapes output of Sigmoid, important for various batch_size support. - final_reshape_shape = np.asarray([self.batch_size, self.first_stage_max_proposals, self.mask_height, self.mask_width], dtype=np.int64) - final_reshape_node = self.graph.op_with_const("Reshape", "Reshape_Final_Masks", last_sigmoid_node.outputs[0], final_reshape_shape) + final_reshape_shape = np.asarray( + [ + self.batch_size, + self.first_stage_max_proposals, + self.mask_height, + self.mask_width, + ], + dtype=np.int64, + ) + final_reshape_node = self.graph.op_with_const( + "Reshape", + "Reshape_Final_Masks", + last_sigmoid_node.outputs[0], + final_reshape_shape, + ) final_reshape_node[0].dtype = np.float32 final_reshape_node[0].name = "detection_masks" @@ -623,17 +955,27 @@ def second_cnr(feature_maps, second_nms_outputs): self.graph.outputs = first_nms(-1, True, first_nms_threshold) self.sanitize() # If your model is Faster R-CNN, you will need 2 NMS nodes with CropAndResize in between. - elif "faster_rcnn" in self.model and self.mask_height is None and self.mask_width is None: + elif ( + "faster_rcnn" in self.model + and self.mask_height is None + and self.mask_width is None + ): first_nms_outputs = first_nms(0, False, first_nms_threshold, "rpn") first_cnr_output, feature_maps = first_cnr(first_nms_outputs[1]) # Set graph outputs. - self.graph.outputs = second_nms(-1, False, first_cnr_output, second_nms_threshold) + self.graph.outputs = second_nms( + -1, False, first_cnr_output, second_nms_threshold + ) self.sanitize() # Mask R-CNN - elif "faster_rcnn" in self.model and not (self.mask_height is None and self.mask_width is None): + elif "faster_rcnn" in self.model and not ( + self.mask_height is None and self.mask_width is None + ): first_nms_outputs = first_nms(0, False, first_nms_threshold, "rpn") first_cnr_output, feature_maps = first_cnr(first_nms_outputs[1]) - second_nms_outputs = second_nms(-1, False, first_cnr_output, second_nms_threshold) + second_nms_outputs = second_nms( + -1, False, first_cnr_output, second_nms_threshold + ) second_cnr_output = second_cnr(feature_maps, second_nms_outputs) # Append segmentation head output. second_nms_outputs.append(second_cnr_output) @@ -655,20 +997,57 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-p", "--pipeline_config", help="Pipeline configuration file to load", type=str) - parser.add_argument("-m", "--saved_model", help="The TensorFlow saved model directory to load", type=str) - parser.add_argument("-o", "--onnx", help="The output ONNX model file to write", type=str) - parser.add_argument("-b", "--batch_size", help="Batch size for the model", type=int, default=1) - parser.add_argument("-t1", "--first_nms_threshold", help="Override the score threshold for the 1st NMS operation", type=float) - parser.add_argument("-t2", "--second_nms_threshold", help="Override the score threshold for the 2nd NMS operation", type=float) - parser.add_argument("-d", "--debug", action='append', help="Add an extra output to debug a particular node") - parser.add_argument("-f", "--input_format", default="NHWC", choices=["NHWC", "NCHW"], - help="Set the input shape of the graph, as comma-separated dimensions in NCHW or NHWC format, default: NHWC") - parser.add_argument("--tf2onnx", help="The path where to save the intermediate ONNX graph generated by tf2onnx, " - "useful for debugging purposes, default: not saved", type=str) + parser.add_argument( + "-p", "--pipeline_config", help="Pipeline configuration file to load", type=str + ) + parser.add_argument( + "-m", + "--saved_model", + help="The TensorFlow saved model directory to load", + type=str, + ) + parser.add_argument( + "-o", "--onnx", help="The output ONNX model file to write", type=str + ) + parser.add_argument( + "-b", "--batch_size", help="Batch size for the model", type=int, default=1 + ) + parser.add_argument( + "-t1", + "--first_nms_threshold", + help="Override the score threshold for the 1st NMS operation", + type=float, + ) + parser.add_argument( + "-t2", + "--second_nms_threshold", + help="Override the score threshold for the 2nd NMS operation", + type=float, + ) + parser.add_argument( + "-d", + "--debug", + action="append", + help="Add an extra output to debug a particular node", + ) + parser.add_argument( + "-f", + "--input_format", + default="NHWC", + choices=["NHWC", "NCHW"], + help="Set the input shape of the graph, as comma-separated dimensions in NCHW or NHWC format, default: NHWC", + ) + parser.add_argument( + "--tf2onnx", + help="The path where to save the intermediate ONNX graph generated by tf2onnx, " + "useful for debugging purposes, default: not saved", + type=str, + ) args = parser.parse_args() if not all([args.pipeline_config, args.saved_model, args.onnx]): parser.print_help() - print("\nThese arguments are required: --pipeline_config, --saved_model and --onnx") + print( + "\nThese arguments are required: --pipeline_config, --saved_model and --onnx" + ) sys.exit(1) main(args) diff --git a/samples/python/tensorflow_object_detection_api/eval_coco.py b/samples/python/tensorflow_object_detection_api/eval_coco.py index 5086c660..f04c17f3 100644 --- a/samples/python/tensorflow_object_detection_api/eval_coco.py +++ b/samples/python/tensorflow_object_detection_api/eval_coco.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,23 +24,35 @@ from infer import TensorRTInfer from image_batcher import ImageBatcher + def main(args): try: import object_detection.metrics.coco_tools as coco_tools except ImportError: - print("Could not import the 'object_detection.metrics.coco_tools' module from TFOD. Maybe you did not install TFOD API") - print("Please install TensorFlow 2 Object Detection API, check https://tensorflow-object-detection-api-tutorial.readthedocs.io/en/latest/install.html") + print( + "Could not import the 'object_detection.metrics.coco_tools' module from TFOD. Maybe you did not install TFOD API" + ) + print( + "Please install TensorFlow 2 Object Detection API, check https://tensorflow-object-detection-api-tutorial.readthedocs.io/en/latest/install.html" + ) sys.exit(1) - trt_infer = TensorRTInfer(args.engine, args.preprocessor, args.detection_type, args.iou_threshold) - batcher = ImageBatcher(args.input, *trt_infer.input_spec(), preprocessor=args.preprocessor) + trt_infer = TensorRTInfer( + args.engine, args.preprocessor, args.detection_type, args.iou_threshold + ) + batcher = ImageBatcher( + args.input, *trt_infer.input_spec(), preprocessor=args.preprocessor + ) # Read annotations json as dictionary. with open(args.annotations) as f: data = json.load(f) groundtruth = coco_tools.COCOWrapper(data, detection_type=args.detection_type) detections_list = [] for batch, images, scales in batcher.get_batch(): - print("Processing Image {} / {}".format(batcher.image_index, batcher.num_images), end="\r") + print( + "Processing Image {} / {}".format(batcher.image_index, batcher.num_images), + end="\r", + ) detections = trt_infer.infer(batch, scales, args.nms_threshold) for i in range(len(images)): # Get inference image resolution. @@ -49,43 +61,52 @@ def main(args): for n in range(len(detections[i])): source_id = int(os.path.splitext(os.path.basename(images[i]))[0]) det = detections[i][n] - if args.detection_type == 'bbox': + if args.detection_type == "bbox": coco_det = { - 'image_id': source_id, - 'category_id': det['class']+1, # adjust class num - 'bbox': [det['xmin'], det['ymin'], det['xmax'] - det['xmin'], det['ymax'] - det['ymin']], - 'score': det['score'] + "image_id": source_id, + "category_id": det["class"] + 1, # adjust class num + "bbox": [ + det["xmin"], + det["ymin"], + det["xmax"] - det["xmin"], + det["ymax"] - det["ymin"], + ], + "score": det["score"], } detections_list.append(coco_det) - elif args.detection_type == 'segmentation': + elif args.detection_type == "segmentation": # Get detection bbox resolution. - det_width = round(det['xmax'] - det['xmin']) - det_height = round(det['ymax'] - det['ymin']) + det_width = round(det["xmax"] - det["xmin"]) + det_height = round(det["ymax"] - det["ymin"]) # Create an image out of predicted mask array. - small_mask = Image.fromarray(det['mask']) + small_mask = Image.fromarray(det["mask"]) # Upsample mask to detection bbox's size. - mask = small_mask.resize((det_width, det_height), resample=Image.BILINEAR) + mask = small_mask.resize( + (det_width, det_height), resample=Image.BILINEAR + ) # Create an original image sized template for correct mask placement. pad = Image.new("L", (im_width, im_height)) # Place your mask according to detection bbox placement. - pad.paste(mask, (round(det['xmin']), (round(det['ymin'])))) + pad.paste(mask, (round(det["xmin"]), (round(det["ymin"])))) # Reconvert mask into numpy array for evaluation. padded_mask = np.array(pad) # Add one more dimension of 1, this is required by ExportSingleImageDetectionMasksToCoco. final_mask = padded_mask[np.newaxis, :, :] # Export detection mask to COCO format - coco_mask = coco_tools.ExportSingleImageDetectionMasksToCoco(image_id=source_id, - category_id_set=set(list(range(1,91))), - detection_classes=np.array([det['class']+1]), - detection_scores=np.array([det['score']]), - detection_masks=final_mask) + coco_mask = coco_tools.ExportSingleImageDetectionMasksToCoco( + image_id=source_id, + category_id_set=set(list(range(1, 91))), + detection_classes=np.array([det["class"] + 1]), + detection_scores=np.array([det["score"]]), + detection_masks=final_mask, + ) detections_list.append(coco_mask[0]) # Finish evalutions. detections = groundtruth.LoadAnnotations(detections_list) - if args.detection_type == 'bbox': + if args.detection_type == "bbox": evaluator = coco_tools.COCOEvalWrapper(groundtruth, detections, iou_type="bbox") - elif args.detection_type == 'segmentation': + elif args.detection_type == "segmentation": evaluator = coco_tools.COCOEvalWrapper(groundtruth, detections, iou_type="segm") evaluator.ComputeMetrics() @@ -93,20 +114,46 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("-e", "--engine", help="The TensorRT engine to infer with.") - parser.add_argument("-i", "--input", - help="The input to infer, either a single image path, or a directory of images.") - parser.add_argument("-d", "--detection_type", default="bbox", choices=["bbox", "segmentation"], - help="Detection type for COCO, either bbox or if you are using Mask R-CNN's instance segmentation - segmentation.") - parser.add_argument("-a", "--annotations", help="Set the json file to use for COCO instance annotations.") - parser.add_argument("-t", "--nms_threshold", type=float, - help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.") - parser.add_argument("--iou_threshold", default=0.5, type=float, - help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0.") - parser.add_argument("--preprocessor", default="fixed_shape_resizer", choices=["fixed_shape_resizer", "keep_aspect_ratio_resizer"], - help="Select the image preprocessor to use based on your pipeline.config, either 'fixed_shape_resizer' or 'keep_aspect_ratio_resizer', default: fixed_shape_resizer.") + parser.add_argument( + "-i", + "--input", + help="The input to infer, either a single image path, or a directory of images.", + ) + parser.add_argument( + "-d", + "--detection_type", + default="bbox", + choices=["bbox", "segmentation"], + help="Detection type for COCO, either bbox or if you are using Mask R-CNN's instance segmentation - segmentation.", + ) + parser.add_argument( + "-a", + "--annotations", + help="Set the json file to use for COCO instance annotations.", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.", + ) + parser.add_argument( + "--iou_threshold", + default=0.5, + type=float, + help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0.", + ) + parser.add_argument( + "--preprocessor", + default="fixed_shape_resizer", + choices=["fixed_shape_resizer", "keep_aspect_ratio_resizer"], + help="Select the image preprocessor to use based on your pipeline.config, either 'fixed_shape_resizer' or 'keep_aspect_ratio_resizer', default: fixed_shape_resizer.", + ) args = parser.parse_args() if not all([args.engine, args.input, args.annotations, args.preprocessor]): parser.print_help() - print("\nThese arguments are required: --engine --input --output and --preprocessor") + print( + "\nThese arguments are required: --engine --input --output and --preprocessor" + ) sys.exit(1) main(args) diff --git a/samples/python/tensorflow_object_detection_api/image_batcher.py b/samples/python/tensorflow_object_detection_api/image_batcher.py index c40e86c8..202e998d 100644 --- a/samples/python/tensorflow_object_detection_api/image_batcher.py +++ b/samples/python/tensorflow_object_detection_api/image_batcher.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,7 +27,15 @@ class ImageBatcher: Creates batches of pre-processed images. """ - def __init__(self, input, shape, dtype, max_num_images=None, exact_batches=False, preprocessor="fixed_shape_resizer"): + def __init__( + self, + input, + shape, + dtype, + max_num_images=None, + exact_batches=False, + preprocessor="fixed_shape_resizer", + ): """ :param input: The input directory to read images from. :param shape: The tensor shape of the batch to prepare, either in NCHW or NHWC format. @@ -45,10 +53,16 @@ def __init__(self, input, shape, dtype, max_num_images=None, exact_batches=False extensions = [".jpg", ".jpeg", ".png", ".bmp"] def is_image(path): - return os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + return ( + os.path.isfile(path) and os.path.splitext(path)[1].lower() in extensions + ) if os.path.isdir(input): - self.images = [os.path.join(input, f) for f in os.listdir(input) if is_image(os.path.join(input, f))] + self.images = [ + os.path.join(input, f) + for f in os.listdir(input) + if is_image(os.path.join(input, f)) + ] self.images.sort() elif os.path.isfile(input): if is_image(input): @@ -85,7 +99,7 @@ def is_image(path): if self.num_images < 1: print("Not enough images to create batches") sys.exit(1) - self.images = self.images[0:self.num_images] + self.images = self.images[0 : self.num_images] # Subdivide the list of images into batches self.num_batches = 1 + int((self.num_images - 1) / self.batch_size) @@ -133,7 +147,10 @@ def resize_pad(image, pad_color=(0, 0, 0)): return image, scale elif self.preprocessor == "keep_aspect_ratio_resizer": scale = 1.0 / max(width_scale, height_scale) - image = image.resize((round(width * scale), round(height * scale)), resample=Image.BILINEAR) + image = image.resize( + (round(width * scale), round(height * scale)), + resample=Image.BILINEAR, + ) pad = Image.new("RGB", (self.width, self.height)) pad.paste(pad_color, [0, 0, self.width, self.height]) pad.paste(image) @@ -141,9 +158,12 @@ def resize_pad(image, pad_color=(0, 0, 0)): scale = None image = Image.open(image_path) - image = image.convert(mode='RGB') - if self.preprocessor == "fixed_shape_resizer" or self.preprocessor == "keep_aspect_ratio_resizer": - #Resize & Pad with ImageNet mean values and keep as [0,255] Normalization + image = image.convert(mode="RGB") + if ( + self.preprocessor == "fixed_shape_resizer" + or self.preprocessor == "keep_aspect_ratio_resizer" + ): + # Resize & Pad with ImageNet mean values and keep as [0,255] Normalization image, scale = resize_pad(image, (124, 116, 104)) image = np.asarray(image, dtype=self.dtype) else: diff --git a/samples/python/tensorflow_object_detection_api/infer.py b/samples/python/tensorflow_object_detection_api/infer.py index 3ea07863..298b7a0c 100644 --- a/samples/python/tensorflow_object_detection_api/infer.py +++ b/samples/python/tensorflow_object_detection_api/infer.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -28,6 +28,7 @@ from image_batcher import ImageBatcher from visualize import visualize_detections + class TensorRTInfer: """ Implements inference for the Model TensorRT engine. @@ -68,11 +69,11 @@ def __init__(self, engine_path, preprocessor, detection_type, iou_threshold): size *= s allocation = common.cuda_call(cudart.cudaMalloc(size)) binding = { - 'index': i, - 'name': name, - 'dtype': np.dtype(trt.nptype(dtype)), - 'shape': list(shape), - 'allocation': allocation, + "index": i, + "name": name, + "dtype": np.dtype(trt.nptype(dtype)), + "shape": list(shape), + "allocation": allocation, } self.allocations.append(allocation) if is_input: @@ -90,7 +91,7 @@ def input_spec(self): Get the specs for the input tensor of the network. Useful to prepare memory allocations. :return: Two items, the shape of the input tensor and its (numpy) datatype. """ - return self.inputs[0]['shape'], self.inputs[0]['dtype'] + return self.inputs[0]["shape"], self.inputs[0]["dtype"] def output_spec(self): """ @@ -99,7 +100,7 @@ def output_spec(self): """ specs = [] for o in self.outputs: - specs.append((o['shape'], o['dtype'])) + specs.append((o["shape"], o["dtype"])) return specs def infer(self, batch, scales=None, nms_threshold=None): @@ -117,10 +118,12 @@ def infer(self, batch, scales=None, nms_threshold=None): outputs.append(np.zeros(shape, dtype)) # Process I/O and execute the network - common.memcpy_host_to_device(self.inputs[0]['allocation'], np.ascontiguousarray(batch)) + common.memcpy_host_to_device( + self.inputs[0]["allocation"], np.ascontiguousarray(batch) + ) self.context.execute_v2(self.allocations) for o in range(len(outputs)): - common.memcpy_device_to_host(outputs[o], self.outputs[o]['allocation']) + common.memcpy_device_to_host(outputs[o], self.outputs[o]["allocation"]) # Process the results nums = outputs[0] @@ -131,14 +134,14 @@ def infer(self, batch, scales=None, nms_threshold=None): if len(outputs) == 5: masks = outputs[4] detections = [] - normalized = (np.max(boxes) < 2.0) + normalized = np.max(boxes) < 2.0 for i in range(self.batch_size): detections.append([]) for n in range(int(nums[i])): # Depending on preprocessor, box scaling will be slightly different. if self.preprocessor == "fixed_shape_resizer": - scale_x = self.inputs[0]['shape'][1] if normalized else 1.0 - scale_y = self.inputs[0]['shape'][2] if normalized else 1.0 + scale_x = self.inputs[0]["shape"][1] if normalized else 1.0 + scale_y = self.inputs[0]["shape"][2] if normalized else 1.0 if scales and i < len(scales): scale_x /= scales[i][0] @@ -146,11 +149,11 @@ def infer(self, batch, scales=None, nms_threshold=None): if nms_threshold and scores[i][n] < nms_threshold: continue # Depending on detection type you need slightly different data. - if self.detection_type == 'bbox': + if self.detection_type == "bbox": mask = None # Segmentation is only supported with Mask R-CNN, which has # fixed_shape_resizer as image_resizer (lookup pipeline.config) - elif self.detection_type == 'segmentation': + elif self.detection_type == "segmentation": # Select a mask mask = masks[i][n] # Slight scaling, to get binary masks after float32 -> uint8 @@ -161,7 +164,7 @@ def infer(self, batch, scales=None, nms_threshold=None): elif self.preprocessor == "keep_aspect_ratio_resizer": # No segmentation models with keep_aspect_ratio_resizer mask = None - scale = self.inputs[0]['shape'][2] if normalized else 1.0 + scale = self.inputs[0]["shape"][2] if normalized else 1.0 if scales and i < len(scales): scale /= scales[i] scale_y = scale @@ -169,15 +172,17 @@ def infer(self, batch, scales=None, nms_threshold=None): if nms_threshold and scores[i][n] < nms_threshold: continue # Append to detections - detections[i].append({ - 'ymin': boxes[i][n][0] * scale_y, - 'xmin': boxes[i][n][1] * scale_x, - 'ymax': boxes[i][n][2] * scale_y, - 'xmax': boxes[i][n][3] * scale_x, - 'score': scores[i][n], - 'class': int(classes[i][n]), - 'mask': mask, - }) + detections[i].append( + { + "ymin": boxes[i][n][0] * scale_y, + "xmin": boxes[i][n][1] * scale_x, + "ymax": boxes[i][n][2] * scale_y, + "xmax": boxes[i][n][3] * scale_x, + "score": scores[i][n], + "class": int(classes[i][n]), + "mask": mask, + } + ) return detections @@ -191,10 +196,17 @@ def main(args): for i, label in enumerate(f): labels.append(label.strip()) - trt_infer = TensorRTInfer(args.engine, args.preprocessor, args.detection_type, args.iou_threshold) - batcher = ImageBatcher(args.input, *trt_infer.input_spec(), preprocessor=args.preprocessor) + trt_infer = TensorRTInfer( + args.engine, args.preprocessor, args.detection_type, args.iou_threshold + ) + batcher = ImageBatcher( + args.input, *trt_infer.input_spec(), preprocessor=args.preprocessor + ) for batch, images, scales in batcher.get_batch(): - print("Processing Image {} / {}".format(batcher.image_index, batcher.num_images), end="\r") + print( + "Processing Image {} / {}".format(batcher.image_index, batcher.num_images), + end="\r", + ) detections = trt_infer.infer(batch, scales, args.nms_threshold) for i in range(len(images)): basename = os.path.splitext(os.path.basename(images[i]))[0] @@ -204,7 +216,14 @@ def main(args): # Text Results output_results = "" for d in detections[i]: - line = [d['xmin'], d['ymin'], d['xmax'], d['ymax'], d['score'], d['class']] + line = [ + d["xmin"], + d["ymin"], + d["xmax"], + d["ymax"], + d["score"], + d["class"], + ] output_results += "\t".join([str(f) for f in line]) + "\n" with open(os.path.join(args.output, "{}.txt".format(basename)), "w") as f: f.write(output_results) @@ -214,22 +233,54 @@ def main(args): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-e", "--engine", default=None, help="The serialized TensorRT engine") - parser.add_argument("-i", "--input", default=None, help="Path to the image or directory to process") - parser.add_argument("-o", "--output", default=None, help="Directory where to save the visualization results") - parser.add_argument("-l", "--labels", default="./labels_coco.txt", - help="File to use for reading the class labels from, default: ./labels_coco.txt") - parser.add_argument("-d", "--detection_type", default="bbox", choices=["bbox", "segmentation"], - help="Detection type for COCO, either bbox or if you are using Mask R-CNN's instance segmentation - segmentation") - parser.add_argument("-t", "--nms_threshold", type=float, - help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.") - parser.add_argument("--iou_threshold", default=0.5, type=float, - help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0") - parser.add_argument("--preprocessor", default="fixed_shape_resizer", choices=["fixed_shape_resizer", "keep_aspect_ratio_resizer"], - help="Select the image preprocessor to use based on your pipeline.config, either 'fixed_shape_resizer' or 'keep_aspect_ratio_resizer', default: fixed_shape_resizer") + parser.add_argument( + "-e", "--engine", default=None, help="The serialized TensorRT engine" + ) + parser.add_argument( + "-i", "--input", default=None, help="Path to the image or directory to process" + ) + parser.add_argument( + "-o", + "--output", + default=None, + help="Directory where to save the visualization results", + ) + parser.add_argument( + "-l", + "--labels", + default="./labels_coco.txt", + help="File to use for reading the class labels from, default: ./labels_coco.txt", + ) + parser.add_argument( + "-d", + "--detection_type", + default="bbox", + choices=["bbox", "segmentation"], + help="Detection type for COCO, either bbox or if you are using Mask R-CNN's instance segmentation - segmentation", + ) + parser.add_argument( + "-t", + "--nms_threshold", + type=float, + help="Override the score threshold for the NMS operation, if higher than the threshold in the engine.", + ) + parser.add_argument( + "--iou_threshold", + default=0.5, + type=float, + help="Select the IoU threshold for the mask segmentation. Range is 0 to 1. Pixel values more than threshold will become 1, less 0", + ) + parser.add_argument( + "--preprocessor", + default="fixed_shape_resizer", + choices=["fixed_shape_resizer", "keep_aspect_ratio_resizer"], + help="Select the image preprocessor to use based on your pipeline.config, either 'fixed_shape_resizer' or 'keep_aspect_ratio_resizer', default: fixed_shape_resizer", + ) args = parser.parse_args() if not all([args.engine, args.input, args.output, args.preprocessor]): parser.print_help() - print("\nThese arguments are required: --engine --input --output and --preprocessor") + print( + "\nThese arguments are required: --engine --input --output and --preprocessor" + ) sys.exit(1) main(args) diff --git a/samples/python/tensorflow_object_detection_api/onnx_utils.py b/samples/python/tensorflow_object_detection_api/onnx_utils.py index b539197a..07819328 100644 --- a/samples/python/tensorflow_object_detection_api/onnx_utils.py +++ b/samples/python/tensorflow_object_detection_api/onnx_utils.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,6 +23,7 @@ logging.getLogger("SSDHelper").setLevel(logging.INFO) log = logging.getLogger("SSDHelper") + @gs.Graph.register() def op_with_const(self, op, name, input, value): """ @@ -35,7 +36,10 @@ def op_with_const(self, op, name, input, value): 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"]) + return self.layer( + name=name, op=op, inputs=[input_tensor, const], outputs=[name + ":0"] + ) + @gs.Graph.register() def matmul(self, name, input, value): @@ -48,7 +52,10 @@ def matmul(self, name, input, value): 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"]) + 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): @@ -61,9 +68,19 @@ def clip(self, name, input, clip_min, clip_max): """ 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"]) + 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): @@ -79,10 +96,22 @@ def slice(self, name, input, starts, ends, axes): 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"]) + 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]): @@ -96,7 +125,14 @@ def unsqueeze(self, name, input, axes=[3]): """ 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}) + 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]): @@ -110,7 +146,14 @@ def squeeze(self, name, input, axes=[2]): """ 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}) + return self.layer( + name=name, + op="Squeeze", + inputs=[input_tensor], + outputs=[name + ":0"], + attrs={"axes": axes}, + ) + @gs.Graph.register() def transpose(self, name, input, perm): @@ -124,7 +167,14 @@ def transpose(self, name, input, perm): """ 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}) + return self.layer( + name=name, + op="Transpose", + inputs=[input_tensor], + outputs=[name + ":0"], + attrs={"perm": perm}, + ) + @gs.Graph.register() def sigmoid(self, name, input): @@ -137,7 +187,10 @@ def sigmoid(self, name, input): """ 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"]) + return self.layer( + name=name, op="Sigmoid", inputs=[input_tensor], outputs=[name + ":0"] + ) + @gs.Graph.register() def plugin(self, op, name, inputs, outputs, attrs): @@ -154,7 +207,10 @@ def plugin(self, op, name, inputs, outputs, attrs): """ input_tensors = inputs if type(inputs) is list else [inputs] log.debug("Created TRT Plugin node '{}': {}".format(name, attrs)) - return self.layer(op=op, name=name, inputs=input_tensors, outputs=outputs, attrs=attrs) + return self.layer( + op=op, name=name, inputs=input_tensors, outputs=outputs, attrs=attrs + ) + @gs.Graph.register() def find_node_by_op(self, op): @@ -169,6 +225,7 @@ def find_node_by_op(self, op): return node return None + @gs.Graph.register() def find_descendant_by_op(self, node, op, depth=10): """ diff --git a/samples/python/tensorflow_object_detection_api/visualize.py b/samples/python/tensorflow_object_detection_api/visualize.py index f3e4ffc1..f88ed6f0 100644 --- a/samples/python/tensorflow_object_detection_api/visualize.py +++ b/samples/python/tensorflow_object_detection_api/visualize.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,6 +16,7 @@ # import numpy as np + np.set_printoptions(threshold=np.inf, suppress=True) import PIL.Image as Image @@ -24,95 +25,228 @@ import PIL.ImageFilter as ImageFilter - -COLORS = ['GoldenRod', 'MediumTurquoise', 'GreenYellow', 'SteelBlue', 'DarkSeaGreen', 'SeaShell', 'LightGrey', - 'IndianRed', 'DarkKhaki', 'LawnGreen', 'WhiteSmoke', 'Peru', 'LightCoral', 'FireBrick', 'OldLace', - 'LightBlue', 'SlateGray', 'OliveDrab', 'NavajoWhite', 'PaleVioletRed', 'SpringGreen', 'AliceBlue', 'Violet', - 'DeepSkyBlue', 'Red', 'MediumVioletRed', 'PaleTurquoise', 'Tomato', 'Azure', 'Yellow', 'Cornsilk', - 'Aquamarine', 'CadetBlue', 'CornflowerBlue', 'DodgerBlue', 'Olive', 'Orchid', 'LemonChiffon', 'Sienna', - 'OrangeRed', 'Orange', 'DarkSalmon', 'Magenta', 'Wheat', 'Lime', 'GhostWhite', 'SlateBlue', 'Aqua', - 'MediumAquaMarine', 'LightSlateGrey', 'MediumSeaGreen', 'SandyBrown', 'YellowGreen', 'Plum', 'FloralWhite', - 'LightPink', 'Thistle', 'DarkViolet', 'Pink', 'Crimson', 'Chocolate', 'DarkGrey', 'Ivory', 'PaleGreen', - 'DarkGoldenRod', 'LavenderBlush', 'SlateGrey', 'DeepPink', 'Gold', 'Cyan', 'LightSteelBlue', 'MediumPurple', - 'ForestGreen', 'DarkOrange', 'Tan', 'Salmon', 'PaleGoldenRod', 'LightGreen', 'LightSlateGray', 'HoneyDew', - 'Fuchsia', 'LightSeaGreen', 'DarkOrchid', 'Green', 'Chartreuse', 'LimeGreen', 'AntiqueWhite', 'Beige', - 'Gainsboro', 'Bisque', 'SaddleBrown', 'Silver', 'Lavender', 'Teal', 'LightCyan', 'PapayaWhip', 'Purple', - 'Coral', 'BurlyWood', 'LightGray', 'Snow', 'MistyRose', 'PowderBlue', 'DarkCyan', 'White', 'Turquoise', - 'MediumSlateBlue', 'PeachPuff', 'Moccasin', 'LightSalmon', 'SkyBlue', 'Khaki', 'MediumSpringGreen', - 'BlueViolet', 'MintCream', 'Linen', 'SeaGreen', 'HotPink', 'LightYellow', 'BlanchedAlmond', 'RoyalBlue', - 'RosyBrown', 'MediumOrchid', 'DarkTurquoise', 'LightGoldenRodYellow', 'LightSkyBlue'] +COLORS = [ + "GoldenRod", + "MediumTurquoise", + "GreenYellow", + "SteelBlue", + "DarkSeaGreen", + "SeaShell", + "LightGrey", + "IndianRed", + "DarkKhaki", + "LawnGreen", + "WhiteSmoke", + "Peru", + "LightCoral", + "FireBrick", + "OldLace", + "LightBlue", + "SlateGray", + "OliveDrab", + "NavajoWhite", + "PaleVioletRed", + "SpringGreen", + "AliceBlue", + "Violet", + "DeepSkyBlue", + "Red", + "MediumVioletRed", + "PaleTurquoise", + "Tomato", + "Azure", + "Yellow", + "Cornsilk", + "Aquamarine", + "CadetBlue", + "CornflowerBlue", + "DodgerBlue", + "Olive", + "Orchid", + "LemonChiffon", + "Sienna", + "OrangeRed", + "Orange", + "DarkSalmon", + "Magenta", + "Wheat", + "Lime", + "GhostWhite", + "SlateBlue", + "Aqua", + "MediumAquaMarine", + "LightSlateGrey", + "MediumSeaGreen", + "SandyBrown", + "YellowGreen", + "Plum", + "FloralWhite", + "LightPink", + "Thistle", + "DarkViolet", + "Pink", + "Crimson", + "Chocolate", + "DarkGrey", + "Ivory", + "PaleGreen", + "DarkGoldenRod", + "LavenderBlush", + "SlateGrey", + "DeepPink", + "Gold", + "Cyan", + "LightSteelBlue", + "MediumPurple", + "ForestGreen", + "DarkOrange", + "Tan", + "Salmon", + "PaleGoldenRod", + "LightGreen", + "LightSlateGray", + "HoneyDew", + "Fuchsia", + "LightSeaGreen", + "DarkOrchid", + "Green", + "Chartreuse", + "LimeGreen", + "AntiqueWhite", + "Beige", + "Gainsboro", + "Bisque", + "SaddleBrown", + "Silver", + "Lavender", + "Teal", + "LightCyan", + "PapayaWhip", + "Purple", + "Coral", + "BurlyWood", + "LightGray", + "Snow", + "MistyRose", + "PowderBlue", + "DarkCyan", + "White", + "Turquoise", + "MediumSlateBlue", + "PeachPuff", + "Moccasin", + "LightSalmon", + "SkyBlue", + "Khaki", + "MediumSpringGreen", + "BlueViolet", + "MintCream", + "Linen", + "SeaGreen", + "HotPink", + "LightYellow", + "BlanchedAlmond", + "RoyalBlue", + "RosyBrown", + "MediumOrchid", + "DarkTurquoise", + "LightGoldenRodYellow", + "LightSkyBlue", +] -#Overlay mask with transparency on top of the image. +# Overlay mask with transparency on top of the image. def overlay(image, mask, color, alpha_transparency=0.5): for channel in range(3): - image[:, :, channel] = np.where(mask == 1, - image[:, :, channel] * - (1 - alpha_transparency) + alpha_transparency * color[channel] * 255, - image[:, :, channel]) + image[:, :, channel] = np.where( + mask == 1, + image[:, :, channel] * (1 - alpha_transparency) + + alpha_transparency * color[channel] * 255, + image[:, :, channel], + ) return image + def visualize_detections(image_path, output_path, detections, labels=[]): - image = Image.open(image_path).convert(mode='RGB') + image = Image.open(image_path).convert(mode="RGB") # Get image dimensions. im_width, im_height = image.size line_width = 2 font = ImageFont.load_default() for d in detections: - color = COLORS[d['class'] % len(COLORS)] + color = COLORS[d["class"] % len(COLORS)] # Dynamically convert PIL color into RGB numpy array. - pixel_color = Image.new("RGB",(1, 1), color) + pixel_color = Image.new("RGB", (1, 1), color) # Normalize. - np_color = (np.asarray(pixel_color)[0][0])/255 + np_color = (np.asarray(pixel_color)[0][0]) / 255 # Process TF and TRT instance segmentation masks. - if isinstance(d['mask'], np.ndarray) and d['mask'].shape == (33, 33): + if isinstance(d["mask"], np.ndarray) and d["mask"].shape == (33, 33): # Get detection bbox resolution. - det_width = round(d['xmax'] - d['xmin']) - det_height = round(d['ymax'] - d['ymin']) + det_width = round(d["xmax"] - d["xmin"]) + det_height = round(d["ymax"] - d["ymin"]) # Create an image out of predicted mask array. - small_mask = Image.fromarray(d['mask']) + small_mask = Image.fromarray(d["mask"]) # Upsample mask to detection bbox's size. mask = small_mask.resize((det_width, det_height), resample=Image.BILINEAR) # Create an original image sized template for correct mask placement. pad = Image.new("L", (im_width, im_height)) # Place your mask according to detection bbox placement. - pad.paste(mask, (round(d['xmin']), (round(d['ymin'])))) + pad.paste(mask, (round(d["xmin"]), (round(d["ymin"])))) # Reconvert mask into numpy array for evaluation. padded_mask = np.array(pad) - #Creat np.array from original image, copy in order to modify. + # Creat np.array from original image, copy in order to modify. image_copy = np.asarray(image).copy() # Image with overlaid mask. masked_image = overlay(image_copy, padded_mask, np_color) # Reconvert back to PIL. image = Image.fromarray(masked_image) # Separate clause for ground truth instance segmentation masks. - elif isinstance(d['mask'], np.ndarray): - #Creat np.array from original image, copy in order to modify. + elif isinstance(d["mask"], np.ndarray): + # Creat np.array from original image, copy in order to modify. image_copy = np.asarray(image).copy() # Image with overlaid mask. - masked_image = overlay(image_copy, d['mask'], np_color) + masked_image = overlay(image_copy, d["mask"], np_color) # Reconvert back to PIL image = Image.fromarray(masked_image) # Bbox lines. draw = ImageDraw.Draw(image) - draw.line([(d['xmin'], d['ymin']), (d['xmin'], d['ymax']), (d['xmax'], d['ymax']), (d['xmax'], d['ymin']), - (d['xmin'], d['ymin'])], width=line_width, fill=color) - label = "Class {}".format(d['class']) - if d['class'] < len(labels): - label = "{}".format(labels[d['class']]) - score = d['score'] + draw.line( + [ + (d["xmin"], d["ymin"]), + (d["xmin"], d["ymax"]), + (d["xmax"], d["ymax"]), + (d["xmax"], d["ymin"]), + (d["xmin"], d["ymin"]), + ], + width=line_width, + fill=color, + ) + label = "Class {}".format(d["class"]) + if d["class"] < len(labels): + label = "{}".format(labels[d["class"]]) + score = d["score"] text = "{}: {}%".format(label, int(100 * score)) if score < 0: text = label left, top, right, bottom = font.getbbox(text) text_width, text_height = right - left, bottom - top - text_bottom = max(text_height, d['ymin']) - text_left = d['xmin'] + text_bottom = max(text_height, d["ymin"]) + text_left = d["xmin"] margin = np.ceil(0.05 * text_height) - draw.rectangle([(text_left, text_bottom - text_height - 2 * margin), (text_left + text_width, text_bottom)], - fill=color) - draw.text((text_left + margin, text_bottom - text_height - margin), text, fill='black', font=font) + draw.rectangle( + [ + (text_left, text_bottom - text_height - 2 * margin), + (text_left + text_width, text_bottom), + ], + fill=color, + ) + draw.text( + (text_left + margin, text_bottom - text_height - margin), + text, + fill="black", + font=font, + ) if output_path is None: return image image.save(output_path) @@ -123,7 +257,12 @@ def draw_text(draw, font, text, width, bar_height, offset, color): left, top, right, bottom = font.getbbox(text) text_width, text_height = right - left, bottom - top draw.rectangle([(offset, 0), (offset + width, bar_height)], fill=color) - draw.text((offset + (width - text_width) / 2, text_height - text_height / 2), text, fill='black', font=font) + draw.text( + (offset + (width - text_width) / 2, text_height - text_height / 2), + text, + fill="black", + font=font, + ) bar_height = 18 width = 0 @@ -132,7 +271,7 @@ def draw_text(draw, font, text, width, bar_height, offset, color): width += im.width height = max(height, im.height) - concat = Image.new('RGB', (width, height + bar_height)) + concat = Image.new("RGB", (width, height + bar_height)) draw = ImageDraw.Draw(concat) font = ImageFont.load_default() diff --git a/samples/python/yolov3_onnx/data_processing.py b/samples/python/yolov3_onnx/data_processing.py index 8a68145f..998cbc5f 100644 --- a/samples/python/yolov3_onnx/data_processing.py +++ b/samples/python/yolov3_onnx/data_processing.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,7 +31,9 @@ def load_label_categories(label_file_path): return categories -LABEL_FILE_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "coco_labels.txt") +LABEL_FILE_PATH = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "coco_labels.txt" +) ALL_CATEGORIES = load_label_categories(LABEL_FILE_PATH) # Let's make sure that there are 80 classes, as expected for the COCO data set: @@ -103,7 +105,14 @@ def _shuffle_and_normalize(self, image): class PostprocessYOLO(object): """Class for post-processing the three outputs tensors from YOLOv3-608.""" - def __init__(self, yolo_masks, yolo_anchors, obj_threshold, nms_threshold, yolo_input_resolution): + def __init__( + self, + yolo_masks, + yolo_anchors, + obj_threshold, + nms_threshold, + yolo_input_resolution, + ): """Initialize with all values that will be kept when processing several frames. Assuming 3 outputs of the network in the case of (large) YOLOv3. @@ -135,7 +144,9 @@ def process(self, outputs, resolution_raw): for output in outputs: outputs_reshaped.append(self._reshape_output(output)) - boxes, categories, confidences = self._process_yolo_output(outputs_reshaped, resolution_raw) + boxes, categories, confidences = self._process_yolo_output( + outputs_reshaped, resolution_raw + ) return boxes, categories, confidences @@ -311,8 +322,12 @@ def _nms_boxes(self, boxes, box_confidences): keep.append(i) xx1 = np.maximum(x_coord[i], x_coord[ordered[1:]]) yy1 = np.maximum(y_coord[i], y_coord[ordered[1:]]) - xx2 = np.minimum(x_coord[i] + width[i], x_coord[ordered[1:]] + width[ordered[1:]]) - yy2 = np.minimum(y_coord[i] + height[i], y_coord[ordered[1:]] + height[ordered[1:]]) + xx2 = np.minimum( + x_coord[i] + width[i], x_coord[ordered[1:]] + width[ordered[1:]] + ) + yy2 = np.minimum( + y_coord[i] + height[i], y_coord[ordered[1:]] + height[ordered[1:]] + ) width1 = np.maximum(0.0, xx2 - xx1 + 1) height1 = np.maximum(0.0, yy2 - yy1 + 1) diff --git a/samples/python/yolov3_onnx/onnx_to_tensorrt.py b/samples/python/yolov3_onnx/onnx_to_tensorrt.py index c7e54d16..2ba322bc 100644 --- a/samples/python/yolov3_onnx/onnx_to_tensorrt.py +++ b/samples/python/yolov3_onnx/onnx_to_tensorrt.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -18,23 +18,25 @@ from __future__ import print_function +import os +import sys + import numpy as np import tensorrt as trt - +from data_processing import ALL_CATEGORIES, PostprocessYOLO, PreprocessYOLO from PIL import ImageDraw -from data_processing import PreprocessYOLO, PostprocessYOLO, ALL_CATEGORIES - -import sys, os - sys.path.insert(1, os.path.join(sys.path[0], "..")) -import common from downloader import getFilePath +import common + TRT_LOGGER = trt.Logger() -def draw_bboxes(image_raw, bboxes, confidences, categories, all_categories, bbox_color="blue"): +def draw_bboxes( + image_raw, bboxes, confidences, categories, all_categories, bbox_color="blue" +): """Draw the bounding boxes on the original input image and return it. Keyword arguments: @@ -58,7 +60,11 @@ def draw_bboxes(image_raw, bboxes, confidences, categories, all_categories, bbox bottom = min(image_raw.height, np.floor(y_coord + height + 0.5).astype(int)) draw.rectangle(((left, top), (right, bottom)), outline=bbox_color) - draw.text((left, top - 12), "{0} {1:.2f}".format(all_categories[category], score), fill=bbox_color) + draw.text( + (left, top - 12), + "{0} {1:.2f}".format(all_categories[category], score), + fill=bbox_color, + ) return image_raw @@ -69,17 +75,21 @@ def get_engine(onnx_file_path, engine_file_path=""): def build_engine(): """Takes an ONNX file and creates a TensorRT engine to run inference with""" with trt.Builder(TRT_LOGGER) as builder, builder.create_network( - 0 + 0 ) as network, builder.create_builder_config() as config, trt.OnnxParser( network, TRT_LOGGER ) as parser, trt.Runtime( TRT_LOGGER ) as runtime: - config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 28) # 256MiB + config.set_memory_pool_limit( + trt.MemoryPoolType.WORKSPACE, 1 << 28 + ) # 256MiB # Parse model file if not os.path.exists(onnx_file_path): print( - "ONNX file {} not found, please run yolov3_to_onnx.py first to generate it.".format(onnx_file_path) + "ONNX file {} not found, please run yolov3_to_onnx.py first to generate it.".format( + onnx_file_path + ) ) exit(0) print("Loading ONNX file from path {}...".format(onnx_file_path)) @@ -93,7 +103,11 @@ def build_engine(): # The actual yolov3.onnx is generated with batch size 64. Reshape input to batch size 1 network.get_input(0).shape = [1, 3, 608, 608] print("Completed parsing of ONNX file") - print("Building an engine from file {}; this may take a while...".format(onnx_file_path)) + print( + "Building an engine from file {}; this may take a while...".format( + onnx_file_path + ) + ) plan = builder.build_serialized_network(network, config) engine = runtime.deserialize_cuda_engine(plan) print("Completed creating Engine") @@ -131,19 +145,34 @@ def main(): output_shapes = [(1, 255, 19, 19), (1, 255, 38, 38), (1, 255, 76, 76)] # Do inference with TensorRT trt_outputs = [] - with get_engine(onnx_file_path, engine_file_path) as engine, engine.create_execution_context() as context: + with get_engine( + onnx_file_path, engine_file_path + ) as engine, engine.create_execution_context() as context: inputs, outputs, bindings, stream = common.allocate_buffers(engine) # Do inference print("Running inference on image {}...".format(input_image_path)) # Set host input to the image. The common.do_inference function will copy the input to the GPU before executing. inputs[0].host = image - trt_outputs = common.do_inference(context, engine=engine, bindings=bindings, inputs=inputs, outputs=outputs, stream=stream) + trt_outputs = common.do_inference( + context, + engine=engine, + bindings=bindings, + inputs=inputs, + outputs=outputs, + stream=stream, + ) # Before doing post-processing, we need to reshape the outputs as the common.do_inference will give us flat arrays. - trt_outputs = [output.reshape(shape) for output, shape in zip(trt_outputs, output_shapes)] + trt_outputs = [ + output.reshape(shape) for output, shape in zip(trt_outputs, output_shapes) + ] postprocessor_args = { - "yolo_masks": [(6, 7, 8), (3, 4, 5), (0, 1, 2)], # A list of 3 three-dimensional tuples for the YOLO masks + "yolo_masks": [ + (6, 7, 8), + (3, 4, 5), + (0, 1, 2), + ], # A list of 3 three-dimensional tuples for the YOLO masks "yolo_anchors": [ (10, 13), (16, 30), @@ -168,7 +197,11 @@ def main(): obj_detected_img = draw_bboxes(image_raw, boxes, scores, classes, ALL_CATEGORIES) output_image_path = "dog_bboxes.png" obj_detected_img.save(output_image_path, "PNG") - print("Saved image with bounding boxes of detected objects to {}.".format(output_image_path)) + print( + "Saved image with bounding boxes of detected objects to {}.".format( + output_image_path + ) + ) # Free host and device memory used for inputs and outputs common.free_buffers(inputs, outputs, stream) diff --git a/samples/python/yolov3_onnx/yolov3_to_onnx.py b/samples/python/yolov3_onnx/yolov3_to_onnx.py index 59f8b3a6..ffd9d19f 100644 --- a/samples/python/yolov3_onnx/yolov3_to_onnx.py +++ b/samples/python/yolov3_onnx/yolov3_to_onnx.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -128,7 +128,9 @@ def _parse_params(self, param_line): param_value = layer_indexes elif isinstance(param_value_raw, str) and not param_value_raw.isalpha(): condition_param_value_positive = param_value_raw.isdigit() - condition_param_value_negative = param_value_raw[0] == "-" and param_value_raw[1:].isdigit() + condition_param_value_negative = ( + param_value_raw[0] == "-" and param_value_raw[1:].isdigit() + ) if condition_param_value_positive or condition_param_value_negative: param_value = int(param_value_raw) else: @@ -276,17 +278,29 @@ def load_conv_weights(self, conv_params): initializer = list() inputs = list() if conv_params.batch_normalize: - bias_init, bias_input = self._create_param_tensors(conv_params, "bn", "bias") - bn_scale_init, bn_scale_input = self._create_param_tensors(conv_params, "bn", "scale") - bn_mean_init, bn_mean_input = self._create_param_tensors(conv_params, "bn", "mean") - bn_var_init, bn_var_input = self._create_param_tensors(conv_params, "bn", "var") + bias_init, bias_input = self._create_param_tensors( + conv_params, "bn", "bias" + ) + bn_scale_init, bn_scale_input = self._create_param_tensors( + conv_params, "bn", "scale" + ) + bn_mean_init, bn_mean_input = self._create_param_tensors( + conv_params, "bn", "mean" + ) + bn_var_init, bn_var_input = self._create_param_tensors( + conv_params, "bn", "var" + ) initializer.extend([bn_scale_init, bias_init, bn_mean_init, bn_var_init]) inputs.extend([bn_scale_input, bias_input, bn_mean_input, bn_var_input]) else: - bias_init, bias_input = self._create_param_tensors(conv_params, "conv", "bias") + bias_init, bias_input = self._create_param_tensors( + conv_params, "conv", "bias" + ) initializer.append(bias_init) inputs.append(bias_input) - conv_init, conv_input = self._create_param_tensors(conv_params, "conv", "weights") + conv_init, conv_input = self._create_param_tensors( + conv_params, "conv", "weights" + ) initializer.append(conv_init) inputs.append(conv_input) return initializer, inputs @@ -299,7 +313,11 @@ def _open_weights_file(self, weights_file_path): """ weights_file = open(weights_file_path, "rb") length_header = 5 - np.ndarray(shape=(length_header,), dtype="int32", buffer=weights_file.read(length_header * 4)) + np.ndarray( + shape=(length_header,), + dtype="int32", + buffer=weights_file.read(length_header * 4), + ) return weights_file def _create_param_tensors(self, conv_params, param_category, suffix): @@ -312,10 +330,16 @@ def _create_param_tensors(self, conv_params, param_category, suffix): suffix -- a string determining the sub-type of above param_category (e.g., 'weights' or 'bias') """ - param_name, param_data, param_data_shape = self._load_one_param_type(conv_params, param_category, suffix) + param_name, param_data, param_data_shape = self._load_one_param_type( + conv_params, param_category, suffix + ) - initializer_tensor = helper.make_tensor(param_name, TensorProto.FLOAT, param_data_shape, param_data) - input_tensor = helper.make_tensor_value_info(param_name, TensorProto.FLOAT, param_data_shape) + initializer_tensor = helper.make_tensor( + param_name, TensorProto.FLOAT, param_data_shape, param_data + ) + input_tensor = helper.make_tensor_value_info( + param_name, TensorProto.FLOAT, param_data_shape + ) return initializer_tensor, input_tensor def _load_one_param_type(self, conv_params, param_category, suffix): @@ -337,7 +361,11 @@ def _load_one_param_type(self, conv_params, param_category, suffix): elif suffix == "bias": param_shape = [channels_out] param_size = np.product(np.array(param_shape)) - param_data = np.ndarray(shape=param_shape, dtype="float32", buffer=self.weights_file.read(param_size * 4)) + param_data = np.ndarray( + shape=param_shape, + dtype="float32", + buffer=self.weights_file.read(param_size * 4), + ) param_data = param_data.flatten().astype(float) return param_name, param_data, param_shape @@ -385,7 +413,9 @@ def build_onnx_graph(self, layer_configs, weights_file_path, verbose=True): output_dims = [ self.batch_size, ] + self.output_tensors[tensor_name] - output_tensor = helper.make_tensor_value_info(tensor_name, TensorProto.FLOAT, output_dims) + output_tensor = helper.make_tensor_value_info( + tensor_name, TensorProto.FLOAT, output_dims + ) outputs.append(output_tensor) inputs = [self.input_tensor] weight_loader = WeightLoader(weights_file_path) @@ -395,20 +425,30 @@ def build_onnx_graph(self, layer_configs, weights_file_path, verbose=True): _, layer_type = layer_name.split("_", 1) params = self.param_dict[layer_name] if layer_type == "convolutional": - initializer_layer, inputs_layer = weight_loader.load_conv_weights(params) + initializer_layer, inputs_layer = weight_loader.load_conv_weights( + params + ) initializer.extend(initializer_layer) inputs.extend(inputs_layer) elif layer_type == "upsample": - initializer_layer, inputs_layer = weight_loader.load_resize_scales(params) + initializer_layer, inputs_layer = weight_loader.load_resize_scales( + params + ) initializer.extend(initializer_layer) inputs.extend(inputs_layer) del weight_loader self.graph_def = helper.make_graph( - nodes=self._nodes, name="YOLOv3-608", inputs=inputs, outputs=outputs, initializer=initializer + nodes=self._nodes, + name="YOLOv3-608", + inputs=inputs, + outputs=outputs, + initializer=initializer, ) if verbose: print(helper.printable_graph(self.graph_def)) - model_def = helper.make_model(self.graph_def, producer_name="NVIDIA TensorRT sample") + model_def = helper.make_model( + self.graph_def, producer_name="NVIDIA TensorRT sample" + ) return model_def def _make_onnx_node(self, layer_name, layer_dict): @@ -423,8 +463,12 @@ def _make_onnx_node(self, layer_name, layer_dict): layer_type = layer_dict["type"] if self.input_tensor is None: if layer_type == "net": - major_node_output_name, major_node_output_channels = self._make_input_tensor(layer_name, layer_dict) - major_node_specs = MajorNodeSpecs(major_node_output_name, major_node_output_channels) + major_node_output_name, major_node_output_channels = ( + self._make_input_tensor(layer_name, layer_dict) + ) + major_node_specs = MajorNodeSpecs( + major_node_output_name, major_node_output_channels + ) else: raise ValueError('The first node has to be of type "net".') else: @@ -435,10 +479,17 @@ def _make_onnx_node(self, layer_name, layer_dict): node_creators["upsample"] = self._make_resize_node if layer_type in node_creators.keys(): - major_node_output_name, major_node_output_channels = node_creators[layer_type](layer_name, layer_dict) - major_node_specs = MajorNodeSpecs(major_node_output_name, major_node_output_channels) + major_node_output_name, major_node_output_channels = node_creators[ + layer_type + ](layer_name, layer_dict) + major_node_specs = MajorNodeSpecs( + major_node_output_name, major_node_output_channels + ) else: - print("Layer of type %s not supported, skipping ONNX node generation." % layer_type) + print( + "Layer of type %s not supported, skipping ONNX node generation." + % layer_type + ) major_node_specs = MajorNodeSpecs(layer_name, None) return major_node_specs @@ -491,7 +542,10 @@ def _make_conv_node(self, layer_name, layer_dict): stride = layer_dict["stride"] filters = layer_dict["filters"] batch_normalize = False - if "batch_normalize" in layer_dict.keys() and layer_dict["batch_normalize"] == 1: + if ( + "batch_normalize" in layer_dict.keys() + and layer_dict["batch_normalize"] == 1 + ): batch_normalize = True kernel_shape = [kernel_size, kernel_size] @@ -542,7 +596,11 @@ def _make_conv_node(self, layer_name, layer_dict): layer_name_lrelu = layer_name + "_lrelu" lrelu_node = helper.make_node( - "LeakyRelu", inputs=inputs, outputs=[layer_name_lrelu], name=layer_name_lrelu, alpha=self.alpha_lrelu + "LeakyRelu", + inputs=inputs, + outputs=[layer_name_lrelu], + name=layer_name_lrelu, + alpha=self.alpha_lrelu, ) self._nodes.append(lrelu_node) inputs = [layer_name_lrelu] @@ -633,7 +691,9 @@ def _make_resize_node(self, layer_name, layer_dict): """ resize_scale_factors = float(layer_dict["stride"]) # Create the scale factor array with node parameters - scales = np.array([1.0, 1.0, resize_scale_factors, resize_scale_factors]).astype(np.float32) + scales = np.array( + [1.0, 1.0, resize_scale_factors, resize_scale_factors] + ).astype(np.float32) previous_node_specs = self._get_previous_node_specs() inputs = [previous_node_specs.name] diff --git a/samples/sampleAlgorithmSelector/CMakeLists.txt b/samples/sampleAlgorithmSelector/CMakeLists.txt index ef9386b3..3b30570c 100644 --- a/samples/sampleAlgorithmSelector/CMakeLists.txt +++ b/samples/sampleAlgorithmSelector/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleAlgorithmSelector/sampleAlgorithmSelector.cpp b/samples/sampleAlgorithmSelector/sampleAlgorithmSelector.cpp index 0072f761..02fd9975 100644 --- a/samples/sampleAlgorithmSelector/sampleAlgorithmSelector.cpp +++ b/samples/sampleAlgorithmSelector/sampleAlgorithmSelector.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/sampleCharRNN/CMakeLists.txt b/samples/sampleCharRNN/CMakeLists.txt index 89d82682..d52245fb 100644 --- a/samples/sampleCharRNN/CMakeLists.txt +++ b/samples/sampleCharRNN/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleCharRNN/sampleCharRNN.cpp b/samples/sampleCharRNN/sampleCharRNN.cpp index 73ba53cc..8ddbb2ac 100644 --- a/samples/sampleCharRNN/sampleCharRNN.cpp +++ b/samples/sampleCharRNN/sampleCharRNN.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/sampleDynamicReshape/CMakeLists.txt b/samples/sampleDynamicReshape/CMakeLists.txt index 374b5566..548e9bd5 100644 --- a/samples/sampleDynamicReshape/CMakeLists.txt +++ b/samples/sampleDynamicReshape/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleDynamicReshape/sampleDynamicReshape.cpp b/samples/sampleDynamicReshape/sampleDynamicReshape.cpp index d91b1a68..0f880509 100644 --- a/samples/sampleDynamicReshape/sampleDynamicReshape.cpp +++ b/samples/sampleDynamicReshape/sampleDynamicReshape.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/sampleINT8API/CMakeLists.txt b/samples/sampleINT8API/CMakeLists.txt index e8eed5c3..00a6e82b 100644 --- a/samples/sampleINT8API/CMakeLists.txt +++ b/samples/sampleINT8API/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleINT8API/sampleINT8API.cpp b/samples/sampleINT8API/sampleINT8API.cpp index a20acff3..7cf6e819 100644 --- a/samples/sampleINT8API/sampleINT8API.cpp +++ b/samples/sampleINT8API/sampleINT8API.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/sampleIOFormats/CMakeLists.txt b/samples/sampleIOFormats/CMakeLists.txt index 4ec93187..4640a2ff 100755 --- a/samples/sampleIOFormats/CMakeLists.txt +++ b/samples/sampleIOFormats/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,6 +16,11 @@ # SET(SAMPLE_SOURCES sampleIOFormats.cpp + ../common/sampleDevice.cpp + ../common/sampleEngines.cpp + ../common/sampleOptions.cpp + ../common/sampleUtils.cpp + ../common/bfloat16.cpp ) SET(SAMPLE_PARSERS "onnx") diff --git a/samples/sampleIOFormats/sampleIOFormats.cpp b/samples/sampleIOFormats/sampleIOFormats.cpp index 2c8b87af..9e167134 100644 --- a/samples/sampleIOFormats/sampleIOFormats.cpp +++ b/samples/sampleIOFormats/sampleIOFormats.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -33,6 +33,7 @@ #include "half.h" #include "logger.h" #include "parserOnnxConfig.h" +#include "sampleOptions.h" #include "NvInfer.h" #include "NvOnnxParser.h" @@ -144,6 +145,15 @@ class BufferDesc } }; +//! Specification for a network I/O tensor. +class TypeSpec +{ +public: + DataType dtype; //!< datatype + TensorFormat format; //!< format + std::string formatName; //!< name of the format +}; + class SampleBuffer { public: @@ -245,30 +255,14 @@ class SampleIOFormats bool build(int32_t dataWidth); //! - //! \brief Runs the TensorRT inference engine for this sample - //! - bool infer(SampleBuffer& inputBuf, SampleBuffer& outputBuf); - - //! - //! \brief Used to run CPU reference and get result - //! - bool reference(); - - //! - //! \brief Used to compare the CPU reference with the TRT result + //! \brief Verify the built engine I/O types and formats. //! - void compareResult(); + bool verify(TypeSpec const& spec); //! - //! \brief Reads the digit map from the file - //! - bool readDigits(SampleBuffer& buffer, int32_t groundTruthDigit); - - //! - //! \brief Verifies that the output is correct and prints it + //! \brief Runs the TensorRT inference engine for this sample //! - template - bool verifyOutput(SampleBuffer& outputBuf, int32_t groundTruthDigit) const; + bool infer(SampleBuffer& inputBuf, SampleBuffer& outputBuf); private: //! @@ -293,6 +287,62 @@ class SampleIOFormats int32_t mDigit; }; +//! +//! \brief Validates engine I/O datatypes and formats against a reference. +//! +//! \details This function queries I/O datatype and format description from the built engine. +//! Validating them is sufficient to ensure that `ITensor::setType` and `ITensor::setAllowedFormats` API as +//! expected. +//! +//! \return true if type and format validation succeeds. +//! +bool SampleIOFormats::verify(TypeSpec const& spec) +{ + assert(mEngine->getNbIOTensors() == 2); + char const* inputName = mEngine->getIOTensorName(0); + char const* outputName = mEngine->getIOTensorName(1); + + auto verifyType = [](DataType actual, DataType expected) { + if (actual != expected) + { + sample::gLogError << "Expected " << expected << " data type, got " << actual; + return false; + } + return true; + }; + + if (!verifyType(mEngine->getTensorDataType(inputName), spec.dtype)) + { + return false; + } + + if (!verifyType(mEngine->getTensorDataType(outputName), spec.dtype)) + { + return false; + } + + auto verifyFormat = [](std::string actual, std::string expected) { + if (expected.find(actual) != std::string::npos) + { + sample::gLogError << "Expected " << expected << " format, got " << actual; + return false; + } + return true; + }; + + if (!verifyFormat(std::string(mEngine->getTensorFormatDesc(inputName)), spec.formatName)) + { + return false; + } + + if (!verifyFormat(std::string(mEngine->getTensorFormatDesc(inputName)), "kLINEAR")) + { + return false; + } + + return true; +} + //! //! \brief Creates the network, configures the builder and creates the network engine //! @@ -474,134 +524,6 @@ bool SampleIOFormats::infer(SampleBuffer& inputBuf, SampleBuffer& outputBuf) return true; } -//! -//! \brief Reads the digit map from file -//! -bool SampleIOFormats::readDigits(SampleBuffer& buffer, int32_t groundTruthDigit) -{ - int32_t const inputH = buffer.dims.d[2]; - int32_t const inputW = buffer.dims.d[3]; - - // Read a random digit file - std::vector fileData(inputH * inputW); - readPGMFile( - locateFile(std::to_string(groundTruthDigit) + ".pgm", mParams.dataDirs), fileData.data(), inputH, inputW); - - // Print ASCII representation of digit - for (int32_t i = 0; i < inputH * inputW; i++) - { - sample::gLogInfo << (" .:-=+*#%@"[fileData[i] / 26]) << (((i + 1) % inputW) ? "" : "\n"); - } - sample::gLogInfo << std::endl; - - float* inputBuf = reinterpret_cast(buffer.buffer); - - for (int32_t i = 0; i < inputH * inputW; i++) - { - inputBuf[i] = 1.0F - static_cast(fileData[i] / 255.0F); - } - - return true; -} - -//! -//! \brief Verifies that the output is correct and prints it -//! -template -bool SampleIOFormats::verifyOutput(SampleBuffer& outputBuf, int32_t groundTruthDigit) const -{ - T const* prob = reinterpret_cast(outputBuf.buffer); - - float val{0.0F}; - float elem{0.0F}; - int32_t idx{0}; - int32_t const kDIGITS = 10; - - for (int32_t i = 0; i < kDIGITS; i++) - { - elem = static_cast(prob[i]); - if (val < elem) - { - val = elem; - idx = i; - } - } - sample::gLogInfo << "Predicted Output: " << idx << std::endl; - - return (idx == groundTruthDigit && val > 0.9F); -} - -int32_t calcIndex(SampleBuffer& buffer, int32_t c, int32_t h, int32_t w) -{ - int32_t index; - - if (!buffer.desc.channelPivot) - { - index = c / buffer.desc.dims[4] * buffer.desc.dims[2] * buffer.desc.dims[3] * buffer.desc.dims[4] - + h * buffer.desc.dims[3] * buffer.desc.dims[4] + w * buffer.desc.dims[4] + c % buffer.desc.dims[4]; - } - else - { - index = h * buffer.desc.dims[3] * buffer.desc.dims[2] + w * buffer.desc.dims[3] + c; - } - - return index; -} - -//! -//! \brief Reformats the buffer. Src and dst buffers should be of same datatype and dims. -//! -template -void reformat(SampleBuffer& src, SampleBuffer& dst) -{ - if (src.format == dst.format) - { - memcpy(dst.buffer, src.buffer, src.getBufferSize()); - return; - } - - int32_t srcIndex, dstIndex; - - T* srcBuf = reinterpret_cast(src.buffer); - T* dstBuf = reinterpret_cast(dst.buffer); - - for (int32_t c = 0; c < src.dims.d[1]; c++) - { - for (int32_t h = 0; h < src.dims.d[2]; h++) - { - for (int32_t w = 0; w < src.dims.d[3]; w++) - { - srcIndex = calcIndex(src, c, h, w); - dstIndex = calcIndex(dst, c, h, w); - dstBuf[dstIndex] = srcBuf[srcIndex]; - } - } - } -} - -template -void convertGoldenData(SampleBuffer& goldenInput, SampleBuffer& dstInput) -{ - SampleBuffer tmpBuf(goldenInput.dims, sizeof(T), goldenInput.format, true); - - float* golden = reinterpret_cast(goldenInput.buffer); - T* tmp = reinterpret_cast(tmpBuf.buffer); - - for (int32_t i = 0; i < goldenInput.desc.getElememtSize(); i++) - { - if (std::is_same::value) - { - tmp[i] = static_cast(1 - ((1.0F - golden[i]) * 255.0F - 128) / 255.0F); - } - else - { - tmp[i] = static_cast(golden[i]); - } - } - - reformat(tmpBuf, dstInput); -} - //! //! \brief Initializes members of the params struct using the command line args //! @@ -644,67 +566,29 @@ void printHelpInfo() //! template bool process(SampleIOFormats& sample, sample::Logger::TestAtom const& sampleTest, SampleBuffer& inputBuf, - SampleBuffer& outputBuf, SampleBuffer& goldenInput) + SampleBuffer& outputBuf, TypeSpec& spec) { sample::gLogInfo << "Building and running a GPU inference engine with specified I/O formats." << std::endl; - inputBuf = SampleBuffer(sample.mInputDims, sizeof(T), sample.mTensorFormat, true); - outputBuf = SampleBuffer(sample.mOutputDims, sizeof(T), TensorFormat::kLINEAR, false); if (!sample.build(sizeof(T))) { return false; } - convertGoldenData(goldenInput, inputBuf); - - if (!sample.infer(inputBuf, outputBuf)) - { - return false; - } - - if (!sample.verifyOutput(outputBuf, sample.mDigit)) - { - return false; - } - - return true; -} - -bool runFP32Reference(SampleIOFormats& sample, sample::Logger::TestAtom const& sampleTest, SampleBuffer& goldenInput, - SampleBuffer& goldenOutput) -{ - sample::gLogInfo << "Building and running a FP32 GPU inference to get golden input/output" << std::endl; - - if (!sample.build(sizeof(float))) + if (!sample.verify(spec)) { return false; } - goldenInput = SampleBuffer(sample.mInputDims, sizeof(float), TensorFormat::kLINEAR, true); - goldenOutput = SampleBuffer(sample.mOutputDims, sizeof(float), TensorFormat::kLINEAR, false); - - sample.readDigits(goldenInput, sample.mDigit); - - if (!sample.infer(goldenInput, goldenOutput)) - { - return false; - } + inputBuf = SampleBuffer(sample.mInputDims, sizeof(T), sample.mTensorFormat, true); + outputBuf = SampleBuffer(sample.mOutputDims, sizeof(T), TensorFormat::kLINEAR, false); - if (!sample.verifyOutput(goldenOutput, sample.mDigit)) + if (!sample.infer(inputBuf, outputBuf)) { return false; } - return true; } -//! Specification for a network I/O tensor. -class IOSpec -{ -public: - TensorFormat format; //!< format - std::string formatName; //!< name of the format -}; - int32_t main(int32_t argc, char** argv) { samplesCommon::Args args; @@ -727,56 +611,45 @@ int32_t main(int32_t argc, char** argv) samplesCommon::OnnxSampleParams params = initializeSampleParams(args); - std::vector vecFP16TensorFmt = { - IOSpec{TensorFormat::kLINEAR, "kLINEAR"}, - IOSpec{TensorFormat::kCHW2, "kCHW2"}, - IOSpec{TensorFormat::kHWC8, "kHWC8"}, - }; - std::vector vecINT8TensorFmt = { - IOSpec{TensorFormat::kLINEAR, "kLINEAR"}, - IOSpec{TensorFormat::kCHW4, "kCHW4"}, - IOSpec{TensorFormat::kCHW32, "kCHW32"}, + std::vector fp16TypeSpec = { + TypeSpec{DataType::kHALF, TensorFormat::kLINEAR, "kLINEAR"}, + TypeSpec{DataType::kHALF, TensorFormat::kCHW2, "kCHW2"}, + TypeSpec{DataType::kHALF, TensorFormat::kHWC8, "kHWC8"}, }; - SampleBuffer goldenInput, goldenOutput; + std::vector int8TypeSpec = { + TypeSpec{DataType::kINT8, TensorFormat::kLINEAR, "kLINEAR"}, + TypeSpec{DataType::kINT8, TensorFormat::kCHW4, "kCHW4"}, + TypeSpec{DataType::kINT8, TensorFormat::kCHW32, "kCHW32"}, + }; SampleIOFormats sample(params); - srand(unsigned(time(nullptr))); - sample.mDigit = rand() % 10; - - sample::gLogInfo << "The test chooses MNIST as the network and recognizes a randomly generated digit" << std::endl; sample::gLogInfo - << "Firstly it runs the FP32 as the golden data, then INT8/FP16 with different formats will be tested" - << std::endl + << "Build TRT engine with different IO data type and formats. Ensure that built engine abide by them" << std::endl; - if (!runFP32Reference(sample, sampleTest, goldenInput, goldenOutput)) - { - return sample::gLogger.reportFail(sampleTest); - } - // Test FP16 formats - for (auto spec : vecFP16TensorFmt) + for (auto spec : fp16TypeSpec) { sample::gLogInfo << "Testing datatype FP16 with format " << spec.formatName << std::endl; sample.mTensorFormat = spec.format; SampleBuffer inputBuf, outputBuf; - if (!process(sample, sampleTest, inputBuf, outputBuf, goldenInput)) + if (!process(sample, sampleTest, inputBuf, outputBuf, spec)) { return sample::gLogger.reportFail(sampleTest); } } // Test INT8 formats - for (auto spec : vecINT8TensorFmt) + for (auto spec : int8TypeSpec) { sample::gLogInfo << "Testing datatype INT8 with format " << spec.formatName << std::endl; sample.mTensorFormat = spec.format; SampleBuffer inputBuf, outputBuf; - if (!process(sample, sampleTest, inputBuf, outputBuf, goldenInput)) + if (!process(sample, sampleTest, inputBuf, outputBuf, spec)) { return sample::gLogger.reportFail(sampleTest); } diff --git a/samples/sampleNamedDimensions/CMakeLists.txt b/samples/sampleNamedDimensions/CMakeLists.txt index f03d19b1..21662668 100644 --- a/samples/sampleNamedDimensions/CMakeLists.txt +++ b/samples/sampleNamedDimensions/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleNamedDimensions/create_model.py b/samples/sampleNamedDimensions/create_model.py index e4146aa5..575bd4e6 100644 --- a/samples/sampleNamedDimensions/create_model.py +++ b/samples/sampleNamedDimensions/create_model.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleNamedDimensions/sampleNamedDimensions.cpp b/samples/sampleNamedDimensions/sampleNamedDimensions.cpp index 42298ba4..11e04841 100644 --- a/samples/sampleNamedDimensions/sampleNamedDimensions.cpp +++ b/samples/sampleNamedDimensions/sampleNamedDimensions.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/sampleNonZeroPlugin/README.md b/samples/sampleNonZeroPlugin/README.md index 15e8e4c2..10e45109 100644 --- a/samples/sampleNonZeroPlugin/README.md +++ b/samples/sampleNonZeroPlugin/README.md @@ -16,7 +16,7 @@ ## Description This sample, sampleNonZeroPlugin, implements a plugin for the NonZero operation, customizable to output the non-zero indices in -either a row major (each set of indices in the same row) or column major format (each set of indices in the same column). +either a row order (each set of indices in the same row) or column order format (each set of indices in the same column). NonZero is an operation where the non-zero indices of the input tensor is found. @@ -36,7 +36,7 @@ Until `IPluginV3` (and associated interfaces), TensorRT plugins could not have o 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-major order. Then the output shape would be $K \times 2$. +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::getOutputShapes()` API. Expressing the second dimension of the output is straightforward: @@ -70,7 +70,7 @@ and let's not forget to declare that the size tensor is a scalar (0-D): outputs[1].nbDims = 0; ``` -The `NonZeroPlugin` can also be configured to emit the non-zero indices in a column-major fashion through the `rowMajor` plugin attribute, by setting it to `0`. +The `NonZeroPlugin` can also be configured to emit the non-zero indices in a column-order fashion through the `rowOrder` plugin attribute, by setting it to `0`. In this case, the first output of the plugin will have shape $2 \times K$, and the output shape specification must be adjusted accordingly. ### Creating network and building the engine @@ -95,7 +95,7 @@ Download the sample data from the [TensorRT release tarball](https://developer.n 2. Run the sample to build and run the MNIST engine from the ONNX model. ``` - ./sample_non_zero_plugin [-h or --help] [-d or --datadir=] [--columnMajor] [--fp16] + ./sample_non_zero_plugin [-h or --help] [-d or --datadir=] [--columnOrder] [--fp16] ``` 3. Verify that the sample ran successfully. If the sample runs successfully you should see output similar to the following: diff --git a/samples/sampleNonZeroPlugin/nonZeroKernel.cu b/samples/sampleNonZeroPlugin/nonZeroKernel.cu index 7e015b2c..cdb4c615 100644 --- a/samples/sampleNonZeroPlugin/nonZeroKernel.cu +++ b/samples/sampleNonZeroPlugin/nonZeroKernel.cu @@ -17,8 +17,23 @@ #include "nonZeroKernel.h" +inline __device__ int32_t isZero(float const& a) +{ + return a == 0.F; +} + +inline __device__ int32_t isZero(half const& a) +{ +#if __CUDA_ARCH__ >= 530 + return a == __float2half(0.F); +#else + return __half2float(a) == 0.F; +#endif +} + +template __global__ void findNonZeroIndicesKernel( - float const* X, int32_t* indices, int32_t* count, int32_t const* K, int32_t R, int32_t C, bool rowMajor) + T const* X, int32_t* indices, int32_t* count, int32_t const* K, int32_t R, int32_t C, int32_t rowOrder) { int32_t col = blockIdx.x * blockDim.x + threadIdx.x; @@ -27,12 +42,12 @@ __global__ void findNonZeroIndicesKernel( { for (int32_t row = 0; row < R; ++row) { - if (X[row + R * col] != 0.F) + if (!isZero(X[row * C + col])) { int32_t index = atomicAdd(count, 1); // Increment count atomically and get the previous value if (indices) { - if(!rowMajor) + if(rowOrder == 0) { indices[index] = row; indices[index + *K] = col; @@ -48,11 +63,20 @@ __global__ void findNonZeroIndicesKernel( } } -void nonZeroIndicesImpl( - float const* X, int32_t* indices, int32_t* count, int32_t const* K, int32_t R, int32_t C, bool rowMajor, cudaStream_t stream) +template +void nonZeroIndicesImpl(T const* X, int32_t* indices, int32_t* count, int32_t const* K, int32_t R, int32_t C, + bool rowOrder, cudaStream_t stream) { constexpr int32_t kBLOCK_SIZE = 256; - int32_t const blocksPerGrid = (R + kBLOCK_SIZE - 1) / kBLOCK_SIZE; - - findNonZeroIndicesKernel<<>>(X, indices, count, K, R, C, rowMajor); + int32_t const blocksPerGrid = (C + kBLOCK_SIZE - 1) / kBLOCK_SIZE; + + findNonZeroIndicesKernel<<>>( + X, indices, count, K, R, C, static_cast(rowOrder)); } + +#define NONZERO_SPECIALIZED_IMPL(T) \ + template void nonZeroIndicesImpl(T const* X, int32_t* indices, int32_t* count, int32_t const* K, int32_t R, \ + int32_t C, bool rowOrder, cudaStream_t stream); + +NONZERO_SPECIALIZED_IMPL(float) +NONZERO_SPECIALIZED_IMPL(half) diff --git a/samples/sampleNonZeroPlugin/nonZeroKernel.h b/samples/sampleNonZeroPlugin/nonZeroKernel.h index 4dbb1ab0..c2f23c8e 100644 --- a/samples/sampleNonZeroPlugin/nonZeroKernel.h +++ b/samples/sampleNonZeroPlugin/nonZeroKernel.h @@ -16,9 +16,13 @@ */ #ifndef SAMPLE_NONZERO_KERNEL_H #define SAMPLE_NONZERO_KERNEL_H + +#include + #include -void nonZeroIndicesImpl(float const* X, int32_t* indices, int32_t* count, int32_t const* K, int32_t R, int32_t C, - bool rowMajor, cudaStream_t stream); +template +void nonZeroIndicesImpl(T const* X, int32_t* indices, int32_t* count, int32_t const* K, int32_t R, int32_t C, + bool rowOrder, cudaStream_t stream); #endif // SAMPLE_NONZERO_KERNEL_H diff --git a/samples/sampleNonZeroPlugin/sampleNonZeroPlugin.cpp b/samples/sampleNonZeroPlugin/sampleNonZeroPlugin.cpp index 47b5e7b3..40313f40 100644 --- a/samples/sampleNonZeroPlugin/sampleNonZeroPlugin.cpp +++ b/samples/sampleNonZeroPlugin/sampleNonZeroPlugin.cpp @@ -46,13 +46,34 @@ using samplesCommon::SampleUniquePtr; std::string const kSAMPLE_NAME = "TensorRT.sample_non_zero_plugin"; +using half = __half; + +void nonZeroIndicesHelper(nvinfer1::DataType type, void const* X, void* indices, void* count, void const* K, int32_t R, + int32_t C, bool rowOrder, cudaStream_t stream) +{ + if (type == nvinfer1::DataType::kFLOAT) + { + nonZeroIndicesImpl(static_cast(X), static_cast(indices), + static_cast(count), static_cast(K), R, C, rowOrder, stream); + } + else if (type == nvinfer1::DataType::kHALF) + { + nonZeroIndicesImpl(static_cast(X), static_cast(indices), + static_cast(count), static_cast(K), R, C, rowOrder, stream); + } + else + { + ASSERT(false && "Unsupported data type"); + } +} + class NonZeroPlugin : public IPluginV3, public IPluginV3OneCore, public IPluginV3OneBuild, public IPluginV3OneRuntime { public: NonZeroPlugin(NonZeroPlugin const& p) = default; - NonZeroPlugin(bool rowMajor) - : mRowMajor(rowMajor) + NonZeroPlugin(bool rowOrder) + : mRowOrder(rowOrder) { initFieldsToSerialize(); } @@ -60,7 +81,7 @@ class NonZeroPlugin : public IPluginV3, public IPluginV3OneCore, public IPluginV void initFieldsToSerialize() { mDataToSerialize.clear(); - mDataToSerialize.emplace_back(PluginField("rowMajor", &mRowMajor, PluginFieldType::kINT32, 1)); + mDataToSerialize.emplace_back(PluginField("rowOrder", &mRowOrder, PluginFieldType::kINT32, 1)); mFCToSerialize.nbFields = mDataToSerialize.size(); mFCToSerialize.fields = mDataToSerialize.data(); } @@ -170,7 +191,7 @@ class NonZeroPlugin : public IPluginV3, public IPluginV3OneCore, public IPluginV auto optValue = exprBuilder.operation(DimensionOperation::kFLOOR_DIV, *upperBound, *exprBuilder.constant(2)); auto numNonZeroSizeTensor = exprBuilder.declareSizeTensor(1, *optValue, *upperBound); - if (!mRowMajor) + if (!mRowOrder) { outputs[0].d[0] = exprBuilder.constant(2); outputs[0].d[1] = numNonZeroSizeTensor; @@ -195,25 +216,29 @@ class NonZeroPlugin : public IPluginV3, public IPluginV3OneCore, public IPluginV int32_t const R = inputDesc[0].dims.d[0]; int32_t const C = inputDesc[0].dims.d[1]; + auto type = inputDesc[0].type; + + if (!(type == nvinfer1::DataType::kHALF || type == nvinfer1::DataType::kFLOAT)) + { + sample::gLogError << "Unsupported: Sample only supports DataType::kHALF and DataType::FLOAT" << std::endl; + return -1; + } + cudaMemsetAsync(outputs[1], 0, sizeof(int32_t), stream); - if (!mRowMajor) + if (!mRowOrder) { // When constructing a column major output, the kernel needs to be aware of the total number of non-zero // elements so as to write the non-zero indices at the correct places. Therefore, we will launch the kernel // twice: first, only to calculate the total non-zero count, which will be stored in workspace; and // then to actually write the non-zero indices to the outputs[0] buffer. cudaMemsetAsync(workspace, 0, sizeof(int32_t), stream); - nonZeroIndicesImpl(static_cast(inputs[0]), nullptr, static_cast(workspace), 0, R, C, - mRowMajor, stream); - - nonZeroIndicesImpl(static_cast(inputs[0]), static_cast(outputs[0]), - static_cast(outputs[1]), static_cast(workspace), R, C, mRowMajor, stream); + nonZeroIndicesHelper(type, inputs[0], nullptr, workspace, 0, R, C, mRowOrder, stream); + nonZeroIndicesHelper(type, inputs[0], outputs[0], outputs[1], workspace, R, C, mRowOrder, stream); } else { - nonZeroIndicesImpl(static_cast(inputs[0]), static_cast(outputs[0]), - static_cast(outputs[1]), 0, R, C, mRowMajor, stream); + nonZeroIndicesHelper(type, inputs[0], outputs[0], outputs[1], 0, R, C, mRowOrder, stream); } return 0; @@ -242,7 +267,7 @@ class NonZeroPlugin : public IPluginV3, public IPluginV3OneCore, public IPluginV } private: - bool mRowMajor{true}; + bool mRowOrder{true}; std::vector mDataToSerialize; nvinfer1::PluginFieldCollection mFCToSerialize; }; @@ -253,7 +278,7 @@ class NonZeroPluginCreator : public nvinfer1::IPluginCreatorV3One NonZeroPluginCreator() { mPluginAttributes.clear(); - mPluginAttributes.emplace_back(PluginField("rowMajor", nullptr, PluginFieldType::kINT32, 1)); + mPluginAttributes.emplace_back(PluginField("rowOrder", nullptr, PluginFieldType::kINT32, 1)); mFC.nbFields = mPluginAttributes.size(); mFC.fields = mPluginAttributes.data(); } @@ -277,16 +302,16 @@ class NonZeroPluginCreator : public nvinfer1::IPluginCreatorV3One { try { - bool rowMajor{true}; + bool rowOrder{true}; for (int32_t i = 0; i < fc->nbFields; ++i) { auto const fieldName(fc->fields[i].name); - if (std::strcmp(fieldName, "rowMajor") == 0) + if (std::strcmp(fieldName, "rowOrder") == 0) { - rowMajor = *static_cast(fc->fields[i].data); + rowOrder = *static_cast(fc->fields[i].data); } } - return new NonZeroPlugin(rowMajor); + return new NonZeroPlugin(rowOrder); } catch (std::exception const& e) { @@ -309,7 +334,7 @@ namespace { struct NonZeroParams : public samplesCommon::SampleParams { - bool rowMajor{true}; + bool rowOrder{true}; }; } // namespace @@ -465,7 +490,7 @@ bool SampleNonZeroPlugin::constructNetwork(SampleUniquePtr& auto* in = network->addInput("Input", DataType::kFLOAT, {2, {R, C}}); ASSERT(in != nullptr); - std::vector const vecPF{{"rowMajor", &mParams.rowMajor, PluginFieldType::kINT32, 1}}; + std::vector const vecPF{{"rowOrder", &mParams.rowOrder, PluginFieldType::kINT32, 1}}; PluginFieldCollection pfc{static_cast(vecPF.size()), vecPF.data()}; auto pluginCreator = static_cast(getPluginRegistry()->getCreator("NonZeroPlugin", "0", "")); @@ -579,7 +604,7 @@ bool SampleNonZeroPlugin::processInput(samplesCommon::BufferManager const& buffe { for (int32_t j = 0; j < inputW; ++j) { - sample::gLogInfo << hostDataBuffer[i + inputH * j]; + sample::gLogInfo << hostDataBuffer[i * inputW + j]; if (j < inputW - 1) { sample::gLogInfo << ", "; @@ -606,7 +631,7 @@ bool SampleNonZeroPlugin::verifyOutput(samplesCommon::BufferManager const& buffe std::vector covered(mInputDims.d[0] * mInputDims.d[1], false); sample::gLogInfo << "Output:" << std::endl; - if (mParams.rowMajor) + if (mParams.rowOrder) { for (int32_t i = 0; i < count; ++i) { @@ -629,11 +654,11 @@ bool SampleNonZeroPlugin::verifyOutput(samplesCommon::BufferManager const& buffe } } - if (!mParams.rowMajor) + if (!mParams.rowOrder) { for (int32_t i = 0; i < count; ++i) { - auto const idx = output[i] + mInputDims.d[0] * output[i + count]; + auto const idx = output[i] * mInputDims.d[1] + output[i + count]; covered[idx] = true; if (input[idx] == 0.F) { @@ -645,7 +670,7 @@ bool SampleNonZeroPlugin::verifyOutput(samplesCommon::BufferManager const& buffe { for (int32_t i = 0; i < count; ++i) { - auto const idx = output[2 * i] + mInputDims.d[0] * output[2 * i + 1]; + auto const idx = output[2 * i] * mInputDims.d[1] + output[2 * i + 1]; covered[idx] = true; if (input[idx] == 0.F) { @@ -688,7 +713,7 @@ NonZeroParams initializeSampleParams(samplesCommon::Args const& args) params.outputTensorNames.push_back("Output0"); params.outputTensorNames.push_back("Output1"); params.fp16 = args.runInFp16; - params.rowMajor = args.rowMajor; + params.rowOrder = args.rowOrder; return params; } @@ -706,7 +731,7 @@ void printHelpInfo() "(data/samples/mnist/, data/mnist/)" << std::endl; std::cout << "--fp16 Run in FP16 mode." << std::endl; - std::cout << "--columnMajor Run plugin in column major output mode." << std::endl; + std::cout << "--columnOrder Run plugin in column major output mode." << std::endl; } int main(int argc, char** argv) diff --git a/samples/sampleOnnxMNIST/CMakeLists.txt b/samples/sampleOnnxMNIST/CMakeLists.txt index 23bd886b..6ed6da36 100644 --- a/samples/sampleOnnxMNIST/CMakeLists.txt +++ b/samples/sampleOnnxMNIST/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp b/samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp index 35bfbd04..9dfd67c8 100644 --- a/samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp +++ b/samples/sampleOnnxMNIST/sampleOnnxMNIST.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/sampleOnnxMnistCoordConvAC/CMakeLists.txt b/samples/sampleOnnxMnistCoordConvAC/CMakeLists.txt index b094cf08..bf8324d8 100644 --- a/samples/sampleOnnxMnistCoordConvAC/CMakeLists.txt +++ b/samples/sampleOnnxMnistCoordConvAC/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleOnnxMnistCoordConvAC/coord_conv.py b/samples/sampleOnnxMnistCoordConvAC/coord_conv.py index b2572ad5..6cd47eca 100644 --- a/samples/sampleOnnxMnistCoordConvAC/coord_conv.py +++ b/samples/sampleOnnxMnistCoordConvAC/coord_conv.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleOnnxMnistCoordConvAC/mnist_coord_conv_train.py b/samples/sampleOnnxMnistCoordConvAC/mnist_coord_conv_train.py index 8d0a9623..c7891d92 100644 --- a/samples/sampleOnnxMnistCoordConvAC/mnist_coord_conv_train.py +++ b/samples/sampleOnnxMnistCoordConvAC/mnist_coord_conv_train.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleOnnxMnistCoordConvAC/modify_onnx_ac.py b/samples/sampleOnnxMnistCoordConvAC/modify_onnx_ac.py index 0de8321d..8eb45bf9 100644 --- a/samples/sampleOnnxMnistCoordConvAC/modify_onnx_ac.py +++ b/samples/sampleOnnxMnistCoordConvAC/modify_onnx_ac.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleOnnxMnistCoordConvAC/sampleOnnxMnistCoordConvAC.cpp b/samples/sampleOnnxMnistCoordConvAC/sampleOnnxMnistCoordConvAC.cpp index 491186e5..02218820 100644 --- a/samples/sampleOnnxMnistCoordConvAC/sampleOnnxMnistCoordConvAC.cpp +++ b/samples/sampleOnnxMnistCoordConvAC/sampleOnnxMnistCoordConvAC.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/sampleProgressMonitor/CMakeLists.txt b/samples/sampleProgressMonitor/CMakeLists.txt index 582cbbf1..bf2cc4c2 100644 --- a/samples/sampleProgressMonitor/CMakeLists.txt +++ b/samples/sampleProgressMonitor/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/sampleProgressMonitor/sampleProgressMonitor.cpp b/samples/sampleProgressMonitor/sampleProgressMonitor.cpp index c9da0f23..393dc617 100644 --- a/samples/sampleProgressMonitor/sampleProgressMonitor.cpp +++ b/samples/sampleProgressMonitor/sampleProgressMonitor.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/trtexec/CMakeLists.txt b/samples/trtexec/CMakeLists.txt index 93b87ec5..c1e3f793 100644 --- a/samples/trtexec/CMakeLists.txt +++ b/samples/trtexec/CMakeLists.txt @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/trtexec/prn_utils.py b/samples/trtexec/prn_utils.py index 6d759238..6b0abf9f 100755 --- a/samples/trtexec/prn_utils.py +++ b/samples/trtexec/prn_utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/trtexec/profiler.py b/samples/trtexec/profiler.py index e251254b..0a34e69f 100755 --- a/samples/trtexec/profiler.py +++ b/samples/trtexec/profiler.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/trtexec/tracer.py b/samples/trtexec/tracer.py index 8a9b7a62..4b093d76 100755 --- a/samples/trtexec/tracer.py +++ b/samples/trtexec/tracer.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/samples/trtexec/trtexec.cpp b/samples/trtexec/trtexec.cpp index ece19ed6..f3f72a1f 100644 --- a/samples/trtexec/trtexec.cpp +++ b/samples/trtexec/trtexec.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); @@ -52,11 +52,11 @@ using LibraryPtr = std::unique_ptr; #if !TRT_STATIC #if defined(_WIN32) -std::string const kNVINFER_PLUGIN_LIBNAME{"nvinfer_plugin.dll"}; -std::string const kNVINFER_LIBNAME{"nvinfer.dll"}; -std::string const kNVONNXPARSER_LIBNAME{"nvonnxparser.dll"}; -std::string const kNVINFER_LEAN_LIBNAME{"nvinfer_lean.dll"}; -std::string const kNVINFER_DISPATCH_LIBNAME{"nvinfer_dispatch.dll"}; +std::string const kNVINFER_PLUGIN_LIBNAME = std::string{"nvinfer_plugin_"} + std::to_string(NV_TENSORRT_MAJOR) + std::string{".dll"}; +std::string const kNVINFER_LIBNAME = std::string{"nvinfer_"} + std::to_string(NV_TENSORRT_MAJOR) + std::string{".dll"}; +std::string const kNVONNXPARSER_LIBNAME = std::string{"nvonnxparser_"} + std::to_string(NV_TENSORRT_MAJOR) + std::string{".dll"}; +std::string const kNVINFER_LEAN_LIBNAME = std::string{"nvinfer_lean_"} + std::to_string(NV_TENSORRT_MAJOR) + std::string{".dll"}; +std::string const kNVINFER_DISPATCH_LIBNAME = std::string{"nvinfer_dispatch_"} + std::to_string(NV_TENSORRT_MAJOR) + std::string{".dll"}; #else std::string const kNVINFER_PLUGIN_LIBNAME = std::string{"libnvinfer_plugin.so."} + std::to_string(NV_TENSORRT_MAJOR); std::string const kNVINFER_LIBNAME = std::string{"libnvinfer.so."} + std::to_string(NV_TENSORRT_MAJOR); diff --git a/samples/utils/fileLock.cpp b/samples/utils/fileLock.cpp index 0b45c2df..e155c0bd 100644 --- a/samples/utils/fileLock.cpp +++ b/samples/utils/fileLock.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/utils/fileLock.h b/samples/utils/fileLock.h index 628da207..d0f64a5b 100644 --- a/samples/utils/fileLock.h +++ b/samples/utils/fileLock.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/utils/timingCache.cpp b/samples/utils/timingCache.cpp index 1ddf083d..aec9674e 100644 --- a/samples/utils/timingCache.cpp +++ b/samples/utils/timingCache.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/samples/utils/timingCache.h b/samples/utils/timingCache.h index fff4a482..c8ffbd97 100644 --- a/samples/utils/timingCache.h +++ b/samples/utils/timingCache.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/scripts/convert_te_onnx_to_trt_onnx.py b/scripts/convert_te_onnx_to_trt_onnx.py index 6969aa2e..e82f82b2 100644 --- a/scripts/convert_te_onnx_to_trt_onnx.py +++ b/scripts/convert_te_onnx_to_trt_onnx.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/scripts/copyright-scan.py b/scripts/copyright-scan.py index cc51c8e1..45b3bbe9 100644 --- a/scripts/copyright-scan.py +++ b/scripts/copyright-scan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/scripts/stubify.sh b/scripts/stubify.sh index aad43500..788d4672 100755 --- a/scripts/stubify.sh +++ b/scripts/stubify.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/third_party/ieee/half.h b/third_party/ieee/half.h index c1f20f16..c4df4b67 100644 --- a/third_party/ieee/half.h +++ b/third_party/ieee/half.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * 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"); diff --git a/third_party/protobuf.cmake b/third_party/protobuf.cmake index 6b1fbd43..6b3d87ff 100644 --- a/third_party/protobuf.cmake +++ b/third_party/protobuf.cmake @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -48,11 +48,12 @@ macro(configure_protobuf VERSION) set(Protobuf_BIN_DIR "${CMAKE_BINARY_DIR}/${Protobuf_TARGET}/bin") find_file (CENTOS_FOUND centos-release PATHS /etc) - if (CENTOS_FOUND) + find_file (ROCKY_FOUND rocky-release PATHS /etc) + if (CENTOS_FOUND OR ROCKY_FOUND) set(Protobuf_LIB_DIR "${CMAKE_BINARY_DIR}/${Protobuf_TARGET}/lib64") - else (CENTOS_FOUND) + else (CENTOS_FOUND OR ROCKY_FOUND) set(Protobuf_LIB_DIR "${CMAKE_BINARY_DIR}/${Protobuf_TARGET}/lib") - endif (CENTOS_FOUND) + endif (CENTOS_FOUND OR ROCKY_FOUND) set(Protobuf_INCLUDE_DIR "${CMAKE_BINARY_DIR}/${Protobuf_TARGET}/include") set(Protobuf_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/${Protobuf_TARGET}/include") set(Protobuf_PROTOC_EXECUTABLE "${Protobuf_BIN_DIR}/protoc") diff --git a/tools/Polygraphy/CHANGELOG.md b/tools/Polygraphy/CHANGELOG.md index a04b5ca5..c3fb0151 100644 --- a/tools/Polygraphy/CHANGELOG.md +++ b/tools/Polygraphy/CHANGELOG.md @@ -3,6 +3,15 @@ Dates are in YYYY-MM-DD format. +## v0.49.10 (2024-04-19) +### Added +- Added an `EngineFromPath` loader to deserialize an engine directly from disk. This will save CPU memory when weight streaming is enabled. + +### Fixed +- Fixed a memory leak in `TrtRunner` caused by creating a new output allocator per inference. +- Fixed a bug where the `Calibrator` would not force non-index inputs to FP32; this is required by TensorRT. + + ## v0.49.9 (2024-03-19) ### Added - Added `run_opts` argument to `tools.main` to allow calling polygraphy tools from within other Python scripts. diff --git a/tools/Polygraphy/Makefile b/tools/Polygraphy/Makefile index 74b701bc..841025c2 100644 --- a/tools/Polygraphy/Makefile +++ b/tools/Polygraphy/Makefile @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/docs/conf.py b/tools/Polygraphy/docs/conf.py index 58c88f92..fff44c97 100644 --- a/tools/Polygraphy/docs/conf.py +++ b/tools/Polygraphy/docs/conf.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,7 +31,15 @@ ] # Want to be able to generate docs with no dependencies installed -autodoc_mock_imports = ["tensorrt", "onnx", "numpy", "tensorflow", "onnx_graphsurgeon", "onnxruntime", "tf2onnx"] +autodoc_mock_imports = [ + "tensorrt", + "onnx", + "numpy", + "tensorflow", + "onnx_graphsurgeon", + "onnxruntime", + "tf2onnx", +] autodoc_default_options = { @@ -56,7 +64,7 @@ # General information about the project. project = "Polygraphy" -copyright = "2022, NVIDIA" +copyright = "2024, NVIDIA" author = "NVIDIA" version = polygraphy.__version__ @@ -89,7 +97,10 @@ # Unlimited depth sidebar. html_theme_options = {"navigation_depth": -1} -html_sidebars = {"**": ["globaltoc.html", "relations.html", "sourcelink.html", "searchbox.html"]} +html_sidebars = { + "**": ["globaltoc.html", "relations.html", "sourcelink.html", "searchbox.html"] +} + # Allows us to override the default page width in the Sphinx theme. def setup(app): diff --git a/tools/Polygraphy/docs/requirements.txt b/tools/Polygraphy/docs/requirements.txt index 7000a6e4..aeb43aed 100644 --- a/tools/Polygraphy/docs/requirements.txt +++ b/tools/Polygraphy/docs/requirements.txt @@ -1,3 +1,3 @@ -sphinx-rtd-theme==1.0.0 -sphinx==4.4.0 -docutils==0.17.1 +sphinx-rtd-theme==2.0.0 +sphinx==7.2.6 +docutils==0.20.1 diff --git a/tools/Polygraphy/examples/api/00_inference_with_tensorrt/build_and_run.py b/tools/Polygraphy/examples/api/00_inference_with_tensorrt/build_and_run.py index 18667f22..7ca9f3a4 100644 --- a/tools/Polygraphy/examples/api/00_inference_with_tensorrt/build_and_run.py +++ b/tools/Polygraphy/examples/api/00_inference_with_tensorrt/build_and_run.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -21,7 +21,13 @@ starting from an ONNX identity model. """ import numpy as np -from polygraphy.backend.trt import CreateConfig, EngineFromNetwork, NetworkFromOnnxPath, SaveEngine, TrtRunner +from polygraphy.backend.trt import ( + CreateConfig, + EngineFromNetwork, + NetworkFromOnnxPath, + SaveEngine, + TrtRunner, +) def main(): diff --git a/tools/Polygraphy/examples/api/00_inference_with_tensorrt/load_and_run.py b/tools/Polygraphy/examples/api/00_inference_with_tensorrt/load_and_run.py index 3ba4c0db..bc05b45d 100644 --- a/tools/Polygraphy/examples/api/00_inference_with_tensorrt/load_and_run.py +++ b/tools/Polygraphy/examples/api/00_inference_with_tensorrt/load_and_run.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/api/01_comparing_frameworks/example.py b/tools/Polygraphy/examples/api/01_comparing_frameworks/example.py index 2503cb03..5eee4f60 100644 --- a/tools/Polygraphy/examples/api/01_comparing_frameworks/example.py +++ b/tools/Polygraphy/examples/api/01_comparing_frameworks/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -51,7 +51,11 @@ def main(): # TIP: The `compare_func` parameter can be used to control how outputs are compared (see API reference for details). # The default comparison function is created by `CompareFunc.simple()`, but we can construct it # explicitly if we want to change the default parameters, such as tolerance. - assert bool(Comparator.compare_accuracy(run_results, compare_func=CompareFunc.simple(atol=1e-8))) + assert bool( + Comparator.compare_accuracy( + run_results, compare_func=CompareFunc.simple(atol=1e-8) + ) + ) # We can use `RunResults.save()` method to save the inference results to a JSON file. # This can be useful if you want to generate and compare results separately. diff --git a/tools/Polygraphy/examples/api/02_validating_on_a_dataset/example.py b/tools/Polygraphy/examples/api/02_validating_on_a_dataset/example.py index f07bc26f..2c2f262b 100644 --- a/tools/Polygraphy/examples/api/02_validating_on_a_dataset/example.py +++ b/tools/Polygraphy/examples/api/02_validating_on_a_dataset/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -40,7 +40,7 @@ def main(): build_engine = EngineFromNetwork(NetworkFromOnnxPath("identity.onnx")) with TrtRunner(build_engine) as runner: - for (data, golden) in zip(REAL_DATASET, EXPECTED_OUTPUTS): + for data, golden in zip(REAL_DATASET, EXPECTED_OUTPUTS): # NOTE: The runner owns the output buffers and is free to reuse them between `infer()` calls. # Thus, if you want to store results from multiple inferences, you should use `copy.deepcopy()`. outputs = runner.infer(feed_dict={"x": data}) diff --git a/tools/Polygraphy/examples/api/03_interoperating_with_tensorrt/example.py b/tools/Polygraphy/examples/api/03_interoperating_with_tensorrt/example.py index 9658dba0..d3b388a5 100644 --- a/tools/Polygraphy/examples/api/03_interoperating_with_tensorrt/example.py +++ b/tools/Polygraphy/examples/api/03_interoperating_with_tensorrt/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,7 +24,12 @@ import numpy as np import tensorrt as trt from polygraphy import func -from polygraphy.backend.trt import CreateConfig, EngineFromNetwork, NetworkFromOnnxPath, TrtRunner +from polygraphy.backend.trt import ( + CreateConfig, + EngineFromNetwork, + NetworkFromOnnxPath, + TrtRunner, +) # TIP: The immediately evaluated functional API makes it very easy to interoperate @@ -33,6 +38,7 @@ # We can use the `extend` decorator to easily extend lazy loaders provided by Polygraphy # The parameters our decorated function takes should match the return values of the loader we are extending. + # For `NetworkFromOnnxPath`, we can see from the API documentation that it returns a TensorRT # builder, network and parser. That is what our function will receive. @func.extend(NetworkFromOnnxPath("identity.onnx")) diff --git a/tools/Polygraphy/examples/api/04_int8_calibration_in_tensorrt/example.py b/tools/Polygraphy/examples/api/04_int8_calibration_in_tensorrt/example.py index 168bf217..6260b8ef 100644 --- a/tools/Polygraphy/examples/api/04_int8_calibration_in_tensorrt/example.py +++ b/tools/Polygraphy/examples/api/04_int8_calibration_in_tensorrt/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -21,7 +21,13 @@ to calibrate a TensorRT engine to run in INT8 precision. """ import numpy as np -from polygraphy.backend.trt import Calibrator, CreateConfig, EngineFromNetwork, NetworkFromOnnxPath, TrtRunner +from polygraphy.backend.trt import ( + Calibrator, + CreateConfig, + EngineFromNetwork, + NetworkFromOnnxPath, + TrtRunner, +) from polygraphy.logger import G_LOGGER @@ -46,7 +52,8 @@ def main(): # We must enable int8 mode in addition to providing the calibrator. build_engine = EngineFromNetwork( - NetworkFromOnnxPath("identity.onnx"), config=CreateConfig(int8=True, calibrator=calibrator) + NetworkFromOnnxPath("identity.onnx"), + config=CreateConfig(int8=True, calibrator=calibrator), ) # When we activate our runner, it will calibrate and build the engine. If we want to diff --git a/tools/Polygraphy/examples/api/05_using_tensorrt_network_api/example.py b/tools/Polygraphy/examples/api/05_using_tensorrt_network_api/example.py index 91a45235..13f6a95d 100644 --- a/tools/Polygraphy/examples/api/05_using_tensorrt_network_api/example.py +++ b/tools/Polygraphy/examples/api/05_using_tensorrt_network_api/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -37,8 +37,12 @@ def create_network(builder, network): # This network will add 1 to the input tensor. inp = network.add_input(name=INPUT_NAME, shape=INPUT_SHAPE, dtype=trt.float32) - ones = network.add_constant(shape=INPUT_SHAPE, weights=np.ones(shape=INPUT_SHAPE, dtype=np.float32)).get_output(0) - add = network.add_elementwise(inp, ones, op=trt.ElementWiseOperation.SUM).get_output(0) + ones = network.add_constant( + shape=INPUT_SHAPE, weights=np.ones(shape=INPUT_SHAPE, dtype=np.float32) + ).get_output(0) + add = network.add_elementwise( + inp, ones, op=trt.ElementWiseOperation.SUM + ).get_output(0) add.name = OUTPUT_NAME network.mark_output(add) @@ -53,7 +57,9 @@ def main(): build_engine = EngineFromNetwork(create_network) with TrtRunner(build_engine) as runner: - feed_dict = {INPUT_NAME: np.random.random_sample(INPUT_SHAPE).astype(np.float32)} + feed_dict = { + INPUT_NAME: np.random.random_sample(INPUT_SHAPE).astype(np.float32) + } # NOTE: The runner owns the output buffers and is free to reuse them between `infer()` calls. # Thus, if you want to store results from multiple inferences, you should use `copy.deepcopy()`. diff --git a/tools/Polygraphy/examples/api/06_immediate_eval_api/build_and_run.py b/tools/Polygraphy/examples/api/06_immediate_eval_api/build_and_run.py index b0cf567e..b1cffd97 100644 --- a/tools/Polygraphy/examples/api/06_immediate_eval_api/build_and_run.py +++ b/tools/Polygraphy/examples/api/06_immediate_eval_api/build_and_run.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,7 +23,13 @@ save the engine, and finally run inference. """ import numpy as np -from polygraphy.backend.trt import TrtRunner, create_config, engine_from_network, network_from_onnx_path, save_engine +from polygraphy.backend.trt import ( + TrtRunner, + create_config, + engine_from_network, + network_from_onnx_path, + save_engine, +) def main(): diff --git a/tools/Polygraphy/examples/api/06_immediate_eval_api/load_and_run.py b/tools/Polygraphy/examples/api/06_immediate_eval_api/load_and_run.py index 6219ae06..1cabcfea 100644 --- a/tools/Polygraphy/examples/api/06_immediate_eval_api/load_and_run.py +++ b/tools/Polygraphy/examples/api/06_immediate_eval_api/load_and_run.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/api/07_tensorrt_and_dynamic_shapes/example.py b/tools/Polygraphy/examples/api/07_tensorrt_and_dynamic_shapes/example.py index 771bbc53..7842e82d 100644 --- a/tools/Polygraphy/examples/api/07_tensorrt_and_dynamic_shapes/example.py +++ b/tools/Polygraphy/examples/api/07_tensorrt_and_dynamic_shapes/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -50,13 +50,16 @@ def main(): # The dynamic batching case. We use `4` for the opt batch size since that's our most common case. Profile().add("X", min=(1, 3, 28, 28), opt=(4, 3, 28, 28), max=(32, 3, 28, 28)), # The offline case. For best performance, min == opt == max. - Profile().add("X", min=(128, 3, 28, 28), opt=(128, 3, 28, 28), max=(128, 3, 28, 28)), + Profile().add( + "X", min=(128, 3, 28, 28), opt=(128, 3, 28, 28), max=(128, 3, 28, 28) + ), ] # See examples/api/06_immediate_eval_api for details on immediately evaluated functional loaders like `engine_from_network`. # Note that we can freely mix lazy and immediately-evaluated loaders. engine = engine_from_network( - network_from_onnx_path("dynamic_identity.onnx"), config=CreateConfig(profiles=profiles) + network_from_onnx_path("dynamic_identity.onnx"), + config=CreateConfig(profiles=profiles), ) # We'll save the engine so that we can inspect it with `inspect model`. @@ -134,9 +137,13 @@ def main(): # # Alternatively, we could have used the `optimization_profile` parameter (see above). # - offline.set_profile(2) # Use the third profile, which is intended for the offline case. + offline.set_profile( + 2 + ) # Use the third profile, which is intended for the offline case. - large_offline_batch = np.repeat(input_img, 128, axis=0) # Shape: (128, 3, 28, 28) + large_offline_batch = np.repeat( + input_img, 128, axis=0 + ) # Shape: (128, 3, 28, 28) outputs = offline.infer({"X": large_offline_batch}) assert np.array_equal(outputs["Y"], large_offline_batch) diff --git a/tools/Polygraphy/examples/api/08_working_with_run_results_and_saved_inputs_manually/example.py b/tools/Polygraphy/examples/api/08_working_with_run_results_and_saved_inputs_manually/example.py index 6c1f3073..3955928a 100644 --- a/tools/Polygraphy/examples/api/08_working_with_run_results_and_saved_inputs_manually/example.py +++ b/tools/Polygraphy/examples/api/08_working_with_run_results_and_saved_inputs_manually/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/api/09_working_with_pytorch_tensors/example.py b/tools/Polygraphy/examples/api/09_working_with_pytorch_tensors/example.py index 3878f32b..a1e8df83 100644 --- a/tools/Polygraphy/examples/api/09_working_with_pytorch_tensors/example.py +++ b/tools/Polygraphy/examples/api/09_working_with_pytorch_tensors/example.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -21,7 +21,13 @@ import torch -from polygraphy.backend.trt import Calibrator, CreateConfig, TrtRunner, engine_from_network, network_from_onnx_path +from polygraphy.backend.trt import ( + Calibrator, + CreateConfig, + TrtRunner, + engine_from_network, + network_from_onnx_path, +) # If your PyTorch installation has GPU support, then we'll allocate the tensors # directly in GPU memory. This will mean that the calibrator and runner can skip the @@ -38,7 +44,8 @@ def main(): calibrator = Calibrator(data_loader=calib_data()) engine = engine_from_network( - network_from_onnx_path("identity.onnx"), config=CreateConfig(int8=True, calibrator=calibrator) + network_from_onnx_path("identity.onnx"), + config=CreateConfig(int8=True, calibrator=calibrator), ) with TrtRunner(engine) as runner: diff --git a/tools/Polygraphy/examples/cli/convert/01_int8_calibration_in_tensorrt/data_loader.py b/tools/Polygraphy/examples/cli/convert/01_int8_calibration_in_tensorrt/data_loader.py index ff8c45e8..06500ad9 100644 --- a/tools/Polygraphy/examples/cli/convert/01_int8_calibration_in_tensorrt/data_loader.py +++ b/tools/Polygraphy/examples/cli/convert/01_int8_calibration_in_tensorrt/data_loader.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -28,4 +28,6 @@ def load_data(): for _ in range(5): - yield {"x": np.ones(shape=INPUT_SHAPE, dtype=np.float32)} # Still totally real data + yield { + "x": np.ones(shape=INPUT_SHAPE, dtype=np.float32) + } # Still totally real data diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md index d0679707..0fe8d60a 100644 --- a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md +++ b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/README.md @@ -39,8 +39,8 @@ The original and the replaced model can be compared to check if they behave the 1. Find and save matches of toyPlugin in the example network: ```bash - polygraphy plugin match graph_with_subgraph_matching_toy_plugin.onnx \ - --plugin-dir ./plugins + polygraphy plugin match toy_subgraph.onnx \ + --plugin-dir ./plugins -o config.yaml ``` @@ -75,7 +75,7 @@ The original and the replaced model can be compared to check if they behave the 2. **[Optional]** List matches of toyPlugin in the example network, without saving config.yaml: ```bash - polygraphy plugin list graph_with_subgraph_matching_toy_plugin.onnx \ + polygraphy plugin list toy_subgraph.onnx \ --plugin-dir ./plugins ``` @@ -106,17 +106,16 @@ The `plugin replace` subtool replaces subgraphs in an onnx model with plugins 3. Replace parts of the example network with toyPlugin: ```bash - polygraphy plugin replace graph_with_subgraph_matching_toy_plugin.onnx \ - --plugin-dir ./plugins \ - -o replaced.onnx + polygraphy plugin replace toy_subgraph.onnx \ + --plugin-dir ./plugins --config config.yaml -o replaced.onnx ``` This will display something like: ``` - [I] Loading model: /Users/pkisfaludi/Documents/git/Polygraphy/examples/cli/plugin/03_replace_subgraph_with_a_plugin/graph_with_subgraph_matching_toy_plugin.onnx + [I] Loading model: /Users/pkisfaludi/Documents/git/Polygraphy/examples/cli/plugin/03_replace_subgraph_with_a_plugin/toy_subgraph.onnx ``` The result file is replaced.onnx, where a subgraph in the example network is replaced by toyPlugin - \ No newline at end of file + diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/__init__.py b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py index 6cf600ba..00c52974 100644 --- a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py +++ b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/plugins/toyPlugin/pattern.py @@ -1,7 +1,8 @@ from polygraphy import mod gs = mod.lazy_import("onnx_graphsurgeon>=0.5.0") +from typing import List,Dict -def get_plugin_pattern() -> gs.GraphPattern: +def get_plugin_pattern(): """ Toy plugin pattern: A B @@ -23,9 +24,25 @@ def get_plugin_pattern() -> gs.GraphPattern: return pattern -def get_plugin_attributes(sg) -> dict: - """ - example plugin attribute mapping, where the plugin has attribute ToyX, which gets its value from C.x * 2 - """ - return {"ToyX": int(sg.get("Cnode").attrs["x"]) * 2} +def get_matching_subgraphs(graph) -> List[Dict[str,str]]: + gp = get_plugin_pattern() + matches = gp.match_all(graph) + ans = [] + for m in matches: + # save the input and output tensor names of the matching subgraph(s) + input_tensors = list(set([ip_tensor.name for ip_tensor in m.inputs])) + output_tensors = list(set([op_tensor.name for op_tensor in m.outputs])) + + attrs = {"ToyX": int(m.get("Cnode").attrs["x"]) * 2} + ioa = { + 'inputs':input_tensors, + 'outputs':output_tensors, + 'attributes':attrs + } + ans.append(ioa) + return ans +def get_plugin_metadata() -> Dict[str,str]: + return {'name':'toyPlugin', + 'op':'CustomToyPlugin', + } diff --git a/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/graph_with_subgraph_matching_toy_plugin.onnx b/tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/toy_subgraph.onnx similarity index 100% rename from tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/graph_with_subgraph_matching_toy_plugin.onnx rename to tools/Polygraphy/examples/cli/plugin/01_match_and_replace_plugin/toy_subgraph.onnx diff --git a/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/create_config.py b/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/create_config.py index eeec81db..29fb2874 100644 --- a/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/create_config.py +++ b/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/create_config.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/define_network.py b/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/define_network.py index 10e2d9e5..d5548261 100755 --- a/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/define_network.py +++ b/tools/Polygraphy/examples/cli/run/04_defining_a_tensorrt_network_or_config_manually/define_network.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,6 +23,7 @@ parse_onnx = NetworkFromOnnxPath("identity.onnx") + # If we define a function called `load_network`, polygraphy can # use it directly in place of using a model file. # diff --git a/tools/Polygraphy/examples/cli/run/05_comparing_with_custom_input_data/data_loader.py b/tools/Polygraphy/examples/cli/run/05_comparing_with_custom_input_data/data_loader.py index 1d025ef5..bde44639 100644 --- a/tools/Polygraphy/examples/cli/run/05_comparing_with_custom_input_data/data_loader.py +++ b/tools/Polygraphy/examples/cli/run/05_comparing_with_custom_input_data/data_loader.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,10 +31,13 @@ INPUT_SHAPE = (1, 2, 28, 28) + # Option 1: Define a function that will yield feed_dicts (i.e. Dict[str, np.ndarray]) def load_data(): for _ in range(5): - yield {"x": np.ones(shape=INPUT_SHAPE, dtype=np.float32)} # Still totally real data + yield { + "x": np.ones(shape=INPUT_SHAPE, dtype=np.float32) + } # Still totally real data # Option 2: Create a JSON file containing the input data using the `save_json()` helper. diff --git a/tools/Polygraphy/examples/cli/run/06_comparing_with_custom_output_data/generate_data.py b/tools/Polygraphy/examples/cli/run/06_comparing_with_custom_output_data/generate_data.py index df44eaa5..9b4aca50 100644 --- a/tools/Polygraphy/examples/cli/run/06_comparing_with_custom_output_data/generate_data.py +++ b/tools/Polygraphy/examples/cli/run/06_comparing_with_custom_output_data/generate_data.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/add_constraints.py b/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/add_constraints.py index fac9de5f..7b993305 100755 --- a/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/add_constraints.py +++ b/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/add_constraints.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/constrained_network.py b/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/constrained_network.py index 2a420031..3a56a440 100755 --- a/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/constrained_network.py +++ b/tools/Polygraphy/examples/cli/run/08_adding_precision_constraints/constrained_network.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/__init__.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/__init__.py index fb3b870b..dadebabb 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/__init__.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/__init__.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/__init__.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/__init__.py index f48a3bc8..f82a9b42 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/__init__.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/__init__.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/loader.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/loader.py index f7a7b6f5..5a3ffba7 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/loader.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -96,14 +96,20 @@ def add_to_script_impl(self, script): # First, import the loader from Polygraphy: script.add_import(imports=["GsFromOnnx"], frm="polygraphy.backend.onnx") # Next, invoke the loader with arguments (in this case, the ONNX model loader name), and add it to the script. - loader_name = script.add_loader(make_invocable("GsFromOnnx", loader_name), loader_id="gs_from_onnx") + loader_name = script.add_loader( + make_invocable("GsFromOnnx", loader_name), loader_id="gs_from_onnx" + ) # Finally, add the ReplaceReshapeArgs loader. # Unlike the Polygraphy loaders, we'll need to import our loader from the extension module. - script.add_import(imports=["ReplaceReshapes"], frm="polygraphy_reshape_destroyer.backend") + script.add_import( + imports=["ReplaceReshapes"], frm="polygraphy_reshape_destroyer.backend" + ) # Add the loader and return the ID so that it can be used by subsequent loaders or runners. # NOTE: We can provide additional positional and keyword arguments to `make_invocable` to pass them on to the loader. return script.add_loader( - make_invocable("ReplaceReshapes", loader_name, rename_nodes=self.rename_nodes), + make_invocable( + "ReplaceReshapes", loader_name, rename_nodes=self.rename_nodes + ), loader_id="replace_reshapes", ) diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/runner.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/runner.py index 1de79035..825bd021 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/runner.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/args/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -71,10 +71,14 @@ def add_to_script_impl(self, script): loader_name = self.arg_groups[ReplaceReshapeArgs].add_to_script(script) # Next, we'll add an import for our runner. - script.add_import(imports=["IdentityOnlyRunner"], frm="polygraphy_reshape_destroyer.backend") + script.add_import( + imports=["IdentityOnlyRunner"], frm="polygraphy_reshape_destroyer.backend" + ) # Lastly, we can add our runner using the `Script.add_runner()` API. # Like in the loader implementation, additional arguments can be provided directly to `make_invocable`. - script.add_runner(make_invocable("IdentityOnlyRunner", loader_name, speed=self.speed)) + script.add_runner( + make_invocable("IdentityOnlyRunner", loader_name, speed=self.speed) + ) # NOTE: Unlike the `add_to_script_impl` method of regular `BaseArgs`, that of `BaseRunnerArgs` # is not expected to return anything. diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/__init__.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/__init__.py index 5e210e27..99c340ba 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/__init__.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/__init__.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/loader.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/loader.py index 13d8667a..615fae80 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/loader.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -61,7 +61,9 @@ class ReplaceReshapes(BaseLoader): Functor that replaces no-op Reshape nodes in an ONNX-GraphSurgeon graph with Identity. """ - def __init__(self, graph: Union[gs.Graph, Callable[[], gs.Graph]], rename_nodes: bool = None): + def __init__( + self, graph: Union[gs.Graph, Callable[[], gs.Graph]], rename_nodes: bool = None + ): """ Replaces no-op Reshape nodes in an ONNX-GraphSurgeon graph with Identity. diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/runner.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/runner.py index b5c2ed75..bb6a280e 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/runner.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/backend/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -59,7 +59,9 @@ def __init__(self, graph, name=None, speed: str = None): VALID_SPEEDS = ["slow", "medium", "fast"] if self.speed not in VALID_SPEEDS: # Like Polygraphy, extension modules should use `G_LOGGER.critical()` for any unrecoverable errors. - G_LOGGER.critical(f"Invalid speed: {self.speed}. Note: Valid speeds are: {VALID_SPEEDS}") + G_LOGGER.critical( + f"Invalid speed: {self.speed}. Note: Valid speeds are: {VALID_SPEEDS}" + ) @util.check_called_by("activate") def activate_impl(self): diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/export.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/export.py index ec3b0349..92de511b 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/export.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/polygraphy_reshape_destroyer/export.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -22,6 +22,7 @@ from polygraphy_reshape_destroyer.args import ReplaceReshapeArgs, IdentityOnlyRunnerArgs + # The entry point is expected to take no arguments and return a list of argument group instances. # # NOTE: Argument groups will be parsed in the order in which they are provided, diff --git a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/setup.py b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/setup.py index a8290580..3150d1a3 100644 --- a/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/setup.py +++ b/tools/Polygraphy/examples/dev/02_extending_polygraphy_run/extension_module/setup.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/__init__.py b/tools/Polygraphy/polygraphy/__init__.py index 41215171..2feb03ed 100644 --- a/tools/Polygraphy/polygraphy/__init__.py +++ b/tools/Polygraphy/polygraphy/__init__.py @@ -1,3 +1,3 @@ import polygraphy.config -__version__ = "0.49.9" +__version__ = "0.49.10" diff --git a/tools/Polygraphy/polygraphy/backend/base/loader.py b/tools/Polygraphy/polygraphy/backend/base/loader.py index 481cbfe1..ab9f4330 100644 --- a/tools/Polygraphy/polygraphy/backend/base/loader.py +++ b/tools/Polygraphy/polygraphy/backend/base/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/backend/base/runner.py b/tools/Polygraphy/polygraphy/backend/base/runner.py index ae033e61..7f9c771b 100644 --- a/tools/Polygraphy/polygraphy/backend/base/runner.py +++ b/tools/Polygraphy/polygraphy/backend/base/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -126,7 +126,9 @@ def get_input_metadata(self, use_numpy_dtypes=None): TensorMetadata: Input names, shapes, and data types. """ if not self.is_active: - G_LOGGER.critical(f"{self.name:35} | Must be activated prior to calling get_input_metadata()") + G_LOGGER.critical( + f"{self.name:35} | Must be activated prior to calling get_input_metadata()" + ) use_numpy_dtypes = util.default(use_numpy_dtypes, True) @@ -180,11 +182,16 @@ def infer(self, feed_dict, check_inputs=True, *args, **kwargs): outputs from multiple inferences, you should make a copy with ``copy.deepcopy(outputs)``. """ if not self.is_active: - G_LOGGER.critical(f"{self.name:35} | Must be activated prior to calling infer()") + G_LOGGER.critical( + f"{self.name:35} | Must be activated prior to calling infer()" + ) if check_inputs: input_metadata = self.get_input_metadata(use_numpy_dtypes=False) - G_LOGGER.verbose(f"{self.name:35} | Input metadata is: {input_metadata}", mode=LogMode.ONCE) + G_LOGGER.verbose( + f"{self.name:35} | Input metadata is: {input_metadata}", + mode=LogMode.ONCE, + ) base_util.check_inputs(feed_dict, input_metadata) return self.infer_impl(feed_dict, *args, **kwargs) @@ -246,4 +253,6 @@ def deactivate(self): def __del__(self): if self.is_active: # __del__ is not guaranteed to be called, but when it is, this could be a useful warning. - print(f"[W] {self.name:35} | Was activated but never deactivated. This could cause a memory leak!") + print( + f"[W] {self.name:35} | Was activated but never deactivated. This could cause a memory leak!" + ) diff --git a/tools/Polygraphy/polygraphy/backend/base/util.py b/tools/Polygraphy/polygraphy/backend/base/util.py index 03821811..0dce8dd1 100644 --- a/tools/Polygraphy/polygraphy/backend/base/util.py +++ b/tools/Polygraphy/polygraphy/backend/base/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -29,7 +29,9 @@ def check_inputs(feed_dict, input_metadata): input_metadata (TensorMetadata): The expected input metadata. """ - util.check_sequence_contains(feed_dict.keys(), input_metadata.keys(), name="input data", items_name="inputs") + util.check_sequence_contains( + feed_dict.keys(), input_metadata.keys(), name="input data", items_name="inputs" + ) for name, inp in feed_dict.items(): meta = input_metadata[name] diff --git a/tools/Polygraphy/polygraphy/backend/common/loader.py b/tools/Polygraphy/polygraphy/backend/common/loader.py index 38894bf6..c246dfa4 100644 --- a/tools/Polygraphy/polygraphy/backend/common/loader.py +++ b/tools/Polygraphy/polygraphy/backend/common/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/backend/onnx/loader.py b/tools/Polygraphy/polygraphy/backend/onnx/loader.py index 6992ad00..44a5897d 100644 --- a/tools/Polygraphy/polygraphy/backend/onnx/loader.py +++ b/tools/Polygraphy/polygraphy/backend/onnx/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -28,14 +28,18 @@ np = mod.lazy_import("numpy") onnx = mod.lazy_import("onnx>=1.8.1") onnxrt = mod.lazy_import("onnxruntime>=1.10.0") -onnxmltools = mod.lazy_import("onnxmltools==1.11.1", requires=["onnxconverter_common==1.12.2"]) +onnxmltools = mod.lazy_import( + "onnxmltools==1.11.1", requires=["onnxconverter_common==1.12.2"] +) tf = mod.lazy_import("tensorflow<2.0") tf2onnx = mod.lazy_import("tf2onnx") tf_util = mod.lazy_import("polygraphy.backend.tf.util", log=False) gs = mod.lazy_import("onnx_graphsurgeon>=0.3.27") # ONNX-RT's shape inference also requires "sympy", but it is not reported as a dependency, # so we work around it by checking for it manually. -onnxrt_symbolic_shape_inference = mod.lazy_import("onnxruntime.tools.symbolic_shape_infer>=1.10.0", requires=["sympy"]) +onnxrt_symbolic_shape_inference = mod.lazy_import( + "onnxruntime.tools.symbolic_shape_infer>=1.10.0", requires=["sympy"] +) LARGE_MODEL_THRESHOLD = 512 << 20 # 512 MiB PROTOBUF_THRESHOLD = 2e9 @@ -151,7 +155,9 @@ def call_impl(self): """ G_LOGGER.info(f"Loading model: {self.path}") # If external_data_dir is not None, we'll load external data ourselves - auto_load_ext_data = self.external_data_dir is None and not self.ignore_external_data + auto_load_ext_data = ( + self.external_data_dir is None and not self.ignore_external_data + ) try: model = onnx.load(self.path, load_external_data=auto_load_ext_data) except FileNotFoundError: @@ -165,7 +171,9 @@ def call_impl(self): if self.external_data_dir is not None: G_LOGGER.verbose(f"Loading external data from: {self.external_data_dir}") - onnx.external_data_helper.load_external_data_for_model(model, self.external_data_dir) + onnx.external_data_helper.load_external_data_for_model( + model, self.external_data_dir + ) return model @@ -202,13 +210,20 @@ def call_impl(self): graphdef = graph.as_graph_def() if self.optimize: - graphdef = tf2onnx.tfonnx.tf_optimize(input_names, output_names, graph.as_graph_def()) + graphdef = tf2onnx.tfonnx.tf_optimize( + input_names, output_names, graph.as_graph_def() + ) - with tf.Graph().as_default() as graph, tf.compat.v1.Session(graph=graph) as sess: + with tf.Graph().as_default() as graph, tf.compat.v1.Session( + graph=graph + ) as sess: tf.import_graph_def(graphdef, name="") onnx_graph = tf2onnx.tfonnx.process_tf_graph( - graph, input_names=input_names, output_names=output_names, opset=self.opset + graph, + input_names=input_names, + output_names=output_names, + opset=self.opset, ) if self.optimize: onnx_graph = tf2onnx.optimizer.optimize_graph(onnx_graph) @@ -386,14 +401,18 @@ def run_const_fold_pass(model): del model graph.fold_constants( - fold_shapes=self.fold_shapes, partitioning=self.partitioning, size_threshold=self.size_threshold + fold_shapes=self.fold_shapes, + partitioning=self.partitioning, + size_threshold=self.size_threshold, ) model = gs.export_onnx(graph.cleanup(), do_type_check=False) del graph if self.fold_shapes and self.do_shape_inference: - model = infer_shapes(model, allow_onnxruntime=self.allow_onnxruntime_shape_inference) + model = infer_shapes( + model, allow_onnxruntime=self.allow_onnxruntime_shape_inference + ) return model # Need to manually trigger the autoinstall this since it's used by ONNX-GS, which does not have an autoinstall mechanism. @@ -410,7 +429,9 @@ def run_const_fold_pass(model): postfold_num_nodes = -1 index = 0 - while (prefold_num_nodes != postfold_num_nodes) and (self.num_passes is None or index < self.num_passes): + while (prefold_num_nodes != postfold_num_nodes) and ( + self.num_passes is None or index < self.num_passes + ): prefold_num_nodes = onnx_util.get_num_nodes(model) G_LOGGER.start(f"Folding Constants | Pass {index + 1}") @@ -419,7 +440,9 @@ def run_const_fold_pass(model): except Exception as err: if not self.error_ok: raise - G_LOGGER.warning(f"Constant folding pass failed. Skipping subsequent passes.\nNote: Error was:\n{err}") + G_LOGGER.warning( + f"Constant folding pass failed. Skipping subsequent passes.\nNote: Error was:\n{err}" + ) break else: postfold_num_nodes = onnx_util.get_num_nodes(model) @@ -484,12 +507,18 @@ def set_upper_bound(graph, target_tensor_list): assert len(tensor.inputs) == 1 producer = tensor.inputs[0] producer_idx = producer.outputs.index(tensor) - tensor_copy = gs.Variable(tensor.name + "_copy", dtype=tensor.dtype, shape=tensor.shape) + tensor_copy = gs.Variable( + tensor.name + "_copy", dtype=tensor.dtype, shape=tensor.shape + ) upper_bound_values = np.array(upper_bound) if tensor.shape is not None and len(tensor.shape) > 0: upper_bound_values = np.array([upper_bound] * len(tensor.shape)) - tensor_upper_bound = gs.Constant(tensor.name + "_upper_bound", values=upper_bound_values) - min_node = gs.Node(op="Min", inputs=[tensor_copy, tensor_upper_bound], outputs=[tensor]) + tensor_upper_bound = gs.Constant( + tensor.name + "_upper_bound", values=upper_bound_values + ) + min_node = gs.Node( + op="Min", inputs=[tensor_copy, tensor_upper_bound], outputs=[tensor] + ) producer.outputs[producer_idx] = tensor_copy tensor.inputs = [min_node] graph.nodes.append(min_node) @@ -573,7 +602,9 @@ def __init__( self.error_ok = util.default(error_ok, True) self.external_data_dir = external_data_dir # Subtract a little so we're below the real threshold - self.save_to_disk_threshold_bytes = util.default(save_to_disk_threshold_bytes, PROTOBUF_THRESHOLD) + self.save_to_disk_threshold_bytes = util.default( + save_to_disk_threshold_bytes, PROTOBUF_THRESHOLD + ) self.allow_onnxruntime = util.default(allow_onnxruntime, True) def _run_onnx_shape_inference(self, model, external_data_dir): @@ -603,7 +634,9 @@ def _run_onnx_shape_inference(self, model, external_data_dir): if isinstance(model, onnx.ModelProto): model = onnx.shape_inference.infer_shapes(model) else: - tmp_path = util.NamedTemporaryFile(prefix="tmp_polygraphy_", suffix=".onnx").name + tmp_path = util.NamedTemporaryFile( + prefix="tmp_polygraphy_", suffix=".onnx" + ).name G_LOGGER.verbose(f"Writing shape-inferred model to: {tmp_path}") onnx.shape_inference.infer_shapes_path(model, tmp_path) # In cases where the original model had external data stored in the same directory, @@ -611,14 +644,19 @@ def _run_onnx_shape_inference(self, model, external_data_dir): # In such cases, we need to use the model's directory as the external data path # for the newly generated model. model = onnx_from_path( - tmp_path, external_data_dir=util.default(external_data_dir, os.path.dirname(model) or None) + tmp_path, + external_data_dir=util.default( + external_data_dir, os.path.dirname(model) or None + ), ) return model def _run_onnxruntime_shape_inference(self, model, external_data_dir): if not isinstance(model, onnx.ModelProto): model = onnx_from_path(model, external_data_dir=external_data_dir) - return onnxrt_symbolic_shape_inference.SymbolicShapeInference.infer_shapes(model, auto_merge=True) + return onnxrt_symbolic_shape_inference.SymbolicShapeInference.infer_shapes( + model, auto_merge=True + ) @util.check_called_by("__call__") def call_impl(self): @@ -635,7 +673,9 @@ def call_impl(self): use_onnx_shape_inference = not self.allow_onnxruntime if self.allow_onnxruntime: try: - model = self._run_onnxruntime_shape_inference(model, external_data_dir) + model = self._run_onnxruntime_shape_inference( + model, external_data_dir + ) G_LOGGER.verbose( "Inferred shapes in the model with `onnxruntime.tools.symbolic_shape_infer`.\n" "Note: To force Polygraphy to use `onnx.shape_inference` instead, set `allow_onnxruntime=False` or " @@ -659,7 +699,9 @@ def call_impl(self): if not self.error_ok: raise G_LOGGER.warning(f"ONNX shape inference exited with an error:\n{err}") - G_LOGGER.internal_error(f"ONNX shape inference exited with an error:\n{err}") + G_LOGGER.internal_error( + f"ONNX shape inference exited with an error:\n{err}" + ) if not isinstance(model, onnx.ModelProto): model = onnx_from_path(model, external_data_dir=external_data_dir) @@ -675,7 +717,9 @@ class ExtractSubgraph(BaseLoader): Functor that extracts a subgraph from an ONNX model. """ - def __init__(self, model, input_metadata=None, output_metadata=None, check_meta=None): + def __init__( + self, model, input_metadata=None, output_metadata=None, check_meta=None + ): """ Extracts a subgraph from an ONNX model. @@ -722,7 +766,9 @@ def update_tensor(name, dtype, shape): # No need to update constants if isinstance(tensor, gs.Variable): tensor.dtype, tensor.shape = ( - DataType.to_dtype(DataType.from_dtype(dtype), "onnx") if dtype is not None else None + DataType.to_dtype(DataType.from_dtype(dtype), "onnx") + if dtype is not None + else None ) or tensor.dtype, shape or tensor.shape return tensor @@ -750,15 +796,24 @@ def check_meta(name, dtype, shape, meta_type, needs_shape=True): graph.outputs.clear() for name, (dtype, shape) in self.output_metadata.items(): tensor = update_tensor(name, dtype, shape) - check_meta(name, tensor.dtype, tensor.shape, "Output", needs_shape=False) + check_meta( + name, tensor.dtype, tensor.shape, "Output", needs_shape=False + ) graph.outputs.append(tensor) graph.cleanup() tensor_map = graph.tensors() for tensor in tensor_map.values(): - if isinstance(tensor, gs.Variable) and not tensor.inputs and tensor not in graph.inputs: - consumer_nodes = [f"Node: '{node.name}' (Op: {node.op})" for node in tensor.outputs] + if ( + isinstance(tensor, gs.Variable) + and not tensor.inputs + and tensor not in graph.inputs + ): + consumer_nodes = [ + f"Node: '{node.name}' (Op: {node.op})" + for node in tensor.outputs + ] G_LOGGER.error( f"Tensor: '{tensor.name}' is a variable tensor consumed by: {consumer_nodes}, " "but is not produced by a node or marked as a graph input." @@ -775,7 +830,14 @@ class SaveOnnx(BaseLoader): Functor that saves an ONNX model to the specified path. """ - def __init__(self, model, path, external_data_path=None, size_threshold=None, all_tensors_to_one_file=None): + def __init__( + self, + model, + path, + external_data_path=None, + size_threshold=None, + all_tensors_to_one_file=None, + ): """ Saves an ONNX model to the specified path. @@ -828,12 +890,16 @@ def call_impl(self): external_data_path = self.external_data_path if external_data_path is not None: - G_LOGGER.verbose(f"Saving external data for ONNX model to: {external_data_path}") + G_LOGGER.verbose( + f"Saving external data for ONNX model to: {external_data_path}" + ) try: onnx.external_data_helper.convert_model_to_external_data( model, location=external_data_path, - all_tensors_to_one_file=util.default(self.all_tensors_to_one_file, True), + all_tensors_to_one_file=util.default( + self.all_tensors_to_one_file, True + ), size_threshold=util.default(self.size_threshold, 1024), ) except TypeError: @@ -844,7 +910,9 @@ def call_impl(self): onnx.external_data_helper.convert_model_to_external_data( model, location=external_data_path, - all_tensors_to_one_file=util.default(self.all_tensors_to_one_file, True), + all_tensors_to_one_file=util.default( + self.all_tensors_to_one_file, True + ), ) else: if self.size_threshold is not None: diff --git a/tools/Polygraphy/polygraphy/backend/onnx/util.py b/tools/Polygraphy/polygraphy/backend/onnx/util.py index 1760cf19..a6eff90c 100644 --- a/tools/Polygraphy/polygraphy/backend/onnx/util.py +++ b/tools/Polygraphy/polygraphy/backend/onnx/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -45,7 +45,12 @@ def _get_num_graph_nodes(graph): def all_tensor_names(model, include_inputs=None): include_inputs = util.default(include_inputs, False) - all_outputs = [output for node in model.graph.node if node.op_type != "Constant" for output in node.output] + all_outputs = [ + output + for node in model.graph.node + if node.op_type != "Constant" + for output in node.output + ] if include_inputs: all_outputs += [inp.name for inp in model.graph.input] all_outputs = util.unique_list(all_outputs) @@ -54,7 +59,9 @@ def all_tensor_names(model, include_inputs=None): def _check_has_tensors(model, outputs): all_outputs = all_tensor_names(model, include_inputs=True) - util.check_sequence_contains(all_outputs, outputs, name="the model", items_name="outputs", check_extra=False) + util.check_sequence_contains( + all_outputs, outputs, name="the model", items_name="outputs", check_extra=False + ) def mark_outputs(model, outputs): @@ -68,7 +75,9 @@ def mark_outputs(model, outputs): value_info_map = {t.name: t for t in model.graph.value_info} out_tensors = [] for output in outputs: - value_info = value_info_map.get(output, onnx.helper.make_empty_tensor_value_info(output)) + value_info = value_info_map.get( + output, onnx.helper.make_empty_tensor_value_info(output) + ) out_tensors.append(value_info) G_LOGGER.ultra_verbose(f"Marked output tensors in ONNX model: {out_tensors}") @@ -125,7 +134,9 @@ def get_values(tensor): try: return onnx_numpy_helper.to_array(tensor) except Exception as err: - G_LOGGER.error(f"Failed to load weights.\nNote: Error was: {err}", mode=LogMode.ONCE) + G_LOGGER.error( + f"Failed to load weights.\nNote: Error was: {err}", mode=LogMode.ONCE + ) return "" @@ -139,7 +150,9 @@ def get_tensor_metadata(tensors): def get_input_metadata(graph): # Some "inputs" are actually weights with initalizers, so we need to eliminate those. initializer_names = {tensor.name for tensor in graph.initializer} - input_tensors = [tensor for tensor in graph.input if tensor.name not in initializer_names] + input_tensors = [ + tensor for tensor in graph.input if tensor.name not in initializer_names + ] return get_tensor_metadata(input_tensors) @@ -182,12 +195,18 @@ def get_opset(): onnx_str += "\n\n" onnx_str += str_from_onnx_graph( - model.graph, tensors={}, show_layers=show_layers, show_attrs=show_attrs, show_weights=show_weights + model.graph, + tensors={}, + show_layers=show_layers, + show_attrs=show_attrs, + show_weights=show_weights, ) return onnx_str -def str_from_onnx_graph(graph, tensors, show_layers, show_attrs, show_weights, indent_level=0): +def str_from_onnx_graph( + graph, tensors, show_layers, show_attrs, show_weights, indent_level=0 +): input_metadata = get_input_metadata(graph) output_metadata = get_output_metadata(graph) initializer_metadata = get_tensor_metadata(graph.initializer) @@ -205,7 +224,9 @@ def str_from_onnx_graph(graph, tensors, show_layers, show_attrs, show_weights, i if show_attrs and graph.doc_string: onnx_str += f"---- Docstring ----\n{graph.doc_string}\n\n" - onnx_str += f"---- {len(input_metadata)} {graph_type} Input(s) ----\n{input_metadata}\n\n" + onnx_str += ( + f"---- {len(input_metadata)} {graph_type} Input(s) ----\n{input_metadata}\n\n" + ) onnx_str += f"---- {len(output_metadata)} {graph_type} Output(s) ----\n{output_metadata}\n\n" onnx_str += f"---- {len(initializer_metadata)} Initializer(s) ----\n" @@ -232,7 +253,12 @@ def get_names_and_meta(names): return names_lst, metadata # Maps values from the AttributeType enum to their string representations, e.g., {1: "FLOAT"} - ATTR_TYPE_MAPPING = dict(zip(onnx.AttributeProto.AttributeType.values(), onnx.AttributeProto.AttributeType.keys())) + ATTR_TYPE_MAPPING = dict( + zip( + onnx.AttributeProto.AttributeType.values(), + onnx.AttributeProto.AttributeType.keys(), + ) + ) # Maps an ONNX attribute to the corresponding Python property ONNX_PYTHON_ATTR_MAPPING = { @@ -257,7 +283,9 @@ def process_attr(attr_str: str): elif attr_str == "TENSOR": tensor_str = f"Tensor: [dtype={get_dtype(processed)}, shape={get_shape(processed)}]" if show_weights: - tensor_str += " | Values:\n" + util.indent_block(str(get_values(processed))) + tensor_str += " | Values:\n" + util.indent_block( + str(get_values(processed)) + ) processed = tensor_str elif attr_str == "GRAPH": processed = "\n" + str_from_onnx_graph( @@ -280,7 +308,9 @@ def process_attr(attr_str: str): if attr_str in ONNX_PYTHON_ATTR_MAPPING: attr_dict[attr.name] = process_attr(attr_str) else: - G_LOGGER.warning(f"Attribute of type {attr_str} is currently unsupported. Skipping attribute.") + G_LOGGER.warning( + f"Attribute of type {attr_str} is currently unsupported. Skipping attribute." + ) else: G_LOGGER.warning( f"Attribute type: {attr.type} was not recognized. Was the graph generated with a newer IR version than the installed `onnx` package? Skipping attribute." @@ -294,7 +324,14 @@ def process_attr(attr_str: str): output_names, output_meta = get_names_and_meta(node.output) onnx_str += util.str_from_layer( - "Node", index, node.name, node.op_type, input_names, input_meta, output_names, output_meta + "Node", + index, + node.name, + node.op_type, + input_names, + input_meta, + output_names, + output_meta, ) if show_attrs: @@ -333,7 +370,9 @@ def set_shapes_from_layerwise_meta(graph, layerwise_meta): for tensor in graph.tensors().values(): if isinstance(tensor, gs.Variable) and tensor.name in layerwise_meta: tensor.shape = layerwise_meta[tensor.name].shape - tensor.dtype = DataType.to_dtype(DataType.from_dtype(layerwise_meta[tensor.name].dtype), "onnx") + tensor.dtype = DataType.to_dtype( + DataType.from_dtype(layerwise_meta[tensor.name].dtype), "onnx" + ) def lower_constant_nodes(graph): @@ -383,7 +422,11 @@ def check_op(node, const_tensor_set): # Find all constant tensors. def get_const_tensors(graph): - return {tensor.name for tensor in graph.tensors().values() if isinstance(tensor, gs.Constant)} + return { + tensor.name + for tensor in graph.tensors().values() + if isinstance(tensor, gs.Constant) + } # Find all dynamic shape symbols, customers will set upper bounds for these symbols when building the model in TensorRT. def get_dynamic_shapes(graph): @@ -426,7 +469,10 @@ def get_target_tensors(graph): if check_node: target_tensor = check_op(node, const_tensor_set) # Avoid duplication. - if target_tensor is not None and target_tensor.name not in target_tensor_names: + if ( + target_tensor is not None + and target_tensor.name not in target_tensor_names + ): target_tensor_names.add(target_tensor.name) target_tensor_list.append(target_tensor) return target_tensor_list diff --git a/tools/Polygraphy/polygraphy/backend/onnxrt/loader.py b/tools/Polygraphy/polygraphy/backend/onnxrt/loader.py index b2eb4ab7..f8f1f4a4 100644 --- a/tools/Polygraphy/polygraphy/backend/onnxrt/loader.py +++ b/tools/Polygraphy/polygraphy/backend/onnxrt/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -64,5 +64,7 @@ def call_impl(self): ) providers.append(matched_prov) - G_LOGGER.start(f"Creating ONNX-Runtime Inference Session with providers: {providers}") + G_LOGGER.start( + f"Creating ONNX-Runtime Inference Session with providers: {providers}" + ) return onnxrt.InferenceSession(model_bytes, providers=providers) diff --git a/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py b/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py index 2c4bf7a0..2b3b1c88 100644 --- a/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py +++ b/tools/Polygraphy/polygraphy/backend/onnxrt/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/backend/pluginref/references.py b/tools/Polygraphy/polygraphy/backend/pluginref/references.py index 2a50006c..1c7e842d 100644 --- a/tools/Polygraphy/polygraphy/backend/pluginref/references.py +++ b/tools/Polygraphy/polygraphy/backend/pluginref/references.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -53,7 +53,9 @@ def wrapped_func(node, intermediate_tensors): f"{op} reference implementation returned the wrong number of outputs.\nNote: Expected {len(node.outputs)} but recevied {len(outputs)}" ) - return {out_tensor.name: out for out_tensor, out in zip(node.outputs, outputs)} + return { + out_tensor.name: out for out_tensor, out in zip(node.outputs, outputs) + } OP_REGISTRY[op] = wrapped_func return wrapped_func diff --git a/tools/Polygraphy/polygraphy/backend/pluginref/runner.py b/tools/Polygraphy/polygraphy/backend/pluginref/runner.py index 150bc38d..e89f050f 100644 --- a/tools/Polygraphy/polygraphy/backend/pluginref/runner.py +++ b/tools/Polygraphy/polygraphy/backend/pluginref/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -61,9 +61,13 @@ def infer_impl(self, feed_dict): intermediate_tensors = copy.copy(feed_dict) for node in self.graph.nodes: if node.op not in OP_REGISTRY: - G_LOGGER.critical(f"Op: {node.op} does not have a reference implementation registered!") + G_LOGGER.critical( + f"Op: {node.op} does not have a reference implementation registered!" + ) - intermediate_tensors.update(OP_REGISTRY[node.op](node, intermediate_tensors)) + intermediate_tensors.update( + OP_REGISTRY[node.op](node, intermediate_tensors) + ) outputs = OrderedDict() for out in self.graph.outputs: diff --git a/tools/Polygraphy/polygraphy/backend/pyt/runner.py b/tools/Polygraphy/polygraphy/backend/pyt/runner.py index 4af81f1f..2240ddb1 100644 --- a/tools/Polygraphy/polygraphy/backend/pyt/runner.py +++ b/tools/Polygraphy/polygraphy/backend/pyt/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -63,7 +63,9 @@ def infer_impl(self, feed_dict): with torch.no_grad(): inputs = [ torch.from_numpy(val.astype(dtype)).cuda() - for (val, (dtype, _)) in zip(feed_dict.values(), self.input_metadata.values()) + for (val, (dtype, _)) in zip( + feed_dict.values(), self.input_metadata.values() + ) ] start = time.time() outputs = self.model(*inputs) diff --git a/tools/Polygraphy/polygraphy/backend/tf/loader.py b/tools/Polygraphy/polygraphy/backend/tf/loader.py index 3b86dcab..49550623 100644 --- a/tools/Polygraphy/polygraphy/backend/tf/loader.py +++ b/tools/Polygraphy/polygraphy/backend/tf/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -42,7 +42,11 @@ def __init__(self, graph): self._graph = graph def constfold(self, graphdef, output_names): - from tensorflow.core.protobuf import config_pb2, meta_graph_pb2, rewriter_config_pb2 + from tensorflow.core.protobuf import ( + config_pb2, + meta_graph_pb2, + rewriter_config_pb2, + ) from tensorflow.python.framework import importer, ops from tensorflow.python.grappler import tf_optimizer from tensorflow.python.training import saver @@ -55,12 +59,16 @@ def constfold(self, graphdef, output_names): output_list.append(output.encode("utf-8")) importer.import_graph_def(graphdef, name="") - metagraph = saver.export_meta_graph(graph_def=graph.as_graph_def(add_shapes=True), graph=graph) + metagraph = saver.export_meta_graph( + graph_def=graph.as_graph_def(add_shapes=True), graph=graph + ) metagraph.collection_def["train_op"].CopyFrom(output_collection) rewriter_config = rewriter_config_pb2.RewriterConfig() rewriter_config.optimizers.extend(["constfold"]) - rewriter_config.meta_optimizer_iterations = rewriter_config_pb2.RewriterConfig.ONE + rewriter_config.meta_optimizer_iterations = ( + rewriter_config_pb2.RewriterConfig.ONE + ) session_config = config_pb2.ConfigProto() session_config.graph_options.resave_options.CopyFrom(rewriter_config) @@ -109,7 +117,9 @@ def call_impl(self): # Strip port information from outputs output_names = [name.split(":")[0] for name in output_names] - output_graph_def = tf.graph_util.convert_variables_to_constants(sess, graphdef, output_names) + output_graph_def = tf.graph_util.convert_variables_to_constants( + sess, graphdef, output_names + ) output_graph_def = self.constfold(output_graph_def, output_names) return graph_from_frozen(output_graph_def) @@ -205,10 +215,14 @@ def call_impl(self): # # where "model" is the checkpoint name if not os.path.isdir(self.dir): - G_LOGGER.warning(f"Specified checkpoint directory: {self.dir} does not look like a directory.") + G_LOGGER.warning( + f"Specified checkpoint directory: {self.dir} does not look like a directory." + ) if self.name is None: - G_LOGGER.verbose("Checkpoint name was not explicitly provided, searching for `checkpoint` file") + G_LOGGER.verbose( + "Checkpoint name was not explicitly provided, searching for `checkpoint` file" + ) checkpoint = tf.train.get_checkpoint_state(self.dir) if checkpoint is None: ckpt_file_contents = '\nmodel_checkpoint_path: "model"\nall_model_checkpoint_paths: "model"\n' @@ -220,7 +234,9 @@ def call_impl(self): input_checkpoint = os.path.join(self.dir, self.name) meta_file = input_checkpoint + ".meta" - with tf.Graph().as_default() as graph, tf.compat.v1.Session(graph=graph).as_default() as sess: + with tf.Graph().as_default() as graph, tf.compat.v1.Session( + graph=graph + ).as_default() as sess: saver = tf.compat.v1.train.import_meta_graph(meta_file, clear_devices=True) saver.restore(sess, input_checkpoint) return graph, tf_util.get_graph_output_names(graph) @@ -386,7 +402,12 @@ def call_impl(self): if node.op == "TRTEngineOp": engine = node.attr["serialized_segment"].s if self.engine_dir is not None: - util.save_file(contents=engine, dest=os.path.join(self.engine_dir, f"segment-{segment_number}")) + util.save_file( + contents=engine, + dest=os.path.join( + self.engine_dir, f"segment-{segment_number}" + ), + ) segment_number += 1 return graph, outputs @@ -422,12 +443,17 @@ def call_impl(self): # Session configuration gpu_options = tf.compat.v1.GPUOptions( - per_process_gpu_memory_fraction=self.gpu_memory_fraction, allow_growth=self.allow_growth + per_process_gpu_memory_fraction=self.gpu_memory_fraction, + allow_growth=self.allow_growth, ) config = tf.compat.v1.ConfigProto(gpu_options=gpu_options) if self.use_xla: - config.graph_options.optimizer_options.global_jit_level = tf.OptimizerOptions.ON_1 - G_LOGGER.verbose(f"Using gpu memory fraction: {self.gpu_memory_fraction}, XLA: {self.use_xla}") + config.graph_options.optimizer_options.global_jit_level = ( + tf.OptimizerOptions.ON_1 + ) + G_LOGGER.verbose( + f"Using gpu memory fraction: {self.gpu_memory_fraction}, XLA: {self.use_xla}" + ) return config @@ -461,7 +487,9 @@ def call_impl(self): config, _ = util.invoke_if_callable(self.config) (graph, output_names), _ = util.invoke_if_callable(self.graph) - with graph.as_default() as graph, tf.compat.v1.Session(graph=graph, config=config).as_default() as sess: + with graph.as_default() as graph, tf.compat.v1.Session( + graph=graph, config=config + ).as_default() as sess: G_LOGGER.verbose(f"Using TensorFlow outputs: {output_names}") G_LOGGER.extra_verbose("Initializing variables in TensorFlow Graph") sess.run(tf.compat.v1.initializers.global_variables()) diff --git a/tools/Polygraphy/polygraphy/backend/tf/runner.py b/tools/Polygraphy/polygraphy/backend/tf/runner.py index 12099855..072598fc 100644 --- a/tools/Polygraphy/polygraphy/backend/tf/runner.py +++ b/tools/Polygraphy/polygraphy/backend/tf/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -74,7 +74,10 @@ def infer_impl(self, feed_dict): G_LOGGER.extra_verbose(f"Received feed_dict: {feed_dict}") start = time.time() inference_outputs = self.sess.run( - self.output_names, feed_dict=feed_dict, options=self.run_options, run_metadata=self.run_metadata + self.output_names, + feed_dict=feed_dict, + options=self.run_options, + run_metadata=self.run_metadata, ) end = time.time() diff --git a/tools/Polygraphy/polygraphy/backend/tf/util.py b/tools/Polygraphy/polygraphy/backend/tf/util.py index 86d1e753..c1525635 100644 --- a/tools/Polygraphy/polygraphy/backend/tf/util.py +++ b/tools/Polygraphy/polygraphy/backend/tf/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -46,7 +46,9 @@ def load_graph(path): graphdef.ParseFromString(util.load_file(path, description="GraphDef")) except google.protobuf.message.DecodeError: G_LOGGER.backtrace() - G_LOGGER.critical(f"Could not import TensorFlow GraphDef from: {path}. Is this a valid TensorFlow model?") + G_LOGGER.critical( + f"Could not import TensorFlow GraphDef from: {path}. Is this a valid TensorFlow model?" + ) elif isinstance(path, tf.compat.v1.GraphDef): graphdef = path @@ -79,7 +81,9 @@ def get_tensor_metadata(tensors): metadata = TensorMetadata() for tensor in tensors: try: - shape = [elem.value if hasattr(elem, "value") else elem for elem in tensor.shape] + shape = [ + elem.value if hasattr(elem, "value") else elem for elem in tensor.shape + ] except ValueError: # Happens when rank is unknown shape = None @@ -90,7 +94,9 @@ def get_tensor_metadata(tensors): def get_input_metadata(graph): input_tensors = [] input_nodes = find_nodes_by_ops(graph.as_graph_def(), ["Placeholder", "FIFOQueue"]) - G_LOGGER.verbose(f"Found input tensors: {[f'{n.name}: {n.op}' for n in input_nodes]}") + G_LOGGER.verbose( + f"Found input tensors: {[f'{n.name}: {n.op}' for n in input_nodes]}" + ) for node in input_nodes: input_tensors.append(graph.get_tensor_by_name(node.name + ":0")) @@ -128,7 +134,9 @@ def is_output_node(node): # Additionally, we sometimes need to exclude entire namespaces e.g. while loops. EXCLUDE_NAMESPACES = ["while", "Assert"] - if any([ex_op in node.op for ex_op in EXCLUDE_OPS]) or any([ns in node.name for ns in EXCLUDE_NAMESPACES]): + if any([ex_op in node.op for ex_op in EXCLUDE_OPS]) or any( + [ns in node.name for ns in EXCLUDE_NAMESPACES] + ): G_LOGGER.extra_verbose( f"Excluding {node.name}, op {node.op} is not a valid output op or is part of an excluded namespace (Note: excluded namespaces: {EXCLUDE_NAMESPACES})" ) @@ -139,7 +147,9 @@ def is_output_node(node): # For layerwise mode, every layer becomes an output. if layerwise: output_nodes = list(graphdef.node) - G_LOGGER.verbose(f"Running in layerwise mode. Marking {len(output_nodes)} layers as potential outputs") + G_LOGGER.verbose( + f"Running in layerwise mode. Marking {len(output_nodes)} layers as potential outputs" + ) else: output_nodes = [node for node in graphdef.node if is_output_node(node)] G_LOGGER.extra_verbose(f"Found likely output nodes: {output_nodes}") @@ -157,7 +167,9 @@ def is_output_node(node): f"Excluded {len(output_nodes) - len(output_tensors)} ops that don't seem like outputs. Use -vv/--super-verbose, or set logging verbosity to EXTRA_VERBOSE to view them." ) - G_LOGGER.extra_verbose(f"Found output op types in graph: {set(tensor.op.type for tensor in output_tensors)}") + G_LOGGER.extra_verbose( + f"Found output op types in graph: {set(tensor.op.type for tensor in output_tensors)}" + ) G_LOGGER.verbose(f"Retrieved TensorFlow output_tensors: {output_tensors}") return get_tensor_metadata(output_tensors) @@ -176,7 +188,9 @@ def str_from_graph(graph, show_layers=None, show_attrs=None, show_weights=None): output_metadata = get_output_metadata(graph) graph_str += f"---- {len(input_metadata)} Graph Inputs ----\n{input_metadata}\n\n" - graph_str += f"---- {len(output_metadata)} Graph Outputs ----\n{output_metadata}\n\n" + graph_str += ( + f"---- {len(output_metadata)} Graph Outputs ----\n{output_metadata}\n\n" + ) graph_str += f"---- {len(graph.as_graph_def().node)} Nodes ----\n" if show_layers: G_LOGGER.warning( diff --git a/tools/Polygraphy/polygraphy/backend/trt/__init__.py b/tools/Polygraphy/polygraphy/backend/trt/__init__.py index 6755a8fa..c87af294 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/__init__.py +++ b/tools/Polygraphy/polygraphy/backend/trt/__init__.py @@ -1,6 +1,7 @@ from polygraphy.backend.trt.algorithm_selector import * from polygraphy.backend.trt.calibrator import * from polygraphy.backend.trt.config import * +from polygraphy.backend.trt.file_reader import * from polygraphy.backend.trt.loader import * from polygraphy.backend.trt.profile import * from polygraphy.backend.trt.runner import * diff --git a/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py b/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py index 35f4153c..e039c81b 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py +++ b/tools/Polygraphy/polygraphy/backend/trt/algorithm_selector.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/backend/trt/calibrator.py b/tools/Polygraphy/polygraphy/backend/trt/calibrator.py index f30c907e..b9361e08 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/calibrator.py +++ b/tools/Polygraphy/polygraphy/backend/trt/calibrator.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -15,13 +15,15 @@ # limitations under the License. # import contextlib +import copy from collections import OrderedDict from polygraphy import mod, util +from polygraphy.backend.base import util as base_util +from polygraphy.backend.trt import util as trt_util +from polygraphy.datatype import DataType from polygraphy.exception import PolygraphyException from polygraphy.logger import G_LOGGER, LogMode -from polygraphy.backend.trt import util as trt_util -from polygraphy.backend.base import util as base_util trt = mod.lazy_import("tensorrt>=8.5") np = mod.lazy_import("numpy") @@ -29,7 +31,13 @@ @mod.export() def Calibrator( - data_loader, cache=None, BaseClass=None, batch_size=None, quantile=None, regression_cutoff=None, algo=None + data_loader, + cache=None, + BaseClass=None, + batch_size=None, + quantile=None, + regression_cutoff=None, + algo=None, ): """ Supplies calibration data to TensorRT to calibrate the network for INT8 inference. @@ -112,10 +120,25 @@ def set_input_metadata(self, input_metadata): using Polygraphy's included `DataLoader` to provide calibration data, or if data type and shape checking is desired. """ - self.input_metadata = input_metadata - if input_metadata is not None: + calibration_metadata = copy.copy(input_metadata) + for name, meta_tuple in calibration_metadata.items(): + if meta_tuple.dtype not in { + DataType.FLOAT32, + DataType.INT32, + DataType.INT64, + DataType.BOOL, + }: + G_LOGGER.warning( + f"TensorRT requires non-index calibration inputs to be provided in float32. " + f"Input: {name} has datatype: {meta_tuple.dtype}, so will override to float32 in the calibrator's metadata. " + f"If you are using a custom data loader with the calibrator, please ensure that you return a float32 tensor for this input." + ) + meta_tuple.dtype = DataType.FLOAT32 + + self.input_metadata = calibration_metadata + if calibration_metadata is not None: with contextlib.suppress(AttributeError): - self.data_loader.input_metadata = input_metadata + self.data_loader.input_metadata = calibration_metadata def reset(self): """ @@ -160,7 +183,9 @@ def _get_batch_impl(self, names): if isinstance(buf, int): ptrs.append(buf) else: - ptrs.append(trt_util._get_array_on_gpu(buf, name, self.device_buffers)) + ptrs.append( + trt_util._get_array_on_gpu(buf, name, self.device_buffers) + ) return ptrs @@ -182,7 +207,9 @@ def load_from_cache(): try: return util.load_file(self._cache, description="calibration cache") except Exception as err: - G_LOGGER.error(f"Could not read from calibration cache: {self._cache}\nNote: Error was: {err}") + G_LOGGER.error( + f"Could not read from calibration cache: {self._cache}\nNote: Error was: {err}" + ) return None if self.cache_contents is not None: @@ -208,9 +235,15 @@ def write_calibration_cache(self, cache): return try: - util.save_file(contents=self.cache_contents, dest=self._cache, description="calibration cache") + util.save_file( + contents=self.cache_contents, + dest=self._cache, + description="calibration cache", + ) except Exception as err: - G_LOGGER.error(f"Could not write to calibration cache: {self._cache}.\nNote: Error was: {err}") + G_LOGGER.error( + f"Could not write to calibration cache: {self._cache}.\nNote: Error was: {err}" + ) def free(self): """ diff --git a/tools/Polygraphy/polygraphy/backend/trt/config.py b/tools/Polygraphy/polygraphy/backend/trt/config.py index 41fc14c3..317deec0 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/config.py +++ b/tools/Polygraphy/polygraphy/backend/trt/config.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/backend/trt/file_reader.py b/tools/Polygraphy/polygraphy/backend/trt/file_reader.py new file mode 100644 index 00000000..c46051ac --- /dev/null +++ b/tools/Polygraphy/polygraphy/backend/trt/file_reader.py @@ -0,0 +1,80 @@ +# +# 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. +# +from pathlib import Path + +from polygraphy import mod, util +from polygraphy.logger import G_LOGGER + +trt = mod.lazy_import("tensorrt>=10.0") + +@mod.export() +def FileReader( + filepath, + BaseClass=None, +): + """ + Class that supplies data to TensorRT from a stream. This may help reduce memory usage during deserialization. + + Args: + filepath (str): + The path to the serialized file. + + """ + BaseClass = util.default(BaseClass, trt.IStreamReader) + + class FileReaderClass(BaseClass): + """ + Class that supplies data to TensorRT from a stream. This may help reduce memory usage during deserialization. + """ + + def __init__(self): + # Must explicitly initialize parent for any trampoline class! Will mysteriously segfault without this. + BaseClass.__init__(self) # type: ignore + + self.filepath = filepath + + if not Path(self.filepath).exists(): + G_LOGGER.error(f"File at {self.filepath} does not exist!") + + self.mode = 'rb' + self.file = open(self.filepath, self.mode) + if not self.file: + G_LOGGER.error(f"Failed to open file at {self.filepath}!") + + self.make_func = FileReader + + def read(self, size: int) -> bytes: + return self.file.read(size) + + def free(self): + if self.file: + self.file.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.free() + + def __repr__(self): + return util.make_repr( + "FileReader", + self.filepath, + BaseClass=BaseClass, + )[0] + + return FileReaderClass() diff --git a/tools/Polygraphy/polygraphy/backend/trt/loader.py b/tools/Polygraphy/polygraphy/backend/trt/loader.py index bb46766e..2fb4bd41 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/loader.py +++ b/tools/Polygraphy/polygraphy/backend/trt/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -19,7 +19,7 @@ from polygraphy import constants, mod, util from polygraphy.backend.base import BaseLoader -from polygraphy.backend.trt import util as trt_util +from polygraphy.backend.trt import util as trt_util, FileReader from polygraphy.backend.trt.config import CreateConfig from polygraphy.datatype import DataType from polygraphy.logger import G_LOGGER @@ -91,7 +91,10 @@ def __init__(self, explicit_batch=None, strongly_typed=None): Whether to mark the network as being strongly typed. Defaults to False. """ - self.explicit_batch = util.default(explicit_batch, True if mod.version(trt.__version__) < mod.version("10.0") else None) + self.explicit_batch = util.default( + explicit_batch, + True if mod.version(trt.__version__) < mod.version("10.0") else None, + ) self.strongly_typed = util.default(strongly_typed, False) @util.check_called_by("__call__") @@ -105,13 +108,17 @@ def call_impl(self): if self.explicit_batch: try: - network_flags |= 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) + network_flags |= 1 << int( + trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH + ) except AttributeError: trt_util.fail_unavailable("explicit_batch") if self.strongly_typed: try: - network_flags |= 1 << int(trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED) + network_flags |= 1 << int( + trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED + ) except AttributeError: trt_util.fail_unavailable("strongly_typed") @@ -138,7 +145,7 @@ def __init__(self, flags=None, plugin_instancenorm=None, strongly_typed=None): Defaults to False. """ self.flags = flags - self.plugin_instancenorm=util.default(plugin_instancenorm, False) + self.plugin_instancenorm = util.default(plugin_instancenorm, False) self.strongly_typed = util.default(strongly_typed, False) @util.check_called_by("__call__") @@ -165,7 +172,9 @@ class NetworkFromOnnxBytes(BaseNetworkFromOnnx): Functor that parses an ONNX model to create a trt.INetworkDefinition. """ - def __init__(self, model_bytes, flags=None, plugin_instancenorm=None, strongly_typed=None): + def __init__( + self, model_bytes, flags=None, plugin_instancenorm=None, strongly_typed=None + ): """ Parses an ONNX model. @@ -185,7 +194,11 @@ def __init__(self, model_bytes, flags=None, plugin_instancenorm=None, strongly_t Whether to mark the network as being strongly typed. Defaults to False. """ - super().__init__(flags=flags, plugin_instancenorm=plugin_instancenorm, strongly_typed=strongly_typed) + super().__init__( + flags=flags, + plugin_instancenorm=plugin_instancenorm, + strongly_typed=strongly_typed, + ) self._model_bytes = model_bytes @util.check_called_by("__call__") @@ -228,7 +241,11 @@ def __init__(self, path, flags=None, plugin_instancenorm=None, strongly_typed=No Whether to mark the network as being strongly typed. Defaults to False. """ - super().__init__(flags=flags, plugin_instancenorm=plugin_instancenorm, strongly_typed=strongly_typed) + super().__init__( + flags=flags, + plugin_instancenorm=plugin_instancenorm, + strongly_typed=strongly_typed, + ) self.path = path @util.check_called_by("__call__") @@ -272,7 +289,9 @@ def __init__(self, network, func, name=None): # Sanity-check that the function passed in is callable if not callable(func): - G_LOGGER.critical(f"Object {func} (of type {type(func)}) is not a callable.") + G_LOGGER.critical( + f"Object {func} (of type {type(func)}) is not a callable." + ) try: func_name = func.__name__ @@ -334,7 +353,9 @@ def __init__(self, network, outputs=None, exclude_outputs=None): Names of tensors to exclude as outputs. This can be useful in conjunction with ``outputs=constants.MARK_ALL`` to omit outputs. """ - func = lambda network: ModifyNetworkOutputs._apply(network, outputs, exclude_outputs) + func = lambda network: ModifyNetworkOutputs._apply( + network, outputs, exclude_outputs + ) super().__init__(network, func, "ModifyNetworkOutputs") @@ -536,12 +557,17 @@ def call_impl(self): network, show_layers=True, show_attrs=True, - show_weights=G_LOGGER.module_severity.get(G_LOGGER.module_path(__file__)) <= G_LOGGER.ULTRA_VERBOSE, + show_weights=G_LOGGER.module_severity.get( + G_LOGGER.module_path(__file__) + ) + <= G_LOGGER.ULTRA_VERBOSE, ) ) ) - G_LOGGER.start(f"Building engine with configuration:\n{trt_util.str_from_config(config)}") + G_LOGGER.start( + f"Building engine with configuration:\n{trt_util.str_from_config(config)}" + ) start_time = time.time() try: @@ -549,14 +575,20 @@ def call_impl(self): except AttributeError: engine = builder.build_engine(network, config) if not engine: - G_LOGGER.critical("Invalid Engine. Please ensure the engine was built correctly") + G_LOGGER.critical( + "Invalid Engine. Please ensure the engine was built correctly" + ) engine_bytes = engine.serialize() end_time = time.time() if not engine_bytes: - G_LOGGER.critical("Invalid Engine. Please ensure the engine was built correctly") + G_LOGGER.critical( + "Invalid Engine. Please ensure the engine was built correctly" + ) - G_LOGGER.finish(f"Finished engine building in {end_time - start_time:.3f} seconds") + G_LOGGER.finish( + f"Finished engine building in {end_time - start_time:.3f} seconds" + ) if self.timing_cache_path: try: @@ -566,18 +598,28 @@ def call_impl(self): with util.LockFile(self.timing_cache_path): try: - prev_cache = config.create_timing_cache(util.load_file(self.timing_cache_path)) + prev_cache = config.create_timing_cache( + util.load_file(self.timing_cache_path) + ) except: prev_cache = None if timing_cache: if prev_cache is not None: - combine_success = timing_cache.combine(prev_cache, ignore_mismatch=True) + combine_success = timing_cache.combine( + prev_cache, ignore_mismatch=True + ) if not combine_success: - G_LOGGER.warning("Could not combine old timing cache into current timing cache") + G_LOGGER.warning( + "Could not combine old timing cache into current timing cache" + ) with timing_cache.serialize() as buffer: - util.save_file(buffer, self.timing_cache_path, description="tactic timing cache") + util.save_file( + buffer, + self.timing_cache_path, + description="tactic timing cache", + ) return engine_bytes @@ -642,7 +684,9 @@ def __init__(self, serialized_engine, runtime=None): If no runtime is provided, one will be created. """ self._serialized_engine = serialized_engine - self._runtime = util.default(runtime, lambda: trt.Runtime(trt_util.get_trt_logger())) + self._runtime = util.default( + runtime, lambda: trt.Runtime(trt_util.get_trt_logger()) + ) @util.check_called_by("__call__") def call_impl(self): @@ -666,6 +710,51 @@ def call_impl(self): return engine +@mod.export(funcify=True) +class EngineFromPath(BaseLoader): + """ + Functor that deserializes an engine from a path. + """ + + def __init__(self, path: str, runtime=None): + """ + Deserializes an engine from a path. + + Args: + path (Union[str, Callable() -> str]): + The file path to the serialized engine or a callable that returns it. + runtime (Union[trt.Runtime, Callable() -> trt.Runtime]): + The runtime to use when deserializing the engine or a callable that returns one. + If no runtime is provided, one will be created. + """ + self._path = path + self._runtime = util.default( + runtime, lambda: trt.Runtime(trt_util.get_trt_logger()) + ) + + @util.check_called_by("__call__") + def call_impl(self): + """ + Returns: + trt.ICudaEngine: The deserialized engine. + """ + path, _ = util.invoke_if_callable(self._path) + runtime, _ = util.invoke_if_callable(self._runtime) + + trt.init_libnvinfer_plugins(trt_util.get_trt_logger(), "") + try: + # To deserialize version compatible engines, we must signal the runtime that host code is allowed + runtime.engine_host_code_allowed = True + except AttributeError: + pass + + file_reader = FileReader(path) + engine = runtime.deserialize_cuda_engine(file_reader) + if not engine: + G_LOGGER.critical("Could not deserialize engine. See log for details.") + return engine + + @mod.export(funcify=True) class BytesFromEngine(BaseLoader): """ @@ -719,7 +808,9 @@ def call_impl(self): """ engine, _ = util.invoke_if_callable(self._engine) - util.save_file(contents=bytes_from_engine(engine), dest=self.path, description="engine") + util.save_file( + contents=bytes_from_engine(engine), dest=self.path, description="engine" + ) return engine @@ -767,13 +858,19 @@ def tensors_from_names_meta(names, meta): for name in names: if name not in tensor_map: dtype, shape = meta[name] - tensor_map[name] = gs.Variable(name=name, dtype=DataType.to_dtype(dtype, "onnx"), shape=shape) + tensor_map[name] = gs.Variable( + name=name, dtype=DataType.to_dtype(dtype, "onnx"), shape=shape + ) tensors.append(tensor_map[name]) return tensors nodes = [] - graph_inputs = tensors_from_names_meta(*trt_util.get_network_input_names_meta(network)) - graph_outputs = tensors_from_names_meta(*trt_util.get_network_output_names_meta(network)) + graph_inputs = tensors_from_names_meta( + *trt_util.get_network_input_names_meta(network) + ) + graph_outputs = tensors_from_names_meta( + *trt_util.get_network_output_names_meta(network) + ) LAYER_TYPE_CLASS_MAPPING = trt_util.get_layer_class_mapping() @@ -782,8 +879,12 @@ def tensors_from_names_meta(names, meta): if layer.type in LAYER_TYPE_CLASS_MAPPING: layer.__class__ = LAYER_TYPE_CLASS_MAPPING[layer.type] - node_inputs = tensors_from_names_meta(*trt_util.get_layer_input_names_meta(layer)) - node_outputs = tensors_from_names_meta(*trt_util.get_layer_output_names_meta(layer)) + node_inputs = tensors_from_names_meta( + *trt_util.get_layer_input_names_meta(layer) + ) + node_outputs = tensors_from_names_meta( + *trt_util.get_layer_output_names_meta(layer) + ) attrs = {} attr_names = trt_util.get_layer_attribute_names(layer) for name in attr_names: @@ -793,7 +894,9 @@ def tensors_from_names_meta(names, meta): except Exception as err: attr = f"" - if util.is_sequence(attr) or any(isinstance(attr, cls) for cls in [trt.Dims, trt.Permutation]): + if util.is_sequence(attr) or any( + isinstance(attr, cls) for cls in [trt.Dims, trt.Permutation] + ): try: attr = list(attr) except ValueError: # Invalid dims @@ -817,12 +920,23 @@ def tensors_from_names_meta(names, meta): attrs[name] = attr - nodes.append(gs.Node(name=layer.name, op=op_name, attrs=attrs, inputs=node_inputs, outputs=node_outputs)) + nodes.append( + gs.Node( + name=layer.name, + op=op_name, + attrs=attrs, + inputs=node_inputs, + outputs=node_outputs, + ) + ) - graph = gs.Graph(name=network.name, inputs=graph_inputs, outputs=graph_outputs, nodes=nodes) + graph = gs.Graph( + name=network.name, inputs=graph_inputs, outputs=graph_outputs, nodes=nodes + ) return gs.export_onnx(graph) + @mod.export(funcify=True) class MarkDebug(PostprocessNetwork): """ diff --git a/tools/Polygraphy/polygraphy/backend/trt/profile.py b/tools/Polygraphy/polygraphy/backend/trt/profile.py index 0c74315a..0449c97e 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/profile.py +++ b/tools/Polygraphy/polygraphy/backend/trt/profile.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -85,7 +85,9 @@ def __getitem__(self, key): corresponding to the input. """ if key not in self: - G_LOGGER.critical(f"Binding: {key} does not have shapes set in this profile") + G_LOGGER.critical( + f"Binding: {key} does not have shapes set in this profile" + ) return super().__getitem__(key) def fill_defaults(self, network, default_shape_value=None): @@ -103,7 +105,9 @@ def fill_defaults(self, network, default_shape_value=None): Returns: Profile: Self """ - default_shape_value = util.default(default_shape_value, constants.DEFAULT_SHAPE_VALUE) + default_shape_value = util.default( + default_shape_value, constants.DEFAULT_SHAPE_VALUE + ) for idx in range(network.num_inputs): inp = network.get_input(idx) @@ -170,7 +174,9 @@ def to_trt(self, builder, network): if is_shape_tensor: if inp.name in self: shapes = self[inp.name] - trt_profile.set_shape_input(inp.name, shapes.min, shapes.opt, shapes.max) + trt_profile.set_shape_input( + inp.name, shapes.min, shapes.opt, shapes.max + ) G_LOGGER.verbose( f"{trt_util.str_from_tensor(inp, is_shape_tensor)} | Setting input shape-tensor value range to: {shapes}" ) diff --git a/tools/Polygraphy/polygraphy/backend/trt/runner.py b/tools/Polygraphy/polygraphy/backend/trt/runner.py index faf2daad..ef77aa79 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/runner.py +++ b/tools/Polygraphy/polygraphy/backend/trt/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -44,14 +44,16 @@ def process_debug_tensor(self, addr, location, type, shape, name, stream): cuda.wrapper().memcpy( dst=util.array.data_ptr(buffer), src=addr, - nbytes=size*datatype.itemsize, + nbytes=size * datatype.itemsize, kind=cuda.MemcpyKind.DeviceToHost, - stream_ptr=stream) + stream_ptr=stream, + ) cuda.wrapper().stream_synchronize(stream) self.debug_tensor_outputs[name] = util.array.resize_or_reallocate(buffer, shape) return DebugTensorWriter() + def _make_output_allocator(): class OutputAllocator(trt.IOutputAllocator): @@ -79,7 +81,7 @@ def notify_shape(self, tensor_name, shape): def set_use_torch(self, use_torch): self.use_torch = use_torch - + return OutputAllocator() @@ -138,7 +140,15 @@ class TrtRunner(BaseRunner): be used only for prototyping, testing, and debugging. """ - def __init__(self, engine, name: str = None, optimization_profile: int = None, allocation_strategy: str = None, weight_streaming_budget: int = None, weight_streaming_percent: float = None): + def __init__( + self, + engine, + name: str = None, + optimization_profile: int = None, + allocation_strategy: str = None, + weight_streaming_budget: int = None, + weight_streaming_percent: float = None, + ): """ Args: engine (Union[Union[trt.ICudaEngine, trt.IExecutionContext], Callable() -> Union[trt.ICudaEngine, trt.IExecutionContext]]): @@ -173,7 +183,6 @@ def __init__(self, engine, name: str = None, optimization_profile: int = None, a self.allocation_strategy = allocation_strategy self.weight_streaming_budget = weight_streaming_budget self.weight_streaming_percent = weight_streaming_percent - self.output_allocator = _make_output_allocator() @util.check_called_by("activate") def activate_impl(self): @@ -193,7 +202,7 @@ def activate_impl(self): elif self.weight_streaming_percent is not None: assert 0 <= self.weight_streaming_percent <= 100 if self.weight_streaming_percent == 0: - budget_bytes = 0 # Disable weight streaming + budget_bytes = 0 # Disable weight streaming else: min_budget = self.engine.minimum_weight_streaming_budget max_budget = self.engine.streamable_weights_size @@ -209,15 +218,15 @@ def activate_impl(self): G_LOGGER.info(f"Weight streaming is enabled with TensorRT automatically determiing the budget.") else: G_LOGGER.info(f"Weight streaming is enabled with a memory budget of {budget_bytes} bytes.") - + allocation_strategy = util.default(self.allocation_strategy, "static") - if allocation_strategy == 'static': + if allocation_strategy == "static": self.context = self.engine.create_execution_context() - elif allocation_strategy in ['profile', 'runtime']: + elif allocation_strategy in ["profile", "runtime"]: # Device memory will be managed by polygraphy self.context = self.engine.create_execution_context(trt.ExecutionContextAllocationStrategy.USER_MANAGED) else: - G_LOGGER.critical("Invalid allocation strategy specified.") + G_LOGGER.critical("Invalid allocation strategy specified.") if not self.context: G_LOGGER.critical("Invalid Context. See error log for details.") elif isinstance(engine_or_context, trt.IExecutionContext): @@ -237,6 +246,7 @@ def activate_impl(self): self.host_output_buffers = OrderedDict() self.stream = cuda.Stream() self.context_memory_buffer = None + self.output_allocator = _make_output_allocator() if self.optimization_profile is not None: self.set_profile(self.optimization_profile) @@ -342,8 +352,10 @@ def get_io(mode): if self.allocation_strategy in ["profile", "runtime"]: if self.allocation_strategy == "profile": # Perform per-profile allocation. - size_to_allocate = self.engine.get_device_memory_size_for_profile(self.context.active_optimization_profile) - elif self.allocation_strategy =="runtime": + size_to_allocate = self.engine.get_device_memory_size_for_profile( + self.context.active_optimization_profile + ) + elif self.allocation_strategy == "runtime": # Perform runtime allocation. size_to_allocate = self.context.update_device_memory_size_for_shapes() @@ -378,7 +390,12 @@ def get_io(mode): if copy_outputs_to_host: raw_array = _get_array_on_cpu( - raw_array, name, self.host_output_buffers, self.stream, nbytes, use_torch=use_torch + raw_array, + name, + self.host_output_buffers, + self.stream, + nbytes, + use_torch=use_torch, ) if using_vectorized_format: @@ -448,4 +465,5 @@ def deactivate_impl(self): self.host_output_buffers, self.stream, self.context_memory_buffer, + self.output_allocator, ) diff --git a/tools/Polygraphy/polygraphy/backend/trt/util.py b/tools/Polygraphy/polygraphy/backend/trt/util.py index ec58d2eb..6eb69456 100644 --- a/tools/Polygraphy/polygraphy/backend/trt/util.py +++ b/tools/Polygraphy/polygraphy/backend/trt/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -791,9 +791,11 @@ def dtype_from_fmt_dtype(contents): name=elem["Name"], dtype=dtype_from_fmt_dtype(elem["Format/Datatype"]), shape=elem["Dimensions"], - docstring=f"Format: {elem['Format/Datatype']}" - if "N/A" not in elem["Format/Datatype"] - else None, + docstring=( + f"Format: {elem['Format/Datatype']}" + if "N/A" not in elem["Format/Datatype"] + else None + ), ) return names, meta diff --git a/tools/Polygraphy/polygraphy/common/interface.py b/tools/Polygraphy/polygraphy/common/interface.py index 0b368015..09d1a66f 100644 --- a/tools/Polygraphy/polygraphy/common/interface.py +++ b/tools/Polygraphy/polygraphy/common/interface.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/common/struct.py b/tools/Polygraphy/polygraphy/common/struct.py index 1e99bcac..cd60d43c 100644 --- a/tools/Polygraphy/polygraphy/common/struct.py +++ b/tools/Polygraphy/polygraphy/common/struct.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -129,7 +129,13 @@ def add(self, name, dtype, shape, min_shape=None, max_shape=None, docstring=None The newly added entry. """ self[name] = MetadataTuple( - dtype, BoundedShape(shape, min=min_shape, max=max_shape) if shape is not None else None, docstring + dtype, + ( + BoundedShape(shape, min=min_shape, max=max_shape) + if shape is not None + else None + ), + docstring, ) return self diff --git a/tools/Polygraphy/polygraphy/comparator/comparator.py b/tools/Polygraphy/polygraphy/comparator/comparator.py index 282050d0..7a70a9a9 100644 --- a/tools/Polygraphy/polygraphy/comparator/comparator.py +++ b/tools/Polygraphy/polygraphy/comparator/comparator.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -96,10 +96,14 @@ def execute_runner(runner, loader_cache): with runner as active_runner: # DataLoaderCache will ensure that the feed_dict does not contain any extra entries # based on the provided input_metadata. - loader_cache.set_input_metadata(active_runner.get_input_metadata(use_numpy_dtypes=False)) + loader_cache.set_input_metadata( + active_runner.get_input_metadata(use_numpy_dtypes=False) + ) if warm_up: - G_LOGGER.start(f"{active_runner.name:35} | Running {warm_up} warm-up run(s)") + G_LOGGER.start( + f"{active_runner.name:35} | Running {warm_up} warm-up run(s)" + ) try: feed_dict = loader_cache[0] except IndexError: @@ -107,11 +111,15 @@ def execute_runner(runner, loader_cache): f"{warm_up} warm-up run(s) were requested, but data loader did not supply any data. Skipping warm-up run(s)" ) else: - G_LOGGER.ultra_verbose(f"Warm-up Input Buffers:\n{util.indent_block(feed_dict)}") + G_LOGGER.ultra_verbose( + f"Warm-up Input Buffers:\n{util.indent_block(feed_dict)}" + ) # First do a few warm-up runs, and don't time them. for _ in range(warm_up): active_runner.infer(feed_dict=feed_dict) - G_LOGGER.finish(f"{active_runner.name:35} | Finished {warm_up} warm-up run(s)") + G_LOGGER.finish( + f"{active_runner.name:35} | Finished {warm_up} warm-up run(s)" + ) # Then, actual iterations. index = 0 @@ -133,7 +141,11 @@ def execute_runner(runner, loader_cache): total_runtime += runtime # Without a deep copy here, outputs will always reference the output of the last run iteration_results.append( - IterationResult(outputs=copy.deepcopy(outputs), runtime=runtime, runner_name=active_runner.name) + IterationResult( + outputs=copy.deepcopy(outputs), + runtime=runtime, + runner_name=active_runner.name, + ) ) G_LOGGER.info( @@ -175,7 +187,10 @@ def execute_runner_with_queue(runner_queue, runner, loader_cache): G_LOGGER.start(f"{runner.name:35} | Activating and starting inference") if use_subprocess: runner_queue = Queue() - process = Process(target=execute_runner_with_queue, args=(runner_queue, runner, loader_cache)) + process = Process( + target=execute_runner_with_queue, + args=(runner_queue, runner, loader_cache), + ) process.start() # If a subprocess hangs in a certain way, then process.join could block forever. Hence, @@ -187,7 +202,9 @@ def execute_runner_with_queue(runner_queue, runner, loader_cache): runner_queue, timeout=subprocess_polling_interval / 2 ) # Receive updated loader cache, or fall back if it could not be sent. - loader_cache = util.try_receive_on_queue(runner_queue, timeout=subprocess_polling_interval / 2) + loader_cache = util.try_receive_on_queue( + runner_queue, timeout=subprocess_polling_interval / 2 + ) except queue.Empty: G_LOGGER.extra_verbose("Polled subprocess - still running") @@ -227,7 +244,9 @@ def postprocess(run_results, postprocess_func): Returns: RunResults: The updated run results. """ - G_LOGGER.start(f"Applying post-processing to outputs: {postprocess_func.__name__}") + G_LOGGER.start( + f"Applying post-processing to outputs: {postprocess_func.__name__}" + ) for _, iteration_results in run_results: for index, iter_res in enumerate(iteration_results): iteration_results[index] = postprocess_func(iter_res) @@ -240,7 +259,9 @@ def default_comparisons(run_results): return [(i, i + 1) for i in range(len(run_results) - 1)] @staticmethod - def compare_accuracy(run_results, fail_fast=False, comparisons=None, compare_func=None): + def compare_accuracy( + run_results, fail_fast=False, comparisons=None, compare_func=None + ): """ Args: run_results (RunResults): The result of Comparator.run() @@ -268,11 +289,16 @@ def find_mismatched(match_dict): return [name for name, matched in match_dict.items() if not bool(matched)] compare_func = util.default(compare_func, CompareFunc.simple()) - comparisons = util.default(comparisons, Comparator.default_comparisons(run_results)) + comparisons = util.default( + comparisons, Comparator.default_comparisons(run_results) + ) accuracy_result = AccuracyResult() for runner0_index, runner1_index in comparisons: - (runner0_name, results0), (runner1_name, results1) = run_results[runner0_index], run_results[runner1_index] + (runner0_name, results0), (runner1_name, results1) = ( + run_results[runner0_index], + run_results[runner1_index], + ) G_LOGGER.start(f"Accuracy Comparison | {runner0_name} vs. {runner1_name}") with G_LOGGER.indent(): @@ -293,7 +319,9 @@ def find_mismatched(match_dict): if fail_fast and mismatched_outputs: return accuracy_result - G_LOGGER.extra_verbose(f"Finished comparing {runner0_name} with {runner1_name}") + G_LOGGER.extra_verbose( + f"Finished comparing {runner0_name} with {runner1_name}" + ) passed, _, total = accuracy_result.stats(runner_pair) pass_rate = accuracy_result.percentage(runner_pair) * 100.0 @@ -327,20 +355,26 @@ def validate(run_results, check_inf=None, check_nan=None, fail_fast=None): def is_finite(output): non_finite = util.array.logical_not(util.array.isfinite(output)) if util.array.any(non_finite): - G_LOGGER.error("Inf Detected | One or more non-finite values were encountered in this output") + G_LOGGER.error( + "Inf Detected | One or more non-finite values were encountered in this output" + ) G_LOGGER.info( "Note: Use -vv or set logging verbosity to EXTRA_VERBOSE to display non-finite values", mode=LogMode.ONCE, ) G_LOGGER.extra_verbose(f"Note: non-finite values at:\n{non_finite}") - G_LOGGER.extra_verbose(f"Note: non-finite values:\n{output[non_finite]}") + G_LOGGER.extra_verbose( + f"Note: non-finite values:\n{output[non_finite]}" + ) return False return True def is_not_nan(output): nans = util.array.isnan(output) if util.array.any(nans): - G_LOGGER.error("NaN Detected | One or more NaNs were encountered in this output") + G_LOGGER.error( + "NaN Detected | One or more NaNs were encountered in this output" + ) G_LOGGER.info( "Note: Use -vv or set logging verbosity to EXTRA_VERBOSE to display locations of NaNs", mode=LogMode.ONCE, diff --git a/tools/Polygraphy/polygraphy/comparator/compare.py b/tools/Polygraphy/polygraphy/comparator/compare.py index fa7cb7c7..b4dc362a 100644 --- a/tools/Polygraphy/polygraphy/comparator/compare.py +++ b/tools/Polygraphy/polygraphy/comparator/compare.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/comparator/data_loader.py b/tools/Polygraphy/polygraphy/comparator/data_loader.py index 7dc11cc2..e5dbbc62 100644 --- a/tools/Polygraphy/polygraphy/comparator/data_loader.py +++ b/tools/Polygraphy/polygraphy/comparator/data_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -47,20 +47,20 @@ def __init__(self, data_loader_backend_module, seed): self.data_loader_backend_module = data_loader_backend_module - if self.data_loader_backend_module == "numpy": self.rng = np.random.RandomState(seed) elif self.data_loader_backend_module == "torch": self.rng = torch.Generator() self.rng.manual_seed(seed) - def sample_integer(self, shape, dtype, low, high): """ Samples an array containing integral values in the range [low, high], inclusive """ dtype = ( - DataType.to_dtype(DataType.from_dtype(dtype), self.data_loader_backend_module) + DataType.to_dtype( + DataType.from_dtype(dtype), self.data_loader_backend_module + ) if dtype is not None else dtype ) @@ -87,7 +87,9 @@ def sample_float(self, shape, dtype, fmin, fmax): scale = fmax dtype = ( - DataType.to_dtype(DataType.from_dtype(dtype), self.data_loader_backend_module) + DataType.to_dtype( + DataType.from_dtype(dtype), self.data_loader_backend_module + ) if dtype is not None else dtype ) @@ -100,7 +102,9 @@ def sample_float(self, shape, dtype, fmin, fmax): def constant_array(self, shape, dtype): dtype = ( - DataType.to_dtype(DataType.from_dtype(dtype), self.data_loader_backend_module) + DataType.to_dtype( + DataType.from_dtype(dtype), self.data_loader_backend_module + ) if dtype is not None else dtype ) diff --git a/tools/Polygraphy/polygraphy/comparator/postprocess.py b/tools/Polygraphy/polygraphy/comparator/postprocess.py index 0ba09eb0..1689a0a4 100644 --- a/tools/Polygraphy/polygraphy/comparator/postprocess.py +++ b/tools/Polygraphy/polygraphy/comparator/postprocess.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/comparator/struct.py b/tools/Polygraphy/polygraphy/comparator/struct.py index ae66f557..0c68e98d 100644 --- a/tools/Polygraphy/polygraphy/comparator/struct.py +++ b/tools/Polygraphy/polygraphy/comparator/struct.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -37,7 +37,9 @@ def __init__(self, arr): """ self.arr = None self.tmpfile = None - if config.ARRAY_SWAP_THRESHOLD_MB >= 0 and util.array.nbytes(arr) > (config.ARRAY_SWAP_THRESHOLD_MB << 20): + if config.ARRAY_SWAP_THRESHOLD_MB >= 0 and util.array.nbytes(arr) > ( + config.ARRAY_SWAP_THRESHOLD_MB << 20 + ): self.tmpfile = util.NamedTemporaryFile(suffix=".json") G_LOGGER.extra_verbose( f"Evicting large array ({util.array.nbytes(arr) / 1024.0 ** 2:.3f} MiB) from memory and saving to {self.tmpfile.name}" @@ -57,7 +59,9 @@ def load(self): return self.arr if self.tmpfile is None: - G_LOGGER.internal_error(f"self.arr is None but self.tmpfile is also None; this should be impossible.") + G_LOGGER.internal_error( + f"self.arr is None but self.tmpfile is also None; this should be impossible." + ) return load_json(self.tmpfile.name) @@ -176,7 +180,9 @@ def encode(iter_result): @Decoder.register(IterationResult) def decode(dct): - return IterationResult(outputs=dct["outputs"], runtime=dct["runtime"], runner_name=dct["runner_name"]) + return IterationResult( + outputs=dct["outputs"], runtime=dct["runtime"], runner_name=dct["runner_name"] + ) @mod.export() @@ -352,7 +358,14 @@ def __bool__(self): Returns: bool """ - return all([bool(match) for outs in self.values() for out in outs for match in out.values()]) + return all( + [ + bool(match) + for outs in self.values() + for out in outs + for match in out.values() + ] + ) def _get_runner_pair(self, runner_pair): return util.default(runner_pair, list(self.keys())[0]) diff --git a/tools/Polygraphy/polygraphy/comparator/util.py b/tools/Polygraphy/polygraphy/comparator/util.py index 04d72c77..d8791a9b 100644 --- a/tools/Polygraphy/polygraphy/comparator/util.py +++ b/tools/Polygraphy/polygraphy/comparator/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -167,11 +167,14 @@ def log_output_stats(output, info_hist=False, runner_name=None, hist_range=None) severity=G_LOGGER.INFO if info_hist else G_LOGGER.VERBOSE, ) G_LOGGER.log( - lambda: str_histogram(output, hist_range), severity=G_LOGGER.INFO if info_hist else G_LOGGER.VERBOSE + lambda: str_histogram(output, hist_range), + severity=G_LOGGER.INFO if info_hist else G_LOGGER.VERBOSE, ) -def build_heatmaps(arr, min_val, max_val, prefix, save_dir=None, show=None, use_lognorm=None): +def build_heatmaps( + arr, min_val, max_val, prefix, save_dir=None, show=None, use_lognorm=None +): """ Display an array as an image or set of images. The last two dimensions are interpreted as the height and width and the leading dimensions are flattened and treated as the number @@ -196,10 +199,18 @@ def build_heatmaps(arr, min_val, max_val, prefix, save_dir=None, show=None, use_ shape = util.array.shape(arr) if len(shape) < 3: - arr = util.array.view(arr, dtype=util.array.dtype(arr), shape=([1] * (3 - len(shape))) + list(shape)) + arr = util.array.view( + arr, + dtype=util.array.dtype(arr), + shape=([1] * (3 - len(shape))) + list(shape), + ) original_shape = util.array.shape(arr) - arr = util.array.view(arr, dtype=util.array.dtype(arr), shape=(-1, original_shape[-2], original_shape[-1])) + arr = util.array.view( + arr, + dtype=util.array.dtype(arr), + shape=(-1, original_shape[-2], original_shape[-1]), + ) shape = util.array.shape(arr) num_images = shape[0] @@ -226,7 +237,9 @@ def coord_str_from_img_idx(img_idx): # Populate each image in each figure. for fig_idx in range(num_figures): - fig, axs = plt.subplots(num_rows, num_cols, squeeze=False, dpi=200, constrained_layout=True) + fig, axs = plt.subplots( + num_rows, num_cols, squeeze=False, dpi=200, constrained_layout=True + ) base_img_idx = fig_idx * num_images_per_figure try: @@ -258,7 +271,11 @@ def coord_str_from_img_idx(img_idx): title = "Out Of Bounds" ax.set_title(title, fontsize=FONT_SIZE) - images.append(ax.imshow(img, cmap="plasma", filternorm=False, resample=False)) + images.append( + ax.imshow( + img, cmap="plasma", filternorm=False, resample=False + ) + ) for im in images: im.set_norm(norm) @@ -306,7 +323,9 @@ def scatter_plot_error_magnitude( save_dir (Optional[str]): Path to a directory in which to save images of the plots. show (Optional[bool]): Whether to display the error metrics plot. """ - G_LOGGER.start(f"Building error metrics plot for {out0_name}. This may take a while...") + G_LOGGER.start( + f"Building error metrics plot for {out0_name}. This may take a while..." + ) with G_LOGGER.indent(): title = f"Error metrics between output0 and output1\noutput0: {runner0_name:35} | {out0_name}\noutput1: {runner1_name:35} | {out1_name}" fname = f"error_metrics_{out0_name}.png" @@ -359,7 +378,9 @@ def set_log_ax(ax, min_diff, max_diff): label_suffix = " (log scale)" else: set_linear_ax(axs[1]) - axs[1].set(xlabel="output1 magnitude", ylabel=f"Relative error{label_suffix}") + axs[1].set( + xlabel="output1 magnitude", ylabel=f"Relative error{label_suffix}" + ) if save_dir is not None: path = os.path.join(save_dir, fname) diff --git a/tools/Polygraphy/polygraphy/config.py b/tools/Polygraphy/polygraphy/config.py index 5db39762..478fd407 100644 --- a/tools/Polygraphy/polygraphy/config.py +++ b/tools/Polygraphy/polygraphy/config.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -17,7 +17,9 @@ import os import sys -INTERNAL_CORRECTNESS_CHECKS = bool(os.environ.get("POLYGRAPHY_INTERNAL_CORRECTNESS_CHECKS", "0") != "0") +INTERNAL_CORRECTNESS_CHECKS = bool( + os.environ.get("POLYGRAPHY_INTERNAL_CORRECTNESS_CHECKS", "0") != "0" +) """ bool: Whether internal correctness checks are enabled. This can be configured by setting the 'POLYGRAPHY_INTERNAL_CORRECTNESS_CHECKS' environment variable. @@ -36,7 +38,9 @@ This can be configured by setting the 'POLYGRAPHY_ASK_BEFORE_INSTALL' environment variable. """ -INSTALL_CMD = os.environ.get("POLYGRAPHY_INSTALL_CMD", f"{sys.executable} -m pip install").split() +INSTALL_CMD = os.environ.get( + "POLYGRAPHY_INSTALL_CMD", f"{sys.executable} -m pip install" +).split() """ List[str]: The command to use to automatically install dependencies. Only relevant when AUTOINSTALL_DEPS is enabled. Defaults to ``["python", "-m", "pip", "install"]``. @@ -44,7 +48,9 @@ string containing the command; for example: ``python3 -m pip install``. """ -ARRAY_SWAP_THRESHOLD_MB = int(os.environ.get("POLYGRAPHY_ARRAY_SWAP_THRESHOLD_MB", "-1")) +ARRAY_SWAP_THRESHOLD_MB = int( + os.environ.get("POLYGRAPHY_ARRAY_SWAP_THRESHOLD_MB", "-1") +) """ int: The threshold, in megabytes, above which Polygraphy will evict an array from memory and swap it to disk. A negative value disables swapping and a value of 0 causes all arrays to be saved to disk. diff --git a/tools/Polygraphy/polygraphy/constants.py b/tools/Polygraphy/polygraphy/constants.py index a8dcc20e..694676d4 100644 --- a/tools/Polygraphy/polygraphy/constants.py +++ b/tools/Polygraphy/polygraphy/constants.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/cuda/cuda.py b/tools/Polygraphy/polygraphy/cuda/cuda.py index b7ff77be..2b6e8cd3 100644 --- a/tools/Polygraphy/polygraphy/cuda/cuda.py +++ b/tools/Polygraphy/polygraphy/cuda/cuda.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -73,12 +73,16 @@ def __init__(self): lib_pat = "libcudart.so*" fallback_lib = "libcudart.so" - cuda_paths = list(filter(lambda x: x, cuda_paths)) # Filter out empty paths (i.e. "") + cuda_paths = list( + filter(lambda x: x, cuda_paths) + ) # Filter out empty paths (i.e. "") candidates = util.find_in_dirs(lib_pat, cuda_paths) if not candidates: log_func = G_LOGGER.critical if fallback_lib is None else G_LOGGER.warning - log_func(f"Could not find the CUDA runtime library.\nNote: Paths searched were:\n{cuda_paths}") + log_func( + f"Could not find the CUDA runtime library.\nNote: Paths searched were:\n{cuda_paths}" + ) lib = fallback_lib G_LOGGER.warning(f"Attempting to load: '{lib}' using default loader paths") @@ -89,7 +93,9 @@ def __init__(self): self.handle = ctypes.CDLL(lib) if not self.handle: - G_LOGGER.critical("Could not load the CUDA runtime library. Is it on your loader path?") + G_LOGGER.critical( + "Could not load the CUDA runtime library. Is it on your loader path?" + ) @func.constantmethod def check(self, status): @@ -170,9 +176,15 @@ def memcpy(self, dst, src, nbytes, kind, stream_ptr=None): """ nbytes = ctypes.c_size_t(nbytes) # Required to prevent overflow if stream_ptr is not None: - self.check(self.handle.cudaMemcpyAsync(void_ptr(dst), void_ptr(src), nbytes, kind, void_ptr(stream_ptr))) + self.check( + self.handle.cudaMemcpyAsync( + void_ptr(dst), void_ptr(src), nbytes, kind, void_ptr(stream_ptr) + ) + ) else: - self.check(self.handle.cudaMemcpy(void_ptr(dst), void_ptr(src), nbytes, kind)) + self.check( + self.handle.cudaMemcpy(void_ptr(dst), void_ptr(src), nbytes, kind) + ) G_CUDA = None @@ -294,7 +306,9 @@ def dtype(self): try: # For backwards compatibility mod.warn_deprecated( - "Using NumPy data types in DeviceView/DeviceArray", use_instead=None, remove_in="0.50.0" + "Using NumPy data types in DeviceView/DeviceArray", + use_instead=None, + remove_in="0.50.0", ) G_LOGGER.warning( f"In the future, you will need to use `DataType.from_dtype(device_view.dtype).numpy()` to retrieve the NumPy data type" @@ -360,7 +374,9 @@ def __str__(self): return f"DeviceView[(dtype={self._dtype.name}, shape={self.shape}), ptr={hex(self.ptr)}]" def __repr__(self): - return util.make_repr("DeviceView", ptr=self.ptr, shape=self.shape, dtype=self._dtype)[0] + return util.make_repr( + "DeviceView", ptr=self.ptr, shape=self.shape, dtype=self._dtype + )[0] @mod.export() @@ -375,7 +391,11 @@ def __init__(self, shape=None, dtype=None): shape (Tuple[int]): The initial shape of the buffer. dtype (DataType): The data type of the buffer. """ - super().__init__(ptr=0, shape=util.default(shape, tuple()), dtype=util.default(dtype, DataType.FLOAT32)) + super().__init__( + ptr=0, + shape=util.default(shape, tuple()), + dtype=util.default(dtype, DataType.FLOAT32), + ) self.allocated_nbytes = 0 self.resize(self.shape) diff --git a/tools/Polygraphy/polygraphy/datatype/datatype.py b/tools/Polygraphy/polygraphy/datatype/datatype.py index c39a3b38..22a1bb9a 100644 --- a/tools/Polygraphy/polygraphy/datatype/datatype.py +++ b/tools/Polygraphy/polygraphy/datatype/datatype.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/datatype/numpy.py b/tools/Polygraphy/polygraphy/datatype/numpy.py index 5f6fedc5..c668a407 100644 --- a/tools/Polygraphy/polygraphy/datatype/numpy.py +++ b/tools/Polygraphy/polygraphy/datatype/numpy.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,7 +16,11 @@ # from polygraphy import mod, util -from polygraphy.datatype.datatype import DataType, register_dtype_importer, register_dtype_exporter +from polygraphy.datatype.datatype import ( + DataType, + register_dtype_importer, + register_dtype_exporter, +) np = mod.lazy_import("numpy") diff --git a/tools/Polygraphy/polygraphy/datatype/onnx.py b/tools/Polygraphy/polygraphy/datatype/onnx.py index 4fc66579..ff716ad2 100644 --- a/tools/Polygraphy/polygraphy/datatype/onnx.py +++ b/tools/Polygraphy/polygraphy/datatype/onnx.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,7 +16,11 @@ # from polygraphy import mod, util -from polygraphy.datatype.datatype import DataType, register_dtype_importer, register_dtype_exporter +from polygraphy.datatype.datatype import ( + DataType, + register_dtype_importer, + register_dtype_exporter, +) onnx = mod.lazy_import("onnx") @@ -46,7 +50,11 @@ def _get_mapping(): del DATATYPE_FROM_ONNX[None] onnx_type_map = dict(onnx.TensorProto.DataType.items()) - return {onnx_type_map[key]: val for key, val in DATATYPE_FROM_ONNX.items() if key in onnx_type_map} + return { + onnx_type_map[key]: val + for key, val in DATATYPE_FROM_ONNX.items() + if key in onnx_type_map + } @register_dtype_importer("onnx") diff --git a/tools/Polygraphy/polygraphy/datatype/onnxrt.py b/tools/Polygraphy/polygraphy/datatype/onnxrt.py index 56a40129..59ff113a 100644 --- a/tools/Polygraphy/polygraphy/datatype/onnxrt.py +++ b/tools/Polygraphy/polygraphy/datatype/onnxrt.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,7 +16,11 @@ # from polygraphy import util -from polygraphy.datatype.datatype import DataType, register_dtype_importer, register_dtype_exporter +from polygraphy.datatype.datatype import ( + DataType, + register_dtype_importer, + register_dtype_exporter, +) __DATATYPE_FROM_ONNXRT = { "tensor(double)": DataType.FLOAT64, diff --git a/tools/Polygraphy/polygraphy/datatype/tensorrt.py b/tools/Polygraphy/polygraphy/datatype/tensorrt.py index ad327e96..f59f8086 100644 --- a/tools/Polygraphy/polygraphy/datatype/tensorrt.py +++ b/tools/Polygraphy/polygraphy/datatype/tensorrt.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/datatype/torch.py b/tools/Polygraphy/polygraphy/datatype/torch.py index cf217d36..e85e4008 100644 --- a/tools/Polygraphy/polygraphy/datatype/torch.py +++ b/tools/Polygraphy/polygraphy/datatype/torch.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,7 +16,11 @@ # from polygraphy import mod, util -from polygraphy.datatype.datatype import DataType, register_dtype_importer, register_dtype_exporter +from polygraphy.datatype.datatype import ( + DataType, + register_dtype_importer, + register_dtype_exporter, +) torch = mod.lazy_import("torch>=1.13.0") diff --git a/tools/Polygraphy/polygraphy/exception/exception.py b/tools/Polygraphy/polygraphy/exception/exception.py index 27ab1f42..294ab37a 100644 --- a/tools/Polygraphy/polygraphy/exception/exception.py +++ b/tools/Polygraphy/polygraphy/exception/exception.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/func/func.py b/tools/Polygraphy/polygraphy/func/func.py index dbf111ab..77b59995 100644 --- a/tools/Polygraphy/polygraphy/func/func.py +++ b/tools/Polygraphy/polygraphy/func/func.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -110,14 +110,25 @@ def extended_func(*args, **kwargs): func_params = inspect.signature(func).parameters # Special case for when the extended function does not return anything - if len(func_params) == 0 and len(extend_func_ret_tuple) == 1 and extend_func_ret_tuple[0] is None: + if ( + len(func_params) == 0 + and len(extend_func_ret_tuple) == 1 + and extend_func_ret_tuple[0] is None + ): func_retval = func() elif len(extend_func_ret_tuple) == len(func_params): func_retval = func(*extend_func_ret_tuple) - elif len(func_params) == len(extend_func_ret_tuple) + len(args) + len(kwargs): + elif len(func_params) == len(extend_func_ret_tuple) + len(args) + len( + kwargs + ): # We need to turn `extend_func_ret_tuple` into keyword arguments so that it can # be ordered after `**kwargs`. - ret_arg_names = [param.name for param in list(func_params.values())[-len(extend_func_ret_tuple) :]] + ret_arg_names = [ + param.name + for param in list(func_params.values())[ + -len(extend_func_ret_tuple) : + ] + ] ret_kwargs = dict(zip(ret_arg_names, extend_func_ret_tuple)) func_retval = func(*args, **kwargs, **ret_kwargs) else: diff --git a/tools/Polygraphy/polygraphy/json/serde.py b/tools/Polygraphy/polygraphy/json/serde.py index fcc73180..607457b8 100644 --- a/tools/Polygraphy/polygraphy/json/serde.py +++ b/tools/Polygraphy/polygraphy/json/serde.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -176,7 +176,9 @@ def __call__(self, pairs): # Handle legacy naming first - these keys should not be present in JSON generated by more recent versions of Polygraphy. for type_str, func in self.polygraphy_registered.items(): - if type_str in dct and dct[type_str] == constants.LEGACY_TYPE_MARKER: # Found a custom type! + if ( + type_str in dct and dct[type_str] == constants.LEGACY_TYPE_MARKER + ): # Found a custom type! return func(dct) type_name = dct.get(constants.TYPE_MARKER) @@ -245,7 +247,11 @@ def load(mode="base64"): NUMPY_REGISTRATION_SUCCESS = True global TORCH_REGISTRATION_SUCCESS - if not TORCH_REGISTRATION_SUCCESS and torch.is_installed() and torch.is_importable(): + if ( + not TORCH_REGISTRATION_SUCCESS + and torch.is_installed() + and torch.is_importable() + ): @Encoder.register(torch.Tensor) def encode(tensor): diff --git a/tools/Polygraphy/polygraphy/logger/logger.py b/tools/Polygraphy/polygraphy/logger/logger.py index a8455360..7260ee9e 100644 --- a/tools/Polygraphy/polygraphy/logger/logger.py +++ b/tools/Polygraphy/polygraphy/logger/logger.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -188,7 +188,9 @@ class Logger: CRITICAL: "light_red", } - def __init__(self, severity=INFO, colors=True, letter=True, timestamp=False, line_info=False): + def __init__( + self, severity=INFO, colors=True, letter=True, timestamp=False, line_info=False + ): """ Args: severity (Union[int, Dict[str, int]]): @@ -465,7 +467,9 @@ def backtrace(self, depth=0, limit=None, severity=ERROR): ) # Info provides 1 stack frame limit = max(limit, 0) frame = sys._getframe(depth + 2) - self.log(" ".join(traceback.format_stack(f=frame, limit=limit)), severity=severity) + self.log( + " ".join(traceback.format_stack(f=frame, limit=limit)), severity=severity + ) def ultra_verbose(self, message, mode=LogMode.EACH): """ diff --git a/tools/Polygraphy/polygraphy/mod/exporter.py b/tools/Polygraphy/polygraphy/mod/exporter.py index cc592fc1..398f9465 100644 --- a/tools/Polygraphy/polygraphy/mod/exporter.py +++ b/tools/Polygraphy/polygraphy/mod/exporter.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -105,7 +105,9 @@ def find_method(symbol, method): if method in vars(ancestor): return vars(ancestor)[method] - assert False, f"Could not find method: {method} in the inheritance hierarcy of: {symbol}" + assert ( + False + ), f"Could not find method: {method} in the inheritance hierarcy of: {symbol}" def export_impl(func_or_cls): _add_to_all(func_or_cls.__name__, module) @@ -115,13 +117,19 @@ def export_impl(func_or_cls): # have no overlapping parameters. from polygraphy.backend.base import BaseLoader - assert inspect.isclass(func_or_cls), "Decorated type must be a loader to use funcify=True" + assert inspect.isclass( + func_or_cls + ), "Decorated type must be a loader to use funcify=True" assert BaseLoader in inspect.getmro( func_or_cls ), "Decorated type must derive from BaseLoader to use funcify=True" def get_params(method): - return list(inspect.signature(find_method(func_or_cls, method)).parameters.values())[1:] + return list( + inspect.signature( + find_method(func_or_cls, method) + ).parameters.values() + )[1:] def is_variadic(param): return param.kind in [param.VAR_POSITIONAL, param.VAR_KEYWORD] @@ -141,7 +149,9 @@ def param_names(params): init_params = get_params("__init__") call_impl_params = get_params("call_impl") - assert (set(param_names(call_impl_params)) - set(param_names(init_params))) == set( + assert ( + set(param_names(call_impl_params)) - set(param_names(init_params)) + ) == set( param_names(call_impl_params) ), "Cannot funcify a type where call_impl and __init__ have the same argument names!" @@ -152,14 +162,22 @@ def param_names(params): def build_arg_list(should_include): def str_from_param(p): - return get_param_name(p) + (f"={p.default}" if has_default(p) else "") + return get_param_name(p) + ( + f"={p.default}" if has_default(p) else "" + ) arg_list = [str_from_param(p) for p in init_params if should_include(p)] - arg_list += [str_from_param(p) for p in call_impl_params if should_include(p)] + arg_list += [ + str_from_param(p) for p in call_impl_params if should_include(p) + ] return arg_list - non_default_args = build_arg_list(should_include=lambda p: not is_variadic(p) and not has_default(p)) - default_args = build_arg_list(should_include=lambda p: not is_variadic(p) and has_default(p)) + non_default_args = build_arg_list( + should_include=lambda p: not is_variadic(p) and not has_default(p) + ) + default_args = build_arg_list( + should_include=lambda p: not is_variadic(p) and has_default(p) + ) special_args = build_arg_list(should_include=is_variadic) signature = ", ".join(non_default_args + default_args + special_args) @@ -168,7 +186,9 @@ def str_from_param(p): call_impl_args = ", ".join(param_names(call_impl_params)) def pascal_to_snake(name): - return "".join(f"_{c.lower()}" if c.isupper() else c for c in name).lstrip("_") + return "".join( + f"_{c.lower()}" if c.isupper() else c for c in name + ).lstrip("_") nonlocal func_name func_name = func_name or pascal_to_snake(loader.__name__) @@ -209,13 +229,19 @@ def try_add_method_doc(method): return export_impl -def warn_deprecated(name, use_instead, remove_in, module_name=None, always_show_warning=False): +def warn_deprecated( + name, use_instead, remove_in, module_name=None, always_show_warning=False +): if version(polygraphy.__version__) >= version(remove_in): - G_LOGGER.internal_error(f"{name} should have been removed in version: {remove_in}") + G_LOGGER.internal_error( + f"{name} should have been removed in version: {remove_in}" + ) full_obj_name = f"{module_name}.{name}" if module_name else name - msg = f"{full_obj_name} is deprecated and will be removed in Polygraphy {remove_in}." + msg = ( + f"{full_obj_name} is deprecated and will be removed in Polygraphy {remove_in}." + ) if use_instead is not None: msg += f" Use {use_instead} instead." @@ -245,8 +271,12 @@ def deprecate(remove_in, use_instead, module_name=None, name=None): """ def deprecate_impl(obj): - if config.INTERNAL_CORRECTNESS_CHECKS and version(polygraphy.__version__) >= version(remove_in): - G_LOGGER.internal_error(f"{obj} should have been removed in version: {remove_in}") + if config.INTERNAL_CORRECTNESS_CHECKS and version( + polygraphy.__version__ + ) >= version(remove_in): + G_LOGGER.internal_error( + f"{obj} should have been removed in version: {remove_in}" + ) nonlocal name name = name or obj.__name__ @@ -316,9 +346,12 @@ def export_deprecated_alias(name, remove_in, use_instead=None): module = inspect.getmodule(sys._getframe(1)) def export_deprecated_alias_impl(obj): - new_obj = deprecate(remove_in, use_instead=use_instead or obj.__name__, module_name=module.__name__, name=name)( - obj - ) + new_obj = deprecate( + remove_in, + use_instead=use_instead or obj.__name__, + module_name=module.__name__, + name=name, + )(obj) _define_in_module(name, new_obj, module) return obj diff --git a/tools/Polygraphy/polygraphy/mod/importer.py b/tools/Polygraphy/polygraphy/mod/importer.py index c686653c..bd1ac92c 100644 --- a/tools/Polygraphy/polygraphy/mod/importer.py +++ b/tools/Polygraphy/polygraphy/mod/importer.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -360,10 +360,12 @@ def import_from_script(path, name): def reset_sys_path(): del sys.path[0] + del sys.modules[modname] stack.callback(reset_sys_path) try: + importlib.invalidate_caches() mod = importlib.import_module(modname) return getattr(mod, name) except Exception as err: @@ -372,5 +374,4 @@ def reset_sys_path(): if ext != ".py": err_msg += f"\nThis could be because the extension of the file is not '.py'. Note: The extension is: {ext}" err_msg += f"\nNote: Error was: {err}" - err_msg += f"\nNote: sys.path was: {sys.path}" G_LOGGER.critical(err_msg) diff --git a/tools/Polygraphy/polygraphy/mod/util.py b/tools/Polygraphy/polygraphy/mod/util.py index c21d6780..b7529cdc 100644 --- a/tools/Polygraphy/polygraphy/mod/util.py +++ b/tools/Polygraphy/polygraphy/mod/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/onnx/loader.py b/tools/Polygraphy/polygraphy/tools/args/backend/onnx/loader.py index a07bb457..ff6bb484 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/onnx/loader.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/onnx/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -25,7 +25,11 @@ from polygraphy.tools.args.base import BaseArgs from polygraphy.tools.args.comparator.data_loader import DataLoaderArgs from polygraphy.tools.args.model import ModelArgs -from polygraphy.tools.script import Script, make_invocable, make_invocable_if_nondefault_kwargs +from polygraphy.tools.script import ( + Script, + make_invocable, + make_invocable_if_nondefault_kwargs, +) onnx_backend = mod.lazy_import("polygraphy.backend.onnx") onnxrt_backend = mod.lazy_import("polygraphy.backend.onnxrt") @@ -153,7 +157,9 @@ def infer_shapes(self, model, force=None): onnx.ModelProto: The model with shapes inferred. """ force = util.default(force, False) - with util.TempAttrChange(self, {"do_shape_inference": True if force else self.do_shape_inference}): + with util.TempAttrChange( + self, {"do_shape_inference": True if force else self.do_shape_inference} + ): loader = args_util.run_script(self.add_to_script, model) return util.invoke_if_callable(loader)[0] @@ -180,18 +186,26 @@ def fallback_inference(self, onnx_model, outputs=None): 2. Metadata for every tensor in the model. """ outputs = util.default(outputs, constants.MARK_ALL) - with G_LOGGER.verbosity(G_LOGGER.module_severity.get(G_LOGGER.module_path(__file__)) + 10): - load_model = onnx_backend.ModifyOutputs(onnx_model, outputs=outputs, copy=True) + with G_LOGGER.verbosity( + G_LOGGER.module_severity.get(G_LOGGER.module_path(__file__)) + 10 + ): + load_model = onnx_backend.ModifyOutputs( + onnx_model, outputs=outputs, copy=True + ) with onnxrt_backend.OnnxrtRunner( onnxrt_backend.SessionFromOnnx(onnx_backend.BytesFromOnnx(load_model)) ) as runner: data_loader = self.arg_groups[DataLoaderArgs].get_data_loader() loader_cache = DataLoaderCache(data_loader) - loader_cache.set_input_metadata(runner.get_input_metadata(use_numpy_dtypes=False)) + loader_cache.set_input_metadata( + runner.get_input_metadata(use_numpy_dtypes=False) + ) feed_dict = loader_cache[0] - with G_LOGGER.verbosity(G_LOGGER.module_severity.get(G_LOGGER.module_path(__file__)) - 10): + with G_LOGGER.verbosity( + G_LOGGER.module_severity.get(G_LOGGER.module_path(__file__)) - 10 + ): G_LOGGER.info( f"Running fallback shape inference using input metadata:\n{TensorMetadata.from_feed_dict(feed_dict)}" ) @@ -262,7 +276,9 @@ def __init__( def add_parser_args_impl(self): if self._output_opt: - params = ([self._output_short_opt] if self._output_short_opt else []) + [f"--{self._output_opt}"] + params = ([self._output_short_opt] if self._output_short_opt else []) + [ + f"--{self._output_opt}" + ] help_msg = "Path to save the ONNX model" if self._allow_multiple_models: help_msg = "Path to a directory in which to save ONNX model(s)" @@ -325,7 +341,9 @@ def parse_impl(self, args): external_data_path = external_data_path[0] or "" self.external_data_path = external_data_path - self.size_threshold = args_util.parse_num_bytes(args_util.get(args, "external_data_size_threshold")) + self.size_threshold = args_util.parse_num_bytes( + args_util.get(args, "external_data_size_threshold") + ) self.all_tensors_to_one_file = args_util.get(args, "all_tensors_to_one_file") def add_to_script_impl(self, script, loader_name): @@ -347,7 +365,9 @@ def add_to_script_impl(self, script, loader_name): # Need to run shape inference again after processing the graph since it may have changed. if self._allow_shape_inference: - loader_name = self.arg_groups[OnnxInferShapesArgs].add_to_script(script, loader_name) + loader_name = self.arg_groups[OnnxInferShapesArgs].add_to_script( + script, loader_name + ) script.add_import(imports=["SaveOnnx"], frm="polygraphy.backend.onnx") loader_name = script.add_loader( @@ -381,9 +401,9 @@ def save_onnx(self, model, path: str = None): attrs = {"path": path, "_disable_add_to_script_check": True} if self._allow_multiple_models: if self.external_data_path is not None: - attrs["external_data_path"] = os.path.basename(os.path.splitext(path)[0]) + ( - self.external_data_path or "_ext_data" - ) + attrs["external_data_path"] = os.path.basename( + os.path.splitext(path)[0] + ) + (self.external_data_path or "_ext_data") with util.TempAttrChange(self, attrs): loader = args_util.run_script(self.add_to_script, model) @@ -435,7 +455,9 @@ def __init__( self._allow_shape_inference = util.default(allow_shape_inference, True) self._outputs_opt_prefix = util.default(outputs_opt_prefix, "onnx-") self._allow_from_tf = util.default(allow_from_tf, False) - self._allow_setting_upper_bounds = util.default(allow_setting_upper_bounds, False) + self._allow_setting_upper_bounds = util.default( + allow_setting_upper_bounds, False + ) def add_parser_args_impl(self): self.group.add_argument( @@ -456,7 +478,9 @@ def add_parser_args_impl(self): default=None, ) - if self._outputs_opt_prefix is not False: # Empty strings should not disable the option + if ( + self._outputs_opt_prefix is not False + ): # Empty strings should not disable the option self.group.add_argument( f"--{self._outputs_opt_prefix}outputs", help="Name(s) of ONNX tensor(s) to mark as output(s). " @@ -500,7 +524,7 @@ def add_parser_args_impl(self): """, nargs="+", default=None, - dest="upper_bounds" + dest="upper_bounds", ) def parse_impl(self, args): @@ -520,9 +544,13 @@ def parse_impl(self, args): self.external_data_dir = args_util.get(args, "external_data_dir") self.ignore_external_data = args_util.get(args, "ignore_external_data") self.convert_to_fp16 = args_util.get(args, "fp_to_fp16") - self.upper_bounds = args_util.parse_arglist_to_dict(args_util.get(args, "upper_bounds")) + self.upper_bounds = args_util.parse_arglist_to_dict( + args_util.get(args, "upper_bounds") + ) - def _add_modify_onnx_outputs(self, script, loader_name, disable_custom_outputs: bool = None): + def _add_modify_onnx_outputs( + self, script, loader_name, disable_custom_outputs: bool = None + ): if disable_custom_outputs: outputs = None exclude_outputs = None @@ -531,10 +559,17 @@ def _add_modify_onnx_outputs(self, script, loader_name, disable_custom_outputs: exclude_outputs = self.exclude_outputs modify_outputs_loader = make_invocable_if_nondefault_kwargs( - "ModifyOnnxOutputs", loader_name, outputs=outputs, exclude_outputs=exclude_outputs + "ModifyOnnxOutputs", + loader_name, + outputs=outputs, + exclude_outputs=exclude_outputs, ) if modify_outputs_loader is not None: - script.add_import(imports="ModifyOutputs", frm="polygraphy.backend.onnx", imp_as="ModifyOnnxOutputs") + script.add_import( + imports="ModifyOutputs", + frm="polygraphy.backend.onnx", + imp_as="ModifyOnnxOutputs", + ) loader_name = script.add_loader( modify_outputs_loader, "modify_outputs", @@ -542,7 +577,9 @@ def _add_modify_onnx_outputs(self, script, loader_name, disable_custom_outputs: return loader_name - def add_to_script_impl(self, script, disable_custom_outputs: bool = None, serialize_model: bool = None): + def add_to_script_impl( + self, script, disable_custom_outputs: bool = None, serialize_model: bool = None + ): """ Args: disable_custom_outputs (bool): @@ -559,10 +596,16 @@ def add_to_script_impl(self, script, disable_custom_outputs: bool = None, serial if model_type.is_onnx(): loader_name = self.arg_groups[ModelArgs].path if self._allow_shape_inference: - loader_name = self.arg_groups[OnnxInferShapesArgs].add_to_script(script, loader_name) + loader_name = self.arg_groups[OnnxInferShapesArgs].add_to_script( + script, loader_name + ) - if loader_name == self.arg_groups[ModelArgs].path: # Shape inference loader isn't being used, have to load. - script.add_import(imports=["OnnxFromPath"], frm="polygraphy.backend.onnx") + if ( + loader_name == self.arg_groups[ModelArgs].path + ): # Shape inference loader isn't being used, have to load. + script.add_import( + imports=["OnnxFromPath"], frm="polygraphy.backend.onnx" + ) loader_str = make_invocable( "OnnxFromPath", self.arg_groups[ModelArgs].path, @@ -575,24 +618,39 @@ def add_to_script_impl(self, script, disable_custom_outputs: bool = None, serial loader_name = self.arg_groups[OnnxFromTfArgs].add_to_script(script) else: - G_LOGGER.critical(f"Model type: {model_type} could not be converted to an ONNX model.") + G_LOGGER.critical( + f"Model type: {model_type} could not be converted to an ONNX model." + ) - loader_name = self._add_modify_onnx_outputs(script, loader_name, disable_custom_outputs=disable_custom_outputs) + loader_name = self._add_modify_onnx_outputs( + script, loader_name, disable_custom_outputs=disable_custom_outputs + ) if self.convert_to_fp16: script.add_import(imports=["ConvertToFp16"], frm="polygraphy.backend.onnx") - loader_name = script.add_loader(make_invocable("ConvertToFp16", loader_name), "convert_to_fp16") + loader_name = script.add_loader( + make_invocable("ConvertToFp16", loader_name), "convert_to_fp16" + ) if self._allow_saving: - loader_name = self.arg_groups[OnnxSaveArgs].add_to_script(script, loader_name) + loader_name = self.arg_groups[OnnxSaveArgs].add_to_script( + script, loader_name + ) if serialize_model: script.add_import(imports=["BytesFromOnnx"], frm="polygraphy.backend.onnx") - loader_name = script.add_loader(make_invocable("BytesFromOnnx", loader_name), "serialize_onnx") + loader_name = script.add_loader( + make_invocable("BytesFromOnnx", loader_name), "serialize_onnx" + ) if self._allow_setting_upper_bounds and self.upper_bounds is not None: script.add_import(imports=["SetUpperBound"], frm="polygraphy.backend.onnx") - loader_name = script.add_loader(make_invocable("SetUpperBound", loader_name, upper_bounds=self.upper_bounds), "set_upper_bound") + loader_name = script.add_loader( + make_invocable( + "SetUpperBound", loader_name, upper_bounds=self.upper_bounds + ), + "set_upper_bound", + ) return loader_name @@ -610,11 +668,23 @@ def must_use_onnx_loader(self, disable_custom_outputs: bool = None): """ tmp_script = Script() inp_loader = "check_needs_modify" - needs_modify = self._add_modify_onnx_outputs(tmp_script, inp_loader, disable_custom_outputs) != inp_loader - needs_shape_inference = self._allow_shape_inference and self.arg_groups[OnnxInferShapesArgs].do_shape_inference - needs_save = self._allow_saving and self.arg_groups[OnnxSaveArgs].path is not None + needs_modify = ( + self._add_modify_onnx_outputs( + tmp_script, inp_loader, disable_custom_outputs + ) + != inp_loader + ) + needs_shape_inference = ( + self._allow_shape_inference + and self.arg_groups[OnnxInferShapesArgs].do_shape_inference + ) + needs_save = ( + self._allow_saving and self.arg_groups[OnnxSaveArgs].path is not None + ) needs_fp16_conversion = self.convert_to_fp16 - needs_setting_upper_bounds = self._allow_setting_upper_bounds and self.upper_bounds is not None + needs_setting_upper_bounds = ( + self._allow_setting_upper_bounds and self.upper_bounds is not None + ) # Currently, other loaders do not support external data, so we must fall back to the ONNX loader if it's present. return ( not self.arg_groups[ModelArgs].model_type.is_onnx() @@ -648,7 +718,12 @@ class OnnxFromTfArgs(BaseArgs): """ def add_parser_args_impl(self): - self.group.add_argument("--opset", help="Opset to use when converting to ONNX", default=None, type=int) + self.group.add_argument( + "--opset", + help="Opset to use when converting to ONNX", + default=None, + type=int, + ) def parse_impl(self, args): """ @@ -670,7 +745,9 @@ def add_to_script_impl(self, script): script.add_import(imports=["OnnxFromTfGraph"], frm="polygraphy.backend.onnx") loader_str = make_invocable( "OnnxFromTfGraph", - self.arg_groups[TfLoadArgs].add_to_script(script, disable_custom_outputs=True), + self.arg_groups[TfLoadArgs].add_to_script( + script, disable_custom_outputs=True + ), opset=self.opset, ) loader_name = script.add_loader(loader_str, "export_onnx_from_tf") diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/loader.py b/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/loader.py index 1cbeb665..8598bcee 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/loader.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -56,15 +56,18 @@ def parse_impl(self, args): self.providers = args_util.get(args, "providers") def add_to_script_impl(self, script, onnx_name=None): - if onnx_name is None: # default behavior according to self.arg_groups + if onnx_name is None: # default behavior according to self.arg_groups if self.arg_groups[OnnxLoadArgs].must_use_onnx_loader(): - onnx_name = self.arg_groups[OnnxLoadArgs].add_to_script(script, serialize_model=True) + onnx_name = self.arg_groups[OnnxLoadArgs].add_to_script( + script, serialize_model=True + ) else: onnx_name = self.arg_groups[ModelArgs].path script.add_import(imports=["SessionFromOnnx"], frm="polygraphy.backend.onnxrt") loader_name = script.add_loader( - make_invocable("SessionFromOnnx", onnx_name, providers=self.providers), "build_onnxrt_session" + make_invocable("SessionFromOnnx", onnx_name, providers=self.providers), + "build_onnxrt_session", ) return loader_name diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/runner.py b/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/runner.py index fd9f93f6..f5c051ab 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/runner.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/onnxrt/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -35,4 +35,8 @@ def get_name_opt_impl(self): def add_to_script_impl(self, script): script.add_import(imports=["OnnxrtRunner"], frm="polygraphy.backend.onnxrt") - script.add_runner(make_invocable("OnnxrtRunner", self.arg_groups[OnnxrtSessionArgs].add_to_script(script))) + script.add_runner( + make_invocable( + "OnnxrtRunner", self.arg_groups[OnnxrtSessionArgs].add_to_script(script) + ) + ) diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/pluginref/runner.py b/tools/Polygraphy/polygraphy/tools/args/backend/pluginref/runner.py index 2d5b5078..9438d9e9 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/pluginref/runner.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/pluginref/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -36,8 +36,12 @@ def get_name_opt_impl(self): def add_to_script_impl(self, script): script.add_import(imports=["GsFromOnnx"], frm="polygraphy.backend.onnx") - script.add_import(imports=["PluginRefRunner"], frm="polygraphy.backend.pluginref") + script.add_import( + imports=["PluginRefRunner"], frm="polygraphy.backend.pluginref" + ) onnx_name = self.arg_groups[OnnxLoadArgs].add_to_script(script) - loader_name = script.add_loader(make_invocable("GsFromOnnx", onnx_name), "pluginref") + loader_name = script.add_loader( + make_invocable("GsFromOnnx", onnx_name), "pluginref" + ) script.add_runner(make_invocable("PluginRefRunner", loader_name)) diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/runner_select.py b/tools/Polygraphy/polygraphy/tools/args/backend/runner_select.py index 8d3fba18..cb19ba2f 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/runner_select.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/runner_select.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -98,7 +98,9 @@ def add_to_script_impl(self, script): str: The name of the list of runners in the script. """ if not self.runners: - G_LOGGER.warning("No runners have been selected. Inference will not be run!") + G_LOGGER.warning( + "No runners have been selected. Inference will not be run!" + ) for opt in self.runners.keys(): self._opt_to_group_map[opt].add_to_script(script) diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/tf/config.py b/tools/Polygraphy/polygraphy/tools/args/backend/tf/config.py index cfee0a8d..859c3f1f 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/tf/config.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/tf/config.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -35,10 +35,16 @@ def add_parser_args_impl(self): default=None, ) self.group.add_argument( - "--allow-growth", help="Allow GPU memory allocated by TensorFlow to grow", action="store_true", default=None + "--allow-growth", + help="Allow GPU memory allocated by TensorFlow to grow", + action="store_true", + default=None, ) self.group.add_argument( - "--xla", help="[EXPERIMENTAL] Attempt to run graph with xla", action="store_true", default=None + "--xla", + help="[EXPERIMENTAL] Attempt to run graph with xla", + action="store_true", + default=None, ) def parse_impl(self, args): @@ -63,7 +69,9 @@ def add_to_script_impl(self, script): ) if config_loader_str is not None: script.add_import(imports=["CreateConfig"], frm="polygraphy.backend.tf") - config_loader_name = script.add_loader(config_loader_str, "create_tf_config") + config_loader_name = script.add_loader( + config_loader_str, "create_tf_config" + ) else: config_loader_name = None return config_loader_name diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/tf/loader.py b/tools/Polygraphy/polygraphy/tools/args/backend/tf/loader.py index 67213cea..1ff44e8c 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/tf/loader.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/tf/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -101,7 +101,12 @@ class TfLoadArgs(BaseArgs): - TrtSaveEngineBytesArgs: if allow_tftrt == True """ - def __init__(self, allow_artifacts: bool = None, allow_custom_outputs: bool = None, allow_tftrt: bool = None): + def __init__( + self, + allow_artifacts: bool = None, + allow_custom_outputs: bool = None, + allow_tftrt: bool = None, + ): """ Args: allow_artifacts (bool): @@ -151,7 +156,10 @@ def add_parser_args_impl(self): ) self.group.add_argument( - "--freeze-graph", help="[EXPERIMENTAL] Attempt to freeze the graph", action="store_true", default=None + "--freeze-graph", + help="[EXPERIMENTAL] Attempt to freeze the graph", + action="store_true", + default=None, ) def parse_impl(self, args): @@ -203,13 +211,17 @@ def add_to_script_impl(self, script, disable_custom_outputs=None): loader_id = "load_frozen" loader_str = make_invocable("GraphFromFrozen", model_file) else: - G_LOGGER.critical(f"Model type: {model_type} cannot be imported with TensorFlow.") + G_LOGGER.critical( + f"Model type: {model_type} cannot be imported with TensorFlow." + ) loader_name = script.add_loader(loader_str, loader_id) if self.freeze_graph: script.add_import(imports=["OptimizeGraph"], frm="polygraphy.backend.tf") - loader_name = script.add_loader(make_invocable("OptimizeGraph", loader_name), "optimize_graph") + loader_name = script.add_loader( + make_invocable("OptimizeGraph", loader_name), "optimize_graph" + ) engine_dir = None if self._allow_tftrt: @@ -219,7 +231,11 @@ def add_to_script_impl(self, script, disable_custom_outputs=None): engine_dir = self.arg_groups[TrtSaveEngineBytesArgs].path MODIFY_TF = "ModifyGraphOutputs" - outputs = None if disable_custom_outputs else args_util.get_outputs_for_script(script, self.outputs) + outputs = ( + None + if disable_custom_outputs + else args_util.get_outputs_for_script(script, self.outputs) + ) modify_tf_str = make_invocable(MODIFY_TF, loader_name, outputs=outputs) if modify_tf_str != make_invocable(MODIFY_TF, loader_name): script.add_import(imports=[MODIFY_TF], frm="polygraphy.backend.tf") diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/tf/runner.py b/tools/Polygraphy/polygraphy/tools/args/backend/tf/runner.py index 1c9ea197..4e46114f 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/tf/runner.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/tf/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -61,7 +61,10 @@ def add_to_script_impl(self, script): script.add_import(imports=["SessionFromGraph"], frm="polygraphy.backend.tf") loader_name = script.add_loader( - make_invocable("SessionFromGraph", graph_name, config=config_name), "build_tf_session" + make_invocable("SessionFromGraph", graph_name, config=config_name), + "build_tf_session", ) - script.add_runner(make_invocable("TfRunner", loader_name, timeline_path=self.timeline_path)) + script.add_runner( + make_invocable("TfRunner", loader_name, timeline_path=self.timeline_path) + ) diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py b/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py index 51f287e3..10aea683 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/trt/config.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -25,7 +25,13 @@ from polygraphy.tools.args.base import BaseArgs from polygraphy.tools.args.comparator.data_loader import DataLoaderArgs from polygraphy.tools.args.model import ModelArgs -from polygraphy.tools.script import inline, inline_identifier, make_invocable, make_invocable_if_nondefault, safe +from polygraphy.tools.script import ( + inline, + inline_identifier, + make_invocable, + make_invocable_if_nondefault, + safe, +) def parse_profile_shapes(default_shapes, min_args, opt_args, max_args): @@ -49,7 +55,10 @@ def get_shapes(lst, idx): default_shapes.update(args_util.parse_meta(lst[idx], includes_dtype=False)) # Don't care about dtype, and need to override dynamic dimensions - shapes = {name: util.override_dynamic_shape(shape) for name, (_, shape) in default_shapes.items()} + shapes = { + name: util.override_dynamic_shape(shape) + for name, (_, shape) in default_shapes.items() + } for name, shape in shapes.items(): if tuple(default_shapes[name].shape) != tuple(shape): @@ -80,7 +89,10 @@ def get_shapes(lst, idx): f"Mismatch in input names between optimum shapes ({list(opt_shapes.keys())}) and maximum shapes ({list(max_shapes.keys())})" ) - profile = {name: (min_shapes[name], opt_shapes[name], max_shapes[name]) for name in min_shapes.keys()} + profile = { + name: (min_shapes[name], opt_shapes[name], max_shapes[name]) + for name in min_shapes.keys() + } profiles.append(profile) return profiles @@ -123,8 +135,12 @@ def __init__( Defaults to False. """ super().__init__() - self._precision_constraints_default = util.default(precision_constraints_default, "none") - self._allow_random_data_calib_warning = util.default(allow_random_data_calib_warning, True) + self._precision_constraints_default = util.default( + precision_constraints_default, "none" + ) + self._allow_random_data_calib_warning = util.default( + allow_random_data_calib_warning, True + ) self._allow_custom_input_shapes = util.default(allow_custom_input_shapes, True) self._allow_engine_capability = util.default(allow_engine_capability, False) self._allow_tensor_formats = util.default(allow_tensor_formats, False) @@ -158,10 +174,30 @@ def add_parser_args_impl(self): default=[], ) - self.group.add_argument("--tf32", help="Enable tf32 precision in TensorRT", action="store_true", default=None) - self.group.add_argument("--fp16", help="Enable fp16 precision in TensorRT", action="store_true", default=None) - self.group.add_argument("--bf16", help="Enable bf16 precision in TensorRT", action="store_true", default=None) - self.group.add_argument("--fp8", help="Enable fp8 precision in TensorRT", action="store_true", default=None) + self.group.add_argument( + "--tf32", + help="Enable tf32 precision in TensorRT", + action="store_true", + default=None, + ) + self.group.add_argument( + "--fp16", + help="Enable fp16 precision in TensorRT", + action="store_true", + default=None, + ) + self.group.add_argument( + "--bf16", + help="Enable bf16 precision in TensorRT", + action="store_true", + default=None, + ) + self.group.add_argument( + "--fp8", + help="Enable fp8 precision in TensorRT", + action="store_true", + default=None, + ) self.group.add_argument( "--int8", help="Enable int8 precision in TensorRT. " @@ -413,7 +449,7 @@ def add_parser_args_impl(self): "--weight-streaming", help="Build a weight streamable engine. Must be set with --strongly-typed. The weight streaming amount can be set with --weight-streaming-budget.", action="store_true", - default=None + default=None, ) if self._allow_engine_capability: @@ -485,10 +521,14 @@ def parse_impl(self, args): default_shapes = TensorMetadata() if self._allow_custom_input_shapes: if not hasattr(self.arg_groups[ModelArgs], "input_shapes"): - G_LOGGER.internal_error("ModelArgs must be parsed before TrtConfigArgs!") + G_LOGGER.internal_error( + "ModelArgs must be parsed before TrtConfigArgs!" + ) default_shapes = self.arg_groups[ModelArgs].input_shapes - self.profile_dicts = parse_profile_shapes(default_shapes, trt_min_shapes, trt_opt_shapes, trt_max_shapes) + self.profile_dicts = parse_profile_shapes( + default_shapes, trt_min_shapes, trt_opt_shapes, trt_max_shapes + ) self.tf32 = args_util.get(args, "tf32") self.fp16 = args_util.get(args, "fp16") @@ -508,7 +548,9 @@ def parse_impl(self, args): calib_base = args_util.get(args, "calibration_base_class") self.calibration_base_class = None if calib_base is not None: - self.calibration_base_class = inline(safe("trt.{:}", inline_identifier(calib_base))) + self.calibration_base_class = inline( + safe("trt.{:}", inline_identifier(calib_base)) + ) self._quantile = args_util.get(args, "quantile") self._regression_cutoff = args_util.get(args, "regression_cutoff") @@ -523,22 +565,31 @@ def parse_impl(self, args): tactic_sources = args_util.get(args, "tactic_sources") self.tactic_sources = None if tactic_sources is not None: - self.tactic_sources = [make_trt_enum_val("TacticSource", source) for source in tactic_sources] + self.tactic_sources = [ + make_trt_enum_val("TacticSource", source) for source in tactic_sources + ] - self.trt_config_script, self.trt_config_func_name = args_util.parse_script_and_func_name( - args_util.get(args, "trt_config_script"), default_func_name="load_config" + self.trt_config_script, self.trt_config_func_name = ( + args_util.parse_script_and_func_name( + args_util.get(args, "trt_config_script"), + default_func_name="load_config", + ) ) ( self.trt_config_postprocess_script, self.trt_config_postprocess_func_name, ) = args_util.parse_script_and_func_name( - args_util.get(args, "trt_config_postprocess_script"), default_func_name="postprocess_config" + args_util.get(args, "trt_config_postprocess_script"), + default_func_name="postprocess_config", ) func_name = args_util.get(args, "trt_config_func_name") if func_name is not None: mod.warn_deprecated( - "--trt-config-func-name", "the config script argument", "0.50.0", always_show_warning=True + "--trt-config-func-name", + "the config script argument", + "0.50.0", + always_show_warning=True, ) self.trt_config_func_name = func_name @@ -546,7 +597,9 @@ def parse_impl(self, args): self.allow_gpu_fallback = args_util.get(args, "allow_gpu_fallback") memory_pool_limits = args_util.parse_arglist_to_dict( - args_util.get(args, "memory_pool_limit"), cast_to=args_util.parse_num_bytes, allow_empty_key=False + args_util.get(args, "memory_pool_limit"), + cast_to=args_util.parse_num_bytes, + allow_empty_key=False, ) self.memory_pool_limits = None if memory_pool_limits is not None: @@ -558,18 +611,27 @@ def parse_impl(self, args): preview_features = args_util.get(args, "preview_features") self.preview_features = None if preview_features is not None: - self.preview_features = [make_trt_enum_val("PreviewFeature", feature) for feature in preview_features] + self.preview_features = [ + make_trt_enum_val("PreviewFeature", feature) + for feature in preview_features + ] engine_capability = args_util.get(args, "engine_capability") self.engine_capability = None if engine_capability is not None: - self.engine_capability = make_trt_enum_val("EngineCapability", engine_capability) + self.engine_capability = make_trt_enum_val( + "EngineCapability", engine_capability + ) self.direct_io = args_util.get(args, "direct_io") - self.builder_optimization_level = args_util.get(args, "builder_optimization_level") + self.builder_optimization_level = args_util.get( + args, "builder_optimization_level" + ) self.hardware_compatibility_level = None - hardware_compatibility_level = args_util.get(args, "hardware_compatibility_level") + hardware_compatibility_level = args_util.get( + args, "hardware_compatibility_level" + ) if hardware_compatibility_level is not None: self.hardware_compatibility_level = make_trt_enum_val( "HardwareCompatibilityLevel", hardware_compatibility_level @@ -589,14 +651,23 @@ def parse_impl(self, args): quantization_flags = args_util.get(args, "quantization_flags") self.quantization_flags = None if quantization_flags is not None: - self.quantization_flags = [make_trt_enum_val("QuantizationFlag", flag) for flag in quantization_flags] + self.quantization_flags = [ + make_trt_enum_val("QuantizationFlag", flag) + for flag in quantization_flags + ] if self.exclude_lean_runtime and not self.version_compatible: - G_LOGGER.critical(f"`--exclude-lean-runtime` requires `--version-compatible` to be enabled.") + G_LOGGER.critical( + f"`--exclude-lean-runtime` requires `--version-compatible` to be enabled." + ) - self.error_on_timing_cache_miss = args_util.get(args, "error_on_timing_cache_miss") + self.error_on_timing_cache_miss = args_util.get( + args, "error_on_timing_cache_miss" + ) - self.disable_compilation_cache = args_util.get(args, "disable_compilation_cache") + self.disable_compilation_cache = args_util.get( + args, "disable_compilation_cache" + ) self.weight_streaming = args_util.get(args, "weight_streaming") @@ -605,19 +676,29 @@ def add_to_script_impl(self, script): for profile_dict in self.profile_dicts: profile_str = "Profile()" for name in profile_dict.keys(): - profile_str += safe(".add({:}, min={:}, opt={:}, max={:})", name, *profile_dict[name]).unwrap() + profile_str += safe( + ".add({:}, min={:}, opt={:}, max={:})", name, *profile_dict[name] + ).unwrap() profiles.append(profile_str) if profiles: script.add_import(imports=["Profile"], frm="polygraphy.backend.trt") profiles = safe( - "[\n{tab}{:}\n]", inline(safe(f",\n{constants.TAB}".join(profiles))), tab=inline(safe(constants.TAB)) + "[\n{tab}{:}\n]", + inline(safe(f",\n{constants.TAB}".join(profiles))), + tab=inline(safe(constants.TAB)), ) profile_name = script.add_loader(profiles, "profiles") else: profile_name = None calibrator = None - if any(arg is not None for arg in [self.calibration_cache, self.calibration_base_class]) and not self.int8: + if ( + any( + arg is not None + for arg in [self.calibration_cache, self.calibration_base_class] + ) + and not self.int8 + ): G_LOGGER.warning( "Some int8 calibrator options were set, but int8 precision is not enabled. " "Calibration options will be ignored. Please set --int8 to enable calibration. " @@ -632,7 +713,10 @@ def add_to_script_impl(self, script): if ( self.arg_groups[DataLoaderArgs].is_using_random_data() - and (not self.calibration_cache or not os.path.exists(self.calibration_cache)) + and ( + not self.calibration_cache + or not os.path.exists(self.calibration_cache) + ) and self._allow_random_data_calib_warning ): G_LOGGER.warning( @@ -644,7 +728,11 @@ def add_to_script_impl(self, script): calibrator = make_invocable( "Calibrator", - data_loader=data_loader_name if data_loader_name else inline(safe("DataLoader()")), + data_loader=( + data_loader_name + if data_loader_name + else inline(safe("DataLoader()")) + ), cache=self.calibration_cache, BaseClass=self.calibration_base_class, quantile=self._quantile, @@ -675,9 +763,13 @@ def add_to_script_impl(self, script): script.add_import(imports="tensorrt", imp_as="trt") if self.trt_config_script is not None: - script.add_import(imports=["InvokeFromScript"], frm="polygraphy.backend.common") + script.add_import( + imports=["InvokeFromScript"], frm="polygraphy.backend.common" + ) config_loader_str = make_invocable( - "InvokeFromScript", self.trt_config_script, name=self.trt_config_func_name + "InvokeFromScript", + self.trt_config_script, + name=self.trt_config_func_name, ) else: config_loader_str = make_invocable_if_nondefault( @@ -715,28 +807,47 @@ def add_to_script_impl(self, script): weight_streaming=self.weight_streaming, ) if config_loader_str is not None: - script.add_import(imports="CreateConfig", frm="polygraphy.backend.trt", imp_as="CreateTrtConfig") + script.add_import( + imports="CreateConfig", + frm="polygraphy.backend.trt", + imp_as="CreateTrtConfig", + ) if config_loader_str is not None: - config_loader_name = script.add_loader(config_loader_str, "create_trt_config") + config_loader_name = script.add_loader( + config_loader_str, "create_trt_config" + ) else: config_loader_name = None if self.trt_config_postprocess_script is not None: # Need to set up a default config if there isn't one since `PostprocessConfig` will require a config. if config_loader_name is None: - script.add_import(imports="CreateConfig", frm="polygraphy.backend.trt", imp_as="CreateTrtConfig") - config_loader_name = script.add_loader(make_invocable("CreateTrtConfig"), "create_trt_config") + script.add_import( + imports="CreateConfig", + frm="polygraphy.backend.trt", + imp_as="CreateTrtConfig", + ) + config_loader_name = script.add_loader( + make_invocable("CreateTrtConfig"), "create_trt_config" + ) - script.add_import(imports=["InvokeFromScript"], frm="polygraphy.backend.common") script.add_import( - imports=["PostprocessConfig"], frm="polygraphy.backend.trt", imp_as="PostprocessTrtConfig" + imports=["InvokeFromScript"], frm="polygraphy.backend.common" + ) + script.add_import( + imports=["PostprocessConfig"], + frm="polygraphy.backend.trt", + imp_as="PostprocessTrtConfig", ) func = make_invocable( - "InvokeFromScript", self.trt_config_postprocess_script, name=self.trt_config_postprocess_func_name + "InvokeFromScript", + self.trt_config_postprocess_script, + name=self.trt_config_postprocess_func_name, ) config_loader_name = script.add_loader( - make_invocable("PostprocessTrtConfig", config_loader_name, func=func), "postprocess_trt_config" + make_invocable("PostprocessTrtConfig", config_loader_name, func=func), + "postprocess_trt_config", ) return config_loader_name diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py b/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py index 7c0f88f4..21763062 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/trt/loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -26,7 +26,13 @@ from polygraphy.tools.args.backend.trt.helper import make_trt_enum_val from polygraphy.tools.args.base import BaseArgs from polygraphy.tools.args.model import ModelArgs -from polygraphy.tools.script import inline, inline_identifier, make_invocable, make_invocable_if_nondefault_kwargs, safe +from polygraphy.tools.script import ( + inline, + inline_identifier, + make_invocable, + make_invocable_if_nondefault_kwargs, + safe, +) @mod.export() @@ -36,7 +42,12 @@ class TrtLoadPluginsArgs(BaseArgs): """ def add_parser_args_impl(self): - self.group.add_argument("--plugins", help="Path(s) of plugin libraries to load", nargs="+", default=None) + self.group.add_argument( + "--plugins", + help="Path(s) of plugin libraries to load", + nargs="+", + default=None, + ) def parse_impl(self, args): """ @@ -56,7 +67,9 @@ def add_to_script_impl(self, script, loader_name: str): """ if self.plugins: script.add_import(imports=["LoadPlugins"], frm="polygraphy.backend.trt") - loader_str = make_invocable("LoadPlugins", plugins=self.plugins, obj=loader_name) + loader_str = make_invocable( + "LoadPlugins", plugins=self.plugins, obj=loader_name + ) loader_name = script.add_loader(loader_str, "load_plugins") return loader_name @@ -97,7 +110,9 @@ def parse_impl(self, args): flags (List[str]): flags for onnxparser """ self._flags = args_util.get(args, "onnx_flags", default=[]) - self._plugin_instancenorm = args_util.get(args, "plugin_instancenorm", default=None) + self._plugin_instancenorm = args_util.get( + args, "plugin_instancenorm", default=None + ) def get_flags(self): """ @@ -119,7 +134,10 @@ def get_flags(self): ) flags.append("native_instancenorm") - return ([make_trt_enum_val("OnnxParserFlag", f) for f in flags] or None, self._plugin_instancenorm) + return ( + [make_trt_enum_val("OnnxParserFlag", f) for f in flags] or None, + self._plugin_instancenorm, + ) @mod.export() @@ -136,7 +154,10 @@ class TrtLoadNetworkArgs(BaseArgs): """ def __init__( - self, allow_custom_outputs: bool = None, allow_onnx_loading: bool = None, allow_tensor_formats: bool = None + self, + allow_custom_outputs: bool = None, + allow_onnx_loading: bool = None, + allow_tensor_formats: bool = None, ): """ Args: @@ -266,26 +287,38 @@ def parse_impl(self, args): self.layer_precisions = None if layer_precisions is not None: self.layer_precisions = { - name: inline(safe("trt.{}", inline_identifier(value))) for name, value in layer_precisions.items() + name: inline(safe("trt.{}", inline_identifier(value))) + for name, value in layer_precisions.items() } - tensor_datatypes = args_util.parse_arglist_to_dict(args_util.get(args, "tensor_dtypes"), allow_empty_key=False) + tensor_datatypes = args_util.parse_arglist_to_dict( + args_util.get(args, "tensor_dtypes"), allow_empty_key=False + ) self.tensor_datatypes = None if tensor_datatypes is not None: self.tensor_datatypes = { - name: inline(safe("trt.{}", inline_identifier(value))) for name, value in tensor_datatypes.items() + name: inline(safe("trt.{}", inline_identifier(value))) + for name, value in tensor_datatypes.items() } - tensor_formats = args_util.parse_arglist_to_dict(args_util.get(args, "tensor_formats"), allow_empty_key=False) + tensor_formats = args_util.parse_arglist_to_dict( + args_util.get(args, "tensor_formats"), allow_empty_key=False + ) self.tensor_formats = None if tensor_formats is not None: self.tensor_formats = { - name: [inline(safe("trt.TensorFormat.{}", inline_identifier(value.upper()))) for value in values] + name: [ + inline( + safe("trt.TensorFormat.{}", inline_identifier(value.upper())) + ) + for value in values + ] for name, values in tensor_formats.items() } pps = args_util.parse_arglist_to_tuple_list( - args_util.get(args, "trt_network_postprocess_script"), treat_missing_sep_as_val=False + args_util.get(args, "trt_network_postprocess_script"), + treat_missing_sep_as_val=False, ) if pps is None: pps = [] @@ -299,13 +332,18 @@ def parse_impl(self, args): self.postprocess_scripts.append((script_path, func)) self.strongly_typed = args_util.get(args, "strongly_typed") - + self.mark_debug = args_util.get(args, "mark_debug") def add_to_script_impl(self, script): network_func_name = self.arg_groups[ModelArgs].extra_model_info if self.trt_network_func_name is not None: - mod.warn_deprecated("--trt-network-func-name", "the model argument", "0.50.0", always_show_warning=True) + mod.warn_deprecated( + "--trt-network-func-name", + "the model argument", + "0.50.0", + always_show_warning=True, + ) network_func_name = self.trt_network_func_name model_file = self.arg_groups[ModelArgs].path @@ -314,12 +352,21 @@ def add_to_script_impl(self, script): parser_flags, plugin_instancenorm = self.arg_groups[TrtOnnxFlagArgs].get_flags() if any( - arg is not None for arg in [self.layer_precisions, self.tensor_datatypes, self.tensor_formats, parser_flags, plugin_instancenorm] + arg is not None + for arg in [ + self.layer_precisions, + self.tensor_datatypes, + self.tensor_formats, + parser_flags, + plugin_instancenorm, + ] ): script.add_import(imports="tensorrt", imp_as="trt") if model_type == "trt-network-script": - script.add_import(imports=["InvokeFromScript"], frm="polygraphy.backend.common") + script.add_import( + imports=["InvokeFromScript"], frm="polygraphy.backend.common" + ) loader_str = make_invocable( "InvokeFromScript", model_file, @@ -327,56 +374,82 @@ def add_to_script_impl(self, script): ) loader_name = script.add_loader(loader_str, "load_network") elif self._allow_onnx_loading: - if self.arg_groups[OnnxLoadArgs].must_use_onnx_loader(disable_custom_outputs=True): + if self.arg_groups[OnnxLoadArgs].must_use_onnx_loader( + disable_custom_outputs=True + ): # When loading from ONNX, we need to disable custom outputs since TRT requires dtypes on outputs, # which our marking function doesn't guarantee. - script.add_import(imports=["NetworkFromOnnxBytes"], frm="polygraphy.backend.trt") + script.add_import( + imports=["NetworkFromOnnxBytes"], frm="polygraphy.backend.trt" + ) onnx_loader = self.arg_groups[OnnxLoadArgs].add_to_script( script, disable_custom_outputs=True, serialize_model=True ) loader_str = make_invocable( "NetworkFromOnnxBytes", - self.arg_groups[TrtLoadPluginsArgs].add_to_script(script, onnx_loader), + self.arg_groups[TrtLoadPluginsArgs].add_to_script( + script, onnx_loader + ), flags=parser_flags, plugin_instancenorm=plugin_instancenorm, strongly_typed=self.strongly_typed, ) loader_name = script.add_loader(loader_str, "parse_network_from_onnx") else: - script.add_import(imports=["NetworkFromOnnxPath"], frm="polygraphy.backend.trt") + script.add_import( + imports=["NetworkFromOnnxPath"], frm="polygraphy.backend.trt" + ) loader_str = make_invocable( "NetworkFromOnnxPath", - self.arg_groups[TrtLoadPluginsArgs].add_to_script(script, model_file), + self.arg_groups[TrtLoadPluginsArgs].add_to_script( + script, model_file + ), flags=parser_flags, plugin_instancenorm=plugin_instancenorm, strongly_typed=self.strongly_typed, ) loader_name = script.add_loader(loader_str, "parse_network_from_onnx") else: - G_LOGGER.internal_error("Loading from ONNX is not enabled and a network script was not provided!") + G_LOGGER.internal_error( + "Loading from ONNX is not enabled and a network script was not provided!" + ) def add_loader_if_nondefault(loader, result_var_name, **kwargs): - loader_str = make_invocable_if_nondefault_kwargs(loader, loader_name, **kwargs) + loader_str = make_invocable_if_nondefault_kwargs( + loader, loader_name, **kwargs + ) if loader_str is not None: script.add_import(imports=[loader], frm="polygraphy.backend.trt") return script.add_loader(loader_str, result_var_name) return loader_name for i, (script_path, func_name) in enumerate(self.postprocess_scripts): - script.add_import(imports=["InvokeFromScript"], frm="polygraphy.backend.common") + script.add_import( + imports=["InvokeFromScript"], frm="polygraphy.backend.common" + ) pps = make_invocable("InvokeFromScript", script_path, name=func_name) loader_name = add_loader_if_nondefault( - "PostprocessNetwork", f"postprocess_step_{i}", func=pps, name=f"{script_path}:{func_name}" + "PostprocessNetwork", + f"postprocess_step_{i}", + func=pps, + name=f"{script_path}:{func_name}", ) loader_name = add_loader_if_nondefault( - "ModifyNetworkOutputs", "set_network_outputs", outputs=outputs, exclude_outputs=self.exclude_outputs + "ModifyNetworkOutputs", + "set_network_outputs", + outputs=outputs, + exclude_outputs=self.exclude_outputs, ) loader_name = add_loader_if_nondefault( - "SetLayerPrecisions", "set_layer_precisions", layer_precisions=self.layer_precisions + "SetLayerPrecisions", + "set_layer_precisions", + layer_precisions=self.layer_precisions, ) loader_name = add_loader_if_nondefault( - "SetTensorDatatypes", "set_tensor_datatypes", tensor_datatypes=self.tensor_datatypes + "SetTensorDatatypes", + "set_tensor_datatypes", + tensor_datatypes=self.tensor_datatypes, ) loader_name = add_loader_if_nondefault( "SetTensorFormats", "set_tensor_formats", tensor_formats=self.tensor_formats @@ -425,8 +498,15 @@ def __init__(self, output_opt: str = None, output_short_opt: str = None): def add_parser_args_impl(self): if self._output_opt: - params = ([self._output_short_opt] if self._output_short_opt else []) + [f"--{self._output_opt}"] - self.group.add_argument(*params, help="Path to save the TensorRT Engine", dest="save_engine", default=None) + params = ([self._output_short_opt] if self._output_short_opt else []) + [ + f"--{self._output_opt}" + ] + self.group.add_argument( + *params, + help="Path to save the TensorRT Engine", + dest="save_engine", + default=None, + ) def parse_impl(self, args): """ @@ -450,7 +530,10 @@ def add_to_script_impl(self, script, loader_name): return loader_name script.add_import(imports=["SaveBytes"], frm="polygraphy.backend.common") - return script.add_loader(make_invocable("SaveBytes", loader_name, path=self.path), "save_engine_bytes") + return script.add_loader( + make_invocable("SaveBytes", loader_name, path=self.path), + "save_engine_bytes", + ) def save_engine_bytes(self, engine_bytes, path=None): """ @@ -502,7 +585,10 @@ def add_to_script_impl(self, script, loader_name): return loader_name script.add_import(imports=["BytesFromEngine"], frm="polygraphy.backend.trt") - loader_name = script.add_loader(make_invocable("BytesFromEngine", loader_name, path=path), "bytes_from_engine") + loader_name = script.add_loader( + make_invocable("BytesFromEngine", loader_name, path=path), + "bytes_from_engine", + ) return self.arg_groups[TrtSaveEngineArgs].add_to_script(script, loader_name) def save_engine(self, engine, path=None): @@ -571,30 +657,43 @@ def add_to_script_impl(self, script, network_name=None): network_name (str): The name of a variable in the script pointing to a network loader. """ if self.arg_groups[ModelArgs].model_type == "engine": - script.add_import(imports=["BytesFromPath"], frm="polygraphy.backend.common") + script.add_import( + imports=["BytesFromPath"], frm="polygraphy.backend.common" + ) return script.add_loader( - make_invocable("BytesFromPath", self.arg_groups[ModelArgs].path), "load_engine_bytes" + make_invocable("BytesFromPath", self.arg_groups[ModelArgs].path), + "load_engine_bytes", ) network_loader_name = network_name if network_loader_name is None: - network_loader_name = self.arg_groups[TrtLoadNetworkArgs].add_to_script(script) + network_loader_name = self.arg_groups[TrtLoadNetworkArgs].add_to_script( + script + ) - script.add_import(imports=["EngineBytesFromNetwork"], frm="polygraphy.backend.trt") + script.add_import( + imports=["EngineBytesFromNetwork"], frm="polygraphy.backend.trt" + ) config_loader_name = self.arg_groups[TrtConfigArgs].add_to_script(script) - script.add_import(imports=["EngineBytesFromNetwork"], frm="polygraphy.backend.trt") + script.add_import( + imports=["EngineBytesFromNetwork"], frm="polygraphy.backend.trt" + ) loader_str = make_invocable( "EngineBytesFromNetwork", - self.arg_groups[TrtLoadPluginsArgs].add_to_script(script, network_loader_name), + self.arg_groups[TrtLoadPluginsArgs].add_to_script( + script, network_loader_name + ), config=config_loader_name, save_timing_cache=self.save_timing_cache, ) loader_name = script.add_loader(loader_str, "build_engine") if self._allow_saving: - loader_name = self.arg_groups[TrtSaveEngineBytesArgs].add_to_script(script, loader_name) + loader_name = self.arg_groups[TrtSaveEngineBytesArgs].add_to_script( + script, loader_name + ) return loader_name def load_engine_bytes(self, network=None): @@ -645,26 +744,34 @@ def parse_impl(self, args): Path rom which to load a runtime that can be used to load a version compatible engine that excludes the lean runtime. """ - self.load_runtime = args_util.parse_path(args_util.get(args, "load_runtime"), "Runtime") + self.load_runtime = args_util.parse_path( + args_util.get(args, "load_runtime"), "Runtime" + ) def add_to_script_impl(self, script, network_name=None): """ Args: network_name (str): The name of a variable in the script pointing to a network loader. """ - load_serialized_engine = self.arg_groups[TrtLoadEngineBytesArgs].add_to_script(script, network_name) + load_serialized_engine = self.arg_groups[TrtLoadEngineBytesArgs].add_to_script( + script, network_name + ) script.add_import(imports=["EngineFromBytes"], frm="polygraphy.backend.trt") runtime_loader = None if self.load_runtime is not None: script.add_import(imports=["LoadRuntime"], frm="polygraphy.backend.trt") - runtime_loader = script.add_loader(make_invocable("LoadRuntime", self.load_runtime), "load_runtime") + runtime_loader = script.add_loader( + make_invocable("LoadRuntime", self.load_runtime), "load_runtime" + ) return script.add_loader( make_invocable( "EngineFromBytes", - self.arg_groups[TrtLoadPluginsArgs].add_to_script(script, load_serialized_engine), + self.arg_groups[TrtLoadPluginsArgs].add_to_script( + script, load_serialized_engine + ), runtime=runtime_loader, ), "deserialize_engine", diff --git a/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py b/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py index 79b3546b..29ef1129 100644 --- a/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py +++ b/tools/Polygraphy/polygraphy/tools/args/backend/trt/runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -61,7 +61,7 @@ def add_parser_args_impl(self): "0 to 100%%: The percentage of weights TRT will stream. 100%% will stream the maximum number of weights. " ">0B: The exact amount of streamable weights that reside on the GPU (unit suffixes are supported).", type=str, - default=None + default=None, ) def parse_impl(self, args): @@ -78,18 +78,31 @@ def parse_impl(self, args): self.allocation_strategy = args_util.get(args, "allocation_strategy") self.weight_streaming_budget = None self.weight_streaming_percent = None - + ws_arg = args_util.get(args, "weight_streaming_budget") if ws_arg and ws_arg.endswith("%"): percent = float(ws_arg[:-1]) - assert 0 <= percent <= 100, "Invalid percentage for --weight-streaming-budget!" + assert ( + 0 <= percent <= 100 + ), "Invalid percentage for --weight-streaming-budget!" self.weight_streaming_percent = percent elif ws_arg: budget = args_util.parse_num_bytes(ws_arg) - assert budget == -1 or budget >= 0, "Invalid amount for --weight-streaming-budget!" + assert ( + budget == -1 or budget >= 0 + ), "Invalid amount for --weight-streaming-budget!" self.weight_streaming_budget = budget def add_to_script_impl(self, script): script.add_import(imports=["TrtRunner"], frm="polygraphy.backend.trt") loader_name = self.arg_groups[TrtLoadEngineArgs].add_to_script(script) - script.add_runner(make_invocable("TrtRunner", loader_name, optimization_profile=self.optimization_profile, allocation_strategy=self.allocation_strategy, weight_streaming_budget=self.weight_streaming_budget, weight_streaming_percent=self.weight_streaming_percent)) + script.add_runner( + make_invocable( + "TrtRunner", + loader_name, + optimization_profile=self.optimization_profile, + allocation_strategy=self.allocation_strategy, + weight_streaming_budget=self.weight_streaming_budget, + weight_streaming_percent=self.weight_streaming_percent, + ) + ) diff --git a/tools/Polygraphy/polygraphy/tools/args/base.py b/tools/Polygraphy/polygraphy/tools/args/base.py index ca274f00..750ba256 100644 --- a/tools/Polygraphy/polygraphy/tools/args/base.py +++ b/tools/Polygraphy/polygraphy/tools/args/base.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -121,7 +121,9 @@ def add_parser_args(self, parser): num_prev_actions = len(parser._actions) - self.group = parser.add_argument_group(title.strip(), f"Options related to {desc.strip()}") + self.group = parser.add_argument_group( + title.strip(), f"Options related to {desc.strip()}" + ) self.add_parser_args_impl() num_added_actions = len(parser._actions) - num_prev_actions diff --git a/tools/Polygraphy/polygraphy/tools/args/comparator/comparator.py b/tools/Polygraphy/polygraphy/tools/args/comparator/comparator.py index d5528e70..7f05908e 100644 --- a/tools/Polygraphy/polygraphy/tools/args/comparator/comparator.py +++ b/tools/Polygraphy/polygraphy/tools/args/comparator/comparator.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -21,7 +21,10 @@ from polygraphy.tools.args import util as args_util from polygraphy.tools.args.backend.runner_select import RunnerSelectArgs from polygraphy.tools.args.base import BaseArgs -from polygraphy.tools.args.comparator.compare import CompareFuncIndicesArgs, CompareFuncSimpleArgs +from polygraphy.tools.args.comparator.compare import ( + CompareFuncIndicesArgs, + CompareFuncSimpleArgs, +) from polygraphy.tools.args.comparator.data_loader import DataLoaderArgs from polygraphy.tools.args.comparator.postprocess import ComparatorPostprocessArgs from polygraphy.tools.script import inline, make_invocable, safe @@ -62,7 +65,8 @@ def add_parser_args_impl(self): self.group.add_argument( "--save-outputs", "--save-results", - help="Path to save results from runners. " "The results (RunResults) will be encoded as JSON and saved", + help="Path to save results from runners. " + "The results (RunResults) will be encoded as JSON and saved", default=None, dest="save_outputs_path", ) @@ -95,13 +99,23 @@ def add_to_script_impl(self, script): use_subprocess=self.use_subprocess, save_inputs_path=self.save_inputs_path, ) - script.append_suffix(safe("\n# Runner Execution\n{results} = {:}", comparator_run, results=RESULTS_VAR_NAME)) + script.append_suffix( + safe( + "\n# Runner Execution\n{results} = {:}", + comparator_run, + results=RESULTS_VAR_NAME, + ) + ) if self.save_outputs_path: G_LOGGER.verbose(f"Will save runner results to: {self.save_outputs_path}") script.add_import(imports=["util"], frm="polygraphy") script.append_suffix( - safe("\n# Save results\n{results}.save({:})", self.save_outputs_path, results=RESULTS_VAR_NAME) + safe( + "\n# Save results\n{results}.save({:})", + self.save_outputs_path, + results=RESULTS_VAR_NAME, + ) ) return RESULTS_VAR_NAME @@ -136,9 +150,17 @@ def add_parser_args_impl(self): "indices": self.arg_groups[CompareFuncIndicesArgs], } - self.group.add_argument("--validate", help="Check outputs for NaNs and Infs", action="store_true", default=None) self.group.add_argument( - "--fail-fast", help="Fail fast (stop comparing after the first failure)", action="store_true", default=None + "--validate", + help="Check outputs for NaNs and Infs", + action="store_true", + default=None, + ) + self.group.add_argument( + "--fail-fast", + help="Fail fast (stop comparing after the first failure)", + action="store_true", + default=None, ) self.group.add_argument( @@ -199,8 +221,11 @@ def parse_impl(self, args): f"The selected comparison function is: '{self.compare_func}', so this option will be ignored." ) - self.compare_func_script, self.compare_func_name = args_util.parse_script_and_func_name( - args_util.get(args, "compare_func_script"), default_func_name="compare_outputs" + self.compare_func_script, self.compare_func_name = ( + args_util.parse_script_and_func_name( + args_util.get(args, "compare_func_script"), + default_func_name="compare_outputs", + ) ) def add_to_script_impl(self, script, results_name): @@ -226,25 +251,47 @@ def add_to_script_impl(self, script, results_name): ) if self._allow_postprocessing: - results_name = self.arg_groups[ComparatorPostprocessArgs].add_to_script(script, results_name) + results_name = self.arg_groups[ComparatorPostprocessArgs].add_to_script( + script, results_name + ) SUCCESS_VAR_NAME = inline(safe("success")) script.append_suffix(safe("\n{success} = True", success=SUCCESS_VAR_NAME)) - if len(self.arg_groups[RunnerSelectArgs].runners) > 1 or self.load_outputs_paths: + if ( + len(self.arg_groups[RunnerSelectArgs].runners) > 1 + or self.load_outputs_paths + ): # Only do comparisons if there's actually something to compare. script.append_suffix(safe("# Accuracy Comparison")) if self.compare_func_script is not None: - script.add_import(imports=["InvokeFromScript"], frm="polygraphy.backend.common") - compare_func = make_invocable("InvokeFromScript", self.compare_func_script, name=self.compare_func_name) + script.add_import( + imports=["InvokeFromScript"], frm="polygraphy.backend.common" + ) + compare_func = make_invocable( + "InvokeFromScript", + self.compare_func_script, + name=self.compare_func_name, + ) else: - compare_func = self._comparison_func_map[self.compare_func].add_to_script(script) + compare_func = self._comparison_func_map[ + self.compare_func + ].add_to_script(script) compare_accuracy = make_invocable( - "Comparator.compare_accuracy", results_name, compare_func=compare_func, fail_fast=self.fail_fast + "Comparator.compare_accuracy", + results_name, + compare_func=compare_func, + fail_fast=self.fail_fast, + ) + script.append_suffix( + safe( + "{success} &= bool({:})\n", + compare_accuracy, + success=SUCCESS_VAR_NAME, + ) ) - script.append_suffix(safe("{success} &= bool({:})\n", compare_accuracy, success=SUCCESS_VAR_NAME)) if self.validate: script.append_suffix( safe( diff --git a/tools/Polygraphy/polygraphy/tools/args/comparator/compare.py b/tools/Polygraphy/polygraphy/tools/args/comparator/compare.py index 8816297b..07de2139 100644 --- a/tools/Polygraphy/polygraphy/tools/args/comparator/compare.py +++ b/tools/Polygraphy/polygraphy/tools/args/comparator/compare.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -18,7 +18,12 @@ from polygraphy.logger import G_LOGGER from polygraphy.tools.args import util as args_util from polygraphy.tools.args.base import BaseArgs -from polygraphy.tools.script import inline, make_invocable, make_invocable_if_nondefault, safe +from polygraphy.tools.script import ( + inline, + make_invocable, + make_invocable_if_nondefault, + safe, +) # # NOTE: The classes here are expected to use `None` as the default value for all arguments. @@ -140,13 +145,17 @@ def parse_impl(self, args): self.no_shape_check = args_util.get(args, "no_shape_check") self.rtol = args_util.parse_arglist_to_dict(args_util.get(args, "rtol")) self.atol = args_util.parse_arglist_to_dict(args_util.get(args, "atol")) - self.check_error_stat = args_util.parse_arglist_to_dict(args_util.get(args, "check_error_stat")) + self.check_error_stat = args_util.parse_arglist_to_dict( + args_util.get(args, "check_error_stat") + ) self.infinities_compare_equal = args_util.get(args, "infinities_compare_equal") self.save_heatmaps = args_util.get(args, "save_heatmaps") self.show_heatmaps = args_util.get(args, "show_heatmaps") self.save_error_metrics_plot = args_util.get(args, "save_error_metrics_plot") self.show_error_metrics_plot = args_util.get(args, "show_error_metrics_plot") - self.error_quantile = args_util.parse_arglist_to_dict(args_util.get(args, "error_quantile")) + self.error_quantile = args_util.parse_arglist_to_dict( + args_util.get(args, "error_quantile") + ) # Without this early check, failure would only happen after inference, which is clearly not desirable. if self.check_error_stat: @@ -172,7 +181,7 @@ def add_to_script_impl(self, script): show_heatmaps=self.show_heatmaps, save_error_metrics_plot=self.save_error_metrics_plot, show_error_metrics_plot=self.show_error_metrics_plot, - error_quantile=self.error_quantile + error_quantile=self.error_quantile, ) compare_func = None if compare_func_str: @@ -211,7 +220,9 @@ def parse_impl(self, args): Attributes: index_tolerance (Dict[str, int]): Per-tensor index tolerance. """ - self.index_tolerance = args_util.parse_arglist_to_dict(args_util.get(args, "index_tolerance")) + self.index_tolerance = args_util.parse_arglist_to_dict( + args_util.get(args, "index_tolerance") + ) def add_to_script_impl(self, script): from polygraphy.tools.args.comparator.comparator import ComparatorCompareArgs diff --git a/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py b/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py index 635860cb..63ea3558 100644 --- a/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py +++ b/tools/Polygraphy/polygraphy/tools/args/comparator/data_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -22,7 +22,13 @@ from polygraphy.tools.args import util as args_util from polygraphy.tools.args.base import BaseArgs from polygraphy.tools.args.model import ModelArgs -from polygraphy.tools.script import Script, inline, make_invocable, make_invocable_if_nondefault, safe +from polygraphy.tools.script import ( + Script, + inline, + make_invocable, + make_invocable_if_nondefault, + safe, +) @mod.export() @@ -46,7 +52,13 @@ def __init__(self, allow_custom_input_shapes: bool = None): self._allow_custom_input_shapes = util.default(allow_custom_input_shapes, True) def add_parser_args_impl(self): - self.group.add_argument("--seed", metavar="SEED", help="Seed to use for random inputs", type=int, default=None) + self.group.add_argument( + "--seed", + metavar="SEED", + help="Seed to use for random inputs", + type=int, + default=None, + ) self.group.add_argument( "--val-range", help="Range of values to generate in the data loader. " @@ -147,8 +159,12 @@ def omit_none_tuple(tup): self.seed = args_util.get(args, "seed") - self._int_range = omit_none_tuple(tup=(args_util.get(args, "int_min"), args_util.get(args, "int_max"))) - self._float_range = omit_none_tuple(tup=(args_util.get(args, "float_min"), args_util.get(args, "float_max"))) + self._int_range = omit_none_tuple( + tup=(args_util.get(args, "int_min"), args_util.get(args, "int_max")) + ) + self._float_range = omit_none_tuple( + tup=(args_util.get(args, "float_min"), args_util.get(args, "float_max")) + ) if self._int_range or self._float_range: mod.warn_deprecated( "--int-min/--int-max and --float-min/--float-max", @@ -178,18 +194,35 @@ def omit_none_tuple(tup): self.load_inputs_paths = args_util.get(args, "load_inputs_paths") - self.data_loader_backend_module = args_util.get(args, "data_loader_backend_module") + self.data_loader_backend_module = args_util.get( + args, "data_loader_backend_module" + ) - self.data_loader_script, self.data_loader_func_name = args_util.parse_script_and_func_name( - args_util.get(args, "data_loader_script"), default_func_name="load_data" + self.data_loader_script, self.data_loader_func_name = ( + args_util.parse_script_and_func_name( + args_util.get(args, "data_loader_script"), default_func_name="load_data" + ) ) func_name = args_util.get(args, "data_loader_func_name") if func_name is not None: - mod.warn_deprecated("--data-loader-func-name", "--data-loader-script", "0.50.0", always_show_warning=True) + mod.warn_deprecated( + "--data-loader-func-name", + "--data-loader-script", + "0.50.0", + always_show_warning=True, + ) self.data_loader_func_name = func_name if self.load_inputs_paths or self.data_loader_script: - for arg in ["seed", "int_min", "int_max", "float_min", "float_max", "val_range", "iterations"]: + for arg in [ + "seed", + "int_min", + "int_max", + "float_min", + "float_max", + "val_range", + "iterations", + ]: val = args_util.get(args, arg) if val is not None: G_LOGGER.warning( @@ -204,7 +237,9 @@ def _add_to_script_helper(self, script, user_input_metadata_str=None): if self.data_loader_script: script.add_import(imports=["mod"], frm="polygraphy") data_loader = make_invocable( - "mod.import_from_script", self.data_loader_script, name=self.data_loader_func_name + "mod.import_from_script", + self.data_loader_script, + name=self.data_loader_func_name, ) needs_invoke = True elif self.load_inputs_paths: @@ -258,7 +293,9 @@ def add_to_script_impl(self, script, user_input_metadata_str=None): str: The data loader, as a string. This may either be the variable name, or an invocation of the data loader function. """ - data_loader, needs_invoke = self._add_to_script_helper(script, user_input_metadata_str) + data_loader, needs_invoke = self._add_to_script_helper( + script, user_input_metadata_str + ) if needs_invoke: data_loader = make_invocable(data_loader) return data_loader @@ -281,7 +318,10 @@ def add_to_script_wrapper(script, *args, **kwargs): name, needs_invoke = self._add_to_script_helper(script, *args, **kwargs) return name - data_loader = util.default(args_util.run_script(add_to_script_wrapper, user_input_metadata), DataLoader()) + data_loader = util.default( + args_util.run_script(add_to_script_wrapper, user_input_metadata), + DataLoader(), + ) if needs_invoke: data_loader = data_loader() return data_loader diff --git a/tools/Polygraphy/polygraphy/tools/args/comparator/postprocess.py b/tools/Polygraphy/polygraphy/tools/args/comparator/postprocess.py index c12c2a91..3b29b4c2 100644 --- a/tools/Polygraphy/polygraphy/tools/args/comparator/postprocess.py +++ b/tools/Polygraphy/polygraphy/tools/args/comparator/postprocess.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -54,7 +54,9 @@ def parse_impl(self, args): {"top_k": {"output1": 5, "output2": 6}} """ - self.postprocess = args_util.parse_arglist_to_dict(args_util.get(args, "postprocess")) + self.postprocess = args_util.parse_arglist_to_dict( + args_util.get(args, "postprocess") + ) postprocess = {} topk_key = inline(safe("top_k")) @@ -62,7 +64,9 @@ def parse_impl(self, args): postprocess[topk_key] = {} for key, val in self.postprocess.items(): if not val.startswith("top-"): - G_LOGGER.critical(f"Invalid post-processing function: {val}. Note: Valid choices are: ['top-'].") + G_LOGGER.critical( + f"Invalid post-processing function: {val}. Note: Valid choices are: ['top-']." + ) k, _, axis = val.partition(",") k = int(k.lstrip("top-")) if axis: diff --git a/tools/Polygraphy/polygraphy/tools/args/logger/logger.py b/tools/Polygraphy/polygraphy/tools/args/logger/logger.py index ea8b412f..6f9a20b7 100644 --- a/tools/Polygraphy/polygraphy/tools/args/logger/logger.py +++ b/tools/Polygraphy/polygraphy/tools/args/logger/logger.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -64,7 +64,9 @@ def add_parser_args_impl(self): default=None, ) - self.group.add_argument("--silent", help="Disable all output", action="store_true", default=None) + self.group.add_argument( + "--silent", help="Disable all output", action="store_true", default=None + ) self.group.add_argument( "--log-format", help="Format for log messages: {{'timestamp': Include timestamp, 'line-info': Include file and line number, " @@ -123,7 +125,9 @@ def parse_impl(self, args): if verbosity is not None: self.verbosity = {} for path, sev in verbosity.items(): - self.verbosity[path] = inline(safe("G_LOGGER.{:}", inline_identifier(sev.upper()))) + self.verbosity[path] = inline( + safe("G_LOGGER.{:}", inline_identifier(sev.upper())) + ) # Enable logger settings immediately on parsing. self.get_logger() @@ -138,7 +142,9 @@ def add_to_script_impl(self, script): logger_settings.append("G_LOGGER.module_severity = G_LOGGER.CRITICAL") elif self.verbosity is not None: # Need to escape braces of the dictionary so it's not treated as a format-string by `safe()`. - logger_settings.append(f"G_LOGGER.module_severity = {'{' + repr(self.verbosity) + '}'}") + logger_settings.append( + f"G_LOGGER.module_severity = {'{' + repr(self.verbosity) + '}'}" + ) for fmt in self.log_format: if fmt == "no-colors": diff --git a/tools/Polygraphy/polygraphy/tools/args/model.py b/tools/Polygraphy/polygraphy/tools/args/model.py index da2d2be8..99e7bcc6 100644 --- a/tools/Polygraphy/polygraphy/tools/args/model.py +++ b/tools/Polygraphy/polygraphy/tools/args/model.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -106,10 +106,16 @@ def __init__( "Model input(s) and their shape(s). " "Used to determine shapes to use while generating input data for inference", ) - self._guess_model_type_from_runners = util.default(guess_model_type_from_runners, False) + self._guess_model_type_from_runners = util.default( + guess_model_type_from_runners, False + ) def add_parser_args_impl(self): - self.group.add_argument("model_file", help="Path to the model", nargs=None if self._model_opt_required else "?") + self.group.add_argument( + "model_file", + help="Path to the model", + nargs=None if self._model_opt_required else "?", + ) if self._required_model_type is None: self.group.add_argument( @@ -189,16 +195,26 @@ def use_ext(ext_mapping): self.input_shapes = TensorMetadata() if args_util.get(args, "input_shapes"): - self.input_shapes = args_util.parse_meta(args_util.get(args, "input_shapes"), includes_dtype=False) + self.input_shapes = args_util.parse_meta( + args_util.get(args, "input_shapes"), includes_dtype=False + ) self.path = None self.extra_model_info = None - self.path, self.extra_model_info = args_util.parse_script_and_func_name(args_util.get(args, "model_file")) + self.path, self.extra_model_info = args_util.parse_script_and_func_name( + args_util.get(args, "model_file") + ) self.path = args_util.parse_path(self.path, "Model") - model_type_str = self._required_model_type if self._required_model_type else determine_model_type(self.path) - self.model_type = ModelArgs.ModelType(model_type_str) if model_type_str else None + model_type_str = ( + self._required_model_type + if self._required_model_type + else determine_model_type(self.path) + ) + self.model_type = ( + ModelArgs.ModelType(model_type_str) if model_type_str else None + ) # Set up extra_model_info defaults for each model type if self.model_type == "trt-network-script": diff --git a/tools/Polygraphy/polygraphy/tools/args/util/util.py b/tools/Polygraphy/polygraphy/tools/args/util/util.py index 97fda30c..c0bc81ec 100644 --- a/tools/Polygraphy/polygraphy/tools/args/util/util.py +++ b/tools/Polygraphy/polygraphy/tools/args/util/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -149,7 +149,12 @@ def datatype_from_str(dt_str): @mod.export() def parse_arglist_to_tuple_list( - arg_lst, cast_to=None, sep=None, allow_empty_key=None, treat_missing_sep_as_val=None, treat_unspecified_as_none=None + arg_lst, + cast_to=None, + sep=None, + allow_empty_key=None, + treat_missing_sep_as_val=None, + treat_unspecified_as_none=None, ): """ Generate a list of (key, value) pairs from a list of arguments of the form: @@ -226,7 +231,12 @@ def parse_arglist_to_tuple_list( @mod.export() def parse_arg_to_tuple( - arg, cast_to=None, sep=None, allow_empty_key=None, treat_missing_sep_as_val=None, treat_unspecified_as_none=None + arg, + cast_to=None, + sep=None, + allow_empty_key=None, + treat_missing_sep_as_val=None, + treat_unspecified_as_none=None, ): """ Similar to `parse_arglist_to_tuple_list` but operates on a single argument and returns a single tuple @@ -244,7 +254,12 @@ def parse_arg_to_tuple( return None tuple_list = parse_arglist_to_tuple_list( - [arg], cast_to, sep, allow_empty_key, treat_missing_sep_as_val, treat_unspecified_as_none + [arg], + cast_to, + sep, + allow_empty_key, + treat_missing_sep_as_val, + treat_unspecified_as_none, ) if tuple_list is None: return None @@ -259,7 +274,12 @@ def parse_arg_to_tuple( @mod.export() def parse_arglist_to_dict( - arg_lst, cast_to=None, sep=None, allow_empty_key=None, treat_missing_sep_as_val=None, treat_unspecified_as_none=None + arg_lst, + cast_to=None, + sep=None, + allow_empty_key=None, + treat_missing_sep_as_val=None, + treat_unspecified_as_none=None, ): """ Similar to `parse_arglist_to_tuple_list` but returns a dictionary instead of a list of tuples. @@ -270,7 +290,12 @@ def parse_arglist_to_dict( was not specified). """ tuple_list = parse_arglist_to_tuple_list( - arg_lst, cast_to, sep, allow_empty_key, treat_missing_sep_as_val, treat_unspecified_as_none + arg_lst, + cast_to, + sep, + allow_empty_key, + treat_missing_sep_as_val, + treat_unspecified_as_none, ) if tuple_list is None: return None @@ -284,7 +309,9 @@ def parse_script_and_func_name(arg, default_func_name=None): # On Windows we need to split the drive letter (e.g. 'C:') so it's not confused with the script/function separator. drive_letter, arg = os.path.splitdrive(arg) - script_and_func_name = parse_arg_to_tuple(arg, treat_missing_sep_as_val=False, treat_unspecified_as_none=True) + script_and_func_name = parse_arg_to_tuple( + arg, treat_missing_sep_as_val=False, treat_unspecified_as_none=True + ) if script_and_func_name is not None: script, func_name = script_and_func_name func_name = util.default(func_name, default_func_name) diff --git a/tools/Polygraphy/polygraphy/tools/base/tool.py b/tools/Polygraphy/polygraphy/tools/base/tool.py index 70e100c1..893409f0 100644 --- a/tools/Polygraphy/polygraphy/tools/base/tool.py +++ b/tools/Polygraphy/polygraphy/tools/base/tool.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -33,7 +33,9 @@ class Tool: def __init__(self, name=None): self.name = name - self.arg_groups = ArgGroups() # Populated by setup_parser based on get_subscriptions() + self.arg_groups = ( + ArgGroups() + ) # Populated by setup_parser based on get_subscriptions() def setup_parser(self, subparsers=None): """ @@ -57,7 +59,9 @@ def setup_parser(self, subparsers=None): m_type = type(arg_group) self.arg_groups[m_type] = arg_group - allow_abbrev = all(arg_group.allows_abbreviation() for arg_group in self.arg_groups.values()) + allow_abbrev = all( + arg_group.allows_abbreviation() for arg_group in self.arg_groups.values() + ) description = dedent(self.__doc__) if subparsers is not None: @@ -78,7 +82,9 @@ def setup_parser(self, subparsers=None): ) parser.set_defaults(subcommand=self) else: - parser = argparse.ArgumentParser(add_help=True, description=description, allow_abbrev=allow_abbrev) + parser = argparse.ArgumentParser( + add_help=True, description=description, allow_abbrev=allow_abbrev + ) for arg_group in self.arg_groups.values(): arg_group.register(self.arg_groups) @@ -89,7 +95,9 @@ def setup_parser(self, subparsers=None): try: self.add_parser_args(parser) except Exception as err: - G_LOGGER.internal_error(f"Could not register tool argument parser for: {self.name}\nNote: Error was: {err}") + G_LOGGER.internal_error( + f"Could not register tool argument parser for: {self.name}\nNote: Error was: {err}" + ) return parser # Implementation for `get_subscriptions`. This should be implemented by child classes instead of `get_subscriptions` diff --git a/tools/Polygraphy/polygraphy/tools/check/check.py b/tools/Polygraphy/polygraphy/tools/check/check.py index 7d7d8dfd..8667f341 100644 --- a/tools/Polygraphy/polygraphy/tools/check/check.py +++ b/tools/Polygraphy/polygraphy/tools/check/check.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py b/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py index 0b45c500..300cd5de 100644 --- a/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py +++ b/tools/Polygraphy/polygraphy/tools/check/subtool/lint.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -33,7 +33,12 @@ from polygraphy.json import save_json from polygraphy.logger import G_LOGGER from polygraphy.tools import util as tools_util -from polygraphy.tools.args import DataLoaderArgs, ModelArgs, OnnxLoadArgs, OnnxrtSessionArgs +from polygraphy.tools.args import ( + DataLoaderArgs, + ModelArgs, + OnnxLoadArgs, + OnnxrtSessionArgs, +) from polygraphy.tools.base import Tool onnx = mod.lazy_import("onnx") @@ -83,7 +88,10 @@ class Lint(Tool): 6. Large models (>2GB) require external data to be in same directory as the model file, custom paths to external data are not supported. """ - CUSTOM_OP_EXCEPTION_SUBSTRS = ["No opset import for domain", "is not a registered function/op"] + CUSTOM_OP_EXCEPTION_SUBSTRS = [ + "No opset import for domain", + "is not a registered function/op", + ] ONNX_CHECKER_IGNORE_SUBSTR = "Bad node spec for node" INVALID_ONNX_EXCEPTION_SUBSTR = "Error parsing message with type 'onnx.ModelProto'" MAXIMUM_PROTOBUF = 2e9 # 2GB @@ -158,9 +166,13 @@ def make_singleton_graph(self) -> Optional["gs.Graph"]: """ node = self.cur_node - inp_names = {inp.name for inp in node.inputs if isinstance(inp, gs.Variable)} + inp_names = { + inp.name for inp in node.inputs if isinstance(inp, gs.Variable) + } - if not all([inp in self.cache for inp in inp_names]): # Need all inputs to be available in the cache + if not all( + [inp in self.cache for inp in inp_names] + ): # Need all inputs to be available in the cache return None singleton = self.graph.copy() @@ -198,7 +210,9 @@ def update(self, output_dict: Optional[dict]): # means that something went wrong with its ancestor nodes. continue self.num_consumers[inp.name] -= 1 - if self.num_consumers[inp.name] == 0: # All consuming nodes of this tensor have been visited + if ( + self.num_consumers[inp.name] == 0 + ): # All consuming nodes of this tensor have been visited G_LOGGER.super_verbose(f"removing tensor: `{inp.name}` from cache") del self.cache[inp.name] # Can delete the tensor from the cache @@ -214,9 +228,13 @@ def update(self, output_dict: Optional[dict]): elif isinstance( out, gs.Constant ): # This theoretically should never happen, as constants are not outputs of nodes - G_LOGGER.critical(f"tensor: `{out.name}` is a constant, but is part of the output!") + G_LOGGER.critical( + f"tensor: `{out.name}` is a constant, but is part of the output!" + ) else: - G_LOGGER.critical(f"tensor: `{out.name}` is neither a variable nor a constant") + G_LOGGER.critical( + f"tensor: `{out.name}` is neither a variable nor a constant" + ) def set_graph_inputs(self, feed_dict: dict): """ @@ -237,7 +255,9 @@ def feed_dict(self) -> dict: f"tensor: {inp.name} missing in input cache! are you sure current node {self.cur_node.name} is valid?" ) # This should never happen elif isinstance(inp, gs.Constant): - G_LOGGER.super_verbose(f"tensor: `{inp.name}` is a constant, not tracked in cache. ") + G_LOGGER.super_verbose( + f"tensor: `{inp.name}` is a constant, not tracked in cache. " + ) continue _feed_dict[inp.name] = self.cache[inp.name] @@ -339,7 +359,9 @@ def add( scope = "" if node_name and op: scope = f"Name: {node_name}, Op: {op} | " - G_LOGGER.log(f"LINT | {scope}{message}", severity=severity_from_level[level]) + G_LOGGER.log( + f"LINT | {scope}{message}", severity=severity_from_level[level] + ) lint_entry = { "level": level.value, "source": source.value, @@ -359,7 +381,9 @@ def add( self.lint_entries.append(lint_entry) - self.is_model_valid = (level != Lint.Level.EXCEPTION) and self.is_model_valid + self.is_model_valid = ( + level != Lint.Level.EXCEPTION + ) and self.is_model_valid elif node_name not in self.summary["failing"]: self.summary["passing"].update([node_name]) @@ -401,7 +425,11 @@ def _prune_ONNXRuntimeError_formatting(message): # The ORT message format is not as expected, so just return the message pruning the prefix return message.split("[ONNXRuntimeError] : ")[1] message = "".join(parts[3:]).replace('"', "`") - for substr in ORT_SUBSTRS_TO_PRUNE: # remove substrings that are not useful in the error message + for ( + substr + ) in ( + ORT_SUBSTRS_TO_PRUNE + ): # remove substrings that are not useful in the error message message = message.replace(substr, "") return message @@ -411,6 +439,9 @@ def _prune_ONNXRuntimeError_formatting(message): "SystemError: " + x.split(" : ")[1] ), # If starts with "SystemError", it is likely due to improper installation of ONNX Runtime. r"\[ONNXRuntimeError\] : .*": _prune_ONNXRuntimeError_formatting, # [ONNXRuntimeError] : {code} : {StatusCodeToString(code)} : {msg} + r"\x1b(?:\[(?:\d+;){0,2}\d+m)(.*)\x1b\[m": lambda msg: re.sub( + r"\x1b(?:\[(?:\d+;){0,2}\d+m)(.*)\x1b\[m", "\\1", msg + ), # Remove log coloration characters from https://github.com/microsoft/onnxruntime/blob/b33216be4c02adfbbdeac2fd30ddc55f673eda3d/onnxruntime/core/common/logging/sinks/ostream_sink.cc#L24 r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ \[.*?\]\ ": lambda msg: re.sub( r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ \[.*?\]\ ", "", msg ), # (e.g: https://github.com/microsoft/onnxruntime/blob/24566058b3e5bb9e511513977cee6e7c553fd5c2/onnxruntime/core/graph/graph.cc#L3545-L3546) @@ -518,7 +549,9 @@ def _generate_unique_name(node_id): while name in names: # guarantee unique name name = f"polygraphy_unnamed_node_{node_id}_{uid}" uid += 1 - G_LOGGER.verbose(f"Node with topological id: {node_id} has empty name. Renaming to: {name}") + G_LOGGER.verbose( + f"Node with topological id: {node_id} has empty name. Renaming to: {name}" + ) return name for node in graph.nodes: @@ -648,21 +681,27 @@ def wrapper(*args, **kwargs): captured_stderr = io.BytesIO() captured_exception = None result = None - with contextlib.redirect_stdout(captured_stdout), stderr_redirector(captured_stderr): + with contextlib.redirect_stdout(captured_stdout), stderr_redirector( + captured_stderr + ): try: # Execute the function result = func(*args, **kwargs) except Exception as err: # pylint: disable=broad-except captured_exception = err UTF_TYPE = "utf-16-le" if os.name == "nt" else "utf-8" - stderr_msg = captured_stderr.getvalue().decode(UTF_TYPE) # platform-dependent + stderr_msg = captured_stderr.getvalue().decode( + UTF_TYPE + ) # platform-dependent stdout_msg = captured_stdout.getvalue() return (result, captured_exception, stderr_msg, stdout_msg) return wrapper @capture - def _ort_inference_check(model_bytes: Union[bytes, str], feed_dict: OrderedDict) -> Optional[OrderedDict]: + def _ort_inference_check( + model_bytes: Union[bytes, str], feed_dict: OrderedDict + ) -> Optional[OrderedDict]: """ Runs inference using ONNX-Runtime. @@ -704,7 +743,10 @@ def _unused_info_helper(graph: "gs.Graph"): cleaned_input_tensor_names = {inp.name for inp in graph.inputs} cleaned_node_info = {(node.name, node.op) for node in graph.nodes} - return (orig_node_info - cleaned_node_info, orig_input_tensor_names - cleaned_input_tensor_names) + return ( + orig_node_info - cleaned_node_info, + orig_input_tensor_names - cleaned_input_tensor_names, + ) def _report_unused_info(graph: "gs.Graph"): """ @@ -721,12 +763,18 @@ def _report_unused_info(graph: "gs.Graph"): - All nodes in the graph are expected to have non-empty names. """ - (unused_node_info, unused_input_tensor_names), exception, _, _ = _unused_info_helper(graph) + (unused_node_info, unused_input_tensor_names), exception, _, _ = ( + _unused_info_helper(graph) + ) if exception: # something went wrong here. - G_LOGGER.internal_error(f"Failed to report unused nodes. Error: {exception}") - G_LOGGER.warning(f"Failed to report unused nodes. Error: {exception}. Continuing...") + G_LOGGER.internal_error( + f"Failed to report unused nodes. Error: {exception}" + ) + G_LOGGER.warning( + f"Failed to report unused nodes. Error: {exception}. Continuing..." + ) # report unused tensors that are also inputs (intermediate tensors are not reported) for inp_name in sorted(list(unused_input_tensor_names)): @@ -804,7 +852,7 @@ def _report_unused_info(graph: "gs.Graph"): # NOTE: This is only done if early-exiting, as otherwise these warnings tend to be repeats # of node level warnings/exceptions. if warn_str: - warnings = warn_str.split('\n') + warnings = warn_str.split("\n") for warning in warnings: if len(warning) > 0: self.report.add( @@ -818,7 +866,9 @@ def _report_unused_info(graph: "gs.Graph"): self.report.summary["passing"] = {node.name for node in graph.nodes} self.report.export(args.output) - G_LOGGER.verbose("ORT inference check passed. Model is valid. Early exiting.") + G_LOGGER.verbose( + "ORT inference check passed. Model is valid. Early exiting." + ) return 0 if isinstance(exception, PolygraphyException): # PolygraphyException is raised when the provided input is not compatible with polygraphy @@ -830,18 +880,29 @@ def _report_unused_info(graph: "gs.Graph"): # start Node-level linting with Lint.ContextManager(graph) as lcm: - lcm.set_graph_inputs(feed_dict) # load the cache with initial feed_dict values for iterative inference. + lcm.set_graph_inputs( + feed_dict + ) # load the cache with initial feed_dict values for iterative inference. for _ in lcm.nodes(): g = lcm.make_singleton_graph() inference_output = None if g: # has valid ancestors. Can perform inference. - model_bytes = onnx_backend.BytesFromOnnx(gs.export_onnx(g, do_type_check=False)) - inference_output, exception, _, _ = _ort_inference_check(model_bytes, lcm.feed_dict()) + model_bytes = onnx_backend.BytesFromOnnx( + gs.export_onnx(g, do_type_check=False) + ) + inference_output, exception, _, _ = _ort_inference_check( + model_bytes, lcm.feed_dict() + ) # NOTE: we ignore stdout and stderr as it contains info from polygraphy not relevant to linting. err_str = str(exception) if exception else "" - if any([substr in err_str for substr in Lint.CUSTOM_OP_EXCEPTION_SUBSTRS]): + if any( + [ + substr in err_str + for substr in Lint.CUSTOM_OP_EXCEPTION_SUBSTRS + ] + ): self.report.add( level=Lint.Level.WARNING, source=Lint.Source.ONNXRUNTIME, diff --git a/tools/Polygraphy/polygraphy/tools/convert/convert.py b/tools/Polygraphy/polygraphy/tools/convert/convert.py index 4d3d3531..219fcc8a 100644 --- a/tools/Polygraphy/polygraphy/tools/convert/convert.py +++ b/tools/Polygraphy/polygraphy/tools/convert/convert.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -65,7 +65,9 @@ def get_subscriptions_impl(self): ] def add_parser_args_impl(self, parser): - parser.add_argument("-o", "--output", help="Path to save the converted model", required=True) + parser.add_argument( + "-o", "--output", help="Path to save the converted model", required=True + ) parser.add_argument( "--convert-to", help="The format to attempt to convert the model to." @@ -86,16 +88,24 @@ def run_impl(self, args): convert_type = "onnx-like-trt-network" else: CONVERT_TO_MODEL_TYPE_MAPPING = {"onnx": "onnx", "trt": "engine"} - convert_type = ModelArgs.ModelType(CONVERT_TO_MODEL_TYPE_MAPPING[args.convert_to]) + convert_type = ModelArgs.ModelType( + CONVERT_TO_MODEL_TYPE_MAPPING[args.convert_to] + ) if convert_type == "onnx-like-trt-network": - onnx_like = trt_backend.onnx_like_from_network(self.arg_groups[TrtLoadNetworkArgs].load_network()) + onnx_like = trt_backend.onnx_like_from_network( + self.arg_groups[TrtLoadNetworkArgs].load_network() + ) onnx_backend.save_onnx(onnx_like, args.output) elif convert_type.is_onnx(): model = self.arg_groups[OnnxLoadArgs].load_onnx() self.arg_groups[OnnxSaveArgs].save_onnx(model, args.output) elif convert_type.is_trt(): - with self.arg_groups[TrtLoadEngineBytesArgs].load_engine_bytes() as serialized_engine: - self.arg_groups[TrtSaveEngineBytesArgs].save_engine_bytes(serialized_engine, args.output) + with self.arg_groups[ + TrtLoadEngineBytesArgs + ].load_engine_bytes() as serialized_engine: + self.arg_groups[TrtSaveEngineBytesArgs].save_engine_bytes( + serialized_engine, args.output + ) else: G_LOGGER.critical(f"Cannot convert to model type: {convert_type}") diff --git a/tools/Polygraphy/polygraphy/tools/data/data.py b/tools/Polygraphy/polygraphy/tools/data/data.py index 52374de2..d26e9d18 100644 --- a/tools/Polygraphy/polygraphy/tools/data/data.py +++ b/tools/Polygraphy/polygraphy/tools/data/data.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/data/subtool/to_input.py b/tools/Polygraphy/polygraphy/tools/data/subtool/to_input.py index f19897c1..e2f92dde 100644 --- a/tools/Polygraphy/polygraphy/tools/data/subtool/to_input.py +++ b/tools/Polygraphy/polygraphy/tools/data/subtool/to_input.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -40,7 +40,9 @@ def add_parser_args(self, parser): "Otherwise, the outputs from one runner may be overwritten by those of a subsequent runner. ", nargs="+", ) - parser.add_argument("-o", "--output", help="Path to the file to generate", required=True) + parser.add_argument( + "-o", "--output", help="Path to the file to generate", required=True + ) def run_impl(self, args): inputs = [] @@ -73,4 +75,8 @@ def update_inputs(new_inputs, path): data = [data] update_inputs(data, path) - save_json(inputs, args.output, description=f"input file containing {len(inputs)} iteration(s)") + save_json( + inputs, + args.output, + description=f"input file containing {len(inputs)} iteration(s)", + ) diff --git a/tools/Polygraphy/polygraphy/tools/debug/debug.py b/tools/Polygraphy/polygraphy/tools/debug/debug.py index 16ddc187..313e0563 100644 --- a/tools/Polygraphy/polygraphy/tools/debug/debug.py +++ b/tools/Polygraphy/polygraphy/tools/debug/debug.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/debug/subtool/base.py b/tools/Polygraphy/polygraphy/tools/debug/subtool/base.py index b94d2363..5982360f 100644 --- a/tools/Polygraphy/polygraphy/tools/debug/subtool/base.py +++ b/tools/Polygraphy/polygraphy/tools/debug/subtool/base.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,7 +31,11 @@ TrtSaveEngineBytesArgs, ) from polygraphy.tools.base import Tool -from polygraphy.tools.debug.subtool.iterative_debug_args import ArtifactSortArgs, CheckCmdArgs, IterativeDebugArgs +from polygraphy.tools.debug.subtool.iterative_debug_args import ( + ArtifactSortArgs, + CheckCmdArgs, + IterativeDebugArgs, +) trt_backend = mod.lazy_import("polygraphy.backend.trt") trt = mod.lazy_import("tensorrt>=8.5") @@ -55,7 +59,9 @@ def __init__( def get_subscriptions_impl(self): return [ CheckCmdArgs(), - ArtifactSortArgs(allow_no_artifacts_warning=self._allow_no_artifacts_warning), + ArtifactSortArgs( + allow_no_artifacts_warning=self._allow_no_artifacts_warning + ), IterativeDebugArgs( iter_art_opt_default="polygraphy_debug.engine", allow_until_opt=self._allow_until_opt, @@ -65,7 +71,9 @@ def get_subscriptions_impl(self): OnnxInferShapesArgs(), OnnxLoadArgs(outputs_opt_prefix=False), DataLoaderArgs(), # For int8 calibration - TrtConfigArgs(precision_constraints_default=self._precision_constraints_default), + TrtConfigArgs( + precision_constraints_default=self._precision_constraints_default + ), TrtLoadPluginsArgs(), TrtOnnxFlagArgs(), TrtLoadNetworkArgs(), @@ -110,7 +118,9 @@ def remaining(self): pass def run_impl(self, args): - builder, network, _ = util.unpack_args(self.arg_groups[TrtLoadNetworkArgs].load_network(), 3) + builder, network, _ = util.unpack_args( + self.arg_groups[TrtLoadNetworkArgs].load_network(), 3 + ) self.setup(args, network) @@ -118,18 +128,23 @@ def make_iter_art(_): self.process_network(network) try: - serialized_engine = self.arg_groups[TrtLoadEngineBytesArgs].load_engine_bytes((builder, network)) + serialized_engine = self.arg_groups[ + TrtLoadEngineBytesArgs + ].load_engine_bytes((builder, network)) except Exception as err: G_LOGGER.warning( f"Failed to create network or engine, continuing to the next iteration.\nNote: Error was: {err}" ) - G_LOGGER.internal_error("Failed to create network or engine. See warning above for details.") + G_LOGGER.internal_error( + "Failed to create network or engine. See warning above for details." + ) self.arg_groups[IterativeDebugArgs].skip_iteration(success=False) else: # Don't need to keep the engine around in memory - just serialize to disk and free it. with serialized_engine: self.arg_groups[TrtSaveEngineBytesArgs].save_engine_bytes( - serialized_engine, self.arg_groups[IterativeDebugArgs].iter_artifact_path + serialized_engine, + self.arg_groups[IterativeDebugArgs].iter_artifact_path, ) def advance(context): diff --git a/tools/Polygraphy/polygraphy/tools/debug/subtool/build.py b/tools/Polygraphy/polygraphy/tools/debug/subtool/build.py index 3854b35a..579e1559 100644 --- a/tools/Polygraphy/polygraphy/tools/debug/subtool/build.py +++ b/tools/Polygraphy/polygraphy/tools/debug/subtool/build.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/debug/subtool/iterative_debug_args.py b/tools/Polygraphy/polygraphy/tools/debug/subtool/iterative_debug_args.py index 664c4837..773551f1 100644 --- a/tools/Polygraphy/polygraphy/tools/debug/subtool/iterative_debug_args.py +++ b/tools/Polygraphy/polygraphy/tools/debug/subtool/iterative_debug_args.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -68,7 +68,9 @@ def encode(iter_context): @Decoder.register(IterationContext) def decode(dct): - return IterationContext(state=dct["state"], iteration_info=dct["iteration_info"], success=dct["success"]) + return IterationContext( + state=dct["state"], iteration_info=dct["iteration_info"], success=dct["success"] + ) class CheckCmdArgs(BaseArgs): @@ -157,7 +159,9 @@ def parse_impl(self, args): self.check = args_util.get(args, "check") if self.check is None: - G_LOGGER.start("Starting interactive debugging session since no `--check` command was provided") + G_LOGGER.start( + "Starting interactive debugging session since no `--check` command was provided" + ) self.fail_codes = args_util.get(args, "fail_codes") self.ignore_fail_codes = args_util.get(args, "ignore_fail_codes") @@ -194,9 +198,7 @@ def prompt_user(msg): return None return res[0] - prompt = ( - f"Did '{iter_artifact_path if iter_artifact_path is not None else 'this iteration'}' [p]ass or [f]ail?" - ) + prompt = f"Did '{iter_artifact_path if iter_artifact_path is not None else 'this iteration'}' [p]ass or [f]ail?" response = prompt_user(prompt) while response not in ["p", "f"]: response = prompt_user("Please choose either: 'p' or 'f':") @@ -209,7 +211,9 @@ def is_status_success(status): has_fail_regex = None if self.fail_regexes is not None: output = status.stdout.decode() + status.stderr.decode() - has_fail_regex = any(regex.search(output) is not None for regex in self.fail_regexes) + has_fail_regex = any( + regex.search(output) is not None for regex in self.fail_regexes + ) if self.fail_codes is not None: # If a fail-code is specified, then we should also check has_fail_regex if provided. @@ -219,7 +223,9 @@ def is_status_success(status): else: # If a fail-code is not specified, we should trigger failures even on 0-status # if the fail regex is found. - failed = status.returncode != 0 if has_fail_regex is None else has_fail_regex + failed = ( + status.returncode != 0 if has_fail_regex is None else has_fail_regex + ) return not failed G_LOGGER.info(f"Running check command: {' '.join(self.check)}") @@ -228,7 +234,9 @@ def is_status_success(status): if self.show_output or (not success and not self.hide_fail_output): stderr_log_level = G_LOGGER.WARNING if success else G_LOGGER.ERROR - G_LOGGER.info(f"========== CAPTURED STDOUT ==========\n{status.stdout.decode()}") + G_LOGGER.info( + f"========== CAPTURED STDOUT ==========\n{status.stdout.decode()}" + ) G_LOGGER.log( f"========== CAPTURED STDERR ==========\n{status.stderr.decode()}", severity=stderr_log_level, @@ -249,7 +257,9 @@ def __init__(self, allow_no_artifacts_warning=None): Defaults to True. """ super().__init__() - self._allow_no_artifacts_warning = util.default(allow_no_artifacts_warning, True) + self._allow_no_artifacts_warning = util.default( + allow_no_artifacts_warning, True + ) def add_parser_args_impl(self): self.group.add_argument( @@ -350,7 +360,11 @@ class IterativeDebugArgs(BaseArgs): """ def __init__( - self, allow_iter_art_opt=None, iter_art_opt_default=None, allow_until_opt=None, allow_debug_replay=None + self, + allow_iter_art_opt=None, + iter_art_opt_default=None, + allow_until_opt=None, + allow_debug_replay=None, ): """ Args: @@ -372,7 +386,9 @@ def __init__( self._iter_art_opt_default = iter_art_opt_default if allow_iter_art_opt and not iter_art_opt_default: - G_LOGGER.internal_error("Must provide iter_art_opt_default if intermediate artifact is enabled") + G_LOGGER.internal_error( + "Must provide iter_art_opt_default if intermediate artifact is enabled" + ) self._allow_until_opt = util.default(allow_until_opt, False) self._allow_debug_replay = util.default(allow_debug_replay, True) @@ -481,7 +497,9 @@ def parse_impl(self, args): except: until = until if until not in ["good", "bad"]: - G_LOGGER.critical(f"--until value must be an integer, 'good', or 'bad', but was: {until}") + G_LOGGER.critical( + f"--until value must be an integer, 'good', or 'bad', but was: {until}" + ) self.until = until self.load_debug_replay = args_util.get(args, "load_debug_replay") @@ -603,7 +621,9 @@ def iterate( def handle_until(context: IterationContext): index, success = context.iteration_info["iteration"], context.success if isinstance(self.until, str): - if (self.until == "good" and success) or (self.until == "bad" and not success): + if (self.until == "good" and success) or ( + self.until == "bad" and not success + ): self.arg_groups[IterativeDebugArgs].stop_iteration() elif index >= self.until: self.arg_groups[IterativeDebugArgs].stop_iteration() @@ -638,10 +658,14 @@ def func(): for index in range(MAX_COUNT): - context = IterationContext(state={}, iteration_info={"iteration": index}, success=True) + context = IterationContext( + state={}, iteration_info={"iteration": index}, success=True + ) with contextlib.ExitStack() as stack, G_LOGGER.indent(): - remaining = get_remaining_func() if get_remaining_func is not None else None + remaining = ( + get_remaining_func() if get_remaining_func is not None else None + ) G_LOGGER.start( f"RUNNING | Iteration {index + 1}{f' | Approximately {remaining} iteration(s) remaining' if remaining is not None else ''}" @@ -653,9 +677,13 @@ def log_status(iter_success, start_time): duration_in_sec = time.time() - start_time if iter_success: num_passed += 1 - G_LOGGER.finish(f"PASSED | Iteration {index + 1} | Duration {duration_in_sec}s") + G_LOGGER.finish( + f"PASSED | Iteration {index + 1} | Duration {duration_in_sec}s" + ) else: - G_LOGGER.error(f"FAILED | Iteration {index + 1} | Duration {duration_in_sec}s") + G_LOGGER.error( + f"FAILED | Iteration {index + 1} | Duration {duration_in_sec}s" + ) # We must include the suffix in the debug replay key to disambiguate debug_replay_key = f"_N{index}" + (("_" + suffix) if suffix else "") @@ -663,7 +691,9 @@ def log_status(iter_success, start_time): start_time = time.time() if debug_replay_key in debug_replay: context = debug_replay[debug_replay_key] - G_LOGGER.info(f"Loading iteration information from debug replay: success={context.success}") + G_LOGGER.info( + f"Loading iteration information from debug replay: success={context.success}" + ) else: # Ensure that the intermediate artifact will be removed at the end of the iteration if requested. if self.iter_artifact_path and self.remove_intermediate: @@ -699,8 +729,12 @@ def save_replay(replay, description=None, suffix=None): save_replay(debug_replay, suffix="_skip_current") if do_check: - context.success = self.arg_groups[CheckCmdArgs].run_check(self.iter_artifact_path) - self.arg_groups[ArtifactSortArgs].sort_artifacts(context.success, suffix=debug_replay_key) + context.success = self.arg_groups[CheckCmdArgs].run_check( + self.iter_artifact_path + ) + self.arg_groups[ArtifactSortArgs].sort_artifacts( + context.success, suffix=debug_replay_key + ) debug_replay[debug_replay_key] = context save_replay(debug_replay, "debug replay") diff --git a/tools/Polygraphy/polygraphy/tools/debug/subtool/precision.py b/tools/Polygraphy/polygraphy/tools/debug/subtool/precision.py index 336ee9ec..2a8588eb 100644 --- a/tools/Polygraphy/polygraphy/tools/debug/subtool/precision.py +++ b/tools/Polygraphy/polygraphy/tools/debug/subtool/precision.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,14 +31,20 @@ def __init__(self, max_layers, direction, num_layers_to_mark): self.max_layers = max_layers self.direction = direction self.num_layers_to_mark = num_layers_to_mark - self.good = max_layers + 1 # Pretend marking all the layers gives us good accuracy. + self.good = ( + max_layers + 1 + ) # Pretend marking all the layers gives us good accuracy. def select_layers(self): if self.direction == "forward": - G_LOGGER.info(f"Selecting first {self.num_layers_to_mark} layer(s) to run in higher precision") + G_LOGGER.info( + f"Selecting first {self.num_layers_to_mark} layer(s) to run in higher precision" + ) return range(0, self.num_layers_to_mark) else: - G_LOGGER.info(f"Selecting last {self.num_layers_to_mark} layer(s) to run in higher precision") + G_LOGGER.info( + f"Selecting last {self.num_layers_to_mark} layer(s) to run in higher precision" + ) return range(self.max_layers - self.num_layers_to_mark, self.max_layers) def success_message(self): @@ -73,13 +79,17 @@ def step(self, success): # then we already have the information we need. if abs(self.good - self.bad) <= 1: if self.good >= self.max_layers: - G_LOGGER.error("Could not find a configuration that satisfied accuracy requirements.") + G_LOGGER.error( + "Could not find a configuration that satisfied accuracy requirements." + ) else: self.success_message() return True if self.num_layers_to_mark > self.max_layers: - G_LOGGER.error("Could not find a configuration that satisfied accuracy requirements.") + G_LOGGER.error( + "Could not find a configuration that satisfied accuracy requirements." + ) return True return False @@ -102,7 +112,9 @@ def step(self, success): return True if self.num_layers_to_mark > self.max_layers: - G_LOGGER.error("Could not find a configuration that satisfied accuracy requirements.") + G_LOGGER.error( + "Could not find a configuration that satisfied accuracy requirements." + ) return True return False @@ -122,7 +134,11 @@ class Precision(BaseCheckerSubtool): """ def __init__(self): - super().__init__("precision", precision_constraints_default="obey", allow_no_artifacts_warning=False) + super().__init__( + "precision", + precision_constraints_default="obey", + allow_no_artifacts_warning=False, + ) def add_parser_args(self, parser): parser.add_argument( @@ -151,7 +167,9 @@ def add_parser_args(self, parser): ) def setup(self, args, network): - self.precision = {"float32": trt.float32, "float16": trt.float16}[args.precision] + self.precision = {"float32": trt.float32, "float16": trt.float16}[ + args.precision + ] if self.precision == trt.float16 and not self.arg_groups[TrtConfigArgs].fp16: G_LOGGER.critical( @@ -172,7 +190,9 @@ def setup(self, args, network): self.arg_groups[TrtConfigArgs].int8, ] ): - G_LOGGER.critical("Please enable at least one precision besides float32 (e.g. --int8, --fp16, --tf32)") + G_LOGGER.critical( + "Please enable at least one precision besides float32 (e.g. --int8, --fp16, --tf32)" + ) if self.arg_groups[ModelArgs].model_type == "engine": G_LOGGER.critical( @@ -194,7 +214,11 @@ def setup(self, args, network): def mark_layers(self, network, indices): EXCLUDE_LAYER_NAMES = ["CONSTANT"] - EXCLUDE_LAYERS = [getattr(trt.LayerType, attr) for attr in EXCLUDE_LAYER_NAMES if hasattr(trt.LayerType, attr)] + EXCLUDE_LAYERS = [ + getattr(trt.LayerType, attr) + for attr in EXCLUDE_LAYER_NAMES + if hasattr(trt.LayerType, attr) + ] # First, reset, since changes from the previous call will persist. for index, layer in enumerate(network): @@ -209,20 +233,30 @@ def mark_layers(self, network, indices): def should_exclude(): has_non_execution_output = any( - not layer.get_output(i).is_execution_tensor for i in range(layer.num_outputs) + not layer.get_output(i).is_execution_tensor + for i in range(layer.num_outputs) ) has_non_activation_output = any( - layer.get_output(i).dtype not in [trt.float32, trt.float16, trt.int8] + layer.get_output(i).dtype + not in [trt.float32, trt.float16, trt.int8] for i in range(layer.num_outputs) ) - return layer.type in EXCLUDE_LAYERS or has_non_execution_output or has_non_activation_output + return ( + layer.type in EXCLUDE_LAYERS + or has_non_execution_output + or has_non_activation_output + ) if not should_exclude(): - G_LOGGER.extra_verbose(f"Running layer in higher precision: {trt_util.str_from_layer(layer, index)}") + G_LOGGER.extra_verbose( + f"Running layer in higher precision: {trt_util.str_from_layer(layer, index)}" + ) layer.precision = self.precision marked_indices.add(index) - G_LOGGER.verbose(f"Marking layer(s): {marked_indices} to run in {self.precision} precision") + G_LOGGER.verbose( + f"Marking layer(s): {marked_indices} to run in {self.precision} precision" + ) def process_network(self, network): indices = list(self.layer_marker.select_layers()) diff --git a/tools/Polygraphy/polygraphy/tools/debug/subtool/reduce.py b/tools/Polygraphy/polygraphy/tools/debug/subtool/reduce.py index 31184920..8126364f 100644 --- a/tools/Polygraphy/polygraphy/tools/debug/subtool/reduce.py +++ b/tools/Polygraphy/polygraphy/tools/debug/subtool/reduce.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,9 +23,19 @@ from polygraphy.datatype import DataType from polygraphy.logger import G_LOGGER, LogMode from polygraphy.tools import util as tools_util -from polygraphy.tools.args import DataLoaderArgs, ModelArgs, OnnxInferShapesArgs, OnnxLoadArgs, OnnxSaveArgs +from polygraphy.tools.args import ( + DataLoaderArgs, + ModelArgs, + OnnxInferShapesArgs, + OnnxLoadArgs, + OnnxSaveArgs, +) from polygraphy.tools.base import Tool -from polygraphy.tools.debug.subtool.iterative_debug_args import ArtifactSortArgs, CheckCmdArgs, IterativeDebugArgs +from polygraphy.tools.debug.subtool.iterative_debug_args import ( + ArtifactSortArgs, + CheckCmdArgs, + IterativeDebugArgs, +) gs = mod.lazy_import("onnx_graphsurgeon>=0.3.6") onnx_backend = mod.lazy_import("polygraphy.backend.onnx") @@ -68,7 +78,9 @@ def finish(self): # Find the index of the node that has the highest number of nodes less than _least_bad_nodes, but still is successful. # Failing that, use the smallest possible subgraph (which will always be > _least_bad_nodes) def split_good(cond): - return {num: idx for num, idx in self._good_node_indices.items() if cond(num)} + return { + num: idx for num, idx in self._good_node_indices.items() if cond(num) + } max_smaller_graph = split_good(lambda num: num < self._least_bad_nodes) min_larger_graph = split_good(lambda num: num >= self._least_bad_nodes) @@ -174,7 +186,11 @@ def get_subscriptions_impl(self): CheckCmdArgs(), ArtifactSortArgs(allow_no_artifacts_warning=False), IterativeDebugArgs(iter_art_opt_default="polygraphy_debug.onnx"), - ModelArgs(model_opt_required=True, input_shapes_opt_name="model-inputs", required_model_type="onnx"), + ModelArgs( + model_opt_required=True, + input_shapes_opt_name="model-inputs", + required_model_type="onnx", + ), OnnxSaveArgs(), OnnxInferShapesArgs(default=True, allow_force_fallback=True), OnnxLoadArgs(outputs_opt_prefix=False), @@ -235,7 +251,9 @@ def run_impl(self, args): user_input_metadata = self.arg_groups[ModelArgs].input_shapes if user_input_metadata: model = gs.export_onnx( - tools_util.override_input_shapes(onnx_backend.gs_from_onnx(model), user_input_metadata) + tools_util.override_input_shapes( + onnx_backend.gs_from_onnx(model), user_input_metadata + ) ) model = self.arg_groups[OnnxInferShapesArgs].infer_shapes(model) @@ -252,7 +270,10 @@ def run_impl(self, args): def load_tensors_from_fallback(names): nonlocal fallback_outputs, fallback_metadata - if all((name in fallback_metadata and name in fallback_outputs) for name in names): + if all( + (name in fallback_metadata and name in fallback_outputs) + for name in names + ): return G_LOGGER.info( @@ -260,12 +281,16 @@ def load_tensors_from_fallback(names): "This will cause intermediate models to have static shapes." ) with G_LOGGER.indent(): - new_outputs, new_meta = self.arg_groups[OnnxInferShapesArgs].fallback_inference(model, outputs=names) + new_outputs, new_meta = self.arg_groups[ + OnnxInferShapesArgs + ].fallback_inference(model, outputs=names) fallback_outputs.update(new_outputs) fallback_metadata.update(new_meta) if self.arg_groups[OnnxInferShapesArgs].force_fallback: - G_LOGGER.info("Freezing shapes in the model according to values determined by fallback shape inference") + G_LOGGER.info( + "Freezing shapes in the model according to values determined by fallback shape inference" + ) load_tensors_from_fallback(constants.MARK_ALL) onnx_util.set_shapes_from_layerwise_meta(GRAPH, fallback_metadata) @@ -275,7 +300,10 @@ def load_tensors_from_fallback(names): "You may want to provide input shapes to `debug reduce` using the " "`--model-input-shapes` option to prevent unexpected behavior.\n" ) - elif any(tensor.shape is None or util.is_shape_dynamic(tensor.shape) for tensor in GRAPH.tensors().values()): + elif any( + tensor.shape is None or util.is_shape_dynamic(tensor.shape) + for tensor in GRAPH.tensors().values() + ): msg = "" if self.arg_groups[OnnxInferShapesArgs].do_shape_inference: msg += "ONNX shape inference was unable to infer some shapes in this model.\n" @@ -309,7 +337,11 @@ def fix_graph(graph): """ def get_tensor_names_needing_fallback(tensors, fix_shape=True): - return [tensor.name for tensor in tensors if (not tensor.shape and fix_shape) or not tensor.dtype] + return [ + tensor.name + for tensor in tensors + if (not tensor.shape and fix_shape) or not tensor.dtype + ] load_tensors_from_fallback( get_tensor_names_needing_fallback(graph.inputs) @@ -320,9 +352,14 @@ def fix_tensor_metadata(tensors): for tensor in tensors: # If a tensor is not in `fallback_metadata`, it means it doesn't require metadata to be updated. if tensor.name in fallback_metadata: - tensor.shape = tensor.shape or fallback_metadata[tensor.name].shape + tensor.shape = ( + tensor.shape or fallback_metadata[tensor.name].shape + ) tensor.dtype = DataType.to_dtype( - DataType.from_dtype(tensor.dtype or fallback_metadata[tensor.name].dtype), "onnx" + DataType.from_dtype( + tensor.dtype or fallback_metadata[tensor.name].dtype + ), + "onnx", ) fix_tensor_metadata(graph.inputs) @@ -334,10 +371,17 @@ def fix_tensor_metadata(tensors): tensor_map = graph.tensors() tensors_to_freeze = [] # Names of tensors we need to freeze in the model. for name, tensor in tensor_map.items(): - if isinstance(tensor, gs.Variable) and not tensor.inputs and tensor not in graph.inputs: + if ( + isinstance(tensor, gs.Variable) + and not tensor.inputs + and tensor not in graph.inputs + ): tensors_to_freeze.append(name) - if tensors_to_freeze and self.arg_groups[DataLoaderArgs].is_using_random_data(): + if ( + tensors_to_freeze + and self.arg_groups[DataLoaderArgs].is_using_random_data() + ): G_LOGGER.warning( "This model includes multiple branches/paths. In order to continue reducing, one branch needs to be folded away.\n" "Please ensure that you have provided a data loader argument directly to `debug reduce` (i.e. prior to `--check`) " @@ -425,7 +469,9 @@ def bisect_io(graph, marker, attr, filter_const=True, debug_replay=None): G_LOGGER.start(f"Reducing model {attr}") def make_iter_art(context): - iter_graph = graph.copy() # This is a very light-weight copy of the entire graph. + iter_graph = ( + graph.copy() + ) # This is a very light-weight copy of the entire graph. with G_LOGGER.indent(): io_list = list(getattr(iter_graph.nodes[marker.node_index], attr)) @@ -459,7 +505,11 @@ def get_io(index): return names_from_tensors(getattr(graph, attr)) return names_from_tensors(list(getattr(graph.nodes[index], attr))) - return get_io(marker.best_bad_node_index), get_io(marker.best_good_node_index), debug_replay + return ( + get_io(marker.best_bad_node_index), + get_io(marker.best_good_node_index), + debug_replay, + ) # We reduce the model in 2 phases: # 1. Find the earliest output nodes that cause a failure. @@ -479,9 +529,15 @@ def get_io(index): if args.reduce_outputs: out_marker = MarkerType(len(bad_graph.nodes)) bad_outputs, good_outputs, debug_replay = bisect_io( - bad_graph, out_marker, attr="outputs", filter_const=False, debug_replay=debug_replay + bad_graph, + out_marker, + attr="outputs", + filter_const=False, + debug_replay=debug_replay, + ) + bad_graph = cleanup( + mark_io(bad_graph, "outputs", lookup_tensors(bad_graph, bad_outputs)) ) - bad_graph = cleanup(mark_io(bad_graph, "outputs", lookup_tensors(bad_graph, bad_outputs))) if good_graph is not None: good_graph = mark_io( good_graph, "outputs", lookup_tensors(good_graph, good_outputs) @@ -494,7 +550,9 @@ def get_io(index): bad_inputs, good_inputs, debug_replay = bisect_io( bad_graph, in_marker, attr="inputs", debug_replay=debug_replay ) - bad_graph = cleanup(mark_io(bad_graph, "inputs", lookup_tensors(bad_graph, bad_inputs))) + bad_graph = cleanup( + mark_io(bad_graph, "inputs", lookup_tensors(bad_graph, bad_inputs)) + ) if good_graph is not None: good_graph = mark_io( good_graph, "inputs", lookup_tensors(good_graph, good_inputs) @@ -516,7 +574,9 @@ def get_io(index): f"It looks like this model could potentially be reduced further.\nYou may want to reduce {self.arg_groups[OnnxSaveArgs].path} again using --mode=linear. " ) - G_LOGGER.info(f"Minimum Bad Model:\n{onnx_util.str_from_onnx(reduced_model)}\n\n") + G_LOGGER.info( + f"Minimum Bad Model:\n{onnx_util.str_from_onnx(reduced_model)}\n\n" + ) self.arg_groups[OnnxSaveArgs].save_onnx(reduced_model) # == Write Good Model == @@ -528,5 +588,7 @@ def get_io(index): "Could not find a minimal model close in size to the reduced model that does not cause a failure." ) else: - G_LOGGER.info(f"Minimum Good Model:\n{onnx_util.str_from_onnx(min_good_model)}\n\n") + G_LOGGER.info( + f"Minimum Good Model:\n{onnx_util.str_from_onnx(min_good_model)}\n\n" + ) self.arg_groups[OnnxSaveArgs].save_onnx(min_good_model, args.min_good) diff --git a/tools/Polygraphy/polygraphy/tools/debug/subtool/repeat.py b/tools/Polygraphy/polygraphy/tools/debug/subtool/repeat.py index 9336a823..db024e18 100644 --- a/tools/Polygraphy/polygraphy/tools/debug/subtool/repeat.py +++ b/tools/Polygraphy/polygraphy/tools/debug/subtool/repeat.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,7 +16,11 @@ # from polygraphy.tools.base import Tool -from polygraphy.tools.debug.subtool.iterative_debug_args import ArtifactSortArgs, CheckCmdArgs, IterativeDebugArgs +from polygraphy.tools.debug.subtool.iterative_debug_args import ( + ArtifactSortArgs, + CheckCmdArgs, + IterativeDebugArgs, +) class Repeat(Tool): @@ -29,7 +33,11 @@ def __init__(self): super().__init__("repeat") def get_subscriptions_impl(self): - return [CheckCmdArgs(), ArtifactSortArgs(), IterativeDebugArgs(allow_iter_art_opt=False, allow_until_opt=True)] + return [ + CheckCmdArgs(), + ArtifactSortArgs(), + IterativeDebugArgs(allow_iter_art_opt=False, allow_until_opt=True), + ] def show_start_end_logging_impl(self, args): return True diff --git a/tools/Polygraphy/polygraphy/tools/inspect/inspect.py b/tools/Polygraphy/polygraphy/tools/inspect/inspect.py index daa8fdc1..3a6d66be 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/inspect.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/inspect.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -15,7 +15,14 @@ # limitations under the License. # from polygraphy.tools.base import Tool -from polygraphy.tools.inspect.subtool import Data, Model, Tactics, Capability, DiffTactics, Sparsity +from polygraphy.tools.inspect.subtool import ( + Data, + Model, + Tactics, + Capability, + DiffTactics, + Sparsity, +) class Inspect(Tool): diff --git a/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py b/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py index 7f4aa9de..c41697ba 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/subtool/capability.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -20,7 +20,12 @@ from polygraphy import mod from polygraphy.common.interface import TypedDict from polygraphy.logger import G_LOGGER -from polygraphy.tools.args import OnnxInferShapesArgs, OnnxLoadArgs, ModelArgs, OnnxSaveArgs +from polygraphy.tools.args import ( + OnnxInferShapesArgs, + OnnxLoadArgs, + ModelArgs, + OnnxSaveArgs, +) from polygraphy.tools.base import Tool common_backend = mod.lazy_import("polygraphy.backend.common") @@ -85,7 +90,9 @@ def supports_model(path): except AttributeError: trt_util.fail_unavailable("supports_model in tensorrt.OnnxParser") - supported, nodelists = parser.supports_model(common_backend.bytes_from_path(path), path) + supported, nodelists = parser.supports_model( + common_backend.bytes_from_path(path), path + ) return supported, nodelists, parser @@ -133,16 +140,24 @@ def save_subgraph(onnx_save_args, graph, start, end, prefix="", use_tmp_file=Fal in_dict = {inp.name: inp for node in subgraph_nodes for inp in node.inputs} # Guess graph inputs/outputs by checking all output tensor names against all input tensor names, and vice-versa. - subgraph_inputs = onnx_util.meta_from_gs_tensors([in_dict[k] for k in in_dict if k not in out_dict]) - subgraph_outputs = onnx_util.meta_from_gs_tensors([out_dict[k] for k in out_dict if k not in in_dict]) + subgraph_inputs = onnx_util.meta_from_gs_tensors( + [in_dict[k] for k in in_dict if k not in out_dict] + ) + subgraph_outputs = onnx_util.meta_from_gs_tensors( + [out_dict[k] for k in out_dict if k not in in_dict] + ) - subgraph = gs.export_onnx(onnx_backend.extract_subgraph(graph, subgraph_inputs, subgraph_outputs)) + subgraph = gs.export_onnx( + onnx_backend.extract_subgraph(graph, subgraph_inputs, subgraph_outputs) + ) if use_tmp_file: path = util.NamedTemporaryFile(prefix=prefix, suffix=".onnx").name else: # end is exclusive, so subtract one to make the model names friendlier. - path = os.path.join(onnx_save_args.path, f"{prefix}_subgraph-nodes-{start}-{end - 1}.onnx") + path = os.path.join( + onnx_save_args.path, f"{prefix}_subgraph-nodes-{start}-{end - 1}.onnx" + ) onnx_save_args.save_onnx(subgraph, path) return path @@ -159,11 +174,17 @@ def gen_results_summary(final_unsupported): str: A summary of all the unsupported ops in model, along with reasons and node index ranges. """ op_width = max(map(len, list(final_unsupported.keys()) + ["Operator "])) - reason_width = max(len(reason) for node_index_map in final_unsupported.values() for reason in node_index_map.keys()) + reason_width = max( + len(reason) + for node_index_map in final_unsupported.values() + for reason in node_index_map.keys() + ) summary = "===== Summary =====\n" - header = f"{'Operator':{op_width}}| {'Count':7} | {'Reason':{reason_width}} | Nodes\n" + header = ( + f"{'Operator':{op_width}}| {'Count':7} | {'Reason':{reason_width}} | Nodes\n" + ) summary += header + "-" * len(header) + "\n" for op, node_index_map in final_unsupported.items(): @@ -183,10 +204,30 @@ def gen_results_summary_no_partitioning(stack_trace_to_errors): Returns: str: A summary of all the unsupported ops in model, along with reasons and stack traces. """ - stack_trace_width = max(map(len, list(stack_trace_to_errors.keys()) + ["Stack trace "])) - op_width = max(max(len(op) for errors_per_stack in stack_trace_to_errors.values() for op, _, _ in errors_per_stack), len("Operator ")) - node_width = max(max(len(node) for errors_per_stack in stack_trace_to_errors.values() for _, node, _ in errors_per_stack), len("Node ")) - reason_width = max(len(reason) for errors_per_stack in stack_trace_to_errors.values() for _, _, reason in errors_per_stack) + stack_trace_width = max( + map(len, list(stack_trace_to_errors.keys()) + ["Stack trace "]) + ) + op_width = max( + max( + len(op) + for errors_per_stack in stack_trace_to_errors.values() + for op, _, _ in errors_per_stack + ), + len("Operator "), + ) + node_width = max( + max( + len(node) + for errors_per_stack in stack_trace_to_errors.values() + for _, node, _ in errors_per_stack + ), + len("Node "), + ) + reason_width = max( + len(reason) + for errors_per_stack in stack_trace_to_errors.values() + for _, _, reason in errors_per_stack + ) summary = "===== Summary =====\n" @@ -210,12 +251,19 @@ def __init__(self): def get_subscriptions_impl(self): return [ - ModelArgs(model_opt_required=True, input_shapes_opt_name=False, required_model_type="onnx"), + ModelArgs( + model_opt_required=True, + input_shapes_opt_name=False, + required_model_type="onnx", + ), OnnxInferShapesArgs(), OnnxLoadArgs(outputs_opt_prefix=False), - OnnxSaveArgs(output_default_path="polygraphy_capability_dumps", allow_multiple_models=True), + OnnxSaveArgs( + output_default_path="polygraphy_capability_dumps", + allow_multiple_models=True, + ), ] - + def add_parser_args_impl(self, parser): parser.add_argument( "--with-partitioning", @@ -232,9 +280,11 @@ def run_impl(self, args): def no_partitioning_variant(self): supported, parser = parse(self.arg_groups[ModelArgs].path) if supported: - G_LOGGER.info("Graph is fully supported by TensorRT; Will not report errors.") + G_LOGGER.info( + "Graph is fully supported by TensorRT; Will not report errors." + ) return - + stack_trace_to_errors = OrderedDict() for err_idx in range(parser.num_errors): parser_error = parser.get_error(err_idx) @@ -244,29 +294,38 @@ def no_partitioning_variant(self): stack_trace += parser_error.local_function_stack()[function_idx] if function_idx != parser_error.local_function_stack_size() - 1: stack_trace += " -> " - + if stack_trace not in stack_trace_to_errors: stack_trace_to_errors[stack_trace] = [] node_operator = parser_error.node_operator() node_name = parser_error.node_name() parser_error_desc = str(parser_error) - stack_trace_to_errors[stack_trace].append(tuple((node_operator, node_name, parser_error_desc))) - + stack_trace_to_errors[stack_trace].append( + tuple((node_operator, node_name, parser_error_desc)) + ) + summary = gen_results_summary_no_partitioning(stack_trace_to_errors) G_LOGGER.info(summary) util.save_file( - summary, os.path.join(self.arg_groups[OnnxSaveArgs].path, "results.txt"), "w", description="results" + summary, + os.path.join(self.arg_groups[OnnxSaveArgs].path, "results.txt"), + "w", + description="results", ) def supports_model_variant(self): supported, nodelists, _ = supports_model(self.arg_groups[ModelArgs].path) if supported: - G_LOGGER.info("Graph is fully supported by TensorRT; Will not generate subgraphs.") + G_LOGGER.info( + "Graph is fully supported by TensorRT; Will not generate subgraphs." + ) return - parent_graph = onnx_backend.gs_from_onnx(self.arg_groups[OnnxLoadArgs].load_onnx()) + parent_graph = onnx_backend.gs_from_onnx( + self.arg_groups[OnnxLoadArgs].load_onnx() + ) def partition(nodelists, offset): """ @@ -284,7 +343,9 @@ def partition(nodelists, offset): supported_subgraphs = [] for node_indices, supported in nodelists: if supported: - supported_subgraphs.append([index + offset for index in node_indices]) + supported_subgraphs.append( + [index + offset for index in node_indices] + ) continue start = node_indices[0] + offset @@ -315,13 +376,23 @@ def save_unsupported_graph(start, end): start (int): The (inclusive) index of the start node. end (int): The (exclusive) index of the end node. """ - subgraph_path = save_subgraph(self.arg_groups[OnnxSaveArgs], parent_graph, start, end, "unsupported") + subgraph_path = save_subgraph( + self.arg_groups[OnnxSaveArgs], parent_graph, start, end, "unsupported" + ) _, _, parser = supports_model(subgraph_path) err_string = ( - " | ".join([str(parser.get_error(err_idx)) for err_idx in range(parser.num_errors)]) or "UNKNOWN ERROR" + " | ".join( + [ + str(parser.get_error(err_idx)) + for err_idx in range(parser.num_errors) + ] + ) + or "UNKNOWN ERROR" + ) + unsupported_node_dict.add( + parent_graph.nodes[start].op, err_string, [start, end] ) - unsupported_node_dict.add(parent_graph.nodes[start].op, err_string, [start, end]) # Log errors for all the unsupported graphs between supported subgraphs. for index, subg_node_idxs in enumerate(supported_subgraphs): @@ -336,7 +407,10 @@ def save_unsupported_graph(start, end): if index == 0 and subg_node_idxs[0] != 0: save_unsupported_graph(0, subg_node_idxs[0]) - if index == len(supported_subgraphs) - 1 and supported_subgraphs[-1][-1] != len(parent_graph.nodes) - 1: + if ( + index == len(supported_subgraphs) - 1 + and supported_subgraphs[-1][-1] != len(parent_graph.nodes) - 1 + ): save_unsupported_graph(subg_node_idxs[-1] + 1, len(parent_graph.nodes)) if index < len(supported_subgraphs) - 1: @@ -347,5 +421,8 @@ def save_unsupported_graph(start, end): G_LOGGER.info(summary) util.save_file( - summary, os.path.join(self.arg_groups[OnnxSaveArgs].path, "results.txt"), "w", description="results" + summary, + os.path.join(self.arg_groups[OnnxSaveArgs].path, "results.txt"), + "w", + description="results", ) diff --git a/tools/Polygraphy/polygraphy/tools/inspect/subtool/data.py b/tools/Polygraphy/polygraphy/tools/inspect/subtool/data.py index 651ed83c..af145769 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/subtool/data.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/subtool/data.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -37,7 +37,10 @@ def __init__(self): super().__init__("data") def add_parser_args(self, parser): - parser.add_argument("path", help="Path to a file containing input or output data from Polygraphy") + parser.add_argument( + "path", + help="Path to a file containing input or output data from Polygraphy", + ) parser.add_argument( "-a", "--all", @@ -45,9 +48,16 @@ def add_parser_args(self, parser): action="store_true", ) parser.add_argument( - "-s", "--show-values", help="Show values of the tensors instead of just metadata", action="store_true" + "-s", + "--show-values", + help="Show values of the tensors instead of just metadata", + action="store_true", + ) + parser.add_argument( + "--histogram", + help="Show a histogram of the value distribution", + action="store_true", ) - parser.add_argument("--histogram", help="Show a histogram of the value distribution", action="store_true") parser.add_argument( "-n", "--num-items", @@ -88,7 +98,9 @@ def str_from_iters(iters): iter_meta = meta_from_iter_result(iter_result) indent = 1 if len(iters) > 1 and args.all: - out_str += util.indent_block(f"\n-- Iteration: {index}\n", indent - 1) + out_str += util.indent_block( + f"\n-- Iteration: {index}\n", indent - 1 + ) indent = 2 for name, arr in iter_result.items(): diff --git a/tools/Polygraphy/polygraphy/tools/inspect/subtool/diff_tactics.py b/tools/Polygraphy/polygraphy/tools/inspect/subtool/diff_tactics.py index a4af2489..4f5fc1ef 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/subtool/diff_tactics.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/subtool/diff_tactics.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -54,7 +54,9 @@ def add_parser_args(self, parser): def run_impl(self, args): if args.dir is None and (args.good is None or args.bad is None): - G_LOGGER.critical("Either `--dir`, or both `--good` and `--bad` must be specified.") + G_LOGGER.critical( + "Either `--dir`, or both `--good` and `--bad` must be specified." + ) def load_tactics(dirpath): """ @@ -77,12 +79,16 @@ def try_load_replay(path): tactics = defaultdict(set) replay_paths = [] search_paths = ( - glob.iglob(os.path.join(dirpath, "**"), recursive=True) if os.path.isdir(dirpath) else [dirpath] + glob.iglob(os.path.join(dirpath, "**"), recursive=True) + if os.path.isdir(dirpath) + else [dirpath] ) for path in search_paths: replay = try_load_replay(path) if replay is None: - G_LOGGER.verbose(f"{path} does not look like a tactic replay file, skipping.") + G_LOGGER.verbose( + f"{path} does not look like a tactic replay file, skipping." + ) continue replay_paths.append(path) @@ -113,7 +119,9 @@ def try_load_replay(path): G_LOGGER.info("Found potentially bad tactics:") for name, algo_set in potential_bad_tactics.items(): algo_set_str = list(map(str, algo_set)) - G_LOGGER.info(f"Layer: {name}\n{constants.TAB}Algorithms: {algo_set_str}") + G_LOGGER.info( + f"Layer: {name}\n{constants.TAB}Algorithms: {algo_set_str}" + ) else: G_LOGGER.info( "Could not determine potentially bad tactics. Try providing more tactic replay files if possible." diff --git a/tools/Polygraphy/polygraphy/tools/inspect/subtool/model.py b/tools/Polygraphy/polygraphy/tools/inspect/subtool/model.py index d4489d9a..9fe01241 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/subtool/model.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/subtool/model.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -88,7 +88,7 @@ def add_parser_args_impl(self, parser): """, action="store_true", default=None, - dest="show_unbounded_dds" + dest="show_unbounded_dds", ) def run_impl(self, args): @@ -100,37 +100,52 @@ def inspect_trt(): with self.arg_groups[TrtLoadEngineArgs].load_engine() as engine: context = engine.create_execution_context() engine_str = trt_util.str_from_engine( - engine, context, show_layers=show("layers"), show_attrs=show("attrs") + engine, + context, + show_layers=show("layers"), + show_attrs=show("attrs"), ) G_LOGGER.info(f"==== TensorRT Engine ====\n{engine_str}") else: - builder, network, parser = util.unpack_args(self.arg_groups[TrtLoadNetworkArgs].load_network(), 3) + builder, network, parser = util.unpack_args( + self.arg_groups[TrtLoadNetworkArgs].load_network(), 3 + ) with contextlib.ExitStack() as stack: stack.enter_context(builder) stack.enter_context(network) if parser: stack.enter_context(parser) network_str = trt_util.str_from_network( - network, show_layers=show("layers"), show_attrs=show("attrs"), show_weights=show("weights") + network, + show_layers=show("layers"), + show_attrs=show("attrs"), + show_weights=show("weights"), ).strip() G_LOGGER.info(f"==== TensorRT Network ====\n{network_str}") def inspect_onnx(): onnx_model = self.arg_groups[OnnxLoadArgs].load_onnx() model_str = onnx_util.str_from_onnx( - onnx_model, show_layers=show("layers"), show_attrs=show("attrs"), show_weights=show("weights") + onnx_model, + show_layers=show("layers"), + show_attrs=show("attrs"), + show_weights=show("weights"), ).strip() G_LOGGER.info(f"==== ONNX Model ====\n{model_str}") if args.show_unbounded_dds: graph = onnx_backend.gs_from_onnx(onnx_model) unbounded_dds_tensors = onnx_util.get_unbounded_dds_tensors(graph) - G_LOGGER.info(f"Found tensors with unbounded DDS: {unbounded_dds_tensors}") - + G_LOGGER.info( + f"Found tensors with unbounded DDS: {unbounded_dds_tensors}" + ) def inspect_tf(): tf_graph, _ = self.arg_groups[TfLoadArgs].load_graph() graph_str = tf_util.str_from_graph( - tf_graph, show_layers=show("layers"), show_attrs=show("attrs"), show_weights=show("weights") + tf_graph, + show_layers=show("layers"), + show_attrs=show("attrs"), + show_weights=show("weights"), ).strip() G_LOGGER.info(f"==== TensorFlow Graph ====\n{graph_str}") @@ -142,5 +157,7 @@ def inspect_tf(): if self.arg_groups[ModelArgs].model_type.is_trt() or args.display_as == "trt": func = inspect_trt if func is None: - G_LOGGER.critical("Could not determine how to display this model. Maybe you need to specify --display-as?") + G_LOGGER.critical( + "Could not determine how to display this model. Maybe you need to specify --display-as?" + ) func() diff --git a/tools/Polygraphy/polygraphy/tools/inspect/subtool/sparsity.py b/tools/Polygraphy/polygraphy/tools/inspect/subtool/sparsity.py index 39197965..b1c99b0f 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/subtool/sparsity.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/subtool/sparsity.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -32,8 +32,16 @@ def show_start_end_logging_impl(self, args): def get_subscriptions_impl(self): return [ - ModelArgs(model_opt_required=True, input_shapes_opt_name=False, required_model_type="onnx"), - OnnxLoadArgs(allow_shape_inference=False, outputs_opt_prefix=False, allow_from_tf=False), + ModelArgs( + model_opt_required=True, + input_shapes_opt_name=False, + required_model_type="onnx", + ), + OnnxLoadArgs( + allow_shape_inference=False, + outputs_opt_prefix=False, + allow_from_tf=False, + ), ] def run_impl(self, args): diff --git a/tools/Polygraphy/polygraphy/tools/inspect/subtool/tactics.py b/tools/Polygraphy/polygraphy/tools/inspect/subtool/tactics.py index d33b0978..8f3a71c6 100644 --- a/tools/Polygraphy/polygraphy/tools/inspect/subtool/tactics.py +++ b/tools/Polygraphy/polygraphy/tools/inspect/subtool/tactics.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/plugin/plugin.py b/tools/Polygraphy/polygraphy/tools/plugin/plugin.py index 41d1b3a6..de4e44b0 100644 --- a/tools/Polygraphy/polygraphy/tools/plugin/plugin.py +++ b/tools/Polygraphy/polygraphy/tools/plugin/plugin.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py index 2b068fb2..442bb51a 100644 --- a/tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/list_plugins.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -21,16 +21,14 @@ from polygraphy.tools.plugin.subtool.plugin_base import PluginBase + class ListPlugins(PluginBase): """ Analyze an onnx model for potential plugin substitutions. """ def __init__(self): - super().__init__("list") - + super().__init__(list_plugins=True, name="list") + def add_parser_args_impl(self, parser): super().add_parser_args_impl(parser) - - def run_impl(self, args): - super().match_plugin(args=args, list_plugins=True) diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py index 0c7b0a2a..c9a501e0 100644 --- a/tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/match.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -28,7 +28,7 @@ class Match(PluginBase): """ def __init__(self): - super().__init__("match") + super().__init__(list_plugins=False, name="match") def add_parser_args_impl(self, parser): super().add_parser_args_impl(parser) @@ -38,6 +38,3 @@ def add_parser_args_impl(self, parser): help="Full path where to save the intermediate file. Defaults to a file called config.yaml in the model directory.", required=False, ) - - def run_impl(self, args): - super().match_plugin(args=args, list_plugins=False) diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py index b594f5c3..fdecf859 100644 --- a/tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/plugin_base.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,7 +23,13 @@ from polygraphy import mod from polygraphy.logger import G_LOGGER from polygraphy.tools import Tool -from polygraphy.tools.args import DataLoaderArgs, OnnxLoadArgs, ModelArgs, OnnxInferShapesArgs +from polygraphy.tools.args import util as args_util +from polygraphy.tools.args import ( + DataLoaderArgs, + OnnxLoadArgs, + ModelArgs, + OnnxInferShapesArgs, +) import os # Your tool should lazily import any external dependencies. By doing so, @@ -36,15 +42,17 @@ onnx = mod.lazy_import("onnx") yaml = mod.lazy_import("yaml", pkg_name="pyyaml") + class PluginBase(Tool): """ Analyze an onnx model for potential plugin substitutions. """ - GRAPH_PATTERN_FILE_NAME="pattern.py" - def __init__(self, name=None): + GRAPH_PATTERN_FILE_NAME = "pattern.py" + + def __init__(self, list_plugins:bool, name=None): super().__init__(name) - self.plugin_dir = None + self.list_plugins = list_plugins def get_subscriptions_impl(self): return [ @@ -57,26 +65,51 @@ def get_subscriptions_impl(self): def add_parser_args_impl(self, parser): parser.add_argument("--plugin-dir", help="Plugin directory.", required=True) include_exclude = parser.add_mutually_exclusive_group() - include_exclude.add_argument("--include", help="Names of plugins to include. Format: `--include ...`", required=False, nargs="+", type=str, default=[]) - include_exclude.add_argument("--exclude", help="Names of plugins to exclude. Format: `--exclude ...`", required=False, nargs="+", type=str, default=[]) + include_exclude.add_argument( + "--include", + help="Names of plugins to include. Format: `--include ...`", + required=False, + nargs="+", + type=str, + default=[], + ) + include_exclude.add_argument( + "--exclude", + help="Names of plugins to exclude. Format: `--exclude ...`", + required=False, + nargs="+", + type=str, + default=[], + ) def run_impl(self, args): - raise NotImplementedError("run_impl() must be implemented by child classes") - - def match_plugin(self, args, list_plugins=False): - - self.plugin_dir = os.path.abspath(args.plugin_dir) - full_pattern = os.path.join(self.plugin_dir, "*", self.GRAPH_PATTERN_FILE_NAME) - - plugin_set = {os.path.basename(os.path.dirname(x)) for x in glob.glob(pathname=full_pattern, recursive=False)} - - if args.include: - plugin_set.intersection_update(set(args.include)) - - if args.exclude: - plugin_set.difference_update(set(args.exclude)) - - graph = gs.import_onnx(self.arg_groups[OnnxLoadArgs].load_onnx()) + self.match_plugin( + model_file=args.model_file, + plugin_dir=args.plugin_dir, + output_file=args_util.get(args,"output"), + include_list=args.include, + exclude_list=args.exclude, + list_plugins=self.list_plugins + ) + + def match_plugin(self, model_file, plugin_dir, output_file=None, include_list=None, exclude_list=None, list_plugins=False): + """ + find matching subgraphs based on plugin pattern + """ + + plugin_dir = os.path.abspath(plugin_dir) + full_pattern = os.path.join(plugin_dir, "*", self.GRAPH_PATTERN_FILE_NAME) + + plugin_set = { + os.path.basename(os.path.dirname(x)) + for x in glob.glob(pathname=full_pattern, recursive=False) + } + + if include_list: + plugin_set.intersection_update(set(include_list)) + + if exclude_list: + plugin_set.difference_update(set(exclude_list)) # list of plugin substitution instances (conent of config.yaml) out_yaml = [] @@ -87,45 +120,29 @@ def match_plugin(self, args, list_plugins=False): G_LOGGER.info(f"checking {plugin} in model") plugin_yaml = {} - #build pattern from plugin - plugin_pattern_loc = os.path.join(self.plugin_dir, plugin, self.GRAPH_PATTERN_FILE_NAME) - graph_pattern = common_backend.invoke_from_script(plugin_pattern_loc, "get_plugin_pattern") - - matched_subgraphs = graph_pattern.match_all(graph) - if matched_subgraphs: - plugin_frequency[plugin] += len(matched_subgraphs) - - plugin_yaml["name"] = plugin - plugin_yaml["instances"] = [] - - for sg in matched_subgraphs: - def get_names(tensors): - return [tensor.name for tensor in tensors] + plugin_pattern_loc = os.path.join(plugin_dir, plugin, self.GRAPH_PATTERN_FILE_NAME) + # create a new graph in every iteration, in case the pattern matching modifies the graph + graph = gs.import_onnx(self.arg_groups[OnnxLoadArgs].load_onnx()) if self.arg_groups else gs.import_onnx(onnx.load(model_file)) - inputs = get_names(sg.inputs) - outputs = get_names(sg.outputs) - attributes = common_backend.invoke_from_script(plugin_pattern_loc, "get_plugin_attributes", sg) - plugin_yaml["instances"].append({ - "inputs": inputs, - "outputs": outputs, - "attributes": attributes - }) + #get inputs, outputs, attributes from plugin + G_LOGGER.ultra_verbose(f"calling get_matching_subgraphs from {plugin_pattern_loc}") + ioattrs = common_backend.invoke_from_script(plugin_pattern_loc, "get_matching_subgraphs", graph) - out_yaml.append(plugin_yaml) + if ioattrs: + G_LOGGER.ultra_verbose("match found") + plugin_yaml["name"] = common_backend.invoke_from_script(plugin_pattern_loc, "get_plugin_metadata")['name'] + plugin_yaml["op"] = common_backend.invoke_from_script(plugin_pattern_loc, "get_plugin_metadata")['op'] + plugin_yaml["instances"] = ioattrs + out_yaml.append(plugin_yaml) + plugin_frequency[plugin] += len(ioattrs) + G_LOGGER.info("the following plugins matched:") + G_LOGGER.info(plugin_frequency) if list_plugins: - G_LOGGER.info("the following plugins would be used:") - G_LOGGER.info(plugin_frequency) return - config_yaml = os.path.join(os.path.dirname(args.model_file),"config.yaml") - if args.output: - config_yaml = args.output + config_yaml = output_file or os.path.abspath(os.path.join(os.path.dirname(model_file),"config.yaml")) with open(config_yaml, "w") as stream: - yaml.dump_all( - out_yaml, - stream, - default_flow_style=False, - sort_keys=False - ) + yaml.dump_all(out_yaml, stream, default_flow_style=False, sort_keys=False) + G_LOGGER.info(f"Matching subgraphs saved to {config_yaml}") diff --git a/tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py b/tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py index 73e95f14..54a578e6 100644 --- a/tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py +++ b/tools/Polygraphy/polygraphy/tools/plugin/subtool/replace.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -39,29 +39,42 @@ gs = mod.lazy_import("onnx_graphsurgeon>=0.5.0") onnx = mod.lazy_import("onnx") yaml = mod.lazy_import("yaml", pkg_name="pyyaml") +common_backend = mod.lazy_import("polygraphy.backend.common") - -def replace_with_plugin(graph, op, inputs, outputs, attrs=None): +def default_replace_with_plugin(graph, input_tensors: list, output_tensors: list, attrs=None, op=None): """ - replaces a subgraph with a plugin + replaces a subgraph (set of nodes) with a single plugin node + default method to be used when the plugin does not specify a custom replacement method """ - # Disconnect output nodes of all input tensors - for inp in inputs: - inp.outputs.clear() + def issubset_unhashable(list_a: list, list_b: list) -> bool: + """ + Return whether list_a is a subset (or equal to) list_b + The objects in list_a and list_b are unhashable, otherwise set(list_a) <= set(list_b) is enough + """ + return len(list_a) <= len(list_b) and all(a in list_b for a in list_a) + + # Disconnect those output nodes of the input tensors whose inputs are a subset of the input tensors + for in_tensor in input_tensors: + to_remove_nodes = [] + for out_node in in_tensor.outputs: + if issubset_unhashable(out_node.inputs, input_tensors): + to_remove_nodes.append(out_node) + for node in to_remove_nodes: + in_tensor.outputs.remove(node) # Disconnet input nodes of all output tensors - for out in outputs: - out.inputs.clear() - - # Insert the new node. - new_node = graph.layer(op=op, inputs=inputs, outputs=outputs, attrs=attrs) - - # Remove the now-dangling subgraph. + for out_tensor in output_tensors: + to_remove_nodes = [] + for in_node in out_tensor.inputs: + to_remove_nodes.append(in_node) + for node in to_remove_nodes: + out_tensor.inputs.remove(node) + + # Insert the new node + new_node = graph.layer(op=op, inputs=input_tensors, outputs=output_tensors, attrs=attrs) graph.cleanup().toposort() - - return new_node - + return new_node[0].inputs[0] class Replace(Tool): # Polygraphy will use the docstring of the tool child class to generate @@ -69,9 +82,10 @@ class Replace(Tool): """ Replace a subgraph in an onnx model with a plugin. """ + GRAPH_PATTERN_FILE_NAME="pattern.py" def __init__(self): - super().__init__("replace") + super().__init__(name="replace") def get_subscriptions_impl(self): return [ @@ -84,33 +98,58 @@ def get_subscriptions_impl(self): def add_parser_args_impl(self, parser): parser.add_argument("--plugin-dir", help="Plugin directory.", required=True) parser.add_argument( - "-o", "--output", help="Where to save the modified model", required=True + "-o", "--output", help="Where to save the modified model", required=False ) parser.add_argument("--config", help="location of config.yaml.") def run_impl(self, args): - graph = gs.import_onnx(self.arg_groups[OnnxLoadArgs].load_onnx()) - tmap = graph.tensors() - config_yaml = os.path.join(os.path.dirname(args.model_file), "config.yaml") - if args.config: - config_yaml = args.config - + self.replace_plugin( + model_file=args.model_file, + plugin_dir=args.plugin_dir, + output=args.output, + config=args.config + ) + def replace_plugin(self, model_file, plugin_dir, output=None, config=None): + graph = gs.import_onnx(self.arg_groups[OnnxLoadArgs].load_onnx()) if self.arg_groups else gs.import_onnx(onnx.load(model_file)) + + tensor_map = graph.tensors() + config_yaml = config or os.path.join(os.path.dirname(model_file), "config.yaml") + + plugin_dir = os.path.abspath(plugin_dir) + with open(config_yaml, "r") as stream: in_yaml = yaml.safe_load_all(stream) for plugin in in_yaml: plugin_name = plugin["name"] - for instance in plugin["instances"]: - inputs = [tmap[tensor_name] for tensor_name in instance["inputs"]] - outputs = [tmap[tensor_name] for tensor_name in instance["outputs"]] - attrs = instance["attributes"] - - replace_with_plugin( - graph=graph, - op=plugin_name, - inputs=inputs, - outputs=outputs, - attrs=attrs, + plugin_op = plugin["op"] + G_LOGGER.ultra_verbose(f"replacing {plugin_name}...") + plugin_pattern_loc = os.path.join(plugin_dir, plugin_name, self.GRAPH_PATTERN_FILE_NAME) + # if the plugin provides a custom replacement method, use that + replace_fn = default_replace_with_plugin + try: + replace_fn = mod.import_from_script( + plugin_pattern_loc, + "replace_with_plugin" ) + except: + pass - onnx.save(gs.export_onnx(graph), args.output) + replace_cnt = 0 + for instance in plugin["instances"]: + attrs = instance.get("attributes", None) + if replace_fn( + graph=graph, + input_tensors=[tensor_map[ip_tensor_name] for ip_tensor_name in instance["inputs"]], + output_tensors=[tensor_map[op_tensor_name] for op_tensor_name in instance["outputs"]], + attrs=attrs, + op=plugin_op + ): + replace_cnt += 1 + G_LOGGER.info(f"replaced {replace_cnt} instances of {plugin_name} plugin") + if replace_cnt != len(plugin['instances']): + G_LOGGER.warning(f"Warning: not all instances of {plugin_name} were replaced!") + + output_onnx = output or os.path.join(os.path.dirname(model_file), "replaced.onnx") + + onnx.save(gs.export_onnx(graph), output_onnx) diff --git a/tools/Polygraphy/polygraphy/tools/registry.py b/tools/Polygraphy/polygraphy/tools/registry.py index ed4597d4..efb655e5 100644 --- a/tools/Polygraphy/polygraphy/tools/registry.py +++ b/tools/Polygraphy/polygraphy/tools/registry.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -28,9 +28,7 @@ def __init__(self, name, err): self.err = err # NOTE: When modifying this error message, make sure to update the checks in # tests/test_dependencies.py so that we don't miss errors! - self.__doc__ = ( - f"[!] This tool could not be loaded due to an error:\n{self.err}\nRun 'polygraphy {self.name}' for details." - ) + self.__doc__ = f"[!] This tool could not be loaded due to an error:\n{self.err}\nRun 'polygraphy {self.name}' for details." def __call__(self, args): G_LOGGER.critical(f"Encountered an error when loading this tool:\n{self.err}") @@ -44,7 +42,9 @@ def try_register_tool(module, tool_class): ToolClass = getattr(toolmod, tool_class) TOOL_REGISTRY.append(ToolClass()) except Exception as err: - G_LOGGER.internal_error(f"Could not load command-line tool: {tool_class.lower()}.\nNote: Error was: {err}") + G_LOGGER.internal_error( + f"Could not load command-line tool: {tool_class.lower()}.\nNote: Error was: {err}" + ) TOOL_REGISTRY.append(MissingTool(tool_class.lower(), err=err)) @@ -62,4 +62,6 @@ def try_register_tool(module, tool_class): tool_names = [tool.name for tool in TOOL_REGISTRY] duplicates = {name for name in tool_names if tool_names.count(name) > 1} if duplicates: - G_LOGGER.internal_error(f"Multiple tools have the same name. Duplicate tool names found: {duplicates}") + G_LOGGER.internal_error( + f"Multiple tools have the same name. Duplicate tool names found: {duplicates}" + ) diff --git a/tools/Polygraphy/polygraphy/tools/run/run.py b/tools/Polygraphy/polygraphy/tools/run/run.py index 2595d90e..9cb32d6a 100644 --- a/tools/Polygraphy/polygraphy/tools/run/run.py +++ b/tools/Polygraphy/polygraphy/tools/run/run.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -87,7 +87,9 @@ def join_list(lst): summary += join_list(runners) + "." if load_results: - summary += f"\nIt will check against outputs stored in {join_list(load_results)}\n" + summary += ( + f"\nIt will check against outputs stored in {join_list(load_results)}\n" + ) return summary @@ -163,7 +165,9 @@ def get_subscriptions_impl(self): get_arg_groups_func = plugin.load() plugin_arg_groups = get_arg_groups_func() except Exception as err: - G_LOGGER.warning(f"Failed to load plugin: {plugin.name}.\nNote: Error was:\n{err}") + G_LOGGER.warning( + f"Failed to load plugin: {plugin.name}.\nNote: Error was:\n{err}" + ) else: deps.extend(plugin_arg_groups) self.loaded_plugins.append(plugin.name) @@ -187,7 +191,10 @@ def show_start_end_logging_impl(self, args): def run_impl(self, args): G_LOGGER.verbose(f"Loaded extension modules: {self.loaded_plugins}") - if self.arg_groups[ModelArgs].path is None and self.arg_groups[RunnerSelectArgs].runners: + if ( + self.arg_groups[ModelArgs].path is None + and self.arg_groups[RunnerSelectArgs].runners + ): G_LOGGER.critical( "One or more runners was specified, but no model file was provided. Make sure you've specified the model path, " "and also that it's not being consumed as an argument for another parameter" @@ -206,7 +213,9 @@ def run_impl(self, args): self.arg_groups[RunnerSelectArgs].add_to_script(script) RESULTS_VAR_NAME = self.arg_groups[ComparatorRunArgs].add_to_script(script) - SUCCESS_VAR_NAME = self.arg_groups[ComparatorCompareArgs].add_to_script(script, results_name=RESULTS_VAR_NAME) + SUCCESS_VAR_NAME = self.arg_groups[ComparatorCompareArgs].add_to_script( + script, results_name=RESULTS_VAR_NAME + ) script.add_import(imports=["PolygraphyException"], frm="polygraphy.exception") exit_status = safe( diff --git a/tools/Polygraphy/polygraphy/tools/script.py b/tools/Polygraphy/polygraphy/tools/script.py index 80ea1004..5dde1114 100644 --- a/tools/Polygraphy/polygraphy/tools/script.py +++ b/tools/Polygraphy/polygraphy/tools/script.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -68,9 +68,13 @@ def ensure_safe(inp): Ensures that the input is marked as a safe string (i.e. Script.String(safe=True)). """ if not isinstance(inp, Script.String): - G_LOGGER.internal_error(f"Input to ensure_safe must be of type Script.String, but was: {inp}") + G_LOGGER.internal_error( + f"Input to ensure_safe must be of type Script.String, but was: {inp}" + ) elif not inp.safe: - G_LOGGER.internal_error(f"Input string: {inp} was not checked for safety. This is a potential security risk!") + G_LOGGER.internal_error( + f"Input string: {inp} was not checked for safety. This is a potential security risk!" + ) return inp @@ -117,8 +121,14 @@ def make_invocable_impl(type_str, *args, **kwargs): """ # We don't need to check obj_str for safety since we know that any inline # args/kwargs are already safe - other types need no checks - obj_str, all_args_default, all_kwargs_default = util.make_repr(type_str, *args, **kwargs) - return Script.String(obj_str, safe=True, inline=True), all_args_default, all_kwargs_default + obj_str, all_args_default, all_kwargs_default = util.make_repr( + type_str, *args, **kwargs + ) + return ( + Script.String(obj_str, safe=True, inline=True), + all_args_default, + all_kwargs_default, + ) @mod.export() @@ -162,7 +172,9 @@ def make_invocable_if_nondefault(type_str, *args, **kwargs): >>> make_invocable_if_nondefault("my_func", None, None, last=None) None """ - obj_str, all_args_default, all_kwargs_default = make_invocable_impl(type_str, *args, **kwargs) + obj_str, all_args_default, all_kwargs_default = make_invocable_impl( + type_str, *args, **kwargs + ) if all_args_default and all_kwargs_default: return None return obj_str @@ -221,9 +233,13 @@ def __repr__(self): def __iadd__(self, other): if not isinstance(other, Script.String): - G_LOGGER.internal_error(f"Cannot concatenate str and Script.String. Note: str was: {other}") + G_LOGGER.internal_error( + f"Cannot concatenate str and Script.String. Note: str was: {other}" + ) elif self.safe != other.safe: - G_LOGGER.internal_error(f"Cannot concatenate unsafe string ({other}) to safe string ({self.s})!") + G_LOGGER.internal_error( + f"Cannot concatenate unsafe string ({other}) to safe string ({self.s})!" + ) self.s += other.s return self @@ -258,8 +274,12 @@ def __init__(self, summary=None, always_create_runners=True): Whether to create the list of runners even if it would be empty. """ self.imports = {} # Dict[str, Set[str, str]]: Maps from: {(import, as), ...} - self.loaders = OrderedDict() # Dict[str, str] Maps a string constructing a loader to a name. - self.loader_count = defaultdict(int) # Dict[str, int] Maps loader_id to the number of loaders sharing that ID + self.loaders = ( + OrderedDict() + ) # Dict[str, str] Maps a string constructing a loader to a name. + self.loader_count = defaultdict( + int + ) # Dict[str, int] Maps loader_id to the number of loaders sharing that ID self.runners = [] # List[str] self.preimport = [] # List[str] self.suffix = [] # List[str] @@ -287,7 +307,9 @@ def add_import(self, imports, frm=None, imp_as=None): imports = {imports} if imp_as and len(imports) > 1: - G_LOGGER.internal_error("When `imp_as` is specified, `imports` must be a string and not a list") + G_LOGGER.internal_error( + "When `imp_as` is specified, `imports` must be a string and not a list" + ) if frm not in self.imports: self.imports[frm] = set() @@ -383,7 +405,11 @@ def __str__(self): script += f"# Generation Command: {' '.join(sys.argv)}\n" if self.summary: script += "# " + "\n# ".join(self.summary.splitlines()) + "\n" - script += ("\n" if self.preimport else "") + "\n".join(self.preimport) + ("\n\n" if self.preimport else "") + script += ( + ("\n" if self.preimport else "") + + "\n".join(self.preimport) + + ("\n\n" if self.preimport else "") + ) has_external_import = False imports = [] @@ -392,7 +418,10 @@ def __str__(self): is_external_import = False if frm is not None: # NOTE: We do not currently translate 'from' imports to `lazy_import`. - imps = [f"{imp}" if imp_as is None else f"{imp} as {imp_as}" for imp, imp_as in imps] + imps = [ + f"{imp}" if imp_as is None else f"{imp} as {imp_as}" + for imp, imp_as in imps + ] imports.append(f"from {frm} import {', '.join(imps)}") else: # When `frm` is None, we want to treat each import separately. @@ -403,7 +432,9 @@ def __str__(self): imp_as = imp_as or imp imports.append(f"{imp_as} = mod.lazy_import({repr(imp)})") else: - imports.append(f"import {imp}{'' if imp_as is None else f' as {imp_as}'}") + imports.append( + f"import {imp}{'' if imp_as is None else f' as {imp_as}'}" + ) has_external_import |= is_external_import if has_external_import: diff --git a/tools/Polygraphy/polygraphy/tools/sparse.py b/tools/Polygraphy/polygraphy/tools/sparse.py index 2cdbbfff..92a64003 100644 --- a/tools/Polygraphy/polygraphy/tools/sparse.py +++ b/tools/Polygraphy/polygraphy/tools/sparse.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -75,15 +75,21 @@ def __tensor(self, t, axis): if t in self.tname2producer: producer = self.tname2producer[t] if producer.op_type in axis_insensitive_op_type: - G_LOGGER.ultra_verbose(f"({t}) is produced by {producer.op_type}, looking back") + G_LOGGER.ultra_verbose( + f"({t}) is produced by {producer.op_type}, looking back" + ) self.__tensor(producer.input[0], axis) elif producer.op_type == "Transpose": - G_LOGGER.ultra_verbose(f"({t}) is produced by {producer.op_type}, checking attributes") + G_LOGGER.ultra_verbose( + f"({t}) is produced by {producer.op_type}, checking attributes" + ) for attr in producer.attribute: if attr.name == "perm": perm = list(attr.ints) new_axis = perm.index(axis) - G_LOGGER.ultra_verbose(f"attribute is {perm}, axis {axis} -> {new_axis}") + G_LOGGER.ultra_verbose( + f"attribute is {perm}, axis {axis} -> {new_axis}" + ) self.__tensor(producer.input[0], new_axis) return G_LOGGER.warning(f"{producer.op_type} doesn't have attribute!") @@ -92,7 +98,9 @@ def __tensor(self, t, axis): f"({t}) produced by {producer.name} type {producer.op_type}. Stopping backward analysis." ) else: - G_LOGGER.warning(f"({t}) produced by {producer.name} type: {producer.op_type} is unsupported!") + G_LOGGER.warning( + f"({t}) produced by {producer.name} type: {producer.op_type} is unsupported!" + ) def __conv(self, node): assert node.op_type == "Conv" @@ -133,7 +141,9 @@ def _walk_nodes(self): count = len(self.g.node) for i in range(count): n = self.g.node[i] - G_LOGGER.super_verbose(f"Processing node {i}/{count} ({n.op_type}): {n.name}") + G_LOGGER.super_verbose( + f"Processing node {i}/{count} ({n.op_type}): {n.name}" + ) if n.op_type == "MatMul": self.__matmul(n) elif n.op_type == "Gemm": @@ -239,7 +249,10 @@ def short2long(idx): zeros = 0 if is_raw_data: for i in range(step): - if data[short2long(i) * 2] == 0 and data[short2long(i) * 2 + 1] == 0: + if ( + data[short2long(i) * 2] == 0 + and data[short2long(i) * 2 + 1] == 0 + ): zeros += 1 else: i32_data_0 = data[short2long(0)] @@ -251,7 +264,9 @@ def bf16_zeros_in_int32(v): v1_zero = 1 if bf16_data_1 == 0 else 0 return v0_zero + v1_zero - zeros = bf16_zeros_in_int32(i32_data_0) + bf16_zeros_in_int32(i32_data_0) + zeros = bf16_zeros_in_int32(i32_data_0) + bf16_zeros_in_int32( + i32_data_0 + ) if zeros < 2: G_LOGGER.warning(f"Found non-sparse tensor: {tensor.name}") return False @@ -289,7 +304,9 @@ def process_tensor(pinfo, tensor, check): outer *= dims[i] for i in range(axis + 1, len(tensor.dims), 1): pstride *= dims[i] - G_LOGGER.ultra_verbose(f"axis {axis} of dims {dims} has stride {pstride} and outer {outer}") + G_LOGGER.ultra_verbose( + f"axis {axis} of dims {dims} has stride {pstride} and outer {outer}" + ) # We need hacks since BF16 has not been fully enabled in Numpy or ONNX. if tensor.data_type is onnx.TensorProto.BFLOAT16: diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/base.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/base.py index 5a8991da..d60643cf 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/base.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/base.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/extract.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/extract.py index fac44dd4..49259be9 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/extract.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/extract.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -19,7 +19,13 @@ from polygraphy import mod from polygraphy.common import TensorMetadata from polygraphy.logger import G_LOGGER -from polygraphy.tools.args import DataLoaderArgs, ModelArgs, OnnxInferShapesArgs, OnnxLoadArgs, OnnxSaveArgs +from polygraphy.tools.args import ( + DataLoaderArgs, + ModelArgs, + OnnxInferShapesArgs, + OnnxLoadArgs, + OnnxSaveArgs, +) from polygraphy.tools.args import util as args_util from polygraphy.tools.surgeon.subtool.base import BaseSurgeonSubtool from polygraphy.datatype import DataType @@ -93,7 +99,9 @@ def missing_meta_tensors(input_metadata, output_metadata): model = super().load_model() user_input_metadata = args_util.parse_meta(args.input_meta) - user_output_metadata = args_util.parse_meta(args.output_meta, includes_shape=False) + user_output_metadata = args_util.parse_meta( + args.output_meta, includes_shape=False + ) # Loads an ONNX-GS graph and create new I/O metadata w/ info missing in user_input/output_metadata. def load_graph_and_io_meta(model): @@ -128,7 +136,10 @@ def make_io_meta(user_meta, tensors): self.arg_groups[OnnxInferShapesArgs].force_fallback or self.arg_groups[OnnxInferShapesArgs].do_shape_inference ) - if missing_meta_tensors(input_metadata, output_metadata) and not skip_shape_inference: + if ( + missing_meta_tensors(input_metadata, output_metadata) + and not skip_shape_inference + ): G_LOGGER.info( "Running shape inference to derive shapes and/or data types for `auto` arguments.\n" "To avoid this, you can specify the shapes and data types explicitly." @@ -147,10 +158,13 @@ def make_io_meta(user_meta, tensors): "\nTo avoid this, please provide metadata on the command-line. " ) else: - G_LOGGER.info("Forcing fallback shape inference. This will cause dynamic dimensions to become static.") + G_LOGGER.info( + "Forcing fallback shape inference. This will cause dynamic dimensions to become static." + ) _, layerwise_meta = self.arg_groups[OnnxInferShapesArgs].fallback_inference( - model, outputs=list(input_metadata.keys()) + list(output_metadata.keys()) + model, + outputs=list(input_metadata.keys()) + list(output_metadata.keys()), ) def update_meta_from_layerwise(meta, user_meta, set_shapes=True): @@ -164,18 +178,31 @@ def choose_meta(user, model, fallback): user_dtype, user_shape = None, None if name in user_meta: - user_dtype, user_shape = user_meta[name].dtype, user_meta[name].shape + user_dtype, user_shape = ( + user_meta[name].dtype, + user_meta[name].shape, + ) - meta[name].dtype = choose_meta(user_dtype, meta[name].dtype, layerwise_meta[name].dtype) + meta[name].dtype = choose_meta( + user_dtype, meta[name].dtype, layerwise_meta[name].dtype + ) if set_shapes: - meta[name].shape = choose_meta(user_shape, meta[name].shape, layerwise_meta[name].shape) - G_LOGGER.verbose(f"Updated tensor: {name} metadata to: {meta[name]}") + meta[name].shape = choose_meta( + user_shape, meta[name].shape, layerwise_meta[name].shape + ) + G_LOGGER.verbose( + f"Updated tensor: {name} metadata to: {meta[name]}" + ) return meta - input_metadata = update_meta_from_layerwise(input_metadata, user_input_metadata) + input_metadata = update_meta_from_layerwise( + input_metadata, user_input_metadata + ) output_metadata = update_meta_from_layerwise( - output_metadata, user_output_metadata, set_shapes=self.arg_groups[OnnxInferShapesArgs].force_fallback + output_metadata, + user_output_metadata, + set_shapes=self.arg_groups[OnnxInferShapesArgs].force_fallback, ) graph = onnx_backend.extract_subgraph(graph, input_metadata, output_metadata) diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/insert.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/insert.py index 3161c883..94747672 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/insert.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/insert.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -16,7 +16,12 @@ # from polygraphy import mod from polygraphy.logger import G_LOGGER -from polygraphy.tools.args import ModelArgs, OnnxInferShapesArgs, OnnxLoadArgs, OnnxSaveArgs +from polygraphy.tools.args import ( + ModelArgs, + OnnxInferShapesArgs, + OnnxLoadArgs, + OnnxSaveArgs, +) from polygraphy.tools.args import util as args_util from polygraphy.tools.args.base import BaseArgs from polygraphy.tools.surgeon.subtool.base import BaseSurgeonSubtool @@ -46,8 +51,12 @@ def add_parser_args_impl(self): nargs="+", required=True, ) - self.group.add_argument("--op", help="The ONNX op to use for the new node", required=True) - self.group.add_argument("--name", help="The name to use for the new node", default=None) + self.group.add_argument( + "--op", help="The ONNX op to use for the new node", required=True + ) + self.group.add_argument( + "--name", help="The name to use for the new node", default=None + ) self.group.add_argument( "--attrs", help="Attributes to set in the new node. " @@ -64,7 +73,9 @@ def parse_impl(self, args): self.op = args_util.get(args, "op") self.name = args_util.get(args, "name") - self.attrs = args_util.parse_arglist_to_dict(args_util.get(args, "attrs"), sep="=") + self.attrs = args_util.parse_arglist_to_dict( + args_util.get(args, "attrs"), sep="=" + ) self.inputs = args_util.get(args, "inputs") self.outputs = args_util.get(args, "outputs") @@ -81,7 +92,11 @@ def __init__(self): def get_subscriptions_impl(self): return [ OnnxNodeArgs(), - ModelArgs(model_opt_required=True, input_shapes_opt_name=False, required_model_type="onnx"), + ModelArgs( + model_opt_required=True, + input_shapes_opt_name=False, + required_model_type="onnx", + ), OnnxInferShapesArgs(), OnnxLoadArgs(outputs_opt_prefix=False), OnnxSaveArgs(allow_shape_inference=True, output_opt_required=True), @@ -129,7 +144,9 @@ def replace_tensor(tensors): tensor.inputs.clear() output_tensors.append(tensor) - input_tensors = [get_tensor(name) for name in self.arg_groups[OnnxNodeArgs].inputs] + input_tensors = [ + get_tensor(name) for name in self.arg_groups[OnnxNodeArgs].inputs + ] new_node = gs.Node( op=self.arg_groups[OnnxNodeArgs].op, @@ -144,7 +161,9 @@ def replace_tensor(tensors): # after its last input node to maintain the sorting. with graph.node_ids(): # Nodes with no inputs can be inserted at index 0 - insert_index = max([node.id + 1 for inp in input_tensors for node in inp.inputs] + [0]) + insert_index = max( + [node.id + 1 for inp in input_tensors for node in inp.inputs] + [0] + ) graph.nodes.insert(insert_index, new_node) diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/prune.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/prune.py index c48d0ff2..6b5d32de 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/prune.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/prune.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -36,8 +36,16 @@ def show_start_end_logging_impl(self, args): def get_subscriptions_impl(self): return [ - ModelArgs(model_opt_required=True, input_shapes_opt_name=False, required_model_type="onnx"), - OnnxLoadArgs(allow_shape_inference=False, outputs_opt_prefix=False, allow_from_tf=False), + ModelArgs( + model_opt_required=True, + input_shapes_opt_name=False, + required_model_type="onnx", + ), + OnnxLoadArgs( + allow_shape_inference=False, + outputs_opt_prefix=False, + allow_from_tf=False, + ), OnnxSaveArgs(allow_shape_inference=False, output_opt_required=True), ] diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/sanitize.py b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/sanitize.py index a185542b..a8b141c2 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/subtool/sanitize.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/subtool/sanitize.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -17,7 +17,13 @@ from polygraphy import mod, util from polygraphy.logger import G_LOGGER from polygraphy.tools import util as tools_util -from polygraphy.tools.args import DataLoaderArgs, ModelArgs, OnnxInferShapesArgs, OnnxLoadArgs, OnnxSaveArgs +from polygraphy.tools.args import ( + DataLoaderArgs, + ModelArgs, + OnnxInferShapesArgs, + OnnxLoadArgs, + OnnxSaveArgs, +) from polygraphy.tools.args import util as args_util from polygraphy.tools.args.base import BaseArgs from polygraphy.tools.script import make_invocable @@ -108,7 +114,9 @@ def parse_impl(self, args): self.partitioning = args_util.get(args, "partitioning") self.fold_shapes = args_util.get(args, "fold_shapes") self.per_pass_shape_inference = args_util.get(args, "per_pass_shape_inference") - self.size_threshold = args_util.parse_num_bytes(args_util.get(args, "fold_size_threshold")) + self.size_threshold = args_util.parse_num_bytes( + args_util.get(args, "fold_size_threshold") + ) if not self.fold_constants: for arg in [ @@ -135,13 +143,18 @@ def add_to_script_impl(self, script, loader_name): "FoldConstants", loader_name, num_passes=self.num_passes, - do_shape_inference=self.arg_groups[OnnxInferShapesArgs].do_shape_inference - if self.per_pass_shape_inference is not False # since `None` indicates default value - else False, + do_shape_inference=( + self.arg_groups[OnnxInferShapesArgs].do_shape_inference + if self.per_pass_shape_inference + is not False # since `None` indicates default value + else False + ), fold_shapes=self.fold_shapes, partitioning=self.partitioning, size_threshold=self.size_threshold, - allow_onnxruntime_shape_inference=self.arg_groups[OnnxInferShapesArgs].allow_onnxruntime, + allow_onnxruntime_shape_inference=self.arg_groups[ + OnnxInferShapesArgs + ].allow_onnxruntime, ), "fold_constants", ) @@ -214,7 +227,9 @@ def get_graph(): rerun_shape_inference = True if self.arg_groups[OnnxInferShapesArgs].force_fallback: - _, layerwise_meta = self.arg_groups[OnnxInferShapesArgs].fallback_inference(model) + _, layerwise_meta = self.arg_groups[ + OnnxInferShapesArgs + ].fallback_inference(model) graph = get_graph() onnx_util.set_shapes_from_layerwise_meta(graph, layerwise_meta) diff --git a/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py b/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py index 0acbc94c..02bf0615 100644 --- a/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py +++ b/tools/Polygraphy/polygraphy/tools/surgeon/surgeon.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -15,7 +15,14 @@ # limitations under the License. # from polygraphy.tools.base import Tool -from polygraphy.tools.surgeon.subtool import Extract, Insert, Sanitize, Prune, WeightStripper, WeightReconstructor +from polygraphy.tools.surgeon.subtool import ( + Extract, + Insert, + Sanitize, + Prune, + WeightStripper, + WeightReconstructor, +) ################################# MAIN TOOL ################################# diff --git a/tools/Polygraphy/polygraphy/tools/template/subtool/base.py b/tools/Polygraphy/polygraphy/tools/template/subtool/base.py index cf6cd802..8aa39c25 100644 --- a/tools/Polygraphy/polygraphy/tools/template/subtool/base.py +++ b/tools/Polygraphy/polygraphy/tools/template/subtool/base.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/template/subtool/onnx_gs.py b/tools/Polygraphy/polygraphy/tools/template/subtool/onnx_gs.py index 023b0f80..64f0981a 100644 --- a/tools/Polygraphy/polygraphy/tools/template/subtool/onnx_gs.py +++ b/tools/Polygraphy/polygraphy/tools/template/subtool/onnx_gs.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -44,9 +44,13 @@ def run_impl(self, args): script.add_import(imports="GsFromOnnx", frm="polygraphy.backend.onnx") loader_name = self.arg_groups[OnnxLoadArgs].add_to_script(script) - loader_name = script.add_loader(make_invocable("GsFromOnnx", loader_name), "load_gs") + loader_name = script.add_loader( + make_invocable("GsFromOnnx", loader_name), "load_gs" + ) - new_model_path = util.add_file_suffix(self.arg_groups[ModelArgs].path, "_updated") + new_model_path = util.add_file_suffix( + self.arg_groups[ModelArgs].path, "_updated" + ) content = safe( dedent( diff --git a/tools/Polygraphy/polygraphy/tools/template/subtool/trt_config.py b/tools/Polygraphy/polygraphy/tools/template/subtool/trt_config.py index f2a16da7..24e3131d 100644 --- a/tools/Polygraphy/polygraphy/tools/template/subtool/trt_config.py +++ b/tools/Polygraphy/polygraphy/tools/template/subtool/trt_config.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -37,7 +37,10 @@ def get_subscriptions_impl(self): ] def run_impl(self, args): - script = Script(summary="Creates a TensorRT Builder Configuration.", always_create_runners=False) + script = Script( + summary="Creates a TensorRT Builder Configuration.", + always_create_runners=False, + ) script.add_import(imports=["func"], frm="polygraphy") script.add_import(imports="tensorrt", imp_as="trt") diff --git a/tools/Polygraphy/polygraphy/tools/template/subtool/trt_network.py b/tools/Polygraphy/polygraphy/tools/template/subtool/trt_network.py index 8796fb39..fe31a869 100644 --- a/tools/Polygraphy/polygraphy/tools/template/subtool/trt_network.py +++ b/tools/Polygraphy/polygraphy/tools/template/subtool/trt_network.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -49,7 +49,10 @@ def get_subscriptions_impl(self): ] def run_impl(self, args): - script = Script(summary="Creates a TensorRT Network using the Network API.", always_create_runners=False) + script = Script( + summary="Creates a TensorRT Network using the Network API.", + always_create_runners=False, + ) script.add_import(imports=["func"], frm="polygraphy") script.add_import(imports="tensorrt", imp_as="trt") @@ -64,7 +67,9 @@ def run_impl(self, args): script.append_suffix(safe("@func.extend({:})", inline(loader_name))) script.append_suffix(safe("def load_network({:}):", inline(params))) script.append_suffix( - safe(f"{constants.TAB}pass # TODO: Set up the network here. This function should not return anything.") + safe( + f"{constants.TAB}pass # TODO: Set up the network here. This function should not return anything." + ) ) script.save(args.output) diff --git a/tools/Polygraphy/polygraphy/tools/template/template.py b/tools/Polygraphy/polygraphy/tools/template/template.py index 4fab5973..5c899493 100644 --- a/tools/Polygraphy/polygraphy/tools/template/template.py +++ b/tools/Polygraphy/polygraphy/tools/template/template.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/polygraphy/tools/util.py b/tools/Polygraphy/polygraphy/tools/util.py index 9ead6c29..7ac5a27a 100644 --- a/tools/Polygraphy/polygraphy/tools/util.py +++ b/tools/Polygraphy/polygraphy/tools/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -37,7 +37,9 @@ def override_input_shapes(graph, user_input_metadata): input_metadata.update(user_input_metadata) graph = onnx_backend.extract_subgraph(graph, input_metadata) - G_LOGGER.info(f"Overriding input shapes to:\n{onnx_util.meta_from_gs_tensors(graph.inputs)}") + G_LOGGER.info( + f"Overriding input shapes to:\n{onnx_util.meta_from_gs_tensors(graph.inputs)}" + ) # Have to unset intermediate shapes as they may cause problems. tensors = graph.tensors() diff --git a/tools/Polygraphy/polygraphy/util/array.py b/tools/Polygraphy/polygraphy/util/array.py index 4381ccf6..57231ff8 100644 --- a/tools/Polygraphy/polygraphy/util/array.py +++ b/tools/Polygraphy/polygraphy/util/array.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -528,9 +528,9 @@ def numpy_impl(obj, shape): return { "numpy": numpy_impl, "torch": lambda obj, shape: obj.resize_(shape) if shape != obj.shape else obj, - "device_view": lambda obj, shape: obj.resize(shape) - if shape != obj.shape - else obj, + "device_view": lambda obj, shape: ( + obj.resize(shape) if shape != obj.shape else obj + ), } diff --git a/tools/Polygraphy/polygraphy/util/util.py b/tools/Polygraphy/polygraphy/util/util.py index 7a28357c..ae1c74e0 100644 --- a/tools/Polygraphy/polygraphy/util/util.py +++ b/tools/Polygraphy/polygraphy/util/util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -73,7 +73,13 @@ def find_str_in_iterable(name, seq, index=None): @mod.export() def check_sequence_contains( - sequence, items, name=None, items_name=None, log_func=None, check_missing=None, check_extra=None + sequence, + items, + name=None, + items_name=None, + log_func=None, + check_missing=None, + check_extra=None, ): """ Checks that a sequence contains the provided items and also @@ -248,7 +254,10 @@ def default(value, default): @mod.export() def is_sequence(obj): return ( - hasattr(obj, "__iter__") and not isinstance(obj, dict) and not isinstance(obj, set) and not isinstance(obj, str) + hasattr(obj, "__iter__") + and not isinstance(obj, dict) + and not isinstance(obj, set) + and not isinstance(obj, str) ) @@ -294,7 +303,9 @@ def __init__(self, mode=None, prefix=None, suffix=None): suffix = default(suffix, "") def rand_path(): - return os.path.join(tempfile.gettempdir(), f"{prefix}{os.urandom(24).hex()}{suffix}") + return os.path.join( + tempfile.gettempdir(), f"{prefix}{os.urandom(24).hex()}{suffix}" + ) # In the unlikely event the path exists, generate a new one. Only try 100 times so # we don't end up in an infinite loop. @@ -304,7 +315,9 @@ def rand_path(): break path = rand_path() else: - G_LOGGER.critical(f"Could not create a temporary file under: {tempfile.gettempdir()}") + G_LOGGER.critical( + f"Could not create a temporary file under: {tempfile.gettempdir()}" + ) self.name = path # Use 'name' to be compatible with tempfile.NamedTemporaryFile open(self.name, "x").close() # `touch` the file @@ -363,7 +376,11 @@ def __enter__(self): locked = False while not locked: try: - msvcrt.locking(self._fhandle.fileno(), msvcrt.LK_RLCK, get_file_size(self._fhandle)) + msvcrt.locking( + self._fhandle.fileno(), + msvcrt.LK_RLCK, + get_file_size(self._fhandle), + ) except OSError: locked = False else: @@ -379,7 +396,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): return if sys.platform.startswith("win"): - msvcrt.locking(self._fhandle.fileno(), msvcrt.LK_UNLCK, get_file_size(self._fhandle)) + msvcrt.locking( + self._fhandle.fileno(), msvcrt.LK_UNLCK, get_file_size(self._fhandle) + ) else: fcntl.lockf(self._fhandle.fileno(), fcntl.LOCK_UN) @@ -634,7 +653,9 @@ def try_send_on_queue(queue, obj): try: send_on_queue(queue, obj) except Exception as err: - G_LOGGER.warning(f"Could not send object on queue: {err}\nSending None instead.") + G_LOGGER.warning( + f"Could not send object on queue: {err}\nSending None instead." + ) queue.put(None) @@ -697,7 +718,9 @@ def wrapped(*args, **kwargs): # Skip checks if we're calling these functions internally module = inspect.getmodule(sys._getframe(1)) called_from_polygraphy = ( - module is not None and module.__name__ and module.__name__.split(".")[0] == "polygraphy" + module is not None + and module.__name__ + and module.__name__.split(".")[0] == "polygraphy" ) if not called_from_polygraphy: @@ -736,14 +759,21 @@ def is_shape_dynamic(shape): @mod.export() def is_valid_shape_override(new_shape, original_shape): ranks_same = len(original_shape) == len(new_shape) - overrides_valid = all([odim == ndim or is_dimension_dynamic(odim) for odim, ndim in zip(original_shape, new_shape)]) + overrides_valid = all( + [ + odim == ndim or is_dimension_dynamic(odim) + for odim, ndim in zip(original_shape, new_shape) + ] + ) return ranks_same and overrides_valid @mod.export() def override_dynamic_shape(shape, default_shape_value=None): default_shape_value = default(default_shape_value, constants.DEFAULT_SHAPE_VALUE) - return [default_shape_value if is_dimension_dynamic(elem) else elem for elem in shape] + return [ + default_shape_value if is_dimension_dynamic(elem) else elem for elem in shape + ] @mod.export() @@ -788,19 +818,25 @@ def try_reshape(arr, shape): ) else: if array_util.shape(arr) != original_shape: - G_LOGGER.info(f"Reshaped array from shape: {original_shape} to: {array_util.shape(arr)}") + G_LOGGER.info( + f"Reshaped array from shape: {original_shape} to: {array_util.shape(arr)}" + ) return arr def try_permute(arr, shape): original_shape = array_util.shape(arr) if sorted(array_util.shape(arr)) != sorted(shape): - G_LOGGER.extra_verbose(f"Array of shape: {array_util.shape(arr)} cannot be permuted to: {shape}") + G_LOGGER.extra_verbose( + f"Array of shape: {array_util.shape(arr)} cannot be permuted to: {shape}" + ) return arr # We need to remove axes from the original shape as we use them to avoid # duplication in the permutation. - arr_shape_indices = {index: dimlen for index, dimlen in enumerate(array_util.shape(arr))} + arr_shape_indices = { + index: dimlen for index, dimlen in enumerate(array_util.shape(arr)) + } # Find which axis in array_util.shape(arr) corresponds to the specified size. Never returns duplicates. def find_axis(dimlen): @@ -830,7 +866,9 @@ def try_freeze_shape(arr, shape): determined_dim = volume(array_util.shape(arr)) // volume(static_dims) except ZeroDivisionError: determined_dim = 0 - shape = [determined_dim if is_dimension_dynamic(elem) else elem for elem in shape] + shape = [ + determined_dim if is_dimension_dynamic(elem) else elem for elem in shape + ] elif is_rank_same(arr, shape): shape = [ arr_shape_elem if is_dimension_dynamic(elem) else elem @@ -860,7 +898,9 @@ def try_freeze_shape(arr, shape): @mod.export() -def str_from_layer(prefix, index, name, op, input_names, input_meta, output_names, output_meta): +def str_from_layer( + prefix, index, name, op, input_names, input_meta, output_names, output_meta +): def tensor_names_to_string(tensor_names, meta): sep = ",\n " elems = [f"{name} {meta[name]}".strip() for name in tensor_names] diff --git a/tools/Polygraphy/setup.py b/tools/Polygraphy/setup.py index b0e14497..d3e7050c 100644 --- a/tools/Polygraphy/setup.py +++ b/tools/Polygraphy/setup.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/backend/base/test_loader.py b/tools/Polygraphy/tests/backend/base/test_loader.py index f836bcae..e32c4e08 100644 --- a/tools/Polygraphy/tests/backend/base/test_loader.py +++ b/tools/Polygraphy/tests/backend/base/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/backend/base/test_runner.py b/tools/Polygraphy/tests/backend/base/test_runner.py index f61b0710..1153de27 100644 --- a/tools/Polygraphy/tests/backend/base/test_runner.py +++ b/tools/Polygraphy/tests/backend/base/test_runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/backend/common/test_loader.py b/tools/Polygraphy/tests/backend/common/test_loader.py index 0aaba8c9..6c97df45 100644 --- a/tools/Polygraphy/tests/backend/common/test_loader.py +++ b/tools/Polygraphy/tests/backend/common/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -71,5 +71,7 @@ def example(): f.flush() os.fsync(f.fileno()) - with pytest.raises(PolygraphyException, match="Could not import symbol: non_existent from"): + with pytest.raises( + PolygraphyException, match="Could not import symbol: non_existent from" + ): invoke_from_script(f.name, "non_existent") diff --git a/tools/Polygraphy/tests/backend/onnx/test_loader.py b/tools/Polygraphy/tests/backend/onnx/test_loader.py index 50d8fede..b178a90d 100644 --- a/tools/Polygraphy/tests/backend/onnx/test_loader.py +++ b/tools/Polygraphy/tests/backend/onnx/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -59,7 +59,9 @@ def test_basic(self): @pytest.mark.serial def test_warn_if_impl_methods_called(self, check_warnings_on_loader_impl_methods): - check_warnings_on_loader_impl_methods(OnnxFromPath(ONNX_MODELS["identity"].path)) + check_warnings_on_loader_impl_methods( + OnnxFromPath(ONNX_MODELS["identity"].path) + ) def test_external_data(self): model = ONNX_MODELS["ext_weights"] @@ -113,7 +115,9 @@ def test_layerwise(self, copy): @pytest.mark.parametrize("output", ["identity_out_0", "identity_out_2"]) def test_custom_outputs(self, output): - loader = ModifyOutputs(OnnxFromPath(ONNX_MODELS["identity_identity"].path), outputs=[output]) + loader = ModifyOutputs( + OnnxFromPath(ONNX_MODELS["identity_identity"].path), outputs=[output] + ) model = loader() assert len(model.graph.output) == 1 assert model.graph.output[0].name == output @@ -148,7 +152,9 @@ def test_model(self, allow_onnxruntime): self.check_model(model) def test_path(self, allow_onnxruntime): - model = infer_shapes(ONNX_MODELS["identity_identity"].path, allow_onnxruntime=allow_onnxruntime) + model = infer_shapes( + ONNX_MODELS["identity_identity"].path, allow_onnxruntime=allow_onnxruntime + ) self.check_model(model) @pytest.mark.parametrize("set_data_dir", [True, False]) @@ -163,7 +169,9 @@ def test_external_data(self, set_data_dir, allow_onnxruntime): def test_save_to_disk_on_size_threshold(self, allow_onnxruntime): model = onnx_from_path(ONNX_MODELS["const_foldable"].path) - model = infer_shapes(model, save_to_disk_threshold_bytes=0, allow_onnxruntime=allow_onnxruntime) + model = infer_shapes( + model, save_to_disk_threshold_bytes=0, allow_onnxruntime=allow_onnxruntime + ) self.check_model(model) @@ -188,7 +196,9 @@ class TestFoldConstants: @pytest.mark.parametrize("partitioning", [None, "basic", "recursive"]) @pytest.mark.parametrize("copy", [True, False]) @pytest.mark.parametrize("allow_onnxruntime_shape_inference", [True, False]) - def test_basic(self, partitioning, fold_shapes, copy, allow_onnxruntime_shape_inference): + def test_basic( + self, partitioning, fold_shapes, copy, allow_onnxruntime_shape_inference + ): original_model = onnx_from_path(ONNX_MODELS["const_foldable"].path) loader = FoldConstants( original_model, @@ -275,7 +285,9 @@ def test_save_onnx(self): def test_external_data(self): with util.NamedTemporaryFile() as path, util.NamedTemporaryFile() as data: model = OnnxFromPath(ONNX_MODELS["const_foldable"].path) - loader = SaveOnnx(model, path.name, external_data_path=data.name, size_threshold=0) + loader = SaveOnnx( + model, path.name, external_data_path=data.name, size_threshold=0 + ) loader() assert is_file_non_empty(path.name) assert is_file_non_empty(data.name) @@ -284,8 +296,14 @@ def test_external_data(self): @pytest.fixture() def extract_model(): input_metadata = TensorMetadata().add("X", dtype=np.float32, shape=(64, 64)) - output_metadata = TensorMetadata().add("identity_out_0", dtype=np.float32, shape=None) - return onnx_from_path(ONNX_MODELS["identity_identity"].path), input_metadata, output_metadata + output_metadata = TensorMetadata().add( + "identity_out_0", dtype=np.float32, shape=None + ) + return ( + onnx_from_path(ONNX_MODELS["identity_identity"].path), + input_metadata, + output_metadata, + ) class TestExtractSubgraph: diff --git a/tools/Polygraphy/tests/backend/onnx/test_util.py b/tools/Polygraphy/tests/backend/onnx/test_util.py index 2f731dff..a009fd56 100644 --- a/tools/Polygraphy/tests/backend/onnx/test_util.py +++ b/tools/Polygraphy/tests/backend/onnx/test_util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -15,10 +15,7 @@ # limitations under the License. # -from polygraphy.backend.onnx import ( - onnx_from_path, - gs_from_onnx -) +from polygraphy.backend.onnx import onnx_from_path, gs_from_onnx from polygraphy.backend.onnx import util as onnx_util from tests.models.meta import ONNX_MODELS @@ -27,9 +24,10 @@ def test_get_num_nodes(): model = onnx_from_path(ONNX_MODELS["scan"].path) assert onnx_util.get_num_nodes(model) == 3 # Should count subgraph nodes. + def test_get_unbounded_dds_tensors(): model = onnx_from_path(ONNX_MODELS["unbounded_dds"].path) graph = gs_from_onnx(model) tensors = onnx_util.get_unbounded_dds_tensors(graph) assert len(tensors) == 1 - assert tensors[0].name == 'cast_out_6' \ No newline at end of file + assert tensors[0].name == "cast_out_6" diff --git a/tools/Polygraphy/tests/backend/onnxrt/test_loader.py b/tools/Polygraphy/tests/backend/onnxrt/test_loader.py index 4efab6f3..7f397b7b 100644 --- a/tools/Polygraphy/tests/backend/onnxrt/test_loader.py +++ b/tools/Polygraphy/tests/backend/onnxrt/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -51,5 +51,8 @@ def test_provider_matching(self, providers, expected): def test_invalid_providers_raise_errors(self): model = ONNX_MODELS["identity"] loader = SessionFromOnnx(model.loader, providers=["cpu", "not_a_real_provider"]) - with pytest.raises(PolygraphyException, match="Could not find specified ONNX-Runtime execution provider"): + with pytest.raises( + PolygraphyException, + match="Could not find specified ONNX-Runtime execution provider", + ): loader() diff --git a/tools/Polygraphy/tests/backend/onnxrt/test_runner.py b/tools/Polygraphy/tests/backend/onnxrt/test_runner.py index b1ed7967..ffb789f4 100644 --- a/tools/Polygraphy/tests/backend/onnxrt/test_runner.py +++ b/tools/Polygraphy/tests/backend/onnxrt/test_runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -85,7 +85,12 @@ def test_error_on_wrong_name_feed_dict(self, names, err): model = ONNX_MODELS["identity"] with OnnxrtRunner(SessionFromOnnx(model.loader)) as runner: with pytest.raises(PolygraphyException, match=err): - runner.infer({name: np.ones(shape=(1, 1, 2, 2), dtype=np.float32) for name in names}) + runner.infer( + { + name: np.ones(shape=(1, 1, 2, 2), dtype=np.float32) + for name in names + } + ) def test_error_on_wrong_dtype_feed_dict(self): model = ONNX_MODELS["identity"] diff --git a/tools/Polygraphy/tests/backend/pluginref/test_runner.py b/tools/Polygraphy/tests/backend/pluginref/test_runner.py index b0a18f0a..6c868d5e 100644 --- a/tools/Polygraphy/tests/backend/pluginref/test_runner.py +++ b/tools/Polygraphy/tests/backend/pluginref/test_runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -57,8 +57,16 @@ def test_works_on_multiple_nodes(self): def test_fail_on_unsupported_node(self): model = ONNX_MODELS["and"] with PluginRefRunner(GsFromOnnx(OnnxFromPath(model.path))) as runner: - with pytest.raises(PolygraphyException, match="does not have a reference implementation registered!"): - runner.infer({"x": np.ones(shape=(3, 4), dtype=bool), "y": np.ones(shape=(3, 4), dtype=bool)}) + with pytest.raises( + PolygraphyException, + match="does not have a reference implementation registered!", + ): + runner.infer( + { + "x": np.ones(shape=(3, 4), dtype=bool), + "y": np.ones(shape=(3, 4), dtype=bool), + } + ) @pytest.mark.parametrize( "names, err", @@ -72,7 +80,12 @@ def test_error_on_wrong_name_feed_dict(self, names, err): model = ONNX_MODELS["identity"] with PluginRefRunner(GsFromOnnx(OnnxFromPath(model.path))) as runner: with pytest.raises(PolygraphyException, match=err): - runner.infer({name: np.ones(shape=(1, 1, 2, 2), dtype=np.float32) for name in names}) + runner.infer( + { + name: np.ones(shape=(1, 1, 2, 2), dtype=np.float32) + for name in names + } + ) def test_error_on_wrong_dtype_feed_dict(self): model = ONNX_MODELS["identity"] diff --git a/tools/Polygraphy/tests/backend/tf/test_loader.py b/tools/Polygraphy/tests/backend/tf/test_loader.py index f05f402b..3b342706 100644 --- a/tools/Polygraphy/tests/backend/tf/test_loader.py +++ b/tools/Polygraphy/tests/backend/tf/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -19,7 +19,12 @@ import pytest from polygraphy import constants, util -from polygraphy.backend.tf import GraphFromFrozen, ModifyGraphOutputs, SaveGraph, graph_from_frozen +from polygraphy.backend.tf import ( + GraphFromFrozen, + ModifyGraphOutputs, + SaveGraph, + graph_from_frozen, +) from polygraphy.logger import G_LOGGER from tests.helper import is_file_non_empty from tests.models.meta import TF_MODELS @@ -61,12 +66,16 @@ def test_layerwise(self): class TestSaveGraph: def test_save_pb(self): with util.NamedTemporaryFile() as outpath: - tf_loader = SaveGraph(GraphFromFrozen(TF_MODELS["identity"].path), path=outpath.name) + tf_loader = SaveGraph( + GraphFromFrozen(TF_MODELS["identity"].path), path=outpath.name + ) tf_loader() assert is_file_non_empty(outpath.name) def test_save_tensorboard(self): with tempfile.TemporaryDirectory() as outdir: - tf_loader = SaveGraph(GraphFromFrozen(TF_MODELS["identity"].path), tensorboard_dir=outdir) + tf_loader = SaveGraph( + GraphFromFrozen(TF_MODELS["identity"].path), tensorboard_dir=outdir + ) tf_loader() assert os.path.exists(tf_loader.tensorboard_dir) diff --git a/tools/Polygraphy/tests/backend/tf/test_runner.py b/tools/Polygraphy/tests/backend/tf/test_runner.py index 22ceb98f..60faf220 100644 --- a/tools/Polygraphy/tests/backend/tf/test_runner.py +++ b/tools/Polygraphy/tests/backend/tf/test_runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -49,7 +49,11 @@ def test_warn_if_impl_methods_called(self, check_warnings_on_runner_impl_methods def test_save_timeline(self): model = TF_MODELS["identity"] with util.NamedTemporaryFile() as outpath: - with TfRunner(SessionFromGraph(model.loader), allow_growth=True, save_timeline=outpath.name) as runner: + with TfRunner( + SessionFromGraph(model.loader), + allow_growth=True, + save_timeline=outpath.name, + ) as runner: model.check_runner(runner) assert is_file_non_empty(outpath.name) @@ -65,16 +69,25 @@ def test_error_on_wrong_name_feed_dict(self, names, err): model = TF_MODELS["identity"] with TfRunner(SessionFromGraph(model.loader)) as runner: with pytest.raises(PolygraphyException, match=err): - runner.infer({name: np.ones(shape=(1, 15, 25, 30), dtype=np.float32) for name in names}) + runner.infer( + { + name: np.ones(shape=(1, 15, 25, 30), dtype=np.float32) + for name in names + } + ) def test_error_on_wrong_dtype_feed_dict(self): model = TF_MODELS["identity"] with TfRunner(SessionFromGraph(model.loader)) as runner: with pytest.raises(PolygraphyException, match="unexpected dtype."): - runner.infer({"Input:0": np.ones(shape=(1, 15, 25, 30), dtype=np.int32)}) + runner.infer( + {"Input:0": np.ones(shape=(1, 15, 25, 30), dtype=np.int32)} + ) def test_error_on_wrong_shape_feed_dict(self): model = TF_MODELS["identity"] with TfRunner(SessionFromGraph(model.loader)) as runner: with pytest.raises(PolygraphyException, match="incompatible shape."): - runner.infer({"Input:0": np.ones(shape=(1, 1, 25, 30), dtype=np.float32)}) + runner.infer( + {"Input:0": np.ones(shape=(1, 1, 25, 30), dtype=np.float32)} + ) diff --git a/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py b/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py index 8e35eaa1..0e6703a0 100644 --- a/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py +++ b/tools/Polygraphy/tests/backend/trt/test_algorithm_selector.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/backend/trt/test_calibrator.py b/tools/Polygraphy/tests/backend/trt/test_calibrator.py index 81926156..94f514a8 100644 --- a/tools/Polygraphy/tests/backend/trt/test_calibrator.py +++ b/tools/Polygraphy/tests/backend/trt/test_calibrator.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -29,6 +29,7 @@ network_from_onnx_bytes, ) from polygraphy.common import TensorMetadata +from polygraphy.comparator import DataLoader from polygraphy.datatype import DataType from polygraphy.exception import PolygraphyException from tests.helper import get_file_size, is_file_non_empty @@ -288,6 +289,19 @@ def test_calibrator_checks_input_metadata(self, expected_meta, meta, should_pass assert (calibrator.get_batch(list(expected_meta.keys())) is not None) == should_pass self.check_calibrator_cleanup(calibrator) + def test_calibrator_forces_float32_data(self): + data_loader = DataLoader() + + calibrator = Calibrator(data_loader) + + meta = TensorMetadata().add("input", dtype=DataType.FLOAT16, shape=(1, 2, 3)) + calibrator.set_input_metadata(meta) + + data = data_loader[0]["input"] + # TRT requires all calibration inputs to be provided in FP32 regardless of the data type + # in the original model. + assert util.array.dtype(data) == DataType.FLOAT32 + # TensorRT does not support changing input shapes during calibration @pytest.mark.xfail def test_calibrator_dynamic_shapes(self, dynamic_identity_builder_network): diff --git a/tools/Polygraphy/tests/backend/trt/test_loader.py b/tools/Polygraphy/tests/backend/trt/test_loader.py index 4ae2608a..2fa3d42b 100644 --- a/tools/Polygraphy/tests/backend/trt/test_loader.py +++ b/tools/Polygraphy/tests/backend/trt/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -26,6 +26,7 @@ EngineBytesFromNetwork, EngineFromBytes, EngineFromNetwork, + EngineFromPath, LoadPlugins, LoadRuntime, ModifyNetworkOutputs, @@ -126,9 +127,11 @@ def get_plugin_names(): loader = LoadPlugins( plugins=[ - "nvinfer_plugin.dll" - if sys.platform.startswith("win") - else "libnvinfer_plugin.so" + ( + "nvinfer_plugin.dll" + if sys.platform.startswith("win") + else "libnvinfer_plugin.so" + ) ] ) loader() @@ -144,7 +147,7 @@ def test_serialized_engine_loader_from_lambda(self, identity_engine): loader = EngineFromBytes(lambda: open(outpath.name, "rb").read()) with loader() as engine: assert isinstance(engine, trt.ICudaEngine) - + def test_serialized_engine_loader_from_buffer(self, identity_engine): with identity_engine.serialize() as buffer: loader = EngineFromBytes(buffer) @@ -158,6 +161,29 @@ def test_serialized_engine_loader_custom_runtime(self, identity_engine): assert isinstance(engine, trt.ICudaEngine) +@pytest.mark.skipif( + mod.version(trt.__version__) < mod.version("10.0"), reason="API was added in TRT 10.0" +) +class TestSerializedEngineLoaderFromDisk: + def test_serialized_engine_loader_from_lambda(self, identity_engine): + with util.NamedTemporaryFile() as outpath: + with open(outpath.name, "wb") as f, identity_engine.serialize() as buffer: + f.write(buffer) + + loader = EngineFromPath(lambda: outpath.name) + with loader() as engine: + assert isinstance(engine, trt.ICudaEngine) + + def test_serialized_engine_loader_custom_runtime(self, identity_engine): + with util.NamedTemporaryFile() as outpath: + with open(outpath.name, "wb") as f, identity_engine.serialize() as buffer: + f.write(buffer) + + loader = EngineFromPath(lambda: outpath.name, runtime=trt.Runtime(get_trt_logger())) + with loader() as engine: + assert isinstance(engine, trt.ICudaEngine) + + @pytest.mark.skipif( mod.version(trt.__version__) < mod.version("8.6"), reason="API was added in TRT 8.6" ) @@ -196,9 +222,16 @@ def test_loader(self): @pytest.mark.parametrize( "kwargs, flag", - [({"strongly_typed": True}, trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)] - if mod.version(trt.__version__) >= mod.version("8.7") - else [], + ( + [ + ( + {"strongly_typed": True}, + trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED, + ) + ] + if mod.version(trt.__version__) >= mod.version("8.7") + else [] + ), ) def test_network_flags(self, kwargs, flag): builder, network, parser = network_from_onnx_bytes( @@ -214,9 +247,16 @@ def test_loader(self): @pytest.mark.parametrize( "kwargs, flag", - [({"strongly_typed": True}, trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)] - if mod.version(trt.__version__) >= mod.version("8.7") - else [], + ( + [ + ( + {"strongly_typed": True}, + trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED, + ) + ] + if mod.version(trt.__version__) >= mod.version("8.7") + else [] + ), ) def test_network_flags(self, kwargs, flag): builder, network, parser = network_from_onnx_path( @@ -539,6 +579,7 @@ def test_onnx_like_from_network(self, model_name): NetworkFromOnnxBytes(ONNX_MODELS[model_name].loader) ) + class TestDefaultPlugins: def test_default_plugins(self): network_loader = NetworkFromOnnxBytes(ONNX_MODELS["roialign"].loader) diff --git a/tools/Polygraphy/tests/backend/trt/test_profile.py b/tools/Polygraphy/tests/backend/trt/test_profile.py index 2a8d5ad6..5cb66137 100644 --- a/tools/Polygraphy/tests/backend/trt/test_profile.py +++ b/tools/Polygraphy/tests/backend/trt/test_profile.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,7 +24,9 @@ @pytest.fixture(scope="session") def dynamic_identity_network(): - builder, network, parser = network_from_onnx_bytes(ONNX_MODELS["dynamic_identity"].loader) + builder, network, parser = network_from_onnx_bytes( + ONNX_MODELS["dynamic_identity"].loader + ) with builder, network, parser: yield builder, network, parser @@ -55,8 +57,18 @@ def test_fill_defaults_scalar_shape_tensor(self): # Need to add some other operations so TensorRT treats `fill_shape` as a shape tensor. fill = network.add_fill(tuple(), trt.FillOperation.LINSPACE) fill.set_input(0, fill_shape) - fill.set_input(1, network.add_constant(shape=tuple(), weights=np.array(0).astype(np.int32)).get_output(0)) - fill.set_input(2, network.add_constant(shape=tuple(), weights=np.array(1).astype(np.int32)).get_output(0)) + fill.set_input( + 1, + network.add_constant( + shape=tuple(), weights=np.array(0).astype(np.int32) + ).get_output(0), + ) + fill.set_input( + 2, + network.add_constant( + shape=tuple(), weights=np.array(1).astype(np.int32) + ).get_output(0), + ) network.mark_output(fill.get_output(0)) diff --git a/tools/Polygraphy/tests/backend/trt/test_runner.py b/tools/Polygraphy/tests/backend/trt/test_runner.py index 7b9f411f..601aff24 100644 --- a/tools/Polygraphy/tests/backend/trt/test_runner.py +++ b/tools/Polygraphy/tests/backend/trt/test_runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -79,7 +79,9 @@ def test_basic(self, identity_engine): assert not runner.is_active @pytest.mark.serial - def test_warn_if_impl_methods_called(self, check_warnings_on_runner_impl_methods, identity_engine): + def test_warn_if_impl_methods_called( + self, check_warnings_on_runner_impl_methods, identity_engine + ): runner = TrtRunner(identity_engine) check_warnings_on_runner_impl_methods(runner) @@ -96,23 +98,34 @@ def test_data_dependent_shapes(self, nonzero_engine, inp, expected): outputs = runner.infer( { "input": np.array( - inp, dtype=np.int32 if mod.version(trt.__version__) < mod.version("9.0") else np.int64 + inp, + dtype=( + np.int32 + if mod.version(trt.__version__) < mod.version("9.0") + else np.int64 + ), ) } ) - assert np.array_equal(outputs["nonzero_out_0"], np.array(expected, dtype=np.int32)) + assert np.array_equal( + outputs["nonzero_out_0"], np.array(expected, dtype=np.int32) + ) @pytest.mark.parametrize("copy_outputs_to_host", [True, False]) @pytest.mark.parametrize("device", ["cpu", "cuda"]) def test_torch_tensors(self, copy_outputs_to_host, identity_engine, device): with TrtRunner(identity_engine) as runner: arr = torch.ones([1, 1, 2, 2], dtype=torch.float32, device=device) - outputs = runner.infer({"x": arr}, copy_outputs_to_host=copy_outputs_to_host) + outputs = runner.infer( + {"x": arr}, copy_outputs_to_host=copy_outputs_to_host + ) assert all(isinstance(t, torch.Tensor) for t in outputs.values()) assert torch.equal(outputs["y"].to("cpu"), arr.to("cpu")) - assert outputs["y"].device.type == ("cpu" if copy_outputs_to_host else "cuda") + assert outputs["y"].device.type == ( + "cpu" if copy_outputs_to_host else "cuda" + ) def test_context(self, identity_engine): with TrtRunner(identity_engine.create_execution_context) as runner: @@ -131,9 +144,15 @@ def test_shape_output(self): model.check_runner(runner) def test_multithreaded_runners_from_engine(self, identity_engine): - with TrtRunner(identity_engine) as runner0, TrtRunner(identity_engine) as runner1: - t1 = threading.Thread(target=ONNX_MODELS["identity"].check_runner, args=(runner0,)) - t2 = threading.Thread(target=ONNX_MODELS["identity"].check_runner, args=(runner1,)) + with TrtRunner(identity_engine) as runner0, TrtRunner( + identity_engine + ) as runner1: + t1 = threading.Thread( + target=ONNX_MODELS["identity"].check_runner, args=(runner0,) + ) + t2 = threading.Thread( + target=ONNX_MODELS["identity"].check_runner, args=(runner1,) + ) t1.start() t2.start() t1.join() @@ -141,12 +160,17 @@ def test_multithreaded_runners_from_engine(self, identity_engine): @pytest.mark.parametrize("use_optimization_profile", [True, False]) @pytest.mark.skipif( - mod.version(trt.__version__) >= mod.version("8.6") and mod.version(trt.__version__) < mod.version("8.7"), + mod.version(trt.__version__) >= mod.version("8.6") + and mod.version(trt.__version__) < mod.version("8.7"), reason="Bug in TRT 8.6", ) def test_multiple_profiles(self, use_optimization_profile): model = ONNX_MODELS["dynamic_identity"] - profile0_shapes = [(1, 2, 1, 1), (1, 2, 1, 1), (1, 2, 1, 1)] # Use min==opt==max to fix shapes in the engine. + profile0_shapes = [ + (1, 2, 1, 1), + (1, 2, 1, 1), + (1, 2, 1, 1), + ] # Use min==opt==max to fix shapes in the engine. profile1_shapes = [(1, 2, 1, 1), (1, 2, 2, 2), (1, 2, 4, 4)] profile2_shapes = [(1, 2, 4, 4), (1, 2, 8, 8), (1, 2, 16, 16)] network_loader = NetworkFromOnnxBytes(model.loader) @@ -159,7 +183,9 @@ def test_multiple_profiles(self, use_optimization_profile): engine = engine_from_network(network_loader, config_loader) context = engine.create_execution_context() - for index, shapes in enumerate([profile0_shapes, profile1_shapes, profile2_shapes]): + for index, shapes in enumerate( + [profile0_shapes, profile1_shapes, profile2_shapes] + ): with TrtRunner( context, optimization_profile=index if use_optimization_profile else None, @@ -171,12 +197,13 @@ def test_multiple_profiles(self, use_optimization_profile): for shape in shapes: model.check_runner(runner, {"X": shape}) - @pytest.mark.skipif( mod.version(trt.__version__) < mod.version("10.0"), reason="Feature not present before 10.0", ) - @pytest.mark.parametrize("allocation_strategy", [None, "static", "profile", "runtime"]) + @pytest.mark.parametrize( + "allocation_strategy", [None, "static", "profile", "runtime"] + ) def test_allocation_strategies(self, allocation_strategy): model = ONNX_MODELS["residual_block"] profile0_shapes = [(1, 3, 224, 224), (1, 3, 224, 224), (1, 3, 224, 224)] @@ -191,7 +218,9 @@ def test_allocation_strategies(self, allocation_strategy): config_loader = CreateConfig(profiles=profiles) engine = engine_from_network(network_loader, config_loader) - for index, shapes in enumerate([profile0_shapes, profile1_shapes, profile2_shapes]): + for index, shapes in enumerate( + [profile0_shapes, profile1_shapes, profile2_shapes] + ): with TrtRunner( engine, optimization_profile=index, @@ -200,7 +229,6 @@ def test_allocation_strategies(self, allocation_strategy): for shape in shapes: model.check_runner(runner, {"gpu_0/data_0": shape}) - def test_empty_tensor_with_dynamic_input_shape_tensor(self): model = ONNX_MODELS["empty_tensor_expand"] shapes = [(1, 2, 0, 3, 0), (2, 2, 0, 3, 0), (4, 2, 0, 3, 0)] @@ -224,7 +252,12 @@ def test_empty_tensor_with_dynamic_input_shape_tensor(self): def test_error_on_wrong_name_feed_dict(self, names, err, identity_engine, module): with TrtRunner(identity_engine) as runner: with pytest.raises(PolygraphyException, match=err): - runner.infer({name: module.ones((1, 1, 2, 2), dtype=module.float32) for name in names}) + runner.infer( + { + name: module.ones((1, 1, 2, 2), dtype=module.float32) + for name in names + } + ) @pytest.mark.parametrize("module", [torch, np]) def test_error_on_wrong_dtype_feed_dict(self, identity_engine, module): @@ -238,9 +271,13 @@ def test_error_on_wrong_shape_feed_dict(self, identity_engine, module): with pytest.raises(PolygraphyException, match="incompatible shape."): runner.infer({"x": module.ones((1, 1, 3, 2), dtype=module.float32)}) - @pytest.mark.parametrize("use_view", [True, False]) # We should be able to use DeviceArray in place of DeviceView + @pytest.mark.parametrize( + "use_view", [True, False] + ) # We should be able to use DeviceArray in place of DeviceView def test_device_views(self, use_view, reducable_engine): - with TrtRunner(reducable_engine) as runner, cuda.DeviceArray((1,), dtype=np.float32) as x: + with TrtRunner(reducable_engine) as runner, cuda.DeviceArray( + (1,), dtype=np.float32 + ) as x: x.copy_from(np.ones((1,), dtype=np.float32)) outputs = runner.infer( { @@ -266,36 +303,71 @@ def check(outputs): assert np.all(outputs["y"] == inp) check(runner.infer({"x": inp})) - check(runner.infer({"x": cuda.DeviceArray(shape=inp.shape, dtype=inp.dtype).copy_from(inp)})) + check( + runner.infer( + { + "x": cuda.DeviceArray( + shape=inp.shape, dtype=inp.dtype + ).copy_from(inp) + } + ) + ) torch_outputs = runner.infer({"x": torch.from_numpy(inp)}) check({name: out.numpy() for name, out in torch_outputs.items()}) check(runner.infer({"x": inp})) - @pytest.mark.parametrize("use_view", [True, False]) # We should be able to use DeviceArray in place of DeviceView + @pytest.mark.parametrize( + "use_view", [True, False] + ) # We should be able to use DeviceArray in place of DeviceView def test_device_view_dynamic_shapes(self, use_view): model = ONNX_MODELS["dynamic_identity"] profiles = [ Profile().add("X", (1, 2, 1, 1), (1, 2, 2, 2), (1, 2, 4, 4)), ] - runner = TrtRunner(EngineFromNetwork(NetworkFromOnnxBytes(model.loader), CreateConfig(profiles=profiles))) + runner = TrtRunner( + EngineFromNetwork( + NetworkFromOnnxBytes(model.loader), CreateConfig(profiles=profiles) + ) + ) with runner, cuda.DeviceArray(shape=(1, 2, 3, 3), dtype=np.float32) as arr: inp = np.random.random_sample(size=(1, 2, 3, 3)).astype(np.float32) arr.copy_from(inp) - outputs = runner.infer({"X": cuda.DeviceView(arr.ptr, arr.shape, arr.dtype) if use_view else arr}) + outputs = runner.infer( + { + "X": ( + cuda.DeviceView(arr.ptr, arr.shape, arr.dtype) + if use_view + else arr + ) + } + ) assert np.all(outputs["Y"] == inp) assert outputs["Y"].shape == (1, 2, 3, 3) def test_cannot_use_device_view_shape_tensor(self): model = ONNX_MODELS["empty_tensor_expand"] - with TrtRunner(EngineFromNetwork(NetworkFromOnnxBytes(model.loader))) as runner, cuda.DeviceArray( - shape=(5,), dtype=np.int32 if mod.version(trt.__version__) < mod.version("9.0") else np.int64 + with TrtRunner( + EngineFromNetwork(NetworkFromOnnxBytes(model.loader)) + ) as runner, cuda.DeviceArray( + shape=(5,), + dtype=( + np.int32 + if mod.version(trt.__version__) < mod.version("9.0") + else np.int64 + ), ) as arr: - with pytest.raises(PolygraphyException, match="it must reside in host memory"): - runner.infer({"data": np.ones((2, 0, 3, 0), dtype=np.float32), "new_shape": arr}) + with pytest.raises( + PolygraphyException, match="it must reside in host memory" + ): + runner.infer( + {"data": np.ones((2, 0, 3, 0), dtype=np.float32), "new_shape": arr} + ) @pytest.mark.parametrize("hwc_input", [True, False], ids=["hwc_input", "chw_input"]) - @pytest.mark.parametrize("hwc_output", [True, False], ids=["hwc_output", "chw_output"]) + @pytest.mark.parametrize( + "hwc_output", [True, False], ids=["hwc_output", "chw_output"] + ) def test_infer_chw_format(self, hwc_input, hwc_output): model = ONNX_MODELS["identity_multi_ch"] inp_shape = model.input_metadata["x"].shape @@ -317,7 +389,9 @@ def test_infer_chw_format(self, hwc_input, hwc_output): outputs = runner.infer({"x": inp}) if hwc_input == hwc_output: # output in CHW/HWC format and similarly shaped assert np.allclose(outputs["y"], inp) - elif not hwc_input and hwc_output: # output in HWC format and shaped (N, H, W, C) + elif ( + not hwc_input and hwc_output + ): # output in HWC format and shaped (N, H, W, C) assert np.allclose(outputs["y"].transpose(0, 3, 1, 2), inp) else: # hwc_input and not hwc_output: output in CHW format and shaped (N, C, H, W) assert np.allclose(outputs["y"].transpose(0, 2, 3, 1), inp) @@ -328,7 +402,9 @@ def test_get_array_on_cpu(self, use_torch): with cuda.DeviceArray.raw(shape) as arr: host_buffers = {} stream = cuda.Stream() - host_arr = _get_array_on_cpu(arr, "test", host_buffers, stream, arr.nbytes, use_torch) + host_arr = _get_array_on_cpu( + arr, "test", host_buffers, stream, arr.nbytes, use_torch + ) if use_torch: assert isinstance(host_arr, torch.Tensor) @@ -345,24 +421,17 @@ def test_weight_streaming(self, budget): network_loader = NetworkFromOnnxBytes(model.loader, strongly_typed=True) config_loader = CreateConfig(weight_streaming=True) engine = engine_from_network(network_loader, config_loader) - + if budget == np.inf: # set to max size - 1 budget = engine.streamable_weights_size - 1 - kwargs = { - "weight_streaming_budget": None, - "weight_streaming_percent": None - } + kwargs = {"weight_streaming_budget": None, "weight_streaming_percent": None} if budget is not None: if 0 < budget <= 1: kwargs["weight_streaming_percent"] = budget * 100 else: kwargs["weight_streaming_budget"] = int(budget) - with TrtRunner( - engine, - optimization_profile=0, - **kwargs - ) as runner: + with TrtRunner(engine, optimization_profile=0, **kwargs) as runner: model.check_runner(runner) diff --git a/tools/Polygraphy/tests/backend/trt/test_util.py b/tools/Polygraphy/tests/backend/trt/test_util.py index 1bd9d44c..730e55d6 100644 --- a/tools/Polygraphy/tests/backend/trt/test_util.py +++ b/tools/Polygraphy/tests/backend/trt/test_util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/common/test_datatype.py b/tools/Polygraphy/tests/common/test_datatype.py index c9e5108a..c1515e18 100644 --- a/tools/Polygraphy/tests/common/test_datatype.py +++ b/tools/Polygraphy/tests/common/test_datatype.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/common/test_interface.py b/tools/Polygraphy/tests/common/test_interface.py index 72d731b7..85ea4dcb 100644 --- a/tools/Polygraphy/tests/common/test_interface.py +++ b/tools/Polygraphy/tests/common/test_interface.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/common/test_struct.py b/tools/Polygraphy/tests/common/test_struct.py index 76c95b93..2014a66d 100644 --- a/tools/Polygraphy/tests/common/test_struct.py +++ b/tools/Polygraphy/tests/common/test_struct.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -17,6 +17,7 @@ from polygraphy.common import TensorMetadata from polygraphy.datatype import DataType + class TestTensorMetadata: def test_str(self): meta = TensorMetadata().add("X", dtype=DataType.FLOAT32, shape=(64, 64)) diff --git a/tools/Polygraphy/tests/comparator/test_comparator.py b/tools/Polygraphy/tests/comparator/test_comparator.py index 720b5549..e5c0e2d6 100644 --- a/tools/Polygraphy/tests/comparator/test_comparator.py +++ b/tools/Polygraphy/tests/comparator/test_comparator.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,9 +24,21 @@ from polygraphy.backend.onnx import GsFromOnnx, OnnxFromBytes from polygraphy.backend.onnxrt import OnnxrtRunner, SessionFromOnnx from polygraphy.backend.pluginref import PluginRefRunner -from polygraphy.backend.trt import EngineFromNetwork, NetworkFromOnnxBytes, TrtRunner, network_from_onnx_bytes +from polygraphy.backend.trt import ( + EngineFromNetwork, + NetworkFromOnnxBytes, + TrtRunner, + network_from_onnx_bytes, +) from polygraphy.backend.trt.util import get_all_tensors -from polygraphy.comparator import Comparator, CompareFunc, DataLoader, IterationResult, PostprocessFunc, RunResults +from polygraphy.comparator import ( + Comparator, + CompareFunc, + DataLoader, + IterationResult, + PostprocessFunc, + RunResults, +) from polygraphy.exception import PolygraphyException from tests.models.meta import ONNX_MODELS @@ -86,7 +98,9 @@ def test_postprocess(self): onnx_loader = ONNX_MODELS["identity"].loader run_results = Comparator.run([OnnxrtRunner(SessionFromOnnx(onnx_loader))]) # Output shape is (1, 1, 2, 2) - postprocessed = Comparator.postprocess(run_results, postprocess_func=PostprocessFunc.top_k(k=(1, -1))) + postprocessed = Comparator.postprocess( + run_results, postprocess_func=PostprocessFunc.top_k(k=(1, -1)) + ) for _, results in postprocessed.items(): for result in results: for _, output in result.items(): @@ -126,13 +140,17 @@ def test_multirun_outputs_are_different(self): @pytest.mark.parametrize("array_type", [np.array, build_torch]) def test_validate_nan(self, array_type): run_results = RunResults() - run_results["fake-runner"] = [IterationResult(outputs={"x": array_type(np.nan)})] + run_results["fake-runner"] = [ + IterationResult(outputs={"x": array_type(np.nan)}) + ] assert not Comparator.validate(run_results) @pytest.mark.parametrize("array_type", [np.array, build_torch]) def test_validate_inf(self, array_type): run_results = RunResults() - run_results["fake-runner"] = [IterationResult(outputs={"x": array_type(np.inf)})] + run_results["fake-runner"] = [ + IterationResult(outputs={"x": array_type(np.inf)}) + ] assert not Comparator.validate(run_results, check_inf=True) def test_dim_param_trt_onnxrt(self): @@ -154,7 +172,7 @@ def test_dim_param_trt_onnxrt(self): mod.version(trt.__version__) < mod.version("10.0"), reason="Feature not present before 10.0", ) - def test_debug_tensors(self): + def test_debug_tensors(self): model = ONNX_MODELS["identity"] builder, network, parser = network_from_onnx_bytes(model.loader) tensor_map = get_all_tensors(network) @@ -165,7 +183,14 @@ def test_debug_tensors(self): run_results = Comparator.run(runners, data_loader=data) for iteration_list in run_results.values(): # There should be 2 outputs, debug tensor "x" and output "y" - assert len(list(iteration_list[0].items())) == 2 - run_results["fake-runner"] = [IterationResult(outputs={"x": np.ones((1, 1, 2, 2), dtype=np.float32), "y": np.ones((1, 1, 2, 2), dtype=np.float32)})] + assert len(list(iteration_list[0].items())) == 2 + run_results["fake-runner"] = [ + IterationResult( + outputs={ + "x": np.ones((1, 1, 2, 2), dtype=np.float32), + "y": np.ones((1, 1, 2, 2), dtype=np.float32), + } + ) + ] compare_func = CompareFunc.simple(check_shapes=True) assert bool(Comparator.compare_accuracy(run_results, compare_func=compare_func)) diff --git a/tools/Polygraphy/tests/comparator/test_compare.py b/tools/Polygraphy/tests/comparator/test_compare.py index 97f255c4..d093d96e 100644 --- a/tools/Polygraphy/tests/comparator/test_compare.py +++ b/tools/Polygraphy/tests/comparator/test_compare.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/comparator/test_data_loader.py b/tools/Polygraphy/tests/comparator/test_data_loader.py index f60fea40..dee4ddab 100644 --- a/tools/Polygraphy/tests/comparator/test_data_loader.py +++ b/tools/Polygraphy/tests/comparator/test_data_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/comparator/test_postprocess.py b/tools/Polygraphy/tests/comparator/test_postprocess.py index c4c2a0d9..a4d82ad6 100644 --- a/tools/Polygraphy/tests/comparator/test_postprocess.py +++ b/tools/Polygraphy/tests/comparator/test_postprocess.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -21,6 +21,7 @@ build_torch = lambda a, **kwargs: util.array.to_torch(np.array(a, **kwargs)) + @pytest.mark.parametrize("array_type", [np.array, build_torch]) class TestTopK: def test_basic(self, array_type): diff --git a/tools/Polygraphy/tests/comparator/test_struct.py b/tools/Polygraphy/tests/comparator/test_struct.py index bbb720f2..316f8020 100644 --- a/tools/Polygraphy/tests/comparator/test_struct.py +++ b/tools/Polygraphy/tests/comparator/test_struct.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -106,7 +106,9 @@ def test_add_new(self): iter_results = results["custom"] assert len(iter_results) == 1 - assert all(isinstance(iter_result, IterationResult) for iter_result in iter_results) + assert all( + isinstance(iter_result, IterationResult) for iter_result in iter_results + ) def test_add_new_default_name(self): results = RunResults() @@ -115,7 +117,9 @@ def test_add_new_default_name(self): name = results[0][0] iter_results = results[name] assert len(iter_results) == 1 - assert all(isinstance(iter_result, IterationResult) for iter_result in iter_results) + assert all( + isinstance(iter_result, IterationResult) for iter_result in iter_results + ) @pytest.mark.parametrize("module", [torch, np]) diff --git a/tools/Polygraphy/tests/conftest.py b/tools/Polygraphy/tests/conftest.py index b9a80865..610ed04a 100644 --- a/tools/Polygraphy/tests/conftest.py +++ b/tools/Polygraphy/tests/conftest.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -34,7 +34,9 @@ def sandboxed_install_run(virtualenv, script_runner): Packages from the test environment are still usable, but those in the virtual environment take precedence """ - VENV_PYTHONPATH = glob.glob(os.path.join(virtualenv.virtualenv, "lib", "python*", "site-packages"))[0] + VENV_PYTHONPATH = glob.glob( + os.path.join(virtualenv.virtualenv, "lib", "python*", "site-packages") + )[0] class StatusWrapper: def __init__(self, stdout=None, stderr=None, success=None) -> None: @@ -58,7 +60,9 @@ def run_impl(command, cwd=None): status.stdout = sr_status.stdout status.success = sr_status.success else: - sp_status = sp.run(command, cwd=cwd, env=env, stdout=sp.PIPE, stderr=sp.PIPE) + sp_status = sp.run( + command, cwd=cwd, env=env, stdout=sp.PIPE, stderr=sp.PIPE + ) def try_decode(inp): try: @@ -124,7 +128,10 @@ def check_warning(method, warning_expected): metadata = runner.get_input_metadata_impl() runner.infer_impl( { - name: np.ones(shape, dtype=DataType.to_dtype(DataType.from_dtype(dtype), "numpy")) + name: np.ones( + shape, + dtype=DataType.to_dtype(DataType.from_dtype(dtype), "numpy"), + ) for name, (dtype, shape) in metadata.items() } ) @@ -174,10 +181,15 @@ def check(loader): @pytest.fixture() -@pytest.mark.skipif(sys.platform.startswith("win"), reason="Fixture has not been updated to work on Windows") +@pytest.mark.skipif( + sys.platform.startswith("win"), + reason="Fixture has not been updated to work on Windows", +) def nvinfer_lean_path(): lean_library_name = ctypes.util.find_library("nvinfer_lean") - for dirname in os.environ.get("LD_LIBRARY_PATH", "").split(os.path.pathsep) + ["/usr/lib/x86_64-linux-gnu"]: + for dirname in os.environ.get("LD_LIBRARY_PATH", "").split(os.path.pathsep) + [ + "/usr/lib/x86_64-linux-gnu" + ]: path = os.path.join(dirname, lean_library_name) if os.path.exists(path): return path diff --git a/tools/Polygraphy/tests/cuda/test_cuda.py b/tools/Polygraphy/tests/cuda/test_cuda.py index 213f8724..7552f2f6 100644 --- a/tools/Polygraphy/tests/cuda/test_cuda.py +++ b/tools/Polygraphy/tests/cuda/test_cuda.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/func/test_func.py b/tools/Polygraphy/tests/func/test_func.py index 3685855a..7ae5eb5d 100644 --- a/tools/Polygraphy/tests/func/test_func.py +++ b/tools/Polygraphy/tests/func/test_func.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -130,7 +130,8 @@ def x(): return 1, 2 with pytest.raises( - PolygraphyException, match=r"Function: y accepts 1 parameter\(s\), but needs to accept 2 parameter\(s\)" + PolygraphyException, + match=r"Function: y accepts 1 parameter\(s\), but needs to accept 2 parameter\(s\)", ): @func.extend(x) @@ -168,7 +169,9 @@ def modify_x(self): self.x = 2 d = Dummy() - with pytest.raises(PolygraphyInternalException, match="was mutated in a constant method"): + with pytest.raises( + PolygraphyInternalException, match="was mutated in a constant method" + ): d.modify_x() def test_cannot_add_attrs(self): @@ -178,5 +181,7 @@ def modify_x(self): self.x = 2 d = Dummy() - with pytest.raises(PolygraphyInternalException, match="was mutated in a constant method"): + with pytest.raises( + PolygraphyInternalException, match="was mutated in a constant method" + ): d.modify_x() diff --git a/tools/Polygraphy/tests/helper.py b/tools/Polygraphy/tests/helper.py index e4141c72..629f7ecd 100644 --- a/tools/Polygraphy/tests/helper.py +++ b/tools/Polygraphy/tests/helper.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -28,7 +28,14 @@ "convert": [], "inspect": ["data", "model", "tactics", "capability", "diff-tactics"], "check": ["lint"], - "surgeon": ["extract", "insert", "sanitize", "prune", "weight-strip", "weight-reconstruct"], + "surgeon": [ + "extract", + "insert", + "sanitize", + "prune", + "weight-strip", + "weight-reconstruct", + ], "template": ["trt-network", "trt-config", "onnx-gs"], "debug": ["build", "precision", "reduce", "repeat"], "data": ["to-input"], diff --git a/tools/Polygraphy/tests/logger/test_logger.py b/tools/Polygraphy/tests/logger/test_logger.py index e7652829..226740e0 100644 --- a/tools/Polygraphy/tests/logger/test_logger.py +++ b/tools/Polygraphy/tests/logger/test_logger.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -41,7 +41,10 @@ def test_line_info(self): logger.info("Hello") log_file.seek(0) - assert f"[I] [tests/logger/test_logger.py:{inspect.currentframe().f_lineno - 2}] Hello\n" == log_file.read() + assert ( + f"[I] [tests/logger/test_logger.py:{inspect.currentframe().f_lineno - 5}] Hello\n" + == log_file.read() + ) def test_severity_trie_with_no_default(self): logger = Logger(severity={"backend/trt": 10}) @@ -91,5 +94,12 @@ class TestSeverityTrie: ) def test_get(self, path, sev): # Duplicate slashes should be handled - trie = SeverityTrie({"": 30, "backend/trt": 20, "backend/trt/loader.py": 50, "backend///////onnx": 28}) + trie = SeverityTrie( + { + "": 30, + "backend/trt": 20, + "backend/trt/loader.py": 50, + "backend///////onnx": 28, + } + ) assert trie.get(path) == sev diff --git a/tools/Polygraphy/tests/mod/conftest.py b/tools/Polygraphy/tests/mod/conftest.py index 89e4d02f..4e6a1d13 100644 --- a/tools/Polygraphy/tests/mod/conftest.py +++ b/tools/Polygraphy/tests/mod/conftest.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/mod/test_dependencies.py b/tools/Polygraphy/tests/mod/test_dependencies.py index 859bd1f9..bba06716 100644 --- a/tools/Polygraphy/tests/mod/test_dependencies.py +++ b/tools/Polygraphy/tests/mod/test_dependencies.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -34,7 +34,11 @@ def is_submodule(path): - file_mod = os.path.isfile(path) and path.endswith(".py") and os.path.basename(path) != "__init__.py" + file_mod = ( + os.path.isfile(path) + and path.endswith(".py") + and os.path.basename(path) != "__init__.py" + ) dir_mod = os.path.isdir(path) and os.path.isfile(os.path.join(path, "__init__.py")) return file_mod or dir_mod @@ -148,7 +152,9 @@ def get_colored_version(): ("==1.4.2", "==1.4.2"), ], ) - def test_can_automatically_install_requirements(self, poly_venv, new_ver, expected, preinstall): + def test_can_automatically_install_requirements( + self, poly_venv, new_ver, expected, preinstall + ): poly_venv.env["POLYGRAPHY_AUTOINSTALL_DEPS"] = "1" def get_colored_version(): @@ -211,7 +217,9 @@ def test_ask_before_autoinstall(self, response, should_install, poly_venv): [ poly_venv.python, "-c", - "from polygraphy import mod; " "colored = mod.lazy_import('colored'); " "mod.autoinstall(colored)", + "from polygraphy import mod; " + "colored = mod.lazy_import('colored'); " + "mod.autoinstall(colored)", ], env=poly_venv.env, stdin=sp.PIPE, diff --git a/tools/Polygraphy/tests/mod/test_exporter.py b/tools/Polygraphy/tests/mod/test_exporter.py index 624a84e6..d8686ad3 100644 --- a/tools/Polygraphy/tests/mod/test_exporter.py +++ b/tools/Polygraphy/tests/mod/test_exporter.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -54,7 +54,9 @@ def __init__(self, x): self.x = x def test_funcify_duplicate_parameters_in_call_init(self): - with pytest.raises(AssertionError, match="call_impl and __init__ have the same argument names"): + with pytest.raises( + AssertionError, match="call_impl and __init__ have the same argument names" + ): @mod.export(funcify=True) class DupArgs(BaseLoader): @@ -78,7 +80,10 @@ def call_impl(self): assert "DocstringFunctor" in __all__ assert "docstring_functor" in __all__ - assert docstring_functor.__doc__ == "Immediately evaluated functional variant of :class:`DocstringFunctor` .\n" + assert ( + docstring_functor.__doc__ + == "Immediately evaluated functional variant of :class:`DocstringFunctor` .\n" + ) def test_funcify_functor_no_call_args(self): @mod.export(funcify=True) diff --git a/tools/Polygraphy/tests/mod/test_importer.py b/tools/Polygraphy/tests/mod/test_importer.py index dc269b8c..5c2dcdc2 100644 --- a/tools/Polygraphy/tests/mod/test_importer.py +++ b/tools/Polygraphy/tests/mod/test_importer.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -21,11 +21,13 @@ from textwrap import dedent import pytest +import tempfile import tensorrt as trt from polygraphy import mod, util from polygraphy.exception import PolygraphyException from polygraphy.mod.importer import _version_ok +common_backend = mod.lazy_import("polygraphy.backend.common") class TestImporter: def test_import_from_script(self): @@ -59,6 +61,41 @@ def load_network(builder, network): assert network.get_layer(0).type == trt.LayerType.IDENTITY assert sys.path == orig_sys_path + def test_import_from_script_same_method_different_modules(self): + module1_script = dedent( + """ + def print_message(): + print(f"msg1::print_message") + return "msg1" + """ + ) + + module2_script = dedent( + """ + def print_message(): + print(f"msg2::print_message") + return "msg2" + """ + ) + + with tempfile.TemporaryDirectory() as tempdir: + os.mkdir(os.path.join(tempdir, "msg1")) + with open(os.path.join(tempdir, "msg1", "msg.py"), "w+") as msg1_msg: + msg1_msg.write(module1_script) + msg1_msg.flush() + os.fsync(msg1_msg.fileno()) + + os.mkdir(os.path.join(tempdir, "msg2")) + with open(os.path.join(tempdir, "msg2", "msg.py"), "w+") as msg2_msg: + msg2_msg.write(module2_script) + msg2_msg.flush() + os.fsync(msg2_msg.fileno()) + + for msg_module in ['msg1', 'msg2']: + msg_loc = os.path.join(tempdir,msg_module,'msg.py') + msg = common_backend.invoke_from_script(msg_loc, "print_message") + assert msg==msg_module + def test_import_non_existent(self): script = dedent( """ diff --git a/tools/Polygraphy/tests/mod/test_util.py b/tools/Polygraphy/tests/mod/test_util.py index beb16268..310665b5 100644 --- a/tools/Polygraphy/tests/mod/test_util.py +++ b/tools/Polygraphy/tests/mod/test_util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/models/make_models.py b/tools/Polygraphy/tests/models/make_models.py index cf3a1e3a..759a5bc0 100644 --- a/tools/Polygraphy/tests/models/make_models.py +++ b/tools/Polygraphy/tests/models/make_models.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,6 +27,7 @@ import onnx_graphsurgeon as gs from meta import ONNX_MODELS from polygraphy.tools.sparse import SparsityPruner + CURDIR = os.path.dirname(__file__) @@ -54,12 +55,16 @@ def sub(self, a, b, **kwargs): @gs.Graph.register() def constant(self, values: gs.Constant, **kwargs): - return self.layer(op="Constant", outputs=["constant_out"], attrs={"value": values}, **kwargs)[0] + return self.layer( + op="Constant", outputs=["constant_out"], attrs={"value": values}, **kwargs + )[0] @gs.Graph.register() def reshape(self, data, shape, **kwargs): - return self.layer(op="Reshape", inputs=[data, shape], outputs=["reshape_out"], **kwargs)[0] + return self.layer( + op="Reshape", inputs=[data, shape], outputs=["reshape_out"], **kwargs + )[0] @gs.Graph.register() @@ -76,28 +81,41 @@ def tile(self, inp, repeats): def nonzero(self, inp, **kwargs): return self.layer(op="NonZero", inputs=[inp], outputs=["nonzero_out"], **kwargs)[0] + # Name range as onnx_range as range is a python built-in function. @gs.Graph.register() def onnx_range(self, start, limit, delta, **kwargs): - return self.layer(op="Range", inputs=[start, limit, delta], outputs=["range_out"], **kwargs)[0] + return self.layer( + op="Range", inputs=[start, limit, delta], outputs=["range_out"], **kwargs + )[0] @gs.Graph.register() def cast(self, input, type, **kwargs): - return self.layer(op="Cast", inputs=[input], attrs={"to": type}, outputs=["cast_out"], **kwargs)[0] + return self.layer( + op="Cast", inputs=[input], attrs={"to": type}, outputs=["cast_out"], **kwargs + )[0] @gs.Graph.register() def reduce_max(self, input, keep_dims, **kwargs): return self.layer( - op="ReduceMax", inputs=[input], attrs={"keepdims": keep_dims}, outputs=["reduce_max_out"], **kwargs + op="ReduceMax", + inputs=[input], + attrs={"keepdims": keep_dims}, + outputs=["reduce_max_out"], + **kwargs, )[0] @gs.Graph.register() def conv(self, input, weights, kernel_shape, **kwargs): return self.layer( - op="Conv", inputs=[input, weights], attrs={"kernel_shape": kernel_shape}, outputs=["conv_out"], **kwargs + op="Conv", + inputs=[input, weights], + attrs={"kernel_shape": kernel_shape}, + outputs=["conv_out"], + **kwargs, )[0] @@ -110,42 +128,45 @@ def split(self, inp, split, axis=0): attrs={"axis": axis, "split": split}, ) + @gs.Graph.register() def transpose(self, inp, **kwargs): return self.layer( - op="Transpose", - inputs=[inp], - outputs=["transpose_out"], - **kwargs + op="Transpose", inputs=[inp], outputs=["transpose_out"], **kwargs )[0] + @gs.Graph.register() def quantize_linear(self, inp, y_scale, y_zero_point, **kwargs): return self.layer( op="QuantizeLinear", inputs=[inp, y_scale, y_zero_point], outputs=["quantize_linear_out"], - **kwargs + **kwargs, )[0] + @gs.Graph.register() def dequantize_linear(self, inp, x_scale, x_zero_point, **kwargs): return self.layer( op="DequantizeLinear", inputs=[inp, x_scale, x_zero_point], outputs=["dequantize_linear_out"], - **kwargs + **kwargs, )[0] + def save(graph, model_name): path = os.path.join(CURDIR, model_name) print(f"Writing: {path}") onnx.save(gs.export_onnx(graph), path) + def make_sparse(graph): sparsity_pruner = SparsityPruner(gs.export_onnx(graph)) return gs.import_onnx(sparsity_pruner.prune()) + # Generates a model with multiple inputs/outputs: # # X0 Y0 @@ -286,10 +307,16 @@ def make_needs_constraints(): x = gs.Variable("x", shape=(1, 1, SIZE, SIZE), dtype=np.float32) I_rot90 = gs.Constant( - name="I_rot90", values=np.rot90(np.identity(SIZE, dtype=np.float32).reshape((1, 1, SIZE, SIZE))) + name="I_rot90", + values=np.rot90( + np.identity(SIZE, dtype=np.float32).reshape((1, 1, SIZE, SIZE)) + ), ) fp16_max = gs.Constant( - name="fp16_max", values=np.array([np.finfo(np.float16).max], dtype=np.float32).reshape((1, 1, 1, 1)) + name="fp16_max", + values=np.array([np.finfo(np.float16).max], dtype=np.float32).reshape( + (1, 1, 1, 1) + ), ) graph = gs.Graph(inputs=[x]) @@ -318,7 +345,9 @@ def make_needs_constraints(): def make_constant_fold_bloater(): graph = gs.Graph() # Input is 1MiB, tiled to 10MiB - out = graph.tile(np.ones(shape=(1024, 256), dtype=np.float32), repeats=np.array([1, 10])) + out = graph.tile( + np.ones(shape=(1024, 256), dtype=np.float32), repeats=np.array([1, 10]) + ) out.dtype = np.float32 graph.outputs = [out] @@ -404,17 +433,29 @@ def make_multi_output(): def make_unbounded_dds(): input = gs.Variable("Input", shape=(1, 3, 10, 10), dtype=np.float32) graph = gs.Graph(inputs=[input], opset=13) - weights_0 = graph.constant(gs.Constant("Weights_0", values=np.ones((3, 3, 3, 3), dtype=np.float32))) - weights_1 = graph.constant(gs.Constant("Weights_1", values=np.ones((4, 1, 1, 1), dtype=np.float32))) + weights_0 = graph.constant( + gs.Constant("Weights_0", values=np.ones((3, 3, 3, 3), dtype=np.float32)) + ) + weights_1 = graph.constant( + gs.Constant("Weights_1", values=np.ones((4, 1, 1, 1), dtype=np.float32)) + ) conv_0 = graph.conv(input, weights_0, [3, 3], name="Conv_0") reduce_max_0 = graph.reduce_max(conv_0, keep_dims=0, name="ReduceMax_0") - cast_0 = graph.cast(reduce_max_0, getattr(onnx.TensorProto, "INT64"), name="Cast_to_int64") - range_0 = graph.onnx_range(np.array(0, dtype=np.int64), cast_0, np.array(1, dtype=np.int64), name="Range") - cast_1 = graph.cast(range_0, getattr(onnx.TensorProto, "FLOAT"), name="Cast_to_float") + cast_0 = graph.cast( + reduce_max_0, getattr(onnx.TensorProto, "INT64"), name="Cast_to_int64" + ) + range_0 = graph.onnx_range( + np.array(0, dtype=np.int64), cast_0, np.array(1, dtype=np.int64), name="Range" + ) + cast_1 = graph.cast( + range_0, getattr(onnx.TensorProto, "FLOAT"), name="Cast_to_float" + ) - reshape_1 = graph.reshape(cast_1, np.array([1, 1, -1, 1], dtype=np.int64), name="Reshape_1") + reshape_1 = graph.reshape( + cast_1, np.array([1, 1, -1, 1], dtype=np.int64), name="Reshape_1" + ) conv_1 = graph.conv(reshape_1, weights_1, [1, 1], name="Conv_1") graph.outputs = [conv_1] @@ -442,7 +483,8 @@ def make_small_matmul(name, dtype, save_sparse=False): save(g, name) if save_sparse: - save(make_sparse(g), 'sparse.'+name) + save(make_sparse(g), "sparse." + name) + make_small_matmul("matmul.onnx", np.float32, save_sparse=True) make_small_matmul("matmul.fp16.onnx", np.float16) @@ -457,14 +499,18 @@ def make_small_conv(name): F = 4 a = gs.Variable("a", shape=(N, C, H, W), dtype=np.float32) g = gs.Graph(inputs=[a], opset=13) - val = np.random.uniform(-3, 3, size=K * C * F * F).reshape((K, C, F, F)).astype(np.float32) + val = ( + np.random.uniform(-3, 3, size=K * C * F * F) + .reshape((K, C, F, F)) + .astype(np.float32) + ) b = gs.Constant("b", values=val) c = g.conv(a, b, (F, F), name="conv") c.dtype = np.float32 g.outputs = [c] save(g, name) - save(make_sparse(g), 'sparse.'+name) + save(make_sparse(g), "sparse." + name) make_small_conv("conv.onnx") @@ -480,13 +526,18 @@ def make_unsorted(): make_unsorted() + + def make_empty(): g = gs.Graph(inputs=[], opset=13) g.outputs = [] save(g, "empty.onnx") + + make_empty() + # Builds a graph that has unused nodes and inputs. # # f e @@ -517,8 +568,11 @@ def make_cleanable(): graph = gs.Graph(nodes=nodes, inputs=[e, f], outputs=[i]) save(graph, "cleanable.onnx") + + make_cleanable() + # Generates a graph with very deranged names # Tests that the unique renaming in lint tool works def make_renamable(): @@ -530,19 +584,26 @@ def make_renamable(): nodes = [ gs.Node(op="Identity", name="", inputs=[a], outputs=[b]), - gs.Node(op="Dropout", name="polygraphy_unnamed_node_0", inputs=[b], outputs=[c]), - gs.Node(op="Identity", name="polygraphy_unnamed_node_0_0", inputs=[c], outputs=[d]), + gs.Node( + op="Dropout", name="polygraphy_unnamed_node_0", inputs=[b], outputs=[c] + ), + gs.Node( + op="Identity", name="polygraphy_unnamed_node_0_0", inputs=[c], outputs=[d] + ), gs.Node(op="Dropout", name="", inputs=[d], outputs=[e]), ] graph = gs.Graph(nodes=nodes, inputs=[a], outputs=[e]) save(graph, "renamable.onnx") + + make_renamable() ####### Generate some invalid models ####### ### Graphs whose errors are data-dependent ### + # Generats an invalid graph with multiple parallel bad nodes. # The graph is invalid due to multiple parallel nodes failing. # This is is the graph: @@ -572,25 +633,34 @@ def make_bad_graph_with_parallel_invalid_nodes(): A = gs.Variable("A", dtype=DTYPE, shape=(1, BAD_DIM)) B = gs.Variable("B", dtype=DTYPE, shape=(4, 4)) - mm_ab_out = graph.matmul(A, B, name="MatMul_0") # This node will fail because A and B are not compatible. + mm_ab_out = graph.matmul( + A, B, name="MatMul_0" + ) # This node will fail because A and B are not compatible. C = gs.Variable("C", dtype=DTYPE, shape=(BAD_DIM, 4)) D = gs.Variable("D", dtype=DTYPE, shape=(4, 1)) - add_cd_out = graph.add(C, D, name="Add_0") # This node will fail because C and D are not compatible. + add_cd_out = graph.add( + C, D, name="Add_0" + ) # This node will fail because C and D are not compatible. pre_out_1 = graph.matmul(mm_ab_out, add_cd_out, name="MatMul_2") E = gs.Variable("E", dtype=DTYPE, shape=(1, 4)) F = gs.Variable("F", dtype=DTYPE, shape=(4, 1)) mm_ef_out = graph.matmul(E, F, name="MatMul_1") - mm_ef_out_int64 = graph.cast(mm_ef_out, onnx.TensorProto.INT64, name="cast_to_int64") - + mm_ef_out_int64 = graph.cast( + mm_ef_out, onnx.TensorProto.INT64, name="cast_to_int64" + ) G = gs.Variable("G", dtype=np.int64, shape=(4, 4)) - nz_g_out = graph.nonzero(G, name="NonZero") # `nz_g_out` shape is data-dependent. + nz_g_out = graph.nonzero(G, name="NonZero") # `nz_g_out` shape is data-dependent. - pre_out_2 = graph.matmul(mm_ef_out_int64, nz_g_out, name="MatMul_3") # This node will fail because `mm_ef_out_int64` and `nz_g_out` are not compatible. - pre_out_2_float = graph.cast(pre_out_2, getattr(onnx.TensorProto, "FLOAT"), name="cast_to_float") + pre_out_2 = graph.matmul( + mm_ef_out_int64, nz_g_out, name="MatMul_3" + ) # This node will fail because `mm_ef_out_int64` and `nz_g_out` are not compatible. + pre_out_2_float = graph.cast( + pre_out_2, getattr(onnx.TensorProto, "FLOAT"), name="cast_to_float" + ) out = graph.add(pre_out_1, pre_out_2_float, name="Add_1") out.dtype = DTYPE @@ -600,6 +670,7 @@ def make_bad_graph_with_parallel_invalid_nodes(): save(graph, "bad_graph_with_parallel_invalid_nodes.onnx") + make_bad_graph_with_parallel_invalid_nodes() @@ -620,11 +691,13 @@ def make_bad_graph_with_parallel_invalid_nodes(): # This graph is useful to check whether the error message is caught or not at runtime based on data input. # def make_bad_graph_conditionally_invalid(): - X = [[4.0], [3.0]] # shape (2, 1), compatible with Z for MatMul - Y = [2.0, 4.0] # shape (2,), incompatible with Z for MatMul - Z = [[2.0, 4.0]] # shape (1, 2) + X = [[4.0], [3.0]] # shape (2, 1), compatible with Z for MatMul + Y = [2.0, 4.0] # shape (2,), incompatible with Z for MatMul + Z = [[2.0, 4.0]] # shape (1, 2) - cond = gs.Variable("cond", dtype=np.bool_, shape=(1,)) # input to If, True or False based on user input. + cond = gs.Variable( + "cond", dtype=np.bool_, shape=(1,) + ) # input to If, True or False based on user input. graph = gs.Graph(name="bad_graph_conditionally_invalid") @@ -634,18 +707,34 @@ def make_bad_graph_conditionally_invalid(): then_out = gs.Variable("then_out", dtype=np.float32, shape=None) else_out = gs.Variable("else_out", dtype=np.float32, shape=None) - then_const_node = gs.Node(op="Constant", inputs=[], outputs=[then_out], attrs={"value":x}) # node for `then_branch` Graph - else_const_node = gs.Node(op="Constant", inputs=[], outputs=[else_out], attrs={"value":y}) # node for `else_branch` Graph + then_const_node = gs.Node( + op="Constant", inputs=[], outputs=[then_out], attrs={"value": x} + ) # node for `then_branch` Graph + else_const_node = gs.Node( + op="Constant", inputs=[], outputs=[else_out], attrs={"value": y} + ) # node for `else_branch` Graph - then_body = gs.Graph(nodes=[then_const_node], name="then_body", inputs=[], outputs=[then_out]) # Graph for `then_branch` - else_body = gs.Graph(nodes=[else_const_node], name="else_body", inputs=[], outputs=[else_out]) # Graph for `else_branch` + then_body = gs.Graph( + nodes=[then_const_node], name="then_body", inputs=[], outputs=[then_out] + ) # Graph for `then_branch` + else_body = gs.Graph( + nodes=[else_const_node], name="else_body", inputs=[], outputs=[else_out] + ) # Graph for `else_branch` res = gs.Variable("res", dtype=np.float32, shape=None) # shape is data-dependent - if_node = gs.Node(op="If", name="If_Node", inputs=[cond], outputs=[res], attrs={"then_branch":then_body, "else_branch":else_body}) + if_node = gs.Node( + op="If", + name="If_Node", + inputs=[cond], + outputs=[res], + attrs={"then_branch": then_body, "else_branch": else_body}, + ) graph.nodes = [if_node] - out = graph.matmul(res, gs.Constant("z", values=np.array(Z, dtype=np.float32)), name="MatMul") + out = graph.matmul( + res, gs.Constant("z", values=np.array(Z, dtype=np.float32)), name="MatMul" + ) out.dtype = np.float32 graph.inputs = [cond] @@ -653,12 +742,14 @@ def make_bad_graph_conditionally_invalid(): save(graph, "bad_graph_conditionally_invalid.onnx") + make_bad_graph_conditionally_invalid() ### Bad GraphProto ### ### Graphs that break the ONNX Specification for GraphProto ### + # Generates a model where the GraphProto has no name. # # This is invalid as ONNX Specification requires that the GraphProto has a name. @@ -679,6 +770,7 @@ def make_bad_graph_with_no_name(): make_bad_graph_with_no_name() + # Generates a model where the GraphProto has no imports. # # This is invalid as ONNX Specification requires that the GraphProto has at least one import. @@ -699,6 +791,7 @@ def make_bad_graph_with_no_import_domains(): make_bad_graph_with_no_import_domains() + # Generates a model where the inputs (value info) of graph are duplicates. # # This is invalid as ONNX Specification requires that the (value info) inputs of a graph are unique. @@ -735,16 +828,18 @@ def make_bad_graph_multi_level_errors(): inp1 = gs.Variable("inp1", dtype=DTYPE, shape=SHAPE) inp2 = gs.Variable("inp2", dtype=DTYPE, shape=SHAPE) - graph = gs.Graph(inputs=[inp1, inp2], name="") # graph-level error: empty name - out = graph.matmul(inp1, inp2) # node-level error: incompatible inputs + graph = gs.Graph(inputs=[inp1, inp2], name="") # graph-level error: empty name + out = graph.matmul(inp1, inp2) # node-level error: incompatible inputs out.dtype = DTYPE - out.shape = [] # we need to specify this so GS creates valid ONNX model. + out.shape = [] # we need to specify this so GS creates valid ONNX model. graph.outputs = [out] save(graph, "bad_graph_with_multi_level_errors.onnx") + make_bad_graph_multi_level_errors() + # Generates a model where graph has multiple node names with same non-empty string. def make_bad_graph_with_duplicate_node_names(): DTYPE = np.float32 @@ -754,12 +849,17 @@ def make_bad_graph_with_duplicate_node_names(): graph = gs.Graph(inputs=[inp], name="bad_graph_with_duplicate_node_names") inter1 = graph.identity(inp, name="identical") - out = graph.identity(inter1, name="identical") # node-level error: duplicate node names + out = graph.identity( + inter1, name="identical" + ) # node-level error: duplicate node names graph.outputs = [out] save(graph, "bad_graph_with_duplicate_node_names.onnx") + + make_bad_graph_with_duplicate_node_names() + # Generates a model where the graph has a subgraph matching toyPlugin's graph pattern def make_graph_with_subgraph_matching_toy_plugin(): i0 = gs.Variable(name="i0", dtype=np.float32) @@ -774,15 +874,22 @@ def make_graph_with_subgraph_matching_toy_plugin(): O_node = gs.Node(op="O", inputs=[i0], outputs=[i1], name="n1") A_node = gs.Node(op="A", inputs=[i1], outputs=[i2], name="n2") B_node = gs.Node(op="B", inputs=[i1], outputs=[i3], name="n3") - C_node = gs.Node(op="C", inputs=[i2,i3], outputs=[i4], attrs={"x":1}, name="n4") + C_node = gs.Node(op="C", inputs=[i2, i3], outputs=[i4], attrs={"x": 1}, name="n4") D_node = gs.Node(op="D", inputs=[i4], outputs=[o1], name="n5") E_node = gs.Node(op="E", inputs=[i4], outputs=[o2], name="n6") - graph = gs.Graph(nodes=[O_node, A_node, B_node, C_node, D_node, E_node], inputs=[i0], outputs=[o1,o2]) + graph = gs.Graph( + nodes=[O_node, A_node, B_node, C_node, D_node, E_node], + inputs=[i0], + outputs=[o1, o2], + ) + + save(graph, "toy_subgraph.onnx") + - save(graph, "graph_with_subgraph_matching_toy_plugin.onnx") make_graph_with_subgraph_matching_toy_plugin() + # Generates the following Graph # # The input to the Transpose op is an initializer @@ -808,8 +915,10 @@ def make_transpose_matmul(): save(g, "transpose_matmul.onnx") + make_transpose_matmul() + # Generates the following Graph # # The input to the QuantizeLinear op is an initializer @@ -823,7 +932,11 @@ def make_transpose_matmul(): # out # def make_qdq_conv(): - x = np.random.uniform(-3, 3, size=3*3*130).astype(np.float32).reshape((1, 3, 3, 130)) + x = ( + np.random.uniform(-3, 3, size=3 * 3 * 130) + .astype(np.float32) + .reshape((1, 3, 3, 130)) + ) y_scale = np.array([2, 4, 5], dtype=np.float32) y_zero_point = np.array([84, 24, 196], dtype=np.uint8) x_const = gs.Constant("x", values=x) @@ -841,14 +954,17 @@ def make_qdq_conv(): save(g, "qdq_conv.onnx") + make_qdq_conv() + def make_weightless_network(model_name): ipath = ONNX_MODELS[model_name].path opath = os.path.join(CURDIR, "weightless." + model_name + ".onnx") cmd = [f"polygraphy surgeon weight-strip {ipath} -o {opath}"] subprocess.run(cmd, shell=True) + make_weightless_network("matmul.fp16") make_weightless_network("matmul.bf16") make_weightless_network("sparse.matmul") diff --git a/tools/Polygraphy/tests/models/meta.py b/tools/Polygraphy/tests/models/meta.py index d0ed444d..04d8217b 100644 --- a/tools/Polygraphy/tests/models/meta.py +++ b/tools/Polygraphy/tests/models/meta.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -35,7 +35,9 @@ def model_path(name=None): class Model: - def __init__(self, path, LoaderType, check_runner, input_metadata=None, ext_data=None): + def __init__( + self, path, LoaderType, check_runner, input_metadata=None, ext_data=None + ): self.path = path self.loader = LoaderType(self.path) self.check_runner = check_runner @@ -44,14 +46,21 @@ def __init__(self, path, LoaderType, check_runner, input_metadata=None, ext_data def check_tf_identity(runner): - feed_dict = {"Input:0": np.random.random_sample(size=(1, 15, 25, 30)).astype(np.float32)} + feed_dict = { + "Input:0": np.random.random_sample(size=(1, 15, 25, 30)).astype(np.float32) + } outputs = runner.infer(feed_dict) assert np.all(outputs["Identity_2:0"] == feed_dict["Input:0"]) + MODELS_DIR = os.path.join(os.path.dirname(__file__)) TF_MODELS = { - "identity": Model(path=model_path("tf_identity.pb"), LoaderType=GraphFromFrozen, check_runner=check_tf_identity), + "identity": Model( + path=model_path("tf_identity.pb"), + LoaderType=GraphFromFrozen, + check_runner=check_tf_identity, + ), } @@ -77,7 +86,14 @@ def check_empty_tensor_expand(runner, shapes): shape = shapes["new_shape"] feed_dict = { "data": np.zeros(shape=(2, 0, 3, 0), dtype=np.float32), - "new_shape": np.array(shape, dtype=np.int32 if mod.version(trt.__version__) < mod.version("9.0") else np.int64), + "new_shape": np.array( + shape, + dtype=( + np.int32 + if mod.version(trt.__version__) < mod.version("9.0") + else np.int64 + ), + ), } outputs = runner.infer(feed_dict) # Empty tensor will still be empty after broadcast @@ -91,16 +107,24 @@ def check_reshape(runner): assert np.all(outputs["output"] == feed_dict["data"].ravel()) -def check_residual_block(runner,shapes): - feed_dict = {"gpu_0/data_0": np.random.random_sample(size=shapes["gpu_0/data_0"]).astype(np.float32)} +def check_residual_block(runner, shapes): + feed_dict = { + "gpu_0/data_0": np.random.random_sample(size=shapes["gpu_0/data_0"]).astype( + np.float32 + ) + } # Confirm inference can go through without error outputs = runner.infer(feed_dict) + def check_matmul_2layer(runner, shape=(2, 8)): - feed_dict = {"onnx::MatMul_0": np.random.random_sample(size=shape).astype(np.float32)} + feed_dict = { + "onnx::MatMul_0": np.random.random_sample(size=shape).astype(np.float32) + } # Confirm inference can go through without error outputs = runner.infer(feed_dict) + def no_check_implemented(runner): raise NotImplementedError("No check_runner implemented for this model") @@ -110,42 +134,76 @@ def no_check_implemented(runner): path=model_path("identity.onnx"), LoaderType=BytesFromPath, check_runner=check_identity, - input_metadata=TensorMetadata().add("x", dtype=DataType.FLOAT32, shape=(1, 1, 2, 2)), + input_metadata=TensorMetadata().add( + "x", dtype=DataType.FLOAT32, shape=(1, 1, 2, 2) + ), ), "identity_identity": Model( - path=model_path("identity_identity.onnx"), LoaderType=BytesFromPath, check_runner=check_identity_identity + path=model_path("identity_identity.onnx"), + LoaderType=BytesFromPath, + check_runner=check_identity_identity, ), "dynamic_identity": Model( path=model_path("dynamic_identity.onnx"), LoaderType=BytesFromPath, check_runner=check_dynamic_identity, - input_metadata=TensorMetadata().add("X", dtype=DataType.FLOAT32, shape=(1, 1, -1, -1)), + input_metadata=TensorMetadata().add( + "X", dtype=DataType.FLOAT32, shape=(1, 1, -1, -1) + ), ), "identity_multi_ch": Model( path=model_path("identity_multi_ch.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented, - input_metadata=TensorMetadata().add("x", dtype=DataType.FLOAT32, shape=(2, 4, 3, 3)), + input_metadata=TensorMetadata().add( + "x", dtype=DataType.FLOAT32, shape=(2, 4, 3, 3) + ), ), "empty_tensor_expand": Model( - path=model_path("empty_tensor_expand.onnx"), LoaderType=BytesFromPath, check_runner=check_empty_tensor_expand + path=model_path("empty_tensor_expand.onnx"), + LoaderType=BytesFromPath, + check_runner=check_empty_tensor_expand, + ), + "and": Model( + path=model_path("and.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, + ), + "scan": Model( + path=model_path("scan.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), - "and": Model(path=model_path("and.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented), - "scan": Model(path=model_path("scan.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented), "pow_scalar": Model( - path=model_path("pow_scalar.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("pow_scalar.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, + ), + "dim_param": Model( + path=model_path("dim_param.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), - "dim_param": Model(path=model_path("dim_param.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented), "tensor_attr": Model( - path=model_path("tensor_attr.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("tensor_attr.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "identity_with_initializer": Model( - path=model_path("identity_with_initializer.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("identity_with_initializer.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "const_foldable": Model( - path=model_path("const_foldable.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("const_foldable.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, + ), + "reshape": Model( + path=model_path("reshape.onnx"), + LoaderType=BytesFromPath, + check_runner=check_reshape, ), - "reshape": Model(path=model_path("reshape.onnx"), LoaderType=BytesFromPath, check_runner=check_reshape), "reducable": Model( path=model_path("reducable.onnx"), LoaderType=BytesFromPath, @@ -172,142 +230,226 @@ def no_check_implemented(runner): ext_data=model_path("ext_weights_same_dir"), ), "capability": Model( - path=model_path("capability.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("capability.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "instancenorm": Model( - path=model_path("instancenorm.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("instancenorm.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "add_with_dup_inputs": Model( - path=model_path("add_with_dup_inputs.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("add_with_dup_inputs.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "needs_constraints": Model( path=model_path("needs_constraints.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented, - input_metadata=TensorMetadata().add("x", dtype=DataType.FLOAT32, shape=(1, 1, 256, 256)), + input_metadata=TensorMetadata().add( + "x", dtype=DataType.FLOAT32, shape=(1, 1, 256, 256) + ), ), "constant_fold_bloater": Model( path=model_path("constant_fold_bloater.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented, ), - "renamable" : Model( + "renamable": Model( path=model_path("renamable.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented, ), - "cleanable" : Model( + "cleanable": Model( path=model_path("cleanable.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented, ), - "nonzero": Model(path=model_path("nonzero.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented), + "nonzero": Model( + path=model_path("nonzero.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, + ), "inp_dim_val_not_set": Model( - path=model_path("inp_dim_val_not_set.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("inp_dim_val_not_set.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "multi_output": Model( - path=model_path("multi_output.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("multi_output.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "unbounded_dds": Model( - path=model_path("unbounded_dds.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("unbounded_dds.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "loop": Model( - path=model_path("loop.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("loop.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "matmul.fp16": Model( - path=model_path("matmul.fp16.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("matmul.fp16.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "matmul": Model( - path=model_path("matmul.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("matmul.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "sparse.matmul": Model( - path=model_path("sparse.matmul.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("sparse.matmul.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "matmul.bf16": Model( - path=model_path("matmul.bf16.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("matmul.bf16.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "matmul.bf16.i32data": Model( - path=model_path("matmul.bf16.i32data.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("matmul.bf16.i32data.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "matmul_2layer": Model( - path=model_path("matmul_2layer.onnx"), LoaderType=BytesFromPath, check_runner=check_matmul_2layer + path=model_path("matmul_2layer.onnx"), + LoaderType=BytesFromPath, + check_runner=check_matmul_2layer, + ), + "unsorted": Model( + path=model_path("unsorted.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), - "unsorted": Model(path=model_path("unsorted.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented), "conv": Model( - path=model_path("conv.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("conv.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "sparse.conv": Model( - path=model_path("sparse.conv.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("sparse.conv.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "no_op_reshape": Model( - path=model_path("no_op_reshape.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("no_op_reshape.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "bad_graph_with_dup_value_info": Model( - path=model_path("bad_graph_with_dup_value_info.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("bad_graph_with_dup_value_info.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "bad_graph_with_no_name": Model( - path=model_path("bad_graph_with_no_name.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("bad_graph_with_no_name.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "bad_graph_with_no_import_domains": Model( - path=model_path("bad_graph_with_no_import_domains.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("bad_graph_with_no_import_domains.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "bad_graph_with_parallel_invalid_nodes": Model( - path=model_path("bad_graph_with_parallel_invalid_nodes.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("bad_graph_with_parallel_invalid_nodes.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "bad_graph_conditionally_invalid": Model( - path=model_path("bad_graph_conditionally_invalid.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("bad_graph_conditionally_invalid.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "custom_op_node": Model( - path=model_path("custom_op_node.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("custom_op_node.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "bad_graph_with_duplicate_node_names": Model( - path=model_path("bad_graph_with_duplicate_node_names.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("bad_graph_with_duplicate_node_names.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "bad_graph_with_multi_level_errors": Model( - path=model_path("bad_graph_with_multi_level_errors.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("bad_graph_with_multi_level_errors.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "invalid": Model( - path=model_path("invalid.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("invalid.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "empty": Model( - path=model_path("empty.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("empty.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "residual_block": Model( - path=model_path("residual_block.onnx"), LoaderType=BytesFromPath, check_runner=check_residual_block + path=model_path("residual_block.onnx"), + LoaderType=BytesFromPath, + check_runner=check_residual_block, ), "graph_with_subgraph_matching_toy_plugin": Model( - path=model_path("graph_with_subgraph_matching_toy_plugin.onnx"), - LoaderType=BytesFromPath, - check_runner=no_check_implemented + path=model_path("toy_subgraph.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "transpose_matmul": Model( - path=model_path("transpose_matmul.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("transpose_matmul.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "qdq_conv": Model( - path=model_path("qdq_conv.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("qdq_conv.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "weightless.matmul.fp16": Model( - path=model_path("weightless.matmul.fp16.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("weightless.matmul.fp16.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "weightless.matmul.bf16": Model( - path=model_path("weightless.matmul.bf16.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("weightless.matmul.bf16.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "weightless.conv": Model( - path=model_path("weightless.conv.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("weightless.conv.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "weightless.sparse.matmul": Model( - path=model_path("weightless.sparse.matmul.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("weightless.sparse.matmul.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "weightless.sparse.conv": Model( - path=model_path("weightless.sparse.conv.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("weightless.sparse.conv.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "weightless.transpose_matmul": Model( - path=model_path("weightless.transpose_matmul.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("weightless.transpose_matmul.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "weightless.qdq_conv": Model( - path=model_path("weightless.qdq_conv.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("weightless.qdq_conv.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), "roialign": Model( - path=model_path("roialign.onnx"), LoaderType=BytesFromPath, check_runner=no_check_implemented + path=model_path("roialign.onnx"), + LoaderType=BytesFromPath, + check_runner=no_check_implemented, ), } diff --git a/tools/Polygraphy/tests/models/plugins/toyPlugin/pattern.py b/tools/Polygraphy/tests/models/plugins/toyPlugin/pattern.py index 6cf600ba..0c299e13 100644 --- a/tools/Polygraphy/tests/models/plugins/toyPlugin/pattern.py +++ b/tools/Polygraphy/tests/models/plugins/toyPlugin/pattern.py @@ -1,7 +1,8 @@ from polygraphy import mod +from typing import List,Dict gs = mod.lazy_import("onnx_graphsurgeon>=0.5.0") -def get_plugin_pattern() -> gs.GraphPattern: +def get_plugin_pattern(): """ Toy plugin pattern: A B @@ -23,9 +24,25 @@ def get_plugin_pattern() -> gs.GraphPattern: return pattern -def get_plugin_attributes(sg) -> dict: - """ - example plugin attribute mapping, where the plugin has attribute ToyX, which gets its value from C.x * 2 - """ - return {"ToyX": int(sg.get("Cnode").attrs["x"]) * 2} +def get_matching_subgraphs(graph) -> List[Dict[str,str]]: + gp = get_plugin_pattern() + matches = gp.match_all(graph) + ans = [] + for m in matches: + # save the input and output tensor names of the matching subgraph(s) + input_tensors = list(set([ip_tensor.name for ip_tensor in m.inputs])) + output_tensors = list(set([op_tensor.name for op_tensor in m.outputs])) + + attrs = {"ToyX": int(m.get("Cnode").attrs["x"]) * 2} + ioa = { + 'inputs':input_tensors, + 'outputs':output_tensors, + 'attributes':attrs + } + ans.append(ioa) + return ans +def get_plugin_metadata() -> Dict[str,str]: + return {'name':'toyPlugin', + 'op':'CustomToyPlugin', + } diff --git a/tools/Polygraphy/tests/models/graph_with_subgraph_matching_toy_plugin.onnx b/tools/Polygraphy/tests/models/toy_subgraph.onnx similarity index 100% rename from tools/Polygraphy/tests/models/graph_with_subgraph_matching_toy_plugin.onnx rename to tools/Polygraphy/tests/models/toy_subgraph.onnx diff --git a/tools/Polygraphy/tests/test_deprecated_aliases.py b/tools/Polygraphy/tests/test_deprecated_aliases.py index dac09083..bedd2936 100644 --- a/tools/Polygraphy/tests/test_deprecated_aliases.py +++ b/tools/Polygraphy/tests/test_deprecated_aliases.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/test_examples.py b/tools/Polygraphy/tests/test_examples.py index a500e230..2333e2fd 100644 --- a/tools/Polygraphy/tests/test_examples.py +++ b/tools/Polygraphy/tests/test_examples.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -354,7 +354,8 @@ def test_api_examples(example, sandboxed_install_run): ), # Plugin Example( - ["cli", "plugin", "01_match_and_replace_plugin"], artifact_names=["config.yaml", "replaced.onnx"] + ["cli", "plugin", "01_match_and_replace_plugin"], + artifact_names=["config.yaml", "replaced.onnx"] ), ] diff --git a/tools/Polygraphy/tests/test_packaging.py b/tools/Polygraphy/tests/test_packaging.py index ef556620..135181fb 100644 --- a/tools/Polygraphy/tests/test_packaging.py +++ b/tools/Polygraphy/tests/test_packaging.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -30,7 +30,9 @@ def test_install(self, virtualenv): virtualenv.run(["python3", "-c", "import polygraphy"]) # Newer versions of setuptools break pytest-virtualenv - virtualenv.run([virtualenv.python, "-m", "pip", "install", "setuptools==59.6.0"]) + virtualenv.run( + [virtualenv.python, "-m", "pip", "install", "setuptools==59.6.0"] + ) virtualenv.run(["make", "install"], cwd=ROOT_DIR) @@ -44,8 +46,12 @@ def test_install(self, virtualenv): assert not os.path.exists(os.path.join(poly_pkg.source_path, "tests")) EXCLUDE_FILES = ["__pycache__"] - all_poly_files = glob.glob(os.path.join(poly_pkg.source_path, "polygraphy", "*")) - all_poly_files = [f for f in map(os.path.basename, all_poly_files) if f not in EXCLUDE_FILES] + all_poly_files = glob.glob( + os.path.join(poly_pkg.source_path, "polygraphy", "*") + ) + all_poly_files = [ + f for f in map(os.path.basename, all_poly_files) if f not in EXCLUDE_FILES + ] # NOTE: This should be updated when new files are added to the top-level package. EXPECTED_FILES = set( diff --git a/tools/Polygraphy/tests/test_tests.py b/tools/Polygraphy/tests/test_tests.py index a621f100..a077e4c8 100644 --- a/tools/Polygraphy/tests/test_tests.py +++ b/tools/Polygraphy/tests/test_tests.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -22,7 +22,9 @@ def test_sandboxed_install_run(sandboxed_install_run): - status = sandboxed_install_run(["python3", "-c", "import colored; print(colored.__path__)"]) + status = sandboxed_install_run( + ["python3", "-c", "import colored; print(colored.__path__)"] + ) assert status.success original_path = status.stdout @@ -30,7 +32,9 @@ def test_sandboxed_install_run(sandboxed_install_run): status = sandboxed_install_run(["python3", "-m", "pip", "install", "colored"]) assert status.success - status = sandboxed_install_run(["python3", "-c", "import colored; print(colored.__path__)"]) + status = sandboxed_install_run( + ["python3", "-c", "import colored; print(colored.__path__)"] + ) assert status.success venv_path = status.stdout diff --git a/tools/Polygraphy/tests/test_ux.py b/tools/Polygraphy/tests/test_ux.py index 67d08d31..82c10047 100644 --- a/tools/Polygraphy/tests/test_ux.py +++ b/tools/Polygraphy/tests/test_ux.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -50,7 +50,9 @@ def test_links_valid(self, readme): if link.startswith("https://"): assert requests.get(link).status_code == 200 else: - assert os.path.pathsep * 2 not in link, "Duplicate slashes break links in GitHub" + assert ( + os.path.pathsep * 2 not in link + ), "Duplicate slashes break links in GitHub" # NOTE: We cannot use repo-root-relative links in Markdown since Polygraphy is also # a subfolder of the OSS repo. assert not link.startswith("/") diff --git a/tools/Polygraphy/tests/tools/args/backend/onnx/test_loader.py b/tools/Polygraphy/tests/tools/args/backend/onnx/test_loader.py index 4011056e..23cfa1ef 100644 --- a/tools/Polygraphy/tests/tools/args/backend/onnx/test_loader.py +++ b/tools/Polygraphy/tests/tools/args/backend/onnx/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -23,7 +23,13 @@ import onnx_graphsurgeon as gs from polygraphy import util from polygraphy.backend.onnx import onnx_from_path -from polygraphy.tools.args import DataLoaderArgs, ModelArgs, OnnxLoadArgs, OnnxSaveArgs, OnnxInferShapesArgs +from polygraphy.tools.args import ( + DataLoaderArgs, + ModelArgs, + OnnxLoadArgs, + OnnxSaveArgs, + OnnxInferShapesArgs, +) from polygraphy.tools.script import Script from tests.helper import is_file_empty, is_file_non_empty from tests.models.meta import ONNX_MODELS @@ -38,8 +44,12 @@ def _check_ext_weights_model(model): class TestOnnxLoaderArgs: def test_basic(self): - arg_group = ArgGroupTestHelper(OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()]) - arg_group.parse_args([ONNX_MODELS["identity_identity"].path, "--onnx-outputs=identity_out_0"]) + arg_group = ArgGroupTestHelper( + OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()] + ) + arg_group.parse_args( + [ONNX_MODELS["identity_identity"].path, "--onnx-outputs=identity_out_0"] + ) model = arg_group.load_onnx() assert len(model.graph.output) == 1 @@ -48,7 +58,10 @@ def test_basic(self): @pytest.mark.parametrize("global_upper_bound", [None, "2000"]) @pytest.mark.parametrize("specified_upper_bound", [None, "cast_out_6:4000"]) def test_setting_upper_bounds(self, global_upper_bound, specified_upper_bound): - arg_group = ArgGroupTestHelper(OnnxLoadArgs(allow_setting_upper_bounds = True), deps=[ModelArgs(), OnnxInferShapesArgs()]) + arg_group = ArgGroupTestHelper( + OnnxLoadArgs(allow_setting_upper_bounds=True), + deps=[ModelArgs(), OnnxInferShapesArgs()], + ) cmd = [ONNX_MODELS["unbounded_dds"].path, "--set-unbounded-dds-upper-bound"] upper_bound = "1000" @@ -67,7 +80,7 @@ def test_setting_upper_bounds(self, global_upper_bound, specified_upper_bound): # Check if there is a Min operator in the modified model find_min = False for node in graph.nodes: - if node.op == 'Min': + if node.op == "Min": find_min = True # Check if the Min operator's second input is a constant tensor assert isinstance(node.inputs[1], gs.Constant) @@ -75,17 +88,21 @@ def test_setting_upper_bounds(self, global_upper_bound, specified_upper_bound): val = node.inputs[1].values # Check if the constant value equals the target upper bound assert str(val) == upper_bound - assert (find_min) + assert find_min def test_external_data(self): - arg_group = ArgGroupTestHelper(OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()]) + arg_group = ArgGroupTestHelper( + OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()] + ) model = ONNX_MODELS["ext_weights"] arg_group.parse_args([model.path, "--external-data-dir", model.ext_data]) model = arg_group.load_onnx() _check_ext_weights_model(model) def test_ignore_external_data(self): - arg_group = ArgGroupTestHelper(OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()]) + arg_group = ArgGroupTestHelper( + OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()] + ) model = ONNX_MODELS["ext_weights"] arg_group.parse_args([model.path, "--ignore-external-data"]) model = arg_group.load_onnx() @@ -94,10 +111,13 @@ def test_ignore_external_data(self): @pytest.mark.parametrize("allow_onnxruntime", [True, False]) def test_shape_inference(self, allow_onnxruntime): # When using shape inference, we should load directly from the path - arg_group = ArgGroupTestHelper(OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()]) + arg_group = ArgGroupTestHelper( + OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()] + ) model = ONNX_MODELS["identity"] arg_group.parse_args( - [model.path, "--shape-inference"] + (["--no-onnxruntime-shape-inference"] if not allow_onnxruntime else []) + [model.path, "--shape-inference"] + + (["--no-onnxruntime-shape-inference"] if not allow_onnxruntime else []) ) assert arg_group.must_use_onnx_loader() @@ -114,7 +134,9 @@ def test_shape_inference(self, allow_onnxruntime): @pytest.mark.parametrize("allow_onnxruntime", [True, False]) def test_shape_inference_ext_data(self, allow_onnxruntime): - arg_group = ArgGroupTestHelper(OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()]) + arg_group = ArgGroupTestHelper( + OnnxLoadArgs(), deps=[ModelArgs(), OnnxInferShapesArgs()] + ) model = ONNX_MODELS["ext_weights"] arg_group.parse_args( [model.path, "--external-data-dir", model.ext_data, "--shape-inference"] @@ -139,16 +161,28 @@ def test_shape_inference_ext_data(self, allow_onnxruntime): class TestOnnxSaveArgs: def test_defaults(self): - arg_group = ArgGroupTestHelper(OnnxSaveArgs(), deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)]) + arg_group = ArgGroupTestHelper( + OnnxSaveArgs(), + deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)], + ) arg_group.parse_args([]) assert arg_group.size_threshold is None def test_external_data(self): model = onnx_from_path(ONNX_MODELS["const_foldable"].path) - arg_group = ArgGroupTestHelper(OnnxSaveArgs(), deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)]) + arg_group = ArgGroupTestHelper( + OnnxSaveArgs(), + deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)], + ) with util.NamedTemporaryFile() as path, util.NamedTemporaryFile() as data: arg_group.parse_args( - ["-o", path.name, "--save-external-data", data.name, "--external-data-size-threshold=0"] + [ + "-o", + path.name, + "--save-external-data", + data.name, + "--external-data-size-threshold=0", + ] ) arg_group.save_onnx(model) @@ -157,10 +191,19 @@ def test_external_data(self): def test_size_threshold(self): model = onnx_from_path(ONNX_MODELS["const_foldable"].path) - arg_group = ArgGroupTestHelper(OnnxSaveArgs(), deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)]) + arg_group = ArgGroupTestHelper( + OnnxSaveArgs(), + deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)], + ) with util.NamedTemporaryFile() as path, util.NamedTemporaryFile() as data: arg_group.parse_args( - ["-o", path.name, "--save-external-data", data.name, "--external-data-size-threshold=1024"] + [ + "-o", + path.name, + "--save-external-data", + data.name, + "--external-data-size-threshold=1024", + ] ) arg_group.save_onnx(model) @@ -169,7 +212,10 @@ def test_size_threshold(self): def test_no_all_tensors_to_one_file(self): model = onnx_from_path(ONNX_MODELS["const_foldable"].path) - arg_group = ArgGroupTestHelper(OnnxSaveArgs(), deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)]) + arg_group = ArgGroupTestHelper( + OnnxSaveArgs(), + deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)], + ) with tempfile.TemporaryDirectory() as outdir: path = os.path.join(outdir, "model.onnx") arg_group.parse_args( @@ -196,7 +242,10 @@ def test_no_all_tensors_to_one_file(self): ], ) def test_size_threshold_parsing(self, arg, expected): - arg_group = ArgGroupTestHelper(OnnxSaveArgs(), deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)]) + arg_group = ArgGroupTestHelper( + OnnxSaveArgs(), + deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)], + ) arg_group.parse_args(["--external-data-size-threshold", arg]) assert arg_group.size_threshold == expected @@ -204,7 +253,8 @@ def test_size_threshold_parsing(self, arg, expected): class TestOnnxShapeInferenceArgs: def test_shape_inference_disabled_on_fallback(self): arg_group = ArgGroupTestHelper( - OnnxInferShapesArgs(default=True, allow_force_fallback=True), deps=[DataLoaderArgs()] + OnnxInferShapesArgs(default=True, allow_force_fallback=True), + deps=[DataLoaderArgs()], ) arg_group.parse_args([]) assert arg_group.do_shape_inference @@ -215,7 +265,10 @@ def test_shape_inference_disabled_on_fallback(self): @pytest.mark.parametrize("allow_onnxruntime", [True, False]) def test_no_onnxruntime_shape_inference(self, allow_onnxruntime): arg_group = ArgGroupTestHelper( - OnnxInferShapesArgs(default=True, allow_force_fallback=True), deps=[DataLoaderArgs()] + OnnxInferShapesArgs(default=True, allow_force_fallback=True), + deps=[DataLoaderArgs()], + ) + arg_group.parse_args( + [] if allow_onnxruntime else ["--no-onnxruntime-shape-inference"] ) - arg_group.parse_args([] if allow_onnxruntime else ["--no-onnxruntime-shape-inference"]) assert arg_group.allow_onnxruntime == (None if allow_onnxruntime else False) diff --git a/tools/Polygraphy/tests/tools/args/backend/onnxrt/test_loader.py b/tools/Polygraphy/tests/tools/args/backend/onnxrt/test_loader.py index 5a69a040..aa1dd0bf 100644 --- a/tools/Polygraphy/tests/tools/args/backend/onnxrt/test_loader.py +++ b/tools/Polygraphy/tests/tools/args/backend/onnxrt/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,9 +24,12 @@ class TestOnnxrtSessionArgs: def test_execution_providers(self): arg_group = ArgGroupTestHelper( - OnnxrtSessionArgs(), deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)] + OnnxrtSessionArgs(), + deps=[ModelArgs(), OnnxLoadArgs(allow_shape_inference=False)], + ) + arg_group.parse_args( + [ONNX_MODELS["identity_identity"].path, "--providers", "cpu"] ) - arg_group.parse_args([ONNX_MODELS["identity_identity"].path, "--providers", "cpu"]) sess = arg_group.load_onnxrt_session() assert sess diff --git a/tools/Polygraphy/tests/tools/args/backend/test_runner_select.py b/tools/Polygraphy/tests/tools/args/backend/test_runner_select.py index 0606ebc9..6445134d 100644 --- a/tools/Polygraphy/tests/tools/args/backend/test_runner_select.py +++ b/tools/Polygraphy/tests/tools/args/backend/test_runner_select.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -63,7 +63,11 @@ class TestRunnerSelectArgs: # We should be able to specify the same runner multiple times. ( ["--onnxrt", "--onnxrt", "--onnxrt"], - [("onnxrt", "ONNX-Runtime"), ("onnxrt", "ONNX-Runtime"), ("onnxrt", "ONNX-Runtime")], + [ + ("onnxrt", "ONNX-Runtime"), + ("onnxrt", "ONNX-Runtime"), + ("onnxrt", "ONNX-Runtime"), + ], ), ], ) diff --git a/tools/Polygraphy/tests/tools/args/backend/tf/test_loader.py b/tools/Polygraphy/tests/tools/args/backend/tf/test_loader.py index a297e12e..454b832e 100644 --- a/tools/Polygraphy/tests/tools/args/backend/tf/test_loader.py +++ b/tools/Polygraphy/tests/tools/args/backend/tf/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py b/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py index 32c3943c..ca002f86 100644 --- a/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py +++ b/tools/Polygraphy/tests/tools/args/backend/trt/test_config.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -479,9 +479,11 @@ def test_memory_pool_limits_empty_key_not_allowed(self, args, trt_config_args): @pytest.mark.parametrize( "preview_features", [ - ["PROFILE_SHAriNG_0806"] - if mod.version(trt.__version__) >= mod.version("10.0") - else ["FASter_DYNAMIC_ShAPeS_0805"], + ( + ["PROFILE_SHAriNG_0806"] + if mod.version(trt.__version__) >= mod.version("10.0") + else ["FASter_DYNAMIC_ShAPeS_0805"] + ), ], ) def test_preview_features(self, trt_config_args, preview_features): diff --git a/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py b/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py index 9c967532..e8dafedf 100644 --- a/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py +++ b/tools/Polygraphy/tests/tools/args/backend/trt/test_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -50,10 +50,12 @@ class TestTrtLoadNetworkArgs: @pytest.mark.parametrize("force_onnx_loader", [True, False]) @pytest.mark.parametrize( "opts,expected_flag", - [([], None)] - + [(["--strongly-typed"], trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)] - if mod.version(trt.__version__) >= mod.version("8.7") - else [], + ( + [([], None)] + + [(["--strongly-typed"], trt.NetworkDefinitionCreationFlag.STRONGLY_TYPED)] + if mod.version(trt.__version__) >= mod.version("8.7") + else [] + ), ) def test_load_network(self, force_onnx_loader, opts, expected_flag): arg_group = ArgGroupTestHelper( diff --git a/tools/Polygraphy/tests/tools/args/backend/trt/test_runner.py b/tools/Polygraphy/tests/tools/args/backend/trt/test_runner.py index fc10831f..f5714c38 100644 --- a/tools/Polygraphy/tests/tools/args/backend/trt/test_runner.py +++ b/tools/Polygraphy/tests/tools/args/backend/trt/test_runner.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -53,7 +53,9 @@ def trt_runner_args(): class TestTrtRunnerArgs: @pytest.mark.parametrize("index", range(0, 3)) def test_optimization_profile(self, trt_runner_args, index): - trt_runner_args.parse_args([ONNX_MODELS["identity"].path, f"--optimization-profile={index}"]) + trt_runner_args.parse_args( + [ONNX_MODELS["identity"].path, f"--optimization-profile={index}"] + ) assert trt_runner_args.optimization_profile == index diff --git a/tools/Polygraphy/tests/tools/args/comparator/test_comparator.py b/tools/Polygraphy/tests/tools/args/comparator/test_comparator.py index 9423cb8c..9eb3bb2d 100644 --- a/tools/Polygraphy/tests/tools/args/comparator/test_comparator.py +++ b/tools/Polygraphy/tests/tools/args/comparator/test_comparator.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -41,12 +41,15 @@ class TestComparatorCompareArgs: ("indices", ["--atol=1"], ["--atol", "--abs-tol"], "simple"), ], ) - def test_compare_func_warnings_for_unused_options(self, compare_func, options, option_names, valid_for): + def test_compare_func_warnings_for_unused_options( + self, compare_func, options, option_names, valid_for + ): outfile = io.StringIO() with contextlib.redirect_stdout(outfile), contextlib.redirect_stderr(outfile): # Keep logger arguments first they're parsed first so we actually write to the log file. arg_group = ArgGroupTestHelper( - ComparatorCompareArgs(), deps=[LoggerArgs(), CompareFuncIndicesArgs(), CompareFuncSimpleArgs()] + ComparatorCompareArgs(), + deps=[LoggerArgs(), CompareFuncIndicesArgs(), CompareFuncSimpleArgs()], ) arg_group.parse_args([f"--compare-func={compare_func}"] + options) @@ -54,7 +57,8 @@ def test_compare_func_warnings_for_unused_options(self, compare_func, options, o logging_out = outfile.read() assert ( f"[W] Option: {'/'.join(option_names)} is only valid for comparison function: '{valid_for}'. " - f"The selected comparison function is: '{compare_func}', so this option will be ignored." in logging_out + f"The selected comparison function is: '{compare_func}', so this option will be ignored." + in logging_out ) diff --git a/tools/Polygraphy/tests/tools/args/comparator/test_compare.py b/tools/Polygraphy/tests/tools/args/comparator/test_compare.py index b44cf45d..d370350f 100644 --- a/tools/Polygraphy/tests/tools/args/comparator/test_compare.py +++ b/tools/Polygraphy/tests/tools/args/comparator/test_compare.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -20,17 +20,24 @@ import pytest from polygraphy.comparator import IterationResult from polygraphy.exception import PolygraphyException -from polygraphy.tools.args import ComparatorCompareArgs, CompareFuncIndicesArgs, CompareFuncSimpleArgs +from polygraphy.tools.args import ( + ComparatorCompareArgs, + CompareFuncIndicesArgs, + CompareFuncSimpleArgs, +) from polygraphy.tools.args import util as args_util from polygraphy.tools.script import Script from tests.tools.args.helper import ArgGroupTestHelper class TestCompareFuncSimple: - @pytest.mark.parametrize("check_error_stat", ["max", "median", "mean", "elemwise", "quantile"]) + @pytest.mark.parametrize( + "check_error_stat", ["max", "median", "mean", "elemwise", "quantile"] + ) def test_error_stat(self, check_error_stat): arg_group = ArgGroupTestHelper( - CompareFuncSimpleArgs(), deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()] + CompareFuncSimpleArgs(), + deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()], ) arg_group.parse_args([f"--check-error-stat={check_error_stat}"]) @@ -39,13 +46,20 @@ def test_error_stat(self, check_error_stat): @pytest.mark.parametrize( "args, expected", [ - (["mean", "output0:median", "output1:max"], {"": "mean", "output0": "median", "output1": "max"}), - (["output0:median", "output1:elemwise"], {"output0": "median", "output1": "elemwise"}), + ( + ["mean", "output0:median", "output1:max"], + {"": "mean", "output0": "median", "output1": "max"}, + ), + ( + ["output0:median", "output1:elemwise"], + {"output0": "median", "output1": "elemwise"}, + ), ], ) def test_error_stat_per_output(self, args, expected): arg_group = ArgGroupTestHelper( - CompareFuncSimpleArgs(), deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()] + CompareFuncSimpleArgs(), + deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()], ) arg_group.parse_args(["--check-error-stat"] + args) @@ -61,14 +75,16 @@ def test_error_stat_per_output(self, args, expected): def test_invalid_error_stat(self, args): with pytest.raises(PolygraphyException, match="Invalid choice"): arg_group = ArgGroupTestHelper( - CompareFuncSimpleArgs(), deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()] + CompareFuncSimpleArgs(), + deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()], ) arg_group.parse_args(["--check-error-stat"] + args) @pytest.mark.parametrize("val", (np.inf, -np.inf)) def test_infinities_compare_equal(self, val): arg_group = ArgGroupTestHelper( - CompareFuncSimpleArgs(), deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()] + CompareFuncSimpleArgs(), + deps=[ComparatorCompareArgs(), CompareFuncIndicesArgs()], ) arg_group.parse_args([f"--infinities-compare-equal"]) @@ -85,7 +101,8 @@ class TestCompareFuncIndices: def test_always_adds_to_script(self): # Indices is not the default comparison func, so it should always add itself to the script. arg_group = ArgGroupTestHelper( - CompareFuncIndicesArgs(), deps=[ComparatorCompareArgs(), CompareFuncSimpleArgs()] + CompareFuncIndicesArgs(), + deps=[ComparatorCompareArgs(), CompareFuncSimpleArgs()], ) arg_group.parse_args([]) @@ -104,7 +121,8 @@ def test_default_args_are_none(self, arg_group_type): other_group_types = set(TestDefaultNone.ARG_GROUP_TYPES) other_group_types.remove(arg_group_type) arg_group = ArgGroupTestHelper( - arg_group_type(), deps=[ComparatorCompareArgs()] + [g() for g in other_group_types] + arg_group_type(), + deps=[ComparatorCompareArgs()] + [g() for g in other_group_types], ) assert len(arg_group.arg_group.group._group_actions) > 0 for action in arg_group.arg_group.group._group_actions: diff --git a/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py b/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py index 7c4dac90..d9c2a053 100644 --- a/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py +++ b/tools/Polygraphy/tests/tools/args/comparator/test_data_loader.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -40,9 +40,18 @@ class TestDataLoaderArgs: (["--seed=123"], ["seed"], [123]), (["--int-min=23", "--int-max=94"], ["_int_range"], [(23, 94)]), (["--float-min=2.3", "--float-max=9.4"], ["_float_range"], [(2.3, 9.4)]), - ([], ["val_range"], [None], [(0.0, 1.0)]), # When not specified, this should default to None. + ( + [], + ["val_range"], + [None], + [(0.0, 1.0)], + ), # When not specified, this should default to None. (["--val-range", "[0.0,2.3]"], ["val_range"], [{"": (0.0, 2.3)}]), - (["--val-range", "[1,5]"], ["val_range"], [{"": (1, 5)}]), # Should work for integral quantities + ( + ["--val-range", "[1,5]"], + ["val_range"], + [{"": (1, 5)}], + ), # Should work for integral quantities ( ["--val-range", "inp0:[0.0,2.3]", "inp1:[4.5,9.6]"], ["val_range"], @@ -57,9 +66,21 @@ class TestDataLoaderArgs: (["--val-range", "'\"':[0.0,2.3]"], ["val_range"], [{"'\"'": (0.0, 2.3)}]), (["--iterations=12"], ["iterations"], [12]), (["--val-range", "[0.0,inf]"], ["val_range"], [{"": (0.0, float("inf"))}]), - (["--val-range", "[-inf,0.0]"], ["val_range"], [{"": (float("-inf"), 0.0)}]), - (["--data-loader-backend-module", "torch"], ["data_loader_backend_module"], ["torch"]), - (["--data-loader-backend-module", "numpy"], ["data_loader_backend_module"], ["numpy"]), + ( + ["--val-range", "[-inf,0.0]"], + ["val_range"], + [{"": (float("-inf"), 0.0)}], + ), + ( + ["--data-loader-backend-module", "torch"], + ["data_loader_backend_module"], + ["torch"], + ), + ( + ["--data-loader-backend-module", "numpy"], + ["data_loader_backend_module"], + ["numpy"], + ), ], ids=lambda c: c[1][0], ) @@ -82,7 +103,9 @@ def test_val_range_nan(self, data_loader_args): assert util.is_nan(val_range[0]) def test_input_metadata(self, data_loader_args): - data_loader_args.parse_args(["--input-shapes", "test0:[1,1,1]", "test1:[2,32,2]"]) + data_loader_args.parse_args( + ["--input-shapes", "test0:[1,1,1]", "test1:[2,32,2]"] + ) data_loader = data_loader_args.get_data_loader() for feed_dict in data_loader: @@ -92,7 +115,9 @@ def test_input_metadata(self, data_loader_args): def test_override_input_metadata(self, data_loader_args): data_loader_args.parse_args([]) data_loader = data_loader_args.get_data_loader( - user_input_metadata=TensorMetadata().add("test0", dtype=np.float32, shape=(4, 4)) + user_input_metadata=TensorMetadata().add( + "test0", dtype=np.float32, shape=(4, 4) + ) ) for feed_dict in data_loader: @@ -118,7 +143,9 @@ def my_load_data(): f.flush() os.fsync(f.fileno()) - data_loader_args.parse_args(["--data-loader-script", f"{f.name}:my_load_data"]) + data_loader_args.parse_args( + ["--data-loader-script", f"{f.name}:my_load_data"] + ) assert data_loader_args.data_loader_script == f.name assert data_loader_args.data_loader_func_name == "my_load_data" @@ -126,13 +153,19 @@ def my_load_data(): data_loader = data_loader_args.get_data_loader() data = list(data_loader) assert len(data) == 5 - assert all(np.all(d["inp"] == np.ones((3, 5), dtype=np.float32) * 6.4341) for d in data) + assert all( + np.all(d["inp"] == np.ones((3, 5), dtype=np.float32) * 6.4341) + for d in data + ) @pytest.mark.parametrize( "opts,expected_err", [ (["--val-range", "x:[y,2]"], "could not be parsed as a number"), - (["--val-range", "x:[1,2,3]"], "expected to receive exactly 2 values, but received 3"), + ( + ["--val-range", "x:[1,2,3]"], + "expected to receive exactly 2 values, but received 3", + ), ], ) def test_val_range_errors(self, data_loader_args, opts, expected_err): @@ -141,4 +174,6 @@ def test_val_range_errors(self, data_loader_args, opts, expected_err): def test_cannot_provide_two_custom_data_loader_methods(self, data_loader_args): with pytest.raises(SystemExit): - data_loader_args.parse_args(["--data-loader-script", "my_script.py", "--load-inputs", "inputs.json"]) + data_loader_args.parse_args( + ["--data-loader-script", "my_script.py", "--load-inputs", "inputs.json"] + ) diff --git a/tools/Polygraphy/tests/tools/args/helper.py b/tools/Polygraphy/tests/tools/args/helper.py index 4e9225e0..4742df84 100644 --- a/tools/Polygraphy/tests/tools/args/helper.py +++ b/tools/Polygraphy/tests/tools/args/helper.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/tools/args/logger/test_logger.py b/tools/Polygraphy/tests/tools/args/logger/test_logger.py index 96c4e464..d9f373a0 100644 --- a/tools/Polygraphy/tests/tools/args/logger/test_logger.py +++ b/tools/Polygraphy/tests/tools/args/logger/test_logger.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -56,7 +56,11 @@ def test_get_logger_verbosities(self, case): (["--verbosity", "info"], {None: G_LOGGER.INFO, "backend/": G_LOGGER.INFO}), ( ["--verbosity", "INFO", "backend:VERBOSE"], - {None: G_LOGGER.INFO, "backend": G_LOGGER.VERBOSE, os.path.join("backend", "trt"): G_LOGGER.VERBOSE}, + { + None: G_LOGGER.INFO, + "backend": G_LOGGER.VERBOSE, + os.path.join("backend", "trt"): G_LOGGER.VERBOSE, + }, ), ( ["--verbosity", "ULTRA_VERBOSE", "backend:VERBOSE"], @@ -68,7 +72,11 @@ def test_get_logger_verbosities(self, case): ), ( ["--verbosity", "backend/trt:VERBOSE"], - {None: G_LOGGER.INFO, "backend/": G_LOGGER.INFO, os.path.join("backend", "trt"): G_LOGGER.VERBOSE}, + { + None: G_LOGGER.INFO, + "backend/": G_LOGGER.INFO, + os.path.join("backend", "trt"): G_LOGGER.VERBOSE, + }, ), ], ) diff --git a/tools/Polygraphy/tests/tools/args/test_docstrings.py b/tools/Polygraphy/tests/tools/args/test_docstrings.py index 671a6557..c79f5df3 100644 --- a/tools/Polygraphy/tests/tools/args/test_docstrings.py +++ b/tools/Polygraphy/tests/tools/args/test_docstrings.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,7 +24,11 @@ from polygraphy.tools.args.base import BaseArgs -ARG_CLASSES = [cls for cls in args_mod.__dict__.values() if inspect.isclass(cls) and issubclass(cls, BaseArgs)] +ARG_CLASSES = [ + cls + for cls in args_mod.__dict__.values() + if inspect.isclass(cls) and issubclass(cls, BaseArgs) +] USES_DEP_PAT = re.compile(r"self.arg_groups\[(.*?)\]") MEMBER_PAT = re.compile(r"self.(.*?)[ ,.\[}]") @@ -54,7 +58,9 @@ def test_docstrings_document_dependencies(self, arg_group_type): continue documented_deps.add(line.lstrip("-").partition(":")[0].strip()) - assert documented_deps == deps, "Documented dependencies do not match actual dependencies" + assert ( + documented_deps == deps + ), "Documented dependencies do not match actual dependencies" # Checks that all members set by `parse` are documented. # @@ -78,7 +84,11 @@ def should_include_member(member): return True - members = {member for member in MEMBER_PAT.findall(code) if should_include_member(member)} + members = { + member + for member in MEMBER_PAT.findall(code) + if should_include_member(member) + } docstring = arg_group_type.parse_impl.__doc__ if docstring is None: @@ -87,7 +97,9 @@ def should_include_member(member): doc_lines = [line.strip() for line in docstring.splitlines() if line.strip()] attributes_doc_start = doc_lines.index("Attributes:") - assert attributes_doc_start >= 0, "Expected parse_impl docstring to contain an `Attributes:` section." + assert ( + attributes_doc_start >= 0 + ), "Expected parse_impl docstring to contain an `Attributes:` section." doc_lines = doc_lines[attributes_doc_start + 1 :] diff --git a/tools/Polygraphy/tests/tools/args/test_model.py b/tools/Polygraphy/tests/tools/args/test_model.py index c32231b7..6b4346df 100644 --- a/tools/Polygraphy/tests/tools/args/test_model.py +++ b/tools/Polygraphy/tests/tools/args/test_model.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -40,7 +40,15 @@ def test_path(self, group): assert group.model_type.is_onnx() def test_input_shapes(self, group): - group.parse_args(["--input-shapes", "test0:[1,1]", "test1:[10]", "test:2:[25,301]", "test3:[]"]) + group.parse_args( + [ + "--input-shapes", + "test0:[1,1]", + "test1:[10]", + "test:2:[25,301]", + "test3:[]", + ] + ) assert group.input_shapes["test0"].shape == [1, 1] assert group.input_shapes["test1"].shape == [10] @@ -55,17 +63,21 @@ def test_fixed_model_type(self): @pytest.mark.parametrize( "arg, expected_model, expected_extra_info", - [ - ("model.onnx", "model.onnx", None), - ("model.onnx:func", "model.onnx", "func"), - ] - if not "win" in sys.platform - else [ - ("C:\\Users\\model.onnx", "C:\\Users\\model.onnx", None), - ("C:\\Users\\model.onnx:func", "C:\\Users\\model.onnx", "func"), - ], + ( + [ + ("model.onnx", "model.onnx", None), + ("model.onnx:func", "model.onnx", "func"), + ] + if not "win" in sys.platform + else [ + ("C:\\Users\\model.onnx", "C:\\Users\\model.onnx", None), + ("C:\\Users\\model.onnx:func", "C:\\Users\\model.onnx", "func"), + ] + ), ) - def test_model_with_extra_info(self, group, arg, expected_model, expected_extra_info): + def test_model_with_extra_info( + self, group, arg, expected_model, expected_extra_info + ): group.parse_args([arg]) assert group.path == expected_model diff --git a/tools/Polygraphy/tests/tools/args/util/test_util.py b/tools/Polygraphy/tests/tools/args/util/test_util.py index c27e683a..a82ac835 100644 --- a/tools/Polygraphy/tests/tools/args/util/test_util.py +++ b/tools/Polygraphy/tests/tools/args/util/test_util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -76,7 +76,9 @@ class TestRunScript: def test_default_args(self): def script_add(script, arg0=0, arg1=0): result_name = safe("result") - script.append_suffix(safe("{:} = {:} + {:}", inline(result_name), arg0, arg1)) + script.append_suffix( + safe("{:} = {:} + {:}", inline(result_name), arg0, arg1) + ) return result_name assert args_util.run_script(script_add) == 0 diff --git a/tools/Polygraphy/tests/tools/conftest.py b/tools/Polygraphy/tests/tools/conftest.py index 508e7155..7d17e327 100644 --- a/tools/Polygraphy/tests/tools/conftest.py +++ b/tools/Polygraphy/tests/tools/conftest.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -45,6 +45,7 @@ def poly_fixture_impl( return poly_fixture + poly = make_poly_fixture([]) poly_run = make_poly_fixture(["run"]) poly_convert = make_poly_fixture(["convert"]) diff --git a/tools/Polygraphy/tests/tools/fake_reduce_checker.py b/tools/Polygraphy/tests/tools/fake_reduce_checker.py index f99085c9..5923bf83 100755 --- a/tools/Polygraphy/tests/tools/fake_reduce_checker.py +++ b/tools/Polygraphy/tests/tools/fake_reduce_checker.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -27,7 +27,9 @@ def main(): - parser = argparse.ArgumentParser(description="Makes Polygraphy think a node in a model is failing") + parser = argparse.ArgumentParser( + description="Makes Polygraphy think a node in a model is failing" + ) parser.add_argument("model", help="The ONNX model") parser.add_argument( "--fail-node", @@ -37,10 +39,16 @@ def main(): nargs="+", ) parser.add_argument( - "--default-return-code", help="The return code to use when there are no failures. ", default=0, type=int + "--default-return-code", + help="The return code to use when there are no failures. ", + default=0, + type=int, ) parser.add_argument( - "--fail-return-code", help="The return code to use when there is a failure. ", default=1, type=int + "--fail-return-code", + help="The return code to use when there is a failure. ", + default=1, + type=int, ) args = parser.parse_args() diff --git a/tools/Polygraphy/tests/tools/test_check.py b/tools/Polygraphy/tests/tools/test_check.py index a3b27a85..ea59c023 100644 --- a/tools/Polygraphy/tests/tools/test_check.py +++ b/tools/Polygraphy/tests/tools/test_check.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -76,18 +76,24 @@ def run_lint_get_json(self, poly_check, model_path, *args, expect_error=False): def eval_per_entry(self, lint_entries, lambda_check): return list(map(lambda_check, lint_entries)) - @pytest.mark.parametrize("case", TEST_LINT_CASES["test_summary"], ids=lambda case: case[0]) + @pytest.mark.parametrize( + "case", TEST_LINT_CASES["test_summary"], ids=lambda case: case[0] + ) @pytest.mark.script_launch_mode("subprocess") def test_summary(self, case, poly_check): """ Basic test to check that nodes are correctly classified as passing or failing """ model_name, expected_passing, expected_failing = case - output_json, status = self.run_lint_get_json(poly_check, ONNX_MODELS[model_name].path) + output_json, status = self.run_lint_get_json( + poly_check, ONNX_MODELS[model_name].path + ) passing = sorted(output_json["summary"].get("passing", [])) assert expected_passing == passing # check that the valid nodes are as expected failing = sorted(output_json["summary"].get("failing", [])) - assert expected_failing == failing # check that the invalid nodes are as expected + assert ( + expected_failing == failing + ) # check that the invalid nodes are as expected @pytest.mark.script_launch_mode("subprocess") def test_duplicate_node_names_caught(self, poly_check): @@ -95,7 +101,9 @@ def test_duplicate_node_names_caught(self, poly_check): Test that duplicate node names are marked as exception """ output_json, _ = self.run_lint_get_json( - poly_check, ONNX_MODELS["bad_graph_with_duplicate_node_names"].path, expect_error=True + poly_check, + ONNX_MODELS["bad_graph_with_duplicate_node_names"].path, + expect_error=True, ) lint_entry = output_json["lint_entries"][0] @@ -108,13 +116,17 @@ def test_duplicate_node_names_caught(self, poly_check): assert lint_entry == expected_entry assert "identical" in output_json["summary"]["failing"] - @pytest.mark.parametrize("model_name", TEST_LINT_CASES["test_onnx_spec_check"], ids=lambda m: m) + @pytest.mark.parametrize( + "model_name", TEST_LINT_CASES["test_onnx_spec_check"], ids=lambda m: m + ) @pytest.mark.script_launch_mode("subprocess") def test_onnx_spec_check(self, model_name, poly_check): """ Test that basic onnx specification errors are caught by the lint command from the ONNX Checker """ - output_json, _ = self.run_lint_get_json(poly_check, ONNX_MODELS[model_name].path, expect_error=True) + output_json, _ = self.run_lint_get_json( + poly_check, ONNX_MODELS[model_name].path, expect_error=True + ) assert any( # Make sure that there is atleast 1 entry with level exception and source onnx_checker self.eval_per_entry( @@ -152,7 +164,9 @@ def test_onnxrt_parity(self, model_name, poly_check, poly_run): } try: # try to run the model using onnxrt, may fail. - status = poly_run([model_path, "--onnxrt", *extra_args_dict.get(model_name, [])]) + status = poly_run( + [model_path, "--onnxrt", *extra_args_dict.get(model_name, [])] + ) poly_run_exception = "FAILED" in status.stdout except Exception as e: poly_run_exception = True @@ -219,7 +233,9 @@ def test_parallel_invalid_nodes_caught(self, poly_check): # Check correct summary assert sorted(expected_valid_nodes) == sorted(output_json["summary"]["passing"]) - assert sorted(expected_invalid_dict.keys()) == sorted(output_json["summary"]["failing"]) + assert sorted(expected_invalid_dict.keys()) == sorted( + output_json["summary"]["failing"] + ) @pytest.mark.parametrize( "input_bool", @@ -270,8 +286,12 @@ def test_data_dependent_errors_caught(self, poly_check, input_bool): } # Check that the output is as expected. - assert sorted(validation_dict[input_bool]["passing"]) == sorted(output_json["summary"]["passing"]) - assert sorted(validation_dict[input_bool]["failing"]) == sorted(output_json["summary"].get("failing", [])) + assert sorted(validation_dict[input_bool]["passing"]) == sorted( + output_json["summary"]["passing"] + ) + assert sorted(validation_dict[input_bool]["failing"]) == sorted( + output_json["summary"].get("failing", []) + ) if validation_dict[input_bool]["failing"]: # when input_bool = False expected_entry = { @@ -296,7 +316,12 @@ def test_custom_op(self, poly_check): expect_error=False, ) condition = ( - lambda entry: any([substr in entry["message"] for substr in Lint.CUSTOM_OP_EXCEPTION_SUBSTRS]) + lambda entry: any( + [ + substr in entry["message"] + for substr in Lint.CUSTOM_OP_EXCEPTION_SUBSTRS + ] + ) and entry["source"] == Lint.Source.ONNXRUNTIME.value and entry["level"] == Lint.Level.WARNING.value ) @@ -323,7 +348,8 @@ def test_multi_level_errors(self, poly_check): # condition for onnx checker entry condition_onnx_checker = ( - lambda entry: "Field 'name' of 'graph' is required to be non-empty." in entry["message"] + lambda entry: "Field 'name' of 'graph' is required to be non-empty." + in entry["message"] and entry["source"] == Lint.Source.ONNX_CHECKER.value and entry["level"] == Lint.Level.EXCEPTION.value ) @@ -337,8 +363,12 @@ def test_multi_level_errors(self, poly_check): # checks assert len(lint_entries) >= 2 # there should be atleast two lint entries - assert any(self.eval_per_entry(lint_entries, condition_onnx_checker)) # condition for onnx checker entry - assert any(self.eval_per_entry(lint_entries, condition_onnxruntime)) # condition for onnxruntime entry + assert any( + self.eval_per_entry(lint_entries, condition_onnx_checker) + ) # condition for onnx checker entry + assert any( + self.eval_per_entry(lint_entries, condition_onnxruntime) + ) # condition for onnxruntime entry @pytest.mark.script_launch_mode("subprocess") def test_invalid_model_error(self, poly_check): @@ -359,13 +389,16 @@ def test_invalid_model_error(self, poly_check): # condition for onnx_loader entry for invalid model condition = ( - lambda entry: "Error parsing message with type 'onnx.ModelProto'" in entry["message"] + lambda entry: "Error parsing message with type 'onnx.ModelProto'" + in entry["message"] and entry["source"] == Lint.Source.ONNX_LOADER.value and entry["level"] == Lint.Level.EXCEPTION.value ) assert len(lint_entries) == 1 # there should be only one lint entry - assert condition(lint_entries[0]) # condition for onnx_loader entry for invalid model + assert condition( + lint_entries[0] + ) # condition for onnx_loader entry for invalid model @pytest.mark.script_launch_mode("subprocess") def test_empty_model_warning(self, poly_check): @@ -377,7 +410,9 @@ def test_empty_model_warning(self, poly_check): empty_model_name = "empty" # Test with empty model - output_json, _ = self.run_lint_get_json(poly_check, ONNX_MODELS[empty_model_name].path, expect_error=False) + output_json, _ = self.run_lint_get_json( + poly_check, ONNX_MODELS[empty_model_name].path, expect_error=False + ) lint_entries = output_json["lint_entries"] # condition for onnx_loader entry for empty model @@ -411,7 +446,8 @@ def test_cleanable_warning(self, poly_check): and entry["nodes"] == ["G"] ) inp_check = ( - lambda entry: "Input: 'e' does not affect outputs, can be removed" in entry["message"] + lambda entry: "Input: 'e' does not affect outputs, can be removed" + in entry["message"] and entry["source"] == Lint.Source.ONNX_GS.value and entry["level"] == Lint.Level.WARNING.value ) @@ -425,7 +461,9 @@ def test_empty_nodes_renaming(self, poly_check): """ Tests that empty nodes are *gauranteed* a unique name while renaming. """ - output_json, _ = self.run_lint_get_json(poly_check, ONNX_MODELS["renamable"].path, expect_error=False) + output_json, _ = self.run_lint_get_json( + poly_check, ONNX_MODELS["renamable"].path, expect_error=False + ) names = output_json["summary"]["passing"] expected_names = [ "polygraphy_unnamed_node_0_0", diff --git a/tools/Polygraphy/tests/tools/test_convert.py b/tools/Polygraphy/tests/tools/test_convert.py index cebfd240..1d97acb9 100644 --- a/tools/Polygraphy/tests/tools/test_convert.py +++ b/tools/Polygraphy/tests/tools/test_convert.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -31,13 +31,21 @@ def test_tf2onnx(self, poly_convert): pytest.importorskip("tensorflow") with util.NamedTemporaryFile(suffix=".onnx") as outmodel: - poly_convert([TF_MODELS["identity"].path, "--model-type=frozen", "-o", outmodel.name]) + poly_convert( + [TF_MODELS["identity"].path, "--model-type=frozen", "-o", outmodel.name] + ) assert onnx.load(outmodel.name) def test_fp_to_fp16(self, poly_convert): with util.NamedTemporaryFile() as outmodel: poly_convert( - [ONNX_MODELS["identity_identity"].path, "--convert-to=onnx", "--fp-to-fp16", "-o", outmodel.name] + [ + ONNX_MODELS["identity_identity"].path, + "--convert-to=onnx", + "--fp-to-fp16", + "-o", + outmodel.name, + ] ) # I/O types should be unchanged model = onnx.load(outmodel.name) @@ -57,14 +65,24 @@ def check_engine(self, path): def test_onnx_to_trt(self, poly_convert): with util.NamedTemporaryFile(suffix=".engine") as outmodel: - poly_convert([ONNX_MODELS["identity"].path, "--model-type=onnx", "-o", outmodel.name]) + poly_convert( + [ONNX_MODELS["identity"].path, "--model-type=onnx", "-o", outmodel.name] + ) self.check_engine(outmodel.name) def test_tf_to_onnx_to_trt(self, poly_convert): pytest.importorskip("tensorflow") with util.NamedTemporaryFile() as outmodel: - poly_convert([TF_MODELS["identity"].path, "--model-type=frozen", "--convert-to=trt", "-o", outmodel.name]) + poly_convert( + [ + TF_MODELS["identity"].path, + "--model-type=frozen", + "--convert-to=trt", + "-o", + outmodel.name, + ] + ) self.check_engine(outmodel.name) def test_trt_network_config_script_to_engine(self, poly_convert): @@ -86,7 +104,9 @@ def load_config(config): """ ) - with util.NamedTemporaryFile("w+", suffix=".py") as f, util.NamedTemporaryFile() as outmodel: + with util.NamedTemporaryFile( + "w+", suffix=".py" + ) as f, util.NamedTemporaryFile() as outmodel: f.write(script) f.flush() os.fsync(f.fileno()) @@ -106,7 +126,16 @@ def load_config(config): def test_modify_onnx_outputs(self, poly_convert): with util.NamedTemporaryFile(suffix=".onnx") as outmodel: - poly_convert([ONNX_MODELS["identity_identity"].path, "-o", outmodel.name, "--onnx-outputs", "mark", "all"]) + poly_convert( + [ + ONNX_MODELS["identity_identity"].path, + "-o", + outmodel.name, + "--onnx-outputs", + "mark", + "all", + ] + ) model = onnx.load(outmodel.name) assert len(model.graph.output) == 2 @@ -114,8 +143,24 @@ def test_modify_onnx_outputs(self, poly_convert): class TestConvertToOnnxLikeTrt: @pytest.mark.parametrize( - "model_name", ["identity", "empty_tensor_expand", "const_foldable", "and", "scan", "dim_param", "tensor_attr"] + "model_name", + [ + "identity", + "empty_tensor_expand", + "const_foldable", + "and", + "scan", + "dim_param", + "tensor_attr", + ], ) def test_onnx_to_trt_to_onnx_like(self, poly_convert, model_name): with util.NamedTemporaryFile() as outmodel: - poly_convert([ONNX_MODELS[model_name].path, "--convert-to=onnx-like-trt-network", "-o", outmodel.name]) + poly_convert( + [ + ONNX_MODELS[model_name].path, + "--convert-to=onnx-like-trt-network", + "-o", + outmodel.name, + ] + ) diff --git a/tools/Polygraphy/tests/tools/test_data.py b/tools/Polygraphy/tests/tools/test_data.py index 09116c72..e31f60dd 100644 --- a/tools/Polygraphy/tests/tools/test_data.py +++ b/tools/Polygraphy/tests/tools/test_data.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -24,7 +24,14 @@ class TestToInput: def test_merge_inputs_outputs(self, poly_run, poly_data): with util.NamedTemporaryFile() as inps, util.NamedTemporaryFile() as outs, util.NamedTemporaryFile() as merged: poly_run( - [ONNX_MODELS["identity"].path, "--onnxrt", "--save-inputs", inps.name, "--save-outputs", outs.name], + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--save-inputs", + inps.name, + "--save-outputs", + outs.name, + ], ) poly_data(["to-input", inps.name, outs.name, "-o", merged.name]) diff --git a/tools/Polygraphy/tests/tools/test_debug.py b/tools/Polygraphy/tests/tools/test_debug.py index d0fb425a..18797861 100644 --- a/tools/Polygraphy/tests/tools/test_debug.py +++ b/tools/Polygraphy/tests/tools/test_debug.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -75,7 +75,16 @@ def check_outdir(subdir): files = glob.glob(os.path.join(outdir, subdir, "*")) assert len(files) == 1 basenames = list(map(os.path.basename, files)) - assert len([f for f in basenames if f.startswith("replay") and f.endswith(".json")]) == 1 + assert ( + len( + [ + f + for f in basenames + if f.startswith("replay") and f.endswith(".json") + ] + ) + == 1 + ) check_outdir("good") check_outdir("bad") @@ -105,7 +114,9 @@ def test_sanity(self, mode, direction, check_status, model, poly_debug): class TestReduce: - FAKE_REDUCE_CHECKER = os.path.join(os.path.dirname(__file__), "fake_reduce_checker.py") + FAKE_REDUCE_CHECKER = os.path.join( + os.path.dirname(__file__), "fake_reduce_checker.py" + ) # Test left branch, right branch, at the point of branching, and after the branch. @pytest.mark.parametrize( @@ -315,7 +326,9 @@ def test_no_reduce_required_branches(self, fail_nodes, poly_debug): model = onnx_from_path(os.path.join(outdir, "reduced.onnx")) node_names = [node.name for node in model.graph.node] assert all(fail_node in node_names for fail_node in fail_nodes) - assert len(model.graph.node) <= 3 # The branch on the opposite side of the model should be removed. + assert ( + len(model.graph.node) <= 3 + ) # The branch on the opposite side of the model should be removed. @pytest.mark.parametrize("opts", [[], ["--force-fallback-shape-inference"]]) def test_reduce_shape_inference(self, opts, poly_debug): @@ -374,7 +387,12 @@ def test_reduce_custom_data_multibranch_input(self, negative, poly_debug, poly_r model = ONNX_MODELS["reducable"].path inp_data_path = os.path.join(outdir, "custom_inputs.json") - inputs = [{"X0": np.array([3.14159265], dtype=np.float32), "Y0": np.array([2.7749389])}] + inputs = [ + { + "X0": np.array([3.14159265], dtype=np.float32), + "Y0": np.array([2.7749389]), + } + ] save_json(inputs, inp_data_path) # Generate golden outputs @@ -416,10 +434,13 @@ def test_reduce_custom_data_multibranch_input(self, negative, poly_debug, poly_r # distinct from the input data we specified. # Otherwise, reduce should use the data loader we provided and hence pass assert ("FAILED" in status.stdout + status.stderr) == negative - assert ("Difference exceeds tolerance" in status.stdout + status.stderr) == negative + assert ( + "Difference exceeds tolerance" in status.stdout + status.stderr + ) == negative # Reduce should issue a warning when it detects that the default data loader is in use. assert ( - "Please ensure that you have provided a data loader argument directly" in status.stdout + status.stderr + "Please ensure that you have provided a data loader argument directly" + in status.stdout + status.stderr ) == negative @pytest.mark.script_launch_mode("subprocess") @@ -524,7 +545,9 @@ class TestRepeat: ) def test_until(self, until, check, expected_iters, poly_debug): with tempfile.TemporaryDirectory() as outdir: - status = poly_debug(["repeat", "--until", until, "--check", check], cwd=outdir) + status = poly_debug( + ["repeat", "--until", until, "--check", check], cwd=outdir + ) assert f"Finished {expected_iters} iteration(s)" in status.stdout def test_iteration_info(self, poly_debug): @@ -579,5 +602,7 @@ def test_iteration_info(self, poly_debug): ) def test_ignore_fail_code(self, poly_debug, opts, expected_output): with tempfile.TemporaryDirectory() as outdir: - status = poly_debug(["repeat", "--until=5"] + opts + ["--check", "false"], cwd=outdir) + status = poly_debug( + ["repeat", "--until=5"] + opts + ["--check", "false"], cwd=outdir + ) assert expected_output in status.stdout diff --git a/tools/Polygraphy/tests/tools/test_deprecated.py b/tools/Polygraphy/tests/tools/test_deprecated.py index b74bee29..4688fe87 100644 --- a/tools/Polygraphy/tests/tools/test_deprecated.py +++ b/tools/Polygraphy/tests/tools/test_deprecated.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/tools/test_inspect.py b/tools/Polygraphy/tests/tools/test_inspect.py index 953c21b3..cd09c581 100644 --- a/tools/Polygraphy/tests/tools/test_inspect.py +++ b/tools/Polygraphy/tests/tools/test_inspect.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -613,19 +613,21 @@ def test_num_items(self, poly_run, poly_inspect, num_items): TACTIC_REPLAY_CASES = [ [ "pow_scalar", - r""" + ( + r""" [I] Layer: (Unnamed Layer* 0) [Shuffle] Algorithm: (Implementation: 2147483661, Tactic: 0) | Inputs: (TensorInfo(DataType.FLOAT, (), -1, 1),) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) Layer: node_of_z Algorithm: (Implementation: 2147483651, Tactic: 1) | Inputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1), TensorInfo(DataType.FLOAT, (1,), -1, 1)) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) """ - if mod.version(trt.__version__) < mod.version("8.7") - else r""" + if mod.version(trt.__version__) < mod.version("8.7") + else r""" [I] Layer: ONNXTRT_Broadcast Algorithm: (Implementation: 2147483661, Tactic: 0) | Inputs: (TensorInfo(DataType.FLOAT, (), -1, 1),) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) Layer: PWN(node_of_z) Algorithm: (Implementation: 2147483688, Tactic: 1) | Inputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1), TensorInfo(DataType.FLOAT, (1,), -1, 1)) | Outputs: (TensorInfo(DataType.FLOAT, (1,), -1, 1),) - """, + """ + ), ], ] @@ -668,22 +670,24 @@ def test_show_tactics(self, case, poly_run, poly_inspect): "unsupported_subgraph-nodes-2-2.onnx", "unsupported_subgraph-nodes-4-4.onnx", ], - """ + ( + """ [I] ===== Summary ===== Operator | Count | Reason | Nodes ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- FAKE! | 2 | In node 0 (importFallbackPluginImporter): UNSUPPORTED_NODE: Assertion failed: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[0, 1], [2, 3]] FAKER! | 1 | In node 0 (importFallbackPluginImporter): UNSUPPORTED_NODE: Assertion failed: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[4, 5]] """ - if mod.version(trt.__version__) < mod.version("10.0") - else """ + if mod.version(trt.__version__) < mod.version("10.0") + else """ [I] ===== Summary ===== Operator | Count | Reason | Nodes -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- FAKE! | 1 | In node 0 with name: Fake1 and operator: FAKE! (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[0, 1]] FAKE! | 1 | In node 0 with name: Fake2 and operator: FAKE! (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[2, 3]] FAKER! | 1 | In node 0 with name: Fake3 and operator: FAKER! (checkFallbackPluginImporter): INVALID_NODE: creator && "Plugin not found, are the plugin name, version, and namespace correct?" | [[4, 5]] - """, + """ + ), ), ( "identity_identity", diff --git a/tools/Polygraphy/tests/tools/test_plugin.py b/tools/Polygraphy/tests/tools/test_plugin.py index a74f0b17..3215d7d5 100644 --- a/tools/Polygraphy/tests/tools/test_plugin.py +++ b/tools/Polygraphy/tests/tools/test_plugin.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -55,14 +55,14 @@ def test_match_toy(self, poly_plugin_match): num_plugins += 1 assert plugin["name"] == "toyPlugin" assert len(plugin["instances"]) == 1 - assert len(plugin["instances"][0]["inputs"]) == 2 + assert len(plugin["instances"][0]["inputs"]) == 1 assert len(plugin["instances"][0]["outputs"]) == 2 assert plugin["instances"][0]["attributes"]["ToyX"] == 2 assert num_plugins == 1 @pytest.mark.script_launch_mode("subprocess") - def test_match_list_toy(self, poly_plugin_list_plugins): + def test_list_toy(self, poly_plugin_list_plugins): status = poly_plugin_list_plugins( [self.TOY_MODEL_PATH, "--plugin-dir", self.PLUGINS_PATH] ) @@ -103,6 +103,6 @@ def test_replace_toy(self, poly_plugin_replace, poly_plugin_match): assert "n1" in node_names assert not node_names.intersection({"n2", "n3", "n4", "n5", "n6"}) - assert model.graph.node[1].op_type == "toyPlugin" + assert model.graph.node[1].op_type == "CustomToyPlugin" assert model.graph.node[1].attribute[0].name == "ToyX" assert model.graph.node[1].attribute[0].i == 2 diff --git a/tools/Polygraphy/tests/tools/test_polygraphy.py b/tools/Polygraphy/tests/tools/test_polygraphy.py index c3469484..a7257039 100644 --- a/tools/Polygraphy/tests/tools/test_polygraphy.py +++ b/tools/Polygraphy/tests/tools/test_polygraphy.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/tools/test_run.py b/tools/Polygraphy/tests/tools/test_run.py index 0e5e4a69..74bf4bcf 100644 --- a/tools/Polygraphy/tests/tools/test_run.py +++ b/tools/Polygraphy/tests/tools/test_run.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -72,12 +72,23 @@ def test_plugins(self, poly_run): ONNX_MODELS["identity"].path, "--trt", "--plugins", - "nvinfer_plugin.dll" if sys.platform.startswith("win") else "libnvinfer_plugin.so", + ( + "nvinfer_plugin.dll" + if sys.platform.startswith("win") + else "libnvinfer_plugin.so" + ), ] ) def test_custom_outputs(self, poly_run): - poly_run([ONNX_MODELS["identity_identity"].path, "--trt", "--trt-outputs", "identity_out_0"]) + poly_run( + [ + ONNX_MODELS["identity_identity"].path, + "--trt", + "--trt-outputs", + "identity_out_0", + ] + ) def test_layerwise_outputs(self, poly_run): with util.NamedTemporaryFile() as outfile0: @@ -125,10 +136,26 @@ def test_sparse_weights(self, poly_run): poly_run([ONNX_MODELS["identity"].path, "--trt", "--sparse-weights"]) def test_input_shape(self, poly_run): - poly_run([ONNX_MODELS["dynamic_identity"].path, "--trt", "--onnxrt", "--input-shapes", "X:[1,2,4,4]"]) + poly_run( + [ + ONNX_MODELS["dynamic_identity"].path, + "--trt", + "--onnxrt", + "--input-shapes", + "X:[1,2,4,4]", + ] + ) def test_dynamic_input_shape(self, poly_run): - poly_run([ONNX_MODELS["dynamic_identity"].path, "--trt", "--onnxrt", "--input-shapes", "X:[1,2,-1,4]"]) + poly_run( + [ + ONNX_MODELS["dynamic_identity"].path, + "--trt", + "--onnxrt", + "--input-shapes", + "X:[1,2,-1,4]", + ] + ) def test_explicit_profile(self, poly_run): poly_run( @@ -212,7 +239,9 @@ def test_multiple_profiles(self, poly_run, optimization_profile): mod.version(trt.__version__) < mod.version("10.0"), reason="Feature not present before 10.0", ) - @pytest.mark.parametrize("allocation_strategy", [None, "static", "profile", "runtime"]) + @pytest.mark.parametrize( + "allocation_strategy", [None, "static", "profile", "runtime"] + ) def test_allocation_strategies(self, poly_run, allocation_strategy): cmd = [ ONNX_MODELS["residual_block"].path, @@ -245,14 +274,28 @@ def test_allocation_strategies(self, poly_run, allocation_strategy): def test_int8_calibration_cache(self, poly_run): with util.NamedTemporaryFile() as outpath: - cmd = [ONNX_MODELS["identity"].path, "--trt", "--int8", "--calibration-cache", outpath.name] + cmd = [ + ONNX_MODELS["identity"].path, + "--trt", + "--int8", + "--calibration-cache", + outpath.name, + ] cmd += ["--onnxrt"] poly_run(cmd) assert is_file_non_empty(outpath.name) - @pytest.mark.parametrize("base_class", ["IInt8LegacyCalibrator", "IInt8EntropyCalibrator2"]) + @pytest.mark.parametrize( + "base_class", ["IInt8LegacyCalibrator", "IInt8EntropyCalibrator2"] + ) def test_int8_calibration_base_class(self, poly_run, base_class): - cmd = [ONNX_MODELS["identity"].path, "--trt", "--int8", "--calibration-base-class", base_class] + cmd = [ + ONNX_MODELS["identity"].path, + "--trt", + "--int8", + "--calibration-base-class", + base_class, + ] cmd += ["--onnxrt"] poly_run() @@ -262,39 +305,89 @@ def test_timing_cache(self, poly_run): total_cache = os.path.join(dir, "total.cache") identity_cache = os.path.join(dir, "identity.cache") - poly_run([ONNX_MODELS["const_foldable"].path, "--trt", "--save-timing-cache", total_cache]) + poly_run( + [ + ONNX_MODELS["const_foldable"].path, + "--trt", + "--save-timing-cache", + total_cache, + ] + ) assert is_file_non_empty(total_cache) const_foldable_cache_size = get_file_size(total_cache) - poly_run([ONNX_MODELS["identity"].path, "--trt", "--save-timing-cache", identity_cache]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--trt", + "--save-timing-cache", + identity_cache, + ] + ) identity_cache_size = get_file_size(identity_cache) - poly_run([ONNX_MODELS["identity"].path, "--trt", "--save-timing-cache", total_cache]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--trt", + "--save-timing-cache", + total_cache, + ] + ) total_cache_size = get_file_size(total_cache) # The total cache should be larger than either of the individual caches. - assert total_cache_size >= const_foldable_cache_size and total_cache_size >= identity_cache_size + assert ( + total_cache_size >= const_foldable_cache_size + and total_cache_size >= identity_cache_size + ) # The total cache should also be smaller than or equal to the sum of the individual caches since # header information should not be duplicated. assert total_cache_size <= (const_foldable_cache_size + identity_cache_size) def test_save_load_engine(self, poly_run): with util.NamedTemporaryFile() as outpath: - poly_run([ONNX_MODELS["identity"].path, "--trt", "--save-engine", outpath.name]) + poly_run( + [ONNX_MODELS["identity"].path, "--trt", "--save-engine", outpath.name] + ) assert is_file_non_empty(outpath.name) poly_run(["--trt", outpath.name, "--model-type=engine"]) def test_tactic_replay(self, poly_run): with util.NamedTemporaryFile() as tactic_replay: - poly_run([ONNX_MODELS["identity"].path, "--trt", "--save-tactics", tactic_replay.name]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--trt", + "--save-tactics", + tactic_replay.name, + ] + ) assert is_file_non_empty(tactic_replay.name) - poly_run([ONNX_MODELS["identity"].path, "--trt", "--load-tactics", tactic_replay.name]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--trt", + "--load-tactics", + tactic_replay.name, + ] + ) def test_tactic_sources(self, poly_run): - poly_run([ONNX_MODELS["identity"].path, "--trt", "--tactic-sources", "CUBLAS", "CUBLAS_LT"]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--trt", + "--tactic-sources", + "CUBLAS", + "CUBLAS_LT", + ] + ) def test_pool_limits(self, poly_run): - poly_run([ONNX_MODELS["identity"].path, "--trt", "--pool-limit", "workspace:32M"]) + poly_run( + [ONNX_MODELS["identity"].path, "--trt", "--pool-limit", "workspace:32M"] + ) def test_data_loader_script_calibration(self, poly_run): with util.NamedTemporaryFile("w+", suffix=".py") as f: @@ -312,7 +405,15 @@ def load_data(): f.flush() os.fsync(f.fileno()) - poly_run([ONNX_MODELS["identity"].path, "--trt", "--int8", "--data-loader-script", f.name]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--trt", + "--int8", + "--data-loader-script", + f.name, + ] + ) class TestTf: @@ -323,13 +424,29 @@ def test_tf(self, poly_run): def test_tf_save_pb(self, poly_run): pytest.importorskip("tensorflow") with util.NamedTemporaryFile() as outpath: - poly_run([TF_MODELS["identity"].path, "--tf", "--gpu-memory-fraction=0.5", "--save-pb", outpath.name]) + poly_run( + [ + TF_MODELS["identity"].path, + "--tf", + "--gpu-memory-fraction=0.5", + "--save-pb", + outpath.name, + ] + ) assert is_file_non_empty(outpath.name) def test_tf_save_tensorboard(self, poly_run): pytest.importorskip("tensorflow") with tempfile.TemporaryDirectory() as outdir: - poly_run([TF_MODELS["identity"].path, "--tf", "--gpu-memory-fraction=0.5", "--save-tensorboard", outdir]) + poly_run( + [ + TF_MODELS["identity"].path, + "--tf", + "--gpu-memory-fraction=0.5", + "--save-tensorboard", + outdir, + ] + ) files = glob.glob(f"{outdir}{os.path.sep}*") assert len(files) == 1 @@ -337,7 +454,15 @@ def test_tf_save_tensorboard(self, poly_run): def test_tf_save_timeline(self, poly_run): pytest.importorskip("tensorflow") with util.NamedTemporaryFile() as outpath: - poly_run([TF_MODELS["identity"].path, "--tf", "--gpu-memory-fraction=0.5", "--save-timeline", outpath.name]) + poly_run( + [ + TF_MODELS["identity"].path, + "--tf", + "--gpu-memory-fraction=0.5", + "--save-timeline", + outpath.name, + ] + ) timelines = glob.glob(os.path.join(outpath.name, "*")) for timeline in timelines: assert is_file_non_empty(timeline) @@ -354,12 +479,21 @@ def test_onnx_rt(self, poly_run): def test_onnx_rt_save_onnx(self, poly_run): with util.NamedTemporaryFile() as outpath: - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--save-onnx", outpath.name]) + poly_run( + [ONNX_MODELS["identity"].path, "--onnxrt", "--save-onnx", outpath.name] + ) assert is_file_non_empty(outpath.name) assert onnx.load(outpath.name) def test_onnx_rt_custom_outputs(self, poly_run): - poly_run([ONNX_MODELS["identity_identity"].path, "--onnxrt", "--onnx-outputs", "identity_out_0"]) + poly_run( + [ + ONNX_MODELS["identity_identity"].path, + "--onnxrt", + "--onnx-outputs", + "identity_out_0", + ] + ) def test_onnx_rt_layerwise_outputs(self, poly_run): with util.NamedTemporaryFile() as outfile0: @@ -417,11 +551,37 @@ def test_subprocess_sanity(self, poly_run): def test_exit_status_on_fail_comparison(self, poly_run, tmp_path): OUTFILE0 = os.path.join(tmp_path, "outputs0.json") - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--save-outputs", OUTFILE0, "--seed=1"]) - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--load-outputs", OUTFILE0, "--seed=2"], expect_error=True) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--save-outputs", + OUTFILE0, + "--seed=1", + ] + ) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--load-outputs", + OUTFILE0, + "--seed=2", + ], + expect_error=True, + ) def test_custom_tolerance(self, poly_run): - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--onnxrt", "--iterations=0", "--atol=1.0", "--rtol=1.0"]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--onnxrt", + "--iterations=0", + "--atol=1.0", + "--rtol=1.0", + ] + ) def test_custom_per_output_tolerance(self, poly_run): poly_run( @@ -444,14 +604,38 @@ def test_custom_per_output_tolerance(self, poly_run): ) def test_custom_input_ranges(self, poly_run): - poly_run([ONNX_MODELS["identity_identity"].path, "--onnxrt", "--val-range", "X:[1.0,2.0]", "[0.5,1.5]"]) + poly_run( + [ + ONNX_MODELS["identity_identity"].path, + "--onnxrt", + "--val-range", + "X:[1.0,2.0]", + "[0.5,1.5]", + ] + ) def test_index_comparison(self, poly_run): - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--postprocess", "top-1", "--compare-func=indices"]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--postprocess", + "top-1", + "--compare-func=indices", + ] + ) @pytest.mark.parametrize("check_error_stat", ["max", "median", "mean", "quantile"]) def test_check_error_stat(self, poly_run, check_error_stat): - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--onnxrt", "--check-error-stat", check_error_stat]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--onnxrt", + "--check-error-stat", + check_error_stat, + ] + ) def test_save_load_outputs(self, poly_run, tmp_path): OUTFILE0 = os.path.join(tmp_path, "outputs0.json") @@ -459,7 +643,15 @@ def test_save_load_outputs(self, poly_run, tmp_path): poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--save-outputs", OUTFILE0]) poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--save-outputs", OUTFILE1]) - status = poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--load-outputs", OUTFILE0, OUTFILE1]) + status = poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--load-outputs", + OUTFILE0, + OUTFILE1, + ] + ) assert ( "Difference is within tolerance" in status.stdout + status.stderr ) # Make sure it actually compared stuff. @@ -471,20 +663,31 @@ def test_save_load_outputs(self, poly_run, tmp_path): ) # Make sure it DIDN'T compare stuff. # Should work even with no runners specified - status = poly_run([ONNX_MODELS["identity"].path, "--load-outputs", OUTFILE0, OUTFILE1]) + status = poly_run( + [ONNX_MODELS["identity"].path, "--load-outputs", OUTFILE0, OUTFILE1] + ) assert ( "Difference is within tolerance" in status.stdout + status.stderr ) # Make sure it actually compared stuff. # Should work even when comparing a single runner to itself. - status = poly_run([ONNX_MODELS["identity"].path, "--load-outputs", OUTFILE0, OUTFILE0]) + status = poly_run( + [ONNX_MODELS["identity"].path, "--load-outputs", OUTFILE0, OUTFILE0] + ) assert ( "Difference is within tolerance" in status.stdout + status.stderr ) # Make sure it actually compared stuff. def test_save_load_inputs(self, poly_run): with util.NamedTemporaryFile() as infile0, util.NamedTemporaryFile() as infile1: - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--save-input-data", infile0.name]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--save-input-data", + infile0.name, + ] + ) poly_run( [ ONNX_MODELS["identity"].path, @@ -495,14 +698,30 @@ def test_save_load_inputs(self, poly_run): infile1.name, ] ) # Copy - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--load-input-data", infile0.name, infile1.name]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--load-input-data", + infile0.name, + infile1.name, + ] + ) def test_load_torch_inputs(self, poly_run): with util.NamedTemporaryFile() as infile: inp = torch.ones((1, 1, 2, 2), dtype=torch.float32) feed_dict = [{"x": inp}] save_json(feed_dict, infile.name) - poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--onnxrt", "--load-inputs", infile.name]) + poly_run( + [ + ONNX_MODELS["identity"].path, + "--onnxrt", + "--onnxrt", + "--load-inputs", + infile.name, + ] + ) def test_runner_coexistence(self, poly_run): poly_run([ONNX_MODELS["identity"].path, "--onnxrt", "--trt"]) @@ -514,7 +733,15 @@ def test_tf2onnxrt(self, poly_run): def test_tf2onnx_save_onnx(self, poly_run): pytest.importorskip("tensorflow") with util.NamedTemporaryFile() as outpath: - poly_run([TF_MODELS["identity"].path, "--onnxrt", "--model-type=frozen", "--save-onnx", outpath.name]) + poly_run( + [ + TF_MODELS["identity"].path, + "--onnxrt", + "--model-type=frozen", + "--save-onnx", + outpath.name, + ] + ) assert is_file_non_empty(outpath.name) assert onnx.load(outpath.name) diff --git a/tools/Polygraphy/tests/tools/test_script.py b/tools/Polygraphy/tests/tools/test_script.py index 4dabb64a..acfebb2c 100644 --- a/tools/Polygraphy/tests/tools/test_script.py +++ b/tools/Polygraphy/tests/tools/test_script.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -17,7 +17,12 @@ import pytest from polygraphy.exception import PolygraphyInternalException -from polygraphy.tools.script import Script, inline, make_invocable, make_invocable_if_nondefault +from polygraphy.tools.script import ( + Script, + inline, + make_invocable, + make_invocable_if_nondefault, +) def make_test_string(): @@ -38,7 +43,9 @@ class TestScript: ) def test_add_funcs_fail_on_unsafe(self, func): script = Script() - with pytest.raises(PolygraphyInternalException, match="was not checked for safety"): + with pytest.raises( + PolygraphyInternalException, match="was not checked for safety" + ): func(script) @pytest.mark.parametrize( diff --git a/tools/Polygraphy/tests/tools/test_surgeon.py b/tools/Polygraphy/tests/tools/test_surgeon.py index 88a4ab61..bbb594a4 100644 --- a/tools/Polygraphy/tests/tools/test_surgeon.py +++ b/tools/Polygraphy/tests/tools/test_surgeon.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -35,6 +35,7 @@ def onnx_model_sanity_check_impl(model_path): return onnx_model_sanity_check_impl + def get_exclude_list(exclude_list): if not exclude_list: return set() @@ -42,29 +43,34 @@ def get_exclude_list(exclude_list): lines = [line.rstrip() for line in fp] return set(lines) + def pruned_initializer_sanity_check(opath, is_sparse=False, exclude_list=None): exclude_list = get_exclude_list(exclude_list) # we only prune the input data of QuantizeLinear and leave the scale and zero_point untouched - if 'qdq' in opath: - exclude_list.add('y_scale') - exclude_list.add('y_zero_point') + if "qdq" in opath: + exclude_list.add("y_scale") + exclude_list.add("y_zero_point") model = onnx.load(opath) for initializer in model.graph.initializer: # If initializer is to be left un-stripped if initializer.name in exclude_list: # ensure initializer is non-empty and doc_string doesn't contain the weightless flag - shape_match = list(numpy_helper.to_array(initializer).shape) == initializer.dims + shape_match = ( + list(numpy_helper.to_array(initializer).shape) == initializer.dims + ) if "TRT_WEIGHTLESS" in initializer.doc_string or not shape_match: return False continue # ensure initializer is empty and doc_string is in required format init_empty = initializer.raw_data == b"" - trt_weightless, sparsity = initializer.doc_string.split('/') + trt_weightless, sparsity = initializer.doc_string.split("/") trt_weightless_correctness = trt_weightless == "TRT_WEIGHTLESS" sparsity_correctness = False - if (not is_sparse and sparsity == "") or (is_sparse and sparsity == "SPARSE_2_4"): + if (not is_sparse and sparsity == "") or ( + is_sparse and sparsity == "SPARSE_2_4" + ): sparsity_correctness = True if not (init_empty and trt_weightless_correctness and sparsity_correctness): @@ -72,6 +78,7 @@ def pruned_initializer_sanity_check(opath, is_sparse=False, exclude_list=None): return True + def get_initializers_to_sparsify(ipath): model = onnx.load(ipath) initializers_to_sparsify = set() @@ -81,6 +88,7 @@ def get_initializers_to_sparsify(ipath): return initializers_to_sparsify + def reconstructed_initializer_sanity_check(opath, initializers_to_sparsify): model = onnx.load(opath) sparsity_checker = SparsityPruner(model) @@ -92,13 +100,19 @@ def reconstructed_initializer_sanity_check(opath, initializers_to_sparsify): return False # ensure sparsity of initializers is retained - if initializer.name in initializers_to_sparsify and initializer.name not in sparse_tensors: + if ( + initializer.name in initializers_to_sparsify + and initializer.name not in sparse_tensors + ): return False return True + def was_shape_inference_run(status, model): - logging_correct = "Shape inference completed successfully" in (status.stdout + status.stderr) + logging_correct = "Shape inference completed successfully" in ( + status.stdout + status.stderr + ) has_shape = True model = onnx.load(model) @@ -109,15 +123,25 @@ def was_shape_inference_run(status, model): class TestSurgeonExtract: - def test_no_shape_inference_if_has_metadata(self, poly_surgeon_extract, onnx_model_sanity_check): + def test_no_shape_inference_if_has_metadata( + self, poly_surgeon_extract, onnx_model_sanity_check + ): with util.NamedTemporaryFile() as outmodel: status = poly_surgeon_extract( - [ONNX_MODELS["identity_identity"].path, "-o", outmodel.name, "--inputs", "X:auto:auto"] + [ + ONNX_MODELS["identity_identity"].path, + "-o", + outmodel.name, + "--inputs", + "X:auto:auto", + ] ) onnx_model_sanity_check(outmodel.name) assert not was_shape_inference_run(status, outmodel.name) - def test_onnx_shape_inference_if_no_metadata(self, poly_surgeon_extract, onnx_model_sanity_check): + def test_onnx_shape_inference_if_no_metadata( + self, poly_surgeon_extract, onnx_model_sanity_check + ): with util.NamedTemporaryFile() as outmodel: status = poly_surgeon_extract( [ @@ -131,7 +155,9 @@ def test_onnx_shape_inference_if_no_metadata(self, poly_surgeon_extract, onnx_mo onnx_model_sanity_check(outmodel.name) assert was_shape_inference_run(status, outmodel.name) - def test_fallback_shape_inference_no_onnx_shape_inference(self, poly_surgeon_extract, onnx_model_sanity_check): + def test_fallback_shape_inference_no_onnx_shape_inference( + self, poly_surgeon_extract, onnx_model_sanity_check + ): with util.NamedTemporaryFile() as outmodel: status = poly_surgeon_extract( [ @@ -177,7 +203,13 @@ def test_sanity_dim_param(self, poly_surgeon_extract, onnx_model_sanity_check): class TestSurgeonInsert: - def check_insert_model(self, path, expected_node_ops, expected_graph_input_names, expected_graph_output_names): + def check_insert_model( + self, + path, + expected_node_ops, + expected_graph_input_names, + expected_graph_output_names, + ): model = onnx.load(path) assert [node.op_type for node in model.graph.node] == expected_node_ops @@ -203,7 +235,12 @@ def test_insert_at_tensor(self, poly_surgeon): "--op=FakeOp", ] ) - self.check_insert_model(outmodel.name, ["Identity", "FakeOp", "Identity"], ["X"], ["identity_out_2"]) + self.check_insert_model( + outmodel.name, + ["Identity", "FakeOp", "Identity"], + ["X"], + ["identity_out_2"], + ) def test_graph_output(self, poly_surgeon): # FakeOp output tensor should be marked as a graph output. Name should be preserved - identity_out_2 @@ -219,7 +256,12 @@ def test_graph_output(self, poly_surgeon): "--op=FakeOp", ] ) - self.check_insert_model(outmodel.name, ["Identity", "Identity", "FakeOp"], ["X"], ["identity_out_2"]) + self.check_insert_model( + outmodel.name, + ["Identity", "Identity", "FakeOp"], + ["X"], + ["identity_out_2"], + ) def test_at_graph_input(self, poly_surgeon): with util.NamedTemporaryFile() as outmodel: @@ -234,7 +276,12 @@ def test_at_graph_input(self, poly_surgeon): "--op=FakeOp", ] ) - self.check_insert_model(outmodel.name, ["FakeOp", "Identity", "Identity"], ["X"], ["identity_out_2"]) + self.check_insert_model( + outmodel.name, + ["FakeOp", "Identity", "Identity"], + ["X"], + ["identity_out_2"], + ) # When a specified input tensor is used by multiple other nodes, it should not be # disconnected from other nodes. @@ -309,7 +356,10 @@ def test_with_attributes(self, poly_surgeon): ] ) model = self.check_insert_model( - outmodel.name, ["FakeOp", "Identity", "Identity"], ["X"], ["identity_out_2"] + outmodel.name, + ["FakeOp", "Identity", "Identity"], + ["X"], + ["identity_out_2"], ) node = model.graph.node[0] @@ -337,10 +387,14 @@ def test_with_attributes(self, poly_surgeon): class TestSurgeonSanitize: - @pytest.mark.parametrize("no_per_pass_shape_inf", [None, "--no-per-pass-shape-inference"]) + @pytest.mark.parametrize( + "no_per_pass_shape_inf", [None, "--no-per-pass-shape-inference"] + ) @pytest.mark.parametrize("fold_shapes", [None, "--no-fold-shapes"]) @pytest.mark.parametrize("partitioning", [None, "basic", "recursive"]) - @pytest.mark.parametrize("no_onnxruntime_shape_inference", [None, "--no-onnxruntime-shape-inference"]) + @pytest.mark.parametrize( + "no_onnxruntime_shape_inference", [None, "--no-onnxruntime-shape-inference"] + ) def test_fold_constants( self, poly_surgeon, @@ -351,7 +405,14 @@ def test_fold_constants( no_onnxruntime_shape_inference, ): with util.NamedTemporaryFile() as outmodel: - cmd = ["sanitize", ONNX_MODELS["const_foldable"].path, "-o", outmodel.name, "--fold-constants", "-v"] + cmd = [ + "sanitize", + ONNX_MODELS["const_foldable"].path, + "-o", + outmodel.name, + "--fold-constants", + "-v", + ] if fold_shapes: cmd += [fold_shapes] if partitioning: @@ -362,9 +423,10 @@ def test_fold_constants( cmd += [no_onnxruntime_shape_inference] status = poly_surgeon(cmd) - assert ("Inferred shapes in the model with `onnxruntime.tools.symbolic_shape_infer`" in status.stdout) == ( - no_onnxruntime_shape_inference is None - ) + assert ( + "Inferred shapes in the model with `onnxruntime.tools.symbolic_shape_infer`" + in status.stdout + ) == (no_onnxruntime_shape_inference is None) onnx_model_sanity_check(outmodel.name) model = onnx.load(outmodel.name) @@ -372,7 +434,13 @@ def test_fold_constants( @pytest.mark.parametrize("global_upper_bound", [None, "2000"]) @pytest.mark.parametrize("specified_upper_bound", [None, "cast_out_6:4000"]) - def test_set_upper_bound(self, poly_surgeon, global_upper_bound, specified_upper_bound, onnx_model_sanity_check): + def test_set_upper_bound( + self, + poly_surgeon, + global_upper_bound, + specified_upper_bound, + onnx_model_sanity_check, + ): with util.NamedTemporaryFile() as outmodel: cmd = [ "sanitize", @@ -474,8 +542,12 @@ def test_override_shapes_partial_inputs(self, poly_surgeon): ] ) model = onnx.load(outmodel.name) - assert model.graph.input[0].type.tensor_type.shape.dim[2].dim_param == "height" - assert model.graph.input[0].type.tensor_type.shape.dim[3].dim_param == "width" + assert ( + model.graph.input[0].type.tensor_type.shape.dim[2].dim_param == "height" + ) + assert ( + model.graph.input[0].type.tensor_type.shape.dim[3].dim_param == "width" + ) def test_override_shapes_no_reorder(self, poly_surgeon): with util.NamedTemporaryFile() as outmodel: @@ -497,7 +569,15 @@ def test_override_shapes_no_reorder(self, poly_surgeon): def test_modify_onnx_outputs(self, poly_surgeon): with util.NamedTemporaryFile(suffix=".onnx") as outmodel: poly_surgeon( - ["sanitize", ONNX_MODELS["identity_identity"].path, "-o", outmodel.name, "--outputs", "mark", "all"] + [ + "sanitize", + ONNX_MODELS["identity_identity"].path, + "-o", + outmodel.name, + "--outputs", + "mark", + "all", + ] ) model = onnx.load(outmodel.name) @@ -561,7 +641,9 @@ def test_external_data(self, poly_surgeon, poly_run): assert is_file_non_empty(os.path.join(outdir, outdata)) assert poly_run([outmodel, "--onnxrt", "--external-data-dir", outdir]) - def test_force_fallback_shape_inference_will_override_model_shapes(self, poly_surgeon, onnx_model_sanity_check): + def test_force_fallback_shape_inference_will_override_model_shapes( + self, poly_surgeon, onnx_model_sanity_check + ): with util.NamedTemporaryFile() as outmodel: status = poly_surgeon( [ @@ -589,7 +671,9 @@ def test_force_fallback_shape_inference_will_override_model_shapes(self, poly_su ("9.99M", False), ], ) - def test_size_threshold(self, poly_surgeon, size_threshold, expect_folding, onnx_model_sanity_check): + def test_size_threshold( + self, poly_surgeon, size_threshold, expect_folding, onnx_model_sanity_check + ): with util.NamedTemporaryFile() as outmodel: poly_surgeon( [ @@ -614,7 +698,10 @@ def test_size_threshold(self, poly_surgeon, size_threshold, expect_folding, onnx class TestSurgeonPrune: - @pytest.mark.parametrize("model_name", ["matmul", "matmul.fp16", "matmul.bf16", "matmul.bf16.i32data", "conv"]) + @pytest.mark.parametrize( + "model_name", + ["matmul", "matmul.fp16", "matmul.bf16", "matmul.bf16.i32data", "conv"], + ) def test_prune(self, poly_surgeon, onnx_model_sanity_check, model_name): with tempfile.TemporaryDirectory() as outdir: ipath = ONNX_MODELS[model_name].path @@ -624,9 +711,21 @@ def test_prune(self, poly_surgeon, onnx_model_sanity_check, model_name): if "bf16" not in ipath: onnx_model_sanity_check(opath) + class TestSurgeonWeightStrip: - @pytest.mark.parametrize("model_name", ["matmul", "matmul.fp16", "matmul.bf16", "conv", "sparse.matmul", "sparse.conv", - "transpose_matmul", "qdq_conv"]) + @pytest.mark.parametrize( + "model_name", + [ + "matmul", + "matmul.fp16", + "matmul.bf16", + "conv", + "sparse.matmul", + "sparse.conv", + "transpose_matmul", + "qdq_conv", + ], + ) def test_weight_strip(self, poly_surgeon, model_name): with tempfile.TemporaryDirectory() as outdir: ipath = ONNX_MODELS[model_name].path @@ -638,25 +737,45 @@ def test_weight_strip(self, poly_surgeon, model_name): assert pruned_initializer_sanity_check(opath, is_sparse=is_sparse) @pytest.mark.parametrize( - "model_name, exclude_list", [ - ["matmul", "matmul.exclude_list.txt"], - ["sparse.conv", "sparse.conv.exclude_list.txt"], - ["qdq_conv", "qdq_conv.exclude_list.txt"]]) + "model_name, exclude_list", + [ + ["matmul", "matmul.exclude_list.txt"], + ["sparse.conv", "sparse.conv.exclude_list.txt"], + ["qdq_conv", "qdq_conv.exclude_list.txt"], + ], + ) def test_weight_strip_exclude_file(self, poly_surgeon, model_name, exclude_list): with tempfile.TemporaryDirectory() as outdir: ipath = ONNX_MODELS[model_name].path exclude_list = model_path(exclude_list) opath = os.path.join(outdir, "weightless_sparse." + os.path.basename(ipath)) - status = poly_surgeon(["weight-strip", ipath, "-o", opath, "--exclude-list", exclude_list]) + status = poly_surgeon( + ["weight-strip", ipath, "-o", opath, "--exclude-list", exclude_list] + ) assert status is_sparse = "sparse" in ipath - assert pruned_initializer_sanity_check(opath, is_sparse=is_sparse, exclude_list=exclude_list) + assert pruned_initializer_sanity_check( + opath, is_sparse=is_sparse, exclude_list=exclude_list + ) + class TestSurgeonWeightReconstruct: - @pytest.mark.parametrize("model_name", ["weightless.matmul.fp16", "weightless.matmul.bf16", "weightless.conv", "weightless.sparse.matmul", - "weightless.sparse.conv", "weightless.transpose_matmul", "weightless.qdq_conv"]) - def test_weight_reconstruct(self, poly_surgeon, onnx_model_sanity_check, model_name): + @pytest.mark.parametrize( + "model_name", + [ + "weightless.matmul.fp16", + "weightless.matmul.bf16", + "weightless.conv", + "weightless.sparse.matmul", + "weightless.sparse.conv", + "weightless.transpose_matmul", + "weightless.qdq_conv", + ], + ) + def test_weight_reconstruct( + self, poly_surgeon, onnx_model_sanity_check, model_name + ): with tempfile.TemporaryDirectory() as outdir: ipath = ONNX_MODELS[model_name].path opath = os.path.join(outdir, "reconstruct." + os.path.basename(ipath)) @@ -666,4 +785,6 @@ def test_weight_reconstruct(self, poly_surgeon, onnx_model_sanity_check, model_n onnx_model_sanity_check(opath) initializers_to_sparsify = get_initializers_to_sparsify(ipath) - assert reconstructed_initializer_sanity_check(opath, initializers_to_sparsify) \ No newline at end of file + assert reconstructed_initializer_sanity_check( + opath, initializers_to_sparsify + ) diff --git a/tools/Polygraphy/tests/tools/test_template.py b/tools/Polygraphy/tests/tools/test_template.py index 7f6ba23b..e4ad9dec 100644 --- a/tools/Polygraphy/tests/tools/test_template.py +++ b/tools/Polygraphy/tests/tools/test_template.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -40,7 +40,9 @@ def test_no_model_file(self, poly_template): def test_with_model_file(self, poly_template): with util.NamedTemporaryFile("w+", suffix=".py") as template: - poly_template(["trt-network", ONNX_MODELS["identity"].path, "-o", template.name]) + poly_template( + ["trt-network", ONNX_MODELS["identity"].path, "-o", template.name] + ) load_network = InvokeFromScript(template.name, "load_network") builder, network, parser = load_network() diff --git a/tools/Polygraphy/tests/util/test_array.py b/tools/Polygraphy/tests/util/test_array.py index 8450be68..eaea1ef8 100644 --- a/tools/Polygraphy/tests/util/test_array.py +++ b/tools/Polygraphy/tests/util/test_array.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -239,10 +239,16 @@ def test_binary_funcs(self, obj, np_arr, func, np_func): @pytest.mark.parametrize( "func, np_func, types", [ - (util.array.where, np.where, tuple(map(DataType.from_dtype, (np.bool8, np.float32, np.float32)))), + ( + util.array.where, + np.where, + tuple(map(DataType.from_dtype, (np.bool8, np.float32, np.float32))), + ), ], ) def test_ternary_funcs(self, obj, np_arr, func, np_func, types): - build_inputs = lambda input: map(lambda pair: util.array.cast(input + pair[0], pair[1]), enumerate(types)) + build_inputs = lambda input: map( + lambda pair: util.array.cast(input + pair[0], pair[1]), enumerate(types) + ) obj = func(*build_inputs(obj)) assert util.array.equal(obj, np.array(np_func(*build_inputs(np_arr)))) diff --git a/tools/Polygraphy/tests/util/test_serde.py b/tools/Polygraphy/tests/util/test_serde.py index 19dcc8bf..89d6d198 100644 --- a/tools/Polygraphy/tests/util/test_serde.py +++ b/tools/Polygraphy/tests/util/test_serde.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); diff --git a/tools/Polygraphy/tests/util/test_util.py b/tools/Polygraphy/tests/util/test_util.py index 6c2f302c..02943c12 100644 --- a/tools/Polygraphy/tests/util/test_util.py +++ b/tools/Polygraphy/tests/util/test_util.py @@ -1,5 +1,5 @@ # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -49,12 +49,23 @@ def __init__(self, name, seq, index, expected): FIND_STR_IN_ITERABLE_CASES = [ # Case insensitve, plus function should return element from sequence, not name. - FindStrInIterableCase("Softmax:0", seq=["Softmax:0"], index=None, expected="Softmax:0"), - FindStrInIterableCase("Softmax:0", seq=["softmax:0"], index=None, expected="softmax:0"), + FindStrInIterableCase( + "Softmax:0", seq=["Softmax:0"], index=None, expected="Softmax:0" + ), + FindStrInIterableCase( + "Softmax:0", seq=["softmax:0"], index=None, expected="softmax:0" + ), # Exact matches should take priority - FindStrInIterableCase("exact_name", seq=["exact_name_plus", "exact_name"], index=0, expected="exact_name"), + FindStrInIterableCase( + "exact_name", + seq=["exact_name_plus", "exact_name"], + index=0, + expected="exact_name", + ), # Index should come into play when no matches are found - FindStrInIterableCase("non-existent", seq=["test", "test2"], index=1, expected="test2"), + FindStrInIterableCase( + "non-existent", seq=["test", "test2"], index=1, expected="test2" + ), ] @@ -72,7 +83,10 @@ def test_find_str_in_iterable(case): @pytest.mark.parametrize("case", SHAPE_OVERRIDE_CASES) def test_is_valid_shape_override(case): override, shape, expected = case - assert util.is_valid_shape_override(new_shape=override, original_shape=shape) == expected + assert ( + util.is_valid_shape_override(new_shape=override, original_shape=shape) + == expected + ) def arange(shape): @@ -88,8 +102,16 @@ def arange(shape): ), # Permutation should make no difference as other dimensions are 1s (arange((3, 3)), (1, 1, 3, 3), arange((1, 1, 3, 3))), # Unsqueeze where needed (arange((3, 3)), (-1, 3), arange((3, 3))), # Infer dynamic - (arange((3 * 2 * 2,)), (None, 3, 2, 2), arange((1, 3, 2, 2))), # Reshape with inferred dimension - (arange((1, 3, 2, 2)), (None, 2, 2, 3), np.transpose(arange((1, 3, 2, 2)), [0, 2, 3, 1])), # Permute + ( + arange((3 * 2 * 2,)), + (None, 3, 2, 2), + arange((1, 3, 2, 2)), + ), # Reshape with inferred dimension + ( + arange((1, 3, 2, 2)), + (None, 2, 2, 3), + np.transpose(arange((1, 3, 2, 2)), [0, 2, 3, 1]), + ), # Permute ] build_torch = lambda a, **kwargs: util.array.to_torch(np.array(a, **kwargs)) @@ -132,7 +154,12 @@ def test_unique_list(case): def test_find_in_dirs(): with tempfile.TemporaryDirectory() as topdir: - dirs = list(map(lambda x: os.path.join(topdir, x), ["test0", "test1", "test2", "test3", "test4"])) + dirs = list( + map( + lambda x: os.path.join(topdir, x), + ["test0", "test1", "test2", "test3", "test4"], + ) + ) for subdir in dirs: os.makedirs(subdir) @@ -171,7 +198,10 @@ def write_to_file(path, content): outfile = util.NamedTemporaryFile() processes = [ - Process(target=write_to_file, args=(outfile.name, f"{proc} - writing line\n" * NUM_LINES)) + Process( + target=write_to_file, + args=(outfile.name, f"{proc} - writing line\n" * NUM_LINES), + ) for proc in range(NUM_PROCESSES) ] @@ -194,7 +224,10 @@ def write_to_file(path, content): for idx in range(NUM_PROCESSES): offset = idx * NUM_LINES expected_prefix = lines[offset].partition("-")[0].strip() - assert all(line.startswith(expected_prefix) for line in lines[offset : offset + NUM_LINES]) + assert all( + line.startswith(expected_prefix) + for line in lines[offset : offset + NUM_LINES] + ) # Make sure the lock file is written to the correct path and not removed automatically. assert os.path.exists(outfile.name + ".lock") @@ -205,20 +238,32 @@ def test_basic(self): assert util.make_repr("Example", 1, x=2) == ("Example(1, x=2)", False, False) def test_default_args(self): - assert util.make_repr("Example", None, None, x=2) == ("Example(None, None, x=2)", True, False) + assert util.make_repr("Example", None, None, x=2) == ( + "Example(None, None, x=2)", + True, + False, + ) def test_empty_args_are_default(self): assert util.make_repr("Example", x=2) == ("Example(x=2)", True, False) def test_default_kwargs(self): - assert util.make_repr("Example", 1, 2, x=None, y=None) == ("Example(1, 2)", False, True) + assert util.make_repr("Example", 1, 2, x=None, y=None) == ( + "Example(1, 2)", + False, + True, + ) def test_empty_kwargs_are_default(self): assert util.make_repr("Example", 1, 2) == ("Example(1, 2)", False, True) def test_does_not_modify(self): obj = {"x": float("inf")} - assert util.make_repr("Example", obj) == ("Example({'x': float('inf')})", False, True) + assert util.make_repr("Example", obj) == ( + "Example({'x': float('inf')})", + False, + True, + ) assert obj == {"x": float("inf")} @pytest.mark.parametrize("obj", [float("nan"), float("inf"), float("-inf")]) diff --git a/tools/experimental/trt-engine-explorer/trex/graphing.py b/tools/experimental/trt-engine-explorer/trex/graphing.py index c378fccc..9f1fc7e4 100644 --- a/tools/experimental/trt-engine-explorer/trex/graphing.py +++ b/tools/experimental/trt-engine-explorer/trex/graphing.py @@ -735,6 +735,7 @@ def __add_dot_layer_nodes(self, plan, plan_graph, node_name_2_node_id): for layer_node in plan_graph.layer_nodes: layer = layer_node.layer latency = _get_latency(plan, layer, self.latency_type) + if not layer.type == 'Constant' or plan_graph.include_constants: dot_id = _get_dot_id(layer.name) node_name_2_node_id[layer.name] = dot_id diff --git a/tools/onnx-graphsurgeon/CHANGELOG.md b/tools/onnx-graphsurgeon/CHANGELOG.md index 3d0be18f..7f555d53 100644 --- a/tools/onnx-graphsurgeon/CHANGELOG.md +++ b/tools/onnx-graphsurgeon/CHANGELOG.md @@ -2,6 +2,10 @@ Dates are in YYYY-MM-DD format. +## v0.5.2 (2024-04-01) +### Added +- Added `export_dtype` field to `gs.Constant` to allow numpy-unsupported dtypes such as BFloat16. + ## v0.5.1 (2024-02-23) ### Changed diff --git a/tools/onnx-graphsurgeon/examples/12_using_bf16/README.md b/tools/onnx-graphsurgeon/examples/12_using_bf16/README.md new file mode 100644 index 00000000..3b8f2571 --- /dev/null +++ b/tools/onnx-graphsurgeon/examples/12_using_bf16/README.md @@ -0,0 +1,26 @@ +# BFloat16 + +## Introduction + +This example generates a model with bf16 weights. + +Numpy currently doesn't support bf16 natively so data values are stored as float32 and the conversion happens prior to onnx export. +```python +tensor = gs.Constant(name="weight", values=np.ones(shape=(5, 3, 3, 3), dtype=np.float32), export_dtype=onnx.TensorProto.BFLOAT16) +# or +tensor = gs.Constant(name="weight", values=np.ones(shape=(5, 3, 3, 3), dtype=np.float32)) +tensor.export_dtype = onnx.TensorProto.BFLOAT16 + +``` + +## Running the example + +1. Generate the model: + ```bash + python3 generate.py + ``` + + This creates a model with bfloat16 weights + + ![../resources/12_bf16.onnx.png](../resources/12_bf16.onnx.png) + diff --git a/demo/Tacotron2/loss_functions.py b/tools/onnx-graphsurgeon/examples/12_using_bf16/generate.py similarity index 52% rename from demo/Tacotron2/loss_functions.py rename to tools/onnx-graphsurgeon/examples/12_using_bf16/generate.py index 7ee1a5b2..ebfe67a1 100644 --- a/demo/Tacotron2/loss_functions.py +++ b/tools/onnx-graphsurgeon/examples/12_using_bf16/generate.py @@ -1,5 +1,6 @@ +#!/usr/bin/env python3 # -# SPDX-FileCopyrightText: Copyright (c) 1993-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# 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"); @@ -15,20 +16,18 @@ # limitations under the License. # -import torch -import torch.nn as nn -from tacotron2.loss_function import Tacotron2Loss -from waveglow.loss_function import WaveGlowLoss +import onnx_graphsurgeon as gs +import numpy as np +import onnx +BF16 = onnx.TensorProto.BFLOAT16 -def get_loss_function(loss_function, sigma=1.0): - if loss_function == 'Tacotron2': - loss = Tacotron2Loss() - elif loss_function == 'WaveGlow': - loss = WaveGlowLoss(sigma=sigma) - else: - raise NotImplementedError( - "unknown loss function requested: {}".format(loss_function)) +X = gs.Variable(name="X", dtype=BF16, shape=(1, 3, 224, 224)) +W = gs.Constant(name="W", values=np.ones(shape=(5, 3, 3, 3), dtype=np.float32) * 0.5, export_dtype=BF16) +Y = gs.Variable(name="Y", dtype=BF16, shape=(1, 5, 222, 222)) - loss.cuda() - return loss +node = gs.Node(op="Conv", inputs=[X, W], outputs=[Y]) + +graph = gs.Graph(nodes=[node], inputs=[X], outputs=[Y]) + +onnx.save(gs.export_onnx(graph), "test_conv_bf16.onnx") diff --git a/tools/onnx-graphsurgeon/examples/resources/12_bf16.onnx.png b/tools/onnx-graphsurgeon/examples/resources/12_bf16.onnx.png new file mode 100644 index 0000000000000000000000000000000000000000..8f2997ec65bbc6829d1bd285d245ee8a9a8e4a77 GIT binary patch literal 36504 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 G{(k`BK;3o# literal 0 HcmV?d00001 diff --git a/tools/onnx-graphsurgeon/onnx_graphsurgeon/exporters/onnx_exporter.py b/tools/onnx-graphsurgeon/onnx_graphsurgeon/exporters/onnx_exporter.py index d7e3e138..1aa64f3e 100644 --- a/tools/onnx-graphsurgeon/onnx_graphsurgeon/exporters/onnx_exporter.py +++ b/tools/onnx-graphsurgeon/onnx_graphsurgeon/exporters/onnx_exporter.py @@ -82,13 +82,32 @@ def update_import_domains(graph): DEFAULT_CUSTOM_OPSET_VERSION = 1 for used_domain in all_used_domains: if used_domain not in current_domains: - graph.import_domains.append( - onnx.helper.make_opsetid(used_domain, DEFAULT_CUSTOM_OPSET_VERSION) - ) + graph.import_domains.append(onnx.helper.make_opsetid(used_domain, DEFAULT_CUSTOM_OPSET_VERSION)) current_domains.add(used_domain) return graph.import_domains +# Converts a fp32 gs.Constant to a bf16 onnx.TensorProto +def tensor_to_onnx_bf16(tensor: Constant): + + # Converts the fp32 numpy array to bf16 values and store in a uint16 numpy array + def np_float32_to_bf16_as_uint16(arr): + new_arr = np.empty(arr.size, dtype=np.uint16) + flatten = arr.flatten() + for i in range(arr.size): + new_arr[i] = onnx.helper.float32_to_bfloat16(flatten[i]) + return new_arr.reshape(arr.shape) + + arr_bf16_as_uint16 = np_float32_to_bf16_as_uint16(tensor.values) + + onnx_tensor = onnx.TensorProto() + onnx_tensor.data_type = onnx.TensorProto.BFLOAT16 + onnx_tensor.dims.extend(arr_bf16_as_uint16.shape) + onnx_tensor.raw_data = arr_bf16_as_uint16.tobytes() + + return onnx_tensor + + class OnnxExporter(BaseExporter): @staticmethod def export_tensor_proto(tensor: Constant) -> onnx.TensorProto: @@ -97,7 +116,19 @@ def export_tensor_proto(tensor: Constant) -> onnx.TensorProto: if isinstance(tensor._values, LazyValues): onnx_tensor = tensor._values.tensor else: - onnx_tensor = onnx.numpy_helper.from_array(tensor.values) + if dtype_to_onnx(tensor.dtype) != dtype_to_onnx(tensor.export_dtype): + assert tensor.dtype == np.float32, ( + f"Cannot convert onnx dtype {dtype_to_onnx(tensor.dtype)} to {dtype_to_onnx(tensor.export_dtype)}." + "Only float32 to bfloat16 is supported" + ) + assert tensor.export_dtype == onnx.TensorProto.BFLOAT16, ( + f"Cannot convert onnx dtype {dtype_to_onnx(tensor.dtype)} to {dtype_to_onnx(tensor.export_dtype)}." + "Only float32 to bfloat16 is supported" + ) + onnx_tensor = tensor_to_onnx_bf16(tensor) + else: + onnx_tensor = onnx.numpy_helper.from_array(tensor.values) + if tensor.data_location is not None: onnx_tensor.data_location = tensor.data_location onnx_tensor.name = tensor.name @@ -108,9 +139,7 @@ def export_sparse_tensor_proto(tensor: Constant) -> onnx.SparseTensorProto: return tensor._values.tensor @staticmethod - def export_value_info_proto( - tensor: Tensor, do_type_check: bool - ) -> onnx.ValueInfoProto: + def export_value_info_proto(tensor: Tensor, do_type_check: bool) -> onnx.ValueInfoProto: if do_type_check and tensor.dtype is None: G_LOGGER.critical( "Graph input and output tensors must include dtype information. Please set the dtype attribute for: {:}".format( @@ -120,9 +149,7 @@ def export_value_info_proto( if tensor.dtype is not None: if isinstance(tensor, Constant) or tensor.type == "tensor_type": - onnx_tensor = onnx.helper.make_tensor_value_info( - tensor.name, dtype_to_onnx(tensor.dtype), tensor.shape - ) + onnx_tensor = onnx.helper.make_tensor_value_info(tensor.name, dtype_to_onnx(tensor.dtype), tensor.shape) elif tensor.type == "sequence_type": onnx_tensor = onnx.helper.make_tensor_sequence_value_info( tensor.name, dtype_to_onnx(tensor.dtype), tensor.shape @@ -152,9 +179,7 @@ def export_attributes(attrs: dict) -> List[onnx.AttributeProto]: # Netron has a bug which makes it crash if a Tensor attribute has no tensor data. # So provide some meaningless tensor data for Netron to read. if val.type == Tensor: - tensor_proto = OnnxExporter.export_tensor_proto( - Constant("", np.array([0], dtype=np.float32)) - ) + tensor_proto = OnnxExporter.export_tensor_proto(Constant("", np.array([0], dtype=np.float32))) onnx_attr.t.CopyFrom(tensor_proto) onnx_attr.ref_attr_name = val.name @@ -198,9 +223,7 @@ def export_function(func: Function) -> onnx.FunctionProto: for tensor in func.tensors().values(): if isinstance(tensor, Constant): # Copying the tensor prevents the new node from appearing in the Constant tensor's inputs. - new_const_nodes.append( - Node("Constant", attrs={"value": tensor}, outputs=[tensor.copy()]) - ) + new_const_nodes.append(Node("Constant", attrs={"value": tensor}, outputs=[tensor.copy()])) # Const nodes have no inputs, so this maintains a topological ordering. func_nodes = new_const_nodes + func_nodes @@ -247,14 +270,8 @@ def export_graph(graph: Graph, do_type_check=True) -> onnx.GraphProto: """ check_duplicate_node_names(graph.nodes, level=G_LOGGER.WARNING) nodes = [OnnxExporter.export_node(node) for node in graph.nodes] - inputs = [ - OnnxExporter.export_value_info_proto(inp, do_type_check) - for inp in graph.inputs - ] - outputs = [ - OnnxExporter.export_value_info_proto(out, do_type_check) - for out in graph.outputs - ] + inputs = [OnnxExporter.export_value_info_proto(inp, do_type_check) for inp in graph.inputs] + outputs = [OnnxExporter.export_value_info_proto(out, do_type_check) for out in graph.outputs] tensor_map = graph.tensors() initializer = [ OnnxExporter.export_tensor_proto(tensor) @@ -275,9 +292,7 @@ def export_graph(graph: Graph, do_type_check=True) -> onnx.GraphProto: # Omit tensors from value_info if we don't know their shape/dtype def has_value_info(tensor): - return isinstance(tensor, Variable) and ( - tensor.dtype is not None or tensor.shape is not None - ) + return isinstance(tensor, Variable) and (tensor.dtype is not None or tensor.shape is not None) value_info = [ OnnxExporter.export_value_info_proto(tensor, do_type_check) diff --git a/tools/onnx-graphsurgeon/onnx_graphsurgeon/ir/tensor.py b/tools/onnx-graphsurgeon/onnx_graphsurgeon/ir/tensor.py index 71425a94..1e3502fb 100644 --- a/tools/onnx-graphsurgeon/onnx_graphsurgeon/ir/tensor.py +++ b/tools/onnx-graphsurgeon/onnx_graphsurgeon/ir/tensor.py @@ -61,7 +61,12 @@ def is_empty(self): """ return self.name == "" - def to_constant(self, values: np.ndarray, data_location: int = None): + def to_constant( + self, + values: np.ndarray, + data_location: int = None, + export_dtype: Union[np.dtype, "onnx.TensorProto.DataType"] = None, + ): """ Modifies this tensor in-place to convert it to a Constant. This means that all consumers/producers of the tensor will see the update. @@ -72,12 +77,15 @@ def to_constant(self, values: np.ndarray, data_location: int = None): An enum value indicating the location where the tensor data is stored. Generally, this will come from onnx.TensorProto.DataLocation. + dtype (Union[numpy.dtype, onnx.TensorProto.DataType]): The data type of the tensor. Returns: self """ self.__class__ = Constant self._values = values self.data_location = data_location + self.export_dtype = export_dtype + return self def to_variable( @@ -95,9 +103,13 @@ def to_variable( Returns: self """ + + variable_dtype = dtype if dtype is not None else self.export_dtype + self.__class__ = Variable - self.dtype = dtype self.shape = shape + self.dtype = variable_dtype + return self def i(self, tensor_idx=0, producer_idx=0): @@ -184,10 +196,11 @@ def __init__( self.shape = misc.default_value(shape, None) self.type = type - def to_constant(self, values: np.ndarray): + def to_constant(self, values: np.ndarray, export_dtype: Union[np.dtype, "onnx.TensorProto.DataType"] = None): del self.dtype del self.shape - return super().to_constant(values) + + return super().to_constant(values, export_dtype=export_dtype) def copy(self): """ @@ -315,6 +328,7 @@ def __init__( name: str, values: Union[np.ndarray, LazyValues], data_location: int = None, + export_dtype: Union[np.dtype, "onnx.TensorProto.DataType"] = None, ): """ Represents a Tensor whose value is known. @@ -326,6 +340,11 @@ def __init__( data_location (int): An enum value indicating the location where the tensor data is stored. Generally, this will come from onnx.TensorProto.DataLocation. + + + export_dtype (Union[np.dtype, onnx.TensorProto.DataType]): + The data type of the tensor when exported to onnx. If not specified, then + the data type of values will be used. """ self.name = name self.inputs = misc.SynchronizedList(self, field_name="outputs", initial=[]) @@ -344,12 +363,18 @@ def __init__( ) self._values = values self.data_location = data_location + self._export_dtype = export_dtype - def to_variable( - self, dtype: np.dtype = None, shape: Sequence[Union[int, str]] = [] - ): + def to_variable(self, dtype: np.dtype = None, shape: Sequence[Union[int, str]] = []): + var_dtype = self.export_dtype + + del self._export_dtype del self._values - return super().to_variable(dtype, shape) + + if dtype is not None: + return super().to_variable(dtype, shape) + + return super().to_variable(var_dtype, shape) def copy(self): """ @@ -357,7 +382,7 @@ def copy(self): Note: Generally, you should only ever make a copy of a Graph. """ - return Constant(self.name, self._values) + return Constant(self.name, self._values, export_dtype=self.export_dtype) @property def values(self): @@ -378,6 +403,17 @@ def shape(self): def dtype(self): return self._values.dtype + @property + def export_dtype(self): + if self._export_dtype is not None: + return self._export_dtype + + return self.dtype + + @export_dtype.setter + def export_dtype(self, export_dtype): + self._export_dtype = export_dtype + def __repr__(self): # Hack to make logging output pretty. ret = self.__str__() ret += "\n{:}".format(self._values) diff --git a/tools/onnx-graphsurgeon/tests/test_examples.py b/tools/onnx-graphsurgeon/tests/test_examples.py index 64a28558..fd86fbb0 100644 --- a/tools/onnx-graphsurgeon/tests/test_examples.py +++ b/tools/onnx-graphsurgeon/tests/test_examples.py @@ -51,6 +51,9 @@ def __init__(self, name, infer=True): ("09_shape_operations_with_the_layer_api", [Artifact("model.onnx")]), ("10_dynamic_batch_size", [Artifact("model.onnx"), Artifact("dynamic.onnx")]), ("11_creating_a_local_function", [Artifact("model.onnx")]), + + # Skipping inference test as bf16 is not supported in ORT yet. + ("12_using_bf16", [Artifact("test_conv_bf16.onnx", infer=False)]), ] diff --git a/tools/onnx-graphsurgeon/tests/test_ir.py b/tools/onnx-graphsurgeon/tests/test_ir.py index 15f650f0..054c3514 100644 --- a/tools/onnx-graphsurgeon/tests/test_ir.py +++ b/tools/onnx-graphsurgeon/tests/test_ir.py @@ -33,13 +33,16 @@ class TensorBaseTests(object): def test_can_convert_in_place_to_constant(self): - tensor = self.tensor.to_constant(values=np.ones((1, 3, 5, 5), dtype=np.float64)) + tensor = self.tensor.to_constant( + values=np.ones((1, 3, 5, 5), dtype=np.float64), export_dtype=onnx.TensorProto.BFLOAT16 + ) assert tensor is self.tensor assert isinstance(tensor, Constant) assert isinstance(self.input_node.outputs[0], Constant) assert isinstance(self.output_node.inputs[0], Constant) assert tensor.shape == (1, 3, 5, 5) assert tensor.dtype == np.float64 + assert tensor.export_dtype == onnx.TensorProto.BFLOAT16 assert np.all(self.input_node.outputs[0].values == tensor.values) assert np.all(self.output_node.inputs[0].values == tensor.values) @@ -136,7 +139,7 @@ def test_equals_name_mismatch(self): class TestConstant(TensorBaseTests): def setup_method(self): self.tensor = Constant( - name="test_tensor", values=np.ones((1, 3, 5, 5), dtype=np.float64) + name="test_tensor", values=np.ones((1, 3, 5, 5), dtype=np.float64), export_dtype=onnx.TensorProto.BFLOAT16 ) self.input_node = Node( op="Add", outputs=[self.tensor] @@ -149,6 +152,9 @@ def test_can_get_shape(self): def test_can_get_dtype(self): assert self.tensor.dtype == np.float64 + def test_can_get_export_dtype(self): + assert self.tensor.export_dtype == onnx.TensorProto.BFLOAT16 + @pytest.fixture def node_with_nested_subgraphs():