记录一下部署vLLM过程中踩到的坑

1. 部署一个vLLM服务 前一段手上拿到一批昇腾 910C,考虑到可以给大家部署一些vLLM服务来用,本来以为就是按照vLLM的官方教程一步一步走完就行了,事实上也确实是这样的,一步一步走完了流程,但是能用和好用确实是两回事情,后面为了vLLM好用踩了不少坑。 先说一下我们用到的模型的情况,经过对kimi-k3、glm-5.2、glm-5.3-flash、deepseek-v4-flash、deepseek-vision等多个模型的测试,在推理性能、占用资源数、模型能力之间取了一个平衡,最终选择部署deepseek-v4.1-flash(dsv4.1-flash),上下文选择了一个比较适中的范围:600k,同时开启了dspark加速。 2. 多实例负载均衡 最先遇到的问题是单实例服务能力不够,最直接的想法就是搞多实例然后在new-api层做负载均衡,单手后期运维起来由于new-api没有做联动,有点运维难度,且负载均衡导致缓存命中降低,所以选择在vLLM和new-api之间加了一层router来做负载均衡。 最早的router是配置文件形态:引擎列表写死在配置里,加减引擎要改配置、重载。引擎少的时候忍忍也就过去了。 后来换了新一代动态router:引擎主动注册,自带cache-aware调度(前缀亲和,同一前缀尽量打到同一引擎)、失败重试、熔断摘除。和看门狗配合之后形成两层防御: 看门狗管"引擎挂死":真实推理探针,连续失败整组重启; Router 管"坏引擎别接流量":探活失败摘出路由,恢复后自动回归。 3. KV缓存问题 影响vLLM的服务质量TTFT的最大因素就是KV缓存,由于我想部署多个实例,导致留给KV的空间就剩下很少了,所以就需要考虑把KV卸载到DRAM或者SSD上,最开始用到的就是MemCache来做KV缓存管理。共享缓存的第一步是 MemCache:vllm-ascend的AscendStoreConnector,在本机内存里开一块池,把前缀 KV 存出去,下一个请求来了先查池。 但是在实际上线服务的时候遇到很多奇怪的问题: 存的日志一切正常,块块都写进去了; 审计计数器显示查询也在跑,exist请求源源不断,还能查到一部分key; 但命中恒为 0,TTFT 纹丝不动。 当时给它起了个名字叫"只存不取",一度怀疑池子根本没接上。真正的转机是把日志开到DEBUG重启,再加一个受控探针:灌入约50k的前缀token → 用流量把池驱逐干净 → 原样重发。 池侧逐组核对,exists全中:96/96、5/386……每个key都在; 紧接着一行 final hit=0; 请求全量冷预填。 所以不是"只存不取",是查到了也算零命中。卡点在"查到之后怎么算命中长度"。 对着源码读才发现是粒度基不一致:池里存的是32-token粒度的块哈希,而vLLM core的契约写得明明白白——查命中时调用方必须传"对齐粒度 = 哈希粒度"(源码注释原话:caller passes alignment_tokens = hash_block_size)。我们部署分支的协调器传的却是引擎块大小128。128对32,取出来的哈希和池里存的根本不是同一批值,第一块就miss,后面全部作废。 先试了一行补丁:把128改成32,第一组立刻命中12288——成功了一组。但滑窗组不支持细粒度命中(直接断言),压缩器状态组的粒度又不一样,池侧还有一层按错误粒度截断的覆盖上限。修到最后确认:这个分支(vLLM 0.23)的命中路径就是个半成品,补丁救不完。 真正的解法是上游 v0.30.0rc1 把这条路径正面重写了:查询集收窄到"可前缀缓存的组",统一调度侧和 worker 侧的命中语义。换镜像,命中日志直接出现,一行补丁都没打。顺手还赚了个惊喜:新版本把查找表挪到了主机内存,腾出来的显存全给了KV池——池容量6.88倍(98万 → 678万token),600K长文加16并发在容量上从此无障碍。 4. 共享KV池 MemCache修好之后,后续的问题也逐渐暴露出来了:池是"每节点一份"的孤立池(元服务绑在本机回环地址上),跨节点从来没有真正通过;引擎一重启,池就空。用户的文档就那几份,却要每个引擎各存一份、重启全丢、再各预填一遍。 4.1 MemCache方案 把各节点的元服务指到同一个中心点:段注册、挂载、全局rank偏移,所有前置步骤全绿。然后跨节点put,100%失败(error -1),重试的put风暴反手把引擎拖死。关掉fabric内存直传的开关复测,依旧。 4.2 Mooncake方案 换分布式KV store:master起来了、几十个段全部挂载成功、对端引擎也能连上——万事俱备。然后put计数恒为 0,设备侧内存拷贝全部超时(EE1002)。想退回普通TCP传输:vllm-ascend硬编码只认昇腾直连协议,NotImplementedError,此路不通。 4.3 问题定位 上述两个方案都是经过单实例验证过的,但是最后都卡死在KV传输上,可奇怪的是这批节点正常训练都NPU传输数据是完全没问题的,凭什么 KV 不行? ...

