CUDA和TensorRT入门

news2024/11/24 1:36:54

CUDA

官方教程:CUDA C++ Programming Guide (nvidia.com)

一、基础知识

首先看一下显卡、GPU、和CUDA的关系介绍:

显卡、GPU和CUDA简介_吴一奇的博客-CSDN博客

延迟:一条指令返回的时间间隔;

吞吐量:单位时间内处理的指令数量;

CPUs

ppIMVc8.png

设计是按照延迟导向设计的;

主要有以下几个特点:

1、内存大:多级缓存结构提高访存速度;

2、控制复杂:分支预测机制(if-else判断)、流水线数据前送;

3、运算单元强大:整型浮点型复杂运算速度快;

GPUs

ppIMu7j.png

设计是按照吞吐导向设计的;

主要有以下几个特点:

1、缓存小:提高内存吞吐;

2、控制简单:没有分支预测,没有数据转发;

3、精简运算单元:需要大量线程来容忍延迟,流水线实现高吞吐量;

注意:显存其实和内存一样,也是用来暂存资料的存储空间,不过显存是帮GPU存储的,而内存是帮CPU存储的;

总结:

CPU相比于GPU,单条复杂指令延迟快10倍以上;

GPU相比于CPU,单位时间执行指令数量10倍以上;

思考:

什么样的问题适合GPU?

计算密集:数值计算的比例要远大于内存操作,因此内存访问的延迟可以被计算掩盖;

数据并行:大任务可以拆解成执行相同指令的小任务,因此对复杂流程的需求控制较低;

CUDA

CUDA:由英伟达公司2007年开始推出,初衷是为GPU增加一个易用的编程接口,让开发者无需学习复杂的着色语言或者图形处理原语;

OpenCL:是2008年发布的异构平台并行编程的开放标准,也是一个编程框架;

下面是CUDA编程的具体结构图:

在这里插入图片描述

其中Device代表GPU,Host代表CPU,Kernel代表GPU上运行的函数;

术语:内存模型的层次:

  • 每个线程处理器(SP)都有自己的registers(寄存器)
  • 每个SP都有自己的local memory(局部内存),寄存器和局部内存只能被线程自己访问;
  • 每个多核处理器(SM)内都有自己的shared memory(共享内存),可以被线程块内所有线程访问;
  • 一个GPU的所有SM共有一块global memory(全局内存),不同线程块的线程都可使用;

术语:软件:

在CUDA中,具体对应结构如下

线程处理器(SP)对应线程;

多核处理器(SM)对应线程块;

设备端(device)对应线程块组合体;

一个kernel一次只能在一个GPU上执行;

线程块的概念:

将线程数组分成多个块,块内的线程通过共享内存、原子操作和屏障同步进行协作,不同块中的线程不能协作;

网格(grid)并行线程块组合的概念:

CUDA中的核函数由线程网格执行,每个线程都有一个索引,用于计算内存地址和做出控制决策;

对于每个线程需要处理的数据,是通过对线程块和线程定义具体的id,根据索引来决定要处理的数据;

线程束(warp)概念:

SM采用的SIMT(单指令多线程)架构,warp(线程束)是最基本的执行单元,一个warp包含32个并行thread,这些thread以不同数据资源执行相同的指令,warp本质是线程在GPU上运行的最小单元;

当一个kernel被执行时,grid中的线程块被分配到SM上,一个线程块的thread只能在一个SM上调度,SM一般可以调度多个线程块,大量thread可能被分到不同的SM上,每个thread拥有它自己的程序计数器和状态寄存器,并且用该线程自己的数据执行指令,这就是所谓的SIMT;

由于warp的大小为32,所以block所含的thread的大小一般要设置为32的倍数;

在这里插入图片描述

案例:向量相加

说明:向量相加满足GPU运行的条件,计算简单且支持并行,内存访问也少;

在CPU中实现两个向量的相加:

void vecAdd(float* A, float* B, float* C, int n)
{
    for (i = 0, i < n, i++)
    C[i] = A[i] + B[i];
}

需要利用循环,依次对两个向量中的数据进行相加,再将结果保存在新的向量中;

在GPU中实现两个向量的相加:

