当前位置: 首页 > news >正文

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

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

  • 模型部署学习笔记——模型部署关键知识点总结
    • 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需要学习,这些就在之后工作上具体遇到实际问题时再进一步了解吧

相关文章:

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

模型部署学习笔记——模型部署关键知识点总结 模型部署学习笔记——模型部署关键知识点总结1. CUDA中Grid和Block的定义是什么&#xff1f;Shared Memory的定义&#xff1f;Bank Conflict的定义&#xff1f;Stream和Event的定义&#xff1f;2. TensorRT的工作流程&#xff1f;3…...

22智能 狄克斯特拉算法复习

狄克斯特拉算法 图 根据边有无方向分为&#xff1a; 有向图、无向图 根据边有无权重变量分为&#xff1a; 有权图、无权图 根据顶点是否连通分为&#xff1a; 连通图和非连通图入度&#xff1a;表示有多少条边指向该顶点出度&#xff1a;表示有多少条边从该顶点指出算法步骤&a…...

首个!艾灵参编的工业边缘计算国家标准正式发布

近日&#xff0c;艾灵参与编制的《面向工业应用的边缘计算 应用指南》&#xff08;以下简称《标准》&#xff09;国家标准正式发布&#xff0c;将于2025年5月1日起实施。这一里程碑式的成果&#xff0c;不仅标志着我国在工业边缘计算技术标准化领域取得了重大突破&#xff0c;成…...

curl也支持断点续传

curl断点续传 访问外网资源&#xff0c;特别是Github上比较大的资源&#xff0c;例如&#xff0c;笔者遇到的calico发布包&#xff0c;经常会遇到在浏览器上下载半途中断。 那么支持断点续传的下载工具&#xff0c;就是应对这种情况的好帮手&#xff01; 简单的断点续传工具…...

交换机链路聚合(手动负载分担模式)(eNSP)

目录 交换机SW_C配置: 交换机-PC划分vlan: 交换机-交换机端口聚合: 交换机SW_D配置: 交换机-PC划分vlan: 交换机-交换机端口聚合: 验证: 链路聚合的端口清除: 交换机端口聚合的存在意义主要有以下几点: 增加带宽 提高冗余性和可靠性 实现负载均衡 降低成本 …...

jmeter 接口性能测试 学习笔记

目录 说明工具准备工具配置jmeter 界面汉化配置汉化步骤汉化结果图 案例1&#xff1a;测试接口接口准备线程组添加线程组配置线程组值线程数&#xff08;Number of Threads&#xff09;Ramp-Up 时间&#xff08;Ramp-Up Period&#xff09;循环次数&#xff08;Loop Count&…...

`HashMap`、`Hashtable` 和 `HashSet`的区别

HashMap、Hashtable 和 HashSet 都是 Java 中常用的集合类&#xff0c;它们的功能和实现有所不同&#xff0c;尽管它们都使用哈希表&#xff08;hash table&#xff09;作为底层数据结构。以下是它们之间的主要区别&#xff1a; 1. HashMap 和 Hashtable 的区别 特性HashMapH…...

Arduino中解析JSON数据

JSON JSON&#xff08;JavaScript Object Notation&#xff0c;即JavaScript对象表示法&#xff09;是一种广泛采用的开放标准文件格式与数据交换格式。它兼具人类可读性和机器易解析性&#xff0c;使得数据的编写、阅读、生成及解析都变得十分便捷。JSON的设计不依赖于特定编…...

linux----文件访问(c语言)

linux文件访问相关函数 打开文件函数 - open 函数原型&#xff1a;int open(const char *pathname, int flags, mode_t mode);参数说明&#xff1a; pathname&#xff1a;这是要打开的文件的路径名&#xff0c;可以是绝对路径或者相对路径。例如&#xff0c;"/home/user/…...

源码分析之Openlayers中MousePosition鼠标位置控件

概述 本文主要介绍 Openlayers 中的MousePosition鼠标位置控件&#xff0c;该控件会创建一个元素在页面的右上方用来实时显示鼠标光标的位置坐标。该控件在实际应用很有效&#xff0c;可以实时获取鼠标位置&#xff0c;但是一般控件元素都会自定义。 源码分析 MousePosition…...

