ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

抢占式 Spot 竞价实例跑离线任务:将大模型微调与批量处理成本打到 1 折

2026/10/7 8:30:09 拓冰建站 浏览量
抢占式 Spot 竞价实例跑离线任务:将大模型微调与批量处理成本打到 1 折 抢占式 Spot 竞价实例跑离线任务将大模型微调与批量处理成本打到 1 折双 11 前夕AI 产品线提了一个极其消耗算力的需求为了上线全新的智能导购 Agent需要对全站 1800 万篇商品评测和问答数据进行全量向量化Embedding构建同时还要基于过去半年的优质客服语料对开源的 14B 大模型进行一轮 LoRA 监督微调SFT。算法同学兴冲冲提了一份服务器采购单交到运维和财务那里需要 4 台配置为 8 卡 A800/4090 的高性能 GPU 实例连续跑满 7 天。财务总监扫了一眼各大云厂商的按量付费On-Demand报价单算力单价每小时近百元整批任务跑下来光服务器租金就要将近 2.8 万元审批单当场被原封不动打了回来。在预算缩紧的小厂硬刚高价算力是走不通的。我们把目光投向了各大主流云厂商常年提供的“抢占式竞价实例Spot Instances”。Spot 实例是云厂商为了利用数据中心闲置算力推出的低价资源其定价通常只有官方标准按量付费价格的10%~18%直接打到 1 到 2 折。原本需要 2.8 万元的离线大任务用 Spot 实例跑完只需要不到 3500 元但是天上不会掉免费的馅饼。Spot 实例之所以便宜是因为它带有一个致命的阿喀琉斯之踵云厂商随时拥有单方面收回机器的权力。当数据中心现货算力吃紧、或者有大客户以原价抢购算力时云厂商会向你的实例发出中断回收信号并在整整 120 秒2 分钟之后强制断电关机。如果你的批量任务没有设计优雅的中断检测与断点续传机制一个已经跑了 6 个小时、只差最后 10 分钟就要跑完的微调任务一旦被拔电源之前的算力和钱全部打水漂。要吃下这 1 折算力的巨大红利就必须依靠一套能在 120 秒倒计时内完成“状态冻结、权重存盘、分片续传”的守护调度系统。架构设计120 秒生死时速的 Spot 守护进程我们基于 Go 编写了一个极轻量的常驻守护程序SpotDaemon它伴随任务脚本在 Spot 实例中一同启动核心执行三大使命高频探针监听中断广播云厂商在准备回收实例时会在本地实例元数据服务Link-Local Metadata API通常为http://169.254.169.254中下发中断动作标记两阶段优雅终止Graceful Teardown探测到回收信号的第 1 秒立即向正在执行微调或向量化的 Python 任务主进程发送SIGTERM信号督促其将当前的训练步数Step、LoRA 权重差量、以及未处理的游标写入本地磁盘断点上传与任务状态归还在接下来的 60 秒内将 Checkpoint 归档压缩包流式推送至对象存储OSS/S3并在中心调度数据库中将该批次分片重置为“等待认领”状态静待下一台 Spot 实例接盘。Go 核心实现中断监听与断点保护调度器以下是生产级 Spot 守护程序的核心逻辑package spotguard import ( context encoding/json errors fmt net/http os os/exec syscall time ) // CloudMetadataSpotAction 云厂商元数据中断响应结构体 type CloudMetadataSpotAction struct { Action string json:action // stop 或 terminate Time time.Time json:time // 预定强制拔电关机时间戳 } type SpotDaemon struct { metadataURL string workerCmd *exec.Cmd taskID string httpClient *http.Client isInterrupted bool } func NewSpotDaemon(taskID string, metadataURL string, workerScript string) *SpotDaemon { if metadataURL { // 阿里云/AWS/腾讯云等元数据中断广播标准接口规范 metadataURL http://169.254.169.254/latest/meta-data/spot/instance-action } return SpotDaemon{ metadataURL: metadataURL, taskID: taskID, workerCmd: exec.Command(python3, workerScript, --task-id, taskID), httpClient: http.Client{Timeout: 1 * time.Second}, } } // Start 启动计算工作进程并开启 5 秒一次的中断监听探针 func (d *SpotDaemon) Start(ctx context.Context) error { // 1. 启动计算任务如 PyTorch 微调或 Embedding 批量构建 d.workerCmd.Stdout os.Stdout d.workerCmd.Stderr os.Stderr if err : d.workerCmd.Start(); err ! nil { return fmt.Errorf(failed to start worker process: %w, err) } ticker : time.NewTicker(5 * time.Second) defer ticker.Stop() // 监控进程退出的通道 workerDone : make(chan error, 1) go func() { workerDone - d.workerCmd.Wait() }() // 2. 主循环监听云厂商中断预警信号 for { select { case -ctx.Done(): return ctx.Err() case err : -workerDone: // 任务正常顺利跑完非抢占退出 if err nil { fmt.Println([SpotDaemon] 离线任务全部正常执行完毕) return nil } return fmt.Errorf(worker terminated with error: %w, err) case -ticker.C: // 探活云厂商元数据中断接口 if action, shouldHalt : d.checkSpotInterruption(); shouldHalt { fmt.Printf([SpotDaemon 预警] 捕获到云厂商强制回收动作%s预定拔电时间%v\n, action.Action, action.Time) return d.emergencySaveAndEvacuate() } } } } // checkSpotInterruption 轮询元数据接口 func (d *SpotDaemon) checkSpotInterruption() (*CloudMetadataSpotAction, bool) { req, _ : http.NewRequest(GET, d.metadataURL, nil) resp, err : d.httpClient.Do(req) if err ! nil || resp.StatusCode ! http.StatusOK { return nil, false // 200 以外或 404 说明实例安全没有回收动作 } defer resp.Body.Close() var action CloudMetadataSpotAction if err : json.NewDecoder(resp.Body).Decode(action); err nil action.Action ! { return action, true } return nil, false } // emergencySaveAndEvacuate 执行 120 秒紧急保存与撤离 func (d *SpotDaemon) emergencySaveAndEvacuate() error { d.isInterrupted true fmt.Println([SpotDaemon 撤离] 开始执行优雅停机与断点落盘...) // 1. 向 Python 训练主进程发送 SIGTERM 信号触发内部 save_checkpoint() if d.workerCmd.Process ! nil { _ d.workerCmd.Process.Signal(syscall.SIGTERM) } // 2. 给予 Python 进程 45 秒的时间完成正在计算批次的 Checkpoint 本地刷盘 time.Sleep(45 * time.Second) // 3. 将本地 Checkpoint 目录同步推送到对象存储 S3/OSS fmt.Println([SpotDaemon 撤离] 正在同步 Checkpoint 至远端对象存储...) uploadCmd : exec.Command(aws, s3, sync, /tmp/checkpoints, fmt.Sprintf(s3://ai-training-checkpoints/%s, d.taskID)) _ uploadCmd.Run() // 4. 通知中心任务调度服务重置本任务状态为 PENDING供其他节点无缝接力 fmt.Println([SpotDaemon 撤离] 状态机回退完成节点准备赴死。) return errors.New(instance preempted: checkpoint successfully preserved) }生产实战的三道“防翻车”护城河在真实的使用过程中光有中断监听脚本还不够以下三点实战经验至关重要多可用区AZ与跨规格打散混部千万不要把所有 Spot 实例全买在同一个可用区的同一种机型上比如全部买华东 2 可用区 A 的同一款 GPU。一旦该机型遭遇现货挤兑整个集群会被成窝端掉。合理的策略是分散在 3 个可用区、跨 2~3 种同性能算力规格混合竞价存储与算力绝对解耦Spot 实例本身只挂载临时的云盘作为工作空间核心的数据集输入和 Checkpoint 输出必须全部依托对象存储或者网络文件系统NAS/NFS。哪怕实例下一秒灰飞烟灭数据资产分毫未伤小步快跑的 Checkpoint 策略在微调代码中不要为了性能把保存间隔设为每 1000 步存一次。在 Spot 架构下建议按时间粒度例如每 20 分钟进行一次增量轻量权重快照将因意外断电导致的平均计算损失回退控制在 10 分钟以内。最终省钱账本复盘依靠这套机制我们在双 11 前完成了全部离线大任务微调与向量化总时长累计消耗了 192 个 GPU 卡时中途遭遇了 3 次 Spot 抢占式强制回收中断自愈表现3 次中断均在 60 秒内完美完成 Checkpoint 上传新启动的 Spot 实例自动下载快照继续接力没有浪费超过 15 分钟的有效计算财务账单原本按量付费需要消耗27,800 元的高额开销实际账单仅支付了3,614 元ROI 暴涨近 8 倍硬生生帮团队节省了将近 87% 的离线算力预算在资源充足的时候省钱是美德在预算紧绷的周期里用架构设计榨干每一分算力红利就是工程师在小厂不可替代的核心生存价值。