在一台同时跑着 ROS2 机械臂控制的 Jetson Orin Nano Super 上,我把 ResNet18 走完了「FP16 基线 → 蒸馏/剪枝 → QAT 显式量化 → DLA 探测」的端侧部署闭环。最戏剧性的一幕发生在 INT8 阶段:引擎体积如期从 22 MB 砍到 13 MB,吞吐却从 1405 FPS 暴跌到 732 FPS。时间拆解之后发现瓶颈不在 GPU 算力,而在 ARM CPU 的 kernel launch 开销——Q/DQ 节点打碎了原本融合得很漂亮的计算图。最后用 CUDA Graph 把整图调度静态化,吞吐拉到 1664 FPS,反超 FP16。这篇文章复盘整条链路:每一步动了什么、改变了哪个物理量、哪些收益是真的、哪些是打印出来骗自己的。

项目 配置
设备 Jetson Orin Nano Super 开发套件(Board ID p3767-0004)
GPU Ampere 架构,4 SMs,Compute Capability 8.7
CPU 6× Cortex-A78AE(其中 2 核被 ROS2 控制节点长期打满)
内存 4 GB 物理内存,系统可见 3601 MiB,可用约 780 MB,Swap 已用 2 GB
软件 JetPack 6(R36.4.3)/ TensorRT 10.7.0 / PyTorch 2.7 / ONNX opset 13
模型 ResNet18(CIFAR-10 规格,输入 1×3×32×32,输出 10 类)


0. 为什么做这个实验

模型训练完只是故事的一半。Loss 收敛、离线指标达标之后,真正决定它能不能上车、上机器人的,是另一组完全不同的量:latency 的 P99、吞吐、显存峰值、engine 体积,以及它和系统里其他进程抢资源时的表现。这些量在训练框架里全部不可见。

我手头这台 Orin Nano 上常驻着一套 ROS2 机械臂控制系统。我想在这台「有人住」的设备上,把端侧部署的几个核心手段——量化、剪枝、蒸馏、异构分流、运行时调度——各自跑一个最小但真实的闭环。目标不是刷出多漂亮的数字,而是搞清楚每个优化动作的因果链:它到底改变了哪个物理瓶颈,收益从哪里来,代价又藏在哪里。

事后看,这轮实验最值钱的产出,恰恰是一次「负优化」。

1. 实验环境:约束先于方案

动手之前先摸底。tegrastatstrtexec 的 device info 给出的设备画像并不乐观:4 GB 的板子,系统层面只看得到 3.6 GB,扣掉 ROS2 和系统常驻之后可用不到 800 MB,Swap 已经被吃掉 2 GB;6 个 CPU 核里有 2 个被机械臂控制节点长期跑满。

这组数字直接决定了贯穿全程的两条实验纪律。

第一,所有 trtexec 构建一律加 --memPoolSize=workspace:512。TensorRT 在 build 阶段做 tactic 搜索时极其吃内存,在可用内存不足 1 GB 的板子上不加限制,大概率触发系统 OOM killer——而 OOM killer 选谁下手是有随机性的,误杀 ROS2 控制节点的后果比 build 失败严重得多。

第二,编译和训练任务一律 taskset -c 0-3 绑核,把另外两个核物理上留给实时控制回路。trtexec 压测瞬间吃满 6 核导致控制节点消息堆积、机械臂丢步,是绝对不能接受的。

在端侧,你优化的从来不是一台干净的裸机。另外这块板子还埋着一个硬件层面的伏笔,留到第 7 节揭晓。

2. 链路设计:五个阶段

整条实验按「确立标尺 → 结构压缩 → 数值压缩 → 硬件分流 → 统一核算」五个阶段递进:

  1. 确立标尺:FP32 训练、导出 ONNX、构建 FP16 引擎,拿到一组未被任何优化手段污染的基线数据;
  2. 结构压缩:知识蒸馏(ResNet50 → ResNet18)与结构化剪枝,从模型架构层面卸冗余;
  3. 数值压缩:PyTorch QAT → Q/DQ ONNX → TensorRT 显式量化,吃 INT8 的位宽红利;
  4. 硬件分流:探测 DLA 异构卸载的可行性;
  5. 统一核算:把所有配置的吞吐、P99、体积、各段耗时摆进一张表。

需要先讲清楚的是定位:阶段二、三在本轮以**链路打通(PoC)**为目标——验证每条工艺路线从训练侧到 TensorRT 引擎是可走通、可复测的,蒸馏的完整训练和量化的精度闭环单独说明。这种「先打通骨架,再填充血肉」的做法,在后文剪枝部分会再展开为什么它是刻意的。