以ATTCK为例构建网络安全知识图

ATT&CK&#xff08;Adversarial Tactics, Techniques, and Common Knowledge &#xff09;是一个攻击行为知识库和模型&#xff0c;主要应用于评估攻防能力覆盖、APT情报分析、威胁狩猎及攻击模拟等领域。本文简单介绍ATT&CK相关的背景概念&#xff0c;并探讨通过ATT&a…...

myexcel的使用

参考&#xff1a; &#xff08;1&#xff09;api文档&#xff1a;https://www.bookstack.cn/read/MyExcel-2.x/624d8ce73162300b.md &#xff08;2&#xff09;源代码&#xff1a; https://github.com/liaochong/myexcel/issues 我&#xff1a; &#xff08;1&#xff09;m…...

Unity 上好用的插件

PlayerMaker BehaviorDesigner Cinemachine Timeline Hybrid Addressable AssetBundle Blower Simple Zoom 大地图上缩放和平移使用ScrollRect的好效果实现...

Vivado - 远程调试 + 远程综合实现 + vmWare网络配置 + NFS 文件共享 + 使用 VIO 核

目录 1. 简介 2. VIO 配置 2.1 VIO IP 2.2 VIO 对比 ILA 3. VIO 示例 3.1 Led 3.1.1 工程配置 3.1.2 效果展示 3.2 Key 3.2.1 工程配置 3.2.1 效果展示 3.3 门控触发 3.3.1 工程配置 3.3.2 效果展示 4. 远程调试 4.1 配置目标主机 4.2 配置本机 4.3 vmWare 网…...

双臂机器人

目录 一、双臂机器人简介 二、双臂机器人系统的组成 三、双臂机器人面临的主要挑战 3.1 协调与协同控制问题 3.2 力控制与柔顺性问题 3.3 路径规划与轨迹优化问题 3.4 感知与环境交互 3.5 人机协作问题 3.6 能源与效率问题 3.7 稳定性与可靠性问题 四、双臂机器人…...

【Lua热更新】上篇

Lua 热更新 - 上篇 下篇链接&#xff1a;【Lua热更新】下篇 文章目录 Lua 热更新 - 上篇一、AssetBundle1.理论2. AB包资源加载 二、Lua 语法1. 简单数据类型2.字符串操作3.运算符4.条件分支语句5.循环语句6.函数7. table数组8.迭代器遍历9.复杂数据类型 - 表9.1字典9.2类9.3…...

Ubuntu批量修改文件名

文章目录 批量重命名文件&#xff1a;Ubuntu下使用find命令结合sed和mv参考 批量重命名文件&#xff1a;Ubuntu下使用find命令结合sed和mv 在日常开发和文件管理中&#xff0c;有时我们需要批量重命名一批文件&#xff0c;比如将文件名中的某个特定字符串替换为另一个字符串。…...

食家巷大烤馍:岁月沉淀下的麦香传奇

在繁华都市的街角巷尾&#xff0c;隐藏着许多不为人知的美食宝藏&#xff0c;食家巷大烤馍便是其中之一。它宛如一位低调的美食大师&#xff0c;默默散发着独特的魅力&#xff0c;用最质朴的味道&#xff0c;征服着每一个过往食客的味蕾。 初见食家巷大烤馍&#xff0c;你会被…...

harmony UI组件学习(1)

Image 图片组件 string格式&#xff0c;通常用来加载网络图片&#xff0c;需要申请网络访问权限:ohos.permission.INTERNET Image(https://xxx.png) PixelMap格式&#xff0c;可以加载像素图&#xff0c;常用在图片编辑中 Image(pixelMapobject) Resource格式&#xff0c;加…...

BTP Integration Suite CPI Apache Camel

官网文档&#xff1a; https://help.sap.com/docs/integration-suite/sap-integration-suite/what-is-sap-integration-suite CPI 云集成(CPI)有以下几个特性&#xff1a; SAP Cloud Integration通过消息交换支持端到端流程集成。 它基于Apache软件基金会的开源框架Camel。 …...

vitepress-打包SyntaxError: Element is missing end tag.

