Skip to content

Commit ab6318b

Browse files
committed
feat(ideogram4): port Ideogram 4.0 to JAX with native Flax Transformer and Qwen text encoder
- Implements `Ideogram4Transformer` using `flax.nnx` - Ports `TorchaxQwen3VLTextEncoder` to handle Ideogram's LLM prompt conditions - Adds strict JSON schema caption key re-ordering in `IdeogramPipeline` - Adds full asymmetric CFG denoising loop
1 parent 0984457 commit ab6318b

21 files changed

Lines changed: 2587 additions & 3 deletions
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
"""
16+
17+
import json
18+
import jax
19+
import numpy as np
20+
from typing import Optional, Tuple
21+
from maxdiffusion.pipelines.ideogram.ideogram_pipeline import IdeogramPipeline
22+
from maxdiffusion import max_logging
23+
from maxdiffusion.checkpointing.checkpointing_utils import create_orbax_checkpoint_manager
24+
import orbax.checkpoint as ocp
25+
from etils import epath
26+
27+
IDEOGRAM_CHECKPOINT = "IDEOGRAM_CHECKPOINT"
28+
29+
30+
class IdeogramCheckpointer:
31+
32+
def __init__(self, config, checkpoint_type: str = IDEOGRAM_CHECKPOINT):
33+
self.config = config
34+
self.checkpoint_type = checkpoint_type
35+
self.opt_state = None
36+
37+
self.checkpoint_manager: ocp.CheckpointManager = create_orbax_checkpoint_manager(
38+
getattr(self.config, "checkpoint_dir", ""),
39+
enable_checkpointing=True,
40+
save_interval_steps=1,
41+
checkpoint_type=checkpoint_type,
42+
dataset_type=getattr(config, "dataset_type", None),
43+
)
44+
45+
def load_ideogram_configs_from_orbax(self, step: Optional[int]) -> Tuple[Optional[dict], Optional[int]]:
46+
if self.checkpoint_manager is None:
47+
max_logging.log("No checkpoint manager configured, skipping Orbax load.")
48+
return None, None
49+
50+
if step is None:
51+
step = self.checkpoint_manager.latest_step()
52+
max_logging.log(f"Latest Ideogram checkpoint step: {step}")
53+
if step is None:
54+
max_logging.log("No Ideogram checkpoint found.")
55+
return None, None
56+
max_logging.log(f"Loading Ideogram checkpoint from step {step}")
57+
metadatas = self.checkpoint_manager.item_metadata(step)
58+
transformer_metadata = metadatas.ideogram_state
59+
abstract_tree_structure_params = jax.tree_util.tree_map(ocp.utils.to_shape_dtype_struct, transformer_metadata)
60+
params_restore = ocp.args.PyTreeRestore(
61+
restore_args=jax.tree.map(
62+
lambda _: ocp.RestoreArgs(restore_type=np.ndarray),
63+
abstract_tree_structure_params,
64+
)
65+
)
66+
67+
max_logging.log("Restoring Ideogram checkpoint")
68+
restored_checkpoint = self.checkpoint_manager.restore(
69+
directory=epath.Path(self.config.checkpoint_dir),
70+
step=step,
71+
args=ocp.args.Composite(
72+
ideogram_state=params_restore,
73+
ideogram_config=ocp.args.JsonRestore(),
74+
),
75+
)
76+
max_logging.log(f"restored checkpoint {restored_checkpoint.keys()}")
77+
max_logging.log(f"restored checkpoint ideogram_state {restored_checkpoint.ideogram_state.keys()}")
78+
max_logging.log(f"optimizer found in checkpoint {'opt_state' in restored_checkpoint.ideogram_state.keys()}")
79+
return restored_checkpoint, step
80+
81+
def load_checkpoint(
82+
self, step=None, vae_only=False, load_transformer=True
83+
) -> Tuple[IdeogramPipeline, Optional[dict], Optional[int]]:
84+
restored_checkpoint, step = self.load_ideogram_configs_from_orbax(step)
85+
opt_state = None
86+
87+
if restored_checkpoint:
88+
max_logging.log("Loading Ideogram pipeline from checkpoint")
89+
pipeline = IdeogramPipeline.from_checkpoint(self.config, restored_checkpoint, vae_only, load_transformer)
90+
if "opt_state" in restored_checkpoint.ideogram_state.keys():
91+
opt_state = restored_checkpoint.ideogram_state["opt_state"]
92+
else:
93+
max_logging.log("No checkpoint found, loading pipeline from pretrained hub")
94+
pipeline = IdeogramPipeline.from_pretrained(self.config, vae_only, load_transformer)
95+
96+
return pipeline, opt_state, step
97+
98+
def save_checkpoint(self, train_step, pipeline: IdeogramPipeline, train_states: dict):
99+
"""Saves the training state and model configurations."""
100+
101+
def config_to_json(model_or_config):
102+
return json.loads(model_or_config.to_json_string())
103+
104+
max_logging.log(f"Saving checkpoint for step {train_step}")
105+
items = {
106+
"ideogram_config": ocp.args.JsonSave(config_to_json(pipeline.transformer)),
107+
}
108+
109+
items["ideogram_state"] = ocp.args.PyTreeSave(train_states)
110+
111+
# Save the checkpoint
112+
self.checkpoint_manager.save(train_step, args=ocp.args.Composite(**items))
113+
max_logging.log(f"Checkpoint for step {train_step} saved.")

src/maxdiffusion/common_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
WAN2_2 = "wan2.2"
5454
LTX2_VIDEO = "ltx2_video"
5555
LTX2_3 = "ltx2.3"
56+
IDEOGRAM4 = "ideogram4"
5657

5758
WAN_MODEL = WAN2_1
5859

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
run_name: ""
16+
metrics_dir: ""
17+
tensorboard_dir: ""
18+
output_dir: ""
19+
20+
model_name: "ideogram4"
21+
pretrained_model_name_or_path: "ideogram-ai/ideogram-4-fp8"
22+
23+
prompt: >
24+
{"high_level_description": "A magical, cinematic shot of a couple holding hands on a flying carpet soaring high above a historic Arabian desert town at sunset.", "style_description": {"aesthetics": "historic, magical realism, epic fantasy, cinematic", "lighting": "golden hour, dramatic sunset lighting, long shadows, warm glowing atmosphere", "photo": "shot on 35mm lens, wide aperture, photorealistic, hyper-detailed", "medium": "digital photography, cinematic film still", "color_palette": ["#FF9B54", "#7A3B69", "#D35B40", "#2C3E50", "#F4D03F"]}, "compositional_deconstruction": {"background": "A vast, sweeping desert landscape with historic Arabian architecture, sand dunes, and a vibrant sunset sky.", "elements": [{"type": "obj", "bbox": [200, 300, 700, 700], "desc": "A richly detailed, ornate flying carpet suspended in mid-air."}, {"type": "obj", "bbox": [100, 350, 600, 650], "desc": "A romantic couple sitting on the flying carpet, holding hands and looking out over the city."}, {"type": "obj", "bbox": [600, 0, 1000, 1000], "desc": "An ancient Arabian desert town with sandstone buildings, domes, and bustling streets far below."}]}}
25+
negative_prompt: ""
26+
height: 1024
27+
width: 1024
28+
num_inference_steps: 50
29+
guidance_scale: 7.0
30+
seed: 42
31+
32+
global_batch_size_to_train_on: 1
33+
per_device_batch_size: 1
34+
35+
enable_profiler: False
36+
enable_ml_diagnostics: False
37+
profiler_steps: 5
38+
skip_jax_distributed_system: False
39+
skip_jax_compilation: False
40+
hardware: "tpu"
41+
jax_cache_dir: ""
42+
weights_dtype: "bfloat16"
43+
activations_dtype: "bfloat16"
44+
save_config_to_gcs: False
45+
attention: "flash"
46+
attention_sharding_uniform: True
47+
mesh_axes: ['data', 'fsdp', 'context', 'tensor']
48+
logical_axis_rules: [
49+
['batch', 'data'],
50+
['activation_heads', 'fsdp'],
51+
['activation_batch', 'data'],
52+
['activation_kv', 'tensor'],
53+
['mlp','tensor'],
54+
['embed','fsdp'],
55+
['heads', 'tensor'],
56+
['norm', 'fsdp'],
57+
['conv_batch', ['data','fsdp']],
58+
['out_channels', 'tensor'],
59+
['conv_out', 'fsdp'],
60+
['conv_in', 'fsdp']
61+
]
62+
data_sharding: [['data', 'fsdp', 'context', 'tensor']]
63+
dcn_data_parallelism: 1
64+
dcn_fsdp_parallelism: -1
65+
dcn_context_parallelism: 1
66+
dcn_tensor_parallelism: 1
67+
ici_data_parallelism: 1
68+
ici_fsdp_parallelism: -1
69+
ici_context_parallelism: 1
70+
ici_tensor_parallelism: 1
71+
allow_split_physical_axes: False
72+
learning_rate_schedule_steps: -1
73+
max_train_steps: 500
74+
unet_checkpoint: ''
75+
dataset_name: ''
76+
dataset_save_location: ''
77+
compile_topology_num_slices: -1
78+
quantization_local_shard_count: -1
79+
use_qwix_quantization: False

0 commit comments

Comments
 (0)