Port Ideogram 4.0 to JAX / MaxDiffusion#442
Conversation
83fcf30 to
23fa1c2
Compare
…r 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
|
/gemini review |
|
🤖 Hi @Perseus14, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @Perseus14, but I was unable to process your request. Please see the logs for more details. |
|
/gemini review |
|
🤖 Hi @mbohlool, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
🤖 I'm sorry @mbohlool, but I was unable to process your request. Please see the logs for more details. |
|
/gemini review |
| # 1. Warmup Compilation | ||
| config.get_keys()["enable_profiler"] = False | ||
| config.get_keys()["enable_ml_diagnostics"] = False | ||
| config.get_keys()["num_inference_steps"] = 2 # lower for warmup |
There was a problem hiding this comment.
since we have z, _, _ = jax.lax.fori_loop(0, num_steps, denoise_step, init_val) in src/maxdiffusion/pipelines/ideogram/ideogram_pipeline.py, setting the warmup step as 2 means that the later 50 step inference time includes compile
I think this is the reason why we see a 50 sec generation time. We could either:
- go with what WAN does: only compile one step and do a plain for loop
- warmup with 50 steps
I am not sure which would be faster, it's worth testing
| @@ -0,0 +1,138 @@ | |||
| import jax | |||
There was a problem hiding this comment.
this file and some other files are missing the license header, this would incur errors when doing copy-bara
| @@ -0,0 +1,138 @@ | |||
| import jax | |||
|
|
|||
There was a problem hiding this comment.
also this file is named for torchax but there's no torchax here (I see device=torch.device("cpu"))
| self.bias = None | ||
|
|
||
| def forward(self, x: torch.Tensor) -> torch.Tensor: | ||
| w = self.weight.to(x.dtype) * self.weight_scale.to(x.dtype).unsqueeze(1) |
There was a problem hiding this comment.
this line dequantizes the full weight tensor on every forward. We should dequantize once at load and cache
| q, k = _apply_rotary_pos_emb(q, k, cos, sin) | ||
|
|
||
| # Block-diagonal mask from segment ids | ||
| attn_mask = jnp.expand_dims(segment_ids, axis=2) == jnp.expand_dims(segment_ids, axis=1) |
There was a problem hiding this comment.
I am not quite sure why we need masks to do this...we can leave this for future optimization
|
|
||
| # JAX scaled dot product attention | ||
| scale = 1.0 / math.sqrt(self.head_dim) | ||
| attn_weights = jnp.einsum("bhqd,bhkd->bhqk", q, k) * scale |
There was a problem hiding this comment.
in base_ideogram.yml we have attention: "flash" but here it uses a naive attention, can be optimized later
|
|
||
| class Ideogram4Attention(nnx.Module): | ||
|
|
||
| def __init__(self, rngs: nnx.Rngs, hidden_size: int, num_heads: int, eps: float = 1e-5, dtype=jnp.float32): |
There was a problem hiding this comment.
dtype=jnp.float32 default is never overridden, so weights_dtype/activations_dtype: bfloat16 in the config are inert.
| schedule_fn = get_schedule_for_resolution((height, width), known_mean=0.5) | ||
| step_intervals = make_step_intervals(num_steps) | ||
| # Compute schedule evaluated at all intervals. schedule_fn returns a continuous scalar mapping. | ||
| sigmas = jnp.array( |
Title: Port Ideogram 4.0 to JAX / MaxDiffusion
Summary:
This PR ports the Ideogram 4.0 pipeline to JAX. It introduces the native Flax NNX
Ideogram4Transformer, integrates theTorchaxQwen3VLTextEncoderfor complex prompt processing, and implements the required asymmetric CFG denoising loop. It also includes caption key re-ordering in the pipeline to enforce Ideogram's strict JSON-based prompt schema (high_level_description,style_description,compositional_deconstruction).Generated Examples:
Prompt:
Prompt:
Prompt: