• 模型部署入门教程(七):TensorRT 模型构建与推理


    模型部署入门教程继续更新啦!相信经过前几期的学习,大家已经对 ONNX 这一中间表示有了一个比较全面的认识,但是在具体的生产环境中,ONNX 模型常常需要被转换成能被具体推理后端使用的模型格式。本篇教程我们就和大家一起来认识大名鼎鼎的推理后端 TensorRT。

    TensorRT 简介

    TensorRT 是由 NVIDIA 发布的深度学习框架,用于在其硬件上运行深度学习推理。TensorRT 提供量化感知训练和离线量化功能,用户可以选择 INT8 和 FP16 两种优化模式,将深度学习模型应用到不同任务的生产部署,如视频流、语音识别、推荐、欺诈检测、文本生成和自然语言处理。TensorRT 经过高度优化,可在 NVIDIA GPU 上运行, 并且可能是目前在 NVIDIA GPU 运行模型最快的推理引擎。关于 TensorRT 更具体的信息可以访问 TensorRT官网 了解。

    安装 TensorRT

    Windows

    默认在一台有 NVIDIA 显卡的机器上,提前安装好 CUDA 和 CUDNN,登录 NVIDIA 官方网站下载和主机 CUDA 版本适配的 TensorRT 压缩包即可。

    以 CUDA 版本是 10.2 为例,选择适配 CUDA 10.2 的 zip 包,下载完成后,有 conda 虚拟环境的用户可以优先切换到虚拟环境中,然后在 powershell 中执行类似如下的命令安装并测试:

    1. cd \the\path\of\tensorrt\zip\file
    2. Expand-Archive TensorRT-8.2.5.1.Windows10.x86_64.cuda-10.2.cudnn8.2.zip .
    3. $env:TENSORRT_DIR = "$pwd\TensorRT-8.2.5.1"
    4. $env:path = "$env:TENSORRT_DIR\lib;" + $env:path
    5. pip install $env:TENSORRT_DIR\python\tensorrt-8.2.5.1-cp36-none-win_amd64.whl
    6. python -c "import tensorrt;print(tensorrt.__version__)"

    上述命令会在安装后检查 TensorRT 版本,如果打印结果是 8.2.5.1,说明安装 Python 包成功了。

    Linux

    和在 Windows 环境下安装类似,默认在一台有 NVIDIA 显卡的机器上,提前安装好 CUDA 和 CUDNN,登录 NVIDIA 官方网站下载和主机 CUDA 版本适配的 TensorRT 压缩包即可。

    以 CUDA 版本是 10.2 为例,选择适配 CUDA 10.2 的 tar 包,然后执行类似如下的命令安装并测试:

    1. cd /the/path/of/tensorrt/tar/gz/file
    2. tar -zxvf TensorRT-8.2.5.1.linux.x86_64-gnu.cuda-10.2.cudnn8.2.tar.gz
    3. export TENSORRT_DIR=$(pwd)/TensorRT-8.2.5.1
    4. export LD_LIBRARY_PATH=$TENSORRT_DIR/lib:$LD_LIBRARY_PATH
    5. pip install TensorRT-8.2.5.1/python/tensorrt-8.2.5.1-cp37-none-linux_x86_64.whl
    6. python -c "import tensorrt;print(tensorrt.__version__)"

    如果发现打印结果是 8.2.5.1,说明安装 Python 包成功了。

    模型构建

    我们使用 TensorRT 生成模型主要有两种方式:

    1. 直接通过 TensorRT 的 API 逐层搭建网络;
    2. 将中间表示的模型转换成 TensorRT 的模型,比如将 ONNX 模型转换成 TensorRT 模型。

    接下来,我们将用 Python 和 C++ 语言分别使用这两种方式构建 TensorRT 模型,并将生成的模型进行推理。

    直接构建

    利用 TensorRT 的 API 逐层搭建网络,这一过程类似使用一般的训练框架,如使用 Pytorch 或者TensorFlow 搭建网络。需要注意的是对于权重部分,如卷积或者归一化层,需要将权重内容赋值到 TensorRT 的网络中。本文就不详细展示,只搭建一个对输入做池化的简单网络。

    使用 Python API 构建

    首先是使用 Python API 直接搭建 TensorRT 网络,这种方法主要是利用 tensorrt.Builder 的 create_builder_config 和 create_network 功能,分别构建 config 和 network,前者用于设置网络的最大工作空间等参数,后者就是网络主体,需要对其逐层添加内容。

    此外,需要定义好输入和输出名称,将构建好的网络序列化,保存成本地文件。值得注意的是:如果想要网络接受不同分辨率的输入输出,需要使用 tensorrt.Builder 的 create_optimization_profile 函数,并设置最小、最大的尺寸。

    实现代码如下:

    1. import tensorrt as trt
    2. verbose = True
    3. IN_NAME = 'input'
    4. OUT_NAME = 'output'
    5. IN_H = 224
    6. IN_W = 224
    7. BATCH_SIZE = 1
    8. EXPLICIT_BATCH = 1 << (int)(
    9. trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
    10. TRT_LOGGER = trt.Logger(trt.Logger.VERBOSE) if verbose else trt.Logger()
    11. with trt.Builder(TRT_LOGGER) as builder, builder.create_builder_config(
    12. ) as config, builder.create_network(EXPLICIT_BATCH) as network:
    13. # define network
    14. input_tensor = network.add_input(
    15. name=IN_NAME, dtype=trt.float32, shape=(BATCH_SIZE, 3, IN_H, IN_W))
    16. pool = network.add_pooling(
    17. input=input_tensor, type=trt.PoolingType.MAX, window_size=(2, 2))
    18. pool.stride = (2, 2)
    19. pool.get_output(0).name = OUT_NAME
    20. network.mark_output(pool.get_output(0))
    21. # serialize the model to engine file
    22. profile = builder.create_optimization_profile()
    23. profile.set_shape_input('input', *[[BATCH_SIZE, 3, IN_H, IN_W]]*3)
    24. builder.max_batch_size = 1
    25. config.max_workspace_size = 1 << 30
    26. engine = builder.build_engine(network, config)
    27. with open('model_python_trt.engine', mode='wb') as f:
    28. f.write(bytearray(engine.serialize()))
    29. print("generating file done!")

    使用 C++ API 构建

    对于想要直接用 C++ 语言构建网络的小伙伴来说,整个流程和上述 Python 的执行过程非常类似,需要注意的点主要有:

    1. nvinfer1:: createInferBuilder 对应 Python 中的 tensorrt.Builder,需要传入 ILogger 类的实例,但是 ILogger 是一个抽象类,需要用户继承该类并实现内部的虚函数。不过此处我们直接使用了 TensorRT 包解压后的 samples 文件夹 ../samples/common/logger.h 文件里的实现 Logger 子类。
    2. 设置 TensorRT 模型的输入尺寸,需要多次调用 IOptimizationProfile 的 setDimensions 方法,比 Python 略繁琐一些。IOptimizationProfile 需要用 createOptimizationProfile 函数,对应 Python 的 create_builder_config 函数。

    实现代码如下:

    1. #include
    2. #include
    3. #include
    4. #include <../samples/common/logger.h>
    5. using namespace nvinfer1;
    6. using namespace sample;
    7. const char* IN_NAME = "input";
    8. const char* OUT_NAME = "output";
    9. static const int IN_H = 224;
    10. static const int IN_W = 224;
    11. static const int BATCH_SIZE = 1;
    12. static const int EXPLICIT_BATCH = 1 << (int)(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
    13. int main(int argc, char** argv)
    14. {
    15. // Create builder
    16. Logger m_logger;
    17. IBuilder* builder = createInferBuilder(m_logger);
    18. IBuilderConfig* config = builder->createBuilderConfig();
    19. // Create model to populate the network
    20. INetworkDefinition* network = builder->createNetworkV2(EXPLICIT_BATCH);
    21. ITensor* input_tensor = network->addInput(IN_NAME, DataType::kFLOAT, Dims4{ BATCH_SIZE, 3, IN_H, IN_W });
    22. IPoolingLayer* pool = network->addPoolingNd(*input_tensor, PoolingType::kMAX, DimsHW{ 2, 2 });
    23. pool->setStrideNd(DimsHW{ 2, 2 });
    24. pool->getOutput(0)->setName(OUT_NAME);
    25. network->markOutput(*pool->getOutput(0));
    26. // Build engine
    27. IOptimizationProfile* profile = builder->createOptimizationProfile();
    28. profile->setDimensions(IN_NAME, OptProfileSelector::kMIN, Dims4(BATCH_SIZE, 3, IN_H, IN_W));
    29. profile->setDimensions(IN_NAME, OptProfileSelector::kOPT, Dims4(BATCH_SIZE, 3, IN_H, IN_W));
    30. profile->setDimensions(IN_NAME, OptProfileSelector::kMAX, Dims4(BATCH_SIZE, 3, IN_H, IN_W));
    31. config->setMaxWorkspaceSize(1 << 20);
    32. ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
    33. // Serialize the model to engine file
    34. IHostMemory* modelStream{ nullptr };
    35. assert(engine != nullptr);
    36. modelStream = engine->serialize();
    37. std::ofstream p("model.engine", std::ios::binary);
    38. if (!p) {
    39. std::cerr << "could not open output file to save model" << std::endl;
    40. return -1;
    41. }
    42. p.write(reinterpret_cast<const char*>(modelStream->data()), modelStream->size());
    43. std::cout << "generating file done!" << std::endl;
    44. // Release resources
    45. modelStream->destroy();
    46. network->destroy();
    47. engine->destroy();
    48. builder->destroy();
    49. config->destroy();
    50. return 0;
    51. }

    IR 转换模型

    除了直接通过 TensorRT 的 API 逐层搭建网络并序列化模型,TensorRT 还支持将中间表示的模型(如 ONNX)转换成 TensorRT 模型。

    使用 Python API 转换

    我们首先使用 Pytorch 实现一个和上文一致的模型,即只对输入做一次池化并输出;然后将 Pytorch 模型转换成 ONNX 模型;最后将 ONNX 模型转换成 TensorRT 模型。

    这里主要使用了 TensorRT 的 OnnxParser 功能,它可以将 ONNX 模型解析到 TensorRT 的网络中。最后我们同样可以得到一个 TensorRT 模型,其功能与上述方式实现的模型功能一致。

    实现代码如下:

    1. import torch
    2. import onnx
    3. import tensorrt as trt
    4. onnx_model = 'model.onnx'
    5. class NaiveModel(torch.nn.Module):
    6. def __init__(self):
    7. super().__init__()
    8. self.pool = torch.nn.MaxPool2d(2, 2)
    9. def forward(self, x):
    10. return self.pool(x)
    11. device = torch.device('cuda:0')
    12. # generate ONNX model
    13. torch.onnx.export(NaiveModel(), torch.randn(1, 3, 224, 224), onnx_model, input_names=['input'], output_names=['output'], opset_version=11)
    14. onnx_model = onnx.load(onnx_model)
    15. # create builder and network
    16. logger = trt.Logger(trt.Logger.ERROR)
    17. builder = trt.Builder(logger)
    18. EXPLICIT_BATCH = 1 << (int)(
    19. trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
    20. network = builder.create_network(EXPLICIT_BATCH)
    21. # parse onnx
    22. parser = trt.OnnxParser(network, logger)
    23. if not parser.parse(onnx_model.SerializeToString()):
    24. error_msgs = ''
    25. for error in range(parser.num_errors):
    26. error_msgs += f'{parser.get_error(error)}\n'
    27. raise RuntimeError(f'Failed to parse onnx, {error_msgs}')
    28. config = builder.create_builder_config()
    29. config.max_workspace_size = 1<<20
    30. profile = builder.create_optimization_profile()
    31. profile.set_shape('input', [1,3 ,224 ,224], [1,3,224, 224], [1,3 ,224 ,224])
    32. config.add_optimization_profile(profile)
    33. # create engine
    34. with torch.cuda.device(device):
    35. engine = builder.build_engine(network, config)
    36. with open('model.engine', mode='wb') as f:
    37. f.write(bytearray(engine.serialize()))
    38. print("generating file done!")

    IR 转换时,如果有多 Batch、多输入、动态 shape 的需求,都可以通过多次调用 set_shape 函数进行设置。set_shape 函数接受的传参分别是:输入节点名称,可接受的最小输入尺寸,最优的输入尺寸,可接受的最大输入尺寸。一般要求这三个尺寸的大小关系为单调递增。

    使用 C++ API 转换

    介绍了如何用 Python 语言将 ONNX 模型转换成 TensorRT 模型后,再介绍下如何用 C++ 将 ONNX 模型转换成 TensorRT 模型。这里通过 NvOnnxParser,我们可以将上一小节转换时得到的 ONNX 文件直接解析到网络中。

    实现代码如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include <../samples/common/logger.h>
    6. using namespace nvinfer1;
    7. using namespace nvonnxparser;
    8. using namespace sample;
    9. int main(int argc, char** argv)
    10. {
    11. // Create builder
    12. Logger m_logger;
    13. IBuilder* builder = createInferBuilder(m_logger);
    14. const auto explicitBatch = 1U << static_cast(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
    15. IBuilderConfig* config = builder->createBuilderConfig();
    16. // Create model to populate the network
    17. INetworkDefinition* network = builder->createNetworkV2(explicitBatch);
    18. // Parse ONNX file
    19. IParser* parser = nvonnxparser::createParser(*network, m_logger);
    20. bool parser_status = parser->parseFromFile("model.onnx", static_cast<int>(ILogger::Severity::kWARNING));
    21. // Get the name of network input
    22. Dims dim = network->getInput(0)->getDimensions();
    23. if (dim.d[0] == -1) // -1 means it is a dynamic model
    24. {
    25. const char* name = network->getInput(0)->getName();
    26. IOptimizationProfile* profile = builder->createOptimizationProfile();
    27. profile->setDimensions(name, OptProfileSelector::kMIN, Dims4(1, dim.d[1], dim.d[2], dim.d[3]));
    28. profile->setDimensions(name, OptProfileSelector::kOPT, Dims4(1, dim.d[1], dim.d[2], dim.d[3]));
    29. profile->setDimensions(name, OptProfileSelector::kMAX, Dims4(1, dim.d[1], dim.d[2], dim.d[3]));
    30. config->addOptimizationProfile(profile);
    31. }
    32. // Build engine
    33. config->setMaxWorkspaceSize(1 << 20);
    34. ICudaEngine* engine = builder->buildEngineWithConfig(*network, *config);
    35. // Serialize the model to engine file
    36. IHostMemory* modelStream{ nullptr };
    37. assert(engine != nullptr);
    38. modelStream = engine->serialize();
    39. std::ofstream p("model.engine", std::ios::binary);
    40. if (!p) {
    41. std::cerr << "could not open output file to save model" << std::endl;
    42. return -1;
    43. }
    44. p.write(reinterpret_cast<const char*>(modelStream->data()), modelStream->size());
    45. std::cout << "generate file success!" << std::endl;
    46. // Release resources
    47. modelStream->destroy();
    48. network->destroy();
    49. engine->destroy();
    50. builder->destroy();
    51. config->destroy();
    52. return 0;
    53. }

    模型推理

    前面,我们使用了两种构建 TensorRT 模型的方式,分别用 Python 和 C++ 两种语言共生成了四个 TensorRT 模型,这四个模型的功能理论上是完全一致的。

    接下来,我们将分别使用 Python 和 C++ 两种语言对生成的 TensorRT 模型进行推理。

    使用 Python API 推理

    首先是使用 Python API 推理 TensorRT 模型,这里部分代码参考了 MMDeploy。运行下面代码,可以发现输入一个 1x3x224x224 的张量,输出一个 1x3x112x112 的张量,完全符合我们对输入池化后结果的预期。

    1. from typing import Union, Optional, Sequence,Dict,Any
    2. import torch
    3. import tensorrt as trt
    4. class TRTWrapper(torch.nn.Module):
    5. def __init__(self,engine: Union[str, trt.ICudaEngine],
    6. output_names: Optional[Sequence[str]] = None) -> None:
    7. super().__init__()
    8. self.engine = engine
    9. if isinstance(self.engine, str):
    10. with trt.Logger() as logger, trt.Runtime(logger) as runtime:
    11. with open(self.engine, mode='rb') as f:
    12. engine_bytes = f.read()
    13. self.engine = runtime.deserialize_cuda_engine(engine_bytes)
    14. self.context = self.engine.create_execution_context()
    15. names = [_ for _ in self.engine]
    16. input_names = list(filter(self.engine.binding_is_input, names))
    17. self._input_names = input_names
    18. self._output_names = output_names
    19. if self._output_names is None:
    20. output_names = list(set(names) - set(input_names))
    21. self._output_names = output_names
    22. def forward(self, inputs: Dict[str, torch.Tensor]):
    23. assert self._input_names is not None
    24. assert self._output_names is not None
    25. bindings = [None] * (len(self._input_names) + len(self._output_names))
    26. profile_id = 0
    27. for input_name, input_tensor in inputs.items():
    28. # check if input shape is valid
    29. profile = self.engine.get_profile_shape(profile_id, input_name)
    30. assert input_tensor.dim() == len(
    31. profile[0]), 'Input dim is different from engine profile.'
    32. for s_min, s_input, s_max in zip(profile[0], input_tensor.shape,
    33. profile[2]):
    34. assert s_min <= s_input <= s_max, \
    35. 'Input shape should be between ' \
    36. + f'{profile[0]} and {profile[2]}' \
    37. + f' but get {tuple(input_tensor.shape)}.'
    38. idx = self.engine.get_binding_index(input_name)
    39. # All input tensors must be gpu variables
    40. assert 'cuda' in input_tensor.device.type
    41. input_tensor = input_tensor.contiguous()
    42. if input_tensor.dtype == torch.long:
    43. input_tensor = input_tensor.int()
    44. self.context.set_binding_shape(idx, tuple(input_tensor.shape))
    45. bindings[idx] = input_tensor.contiguous().data_ptr()
    46. # create output tensors
    47. outputs = {}
    48. for output_name in self._output_names:
    49. idx = self.engine.get_binding_index(output_name)
    50. dtype = torch.float32
    51. shape = tuple(self.context.get_binding_shape(idx))
    52. device = torch.device('cuda')
    53. output = torch.empty(size=shape, dtype=dtype, device=device)
    54. outputs[output_name] = output
    55. bindings[idx] = output.data_ptr()
    56. self.context.execute_async_v2(bindings,
    57. torch.cuda.current_stream().cuda_stream)
    58. return outputs
    59. model = TRTWrapper('model.engine', ['output'])
    60. output = model(dict(input = torch.randn(1, 3, 224, 224).cuda()))
    61. print(output)

    使用 C++ API 推理

    最后,在很多实际生产环境中,我们都会使用 C++ 语言完成具体的任务,以达到更加高效的代码运行效果,另外 TensoRT 的用户一般也都更看重其在 C++ 下的使用,所以我们也用 C++ 语言实现一遍模型推理,这也可以和用 Python API 推理模型做一个对比。

    实现代码如下:

    1. #include
    2. #include
    3. #include
    4. #include <../samples/common/logger.h>
    5. #define CHECK(status) \
    6. do\
    7. {\
    8. auto ret = (status);\
    9. if (ret != 0)\
    10. {\
    11. std::cerr << "Cuda failure: " << ret << std::endl;\
    12. abort();\
    13. }\
    14. } while (0)
    15. using namespace nvinfer1;
    16. using namespace sample;
    17. const char* IN_NAME = "input";
    18. const char* OUT_NAME = "output";
    19. static const int IN_H = 224;
    20. static const int IN_W = 224;
    21. static const int BATCH_SIZE = 1;
    22. static const int EXPLICIT_BATCH = 1 << (int)(NetworkDefinitionCreationFlag::kEXPLICIT_BATCH);
    23. void doInference(IExecutionContext& context, float* input, float* output, int batchSize)
    24. {
    25. const ICudaEngine& engine = context.getEngine();
    26. // Pointers to input and output device buffers to pass to engine.
    27. // Engine requires exactly IEngine::getNbBindings() number of buffers.
    28. assert(engine.getNbBindings() == 2);
    29. void* buffers[2];
    30. // In order to bind the buffers, we need to know the names of the input and output tensors.
    31. // Note that indices are guaranteed to be less than IEngine::getNbBindings()
    32. const int inputIndex = engine.getBindingIndex(IN_NAME);
    33. const int outputIndex = engine.getBindingIndex(OUT_NAME);
    34. // Create GPU buffers on device
    35. CHECK(cudaMalloc(&buffers[inputIndex], batchSize * 3 * IN_H * IN_W * sizeof(float)));
    36. CHECK(cudaMalloc(&buffers[outputIndex], batchSize * 3 * IN_H * IN_W /4 * sizeof(float)));
    37. // Create stream
    38. cudaStream_t stream;
    39. CHECK(cudaStreamCreate(&stream));
    40. // DMA input batch data to device, infer on the batch asynchronously, and DMA output back to host
    41. CHECK(cudaMemcpyAsync(buffers[inputIndex], input, batchSize * 3 * IN_H * IN_W * sizeof(float), cudaMemcpyHostToDevice, stream));
    42. context.enqueue(batchSize, buffers, stream, nullptr);
    43. CHECK(cudaMemcpyAsync(output, buffers[outputIndex], batchSize * 3 * IN_H * IN_W / 4 * sizeof(float), cudaMemcpyDeviceToHost, stream));
    44. cudaStreamSynchronize(stream);
    45. // Release stream and buffers
    46. cudaStreamDestroy(stream);
    47. CHECK(cudaFree(buffers[inputIndex]));
    48. CHECK(cudaFree(buffers[outputIndex]));
    49. }
    50. int main(int argc, char** argv)
    51. {
    52. // create a model using the API directly and serialize it to a stream
    53. char *trtModelStream{ nullptr };
    54. size_t size{ 0 };
    55. std::ifstream file("model.engine", std::ios::binary);
    56. if (file.good()) {
    57. file.seekg(0, file.end);
    58. size = file.tellg();
    59. file.seekg(0, file.beg);
    60. trtModelStream = new char[size];
    61. assert(trtModelStream);
    62. file.read(trtModelStream, size);
    63. file.close();
    64. }
    65. Logger m_logger;
    66. IRuntime* runtime = createInferRuntime(m_logger);
    67. assert(runtime != nullptr);
    68. ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream, size, nullptr);
    69. assert(engine != nullptr);
    70. IExecutionContext* context = engine->createExecutionContext();
    71. assert(context != nullptr);
    72. // generate input data
    73. float data[BATCH_SIZE * 3 * IN_H * IN_W];
    74. for (int i = 0; i < BATCH_SIZE * 3 * IN_H * IN_W; i++)
    75. data[i] = 1;
    76. // Run inference
    77. float prob[BATCH_SIZE * 3 * IN_H * IN_W /4];
    78. doInference(*context, data, prob, BATCH_SIZE);
    79. // Destroy the engine
    80. context->destroy();
    81. engine->destroy();
    82. runtime->destroy();
    83. return 0;
    84. }

    总结

    通过本文的学习,我们掌握了两种构建 TensorRT 模型的方式:直接通过 TensorRT 的 API 逐层搭建网络;将中间表示的模型转换成 TensorRT 的模型。不仅如此,我们还分别用 C++ 和 Python 两种语言完成了 TensorRT 模型的构建及推理,相信大家都有所收获!在下一篇文章中,我们将和大家一起学习何添加 TensorRT 自定义算子,敬请期待哦~

    FAQ

    • Q:运行代码时报错:Could not find: cudnn64_8.dll. Is it on your PATH?
    • A:首先检查下自己的环境变量中是否包含 cudnn64_8.dll 所在的路径,若发现 cudnn 的路径在 C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.2\\bin 中,但是里面只有 cudnn64_7.dll,解决方法是去 NVIDIA 官网下载 cuDNN zip 包,解压后,复制其中的 cudnn64_8.dll 到 CUDA Toolkit 的 bin 目录下。这时也可以复制一份 cudnn64_7.dll,然后将复制的那份改名成 cudnn64_8.dll,同样可以解决这个问题。

    参考

    GitHub - wang-xinyu/tensorrtx: Implementation of popular deep learning networks with TensorRT networ

    GitHub - NVIDIA/TensorRT: TensorRT is a C++ library for high performance inference on NVIDIA GPUs an

    系列传送门

    OpenMMLab:模型部署入门教程(一):模型部署简介172 赞同 · 22 评论文章213 赞同 · 24 评论文章241 赞同 · 31 评论文章241 赞同 · 31 评论文章正在上传…重新上传取消

    OpenMMLab:模型部署入门教程(二):解决模型部署中的难题77 赞同 · 22 评论文章89 赞同 · 27 评论文章96 赞同 · 27 评论文章96 赞同 · 27 评论文章正在上传…重新上传取消

    OpenMMLab:模型部署入门教程(三):PyTorch 转 ONNX 详解131 赞同 · 25 评论文章146 赞同 · 32 评论文章153 赞同 · 34 评论文章153 赞同 · 34 评论文章正在上传…重新上传取消

    OpenMMLab:模型部署入门教程(四):在 PyTorch 中支持更多 ONNX 算子68 赞同 · 19 评论文章80 赞同 · 28 评论文章86 赞同 · 32 评论文章86 赞同 · 32 评论文章正在上传…重新上传取消

    OpenMMLab:模型部署入门教程(五):ONNX 模型的修改与调试86 赞同 · 4 评论文章115 赞同 · 10 评论文章122 赞同 · 16 评论文章122 赞同 · 16 评论文章正在上传…重新上传取消

    OpenMMLab:模型部署入门教程(六):实现 PyTorch-ONNX 精度对齐工具91 赞同 · 7 评论文章正在上传…重新上传取消

    OpenMMLab:TorchScript 解读(一):初识 TorchScript83 赞同 · 10 评论文章106 赞同 · 15 评论文章118 赞同 · 15 评论文章118 赞同 · 15 评论文章正在上传…重新上传取消

    OpenMMLab:TorchScript 解读(二):Torch jit tracer 实现解析39 赞同 · 2 评论文章46 赞同 · 2 评论文章46 赞同 · 2 评论文章46 赞同 · 2 评论文章正在上传…重新上传取消

    OpenMMLab:TorchScript 解读(三):jit 中的 subgraph rewriter24 赞同 · 0 评论文章29 赞同 · 0 评论文章32 赞同 · 0 评论文章32 赞同 · 0 评论文章正在上传…重新上传取消

    OpenMMLab:TorchScript 解读(四):Torch jit 中的别名分析7 赞同 · 0 评论文章14 赞同 · 0 评论文章16 赞同 · 0 评论文章16 赞同 · 0 评论文章正在上传…重新上传取消

  • 相关阅读:
    Mysql优化---锁机制、行锁及表锁
    LuatOS-SOC接口文档(air780E)--mcu - 封装mcu一些特殊操作
    排序算法时间复杂
    【D3.js】1.18-给 D3 标签添加样式
    【AICFD案例操作】汽车外气动分析
    线框图软件:Balsamiq Wireframes mac中文介绍
    数据库调优:Mysql索引对group by 排序的影响
    学点Java打小工_Day4_数组_冒泡排序
    Mysql提取表字段中的字符串
    LuatOS-SOC接口文档(air780E)--dac - 数模转换
  • 原文地址:https://blog.csdn.net/qq_39967751/article/details/126061511