Skip to content

Commit 5191d32

Browse files
committed
Decouple Cosmos3 transfer blocks from task-pipeline internals
1 parent 0cfc55c commit 5191d32

5 files changed

Lines changed: 99 additions & 68 deletions

File tree

src/diffusers/modular_pipelines/cosmos/before_denoise.py

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,7 +1056,9 @@ def description(self) -> str:
10561056
@property
10571057
def inputs(self) -> list[InputParam]:
10581058
return [
1059-
InputParam(name="cond_text_segment", type_hint=dict, required=True, description="Conditional text segment."),
1059+
InputParam(
1060+
name="cond_text_segment", type_hint=dict, required=True, description="Conditional text segment."
1061+
),
10601062
InputParam(
10611063
name="uncond_text_segment", type_hint=dict, required=True, description="Unconditional text segment."
10621064
),
@@ -1067,7 +1069,10 @@ def inputs(self) -> list[InputParam]:
10671069
description="Clean control latents for this chunk, one per hint in canonical order.",
10681070
),
10691071
InputParam(
1070-
name="latents", type_hint=torch.Tensor, required=True, description="Noisy target latents for this chunk."
1072+
name="latents",
1073+
type_hint=torch.Tensor,
1074+
required=True,
1075+
description="Noisy target latents for this chunk.",
10711076
),
10721077
InputParam(
10731078
name="target_condition_indexes",
@@ -1076,12 +1081,6 @@ def inputs(self) -> list[InputParam]:
10761081
description="Latent-frame indexes fixed by the chunk's conditioning.",
10771082
),
10781083
InputParam(name="fps", type_hint=float, default=24.0, description="Frame rate of the generated video."),
1079-
InputParam(
1080-
name="share_vision_temporal_positions",
1081-
type_hint=bool,
1082-
default=True,
1083-
description="Whether control and target items share vision temporal positions.",
1084-
),
10851084
]
10861085

