37岁,上海封了,我的书房成了最后的避难所。这句话现在敲出来,感觉像在说别人的事。窗外的静是物理性的,能听见自己敲键盘的回音。团队散了,办公室退了,去年底那场断尾求生,现在看来是老天爷提前给我发的逃生舱票。现在这间二十平的书房,就是我的全部生产力单元,也是唯一的情绪容器。
Rembg这个背景移除模型,开源社区里早就有了,但把它封装成能稳定处理海量图片的“Pro”版本,是过去三个月我唯一的执念。客户是跨境电商,一天十几万张商品图要处理,原来的单线程脚本跑起来像老牛拉破车,AWS账单倒是跑得飞快。问题从来不在模型精度,而在并发。Python的GIL锁就是个温柔的监狱,你以为开了十个线程,其实大部分时间在排队等钥匙。
我试了multiprocessing,进程间通信和数据序列化立刻成了新瓶颈。一张图片从磁盘读到内存,在进程间传来传去,光Pickle序列化的开销就能吃掉30%的CPU。更别说内存了,十万张图,每张先解码成NumPy数组,十个进程一开,64G的服务器内存瞬间见底,开始疯狂Swap,整个系统直接卡死。那感觉就像你指挥一支军队冲锋,结果所有人都在门口挤成了沙丁鱼,仗还没打,自己先踩死一半。
后来把路子彻底换了。不用多进程,就用异步IO。asyncio + aiofiles + aiohttp,如果模型推理也能异步就好了。但PyTorch的模型,forward函数是阻塞的。卡在这里整整一周。直到看到一篇讲TorchServe的博客,才意识到方向错了——不应该让Python主进程去扛计算,应该把模型扔进一个独立的推理服务里,用gRPC或者简单的HTTP去调用。相当于在厨房外单独盖了个专业灶台,厨师(Python主程序)只管点菜和传菜,炒菜(模型推理)交给灶台自己忙。
自己用FastAPI搭了个推理服务,把模型加载进去。主程序这边,用aiohttp并发地往这个服务端点发图片二进制流。瓶颈一下子从CPU计算转移到了网络IO和服务的吞吐量。服务端又要优化了,用uvicorn多worker启动,每个worker卡一个GPU进程。但GPU内存又不够了,同时处理太多请求会OOM。得加队列,用Celery或者干脆自己写个简单的内存队列,控制同时推理的图片数量,超出的排队。
最深的体会是,深度学习落地,90%的坑不在算法,而在这些脏活累活里。论文里只会给你看准确率、F1分数,不会告诉你怎么让一万个请求同时过来时系统不崩。调优到最后,其实是在平衡几个数字:GPU内存大小、批处理(batch size)的数值、队列长度、请求超时时间。这四个数像跷跷板,动一个,其他全跟着变。batch size设大了,单张图片处理速度快,但延迟高,因为要等凑够一个batch;设小了,GPU利用率上不去,吞吐量暴跌。队列设长了,能抗瞬时高峰,但内存可能爆;设短了,请求直接被拒绝,客户那边就显示失败。
今天下午终于跑通了一个压力测试:模拟连续发送五万张图片,512×512分辨率,平均延迟控制在1.2秒,失败率0.05%以下。看着监控面板上平稳的曲线和稳定的内存占用,那种感觉,比当年第一次跑通爬虫拿到数据还踏实。这是一种非常具体的、可测量的掌控感。外面世界封着,混乱且不可控;但在这个命令行窗口里,每一个问题都有边界,都有参数可以调整,都有日志可以追溯。
37岁,从管理一堆人的焦虑,变成管理一堆进程和内存的焦虑。后者至少讲道理。身体还是得管,晚上得跟着Keep练一组。没了,就这些。