主要分为以下几个步骤:

  • 为数据分配内存空间

    cudaError_t cudaMalloc (void **devPtr, size_t size):两个参数为地址、申请内存大小;

    作用:在设备全局内存中分配对象;

    cudaError_t cudaMalloc (void **devPtr, size_t size):参数为需要释放的指针对象地址

    作用:从设备全局内存中释放对象;

  • 定义核函数(计算函数)

  • 数据传输

    这里涉及到一个数据拷贝,函数为cudaError_t cudaMemcpy (void *dst, const void *src, size_t count, cudaMemcpyKind kind)

    cudaMemcpyKind 支持的四种选项:cudaMemcpyHostToDevice、cudaMemcpyDeviceToHost、cudaMemcpyDeviceToDevice

    作用:内存数据在主机端和设备端的传输;

核函数的定义和调用:

  • 在GPU上执行的函数;
  • 一般通过标识符__ global __修饰;
  • 函数调用通过<<<参数1,参数2>>>,参数用于说明核函数中的线程数量,以及线程的组织;
  • 以网格(grid)的形式组织,每个线程格由若干个线程块(block)组成,而每个线程块又由若干个线程(thread)组成;
  • 调用时必须声明内核函数的执行参数;
  • 在编程时,必须先为kernel函数中用到的数组或变量分配好足够的空间,再调用kernel函数,否则在GPU计算时会发生错误;

CUDA编程的标识符号:

__ global __ : 核函数的返回一定要用void,也是最常见的;

在这里插入图片描述

CUDA编程流程:

在这里插入图片描述

主要有几种编译的方式,比如逐文件编译,整个cuda文件编译成动态库,以及cmake编译(最常用)!

代码实现:

CPU中向量相加的实现:

void vecAdd(float* A, float* B, float* C, int n) {
    for (int i = 0; i < n; i++) {
        C[i] = A[i] + B[i];
    }
}

GPU中向量相加的实现:

void vecAddKernel(float* A_d, float* B_d, float* C_d, int n)
{
    int i = threadIdx.x + blockDim.x * blockIdx.x;
    if (i < n) C_d[i] = A_d[i] + B_d[i];
}

// 下面是调用,上面是定义核函数
vecAddKernel <<< blockPerGrid, threadPerBlock >>> (da, db, dc, n);

在实际环境下编译后运行两个代码看看耗时:

在这里插入图片描述

可以看出,在飞浆的环境下,十万维度的向量,GPU会比CPU快上9倍左右;

案例:矩阵相乘

矩阵相乘作为深度学习任务中最常见的计算,也是GPU优化的重点;

正常的矩阵乘法是行和列之间的相乘得到一个元素,也就是一个线程负责计算一个元素;

在这里插入图片描述

这里存在最主要的问题就是,数据的读取过于频繁,将会浪费大量时间在读取数据上;

优化思路:

将多次访问的数据放到共享内存中,减少重复读取的次数,充分利用共享内存的延迟低的优势;

CUDA中的内存读取速度:

  • 各自线程寄存器(1周期)
  • 线程块共享内存(5周期)
  • Grid全局内存(500周期)
  • Grid常量内存(5周期)

在这里插入图片描述

CUDA中的共享内存:

概念:一种特殊类型的内存,内容在源码中被显式声明和使用;

(位于处理器中,以更高速度访问,被内存访问指令访问,别名暂存存储器)

特点:

  • 读取速度等同于缓存,在很多显卡上,缓存和内存使用的是同一块硬件,并且可以配置大小;
  • 共享内存属于线程块,可以被一个线程块内所有线程访问;
  • 共享内存有两种申请空间方式,静态申请和动态申请;
  • 共享内存大小只有十几K,过度使用共享内存会降低程序的并行性;

使用方法:

  • 使用__ shared __ 关键字;
  • 注意数据存在交叉,应该将边界上的数据拷贝进来;

这里有个线程同步的函数——__syncthreads():

概念:是cuda的内建函数,用于块内线程通信;

申请共享内存的两种方式:

1、静态方式:

__shared__ int s[64];

共享内存大小明确;

2、动态方式:

__shared__ int s[64];

共享内存大小不明确;

平铺矩阵乘法:

原理:将内核的执行分解为多个阶段,使每个阶段的数据访问集中在一个(Md和Nd)的子集上;

在这里插入图片描述

当然,需要使用内置函数__syncthreads()来确保平铺矩阵中的所有元素都被加载使用;

理论上加速的比例:原始矩阵乘法需要从全局内存中取2mnk次,平铺矩阵乘法只需要取2mnk/block_size次,加速了block_size倍,考虑到同步函数和共享内存的读写,实际上加速效率比这个低;

