🔎 Search before asking | 提交之前请先搜索
🤖 Consult the online AI assistant for assistance | 在线 AI 助手咨询
Description of the bug | 错误描述
vlm-engine 后端版面识别输出乱码缺陷报告
发现日期: 2026-07-14
影响范围: vlm-engine / hybrid-engine 后端的 VLM 版面识别全流程
严重程度: P0 - 致命(该后端下所有任务 100% 失效)
根因层级: mineru_vl_utils 与 vllm 0.21.0 的多模态预处理机制不兼容
一、问题现象
使用 vlm-engine 后端(worker 进程内 vllm.LLM 直接推理)解析任意 PDF/图片时,
版面识别第一步(Layout Detection)输出残缺的特殊 token 片段,无法解析出任何 block,
任务产出空结果。同一文件用 vlm-http-client 后端(手动启动 mineru-openai-server)
则完全正常。
1.1 对比日志
情况一:vlm-http-client(正常)
手动启动 vllm server:
CUDA_VISIBLE_DEVICES=0 mineru-openai-server \
--engine vllm --port 30000 --host 0.0.0.0 --gpu-memory-utilization 0.8
worker 走 HTTP 调用,layout 输出规范:
2026-07-14 06:02:29.385 | DEBUG | Layout raw output: <|box_start|>056 192 072 221<|box_end|><|ref_start|>page_number<|ref_end|><|rotate_right|> <|box_start|>081 149 918 409<|box_end|><|ref_start|>table<|ref_end|><|rotate_up|>
情况二:vlm-engine(异常)
worker 进程内 vllm.LLM(**kwargs) 自动推理,layout 输出乱码:
2026-07-14 03:30:31.737 | DEBUG | Layout raw output: ]]<|><|><|><|>
2026-07-14 03:30:31.739 | WARNING | Layout output does not match expected format: ]]<|><|><|><|>
2026-07-14 03:30:31.741 | DEBUG | Layout raw output: ]]<|><|><|><|>
2026-07-14 03:30:31.743 | WARNING | Layout output does not match expected format: ]]<|><|><|><|>
1.2 关键特征
| 特征 |
观察 |
| 输出内容 |
`]]< |
finish_reason |
stop(模型"正常"停了,不抛异常) |
| 推理速度 |
3.922 page/s,远快于正常的 0.426 page/s(只生成几个 token 就停) |
| block 数量 |
Page 0: 0 blocks(无任何识别结果) |
| 影响范围 |
第一步 Layout Detection 即乱码,后续 content extract 不执行 |
| 触发条件 |
走 VllmEngineVlmClient 路径即复现,与文件内容无关(合成图也复现) |
二、复现步骤
2.1 环境前提
- 容器:
mineru-worker 镜像(Linux, Python 3.12)
- vllm:
0.21.0
- mineru_vl_utils:随 mineru 安装的版本
- GPU:compute_capability 8.6
- 模型:
OpenDataLab/MinerU2.5-Pro-2605-1.2B(Qwen2VL 架构)
2.2 最小复现脚本
以下脚本在 worker 容器内执行,用一张 PIL 合成的矩形图即可 100% 复现,
无需真实 PDF:
# /app/repro.py
import os
os.environ.setdefault("OMP_NUM_THREADS", "1")
def main():
from mineru.utils.models_download_utils import auto_download_and_get_model_root_path
model_path = auto_download_and_get_model_root_path("/", "vlm")
import vllm
from mineru_vl_utils.vlm_client.vllm_engine_client import VllmEngineVlmClient
from mineru_vl_utils.vlm_client.base_client import SamplingParams
from PIL import Image, ImageDraw
vllm_llm = vllm.LLM(
model=model_path,
gpu_memory_utilization=0.5,
limit_mm_per_prompt={"image": 1},
)
client = VllmEngineVlmClient(vllm_llm=vllm_llm)
img = Image.new("RGB", (1036, 1036), (255, 255, 255))
draw = ImageDraw.Draw(img)
draw.rectangle([50, 50, 900, 900], outline=(0, 0, 0), width=2)
draw.text((100, 100), "Cell A1", fill=(0, 0, 0))
sp = SamplingParams(temperature=0, top_p=0.01, top_k=1)
out = client.batch_predict(
images=[img],
prompts=["\nLayout Detection:"],
sampling_params=[sp],
)
print("输出:", repr(out[0][:120]))
if __name__ == "__main__":
main()
运行:
预期(异常)输出:
2.3 对照实验(正常路径)
把上面脚本的 client.batch_predict(...) 换成直接 vllm_llm.generate(...):
tokenizer = vllm_llm.get_tokenizer()
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": [
{"type": "image"},
{"type": "text", "text": "\nLayout Detection:"},
]},
]
chat_prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
vsp = vllm.SamplingParams(temperature=0, top_p=0.01, top_k=1, max_tokens=512, skip_special_tokens=False)
out = vllm_llm.generate(
prompts=[{"prompt": chat_prompt, "multi_modal_data": {"image": [img]}}],
sampling_params=vsp,
use_tqdm=False,
)
print("输出:", repr(out[0].outputs[0].text[:120]))
正常输出:
输出: '<|box_start|>094 094 131 110<|box_end|><|ref_start|>text<|ref_end|><|rotate_up|>'
核心差异:两条路径用相同的模型、相同的图、相同的 prompt、相同的采样参数,
唯一区别是调用方式——VllmEngineVlmClient.batch_predict vs 直接 vllm_llm.generate。
三、根因分析
3.1 调用路径差异
直接 vllm_llm.generate(正常路径):
vllm_llm.generate(
prompts=[{"prompt": chat_prompt, "multi_modal_data": {"image": [img]}}],
...
)
vllm 0.21.0 的 LLM.generate 内部一次性完成:
- renderer 预处理(计算
mm_hashes、mm_placeholders、mm_kwargs)
mm_receiver_cache 按hash缓存多模态特征
- EngineCore 取 cache 中的特征 + 推理
三步在同一次调用内完成,cache 状态一致。
VllmEngineVlmClient._predict_one_batch(异常路径):
def _predict_one_batch(self, image_lists, chat_prompts, vllm_sampling_params):
raw_prompts = [
{"prompt": chat_prompt, **({"multi_modal_data": {"image": image}} if image else {})}
for chat_prompt, image in zip(chat_prompts, image_lists)
]
vllm_prompts = self._render_vllm_cmpl_inputs(raw_prompts) # ← 关键:客户端预渲染
outputs = self.vllm_llm.generate(prompts=vllm_prompts, ...)
return [self.get_output_content(output) for output in outputs]
其中 _render_vllm_cmpl_inputs 的实现:
def _render_vllm_cmpl_inputs(self, raw_prompts):
"""优先使用新版 vLLM Renderer 预处理 prompt,旧版无 renderer 时保持原输入。"""
renderer = getattr(self.vllm_llm, "renderer", None)
render_cmpl = getattr(renderer, "render_cmpl", None) if renderer is not None else None
if callable(render_cmpl):
return render_cmpl(raw_prompts) # vllm 0.21.0 走这条
return raw_prompts
vllm 0.21.0 的 LLM.renderer.render_cmpl 把原始 dict 预渲染成 TokensPrompt:
原始: {"prompt": str, "multi_modal_data": {"image": [PIL]}}
预渲染后: {
"type": "...",
"prompt_token_ids": [...],
"mm_kwargs": <MultiModalKwargsItems>,
"mm_hashes": [...],
"mm_placeholders": {"image": [PlaceholderRange(offset=15, length=1369, ...)]},
"prompt": str,
"arrival_time": ...
}
3.2 cache 脱节机制
vllm 0.21.0 引入了 mm_receiver_cache(vllm/multimodal/cache.py),要求:
预渲染产生的 mm_hash 对应的多模态特征必须缓存在 EngineCore 内,
generate 内部用同一个 mm_hash 取回特征。
但 _render_vllm_cmpl_inputs 在客户端进程预渲染后,把 TokensPrompt(含 mm_hashes)
传给 LLM.generate,generate 内部又会再次预处理。两次预处理的 cache 状态脱节:
_render_vllm_cmpl_inputs 预渲染 → 生成 mm_hash='c5f00baa...',但特征没进 EngineCore cache
LLM.generate 收到 TokensPrompt(已有 mm_hashes)→ 跳过自己的预渲染
- EngineCore 用
mm_hash 去 mm_receiver_cache.get_and_update_features 取特征
- cache 里找不到 →
mm_features = None
- 模型收到的图片特征是空的,只能吐出
]]<|><|><|><|> 这种残缺片段后 stop
3.3 直接证据
对 _predict_one_batch 内部调用的 vllm_llm.generate 做 spy,确认它收到的是
预渲染后的 TokensPrompt(含 mm_placeholders),而非原始 dict:
[SPY generate] p keys: ['type', 'prompt_token_ids', 'mm_kwargs', 'mm_hashes',
'mm_placeholders', 'prompt', 'arrival_time']
[SPY generate] mm_placeholders: {'image': [PlaceholderRange(offset=15, length=1369, is_embed=None)]}
[SPY generate] out[0] text: ']]<|><|><|><|>'
对照实验中,手动绕过 _render_vllm_cmpl_inputs,把原始 dict 直接传给 generate,
vllm 内部一次性预处理,输出正常:
=== 修复方案: patch _predict_one_batch 跳过 _render ===
text: '<|box_start|>094 094 131 110<|box_end|><|ref_start|>text<|ref_end|><|rotate_up|>'
3.4 为什么 http-client 正常
HttpVlmClient(http_client.py)走 HTTP /v1/chat/completions,vllm server 端
自己做完整的多模态预处理(renderer + cache 在同一次调用内完成),不存在
客户端预渲染 + 服务端再处理的 cache 脱节问题。
3.5 诊断过程中的排除项
以下假设在诊断中被逐一排除,记录于此避免重复排查:
| 假设 |
排除依据 |
worker CLI 的 --backend 参数未生效 |
worker 用 message 的 backend 字段(processor.py:34),CLI 参数只进启动日志 |
enable_custom_logits_processors() 返回 False |
容器内实测返回 True,logits processor 已注册 |
MinerULogitsProcessor 的 ban-token 逻辑有 bug |
no_repeat_ngram_size=0 禁用后仍乱码(diag_vlm9) |
采样参数 top_p/top_k/temp/max_tokens 差异 |
vllm 0.21.0 在 temperature=0 时重置 top_p=1.0, top_k=0,但用相同参数直接 generate 正常(diag_vlm19) |
sampling_params 传 list vs 单个对象 |
4 种组合都正常(diag_vlm18) |
image_to_obj_list 转换了图片 |
实测 is img: True,同一对象(diag_vlm22) |
| 真实 PDF 图片触发的特定问题 |
合成 PIL 矩形图也复现(diag_vlm8) |
VllmEngineVlmClient.__init__ 污染全局状态 |
创建 client 前后直接 generate 都正常(diag_vlm17) |
| 表格图片复杂度触发 |
简单矩形图、复杂表格图都复现,与内容无关 |
四、修复方案
方案1(推荐):Monkey-patch 跳过 _render_vllm_cmpl_inputs
原理:patch VllmEngineVlmClient._predict_one_batch,跳过 _render_vllm_cmpl_inputs
预渲染步骤,把原始 {"prompt": str, "multi_modal_data": {"image": [PIL]}} 直接交给
LLM.generate(),让 vllm 0.21.0 自己在内部一次性完成 renderer + cache + 推理。
优点:
- 不改
mineru_vl_utils 源码,风险最低
- 已通过合成图验证有效(
diag_vlm27)
- 对旧版 vllm 无副作用(旧版
_render_vllm_cmpl_inputs 本就是 no-op,跳过等价)
- 不影响
http-client 后端
实现:
新建 mineru/utils/vllm_compat_patch.py:
# Copyright (c) Opendatalab. All rights reserved.
"""vLLM 0.21.0 兼容补丁:绕过 VllmEngineVlmClient 的客户端预渲染。
背景见 docs/bugs/2026-07-14-vlm-engine-layout-garbled-output.md
"""
from loguru import logger
_patch_applied = False
def apply_vllm_engine_patch() -> bool:
"""Monkey-patch VllmEngineVlmClient._predict_one_batch。
Returns:
True - 本次应用了 patch
False - 已应用过 / 无法导入目标类 / 跳过
"""
global _patch_applied
if _patch_applied:
return False
try:
from mineru_vl_utils.vlm_client.vllm_engine_client import VllmEngineVlmClient
except ImportError:
# vllm 未安装(CPU 环境)或 mineru_vl_utils 结构变化,静默跳过
return False
original = getattr(VllmEngineVlmClient, "_predict_one_batch", None)
if original is None:
return False
# 已 patch 过(幂等保护)
if getattr(original, "_mineru_skip_render_patch", False):
_patch_applied = True
return False
def patched_predict_one_batch(self, image_lists, chat_prompts, vllm_sampling_params):
# 与原始实现一致,唯一区别:不调 self._render_vllm_cmpl_inputs,
# 把原始 {"prompt": str, "multi_modal_data": {"image": [PIL]}} 直接
# 交给 LLM.generate(),由 vLLM 内部一次性完成 renderer + cache。
vllm_prompts = [
{"prompt": chat_prompt, **({"multi_modal_data": {"image": image}} if image else {})}
for chat_prompt, image in zip(chat_prompts, image_lists)
]
outputs = self.vllm_llm.generate(
prompts=vllm_prompts, # type: ignore
sampling_params=vllm_sampling_params,
use_tqdm=self.use_tqdm,
)
return [self.get_output_content(output) for output in outputs]
patched_predict_one_batch._mineru_skip_render_patch = True
VllmEngineVlmClient._predict_one_batch = patched_predict_one_batch
_patch_applied = True
logger.info(
"Applied VllmEngineVlmClient._predict_one_batch patch: "
"bypass _render_vllm_cmpl_inputs for vLLM mm_receiver_cache compatibility"
)
return True
在 mineru/backend/vlm/vlm_analyze.py 顶部,from mineru_vl_utils import MinerUClient
之后注入:
from mineru_vl_utils import MinerUClient
from packaging import version
# vLLM 0.21.0 兼容补丁:必须在 VllmEngineVlmClient 实例化前应用
from mineru.utils.vllm_compat_patch import apply_vllm_engine_patch
apply_vllm_engine_patch()
为什么注入在 vlm_analyze.py:
所有 vlm-engine 路径(CLI do_parse、worker process_task、gradio)都会 import
vlm_analyze,patch 在此处应用可覆盖全部入口,且早于 VllmEngineVlmClient 实例化。
方案2(最干净):升级或降级 vllm
- 降级到
vllm==0.10.x:没有 LLM.renderer.render_cmpl,
_render_vllm_cmpl_inputs 走 return raw_prompts 分支(no-op),问题消失。
- 等
mineru_vl_utils 上游修复:让 _render_vllm_cmpl_inputs 对 vllm 0.21.0 的
mm_receiver_cache 机制做兼容(预渲染时同步填充 cache,或直接不预渲染)。
方案3(绕过):继续用 vlm-http-client
手动启动 mineru-openai-server,worker 用 vlm-http-client 后端。已验证可用,
但失去了 worker 进程内推理的便利性。
五、验证清单
修复后需确认以下场景:
六、诊断过程时间线
完整诊断历经 27 个实验脚本,关键转折点记录如下:
| 脚本 |
发现 |
diag_vlm.py |
LLM.generate 和 LLM.chat 在合成图上都正常,排除"直接调用 vllm 坏了" |
diag_vlm4 |
加 MinerULogitsProcessor 后直接 generate 仍正常,排除 logits processor |
diag_vlm6 |
复杂表格图直接 generate 正常,排除"图片复杂度" |
diag_vlm8 |
MinerUClient.batch_two_step_extract 合成图也乱码,锁定封装层 |
diag_vlm9 |
no_repeat_ngram_size=0 禁用 logits processor 仍乱码,排除 ban-token 逻辑 |
diag_vlm13 |
vllm 0.21.0 在 temperature=0 时重置 top_p=1.0, top_k=0(假线索) |
diag_vlm14 |
temperature=0.01 保留 top_p/top_k 后仍乱码,排除采样参数重置 |
diag_vlm18/19 |
所有采样参数组合直接 generate 都正常,彻底排除采样参数 |
diag_vlm23 |
client.vllm_llm.generate 直接调用正常,client._predict_one_batch 乱码 |
diag_vlm24 |
spy 确认 _predict_one_batch 传给 generate 的是预渲染后的 TokensPrompt |
diag_vlm26 |
源码确认 _predict_one_batch 调 _render_vllm_cmpl_inputs;手动构造原始 dict 调 generate 报 mm_hash cache miss |
diag_vlm27 |
patch 跳过 _render_vllm_cmpl_inputs 后输出正常,确认修复方向 |
七、相关文件与代码位置
| 文件 |
位置 |
说明 |
mineru_vl_utils/vlm_client/vllm_engine_client.py |
_predict_one_batch |
调 _render_vllm_cmpl_inputs 预渲染,根因所在 |
mineru_vl_utils/vlm_client/vllm_engine_client.py |
_render_vllm_cmpl_inputs |
vllm 0.21.0 走 renderer.render_cmpl 预渲染 |
mineru_vl_utils/mineru_client.py |
MinerUClient.batch_two_step_extract |
入口,走 batching_mode="stepping" → _predict_one_batch |
mineru/backend/vlm/vlm_analyze.py |
ModelSingleton.get_model |
构造 MinerUClient(backend="vllm-engine") |
mineru/cli/common.py:725-740 |
do_parse vlm 分支 |
vlm-engine → get_vlm_engine("auto") → _process_vlm |
mineru/worker/processor.py:34 |
process_task |
用 message 的 backend 字段,非 CLI 参数 |
vllm/multimodal/cache.py:660 |
get_and_update_item |
mm_receiver_cache 的 assert miss 触发点 |
mineru/utils/vllm_compat_patch.py |
新增 |
方案1 的 patch 实现 |
How to reproduce the bug | 如何复现
1
Operating System Mode | 操作系统类型
No response
Operating System Version| 操作系统版本
1
Python version | Python 版本
No response
Software version | 软件版本 (mineru --version)
No response
Backend name | 解析后端
No response
Device mode | 设备模式
No response
🔎 Search before asking | 提交之前请先搜索
🤖 Consult the online AI assistant for assistance | 在线 AI 助手咨询
Description of the bug | 错误描述
vlm-engine 后端版面识别输出乱码缺陷报告
一、问题现象
使用
vlm-engine后端(worker 进程内vllm.LLM直接推理)解析任意 PDF/图片时,版面识别第一步(Layout Detection)输出残缺的特殊 token 片段,无法解析出任何 block,
任务产出空结果。同一文件用
vlm-http-client后端(手动启动mineru-openai-server)则完全正常。
1.1 对比日志
情况一:
vlm-http-client(正常)手动启动 vllm server:
worker 走 HTTP 调用,layout 输出规范:
情况二:
vlm-engine(异常)worker 进程内
vllm.LLM(**kwargs)自动推理,layout 输出乱码:1.2 关键特征
finish_reasonstop(模型"正常"停了,不抛异常)3.922 page/s,远快于正常的0.426 page/s(只生成几个 token 就停)Page 0: 0 blocks(无任何识别结果)VllmEngineVlmClient路径即复现,与文件内容无关(合成图也复现)二、复现步骤
2.1 环境前提
mineru-worker镜像(Linux, Python 3.12)0.21.0OpenDataLab/MinerU2.5-Pro-2605-1.2B(Qwen2VL 架构)2.2 最小复现脚本
以下脚本在 worker 容器内执行,用一张 PIL 合成的矩形图即可 100% 复现,
无需真实 PDF:
运行:
预期(异常)输出:
2.3 对照实验(正常路径)
把上面脚本的
client.batch_predict(...)换成直接vllm_llm.generate(...):正常输出:
三、根因分析
3.1 调用路径差异
直接
vllm_llm.generate(正常路径):vllm 0.21.0 的
LLM.generate内部一次性完成:mm_hashes、mm_placeholders、mm_kwargs)mm_receiver_cache按hash缓存多模态特征三步在同一次调用内完成,cache 状态一致。
VllmEngineVlmClient._predict_one_batch(异常路径):其中
_render_vllm_cmpl_inputs的实现:vllm 0.21.0 的
LLM.renderer.render_cmpl把原始 dict 预渲染成TokensPrompt:3.2 cache 脱节机制
vllm 0.21.0 引入了
mm_receiver_cache(vllm/multimodal/cache.py),要求:但
_render_vllm_cmpl_inputs在客户端进程预渲染后,把TokensPrompt(含mm_hashes)传给
LLM.generate,generate内部又会再次预处理。两次预处理的 cache 状态脱节:_render_vllm_cmpl_inputs预渲染 → 生成mm_hash='c5f00baa...',但特征没进 EngineCore cacheLLM.generate收到TokensPrompt(已有mm_hashes)→ 跳过自己的预渲染mm_hash去mm_receiver_cache.get_and_update_features取特征mm_features = None]]<|><|><|><|>这种残缺片段后stop3.3 直接证据
对
_predict_one_batch内部调用的vllm_llm.generate做 spy,确认它收到的是预渲染后的
TokensPrompt(含mm_placeholders),而非原始 dict:对照实验中,手动绕过
_render_vllm_cmpl_inputs,把原始 dict 直接传给generate,vllm 内部一次性预处理,输出正常:
3.4 为什么 http-client 正常
HttpVlmClient(http_client.py)走 HTTP/v1/chat/completions,vllm server 端自己做完整的多模态预处理(renderer + cache 在同一次调用内完成),不存在
客户端预渲染 + 服务端再处理的 cache 脱节问题。
3.5 诊断过程中的排除项
以下假设在诊断中被逐一排除,记录于此避免重复排查:
--backend参数未生效backend字段(processor.py:34),CLI 参数只进启动日志enable_custom_logits_processors()返回 FalseTrue,logits processor 已注册MinerULogitsProcessor的 ban-token 逻辑有 bugno_repeat_ngram_size=0禁用后仍乱码(diag_vlm9)top_p/top_k/temp/max_tokens差异temperature=0时重置top_p=1.0, top_k=0,但用相同参数直接generate正常(diag_vlm19)sampling_params传 list vs 单个对象diag_vlm18)image_to_obj_list转换了图片is img: True,同一对象(diag_vlm22)diag_vlm8)VllmEngineVlmClient.__init__污染全局状态generate都正常(diag_vlm17)四、修复方案
方案1(推荐):Monkey-patch 跳过
_render_vllm_cmpl_inputs原理:patch
VllmEngineVlmClient._predict_one_batch,跳过_render_vllm_cmpl_inputs预渲染步骤,把原始
{"prompt": str, "multi_modal_data": {"image": [PIL]}}直接交给LLM.generate(),让 vllm 0.21.0 自己在内部一次性完成 renderer + cache + 推理。优点:
mineru_vl_utils源码,风险最低diag_vlm27)_render_vllm_cmpl_inputs本就是 no-op,跳过等价)http-client后端实现:
新建
mineru/utils/vllm_compat_patch.py:在
mineru/backend/vlm/vlm_analyze.py顶部,from mineru_vl_utils import MinerUClient之后注入:
为什么注入在
vlm_analyze.py:所有 vlm-engine 路径(CLI
do_parse、workerprocess_task、gradio)都会 importvlm_analyze,patch 在此处应用可覆盖全部入口,且早于VllmEngineVlmClient实例化。方案2(最干净):升级或降级 vllm
vllm==0.10.x:没有LLM.renderer.render_cmpl,_render_vllm_cmpl_inputs走return raw_prompts分支(no-op),问题消失。mineru_vl_utils上游修复:让_render_vllm_cmpl_inputs对 vllm 0.21.0 的mm_receiver_cache机制做兼容(预渲染时同步填充 cache,或直接不预渲染)。方案3(绕过):继续用
vlm-http-client手动启动
mineru-openai-server,worker 用vlm-http-client后端。已验证可用,但失去了 worker 进程内推理的便利性。
五、验证清单
修复后需确认以下场景:
vlm-engine后端解析合成 PIL 图,layout 输出<|box_start|>...<|box_end|>规范格式vlm-engine后端解析真实 PDF,layout 正常识别 blockvlm-engine后端完整跑通batch_two_step_extract(layout + content extract)vlm-http-client后端行为不受影响(patch 只动VllmEngineVlmClient)hybrid-engine后端正常(同样走VllmEngineVlmClient)vlm_analyze不重复 patch(幂等)renderer.render_cmpl)下 patch 无副作用六、诊断过程时间线
完整诊断历经 27 个实验脚本,关键转折点记录如下:
diag_vlm.pyLLM.generate和LLM.chat在合成图上都正常,排除"直接调用 vllm 坏了"diag_vlm4MinerULogitsProcessor后直接generate仍正常,排除 logits processordiag_vlm6generate正常,排除"图片复杂度"diag_vlm8MinerUClient.batch_two_step_extract合成图也乱码,锁定封装层diag_vlm9no_repeat_ngram_size=0禁用 logits processor 仍乱码,排除 ban-token 逻辑diag_vlm13temperature=0时重置top_p=1.0, top_k=0(假线索)diag_vlm14temperature=0.01保留top_p/top_k后仍乱码,排除采样参数重置diag_vlm18/19generate都正常,彻底排除采样参数diag_vlm23client.vllm_llm.generate直接调用正常,client._predict_one_batch乱码diag_vlm24_predict_one_batch传给generate的是预渲染后的TokensPromptdiag_vlm26_predict_one_batch调_render_vllm_cmpl_inputs;手动构造原始 dict 调generate报mm_hashcache missdiag_vlm27_render_vllm_cmpl_inputs后输出正常,确认修复方向七、相关文件与代码位置
mineru_vl_utils/vlm_client/vllm_engine_client.py_predict_one_batch_render_vllm_cmpl_inputs预渲染,根因所在mineru_vl_utils/vlm_client/vllm_engine_client.py_render_vllm_cmpl_inputsrenderer.render_cmpl预渲染mineru_vl_utils/mineru_client.pyMinerUClient.batch_two_step_extractbatching_mode="stepping"→_predict_one_batchmineru/backend/vlm/vlm_analyze.pyModelSingleton.get_modelMinerUClient(backend="vllm-engine")mineru/cli/common.py:725-740do_parsevlm 分支vlm-engine→get_vlm_engine("auto")→_process_vlmmineru/worker/processor.py:34process_taskbackend字段,非 CLI 参数vllm/multimodal/cache.py:660get_and_update_itemmm_receiver_cache的 assert miss 触发点mineru/utils/vllm_compat_patch.pyHow to reproduce the bug | 如何复现
1
Operating System Mode | 操作系统类型
No response
Operating System Version| 操作系统版本
1
Python version | Python 版本
No response
Software version | 软件版本 (mineru --version)
No response
Backend name | 解析后端
No response
Device mode | 设备模式
No response