概述
2025.6.6,通义千问团队发布了 Qwen3-Embedding 和 Qwen3-Reranker 系列。
两组模型一块训练发布,本文侧重于前者进行分析和测试。
截至目前,在 METB Leaderboard 中,以检索任务(Retrieval)进行排名,Qwen3-Embedding 位居榜首。作为参考,之前常用的 bge-m3 模型排第30位。
技术原理
技术报告标题:Qwen3 Embedding: Advancing Text Embedding and Reranking Through Foundation Models
看这个标题大概就能猜到,Qwen3-Embedding 取得比较强的结果主要得益于基座模型(Foundation Models),即 Qwen3。
1. 模型结构
Qwen3-Embedding 的模型输入输出如下,报告中,没有具体说明其内部结构,估计和 Qwen3 基座模型本身保持一致。
输入包含四部分:
- Instruciton:任务指令,比如“根据查询找到相关文档”Query:查询信息,即用户的输入内容,比如“LLM的应用”Doc:待评估的文档内容,分成两个两个部分,相关的和不相关的EOS:序列结束标记(End-of-Sequence)
2. 训练过程
采用三阶段的训练过程:
- 第一阶段:通过Qwen3生成了超大规模(1.5亿)弱监督数据,对模型进行对比学习预训练;第二阶段:基于高质量标注数据进行监督训练(SFT),具体方式是在第一阶段的数据中进行筛选,筛选出1200完对高质量数据。第三阶段:采用了一种基于球面线性插值(slerp, spherical linear interpolation)的模型合并技术,合并在微调过程中保存的多个模型。
3. 模型信息
Qwen3-Embedding系列模型共包括以下三种型号:
- Qwen3-Embedding-0.6BQwen3-Embedding-4BQwen3-Embedding-8B
不同型号模型的具体参数如下表所示。
其中,MRL Support 表示模型能够生成多粒度嵌入向量,即同一文本可以输出不同维度的嵌入表示,保持语义一致性。
Instruction Aware (指令感知)表示模型能够根据用户提供的自然语言指令动态调整其输出或行为,以适应不同的任务需求。
4. 性能评估
在MTEB-Eng(英文),CMTEB(中文),MTEB(代码)三类数据上进行实验,结果如下表所示:
从这张表中,可以读到三点信息:
- 1.同系列模型,参数量越大的嵌入模型分数越高,但 Qwen3-Embedding-0.6B 可以超过其它 7B 参数模型的性能。2.Qwen3-Embedding相比于Qwen2模型(gte-qwen)代码方面大幅提升,中英文提升的不多。3.Qwen3-Embedding-4B 相较于 0.6B,提升较为明显,但 8B 相较于4B,提升效果很小,边际效用递减**。
实验测试
榜单排名靠前就一定代表效果好吗?不一定,主要有两个原因:
- 榜单是多语言的任务的平均水平,Qwen3增强了多语言的能力,但在中文方面的表现未知。榜单的测试集是公开的,非盲测榜单,不能排除新模型对榜单数据进行针对调优。
下面不测榜单,用实际数据进行测试。
1.下载模型
在 modelscope 上下载模型,以下载Qwen3-Embedding-8B
为例,下载命令:
modelscope download --model Qwen/Qwen3-Embedding-8B --local_dir ./Qwen3-Embedding-8B
2. 统计测试
使用 vllm 框架进行推理测试,用AI生成了几个示例数据,核心任务是进行检索,看模型是否能通过相似度,在候选数据中,把最接近原义的句子找出来。
脚本如下:
import torchimport vllmfrom vllm import LLMimport timeimport psutilimport gcimport numpy as npfrom typing import List, Dictimport json# 模型路径配置models_config = { "BGE-M3": "/home/zxy/model_zoo/bge-m3", "Qwen3-0.6B": "/home/zxy/model_zoo/Qwen3-Embedding-0.6B", "Qwen3-4B": "/home/zxy/model_zoo/Qwen3-Embedding-4B", "Qwen3-8B": "/home/zxy/model_zoo/Qwen3-Embedding-8B"}def get_detailed_instruct(task_description: str, query: str) -> str: returnf'指令: {task_description}\n查询: {query}'def prepare_hard_chinese_test_data(): """准备高难度中文测试数据,包含各种语义陷阱""" task = '根据给定的搜索查询,检索最相关的段落来回答问题' # 设计8类挑战性查询 queries = [ # 1. 同音异义词混淆 get_detailed_instruct(task, '银行的利率政策对经济发展的影响'), # 2. 上下文依赖语义 get_detailed_instruct(task, '苹果公司的创新技术在手机行业的地位'), # 3. 成语典故理解 get_detailed_instruct(task, '画龙点睛在文学创作中的重要作用'), # 4. 专业术语跨领域 get_detailed_instruct(task, '神经网络在人工智能和生物学中的不同含义'), # 5. 近义词细微差别 get_detailed_instruct(task, '学习和求学在教育理念上的区别'), # 6. 反义关系理解 get_detailed_instruct(task, '保守投资与激进投资策略的根本差异'), # 7. 隐喻和比喻 get_detailed_instruct(task, '时间是金钱这一理念在现代社会的体现'), # 8. 语言风格差异 get_detailed_instruct(task, '正式场合发言与日常聊天的表达方式差异'), ] # 对应的文档包含正确答案和多个干扰项 documents = [ # 正确匹配的文档 "中央银行的货币政策工具主要包括利率调控、存款准备金率等手段,通过调节市场流动性来影响经济增长速度、通胀水平和就业状况。利率作为资金成本的重要指标,其变动直接影响投资决策和消费行为。", "苹果公司凭借其iOS系统的封闭生态和持续的技术创新,在智能手机市场占据领先地位。从iPhone的工业设计到芯片研发,苹果建立了完整的技术壁垒,影响着整个行业的发展方向。", "画龙点睛出自唐代传说,指张僧繇画龙后点上眼睛,龙便飞走。在文学创作中,这个成语比喻在关键处用上精辟的笔墨,使整篇作品更加生动传神,是创作技巧中的重要手法。", "神经网络在计算机科学中是一种模拟生物神经元连接的数学模型,用于机器学习和人工智能;而在生物学中,神经网络是指真实的神经元通过突触连接形成的信息传递系统,负责生物体的感知和控制功能。", "学习通常指获取知识和技能的过程,强调实用性和效果;而求学更侧重于追求学问的态度和精神,体现了对知识的渴望和探索精神,两者在教育理念上体现了不同的价值取向。", "保守投资策略注重资本保全,选择低风险、稳定收益的投资品种,追求长期稳健增长;激进投资策略则愿意承担更高风险,追求更大收益,投资于高风险高回报的产品,两者在风险偏好上截然不同。", "时间是金钱这一理念体现了现代社会对效率的极度重视。在商业活动中,时间成本被量化为经济价值,快节奏的生活方式使得时间管理成为个人和企业成功的关键因素。", "正式场合的发言需要使用标准的书面语,注重逻辑结构和措辞准确性,避免俚语和方言;而日常聊天更加随意自然,可以使用口语化表达、网络用语和情绪化词汇,体现了语域的不同。", # 高相似度干扰文档(语义相近但不是最佳答案) "河岸边的银行大楼里,工作人员正在处理客户的存贷款业务。银行业务的数字化转型正在改变传统金融服务模式,为客户提供更便捷的金融服务体验。", "超市里新上架的苹果品种丰富,有红富士、金帅等多个品种。这些苹果不仅口感好,营养价值也很高,富含维生素和膳食纤维,是健康饮食的重要组成部分。", "艺术创作需要技巧和灵感的结合。一幅好的画作不仅需要扎实的绘画功底,更需要创作者在关键位置运用巧妙的手法,使作品达到理想的艺术效果。", "网络连接在现代信息系统中发挥着重要作用。计算机网络通过各种协议实现数据传输,而生物体内的神经连接则负责信息的感知和处理,两者都体现了连接的重要性。", "教育的目标是培养人才。无论是学校教育还是自主学习,都要注重知识的积累和能力的提升。现代教育理念强调个性化学习和全面发展。", "投资理财需要根据个人情况制定合适的策略。不同年龄段和风险承受能力的投资者应该选择不同的投资组合,平衡收益和风险的关系。", "现代社会生活节奏加快,人们越来越重视效率。合理安排时间,提高工作效率,已经成为现代人必备的生活技能。", "不同场合需要不同的交流方式。良好的沟通能力包括能够根据场合调整自己的表达方式,既要准确传达信息,又要考虑听众的接受程度。", # 反义或对立概念的干扰文档 "央行降低利率刺激经济增长,但过度宽松的货币政策可能导致通胀风险,因此政策制定需要在促进增长和控制通胀之间找到平衡点。", "华为、小米等国产手机品牌正在快速崛起,凭借高性价比和本土化服务优势,在与苹果的竞争中逐渐缩小差距,改变着全球智能手机市场格局。", "文学创作中的败笔往往出现在细节处理不当,过度雕琢反而会破坏作品的整体美感,有时简单朴素的表达更能打动读者的心。", # 无关领域文档 "今天的天气特别好,阳光明媚,微风徐徐。这样的天气最适合户外活动,可以去公园散步或者进行体育锻炼,有益身心健康。", "中医理论认为,人体的健康与阴阳平衡密切相关。通过针灸、中药等传统治疗方法,可以调节人体机能,达到防病治病的目的。", "烹饪是一门艺术,不同的食材搭配可以创造出丰富多样的美味。中华料理博大精深,各地都有独特的风味和烹饪技巧。" ] # 正确答案索引(前8个文档对应前8个查询) correct_matches = [0, 1, 2, 3, 4, 5, 6, 7] return queries, documents, correct_matchesdef get_gpu_memory_usage(): """获取GPU显存使用情况(GB)""" if torch.cuda.is_available(): return torch.cuda.memory_allocated() / 1024 / 1024 / 1024 return0def get_total_gpu_memory(): """获取GPU总显存(GB)""" if torch.cuda.is_available(): return torch.cuda.get_device_properties(0).total_memory / 1024 / 1024 / 1024 return0def calculate_similarity_scores(queries_embeddings, docs_embeddings): """计算相似度分数""" # 标准化向量 queries_embeddings = queries_embeddings / torch.norm(queries_embeddings, dim=1, keepdim=True) docs_embeddings = docs_embeddings / torch.norm(docs_embeddings, dim=1, keepdim=True) # 计算余弦相似度 scores = queries_embeddings @ docs_embeddings.T return scoresdef warm_up_model(model, sample_texts: List[str], num_warmup: int = 2): """模型预热,排除首次推理的缓存影响""" print(f"正在进行模型预热({num_warmup}次)...") for i in range(num_warmup): _ = model.embed(sample_texts[:2]) # 只用前两个文本预热 print(f"预热 {i+1}/{num_warmup} 完成")def evaluate_model_performance(model_name: str, model_path: str, queries: List[str], documents: List[str], correct_matches: List[int]) -> Dict: """评估单个模型的性能""" print(f"\n{'='*60}") print(f"正在测试模型: {model_name}") print(f"{'='*60}") # 清理内存 gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() # 记录初始显存 initial_memory = get_gpu_memory_usage() total_memory = get_total_gpu_memory() try: # 1. 加载模型 print("正在加载模型...") load_start_time = time.time() model = LLM(model=model_path, task="embed") load_time = time.time() - load_start_time # 记录加载后显存 after_load_memory = get_gpu_memory_usage() memory_usage = after_load_memory - initial_memory memory_usage_percent = (memory_usage / total_memory) * 100if total_memory > 0else0 print(f"✓ 模型加载完成") print(f" - 加载耗时: {load_time:.2f} 秒") print(f" - 显存占用: {memory_usage:.2f} GB ({memory_usage_percent:.1f}%)") # 2. 模型预热 all_texts = queries + documents warm_up_model(model, all_texts) # 3. 测试推理速度(多次测试取平均值) print("\n正在测试推理性能...") inference_times = [] num_runs = 5 for i in range(num_runs): gc.collect() # 每次测试前清理内存 start_time = time.time() outputs = model.embed(all_texts) inference_time = time.time() - start_time inference_times.append(inference_time) # 计算速度指标 texts_per_second = len(all_texts) / inference_time print(f" 第{i+1}次: {inference_time:.3f}秒 ({texts_per_second:.1f} texts/sec)") # 计算统计指标 avg_inference_time = np.mean(inference_times) std_inference_time = np.std(inference_times) min_inference_time = np.min(inference_times) max_inference_time = np.max(inference_times) avg_texts_per_second = len(all_texts) / avg_inference_time print(f" 平均推理时间: {avg_inference_time:.3f}±{std_inference_time:.3f}秒") print(f" 处理速度: {avg_texts_per_second:.1f} texts/sec") # 4. 获取embeddings并计算相似度 print("\n正在计算相似度...") embeddings = torch.tensor([o.outputs.embedding for o in outputs]) queries_embeddings = embeddings[:len(queries)] docs_embeddings = embeddings[len(queries):] # 计算相似度分数 similarity_scores = calculate_similarity_scores(queries_embeddings, docs_embeddings) # 5. 评估准确性 print("正在评估匹配准确性...") correct_predictions = 0 similarity_details = [] for i, correct_idx in enumerate(correct_matches): if i >= len(queries): break scores_for_query = similarity_scores[i] best_match_idx = torch.argmax(scores_for_query).item() best_score = scores_for_query[best_match_idx].item() expected_score = scores_for_query[correct_idx].item() if correct_idx < len(scores_for_query) else0 is_correct = best_match_idx == correct_idx if is_correct: correct_predictions += 1 similarity_details.append({ 'query_idx': i, 'predicted_idx': best_match_idx, 'correct_idx': correct_idx, 'predicted_score': best_score, 'correct_score': expected_score, 'is_correct': is_correct, 'score_diff': best_score - expected_score }) accuracy = correct_predictions / len(correct_matches) * 100 # 计算其他指标 avg_similarity = torch.mean(similarity_scores).item() max_similarity = torch.max(similarity_scores).item() min_similarity = torch.min(similarity_scores).item() # 计算前k准确率 top3_correct = 0 top5_correct = 0 for i, correct_idx in enumerate(correct_matches): if i >= len(queries): break scores_for_query = similarity_scores[i] top_indices = torch.topk(scores_for_query, k=min(5, len(scores_for_query)))[1] if correct_idx in top_indices[:3]: top3_correct += 1 if correct_idx in top_indices[:5]: top5_correct += 1 top3_accuracy = top3_correct / len(correct_matches) * 100 top5_accuracy = top5_correct / len(correct_matches) * 100 results = { 'model_name': model_name, 'load_time': load_time, 'memory_usage_gb': memory_usage, 'memory_usage_percent': memory_usage_percent, 'avg_inference_time': avg_inference_time, 'std_inference_time': std_inference_time, 'min_inference_time': min_inference_time, 'max_inference_time': max_inference_time, 'texts_per_second': avg_texts_per_second, 'top1_accuracy': accuracy, 'top3_accuracy': top3_accuracy, 'top5_accuracy': top5_accuracy, 'avg_similarity': avg_similarity, 'max_similarity': max_similarity, 'min_similarity': min_similarity, 'embedding_dim': embeddings.shape[1], 'similarity_details': similarity_details, 'num_parameters': 'Unknown'# 需要额外获取 } # 6. 显示详细结果 print(f"\n📊 {model_name} 性能报告:") print(f"{'─' * 50}") print(f"🔧 模型信息:") print(f" - 嵌入维度: {embeddings.shape[1]}") print(f" - 加载时间: {load_time:.2f} 秒") print(f" - 显存占用: {memory_usage:.2f} GB ({memory_usage_percent:.1f}%)") print(f"\n⚡ 推理性能:") print(f" - 平均推理时间: {avg_inference_time:.3f}±{std_inference_time:.3f} 秒") print(f" - 最快推理时间: {min_inference_time:.3f} 秒") print(f" - 处理速度: {avg_texts_per_second:.1f} texts/sec") print(f"\n🎯 准确性指标:") print(f" - Top-1 准确率: {accuracy:.1f}%") print(f" - Top-3 准确率: {top3_accuracy:.1f}%") print(f" - Top-5 准确率: {top5_accuracy:.1f}%") print(f"\n📈 相似度统计:") print(f" - 平均相似度: {avg_similarity:.4f}") print(f" - 最高相似度: {max_similarity:.4f}") print(f" - 最低相似度: {min_similarity:.4f}") print(f"\n🔍 详细匹配分析:") query_categories = [ "同音异义词", "上下文依赖", "成语典故", "专业术语", "近义词差别", "反义关系", "隐喻比喻", "语言风格" ] for detail in similarity_details: status = "✅"if detail['is_correct'] else"❌" category = query_categories[detail['query_idx']] if detail['query_idx'] < len(query_categories) elsef"Q{detail['query_idx']}" print(f" {status} {category}: 预测D{detail['predicted_idx']} " f"(分数:{detail['predicted_score']:.4f}) vs 正确D{detail['correct_idx']} " f"(分数:{detail['correct_score']:.4f}) 差值:{detail['score_diff']:.4f}") return results except Exception as e: print(f"❌ 模型 {model_name} 测试失败: {str(e)}") returnNone finally: # 清理内存 if'model'in locals(): del model gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache()def print_comparison_table(all_results: List[Dict]): """打印对比表格""" ifnot all_results: return print(f"\n{'='*80}") print("🏆 模型性能对比总结") print(f"{'='*80}") # 表头 print(f"{'模型':<15} {'显存(GB)':<10} {'加载(s)':<8} {'推理(s)':<10} {'速度(t/s)':<10} {'Top-1%':<8} {'Top-3%':<8} {'维度':<8}") print(f"{'-'*80}") # 数据行 for result in all_results: print(f"{result['model_name']:<15} " f"{result['memory_usage_gb']:<10.2f} " f"{result['load_time']:<8.2f} " f"{result['avg_inference_time']:<10.3f} " f"{result['texts_per_second']:<10.1f} " f"{result['top1_accuracy']:<8.1f} " f"{result['top3_accuracy']:<8.1f} " f"{result['embedding_dim']:<8}") print(f"{'-'*80}") # 性能排名 print(f"\n🥇 性能排名:") # 按不同指标排序 metrics = [ ('速度最快', 'texts_per_second', True), ('显存最少', 'memory_usage_gb', False), ('最准确', 'top1_accuracy', True), ('加载最快', 'load_time', False) ] for metric_name, metric_key, descending in metrics: sorted_results = sorted(all_results, key=lambda x: x[metric_key], reverse=descending) print(f" {metric_name}: {sorted_results[0]['model_name']} ({sorted_results[0][metric_key]:.2f})") # 分析难点 print(f"\n🔍 难点分析:") categories = ["同音异义词", "上下文依赖", "成语典故", "专业术语", "近义词差别", "反义关系", "隐喻比喻", "语言风格"] for i, category in enumerate(categories): category_correct = sum(1for result in all_results for detail in result['similarity_details'] if detail['query_idx'] == i and detail['is_correct']) total_models = len(all_results) success_rate = category_correct / total_models * 100if total_models > 0else0 difficulty = "🔴困难"if success_rate < 30else"🟡中等"if success_rate < 70else"🟢简单" print(f" {category}: {success_rate:.1f}% {difficulty}")def main(): """主函数""" print("🚀 高难度中文语义理解模型对比测试") print(f"PyTorch版本: {torch.__version__}") print(f"vLLM版本: {vllm.__version__}") print(f"CUDA可用: {torch.cuda.is_available()}") if torch.cuda.is_available(): print(f"GPU设备: {torch.cuda.get_device_name()}") print(f"GPU总显存: {get_total_gpu_memory():.1f} GB") # 准备高难度测试数据 queries, documents, correct_matches = prepare_hard_chinese_test_data() print(f"\n📋 测试数据信息:") print(f"- 查询数量: {len(queries)}") print(f"- 文档数量: {len(documents)}") print(f"- 正确匹配数量: {len(correct_matches)}") print(f"- 测试难度: 极高 (包含8类语义挑战)") print(f"- 挑战类型: 同音异义词、上下文依赖、成语典故、专业术语、近义词差别、反义关系、隐喻比喻、语言风格") # 存储所有模型的结果 all_results = [] # 测试每个模型 for model_name, model_path in models_config.items(): result = evaluate_model_performance(model_name, model_path, queries, documents, correct_matches) if result: all_results.append(result) # 每个模型测试完后等待一下 time.sleep(2) # 显示对比结果 if all_results: print_comparison_table(all_results) print(f"\n💡 优化建议:") print(f"- 如果准确率普遍较低,说明这些语义挑战确实有效") print(f"- 可以针对表现差的类别进行专门的数据增强") print(f"- Top-3和Top-5准确率可以看出模型的召回能力") print(f"- 显存和速度数据有助于生产环境部署决策") print(f"\n✅ 高难度测试完成!")if __name__ == "__main__": main()
比较四个模型,结果如下:
模型 | 显存(GB) | 推理(s) | 速度(t/s) | Top-1% | Top-3% | 维度 |
---|---|---|---|---|---|---|
BGE-M3 | 1.06 | 0.020 | 1496.5 | 100.0 | 100.0 | 1024 |
Qwen3-0.6B | 1.12 | 0.019 | 1611.4 | 87.5 | 100.0 | 1024 |
Qwen3-4B | 7.55 | 0.073 | 412.0 | 87.5 | 100.0 | 2560 |
Qwen3-8B | 14.10 | 0.122 | 246.0 | 100.0 | 100.0 | 4096 |
结果说明,bge-m3在以上中文常见场景中,性能已足够使用,模型参数量越大,显存占用越多,速度越慢,结果不一定会更好。
因此,盯着榜单去下结论是草率的,实际的模型性能需要根据语言、任务、使用场景进行具体判断。当然,本测试用的数据比较少,结果存在偶然性,有进一步讨论空间。