代码实现:

CPU下矩阵相乘的代码:

void  multiplicateMatrixOnHost(float *array_A, float *array_B, float *array_C, int M_p, int K_p, int N_p)
{
	for (int i = 0; i < M_p; i++)
	{
		for (int j = 0; j < N_p; j++)
		{
			float sum = 0;
			for (int k = 0; k < K_p; k++)
			{
				sum += array_A[i*K_p + k] * array_B[k*N_p + j];
			}
			array_C[i*N_p + j] = sum;
		}
	}
}

GPU下矩阵相乘代码:

// 下面是在GPU上不适用共享内存的实现
__global__ void multiplicateMatrixOnDevice(float *array_A, float *array_B, float *array_C, int M_p, int K_p, int N_p)
{
	int ix = threadIdx.x + blockDim.x*blockIdx.x;//row number
	int iy = threadIdx.y + blockDim.y*blockIdx.y;//col number

	if (ix < N_p && iy < M_p)
	{
		float sum = 0;
		for (int k = 0; k < K_p; k++)
		{
			sum += array_A[iy*K_p + k] * array_B[k*N_p + ix];
		}
		array_C[iy*N_p + ix] = sum;
	}
}

// 下面是在GPU上使用共享内存的实现
__global__ void matrixMultiplyShared(float *A, float *B, float *C,
	int numARows, int numAColumns, int numBRows, int numBColumns, int numCRows, int numCColumns)
{
	__shared__ float sharedM[BLOCK_SIZE][BLOCK_SIZE];
	__shared__ float sharedN[BLOCK_SIZE][BLOCK_SIZE];

	int bx = blockIdx.x;
	int by = blockIdx.y;
	int tx = threadIdx.x;
	int ty = threadIdx.y;

	int row = by * BLOCK_SIZE + ty;
	int col = bx * BLOCK_SIZE + tx;

	float Csub = 0.0;

	for (int i = 0; i < (int)(ceil((float)numAColumns / BLOCK_SIZE)); i++)
	{
		if (i*BLOCK_SIZE + tx < numAColumns && row < numARows)
			sharedM[ty][tx] = A[row*numAColumns + i * BLOCK_SIZE + tx];
		else
			sharedM[ty][tx] = 0.0;

		if (i*BLOCK_SIZE + ty < numBRows && col < numBColumns)
			sharedN[ty][tx] = B[(i*BLOCK_SIZE + ty)*numBColumns + col];
		else
			sharedN[ty][tx] = 0.0;
		__syncthreads();			// 线程同步

		for (int j = 0; j < BLOCK_SIZE; j++)
			Csub += sharedM[ty][j] * sharedN[j][tx];
		__syncthreads();			// 线程同步
	}

	if (row < numCRows && col < numCColumns)
		C[row*numCColumns + col] = Csub;
}

// 在cuda的内置库中,可以直接通过cublasSgemm()这个函数实现矩阵在共享内存中的相乘

下面是运行时间:

在这里插入图片描述

结论:

可以看出来GPU确实有实现一定效率的提升,但提升的并不多,这是因为这里的时间计算将数据的拷贝也包含了进去,如果在大的矩阵相乘的情况下,那么数据拷贝的时间可以忽略不计,而计算的效率可以看出GPU是比CPU快上很多的;

二、进阶学习

CUDA Stream

概念:CUDA Stream是GPU上task的执行队列,所有CUDA操作(kernel,内存拷贝等)都是在stream上执行的;

种类:

1、隐式流:又称为默认流、NULL流;

所有的CUDA操作默认运行在隐式流里,隐式流里的GOU和CPU端计算是同步的,也就是串行的;

在这里插入图片描述

2、显示流:显示申请的流;

显示流里的GPU task和CPU端计算是异步的,不同显示流内的GPU task执行也是异步的、并行的;

在这里插入图片描述

简单代码案例:

