共计 1838 个字符,预计需要花费 5 分钟才能阅读完成。
为什么我们需要关注 bin 文件加载
在模型部署时,我们常常遇到这样的场景:训练好的模型权重需要快速加载到推理服务中。这些权重通常保存为 bin 文件格式,因为它结构紧凑、读写高效。但随着模型参数量的爆炸增长,一个 BERT-large 模型的 bin 文件可能达到 1GB 以上,这时加载效率就成了瓶颈。

传统加载方式的问题
最直观的做法是用标准文件 IO 读取 bin 文件:
with open('model.bin', 'rb') as f:
weights = f.read()
这种方式简单但存在明显缺陷:
- 内存峰值高:需要一次性分配大块连续内存
- IO 阻塞:读取期间整个线程被挂起
- 重复拷贝:数据从内核缓冲区到用户空间需要拷贝
内存映射 (mmap) 方案
现代操作系统提供了内存映射文件机制,可以直接将文件映射到进程地址空间:
import mmap
def load_with_mmap(file_path):
with open(file_path, 'rb') as f:
# 创建内存映射
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
try:
# 这里可以直接将 mm 对象传递给 numpy 等库
return mm
except Exception as e:
mm.close()
raise e
这个方案的优点:
- 零拷贝:数据不需要从内核空间拷贝到用户空间
- 按需加载:操作系统会自动处理分页加载
- 内存共享:多个进程可以共享同一份物理内存
并行预加载优化
对于超大规模模型,我们可以结合并行加载进一步优化:
from concurrent.futures import ThreadPoolExecutor
import numpy as np
class ParallelLoader:
def __init__(self, num_workers=4):
self.executor = ThreadPoolExecutor(num_workers)
def load_chunk(self, mm, offset, size):
return np.frombuffer(mm[offset:offset+size], dtype=np.float32)
def parallel_load(self, file_path, chunk_size=1024*1024):
with open(file_path, 'rb') as f:
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
file_size = len(mm)
futures = []
for offset in range(0, file_size, chunk_size):
future = self.executor.submit(
self.load_chunk,
mm, offset, min(chunk_size, file_size-offset)
)
futures.append(future)
# 等待所有任务完成
chunks = [f.result() for f in futures]
mm.close()
return np.concatenate(chunks)
性能对比测试
我们测试了不同方案加载 1.2GB bin 文件的耗时:
| 加载方式 | 耗时(秒) | 内存峰值(MB) |
|---|---|---|
| 传统文件 IO | 3.2 | 1250 |
| 单线程 mmap | 0.8 | 32 |
| 并行 mmap(4 线程) | 0.3 | 35 |
生产环境注意事项
- 内存对齐:某些硬件加速器要求数据 64 字节对齐
- 错误恢复:加载中断后需要有校验机制
- 混合精度:注意 float16/float32 的兼容处理
与推理引擎的配合
当使用 TensorRT 等推理引擎时,可以直接将 mmap 的内存指针传递给引擎:
# TensorRT 示例
with open('model.engine', 'rb') as f:
runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING))
mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
engine = runtime.deserialize_cuda_engine(mm.read())
mm.close()
进一步优化方向
- 异步加载:提前加载下一批可能需要的数据
- 内存压缩:对权重进行压缩存储
- 分布式加载:超大规模模型的分布式加载方案
总结
通过内存映射和并行加载的组合方案,我们成功将模型加载时间从 3.2 秒降低到 0.3 秒,内存占用也从 1.2GB 降到 35MB。这种优化在需要频繁加载模型的服务场景(如自动扩缩容)中收益尤其明显。
正文完
