Prhub

#31590 Add Cosmos3 Edge and Distilled checkpoints support

原始 PR 作者 nv-dmajchrowski 合并时间 2026-08-12 10:29 文件变更 13 提交数 20 评论 22 代码增减 +1036 / -104

执行摘要

Cosmos3 Edge 与蒸馏变体支持,变体自动识别

PR body 明确:"All variants continue to be served by the single Cosmos3 pipeline, with the variant selected from the checkpoint config at load time." 此前 PR#24994、PR#26492、PR#27168 建立了 Cosmos3 家族基础支持,本 PR 在此基础上扩展 Edge 与 Distilled 变体。PR 评论中 nv-dmajchrowski 补充:"after the previous approval we had to add few more changes (edge checkpoint format changed slightly after the release + we had small accuracy issues)",说明 Edge 发布后 checkpoint 格式有微调,本 PR 是完整适配。

值得精读。重点看三处设计:① 变体检测一次性预计算(update_config_from_dict + lru_cache)避免 per-request 下载 checkpoint 配置,这是 diffusion 多变体服务的通用模式;② Ulysses SP 下 SDE 噪声生成器跨 rank 同步的思路,可复用到其它扩散模型;③ _predict_noise_cfg 对 CFG padding 在无 mask cross-attention 下语义的分析(零 K/V 仍占 softmax 权重),是少见的数值质量权衡。另外 review 中 mickqian 对防御性代码的两次拒绝(fail-fast、显式契约)展示了该仓库的工程偏好,值得团队对齐。

讨论亮点

围绕 5 个话题展开,其中 3 个被采纳修复、2 个被维护者明确拒绝。核心交锋:① gemini-code-assist[bot] 指出蒸馏 sigma 双重 shift 问题,mickqian 确认在 commit 78ce372 修复;② kediwu0331 发现 guidance_scale 默认 None 导致 warmup 崩溃,作者随即修复;③ mickqian 两次拒绝 bot 的防御性建议(model_path 空值防护、inspect.signature try-except),理由是 fail-fast 优于静默选错变体、过滤契约必须可检查;④ bot 建议 generator 用 latents.device,已被采纳。

实现拆解

  1. 变体检测与预计算:在 configs/pipeline_configs/cosmos3.py 新增 is_edge_checkpoint(读取 transformer/config.jsonbackbone_typehidden_act=relu2)、_distilled_sampler_config(读取 scheduler config 的 _class_namefixed_step_sampler_config.t_list)、get_distilled_sigmas,并使用 functools.lru_cache 缓存;Cosmos3Config.update_config_from_dictmodel_path 就绪后一次性算出 is_edgedistilled_sigmas 存入配置对象,避免每个请求重复下载 checkpoint 子目录(commit 3b51f64 的优化)。

  2. Edge 架构支持configs/models/dits/cosmos3video.py_build_cosmos3_param_names_mapping 参数化为 gated_mlp 模式,dense 变体跳过 gate/up 合并规则,up_proj/down_proj 直通;Cosmos3VideoArchConfig 新增 hidden_actqk_norm_for_textuse_und_k_norm_for_gen 字段。runtime/models/dits/cosmos3video.py 新增 Cosmos3DenseMLP(up → ReLU → 平方 → down)与 _build_mlp 工厂,并按 hidden_act 选择 MLP 结构;Cosmos3CausalAttention 支持可选 QK-norm 与独立的 k_norm_und_for_gen(GEN 交叉注意力单独归一化 K)。

  3. 采样默认值configs/sample/cosmos3.py 新增 _resolve_variant_defaults,按 is_edge / is_distilled 解析;height/width 默认改为 None 由变体填充;Edge 使用 256p/480p 分辨率白名单与对应默认尺寸,T2I/T2V 分别用 guidance 7.0/5.0,T2I 默认 CFG 区间 (400, 1000);蒸馏变体强制 guidance_scale = 1.0num_inference_steps 固定为 sigma 表长度(期间修复了默认 None 导致 warmup 崩溃的问题)。

  4. 调度与去噪Cosmos3TimestepPreparationStage 蒸馏路径先 set_shift(1.0) 再用 set_timesteps(sigmas=distilled_sigmas) 驱动 scheduler,防止双重重偏移;_default_flow_shift_for_mode 对 Edge 视频模式统一返回 3.0。Cosmos3DenoisingStage 将批次 seed 构造的 torch.Generator(device 取 latents.device)传入 scheduler step,保证 Ulysses SP 下各 rank 噪声一致;_predict_noise_cfg 在正负 prompt 长度不等时把 CFG 两个分支拆成独立 forward,避免零 padding 的 K/V 稀释注意力。scheduler_loader.py 新增 _supported_init_kwargs,按构造函数签名过滤 scheduler_config 不接受的键。

  5. 兼容与配套registry.py 注册 Cosmos3OmniPipeline 并保留 Cosmos3OmniDiffusersPipeline 别名;hf_diffusers_utils.py 新增 _split_hf_subfolder 支持 namespace/repo/subfolder 局部下载;权重加载时丢弃 *_quantizer.* ModelOpt 校准缓存;action/api.py 增加 multipart/form-data 上传支持;video_api.py 新增 form_text_value 防止 JSON prompt 被反序列化。测试 test_cosmos3.py 新增 dense 参数映射、Edge flow_shift/采样默认值、_class_name 双拼写注册等用例。