// 创建两个流
cudaStream_t stream[2];		// 定义流对象
for (int i = 0; i < 2; ++i){
	cudaStreamCreate(&stream[i]);
}
float* hostPtr;
cudaMallocHost(&hostPtr, 2 * size);
...
// 两个流,每个流有三个命令
for (int i = 0; i < 2; ++i){
	// 从主机内存复制数据到设备内存
	cudaMemcpyAsync(inputDevPtr + i * size, hostPtr + i * size, size, cudaMemcpyHostToDevice, stream[i]);
	// 执行kernel处理
	MyKernrl <<grid, block, 0, stream[i]>>(outputDevPtr + i * size, inputDevPtr + i * size, size);
	// 从设备内存复制数据到主机内存
	cudaMemcpyAsync(hostPtr + i * size, outputDevPtr + i * size, size, cudaMemcpyD	eviceToHost, stream[i]);
}
// 同步流
for (int i = 0; i < 2; i++){
	cudaStreamSyncchronize(stream[i]);
	...
}
// 销毁流
for (int i = 0; i < 2; ++i){
	cudaStreamDestory(stream[i]);
}

优点:

  • CPU计算和kernel计算并行;
  • CPU计算和数据传输并行;
  • 数据传输和kernel计算并行;
  • kernel计算并行;

注意点:

显示流里的GPU task和GPU端的task的执行是异步的,使用stream一定要注意同步;

以下接口作用是同步流:

cudaStreamSyncchronize():同步一个流;

cudaDeviceSynchronize():同步设备上所有流;

cudaStreamQuery():查询一个流任务是否完成;

下面看一个案例:数据传输和GPU计算通过流实现并行

在这里插入图片描述

注意:从下面的任务执行顺序可以看出,数据传输并不是重叠到一起的,这是因为CPU和GPU数据的传输是经过PCle总线的,PCle的操作是顺序的;

提问:

1、CUDA Stream为什么有效?

  • PCle总线传输速度慢,是瓶颈,会导致数据传输的时候GPU处于空闲状态,多流可以实现数据传输与kernel计算的并行;
  • 一个kernel往往用不了整个GPU的算力,多流可以让多个kernel同时计算;
  • 不是流越多越好,类似于CPU的多核一样,也是有数量限制的;

这里还有一个优化的策略,就是将小任务合并成大任务;

在这里插入图片描述

还需要注意一点,当同时存在默认流和显示流时,编译需要加上一个参数;

nvcc --default-stream per-thread ./stream_test.cu -o stream_per-thread

CUDA Event

CUDA Event是在stream中插入一个事件,类似于打一个标记位,用来记录stream是否执行到当前位置,Event有两个状态,已被执行和未被执行;

最常用的用法是来测时间:

// 使用event计算时间
float time_elapsed = 0;
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);

cudaEventRecord(start, 0);	// 记录当前时间
mul<<<blocks, threads, 0, 0>>>(dev_a, NUM);
cudaEventRecord(stop, 0);	// 记录当前时间

cudaEventSynchronize(start);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&time_elapsed, start, stop); // 计算时间差

cudaEventDestroy(start);
cudaEventDestroy(stop);
printf("执行时间: %f(ms)\n", time_elapsed);

CUDA同步操作(分为四类):

  • device synchronize:影响很大,必须等待全部kernel执行完才能执行CPU端任务;
  • stream synchronize:影响单个流和CPU,需要等待这个流执行完才能继续CPU;
  • event synchronize:影响CPU,更细粒度的同步;
  • synchronizing across streams using an event:高级控制;

NVVP

概念:NVIDIA Visual Profiler(NVVP)是NVIDIA推出的跨平台的CUDA程序性能分析工具;

主要有以下特点:

  • 随着CUDA安装,不需要额外安装;
  • 有图形界面,可以快速找到程序中的性能瓶颈;
  • 以时间线的形式展示CPU和GPU的操作;
  • 可以查看数据传输和kernel的各种软件参数(速度)和硬件参数(L1 cache命中率);

在Window端打开可视化功能还需要参考以下文章安装:

(66条消息) Package | Windows10 CUDA10.2 JDK8 环境下安装NVidia Visual Profiler(nvvp)安装Bug笔记_1LOVESJohnny的博客-CSDN博客

Cublas

概念:是一个BLAS的实现,允许用户使用NVIDIA的GPU计算资源,使用cuBLAS的时候,应用应该分配矩阵或向量所需的GPU内存空间,并加载数据,调用所需的cuBLAS函数,然后从GPU的内存空间上传计算结果至主机,cuBLAS也提供了一些帮助函数来写或者读取数据从GPU中;

学习网站:cuBLAS (nvidia.com)

说明:这一部分主要是在GPU上的线性的一些函数用法,主要用于处理向量标量、向量矩阵、矩阵矩阵的一些函数,由于短期内还用不到,这里不进行深入学习;

Cudnn

概念:NVIDIA cuDNN是用于深度神经网络的GPU加速库,它强调性能、易用性和低内存开销,并且可以继承倒更高级的机器学习框架中;

学习网站:NVIDIA cuDNN Documentation

实现步骤:

// 1、创建cuDNN句柄
cudnnStatus_t cudnnCreate(cudnnHandle_t *handle)
// 2、以Host方式调用在Device上运行的函数
比如卷积函数: cudnnConvolutionForward等
//3、释放cuDNN句柄
cudnnStatus_t cudnnDestroy(cudnnHandle_t handle)
// 4、将CUDA流设置&返回成cudnn句柄
cudnnStatus_t cudnnSetStream( cudnnHandle_t handle, cudaStream_t streamId)
cudnnStatus_t cudnnGetStream( cudnnHandle_t handle, cudaStream_t *streamId)

三、TensorRT学习

基础概念

首先要明确TensorRT的定位,是一个推理框架:

在这里插入图片描述

具有以下几个特点:

  • 高性能深度学习推理优化器和加速库;
  • 低延迟和高吞吐量;
  • 部署到超大规模数据中心、嵌入式或汽车产品;
  • 相对于其他推理框架,闭源也是其特点之一;

其实现步骤主要分为两步:转换优化引擎和执行引擎;

在这里插入图片描述

其中最常见的优化方式也就是量化(低精度)和算子融合(例如将卷积池化和激活层融合成一层)

使用流程

其使用流程分为以下几个步骤:

在这里插入图片描述

其中构建网络分为两种方式,一种是API构建,也就是网络的每一层都重新用代码构建,相对来说比较复杂;一种是用Parser来构建,也就是特定的网络有其特定的框架有对应的加载接口,只需要简单几行代码就可以构建网络结构;

模型转换

ONNX转trt:https://github.com/onnx/onnx-tensorrt/tree/6872a9473391a73b96741711d52b98c2c3e25146

Pytorch转trt:NVIDIA-AI-IOT/torch2trt: An easy to use PyTorch to TensorRT converter (github.com)

TensorFlow转trt:tensorflow/tensorflow/compiler/tf2tensorrt at 1cca70b80504474402215d2a4e55bc44621b691d · tensorflow/tensorflow (github.com)

这里介绍一个转换工具网站:https://convertmodel.com/

其中具体的一些转换的技巧还需要在实践中去探索,但最好将模型转换为onnx,再通过onnx转换为trt;

简单案例

官方源码中给出了很多案例:

https://github.com/NVIDIA/TensorRT/tree/release/6.0/samples/opensource/sampleMNIST

这是其中一个MNIST的数字识别的案例;

在AIStudio中跑了一下案例,可以得到如下结果:

在这里插入图片描述

四、TensorRT进阶

plugin用法

作用:

1、trt支持的算子有限,可以实现不支持的算子;

2、进行深度优化、合并算子;

工作流程:

在这里插入图片描述

API的讲解:

首先要清楚有两种类型的定义:

  • Dynamic Shape:输入维度是动态的,继承IPluginV2IOExt基础类;
  • Static Shape:输入维度是静态的,继承IPluginV2DynamicExt基础类;

构造函数:

1、用于network definition阶段,PluginCreator创建该插件时调用的构造函数,需要传递权重信息以及参数。 也可用于clone阶段,或者再写一个clone构造函数;

MyCustomPlugin(int in_channel, nvinfer1::Weights const& weight, nvinfer1::Weights const& bias);

2、 用于在deserialize阶段,用于将序列化好的权重和参数传入该plugin并创建;

MyCustomPlugin(void const* serialData, size_t serialLength);

3、注意把默认构造函数删掉;

MyCustomPlugin() = delete;

析构函数:

析构函数则需要执行terminate,terminate函数就是释放这个op之前开辟的一些显存空间;

MyCustomPlugin::~MyCustomPlugin() {
	terminate();
}

输出相关函数:

1、获得layer的输出个数;

int getNbOutputs() const;

2、根据输入个数和输入维度,获得第index个输出的维度;

nvinfer1::Dims getOutputDimensions(int index, const nvinfer1::Dims* inputs, int nbInputDims); 

3、根据输入个数和输入类型,获得第index个输出的类型;

nvinfer1::DataType getOutputDataType(int index, const nvinfer1::DataType* inputTypes, int nbInputs) const; 

序列化和反序列化相关函数:

1、返回序列化时需要写多少字节到buffer中;

