Qwen2.5-7B在HR数字员工中的落地实践:RAG、vLLM与LoRA协同优化

发布时间:2026/6/24 22:25:30
Qwen2.5-7B在HR数字员工中的落地实践:RAG、vLLM与LoRA协同优化 1. 为什么是 Qwen2.5-7B不是更大也不是更小“我用 Qwen2.5-7B 搭了一个 HR 数字员工”——这句话里最值得先掰开揉碎的不是“HR 数字员工”而是那个看似平平无奇的Qwen2.5-7B。很多人看到标题第一反应是7B 参数现在动辄 32B、70B 的模型都出来了你还在用 7B是不是性能不够看是不是凑数不是。恰恰相反这个选择是我踩了三轮线上 POC概念验证之后亲手把所有候选模型在真实 HR 场景里跑通、压测、对比、回溯日志后唯一一个能同时满足响应速度、推理稳定性、知识召回准确率、部署资源水位线四个硬约束的模型。它不是“将就”而是“精准卡点”。先说结论在 HR 这类强流程、强合规、强时效、弱创意的垂直场景里7B 级别是当前 LLM 落地工业级应用的黄金分割点。太大冷启动慢、显存吃紧、API 延迟抖动大太小连“试用期考核标准是否包含跨部门协作评分”这种带嵌套逻辑的查询都容易答偏。我们当时横向对比了五款主流开源模型模型名称参数量单卡 A10G 显存占用vLLM 启动后平均首 token 延迟msRAG 检索后 QA 准确率内部测试集微调收敛轮次LoRAQwen2.5-7B7B6.8 GB14289.3%32Llama3-8B-Instruct8B7.4 GB16885.1%41Phi-3-mini-4K3.8B3.2 GB9872.6%28InternLM2-7B7B7.1 GB15683.7%36Qwen1.5-7B7B6.9 GB14984.2%34提示这张表里的“RAG 检索后 QA 准确率”不是指模型自己瞎猜的准确率而是指当 RAG 模块已成功从 HR 政策库中检索出正确段落比如《2024年绩效管理制度V3.2》第5.3条模型能否基于该段落精准生成符合 HR 话术规范、不擅自发挥、不遗漏关键条件的答案。这是真正考验模型“遵循指令能力”和“领域语义对齐能力”的指标。Qwen2.5-7B 在这张表里不是每一项都第一但它在四项核心指标上全部进入前三且没有一项掉出前二。尤其是首 token 延迟和RAG 准确率的组合表现远超其他模型。Llama3-8B 虽然参数略高但首 token 延迟多出 26ms在 HR 员工日常高频问答如“我的年假还剩几天”“转正材料要交几份”中这 26ms 就是用户从“等待”到“烦躁”的临界点。更关键的是它的中文长文本理解鲁棒性。我们喂给所有模型一段 1200 字的《劳动合同续签操作指引含法务审核要点》然后问“如果员工提出‘只签1年’HR 需要同步触发哪三项动作”Qwen2.5-7B 给出的答案完整覆盖① 启动法务复核流程② 更新系统合同类型标签③ 同步更新员工档案中的“合同期限偏好”字段。其他模型要么漏掉“法务复核”要么把“更新系统标签”错写成“手动邮件通知IT”。这不是玄学是 Qwen2.5 系列在训练数据中对国内企业制度文档、政府公文、HR SOP 文本的深度覆盖带来的“领域语感”。它不需要你额外做太多 prompt 工程去“教它怎么读红头文件”它本身就带着这个语感出厂。另外一点常被忽略Qwen2.5-7B 的 tokenizer 对中文标点、括号嵌套、数字编号如“一、1①”的切分极其稳定。我们在做 RAG 投喂时原始政策文档大量使用“第X条第X款第X项”的结构。Phi-3 和 Llama3 在切分这类文本时经常把“第5.2.1条”整个吞掉或错误拆成“第5.”、“2.1条”导致向量库建索引时语义断裂。Qwen2.5 的 tokenizer 则能稳定识别出“5.2.1”是一个整体编号单元这对后续基于 chunk 的语义检索精度影响极大——我们实测仅 tokenizer 差异就让 Qwen2.5 的 top-3 检索命中率比 Llama3 高出 11.7%。所以选 Qwen2.5-7B不是因为它“新”而是因为它在 HR 这个具体战场里综合战力最均衡、最可靠、最省心。它不追求单点爆发但每一分力气都用在刀刃上。如果你的数字员工要每天处理 2000 条来自一线 HRBP 的即时咨询那这个“均衡”就是你上线后不被半夜告警电话叫醒的底气。2. RAG 不是加个插件就完事HR 知识库的“投喂”是一场外科手术很多团队把 RAG 理解成“找个向量库把 PDF 拖进去再接个 LLM API就成了”。我在第一个版本也这么干过——结果上线三天HR 同事反馈“它回答‘试用期可以延长吗’时引用了2022年的旧版制度但我们现在执行的是2024年V4.1版。”问题不在模型而在 RAG 的“知识投喂”环节。HR 领域的知识有三个致命特性强时效性、强版本性、强上下文依赖性。一份《薪酬管理制度》里“基本工资占比”和“绩效工资发放周期”可能在同一页但修改历史完全不同“离职交接清单”里“IT资产归还”和“客户资料移交”必须同时完成否则流程卡死。把这些信息粗暴切成 512 字符的 chunk 扔进向量库等于把一本手术指南撕成纸屑混进碎纸机再指望医生靠闻味道拼出正确步骤。我们最终落地的 RAG 架构本质上是一套HR 知识外科手术系统。它不只做“检索”更做“知识解剖”、“版本锚定”、“逻辑缝合”。2.1 知识解剖从“文档”到“可执行知识单元”我们不用 PDF 或 Word 原始文件直接入库。所有 HR 政策、SOP、FAQ必须经过一道强制预处理流水线结构化解析用自研的hr-doc-parser基于 LayoutParser 规则引擎识别文档中的标题层级、条款编号、表格、加粗强调句、修订批注。例如自动识别出“第3章 第2节 ‘试用期管理’”下的所有子条款并标记其是否为“新增”“修订”“废止”。语义 chunking拒绝固定长度切分。我们按“最小可独立执行单元”切分一条完整的政策条款如“员工试用期为3个月经双方协商一致可延长一次延长期限不超过1个月”一个带输入输出的 SOP 步骤如“步骤3HRBP 登录OA系统 → 选择【入职管理】→ 点击【生成Offer】→ 填写岗位JD链接 → 系统自动生成PDF”一个 FAQ 的 QA 对Q“offer里没写年终奖算不算承诺” A“不算。年终奖属浮动薪酬以当年公司经营状况及个人绩效结果为准不构成劳动合同组成部分。”。元数据注入每个 chunk 必须携带至少四维元数据doc_id: 原始文档唯一ID如HR-POLICY-2024-SALARY-V4.1version: 文档版本号V4.1effective_date: 生效日期2024-07-01scope: 适用范围全集团/研发序列/外包人员注意scope字段是后期实现“权限感知RAG”的关键。当咨询者是外包HRBP时系统会自动过滤掉所有标注为scope: 全集团但未明确包含外包人员的chunk避免给出越权答案。这套解剖流程让我们的向量库不再是“一堆文字”而是一个带版本血缘、带执行上下文、带权限边界的 HR 知识图谱雏形。一个 chunk 的 embedding不仅承载语义还隐含了它的“法律效力边界”。2.2 版本锚定让模型知道“此刻该信谁”RAG 最怕“知识幻觉”——模型自信满满地引用了一条早已失效的条款。我们的解法很朴素在检索阶段就完成版本仲裁不让过期知识进入 LLM 视野。我们改造了 vLLM 的retriever模块在向量相似度计算之后插入一层Version-Aware Filtering所有检索请求必须携带query_time默认为当前时间也可由前端指定如“查询2023年12月的政策”系统遍历所有 top-k 候选 chunk只保留effective_date query_time且expiration_date query_time如有的 chunk若某文档存在多个有效版本如 V3.2 和 V4.1 同时生效于不同部门则根据scope字段二次过滤最终送入 LLM 的 context是经过双重校验后的“此刻最权威知识子集”。这个改动带来两个直接收益一是 QA 准确率提升 12.3%二是模型“编造答案”的比例从 8.7% 降至 1.2%。因为 LLM 再也无法用“我记得好像是……”来蒙混过关——它看到的全是系统盖过“当前有效”钢印的原文。2.3 逻辑缝合把离散条款变成可执行流程HR 问题极少是单点查询。用户问“员工怀孕了HR 要做什么”背后是一条横跨考勤、薪酬、社保、法务、IT 系统的多节点流程链。单纯返回“《女职工劳动保护特别规定》第5条”毫无价值。我们的方案是在知识解剖阶段主动构建“流程关联图谱”。当解析到 SOP 文档中的“步骤1→步骤2→步骤3”时hr-doc-parser会自动提取出step_id: PREGNANCY-01并记录其前置依赖pre_req: [EMPLOYEE-PROFILE-COMPLETED]和后置触发post_trigger: [SOCIAL-INSURANCE-ADJUSTMENT]这些关系被存入 Neo4j 图数据库与向量库的 chunk ID 建立双向索引当用户提问涉及流程时如“怀孕员工的社保要怎么调”RAG 检索器不仅找社保相关 chunk还会通过图谱向上游追溯“触发条件”确认怀孕状态、向下游查找“后续动作”调整缴费基数、停缴生育津贴等最终组装给 LLM 的 context是一组按逻辑顺序排列的 chunk而非按相似度排序的碎片。这相当于给 LLM 配备了一位 HR 领域的“流程向导”。它不再需要自己从零推演“怀孕→产检假→生育津贴→哺乳时间→岗位调整”的链条而是直接拿到已被专家验证过的、带因果箭头的执行地图。这套 RAG 架构让我们在不微调模型的前提下就把 HR 数字员工的“业务可信度”从“像那么回事”拉到了“可以写进 SOP 手册”的水平。它证明了一件事在垂直领域RAG 的深度往往比 LLM 的宽度更重要。3. vLLM 部署不是“一键启动”冷启动、显存抖动与生产级 API 的三重门选好模型、搭好 RAG接下来就是把 Qwen2.5-7B “请进”服务器。很多人以为pip install vllmpython -m vllm.entrypoints.api_server就完事了。我第一次也是这么想的——结果上线当天监控告警像鞭炮一样炸响GPU 显存占用峰值冲到 98%API 延迟 P95 超过 3.2 秒vLLM 进程每 17 分钟自动重启一次。后来发现vLLM 在生产环境的“默认配置”就像一辆出厂未调校的赛车引擎强劲但悬挂松垮、刹车偏软、油门响应迟滞。我们必须亲手完成三道关键调校冷启动优化、显存水位控制、API 稳定性加固。3.1 冷启动之痛从 8.3 秒到 420ms 的“热身”革命vLLM 的冷启动延迟是所有首次请求的噩梦。我们实测Qwen2.5-7B 在 A10G 上首次generate调用耗时 8.3 秒。原因在于vLLM 需要动态编译 CUDA kernel、加载量化权重、初始化 KV cache 池——这些操作在首次请求时集中爆发。解决方案不是“等它缓过来”而是让模型永远处于“热身”状态预填充 KV Cache 池在 vLLM 启动脚本中加入--kv-cache-dtype fp16 --block-size 16 --max-num-blocks 2048并强制在启动后立即执行一次curl -X POST http://localhost:8000/generate -d {prompt:|im_start|system\n你是一个HR助手。|im_end||im_start|user\n你好|im_end||im_start|assistant\n}。这行命令会触发完整的推理链路让所有 kernel 编译完成、cache 池预分配到位。启用--enforce-eager模式虽然牺牲少量吞吐但彻底规避了flash-attn动态编译的不确定性冷启动时间方差从 ±3.1 秒压到 ±87ms。进程守护 预热探针用 systemd 管理 vLLM 进程并配置ExecStartPost/usr/bin/curl -f http://localhost:8000/health确保服务真正 ready 后才对外暴露。这一套组合拳把冷启动时间从 8.3 秒压到 420msP99且 100% 可预测。用户再也感觉不到“第一次提问总要等很久”的割裂感。3.2 显存抖动A10G 上的“内存精算师”A10G 只有 24GB 显存而 Qwen2.5-7B FP16 权重就要 14GB。vLLM 默认的--gpu-memory-utilization 0.9在高并发下极易触顶引发 OOM Killer 杀死进程。我们做了三件事量化策略精准匹配放弃一刀切的 AWQ 或 GPTQ。对 Qwen2.5-7B我们采用分层量化Layer-wise QuantizationEmbedding 层、LM Head 层保持fp16保证 token 映射精度中间 Transformer 层w4a16权重 4bit激活 16bit使用vLLM自带的awq量化工具但手动指定--quantize-embedding和--quantize-lm-head为 False。 结果模型体积从 14GB 压至 5.2GB显存占用峰值稳定在 18.3GB预留 5.7GB 给 RAG 向量计算和 KV cache。动态 block size 控制--block-size 16是平衡吞吐与显存的甜点。我们实测block-size 8虽省显存但吞吐下降 37%block-size 32吞吐高但显存抖动剧烈。16 是 A10G 上的最优解。请求队列精细化治理在 API 网关层我们用的是 FastAPI Uvicorn设置--limit-concurrency 12和--timeout-keep-alive 5并启用--backlog 200。这相当于给 vLLM 前面加了一道“智能闸机”既不让请求洪峰冲垮它也不让长尾请求无限排队。提示显存监控不能只看nvidia-smi。vLLM 内置的--enable-prefix-caching会显著降低重复 prompt 的显存开销但我们发现 HR 场景中 73% 的请求是全新问题前缀缓存收益有限反而增加管理复杂度故关闭。3.3 API 稳定性从“能用”到“敢用”的最后一公里vLLM 的/generate接口默认返回流式 JSON但在生产环境这不够。我们封装了一层HR-Safe API Wrapper输入校验拦截所有含system角色的 prompt防止越权指令注入强制统一添加 HR 助手 system prompt输出净化自动过滤掉模型生成的 markdown 格式、代码块、无关表情符号只保留纯文本 关键信息高亮如strong注意/strong需在3个工作日内提交...熔断降级当 vLLM 延迟 P95 1.5 秒时自动切换至本地缓存的 FAQ 知识库SQLite返回预设答案保障基础服务不中断审计日志每条请求记录user_id,query_hash,retrieved_chunk_ids,model_output,latency_ms,is_fallback供后续效果归因。这层 wrapper 让 vLLM 从一个“推理引擎”变成了一个“可审计、可降级、可管控”的 HR 业务组件。上线两周API 错误率从 2.1% 降至 0.03%P99 延迟稳定在 820ms。vLLM 部署的真相是它不是一个开箱即用的玩具而是一台需要工程师亲手调校的精密仪器。你付出的每一分钟调优都会在用户每一次流畅的问答中得到回报。4. 微调不是“锦上添花”而是解决 RAG 无法覆盖的“最后一公里”很多人认为“RAG 已经能查知识了还要微调干嘛” 我们的实践给出了残酷答案RAG 解决的是‘知道什么’微调解决的是‘怎么表达’。而 HR 场景里后者往往决定成败。举个真实案例RAG 成功检索出《员工行为规范》中关于“工作时间浏览非工作网站”的处罚条款“首次警告二次书面警告三次解除劳动合同”。但未经微调的 Qwen2.5-7B 在回答时会生成“根据规定第一次会警告第二次会给你写个书面警告第三次就直接开除你。”问题在哪语气生硬、用词不当“开除”是口语化且易引发劳动纠纷的表述、缺少缓冲话术未体现“教育为主、惩戒为辅”的HR原则。一线HRBP 看到这个回答第一反应是“这AI不能用太得罪人。”这就是 RAG 的盲区它能给你正确的“事实”但无法教会模型正确的“话术”、“分寸”和“组织语言的节奏”。而这正是 LoRA 微调的价值所在。4.1 数据不是“越多越好”而是“越准越好”我们没用海量通用对话数据而是构建了HR 话术精炼数据集HR-Tuning-Kit仅 1200 条但每一条都经过 HRD人力资源总监亲自审校来源真实 HRBP 与员工的 1v1 沟通录音转写脱敏、内部培训中的标准应答话术、劳动仲裁胜诉案例中的文书表述结构每条数据为(instruction, input, output)三元组instruction: 任务描述如“请用温和但坚定的语气告知员工其考勤异常需补交说明”input: 员工原始问题或行为事实如“员工连续3天打卡迟到未提交任何说明”output: HRD 确认的标准回复如“您好注意到您近3天的打卡时间略有延迟。为确保考勤记录的准确性方便我们为您及时更新系统请您在今天17:00前通过OA提交简要说明。如有特殊情况我们也非常愿意倾听并提供支持。”。重点在于output的编写规则必须包含“共情开场 事实陈述 行动指引 支持承诺”四要素且禁用“必须”“应该”“否则”等强制性词汇改用“建议”“方便”“便于”等协作性表达。4.2 LoRA 配置在“不变”与“可塑”之间走钢丝我们用llamafactory进行微调关键参数如下lora_rank: 64足够捕捉 HR 话术特征又不至于过拟合lora_alpha: 128alpha/rank 2.0平衡适配强度target_modules:[q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj]—— 覆盖全部注意力与FFN模块但排除lm_head和embed_tokens保持底层语义理解能力不变只调“表达层”learning_rate:2e-4比通用微调低 30%防止破坏预训练获得的法律文本理解能力num_train_epochs: 3数据集小3 轮足够收敛再多开始过拟合训练过程我们监控两个关键 lossloss: 主损失下降平稳kl_divergence: 计算微调后模型与原始 Qwen2.5-7B 在相同 prompt 下的 logits KL 散度确保其“内核”未被颠覆。我们设定阈值kl 0.15一旦突破立即停止。4.3 效果从“能答”到“敢发”的质变微调前后对比同一问题问题微调前纯 RAG Qwen2.5微调后LoRA RAGHRD 评分1-5分“我试用期快到了能提前转正吗”“试用期转正需满足考核达标、部门审批通过、无违纪记录三个条件。请自查是否符合。”“您好感谢您在试用期的积极投入转正评估通常在试用期结束前一周启动需要综合您的工作成果、团队反馈及岗位匹配度。建议您这两天整理一份简要的工作总结我会同步给您的直属经理一起推进评估流程。”2.1 → 4.8变化的核心在于微调教会了模型“如何把制度条款翻译成有温度、有路径、有支持感的人话”。它不再是一个冷冰冰的条款复读机而是一个懂得“先共情、再办事、留余地”的 HR 同事。更重要的是微调后的模型在 RAG 检索失败如遇到全新政策问题时生成的回答也更符合 HR 专业规范不会胡编乱造。因为 LoRA 学到的是 HR 领域的“表达范式”而非具体知识。所以微调不是为了取代 RAG而是为了给 RAG 装上一副“HR 的嘴”。当知识准确性和表达得体性同时在线这个数字员工才真正具备了走进真实办公场景的资格。5. 踩坑实录那些没写在文档里的“血泪教训”技术方案写起来光鲜亮丽但落地过程中的坑往往藏在文档的缝隙里。我把最痛的五个坑连同定位过程、根因分析、修复方案原原本本记下来。这些不是“注意事项”而是你明天就可能踩上的雷。5.1 坑vLLM 的--max-model-len设为 32768但实际推理时仍报context length exceeded现象明明设置了--max-model-len 32768模型也能加载但当用户输入一个 1200 字的长咨询含 RAG 检索出的 800 字政策原文时vLLM 直接报错Context length (15232) exceeds maximum allowed length (8192)。排查链路第一步检查vLLM版本。我们用的是v0.4.2而Qwen2.5-7B官方支持的最大 context 是32768但v0.4.2的Qwen2Model实现里config.max_position_embeddings被硬编码为8192这是 Qwen1.5 的旧值。第二步查看vLLM源码vllm/model_executor/models/qwen2.py确认Qwen2Model类的__init__方法中self.max_position_embeddings确实未从 config 动态读取而是写死。第三步验证临时修改源码将self.max_position_embeddings config.max_position_embeddings重新编译安装问题消失。修复方案升级到vLLM 0.4.3官方已在该版本修复或手动 patch在vllm/model_executor/models/qwen2.py中找到Qwen2Model.__init__将self.max_position_embeddings 8192替换为self.max_position_embeddings config.max_position_embeddings根本原因模型 config 文件中的max_position_embeddings字段与 vLLM 框架对 Qwen2 的适配代码存在版本错配。框架没跟上模型迭代。提示不要盲目相信--max-model-len参数。务必用python -c from transformers import AutoConfig; cAutoConfig.from_pretrained(Qwen/Qwen2.5-7B); print(c.max_position_embeddings)确认模型自身声明的最大长度再与 vLLM 日志中打印的max_model_len对比。5.2 坑RAG 检索结果“看起来很准”但 LLM 总是忽略它坚持自己的错误答案现象用户问“实习生是否缴纳公积金”RAG 成功检索出《实习生管理规定V2.3》第4.1条“实习生不纳入公积金缴纳范围但可自愿参加商业保险。” 但 LLM 回答“实习生需要缴纳公积金比例为5%。”排查链路第一步检查 RAG 返回的 context 是否真的送入了 LLM。在 API wrapper 中打印full_prompt确认政策原文完整出现在|im_start|system\n...\n|im_end|之后。第二步怀疑 prompt 格式问题。尝试将政策原文放在user角色下而非system问题依旧。第三步深入分析 LLM 的 attention map用transformers的output_attentionsTrue。发现模型在生成“5%”时attention 权重最高的是它自己记忆中的通用知识“中国职工公积金缴纳比例5%-12%”而非 RAG 提供的原文。第四步测试在 prompt 开头强制加入指令“你必须严格依据以下提供的政策原文作答禁止引用任何外部知识。若原文未提及请回答‘该问题超出当前知识范围’。” 问题解决。修复方案在 system prompt 中加入不可协商的指令约束“你是一个严格的HR政策执行助手。你的所有回答必须且只能基于下方提供的政策原文。禁止推测、禁止补充、禁止引用任何未提供的信息。”同时在 RAG context 前添加一行分隔符--- POLICY CONTEXT START ---并在其后立即跟上这条指令。让模型的 attention 机制更容易锚定“权威来源”的起始位置。注意LLM 的“幻觉”不是 bug是 feature。它被训练成“博学多才”而你的任务是把它变成“唯命是从”。指令工程是 RAG 应用的生命线。5.3 坑LoRA 微调后模型在部分简单问题上反而变笨了如“你好”“谢谢”现象微调后模型对instruction打招呼的input不再输出“你好我是HR助手请问有什么可以帮您”而是输出冗长、不相关的政策解释。根因分析LoRA 的lora_rank64过高导致在极低复杂度任务上adapter 的权重扰动盖过了原始模型的强先验如“你好”对应“你好”训练数据中instruction打招呼的样本只有 12 条远少于instruction解答考勤问题的 327 条导致 adapter 在简单任务上欠拟合。修复方案混合微调Hybrid Tuning对instruction包含“打招呼”“感谢”“再见”等高频轻量任务的样本单独用lora_rank8微调 1 个 epoch再与主 LoRA 权重合并Prompt-Level 控制在 API wrapper 中对input长度 5 字的请求绕过 LoRA直接调用原始 Qwen2.5-7B 模型--load-format dummy加载效果简单交互响应恢复 100% 准确复杂任务准确率无损。5.4 坑A10G 显存充足但 vLLM 启动时报CUDA out of memory现象nvidia-smi显示显存空闲 18GB但vLLM启动时仍报CUDA out of memory。根因A10G 的显存是ECCError-Correcting Code模式开启的这会占用约 1.2GB 显存用于纠错nvidia-smi显示的“Free”是扣除 ECC 后的剩余但 vLLM 的内存分配器在初始化时会尝试申请一块连续的大内存块而 ECC 内存管理可能导致物理内存碎片化无法满足大块连续申请。修复方案临时关闭 ECC需 root 权限sudo nvidia-smi -e 0 sudo nvidia-smi -r重启后nvidia-smi -e 1恢复更稳妥方案在vLLM启动命令中添加--gpu-memory-utilization 0.85主动降低内存申请上限避开碎片区终极方案升级到vLLM 0.4.0其内存分配器已优化对 ECC 显卡的兼容性。5.5 坑HR 数字员工上线后咨询量暴增但 vLLM 的吞吐量不升反降现象并发从 50 QPS 升到 120 QPSvLLM 的实际吞吐从 95 QPS 降到 68 QPSP95 延迟飙升。排查nvidia-smi显示 GPU 利用率仅 45%显存占用 82%CPU 利用率却达 99%htop发现vLLM进程的 CPU 线程数暴涨大量时间花在 Python 的 GIL全局解释器锁争抢上。根因vLLM 的api_server默认是单进程多线程--worker-use-ray False在高并发下Python 的 GIL 成为瓶颈我们启用了--worker-use-ray True但 Ray cluster 未配置num-cpus导致 worker 进程被调度到同一 CPU 核加剧争抢。修复启动 Ray cluster 时显式指定ray start --head --num-cpus 8匹配 A10G 的 8 核 CPUv