3. 阶段一:FP16 基线——先解剖,再压测

3.1 构建与体积自检

taskset -c 0-3 trtexec --onnx=models/resnet18_baseline.onnx \
        --fp16 \
        --memPoolSize=workspace:512 \
        --saveEngine=engines/resnet18_baseline_fp16.engine \
        --warmUp=500 --duration=10

31 秒 build 完成,日志里有两个数字值得停一下:

[I] [TRT] Total Weights Memory: 22367252 bytes
[I] [TRT] [MemUsageStats] Peak memory usage of TRT CPU/GPU memory allocators: CPU 4 MiB, GPU 56 MiB
[I] Created engine with size: 21.5244 MiB

ResNet18 约 1170 万参数,FP32 权重约 45 MB,FP16 恰好砍半——21.5 MiB 的引擎体积和 22.4 MB 的权重内存互相印证。体积是 --fp16 真正生效的最直接物证:如果图里大量算子转不动 FP16,体积是降不下来的。同时 build 峰值显存只有 56 MiB,workspace 限制生效,全程对 ROS2 无感。

3.2 打开黑盒:一张被融合到只剩 24 层的图

Engine 是序列化的二进制黑盒,但可以反向解剖:

trtexec --loadEngine=engines/resnet18_baseline_fp16.engine \
        --exportLayerInfo=logs/resnet18_layer_info.json

导出的层信息里,原始 ONNX 上百个节点的 ResNet18,最终在 GPU 上执行的只有 24 个层。摘几行:

"Reformatting CopyNode for Input Tensor 0 to /conv1/Conv + /relu/Relu",
"/conv1/Conv + /relu/Relu",
"/layer1/layer1.0/conv1/Conv + /layer1/layer1.0/relu/Relu",
"/layer1/layer1.0/conv2/Conv + /layer1/layer1.0/Add + /layer1/layer1.0/relu_1/Relu",
...

三个观察:

第一,垂直融合。 Conv + Add + Relu 出现在同一个条目里,意味着残差块尾部这三步被焊成了一个 CUDA kernel。原本的执行方式是:卷积算完写回显存,加法把它读出来算完再写回去,ReLU 再来一轮——三次 HBM 往返。融合之后变成读一次、在片上算完三步、写一次。这类逐元素操作是典型的访存密集型(memory-bound)算子,融合直接打在它的命门上。

第二,BatchNorm 物理消失。 整个层列表里找不到任何 BN 节点。BN 在推理期是个固定的线性变换,TensorRT 在编译阶段把它的缩放和偏置直接折叠进了前面 Conv 的权重(常量折叠),推理时这个算子根本不存在,一微秒都不花。

第三,入口处的 Reformatting CopyNode 输入是常规的 NCHW 排布,但 Ampere 的 Tensor Core 在 FP16 下偏好特定的分块内存格式,TensorRT 自动在入口插了一个重排节点,把数据整理成对计算单元最友好的 layout。

这张 24 层的图,后面会成为关键参照系——INT8 之后它会发生什么。

3.3 基线数字,以及 trtexec 的正确读法

Throughput: 1405.44 qps
Latency:          mean = 0.729 ms, p99 = 0.808 ms
Enqueue Time:     mean = 0.243 ms
GPU Compute Time: mean = 0.708 ms
H2D / D2H:        mean = 0.015 / 0.005 ms

几个指标的判读方法值得单独说。Latency = H2D + GPU Compute + D2H,是单次推理的端到端时延;Enqueue Time 是 CPU 向 GPU 提交命令的耗时,它和 GPU 执行是并行流水的,所以不直接累加进 Latency,但它划定了吞吐的天花板。两条经验规则:Enqueue ≈ GPU Compute 时,系统是 CPU bound,GPU 在等 CPU 发号施令;Latency 远大于 GPU Compute 时,卡在拷贝或同步上

此刻 Enqueue(0.24 ms)小于 GPU Compute(0.71 ms),系统健康,但这个比值已经不算小——记住它,这是后面剧情的伏笔。

日志末尾还有一条 Warning:GPU compute time is unstable, with coefficient of variance = 3.5%。这是 Jetson 默认的 DVFS 动态调频在作怪——负载起伏时 GPU 频率跟着跳。压测 trace 开头从 0.9 ms 慢慢滑到 0.6 ms 的「爬坡」也是同一个原因。正式出报告前应该 sudo jetson_clocks 锁频,本轮为了贴近真实运行状态没有锁,但所有对比实验都在相同条件下完成。

