模型部署学习笔记——模型部署关键知识点总结

模型部署学习笔记——模型部署关键知识点总结

  • 模型部署学习笔记——模型部署关键知识点总结
    • 1. CUDA中Grid和Block的定义是什么?Shared Memory的定义?Bank Conflict的定义?Stream和Event的定义?
    • 2. TensorRT的工作流程?
    • 3. ONNX是什么?如何从Pytorch导出ONNX?又如何将ONNX导入TensorRT?可能遇到的问题是什么?
    • 4. Layer Fusion的方法有哪些?
    • 5. FLOPS、TOPS、FLOPs等的定义是什么?Roofline Model是定义和用法?
    • 6. 模型量化中PTQ和QAT的区别?Calibration的种类?常用的量化粒度有哪些?如果出现精度下降怎么办?
    • 7. 什么是模型剪枝?模型剪枝的分类,各类剪枝的利弊都有哪些?
    • 8. 模型推理中的基本框架?
    • 9. 模型部署中的一些注意点

模型部署学习笔记——模型部署关键知识点总结

最近在工作之余学习了一些和模型部署相关的课程,本博客是对其中一些知识点的记录和总结,主要是模型部署中的一些基本概念。

1. CUDA中Grid和Block的定义是什么?Shared Memory的定义?Bank Conflict的定义?Stream和Event的定义?

在这里插入图片描述

Thread是执行计算任务的基本单位,每个线程执行同一内核函数(Kernel Function),但每个线程可以根据其Thread ID来处理不同的数据或计算任务:

  1. 线程是CUDA程序中并行计算的基本单位,它们通过Grid和Block组织起来,并执行内核函数。
  2. 每个线程通过Thread ID标识其在线程块中的位置,通过Block ID标识线程块在Grid中的位置。
  3. 线程之间可以通过共享内存和同步机制进行协调,但它们本身是独立执行的。

Block 是Grid的子集,每个Block内包含多个Thread,这些线程在同一个SM(Streaming Multiprocessor)上执行。Block是一个共享资源单元,在同一Block内的线程能够共享内存和同步执行。

  1. 每个Block有一个维度,通常为1D、2D或3D。
  2. Block中线程的数量是有限的,通常由硬件决定(每个Block最多1024个线程,具体限制取决于GPU架构)。
  3. 在同一个Block内,线程可以通过共享内存进行高效的通信。

Grid 是线程块(Block)的集合,表示了CUDA程序中的一个大规模并行计算任务的整体。每个Grid包含多个Block,这些Block可以在不同的SM(Streaming Multiprocessor)上并行执行。

  1. 一个Grid由多个线程块组成。
  2. 每个Grid有一个维度,通常为1D、2D或3D,具体取决于任务的需求。
  3. 每个Grid的大小通常由总的线程块数和每个Block中线程的数量决定。

Shared Memory是CUDA编程模型中的一种高效的内存类型,专门用于Block内部的线程之间的高速数据共享和通信。Shared Memory位于每个线程块内部,它比Global Memory要快得多,但每个Block只能访问其自己Block内的共享内存,不能访问其他Block的共享内存
不使用Shared Memory的代码如下:

__global__ void matrixMultiply(float *A, float *B, float *C, int N) {int row = threadIdx.y + blockIdx.y * blockDim.y;int col = threadIdx.x + blockIdx.x * blockDim.x;if (row < N && col < N) {float value = 0;for (int i = 0; i < N; i++) {value += A[row * N + i] * B[i * N + col];}C[row * N + col] = value;}
}

使用Shared Memory的代码如下:

__global__ void matmul_shared_memory(float *A, float *B, float *C, int N) {__shared__ float sharedA[BLOCK_SIZE][BLOCK_SIZE];__shared__ float sharedB[BLOCK_SIZE][BLOCK_SIZE];int row = blockIdx.y * blockDim.y + threadIdx.y;int col = blockIdx.x * blockDim.x + threadIdx.x;float value = 0.0;for (int k = 0; k < (N / BLOCK_SIZE); ++k) {// Load data into shared memorysharedA[threadIdx.y][threadIdx.x] = A[row * N + k * BLOCK_SIZE + threadIdx.x];sharedB[threadIdx.y][threadIdx.x] = B[(k * BLOCK_SIZE + threadIdx.y) * N + col];// Synchronize threads to ensure shared memory is filled__syncthreads();// Perform multiplicationfor (int i = 0; i < BLOCK_SIZE; ++i) {value += sharedA[threadIdx.y][i] * sharedB[i][threadIdx.x];}// Synchronize threads before the next iteration__syncthreads();}// Store the resultC[row * N + col] = value;
}

Bank Conflict 是指当多个线程在同一时刻访问共享内存的同一Bank时发生的冲突。共享内存的访问速度比全局内存快得多,而通过将共享内存划分为多个Bank,能够让多个线程同时访问共享内存而不会发生冲突,从而提高性能。如果多个线程访问同一个Bank上的不同位置,硬件必须序列化这些访问,这样就会导致性能下降。一种比较简单地解决Bank Conflict问题的方法是在申请共享内存时进行Padding,如下:

#define N 32  // 假设线程块大小为32__global__ void avoidBankConflict(float *data) {__shared__ float shared[N + 1];  // 添加了padding,数组大小为N+1int index = threadIdx.x;// 原始数组数据存储shared[index] = data[index];// 示例计算:每个线程计算一个数据元素shared[index] = shared[index] * 2.0f;// 将结果存回全局内存data[index] = shared[index];
}