一、vitepress打包编译报错Element is missing end tag. 背景&#xff1a; 新增了一些笔记准备上传到git仓库&#xff0c;持续集成部署的时候&#xff0c;控制台报错了&#xff0c;错误信息如下&#xff1a; SyntaxError: Element is missing end tag. 仔细看了下控制台几乎没啥…...

【从零开始入门unity游戏开发之——C#篇21】C#面向对象的封装——`this`扩展方法、运算符重载、内部类、`partial` 定义分部类

文章目录 一、this扩展方法1、扩展方法的基本语法2、使用扩展方法3、扩展方法的注意事项5、扩展方法的限制6、总结 二、运算符重载1、C# 运算符重载2、运算符重载的基本语法3. 示例&#xff1a;重载加法运算符 ()4、使用重载的运算符5、支持重载的运算符6、不能重载的运算符7、…...

Java进程占用的内存有哪些部分?

大家好&#xff0c;我是锋哥。今天分享关于【Java进程占用的内存有哪些部分?】面试题。希望对大家有帮助&#xff1b; Java进程占用的内存有哪些部分? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Java进程在运行时&#xff0c;会将内存划分为多个区域&#xf…...

【华为OD机试真题】【2024年E卷】数值同化-队列BFS(C++/Java/Python)

文章目录 分值&#xff1a;200题目描述思路复杂度分析AC 代码 分值&#xff1a;200 题目描述 存在一个 m * n 的 二维数组只&#xff0c;其成员取值范围为0, 1, 2。其中值为1的元素具备同化特性&#xff0c;每经过1S&#xff0c;将上下左右值为0的元素同化为1。而值为2的元素…...

“魔法糖果盒的秘密:用朴素贝叶斯算法猜糖果颜色”

想象一下&#xff0c;你有一个神奇的糖果盒&#xff0c;这个糖果盒里有两种糖果&#xff1a;红色的和蓝色的。你闭上眼睛&#xff0c;从盒子里拿出一个糖果&#xff0c;然后尝一尝&#xff0c;你想知道这个糖果是红色的还是蓝色的。朴素贝叶斯算法就像是一个魔法规则&#xff0…...

linux中docker命令大全

基本命令 docker pull 拉取镜像 docker pull docker push 推送镜像到DockerRegistry docker push docker images 查看本地镜像 docker images docker rmi 删除本地镜像 docker rmi docker run 创建并运行容器&#xff08;不能重复创建&#xff09; docker run d…...

Python `str.strip()` 的高级用法详解

Python str.strip 的高级用法详解 1. str.strip() 的基本用法2. str.strip() 的高级用法2.1 移除指定字符2.2 移除多个指定字符2.3 移除换行符和制表符2.4 结合正则表达式的高级处理 3. lstrip() 和 rstrip() 的用法3.1 lstrip()&#xff1a;移除左端字符3.2 rstrip()&#xff…...

[蓝桥杯 2019 国 B] 排列数

目录 前言 题解 思路 疑问 解答 前言 对于本篇文章是站在别人的基础之上来写的&#xff0c;对于这道题作为2019年国赛B组的最难的一题&#xff0c;他的难度肯定是不小的&#xff0c;这道题我再一开始接触的时候连思路都没有&#xff0c;也是看了两三遍别人发的题解&#x…...

[bug] StarRocks borker load意向之外的bug

意向之外&#xff0c;又清理之中 背景&#xff1a; StarRocks各方面碾压相同类型的数据库&#xff0c;最近我们要从生成HIVE导历史数据&#xff08;ORC格式&#xff09;到StarRocks&#xff0c;前期小测一下&#xff0c;在测试是没问题&#xff0c;上生产先导2个月的数据&…...

2025年前端面试热门题目——HTML|CSS|Javascript|TS知识

以下是对这些 HTML 面试问题的详细解答&#xff1a; 1. HTML 的 src 和 href 属性有什么区别? src (Source) 属性&#xff1a; 用于嵌入资源&#xff0c;例如图像、脚本或 iframe。加载资源时&#xff0c;当前页面的加载会暂停&#xff0c;直到资源加载完成。常用于 <img&g…...