CUDA Stream 允许主机按队列提交异步工作但“使用了两条 Stream”并不自动意味着正确并发更不等于性能一定提升。数据生产和消费跨越不同 Stream 时必须用 Event 或其他同步机制建立依赖主机读取结果前也必须等待对应工作完成。否则程序可能偶尔得到正确数据却在负载或硬件变化后暴露竞态。TensorRT CSharp API v4.0 4.0.0 的MultiStream示例用两个确定性阶段说明这件事第一阶段在两条非阻塞 Stream 上独立填充并读回两块显存第二阶段由 Stream A 写入数据并记录 EventStream B 等待该 Event 后再读取同一块显存。程序逐字节检查结果只有独立路径和跨流路径都正确才输出MultiStream PassedTrue。本文是 TensorRT CSharp API v4.0 4.0.0 Samples 系列的SMP-007对应源码samples/Performance/01.MultiStream。它是 CUDA 正确性与同步教程不是吞吐性能基准也不创建 TensorRT Engine。1. 前言TensorRT CSharp API v4.0 是一个面向 C#/.NET 开发者的 TensorRT 与 CUDA 工程化接口项目。它把 NVIDIA 原生运行时、生成式绑定、C Bridge、托管对象模型和可验证的示例程序组织成一条完整链路使使用者可以在熟悉的 .NET 项目中完成 Engine 构建、反序列化、ExecutionContext 管理、CUDA 内存操作、异步流同步和结果校验。项目的目标不是隐藏 TensorRT 的概念而是把这些概念转换为有明确生命周期、所有权和错误边界的 C# API。4.0.0 是一次完整重构后的正式版本。核心接口、Bridge 边界、Runtime 包命名、样例目录和验证方式都以 4.x 设计为准不能把 3.x 的类型名、旧包名或旧 DLL 目录直接复制到新项目。托管包只提供项目接口和自有 BridgeTensorRT、CUDA、cuDNN、显卡驱动以及对应许可证仍由使用者按目标平台安装和管理。单篇文章也应能够独立阅读读者可以先从项目入口确认源码和包再根据本文的程序路径准备依赖最后用输出中的状态、计数、Shape、哈希或结果图片判断流程是否真的完成。对于尚未具备兼容 GPU 的环境本文会把静态检查、期望输出和真实运行结果分开标记不把帮助命令或 build-only 结果包装成推理成功。项目、包和源码入口以下地址保留明文便于复制到不完整支持 Markdown 链接的平台项目主页https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp4.0核心 NuGethttps://www.nuget.org/packages/JYPPX.TensorRT.CSharp.API/4.0.0Runtime Bridge 包列表https://www.nuget.org/packages?qJYPPX.TensorRT.CSharpincludeComputedFrameworkstrueprereltruesortbyrelevance运行库清单https://github.com/guojin-yan/TensorRT-CSharp-API/blob/TensorRtSharp4.0/pack/runtime/runtime-packages.manifest.json1.1 程序出处与输出说明本文涉及的程序、脚本或命令均以仓库中的实现为准对应源码入口https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp4.0/samples运行示例必须同时给出程序输出和判定标准。终端中的status、Ready、Bound、enqueueCount、OutputMatch、进程退出码、报告文件或结果图片分别说明不同层次的事实只有明确写出这些结果读者才能区分程序启动、Engine 构建、GPU enqueue 和业务结果语义。1.2 项目简介TensorRT CSharp API v4.0 为 C#/.NET 提供 TensorRT 与 CUDA 的托管接口。除模型推理外库中的CudaStream、CudaEvent、CudaMemory和CudaPinnedMemory可以用于异步数据传输、预处理、后处理以及多请求调度。多流程序最难的通常不是创建对象而是明确“谁生产数据、谁消费数据、依赖在哪条流上记录、主机在何时可以读取、资源最早何时能释放”。本例先用最小的 4096 字节工作负载把这些关系做成可验证输出再把同样的原则映射到 TensorRT 推理场景。1.3 项目链接与包列表项目内容入口项目源码TensorRT-CSharp-API 4.0 分支https://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp4.0本文案例源码samples/Performance/01.MultiStreamhttps://github.com/guojin-yan/TensorRT-CSharp-API/tree/TensorRtSharp4.0/samples/Performance/01.MultiStream程序入口Program.cshttps://github.com/guojin-yan/TensorRT-CSharp-API/blob/TensorRtSharp4.0/samples/Performance/01.MultiStream/Program.cs中文案例说明README.zh-CN.mdhttps://github.com/guojin-yan/TensorRT-CSharp-API/blob/TensorRtSharp4.0/samples/Performance/01.MultiStream/README.zh-CN.md核心 NuGetJYPPX.TensorRT.CSharp.API 4.0.0https://www.nuget.org/packages/JYPPX.TensorRT.CSharp.API/4.0.0Runtime BridgeNuGet 包列表https://www.nuget.org/packages?qJYPPX.TensorRT.CSharpincludeComputedFrameworkstrueprereltruesortbyrelevance所有示例代码通过JYPPX.TensorRT.CSharp.API 4.0.0使用JYPPX.CudaSharp。真实 GPU 运行还需要唯一一个匹配 OS、RID、TensorRT、CUDA 和 cuDNN 组合的 Bridge以及用户安装的 NVIDIA 驱动和 CUDA 运行库。1.4 本文结构本文先解释 Stream/Event 的顺序语义然后分解独立流与跨流等待代码给出安装和运行命令、当前真实结果、与 TensorRT ExecutionContext 的关系以及常见错误。全文只讨论正确性不根据单次运行推导性能收益。2. Stream 和 Event 分别解决什么问题CudaStream表示设备工作队列。同一条 Stream 中的操作按提交顺序执行不同 Stream 之间默认没有全局先后关系。CudaEvent可以记录某条 Stream 到达的时间点另一个 Stream 再等待该 Event从而在设备侧建立依赖。Stream BCUDA EventStream ACStream BCUDA EventStream ACFillAsync(deviceA, 0x33)Event.Record(A)WaitFor(Event)CopyToAsync(host, deviceA)Synchronize()host data is now readable关键点是 Event 记录在生产者 Stream A等待发生在消费者 Stream B。若只在主机上创建 Event却没有Record或让错误的 Stream 等待依赖就没有建立。3. 环境与安装3.1 运行要求组件要求.NET.NET 8 SDK 或更高兼容 SDKGPU/Driver可运行目标 CUDA 组合的 NVIDIA GPU 与驱动CUDA与所选 Bridge 包名中的版本一致托管包JYPPX.TensorRT.CSharp.API4.0.0Bridge与当前 OS/RID/厂商库精确匹配的一个*.Bridge4.0.0ONNX/TensorRT Engine不需要3.2 安装示例以下仍以 Windows x64、TensorRT 10.11、CUDA 12.9、cuDNN 9.22 组合为例dotnet add package JYPPX.TensorRT.CSharp.API--version 4.0.0 dotnet add package JYPPX.TensorRT.CSharp.API.Runtime.win-x64.trt10.11.cuda12.9.cudnn9.22.Bridge--version 4.0.0虽然本例只调用 CUDABridge 包仍按完整发行矩阵命名。不要安装多个 Bridge 来“自动匹配”也不要把另一个 CUDA 版本的 Bridge 与当前进程加载的运行库混用。4. 先检查离线帮助和环境探测dotnet run--project.\samples\Performance\01.MultiStream----help帮助分支不会创建 Stream 或访问 GPU。真实运行首先调用CudaEnvironmentProbe.GetCurrent()CudaEnvironmentSnapshotsnapshot;try{snapshotCudaEnvironmentProbe.GetCurrent();}catch(CudaExceptionexception){Console.WriteLine($MultiStreamSkipped Reason{exception.Message});return0;}Skipped是部署诊断不是测试通过。自动化中应同时检查输出分类不能因为进程返回 0 就把缺少 CUDA 的机器记为成功运行。5. 第一阶段两条独立 Stream5.1 创建资源并限制生命周期usingCudaStreamstreamAnewCudaStream(CudaStreamCreationFlags.NonBlocking);usingCudaStreamstreamBnewCudaStream(CudaStreamCreationFlags.NonBlocking);usingCudaMemorydeviceAnewCudaMemory(4096);usingCudaMemorydeviceBnewCudaMemory(4096);usingCudaPinnedMemoryhostAnewCudaPinnedMemory(4096);usingCudaPinnedMemoryhostBnewCudaPinnedMemory(4096);usingCudaEventeventAnewCudaEvent();usingCudaEventeventBnewCudaEvent();Pinned host memory 适合参与异步拷贝因为运行时不需要临时固定普通托管数组。显存、固定内存、Stream 和 Event 都持有原生资源应在异步工作完成后确定性释放。5.2 独立提交写入和读回deviceA.FillAsync(0x11,4096,streamA);deviceB.FillAsync(0x22,4096,streamB);deviceA.CopyToAsync(hostA,4096,streamA);deviceB.CopyToAsync(hostB,4096,streamB);eventA.Record(streamA);eventB.Record(streamB);eventA.Synchronize();eventB.Synchronize();两组操作分别在自己的 Stream 中保持顺序填充先于拷贝。主机在两个 Event 同步后再读取hostA和hostB逐字节检查是否分别为0x11和0x22。这里验证的是两条独立队列都产生正确结果不证明它们在时间线上有多少重叠。要证明并发或吞吐收益需要更大的工作负载、时间线工具、预热和统计测试。6. 第二阶段跨 Stream Event 等待usingCudaPinnedMemoryorderedHostnewCudaPinnedMemory(4096);usingCudaEventorderingEventnewCudaEvent();deviceA.FillAsync(0x33,4096,streamA);orderingEvent.Record(streamA);streamB.WaitFor(orderingEvent);deviceA.CopyToAsync(orderedHost,4096,streamB);streamB.Synchronize();这段代码包含完整的生产者/消费者关系Stream A 异步把deviceA写成0x33。Event 在 Stream A 中记录表示此前写入已到达同步点。Stream B 等待 Event。Stream B 把deviceA复制到orderedHost。主机同步 Stream B 后再读取结果。若省略第 2 或第 3 步Stream B 可能在 Stream A 写完之前读取若省略第 5 步CPU 可能在异步复制完成前访问固定内存。7. 为什么不用全局同步设备级同步可以粗暴地让所有工作结束但它会扩大等待范围掩盖真正的数据依赖。Event 让消费者只等待所需生产者的某个时间点其他无关 Stream 仍可继续执行。正确的设计原则是同一数据链尽量在同一 Stream 内保持自然顺序跨 Stream 共享数据时用 Event 表达最小依赖只有主机确实要读取或复用资源时才同步生命周期至少覆盖最后一个异步使用者不依赖默认 Stream 的隐式行为来修复错误顺序。8. 编译与运行dotnet restore.\samples\Performance\01.MultiStream\MultiStream.csproj dotnet build.\samples\Performance\01.MultiStream\MultiStream.csproj -c Release--no-restore/p:UseSharedCompilationfalse dotnet run --project.\samples\Performance\01.MultiStream\MultiStream.csproj -c Release--no-build源码仓库验证本地 Bridge 时可以设置$env:JYPPX_NATIVE_BRIDGE_PATH 与本机 ABI 匹配的 Bridge 文件$env:JYPPX_ENABLE_DEVELOPMENT_PROBING 1正式业务项目应通过匹配的 Bridge 包部署并让 NVIDIA 动态库来自明确的目标安装目录。9. 本次真实运行结果2026-08-11 在 Windows、RTX 3060 Laptop GPU、驱动 576.02、CUDA 12.9 对应 Bridge 上重新运行当前源码进程返回 0Bridgejyppxtrtbridge CUDA Toolkit12.9 DeviceCount1 IndependentStreamsTrue ATrue BTrue Bytes4096 CrossStreamWaitTrue ProducerStreamNonBlocking ConsumerStreamNonBlocking StreamIds A13 B14 MultiStream PassedTrue上图是该案例同一 GPU/CUDA 环境中归档的运行截图2026-08-11 已用当前源码再次执行并得到相同的关键结论。Stream ID 是本次进程内的诊断值不应写入长期断言。检查项当前结果能证明什么设备数量1CUDA Runtime 看到了当前 GPUStream A 读回全部0x11A 队列的填充与异步复制顺序正确Stream B 读回全部0x22B 队列的填充与异步复制顺序正确IndependentStreamsTrue两条独立数据路径均通过逐字节检查CrossStreamWaitTrueB 等待 A 的 Event 后读到全部0x33MultiStream PassedTrue两阶段正确性条件同时成立这里没有记录毫秒数也没有声称多流比单流更快。4096 字节工作负载用于可重复的同步验证不适合推导生产吞吐。10. 与 TensorRT 推理的关系TensorRT 的EnqueueAsync同样接收 CUDA Stream。将多请求或预处理/推理/后处理放到多条 Stream 时需要遵守相同规则输入拷贝必须在该次推理消费前完成ExecutionContext、Bindings 和显存不能在 Enqueue 完成前复用或释放一个 ExecutionContext 不应被多个并发执行错误共享跨流传递 tensor 时用 Event 建立显式依赖输出读回前同步对应的完成点而不是依赖偶然时序。本例没有创建 Engine 或 ExecutionContext因此它只证明 CUDA Stream/Event 基础路径不是 TensorRT 多 Context 并发或模型吞吐证明。11. 常见问题11.1 输出MultiStreamSkipped检查 Bridge、进程架构、NVIDIA Driver 和 CUDA Runtime。Skip 表示依赖不可用不能在报告中归类为Passed。11.2IndependentStreamsFalse先分别检查 Fill/Copy 是否使用了同一条预期 Stream、字节数是否一致、Pinned Memory 是否在同步前被访问以及显存是否提前释放或复用。11.3CrossStreamWaitFalse确认 Event 在生产者写入之后记录消费者 Stream 确实调用WaitFor并且主机在检查orderedHost前同步了消费者 Stream。11.4 更换 Stream 后出现随机错误检查是否有资源仍绑定旧 Stream、是否共享了非线程安全的 Context、是否漏掉跨流 Event以及对象是否在异步操作完成前离开using作用域。11.5 多流没有提速并发收益受工作负载大小、拷贝方向、Pinned Memory、GPU 引擎数量、Kernel 占用、同步密度和 Context 设计影响。先用 Nsight Systems 等时间线工具确认是否真正重叠再做预热、多次迭代和统计不要从本例的正确性输出推断性能。12. 结论与证据边界本文用 TensorRT CSharp API v4.0 4.0.0 完成了两条非阻塞 CUDA Stream 的独立读回以及生产者 Event 到消费者 Wait 的跨流顺序验证。当前运行确认 4096 字节的三组固定值全部正确读回。2026-08-11 的结论属于当前 Windows/CUDA 12.9 源码树运行。它不是 TensorRT 推理、模型精度、Linux、公开包消费者或 post-publish proof也不是多流性能基准。本文没有执行 NuGet、GitHub Release 或其他发布操作。13. 延伸阅读SMP-006动态编译并运行 CUDA Kernelhttps://github.com/guojin-yan/TensorRT-CSharp-API/blob/TensorRtSharp4.0/docs/articles/zh-cn/02-samples/smp-006-cuda-runtime-compilation.mdSMP-002推理输入、显存绑定与 GPU 输出读回https://github.com/guojin-yan/TensorRT-CSharp-API/blob/TensorRtSharp4.0/docs/articles/zh-cn/02-samples/smp-002-inference-bindings.mdSMP-003Dynamic Shape 与动态 Batch 推理https://github.com/guojin-yan/TensorRT-CSharp-API/blob/TensorRtSharp4.0/docs/articles/zh-cn/02-samples/smp-003-dynamic-shapes.md托管包与 Bridge 运行时包如何选择https://github.com/guojin-yan/TensorRT-CSharp-API/blob/TensorRtSharp4.0/docs/articles/zh-cn/05-installation/packages/msc-004-managed-and-bridge-package-selection.md14. 文章声明14.1 开源协议声明作者所有开源项目代码均遵循 Apache License 2.0 开源协议。特别说明本项目集成了若干第三方库。若任何第三方库的许可协议与 Apache 2.0 协议存在冲突或不一致均以该第三方库的原始许可协议为准。本项目不包含也不代表这些第三方库的授权声明使用前请务必阅读并遵守第三方库的相关许可。14.2 代码开发与质量说明AI 辅助开发本代码在开发过程中使用了人工智能AI辅助生成与优化并非完全由人工逐行编写。安全性承诺作者郑重声明本代码中绝无任何有意设置的后门、病毒、木马或旨在破坏用户设备、窃取数据的恶意代码。技术局限性受限于作者个人的技术水平与能力代码中可能存在因逻辑不严谨、优化不足或经验欠缺导致的低级问题例如但不限于内存泄漏、偶发崩溃、资源未释放等。这些问题纯属能力不足所致并非主观故意。测试范围由于作者精力有限未对本软件进行全方位、覆盖所有边缘场景的完整测试。14.3 免责声明重要请在将本代码应用于任何实际项目特别是商业、工业或关键任务环境之前务必进行详尽、严格的自行测试与验证。 鉴于上述可能存在的代码缺陷及测试覆盖不足因使用本代码而导致的任何直接或间接损失包括但不限于设备故障、数据丢失、系统瘫痪或利润损失等本作者概不负责。 一旦您开始使用本代码即表示您已知晓上述风险并同意自行承担一切后果相关问题与本作者无关。14.4 代码开源范围本项目承诺核心逻辑代码完全开源但上述提到的“第三方库”的二进制文件、源代码或相关资源不在本项目的开源义务范围内请根据其各自的指引获取。14.5 社区与反馈尽管存在上述不足我们仍欢迎大家下载使用、提交 Issue 或参与测试共同完善项目。如果您在使用过程中发现 Bug、内存溢出或有改进建议欢迎通过项目主页提供的联系方式与作者取得联系我们将尽力在有限的时间内提供协助。