既然回到了书房,我就把那套“分布式采集”彻底本地化

既然回到了书房,我就把那套“分布式采集”彻底本地化。今天搞定了 Rembg Pro 的核心瓶颈,不是算法,是并发。之前用云函数跑,钱烧得跟纸一样,现在必须把所有东西都锁死在本地服务器上,一根网线都不能依赖。

这玩意儿本质上就是个抠图模型,但客户要的是批量处理电商平台的商品图,一天几十万张的量。最开始用 Flask 搭了个简单 API,单线程,一张图跑 3 秒,队列能排到明年。后来上 Celery,加 Redis 做消息队列,以为万事大吉了,结果死锁和内存泄漏轮着来。监控面板上那些 worker 进程,动不动就僵在那里,像极了 2020 年我那支半死不活的团队,指令发下去,没反应,你还不知道卡在哪个环节。

真正的毒打来自 GPU 内存管理。PyTorch 那套默认机制,在多进程环境下就是个坑。每个 worker 进程都去加载一次模型,16G 的显存瞬间被四五个进程瓜分干净,然后一起 OOM 崩溃。日志里全是 CUDA out of memory,看得人血压飙升。这就像当年管人,每个人都觉得自己该占一份资源,结果项目没启动,预算先打光了。

解决方案土得掉渣,但有效。搞了个模型预加载的单例服务,让所有 worker 进程通过共享内存来访问同一块显存里的模型权重。听起来简单,但光是解决进程间通信和锁的问题,就耗了我两个通宵。用的是 Python 的 multiprocessing 模块,配合 `torch.multiprocessing` 做数据共享,把模型从 `nn.Module` 转成 `SharedMemory` 能识别的状态字典,再塞进一个全局的 Manager dict 里。每个 worker 启动时,不再加载模型,而是去这个共享字典里“偷”权重。这里有个魔鬼细节,模型的 `forward` 方法里如果有非参数的缓存,也得一并清理,不然会在并发推理时引发难以追踪的随机错误。

调优的重点从“如何更快”变成了“如何更稳”。我放弃了追求极致的单张图片处理速度,转而优化吞吐量和失败重试机制。用 `concurrent.futures` 的 `ThreadPoolExecutor` 控制每个 worker 内部的并发度,避免把 GPU 喂得太饱。给每个处理任务加了唯一 UUID 和超时控制,超时或失败的任务会自动回滚到队列尾部,并记录失败次数,超过三次才扔进死信队列。这套逻辑写下来,感觉不像在搞技术,像在给一个脆弱的系统上呼吸机,时刻盯着心跳。

现在这套东西跑在书房角落那台旧服务器上,双路 E5,配了张 RTX 3090。24 个逻辑核心,我开了 16 个 worker,每个 worker 内部并发 4 个线程。峰值时能同时啃 64 张图,平均延迟控制在 1.5 秒以内,吞吐量比最初的版本翻了二十倍不止。最关键的是,它稳了。听着机箱风扇均匀的呼啸声,比听下属汇报那些掺水的周报,踏实多了。

疫情把所有人都赶回了原点,也把我逼回了这台机器面前。什么管理、什么规模,都是虚的。代码不会骗你,内存占用和日志时间戳不会骗你。问题就在那里,解得了就活,解不了就死。所谓超级个体,无非是把过去依赖团队、依赖云服务的环节,一个个掰碎了,吞下去,消化掉,变成自己肌肉记忆的一部分。Rembg 只是个开始,后面还有更多模型、更多流水线要本地化。这根网线,能拔的,我迟早全给它拔了。

© 版权声明
THE END
喜欢就支持一下吧
点赞62 分享