10871086
@property
@@ -1127,17 +1126,50 @@ def _vision_pack(text_segment: dict, include_controls: bool) -> dict:
11271126
vision_items = [block_state.latents]
11281127
condition_indexes = [block_state.target_condition_indexes]
11291128
clean_flags = [False]
1130-
vision_segment = components._prepare_vision_segment(
1131-
input_vision_tokens=vision_items,
1132-
has_image_condition=False,
1133-
mrope_offset=text_segment["vision_start_temporal_offset"],
1134-
vision_fps=block_state.fps,
1135-
curr=text_segment["und_len"],
1136-
device=device,
1137-
condition_frame_indexes=condition_indexes,
1138-
clean_item_flags=clean_flags,
1139-
share_vision_temporal_positions=block_state.share_vision_temporal_positions,
1140-
)
1129+
1130+
# Transfer packs [ctrl_1, ..., ctrl_N, target] into one vision segment
1131+
mrope_offset = text_segment["vision_start_temporal_offset"]
1132+
item_curr = text_segment["und_len"]
1133+
token_shapes = []
1134+
sequence_index_parts = []
1135+
mse_loss_index_parts = []
1136+
noisy_frame_indexes_per_item = []
1137+
mrope_id_parts = []
1138+
num_vision_tokens = 0
1139+
num_noisy_vision_tokens = 0
1140+
for item, item_condition, is_clean in zip(vision_items, condition_indexes, clean_flags):
1141+
latent_t = item.shape[2]
1142+
if is_clean:
1143+
frame_condition = list(range(latent_t))
1144+
else:
1145+
frame_condition = item_condition if item_condition is not None else []
1146+
item_segment = components._prepare_vision_segment(
1147+
input_vision_tokens=item,
1148+
has_image_condition=False,
1149+
mrope_offset=mrope_offset,
1150+
vision_fps=block_state.fps,
1151+
curr=item_curr,
1152+
device=device,
1153+
condition_frame_indexes=frame_condition,
1154+
)
1155+
token_shapes.extend(item_segment["vision_token_shapes"])
1156+
sequence_index_parts.append(item_segment["vision_sequence_indexes"])
1157+
mse_loss_index_parts.append(item_segment["vision_mse_loss_indexes"])
1158+
noisy_frame_indexes_per_item.extend(item_segment["vision_noisy_frame_indexes"])
1159+
mrope_id_parts.append(item_segment["vision_mrope_ids"])
1160+
num_vision_tokens += item_segment["num_vision_tokens"]
1161+
num_noisy_vision_tokens += item_segment["num_noisy_vision_tokens"]
1162+
item_curr += item_segment["num_vision_tokens"]
1163+
1164+
vision_segment = {
1165+
"vision_token_shapes": token_shapes,
1166+
"vision_sequence_indexes": torch.cat(sequence_index_parts, dim=0),
1167+
"vision_mse_loss_indexes": torch.cat(mse_loss_index_parts, dim=0),
1168+
"vision_noisy_frame_indexes": noisy_frame_indexes_per_item,
1169+
"vision_mrope_ids": torch.cat(mrope_id_parts, dim=1),
1170+
"num_vision_tokens": num_vision_tokens,
1171+
"num_noisy_vision_tokens": num_noisy_vision_tokens,
1172+
}
11411173
return {
11421174
**text_segment,
11431175
**vision_segment,

src/diffusers/modular_pipelines/cosmos/decoders.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ def expected_components(self) -> list[ComponentSpec]:
135135
def inputs(self) -> list[InputParam]:
136136
return [
137137
InputParam(
138-
name="latents", type_hint=torch.Tensor, required=True, description="Denoised target latents for this chunk."
138+
name="latents",
139+
type_hint=torch.Tensor,
140+
required=True,
141+
description="Denoised target latents for this chunk.",
139142
),
140143
InputParam(name="chunk_id", type_hint=int, default=0, description="Index of the current chunk."),
141144
InputParam(

src/diffusers/modular_pipelines/cosmos/denoise.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,10 @@ def inputs(self) -> list[InputParam]:
590590
description="Mask that zeroes the velocity on conditioned (clean) latent frames.",
591591
),
592592
InputParam(
593-
name="guidance_scale", type_hint=float, default=6.0, description="Scale for text classifier-free guidance."
593+
name="guidance_scale",
594+
type_hint=float,
595+
default=6.0,
596+
description="Scale for text classifier-free guidance.",
594597
),
595598
InputParam(
596599
name="control_guidance",
@@ -708,7 +711,10 @@ def inputs(self) -> list[InputParam]:
708711
name="latents", type_hint=torch.Tensor, required=True, description="Noisy target latents to update."
709712
),
710713
InputParam(
711-
name="velocity", type_hint=torch.Tensor, required=True, description="Predicted (masked) transfer velocity."
714+
name="velocity",
715+
type_hint=torch.Tensor,
716+
required=True,
717+
description="Predicted (masked) transfer velocity.",
712718
),
713719
InputParam(
714720
name="velocity_mask",

src/diffusers/modular_pipelines/cosmos/encoders.py

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
logger = logging.get_logger(__name__)
1818

1919

20+
# Transfer conditions on control signals (edge/blur/depth/seg/wsm), so it uses its own system prompt instead of the
21+
# plain image/video ones. Defined here (not on the task pipeline) so the transfer text block is self-contained.
22+
_SYSTEM_PROMPT_TRANSFER = (
23+
"You are a helpful assistant that generates images or videos following the user's instructions"
24+
" and control signals (edge maps, blur, depth, or segmentation)."
25+
)
26+
27+
2028
class Cosmos3TextEncoderStep(ModularPipelineBlocks):
2129
model_name = "cosmos3-omni"
2230

@@ -150,8 +158,9 @@ class Cosmos3TransferTextStep(ModularPipelineBlocks):
150158
@property
151159
def description(self) -> str:
152160
return (
153-
"Tokenizes the transfer prompt in transfer mode using the per-chunk frame count. Transfer prompts are "
154-
"pre-upsampled JSON captions passed through verbatim (the metadata templates are skipped)."
161+
"Tokenizes the transfer prompt with the transfer system prompt. Transfer prompts are pre-upsampled JSON "
162+
"captions passed through verbatim (no resolution/duration templates), so this is self-contained and does "
163+
"not reuse the standard text step."
155164
)
156165

157166
@staticmethod
@@ -189,36 +198,11 @@ def inputs(self) -> list[InputParam]:
189198
default=None,
190199
description="The negative text prompt used for classifier-free guidance.",
191200
),
192-
InputParam(
193-
name="chunk_frames", type_hint=int, required=True, description="Number of pixel frames in this chunk."
194-
),
195-
InputParam(
196-
name="height",
197-
type_hint=int,
198-
default=None,
199-
description="Height of the generated video in pixels.",
200-
),
201-
InputParam(
202-
name="width", type_hint=int, default=None, description="Width of the generated video in pixels."
203-
),
204-
InputParam(name="fps", type_hint=float, default=24.0, description="Frame rate of the generated video."),
205201
InputParam(
206202
name="use_system_prompt",
207203
type_hint=bool,
208204
default=True,
209-
description="Whether to prepend the Cosmos3 system prompt.",
210-
),
211-
InputParam(
212-
name="add_resolution_template",
213-
type_hint=bool,
214-
default=True,
215-
description="Whether to add resolution metadata to the prompt.",
216-
),
217-
InputParam(
218-
name="add_duration_template",
219-
type_hint=bool,
220-
default=True,
221-
description="Whether to add duration metadata to the prompt.",
205+
description="Whether to prepend the Cosmos3 transfer system prompt.",
222206
),
223207
]
224208

@@ -258,20 +242,30 @@ def __call__(self, components: Cosmos3OmniModularPipeline, state: PipelineState)
258242
finally:
259243
components.safety_checker.to("cpu")
260244

261-
block_state.cond_input_ids, block_state.uncond_input_ids = components.tokenize_prompt(
262-
block_state.prompt,
263-
block_state.negative_prompt,
264-
num_frames=block_state.chunk_frames,
265-
height=block_state.height,
266-
width=block_state.width,
267-
fps=block_state.fps,
268-
use_system_prompt=block_state.use_system_prompt,
269-
add_resolution_template=block_state.add_resolution_template,
270-
add_duration_template=block_state.add_duration_template,
271-
action_mode=None,
272-
action_view_point=None,
273-
transfer_mode=True,
274-
)
245+
# Transfer prompts are pre-upsampled JSON captions: tokenize them verbatim (no resolution/duration templates)
246+
# under the transfer system prompt. Kept self-contained here rather than adding a flag to the standard step.
247+
negative_prompt = block_state.negative_prompt if block_state.negative_prompt is not None else ""
248+
special_tokens = components.llm_special_tokens
249+
250+
def _tokenize(text: str) -> list[int]:
251+
conversations = []
252+
if block_state.use_system_prompt:
253+
conversations.append({"role": "system", "content": _SYSTEM_PROMPT_TRANSFER})
254+
conversations.append({"role": "user", "content": text})
255+
encoding = components.text_tokenizer.apply_chat_template(
256+
conversations,
257+
tokenize=True,
258+
add_generation_prompt=True,
259+
add_vision_id=False,
260+
return_dict=True,
261+
)
262+
return list(encoding.input_ids) + [
263+
special_tokens["eos_token_id"],
264+
special_tokens["start_of_generation"],
265+
]
266+
267+
block_state.cond_input_ids = _tokenize(block_state.prompt)
268+
block_state.uncond_input_ids = _tokenize(negative_prompt)
275269

276270
self.set_block_state(state, block_state)
277271
return components, state

src/diffusers/modular_pipelines/cosmos/modular_blocks_cosmos3.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -753,8 +753,6 @@ class Cosmos3AutoCoreDenoiseStep(ConditionalPipelineBlocks):
753753
Torch generator for deterministic generation.
754754
fps (`float`, *optional*, defaults to 24.0):
755755
Frame rate of the generated video.
756-
share_vision_temporal_positions (`bool`, *optional*, defaults to True):
757-
Whether control and target items share vision temporal positions.
758756
num_inference_steps (`int`):
759757
The number of denoising steps.
760758
**denoiser_input_fields (`None`, *optional*):
@@ -958,8 +956,6 @@ class Cosmos3OmniBlocks(SequentialPipelineBlocks):
958956
Number of frames the first chunk reuses from the input video.
959957
generator (`Generator`, *optional*):
960958
Torch generator for deterministic generation.
961-
share_vision_temporal_positions (`bool`, *optional*, defaults to True):
962-
Whether control and target items share vision temporal positions.
963959
num_inference_steps (`int`):
964960
The number of denoising steps.
965961
**denoiser_input_fields (`None`, *optional*):

0 commit comments

Comments
 (0)