执行摘要
- 一句话:修复 EC 共享区在旧内核上的 madvise 崩溃
- 推荐动作:值得精读,建议关注三点:(1) “单页探测 + 双路径回退”的内核特性兼容模式,是处理跨内核版本 API 差异的通用做法;(2) |= 0 读-改-写保留共享数据的技巧,以及对应的 mincore 页面驻留测试方法;(3) 该 PR 是典型的“重复实现未同步修复”教训——ec_shared_region.py 与 shared_offload_region.py 已两次出现同类 bug 不同步,值得跟进抽取公共 helper 的后续重构。整体改动小、测试扎实、无行为回归。
功能与动机
PR body 明确指出崩溃根因:MADV_POPULATE_WRITE 在 Linux 5.14 才引入,老内核上 mmap 模块没有该常量,getattr(mmap, "MADV_POPULATE_WRITE", 23) 的默认值 23 被传给不认识它的内核,madvise 返回 EINVAL,导致“Every code path that constructs an ECSharedRegion then aborts”。这是 #51116 在 SharedOffloadRegion(vllm/v1/kv_offload/cpu/shared_offload_region.py,merged ef2615c2e011,修复 #44888)中修复过的同一 bug,但 ec_shared_region.py 的 docstring 自称“Modeled after SharedOffloadRegion”,却是 #47423 单独引入、未同步该修复,因此 EC 连接器在 Linux < 5.14 上仍然崩溃。回退方案沿用 #51116 review 结论:用 |= 0 而非 = 0,避免破坏对等 worker 已写入共享 mmap 的 EC 数据。
实现拆解
变更入口是 vllm/distributed/ec_transfer/ec_connector/cpu/ec_shared_region.py,将原先 ECSharedRegion.init 内联的 getattr(mmap, "MADV_POPULATE_WRITE", 23) + madvise(...) 调用重构为模块级“探测 + 双路径回退”结构,具体分五步:
-
提升为模块级辅助函数:新增 _MADV_POPULATE_WRITE 常量与三个函数——_madvise_populate_write(原生 madvise 路径)、_fallback_populate_write(逐页触页回退)、_get_populate_write_fn(探测选择器);同时新增 errno、numpy、collections.abc.Callable 导入。模块级函数同时充当测试缝隙:Python 3.12+ 的 mmap.mmap 是不可变 C 类型,无法子类化或打补丁,独立函数可以直接被 monkeypatch 替换为 spy。
-
单页探测与选择逻辑:_get_populate_write_fn(mmap_obj) 先对一页(mmap.PAGESIZE)调用原生路径探测。仅当 OSError.errno == errno.EINVAL 时判定“内核不支持该 advice”,记录 warning 后返回 _fallback_populate_write;其他 OSError(如 EIO)原样抛出,避免把真实 I/O 故障误判为内核能力不足而静默掩盖。
-
回退实现:_fallback_populate_write 用 np.frombuffer 把 mmap 包装为 uint8 数组,对 [offset : offset + length : mmap.PAGESIZE] 切片执行 |= 0。逐页读-改-写会触发写时缺页使页面驻留,但字节内容不变——保证对等 worker 已写入的 EC 数据不会被清零,这是 #51116 review 中定下的决策。
-
调用点改造:init 中仅创建者(_is_creator)执行 populate_write_fn = _get_populate_write_fn(self._mmap_obj) 后对整个 region 调用一次预触页;非创建者既不探测也不触碰页面。内核 ≥ 5.14 时行为与原来完全一致。
-
测试配套:tests/v1/ec_connector/unit/cpu/test_ec_shared_region.py 新增 5 个测试与两个辅助设施(_region 上下文管理器、_page_residency 页面驻留探测)。测试通过 monkeypatch 把两个 helper 换成 spy 断言调用次数,并用 ctypes 调用 mincore 系统调用验证页面真实驻留状态。覆盖路径包括:原生路径 1 次探测 + 1 次全量预触页且 fallback 零调用;EINVAL 时对全量 region 走回退且 region 仍可读写;非创建者零调用;|= 0 保留已有字节且逐页驻留;非 EINVAL 的 OSError 向上传播(并手动清理 /dev/shm 残留文件)。
关键文件:
vllm/distributed/ec_transfer/ec_connector/cpu/ec_shared_region.py(模块 EC 连接器;类别 source;类型 core-logic;符号 _MADV_POPULATE_WRITE, _madvise_populate_write, _fallback_populate_write, _get_populate_write_fn): 核心修复文件:将硬编码的 madvise(MADV_POPULATE_WRITE, advice=23) 调用重构为“单页探测 + 原生/逐页回退”双路径,修复 Linux < 5.14 上每次 ECSharedRegion 构造都因 EINVAL 崩溃的问题;回退用 |= 0 保留对等 worker 已写入的数据。
tests/v1/ec_connector/unit/cpu/test_ec_shared_region.py(模块 EC 连接器;类别 test;类型 test-coverage;符号 _region, _page_residency, test_madvise_success_selects_madvise_population, test_madvise_einval_selects_fallback_for_whole_region): 测试配套:新增 5 个单元测试覆盖原生路径、EINVAL 回退、非创建者零调用、|= 0 字节保留与异常传播;通过 spy 断言调用次数,并用 ctypes 调 mincore 验证页面真实驻留,是本次修复可信度的主要保障。
关键符号:_get_populate_write_fn, _madvise_populate_write, _fallback_populate_write, _page_residency, test_madvise_success_selects_madvise_population, test_madvise_einval_selects_fallback_for_whole_region, test_non_creator_does_not_probe_or_populate, test_fallback_populate_write_preserves_bytes_and_faults_pages, test_madvise_unexpected_oserror_propagates
关键源码片段
vllm/distributed/ec_transfer/ec_connector/cpu/ec_shared_region.py
核心修复文件:将硬编码的 madvise(MADV_POPULATE_WRITE, advice=23) 调用重构为“单页探测 + 原生/逐页回退”双路径,修复 Linux < 5.14 上每次 ECSharedRegion 构造都因 EINVAL 崩溃的问题;回退用 |= 0 保留对等 worker 已写入的数据。
# vllm/distributed/ec_transfer/ec_connector/cpu/ec_shared_region.py
# Linux 5.14 才引入 MADV_POPULATE_WRITE(advice 值 23)。老内核上
# getattr 取默认值 23,内核不识别该 advice,madvise 直接返回 EINVAL
# —— 这正是本 PR 修复的崩溃根因。
_MADV_POPULATE_WRITE = getattr(mmap, "MADV_POPULATE_WRITE", 23)
def _madvise_populate_write(mmap_obj: mmap.mmap, offset: int, length: int) -> None:
# 原生路径:请求内核一次性把 [offset, offset + length) 的页面预触到位。
mmap_obj.madvise(_MADV_POPULATE_WRITE, offset, length)
def _fallback_populate_write(mmap_obj: mmap.mmap, offset: int, length: int) -> None:
# 回退路径:逐页做一次读 - 改 - 写,触发写时缺页让页面驻留。
# 用 |= 0 而不是直接清零是关键:对等 worker 可能已经向共享 mmap
# 写入了 EC 数据,|= 0 不改变任何字节内容,只产生页面驻留效果。
arr = np.frombuffer(mmap_obj, dtype=np.uint8)
arr[offset : offset + length : mmap.PAGESIZE] |= 0
def _get_populate_write_fn(
mmap_obj: mmap.mmap,
) -> Callable[[mmap.mmap, int, int], None]:
# 单页探测一次,为整个 region 选定预触页方案。
# 只把 EINVAL 当作“内核不支持”,其他 OSError(如 EIO)向上抛出,
# 避免把真实 I/O 故障误判为内核能力不足而静默掩盖。
try:
_madvise_populate_write(mmap_obj, 0, mmap.PAGESIZE)
except OSError as e:
if e.errno != errno.EINVAL:
raise
logger.warning(
"MADV_POPULATE_WRITE is not supported; falling back to per-page "
"writes for EC mmap pre-population. Startup may be slower."
)
return _fallback_populate_write
return _madvise_populate_write
# ECSharedRegion.__init__ 中的调用点替换为以下逻辑:
# 原来内联的 getattr + madvise 被拆成“探测一次 + 按结果选函数”,
# 且只由创建者执行;非创建者打开既有文件时不探测也不触碰页面。
if self._is_creator:
populate_write_fn = _get_populate_write_fn(self._mmap_obj)
populate_write_fn(self._mmap_obj, 0, total_size_bytes)
tests/v1/ec_connector/unit/cpu/test_ec_shared_region.py
测试配套:新增 5 个单元测试覆盖原生路径、EINVAL 回退、非创建者零调用、|= 0 字节保留与异常传播;通过 spy 断言调用次数,并用 ctypes 调 mincore 验证页面真实驻留,是本次修复可信度的主要保障。
# tests/v1/ec_connector/unit/cpu/test_ec_shared_region.py
def _page_residency(mmap_obj: mmap.mmap, length: int) -> list[bool]:
# 通过 mincore 系统调用读取 Linux 页面驻留位,用于验证预触页真的生效。
# 之所以要观测真实内核状态,是因为 Python 3.12+ 的 mmap.mmap 是
# 不可变 C 类型,无法子类化或打补丁,只能用行为证据来断言。
try:
libc = ctypes.CDLL(None, use_errno=True)
mincore = libc.mincore
except AttributeError:
pytest.skip("mincore is unavailable")
page_count = (length + mmap.PAGESIZE - 1) // mmap.PAGESIZE
mincore.argtypes = [
ctypes.c_void_p,
ctypes.c_size_t,
ctypes.POINTER(ctypes.c_ubyte),
]
mincore.restype = ctypes.c_int
vector = (ctypes.c_ubyte * page_count)()
address = ctypes.addressof(ctypes.c_ubyte.from_buffer(mmap_obj))
result = mincore(ctypes.c_void_p(address), length, vector)
if result != 0:
raise OSError(ctypes.get_errno())
return [bool(value & 1) for value in vector]
评论区精华
整个 PR 的实质 review 交锋很少,核心只有一条:
风险与影响
关联脉络
- PR #51116 Fall back when MADV_POPULATE_WRITE is unsupported (SharedOffloadRegion): 本 PR 的直接源头:同一 bug 在 SharedOffloadRegion(vllm/v1/kv_offload/cpu/shared_offload_region.py)上的修复,PR body 引用其 merged commit ef2615c2e011(修复 #44888),回退方案 |= 0 即其 review 结论;标题按 PR body 描述推断。
- PR #47423 Add ECSharedRegion mmap-backed shared region: PR body 说明 ec_shared_region.py 由该 PR 单独引入、未同步 #51116 的修复,是本 bug 残留的根源;标题为按正文描述推断。
参与讨论