Misc. bug: /props publishes the randomized media_marker, defeating PR #21962’s collision defense

这个报错通常出现在 llama.cpp server 已启动多模态( --mmproj )时:客户端读取了 GET /props 返回的随机 media_marker ,又把该响应内容作为普通用户文本回传,导致服务端在渲染后的 prompt 中扫描到的媒体标记数量与随附的 bitmap 数量不一致,

快速结论:这个报错通常出现在 llama.cpp server 已启动多模态(--mmproj)时:客户端读取了 GET /props 返回的随机 media_marker,又把该响应内容作为普通用户文本回传,导致服务端在渲染后的 prompt 中扫描到的媒体标记数量与随附的 bitmap 数量不一致,从而返回 400。优先排查调用链里是否把 /props 响应原样写进了对话历史或工具输出。

适用环境:Issue 中确认的环境为 ghcr.io/ggml-org/llama.cpp:server-cuda-v0.4.1、build_info b10964-b29c606e2、libmtmd.so.0.4.1,并以 --mmproj 启动。原 Issue 未提供操作系统、Python、CUDA 版本或具体显卡型号。

最快修复方案:暂无确认的一步修复方案。Issue 以 duplicate 关闭并指向 #28249,其中说明根因是渲染后的 prompt 在解析媒体标记与特殊 token 时未区分用户输入内容;因此可优先尝试的规避方式是:不要让 /props 的响应进入会话历史或 prompt,或确保一次请求中“文本里的 marker 数量”与“实际附带的 bitmap 数量”严格一致。不要把它当成已验证的最终修复。

注意事项:Issue 中 /props 会把当前进程的 marker 发给任意未认证调用方,且该 marker 在进程生命周期内保持稳定,所以一旦进入 transcript,每次重放都会复现;服务端返回的是被折叠后的通用错误,难以直接定位到 marker 计数问题。该 Issue 已被关闭为 #28249 的重复项,以上规避措施属于可优先尝试,并非官方确认修复。

问题场景

用户运行 llama.cpp server(CUDA 容器镜像),以 --mmproj 启动多模态能力,然后通过 HTTP API 调用 /v1/chat/completions。典型触发路径是 LLM agent 或客户端先调用 GET /props 做能力发现,把返回的 media_marker 当作普通文本保存为工具输出,随后每一轮都把这段文本重新发回服务端。此时即使请求里带了真实图片,也会因为渲染后的 prompt 中 marker 数量多于 bitmap 数量而失败。

报错原文

400 {"error":{"code":400,"message":"Failed to tokenize prompt","type":"invalid_request_error"}}

number of media markers in text (%zu) does not match number of bitmaps (%zu)

原因分析

最可能的原因是 mtmd_tokenize 在渲染后的 prompt 文本中统计媒体标记数量,并要求该数量等于随请求附带的 bitmap 数量;而 get_media_marker() 不做验证或转义,随机化本身是唯一防线。PR #21962 的随机化防的是“意外碰撞”,但 GET /props 会把当前进程的 marker 公开给未认证调用方,任何读取并回放该响应的客户端都会“必然”碰撞。服务端随后把具体的 mtmd 错误折叠成通用的 Failed to tokenize prompt,导致诊断成本很高。原 Issue 还指出,文本中 marker 与真实图片同时出现时(2 个 marker vs 1 个 bitmap)同样返回 400,说明这不只是纯文本轮次的问题。

环境排查

  • 确认 llama.cpp server 是否以 --mmproj 启动,且容器/构建版本为 ghcr.io/ggml-org/llama.cpp:server-cuda-v0.4.1、build_info b10964-b29c606e2、libmtmd.so.0.4.1 这类包含 mtmd 的版本;Issue 未确认其他版本是否受影响。
  • 确认客户端调用链中是否存在读取 /props 并把返回内容写入 prompt、工具输出或对话历史的行为。
  • 确认失败请求中文本部分是否包含当前服务进程的 media_marker,以及该请求实际附带的 bitmap 数量。
  • 确认是否处于负载均衡或 failover 之后;复现时同一段文本在不同 server 上可能表现不同,容易被误判为间歇性故障。
  • 确认排查时 /health、/v1/models、/slots、/metrics、/apply-template 以及 400 错误体都不包含 marker,只有 /props 会泄漏。

解决步骤

  1. 先复现并确认 marker 来源。原 Issue 的复现方式是用 curl -s http://SERVER:PORT/props | jq -r .media_marker 取出 marker,再把它作为 user content 发送到 /v1/chat/completions,应得到上述 400。
  2. 检查客户端/agent 的工具输出与历史管理逻辑,找出把 /props 响应内容持久化并回放的地方。若确实存在,优先阻止该响应进入 prompt 或 transcript。
  3. 如果必须调用 /props 做能力发现,可优先尝试只提取所需字段(例如模型名、能力标志),不要把整个响应体或 media_marker 字段内容写入对话历史。
  4. 对于视觉请求,确保渲染后文本中出现的媒体标记数量与请求实际附带的 bitmap 数量一致;原 Issue 测得“真实图片 + 文本中 marker”会变成 2 个 marker vs 1 个 bitmap 并返回 400。
  5. 若仍触发,记录失败请求的文本内容、附带图片数量、服务端启动参数和 llama.cpp 版本,作为向 #28249 反馈的依据;该 Issue 的根因处理指向渲染 prompt 中对用户文本的媒体标记/特殊 token 解析问题。

验证方法

用同一段曾触发 400 的文本重新发送请求:如果不再出现 Failed to tokenize prompt,且不包含 stray marker 的纯文本请求返回正常、带真实图片且无多余 marker 的请求返回 200,说明规避生效。也可用原 Issue 的对照思路验证:改用随机不同字符或使用 pre-#21962 的默认 <__media__> 时应返回 200,而包含当前进程 marker 的请求返回 400,以此确认触发条件是否仍存在。

参考来源

ggml-org/llama.cpp #29118

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 26128

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注