跑 LLM 谁都会:开个对话框、键入一句话,模型吐 token,效果好不好一眼就能看出来。 但 Embedding 模型呢?跑完它给你一个 1024 维浮点数组 ,普通工程师面对屏幕只剩四个字------"看不出来" 。 调试 RAG 时排查一个错检索的 case,往往要先打开 Jupyter,写五行 numpy 代码算余弦相似度,再切回业务代码。 Xinference PR #5022 把这件事直接做成了 UI:点一下"Launch Web UI",浏览器打开就能调 Embedding。 这篇博文讲一讲为什么 embedding 也需要游乐场、PR 是怎么做的、以及它如何反过来变成你做 RAG 调优的"显微镜"。
一、问题:Embedding 模型一直缺一块"立等可见"的体验
Xinference 早就给 LLM、Image、Video、Audio 都做了 Gradio 测试页:
| 模型类别 | 已有 Web UI | 测试方式 |
|---|---|---|
| LLM / VLM | ✅ ChatInterface | 输入消息看输出 |
| Image / Video / Audio | ✅ MediaInterface | 上传文件看生成 |
| Embedding / Rerank | ❌ | 只能 curl + python |
| OCR | ✅ | 上传图片看文本 |
为什么 embedding 一直没有?因为它的"输出"不是给人看的,是给下游算法看的。但实际工程里 embedding 出错的场景特别多:
- task 参数选错 :v3 的
prompt_name="retrieval.query"vs v4/v5 的task="retrieval",传错了模型不报错,分数悄悄降 30%。 - 维度截断不一致 :索引建的时候 1024 维,查询时业务侧不小心传了
dimensions=512,召回率断崖式下跌。 - 多模态路由 :jina-embeddings-v5-omni 这种支持 text/image/video/audio 的模型,传
{"image": "url"}还是"url"?少一层 dict 模型就把整个 URL 当字符串 embed 了。 - 相似度 sanity check:换了模型,"今天天气真好"和"今晚吃什么"的相似度应该是 0.3 还是 0.7?没工具就只能靠猜。
这些坑,PR #5022 直接给出一个开箱即用的可视化诊断台。
二、PR #5022 做了什么
PR 改了 7 个文件,核心是新增一个 EmbeddingInterface(552 行,干净的 Gradio 页面)+ 一个 REST 端点 + 前端的 "Launch Web UI" 按钮。
1. 新建 xinference/ui/gradio/embedding_interface.py
三个 Tab:
| Tab | 作用 | 输入 | 输出 |
|---|---|---|---|
| Text Encoding | 跑一段文字,看它的向量 | 文本 + task + dimensions | embedding 向量、维度、token usage |
| Multimodal Encoding | omni 模型专用 | 文本 / 图像 / 视频 / 音频任选 | 同上 |
| Similarity Comparison | 两段文本算余弦相似度 | 文本 A、文本 B + 参数 | 相似度数值 + 两边维度 |
每个 Tab 都通过 RESTfulEmbeddingModelHandle 直接走 Xinference 的 OpenAI 兼容 API 调用模型,和你业务侧的代码路径一致------这意味着 UI 里能复现的问题,业务侧 100% 能复现,反之亦然。
最值得说的是任务参数透传:
python
kwargs: Dict[str, Any] = {}
if task:
kwargs["task"] = task # v4/v5 的 task: retrieval / text-matching / classification / clustering
if dimensions:
kwargs["dimensions"] = dimensions # OpenAI 风格的 Matryoshka 截断
response = model.create_embedding(input=text, **kwargs)
页面里直接放下拉框选 task、滑杆设 dimensions------以前要写代码才能做的对照实验("同一句话,做 retrieval 和 text-matching 出来的向量分布有什么不一样"),现在变成两次点击。
2. 多模态输入的零代码体验
omni 这类模型最容易踩"输入格式陷阱"。PR 里把图像/视频/音频的入参都封成了文件上传 + 路径输入框:
python
if input_type == "image":
input_data = {"image": image_path} # 自动包成 dict
elif input_type == "video":
input_data = {"video": video_path}
elif input_type == "audio":
input_data = {"audio": audio_path}
界面上只要拖文件进去就行,dict 包装、URL/路径/base64 路由、tower 选择全部由 Xinference 后端自动处理(前阵子 PR #5018 接 jina-embeddings-v5 时就把这条路走通了)。
3. 相似度对比:Embedding 调优的"显微镜"
compare_embeddings 是这次 PR 我最喜欢的部分,看起来是一个简单的相似度计算,但工程细节很到位:
python
noop_progress = lambda *args, **kwargs: None
progress(0.1, desc="Encoding text A...")
result_a = encode_text(text_a, task, dimensions, noop_progress) # 内部不再操作进度条
...
progress(0.5, desc="Encoding text B...")
result_b = encode_text(text_b, task, dimensions, noop_progress)
...
progress(0.9, desc="Computing similarity...")
sim = cosine_similarity(result_a["embedding"], result_b["embedding"])
注意那个 noop_progress ------ 子调用不要再操作外层的 progress bar,否则两次 encode 各自从 0.1 开始走,进度条会反复"回退"。这是真实做过 Gradio 的人才会写出来的细节。
实际效果:
yaml
文本 A: 今天天气真好
文本 B: 今天阳光明媚
任务: retrieval
维度: 1024
→ Cosine Similarity: 0.8623
Dim A: 1024 Dim B: 1024
业务里调 RAG 召回率,做 hard-negative mining,做 prompt 改写效果对比------以前要写脚本,现在直接点。
4. 后端:/v1/ui/embeddings/{model_uid}
xinference/api/restful_api.py 加了一个 build_gradio_embedding_interface:
python
async def build_gradio_embedding_interface(self, model_uid: str, request: Request):
body = BuildGradioEmbeddingInterfaceRequest.parse_obj(payload)
if body.model_type != "embedding":
raise HTTPException(status_code=400, detail="Invalid model type")
interface = EmbeddingInterface(
endpoint=f"http://{internal_host}:{self._port}",
model_uid=model_uid,
...,
).build()
gr.mount_gradio_app(self._app, interface, f"/{model_uid}")
挂载路径就是 model_uid 本身------和 LLM、Media 的语义完全一致。这套约定意味着只要你启动了一个 embedding 模型 my-embed,访问 http://localhost:9997/my-embed 就直接进交互页面。
5. 前端:在"Running Models"列表里加按钮
PR 改了 running_models/index.js,让 embedding 模型也显示 "Launch Web UI" 按钮,点击后调用上面的端点,挂载完成后跳转到 Gradio 页面。
这就是体验闭环:列表 → 一键启动 UI → 浏览器跳转 → 拖入文本/文件 → 出结果。
三、它到底解决了什么实际问题
举三个真实场景:
场景 1:选 embedding 模型
- 候选:
bge-large-zh-v1.5/jina-embeddings-v5-text-small/gte-Qwen2-1.5B - 任务:判断"我想退订套餐"和"取消我的订阅"的语义相似度
- 以前:写 30 行 Python 跑三遍。
- 现在:分别启动三个模型,每个都打开 Web UI 输文本对比,5 分钟出结果。
场景 2:调 RAG 召回率
业务反馈"问'XX 怎么报销' 召回不到对应文档"。
- 以前:登服务器、查日志、复现脚本、打印 vec、numpy 算相似度......
- 现在 :在 Web UI 里粘问题 + 粘文档,看相似度数;想试
task=documentvstask=passage直接点下拉。
场景 3:验证多模态 omni 模型
老板问"这个新接的 jina-omni 真能跨模态吗?"
- 以前:写代码上传图片、读音频文件、算两个 modality 的 cosine。
- 现在:Tab 切到 Multimodal Encoding,左边输 "一只在沙发上睡觉的橘猫",右边上传猫睡觉的图片,分别拿到向量后切到 Similarity Tab 算------5 步演示给老板看。
四、5 行命令,今晚就能用
bash
pip install "xinference[all]"
xinference-local -H localhost -p 9997
然后在 http://localhost:9997 Web UI:
- 进入 Launch Model ,搜索
jina-embeddings-v5-text-small(或者你想测的任何 embedding 模型),点 Launch。 - 进入 Running Models ,找到对应行,点 Launch Web UI。
- 浏览器自动打开
http://localhost:9997/<model_uid>,开始玩。
也可以纯 API:
python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:9997/v1", api_key="EMPTY")
# UI 里能做的所有事,API 也能做
resp = client.embeddings.create(
model="jina-embeddings-v5-text-small",
input=["今天天气真好"],
dimensions=512,
extra_body={"task": "retrieval"},
)
print(len(resp.data[0].embedding)) # 512
五、为什么我推荐 Xinference 作为推理底座
很多团队对 embedding 服务的态度是"反正只是个向量化,自己写个 FastAPI 包一下就行"。结果一旦上规模就开始踩坑:
| 痛点 | Xinference 的解法 |
|---|---|
| LLM / Embedding / Rerank / 多模态 各自一份服务 | 一个进程,一份 OpenAI 兼容 API 跑完所有模态 |
| 不同模型依赖冲突(v5 要 transformers≥4.57,老模型要 <5) | virtualenv 引擎隔离,按模型分依赖 |
| 国内 HuggingFace 拉不动 | HF + ModelScope 双源,自动 fallback |
| 调试 embedding 要写代码 | ✅ PR #5022 直接给 Web UI |
| 业务测试 / 演示需要前端 | LLM、媒体、embedding 都有内置 Gradio 页面 |
| 多 GPU 调度 / 模型热加载 | supervisor + worker 架构原生支持 |
| OpenAI 风格客户端无缝切换 | /v1/embeddings、/v1/chat/completions、/v1/rerank 全兼容 |
如果你的 stack 是 "vLLM + sentence-transformers + 一堆胶水"------可以试试用 Xinference 把它们统一掉。Embedding 调试体验从此和 LLM 对齐:
bash
pip install "xinference[all]"
xinference-local -H localhost
# 浏览器打开 http://localhost:9997 即可
六、写在最后
这次 PR 看似只是"加了一个 Gradio 页面",实质上补齐了 Xinference 模型生态的最后一块体验拼图:
- LLM ✅ 早就有
- Media ✅ 早就有
- Embedding ✅ 现在有了
- Rerank(下一个?)
调试 RAG 不再需要切到 Jupyter,对比 embedding 模型不再需要写脚本,多模态向量验证也只剩拖文件 → 点按钮 → 看数字。
好框架的标志,是把工程师从"写胶水代码"里解放出来,让他们专心做模型选型和业务决策。 PR #5022 就是这个方向上的一小步------但对每天调 embedding 的人来说,是非常实在的一大步。
参考链接
- PR:xorbitsai/inference#5022 --- feat: add Gradio test page for embedding models
- 关联工作:#5018 jina-embeddings-v5 接入(多模态 dict 输入约定)
- Xinference:github.com/xorbitsai/i...
- 文档:Xinference Documentation