| 项目 | 链接 |
|---|---|
| 在线 APP | polyustar.github.io/sonox |
| 论文 | Switch: A Semi-Supervised Learning Framework for Ultrasound Image Segmentation |
| 源代码 | github.com/jinggqu/Switch |
背景
今年买了阿里云 99 元/年的新老同享 ECS,想在上面部署 SonoX — 一个基于 Switch 半监督学习框架的超声影像病灶分割与分类平台。
服务器配置如下:
| 项目 | 规格 |
|---|---|
| 实例型号 | ecs.e-c1m1.large |
| CPU | 2 vCPU(物理单核超线程) |
| 内存 | 2 GB |
| 公网带宽 | 3 Mbps (出) / 200 Mbps (入) |
| 系统 | Debian 13.5 |
| 费用 | 99 元/年 |
项目本身的资源需求:
| 组件 | 规模 |
|---|---|
| 分割模型 (UNet) | 4 个数据集 × 6 个半监督变体 + baseline = 28 个,每个 ~7MB |
| 分类模型 | ViT-Base ~328MB + 融合头 ~15MB + Radiomics ~6MB |
| 模型权重总大小 | ~540MB |
| 后端代码 | 单文件 ~770 行 FastAPI |
核心矛盾:~540MB 模型权重 + PyTorch 运行时,要塞进 2GB 内存的服务器里,还要留余地给推理计算。
下面记录优化过程中踩过的坑和实际有效的措施。
选 Debian 而不是 Ubuntu
同一台机器,Ubuntu Server 基础安装就要吃掉 ~400MB(snapd、networkd 等),而 Debian 最小化安装只需 ~128MB。二者都用 apt,二进制兼容,迁移成本几乎为零。
| Ubuntu | Debian | |
|---|---|---|
| 基础内存占用 | ~400MB | ~128MB |
| Snap 守护进程 | 有 (~50MB) | 无 |
| 默认服务 | 多 | 少 |
在 2GB 服务器上,Debian 比 Ubuntu 省出约 250MB — 对一个内存紧张的深度学习服务来说,这就是能跑和不能跑的区别。
关闭 crashkernel,回收「公摊内存」
开机 free -h 一看,标称 2GB 的服务器实际可用只有约 1.65GB。少掉的 ~350MB 主要来自两块:内核自身开销 + crashkernel 预留。 对个人项目来说,服务器真 crash 了直接重启即可,不需要 kdump。
| |
重启后可用内存涨到 ~1.8GB,找回 100MB+。
创建 Swap:最后的保险丝
上面的优化都是在物理内存上腾挪。但模型推理是尖刺负载——平时风平浪静,一次分类请求可能瞬间多占几百 MB。2GB 物理内存没有容错空间,一旦超了就 OOM Kill。
加一个 2GB 的 Swap 文件作为缓冲:
| |
验证:
| |
但阿里云的磁盘性能本来就很垃圾,开启 Swap 纯属心理安慰聊胜于无吧。
模型加载策略:懒加载 + 用完即释放
28 个分割模型如果全部预加载,内存直接爆炸。核心策略:
分割模型:按数据集按需加载。用户请求 lymph-node 时才加载该数据集下的 6 个模型,处理完后根据配置决定是否卸载。
分类模型:更大的 ViT (328MB) 采用懒加载 — 第一次分类请求到达时才加载,使用完毕后立即释放:
| |
实际运行中,平时内存占用约 500MB(仅常驻一个数据集的分割模型),峰值不超过 1.4GB。比全量预加载节省约 300-400MB。
CPU 动态量化:328MB ViT 降到 ~85MB
在无 GPU 的 CPU 服务器上跑 ViT-Base 推理是一件奢侈的事。PyTorch 的动态量化可以把模型中的 nn.Linear 权重从 float32 压缩到 int8,内存占用减少约 75%,推理速度反而提升。
| |
配合 SONOX_CPU_THREADS=1(限制 PyTorch 线程数避免争抢),在 2 vCPU 上单次分类推理约 3-5 秒,内存峰值可控。
磁盘缓存:避免重复推理
SonoX 的 API 按原始文件名标识同一张图片。如果用户反复查看同一张图的 segmentation/classification 结果,完全没有必要重新跑一遍推理。
| |
缓存文件存于 cache/ 目录,JSON 格式,一条记录约 200KB(含 base64 图片 overlay)。使用 run.sh 重启服务时自动清理。
开发环境优化
uv 镜像源
国内服务器拉 PyPI 慢到怀疑人生。在 pyproject.toml 中直接配置阿里云镜像:
| |
SSH 代理转发拉 GitHub 代码
从国内服务器 git clone GitHub 经常 TLS 超时。最稳定方案:SSH 远程端口转发 + Git 代理。
本地 ~/.ssh/config:
| |
服务器端:
| |
如果转发失败,检查 /etc/ssh/sshd_config 中是否开启了 AllowTcpForwarding yes:
| |
更省事的方案:配置 SSH Key 后用 git clone git@github.com:...,完全绕过代理。
Git 信息持久化
| |
zsh + 插件
| |
效果总结
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 系统可用内存 | ~1.65 GB | ~1.80 GB |
| 系统基础占用 | ~400MB (Ubuntu) | ~128MB (Debian) |
| 模型常驻内存 | ~500MB (全量预加载) | ~150MB (按需加载) |
| 分类模型内存 | ~350MB (FP32) | ~100MB (INT8) |
| 空闲内存占用 | Overflow | ~80% |
| 重复请求耗时 | 3-5s | <10ms (缓存命中) |
最终在 99 元/年的机器上跑起了一个包含 28 个分割模型 + 3 个分类模型的超声影像分析服务,内存有余量,响应可接受。
核心思路:省着用(懒加载)、压着用(量化)、别重复(缓存)。