ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

vLLM Multi-LoRA动态部署实战:共享基座省显存,业务切换零重启

vLLM Multi-LoRA动态部署实战:共享基座省显存,业务切换零重启 上个月我们把推理平台里六个业务线的定制模型全部收编到一套 vLLM 服务里基座是 Qwen3.5-9B六个业务各挂一个 LoRA支持动态加载、动态卸载。折腾完回头看这个方案本身不神秘但真正把它用到生产环境中间有不少文档上不会写的细节。这篇就把部署思路、技术机制、完整操作和踩坑过程都盘一遍给准备做 Multi-LoRA 动态部署的同学当个参考。先说下背景。我们当时同时接了客服、营销文案、代码问答、法务、医疗科普、报表解释六个业务线的定制需求每条业务线都有自己基于 Qwen3.5-9B 微调出来的 LoRA。最开始的部署方式非常粗暴每个业务线起一个独立的 vLLM 服务每个服务加载一份完整的 Qwen3.5-9B 权重然后挂各自的 LoRA。GPU 很快就顶不住了A100 八卡都不够分而且大部分时间利用率不到 15%。这种局面逼着我重新思考部署架构最终选择了 vLLM 官方支持的多 LoRA 方案也就是一个基座进程内同时管理多个 LoRA adapter按请求动态切换。这篇文章适合三类人一是已经用 LoRA 微调了多个模型、想省显存的人二是想搞懂 vLLM 的 LoRA 调度原理、避免被缓存问题坑到的人三是正在把多 LoRA 服务推向生产、需要处理性能调优和稳定性问题的同学。我会把原理、参数、API、坑全部串起来讲尽量做到看完就能直接动手。1. 多业务共存时的部署困境与 LoRA 共享基座的账1.1 三种方案摆在一起对比在动手之前我先把候选方案列了一遍。需要注意的是这里讨论的是“多个 LoRA 服务多个场景”的问题不是单 LoRA 微调上线。很多团队第一次遇到多条业务线同时要模型时会本能地选择最直接的方案一个业务一套模型服务。方案 A 是每个 LoRA 各部署一个完整模型服务。这个方案最省事业务隔离也最干净但资源消耗几乎是线性增长。Qwen3.5-9B 的 BF16 权重就有 18GB 左右再加上 KV cache、CUDA context、激活值一张 24GB 的卡跑一个服务都紧巴巴。六个业务就是六张卡起步如果还要做多副本保障GPU 数量直接爆炸。而且大部分业务线流量并不高很多时间的算力都在空转。方案 B 是多个 LoRA 合并在同一个模型备份里每个服务加载一个 LoRA。这种做法比方案 A 省一点模型加载内存因为几个 LoRA 可以共用同一份基座权重副本但本质上还是一个进程服务一个业务并没有解决多业务共存的问题。而且如果想要动态切换业务还得重启服务或者靠额外的路由层转发工程复杂度一点没少。方案 C 就是最终采用的 vLLM Multi-LoRA 方案。一个 vLLM 进程里加载一份 Qwen3.5-9B 权重同时注册多个 LoRA adapter请求进来时通过 LoRA 标识选择用哪个 adapter。adapter 可以启动时预加载也可以在运行过程中动态加载和卸载整个过程不需要重启服务。业务之间的显存开销只是多几个 LoRA 权重的体积几乎可以忽略。三个方案放在一起看差异非常明显维度方案 A每 LoRA 独立服务方案 B每 LoRA 单独进程方案 CMulti-LoRA 共享基座GPU 显存需求约 N 份完整模型资源约 N 份完整模型资源1 份基座 N 个 adapter业务隔离最好好中等靠路由和权限隔离新 LoRA 上线重新部署服务重新部署服务动态加载 API 即可切换 LoRA改路由/重启改路由/重启请求级切换运维复杂度高服务数量多中低集中管理适合场景大型独立业务、强隔离要求中型团队批量管理中小业务线多GPU 预算有限1.2 算清楚显存和切换成本很多第一次接触 Multi-LoRA 的人会问一个 LoRA adapter 那么小为什么需要单独部署一个服务答案是问题不在 adapter 本身而在“完整模型权重 KV cache”的开销。一个 rank16 的 LoRA adapter 权重文件通常只有几十 MB但陪它一起运行的基座模型是 9B 参数的完整权重这才是显存大头。拿我们当时的情况算一笔账Qwen3.5-9B 的 BF16 权重约 18GBKV cache 按 4K 上下文、并发 16 估算预留 6GB再加上 CUDA context 和碎片单份模型服务至少吃掉 25GB 显存。六个业务独立部署时显存需求是 6 × 25GB ≈ 150GB四张 A100 都不太够舒服地跑。换成 Multi-LoRA 共享基座基座加 KV cache 还是 25GB 左右每个 LoRA adapter 运行时代价大约 100MB 到 300MB取决于 rank 和 target modules 数量就算六个 adapter 全部加载总量也就 27GB 上下一张 32GB 的卡就能扛下来。但这里要泼一盆冷水共享基座省的是显存不是算力。六个业务请求都打到一个服务上GPU 计算压力会叠加如果总并发上去了单卡依然扛不住。所以 Multi-LoRA 适合的是“业务多、单业务流量不高”的场景而不是把高流量业务硬塞到一个进程里。我们后来给高流量业务单独留了副本低流量业务共享一套服务这样就平衡了成本和稳定性。切换成本的账也要算清楚。传统方案里业务从模型 A 切到模型 B要么改网关转发要么重启服务加载新权重冷启动时间从几十秒到几分钟不等。Multi-LoRA 方案里切换只是一个请求级的 LoRA 标识变化adapter 已经在 GPU 缓存里的话切换代价基本为零如果 adapter 不在缓存里需要从磁盘或 CPU 内存换入这个代价我会在第 2 节详细讲。2. vLLM 的动态 LoRA 机制EngineCore、Scheduler 与 Executor 的配合2.1 一次带 LoRA 的请求在 vLLM 内部怎么走理解了“为什么要共享基座”接下来要搞明白 vLLM 到底怎么实现“按请求切换 LoRA”的。这部分如果不了解后面遇到缓存性能问题会一头雾水。vLLM 在收到一个带 LoRA 标识的请求后会经过这么一条链路HTTP 入口把请求解析成带 LoRA Request 的调度任务交给 EngineCore。EngineCore 是 vLLM 的核心控制组件相当于推理服务的大脑它维护模型状态、请求队列和 LoRA 管理器。EngineCore 不会直接执行计算而是把任务交给 Scheduler 去安排。Scheduler 是整个调度流程里和 LoRA 关系最密切的部分。它会检查请求使用的 LoRA adapter 当前是否已经加载在 GPU 缓存中。如果已经加载这个请求直接进入正常的 prefill 和 decode 调度如果没有加载Scheduler 需要先把目标 adapter 换入显存如果有必要还会驱逐一部分暂时不用的 adapter。这个换入换出的过程发生在每次调度之前所以频繁切换 LoRA 会直接影响调度效率和吞吐。调度完成后真正的矩阵计算发生在 Executor 层。Executor 负责驱动 GPU kernel把基座模型的权重和 LoRA 的增量权重组合起来执行 attention 和 MLP 计算。在单卡环境下这个过程相对简单在多卡张量并行环境下LoRA 权重还要考虑分片问题我后面会单独讲。2.2 LoRA 缓存为什么“动态”不是免费的vLLM 实现多 LoRA 动态加载的核心是 LoRA 管理器本质上是一个带容量上限的缓存系统。启动时注册的所有 LoRA 是“已知 adapter”但“知道”不代表“常驻显存”。vLLM 会按最近使用情况维护一个 LRU 风格的缓存把当前最热的 adapter 保留在 GPU 显存里冷门的 adapter 要么不加载、要么被换出到 CPU 内存或磁盘。这个设计非常聪明但也有代价。第一个代价是首次访问某个冷 LoRA 时请求需要等待 adapter 从磁盘或 CPU 内存加载到 GPU延迟会从几十毫秒级别跳到几百毫秒甚至更高这就是典型的“冷启动延迟尖刺”。第二个代价是当缓存容量不够时Scheduler 要执行驱逐操作如果被驱逐的 adapter 马上又被访问就会出现反复换入换出的“抖动”整卡吞吐都会受到影响。所以 vLLM 的参数里才会有--max-loras和--max-cpu-loras两个维度。--max-loras控制 GPU 上最多同时保存多少个 LoRA 适配器--max-cpu-loras控制 CPU 内存里最多缓存多少个 adapter 作为快速换入的后备。CPU 内存的加载速度虽然不如显存但比磁盘快很多把冷门 adapter 放 CPU 缓存是一个比每次都读盘稳妥得多的策略。我在生产里遇到的一个教训是一开始把--max-loras设得很小以为动态加载功能会自动处理一切结果热门 adapter 被频繁驱逐服务吞吐忽高忽低。后来把热门业务的 adapter 数量调大同时把冷门业务放到 CPU 缓存抖动才消失。2.3 加载和卸载 API 进入控制流的路径动态部署里最吸引人的能力是“运行时不重启地加载新 LoRA”。vLLM 从某个版本开始提供了两个专门接口POST /v1/load_lora_adapter和POST /v1/unload_lora_adapter。这两个接口的本质是把 LoRA 管理操作注入 EngineCore 的控制流而不是简单地在文件系统里复制文件。调用 load 接口时EngineCore 会读取请求体里的 adapter 路径检查 PEFT config 与基座模型的兼容性然后把 adapter 信息加入 LoRA 管理器的注册列表。此时如果 GPU 缓存还有空间vLLM 可以立即把 adapter 加载进显存如果没有空间它会先把 adapter 放到 CPU 缓存等待后续请求触发换入。unload 接口则相反它会把指定 adapter 从注册列表和缓存里移除。这个移除是幂等的重复卸载不会报错所以脚本里可以大胆调用。这个机制让我在部署新业务时轻松很多。以前新业务上线要排期、改配置、重启服务现在只要把训练好的 adapter 目录放到约定路径调用一次 load 接口然后让请求带上新的lora_name就能立刻切换。3. 搭建部署环境模型目录、LoRA 产物与 vLLM 安装3.1 GPU、驱动与 vLLM 版本怎么选先说硬件。Qwen3.5-9B 这个规模的模型如果要跑 4K 上下文和正常并发建议单卡显存不低于 24GB。A10、A30、L20、A100、H100 都可以我们实际用的是 32GB 卡。如果显存只有 16GB可以上 AWQ 量化版本但要注意量化模型搭配 LoRA 时需要确认 vLLM 对量化 LoRA 的支持情况不同版本差异很大不要想当然。vLLM 版本选择是我最想强调的一点。这个项目我们最开始图新鲜装了当时最新的 vLLM结果跑起来性能还不如老版本后来回退到稳定的 0.8.x 系列才恢复正常。在生产环境里vLLM 的版本策略应该是“选一个社区验证过的稳定版固定住不要随意升级”。每次升级前要在测试环境把多 LoRA 场景完整回归一遍特别是动态加载接口和缓存行为否则很容易踩到“升级后性能反而下降”的坑。Windows 用户要注意vLLM 对原生 Windows 的支持非常有限很多 CUDA kernel 编译和依赖链在 Windows 上容易出问题。我们内部统一用 Linux 容器Windows 开发机一般通过 WSL2 跑。如果你必须在 Windows 环境试验建议直接用 Docker 镜像或者 WSL2别在原生环境硬刚。3.2 Qwen3.5-9B 基座与 LoRA adapter 的目录规范模型和 adapter 的目录规划看起来是小事但多业务协作时一个混乱的目录结构会引发一堆问题。我们的规范是/data/models/ Qwen3.5-9B/ # 基座模型HF 格式 config.json model.safetensors.index.json model-00001-of-0000X.safetensors tokenizer.json tokenizer_config.json ... /data/loras/ customer-service/ # 业务线一 adapter_config.json adapter_model.safetensors marketing-copy/ # 业务线二 adapter_config.json adapter_model.safetensors code-assistant/ legal/ medical-science/ report-analysis/LoRA adapter 的产物格式必须保持标准 PEFT 格式也就是至少包含adapter_config.json和adapter_model.safetensors。adapter_config.json里的base_model_name_or_path字段要特别注意我见过不少同事训练完 adapter 后换了机器部署base model 路径对不上vLLM 加载时直接报错。这种情况可以核对模型路径或者编辑 config 文件里的base_model_name_or_path字段让它和实际基座路径一致。另外adapter 的rank和target_modules在部署时无法随意更改。比如训练时用的 rank32部署时 vLLM 会自动读取 config不需要手动指定但如果文件损坏或者字段缺失加载就会失败。建议所有 adapter 在上传前跑一个简单的校验脚本用peft库加载一遍确保文件完整。3.3 安装验证这条链路安装 vLLM 本身不复杂但我建议在干净的 Python 3.10 环境里安装避免依赖冲突。基础命令是pip install vllm如果需要固定版本可以指定版本号安装。装完后先跑一个最小的加载验证确认 CUDA、模型和 LoRA 链路是通的python -c import vllm; print(vllm.__version__)接下来用官方 OpenAI 兼容接口做一次最简单的推理不带 LoRA确认基座服务正常curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen3.5-9b, messages: [{role: user, content: 你好}], max_tokens: 32 }能正常返回内容说明环境基本没问题。然后再进入多 LoRA 配置阶段。4. 实操启动服务、动态加载与按请求切换 LoRA4.1 启动命令和每个参数真实含义先给一个可以被直接复用的启动命令然后逐个参数解释它的意义vllm serve /data/models/Qwen3.5-9B \ --served-model-name qwen3.5-9b \ --tensor-parallel-size 1 \ --enable-lora \ --max-loras 12 \ --max-cpu-loras 24 \ --gpu-memory-utilization 0.92 \ --lora-modules \ customer-service/data/loras/customer-service \ marketing-copy/data/loras/marketing-copy \ code-assistant/data/loras/code-assistant--enable-lora是总开关不加这个参数后面所有 LoRA 相关配置都不会生效。--lora-modules用来指定启动时预注册的 adapter格式是“名称路径”可以有多个。预注册的 adapter 会立即变得可用但未必全部常驻显存加载策略还是交给缓存管理器决定。--max-loras表示 GPU 上最多同时缓存多少个 LoRA adapter。这个值不是越大越好每个 adapter 都会占用显存设太大会挤占 KV cache 的空间反而降低并发能力。--max-cpu-loras则表示在 CPU 内存中缓存多少个 adapter 作为后备用于快速换入。--gpu-memory-utilization控制给 vLLM 预留的显存比例。多 LoRA 场景下我建议留一点余量不要设到 0.98否则 LoRA 换入时可能因为显存不足出现驱逐困难或者 OOM。实测 0.9 到 0.94 之间比较舒服。如果你的服务是多卡张量并行还要注意--fully-sharded-loras参数。它在多 GPU 场景下会把 LoRA 权重也做分片避免每张卡都保存一份完整 adapter。单卡场景不需要这个参数。4.2 运行时加载/卸载 LoRA 的接口调用服务启动后新增一个业务线的 LoRA 不需要重启。把 adapter 目录放到约定位置然后调用 vLLM 的动态加载接口curl -X POST http://localhost:8000/v1/load_lora_adapter \ -H Content-Type: application/json \ -d { lora_name: medical-science, lora_path: /data/loras/medical-science }返回成功后就注册到当前服务了。卸载同理curl -X POST http://localhost:8000/v1/unload_lora_adapter \ -H Content-Type: application/json \ -d { lora_name: report-analysis }这里要强调一个容易误解的点动态加载接口只是把 adapter 加入注册表和缓存如果 GPU 缓存空间不足新加载的 adapter 可能处于“已注册但未换入”的状态第一次实际请求它时依然有加载延迟。所以不要以为调用完 load 接口就万事大吉新业务上线最好先发一两个预热请求让 adapter 真正进入显存。请求时指定使用哪个 LoRA在 vLLM 的 OpenAI 兼容接口里通过在请求体中携带 LoRA 名称字段来实现。具体字段名在不同版本里略有差异比较通用的是lora_name。示例curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen3.5-9b, lora_name: customer-service, messages: [{role: user, content: 我的订单两天了还没发货}], max_tokens: 128 }如果是 Python 客户端用 OpenAI SDK 也能传扩展字段因为 vLLM 的接口兼容 OpenAI 协议但额外字段在标准 SDK 里可能不直接暴露。我们实际用的是自定义 httpx 请求来带lora_name这样最可控。4.3 从“冷启动”到“业务可用”的一次完整演练最后把完整流程串一遍。假设我现在要上线一个新业务“留学咨询”adapter 已经训练好放在/data/loras/study-abroad。第一步先确认服务健康状况和 GPU 余量curl http://localhost:8000/health第二步加载新 adaptercurl -X POST http://localhost:8000/v1/load_lora_adapter \ -H Content-Type: application/json \ -d {lora_name: study-abroad, lora_path: /data/loras/study-abroad}第三步发两个预热请求让 adapter 换入显存curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen3.5-9b, lora_name: study-abroad, messages: [{role: user, content: hi}], max_tokens: 1 }第四步做一轮自动化冒烟测试用几条真实业务语料验证生成质量确认没问题后把流量切过来。第五步如果有旧业务下线调用 unload 接口把不用的 adapter 卸掉释放显存和缓存槽位。这个流程看起来平淡无奇但实际价值很大从拿到新 adapter 到业务全量可用只需要十几分钟而且全程不需要重启推理服务其他业务的请求完全不受影响。5. 性能调优与生产环境踩坑记录5.1 显存、并发和缓存参数到底怎么调多 LoRA 服务调优的核心不是单次推理速度而是缓存命中率和请求调度效率。我在 vLLM 的 metrics 里主要盯三类指标总吞吐、排队时延、以及和 LoRA 相关的缓存命中情况。如果发现大量请求都落在同一个热门 LoRA 上说明缓存策略压力很小核心瓶颈在模型计算本身这时候应该考虑加副本或者换更强 GPU。如果发现请求分散在多个 LoRA 上而且频繁出现冷 adapter 访问那就该调大--max-loras给热门 adapter 腾出更多常驻空间或者调大--max-cpu-loras加快冷 adapter 的换入速度。如果显存还有富余适当调高--max-loras通常能显著减少延迟尖刺。并发参数也要配合调整。多 LoRA 场景下服务可能同时收到来自多个业务的请求相同 LoRA 的请求可以高效批处理不同 LoRA 的请求则要看缓存是否命中。如果同时发起大量不同 LoRA 的并发请求Scheduler 会被迫做大量换入换出反而拖累整体吞吐。我们在生产里给低优先级业务做了限流避免它们一次涌入太多不同 LoRA 的请求把缓存冲垮。5.2 冷 LoRA 触发加载时出现的延迟尖刺这是多 LoRA 部署里最典型的问题。我遇到过的情况是服务跑得很稳突然某个请求延迟从 100ms 涨到 800ms然后再恢复。排了很久发现不是网络问题而是这个请求访问了一个冷门的 LoRAvLLM 正在从磁盘加载 adapter。这种延迟尖刺对普通聊天场景还能接受但对要求低延迟的业务是致命的。解决思路有几个一是给所有 adapter 做启动预热让它们在服务刚启动时就进入 CPU 或 GPU 缓存二是对低延迟业务单独部署副本不和其他冷门业务混跑三是把 adapter 文件放到高速 SSD 上至少别用网络存储来加载 adapterIO 延迟会被放大很多。我后来写了一个简单的预热脚本遍历所有已注册 LoRA各发一个max_tokens1的请求确保 adapter 进入缓存。每次服务启动、每次批量加载新 adapter 后都跑一遍延迟尖刺基本消失。5.3 升级 vLLM 后发现性能不升反降这个坑必须单独拎出来说。有一次我们发现升级到某个新版本后同样负载下吞吐量下降了 30% 左右。一开始怀疑是参数配置变化反复对比后发现新版本在多 LoRA 场景下的缓存策略和调度行为有调整而我们的 adapter 数量比较多正好踩中了性能回退区间。最后我们做的不是去深挖源码而是立即回退到稳定版本然后在测试环境把升级验证流程固定下来统一压测脚本、固定并发模型、对比升级前后 P99 延迟和吞吐。以后再升级版本第一步先跑这套基线不达标就直接放弃。多 LoRA 场景对版本非常敏感不建议在生产环境尝鲜。5.4 几个折腾过的报错和排查方法这里列几个实际遇到过的报错都是比较有共性的第一个是 adapter 加载时报 PEFT config 解析错误。原因通常是adapter_config.json里字段缺失或者格式不对比如缺少base_model_name_or_path。排查方法是用peft库直接加载 adapter看报错信息是不是一致然后把 config 文件补全。第二个是加载 adapter 时提示 rank 或 target modules 不匹配。这类报错属于训练和部署环境不一致比如训练代码里用了自定义 target modules部署配置里却按默认 8 个线性层去算。解决方法是保证训练脚本和部署环境使用同一份 PEFT 配置训练完把adapter_config.json原封不动地作为部署依据。第三个是某些新模型在 vLLM 启动时报类似ValueError: model class ... not found的错误。这个报错本质是 vLLM 内置的模型注册表里没有这个新模型类需要升级 vLLM 版本或者通过模型实现扩展机制注册。Qwen3.5-9B 本身没有问题但如果你换了更新的模型一定要先确认 vLLM 版本是否支持否则启动阶段就会直接挂掉。第四个是动态加载接口返回成功但请求时提示 LoRA 未找到。这种情况多半是请求体里的lora_name和加载时注册的名称不一致或者是请求没有正确传 LoRA 标识字段。检查日志里的 LoRA 解析过程通常一眼就能定位。6. 落地到多个业务线的路由、监控与后续扩展6.1 用一层简单网关做按业务路由vLLM 本身只负责“请求带 LoRA 标识”不负责“业务流量怎么路由到对应 LoRA”。如果让业务方直接构造带lora_name的请求容易出错也容易泄露内部目录命名。我们在前面加了一层非常轻的网关服务负责从业务请求里识别业务线 ID然后自动加上对应的 LoRA 标识转发给 vLLM。网关逻辑很简单核心就是一张配置表LORA_ROUTE { customer_service: customer-service, marketing: marketing-copy, code: code-assistant, legal: legal, medical: medical-science, report: report-analysis, } def route_request(biz_id: str, payload: dict): lora_name LORA_ROUTE[biz_id] payload[lora_name] lora_name return forward_to_vllm(payload)这样业务方只需要使用自己熟悉的业务 ID完全不用关心 LoRA 的内部名称切换配置也只改网关上的映射表不需要动业务代码。6.2 监控该盯哪些指标多 LoRA 服务比单模型服务多了一类需要重点监控的指标LoRA 缓存的命中与驱逐情况。如果命中率低说明 adapter 换入换出太频繁用户体验会明显变差。我们会在监控面板里同时展示 GPU 利用率、请求排队时延、平均吞吐、以及 LoRA 相关计数器的变化趋势。另外要监控每个 LoRA 单独的请求量和错误率。一个业务线的异常请求不应该影响其他业务线但共享一个进程意味着一旦 GPU OOM 或 Scheduler 卡死所有业务都会受影响。所以在网关层做限流和熔断比在 vLLM 层做更有效。6.3 从 9B 迁到更大模型以及为什么仍用 vLLM这套多 LoRA 架构并不绑定 Qwen3.5-9B 这个具体模型。如果后续要换更大的基座比如 Qwen3.6-27B 或者类似的 27B 级别模型部署流程几乎可以平移主要变化是显存预算和量化选择。27B 的 BF16 权重接近 54GB单卡搞不定需要走多卡张量并行这时要把--tensor-parallel-size调大并开启--fully-sharded-loras让 LoRA 权重也跟着分片。有人问过为什么不用 sglang 或者其他推理框架。sglang 的调度性能也很强对 LoRA 也有支持但 vLLM 在多 LoRA 场景下胜在生态最完整动态加载/卸载接口是现成的OpenAI 兼容接口直接可用社区踩坑资料多团队内部也更容易维护。另外一个现实原因是我们的网关、压测脚本、监控告警都已经基于 vLLM 的接口形态做好了迁移其他框架意味着整套工具链重写收益不大。框架选型最终还是要看团队对现有体系的掌控力和实际业务需求。最后分享一个小技巧如果业务方改 LoRA 很频繁别每次都手动调 load/unload 接口可以把 adapter 的“名称、路径、服务实例、上线状态”存在一张配置表里写一个几十行的同步脚本监听配置表变化自动完成加载、预热、卸载。我们的新业务上线从十几分钟缩短到了几分钟而且人工操作带来的错误率几乎降为零。这个思路回头可以单独写一篇这里先埋个伏笔。
返回列表