基于 Token Logprobs 的类似 Jev 的 Python 包装器,用于 LLM 和视觉模型

TL;DR – 包装器的作用及其重要性

作者构建了一个极简的 Python 包装器,向任何聊天完成端点发送类似 Jev 的请求(状态、问题、可选的图像附件),要求模型输出 一个 token(代表所选选项的字母),并读取所有候选 token 的 log-probabilities。该技术将生成式 LLM 转化为一种廉价、低延迟的分类器,适用于纯文本和视觉增强模型。


核心思想 – 使用 log-probs 将生成转化为分类

  • 提示格式 – 包装器构建的提示包含一个状态、一个问题和一组标记为 [A]、[B] … 的选项,最后以指令 “仅用所选选项的字母作答。” 结束。示例:
    状态:
    检查此网络摄像头帧。仅根据可见内容进行判断。
    
    问题:是否可见一个人?
    选项:
    [A] true
    [B] false
    
    仅用所选选项的字母作答。
    
  • API 参数 – 请求包含:
    {
      "max_completion_tokens": 1,
      "logprobs": true,
      "top_logprobs": 20,
      "temperature": 0
    }
    
    将 max_completion_tokens 设为 1 可强制模型输出单个 token,使响应极小且延迟低。
  • Log-prob 提取 – API 返回前 N 个 token 候选及其 log-probabilities。通过将每个选项字母映射到一个 token,包装器将这些 log-probabilities 转换为选项上的归一化概率分布。
  • 结果解释 – 对于二元问题(noul 类型),包装器返回 true 的概率。对于多选问题,返回最可能的选项和完整的概率表。对于序数评分,计算期望值。

扩展 Jev 至视觉 – attachments 字段

  • 原始 Jev 规范仅支持文本/JSON 的 state。作者添加了 attachments 数组,可包含文件路径或 base64 编码的数据 URL。
  • 当请求发送到 OpenAI 端点时,每张图像作为 type: "input_image" 元素添加;对于 llama.cpp 则作为 type: "image_url" 添加。
  • 示例使用 OpenCV 捕获实时网络摄像头帧,将其编码为 JPEG,封装为数据 URL,并在每次请求前放入 attachments。

端到端 Python 示例(约 150 行)

该脚本在循环中执行三个步骤:

  1. 捕获来自 /dev/video0 的帧。
  2. 编码帧为 base64 JPEG 并设置 data["attachments"]。
  3. 提交请求至选定后端(本地 llama.cpp 服务器或 OpenAI),使用后台线程。
  4. 打印包含最新答案和测量 FPS 的表格。

关键函数:

  • score(data, url, model) – 为每个问题构建提示,发送请求,归一化 log-probs,并返回结构化答案字典。
  • main – 解析 CLI 参数(url 和 model),启动网络摄像头,并协调异步评分。

该脚本刻意保持自包含;唯一外部依赖是 opencv-python 用于访问网络摄像头。其余所有库均为 Python 标准库的一部分。


作者报告的性能数据

后端 模型 硬件 FPS(帧/秒)
llama.cpp(本地) Gemma‑4‑12B‑QAT(GGUF) RTX 3090 ≈ 1 fps(每帧三个问题)
OpenAI gpt‑6‑luna 云 ≈ 0.2 fps

作者指出,OpenAI 运行较慢部分原因是每帧为每个问题创建 新的 HTTP 连接,这可以优化。


本地 llama.cpp 服务器设置说明

# 1. 下载 7 GB 的 Gemma‑4‑12B 模型及其多模态投影器(约 175 MB)
mkdir -p ~/models/gemma-4-12b/
cd ~/models/gemma-4-12b/
curl -fL -C - -o gemma-4-12b-it-qat-q4_0.gguf \
  https://huggingface.co/google/gemma-4-12B-it-qat-q4_0-gguf/resolve/main/gemma-4-12b-it-qat-q4_0.gguf
curl -fL -C - -o mmproj-gemma-4-12b-it-qat-q4_0.gguf \
  https://huggingface.co/google/gemma-4-12B-it-qat-q4_0-gguf/resolve/main/mmproj-gemma-4-12b-it-qat-q4_0.gguf

# 2. 安装 CUDA-86 的 llama.cpp 二进制文件
curl -fL -o llama.zst \
  https://huggingface.co/buckets/ggml-org/install.sh/resolve/b11160/x86_64/linux/cuda/86/llama-app.zst
mkdir -p ~/bin/
zstd -d llama.zst -o ~/bin/llama
chmod +x ~/bin/llama

# 3. 在端口 8060 上启动服务器
~/bin/llama serve --models-dir ~/models/ --port 8060

服务器运行后,执行包装器:

uv run webcam.py http://localhost:8060/v1 gemma-4-12b
# 或使用 OpenAI(需先设置 OPENAI_API_KEY)
uv run webcam.py https://api.openai.com/v1 gpt-6-luna

社区反馈(精选 HN 评论)

@TeMPOraL – “这就是我们实现《星际迷航》式环境感知的方式:从多模态上下文中推断意图并自动执行。” 该评论强调了使用此类包装器进行实时意图检测的更广泛愿景,超越简单分类。

@prathje – “这不就是基于语法的解码并返回 JSON 响应吗?如果是,Jev 本质上只是在那之上加了个缓存层。” 作者的实现确实依赖于确定性提示和 token 级 logits,这与语法约束解码类似,但增加了轻量级缓存策略以应对重复的状态前缀。

@frabcus – “使用 RLHF 训练的普通 LLM 可能在网络早期就做出决定,因此类似 Jev 的 log-prob 包装器可能不如专门训练用于校准决策的模型准确。” 这指出了潜在局限性:普通模型的概率校准可能对下游决策不够理想。

@arcticbull – “这就像 Jev,但成本和速度高几个数量级。” 该评论反映了通用 LLM 的灵活性与专用视觉分类器效率之间的权衡。

@CROON_tv – “我想看看尾延迟数据;在我们的实时语音端点中,Jev 比使用相同提示的普通 LLM 更快且更果断。” 延迟是交互式应用的关键指标,而该包装器的单 token 方法有助于保持尾延迟较低。

@czl_my – “我创建了一个适用于任何 OpenAI 兼容端点的 Jev 包装器:https://github.com/zhulinchng/jevper。” 外部实现表明该想法正在获得关注。


局限性与待解决问题

  • 概率校准 – 普通模型的原始 log-probs 可能未充分校准,尤其是对罕见 token。用户可能需要温度缩放或事后校准。
  • 缺失 token 处理 – 包装器将不在 top-N 列表中的选项视为零概率,但如果遗漏质量超过 1e-6 则会报错。此安全检查可防止无声误排序。
  • 可扩展性 – 每个问题发送单独请求(演示中每帧三个)会增加网络开销。将多个问题批量合并到单个提示中可提高吞吐量。
  • 视觉模型选择 – 虽然 Gemma‑4‑12B 可用,但专用多模态模型(如 CLIP、Florence)可能实现更高 FPS 和更好的视觉准确性。

总结

所发布的包装器展示了一种实用、与语言模型无关的方法,通过利用 token 级 log-probabilities,将任何聊天完成 API(包括视觉增强后端)转化为快速、确定性的分类器。其简洁性(单个 Python 函数)和添加图像附件的能力,使其成为实时多模态决策系统的有用构建模块,尽管仍需注意概率校准和每问题请求开销的常规限制。

Sources