2026年10月28日 · 1 分钟

记录在昇腾上训练模型时踩到的一个坑

摘要:最近用 MindSpeed 预训练一个 MoE 模型,碰到了两件怪事:续训后周期性 NaN,以及每次重启 loss 都往上跳一截。查到最后,是数据侧一行原地写把缓存的 mask 模板慢慢写坏了。 1. 事情的起因 最近在用 MindSpeed 预训练一个 MoE 模型,大概用了4台机器训练一个13B左右的参数,设置的是每 1000 步存一次Checkpoint 然后就遇到两件怪事: NaN:续训跑一段时间,反向传播里突然报 found NaN in local grad norm。奇怪的是loss还没爆炸,但是梯度先坏了,重启一下续训又好了。 loss 跳升:每次重启之后的第一段日志,loss比重启前都要稍微高一点,0.35 到 0.45 之间。 单看哪一条都不像大事。跳升嘛,换个数据窗口很正常;NaN 嘛,偶发的数值意外。但这两件事都只在"重启"之后出现,就有点意思了。 2. 问题排查 一开始的怀疑清单很长:硬件、通信、精度、学习率、数据…逐个排查。 通信和硬件:小整数all_reduce之类的逐位校验跑了一遍,多种dtype、多种尺寸,还加了乱序压力,全过;训练期间的通信日志也很干净。 精度:bf16换FP32残差、改路由精度,问题依旧。 学习率:跨15倍幅度、换warmup形状,问题还是依旧。 数据内容:把挂掉那一步的原始文档离线解码出来看,就是普通文本,没毒;换个seed重建整条样本流,它还是死在进程启动之后的某个固定的位置。 排到后面就剩一条线索:死点跟"训练到第几步"关系不大,跟"这个进程启动之后吃了多少样本"关系很大。同一份配置重启一次,计数器清零,故障就得重新攒一遍才出现。 换句话说,问题不在训练动力学里,在"某个随进程增长的状态"里。范围一下小了很多。 3. 罪魁祸首 GPTDataset.__getitem__ 会缓存loss mask,但缓存的是引用。 # 屏蔽PAD,代码直接原地清零 loss_mask[labels == self._pad_token_id] = 0.0 于是每个DataLoader worker都在改同一份模板。偏偏这份数据的PAD和EOS用的是同一个token id(151643),packed之后样本里到处都是它,模板里的有效位置就这么被一点点吃掉。 造成的影响: 软污染:mask的有效token变少,训练loss的计算口径慢慢偏。污染期间loss偏低,重启重建模板之后就"跳升"——其实只是回到了正确值。 硬失败:当某个局部microbatch的mask被吃到全零,token分母为0,反向直接出非有限值。 而这份缓存是每个worker自己的,一重启就重建。所以故障严格按"进程内消费的样本数"复现,严丝合缝。 4. 实锤的证据 用原版代码、原数据和原helper,把DP32 × 2worker的64条数据流在CPU上重放了一遍:最早出现全零mask的是rank4/worker1,相对训练步1508,而历史崩溃日志里记的计数器是1507——对上了。加clone之后,那条样本的有效token是 4091、raw PAD是5,64条流逐样本和当前标签一致。 更直接的是冻结权重:同一份权重、同一批输入、同一份模型输出,只换mask,重算5120条序列的CE。 同一权重、同一模型输出 CE 均值 重建历史污染mask 1.902 当前样本的正确mask 2.351 差值 +0.449 nats 当然,这只能量化mask的测量效应,不能反推历史每一步的权重到底偏了多少。 ...