其中shared[N + 1]中的N代表线程块中的线程数(假设为32),而我们给共享内存数组增加了1个额外元素(即N+1),这就是Padding。这样,线程0将访问shared[0],线程1访问shared[2],线程2访问shared[4],依此类推。通过间隔增加访问步长,避免了线程间的冲突。

Stream 是CUDA中的一种机制,用于管理异步执行的操作。同一个Stream的执行顺序和各个Kernel以及Memory Operation的启动顺序是一致的,但是只要资源没有被占用,不同流之间的执行时可以Overlap的,如下图所示:
在这里插入图片描述
如下是一个单流版本代码:

#include <cuda_runtime.h>
#include <stdio.h>#define N (1 << 20)  // 数据大小
#define NUM_STREAMS 4  // 数据块数量__global__ void kernel(float *data, int size) {int idx = blockIdx.x * blockDim.x + threadIdx.x;if (idx < size) {data[idx] *= 2.0f;  // 简单的倍乘操作}
}void singleStreamExample() {float *h_data, *d_data;size_t size = N * sizeof(float);cudaMallocHost(&h_data, size);  // 主机内存cudaMalloc(&d_data, size);     // 设备内存// 初始化数据for (int i = 0; i < N; i++) h_data[i] = i;cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice);  // 数据传输kernel<<<N / 256, 256>>>(d_data, N);                      // 内核执行cudaMemcpy(h_data, d_data, size, cudaMemcpyDeviceToHost);  // 数据返回cudaFree(d_data);cudaFreeHost(h_data);
}

如下是修改为多流版本的代码:

void multiStreamExample() {float *h_data[NUM_STREAMS], *d_data[NUM_STREAMS];cudaStream_t streams[NUM_STREAMS];size_t size = (N / NUM_STREAMS) * sizeof(float);// 为每个数据块分配内存和创建流for (int i = 0; i < NUM_STREAMS; i++) {cudaMallocHost(&h_data[i], size);  // 主机内存cudaMalloc(&d_data[i], size);     // 设备内存cudaStreamCreate(&streams[i]);    // 创建流// 初始化数据for (int j = 0; j < N / NUM_STREAMS; j++) {h_data[i][j] = j + i * (N / NUM_STREAMS);}}// 使用多个流异步执行for (int i = 0; i < NUM_STREAMS; i++) {cudaMemcpyAsync(d_data[i], h_data[i], size, cudaMemcpyHostToDevice, streams[i]);  // 异步数据传输kernel<<<(N / NUM_STREAMS) / 256, 256, 0, streams[i]>>>(d_data[i], N / NUM_STREAMS);  // 异步内核执行cudaMemcpyAsync(h_data[i], d_data[i], size, cudaMemcpyDeviceToHost, streams[i]);  // 异步数据返回}// 等待所有流完成for (int i = 0; i < NUM_STREAMS; i++) {cudaStreamSynchronize(streams[i]);  // 等待每个流完成}// 释放资源for (int i = 0; i < NUM_STREAMS; i++) {cudaFree(d_data[i]);cudaFreeHost(h_data[i]);cudaStreamDestroy(streams[i]);}
}

Event 是CUDA中的一种机制,用于标记某些操作的完成,并允许用户在不同的流或操作之间进行同步。事件通常用于监控计算或内存操作的进度,并在不同流之间建立同步依赖关系。如下是使用Event对Stream进行时间测量的代码:

cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);cudaEventRecord(start, 0);
// 调用 singleStreamExample 或 multiStreamExample
cudaEventRecord(stop, 0);cudaEventSynchronize(stop);
float milliseconds = 0;
cudaEventElapsedTime(&milliseconds, start, stop);
printf("Execution time: %f ms\n", milliseconds);cudaEventDestroy(start);
cudaEventDestroy(stop);

2. TensorRT的工作流程?

