【Bug已解决】[Bug]: vllm start with TieringOffloadingSpec mmap_obj error 解决方案

【Bug已解决】[Bug]: vllm start with TieringOffloadingSpec mmap_obj error 解决方案 【Bug已解决】[Bug]: vllm start with TieringOffloadingSpec mmap_obj error 解决方案一、现象长什么样vLLM 的TieringOffloadingSpec分级卸载规格用来把 KV cache / 权重按冷热分层把冷数据 mmap 到文件或共享内存作为慢速层。启动带上这个 spec 时会撞到一类 mmap 相关的报错File .../tiering/offloading.py, line 73, in _map_tier self.obj mmap.mmap(fd, size) ValueError: mmap.mmap(-1, 0): [Errno 22] Invalid argument或OSError: [Errno 13] Permission denied: /mnt/tier1/kv_offload.bin或 macOS / 某些文件系统上OSError: [Errno 19] Operation not supported: mmap on this filesystem几个典型表征只在启用TieringOffloadingSpec时出现关掉就正常说明问题 100% 在分级卸载的 mmap 初始化这一步不是模型或调度。报错在_map_tier/mmap.mmap调用处要么是size0文件还没分配长度就 mmap要么是 fd 无效要么是底层文件系统不支持 mmap。跨平台不一致Linux 上能跑的 spec在 macOS / 某些网络文件系统NFS 部分挂载上直接Operation not supported因为不是所有文件系统都支持mmap文件映射。这不是 vLLM 逻辑 bug而是分级卸载层在创建 mmap 对象时没有对文件大小 / 权限 / 文件系统支持度做防御。下面给出定位与修复。二、背景TieringOffloadingSpec的核心是把一层存储实现为内存映射文件fd os.open(path, os.O_CREAT | os.O_RDWR) os.ftruncate(fd, size) # 必须先分配长度 obj mmap.mmap(fd, size) # 再映射mmap有几个硬性约定映射长度必须 0且是页大小通常 4096的整数倍传 0 直接Invalid argument文件描述符必须可写O_RDWR只读 fd 做可写 mmap 会Permission denied底层文件系统必须支持 mmaptmpfs / 本地 ext4 支持部分 NFS / 网络盘 / 某些 FUSE 不支持/dev/shm或普通文件才可靠匿名 mmap 用于纯内存层。现状的mmap_obj error几乎都是这几条约定没满足spec 里只给了路径没先ftruncate到正确大小、或路径所在文件系统不支持 mmap、或权限不够。修复就是把这些约定在前置检查里一次性验证并按平台回退到匿名 mmap。三、根因拆成三条独立根因映射前没分配文件长度mmap.mmap(fd, size)的size来自 spec但代码可能直接用实际已写入数据量而文件刚open时长度是 0 →mmap(.., 0)直接Invalid argument。根因是mmap 前缺少os.ftruncate(fd, size)把文件撑到目标长度。文件系统 / 平台不支持文件 mmap在 macOS 或部分网络文件系统上文件 mmap 受限制或不被支持返回Operation not supported。根因是没有做平台/文件系统能力探测也没有回退到匿名 mmap。权限 / fd 模式不对用O_RDONLY打开却要可写 mmap或路径目录不可写导致Permission denied。根因是fd 模式与 mmap 访问意图不匹配。修复方向封装一个MmapTier类强制先 ftruncate 到页对齐大小 用 O_RDWR 做文件系统能力探测 不支持时回退匿名 mmap并把每类失败映射成清晰错误码。四、最小可运行复现下面复现mmap 长度 0 / 文件系统不支持两类错误import mmap import os import tempfile def naive_mmap(path, size): 现状直接 mmap不做任何前置校验。 fd os.open(path, os.O_CREAT | os.O_RDWR) obj mmap.mmap(fd, size) # size0 或非页对齐会直接炸 return obj, fd # 复现 1size0 try: naive_mmap(os.path.join(tempfile.gettempdir(), x.bin), 0) except ValueError as e: print(复现 size0:, e) # 复现 2非页对齐 try: naive_mmap(os.path.join(tempfile.gettempdir(), y.bin), 100) except (ValueError, OSError) as e: print(复现非页对齐:, e)复现 size0: ... Invalid argument即复现了mmap_obj error的头号原因。下面把它重做成带校验、带回退的版本。五、解决方案第一层最小直接修复最小修复封装MmapTier强制页对齐大小、先ftruncate、用O_RDWR并在不支持文件 mmap 时回退到匿名 mmap。import mmap import os import tempfile PAGE 4096 def round_up_page(size: int) - int: return max(PAGE, (size PAGE - 1) // PAGE * PAGE) def make_mmap_tier(path: str, size: int, anon_fallback: bool True): 创建一个文件支持的 mmap 层不支持时回退匿名 mmap。 size round_up_page(size) try: fd os.open(path, os.O_CREAT | os.O_RDWR, 0o600) os.ftruncate(fd, size) # 关键先把文件撑到目标长度 obj mmap.mmap(fd, size) return obj, fd except OSError as e: if anon_fallback and Operation not supported in str(e): # 文件系统不支持 mmap回退匿名纯内存层无文件后端 obj mmap.mmap(-1, size) return obj, -1 raise RuntimeError(f创建 mmap 层失败 path{path} size{size}: {e}) from e # 用法 tier, fd make_mmap_tier(/tmp/kv_tier.bin, 1024 * 1024) tier[0:4] bkv00 # 像写字节数组一样用 print(tier 前 4 字节:, tier[0:4]) if fd ! -1: os.close(fd)这一层改动让 mmap 初始化不会再因 size0 / 非页对齐 / 权限而崩且在不支持的文件系统上自动降级为内存层。六、解决方案第二层结构化改进把分级卸载层做成结构化组件在创建前做能力探测文件系统是否支持 mmap、是否有写权限、剩余空间是否够并把每类失败映射成清晰错误码供TieringOffloadingSpec启动时给出可操作的报错。import enum import os import stat import tempfile class MmapTierError(enum.Enum): SIZE_ZERO SIZE_ZERO NOT_PAGE_ALIGNED NOT_PAGE_ALIGNED FS_UNSUPPORTED FS_UNSUPPORTED PERMISSION_DENIED PERMISSION_DENIED NO_SPACE NO_SPACE class TierSpec: def __init__(self, path: str, size: int, prefer_file: bool True): self.path path self.size size self.prefer_file prefer_file def validate_tier_spec(spec: TierSpec): problems [] if spec.size 0: problems.append(MmapTierError.SIZE_ZERO) if spec.size % PAGE ! 0: problems.append(MmapTierError.NOT_PAGE_ALIGNED) if spec.prefer_file: d os.path.dirname(spec.path) or . st os.statvfs(d) free st.f_bavail * st.f_frsize if free spec.size: problems.append(MmapTierError.NO_SPACE) # 探测目录是否可写 if not os.access(d, os.W_OK): problems.append(MmapTierError.PERMISSION_DENIED) return problems class OffloadingTier: def __init__(self, spec: TierSpec): errs validate_tier_spec(spec) if errs: raise RuntimeError(f分级卸载规格无效: {[e.value for e in errs]}) self.obj, self.fd make_mmap_tier(spec.path, spec.size, anon_fallbackspec.prefer_file) self.size spec.size # 用法启动时先校验给出清晰错误而不是崩溃在 mmap 内部 spec TierSpec(/tmp/kv_tier.bin, 1024 * 1024) tier OffloadingTier(spec)validate_tier_spec在调mmap之前就返回所有问题启动失败时能直接告诉用户是空间不够 / 权限不足 / 文件系统不支持而不是抛出底层Errno 19。七、解决方案第三层断言 / CI 守护mmap 层最怕size 不对或回退失效。用断言守两条不变量def check_mmap_tier_invariants(path, size): obj, fd make_mmap_tier(path, size) # 不变量 1映射长度必须页对齐且 0 assert len(obj) round_up_page(size), 映射长度未页对齐 assert len(obj) PAGE, 映射长度 0 # 不变量 2可读写分级卸载需要写回冷数据 obj[0:3] babc assert obj[0:3] babc, mmap 层不可写 if fd ! -1: os.close(fd) return True def test_tiering_mmap(): import tempfile d tempfile.gettempdir() for sz in (4096, 100, 0, 1 20): if sz 0: continue check_mmap_tier_invariants(os.path.join(d, ft{sz}.bin), sz) print(OK: 分级卸载 mmap 层不变量通过) if __name__ __main__: test_tiering_mmap()把test_tiering_mmap接进 CI含 macOS runner任何未页对齐 / 不可写 / 回退失效的改动都会立即红。八、排查清单启用TieringOffloadingSpec报 mmap_obj error按序查看错误关键字Invalid argumentmmap(.., 0)→ 是 size0确认 spec 的 size 配置 0 且页对齐Permission denied→ 路径目录不可写或 fd 模式错Operation not supported→ 文件系统不支持 mmap需要回退匿名层。确认 mmap 前os.ftruncate(fd, size)文件刚open时长度为 0必须先撑到目标大小再mmap否则必炸。size 必须页对齐用round_up_page(size)把 size 向上取到 4096 的倍数。fd 用O_RDWR分级卸载需要写回冷数据只读 fd 做可写 mmap 会拒。文件系统支持度/tmp、/dev/shm、本地 ext4/xfs 支持 mmap部分 NFS / 网络盘 / FUSE 不支持。启动时用validate_tier_spec探测不支持就回退匿名 mmap牺牲持久化但能跑。剩余空间用os.statvfs确认目录剩余空间 ≥ size否则NO_SPACE别等 mmap 中途SIGBUS。跨平台CI 里加 macOS runner 跑test_tiering_mmapmacOS 上某些路径如/System不可写spec 默认路径要避开。九、小结TieringOffloadingSpec启动报 mmap_obj error 的根因是分级卸载层在 mmap 前没满足它的硬性约定size 必须为页对齐正数、fd 必须可写、文件系统必须支持映射。三层修复第一层make_mmap_tier强制先ftruncate到页对齐大小、用O_RDWR、不支持时回退匿名 mmap最小改动消掉Invalid argument / Permission denied / Operation not supported第二层validate_tier_spec在调 mmap 前探测空间/权限/对齐把每类失败映射成SIZE_ZERO/FS_UNSUPPORTED/...清晰错误码OffloadingTier启动时给可操作报错第三层CI 断言守住长度页对齐 / 可读写 / 回退有效含 macOS runner。落实后分级卸载层要么正确映射、要么在启动校验阶段就给出精确原因哪个文件系统、缺多少空间而不是崩在mmap.mmap内部的底层 errno。