2026年10月27日 · 1 分钟

多Agent协作开发有感

摘要:用 16 个 Claude Code 实例(独立容器、独立 Gitea 身份)完成一个昇腾 910C Triton 算子数据集,观察多 Agent 的自组织、互检与平台瓶颈。 1. 引言 最近RSI相关的研究比较热门,我手上正好有一些vLLM的推理资源,平时用来做主力大模型用感觉有些慢,所以恰巧可以拿这些资源来做一些多Agent协作的研究和探索。 初步的实验发现:多Agent协作确实可以做到一些单Agent做不到的事情,但是仍然存在很大的问题,这可能也是多Agent协作还没形成非常完整的工具链形态的原因之一吧。 2. 环境准备 多Agent协作最Naive的想法就是每个Agent都有独立的任何和独立的上下文关系维护,没错,我现在也是这么干的,后端的Agent工具使用Claude Code,通过SDK把他接入群聊会话,但是在实现的路上也遇到了一些别的问题和思考: 2.1 上下文隔离 CC没有Hermes那样的独立Profile,默认情况下所有实例共用同一份配置目录。仅通过环境变量拆分工作目录,在放开权限后仍不能保证实例不会越狱。一旦某个实例的上下文被污染,并且污染被写入本地文件或Memory,很容易在实例之间连续传播。 采用的方式比较简单:1. 一个Agent一个Docker 容器;2. 容器之间共享同一份CC二进制文件;3. 工作目录、Memory、History各自独立。 这样可以保证即使某个Agent的上下文被污染,影响范围也被限制在它自己的容器内。 实测发现,污染主要发生在Memory和本地文件这条路径上。如果污染是通过Agent之间的消息传播的,其他Agent反而更倾向于质疑、复现和纠正,这一点和预期相反。 2.2 身份与权限 多 Agent 协作首先需要解决身份问题。如果所有 Agent 共用一个git身份,会出现两类问题:1. 把其他 Agent 的提交认成自己的;2. 否认自己实际做过的提交。 任务越长,这种身份错乱越严重,并且会加重幻觉。因此每个 Agent 都需要独立的 Gitea 账号、独立的 token,以及对应的 git user.name 和 git user.email。谁提交、谁评审、谁验收,必须能对应到具体身份。 2.3 平台能力 平台主要提供三类工具面(独立的运管平台): 统一MCP入口:代码提交、作业提交、知识库查询统一到一个入口; 群聊MCP网关:支持私聊、群聊和@; 项目管理工具:Issue、看板和PR,提供但不强制使用(需要把选择权交给Agent本身)。 但是实际上测试的时候发现,即便有这样的软约束,一旦Agent之间达成共识,也是按照他们的共识进行项目管理。(当然,并不是说这种方式不好,只是他们会在Git里面提交大量的实验日志和实验记录,并不太符合我的预期。) 3. 有趣的发现 3.1 自组织 向 Agent 下发一条约束:作业必须使用单机 8 卡提交。没有规定它们如何验证。 ...

2026年10月20日 · 1 分钟

文献阅读《Alibaba HPN: A Data Center Network for Large Language Model Training》