TensorRT 的工作流程是:

  1. 模型导入:通过工具(如Tensorflow中的tf2onnx或PyTorch中的torch.onnx.export)将训练好的模型转换为ONNX格式再加载到TensorRT中,或者通过Tensor API手动构建模型。
  2. 模型优化:TensorRT中模型优化策略包括Layer& Tensor Fusion,Kernel Auto-Tuning、Quantization等,如下图所示:

在这里插入图片描述

  1. 推理引擎构建:即使用优化后的模型构建一个高效的推理引擎(Engine)。推理引擎是一个轻量化、高性能的运行时表示,仅用于推理
  2. 模型推理:将Engine部署到端侧,输入实时数据进行推理。

3. ONNX是什么?如何从Pytorch导出ONNX?又如何将ONNX导入TensorRT?可能遇到的问题是什么?

  1. ONNX(Open Neural Network Exchange)是一个开放的深度学习模型交换格式,旨在实现不同深度学习框架之间的互操作性。
    ONNX采用Protobuf二进制形式进行序列化模型。模型的组织结构由几个主要的组件构成如下,如下是一个简化的 ONNX 模型的组织结构:
ModelProto├── ir_version: 7├── producer_name: "PyTorch"├── producer_version: "1.10"├── model_version: 1├── graph│    ├── name: "example_graph"│    ├── node│    │    ├── op_type: "Conv"│    │    ├── input: ["input_tensor", "weight"]│    │    ├── output: ["output_tensor"]│    │    └── attribute: [{name: "kernel_shape", value: [3,3]}]│    ├── node│    │    ├── op_type: "Relu"│    │    ├── input: ["output_tensor"]│    │    └── output: ["relu_output"]│    ├── input: ["input_tensor"]│    ├── output: ["relu_output"]│    ├── initializer: [weight_tensor]│    └── value_info: [intermediate_tensor]├── metadata_props: []

其中,ModelProto 包含了模型的基本信息;GraphProto 包含了一个名为 example_graph 的计算图,该图包含两个节点(一个卷积层和一个 ReLU 激活层);NodeProto 定义了每个操作节点,例如卷积层(Conv)和 ReLU 层(Relu);TensorProto 用于表示图中的张量(如 weight_tensor 和 input_tensor)。

  1. 从Pytorch导出ONNX方法如下:
import torch
import torch.onnx# 假设你有一个训练好的 PyTorch 模型 `model`
model = ...  # 加载或定义你的模型
model.eval()  # 设置为评估模式# 输入示例数据,应该与模型的输入形状相同
dummy_input = torch.randn(1, 3, 224, 224)  # 示例输入(假设是一个图片输入)# 导出模型为 ONNX 文件
onnx_path = "model.onnx"
torch.onnx.export(model, dummy_input, onnx_path, input_names=['input'], output_names=['output'])
  1. 将ONNX导入TensorRT方法如下:
import tensorrt as trt
import onnx# 1. 加载 ONNX 模型
onnx_model_path = "model.onnx"
onnx_model = onnx.load(onnx_model_path)# 2. 创建 TensorRT 的 ONNX 解析器
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(common.EXPLICIT_BATCH)
parser = trt.OnnxParser(network, TRT_LOGGER)# 3. 解析 ONNX 模型并构建 TensorRT 引擎
with open(onnx_model_path, 'rb') as f:parser.parse(f.read())# 4. 构建 TensorRT 引擎
engine = builder.build_cuda_engine(network)# 5. 使用 TensorRT 引擎执行推理(需要设置执行上下文等)
  1. 从Pytorch到出ONNX的时候有时候会遇到导出失败的问题,此时解决方案通常如下:
    (1)修改opset的版本:查看不支持的算子在新的opset中是否被支持,以及onnx-tensorrt中是否被支持。opset支持的算子通常在ONNX算子文档有列出,onnx-tensorrt 支持的算子集通常会在其官方文档中列出。
    (2)替换pytorch中的算子组合:即将某些计算替换为onnx中可以识别的算子。
    (3)在pytorch登记onnx中某些算子:有可能onnx中支持,但是pytorch中没有被登记,登记方式可以使用 torch.onnx.export 的 custom_opsets 注册自定义算子或者通过 torch.onnx.symbolic 定义算子的符号
    (4)直接修改onnx,创建plugin:这种方法需要使用onnx-surgeon,onnx-surgeon是一个Python 库,用于对ONNX模型进行修改和修剪。它允许用户通过 API 对 ONNX 模型的结构进行编辑、调整和修剪,支持添加、删除、修改节点等操作。
    以上四种解决方案难度由易到难。

4. Layer Fusion的方法有哪些?