size_t getSerializationSize() const;

2、序列化函数,将plugin的参数权值写入到buffer中;

void serialize(void* buffer) const;

3、获得plugin的type和version,用于反序列化使用;

const char* getPluginType() const;
const char* getPluginVersion() const;

初始化、配置和销毁函数:

1、初始化函数,在这个插件准备开始run之前执行。一般申请权值显存空间并copy权值;

int initialize(); 

2、terminate函数就是释放initialize开辟的一些显存空间;

void terminate(); 

3、释放整个plugin占用的资源;

void destroy();

4、配置这个插件op,判断输入和输出类型数量是否正确;

void configurePlugin(const nvinfer1::PluginTensorDesc* in, int nbInput, const nvinfer1::PluginTensorDesc* out, int nbOutput);

5、判断pos索引的输入/输出是否支持inOut[pos].format和inOut[pos].type指定的格式/数据类型;

bool supportsFormatCombination(int pos, const nvinfer1::PluginTensorDesc* inOut, int nbInputs, int nbOutputs) const;

运行时相关函数:

1、获得plugin所需要的显存大小。最好不要在plugin enqueue中使用cudaMalloc申请显存;

size_t getWorkspaceSize(int maxBatchSize) const;

2、推理函数;

int enqueue(int batchSize, const void* const* inputs, void** outputs, void* workspace, cudaStream_t stream);

IPluginCreator相关函数:

1、获得pluginname和version,用于辨识creator;

const char* getPluginName() const; 
const char* getPluginVersion() const; 

2、通过PluginFieldCollection去创建plugin 将op需要的权重和参数一个一个取出来,然后调用上文提到的第一个构造函数:

const nvinfer1::PluginFieldCollection* getFieldNames(); 
nvinfer1::IPluginV2* createPlugin(const char* name, const nvinfer1::PluginFieldCollection* 
fc);

3、反序列化,调用反序列化那个构造函数,生成plugin;

nvinfer1::IPluginV2* deserializePlugin(const char* name, const void* serialData, size_t serialLength);

建议参考官方的案例进行学习,来巩固代码的实现流程;

优化

首先可以了解下FP32、FP16类型的具体定义:

参考:ARM CPU性能优化:FP32 、FP16 和BF16区别 - 知乎 (zhihu.com)

INT8量化的含义:

将基于浮点的模型转换成低精度的int8数值进行运算,以加快推理速度;

在这里插入图片描述

INT8和FP16加速推理的原理:

通过指令或硬件技术,在单位时钟周期内,FP16和INT8类型的运算次数大于FP32类型的运算次数;

INT8量化为什么不会大幅损失精度?

由于神经网络具有以下特性:具有一定的鲁棒性;

原因:训练数据一般都是有噪声的,神经网络的训练过程往往就是从噪声中识别出有效信息,可以将降低精度计算造成的损失理解成另一种噪声;

INT8量化的分类:

动态对称量化算法(ONNX量化、torch动态量化)

动态非对称量化算法(Google Gemmlowp)

静态对称量化算法(torch静态量化、TensorRT、NCNN)

动态对称量化算法:

在这里插入图片描述

计算公式:

scale = |max| * 2/256;

real_value = scale * quantized_value;

其中real_value为真实值(float类型),quantized_value为INT8量化的结果(char类型)

优点:算法简单,量化步骤耗时短;

缺点:会造成位宽浪费,影响精度,也就是说像可能转换成8位的值,有一位的值可能是空的;

动态非对称量化算法:

在这里插入图片描述

计算公式:

scale = |max - min| / 256;

real_value = scale * (quantized_value - zero_point);

其中real_value位真实值(float类型),quantized_value为INT8量化的结果(char类型),zero_point为零点值

优点:不会造成bit位宽浪费,精度有保障;

缺点:算法比较复杂,量化步骤耗时较长;

静态对称量化算法:

在这里插入图片描述

动态量化:推理时实时统计数值|max|;

静态量化:推理时使用预先统计的缩放阈值,截断部分阈值外的数据;

优点:算法最简单,量化耗时最短,精度也有一定保证;

缺点:构建量化网络比较麻烦;

主要采用KL散度来计算量化的阈值,可以参考下面文章:

(72条消息) TensorRT INT8量化原理与实现(非常详细)_Nicholson07的博客-CSDN博客

源代码案例:TensorRT/samples/opensource/sampleINT8 at release/7.2 · NVIDIA/TensorRT · GitHub

