)
TensorRT 可编辑 Timing Cache 实战通过修改时序缓存实现确定性引擎构建sampleEditableTimingCache 深度解析【免费下载链接】TensorRTNVIDIA® TensorRT™ is an SDK for high-performance deep learning inference on NVIDIA GPUs. This repository contains the open source components of TensorRT.项目地址: https://gitcode.com/GitHub_Trending/tens/TensorRT本指南以 NVIDIA TensorRT 开源仓库中的sampleEditableTimingCache样例samples/sampleEditableTimingCache/README.md为核心系统讲解如何使用可编辑时序缓存editable timing cache为指定算子强制指定期望的 kernel 策略tactic。读完本文你将掌握 Timing Cache 编辑 APIITimingCache::query/update、kEDITABLE_TIMING_CACHE构建标志的用法以及如何通过解析 profiling 日志来实现不重新耗时 profiling、直接复用并改写缓存的确定性引擎构建流程。背景Tactics、Timing Cache 与可编辑能力的由来在 TensorRT 中同一个网络层往往存在多种底层实现这些候选实现被称为tactics策略。构建引擎时TensorRT 会逐一 profile 所有可用 tactics选出耗时最短的一个写入Timing Cache时序缓存并在最终引擎中使用该 tactic。默认情况下用户只能接受TensorRT 自动选择的结果。但在某些场景下期望使用的 tactic 并非实测最快的那个例如出于数值精度、特定硬件行为或业务合规的考虑此时就需要人为干预把最佳 tactic 替换为另一个候选 tactic。这个需求可以通过编辑时序缓存来满足——这正是本样例演示的核心能力借助Timing Cache 编辑 APIITimingCache::query()/ITimingCache::update()与profiling 日志为指定算子写入或覆盖期望的 tactic 哈希从而让下一次构建直接采用用户指定的实现。从源码结构看该能力由BuilderFlag::kEDITABLE_TIMING_CACHE构建标志驱动见 include/NvInfer.h在可编辑模式下构建日志会输出所有层的 profiling 结果每个算子可用的 tactics 及最终选中的 tactic每一层拥有独立的 tactic 记录修改某一层不会影响其他层。样例总体设计MatMul → Softmax → MatMul本样例构造了一个仅有 3 个节点的极简网络MatMul - Softmax - MatMul。关键在于两个 MatMul 除了名字不同其余属性完全一致。这样一个对称的网络让演示效果非常直观——两个算子共享同一组候选 tactics因此我们可以对第一个 MatMul 换用不同 tactic并清楚地观察到第二个 MatMul 不受影响。整个样例的运行流程分为三步与 samples/sampleEditableTimingCache/README.md 的 Description 完全对应首次构建构造网络并构建第一个引擎。此时BuilderConfig已启用可编辑 timing cacheTensorRT 会在日志中输出 profiling 信息同时把各算子的 tactic 决策记录进 Timing Cache改写缓存从 profiling 结果中为第一个 MatMul 挑选一个与当前选中项不同的 tactic通过缓存编辑 API 写入 Timing Cache再次构建复用被修改过的缓存重新构建第二个引擎。由于缓存已命中TensorRT不再执行 profiling而是直接采用缓存中记录的 tactics。最终效果是除第一个 MatMul 外其余所有层的 tactic 与第一次构建保持一致。网络的具体构造位于 samples/sampleEditableTimingCache/sampleEditableTimingCache.cpp输入input128×128 FP32与权重weight1经addMatrixMultiply得到matMul1接addSoftMax再与weight2相乘得到matMul2并标记为网络输出。三个层分别被命名为matMul1、softmax、matMul2后续日志解析与缓存改写都依赖这些名字。关键 API 与配置项解析1.kEDITABLE_TIMING_CACHE构建标志config-setFlag(BuilderFlag::kEDITABLE_TIMING_CACHE);该标志枚举值 26见 include/NvInfer.h的作用是启用可编辑时序缓存。在可编辑模式下构建日志会包含所有层的 profiling 结果且每层拥有独立的 tactic 记录。配套使用的还有ProfilingVerbosity::kDETAILED见 include/NvInfer.h它要求 TensorRT 在引擎中保存每层实际使用的 tactic 名称kernel name这是后续通过IEngineInspector验证新引擎确实采用了新 tactic的前提config-setProfilingVerbosity(ProfilingVerbosity::kDETAILED);2. Timing Cache 的创建与绑定std::unique_ptrITimingCache timingCache{config-createTimingCache(nullptr, 0)}; config-setTimingCache(*timingCache, true);IBuilderConfig::createTimingCache(blob, size)见 include/NvInfer.h可从序列化数据创建缓存实例传入nullptr, 0表示创建空缓存。创建的缓存不隶属于某个具体IBuilderConfig可被多个 builder 共享setTimingCache(cache, ignoreMismatch)见 include/NvInfer.h将缓存绑定到当前配置ignoreMismatchtrue表示容忍缓存校验头不匹配例如跨设备复用缓存时缓存的生命周期必须长于所有使用它的 builder。3.TimingCacheKey与TimingCacheValue缓存条目的数据模型struct TimingCacheKey { uint8_t data[16]; }; struct TimingCacheValue { uint64_t tacticHash; // 选中的 tactic 哈希 float timingMSec; // 该 tactic 的耗时毫秒负数与 NaN 为非法值 static constexpr uint64_t kINVALID_TACTIC_HASH UINT64_MAX; };定义见 include/NvInfer.h。其中TimingCacheKey有两种表示形式二进制16 字节uint8_t数组与字符串日志中打印的形如0x1814870c44ff0f8574df6e3dda04cbd7的十六进制串。二者的转换规则在头文件中明确给出将二进制 key 中每个字节转换为两个十六进制 ascii 字符如0xab→ab依序拼接所有字节的 ascii 字符得到恰好 32 个字符在字符串前添加前缀0x。日志中出现的key即算子的唯一标识也是 Timing Cache 的查询键tactic hash如0x665ded9abbf88则唯一标识某个 kernel 实现。样例源码中 parseKey() 与 parseTactic() 正是按上述规则把日志中的字符串解析回二进制TimingCacheKey与size_t形式的 tactic 哈希。4.ITimingCache编辑 APIITimingCache见 include/NvInfer.h提供以下核心方法方法作用query(key)查询指定 key 的缓存值key 不存在时返回非法值update(key, value)更新指定 key 的缓存值key 不存在时返回falsequeryKeys(buffer, capacity)查询缓存条目数量并把 keys 写入缓冲区serialize()将当前缓存序列化为IHostMemory便于持久化复用combine(inputCache, ignoreMismatch)合并另一个缓存中的条目冲突条目会被跳过输入缓存必须由完全相同版本的 TensorRT 构建生成否则合并失败reset()清空缓存其中update()的语义值得特别注意见 include/NvInfer.hkey 不存在 → 返回falsekey 存在且新 tactic 耗时为NaN→ 删除该缓存条目并返回truekey 存在、耗时非 NaN 且新值合法 → 覆盖原值并返回true若该层实际上无法使用新写入的 tactic下一次构建引擎时会直接报错——这是把不可用 tactic写入缓存的安全兜底机制。在样例中改写缓存的核心函数为 setTactic()它把解析出的TimingCacheKey与 tactic 哈希组装成TimingCacheValue其中timingMSec被置为1.0F随后调用cache-update(key, value)完成覆盖。这里的时间值本身并不关键——缓存中记录的时间只在缓存 miss、需要比较候选 tactic 时才会参与排序而样例的目标是强制指定因此直接写入一个合法值即可。源码级流程剖析从日志解析到缓存改写第一步开启详细日志并用ProfilingLogger收集信息由于 profiling 信息通过日志输出样例首先把日志级别设为kVERBOSE见 sampleEditableTimingCache.cpp并定义了一个ProfilingLogger类见 sampleEditableTimingCache.cpp作为ILogger的装饰器它把每条日志转发给底层 logger同时从中提取 profiling 信息。由于 profiling 信息分散在多行日志中ProfilingLogger内部实现了一个简单的状态机依次经历四个状态kEXPECT_KEY匹配形如Autotuning op matMul1(key: 0x1814...)的行得到算子名与 keykEXPECT_TACTIC_HEADER匹配表头tactic_id, cost(in ms), cost/fastest_cost, ...kEXPECT_TACTIC逐行匹配候选 tactictactic 哈希 kernel 名称直到遇到不匹配的行kEXPECT_SELECTION匹配The selected tactic is (tactic hash, cost(in ms)):...记录最终选中项后回到初始状态。对应的模式匹配函数定义在namespace patterns中见 sampleEditableTimingCache.cpp包括matchOpKey、matchTacticKernel、matchSelection、matchLayerKernel。注释特别说明由于std::regex在部分平台上的行为不可靠这里全部使用基础字符串接口strstr/sscanf完成匹配这是值得借鉴的工程细节。收集到的信息被组织为ProfilingRecord算子名 key 可用 tactics 列表 选中项与ProfilingTable算子名到记录的映射供后续查找与改写使用。第二步构建第一个引擎并抽取层信息config-setProfilingVerbosity(ProfilingVerbosity::kDETAILED); config-setFlag(BuilderFlag::kEDITABLE_TIMING_CACHE); std::unique_ptrITimingCache timingCache{config-createTimingCache(nullptr, 0)}; config-setTimingCache(*timingCache, true); std::unique_ptrIHostMemory plan{builder-buildSerializedNetwork(*network, *config)};构建完成后样例通过 extractLayerKernels() 使用IEngineInspectorengine-createEngineInspector()getLayerInformation(i, LayerInformationFormat::kONELINE)遍历引擎每一层提取出层名 → kernel 名的映射。这正是kDETAILED的TacticName字段日志中形如TacticName: sm80_xmma_gemm_...发挥作用的地方。随后 findLayer() 通过层名以算子名开头这一前缀规则定位出由第一个 MatMul 派生出的层本样例中为matMul1_myl0_0。第三步选择不同 tactic 并改写缓存ProfilingRecord const opRecord table.at(opName); std::optionalTactic newTactic findDifferentTactic(opRecord); setTactic(timingCache.get(), opRecord.key, newTactic-hash);findDifferentTactic() 在候选 tactics 列表中查找第一个哈希不等于当前选中项的 tactic。本例中matMul1的候选集包含0x665ded9abbf88tilesize 32×32×64、0x393e4ef8ad243tilesize 64×32×64等因此会选中与默认不同的0x393e4ef8ad243对应的 kernelsm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize64x32x64_stage4_warpsize2x1x2_tensor16x8x8。第四步复用缓存重建引擎并验证std::unique_ptrIHostMemory newPlan{builder-buildSerializedNetwork(*network, *config)};第二次构建使用同一个config和同一个timingCache。因为缓存已包含全部算子的 tactic 记录TensorRT 命中缓存、跳过 profiling直接按缓存内容确定各层 tactic。最后样例再次用IEngineInspector抽取新引擎的层信息并通过断言newMatMulLayer-kernel newTactic-kernel见 sampleEditableTimingCache.cpp验证第一个 MatMul 确实切换到了新 kernel。若验证失败样例将输出错误并报告失败。构建与运行编译该样例随 TensorRT OSS 一起编译。构建规则见 samples/sampleEditableTimingCache/CMakeLists.txtadd_executable生成名为sample_editable_timing_cache的可执行文件链接trt_samples_common与TRT_SAMPLES::tensorrt。按仓库 samples/README.md 中列出的方式配置 CMake 并编译后可执行文件将出现在输出目录中。编译完成后./sample_editable_timing_cache预期输出样例会输出大量日志主要包含两类信息README 中的原始示例1. profiling 日志——报告被 profiling 的算子、key、候选 tactics 及最终选中项Autotuning op matMul1(key: 0x1814870c44ff0f8574df6e3dda04cbd7): Sorted table of all evaluated tactics: tactic_id, cost(in ms), cost/fastest_cost, prediction_correlation, kernel_name, tactic_hash, tunable_parameter 3, 0.0112640, 1.00000, 0.50673, sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize32x32x64_stage3_warpsize2x1x2_tensor16x8x8, 0x665ded9abbf88, 5, 0.0118784, 1.05455, 0.51157, sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize64x32x64_stage4_warpsize2x1x2_tensor16x8x8, 0x393e4ef8ad243, 6, 0.0123904, 1.10000, 0.50600, sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize64x32x64_stage5_warpsize2x2x1_tensor16x8x8, 0x2ad3a182fb05c, ... The selected tactic is (tactic hash, cost(in ms)):0x665ded9abbf88, 0.011264 Writing the best tactic (0x665ded9abbf88) to cache每条候选记录依次给出tactic_id、耗时ms、相对最快耗时的比值、预测相关性、kernel 名称与 tactic 哈希。cost/fastest_cost列可直观看出各候选与最快实现的性能差距。2. 引擎层信息日志——报告引擎中各层实际使用的 kernel依赖kDETAILED的TacticName字段Name: matMul1_myl0_0, LayerType: gemm, Inputs: [ { Name: input, Dimensions: [128,128], Format/Datatype: Float }, { Name: weight1, Dimensions: [128,128], Format/Datatype: Float }, { Name: __mye34matMul1_alpha, Dimensions: [1], Format/Datatype: Float }, { Name: __mye35matMul1_beta, Dimensions: [1], Format/Datatype: Float }], Outputs: [ { Name: __myln_k_arg__bb1_4, Dimensions: [128,128], Format/Datatype: Float }], TacticName: sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize32x32x64_stage3_warpsize2x1x2_tensor16x8x8, StreamId: 0, Metadata:结果验证两次引擎的层对比由于上述日志可读性一般样例在结尾打印了精简汇总版本。第一次引擎与第二次引擎的对比如下Layers of the first engine: #0: matMul1_myl0_0 uses sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize32x32x64_stage3_warpsize2x1x2_tensor16x8x8 #1: __myl_TraMaxSubExpSum_myl0_1 uses __myl_TraMaxSubExpSum_0xcbcb71f14cb4526fd18f61134658c571 #2: __myl_DivMul_myl0_2 uses __myl_DivMul_0x80125aec9f1e9979e47ef2b407811651 #3: matMul2_myl0_3 uses sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize32x32x64_stage3_warpsize2x1x2_tensor16x8x8可见第一次构建中matMul1_myl0_0使用了默认最快的 tilesize 32×32×64 kernelsoftmax 被拆分为TraMaxSubExpSum与DivMul两个内部实现第二个 MatMul 与第一个使用相同 kernel。改写缓存后第二次引擎的层信息变为Layers of the second engine: #0: matMul1_myl0_0 uses sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize64x32x64_stage4_warpsize2x1x2_tensor16x8x8 #1: __myl_TraMaxSubExpSum_myl0_1 uses __myl_TraMaxSubExpSum_0xcbcb71f14cb4526fd18f61134658c571 #2: __myl_DivMul_myl0_2 uses __myl_DivMul_0x80125aec9f1e9979e47ef2b407811651 #3: matMul2_myl0_3 uses sm80_xmma_gemm_f32f32_tf32f32_f32_nn_n_tilesize32x32x64_stage3_warpsize2x1x2_tensor16x8x8对比结论一目了然只有#0第一个 MatMul切换到了 tilesize 64×32×64 的新 kernel其余所有层包括第二个 MatMul的 tactic 保持不变——这正是通过编辑缓存实现确定性构建的预期效果。若样例运行成功日志末尾将出现 PASSED TensorRT.sample_editable_timing_cache [TensorRT v100800] [b18] # sample_editable_timing_cachePASSED行中的版本号会随当前 TensorRT 版本变化。设计要点与工程细节1. 为什么强类型网络样例在创建网络时使用了kSTRONGLY_TYPED标志见 sampleEditableTimingCache.cpp即 include/NvInfer.h 对应的强类型 APIREADME 的 Changelog 也注明 2025 年 10 月已迁移到 strongly typed APIs。强类型网络下层的输入输出格式在构建前即被确定这保证了两次构建之间网络结构完全等价也使得除目标层外其余层 tactic 不变的对比结论干净可信。2. 时间值只是占位符setTactic()中value.timingMSec 1.0F并非实际测量值。由于缓存命中时 TensorRT 直接信任缓存中的 tactic 决策、不再比较耗时写入什么时间值都不影响最终选择写入一个合法非负、非 NaN的浮点数即可通过update()的合法性校验。3. 错误处理与断言样例定义了FAIL_IF_NOT宏见 sampleEditableTimingCache.cpp对 builder/runtime 创建失败、缓存设置失败、未找到目标层、找不到其他候选 tactic、新引擎未采用新 kernel 等所有关键步骤逐一校验任何一个环节异常都会以sample::gLogger.reportFail报告失败配合 samples/common/logger.h 统一日志体系运行结果非常明确。适用场景与注意事项确定性构建当你不希望 TensorRT 因环境波动而改变 tactic 选择时可先构建一次生成缓存再在后续构建中复用该缓存获得层与 kernel 完全一致的引擎本样例则更进一步演示了如何在复用前定向改写某一层的 tactic。强制指定 kernelupdate()的语义保证——若写入的 tactic 对该层不可用下一次构建会直接报错因此改写动作本身是安全的错误会在构建阶段暴露。缓存跨版本/跨设备限制combine()要求输入缓存由完全相同版本的 TensorRT 生成否则合并被跳过并返回false合并不同设备属性生成的缓存可能引发功能或性能问题见 include/NvInfer.h跨设备合并需显式设置ignoreMismatchtrue。弱类型网络的变异性头文件明确警告见 include/NvInfer.h若使用弱类型非强类型网络即使复用同一缓存多次构建生成的引擎在 tactics 与格式上仍可能不同——这正是本样例采用强类型网络的原因之一。缓存未命中时的行为BuilderFlag::kERROR_ON_TIMING_CACHE_MISS见 include/NvInfer.h可让 TensorRT 在某个被计时的 tactic 不在缓存中时报错该标志仅在IBuilderConfig关联了ITimingCache时生效可用于严格校验缓存完整性。README 的 Known issues 一节目前为空本文未发现与该样例相关的已登记问题具体行为仍以当前仓库中的源码与头文件注释为准。本样例日志中的 kernel 名称如sm80_xmma_gemm_...带有架构前缀说明 tactic 集合与具体 GPU 架构如 sm80/sm86/sm90相关换卡构建时缓存可能 miss 并触发重新 profiling。总结sampleEditableTimingCache用不到 200 行核心逻辑完整演示了 TensorRT profiling → 编辑缓存 → 确定性重建 的闭环通过kEDITABLE_TIMING_CACHE让构建日志携带 profiling 信息借助ProfilingLogger状态机从日志中提取算子 key 与候选 tactics再通过ITimingCache::update()将期望的 tactic 写入缓存最后复用缓存构建出仅目标层发生变化的引擎并用IEngineInspector完成结果验证。这套方法为需要精细化控制 kernel 选择的用户如追求特定数值行为或复现特定性能表现提供了标准化的官方路径。如需进一步探索相关主题可以继续阅读仓库中的 samples/README.md样例总览与 include/NvInfer.h 中IBuilderConfig、ITimingCache的完整接口注释以及 samples/sampleEngines通用引擎构建样例、samples/sampleDynamicReshape动态形状构建等同属于构建流程控制主题的样例。【免费下载链接】TensorRTNVIDIA® TensorRT™ is an SDK for high-performance deep learning inference on NVIDIA GPUs. This repository contains the open source components of TensorRT.项目地址: https://gitcode.com/GitHub_Trending/tens/TensorRT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考