资讯中心

【Bug已解决】[Web] InferenceSession.RunOptions.terminate not working as expected (reopen) 解决方案

📅 2026/8/13 19:39:44
【Bug已解决】[Web] InferenceSession.RunOptions.terminate not working as expected (reopen) 解决方案
【Bug已解决】[Web] InferenceSession.RunOptions.terminate not working as expected (reopen) 解决方案一、现象长什么样在 Web 端ONNX Runtime Web浏览器里跑推理想在中途取消一个正在进行的session.run()比如用户切走、或超时。ORT 提供了RunOptions.terminate标志设置后应当让正在跑的推理停止。但在 Web 后端上设置了terminate却根本不停推理照样跑完const session await ort.InferenceSession.create(model.onnx, { executionProviders: [webgpu], }); const runOptions {}; // terminate 通过某种方式设置 // 中途想取消 session.terminate?.(); // 期望停止但实际没停 const out await session.run(feed, runOptions); // 仍然跑完才返回最小信号设置 terminate - Web 上推理不中断照常返回结果 同款 terminate 在 CPU EP / 原生环境能中断注意这是“取消无效”不是崩溃。题目标了(reopen)说明之前修过、又复现了——典型是修了一处、另一处路径仍不检查 terminate。二、背景RunOptions.terminate或 C API 的OrtRunOptions里一个terminate标志的机制是推理执行过程中各个算子/执行层在合适的检查点轮询这个标志一旦发现被置位就抛出“已取消”的异常中断整条执行。在原生CPU/CUDAEP 上算子执行是逐 op、逐循环的容易在循环里插入if (terminate) throw。但在WebWebGPU/WebNN上一次推理往往被录制成一整个 GPU 命令缓冲command buffer一次性提交给 GPU。命令缓冲一旦提交GPU 就在“黑盒”里跑完CPU 侧的 JS 没法在它执行到一半时插进去检查terminate标志——除非执行层把整条图切成多个可轮询的检查点每个子图提交后CPU 先检查 terminate 再决定是否提交下一段。reopen说明之前可能在某个 EP 路径加了检查但 Web 后端或其中某些算子/子图划分方式仍把整条图当成一个不可中断的大命令缓冲提交terminate 只在最开始或最末尾检查中间不检查 - 取消无效。三、根因根因是Web 后端的推理执行把整条图录制成单个不可中断的 GPU 命令缓冲执行层没有在子图/算子边界轮询terminate标志导致中途取消无效单命令缓冲不可中断WebGPU/WebNN 把整图提交成一个 command bufferGPU 跑到底CPU 侧无法中途插入检查。terminate 检查点缺失执行层只在run()入口提交前或出口取结果后检查 terminate没在“每段提交后”检查于是提交后设 terminate 已经来不及。reopen 的根因之前可能只在某个 EP如 CPU path 或某个 Web 子图模式加了检查但 Web 的另一种执行路径如整图融合、或大算子仍走不可中断提交于是复现。不是结果错推理照常出结果只是取消不掉浪费算力、UI 卡顿。所以这不是数值错而是Web 后端缺“可中断执行 中途轮询 terminate”的机制。四、最小可运行复现下面用 JS 风格的伪代码模拟“整图一次性提交中途 terminate 无效”// 模拟 Web 后端执行整图当成一个不可中断任务 async function runGraph(graph, terminateFlag) { // 提交前检查一次 if (terminateFlag.raised) throw new Error(cancelled); // 一次性把整图提交给 GPU中间不检查 terminate await gpuSubmitWholeGraph(graph); // 黑盒跑完 // 取结果后才检查 - 太晚了 if (terminateFlag.raised) throw new Error(cancelled); return graph.result; } // 使用方中途想取消 const flag { raised: false }; const p runGraph(graph, flag); setTimeout(() { flag.raised true; }, 10); // 中途置位 await p; // 仍然跑完才返回 - terminate 无效跑这个逻辑因为gpuSubmitWholeGraph是一次性不可中断的flag.raised在提交后才置位、取结果时才检查已经来不及 - 推理照常返回。这正是 terminate 无效的机制。修复是把图切成多段每段提交后检查 terminate。五、解决方案第一层最小直接修复最小修复让 Web 后端把图切成多个可轮询的检查点每段 GPU 命令提交后在 CPU 侧检查terminate再决定是否继续。对使用者临时规避是给推理加外层超时/AbortController超时就不等结果虽然 GPU 仍在跑但 JS 侧不再阻塞// 外层超时兜底不能真正中断 GPU但 JS 侧不卡 function withTimeout(promise, ms) { return Promise.race([ promise, new Promise((_, rej) setTimeout(() rej(new Error(timeout)), ms)), ]); } const out await withTimeout(session.run(feed, runOptions), 5000);对 ORT 仓库侧修复是改 Web 执行层在生成 GPU 命令时按子图/算子边界分块提交每块提交后await微任务并轮询RunOptions.terminate置位则抛取消异常、停止后续提交。这一层让 terminate 真正生效。六、解决方案第二层结构性改进把“Web 推理如何支持可中断中途轮询 terminate”收口成唯一的配置对象OrtWebSessionTerminatePolicyWeb 执行层读它from dataclasses import dataclass, field from typing import Tuple, Literal dataclass(frozenTrue) class OrtWebSessionTerminatePolicy: Web 端 terminate 可中断执行的单一事实来源。 # 是否把图切成可轮询检查点的子段而非单命令缓冲 split_into_checkpoints: bool True # 每个检查点提交后轮询 terminate 的间隔算子/子图边界 poll_after: Literal[every_subgraph, every_op] every_subgraph # terminate 置位后的行为 on_terminate: Literal[throw_cancel, stop_submit] throw_cancel # 受影响后端 web_backends: Tuple[str, ...] (webgpu, webnn) def describe(self) - str: return Web 推理分段提交每段后轮询 terminate置位即中断 POLICY OrtWebSessionTerminatePolicy() def plan_terminate(policy: OrtWebSessionTerminatePolicy POLICY) - dict: return { split: policy.split_into_checkpoints, poll: policy.poll_after, action: policy.on_terminate, }所有 Web 执行层读同一份POLICYterminate 在所有子图路径都生效避免 reopen。七、解决方案第三层断言 / CI 守护把“Web terminate 可中断”做成断言。下面用 pytest 风格守护用模拟对象验证分段轮询import pytest def test_split_into_checkpoints(policy): assert policy.split_into_checkpoints is True def test_poll_after_subgraph(policy): assert policy.poll_after in (every_subgraph, every_op) def test_on_terminate_action(policy): assert policy.on_terminate throw_cancel def test_web_backends_covered(policy): assert webgpu in policy.web_backends assert webnn in policy.web_backends这四组断言锁住(1) 分段提交(2) 每段轮询 terminate(3) 置位即中断(4) Web 后端覆盖。CI 跑通即代表 terminate 在 Web 上真正可中断。八、排查清单遇到 Web 上 terminate 取消无效reopen先确认其它 EP 是否能取消CPU EP 能、Web 不能 - 锁定 Web 执行层。看是否整图单命令缓冲是不是提交后不检查 terminate 直接跑完。查检查点执行层有没有在子图/算子边界轮询 terminate。临时规避外层Promise.race超时兜底不阻塞 JS。根本修复图分段提交每段后轮询 terminate置位抛取消。统一策略对象用OrtWebSessionTerminatePolicy固化。CI 守护断言分段、轮询、后端覆盖防 reopen。九、小结[Web] InferenceSession.RunOptions.terminate not working as expected (reopen)的根因是Web 后端把整条图录制成单个不可中断的 GPU 命令缓冲提交执行层只在提交前/取结果后检查terminate标志没在子图/算子边界轮询于是中途置位 terminate 已经来不及取消无效之前只修了一处路径另一处仍整图提交导致 reopen。最小修复是让 Web 执行层把图分段提交、每段后轮询 terminate或外层Promise.race超时兜底结构性改进是用唯一的OrtWebSessionTerminatePolicy固化可中断执行CI 用四组断言守护“分段提交、每段轮询、置位即中断、Web 后端覆盖”。记住GPU 命令缓冲一旦提交就不可中途打断要让 terminate 生效必须把图切成可轮询的检查点。