ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

【Bug已解决】Compile Grounding DINO 解决方案

【Bug已解决】Compile Grounding DINO 解决方案 【Bug已解决】Compile Grounding DINO 解决方案一、现象长什么样Grounding DINO 是开放词汇目标检测模型用文本描述定位物体。你想用torch.compile加速它但编译失败或编译后出错# 现象 Atorch.compile 报动态形状不支持 torch._dynamo.exc.Unsupported: Dynamic shape operation ... in GroundingDINO # 模型里文本 query 数量随输入描述长度变化动态compile 拒绝 # 现象 B编译成功但推理结果错控制流被 graph break # Grounding DINO 里有 Python if/for 控制流按文本 token 数循环 # Dynamo 触发 graph break部分走 eager结果数值偏移 # 现象 C重复编译recompile卡顿 # 每换一个不同长度的描述就重编译一次比不编译还慢 # 典型触发 import torch model GroundingDinoForObjectDetection.from_pretrained(grounding-dino-base) compiled torch.compile(model, modereduce-overhead) out compiled(images, input_idstext_input) # 报动态形状或 graph break最典型的指纹Grounding DINO 因为文本 query 数量动态变化和按 token 数的控制流与 torch.compile 的静态图假设冲突要么编译失败、要么结果错、要么反复重编译。二、背景Grounding DINO 的特殊之处它是以文搜图的检测器。输入除了图像还有一段文本描述如 cat . dog . chair文本被 tokenize 后变成一组text queries数量 描述的 token 数或按 . 分割的短语数。这些 query 数量随输入文本变化——这是它的核心特性。而torch.compileDynamo Inductor默认假设计算图形状尽量静态对形状随输入变的张量如text_queries的长度维敏感。具体冲突text query 数动态→ 编译生成的 kernel 假设固定 query 数换个描述长度就形状失配 → 重编译或报错。按 query 数的循环/条件→ 若用 Pythonfor遍历 query而非向量化Dynamo 会 graph break部分图走 eager数值可能与全图不一致。变长输出检测结果数→ 后处理NMS输出长度不定compile 难处理。三、根因根因有三类text query 维度是动态形状compile 未设上限。text_queries的形状(batch, num_queries, dim)中num_queries随文本变。Dynamo 在无dynamicFalse或没tensor_dim提示时会对每个新长度重编译 → 卡顿/报错。Python 控制流绕过了编译图graph break。 模型里若有for q in range(num_queries): ...Python 级循环Dynamo 不能把它编进图发生 graph break。graph break 后那段走 eager与编译段拼接若两段对同一个张量的处理假设不同如 one 用编译的、one 用 eager 的 dtype 路径结果可能错。后处理NMS输出长度动态无法静态化。 检测的框数是变量compile 对输出长度随输入变的函数如torch.nonzero后 gather支持差常 graph break。四、最小可运行复现下面用纯 Python 模拟动态 query 数导致 compile 反复重编译from typing import List, Dict COMPILED_SHAPES [] def compile_and_run(num_queries: int, use_cache: bool True): 模拟 torch.compile按第一次见到的 query 数编译不同则重编译。 if use_cache: if num_queries not in COMPILED_SHAPES: COMPILED_SHAPES.append(num_queries) return fcompiled for q{num_queries} return freused q{num_queries} return eager # 不同描述长度 - 不同 query 数 - 反复重编译 for nq in [5, 8, 5, 12, 8]: print(compile_and_run(nq)) # 修正给 query 数一个固定上限pad 到 max只编译一次 MAX_Q 16 COMPILED_SHAPES.clear() for nq in [5, 8, 5, 12, 8]: padded min(nq, MAX_Q) # 固定上限形状恒定 print(compile_and_run(padded)) # 只编译一次 MAX_Q print(编译过的形状集合:, COMPILED_SHAPES) assert COMPILED_SHAPES [16], 复现失败固定上限应只编译一次运行后动态 query 数导致每种长度各编译一次5 次固定上限pad 到 MAX_Q16后只编译一次复现并修复了根因 1。五、解决方案第一层最小直接修复最快的止血给 Grounding DINO 的动态维度text query 数设固定上限并 pad并显式告诉torch.compile哪些维度是动态的避免 graph break 与重编译import torch # 1) 把文本 query 数 pad 到固定上限形状恒定 - compile 只编一次 MAX_QUERIES 256 def pad_text_queries(text_queries, max_qMAX_QUERIES): b, n, d text_queries.shape if n max_q: pad torch.zeros(b, max_q - n, d, dtypetext_queries.dtype, devicetext_queries.device) text_queries torch.cat([text_queries, pad], dim1) else: text_queries text_queries[:, :max_q] return text_queries # 2) 编译时声明动态维度避免 Dynamo 误判静态 model GroundingDinoForObjectDetection.from_pretrained(grounding-dino-base) compiled torch.compile( model, dynamicFalse, # 因已 pad 到固定上限可关动态 # 若仍有动态维用 fullgraphFalse 容忍 graph break ) # 推理前 pad text_queries pad_text_queries(text_queries) out compiled(images, text_queriestext_queries)第一层让用户立刻消除反复重编译与动态形状报错Grounding DINO 可被torch.compile加速。六、解决方案第二层结构性改进用CompilableGroundingDINO把动态维度 pad 控制流向量化 编译配置收口from dataclasses import dataclass from typing import Optional dataclass class CompilableGroundingDINO: 让 Grounding DINO 可 torch.compile固定动态维度 向量化控制流。 max_queries: int 256 def prepare(self, text_queries, images): # 固定 query 数pad/trunc形状恒定 text_queries self._pad_queries(text_queries) return text_queries, images def _pad_queries(self, tq): b, n, d tq.shape if n self.max_queries: pad torch.zeros(b, self.max_queries - n, d, dtypetq.dtype, devicetq.device) tq torch.cat([tq, pad], dim1) return tq[:, :self.max_queries] def compile_config(self): # 因已固定维度关动态、允许有限 graph break return {dynamic: False, fullgraph: False, mode: reduce-overhead} # 使用 helper CompilableGroundingDINO(max_queries256) tq, im helper.prepare(text_queries, images) compiled torch.compile(model, **helper.compile_config()) out compiled(im, text_queriestq)CompilableGroundingDINO的语义是Grounding DINO 的动态本质在 text query 数把它 pad 成固定上限编译就稳定控制流尽量向量化避免 Python 循环减少 graph break。七、解决方案第三层断言 / CI 守护用 pytest 固化text query pad 到固定上限、编译只一次、输出不变import pytest import torch def test_queries_padded_to_fixed_max(): from grounding_compile import CompilableGroundingDINO h CompilableGroundingDINO(max_queries16) tq torch.randn(1, 5, 64) # 动态 5 out h._pad_queries(tq) assert out.shape[1] 16, query 数应 pad 到固定上限 def test_compile_once_for_varying_queries(): from grounding_compile import CompilableGroundingDINO h CompilableGroundingDINO(max_queries16) seen [] for nq in [5, 8, 12]: tq h._pad_queries(torch.randn(1, nq, 64)) key tq.shape[1] if key not in seen: seen.append(key) assert seen [16], 不同 query 数 pad 后形状应一致只编译一次 def test_compile_config_disables_dynamic(): from grounding_compile import CompilableGroundingDINO cfg CompilableGroundingDINO().compile_config() assert cfg[dynamic] is FalseCI 跑pytest tests/test_grounding_compile.py以后只要有人又对未 pad 的动态 query 数裸调torch.compile测试立刻红灯。八、排查清单当 Grounding DINO 的 torch.compile 失败/变慢按顺序查报动态形状 / 反复重编译 → text query 数随文本变pad 到固定上限如 256。结果错 / graph break → 模型里有 Python 控制流遍历 query改成向量化一次算所有 query。比不编译还慢 → 每种描述长度重编译一次固定维度后只编一次。后处理 NMS 输出动态 → NMS 放 compile 图外不参与编译只编译主干。长期方案用CompilableGroundingDINO把动态维度 pad 向量化 编译配置收口。九、小结Compile Grounding DINO 的根因是Grounding DINO 的text_queries数量随输入文本长度动态变化且模型内有按 query 数的 Python 控制流与torch.compile的静态图/固定形状假设冲突于是动态形状报错、graph break 结果错、反复重编译变慢。第一层把 text query 数 pad/trunc 到固定上限并显式配置编译关动态立刻可编译且只编一次。第二层用CompilableGroundingDINO把动态维度 pad 控制流向量化 编译配置收口减少 graph break。第三层pytest 断言query pad 到固定上限、编译只一次、配置禁用动态防止回归。记住Grounding DINO 的动态集中在 text query 数量把它 pad 成固定上限torch.compile 就能稳定编译——不要对未定形的动态维度裸调 compile。
返回列表