4. 第一次转折:同一个引擎,CUDA Graph 推到 1597 FPS

模型一个字节没动,只在压测命令上加一个 flag:

trtexec --loadEngine=engines/resnet18_baseline_fp16.engine \
        --warmUp=500 --duration=10 --useCudaGraph
FP16 吞吐 P99 GPU Compute Enqueue
默认调度 1405 qps 0.81 ms 0.708 ms 0.243 ms
+ CUDA Graph 1597 qps 0.74 ms 0.621 ms 0.013 ms

Enqueue 从 0.243 ms 被碾到 0.013 ms,缩了近 20 倍。

原理不复杂:默认模式下,CPU 要为图里每一个 kernel 单独发起一次 launch,这块板子的 A78AE 单次 launch 是十微秒量级,24 个 kernel 串下来就是 0.2 ms 以上。CUDA Graph 让 CPU 带着 GPU 先「彩排」一遍,把整条 kernel 序列录制成静态图(日志里能看到 Capturing CUDA graph... Successfully captured),之后每次推理只需一次提交,GPU 自己按图行云流水地跑完。

在 FP16 上这一步只是锦上添花——0.7 ms 的 GPU 计算还兜得住 0.24 ms 的 CPU 调度。但它证明了一件事:在这台机器上,CPU 调度开销和 GPU 算力是同一量级的对手。 这个结论马上会变得性命攸关。

5. 阶段二:蒸馏与「真假剪枝」

5.1 蒸馏:一句话带过流程

结构压缩的第一条路是知识蒸馏:ResNet50 当 Teacher,ResNet18 当 Student,联合损失为

L = α · CE(student, label) + (1-α) · T² · KL(softmax(student/T) ‖ softmax(teacher/T))

温度 T 把 Teacher 的输出分布抹平滑,让 Student 不只学「对错」,还学类别之间的相对关系。本轮验证了单步 forward/backward 链路(loss ≈ 3.18,梯度正常回传),完整的多 epoch 蒸馏训练不在本篇范围。值得说的反而是剪枝——因为那里有一个几乎所有人都会踩的坑。

5.2 剪枝的坑:mask 置零不是剪枝

PyTorch 自带的剪枝接口用起来非常顺手:

prune.ln_structured(model.conv1, name="weight", amount=0.2, n=1, dim=0)
prune.remove(model.conv1, "weight")
print(model.conv1.weight.shape)   # torch.Size([64, 3, 7, 7]) —— 纹丝不动

按 L1-norm 剪掉 20% 的输出通道,打印参数稀疏度也确实降了。但权重张量的物理形状还是 [64, 3, 7, 7]:PyTorch 的 prune 是把被剪通道的数值置零,结构一点没变。把这个模型导出 ONNX 丢给 TensorRT,Tensor Core 不会识别、更不会跳过这些 0,64 个通道的矩阵乘照常做满。参数量是 print 出来给你看的,latency 一微秒都不会动。这就是「假剪枝」:压缩率存在于打印日志里,不存在于 GPU 的执行里。

5.3 真剪枝:结构重建

要在 TensorRT 里换到真实加速,必须做物理层面的结构重建,四步:

  1. 按 L1-norm(或 BN gamma)算出每个输出通道的重要性,确定保留的通道索引;
  2. 物理实例化一个更窄的新 Conv 层(本轮 conv1 从 64 通道收窄到 51);
  3. 把保留通道的权重拷贝进新层;
  4. 同步收窄下游层(以及对应 BN)的输入通道,保证图的拓扑一致,最后导出「窄体 ONNX」。

本轮我只对 conv1 动了刀,总参数仅下降 0.05%——这是刻意的。conv1 只有 64×3×7×7 ≈ 9.4K 参数,占 ResNet18 总参数不到 0.1%,这一步的 KPI 根本不是压缩率,而是验证「选通道 → 重建窄层 → 拷权重 → 导出 → TensorRT 解析 → 输出 shape 与数值一致」整条链路的确定性。链路通了,对 layer3/layer4 这种参数大头做递归剪枝才是收益所在,而且每剪一刀都可复测、可回滚。盲目一上来砍 30% 通道,精度崩了连是哪层的锅都查不出来。

6. 阶段三:QAT 显式量化,与一次教科书级的性能倒挂

6.1 链路,以及一条「成功的 Warning」