Layer Fusion是指将多个层可以通过融合来优化计算。Layer Fusion分为Horizontal Layer Fusion和Vertical Layer Fusion。Vertical Layer Fusion中典型的融合方式有Conv+ReLUConv+BN+ReLU,Horizontal Layer Fusion是将水平方向上同类Layer进行融合,如下图所示是两类融合方式的示意图;
在这里插入图片描述
Vertical Layer Fusion融合结果:
在这里插入图片描述
Horizontal Layer Fusion融合结果:
在这里插入图片描述
为什么融合可以减少计算量呢,我们知道BN的公式如下: y i = γ ∗ x i − μ B σ B 2 + ϵ + β y_i=\gamma * \frac{x_i-\mu_B}{\sqrt{\sigma_B^2+\epsilon}}+\beta yi=γσB2+ϵ xiμB+β其中 γ \gamma γ β \beta β为可学习参数, μ B = 1 B ∑ i = 1 B x i \mu_B=\frac{1}{B} \sum_{i=1}^B x_i μB=B1i=1Bxi σ B 2 = 1 B ∑ i = 1 B ( x i − μ B ) 2 + ϵ \sigma_B^2=\frac{1}{B} \sum_{i=1}^B\left(x_i-\mu_B\right)^2+\epsilon σB2=B1i=1B(xiμB)2+ϵ,我们将卷积公式 x i = w ∗ x i + b x_i=w * x_i+b xi=wxi+b代入得: y = γ ∗ w ∗ x + b − μ B σ B 2 + ϵ + β y=\gamma * \frac{w * x+b-\mu_B}{\sqrt{\sigma_B^2+\epsilon}}+\beta y=γσB2+ϵ wx+bμB+β进一步展开和代换可得: y = ( γ ∗ w σ B 2 + ϵ ) ∗ x + ( γ σ B 2 + ϵ ( b − μ B ) + β ) y=\left(\frac{\gamma * w}{\sqrt{\sigma_B^2+\epsilon}}\right) * x+\left(\frac{\gamma}{\sqrt{\sigma_B^2+\epsilon}}\left(b-\mu_B\right)+\beta\right) y=(σB2+ϵ γw)x+(σB2+ϵ γ(bμB)+β)我们定义 w ^ = γ ∗ w σ B 2 + ϵ \widehat{w}=\frac{\gamma * w}{\sqrt{\sigma_B^2+\epsilon}} w =σB2+ϵ γw b ^ = γ σ B 2 + ϵ ( b − μ B ) + β \hat{b}=\frac{\gamma}{\sqrt{\sigma_B^2+\epsilon}}\left(b-\mu_B\right)+\beta b^=σB2+ϵ γ(bμB)+β由于 γ \gamma γ β \beta β在训练完成后都是已知的,因此我们可以将上述公式简化为: y = w ^ ∗ x + b ^ y=\widehat{w} * x+\hat{b} y=w x+b^这样Conv+BN的计算量就和Conv本身基本接近。而ReLU只是做一个截取,因此进行融合。

5. FLOPS、TOPS、FLOPs等的定义是什么?Roofline Model是定义和用法?

名词缩写/单位定义和作用
计算峰值FLOPS (Floating point number operations per second)表示每秒钟进行的浮点运算次数,是衡量计算机性能的一个常见指标
计算峰值TOPS (Tera operations per second)表示每秒钟处理的整型运算的次数,是衡量计算机性能的另一个指标
计算量FLOPs(Floating point number operations)表示模型中有多少个浮点运算,是衡量模型大小的一个指标
参数量Byte表示模型中所有的weights的量,是另一个衡量模型大小的指标,指的注意的是conv的参数量和input/output无关,而transformer的参数会根据输入tensor的大小改变而改变
访存量Byte表示模型中模型中某一个算子或者某一个layer进行计算时需要与memory产生读写的量,是分析模型中某些计算的计算效率的标准之一
带宽量Byte/s表示的是单位时间内可以传输的数据量的多少,是衡量计算机硬件memory的性能的标准之一
计算密度Operation Intensity(FLOPs/Bytes)表示的是单位数据可以进行的浮点运算数,计算密度=计算量/访存量

Roofline Model 是一种用于描述计算机系统性能的可视化模型,特别用于展示计算密集型任务的性能瓶颈,如下图所示:
在这里插入图片描述
X轴表示计算密集度,单位为 FLOP/byte(浮点运算数与内存数据量的比率)。它衡量了应用程序的计算与数据访问的关系,通常越高表示计算密集度越大。Y轴表示计算性能,通常单位是 FLOPS(浮点运算每秒),即系统能够执行的浮点运算数。

实际应用的表现会被标示在 Roofline 图中,通常是在 Roofline 上下限之间。如果应用的性能接近 Roofline,那么它就已经接近硬件的理论最大性能;如果远低于 Roofline,则意味着有潜在的性能瓶颈,如上图中标识的,Algorithm Fully-connected计算密度过低,因此没法充分利用硬件性能,而Algorithm 2 Convolution已经达到Roofline。

