@@ -74,7 +74,31 @@ class _MaterializedCandidate:
7474 noise_seed : int
7575 start_adjusted : bool
7676 pass_rollouts : Mapping [str , int ]
77- prepared : _PreparedCase
77+ source_pixel_frames : int
78+ camera_fingerprint : str
79+ prepared : _PreparedCase | None
80+
81+
82+ @dataclass (frozen = True )
83+ class _DeferredCameraCase :
84+ row : IndexRow
85+ sample_plan : SamplePlan
86+ noise_seed : int
87+ pass_rollouts : Mapping [str , int ]
88+ source_pixel_frames : int
89+ prompt : str
90+ camera_fingerprint : str
91+
92+
93+ @dataclass
94+ class _DeferredCameraInputs :
95+ rows : tuple [IndexRow , ...]
96+ resolver : Any
97+ runtime_guards : CameraGuards
98+ manifest_guards : CameraGuards
99+ cases : dict [int , _DeferredCameraCase ]
100+ shards : TarShardReader | None = None
101+ reader : RawSampleReader | None = None
78102
79103
80104class _FamilyAdapter :
@@ -600,6 +624,7 @@ def __init__(self, config: Mapping[str, Any], plan: GenerationPlan) -> None:
600624 self .vae .to (self .device )
601625 self ._loaded_role : str | None = None
602626 self ._prepared : dict [int , _PreparedCase ] = {}
627+ self ._deferred_camera_inputs : _DeferredCameraInputs | None = None
603628
604629 def sync (self ) -> None :
605630 import torch .distributed as dist
@@ -610,7 +635,91 @@ def sync(self) -> None:
610635 def close (self ) -> None :
611636 from .distributed import cleanup_torchrun
612637
613- cleanup_torchrun ()
638+ try :
639+ self ._close_deferred_camera_inputs ()
640+ finally :
641+ cleanup_torchrun ()
642+
643+ def _close_deferred_camera_inputs (self ) -> None :
644+ deferred = getattr (self , "_deferred_camera_inputs" , None )
645+ if deferred is None :
646+ return
647+ self ._deferred_camera_inputs = None
648+ try :
649+ if deferred .shards is not None :
650+ deferred .shards .close ()
651+ finally :
652+ for slot in deferred .cases :
653+ self ._prepared .pop (slot , None )
654+ deferred .cases .clear ()
655+ deferred .reader = None
656+ deferred .shards = None
657+
658+ def _materialize_deferred_camera_case (self , case : InferenceCase ) -> None :
659+ deferred = getattr (self , "_deferred_camera_inputs" , None )
660+ if deferred is None :
661+ raise BackendContractError ("Wan adapter has no deferred camera inputs" )
662+ descriptor = deferred .cases .get (case .slot )
663+ if descriptor is None :
664+ raise BackendContractError (
665+ f"Wan adapter has no deferred camera case { case .sample_id !r} "
666+ )
667+ if self ._prepared :
668+ raise BackendContractError ("Wan adapter retained another prepared camera case" )
669+ metadata = case .metadata
670+ if (
671+ descriptor .row .sample_id != case .sample_id
672+ or descriptor .sample_plan .start_frame != case .start_frame
673+ or descriptor .noise_seed != case .noise_seed
674+ or descriptor .prompt != case .prompt
675+ or descriptor .camera_fingerprint != case .camera_fingerprint
676+ or descriptor .row .key != str (metadata .get ("key" , "" ))
677+ or descriptor .row .shard != str (metadata .get ("source_shard" , "" ))
678+ or descriptor .row .ordinal != int (metadata .get ("source_row_ordinal" , - 1 ))
679+ or descriptor .source_pixel_frames != int (metadata .get ("source_pixel_frames" , - 1 ))
680+ or descriptor .sample_plan .source_frame_indices [- 1 ]
681+ != int (metadata .get ("source_frame_last" , - 1 ))
682+ or dict (descriptor .pass_rollouts )
683+ != dict (metadata .get ("rollout_latent_frames_by_pass" , {}))
684+ ):
685+ raise DataContractError (
686+ f"deferred camera case { case .sample_id !r} identity drifted"
687+ )
688+ if deferred .reader is None :
689+ deferred .shards = TarShardReader (
690+ deferred .resolver ,
691+ max_open = int (self .config ["data" ].get ("tar_cache_size" , 4 )),
692+ )
693+ deferred .reader = RawSampleReader (deferred .rows , deferred .shards )
694+ raw = deferred .reader .materialize (descriptor .sample_plan )
695+ camera_fingerprint = hashlib .blake2s (raw .members ["camera_member" ]).hexdigest ()
696+ if raw .caption != case .prompt or camera_fingerprint != case .camera_fingerprint :
697+ raise DataContractError (
698+ f"deferred camera case { case .sample_id !r} payload drifted"
699+ )
700+ data = self .config ["data" ]
701+ camera = build_camera_tokens (
702+ raw .members ["camera_member" ],
703+ descriptor .sample_plan .source_frame_indices ,
704+ raw .manifest ,
705+ source_fps = _source_fps (descriptor .row ),
706+ output_fps = float (data .get ("fps" , 16.0 )),
707+ frame_sequence_length = int (self .config ["model" ]["frame_sequence_length" ]),
708+ guards = deferred .runtime_guards ,
709+ manifest_guards = deferred .manifest_guards ,
710+ configured_array_key = str (data ["camera_array_key" ]),
711+ )
712+ pixels = decode_video (
713+ raw .members ["video_member" ],
714+ descriptor .sample_plan .source_frame_indices ,
715+ height = int (data ["height" ]),
716+ width = int (data ["width" ]),
717+ )
718+ self ._prepared [case .slot ] = _PreparedCase (
719+ pixels = pixels ,
720+ camera = camera ,
721+ source_pixel_frames = descriptor .source_pixel_frames ,
722+ )
614723
615724 def weight_id (self , role : str ) -> str :
616725 if role not in {"live" , "ema" }:
@@ -669,6 +778,11 @@ def _load_role(self, role: str) -> None:
669778 def build_cases (self , plan : GenerationPlan ) -> tuple [InferenceCase , ...]:
670779 data = self .config ["data" ]
671780 validation = self .config ["validation" ]
781+ camera_length = (
782+ str (self .config .get ("inference" , {}).get ("length" , "fixed" )).strip ().lower () == "camera"
783+ )
784+ if camera_length :
785+ self ._close_deferred_camera_inputs ()
672786 frozen_path_value = getattr (self , "validation_plan_path" , None )
673787 frozen_path = Path (str (frozen_path_value )) if frozen_path_value is not None else None
674788 frozen_key = validation_plan_key ("wan22" , self .config )
@@ -724,9 +838,14 @@ def validation_guard(name: str) -> float | None:
724838 max_rel_translation = float (data ["max_rel_translation" ]),
725839 max_camera_abs = float (data ["max_camera_abs" ]),
726840 )
727- camera_length = (
728- str (self .config .get ("inference" , {}).get ("length" , "fixed" )).strip ().lower () == "camera"
729- )
841+ if camera_length :
842+ self ._deferred_camera_inputs = _DeferredCameraInputs (
843+ rows = tuple (source_rows ),
844+ resolver = resolver ,
845+ runtime_guards = runtime_guards ,
846+ manifest_guards = manifest_guards ,
847+ cases = {},
848+ )
730849 variable_rollout = camera_length or any (
731850 item .variable_rollout_by_source for item in plan .passes
732851 )
@@ -742,6 +861,11 @@ def validation_guard(name: str) -> float | None:
742861 # Candidate rows retain full-index ordinals, so random access must
743862 # address the complete source index rather than the seeded order.
744863 reader = RawSampleReader (source_rows , shards )
864+ camera_reader = RawSampleReader (
865+ source_rows ,
866+ shards ,
867+ member_fields = ("camera_member" ,),
868+ )
745869
746870 def materialize_candidate (row : IndexRow , slot : int ) -> _MaterializedCandidate :
747871 pass_rollouts = {
@@ -765,13 +889,15 @@ def materialize_candidate(row: IndexRow, slot: int) -> _MaterializedCandidate:
765889 variable_rollout_by_source = variable_rollout ,
766890 start_at_first_frame = camera_length ,
767891 )
768- raw = reader .materialize (sample_plan )
769- pixels = decode_video (
770- raw .members ["video_member" ],
771- sample_plan .source_frame_indices ,
772- height = int (data ["height" ]),
773- width = int (data ["width" ]),
774- )
892+ raw = (camera_reader if camera_length else reader ).materialize (sample_plan )
893+ pixels = None
894+ if not camera_length :
895+ pixels = decode_video (
896+ raw .members ["video_member" ],
897+ sample_plan .source_frame_indices ,
898+ height = int (data ["height" ]),
899+ width = int (data ["width" ]),
900+ )
775901 camera = build_camera_tokens (
776902 raw .members ["camera_member" ],
777903 sample_plan .source_frame_indices ,
@@ -789,13 +915,44 @@ def materialize_candidate(row: IndexRow, slot: int) -> _MaterializedCandidate:
789915 noise_seed = noise_seed ,
790916 start_adjusted = start_adjusted ,
791917 pass_rollouts = pass_rollouts ,
792- prepared = _PreparedCase (
793- pixels = pixels ,
794- camera = camera ,
795- source_pixel_frames = source_pixel_frames ,
918+ source_pixel_frames = source_pixel_frames ,
919+ camera_fingerprint = hashlib .blake2s (
920+ raw .members ["camera_member" ]
921+ ).hexdigest (),
922+ prepared = (
923+ None
924+ if camera_length
925+ else _PreparedCase (
926+ pixels = pixels ,
927+ camera = camera ,
928+ source_pixel_frames = source_pixel_frames ,
929+ )
796930 ),
797931 )
798932
933+ def retain_candidate (
934+ row : IndexRow ,
935+ slot : int ,
936+ materialized : _MaterializedCandidate ,
937+ ) -> None :
938+ if camera_length :
939+ deferred = self ._deferred_camera_inputs
940+ if deferred is None or materialized .prepared is not None :
941+ raise BackendContractError ("Wan deferred camera state is invalid" )
942+ deferred .cases [slot ] = _DeferredCameraCase (
943+ row = row ,
944+ sample_plan = materialized .sample_plan ,
945+ noise_seed = materialized .noise_seed ,
946+ pass_rollouts = dict (materialized .pass_rollouts ),
947+ source_pixel_frames = materialized .source_pixel_frames ,
948+ prompt = str (materialized .raw .caption ),
949+ camera_fingerprint = materialized .camera_fingerprint ,
950+ )
951+ return
952+ if materialized .prepared is None :
953+ raise BackendContractError ("Wan fixed validation case lacks pixels" )
954+ self ._prepared [slot ] = materialized .prepared
955+
799956 if frozen_cases is not None :
800957 restored : list [InferenceCase ] = []
801958 for case in frozen_cases :
@@ -821,21 +978,18 @@ def materialize_candidate(row: IndexRow, slot: int) -> _MaterializedCandidate:
821978 raise DataContractError (
822979 f"frozen validation sample { case .sample_id !r} changed camera validity"
823980 ) from exc
824- camera_fingerprint = hashlib .blake2s (
825- materialized .raw .members ["camera_member" ]
826- ).hexdigest ()
827981 if (
828982 materialized .sample_plan .start_frame != case .start_frame
829983 or materialized .noise_seed != case .noise_seed
830984 or materialized .raw .caption != case .prompt
831- or camera_fingerprint != case .camera_fingerprint
985+ or materialized . camera_fingerprint != case .camera_fingerprint
832986 or dict (materialized .pass_rollouts )
833987 != dict (case .metadata .get ("rollout_latent_frames_by_pass" , {}))
834988 ):
835989 raise DataContractError (
836990 f"frozen validation sample { case .sample_id !r} materialization drifted"
837991 )
838- self . _prepared [ case .slot ] = materialized . prepared
992+ retain_candidate ( row , case .slot , materialized )
839993 restored .append (case )
840994 return tuple (restored )
841995
@@ -930,17 +1084,15 @@ def materialize_candidate(row: IndexRow, slot: int) -> _MaterializedCandidate:
9301084 prompt = raw .caption ,
9311085 start_frame = sample_plan .start_frame ,
9321086 noise_seed = materialized .noise_seed ,
933- camera_fingerprint = hashlib .blake2s (
934- raw .members ["camera_member" ]
935- ).hexdigest (),
1087+ camera_fingerprint = materialized .camera_fingerprint ,
9361088 metadata = {
9371089 "key" : row .key ,
9381090 "source_shard" : row .shard ,
9391091 "source_row_ordinal" : row .ordinal ,
9401092 "dataset" : _row_dataset (row ),
9411093 "scene" : row .values .get ("scene" ),
9421094 "source_num_frames" : _num_frames (row ),
943- "source_pixel_frames" : materialized .prepared . source_pixel_frames ,
1095+ "source_pixel_frames" : materialized .source_pixel_frames ,
9441096 "train_latent_frames" : int (data ["latent_frames" ]),
9451097 "rollout_latent_frames_by_pass" : materialized .pass_rollouts ,
9461098 ** ({"rollout_length_source" : "camera" } if camera_length else {}),
@@ -955,7 +1107,7 @@ def materialize_candidate(row: IndexRow, slot: int) -> _MaterializedCandidate:
9551107 },
9561108 )
9571109 )
958- self . _prepared [ slot ] = materialized . prepared
1110+ retain_candidate ( row , slot , materialized )
9591111 local_cases = tuple (cases )
9601112 if frozen_path is not None :
9611113 all_cases = _gather_partitioned_cases (local_cases )
0 commit comments