
上个月一个 agent 项目需要在本地做决策推理我手头只有一张 6GB 显存的卡却要同时跑 Kev 和 Laya 两个开源决策模型。Kev 负责在几条候选策略里拍板Laya 负责对拍板结果做二次校验串起来就是一条简单的决策流水线。我原以为装两个模型不过是两条命令的事结果从晚上八点折腾到凌晨三点翻车翻出了完整的三连。这篇记录是让 DeepSeek 从我的散装笔记里整理出来的脉络比我当时脑子里清楚得多坑的位置也重新标了一遍。1. 为什么非得把两个决策模型塞进同一张 6GB 卡1.1 Kev 和 Laya 到底是谁Kev 和 Laya 是社区里最近讨论度不低的两个轻量化决策模型。Kev 是 7B 参数这一档擅长从多个候选方案里选最优策略输入是带上下文的决策请求输出是一个带理由的动作序列Laya 是更小的 3B 档做的事是“校验”——把 Kev 的输出翻来覆去看一遍判断有没有明显漏洞、是不是答非所问、策略之间是否矛盾。这种“决策器 校验器”的结构在本地 agent 里很常见。单个模型自己决策很容易出现自信满满的错误加一个轻量校验模型在背后把关能把明显幻觉滤掉一大半。实际跑起来我的调用顺序也很简单先请求 Kev 拿到决策结果再把结果连同原始问题丢给 Laya拿到“通过”或“打回”的结论。两个模型一个管进攻一个管防守正好凑成完整流程。1.2 为什么不上云端 API非要本地跑说实话这个流程图如果丢给云端接口半小时就能跑通。但我这个项目的数据是用户操作记录里面有大量内部行为特征不适合出本机只能本地推理。另外决策流水线每次调用要传一长串上下文本地跑没有按 token 计费的压力反复调试也不用担心费用爆炸。低延迟反而是没那么重要的点——单轮决策允许几秒延迟但数据隐私和可中断性要求本机独立完成。所以需求就很明确一张 6GB 卡两个模型一个完整流程。这不是“能不能跑”的问题而是必须在物理限制下找到一个可持续运行的方案。1.3 6GB 显存意味着什么先说个直观概念一个 7B 模型的 fp16 权重大约是 14GB6GB 显存连一半都装不下必须量化。量化到 Q4 之后Kev 的 GGUF 文件大约 4.1GBLaya 大约 1.9GB光看文件总和已经接近 6GB。但模型不是“文件放到显存里”就行运行时要申请 KV cache 来存上下文状态要留出计算图缓冲还要给 CUDA context 留底。这些杂项加起来6GB 卡真正能用来放权重的空间一般只有 4.8GB 到 5.5GB。所以问题的本质不是“两个模型加起来 6GB 所以能装”而是“装完之后还有没有空间做计算”。我第一晚就栽在这个认知错位上后面会详细展开。总之想在 6GB 卡上跑决策流水线最忌讳的就是只算权重文件大小。2. 部署前算账显存预算、量化档位与工具链取舍2.1 量化档位的真实开销我先把两个模型的量化候选都看了一圈。以 Kev 的 7B 为例常见 GGUF 档位大概是档位文件大小可运行显存估算决策能力保留程度Q8_0约 7.2GB6GB 直接不够很高Q5_K_M约 4.8GB够但留给上下文极少高Q4_K_M约 4.4GB勉强建议缩减上下文中高Q4_K_S约 4.1GB合适配合 2K 上下文可用中高Q3_K_M约 3.3GB宽裕但决策质量肉眼可见下滑中低Laya 作为 3B 模型就从容得多Q4_K_S 大概 1.9GBQ3_K_S 只有 1.4GB 左右。这里我最想提醒的是不要盯着“权重能不能放下”看要盯“运行态显存峰值”。一个 7B Q4 模型真实运行起来显存占用大约是权重体积加 200 到 400MB 的计算缓冲再往上加 KV cache。如果 Kev 的上下文窗口开 8192KV cache 可能再吃掉 600MB 到 1GB合计就奔着 5GB 去了这时候再想加载 Laya哪怕 Laya 只要 1.9GB也只能看着 CUDA OOM 报错。2.2 工具链选型Ollama、llama.cpp 还是 vLLM三套主流方案我都试过分别说说它们在这个场景下的脾气。Ollama 的优势是安装即用拉模型、跑服务一条命令搞定Api 兼容 OpenAI 风格写代码非常顺手。但它的缺点是调度策略对低显存卡不够透明——Ollama 倾向于把当前模型尽量留在显存里如果你想在同一张卡上两个模型轮转要么等它自动卸载要么手动强制 keep_alive0中间会有明显卡顿。而且 Ollama 在显存不足时会把层悄悄搬到内存速度断崖式下降中间发生了什么还需要查日志才知道。vLLM 的 PagedAttention 对高并发、长上下文非常有效但在 6GB 卡上属于高射炮打蚊子。它的显存预分配策略、CUDA 版本要求和启动参数复杂度都更适合 A100 这类大卡。我试过一次光是配置 KV cache 的预留比例就和显存计算纠缠了半天最终放弃了。llama.cpp 自带的 llama-server 才是这个场景最合适的工具。它能精确控制 -ngl多少层放 GPU、-c上下文长度、-np并行槽位模型加载和卸载由进程生命周期决定想 A 模型跑完杀掉、再拉 B 模型只要在脚本里控制进程就行。GGUF 本身就是它的主格式不用转来转去。2.3 6GB 卡上必须有的“显存预算表”最终我按照如下公式做预算可用显存 6GB - CUDA context 与驱动预留 - 推理计算缓冲 - KV cache其中 CUDA context 和驱动预留大约 300 到 500MBllama.cpp 的计算缓冲每个实例约 200 到 400MB。把这些减掉之后真正能分配出去的显存大概在 5.0GB 到 5.3GB 之间。如果 Kev 和 Laya 都常驻显存Kev Q4_K_S 约 4.1GBLaya Q4_K_S 约 1.9GB加一起就 6GB 了再叠加杂项开销必然爆。所以我把目标从“两个同时常驻”改成“两个都能被快速调度、轮流驻留”。这个思路转变是后面能跑通的关键。3. 第一晚翻车完整链路三次 OOM 与一次假死3.1 第一次翻车Kev 加载到一半直接 OOM起因是默认上下文 8192我一开始太乐观直接用 llama-server 加载 Kev命令参数看了文档但没细想llama-server -m Kev-Q4_K_S.gguf -ngl 32 -c 8192 --port 8081前半段模型权重加载没问题大概到 80% 的时候进程被杀日志里出现 CUDA out of memory。我还以为是权重文件占用被低估了后来才反应过来-c 8192 意味着要准备 8192 个 token 的 KV cache在 7B 模型上这部分大概需要 700MB 到 1GB 显存。加载权重时显存已经接近临界再申请 KV cache 就爆了。不要以为“上下文开小一点会丢信息开大一点最多慢一点”在低显存卡上上下文越大越可能连模型都加载不出来。教训第一条显存预算里必须给 KV cache 单独列一行不能只算权重。3.2 第二次翻车两个 llama-server 实例同时跑显存直接叠加Kev 的上下文改成 2048 之后单跑已经很稳。我就开始想当然既然 Kev 占 4.1GB那再开一个 llama-server 跑 Laya剩下的 1.9GB 应该够吧实际上完全不够。两个 llama-server 实例各自有独立的 CUDA context 和计算缓冲Kev 实例实际占用约 4.5GBLaya 实例实际占用约 2.3GB加起来 6.8GB远超物理显存。启动 Laya 的那个瞬间整个窗口像是被冻结然后两个进程相继报错退出连 GPU 驱动都短暂无响应。这里要说明一个反直觉的点llama.cpp 的显存占用不是“权重文件大小 一点点缓冲”这么简单。每个 CUDA 实例都会分配独立的上下文区、cuBLAS workspace 和 CUDA context这些都是看不见的隐性开销。两个模型并行加载时必须把每份实例的“管理开销”都算进去而不是只做简单的文件大小加法。第二次翻车给我的教训更狠在一个进程里跑两个模型是不行的但开两个进程更不行。除非显存余量非常充足否则同一张卡同时驻留两个模型本质是在和 CUDA 的底层资源管理打架。3.3 第三次翻车把 Laya 的部分层扔给 CPU速度变成了 PPT两次 OOM 之后我开始想“曲线救国”把 Laya 的 24 层只放 16 层到 GPU剩下 8 层交给 CPU 计算。llama-server -m Laya-Q4_K_S.gguf -ngl 16 -c 4096 --port 8082这次启动是成功了显存占用也控制住了但实测生成速度只有 4 到 6 token/s。Laya 生成一次校验结果平均要 200 到 400 token意味着单次校验请求要等 40 到 90 秒用户根本没法等。为什么这么慢因为每层的张量需要从 GPU 显存拷贝回 CPU 内存做矩阵运算算完再拷回去PCIe 带宽被反复打满。CPU 本身算力也不够所以-ngl 减半等于两端同时吃力。低显存卡不是不能 offload但 offload 的比例必须控制在 10% 以内否则交互体验基本报废。3.4 假死事件第二个请求进来时整个服务像崩了一样第三晚我还遇到一起“假死”Kev 正在生成结果时我又发了一个请求给 Laya。由于 Laya 还没从显存里加载请求一直挂在服务端调用端也傻等直到超时。我一度以为是进程崩溃最后发现是 llama-server 默认串行处理请求新请求排队但没有任何日志反馈。这个不算技术坑但影响判断——本地多模型调度时必须设计显式的请求队列和超时机制不能靠客户端傻等。后面我会给出一套简单的串行调度方案。4. 最后一晚的组合拳降上下文、降并发、轮流驻留4.1 重新分配显存Kev 用 Q4_K_SLaya 也用 Q4_K_S但不同时驻留经过三次翻车我重新做了规划。首先明确一个原则单个决策流程本身是天然串行的Kev 决策完Laya 才能校验两个模型根本不需要同时响应。既然不需要并发推理那就没必要让它们都留在显存里。我的最终预算表是这样的项目分配Kev 权重Q4_K_S4.1GBKev 的 2048 上下文 KV cache约 250MBKev 实例计算缓冲约 300MB系统与显存管理预留约 400MBKev 单实例总占用约 5.0GBLaya 权重Q4_K_S1.9GBLaya 的 4096 上下文 KV cache约 200MBLaya 单实例总占用约 2.4GB两个模型单独运行时都非常宽裕但“同时驻留”加起来就需要 7.4GB物理上不可能。所以我的方案是让它们轮流使用显存——Kev 用的时候 Laya 的进程不启动Laya 用的时候 Kev 的进程被杀掉。单个模型加载只需要 8 到 15 秒在决策流水线场景里完全可接受。4.2 轮流驻留的调度脚本一个简单的进程管理器实现方式很简单我用 Python 写了 60 行左右的 wrapper核心逻辑是import requests import subprocess import time MODELS { kev: { cmd: [llama-server, -m, Kev-Q4_K_S.gguf, -ngl, 32, -c, 2048, --port, 8081], port: 8081, }, laya: { cmd: [llama-server, -m, Laya-Q4_K_S.gguf, -ngl, 32, -c, 4096, --port, 8082], port: 8082, }, } def is_alive(name): try: r requests.get(fhttp://127.0.0.1:{MODELS[name][port]}/health, timeout1) return r.status_code 200 except Exception: return False def switch_to(name): for other in MODELS: if other ! name and is_alive(other): requests.post(fhttp://127.0.0.1:{MODELS[other][port]}/shutdown, timeout5) time.sleep(2) if not is_alive(name): subprocess.Popen(MODELS[name][cmd], stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) for _ in range(60): if is_alive(name): break time.sleep(1) def call_model(name, payload): switch_to(name) r requests.post(fhttp://127.0.0.1:{MODELS[name][port]}/v1/chat/completions, jsonpayload, timeout180) return r.json()这里有几个细节值得说先检查另一个模型是否存活存活就先 shutdown避免两个实例抢显存。切换时等待健康检查通过最多等 60 秒。如果 15 秒还没就绪日志里会有具体的 llama-server 报错方便排查。调用超时设成 180 秒把生成速度和排队时间都留够。对应到决策流程就是kev_result call_model(kev, {messages: [{role: user, content: decision_prompt}]}) laya_result call_model(laya, {messages: [{role: user, content: verify_prompt(kev_result)}]})因为两次调用天然串行轮转开销只有中间一次模型加载的等待整个决策流水线从用户视角看就是“先出方案再出校验结论”不会觉得两个模型在抢资源。4.3 为什么不在同一个进程里做热切换可能有人会问Ollama 可以注册两个模型调用时自动切换为什么不用我试过。Ollama 在 6GB 卡上确实能管理两个模型但它的调度策略更倾向于把模型尽可能留在显存里。显存不够时Ollama 会把部分层挤到内存默认行为不易控制而且切换两个不同模型时的触发时间是黑盒。相比之下自己写进程管理虽然“土”但每一步发生什么都在掌控范围内。低显存环境最怕的不是慢而是“不知道它会怎么处理资源”所以我宁可自己接管。llama-server 本身不支持一个进程加载两个模型也没有热切换模型的能力所以上述 wrapper 就是最直白的解法。它的缺点是切换模型时要杀掉进程重来但换来的是每次推理的显存状态完全干净不会有残留上下文或缓存导致的不稳定。4.4 并行度调节绝不开第二个推理槽还有一个容易忽略的参数是 -np。llama-server 支持多个并行槽位但我在这台机器上只敢设 -np 1也就是同一时间只处理一个请求。理由很朴素多个并发请求会增加 KV cache 的总量哪怕上下文平均长度不高预留空间也会成倍增长。6GB 卡的空间每一兆都金贵并发一开可能一个模型就又把显存吃满了。如果未来需要更高吞吐我会优先考虑把 Laya 换成更小的量化档或者干脆把 Kev 的上下文再压到 1024而不是开并行槽位。5. 被量化“变笨”的决策模型怎么判断还能不能用5.1 一个典型翻车Q3_K_M 的 Kev 选了明显离谱的策略显存问题解决后我开始关注更隐蔽的问题量化是否破坏了模型的决策能力。决策任务和闲聊不同闲聊里输出稍微不通顺还能读决策任务如果策略选错下游执行就会出大问题。我用一个简单的“三选一”测试题给 Kev 试跑。场景是系统资源紧张时有三个候选策略——A 是减少任务并发数并优先关键任务B 是立即停止所有非关键任务C 是完全不调整并继续接受新任务。Q4_K_S 的 Kev 稳定选 A解释也很清晰但 Q3_K_M 的 Kev 有 3 次选了 C而且给出的理由是“需要最大限度保持系统的开放性”这就是典型的量化损伤。超过一定程度的量化会让模型的“逻辑深层结构”坍塌尤其是策略排序这类需要多因素权衡的任务表现得最明显。Q4_K_S 和 Q5_K_M 在决策任务上的表现差距很小但 Q3 和 Q4 之间有一条肉眼可见的界线。所以我的建议是决策模型至少保留 Q4_K_S不要为了省几百兆显存降到 Q3。5.2 用“决策一致性”测试替代 Benchmark决策模型不像翻译模型有明确的 BLEU 分数也不像分类模型有精度指标。我最关心的其实是两个东西固定输入下是否能稳定复现同一个决策以及决策理由是否自洽。所以我设计了一个非常土但有效的测试准备 5 个典型决策场景每个场景重复调用 20 次统计最终选择结果的一致性。Q4_K_S 的 Kev 在 5 个场景里稳定率达到 18/20 到 20/20但 Q3_K_M 只有 11/20 到 15/20。一致性低到这个程度就算单次输出看起来合理也无法用于生产流程因为你无法判断它到底是“深思熟虑”还是“抛硬币”。量化后跑回归测试这件事我建议每个模型都做一遍并且把每次输出的快照存下来。Hugging Face 模型卡上的量化损失报告大多是理论值到了具体决策场景差异会超出你的预期。5.3 决策体验的一个重要拐点宁可缩上下文不要降比特在 6GB 卡上如果 Kev 想用 Q4_K_S 又要跑 8192 上下文显存会爆。这时你会面临一个诱惑把 Kev 换成 Q3_K_L省下 600MB然后扩大上下文。我强烈不建议这样做。对决策流水线来说原始输入通常被压缩成几百 token 的结构化摘要真正需要传的上下文并不长。我把 Kev 的上下文窗口从 8192 一路压到 2048决策效果没有任何下降反而推理速度和稳定性都有提升。如果你真的遇到“上下文和比特数不可兼得”的局面压缩输入、截断历史记录比降模型智商更合理。6. 最终配置、实测数据与什么情况下别硬上6.1 最终配置清单折腾一晚上后最终的稳定配置如下模型量化上下文GPU 层数端口备注KevQ4_K_S20483280817B 主决策器LayaQ4_K_S4096全部层80823B 结果校验器调度方式为 Python wrapper 进程管理两个模型轮流驻留显存同一时间只存在一个 llama-server 实例。6.2 实测性能数据这是同一台机器上、室温环境下、连续跑 100 轮决策流程的统计值指标KevLaya冷启动 / 模型加载耗时12 到 15 秒6 到 8 秒单次请求首 token 延迟0.8 到 1.5 秒0.3 到 0.6 秒生成速度18 到 22 token/s24 到 30 token/s单次完整决策调用耗时约 3 到 6 秒约 1 到 3 秒切换模型额外耗时12 到 18 秒8 到 12 秒完整流水线一次耗时约 6 到 10 秒对离线决策场景完全够用。如果切到 Q3 可以再省几百兆显存但决策稳定性明显变差我已经不会再选。6.3 什么情况下 6GB 卡真的不适合这么干最后说句实在话。这个方案适合的是“单用户离线调用、决策流程天然串行、单次上下文不超过 4096 token”的场景。如果你的需求是两个模型需要真正并发响应比如同时为多个用户服务必须保持 8192 以上的上下文且输入无法压缩对端到端延迟要求在 1 秒以内需要高频切换模型比如每隔几秒就切一次那我劝你直接放弃 6GB 卡要么换一张 12GB 或 24GB 的卡要么把非敏感部分丢给远程 API。低显存部署的本质是需求的妥协而不是技术上的魔法。6.4 回顾这次折腾最有价值的三件事如果让我给自己留三条备忘第一显存预算永远按运行态峰值算不要按文件大小算。第二决策模型的量化底线是 Q4_K_S低于这个档位后必须跑一致性测试否则下游策略无法信任。第三进程级轮流调度是低显存环境下最可控的方案Ollama 和 vLLM 的自动调度反而会让你失去对显存细节的把控。这次翻车最后能跑通原因是把“同时跑两个模型”改写成了“两个模型在一条串行流水线上轮转”。Kev 和 Laya 现在稳稳地住在这张 6GB 卡上每次切换 10 秒左右我的离线决策任务再也没有报过 CUDA OOM。整理这篇记录时 DeepSeek 帮了大忙从一堆乱七八糟的终端截图和笔记里捋出了完整的复盘顺序至少下次换新模型时我可以直接照着这个流程走不用再熬一个凌晨。