Prhub

#29217 [MLX] Fix step-bounded profiling for bench tools on Apple Silicon

原始 PR 作者 LijuanTang94 合并时间 2026-07-01 13:55 文件变更 3 提交数 2 评论 4 代码增减 +96 / -0

执行摘要

修复 MLX 后端步长限制分析无法自动停止的问题

PR #29217 是 Apple 设备支持路线图(#19137)Profiling 部分的后续。在 MLX 后端,bench_offline_throughput、bench_one_batch_server 和 bench_serving 无法产出可用的 Metal trace:step-bounded profiling 从不自动停止,导致 GPU 捕获整个生成过程,在 MTL_CAPTURE_ENABLED=1 下更慢,最终调度器 watchdog(300s)超时 SIGQUIT 杀掉服务器。根原因是 MLX overlap loop(event_loop_overlap_mlx)跳过了 Scheduler.run_batch(),而 forward_ct 递增和 profiler predicate 调用恰在 run_batch 中,导致 forward_ct 始终为 0,profiler 的 step 条件无法满足,watchdog 也因 liveness 计数器不更新而误判。

值得精读。该 PR 展示了在非标准调度路径中准确插桩 profiling 钩子的模式,以及如何通过幂等设计提高 CLI 工具的健壮性。对于维护 MLX 后端或类似定制调度逻辑的开发者有直接参考价值。

讨论亮点

Reviewer jlee5814 指出:当 --profile-steps N 指定的步数超过实际生成步数时,monitor_trace_file 会无限循环等待,建议将 stop_profile 调用包裹为幂等。作者随后修改,添加 try/except RuntimeError。该提议被接纳并合并。

实现拆解

  1. 在 MLX 重叠调度关键路径添加 profiler 支持:在 python/sglang/srt/hardware_backend/mlx/scheduler_mixin.py_finalize_mlx_pending_job 方法开头增加 self.forward_ct += 1self.profiler_manager._profile_batch_predicate(pending.schedule_batch),模拟 run_batch() 中的行为。
  2. 使 bench 工具 stop_profile 幂等:在 python/sglang/benchmark/offline_throughput.py 中,将直接调用 backend.stop_profile() 替换为 try: backend.stop_profile() except RuntimeError: pass,以兼容调度器自动停止后再次调用的情况。
  3. 新增单元测试:新增 test/registered/unit/hardware_backend/mlx/test_scheduler_mixin.py,覆盖 forward_ct 递增和 profiler predicate 调用,验证每次 job finalize 恰好对应一次计数和一次 predicate,且 process_batch_result 仍正常执行。
文件 模块 状态 重要度
python/sglang/srt/hardware_backend/mlx/scheduler_mixin.py MLX 调度器 modified 6.07
python/sglang/benchmark/offline_throughput.py 基准工具 modified 5.61
test/registered/unit/hardware_backend/mlx/test_scheduler_mixin.py 测试 added 7.26

关键符号

SchedulerMlxOverlapMixin._finalize_mlx_pending_job

关键源码片段

python/sglang/srt/hardware_backend/mlx/scheduler_mixin.py core-logic

核心修复:在 _finalize_mlx_pending_job 中添加 forward_ct 递增和 profiler predicate 调用,使 step-bounded profiling 在 MLX 重叠循环中生效。

def _finalize_mlx_pending_job(self: Scheduler, pending: MlxPendingJob):
    # 前进 forward_ct 并触发 profiler 批处理谓词;
    # 标准调度器在 run_batch 中做这些,但 MLX 重叠循环绕过 run_batch,
    # 因此在此处手动补齐,使得 step-bounded profiling 能自动停止。
    self.forward_ct += 1
    self.profiler_manager._profile_batch_predicate(pending.schedule_batch)
​
    # 原有 finalize 逻辑保持不变
    result = self.tp_worker.finalize_mlx_result(
        pending.prefills, pending.extends,
        pending.decode, pending.mode, pending.reqs,
    )
    if result.next_token_ids is not None:
        pending.batch_copy.input_ids = result.next_token_ids
        pending.schedule_batch.input_ids = result.next_token_ids
    self.last_batch = pending.schedule_batch
    self.process_batch_result(pending.batch_copy, result)
python/sglang/benchmark/offline_throughput.py core-logic

为 stop_profile 添加幂等处理,防止调度器已自动停止后再次调用抛异常。

    if profile:
        dir = os.getenv("SGLANG_TORCH_PROFILER_DIR")
        if not profile_steps:
            known_files = set(os.listdir(dir))
        # 当 --profile-steps 指定步数时,调度器会在 N 步后自动停止;
        # 此时再调 stop_profile 会抛 "not in progress" RuntimeError。
        # 短于 N 步的 runs 未达目标,仍需显式停止。
        # 无论如何,stop 必须在 monitor_trace_file 之前完成。
        try:
            backend.stop_profile()
        except RuntimeError:
            pass
        monitor_trace_file(known_files, dir)
test/registered/unit/hardware_backend/mlx/test_scheduler_mixin.py test-coverage

新增单元测试,验证 forward_ct 递增和 profiler predicate 调用,防止回归。

def test_finalize_advances_forward_ct_and_runs_predicate(self):
    from sglang.srt.hardware_backend.mlx.scheduler_mixin import (
        SchedulerMlxOverlapMixin,
    )
    scheduler = self._make_scheduler()
    pending = MagicMock()
    SchedulerMlxOverlapMixin._finalize_mlx_pending_job(scheduler, pending)
    # forward_ct 应从 0 变为 1
    self.assertEqual(scheduler.forward_ct, 1)
    # profiler 批处理谓词应被调用一次
    scheduler.profiler_manager._profile_batch_predicate.assert_called_once_with(
        pending.schedule_batch
    )
    # 原有 finalize 流程仍正常执行
    scheduler.process_batch_result.assert_called_once()def test_forward_ct_advances_once_per_step(self):
    # 连续调用三次,验证 forward_ct 每次递增 1,数量匹配

评论区精华

stop_profile 幂等性需求 正确性

reviewer jlee5814 指出当 --profile-steps 指定步数超过实际生成步数时,monitor_trace_file 会挂起,建议将 stop_profile 调用包裹为幂等。

结论:作者添加 try/except RuntimeError 实现幂等,被接受。 · 已解决

风险与影响

变更仅影响 MLX 后端且 profiling 启用时,正常推理不受影响。新代码引入的整数递增和方法调用开销可忽略(O(1))。风险点在于 self.profiler_manager 在 mixin 中是否确保初始化;单元测试通过 MagicMock 验证了调用,但在实际调度器中可能因缺失属性导致 AttributeError。不过 profiler_manager 在 Scheduler 基类中已有默认初始化,因此风险低。

用户影响:Apple Silicon 上使用 MLX 后端的开发者,现在可以通过 --profile-steps 正常进行 step-bounded profiling,获得可用的 .gputrace 文件,且不再因 watchdog 超时而崩溃。
系统影响:无性能退化,因 forward_ct 递增和空 predicate 不在标准路径上。
团队影响:为后续 MLX 性能分析工作流铺平道路,减少调试 profiling 工具的时间。

MLX 后端变更 步长分析修复 幂等设计

关联 Issue

#19137 [Roadmap] Apple Device Support (2026 Q2)
#28122 [MLX] Add Metal profiling hooks to server profiler

完整报告

参与讨论