让大模型住进服务器
不是"在服务器上跑大模型"
是"给大模型一间能住的房子"
我家有一台小盒子,瑞芯微 RK3566,四个 Cortex-A55 小核,4GB 内存,系统跑在一块 8GB 的 eMMC 上,数据盘是一块 240GB 的 SATA 固态。它已经连续开机 268 天,现在 free 只剩 65Mi,内存里塞着十几个 Docker 容器:Home Assistant、ESPHome、Node-RED、go2rtc、MariaDB、Samba、Mosquitto……还有四路摄像头、一堆定时任务、一整套截图/转码/推送流水线。
就是这台机器,最近住进了一个大模型。
不过我得先把话说清楚,因为"让大模型住进服务器"这句话,大多数人第一反应是 Ollama、是 llama.cpp、是把模型权重下载到本地跑推理。我一开始也是这么想的,然后我看了看内存:
Mem: 3.8Gi used 2.6Gi available 1.2Gi
剩余可用内存也才 1.2Gi,连一个 3B 的量化模型都塞不进去,更别说 7B。这条路在硬件层面就被判了死刑。所以我换了个思路:模型不搬进来,给它一间房,让它住进来。
大脑还在云上(我这边接的是 DeepSeek V4.1 和 ChatGPT),住在服务器里的是它的身体——一个能读日志、能改脚本、能查数据库、能开定时任务的 Codex 智能体。它不是"聊天的那个",它是会在这个盒子里留下痕迹的那个。
这篇文章讲的就是这间房是怎么收拾出来的,以及一个住了几个月之后,我发现哪些事比想象中重要。
一、第一件事是分房睡
大模型住进来,第一步不是装软件,是决定它睡哪儿。
这台机器的 eMMC 只有 8G,而且是唯一没有冗余的地方——系统盘写坏了,机器就成砖。所以从我把它接进来那天起,一条铁律就写进了它的"家规":尽量少写 eMMC。
而智能体最擅长的恰恰是写东西:每一次会话、每一条日志、每一个中间状态,它都往 home 目录里丢。看它真正跑来之后的体积就明白了——光一个 logs_2.sqlite 就 24M,还有 thread history、state、models cache,加起来几十兆,而且是持续增长的。放在 eMMC 上就是慢性自杀。
所以它的家在 SATA 上:
/somedir/codex/deepseek-home/.codex/
├── config.toml # 配置:模型、provider、MCP
├── models.json # 模型目录(注意别手改,升级会被覆盖)
├── sessions/ # 每次会话的记录
├── logs_2.sqlite # 运行日志,24M 且在长大
├── memories_1.sqlite # 它自己攒的记忆
└── skills/ # 技能
这台机器上的 /root/.codex 是一个 bind mount,指到上面那个目录。这里踩过一个挺典型的坑:最早是用软链接做的,结果沙箱直接报 cannot enforce sandbox read-only path … crosses writable symlink,拒绝启动。因为安全边界不允许一条可写路径穿过符号链接——这个设计其实是对的,不然沙箱形同虚设。改成 bind mount 就干净了。
分房的收益是隐形的:它的工作痕迹全部落在数据盘上,备份、快照、容量统计都跟着数据盘走;而这块只有 6.5G 的系统盘,至少不用再为它腾地方。
二、一把有齿的钥匙
让一个模型在你自己的服务器上跑,最需要想清楚的其实不是"它能不能做到",而是"它被允许做到什么"。
我的做法是把权限拆成三层,一层比一层窄:
能读的:整个文件系统。日志、配置、脚本、/proc、/sys,随便看。这是它的眼睛,给足没关系。
能写的:只有一个工作目录,外加 /tmp。路径之外的写操作直接拒绝,它自己都没法绕过——不是纪律问题,是机制问题。
要批的:删除、覆盖、重启服务、改系统配置这类动作。它不是不能做,而是每次都要报备。而且我给它配的是自动复核,相当于一个便宜的"二次确认":你确定要删这个?你真的看过那个路径了吗?
除了沙箱,还有一层更土但更好用的东西:MCP。
我接的是 1Panel 官方的 MCP,只读,只有六个工具:看面板概览、看系统信息、列网站、列证书、列应用、列数据库。就这么点能力,但它解决了一个非常具体的问题——以前我查"哪个站点挂了、哪张证书快过期了",要么开网页点进面板,要么记一堆 API 参数。现在我可以直接问,它自己去查。
关键是这个"只读"。改配置、重启容器这些事,它查得到但动不了,还得我自己在面板上点。 这是我故意留的口子。一个能在生产服务器上改配置的模型,和一个能看懂生产服务器状态的模型,是两个完全不同的风险等级,而后者已经能解决我 80% 的日常问题了。
最后还有一层写在纸上的规矩,就是那台机器上的 AGENTS.md,里面有几条运维红线:
- 尽量少写 eMMC;
- 改任何配置之前,先把原件备份到 SATA;
- 往
SATA写东西之前,先确认外挂盘真的挂着; - 不确定的破坏性操作,先问人。
这几条我都记得很清楚,因为每一条背后基本都有一次真实的翻车。第三条尤其——外挂盘掉线的时候,SATA 就是一个空目录,你的脚本会开开心心地往里写,写满 eMMC,而不是报错。
三、比"记住"更重要的是"知道什么不该记"
一个住进来的模型,如果没有记忆,那每次来都是新来的实习生:你得重新介绍服务器、重新解释为什么不能用软件编码、重新提醒它别碰 eMMC。
但如果记忆做成了流水账,它又会变成一个装满过期信息的垃圾场:三个月前的磁盘占用、上个月的任务状态、早就被删掉的容器名字,全在里面。等哪天它拿着一份过期的事实作出判断,你还得回头查证,反而更累。
我最后用的结构是:记忆 = 索引 + 索引指向的文档。
根目录那份 AGENTS.md 很短,只放三类东西:会变的事实、任务清单、结论和坑。它不写细节,细节在各自目录的 md 里。而且里面有一条规矩写得很死:
易失信息(任务状态、磁盘用量、镜像数量)不要写死,用「实时取数」现查。
这句话是整份文件里最重要的一行。因为记忆要有用,靠的不是记得多,而是分得清哪些是"结论"(值得记),哪些是"读数"(必须现查)。"ffmpeg 没有 libx264"是结论,可以记十年;"当前可用空间 104G"是读数,记下来半小时后就可能害人。
另一个约定是变更历史:每改一件事,按日期倒序追加到文末,一条写清结论、坑、以及备份路径。这个习惯带来的最大好处不是回忆,是回滚——当某天某个任务莫名其妙地挂了,你至少知道最近动过什么、原件存在哪儿。
四、它开始值夜班
到这一步,它还只是个"能干的助手"。真正让它从聊天窗口里搬出来的,是定时任务。
这台机器上有一张任务表:每天晚上 20:10 从 NAS 拉延时源、归档成片;每天 10:30 抓四路摄像头的主帧;11 点到 15 点半静默补拍缺的;16:30 日终校验,还缺才告警;7 点到 19 点每小时补一张 720p 的帧;21:00 把四路画面上下拼成一天的缩时;每月 1 号、15 号凌晨备份 eMMC 和数据库。
这些脚本我一个一个调过,而模型在里面干的事,不是替我写代码,而是替我看。你没法给一个定时任务写单元测试——它的输入是现实世界:摄像头会不会迟到、NFS 会不会掉线、两个任务会不会抢同一台相机、编码器会不会在分辨率切换的那一帧崩掉。这些都是跑出来的,不是设计出来的。
而跑出来的东西,只躺在日志里。谁读日志?以前是我。现在是它。
而且它读日志的时候会顺手纠正一个特别常见的误会:一个显示"失败"的任务,不一定失败了。转码任务在面板上常年标红,原因是我们那边推送脚本在成功之后还要覆盖一次测试内容,然后无论成败都 exit 1。只看面板的话,结论是"转码从来没成功过";看日志才知道片子天天都在出。这种"状态显示和事实不一致"的事,在一个有十几条流水线的系统里遍地都是,靠人肉看不过来。
五、几个真实战例
前面讲的都是设计。但一件东西值不值得,得看它在真实故障面前的表现。下面这几件都是真事,而且我挑的都是"如果没有它,我大概率不会去查"的那种。
一个从来没出过片的年度视频。
年度片脚本写得很漂亮,跑起来也一脸正常,就是永远没有产物。查下去发现原因荒谬得可爱:脚本用的编码器是 libx264,而这台机器上的 ffmpeg 根本没编进去——ffmpeg -encoders 里只有 h264_rkmpp 这类硬编。也就是说,这段代码从写下的第一天起,就一次都没成功过。
后来改用了 h264_rkmpp,顺手得出一个很好用的判断法:在这台机器上,只要片子出来了,那就一定是硬编做的,因为软件 H.264 编码器压根不存在。四路各自的年度截图(每路两百来张)跑成年度视频,每路二十几秒。
画面上的淡条纹。
年度片能出之后,画面上出现了淡淡的一圈圈条纹,像电视机信号不好的那种。源图是好的,同一张图单看完全正常,只有编成视频才有。这种问题最难受的地方在于:直觉上你会去怀疑素材,而真正的原因在编码器的内部行为——RK3566 的硬编对一串"每天变化很大的静态图"用了 GOP 64 的帧间预测,把前一天的信息当参考帧抹了上来。
试过 -g 1(每帧都是关键帧),结果硬编直接初始化失败——这芯片不吃这套。最后的解是保留 GOP 64,但用 force_key_frames 让 8fps 的每一帧都被强制成关键帧,等于绕开编码器的选择,把意图直接告诉它。另外,硬编的编码上下文偶尔不释放,脚本现在会在每路编码前短暂重启一次取流服务。这种补救措施谈不上优雅,但它让每年都要跑的任务变得可靠了——在嵌入式设备上,可靠性经常不是靠"更正确的代码",而是靠"承认硬件的脾气"。
灰屏和半幅花屏。
摄像头抓帧偶尔会出废片:整张灰的,或者上半截正常下半截花掉。查出来是 RTSP 礼仪问题——刚连上流的时候收到的第一帧,多半是个没有参考帧的 P 帧,解出来就是这德行。修法是 -skip_frame nokey,只取关键帧,代价是多等大概两秒。
中间走过一段弯路:先试的是"丢掉前 12 帧"(select='gte(n\,12)'),结果更糟,直接解出 Could not find ref with POC。这个弯路比正确答案更值得记下来,因为它说明了一件事:让模型调 bug 的价值不在于它背得出正确的参数,而在于它能提出假设、设计实验、然后承认假设错了。它试错的速度比我快得多,而试错这件事,本来就是要花钱的。
一条自己把自己锁死的自动化。
这个不是脚本,是 Home Assistant 里的自动化。两条规则互相咬住了:一条把整段文案写进一个 helper,又要求这个 helper 非空;某天站点返回了一个空字符串,于是它自此再也没动过。更讽刺的是,唯一能把这个值写回去的那条老自动化,早就被我关掉了。
这种 bug 靠"看一眼配置"是看不出来的,因为每条规则单看都合理,是它们的互相约束出了问题。模型做的事就是把两条规则摊开、顺着执行顺序走了一遍,然后指出那个循环依赖。修完顺手补了守卫,把空值和 unknown 挡在门外。
一次不可逆的删除。
这个教训最贵。我给 eMMC 镜像备份任务做了保留策略:本地只留最新的 4 个。跑完很干净,那个目录一下从 36G 降到 6.5G。
然后核对的时候发现:被删掉的那 19 个镜像,云端从来就没有过副本。上传那一步是最近才启用的,而清理逻辑早就跑起来了。19 个系统镜像,永久丢失。
这不是"模型删错了东西"——它删得很准,策略是我定的。这恰恰是最值得说的一句:当清理动作交给一个照章办事的智能体时,你的备份策略就是你唯一的防线。 "本地有的云端都有"这个假设,我之前从来没验证过,因为验证它是件无聊的事。而无聊的事,正是应该交出去的事——但前提是你得先意识到自己有个假设。
从那以后,那份任务表里多了一行,写明了顺序:先上传,再清理。
六、说点不好听的
讲了这么多好处,得说说代价。让模型住进服务器,不是白拿的。
第一,幻觉的成本变了。 在聊天窗口里,模型说错了,你笑一下就算了。在 root shell 里,它说错了,你少一份数据。这就是为什么前面那三层权限不是可选项:沙箱、审批、只读接口。我宁愿它多报备几次,也不想它"聪明地"帮我省掉一个确认。
第二,它会忠实地执行你没想清楚的命令。 它不会替你拦住"这个删除策略以后会不会出事"——你让它删,它就删,而且删得很干净、很高效、日志写得很全。它的执行力不是安全网,是放大器。你方向对了,它帮你放大十倍;你把顺序写反了,它也帮你放大十倍。
第三,凭据是真的会堆在磁盘上。 服务器上的 API Key、面板 Token、HA 的长效令牌、推送用的企业微信凭据……这些东西平时四散在脚本里,你不太会注意;等到真有个智能体能读整个文件系统的时候,你就得重新数一遍它们分别躺在哪儿、权限是 600 还是 644、会不会被写进日志。(顺便说一句:配置里放明文的 Key 我这边也有,属于待办。)
第四,它不神。 上面那五个战例,没有一个是它"看一眼就知道"的。它读了日志、列了 ffmpeg 的编码器、grep 了配置、做了几次对照实验,中间还试错过。它的价值在于不厌其烦,不在于未卜先知。判断仍然是我的,只不过从"自己挖一百条日志"变成了"看它给出的三个假设,决定先验哪个"。
七、为什么我觉得这事值得
说回那台盒子。
它的算力小得可怜,四个小核、3.8G 内存、跑着一台家庭服务器的全部家当。让它自己跑模型,是没戏的。但把它变成一个大脑在云上、身体在本地的东西,反而把这台机器的价值又往上抬了一层。
因为本地服务器上最值钱的东西,从来不是 CPU——是那些只存在于你脑子里的事实:为什么这个脚本要绕一圈、为什么这台机器不能这么写、哪块盘掉线时候最危险、哪个任务显示失败其实是成功的。这些事实以前只存在于我的记忆和一堆散落的注释里,现在它们有了一个载体:一间房、一份索引、一套权限、一张值班表。
而且我越来越觉得,在这个组合里,模型是最便宜、最可替换的那部分。
权重会升级,价格会变,今天好用的模型明年可能就过气了。但住处的资产是留得下来的:你的目录怎么分、权限怎么切、记忆怎么写、任务怎么排、备份放哪儿、哪些红线不能碰。换一个模型进来,这些东西一天都不用重做——就像换房客,房子还在。
所以我给这件事的定义不是"部署一个 AI",而是给服务器招一个住客:给它一间房,给它一把有齿的钥匙,给它一份会过期的记忆,给它一张夜班表,然后看它在真实世界里出事的时候,能不能比你自己查得更快。
答案在这个盒子上已经跑了几个月了。268 天的开机时间还在往上走,而它现在是这份记录的一部分。
由 AI 生成
评论功能已关闭