文件 模块 状态 重要度
python/sglang/multimodal_gen/configs/pipeline_configs/cosmos3.py 管线配置 modified 8.43
python/sglang/multimodal_gen/runtime/models/dits/cosmos3video.py 模型层 modified 8.84
python/sglang/multimodal_gen/configs/sample/cosmos3.py 采样配置 modified 7.84
python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/cosmos3.py 管线阶段 modified 8.73
python/sglang/multimodal_gen/configs/models/dits/cosmos3video.py 模型配置 modified 7.81
python/sglang/multimodal_gen/test/unit/test_cosmos3.py 单元测试 modified 7.52
python/sglang/multimodal_gen/runtime/loader/component_loaders/scheduler_loader.py 组件加载 modified 6.8
python/sglang/multimodal_gen/runtime/utils/hf_diffusers_utils.py 工具层 modified 6.64
python/sglang/multimodal_gen/runtime/entrypoints/action/api.py 入口服务 modified 8.36
python/sglang/multimodal_gen/runtime/entrypoints/openai/video_api.py 入口服务 modified 6.15

关键符号

is_edge_checkpoint is_distilled_checkpoint get_distilled_sigmas update_config_from_dict Cosmos3DenseMLP _build_mlp _build_cosmos3_param_names_mapping _resolve_variant_defaults _guidance_is_explicit _inject_caption_metadata _default_flow_shift_for_mode _predict_noise_cfg _supported_init_kwargs _split_hf_subfolder _multipart_action_payload form_text_value

关键源码片段

python/sglang/multimodal_gen/configs/pipeline_configs/cosmos3.py core-logic

变体检测核心:is_edge_checkpoint / _distilled_sampler_config / get_distilled_sigmas,并在 update_config_from_dict 中一次性预计算,per-request 路径都从这里读。

# Cosmos3 变体检测:Edge 与蒸馏 checkpoints 的识别全部基于
# checkpoint 自身配置,而不是硬编码模型名,保证同一管线可服务整个家族。
# 函数带 lru_cache,model_path 首次解析后结果常驻内存。@functools.lru_cache(maxsize=None)
def is_edge_checkpoint(model_path: str) -> bool:
    """判断 checkpoint 是否为 Edge(dense 骨干)变体。    直接从 transformer/config.json 读取,早于权重加载,
    因此客户端进程在解析采样默认值时无需把权重搬上设备。
    """
    from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
        get_diffusers_component_config,
    )
​
    config = get_diffusers_component_config(
        component_path=os.path.join(model_path, "transformer")
    )
    # Edge 的 dense 骨干可能以 backbone_type 或 hidden_act=relu2 之一标记
    return (
        config.get("backbone_type") == COSMOS3_EDGE_BACKBONE_TYPE
        or config.get("hidden_act") == "relu2"
    )
​
​
@functools.lru_cache(maxsize=None)
def _distilled_sampler_config(model_path: str) -> dict | None:
    """读取蒸馏 checkpoint 的固定步 sigma 表,非蒸馏返回 None。    蒸馏只体现在 scheduler:checkpoint 自带
    FlowMatchEulerDiscreteScheduler 与 fixed_step_sampler_config.t_list,
    而其余变体使用多步 FlowUniPC。
    """
    from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import (
        get_diffusers_component_config,
    )
​
    config = get_diffusers_component_config(
        component_path=os.path.join(model_path, "scheduler")
    )
    # 只有显式声明了固定步表才判定为蒸馏变体
    if config.get("_class_name") != "FlowMatchEulerDiscreteScheduler":
        return None
    sampler = config.get("fixed_step_sampler_config")
    if not sampler or not sampler.get("t_list"):
        return None
    return sampler
