Prhub

#24559 Fix weight_checker e2e OOM on 32GB GPU + move to nightly

原始 PR 作者 fzyzcjy 合并时间 2026-05-07 12:06 文件变更 1 提交数 5 评论 7 代码增减 +8 / -1

执行摘要

修复 weight checker 端到端测试 OOM 并移至 nightly

test_weight_checker_e2e 在 32GB 5090 的 per-commit runner 上 OOM(见失败 CI 运行),#24553 通过切换到 large (H100 80GB) 规避,但该测试仍占用 per-commit 池。本 PR 根治 OOM 并将测试移出 per-commit 池。

无需精读,但值得关注其根因分析模式:_check_tensors 的 CPU→GPU 往返在低显存环境下脆弱。该 PR 展示了如何通过调整 mem-fraction 解决显存瓶颈,对类似问题有参考价值。

讨论亮点

无 review 评论。作者在 PR body 中详细描述了 OOM 根因分析:默认 mem-fraction-static=0.88 时 sglang 占用 ~29.43GB(32GB 总量),仅剩 ~207MB 空闲,vocab-embedding 往返需要 ~600MB 导致 OOM。通过两次 rerun-test 验证:第一次在 32GB 5090 上验证 mem-fraction 0.7 修复 OOM(115s 通过),第二次在 H100 80GB nightly runner 上验证测试通过(190s)。

实现拆解

  1. 添加 --mem-fraction-static 0.7 参数:在 setUpClasspopen_launch_server 中传入 other_args=["--mem-fraction-static", "0.7"],降低 sglang 引擎的显存占用比例,留出足够空闲显存供 _check_tensors 的 CPU→GPU 张量往返。
  2. 修改测试套件注册:将 register_cuda_cisuitestage-b-test-1-gpu-large 改为 nightly-1-gpu,并添加 nightly=True,使测试仅每日运行而非每次提交。
  3. 保留 mem-fraction 防御:即使 nightly 运行在 H100 80GB 上,仍保留 0.7 的 mem-fraction 设置,因为 _check_tensors 的显存脆弱性值得在所有环境下防范。
文件 模块 状态 重要度
test/registered/rl/test_weight_checker_e2e.py 权重检查器 modified 4.4

关键源码片段

test/registered/rl/test_weight_checker_e2e.py test-coverage

唯一变更文件,包含两个关键修改:降低 mem-fraction 修复根本 OOM,以及将测试套件移至 nightly。

# test/registered/rl/test_weight_checker_e2e.pyimport unittest
from typing import List, Tupleimport requests
import torchfrom sglang.srt.utils import MultiprocessingSerializer, kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.test_utils import (
    DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
    DEFAULT_URL_FOR_TEST,
    CustomTestCase,
    popen_launch_server,
)# 将测试从 per-commit large 套件移至 nightly 套件,节省每提交 CI 时间
register_cuda_ci(est_time=150, suite="nightly-1-gpu", nightly=True)_MODEL_NAME = "Qwen/Qwen3-0.6B"
# We address the up half via the HF-style unfused name "up_proj.weight". sglang's
# stacked_params_mapping rewrites this to "gate_up_proj.weight" with shard_id=1,
# so the upload writes only the up half of the fused tensor. Sending the fused
# name directly hits a name.replace() collision (gate_up_proj contains up_proj),
# producing a malformed key like "gate_gate_up_proj.weight" and crashing load.
_UP_PROJ_SHAPE = (3072, 1024) # intermediate_size, hidden_size for Qwen3-0.6B
​
​
class TestWeightCheckerE2E(CustomTestCase):
    """All cases share one launched server (setUpClass).    The reset case mutates weights to random; it is named to sort last so any
    case that needs intact weights runs first. The server is torn down right
    after, so leaving the engine in a corrupted state is harmless."""
​
    @classmethod
    def setUpClass(cls):
        cls.url = DEFAULT_URL_FOR_TEST
        # 降低 mem-fraction 至 0.7,确保 _check_tensors 的 CPU->GPU 往返不 OOM
        # 默认 0.88 时 sglang 占用 ~29GB(32GB GPU),仅剩 ~200MB 空闲
        # vocab-embedding 往返需要 ~600MB,导致 snapshot/reset/compare 循环 OOM
        cls.process = popen_launch_server(
            _MODEL_NAME,
            cls.url,
            timeout=DEFAULT_TIMEOUT_FOR_SERVER_LAUNCH,
            other_args=["--mem-fraction-static", "0.7"],
        )
​
    @classmethod
    def tearDownClass(cls):
        kill_process_tree(cls.process.pid)
​
    def _post(self, action: str) -> requests.Response:
        return requests.post(
            f"{self.url}/weights_checker", json={"action": action}, timeout=120
        )
​
    def _update_weights(
        self, named_tensors: List[Tuple[str, torch.Tensor]]
    ) -> requests.Response:
        return requests.post(
            f"{self.url}/update_weights_from_tensor",
            json={
                "serialized_named_tensors": [
                    MultiprocessingSerializer.serialize(named_tensors, output_str=True)
                ],
                "flush_cache": True,
            },
            timeout=120,
        )
​
    def test_a_snapshot_then_compare_unchanged_succeeds(self):
        resp = self._post("snapshot")
        self.assertEqual(resp.status_code, 200)
        self.assertTrue(resp.json()["success"])
​
        resp = self._post("compare")

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 回归风险:降低 mem-fraction-static 可能影响大模型加载,但仅在测试中修改,不影响生产环境。
  2. 测试覆盖风险:移至 nightly 后,per-commit CI 不再运行该测试,可能延迟发现自动化问题,但 unit 测试(test_weight_checker.py)仍在 per-commit 中。
  3. 性能影响:无。

影响范围小,仅限于 weight checker 的端到端测试。正面影响:

1) 解除 32GB GPU 上的 OOM 阻塞;
2) 释放 per-commit CI 资源(150s 测试不再每次提交运行)。

测试覆盖迁移至 nightly mem-fraction 仅测试环境调整

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论