这里我们可以给出一些结论:
(1)对于Convlution,随着Kernel Size、Output Size、Channel Size的增大计算密度都是增大的,但是计算密度增长率会逐渐下降;
(2) 1 × 1 1\times 1 1×1 Conv,Depth Wise Conv虽然计算量小,但是其计算密度很低;
(3)Fully Connected Layer计算密度非常小;
(4)模型中Reshape操作多也会导致整个模型的计算密度下降。

6. 模型量化中PTQ和QAT的区别?Calibration的种类?常用的量化粒度有哪些?如果出现精度下降怎么办?

  1. PTQ和QAT的区别如下
    PTQ (Post-Training Quantization) 是一种在模型训练完成后对模型进行量化的方法。它不需要重新训练模型,而是通过在训练好的模型上直接应用量化算法来减少模型的存储需求和加速推理过程。其优点是方便,缺点是会掉精度;
    (1)TensorR的Kernel Autotuning会选择核函数来做FP16/INT8的计算,有可能会出现FP16在Tensor Core上计算,而INT8则在Cuda Core上,这样就有可能导致INT8量化后的速度反而比FP16/FP32要慢
    (2)量化后可能会导致层融合失效,这样也可能会导致INT8量化速度反而变慢的问题
    (3)量化过程中需要注意Sensitive Analysis,对于位于输入和输出的敏感层应该尽量使用FP16进行量化
    QAT(Quantization-Aware Training) 是一种在训练过程中就引入量化操作的技术,通过在训练时模拟低精度的运算来帮助模型适应量化带来的误差。其优点是精度高,缺点是实现复杂
    (1)QAT通常通过Q/DQ Node实现,Q/DQ Node也被称为Fake Quantization Node实现,用来模拟FP32->INT8量化的Scale和Shift,以及INT8->FP32的反量化的Scale和Shift,QAT通过Q和DQ Node里面存储的信息对FP32或者INT8进行线性变换。
    (2)QAT是一种Fine-Tuning方式,通过一个Pre-Trained Model进行添加Q/DQ节点模拟量化,并通过训练来更新权重去吸收量化过程带来的误差。添加Q/DQ节点后算子会以INT8精度执行

在这里插入图片描述

  1. Calibration的种类有
    (1)Minmax Calibration、Entropy Calibration、Percentile Calibration;
    (2)如果Floating Point的分布比较分散,各个区间下都比较均匀,Minmax Calibration是一个不错的选择;
    (3)目前TensorRT使用默认是Entropy Calibration。一般来讲Entropy Calibration精度可以比较好;
    (4)Weight一般使用Minmax Calibration(信息尽量不要有遗漏),Activation Values一般使用Entropy或者Percentile Calibration(不同Layer的Activation Values值分布会有较大不同);
    (5)Calibration时Batch Size会影响精度,总的来讲,Batch Size越大越好,但不是绝对的。

  2. 常用的量化粒度
    (1)Per-tensor 量化:在 Per-tensor 量化中,整个张量的所有元素使用相同的量化尺度和偏移量。也就是说,对于一个特定的层或整个网络的某一张量,所有的元素都共享一个全局的量化范围。其优点是延迟低,缺点是错误率高,因此一个Scale很难覆盖所有FP32的Dynamic Range。
    (2)Per-channel 量化:在 Per-channel 量化中,每个通道(例如卷积核或特征图的通道)会使用独立的量化范围。这意味着每个通道的激活值或权重会有不同的量化尺度和偏移量。其优点是低错误率,缺点是高延迟,因为需要使用vector来存储一个Channel的Scale。
    (3)Weight的量化一般会选取Per-Channel量化(因为BN的参数会融合在线性计算中,BN的参数是Per-Channle的,此外还有Depthwise Convolution的存在),Activation Values的量化一般会选取Per-Tensor量化

  3. 如果出现精度下降怎么办?
    在我们进行模型量化时,
    (1)第一步应该先进行PTQ,尝试多种Calibration算法,如果精度不满足考虑进行第二步,
    (2)第二步应该是进行Partial-Quntization,通过Layer-wise的Sensitive Analysis分析每一层精度损失,尝试FP16+INT8的组合,将FP16用在敏感层,INT8用在计算密集层,如果精度还不满足再考虑第三步,
    (3)第三步才是QAT,通过Fine-Tuning来训练权重(原本训练的10%个Epoch)。
    量化后精度下降控制在相对精度损失小于2%是合理的