INT8量化大规模上线:

在这里插入图片描述

总结

1、对于深度神经网络的推理,TRT可以充分发挥GPU的算力,以及节省GPU的存储空间;

2、要多参考官方源码的sample案例,尝试替换现有模型,再深入了解API进行网络的搭建;

3、如果要使用自定义组件,至少先了解CUDA基本架构以及常用属性;

4、推荐使用FP16(定义很少变量,明显能提高速度,精度影响不大)和INT8(更大的潜力,可能导致精度下降)这两种量化模式;

5、在不同架构的GPU或者不同的软件版本的设备上,引擎不能通用,要重新生成一个;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/422238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

libwebsockets交叉编译

libwebsockets官网&#xff1a;https://libwebsockets.org/ github&#xff1a;https://github.com/warmcat/libwebsockets 首先下载libwebsockets源码&#xff0c;源码既可以在其官网上下载&#xff0c;也可以到github上下载。这里以v4.1.6为例&#xff0c;https://github.co…

react项目中自定义一个markdown编辑器

Markdown 是一种轻量级标记语言。 Markdown是一种简单的格式化文本的方法&#xff0c;在任何设备上看起来都很棒。它不会做任何花哨的事情&#xff0c;比如改变字体大小、颜色或类型——只是基本的&#xff0c;使用你已经知道的键盘符号。 它还允许人们使用易读易写的纯文本格…

为什么众多大型国企都在升级企业数智化底座?

在数字经济大潮中&#xff0c;数字化转型已不是企业的“选修课”&#xff0c;而是关乎企业生存和长远发展的“必修课”。在企业数字化转型中&#xff0c;国有企业特别是中央企业普遍将数字化转型战略作为“十四五”时期业务规划的重要内容之一&#xff0c;数字化能力也成为衡量…

图文详解CAN Log文件 - ASC文件格式

目录 1 CAN Log文件 -- ASC文件格式 1.1 Header 1.2 版本编号 1.3 经典CAN网络中的描述 1.3.1 经典CAN Standard标准帧的描述 1.3.2 经典CAN Extended扩展帧的描述 1.3.3 CAN Remote远程帧的描述 1.3.4 CAN Error错误帧的描述 1.4 CANFD网络中的描述 1.4.1 经典CAN S…

图解并用 C 语言实现非比较排序(计数排序、桶排序和基数排序)

目录 一、计数排序 二、桶排序 三、基数排序 一、计数排序 算法步骤&#xff1a; 找出待排序数组 arr 中的最小值和最大值&#xff08;分别用 min 和 max 表示&#xff09;。 创建一个长度为 max - min 1、元素初始值全为 0 的计数器数组 count。 扫描一遍原始数组&…

Nacos客户端实例注册源码分析-篇一

Nacos客户端实例注册源码分析-篇一 版本 nacos 服务器端 nacos 2.0.3 实例客户端注册入口 注册案例 回到之前搭建的服务提供者项目 9002 &#xff0c;在真实的生产环境下&#xff0c;如果需要让某一个服务注册到 Nacos 的服务当中&#xff0c;我们引入对应的 nacos 发现依赖&…

4月Google Play政策更新,游戏上架需要注意这几点

3月21日&#xff0c;据路透社报道&#xff0c;由于发现国内某知名电商应用存在恶意软件问题&#xff0c;谷歌已暂时将该APP从商店下架&#xff0c;并表示&#xff1a;将该APP下架是一种安全预防措施&#xff0c;已经下载的用户也会收到警告&#xff0c;提示他们进行卸载。 4月…

基于深度学习的动物识别系统(YOLOv5清新界面版,Python代码)

摘要&#xff1a;动物识别系统用于识别和统计常见动物数量&#xff0c;通过深度学习技术检测日常几种动物图像识别&#xff0c;支持图片、视频和摄像头画面等形式。在介绍算法原理的同时&#xff0c;给出Python的实现代码、训练数据集以及PyQt的UI界面。动物识别系统主要用于常…

c/c++:算术运算符,赋值运算,逻辑运算,比较运算,三目运算,逗号运算,数据类型转换

c/c&#xff1a;算术运算符&#xff0c;赋值运算&#xff0c;逻辑运算&#xff0c;比较运算&#xff0c;三目运算&#xff0c;逗号运算&#xff0c;数据类型转换 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;此时学会c的…

