CUDA和TensorRT入门

2023-11-10

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或者不同的软件版本的设备上,引擎不能通用,要重新生成一个;

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CUDA和TensorRT入门 的相关文章

  • CUDA - 为什么基于扭曲的并行减少速度较慢?

    我有关于基于扭曲的并行减少的想法 因为根据定义 扭曲的所有线程都是同步的 因此 我们的想法是输入数据可以减少 64 倍 每个线程减少两个元素 而无需任何同步 与 Mark Harris 的原始实现相同 减少应用于块级 数据位于共享内存上 h
  • 使用常量内存打印地址而不是cuda中的值

    我试图在代码中使用常量内存 并从内核分配常量内存值 而不是使用 cudacopytosymbol include
  • 指定 NVCC 用于编译主机代码的编译器

    运行 nvcc 时 它始终使用 Visual C 编译器 cl exe 我怎样才能让它使用GCC编译器 设置CC环境变量到gcc没有修复它 我在可执行文件帮助输出中也找不到任何选项 在 Windows 上 NVCC 仅支持 Visual C
  • 如何在 Windows 上的 nvidia GPU 的 Visual Studio 2010 中配置 OpenCL?

    我在华硕笔记本电脑上的 Wwindows 7 操作系统上使用 NVIDIA GeForce GTX 480 GPU 我已经为 CUDA 4 2 配置了 Visual Studio 2010 如何在 Visual Studio 2010 上为
  • 使用内置显卡,没有NVIDIA显卡,可以使用CUDA和Caffe库吗?

    使用内置显卡 没有 NVIDIA 显卡 可以使用 CUDA 和 Caffe 库吗 我的操作系统是 ubuntu 15 CPU为 Intel i5 4670 3 40GHz 4核 内存为12 0GB 我想开始学习深度学习 CUDA 适用于 N
  • CUDA:如何检查计算能力是否正确?

    使用较高计算能力编译的 CUDA 代码将在计算能力较低的设备上完美执行很长一段时间 然后有一天在某些内核中默默地失败 我花了半天时间追寻一个难以捉摸的错误 结果发现构建规则已经sm 21而该设备 Tesla C2050 是2 0 是否有任何
  • 用于类型比较的 Boost 静态断言

    以下问题给我编译器错误 我不知道如何正确编写它 struct FalseType enum value false struct TrueType enum value true template
  • CUDA程序导致nvidia驱动程序崩溃

    当我超过大约 500 次试验和 256 个完整块时 我的 monte carlo pi 计算 CUDA 程序导致我的 nvidia 驱动程序崩溃 这似乎发生在 monteCarlo 内核函数中 任何帮助都会受到赞赏 include
  • cuda cpu功能-gpu内核重叠

    我在尝试开发以练习 CUDA 的 CUDA 应用程序时遇到并发问题 我想通过使用 cudaMemecpyAsync 和 CUDA 内核的异步行为来共享 GPU 和 CPU 之间的工作 但我无法成功重叠 CPU 执行和 GPU 执行 它与主机
  • 为什么numba cuda调用几次后运行速度变慢?

    我正在尝试如何在 numba 中使用 cuda 然而我却遇到了与我预想不同的事情 这是我的代码 from numba import cuda cuda jit def matmul A B C Perform square matrix m
  • cuda 共享内存 - 结果不一致

    我正在尝试并行缩减以对 CUDA 中的数组求和 目前我传递一个数组来存储每个块中元素的总和 这是我的代码 include
  • 如何并行从数组中删除零值

    如何使用 CUDA 并行有效地从数组中删除零值 有关零值数量的信息是预先可用的 这应该可以简化这项任务 重要的是数字必须保持源数组中的顺序 当被复制到结果数组时 Example 该数组将例如包含以下值 0 0 19 7 0 3 5 0 0
  • CUDA线程执行顺序

    我有一个 CUDA 程序的以下代码 include
  • Nvcc 的版本与 CUDA 不同

    我安装了 cuda 7 但是当我点击 nvcc version 时 它打印出 6 5 我想在 GTX 960 卡上安装 Theano 库 但它需要 nvcc 7 0 我尝试重新安装cuda 但它没有更新nvcc 当我运行 apt get i
  • 设置最大 CUDA 资源

    我想知道是否可以设置 CUDA 应用程序的最大 GPU 资源 例如 如果我有一个 4GB GPU 但希望给定的应用程序只能访问 2GB 如果它尝试分配更多 就会失败 理想情况下 这可以在进程级别或 CUDA 上下文级别上设置 不 目前没有允
  • 在 __device/global__ CUDA 内核中动态分配内存

    根据CUDA 编程指南 http developer download nvidia com compute cuda 3 2 prod toolkit docs CUDA C Programming Guide pdf 第 122 页 可
  • cuda中有模板化的数学函数吗? [复制]

    这个问题在这里已经有答案了 我一直在寻找 cuda 中的模板化数学函数 但似乎找不到 在普通的 C 中 如果我调用std sqrt它是模板化的 并且将根据参数是浮点数还是双精度数执行不同的版本 我想要这样的 CUDA 设备代码 我的内核将真
  • 有没有一种有效的方法来优化我的序列化代码?

    这个问题缺乏细节 因此 我决定创建另一个问题而不是编辑这个问题 新问题在这里 我可以并行化我的代码吗 还是不值得 https stackoverflow com questions 17937438 can i parallelize my
  • CUDA - 将 CPU 变量传输到 GPU __constant__ 变量

    与 CUDA 的任何事情一样 最基本的事情有时也是最难的 所以 我只想将变量从 CPU 复制到 GPUconstant变量 我很难过 这就是我所拥有的 constant int contadorlinhasx d int main int
  • VS 程序在调试模式下崩溃,但在发布模式下不崩溃?

    我正在 VS 2012 中运行以下程序来尝试 Thrust 函数查找 include cuda runtime h include device launch parameters h include

随机推荐

  • 探讨STOS指令

    转载在http hi baidu com darks00n blog item 4c019ec42ad0cdcad00060b1 html 下面是一段win32 console程序 Debug版 的反汇编代码 很程式化的东西 本文不讨论这段
  • Chromium Win10 开发环境搭建

    记录chromium 开发搭建过程 系统 软件环境不同 所遇问题可能不同 但主体关键相似 仅供参考 VS 安装 安装vs2019 the version 10 0 19041 or higher Windows 10 SDK install
  • idea maven项目运行不了,好多包导不了

    其实是idea默认给你选择了自带的maven和仓库 你可以改成你自己的 使用国内镜像就可以了 先简单记录一下 到时再详细写
  • 使用php 实现生成Excel文件并导出

    在现在的项目里 不管是电商项目还是别的项目 在管理端都会有导出的功能 比方说订单表导出 用户表导出 业绩表导出 这些都需要提前生成excel表 然后在导出 实际上是在代码里生成一张excel表 然后通过下载api进行导出的 好了 先给大家讲
  • promise跟ajax区别,Promise和AJAX有什么区别?

    你感到困惑的承诺和Ajax调用 它们有点像苹果和刀子 你可以用刀切苹果 刀是可以应用于苹果的工具 但这两者是非常不同的东西 承诺是管理异步操作的工具 他们会跟踪异步操作何时完成以及结果如何 并让您与其他代码或其他异步操作协调完成以及这些结果
  • 嵌入式学习--vi的基本命令二

    嵌入式学习 vi的基本命令二 vi查找命令 vi替换命令 vi复制和剪切命令 vi查找命令 string 查找字符串string n继续向下查找 N向上查找 按回车后 光标的位置直接跳转到字符char的前面 vi替换命令 范围 s 旧str
  • 2021 字节跳动面试总监首发 1121 道 LeetCode 算法刷题笔记(含答案)

    关于算法刷题的困惑和疑问也经常听朋友们提及 这份笔记里面共包含作者刷 LeetCode 算法题后整理的数百道题 每道题均附有详细题解过程 很多人表示刷数据结构和算法题效率不高 甚是痛苦 有了这个笔记的总结 对校招和社招的算法刷题帮助之大不言
  • Windows与网络基础-1-2-虚拟机安装Windows10/ server2016

    目录 一 下载虚拟机软件 1 1新建虚拟机 1 2选择操作系统类型和windows版本 1 3自定义虚拟机名称 1 4设置最大磁盘大小 1 5配置内存和处理器 1 6挂载镜像文件 1 7进入window配置界面 二 点击进去按步骤安装即可
  • 常用邮箱工具类

    废话不多说 直接上代码 自个耍 package com example demo util import javax mail import javax mail internet InternetAddress import javax
  • 多线程基础篇(包教包会)

    文章目录 一 第一个多线程程序 1 Jconsole观察线程 2 线程休眠 sleep 二 创建线程 三 Thread类及常见方法 1 Thread 的常见构造方法 2 Thread 的几个常见属性 3 启动线程 start 4 中断线程
  • 宝塔面板忘记用户名,忘记密码怎么办?

    1 重新设置密码找回用户名 情况一 已经修改过用户名和密码 很多网上的解决方法都只是告诉我们进入SSH设置 但是很多小伙伴并不知道怎么进入SSH 导致解决该问题很麻烦 其实我们直接在阿里云中云服务器中的远程连接就可以解决这个问题 第一步 直
  • 利用security.js实现RSA加密

    在项目中遇到要对用户输入的密码进行RSA加密的需求 总结一下实现过程 div div
  • Android和java两平台AES的互相加密解密

    import java io UnsupportedEncodingException import java security InvalidKeyException import java security Key import jav
  • 什么是多态机制?Java语言是如何实现多态的?

    多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用 在编程时并不确定 而在程序运行期间才确定 即一个引用变量到底会指向哪个类的实例对象 该引用变量发出的方法到底调用哪个类中实现的方法 必须由程序运行期间才能决定 因为
  • 计算机系统唯一能识别的不需要翻译,第一章 计算机基础知识06966new.ppt

    第一章 计算机基础知识06966new ppt 第一章 计算机基础知识 2 1 计算机软件系统 输入计算机的信息一般有两类 一类称为数据 一类称为程序 计算机是通过执行程序所规定的各种指令来处理各种数据的 1 指令 是指示计算机执行某种操作
  • IntelliJ IDEA(九) :酷炫插件系列

    最近项目比较忙 很久没有更新IDEA系列了 今天介绍一下IDEA的一些炫酷的插件 IDEA强大的插件库 不仅能给我们带来一些开发的便捷 还能体现我们的与众不同 1 插件的安装 打开setting文件选择Plugins选项 Ctrl Alt
  • 尊云服务器出问题,云服务器用户常见问题

    云服务器用户常见问题 Q 装预装操作系统以后的默认密码是什么 A 默认的密码和云服务器开通时输入的密码是一样的 就是一个用户一个密码 不是固定的 Q 云服务器中如何划分硬盘的分区 A 云服务器系统系统安装后 默认只有一个10G的C盘用于操作
  • Android进阶:架构师花费近一年时间整理出来的安卓核心知识,聪明人已经收藏了!

    我们程序员经常迷茫于有太多东西要学 有些找不到方向 不知所措 很多程序员都愿意说 我想变得更好 但是更好是什么却很模糊 同时我们又不知道该怎么样去做 我们的生命如此短暂 作为程序员的职业生涯可能会更短 所以我们更加需要充分利用工作 工作间隙
  • 踩坑,发现一个ShardingJdbc读写分离的BUG

    前言 最近公司准备接入ShardingJdbc做读写分离了 老大让我们理一理有没有写完数据立马读的场景 因为主从同步是有延迟的 如果写完读取数据走到从库 而从库正好有延迟 没读取到数据 岂不是造成了生产事故 今天我们来看看 Sharding
  • CUDA和TensorRT入门

    CUDA 官方教程 CUDA C Programming Guide nvidia com 一 基础知识 首先看一下显卡 GPU 和CUDA的关系介绍 显卡 GPU和CUDA简介 吴一奇的博客 CSDN博客 延迟 一条指令返回的时间间隔 吞