数值压缩走的是 QAT(量化感知训练)路线:用 pytorch-quantization 把原生层替换为带 FakeQuant/Observer 的量化层,在校准数据上前向传播算出每个张量的 scale(生产流程中此处还应有若干 epoch 的微调,让权重提前适应量化误差;本轮以导出链路为主),最后导出 ONNX。用 Netron 打开能看到成对的 QuantizeLinear / DequantizeLinear(Q/DQ)节点。

把这个 ONNX 喂给 TensorRT:

taskset -c 0-3 trtexec --onnx=models/resnet18_qat.onnx --int8 \
        --memPoolSize=workspace:512 \
        --saveEngine=engines/resnet18_qat_int8.engine \
        --warmUp=500 --duration=10

日志里跳出一条 Warning:

[W] [TRT] Calibrator won't be used in explicit quantization mode.
    Please insert Quantize/Dequantize layers to indicate which tensors to quantize/dequantize.

这条 Warning 其实是成功宣告。隐式量化(PTQ)路线下,--int8 需要校准器现场统计每层的缩放比例;而 TensorRT 在图里扫到 Q/DQ 节点后,会自动切换到显式量化模式——scale 来自图本身,也就是来自训练/校准阶段,内置校准器直接弃用。量化的控制权从推理引擎收回到了训练侧,这正是 QAT 这条路线的意义。

6.2 体积验收:22 MB → 13 MB

-rw-rw-r-- 22M  resnet18_baseline_fp16.engine
-rw-rw-r-- 13M  resnet18_qat_int8.engine

权重从 16 bit 压到 8 bit,物理体积是骗不了人的第二张验收单。如果 Q/DQ 没插对,TensorRT 会默默回退高精度,体积根本降不下来。

6.3 倒挂现场

然后是这轮实验最重要的一组数据。INT8 引擎(未开 CUDA Graph)的压测结果:

Throughput: 732 qps          ← FP16 是 1405
Latency:          p99 = 1.59 ms
Enqueue Time:     mean = 1.308 ms
GPU Compute Time: mean = 1.343 ms
[W] Throughput may be bound by Enqueue Time rather than GPU Compute
    and the GPU may be under-utilized.

理论上该快一倍的 INT8,吞吐只有 FP16 的一半。但时间拆解把答案直接摆在了桌面上:Enqueue ≈ GPU Compute,两条曲线几乎贴着走——教科书级的 launch bound。trtexec 自己都在 Warning 里给出了诊断和药方。

6.4 归因:Q/DQ 打碎了那张 24 层的图

FP16 时代 Enqueue 只有 0.24 ms,为什么 INT8 翻到 1.31 ms?

回到 3.2 节那张参照图。显式量化的 ONNX 里,量化边界上站满了 DQ → op → Q 的节点对。理想情况下 TensorRT 能把它们和算子融合成原生 INT8 kernel,但 Q/DQ 的插入位置决定融合质量——eager 模式自动插入的量化边界相当保守,残差相加、池化这类节点附近留下了大量无法并入融合的独立 Q/DQ 与 reformat 小算子。FP16 引擎里 24 个 kernel 的图,在 INT8 引擎里碎成了明显更多的独立 kernel(对 INT8 引擎再跑一次 --exportLayerInfo 对比层数即可验证)。kernel 数量上去了,而 ARM 核单次 launch 的成本是固定的,乘起来就是 Enqueue 爆炸。

还有一个更隐蔽的细节:那 1.34 ms 的「GPU Compute Time」并不全是计算。launch bound 状态下,GPU 时间线上 kernel 与 kernel 之间塞满了等待 CPU 下发指令的气泡,而 trtexec 的事件计时从第一个 kernel 起跑覆盖到最后一个 kernel 结束,气泡被一并计入。换句话说,这个数字严重高估了 INT8 的真实计算耗时——证据马上揭晓。

6.5 CUDA Graph 救场

既然瓶颈是 CPU 提交指令太碎,那就把碎指令打包:

taskset -c 0-3 trtexec --loadEngine=engines/resnet18_qat_int8.engine \
        --warmUp=500 --duration=10 --useCudaGraph
Throughput: 1664.15 qps      ← 从 732 翻倍,反超 FP16
Latency:          p99 = 0.81 ms
Enqueue Time:     mean = 0.012 ms
GPU Compute Time: mean = 0.596 ms

