执行摘要
- 一句话:明确CUDA cache清理时机,移除stage边界隐式清理
- 推荐动作:推荐阅读,尤其是以下设计决策:
- 将隐式副作用(debug日志中的
empty_cache)显式化,分离观测和清理职责,是良好的工程实践。
- 只在
memory_intensive组件释放且满足特定条件(CUDA存储释放或LayerwiseOffloadStrategy)时清理,避免频繁empty_cache。
- 性能基线的同步更新策略以及为有波动的stage设置动态容差的方法值得借鉴。
功能与动机
旧debug内存日志在每次stage边界隐式调用empty_cache(),导致debug日志改变请求延迟和stage计时;完全移除后又可能在请求失败或大组件释放后留下内存压力。本PR将cache清理移动到明确的点:请求失败/OOM处理,以及组件释放(_empty_cache_after_large_release)时。
实现拆解
- 组件管理器 (component_manager.py):新增
_module_on_cuda和_empty_cache_after_large_release方法,在_finish_use和finish_request中调用。仅对memory_intensive组件且在释放CUDA存储或使用LayerwiseOffloadStrategy时执行empty_cache(),替代原先每个stage边界隐式清理。
- 性能日志 (perf_logger.py):修改
StageProfiler.__enter__中的debug内存日志,调用current_platform.get_available_gpu_memory(empty_cache=False),保持观察性而不触发清理。
- GPU工作器 (gpu_worker.py):在异常捕获片段中,若为OOM异常,添加
torch.cuda.empty_cache(),确保失败后即时释放。
- 性能基线 (perf_baselines.json):收紧
joyai_image_edit_ti2i和qwen_image_edit_2511_ti2i等模型的多个stage期望值(如ImageEncodingStage从948ms降至740ms,ImageVAEEncodingStage从96ms降至84ms等)。
- 测试工具 (test_server_utils.py):在stage验证逻辑中,对
DecodingStage增加90%相对容差和250ms最小绝对容差,以容忍因异步work归属导致的波动;其他stage保持原120ms绝对容差。
关键文件:
python/sglang/multimodal_gen/runtime/managers/component_manager.py(模块 组件管理器;类别 source;类型 core-logic;符号 _module_on_cuda, _empty_cache_after_large_release): 核心变更,新增_module_on_cuda和_empty_cache_after_large_release方法,控制cache清理时机。
python/sglang/multimodal_gen/runtime/utils/perf_logger.py(模块 性能日志;类别 source;类型 core-logic): 修改debug内存日志,不再隐式调用empty_cache,保持观测性。
python/sglang/multimodal_gen/runtime/managers/gpu_worker.py(模块 GPU工作器;类别 source;类型 core-logic): 在OOM异常处理后添加empty_cache,确保失败后内存释放。
python/sglang/multimodal_gen/test/server/perf_baselines.json(模块 性能基线;类别 test;类型 test-coverage): 更新性能基线以匹配新的stage计时。
python/sglang/multimodal_gen/test/server/test_server_utils.py(模块 测试工具;类别 test;类型 test-coverage): 调整DecodingStage的测试容差,避免因异步work归属导致flaky。
关键符号:_module_on_cuda, _empty_cache_after_large_release, StageProfiler.enter, _execute_forward_common (OOM处理)
关键源码片段
python/sglang/multimodal_gen/runtime/managers/component_manager.py
核心变更,新增_module_on_cuda和_empty_cache_after_large_release方法,控制cache清理时机。
# 在 ComponentResidencyManager 类中
def _module_on_cuda(self, module: nn.Module | None) -> bool:
"""返回模块是否在 CUDA 上"""
return self._module_device(module) == "cuda"
def _empty_cache_after_large_release(
self,
use: ComponentUse,
strategy: ComponentResidencyStrategy,
module: nn.Module,
was_on_cuda: bool,
) -> None:
"""在可能释放大组件后显式清理 CUDA 缓存"""
if not use.memory_intensive: # 仅针对标记为 memory intensive 的组件
return
# 检查是否从 CUDA 移出(释放了 CUDA 存储)
released_cuda_storage = was_on_cuda and not self._module_on_cuda(module)
# 或者是 LayerwiseOffloadStrategy(逐层卸载)
released_layerwise_storage = isinstance(strategy, LayerwiseOffloadStrategy)
if not (released_cuda_storage or released_layerwise_storage):
return
if not torch.get_device_module().is_available():
return
torch.get_device_module().empty_cache()
self._trace("empty_cache", use, strategy, module, detail="after_release")
def _finish_use(self, use, *, module=None, keep_on_warmup=False):
module = module or self.get_module(use.component_name)
if module is None:
self._trace("skip_missing", use)
return
should_keep = (keep_on_warmup and self.state.batch_is_warmup) \
or self._should_keep_after_use(use)
if should_keep:
self._trace("keep", use, self.strategy_for(use.component_name, module), module)
return
strategy = self.strategy_for(use.component_name, module)
self._trace("finish", use, strategy, module)
# 新增:记录当前模块是否在 CUDA 上
was_on_cuda = self._module_on_cuda(module)
strategy.finish_use(module, use, self.state)
# 新增:在可能释放大组件后清理 cache
self._empty_cache_after_large_release(use, strategy, module, was_on_cuda)
python/sglang/multimodal_gen/runtime/utils/perf_logger.py
修改debug内存日志,不再隐式调用empty_cache,保持观测性。
def __enter__(self):
if self.log_stage_start_end:
msg = f"[{self.stage_name}] started..."
if self.logger.isEnabledFor(logging.DEBUG):
# 保持观测性:获取可用内存但不隐式调用 empty_cache
available_memory = current_platform.get_available_gpu_memory(
empty_cache=False
)
msg += f" ({round(available_memory, 2)} GB left)"
self.logger.info(msg)
# ... 其余部分
评论区精华
本PR无review评论,由作者直接合并。
风险与影响
- 风险:
- 若
memory_intensive标记不准确,某些大组件释放后可能不会触发cache清理,但OOM路径始终会清理,降低了风险。
- 放宽
DecodingStage容差(90% + 250ms)可能掩盖该stage的真实性能回归,需关注CI长期趋势。
- 收紧的性能基线在硬件或驱动变化时可能误报,需定期维护。
- 整体风险较低,变更经过CI验证(NVIDIA CI job 25357009336和74363515606均通过)。
- 影响:
- 用户:成功请求不再支付per-stage
empty_cache开销,延迟降低(如qwen_image_edit_2511_ti2i E2E从23525ms降至23405ms,joyai_image_edit_ti2i ImageEncodingStage从948ms降至740ms)。
- 系统:GPU内存清理时机从“每个stage”变为“失败或大组件释放后”,减少不必要的同步开销。
- 团队:需要同步本地测试基线;CI中解码阶段容差放宽降低flaky,但需注意不掩盖回归。
- 风险标记:内存回收时机变更, 测试容差放宽, 性能基线收紧, 核心路径变更
关联脉络
参与讨论