7. 什么是模型剪枝?模型剪枝的分类,各类剪枝的利弊都有哪些?

  1. 模型剪枝(Model Pruning) 是一种深度学习模型优化技术,旨在减少模型的复杂度(比如模型的参数数目或计算量),从而提升推理速度、减少内存占用并提高模型的部署效率。它通过移除不重要的模型参数,使得模型更加紧凑。步骤通常如下:
    (1)获取一个已经训练好的初始模型
    (2)对这个模型进行剪枝
            1. 可以通过训练的方式让模型去学习哪些权重可以归零,例如使用L1 Regularization和BN的Scaling Factor让权重归零;
            2. 自定义一些规则手动地让某些权重归零,例如对 1 × 4 1\times 4 1×4的Vector进行2:4的Weight Prunning;
    (3)对剪枝后的模型进行Fine-tuning(剪枝后初期模型通常会掉点,通过Fine-tuning来恢复精度)
    (4)获取到一个压缩的模型

  2. 模型分类通常分为结构化剪枝和非结构化剪枝、粗粒度剪枝和细粒度剪枝
    (1)粗粒度剪枝
            1. 比较常见的是Channel/Kernel Pruning,通常是使用L1 Norm寻找权重中影响度比较低的卷积核
            2. 优势是不依赖硬件,劣势是更容易掉精度,剪枝后可能反而不适合硬件加速(Tensor Core的使用条件是Channel是8或者16的倍数)
    (2)细粒度剪枝
            1. 细粒度剪枝可以进一步分为结构化剪枝(Vector-wise和Block-wise)和非结构化剪枝(Element-wise)
            2. 优势是对精度影响较小,劣势是需要特殊硬件支持(Tensor Core可以支持Sparse)以及需要额外的Memory来存储哪些Index是可以保留计算的

  3. Channel-Level Pruning
    参考文章是Learning Efficient Convolutional Networks through Network Slimming,主要思路是通过使用BN中的Scaling Factor与L1-Regularization的训练可以让权重趋于0,找到Conv中不是很重要的Channel,实现Channel-Level的Pruning。
    在这里插入图片描述
    (1)L1 Regularization可以用来系数参数或者让参数趋向于零,L2 Regularization可以用来减小参数值的大小,这个通过L1 Regularization的公式就能理解: L = min ⁡ f ( x ) + λ ∑ i = 1 n ∣ w i ∣ L=\min f(x)+\lambda \sum_{i=1}^n\left|w_i\right| L=minf(x)+λi=1nwi(2)Batch Normalization通常放在Conv之后对Conv的输出进行Normalization。整个计算是Channel-wise的,每个Channel都拥有自己的BN参数,如果Batch Normalization之后发现某一个Channle的Scaling非常小或者为零,那么就可以认为这个Channel参与的计算没有非常的大强度的改变或者特征提取,可以忽略。
    (3)使用Batch Normalization和L1 Regularization对模型的权重进行计算和重要度排序: L = ∑ ( x , y ) l ( f ( x , W ) , y ) + λ ∑ γ ∈ Γ g ( γ ) g ( s ) = ∣ s ∣ L=\sum_{(x, y)} l(f(x, W), y)+\lambda \sum_{\gamma \in \Gamma} g(\gamma) \quad g(s)=|s| L=(x,y)l(f(x,W),y)+λγΓg(γ)g(s)=s这里和普通的L1 Regularization区别是惩罚的不再是每一个权重,而是Batch Normalization对于Conv中每一个Channel的Scaling Factor。从而在学习过程中使得Scaling Factor趋近于零。如上图所示我们可以将 C i 2 C_{i2} Ci2 C i 4 C_{i4} Ci4去除掉。

8. 模型推理中的基本框架?

当我们对不同模型部署的时候可以发现:
(1)不管是分类、分割还是检测,模型推理的流程都是 前处理->模型推理->后处理
(2)在创建推理引擎时,流程都是bulider->nertwork->config->serialize->save file
(3)在执行推理引擎时,流程都是load file->deserialize->engine->context->enqueue
(4)在模型推理中用得比较多的设计模式是工厂模式和消费者-生产者模式;
研究开源代码推荐Lidar_AI_Solution

9. 模型部署中的一些注意点

  1. FLOPs不能完全衡量模型性能
    (1)因为FLOPs只是模型大小的单位
    (2)模型性能还需要考虑访存量和计算无关的DNN部分(Reshape、Shortcut等)、和DNN以外的部分(前处理、后处理)
  2. 模型部署不能完全依靠TensorRT
    (1)计算密度低的 1 × 1 1\times 1 1×1 Conv,Depth Wise Conv不会重构;
    (2)GPU无法优化的地方会到CPU执行,此时需要手动修改代码实现部分,让部分CPU执行转到GPU;
    (3)部分冗长的计算,TensorRT可能为了优化天机一些多余的操作,比如reformatter这种;
    (4)存在TensorRT尚未支持的算子
    (5)TensorRT不一定会分配Tensor Core,因为TensorRT Kerner Auto Tunning会选择最合适的Kernel;
  3. CUDA Core和Tensor Core的使用
    (1)因此Kernel Auto Tuning自动选择最优解,有时候TensorRT并不会分配Tensor Core,所以有时候会出现INT8速度比FP16慢的问题;
    (2)使用Tensor Core需要让Tensor Size为8或者16的倍数,8的倍数为FP16的精度,16的倍数为INT8的精度;
  4. 不能前处理和后处理的Overhead
    (1)对于一些轻量模型,前处理/后处理可能会更加耗时;
    (2)可以把有些前处理/后处理中可并行的地方用GPU处理,例如RGP2BGR、Normalization、Resize、Crop、NCHW2NHWC;
    (3)可以在CPU上使用一些图像处理优化库,例如Halide,Blur、Resize、Crop、DBSCAN、Sobel这些操作会比Opencv要快;

以上是对一些基本概念的总结,实际模型部署的工作会比上述更加复杂有趣,比如如果部署的算子导出失败如何通过ONNX的插件构建和修改等等,还会有很多工具和API需要学习,这些就在之后工作上具体遇到实际问题时再进一步了解吧

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

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

相关文章

Spring Cloud Gateway 源码

Spring Cloud Gateway 架构图 按照以上架构图&#xff0c;请求的处理流程&#xff1a; 1.客户端请求发送到网关 DispatcherHandler 2.网关通过 HandlerMapping 找到相应的 WebHandler 3.WebHandler生成FilterChain过滤器链执行所有的过滤器 4.返回Response结果 自动装配类Gat…

基于Spring Boot的店铺租赁平台的设计与实现

一、项目背景 随着互联网技术的飞速发展&#xff0c;线上交易已成为商业活动的重要趋势。店铺租赁作为商业地产的核心环节&#xff0c;其传统模式面临着信息不对称、交易效率低下等问题。因此&#xff0c;开发一个高效、便捷的线上店铺租赁平台显得尤为重要。本项目利用Java S…

基于卷积神经网络(CNN)和ResNet50的水果与蔬菜图像分类系统

前言 在现代智能生活中&#xff0c;计算机视觉技术已经成为不可或缺的工具&#xff0c;特别是在食物识别领域。想象一下&#xff0c;您只需拍摄一张水果或蔬菜的照片&#xff0c;系统就能自动识别其种类并为您提供丰富的食谱建议。这项技术不仅在日常生活中极具实用性&#xf…

Tomcat部署war包项目解决404问题

问题出在了Tomcat的版本上了&#xff0c;应该先去看这个项目使用的springboot版本&#xff0c;然后去仓库里找到对应Tomcat版本。 Maven Repository: org.springframework.boot spring-boot-starter-tomcat 因此我们应该选择Tomcat9版本。 当我把Tomcat11换成Tomcat9时&…

Redis篇--常见问题篇1--缓存穿透(缓存空值,布隆过滤器,接口限流)

1、概述 缓存穿透是指客户端请求的数据既不在Redis缓存中&#xff0c;也不在数据库中。换句话说&#xff0c;缓存和数据库中都不存在该数据&#xff0c;但客户端仍然发起了查询请求。这种情况下&#xff0c;缓存无法命中&#xff0c;请求会直接穿透到数据库&#xff0c;而数据…

前端使用 Konva 实现可视化设计器(20)- 性能优化、UI 美化

这一章主要分享一下使用 Konva 遇到的性能优化问题&#xff0c;并且介绍一下 UI 美化的思路。 至少有 2 位小伙伴积极反馈&#xff0c;发现本示例有明显的性能问题&#xff0c;一是内存溢出问题&#xff0c;二是卡顿的问题&#xff0c;在这里感谢大家的提醒。 请大家动动小手&a…

BlueLM:以2.6万亿token铸就7B参数超大规模语言模型

一、介绍 BlueLM 是由 vivo AI 全球研究院自主研发的大规模预训练语言模型&#xff0c;本次发布包含 7B 基础 (base) 模型和 7B 对话 (chat) 模型&#xff0c;同时我们开源了支持 32K 的长文本基础 (base) 模型和对话 (chat) 模型。 更大量的优质数据 &#xff1a;高质量语料…

C语言基础16(文件IO)

文章目录 构造类型枚举类型typedef 文件操作(文件IO)概述文件的操作文件的打开与关闭打开文件关闭文件文件打开与关闭案例 文件的顺序读写单字符读取多字符读取单字符写入多字符写入 综合案例&#xff1a;文件拷贝判别文件结束 数据块的读写(二进制)数据块的读取数据块的写入 文…

