Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
492bea4
Add Cosmos3-Edge inference checkpoint (HF-backed, 2B)
pengcuo Jul 9, 2026
026f14b
docs(inference): document Cosmos3-Edge
pengcuo Jul 9, 2026
5bc4c4c
Merge pull request #1 from nvidia-cosmos/add-cosmos3-edge
lfengad Jul 9, 2026
92b21fc
Merge origin/main (b2237fd) into private mirror main
lfengad Jul 9, 2026
87ce58a
feat(reasoner): image-conditioned generation for Nemotron 3 Dense VL …
pengcuo Jul 10, 2026
fe0d62a
Merge origin/main (3d9c087) into private mirror main (#4)
lfengad Jul 10, 2026
1bc261d
Add Cosmos3-Edge generator / reasoner SFT examples (#5)
Xuanmeng-Zhang Jul 11, 2026
a47f4ab
Merge origin/main (0fa3ba4) into private mirror main
lfengad Jul 11, 2026
45b4927
Merge origin/main (1e139fb) into private mirror main (#6)
lfengad Jul 11, 2026
e19b7b2
Merge latest private mirror main (45b4927) into sync branch
lfengad Jul 11, 2026
5c39eb8
Merge pull request #7 from nvidia-cosmos/oss-sync-2026-07-11-2
lfengad Jul 12, 2026
6b115bf
tune: raise vision full fine-tune SFT lr 2e-5 -> 1e-4
Xuanmeng-Zhang Jul 13, 2026
0df4048
Merge pull request #9 from nvidia-cosmos/tune-vision-sft-lr
lfengad Jul 13, 2026
85f1712
test: pin vision_sft_nano regression to lr=2e-5 (goldens capture value)
Xuanmeng-Zhang Jul 13, 2026
b740a1c
Merge pull request #11 from nvidia-cosmos/tune-vision-sft-lr
lfengad Jul 13, 2026
05536e6
feat(reasoner): video-conditioned generation for Nemotron 3 Dense VL …
pengcuo Jul 13, 2026
48bcd54
Merge pull request #10 from nvidia-cosmos/feat/edge-reasoner-video-co…
lfengad Jul 14, 2026
38cb38e
Release 2026-07-13 (i4 c0d7b004) (#108)
yy-code-nv Jul 14, 2026
35f3e1c
feat: wire MXFP8/NVFP4 quantization into inference CLI (#113)
lfengad Jul 15, 2026
0454b64
Sync Cosmos3 Diffusers converter support (#110)
lfengad Jul 15, 2026
f6ef504
Release 2026-07-14 (i4 b66cfd81) (#109)
yy-code-nv Jul 15, 2026
e1a8dc8
Merge remote-tracking branch 'origin/main' into merge-oss-main-2026-0…
lfengad Jul 16, 2026
ced2f29
Merge pull request #17 from nvidia-cosmos/merge-oss-main-2026-07-15
lfengad Jul 16, 2026
417643d
cleanup(reasoner): dead code, i18n, and file handles in Nemotron 3 De…
pengcuo Jul 16, 2026
d549fbb
Merge pull request #19 from nvidia-cosmos/pzeren/edge-reasoner-review…
lfengad Jul 16, 2026
e01e359
fix(sft): Cosmos3-Edge vision SFT NaN loss — scope action_gen=False t…
Xuanmeng-Zhang Jul 16, 2026
2706a4f
Release 2026-07-15 (i4 a54b7da3) (#14)
kane-vln Jul 17, 2026
65c169a
feat(edge): migrate Edge SFT recipes to renewed nvidia/Cosmos3-Edge (…
Xuanmeng-Zhang Jul 17, 2026
34462bd
fix(ci): retry hf downloads once with `uvx --refresh` (stale-index ra…
Xuanmeng-Zhang Jul 17, 2026
4f8e12a
fix(edge): reasoner vision tower config fallback + raise default max_…
pengcuo Jul 17, 2026
2f490cd
feat(action): Cosmos3-Edge DROID action-policy post-training recipe (…
fwd4 Jul 19, 2026
0a3cc4a
docs(training): document Edge ViT export (--no-vit / --vit-checkpoint…
lfengad Jul 19, 2026
b0d5d5e
Add support for Cosmos3-Edge-Policy-DROID in robolab policy server (#27)
ychao-nvidia Jul 19, 2026
69b35e6
Update doc for Cosmos3-Edge-Policy-DROID (#26)
ychao-nvidia Jul 19, 2026
cc414ac
docs+examples: fix DROID_ROOT guidance for the public Cosmos3-DROID d…
fwd4 Jul 19, 2026
d23d06f
feat(scripts): sync Cosmos3 Edge + distilled diffusers converter (#29)
lfengad Jul 19, 2026
3daef91
Remove the Cosmos3-Edge DROID action-policy recipe (#30)
fwd4 Jul 20, 2026
97223ed
feat(diffusers): include Edge generator-only K norm in converter (pic…
lfengad Jul 20, 2026
8cf08b4
fix(inference): enable und-K norm for Cosmos3-Edge generator (#32)
pengcuo Jul 20, 2026
87d717f
fix(inference): load diffusers checkpoints with malformed k-norm root…
pengcuo Jul 20, 2026
3e69b0a
Merge remote-tracking branch 'origin/main' into merge-oss-main-2026-0…
lfengad Jul 20, 2026
888f9eb
fix(edge): adopt the HF K-norm restoration in the Edge SFT recipe (#34)
Xuanmeng-Zhang Jul 20, 2026
de8adf5
Merge OSS main into private main (2026-07-19) (#35)
lfengad Jul 20, 2026
2bdf91d
chore(edge): drop pre-K-norm transition handling; keep comments brief…
Xuanmeng-Zhang Jul 20, 2026
251facc
Release 2026-07-20 (i4 edfc054c) (#36)
kane-vln Jul 20, 2026
721da7b
test(edge): read Edge export/snapshot dirs from env, drop internal pa…
lfengad Jul 20, 2026
fb807e4
Revert "Release 2026-07-20 (i4 edfc054c)" (#39)
lfengad Jul 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,6 @@ first; the skills above are referenced from there.
| [Code Structure](./docs/code_structure.md) | Repository layout and a per-subpackage tour of `cosmos_framework/` — where each concern lives and where to add new code. |
| [Training](./docs/training.md) | Launching multi-GPU and multi-node runs; parallelism strategies; mixed precision; resuming. |
| [Inference (from a trained checkpoint)](./docs/inference.md) | Loading a trained checkpoint into one of the inference backends. |
| [Policy Server](./docs/action_policy_droid_server.md) | Running the server-client pipeline for Cosmos3-Nano-Policy-DROID. |
| [Policy Server](./docs/action_policy_droid_server.md) | Running the server-client pipeline for Cosmos3-Policy-DROID. |
| [FAQ](./docs/faq.md) | Troubleshooting (OOM, NCCL hangs, slow training), environment variables, and common pitfalls. |
| [AGENTS.md](./AGENTS.md) + [Agent Skills](#agent-skills) | Repo map and task-specific `SKILL.md` files loaded automatically by AGENTS.md-aware coding agents (Claude Code, Codex CLI, Cursor, etc.). |
218 changes: 218 additions & 0 deletions cosmos_framework/callbacks/dmd2_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: OpenMDW-1.1

from __future__ import annotations

import hashlib
import json
import pickle
import random
from collections.abc import Iterable
from pathlib import Path
from typing import Any

import numpy as np
import torch
import torch.distributed as dist

from cosmos_framework.model._base import ImaginaireModel
from cosmos_framework.utils import distributed
from cosmos_framework.utils.callback import Callback

__all__: tuple[str, ...] = ("DMD2Metrics", "DMD2ParityLedger", "PARITY_KEYS")

PARITY_KEYS: tuple[str, ...] = (
"vsd_loss",
"total_generator_loss",
"dmd_loss_generator",
"dmd_vsd_grad_norm",
"fake_score_loss",
"total_critic_loss",
"dmd_loss_critic",
"clip_grad_norm/net_selected_preclip",
"clip_grad_norm/net_selected_clip_scale",
"clip_grad_norm/net_selected_clip_norm",
"clip_grad_norm/fake_score_selected_preclip",
"clip_grad_norm/fake_score_selected_clip_scale",
"clip_grad_norm/fake_score_selected_clip_norm",
)

_SAMPLE_KEY_FIELDS: tuple[str, ...] = ("__key__", "sample_id", "uuid")
_VOLATILE_INPUT_FIELDS: frozenset[str] = frozenset(
{
"_aug_step_times",
"_aug_time",
"_pre_aug_time",
"_sample_time",
"_worker_aug_step_times",
"_worker_aug_time",
"_worker_batch_time",
"_worker_id",
"_worker_io_time",
}
)


def _local_rng_checksum() -> str:
"""Hash local RNG states without advancing any generator."""
digest = hashlib.sha256()
torch_state = torch.get_rng_state() # [N_cpu_rng]
digest.update(torch_state.numpy().tobytes())
if torch.cuda.is_available():
cuda_state = torch.cuda.get_rng_state() # [N_cuda_rng]
digest.update(cuda_state.cpu().numpy().tobytes())
digest.update(pickle.dumps(np.random.get_state(), protocol=pickle.HIGHEST_PROTOCOL))
digest.update(pickle.dumps(random.getstate(), protocol=pickle.HIGHEST_PROTOCOL))
return digest.hexdigest()


def _gather_objects(payload: object) -> list[object]:
if not dist.is_available() or not dist.is_initialized():
return [payload]
return distributed.all_gather_object(payload)


def _flatten_sample_keys(value: object) -> list[str]:
if value is None:
return []
if isinstance(value, torch.Tensor):
local_value = value.detach().cpu() # [...]
return [str(item) for item in local_value.reshape(-1).tolist()]
if isinstance(value, bytes):
return [value.decode(errors="replace")]
if isinstance(value, str):
return [value]
if isinstance(value, Iterable):
flattened: list[str] = []
for item in value:
flattened.extend(_flatten_sample_keys(item))
return flattened
return [str(value)]


def _sample_keys(data_batch: dict[str, object]) -> list[str]:
local_keys: list[str] = []
for field in _SAMPLE_KEY_FIELDS:
if field in data_batch:
local_keys = _flatten_sample_keys(data_batch[field])
break
gathered_keys = _gather_objects(local_keys)
return sorted(key for rank_keys in gathered_keys for key in _flatten_sample_keys(rank_keys))


def _rng_checksum() -> str:
gathered_checksums = [str(checksum) for checksum in _gather_objects(_local_rng_checksum())]
return hashlib.sha256("\n".join(gathered_checksums).encode()).hexdigest()


def _update_input_digest(digest: Any, value: object) -> None:
if isinstance(value, torch.Tensor):
to_local = getattr(value, "to_local", None)
local_value = to_local() if callable(to_local) else value # [...]
cpu_value = local_value.detach().cpu().contiguous() # [...]
byte_view = cpu_value.view(torch.uint8) # [N_bytes]
digest.update(f"tensor:{cpu_value.dtype}:{tuple(cpu_value.shape)}:".encode())
digest.update(byte_view.numpy().tobytes())
return
if isinstance(value, np.ndarray):
contiguous_value = np.ascontiguousarray(value)
digest.update(f"ndarray:{contiguous_value.dtype}:{contiguous_value.shape}:".encode())
digest.update(contiguous_value.tobytes())
return
if isinstance(value, dict):
digest.update(b"dict:")
for key in sorted(value, key=lambda item: (type(item).__qualname__, str(item))):
_update_input_digest(digest, key)
_update_input_digest(digest, value[key])
return
if isinstance(value, (list, tuple)):
digest.update(f"{type(value).__qualname__}:{len(value)}:".encode())
for item in value:
_update_input_digest(digest, item)
return
if isinstance(value, bytes):
digest.update(f"bytes:{len(value)}:".encode())
digest.update(value)
return
if isinstance(value, str):
encoded_value = value.encode()
digest.update(f"str:{len(encoded_value)}:".encode())
digest.update(encoded_value)
return
digest.update(
pickle.dumps((type(value).__module__, type(value).__qualname__, value), protocol=pickle.HIGHEST_PROTOCOL)
)


def _input_digest(data_batch: dict[str, object]) -> str:
stable_batch = {key: value for key, value in data_batch.items() if key not in _VOLATILE_INPUT_FIELDS}
local_hasher = hashlib.sha256()
_update_input_digest(local_hasher, stable_batch)
local_digest = local_hasher.hexdigest()
gathered_digests = [str(digest) for digest in _gather_objects(local_digest)]
return hashlib.sha256("\n".join(gathered_digests).encode()).hexdigest()


def _average_metric(value: object, device: torch.device) -> float:
if isinstance(value, torch.Tensor):
to_local = getattr(value, "to_local", None)
local_value = to_local() if callable(to_local) else value # [...]
metric = local_value.detach().float().clone() # [...]
else:
metric = torch.tensor(value, device=device, dtype=torch.float32) # []
if dist.is_available() and dist.is_initialized():
dist.all_reduce(metric, op=dist.ReduceOp.AVG)
return metric.mean().item()


def _phase(model: torch.nn.Module, iteration: int) -> str:
get_phase = getattr(model, "get_phase", None)
if callable(get_phase):
return str(get_phase(iteration))
get_optimizer_key = getattr(model, "get_optimizer_key", None)
if callable(get_optimizer_key):
return "student" if str(get_optimizer_key(iteration)) == "net" else "critic"
raise AttributeError(f"{type(model).__name__} must define get_phase() or get_optimizer_key()")


class DMD2ParityLedger(Callback):
"""Append deterministic DMD2 parity evidence to rank-0 JSONL."""

def __init__(self, output_path: str = "") -> None:
super().__init__()
self.output_path: str = output_path

def on_training_step_end(
self,
model: ImaginaireModel,
data_batch: dict[str, torch.Tensor],
output_batch: dict[str, torch.Tensor],
loss: torch.Tensor,
iteration: int = 0,
) -> None:
if not self.output_path:
return

grad_metrics = getattr(model, "_distillation_parity_grad_metrics", {})
metric_values = {**output_batch, **grad_metrics}
record: dict[str, object] = {
"iteration": iteration,
"phase": _phase(model, max(iteration - 1, 0)),
"sample_keys": _sample_keys(data_batch),
"input_digest": _input_digest(data_batch),
"rng_checksum": _rng_checksum(),
}
for key in PARITY_KEYS:
if key in metric_values:
record[key] = _average_metric(metric_values[key], loss.device)

if not distributed.is_rank0():
return
output_path = Path(self.output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
with output_path.open("a", encoding="utf-8") as output_file:
output_file.write(json.dumps(record, sort_keys=True, separators=(",", ":")) + "\n")


class DMD2Metrics(DMD2ParityLedger):
"""Public OSS name for the DMD2 parity and diagnostic metrics callback."""
Loading