​
​
def get_distilled_sigmas(model_path: str) -> list[float] | None:
    """向调度阶段暴露蒸馏 sigma 表;非蒸馏 checkpoint 返回 None。"""
    sampler = _distilled_sampler_config(model_path)
    return list(sampler["t_list"]) if sampler is not None else None
python/sglang/multimodal_gen/runtime/models/dits/cosmos3video.py data-contract

Edge dense 骨干的模型侧实现:新增 Cosmos3DenseMLP 与 _build_mlp 工厂,Cosmos3CausalAttention 支持可选 QK-norm 与 GEN 侧独立 K 归一化。

class Cosmos3DenseMLP(nn.Module):
    """dense MLP:down(relu(up(x)) ** 2),Edge 变体的非门控结构。    与 SwiGLU 的 gate_up_proj 合并权重布局不同,dense 变体只有独立的
    up_proj / down_proj,权重加载走未合并的直通路径。
    """
​
    def __init__(
        self,
        hidden_size: int,
        intermediate_size: int,
        prefix: str = "",
        quant_config: QuantizationConfig | None = None,
    ):
        super().__init__()
        # up_proj 不合并 gate,直接展开放大中间维度
        self.up_proj = ColumnParallelLinear(
            hidden_size,
            intermediate_size,
            bias=False,
            gather_output=False,
            quant_config=quant_config,
            prefix=add_prefix("up_proj", prefix),
        )
        self.down_proj = RowParallelLinear(
            intermediate_size,
            hidden_size,
            bias=False,
            input_is_parallel=True,
            quant_config=quant_config,
            prefix=add_prefix("down_proj", prefix),
        )
​
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        up, _ = self.up_proj(x)
        up = F.relu(up)
        out, _ = self.down_proj(up * up) # squared-ReLU:relu(x) ** 2
        return out
​
​
def _build_mlp(
    hidden_act: str,
    hidden_size: int,
    intermediate_size: int,
    prefix: str,
    quant_config: QuantizationConfig | None,
) -> nn.Module:
    """按 hidden_act 选择 MLP 结构:relu2 走 dense,其余走 SwiGLU 门控。"""
    mlp_cls = Cosmos3DenseMLP if hidden_act == "relu2" else Cosmos3GatedMLP
    return mlp_cls(
        hidden_size=hidden_size,
        intermediate_size=intermediate_size,
        prefix=prefix,
        quant_config=quant_config,
    )
python/sglang/multimodal_gen/configs/sample/cosmos3.py core-logic

变体采样默认值(分辨率、guidance、CFG 区间、步数)在此解析,直接影响每个请求的采样行为。

def _resolve_variant_defaults(self, is_edge: bool, is_distilled: bool = False) -> None:
    """按变体补齐未显式指定的分辨率与 guidance 默认值。    Edge 只在 256p/480p 分辨率上训练过,更大尺寸会把空间 mRoPE
    网格推到训练范围外导致画面破碎,因此必须收窄分辨率白名单并
    给出对应的默认尺寸。
    """
    is_t2i = self.num_frames == 1
    if is_distilled:
        # 蒸馏模型把 guidance 烘焙进权重,推理只需单次 forward,
        # 关闭 CFG 并强制 guidance_scale = 1.0
        self.guidance_scale = 1.0
    elif is_edge and not self._guidance_is_explicit():
        # 用户显式传入 guidance_scale 时尊重显式值,避免被默认覆盖
        self.guidance_scale = (
            COSMOS3_EDGE_T2I_GUIDANCE_SCALE
            if is_t2i
            else COSMOS3_EDGE_T2V_GUIDANCE_SCALE
        )
    # T2I 只在噪声高的 [400, 1000] 区间施加 CFG;
    # 低噪声步的 guidance 已被证明会损害采样质量
    if is_t2i and not is_distilled and self.guidance_interval is None:
        self.guidance_interval = COSMOS3_T2I_GUIDANCE_INTERVAL
    if is_edge:
        self.supported_resolutions = COSMOS3_EDGE_SUPPORTED_RESOLUTIONS
        if self.height is None and self.width is None:
            if is_t2i:
                self.width = self.height = COSMOS3_EDGE_T2I_SIZE
            else:
                self.width, self.height = (
                    COSMOS3_EDGE_T2V_WIDTH,
                    COSMOS3_EDGE_T2V_HEIGHT,
                )

评论区精华

蒸馏 checkpoint 的 sigma 双重 shift 正确性