冯诺依曼架构与哈佛架构的对比与应用

冯诺依曼架构&#xff08;Von Neumann Architecture&#xff09;&#xff0c;也称为 冯诺依曼模型&#xff0c;是由著名数学家和计算机科学家约翰冯诺依曼&#xff08;John von Neumann&#xff09;在1945年提出的。冯诺依曼架构为现代计算机奠定了基础&#xff0c;几乎所有现代…

3D造型软件solvespace在windows下的编译

3D造型软件solvespace在windows下的编译 在逛开源社区的时候发现了几款开源CAD建模软件&#xff0c;一直囿于没有合适的建模软件&#xff0c;虽然了解了很多的模拟分析软件&#xff0c;却不能使之成为整体的解决方案&#xff0c;从而无法产生价值。opencascad之流虽然可行&…

机器学习04-为什么Relu函数

机器学习0-为什么Relu函数 文章目录 机器学习0-为什么Relu函数 [toc]1-手搓神经网络步骤总结2-为什么要用Relu函数3-进行L1正则化修改后的代码解释 4-进行L2正则化解释注意事项 5-Relu激活函数多有夸张1-细数Relu函数的5宗罪2-Relu函数5宗罪详述 6-那为什么要用这个Relu函数7-文…

QScreen在Qt5.15与Qt6.8版本下的区别

简述 QScreen主要用于提供与屏幕相关的信息。它可以获取有关显示设备的分辨率、尺寸、DPI&#xff08;每英寸点数&#xff09;等信息。本文主要是介绍Qt5.15与Qt6环境下&#xff0c;QScreen的差异&#xff0c;以及如何判断高DPI设备。 属性说明 logicalDotsPerInch&#xff1…

[HNCTF 2022 Week1]你想学密码吗?

下载附件用记事本打开 把这些代码放在pytho中 # encode utf-8 # python3 # pycryptodemo 3.12.0import Crypto.PublicKey as pk from hashlib import md5 from functools import reducea sum([len(str(i)) for i in pk.__dict__]) funcs list(pk.__dict__.keys()) b reduc…

shell8

until循环(条件为假的时候一直循环和while相反) i0 until [ ! $i -lt 10 ] doecho $i((i)) done分析 初始化变量&#xff1a; i0&#xff1a;将变量i初始化为0。 条件判断 (until 循环)&#xff1a; until [ ! $i -lt 10 ]&#xff1a;这里的逻辑有些复杂。它使用了until循环…

【游戏中orika完成一个Entity的复制及其Entity异步落地的实现】 1.ctrl+shift+a是飞书下的截图 2.落地实现

一、orika工具使用 1)工具类 package com.xinyue.game.utils;import ma.glasnost.orika.MapperFactory; import ma.glasnost.orika.impl.DefaultMapperFactory;/*** author 王广帅* since 2022/2/8 22:37*/ public class XyBeanCopyUtil {private static MapperFactory mappe…

【十进制整数转换为其他进制数——短除形式的贪心算法】

之前写过一篇用贪心算法计算十进制转换二进制的方法&#xff0c;详见&#xff1a;用贪心算法计算十进制数转二进制数&#xff08;整数部分&#xff09;_短除法求二进制-CSDN博客 经过一段时间的研究&#xff0c;本人又发现两个规律&#xff1a; 1、不仅仅十进制整数转二进制可…

【Harmony Next】多个图文配合解释DevEco Studio工程中,如何配置App相关内容,一次解决多个问题?

解决App配置相关问题列表 1、Harmony Next如何配置图标&#xff1f; 2、Harmony Next如何配置App名称&#xff1f; 3、Harmony Next如何配置版本号&#xff1f; 4、Harmony Next如何配置Bundle ID? 5、Harmony Next如何配置build号&#xff1f; 6、Harmony Next多语言配置在哪…

Mybatis分页插件的使用问题记录

项目中配置的分页插件依赖为 <dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.7</version></dependency>之前的项目代码编写分页的方式为&#xff0c;通过传入的条件…

【技术干货】移动SDK安全风险及应对策略

移动SDK&#xff08;软件开发工具包&#xff09;已经成为应用开发中不可或缺的一部分。通过SDK&#xff0c;开发者能够快速集成分析、广告调度、音视频处理、社交功能和用户身份验证等常见功能&#xff0c;而无需从零开始构建。这不仅能节省时间和资源&#xff0c;还能提高开发…

易语言OCR银行卡文字识别

一.引言 文字识别&#xff0c;也称为光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09;&#xff0c;是一种将不同形式的文档&#xff08;如扫描的纸质文档、PDF文件或数字相机拍摄的图片&#xff09;中的文字转换成可编辑和可搜索的数据的技术。随着技…