【自定义表格穿梭框】自定义封装jqgrid表格穿梭框,支持分页复选全选(附完整源码及效果图)

【写在前面】其实之前业务中也有这个方面的需求&#xff0c;但是总觉得自己写的有点乱&#xff0c;此时也就借这个机会重新封装一个公共的函数去实现这个穿梭框的效果&#xff0c;支持分页勾选&#xff0c;页面展示已选中和未选择的数据&#xff0c;使得系统操作更友好。 涉及知…

数学建模(三):模拟退火算法(SA)

文章目录模拟退火算法&#xff08;SA&#xff09;一、 概述1、 算法简介2、 核心思想3、 数学原理4、 模拟退火的流程二、 实例分析1、 初始化参数2、 Metrospolis 准则3、 生成新的值4、 获取最优值5、 主程序6、 总代码模拟退火算法&#xff08;SA&#xff09; 一、 概述 1…

折叠屏市场起风,华为、OPPO“你追我赶”

配图来自Canva可画 现如今&#xff0c;智能手机已经成为了人们生活中不可或缺的重要工具&#xff0c;无论是出行&#xff0c;还是社交&#xff0c;亦或是支付&#xff0c;只需要一部智能手机就可以通通搞定。因此&#xff0c;在消费者多样化需求的助推下&#xff0c;智能手机行…

【Spring】—Spring中Bean的配置、作用域

一、Bean的配置 Spring用于生产和管理Spring容器中的Bean&#xff0c;需要开发者对Spring的配置文件进行配置。在实际开发中&#xff0c;最常采用XML格式的配置方式&#xff0c;即通过XML文件来注册并管理Bean之间的依赖关系。 在Spring中&#xff0c;XML配置文件的根元素是…

易基因:全基因组CpG密度和DNA甲基化分析方法比较(MeDIP、RRBS和WGBS)| 研究综述

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 CpG密度&#xff08;CpG density&#xff09;与各种组织中的DNA甲基化相关。基因组按CpG密度分为&#xff1a;CpG岛&#xff08;CpG island&#xff0c;CGI&#xff09;、CpG岛上下游2kb…

FFMPEG VCL Pack Crack显示位置支持或光标

FFMPEG VCL Pack Crack显示位置支持或光标 FFMPEG VCL Pack是一个组合解决方案和平台&#xff0c;用于在Delphi中录制、转换和传播音频和视频&#xff0c;其中包括音频/视频库中的前一个libavcodec。 FFMPEG VCL Pack功能和选项&#xff1a; 新的Live555公司基于Rtsp Media Ser…

基于深度学习的安全帽检测系统(YOLOv5清新界面版,Python代码)

摘要&#xff1a;安全帽检测系统用于自动化监测安全帽佩戴情况&#xff0c;在需要佩戴安全帽的场合自动安全提醒&#xff0c;实现图片、视频和摄像头等多种形式监测。在介绍算法原理的同时&#xff0c;给出Python的实现代码、训练数据集&#xff0c;以及PyQt的UI界面。安全帽检…

设计模式之迭代器模式(C++)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 一、迭代器模式是什么&#xff1f; 迭代器模式是一种行为型的软件设计模式&#xff0c;提供一种方法能顺序访问聚合对象中的各个元…

如何做好缓存设计?

大家好&#xff0c;我是易安&#xff01;今天我们来谈一谈缓存应该如何设计。 什么是缓存 缓存是一种临时储存数据的方式。当用户查询数据时&#xff0c;系统会首先在缓存中查找&#xff0c;如果数据已经存在于缓存中&#xff0c;则直接使用&#xff0c;否则系统会到数据的原始…

研报精选230410

目录 【行业230410西南证券】医药行业2023年4月投资月报&#xff1a;看好创新药和中药行情【行业230410国信证券】汽车行业4月投资策略&#xff1a;3月新能源乘用车批发销量预计同比增长32%&#xff0c;持续关注板块年报季报行情【行业230410西南证券】医药行业周报&#xff1a…

【集成架构】探索3种顶级「集成框架」Apache、Spring和Mule

正确的集成框架是绑定应用程序架构构建块的粘合剂。应用程序组件必须不断交换关键数据&#xff0c;以方便用户操作、服务扩展、威胁监视、后端操作、事件触发等。如果没有可靠的集成过程&#xff0c;应用程序和服务故障将淹没软件环境。正确的集成框架是绑定应用程序架构构建块…