Vast.ai 竞价实例的中断风险与应对:断点续跑与数据保全
一个很常见的现场:微调任务挂在后台跑着,第二天早上打开控制台,实例卡片上的状态不是「Open」也不是「Connect」,而是一个安静的「Inactive」。日志停在半夜某一刻,GPU 没了,盘还在,账单也还在走。你没有做错任何操作——你只是租了一台竞价实例,而半夜有人出了更高的价。
便宜是真的。问题从来不是「竞价实例值不值得用」,而是「代价怎么兜住」。这篇只谈后半句。
一、它的中断逻辑和你熟悉的那套不一样
很多人第一次用竞价实例,脑子里装的是另一家云的模型:你出一个上限价,平台按容量情况决定要不要收回,回收前给你一个短暂的通知窗口,窗口过了直接终止。所以大家的第一反应是去找「中断通知接口在哪」「有没有两分钟的宽限期」。
Vast.ai 不是这个路子,官方文章里把自己的机制说得很直白:它用一个持续运行的直接竞价系统来决定优先级(原文是 “a direct bidding system to determine priority on a continual basis”),并且「当前最高出价决定哪个实例在运行,其余的被暂停」(“the current highest bid determines the instance that runs; any others are paused”)。
三个词值得咬一下。直接,意味着你的出价本身就是排序依据,不是一个「愿意支付的上限」而已。持续,意味着这个排序随时在重算,不存在「过了某个时间点就安全了」的心理安慰。暂停,意味着结局不是终止——这一点是整篇文章的转折点,下一节专门讲。
官方还给了一句相当硬的话:An interruptible instance can be abruptly paused at any time if another user places a higher bid or if an on-demand rental is created for the same resources.「任何时刻」「突然」这两个限定词写在官方文档里,就等于告诉你:不要围绕「怎么提前得到通知」做设计,要围绕「任何一个指令执行完之后都可能是最后一个」做设计。这是两种完全不同的工程姿态。
如果你还没决定要不要走这条路,建议先把平台本身的定位和机器筛选看一遍,再回来读容错:Vast.ai 是什么 和 Vast.ai 机器怎么挑。
二、「暂停」和「销毁」是两回事,这决定了你写什么恢复逻辑
这是竞价实例最值钱的一个认知差。文档对竞价实例的描述是:被抢占时「数据保留,但实例不可用」(data preserved when paused but instance not functional),并且「优先级恢复时自动恢复运行」(resume automatically when priority returns)。
翻译成工程语言:你丢的是算力和进程,不是磁盘。容器里的文件还在,检查点还在,装好的依赖还在。被暂停不等于回到零。
对比一下另外两种真正的「没了」:
- Destroy(销毁):文档写得毫不含糊,实例和其中所有数据被永久删除。这是你自己点的,也是唯一真正不可逆的一步。
- 合约过期:文档明确说过期实例会在过期后进入一个待删除的窗口,窗口过去数据就没了,而且过期状态下无法重启。所以租之前要看 offer 卡上的最大时长(Max Duration),官方对长任务的建议原文就是
ensure that the maximum duration exceeds the estimated job completion time——让合约的最大时长超过你估的任务完成时间。这句话听起来像废话,但它治的是一个真实的病:大家挑机器只看价格和卡型,很少有人认真看最大时长,结果任务还没跑完合约先到期,这时候丢数据和竞价一点关系都没有。
搞清这个区别,你的恢复逻辑就能写得克制得多:不需要在中断时把整个环境往外搬,只需要保证「进程被砍在任意一行都能从磁盘上接着来」。反过来,如果你误以为中断等于销毁,就会设计出一套每几分钟往云存储同步整个工作目录的方案,带宽是单独计费的,这笔钱可能比你省下来的算力还贵。
三、谁会抢占你
优先级规则文档列得很清楚,而且有一句斩钉截铁的原话:On-demand instances will always take precedence over interruptible instances. 按需实例始终优先于竞价实例。排下来是这样:
- 按需 / 预留实例:高优先级,文档的说法是不会被打断;
- 高出价的竞价实例:资源可用时运行;
- 低出价的竞价实例:暂停,等前面的跑完。
于是抢占的触发条件只有两类:有人在同一批资源上出了更高的价,或者有人把这批资源按需租走了。第二类是很多人忽略的——你不是只和其他抠成本的人竞争,你是和所有愿意付全价的人竞争,而后者永远赢。这也解释了为什么热门卡型上的竞价实例体感更不稳定:不是竞价的人多,是按需的人多。
还有一类中断和竞价机制完全无关,但后果一样,值得单独记一笔:余额见底。文档写的口径是,信用余额归零时实例会被自动停止、GPU 被释放、正在跑的任务被打断;如果账上留了银行卡会自动扣款续上,数据和实例都还在;如果一分钱支付方式都没留,实例和数据会被安排删除,除非你及时充值。所以一个只做了检查点、没做余额告警的方案,仍然是有漏洞的。
四、断点续跑的三件事:频率、位置、幂等
频率怎么定。 别去找「推荐值」,这个数没有普适答案,它是一道权衡题。往频繁的一侧压:每次写检查点都要停下计算、占磁盘 IO、吃掉一部分有效算力,写得越勤,单位时间产出越低。往稀疏的一侧压:中断发生时,你平均会丢掉「上一个检查点到现在」这段时间的一半工作量。所以真正要拿的输入是三个——写一次检查点的耗时、你观察到的中断间隔大概量级、以及重跑一段的代价。把这三个量摆出来,你会发现结论往往由第一项决定:如果写一次检查点便宜到几乎无感,那就尽量勤;如果每次落盘都很重(大模型的优化器状态就属于这类),就得先想办法把落盘变轻,而不是硬着头皮拉长间隔。官方给的建议本身也只是定性的:it is recommended to save your work to disk and consider saving your outputs to cloud storage at regular intervals——存盘,并考虑按固定间隔把输出同步到云存储,「固定间隔」是多少它没说,因为确实说不了。
状态往哪存。 Vast.ai 上有几个层次,搞混了会吃亏:
- 容器存储:默认那块盘,实例存在期间数据都在,停机也在,但实例一销毁就全没了。而且它有个硬约束——大小在创建时固定,之后不能改。这对检查点方案是直接的限制:你打算保留几份历史检查点、每份多大,必须在点 RENT 之前算完,因为盘满了没有扩容这条路,只能新建实例再搬数据。文档里那条「定期用
df -h看盘」的建议,在竞价场景下应该升级成任务里的一个主动检查。 - Volume(卷):能在实例被销毁后存活、能重新挂载,看起来是理想选择,但有个关键限制——只在本机。它绑死在创建它的那台物理机上,不能迁移到别的机器,只能挂给同一台主机上的实例。对竞价来说这是个双面刃:被暂停后在原机恢复,卷当然还在;可一旦你判断这台机器竞争太凶、决定换一台重开,卷是搬不走的。
- 云存储 / Cloud Sync:唯一真正离开这台机器的备份。文档这里埋了一个不太显眼的提醒:Cloud Sync 只建议在可信的数据中心机器上用(卡片上带 Secure 标记的那类)。这和竞价的省钱动机存在张力——你为了便宜去挑机器,而真正重要的产出又建议只从可信机器往外同步。这个矛盾官方没有给出解法,只能你自己按数据敏感度权衡。另外文档也直说了,主机方在技术上是能接触到机器上的文件的,敏感数据要自己加密。
一个合用的分层是:中间状态(优化器状态、临时特征、缓存)留在容器存储,反正中断也不丢;真正不可再生的产出(最终权重、评测结果、结构化输出)按间隔推到对象存储。别把两类混在一个目录里整体同步。
恢复怎么保证幂等。 「突然暂停」的最坏时点是你正在写文件的那一刻,所以:
- 检查点落盘走「写临时文件 → 校验 → 原子改名」,恢复时只认改名成功的那一份。少了这一步,你迟早会在恢复时读到一个被截断的半成品,而这种故障排查起来特别费劲,因为文件明明存在。
- 恢复入口做成幂等的:进程起来先扫最新的有效检查点,有就续,没有就从头。同一个脚本既能冷启动也能续跑,不要维护两条代码路径——中断发生时没人会来手动选路径。
- 批处理型任务把「哪些分片做完了」记在实例外面,用输出本身或者一个独立的完成标记来判断,而不是靠内存里的进度变量。启动时先跳过已完成分片,是最省事的幂等实现。这套思路和离线批处理的调度是通的,可以顺着 离线批量推理怎么组织 再看一遍。
- 让恢复动作不依赖人。模板支持在实例启动时执行 on-start 脚本,把「拉代码、找检查点、接着跑」放进去,自动恢复才真的自动;否则你的容错方案本质上是「我半夜会醒」。
五、暂停期间那笔最反直觉的账
竞价实例最容易让人算错的不是算力钱,是存储钱。
文档在两处重复强调同一件事:存储在实例存在期间持续计费,与是否在运行无关;要停止存储计费,必须彻底销毁实例。还有一句更扎心的:停机状态的存储费率可能比运行状态更高。
把它和竞价机制叠起来看就明白问题在哪了——你被抢占,GPU 停了,算力费停了,但你为了放检查点而慷慨申请的那块盘,一分钱不少地继续走。如果你的出价长期竞争不过别人,实例就会长期停在暂停状态,这时候你付的是「纯存储费换一个排队位置」。这笔账在按需实例上根本不存在,因为按需不会被暂停;它是竞价实例特有的隐性成本,而且完全不出现在「便宜多少」的对比里。
所以竞价场景下的盘容量决策方向和直觉相反:不是「反正便宜多要点」,而是够放你真正需要保留的那几份检查点就行。旧检查点要有清理策略。同理,任务跑完之后第一件事是把产出取走然后销毁实例——留着一个「万一还要用」的停机实例,是这套玩法里最常见的漏财方式。
六、什么该上,什么绝对不该上
文档自己给的适配表已经说得很实在,把判据抽出来就一条:任务能不能重入。
适合的:超参搜索和微调实验(每组配置本来就是独立的,挂了重跑一组不心疼)、数据预处理(文档的原话就是「可以从上次的地方继续」)、离线批量推理、开发和调试环境(会话短、对成本敏感)。这些任务的共同点是「中断只损失一段时间,不损失正确性」。
绝对不适合的:任何需要对外承诺可用性的在线推理。这不是「风险偏好」问题,是机制层面的不可能——官方已经写明按需始终优先、暂停可能发生在任何时刻,你无法在这个前提之上承诺一个响应时间。对外服务的那一层要么走按需/预留,要么走托管的推理服务,把竞价留给背后的批处理。这一层怎么划,可以对着 自建还是直接调 API 的那套账想清楚再动手。同样不适合的还有硬 deadline 的任务:你付的价格买的是优先级,不是时间保证,排队多久没人能告诉你。
顺便说一个操作细节:实例类型在创建界面选定后不能改,按需和竞价之间互转都得重建实例(只有按需转预留是支持的)。所以「先按竞价试试,不行再切按需」这个念头行不通——想切就是重开一台,检查点能不能被新实例读到,又回到上一节的存储分层问题。选型阶段的功课得做在前面,可以参考 云 GPU 租用的整体取舍。
七、一句不太好听的话
竞价实例省下来的那部分,是拿工程复杂度换的。检查点、原子落盘、幂等恢复、自动重启、余额告警、盘容量规划、产出外推——这些活不写,省下的钱就是账面上的;写了,你得承认它们本身是有成本的,而且是长期维护成本。
所以判断标准很朴素:如果你的任务不能重入,那么竞价省下的钱,大概会被返工和人力吃掉。 一个跑一半就得从头开始的任务,中断一次的损失是整段算力加上一整段时间,这种情况下便宜是假的。反过来,如果你的任务天然分片、天然可续跑——超参网格、批量离线处理、数据清洗——那这套机制几乎是白送的折扣,值得认真投入去把容错做扎实。
至于具体的价差和你这类任务的实际中断频率,这两个数都得你自己量:价格随市场实时浮动,中断频率取决于你选的卡型、地区和出价策略。最省事的验证方式是先拿一个不重要的小任务跑几天,观察它被暂停了几次、每次停多久,再决定要不要把主力任务搬过来。别用别人的体感替你做这个决定,也别用官方文档里任何一个百分比替代你自己的测量——文档能告诉你机制,只有你的账单和日志能告诉你结果。
算完账发现自建推理不划算?
先用托管端点把业务跑起来,量上来了再回头算自建的平衡点。