Alibaba High Performance Network (HPN) HPN介绍了一种两层的双平面网络,可以在一个Pod中接入1.5w个GPU,通常需要3层Clos架构的网络才能容纳这么多GPU HPN 提出了一种新的双 ToR 设计,以取代传统数据中心网络中的单 ToR 1. 贡献与挑战 挑战 流量模式 传统网络无法应对LLM的低熵、周期性、突发的网络特点,传统的数据中心网络一般是高熵的(传统网络中会存在百万条流,但是每条流的利用率特别低,一半小于20%) ECMP在传统的数据中心的高熵、低利用率的网络中性能表现较好,但是在LLM训练中并不是最佳的 单点故障的敏感性高 LLM训练是一个同步过程,所有GPU合作完成一系列迭代,任何一个GPU发生故障都有可能导致整个训练过程出现问题 对LLM训练影响最大的是与机架顶部(ToR)相关的单点故障,这种故障可能会影响到各种GPU 生产统计数据显示,LLM训练中出现的故障造成的损失是一般云计算故障的 20 倍 贡献 非堆叠的双层ToR网络,可以在两个交换机之间直接同步,很好的改善了大规模部署的可信度 适配最新的51.2Tbps的交换机芯片,可以将1K个GPU部署在Tier 1网络中,使96.3%的训练任务获得高性能的网络 我们的实验表明,使用HPN的LLM训练吞吐量比传统数据中心网络高14.9倍 2. 背景知识 LLM的并行策略 DP:每次迭代使用All Reduce同步计算梯度数据 PP:每次使用Send/Recv传递中间结果 TP:每次迭代使用All Reduce/All Gather同步输出和对应的梯度 LLM的流量特征 带宽利用率周期性突发 流的数量少 传统的数据中心网络不适用于LLM流量模式 LLM对单点故障的敏感性 LLM每次的Checkpoint间隔大概在2-4小时,按照一个3k张GPU的集群计算,每次失败导致的回滚都将导致大约3w美元的损失 常见的监控工具和异常分析工具并不能避免网络产生故障,根据实验表明,在NIC到ToR之间每个月大概有0.057%的Link故障率,在ToR交换机每个月的故障率大约是0.051%,即一个LLM训练集群每个月可能会产生1-2次的Crash,5k-60k次的Link故障 LLM网络架构的建设目标 规模化:达到15K个GPU的规模,未来可以扩展到100K个GPU的规模 高性能:尽可能小的网络跳数,尽可能使用GPU-GPU直达的连接 单点故障容错率:在网络拓扑层尽可能改善单点故障的容错率 3. HPN架构 分为前端网络和后端网络,前端网络主要负责例如管理、接口、存储等网络流量,后端网络负责LLM的训练时的流量 每个Host包含8个GPU,使用NVLink互联,双向带宽达到400GBps-900GBps 每个Host包含9个2x200Gbps的网卡,其中一个连接到前端网络,其余的8个与GPU直连,接入后端网络 每个网卡的2个端口分别连接至不同的ToR网络,这种双ToR设计可以避免单点故障 4. 双ToR网络 传统数据中心的NIC的两个光缆接入一个交换机成为单ToR设计 双ToR设计将NIC的两个光缆接口分别接入不同的ToR交换机中 两个接口配置相同的IP和MAC地址,即使其中一个ToR故障,另外一个依旧可以正常工作 基于以上,同一个NIC的两个网卡共享相同的Queue Pair Context ...

2024年8月28日 · 1 分钟

Llama2部署记录

1. 创建docker 1.1 docker命令 显示全部的镜像:docker images 创建一个容器:docker run ...<查看1.2节内容> 显示全部的容器:docker ps -a 启动一个容器:docker start <id> 停止一个容器:docker stop <id> 进入一个正在运行的程序:docker exec -it <id> /bin/bash 删除一个容器:docker rm <id> 1.2 创建容器 创建一个docker容器的时候需要同时挂载本地工作目录和相关的程序驱动,因此创建docker容器的命令参数较多,以下为一个创建docker容器命令的基本格式: docker run -it --ipc=host --name <name> -v <local_path>:<docker_path> \ --workdir=<docker_path> \ --pids-limit 409600 \ --privileged --network=host \ --shm-size=128G \ --device=/dev/davinci0 \ --device=/dev/davinci1 \ --device=/dev/davinci2 \ --device=/dev/davinci3 \ --device=/dev/davinci4 \ --device=/dev/davinci5 \ --device=/dev/davinci6 \ --device=/dev/davinci7 \ --device=/dev/davinci_manager \ --device=/dev/devmm_svm \ --device=/dev/hisi_hdc \ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \ -v /usr/local/Ascend/firmware:/usr/local/Ascend/firmware \ -v /usr/local/Ascend/add-ons/:/usr/local/Ascend/add-ons/ \ -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ -v /usr/local/dcmi:/usr/local/dcmi \ -v /etc/ascend_install.info:/etc/ascend_install.info \ -v /etc/bashrc:/etc/bashrc \ -p 223:223 \ -p 224:6006 \ -p 225:8080 \ -p 226:8888 \ -u root \ <image_id> /bin/bash -it:已交互方式运行容器,并分配一个终端 ...

2024年4月9日 · 5 分钟