gemini-code-assist[bot] 指出蒸馏 checkpoint 的 sigma 表已内置 shift,若 scheduler 的 shift 配置不是 1.0,set_timesteps 会再次应用 shift 导致双重偏移,建议在 set_timesteps 前 set_shift(1.0)。

结论:mickqian 确认已在 commit 78ce372 修复:蒸馏路径先 set_shift(1.0) 再应用 checkpoint 提供的 sigma 表。 · 已解决

guidance_scale 默认 None 导致 warmup 崩溃 正确性

kediwu0331 报告将 guidance_scale 默认值改为 None 后,warmup 请求构造时 Req.validate 比较 None > 1.0 抛 TypeError(' > ' not supported between instances of 'NoneType' and 'float')。

结论:nv-dmajchrowski 已推送修复,恢复有值默认并配合 _guidance_is_explicit 判断。 · 已解决

is_edge_checkpoint 是否需要空 model_path 防护 question

gemini-code-assist[bot] 建议对 None/ 空 model_path 提前 return False 防止 os.path.join 抛 TypeError。mickqian 拒绝:该函数只在 ServerArgs 与管线配置解析出必填 model_path 后调用;对非法内部状态返回 False 会静默选错 Cosmos3 变体,反而掩盖配置错误。

结论:不添加防护,保持 fail-fast。 · 已解决

inspect.signature 是否加 try-except 兜底 设计

gemini-code-assist[bot] 建议包裹 ValueError 防止签名不可解析时崩溃。mickqian 拒绝:scheduler_cls 来自 ModelRegistry,必须是可检查签名的 Python 类,否则过滤逻辑本身无法保证正确;回退到未过滤配置只是把同样的不兼容推迟到构造调用并掩盖根因。

结论:不添加兜底。 · 已解决

随机噪声 generator 的 device 选择 正确性

gemini-code-assist[bot] 建议用 latents.device 而非 get_local_torch_device(),避免 CPU offload 与单测场景下 device 不一致导致调度器输入设备不匹配。

结论:mickqian 确认已在 commit 78ce372 改为 latents.device。 · 已解决

风险与影响

  1. 调度器配置键过滤scheduler_loader._supported_init_kwargs 依赖 inspect.signature 过滤配置键,若调度器类不可检查(如动态生成类),当前实现会直接抛异常;mickqian 明确拒绝加 try-except 兜底,该假设是硬性的,未来接入新调度器时需要留意。
  2. checkpoint 配置读取崩溃is_edge_checkpoint_distilled_sampler_configmodel_path 为空或路径非法时 os.path.join 会抛 TypeError,且 get_diffusers_component_config 读取失败会中断服务启动——这是有意的 fail-fast,但对间接调用路径(如测试 mock 或非 Cosmos3 配置)可能引入意外崩溃。
  3. CFG 分支性能回退_predict_noise_cfg 在正负 prompt 长度不一致时拆成两次独立 forward,Edge 默认 negative_prompt="" 与长 prompt 之间几乎必然触发该路径,推理耗时约为合并批次的两倍(但避免了 padding 造成的质量损失)。
  4. 蒸馏步数契约:distilled 路径忽略用户传入的 num_inference_steps,步数固定为 sigma 表长度,用户期望与实际行为可能不一致。
  5. SP 噪声行为改变:SP 下 SDE 噪声从各 rank 独立随机改为种子一致,修复 shard-boundary artifact 的同时改变了既有输出的随机性分布,属于有意的行为变更。
  6. multipart form 新逻辑:action 端点的 multipart 解析、上传文件落盘与临时目录清理是新增入口逻辑,文件路径拼接使用 request_id + filename,存在路径注入与文件清理风险,需要验证。

功能影响:Cosmos3 全家族(Nano/Super/Edge,普通与蒸馏)由一个管线统一服务,checkpoint 变体在加载时自动识别;旧 checkpoint 的 _class_name 拼写(Cosmos3OmniDiffusersPipeline)继续可用,无需用户迁移。系统影响:扩散管线的时间步准备与噪声生成路径改动影响所有 Cosmos3 请求;蒸馏请求默认走固定 sigma 表,不再受 num_inference_steps 控制。团队影响:diffusion 维护者需要理解变体检测的缓存时机与 CFG 拆分语义;后续新增变体只需扩展检测函数与默认值表。用户影响:Edge 分辨率被限制在 256p/480p,显式请求更高分辨率会被拒绝或警告,属于有意的产品约束,需要文档说明。

核心路径变更 checkpoint 格式兼容 蒸馏调度契约 CFG 分支性能回退 SP 噪声一致性

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论