
1. Qwen3 Embedding 是什么从 LLMs 向量化检索场景说起如果你正在做 RAG、语义搜索、推荐召回或者文本聚类大概率绕不开一个核心问题怎么把一段文本变成一串能算相似度的数字。这就是 Embedding 模型要干的事。Qwen3 Embedding 是 Qwen 家族在 2025 年 6 月推出的专用文本嵌入与重排序模型系列构建在 Qwen3 密集基础模型之上提供 0.6B、4B、8B 三个尺寸覆盖文本嵌入和重排序两类任务。它适合谁适合需要自建向量化服务、又不想被闭源 API 的调用成本和数据外流问题卡住的开发者。我先把它的几个关键特性讲清楚后面配置和验证才不会迷路。第一是通用性8B 嵌入模型在 MTEB 多语言榜单上拿到过 70.58 的均分重排序模型在检索场景里表现也很稳。第二是灵活性嵌入模型支持 MRLMatryoshka Representation Learning也就是你可以把 4096 维的向量截断成 1024 维甚至更低精度损失可控这对存储成本敏感的场景很实用。第三是多语言官方说支持 100 多种语言包括各种编程语言代码检索也能打。第四是指令感知嵌入和重排序模型都支持用户自定义指令你可以针对特定任务、语言或场景写一句 task description 来提升效果。模型列表这块嵌入模型三个尺寸的向量维度分别是 1024、2560、4096层数 28 到 36 不等序列长度都支持 32K。重排序模型同样三个尺寸但输出的是相关性分数而不是向量。选哪个尺寸我的经验是0.6B 适合本地开发和小规模验证4B 是性价比甜点8B 适合对召回质量要求高的生产检索。下面这张表帮你快速对照。模型类型尺寸层数序列长度向量维度MRL指令感知Qwen3-Embedding-0.6B嵌入0.6B2832K1024是是Qwen3-Embedding-4B嵌入4B3632K2560是是Qwen3-Embedding-8B嵌入8B3632K4096是是Qwen3-Reranker-0.6B重排序0.6B2832K-是是Qwen3-Reranker-4B重排序4B3632K-是是Qwen3-Reranker-8B重排序8B3632K-是是评测结果方面MTEB 多语言榜上 Qwen3-Embedding-8B 的 Mean(Task) 是 70.58超过 gemini-embedding-exp-03-07 的 68.37 和 gte-Qwen2-7b-Instruct 的 62.51。英文榜 8B 拿到 75.22中文 C-MTEB 拿到 73.84。重排序模型在 MTEB-R 上 8B 是 69.02CMTEB-R 是 77.45。这些数字说明它在多语言和中文场景都有竞争力但具体到你的业务还是得用自己的数据跑一遍召回率才算数。注意Embedding 模型输出的是向量Reranker 输出的是分数两者组合使用是检索系统的常见架构——先用嵌入做粗召回再用重排序做精排。2. 环境安装与模型下载Transformers 和 vLLM 两条路径怎么选安装这一步核心是选对推理框架。Transformers 适合快速验证和单条推理vLLM 适合批量编码和高并发服务。我建议两个都装开发阶段用 Transformers 调通逻辑上线用 vLLM 扛吞吐。先看依赖清单。Transformers 路径需要 transformers4.51.0这是硬性要求低于这个版本加载 Qwen3 Embedding 会报错。vLLM 路径需要 vllm0.8.5。另外 sentence-transformers2.7.0 可以走第三条更简洁的路。Python 版本建议 3.10 以上torch 建议 2.4 以上。# 创建虚拟环境 python -m venv qwen3-embed-env source qwen3-embed-env/bin/activate # 安装 Transformers 路径依赖 pip install transformers4.51.0 torch sentence-transformers2.7.0 # 安装 vLLM 路径依赖注意 vllm 对 torch 版本有要求 pip install vllm0.8.5如果你要用 flash_attention_2 加速还需要额外装 flash-attn但这个包编译比较挑环境建议先跑通基础版本再折腾。模型下载地址在 Hugging Face 的 Qwen 集合页国内网络环境可以用镜像站或者提前下载到本地目录。# 方式一huggingface-cli 下载 huggingface-cli download Qwen/Qwen3-Embedding-0.6B --local-dir ./models/Qwen3-Embedding-0.6B # 方式二git clone需要 git-lfs git lfs install git clone https://huggingface.co/Qwen/Qwen3-Embedding-0.6B ./models/Qwen3-Embedding-0.6B下载完检查一下目录里有没有 config.json、model.safetensors、tokenizer.json 这几个关键文件。0.6B 的模型大概 1.2GB 左右4B 约 8GB8B 约 16GB提前留好磁盘空间。这里说一个我踩过的坑tokenizer 的 padding_side 必须设成 left。Qwen3 Embedding 用的是 last token pooling也就是取最后一个有效 token 的隐藏状态作为句向量。如果 padding 在右边最后一个 token 可能是 pad取出来的向量就是错的。这个细节在官方示例里写了但很容易被忽略。提示如果你只是做小规模验证0.6B 模型在 CPU 上也能跑只是慢。批量编码建议上 GPU显存 8GB 能跑 0.6B 的 batch4B 建议 16GB 以上。3. 可复制配置Transformers 加载脚本与 vLLM 服务启动参数这一节给你可以直接复制运行的配置。先看 Transformers 路径的完整加载脚本我把它拆成 tokenizer 初始化、模型加载、编码函数三块方便你嵌入自己的项目。# embed_transformers.py # Requires transformers4.51.0 import torch import torch.nn.functional as F from torch import Tensor from transformers import AutoTokenizer, AutoModel MODEL_PATH Qwen/Qwen3-Embedding-0.6B # 可替换为本地路径 def last_token_pool(last_hidden_states: Tensor, attention_mask: Tensor) - Tensor: left_padding (attention_mask[:, -1].sum() attention_mask.shape[0]) if left_padding: return last_hidden_states[:, -1] else: sequence_lengths attention_mask.sum(dim1) - 1 batch_size last_hidden_states.shape[0] return last_hidden_states[ torch.arange(batch_size, devicelast_hidden_states.device), sequence_lengths ] def get_detailed_instruct(task_description: str, query: str) - str: return fInstruct: {task_description}\nQuery:{query} # 初始化 tokenizerpadding_side 必须是 left tokenizer AutoTokenizer.from_pretrained(MODEL_PATH, padding_sideleft) model AutoModel.from_pretrained(MODEL_PATH) model.eval() # 如果有 GPU把模型搬过去 device cuda if torch.cuda.is_available() else cpu model.to(device) def encode(texts, is_queryFalse, taskNone, max_length8192): if is_query and task: texts [get_detailed_instruct(task, t) for t in texts] batch_dict tokenizer( texts, paddingTrue, truncationTrue, max_lengthmax_length, return_tensorspt, ) batch_dict.to(model.device) with torch.no_grad(): outputs model(**batch_dict) embeddings last_token_pool(outputs.last_hidden_state, batch_dict[attention_mask]) embeddings F.normalize(embeddings, p2, dim1) return embeddings if __name__ __main__: task Given a web search query, retrieve relevant passages that answer the query queries [ get_detailed_instruct(task, 中国的首都是哪里), get_detailed_instruct(task, 解释一下重力), ] documents [ 中国的首都是北京。, 重力是一种使两个物体相互吸引的力它赋予物理对象重量并负责行星绕太阳的运动。, ] input_texts queries documents embeddings encode(input_texts) scores (embeddings[:2] embeddings[2:].T) print(scores.tolist())这段脚本跑出来应该是一个 2x2 的相似度矩阵对角线上的值明显高于非对角线。如果你看到对角线和非对角线差不多大概率是 padding_side 设错了或者忘了做归一化。再看 vLLM 路径。vLLM 的优势是批量推理吞吐高而且可以起一个 OpenAI 兼容的服务让其他语言的服务直接调。启动参数这块--task embed是关键告诉 vLLM 这是嵌入模型而不是生成模型。# 启动 vLLM 嵌入服务 python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen3-Embedding-0.6B \ --task embed \ --dtype auto \ --max-model-len 8192 \ --gpu-memory-utilization 0.85 \ --port 8000如果你要用 Python 脚本直接调 vLLM 的 LLM 类配置如下# embed_vllm.py # Requires vllm0.8.5 import torch from vllm import LLM def get_detailed_instruct(task_description: str, query: str) - str: return fInstruct: {task_description}\nQuery:{query} task Given a web search query, retrieve relevant passages that answer the query queries [ get_detailed_instruct(task, 中国的首都是哪里), get_detailed_instruct(task, 解释一下重力), ] documents [ 中国的首都是北京。, 重力是一种使两个物体相互吸引的力它赋予物理对象重量并负责行星绕太阳的运动。, ] input_texts queries documents model LLM(modelQwen/Qwen3-Embedding-0.6B, taskembed) outputs model.embed(input_texts) embeddings torch.tensor([o.outputs.embedding for o in outputs]) scores (embeddings[:2] embeddings[2:].T) print(scores.tolist())vLLM 的model.embed()返回的向量默认已经做了归一化所以直接点积就是余弦相似度。但如果你走 OpenAI 兼容接口返回的向量需要自己确认是否归一化不同版本行为可能有差异。注意vLLM 启动时如果报显存不足先把--gpu-memory-utilization降到 0.7 试试或者换更小的模型。--max-model-len设太大也会吃显存按实际文本长度需求调。4. 验证请求与成功结果向量维度、归一化与召回效果怎么测配置跑通只是第一步你得验证输出是不是对的。我一般分三个动作查维度、验归一化、测召回。第一个动作查向量维度。0.6B 模型输出 1024 维4B 是 2560 维8B 是 4096 维。如果你拿到的维度不对说明模型加载错了或者 pooling 逻辑有问题。# 验证维度 emb encode([测试文本]) print(向量维度:, emb.shape) # 期望 torch.Size([1, 1024])第二个动作验归一化。归一化后的向量 L2 范数应该接近 1。如果范数明显偏离 1说明 F.normalize 没生效或者 vLLM 版本行为不一致。import torch norm torch.norm(emb, p2, dim1) print(L2 范数:, norm.tolist()) # 期望接近 [1.0]第三个动作测召回效果。构造一组 query 和候选文档看正确文档的相似度是不是排在第一。下面这个例子用中文测试因为 Qwen3 Embedding 的中文能力是重点。task Given a web search query, retrieve relevant passages that answer the query queries [ get_detailed_instruct(task, 如何用 Python 读取 CSV 文件), ] documents [ 使用 pandas 的 read_csv 函数可以读取 CSV 文件。, 今天天气不错适合出门散步。, Python 的 csv 模块也提供了读取 CSV 的能力。, 机器学习模型需要大量数据进行训练。, ] input_texts queries documents embeddings encode(input_texts) scores (embeddings[:1] embeddings[1:].T).squeeze() for doc, score in zip(documents, scores.tolist()): print(f{score:.4f} {doc})预期结果是第一条和第三条的分数明显高于第二条和第四条。如果排序不对检查一下 query 有没有加 instructiondocuments 是不需要加 instruction 的这个不对称设计是 Qwen3 Embedding 的特点。vLLM 服务的验证可以用 curlcurl http://localhost:8000/v1/embeddings \ -H Content-Type: application/json \ -d { model: Qwen/Qwen3-Embedding-0.6B, input: [中国的首都是哪里, 中国的首都是北京。] }返回的 JSON 里 data 数组每个元素有 embedding 字段长度应该是 1024。如果返回 404检查--task embed有没有加如果返回 400检查 input 格式是不是数组。MRL 截断验证也值得做一下。取 1024 维向量的前 256 维重新归一化再算相似度看排序是否基本保持。如果排序变化很大说明你的任务对维度敏感不建议截断太多。# MRL 截断验证 full_emb encode(input_texts) trunc_emb full_emb[:, :256] trunc_emb torch.nn.functional.normalize(trunc_emb, p2, dim1) scores_trunc (trunc_emb[:1] trunc_emb[1:].T).squeeze() print(截断后分数:, scores_trunc.tolist())5. 本篇常见错排查401、local proxy failed、reading choices 与 OAuth 报错这一节把我在接入过程中遇到的真实报错和排查路径列出来你大概率会碰到其中几个。报错一401 Unauthorized。这个通常出现在你通过 API 网关调用嵌入服务时。如果你用的是自建 vLLM检查有没有配--api-key如果走的是托管服务检查 Key 有没有过期、有没有拼错。TaoToken 的 API Key 在控制台生成格式是一串以 sk- 开头的字符串。401 的另一个常见原因是 Base URL 写错了比如把/v1漏了或者多写了。报错二local proxy failed。这个报错一般出现在你本地起了代理但代理没通或者环境变量里残留了 HTTP_PROXY/HTTPS_PROXY 指向一个已经关掉的端口。排查方法是先unset HTTP_PROXY HTTPS_PROXY再重跑。如果你确实需要走网络中间层确认端口和协议对得上。报错三Error reading choices 或 reading choices 相关。这个报错在 vLLM 的 OpenAI 兼容接口里比较常见通常是因为你调的是/v1/chat/completions但模型是嵌入模型返回结构里没有 choices 字段。嵌入模型应该调/v1/embeddings。另一个可能是 vLLM 版本和模型不匹配升级到 0.8.5 以上。报错四OAuth 相关报错。如果你在 Claude Code 或类似工具里配置嵌入服务可能会碰到 OAuth token 失效的提示。这类工具通常需要三件套Base URL、API Key、Model ID。Base URL 填https://taotoken.net/apiKey 填控制台生成的Model ID 填Qwen/Qwen3-Embedding-0.6B或你实际用的模型名。三件套缺一个都会报鉴权失败。报错五向量维度对不上。比如你期望 1024 维但拿到 4096 维说明加载的是 8B 模型而不是 0.6B。检查 MODEL_PATH 有没有指错。反过来如果拿到 768 维可能是加载了别的模型。报错六相似度全是 0 或全是 1。全是 0 通常是归一化后向量点积但向量本身是零向量检查输入文本是不是空字符串。全是 1 可能是所有文本被 padding 成了同一个向量检查 padding_side 和 attention_mask。提示排查顺序建议从「模型加载日志 → tokenizer 配置 → 输入文本 → 输出维度 → 相似度」逐层往下不要一上来就怀疑模型本身。6. 语义一致 CTA把 Qwen3 Embedding 接进你的检索链路到这里你已经有了一个能跑的嵌入服务。下一步是把它接进实际链路。如果你只是验证模型效果可以直接用模型对话页面快速试几条文本看看相似度排序符不符合直觉。如果你要长期跑编码任务或者搭 Agent 的检索层建议走 Coding Plan把嵌入服务和重排序服务组合起来粗召回加精排的架构比单用嵌入的召回质量高不少。接入的时候记住三件套Base URL 用https://taotoken.net/apiAPI Key 在控制台的 API Keys 页面生成Model ID 按你实际部署的模型填。文档页有完整的接口说明和参数列表遇到不确定的字段先去文档查一遍比在代码里试错快。最后说一个实用技巧嵌入模型和重排序模型的 instruction 要分开写。嵌入模型的 query instruction 描述的是「检索任务是什么」重排序模型的 instruction 描述的是「判断文档是否满足查询」。两者不要混用混用会导致分数分布偏移。我一般把 instruction 定义成常量放在配置文件里方便统一管理和 A/B 测试。