# Copyright (c) 2024 PaddlePaddle Authors. 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. import ctypes import hashlib import logging import paddle paddle.base.core.register_paddle_plugin() import tensorrt as trt import paddle from paddle import pir from paddle.base.core import clear_shape_info, get_value_shape_range_info from paddle.base.log_helper import get_logger from .impls.activation import * # noqa: F403 from .impls.attribute import * # noqa: F403 from .impls.common import * # noqa: F403 from .impls.conv import * # noqa: F403 from .impls.creation import * # noqa: F403 from .impls.einsum import * # noqa: F403 from .impls.input import * # noqa: F403 from .impls.linalg import * # noqa: F403 from .impls.logic import * # noqa: F403 from .impls.manipulation import * # noqa: F403 from .impls.math import * # noqa: F403 from .impls.norm import * # noqa: F403 from .impls.ops import * # noqa: F403 from .impls.others import * # noqa: F403 from .impls.pooling import * # noqa: F403 from .impls.search import * # noqa: F403 from .impls.stat import * # noqa: F403 from .impls.vision import * # noqa: F403 from .register import converter_registry from .util import ( RefitManager, RefitRole, TensorRTConfigManager, TensorRTConstantManager, all_ops_into_trt, get_cache_path, get_trt_version, get_trt_version_list, is_shape_tensor, map_dtype, remove_duplicate_value, set_dynamic_range, weight_to_tensor, zero_dims_to_one_dims, ) version_list = get_trt_version_list() _logger = get_logger( __name__, logging.INFO, fmt='%(asctime)s-%(levelname)s: %(message)s' ) class PaddleToTensorRTConverter: def __init__(self, paddle_program, scope, trt_config=None): self.scope = scope self.program = paddle_program self.trt_config = trt_config self.constant_manager = TensorRTConstantManager() self.refit_manager = RefitManager() params = paddle_program.global_block().all_parameters() param_dict = {} # save parameters for v in params: name = v.get_defining_op().attrs()["parameter_name"] weight_tensor = self.scope.var(name).get_tensor() self.constant_manager.set_constant_value(name, weight_tensor, v) self.input_info = {} self.trt_output_value_map = {} self.engine_num = 0 # init tensorrt plugin trt_plugin_lib = ctypes.CDLL('libnvinfer_plugin.so') trt_plugin_lib.initLibNvInferPlugins(None, "") def find_graph_inputs_outputs(self, group_op): operations = next(iter(group_op.blocks())).ops all_values = {} output_values = {} graph_output_values = [] def __is_output_value(value): for op in value.all_used_ops(): if op.name() == "cf.yield": return True return False # Collect all output values from all operations for op in operations: for result in op.results(): output_values[result.id] = result all_values[result.id] = result if __is_output_value(result): graph_output_values.append(result) for operand in op.operands(): source = operand.source() if not source.initialized(): _logger.warning(f"Skipping uninitialized source: {source}") continue else: all_values[source.id] = source # Input values are those that are in all_values but not in output_values input_values = [ value for value_id, value in all_values.items() if value_id not in output_values ] return input_values, graph_output_values def convert_subgraph_to_trt(self, program, group_op): from .export import PrecisionMode trt_manager = TensorRTConfigManager(self.trt_config) if self.trt_config is not None and self.trt_config.ops_run_float: _logger.info(f"force_fp32_ops: {trt_manager.get_force_fp32_ops()}") if not self.trt_config.disable_logging: _logger.info(f"start process {group_op}") operations = next(iter(group_op.blocks())).ops input_values, output_values = self.find_graph_inputs_outputs(group_op) builder = trt.Builder(trt.Logger(trt.Logger.ERROR)) network = builder.create_network( 1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) ) profile = builder.create_optimization_profile() # Mapping from Value id to TensorRT ITensor value_to_trt_tensor = {} min_shape_map = {} opt_shape_map = {} max_shape_map = {} min_value_map = {} opt_value_map = {} max_value_map = {} input_names = [] new_input_values = [] refit_param_name = [] precision_mode = PrecisionMode.FP32 if self.trt_config is not None: precision_mode = self.trt_config.precision_mode # Because one of the inputs to pd_op.concat is builtin.combine, # during the conversion process using the converter, # it is necessary to obtain the input of builtin.combine. origin_input_value = [] for value in input_values: defining_op = value.get_defining_op() if defining_op.name() == "builtin.combine": for operand in defining_op.operands(): source = operand.source() origin_input_value.append(source) else: origin_input_value.append(value) origin_input_value = remove_duplicate_value(origin_input_value) # create TRT Weight and TRT Input for value in origin_input_value: defining_op = value.get_defining_op() if defining_op.name() == "builtin.parameter": param_name = defining_op.attrs()["parameter_name"] refit_param_name.append(param_name) weight = trt.Weights( self.constant_manager.get_constant_value(param_name) ) if self.trt_config.refit_params_path: paddle_shape = value.shape trt_shape = trt.Dims(paddle_shape) constant_layer = network.add_constant(trt_shape, weight) constant_layer.name = param_name value_to_trt_tensor[value.id] = constant_layer.get_output(0) self.refit_manager.set_trt_weight_tensor( constant_layer.get_output(0).name, weight ) self.refit_manager.set_mapping( param_name, param_name, RefitRole.CONSTANT ) else: value_to_trt_tensor[value.id] = weight elif defining_op.name() == "builtin.constant": constant_value_name = defining_op.attrs()["value"] constant_tensor = self.scope.var( constant_value_name ).get_tensor() self.constant_manager.set_constant_value( constant_value_name, constant_tensor, value ) constant_tensor = trt.Weights( self.constant_manager.get_constant_value( constant_value_name ) ) if self.trt_config.refit_params_path: paddle_shape = value.shape trt_shape = trt.Dims(paddle_shape) constant_layer = network.add_constant( trt_shape, constant_tensor ) constant_layer.name = constant_value_name value_to_trt_tensor[value.id] = constant_layer.get_output(0) self.refit_manager.set_trt_weight_tensor( constant_layer.get_output(0).name, constant_tensor ) else: value_to_trt_tensor[value.id] = constant_tensor else: shape = value.shape dtype = map_dtype(value.dtype.name) input_name = f"input_{value.id}" # 0-dims -> 1-dims if len(shape) == 0: shape = [1] input_tensor = network.add_input( name=input_name, dtype=dtype, shape=shape ) input_names.append(input_name) new_input_values.append(value) value_to_trt_tensor[value.id] = input_tensor for op in operations: # Adding marker labels to builtin ops facilitates convert processing, but they ultimately do not enter the TensorRT subgraph. if op.name() == "builtin.split" or op.name() == "builtin.combine": continue operands = [] for operand in op.operands(): source = operand.source() if not source.initialized(): operands.append(None) continue vec_type = source.type().as_vec_type() if vec_type is not None and len(vec_type.as_list()) == 0: continue define_op_name = source.get_defining_op().name() if define_op_name == "builtin.combine": operand_list = [] for combined_operand in source.get_defining_op().operands(): combined_source = combined_operand.source() combined_source_id = combined_source.id if combined_source_id in value_to_trt_tensor: trt_input_tensor = weight_to_tensor( network, combined_source, value_to_trt_tensor[combined_source_id], op.name(), ) trt_input_tensor = zero_dims_to_one_dims( network, trt_input_tensor ) operand_list.append(trt_input_tensor) else: raise RuntimeError( f'{combined_source_id} not found in value_to_trt_tensor' ) operands.append(operand_list) else: source_id = source.id if source_id in value_to_trt_tensor: trt_input_tensor = weight_to_tensor( network, source, value_to_trt_tensor[source_id], op.name(), ) trt_input_tensor = zero_dims_to_one_dims( network, trt_input_tensor ) operands.append(trt_input_tensor) else: raise RuntimeError( f'{source_id} not found in value_to_trt_tensor' ) if precision_mode.value == PrecisionMode.INT8.value: set_dynamic_range(op, operands) trt_outs = self.convert(network, op, operands) results = [] for idx, result in enumerate(op.results()): if result.is_combine(): # empty vec value condition if len(result.type().as_vec_type().as_list()) == 0: results.append(result) continue used_ops = result.all_used_ops() for use_op in used_ops: if use_op.name() == "builtin.split": split_outputs = use_op.results() results.extend(split_outputs) else: results.append(result) for idx, result in enumerate(results): if idx < len(trt_outs): value_to_trt_tensor[result.id] = trt_outs[idx] else: value_to_trt_tensor[result.id] = None # Set TRT min/opt/max input shape and the value of shape tensor for i, value in enumerate(origin_input_value): trt_input = value_to_trt_tensor[value.id] defining_op_name = value.get_defining_op().name() if ( defining_op_name == "builtin.parameter" or defining_op_name == "builtin.constant" ): # constant/parameter condition, needn't get min/opt/max shape continue input_name = trt_input.name if not self.trt_config.disable_logging: _logger.info( f"set shape of {value}, op is: {value.get_defining_op()}" ) min_shape = [] opt_shape = [] max_shape = [] min_value = [] opt_value = [] max_value = [] value_define_op = value.get_defining_op() # if the input value is generated by the other trt_engine_op, so the shape is searched by origin value if ( value_define_op.name() == "builtin.split" and value_define_op.operand_source(0).get_defining_op().name() == "pd_op.tensorrt_engine" ): min_shape = self.input_info[value.id]["min_shape"] opt_shape = self.input_info[value.id]["opt_shape"] max_shape = self.input_info[value.id]["max_shape"] if trt_input.is_shape_tensor: min_value = self.input_info[value.id]["min_value"] opt_value = self.input_info[value.id]["opt_value"] max_value = self.input_info[value.id]["max_value"] else: min_shape = get_value_shape_range_info( value, False, paddle.base.core.ShapeMode.kMIN ) opt_shape = get_value_shape_range_info( value, False, paddle.base.core.ShapeMode.kOPT ) max_shape = get_value_shape_range_info( value, False, paddle.base.core.ShapeMode.kMAX ) if trt_input.is_shape_tensor: min_value = get_value_shape_range_info( value, True, paddle.base.core.ShapeMode.kMIN ) opt_value = get_value_shape_range_info( value, True, paddle.base.core.ShapeMode.kOPT ) max_value = get_value_shape_range_info( value, True, paddle.base.core.ShapeMode.kMAX ) if not trt_input.is_shape_tensor: if not self.trt_config.disable_logging: _logger.info(f"set min_shape of {value} as {min_shape}") _logger.info(f"set opt_shape of {value} as {opt_shape}") _logger.info(f"set max_shape of {value} as {max_shape}") profile.set_shape( input_name, min=min_shape, opt=opt_shape, max=max_shape ) else: if not self.trt_config.disable_logging: _logger.info( f"set min_value of shape input: {value} as {min_value}" ) _logger.info( f"set opt_value of shape input: {value} as {opt_value}" ) _logger.info( f"set max_value of shape input: {value} as {max_value}" ) profile.set_shape_input( input_name, min=min_value, opt=opt_value, max=max_value ) min_shape_map[input_name] = min_shape opt_shape_map[input_name] = opt_shape max_shape_map[input_name] = max_shape min_value_map[input_name] = min_value opt_value_map[input_name] = opt_value max_value_map[input_name] = max_value out_shapes = [] out_names = [] out_types = [] for out_index in range(len(output_values)): result_value = output_values[out_index] output_tensor = value_to_trt_tensor[result_value.id] if output_tensor is None: out_names.append("") out_shapes.append([]) out_types.append(None) continue network.mark_output(output_tensor) out_names.append(output_tensor.name) out_shapes.append(result_value.shape) out_types.append(result_value.dtype) if group_op.result(out_index).use_empty(): # if result value is not used, it doesn't need get shape, continue continue min_shape = [] opt_shape = [] max_shape = [] if len(result_value.shape) != 0: min_shape = get_value_shape_range_info( result_value, False, paddle.base.core.ShapeMode.kMIN ) opt_shape = get_value_shape_range_info( result_value, False, paddle.base.core.ShapeMode.kOPT ) max_shape = get_value_shape_range_info( result_value, False, paddle.base.core.ShapeMode.kMAX ) min_value = [] opt_value = [] max_value = [] if is_shape_tensor(result_value): min_value = get_value_shape_range_info( result_value, True, paddle.base.core.ShapeMode.kMIN ) opt_value = get_value_shape_range_info( result_value, True, paddle.base.core.ShapeMode.kOPT ) max_value = get_value_shape_range_info( result_value, True, paddle.base.core.ShapeMode.kMAX ) self.input_info[result_value.id] = { "min_shape": min_shape, "opt_shape": opt_shape, "max_shape": max_shape, "min_value": min_value, "opt_value": opt_value, "max_value": max_value, } config = builder.create_builder_config() if self.trt_config and self.trt_config.refit_params_path: config.set_flag(trt.BuilderFlag.REFIT) config.add_optimization_profile(profile) if version_list[0] > 8 or ( version_list[0] == 8 and version_list[1] >= 6 ): # trt version >= 8.6 config.builder_optimization_level = ( self.trt_config.optimization_level ) config.set_memory_pool_limit( trt.MemoryPoolType.WORKSPACE, self.trt_config.workspace_size ) if precision_mode.value == PrecisionMode.FP16.value: if builder.platform_has_fast_fp16: config.set_flag(trt.BuilderFlag.FP16) _logger.info("Run Paddle-TRT FP16 mode") else: _logger.warning( "Hardware does not support FP16. Continuing in FP32 mode." ) elif precision_mode.value == PrecisionMode.BF16.value: if version_list[0] >= 9: if builder.platform_has_fast_bfp16 and hasattr( builder, 'platform_has_fast_bf16' ): config.set_flag(trt.BuilderFlag.BF16) _logger.info("Run Paddle-TRT BF16 mode") else: _logger.warning( "Hardware does not support BF16. Continuing in FP32 mode." ) else: if builder.platform_has_fast_fp16: config.set_flag(trt.BuilderFlag.FP16) _logger.warning( "Because the version of TensorRT is less than 9.0, run Paddle-TRT FP16 mode" ) else: _logger.warning( "Hardware does not support FP16. Continuing in FP32 mode." ) elif precision_mode.value == PrecisionMode.INT8.value: config.set_flag(trt.BuilderFlag.INT8) _logger.info("Run Paddle-TRT INT8 mode") elif self.trt_config is not None: _logger.info( f"Default precision mode {self.trt_config.precision_mode}" ) if ( version_list[0] > 8 or version_list[0] == 8 and version_list[1] >= 2 and version_list[2] >= 1 ): if self.trt_config is not None and self.trt_config.ops_run_float: config.set_flag(trt.BuilderFlag.PREFER_PRECISION_CONSTRAINTS) trt_engine = builder.build_serialized_network(network, config) assert trt_engine is not None, ( 'Failed to build engine. please see ERROR log from trt.Logger' ) trt_params = paddle.base.libpaddle.TRTEngineParams() trt_params.min_input_shape = min_shape_map trt_params.max_input_shape = max_shape_map trt_params.optim_input_shape = opt_shape_map trt_params.min_shape_tensor = min_value_map trt_params.max_shape_tensor = max_value_map trt_params.optim_shape_tensor = opt_value_map trt_params.use_cuda_graph = self.trt_config.use_cuda_graph all_nodes_offload_to_trt = all_ops_into_trt(self.program) if self.trt_config.use_cuda_graph and not all_nodes_offload_to_trt: _logger.info( "You have enabled CudaGraph, but not the entire graph offload to " "trt, now return to normal mode." ) trt_params.use_cuda_graph = False if self.trt_config.refit_params_path: trt_params.refit_params_path = self.trt_config.refit_params_path trt_params.refit_param_name = refit_param_name trt_params.refit_param_names2trt_names = ( self.refit_manager.get_all_mappings() ) group_str = str(group_op) engine_name = ( int(hashlib.sha256(group_str.encode('utf-8')).hexdigest(), 16) % 10**8 ) CACHE_ROOT = get_cache_path(self.trt_config.save_model_dir) CACHE_FILE = f"{CACHE_ROOT}/engine_{engine_name}_{self.engine_num}.trt" with open(CACHE_FILE, "wb") as f: f.write(trt_engine) PIR_DUMP_FILE = ( f"{CACHE_ROOT}/engine_{engine_name}_{self.engine_num}.pir" ) with open(PIR_DUMP_FILE, "w") as f: f.write(group_str) trt_params.engine_serialized_data = CACHE_FILE with paddle.pir_utils.IrGuard(), paddle.pir.core.program_guard(program): pir.set_insertion_point(group_op) out = paddle._C_ops.tensorrt_engine( new_input_values, trt_params, input_names, out_names, out_shapes, out_types, "", ) for out_index in range(len(out)): if group_op.result(out_index).use_empty(): # if result value is not been used, it doesn't need get shape, continue continue ori_value = output_values[out_index] current_value = out[out_index] orin_min_shape = self.input_info[ori_value.id]["min_shape"] orin_opt_shape = self.input_info[ori_value.id]["opt_shape"] orin_max_shape = self.input_info[ori_value.id]["max_shape"] orin_min_value = self.input_info[ori_value.id]["min_value"] orin_opt_value = self.input_info[ori_value.id]["opt_value"] orin_max_value = self.input_info[ori_value.id]["max_value"] self.input_info[current_value.id] = { "min_shape": orin_min_shape, "opt_shape": orin_opt_shape, "max_shape": orin_max_shape, "min_value": orin_min_value, "opt_value": orin_opt_value, "max_value": orin_max_value, } return out def convert(self, network, paddle_op, inputs): trt_version = get_trt_version() op_name = paddle_op.name() if op_name in ["cf.yield"]: return else: converter_func = converter_registry.get(op_name, trt_version) if converter_func is None: raise NotImplementedError( f"Converter for {op_name} not implemented." ) outs = converter_func(network, paddle_op, inputs) if isinstance(outs, trt.ITensor): return (outs,) else: return outs def convert_program_to_trt(self): for op in self.program.global_block().ops: if op.name() == "cinn_op.group" or op.name() == "builtin.group": if not self.trt_config.disable_logging: _logger.info(f"start process {op.name()}") self.engine_num += 1 new_out = self.convert_subgraph_to_trt(self.program, op) orin_out_values = op.results() for o_i in range(len(orin_out_values)): orin_out_values[o_i].replace_all_uses_with(new_out[o_i]) self.program.global_block().remove_op(op) save_one_parameter = ( False # We need to keep at least one parameter for save ) for op in self.program.global_block().ops: if op.name() == "builtin.parameter": parameter_name = op.attrs()["parameter_name"] if ( not save_one_parameter and "constant_folding" not in parameter_name ): save_one_parameter = True continue if op.results()[0].use_empty(): self.program.global_block().remove_op(op) if op.name() == "builtin.constant": # builtin.constant can't be saved/loaded, we need del it if op.results()[0].use_empty(): self.program.global_block().remove_op(op) else: constant_result = op.results()[0] constant_value_name = op.attrs()["value"] out_dtype = np.dtype( paddle.pir.core.datatype_to_str[constant_result.dtype] ) tensor_data = self.scope.var( constant_value_name ).get_tensor() constant_array = np.array( tensor_data, dtype=out_dtype ).tolist() if isinstance(constant_array, (int, float)): constant_array = [constant_array] # convert builtin.constant to pd_op.full_int_array/full and then delete it with paddle.pir.core.program_guard(self.program): paddle.base.libpaddle.pir.reset_insertion_point_to_start() if len(constant_array) == 1: full_value = paddle._C_ops.full( [1], constant_array[0], constant_result.dtype, paddle.CUDAPlace(0), ) else: full_value = paddle._C_ops.full_int_array( constant_array, constant_result.dtype, paddle.CUDAPlace(0), ) op.replace_all_uses_with([full_value]) self.program.global_block().remove_op(op) # Call clear_shape_info to clear the previous shape information clear_shape_info()