Enqueue 被碾到 0.012 ms 之后,「GPU Compute」从 1.34 ms 回落到 0.60 ms——气泡抽干,这才是 INT8 引擎的真实算力耗时。吞吐 1664 FPS,反超 FP16 + CUDA Graph 的 1597。

这里能看清算子融合与 CUDA Graph 的真实关系:融合在编译期减少显存往返,治 memory-bound,是治本;CUDA Graph 在运行期消灭 launch 开销,治 launch-bound,是兜底。 当 Q/DQ 把前者打碎时,后者就是唯一的救场方案。两者不是竞争,是接力。

6.6 两盆必须泼的冷水

第一盆:这一轮 INT8 的算力红利其实很薄。 抽掉气泡之后对比真实 GPU 耗时,FP16 + Graph 是 0.62 ms,INT8 + Graph 是 0.60 ms——对一个输入只有 32×32 的小模型,计算量本来就小,远算不上 compute bound,INT8 的位宽优势吃不满,还要付 Q/DQ 转换的税。这一轮真正赚到的是体积砍半调度修复。换成 640×640 的检测输入、更大的 batch,权重和激活的搬运量上去之后,INT8 的访存与算力收益才会真正放大。

第二盆:trtexec 全程喂随机数,它只测速度,测不了精度。 而 QAT 的全部意义在精度。所以这条链路目前还欠着最后一环:用 TensorRT Python API 加载引擎,喂真实测试集,把 FP16 / PTQ-INT8 / QAT-INT8 三者的 accuracy 摆到同一张表里。速度数字再漂亮,精度没闭环之前都不能叫交付。

7. 阶段四:DLA 探伤——一行报错值十页方案

按教程里的标准动作,下一步应该把卷积主干卸载到 DLA(深度学习加速器),省出 GPU 给其他任务:

trtexec --onnx=models/resnet18_baseline.onnx --useDLACore=0 --fp16
[E] Cannot create DLA engine, 0 not available

不是配置错,也不是驱动问题——查 Board ID(p3767-0004)确认,Orin Nano 这个 SKU 的物理硅片上就没有 DLA 核心。DLA 是 Orin NX 和 AGX Orin 才有的配置。网上大量「Jetson 部署把 CNN 卸载到 DLA」的方案,在这块板子上是伪命题。

这行报错改变了整个实验的策略:彻底放弃异构分流的幻想,把全部火力集中到纯 GPU 路线——也就是前面 QAT + CUDA Graph 的组合。事后看,这反而是这台设备上的最优解。端侧部署的第一原则是尊重物理边界:算力分配方案必须建立在真实的硬件探伤之上,而不是建立在文档和教程之上。

顺带把在带 DLA 的 SKU 上该怎么做记录在案,留给以后补课:先 --allowGPUFallback 构建,翻日志统计哪些层被踢回 GPU——YOLO 系的 Detect Head、Resize、Concat 是常客;如果 fallback 太碎,DLA 与 GPU 之间来回的数据搬运会反噬全部收益,端到端反而比纯 GPU 更慢。这时的解法不是调参,而是图切分:把纯卷积的 backbone 单独导出成 DLA engine,把检测头留在 GPU,让跨设备的边界在整条流水线上只发生一次。

8. 统一核算

配置 Engine 体积 吞吐 P99 延迟 GPU Compute (mean) Enqueue (mean)
FP16 21.5 MB 1405 qps 0.81 ms 0.708 ms 0.243 ms
FP16 + CUDA Graph 21.5 MB 1597 qps 0.74 ms 0.621 ms 0.013 ms
QAT INT8 13 MB 732 qps 1.59 ms 1.343 ms¹ 1.308 ms
QAT INT8 + CUDA Graph 13 MB 1664 qps 0.81 ms 0.596 ms 0.012 ms

¹ 含 launch 气泡,并非真实计算耗时,见 6.4 节。

测试条件:ResNet18,输入 1×3×32×32,batch=1,单推理流,500 ms warmup + 10 s 压测,DVFS 未锁频,trtexec 随机输入。绝对数字只对这台设备、这个输入规格成立;可迁移的不是数字,是方法和因果链。

9. 带得走的六条经验

一、量化不等于加速。 INT8 改变的是数据位宽,但系统的瓶颈未必在位宽上。拿到任何性能数据,先拆 Enqueue 和 GPU Compute:两者接近是 CPU bound,该动调度;GPU Compute 占大头才是 GPU bound,量化和融合才有用武之地。在 ARM CPU 偏弱的端侧设备上,调度开销反噬算力红利是常态而非例外。

