[feat] cosmos3: strict-load verified (identity; needs_conversion=no)

This commit is contained in:
SolitaryThinker
2026-07-21 15:45:37 -07:00
parent 060eec10c6
commit 4ae2a1697b
2 changed files with 140 additions and 219 deletions
+60 -219
View File
@@ -1,246 +1,87 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Convert a Diffusers-format Cosmos3 checkpoint to FastVideo state-dict layout.
"""Cosmos3 checkpoint strict-load verifier (no weight conversion required).
Cosmos3 weights (when published as ``nvidia/Cosmos3-Nano``) ship in a
Diffusers-style namespace with ``model.embed_tokens`` /
``model.layers.{i}.self_attn.*_moe_gen`` / ``vae2llm`` / ``llm2vae`` /
``time_embedder`` layout. FastVideo's ``Cosmos3VFMTransformer`` splits the
UND/GEN paths into separate ``language_model.layers.{i}`` and
``gen_layers.{i}`` subtrees. This script applies the 14-rule remap from
``Cosmos3OmniDiffusersPipeline._remap_ckpt_key`` to produce a state-dict
that loads cleanly into
``fastvideo.models.dits.cosmos3.Cosmos3VFMTransformer``.
The published ``nvidia/Cosmos3-Nano`` checkpoint is diffusers-format and its
transformer weight keys map 1:1 (identity) onto FastVideo's native
``Cosmos3VFMTransformer`` parameters -- ``needs_conversion=no``. There is no
remap to apply; the checkpoint loads directly.
Reference: vllm-omni PR #3454 (https://github.com/vllm-project/vllm-omni/pull/3454),
HEAD ``8536f5b1421f``; upstream ``pipeline_cosmos3.py:319-411``.
This utility verifies strict-load completeness (every checkpoint key has a
matching DiT parameter of the right shape, and every DiT parameter is provided
by the checkpoint) without allocating the full ~30 GB model, by reading
safetensors headers and instantiating the DiT on the ``meta`` device.
Usage:
python scripts/checkpoint_conversion/cosmos3_convert.py \\
--src /path/to/nvidia--Cosmos3-Nano \\
--dst /path/to/cosmos3_nano_fastvideo
python scripts/checkpoint_conversion/cosmos3_convert.py --smoke-test
python scripts/checkpoint_conversion/cosmos3_convert.py \
--transformer official_weights/cosmos3/transformer
"""
from __future__ import annotations
import argparse
from pathlib import Path
import glob
import os
import re
import safetensors.torch
import torch
from safetensors import safe_open
from fastvideo.pipelines.basic.cosmos3.cosmos3_pipeline import (
Cosmos3OmniDiffusersPipeline,
)
from fastvideo.configs.models.dits.cosmos3 import Cosmos3VideoConfig
from fastvideo.models.dits.cosmos3 import Cosmos3VFMTransformer
def convert_state_dict(
src_state: dict[str, torch.Tensor],
) -> tuple[dict[str, torch.Tensor], list[str], list[str]]:
"""Apply the Cosmos3 remap to a single state dict.
Returns ``(new_state, skipped_keys, unmapped_keys)`` where:
* ``new_state`` is the FastVideo-namespaced state dict.
* ``skipped_keys`` lists source keys deliberately dropped by the remap
(currently only ``lm_head.*``; the language-model head is not part of
the diffusion transformer).
* ``unmapped_keys`` lists source keys that did not match any remap rule.
Should be empty for a clean Diffusers Cosmos3 checkpoint; non-empty
results signal either an unexpected upstream key or a missing rule.
"""
new_state: dict[str, torch.Tensor] = {}
skipped: list[str] = []
unmapped: list[str] = []
for src_key, tensor in src_state.items():
new_key = Cosmos3OmniDiffusersPipeline._remap_ckpt_key(src_key)
if new_key is None:
# Either a deliberate skip (lm_head.*) or an unmapped key.
if src_key.startswith("lm_head.") or src_key.startswith(
"transformer.lm_head."):
skipped.append(src_key)
else:
unmapped.append(src_key)
continue
if new_key in new_state:
raise KeyError(
f"Duplicate target key {new_key!r} produced by remap; "
f"colliding source keys include {src_key!r}")
new_state[new_key] = tensor
return new_state, skipped, unmapped
def checkpoint_key_shapes(transformer_dir: str) -> dict[str, tuple[int, ...]]:
"""Read ``{key: shape}`` from a sharded safetensors transformer dir."""
shards = sorted(glob.glob(os.path.join(transformer_dir, "*.safetensors")))
if not shards:
raise FileNotFoundError(f"no .safetensors found in {transformer_dir}")
shapes: dict[str, tuple[int, ...]] = {}
for shard in shards:
with safe_open(shard, framework="pt") as handle:
for key in handle.keys():
shapes[key] = tuple(handle.get_slice(key).get_shape())
return shapes
def convert_checkpoint_dir(src: Path, dst: Path) -> None:
"""Convert all ``*.safetensors`` shards in ``src`` and write to ``dst``.
def verify_strict_load(transformer_dir: str) -> None:
"""Raise SystemExit if the checkpoint does not strict-load into the DiT."""
ckpt = checkpoint_key_shapes(transformer_dir)
cfg = Cosmos3VideoConfig()
with torch.device("meta"):
dit = Cosmos3VFMTransformer(cfg, hf_config={})
params = {name: tuple(p.shape) for name, p in dit.named_parameters()}
buffers = {name for name, _ in dit.named_buffers()}
name_map: dict[str, str] = cfg.arch_config.param_names_mapping
Reads each safetensors shard, applies the remap, and writes a single
consolidated safetensors file at ``dst/model.safetensors``. This keeps
the converter dependency surface small (single file out) for early
Phase 5 work; once shard splitting becomes necessary, switch to
per-shard outputs and emit ``model.safetensors.index.json``.
def remap(key: str) -> str:
for pattern, replacement in name_map.items():
if re.match(pattern, key):
return re.sub(pattern, replacement, key)
return key
TODO: Emit a sharded layout with ``model.safetensors.index.json`` when
the real ``nvidia/Cosmos3-Nano`` weights land and exceed the
single-file safetensors size budget.
"""
src = Path(src)
dst = Path(dst)
if not src.exists():
raise FileNotFoundError(f"Source checkpoint directory not found: {src}")
if not src.is_dir():
raise NotADirectoryError(
f"Source path is not a directory: {src}")
mapped = {remap(key): shape for key, shape in ckpt.items()}
unexpected = sorted(set(mapped) - set(params) - buffers)
missing = sorted(set(params) - set(mapped))
mismatched = [(k, mapped[k], params[k]) for k in (set(mapped) & set(params)) if mapped[k] != params[k]]
shard_paths = sorted(src.glob("*.safetensors"))
if not shard_paths:
raise FileNotFoundError(
f"No *.safetensors files found in {src}. Expected a Diffusers-format "
"checkpoint directory.")
dst.mkdir(parents=True, exist_ok=True)
print(f"Loading {len(shard_paths)} shard(s) from {src}")
src_state: dict[str, torch.Tensor] = {}
for shard_path in shard_paths:
print(f" Reading {shard_path.name}")
shard = safetensors.torch.load_file(str(shard_path))
for key, tensor in shard.items():
if key in src_state:
raise KeyError(
f"Duplicate key {key!r} found across shards "
f"(latest in {shard_path.name}); aborting.")
src_state[key] = tensor
print(f"Loaded {len(src_state)} parameters; applying remap")
new_state, skipped, unmapped = convert_state_dict(src_state)
print(f" Remapped {len(new_state)} parameters")
print(f" Skipped {len(skipped)} parameters (lm_head.*)")
if unmapped:
print(f" WARNING: {len(unmapped)} unmapped key(s):")
for k in unmapped[:10]:
print(f" {k}")
if len(unmapped) > 10:
print(f" ... and {len(unmapped) - 10} more")
out_path = dst / "model.safetensors"
print(f"Writing {out_path}")
safetensors.torch.save_file(new_state, str(out_path))
size_gb = out_path.stat().st_size / (1024**3)
print(f" Wrote {size_gb:.2f} GB to {out_path}")
if unexpected or missing or mismatched:
raise SystemExit("strict-load FAILED: "
f"unexpected={unexpected[:10]} missing={missing[:10]} "
f"shape_mismatch={mismatched[:10]}")
print(f"strict-load OK: {len(ckpt)} checkpoint keys map 1:1 onto "
f"{len(params)} DiT params (identity; needs_conversion=no)")
def smoke_test() -> int:
"""Exercise the remap on a synthetic state dict covering all rule branches.
Runs WITHOUT real weights and acts as a CI guard against drift between
``_remap_ckpt_key`` and this converter. Returns 0 on success, 1 on
failure.
The synthetic input contains one representative key per remap branch:
top-level (``vae2llm`` / ``llm2vae`` / ``time_embedder``), language-model
embeddings/norms, the standalone ``norm_moe_gen``, both UND and GEN
attention/norm/MLP variants inside ``model.layers.{i}.*``, and the
deliberately-skipped ``lm_head.weight``.
"""
expected: dict[str, str | None] = {
# Language-model trunk (UND path).
"model.embed_tokens.weight":
"transformer.language_model.embed_tokens.weight",
"model.norm.weight":
"transformer.language_model.norm.weight",
# Standalone GEN norm at the top of the trunk.
"model.norm_moe_gen.weight": "transformer.norm_moe_gen.weight",
# UND self-attention (representative: q_proj).
"model.layers.3.self_attn.q_proj.weight":
"transformer.language_model.layers.3.self_attn.q_proj.weight",
# GEN cross-attention via *_moe_gen suffix (representative: q_proj).
"model.layers.3.self_attn.q_proj_moe_gen.weight":
"transformer.gen_layers.3.cross_attention.q_proj.weight",
# GEN cross-attention norm (representative: k_norm_moe_gen).
"model.layers.3.self_attn.k_norm_moe_gen.weight":
"transformer.gen_layers.3.cross_attention.k_norm.weight",
# UND block norms.
"model.layers.3.input_layernorm.weight":
"transformer.language_model.layers.3.input_layernorm.weight",
# GEN block norms.
"model.layers.3.input_layernorm_moe_gen.weight":
"transformer.gen_layers.3.input_layernorm.weight",
# UND MLP (representative: gate_proj).
"model.layers.3.mlp.gate_proj.weight":
"transformer.language_model.layers.3.mlp.gate_proj.weight",
# GEN MLP (representative: up_proj).
"model.layers.3.mlp_moe_gen.up_proj.weight":
"transformer.gen_layers.3.mlp.up_proj.weight",
# Top-level generation projections.
"vae2llm.weight": "transformer.vae2llm.weight",
"llm2vae.weight": "transformer.llm2vae.weight",
"time_embedder.linear_1.weight":
"transformer.time_embedder.linear_1.weight",
# Deliberately skipped.
"lm_head.weight": None,
}
synthetic = {k: torch.zeros(1) for k in expected}
new_state, skipped, unmapped = convert_state_dict(synthetic)
failures: list[str] = []
for src_key, expected_tgt in expected.items():
if expected_tgt is None:
if src_key not in skipped:
failures.append(
f" expected {src_key!r} in skipped; "
f"new_state contains: {sorted(new_state)}; "
f"unmapped contains: {unmapped}")
continue
if expected_tgt not in new_state:
failures.append(
f" expected target key {expected_tgt!r} (from src "
f"{src_key!r}) in new_state; got keys {sorted(new_state)}")
if unmapped:
failures.append(
f" unmapped keys (should be empty): {unmapped}")
if failures:
print("smoke_test FAILED:")
for line in failures:
print(line)
return 1
print(
f"smoke_test PASSED: {len(new_state)} new keys, "
f"{len(skipped)} skipped, {len(unmapped)} unmapped")
return 0
def main() -> int:
parser = argparse.ArgumentParser(
description="Convert a Diffusers-format Cosmos3 checkpoint to "
"FastVideo state-dict layout.")
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--src",
type=Path,
default=None,
help="Diffusers-format source checkpoint directory (e.g. an HF "
"snapshot of nvidia/Cosmos3-Nano).")
parser.add_argument(
"--dst",
type=Path,
default=None,
help="Output directory; the converted state dict is written to "
"<dst>/model.safetensors.")
parser.add_argument(
"--smoke-test",
action="store_true",
help="Run a no-weights smoke test that exercises every remap branch "
"on a synthetic state dict. Useful as a CI guard.")
"--transformer",
default=os.path.join("official_weights", "cosmos3", "transformer"),
help="path to the checkpoint transformer/ directory",
)
args = parser.parse_args()
if args.smoke_test:
return smoke_test()
if args.src is None or args.dst is None:
parser.error("--src and --dst are required when not running --smoke-test")
convert_checkpoint_dir(args.src, args.dst)
return 0
verify_strict_load(args.transformer)
if __name__ == "__main__":
raise SystemExit(main())
main()
@@ -0,0 +1,80 @@
# SPDX-License-Identifier: Apache-2.0
"""Strict-load completeness: real Cosmos3-Nano transformer <-> FastVideo DiT.
The published ``nvidia/Cosmos3-Nano`` checkpoint is diffusers-format
(``needs_conversion=no``). This test verifies that EVERY transformer weight key
in the checkpoint maps 1:1 (via the DiT's ``param_names_mapping``) onto a
FastVideo ``Cosmos3VFMTransformer`` parameter of matching shape, and that no DiT
parameter is left unfilled -- i.e. a ``strict=True`` load will succeed.
It runs on the ``meta`` device (no 30 GB allocation, no GPU) by reading only the
safetensors headers. Skips cleanly if the checkpoint is not present locally.
"""
from __future__ import annotations
import glob
import os
import re
import pytest
import torch
pytestmark = [pytest.mark.local]
_CKPT_DIR = os.path.join("official_weights", "cosmos3", "transformer")
def _checkpoint_key_shapes() -> dict[str, tuple[int, ...]]:
from safetensors import safe_open
shards = sorted(glob.glob(os.path.join(_CKPT_DIR, "*.safetensors")))
if not shards:
pytest.skip(f"Cosmos3 transformer checkpoint not present: {_CKPT_DIR}")
out: dict[str, tuple[int, ...]] = {}
for shard in shards:
with safe_open(shard, framework="pt") as f:
for k in f.keys():
out[k] = tuple(f.get_slice(k).get_shape())
return out
def _meta_dit():
from fastvideo.configs.models.dits.cosmos3 import Cosmos3VideoConfig
from fastvideo.models.dits.cosmos3 import Cosmos3VFMTransformer
cfg = Cosmos3VideoConfig()
with torch.device("meta"):
dit = Cosmos3VFMTransformer(cfg, hf_config={})
return dit, cfg
def _apply_mapping(key: str, pmap: dict[str, str]) -> str:
for pat, repl in pmap.items():
if re.match(pat, key):
return re.sub(pat, repl, key)
return key
def test_strict_load_completeness():
ckpt = _checkpoint_key_shapes()
dit, cfg = _meta_dit()
dit_params = {n: tuple(p.shape) for n, p in dit.named_parameters()}
dit_buffers = {n: tuple(b.shape) for n, b in dit.named_buffers()}
pmap = cfg.arch_config.param_names_mapping
mapped = {_apply_mapping(k, pmap): v for k, v in ckpt.items()}
ckpt_names = set(mapped)
param_names = set(dit_params)
buffer_names = set(dit_buffers)
# Every checkpoint key must land on a DiT parameter (non-persistent buffers excepted).
unexpected = sorted(ckpt_names - param_names - buffer_names)
assert not unexpected, f"checkpoint keys with no DiT param: {unexpected[:20]}"
# Every DiT parameter must be provided by the checkpoint (true strict load).
missing = sorted(param_names - ckpt_names)
assert not missing, f"DiT params not provided by checkpoint: {missing[:20]}"
# Shapes must match exactly.
mism = [(k, mapped[k], dit_params[k]) for k in (ckpt_names & param_names) if mapped[k] != dit_params[k]]
assert not mism, f"shape mismatches: {mism[:10]}"
assert len(ckpt) == len(dit_params), f"key count mismatch: ckpt={len(ckpt)} dit={len(dit_params)}"