|
11 | 11 | from solarwm.backends.wan22.runtime.stage2 import ( |
12 | 12 | RoleCheckpointReceipt, |
13 | 13 | Wan5BStage2Runtime, |
| 14 | + _published_default_weight_role, |
14 | 15 | _stage2_generated_sample, |
15 | 16 | _stage2_initialization_receipt, |
16 | 17 | _stage2_self_forcing_latents, |
@@ -265,6 +266,97 @@ def test_stage2_generated_sample_preserves_configured_denoising_steps( |
265 | 266 | assert generated.provenance["denoising_step_list"] == sentinel |
266 | 267 |
|
267 | 268 |
|
| 269 | +def test_stage2_long_generation_uses_streaming_vae_tiles( |
| 270 | + monkeypatch: pytest.MonkeyPatch, |
| 271 | +) -> None: |
| 272 | + torch = pytest.importorskip("torch") |
| 273 | + from solarwm.backends.wan22.runtime import inference, stage2 |
| 274 | + |
| 275 | + calls: list[int] = [] |
| 276 | + |
| 277 | + class _VAE: |
| 278 | + @staticmethod |
| 279 | + def decode_streaming(value: object, *, chunk_latent_frames: int) -> object: |
| 280 | + calls.extend((int(value.shape[1]), chunk_latent_frames)) |
| 281 | + return torch.zeros((1, 957, 3, 1, 1)) |
| 282 | + |
| 283 | + @staticmethod |
| 284 | + def decode(*_args: object, **_kwargs: object) -> object: |
| 285 | + raise AssertionError("long Stage2 generation must not use one-shot VAE decode") |
| 286 | + |
| 287 | + monkeypatch.setattr( |
| 288 | + stage2, |
| 289 | + "_stage2_self_forcing_latents", |
| 290 | + lambda *_: ( |
| 291 | + torch.zeros((1, 240, 1, 1, 1)), |
| 292 | + {"timesteps": [1000, 750, 500, 250]}, |
| 293 | + ), |
| 294 | + ) |
| 295 | + monkeypatch.setattr(inference, "_encode_compare_mp4", lambda *_args, **_kwargs: b"compare") |
| 296 | + provider = SimpleNamespace( |
| 297 | + device=torch.device("cpu"), |
| 298 | + config={ |
| 299 | + "data": {"fps": 16.0}, |
| 300 | + "model": {"camera_translation_transform": "linear"}, |
| 301 | + "train": {"denoising_step_list": [1000, 750, 500, 250]}, |
| 302 | + }, |
| 303 | + _conditions=lambda *_args, **_kwargs: ( |
| 304 | + torch.zeros((1, 1, 1, 1, 1)), |
| 305 | + {}, |
| 306 | + {}, |
| 307 | + None, |
| 308 | + ), |
| 309 | + vae=_VAE(), |
| 310 | + video_encoder=lambda *_args, **_kwargs: b"video", |
| 311 | + _prepared={0: object()}, |
| 312 | + _model_weight_role="ema", |
| 313 | + ) |
| 314 | + case = SimpleNamespace( |
| 315 | + slot=0, |
| 316 | + noise_seed=42, |
| 317 | + metadata={ |
| 318 | + "generation_pass": { |
| 319 | + "name": "model_self_forcing_nfe4", |
| 320 | + "weights": "model", |
| 321 | + "mode": "autoregressive", |
| 322 | + "solver": "self_forcing", |
| 323 | + "num_inference_steps": 4, |
| 324 | + "rollout_latent_frames": 240, |
| 325 | + } |
| 326 | + }, |
| 327 | + ) |
| 328 | + |
| 329 | + generated = _stage2_generated_sample(provider, case, weights_id="release#ema") |
| 330 | + |
| 331 | + assert calls == [240, 60] |
| 332 | + assert generated.shape == (1, 957, 3, 1, 1) |
| 333 | + assert generated.provenance["resolved_weights_role"] == "ema" |
| 334 | + assert generated.provenance["vae_decode"] == { |
| 335 | + "mode": "continuous_cached_tiles", |
| 336 | + "chunk_latent_frames": 60, |
| 337 | + } |
| 338 | + |
| 339 | + |
| 340 | +def test_stage2_camera_length_resolves_release_default_weights(tmp_path: Path) -> None: |
| 341 | + manifest = { |
| 342 | + "schema": "solarwm.public-weight-manifest.v1", |
| 343 | + "identity": {"model": {"weight_role": "live+ema"}}, |
| 344 | + "load": { |
| 345 | + "default_weights": "ema", |
| 346 | + "entrypoint": ".", |
| 347 | + "format": "solarwm_wan_stage2_transaction_v1", |
| 348 | + }, |
| 349 | + } |
| 350 | + (tmp_path / "release-manifest.json").write_text(json.dumps(manifest), encoding="utf-8") |
| 351 | + |
| 352 | + assert _published_default_weight_role(tmp_path) == "ema" |
| 353 | + |
| 354 | + manifest["load"]["default_weights"] = "unknown" |
| 355 | + (tmp_path / "release-manifest.json").write_text(json.dumps(manifest), encoding="utf-8") |
| 356 | + with pytest.raises(BackendContractError, match="default weight role is invalid"): |
| 357 | + _published_default_weight_role(tmp_path) |
| 358 | + |
| 359 | + |
268 | 360 | def test_stage2_unconditional_matches_conditional_dtype() -> None: |
269 | 361 | torch = pytest.importorskip("torch") |
270 | 362 |
|
@@ -432,6 +524,7 @@ def to(self, *args: object, **kwargs: object) -> _Movable: |
432 | 524 | "_verified_stage2_inference_checkpoint", |
433 | 525 | lambda *_: (tmp_path / "model.pt", "b" * 64, 200), |
434 | 526 | ) |
| 527 | + monkeypatch.setattr(stage2, "_published_default_weight_role", lambda *_: "ema") |
435 | 528 | monkeypatch.setattr( |
436 | 529 | stage2.WanAssetLayout, |
437 | 530 | "from_config", |
|
0 commit comments