二、算子融合与 CUDA Graph 是接力,不是替代。 前者是编译期手段,把多次显存往返压成一次片上计算,对付 memory-bound;后者是运行期手段,把成百上千次 kernel launch 压成一次提交,对付 launch-bound。本轮实验里 Q/DQ 打碎了融合,CUDA Graph 兜住了底——理解两者各自打在哪个物理量上,才知道什么时候该用哪个。

三、剪枝必须物理重建。 mask 置零的「压缩率」存在于 PyTorch 的打印里,不存在于 Tensor Core 的执行里。只有真正删掉通道、收窄上下游、导出窄体 ONNX,TensorRT 才会还你真实的 latency 和体积收益。

四、先查 SKU,再谈架构。 Cannot create DLA engine, 0 not available 这一行报错,否决了所有基于 DLA 的方案设计。任何异构分流、零拷贝、算力规划,第一步都是确认硬件上到底有什么。

五、「精度崩了」是两种病,要分开治。 FP16 溢出是数值越界:RMSNorm 的平方累加、Attention score 这类大数运算超出 65504 的上限,直接产出 NaN,输出彻底崩溃——解法是逐层比对定位敏感层,局部回退 FP32 或插 Cast。INT8 掉点是分辨率截断:离群点把 256 个刻度撑爆,细粒度特征被抹平,模型「变笨」但不报错——解法是换校准策略,不行就上 QAT。排障顺序也由此确定:先把 FP16 跑干净,确认无 NaN,再做 INT8;两种问题混在一起查,只会原地打转。

六、测量是有纪律的。 足够的 warmup、看 P99 而不是 mean、留意 CoV 警告背后的 DVFS 调频(必要时 jetson_clocks 锁频)、记住随机输入永远测不出精度。没有测量纪律,所有「优化收益」都是噪声。

10. 还没做完的事

这条链路目前收在速度侧,几件事排在后面:

精度闭环。 写一个基于 TensorRT Python API 的推理脚本,喂真实测试集,把 FP16 / PTQ-INT8 / QAT-INT8 的 accuracy 和速度放进同一张表,验证 QAT 相对 PTQ 的精度恢复到底值多少。

剪枝推到参数大头。 用第 5 节验证过的重建链路,对 layer3/layer4 做递归通道剪枝加少量微调,复测 latency 与 engine 体积,画出精度-时延的 Pareto 曲线。

DLA 补课。 找一台 Orin NX 或 AGX Orin,把 YOLOv8n 的 fallback 实验和图切分方案真正跑一遍。

数据链路零拷贝。 Orin 是 CPU/GPU 共享物理内存的 iGPU 架构,但默认的 cudaMemcpy 依然是同一块内存条上 A 区到 B 区的真实搬运。接入 camera 输入后,用 pinned memory / unified memory 乃至 NvBuffer(DMA)把这层搬运也省掉,再配合多 CUDA Stream 做拷贝与计算的流水线重叠,和 ROS2 节点一起做共存压测。

最后说一句。这次实验里最值钱的不是 1664 这个数字,而是 732 那次失败——它把一件事钉死在了日志里:优化的对象是物理瓶颈,不是技术名词。 INT8、QAT、剪枝、DLA,每一个词背后都对应着一个具体的物理量;搞清楚你的瓶颈在哪个量上,再决定从工具箱里掏哪件兵器。


附:核心命令速查

# FP16 基线构建(限制 workspace + 绑核,保护共存进程)
taskset -c 0-3 trtexec --onnx=models/resnet18_baseline.onnx --fp16 \
  --memPoolSize=workspace:512 \
  --saveEngine=engines/resnet18_baseline_fp16.engine \
  --warmUp=500 --duration=10

# 压测(CUDA Graph 调度)
trtexec --loadEngine=engines/resnet18_baseline_fp16.engine \
  --warmUp=500 --duration=10 --useCudaGraph

# 解剖融合后的计算图
trtexec --loadEngine=engines/resnet18_baseline_fp16.engine \
  --exportLayerInfo=logs/layer_info.json

# QAT Q/DQ ONNX → 显式 INT8 量化
taskset -c 0-3 trtexec --onnx=models/resnet18_qat.onnx --int8 \
  --memPoolSize=workspace:512 \
  --saveEngine=engines/resnet18_qat_int8.engine \
  --warmUp=500 --duration=10

# DLA 硬件探测(Orin Nano 上预期失败:0 not available)
trtexec --onnx=models/resnet18_baseline.onnx --useDLACore=0 --fp16
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