Skip to content

Commit 6a4c956

Browse files
committed
Onboard Flux.2-klein-4B multi-host TPU pod support (TP=4, DP=2, B=16)
1 parent ea2603b commit 6a4c956

3 files changed

Lines changed: 36 additions & 68 deletions

File tree

src/maxdiffusion/configs/base_flux2klein.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,6 @@ width: 1024
270270
batch_size: 4
271271
interactive: False
272272

273-
# 4B Architecture Dimensions
274-
depth: 20 # num_single_layers
275-
num_double_layers: 5
276-
hidden_size: 3072
277-
num_attention_heads: 24
273+
# Note: Architecture dimensions (depth, num_double_layers, num_attention_heads) are
274+
# automatically inferred from pretrained_model_name_or_path (transformer/config.json).
278275

src/maxdiffusion/generate_flux2klein.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
from maxdiffusion import pyconfig
3333
from maxdiffusion import max_logging
34+
from maxdiffusion import max_utils
3435
from maxdiffusion.max_utils import create_device_mesh
3536
from maxdiffusion.train_utils import transformer_engine_context
3637

@@ -176,10 +177,16 @@ def main(argv):
176177
repo_id = "black-forest-labs/FLUX.2-klein-9B" if depth_val == 24 else "black-forest-labs/FLUX.2-klein-4B"
177178
max_logging.log(f"Target model detected: {repo_id}")
178179

179-
from huggingface_hub import snapshot_download
180+
import glob
181+
cache_pattern = os.path.expanduser(f"~/.cache/huggingface/hub/models--{repo_id.replace('/', '--')}/snapshots/*")
182+
snapshots = sorted(glob.glob(cache_pattern))
183+
if snapshots:
184+
snapshot_dir = snapshots[-1]
185+
else:
186+
from huggingface_hub import snapshot_download
187+
snapshot_dir = snapshot_download(repo_id=repo_id)
180188

181-
max_logging.log(f"Resolving snapshot directory for model '{repo_id}' from HF Hub...")
182-
snapshot_dir = snapshot_download(repo_id=repo_id)
189+
max_logging.log(f"Host {jax.process_index()} using HF snapshot directory: {snapshot_dir}")
183190
safetensors_path = os.path.join(snapshot_dir, "transformer")
184191
vae_safetensors_path = os.path.join(snapshot_dir, "vae", "diffusion_pytorch_model.safetensors")
185192
text_encoder_path = os.path.join(snapshot_dir, "text_encoder")
@@ -326,7 +333,7 @@ def qwen3_init_fn():
326333
# 8. Load weights on Host CPU
327334
max_logging.log("Loading parameters on Host CPU...")
328335
t_load_start = time.time()
329-
cpu_device = jax.devices("cpu")[0]
336+
cpu_device = jax.local_devices(backend="cpu")[0]
330337
with jax.default_device(cpu_device):
331338
with mesh, nn_partitioning.axis_rules(config.logical_axis_rules):
332339
import flax.linen.spmd as flax_spmd
@@ -355,9 +362,9 @@ def unbox_fn(x):
355362

356363
if config.weights_dtype == "bfloat16":
357364
max_logging.log("Casting JAX parameters to bfloat16 in-place...")
358-
cast_dict_to_bfloat16_inplace(params, device=cpu_device, exclude_keywords=("norm",))
359-
cast_dict_to_bfloat16_inplace(vae_params, device=cpu_device, exclude_keywords=("norm",))
360-
cast_dict_to_bfloat16_inplace(qwen3_params, device=cpu_device, exclude_keywords=("norm",))
365+
cast_dict_to_bfloat16_inplace(params, exclude_keywords=("norm",))
366+
cast_dict_to_bfloat16_inplace(vae_params, exclude_keywords=("norm",))
367+
cast_dict_to_bfloat16_inplace(qwen3_params, exclude_keywords=("norm",))
361368
vae_bn_mean = vae_bn_mean.astype(jnp.bfloat16)
362369
vae_bn_std = vae_bn_std.astype(jnp.bfloat16)
363370

@@ -371,7 +378,7 @@ def unbox_fn(x):
371378
max_logging.log("Putting params on TPU HBM...")
372379
with mesh, nn_partitioning.axis_rules(config.logical_axis_rules):
373380
try:
374-
params = jax.device_put(params, transformer_shardings)
381+
params = jax.tree_util.tree_map(max_utils.device_put_replicated, params, transformer_shardings)
375382
except Exception as err:
376383
max_logging.log("\n❌ jax.device_put(params, transformer_shardings) FAILED!")
377384
flat_p = flax.traverse_util.flatten_dict(params)
@@ -383,9 +390,9 @@ def unbox_fn(x):
383390
sys.stdout.flush()
384391
raise err
385392
max_logging.log("Putting vae_params on TPU HBM...")
386-
vae_params = jax.device_put(vae_params, vae_shardings)
393+
vae_params = jax.tree_util.tree_map(max_utils.device_put_replicated, vae_params, vae_shardings)
387394
max_logging.log("Putting qwen3_params on TPU HBM...")
388-
qwen3_params = jax.device_put(qwen3_params, qwen3_shardings)
395+
qwen3_params = jax.tree_util.tree_map(max_utils.device_put_replicated, qwen3_params, qwen3_shardings)
389396
max_logging.log("All parameters placed on TPU HBM successfully!")
390397
gc.collect()
391398
jax.effects_barrier()

src/maxdiffusion/models/flux/util.py

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,14 @@ def validate_flax_state_dict(expected_pytree: dict, new_pytree: dict):
118118
expected_pytree: dict - a pytree that comes from initializing the model.
119119
new_pytree: dict - a pytree that has been created from pytorch weights.
120120
"""
121-
expected_flat = (
122-
flatten_dict(expected_pytree) if not isinstance(next(iter(expected_pytree.keys()), None), tuple) else expected_pytree
123-
)
121+
expected_flat = flatten_dict(expected_pytree) if not isinstance(next(iter(expected_pytree.keys()), None), tuple) else expected_pytree
124122
new_flat = flatten_dict(new_pytree) if not isinstance(next(iter(new_pytree.keys()), None), tuple) else new_pytree
125123

126124
if len(expected_flat.keys()) != len(new_flat.keys()):
127125
set1 = set(expected_flat.keys())
128126
set2 = set(new_flat.keys())
129127
missing_keys = set1 ^ set2
130-
max_logging.log(
131-
f"Missing or extra parameter keys count mismatch ({len(expected_flat)} expected vs {len(new_flat)} converted): {missing_keys}"
132-
)
128+
max_logging.log(f"Missing or extra parameter keys count mismatch ({len(expected_flat)} expected vs {len(new_flat)} converted): {missing_keys}")
133129

134130
for key in expected_flat.keys():
135131
if key in new_flat.keys():
@@ -273,7 +269,6 @@ def load_flow_model(name: str, eval_shapes: dict, device: str, hf_download: bool
273269
jax.clear_caches()
274270
return flax_state_dict
275271

276-
277272
# -----------------------------------------------------------------------------
278273
# Latent Packing & Unpacking Helpers
279274
# -----------------------------------------------------------------------------
@@ -399,10 +394,7 @@ def cast_dict_to_bfloat16_inplace(d, device=None, exclude_keywords=None, parent_
399394
is_excluded = exclude_keywords and any(kw.lower() in current_key.lower() for kw in exclude_keywords)
400395
target_dtype = jnp.float32 if is_excluded else jnp.bfloat16
401396

402-
if device is not None:
403-
d[k] = jax.device_put(jnp.array(v, dtype=target_dtype), device=device)
404-
else:
405-
d[k] = jnp.array(v, dtype=target_dtype)
397+
d[k] = v.astype(target_dtype)
406398
if hasattr(d[k], "block_until_ready"):
407399
d[k].block_until_ready()
408400
del v
@@ -450,9 +442,7 @@ def convert_and_transpose_tensor(tensor, transpose=False):
450442
return jnp.array(tensor, dtype=target_dtype)
451443

452444
# Global layers
453-
params["context_embedder"]["kernel"] = convert_and_transpose_tensor(
454-
pt_state_dict.pop("context_embedder.weight"), transpose=True
455-
)
445+
params["context_embedder"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop("context_embedder.weight"), transpose=True)
456446
params["x_embedder"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop("x_embedder.weight"), transpose=True)
457447
params["double_stream_modulation_img"]["kernel"] = convert_and_transpose_tensor(
458448
pt_state_dict.pop("double_stream_modulation_img.linear.weight"), transpose=True
@@ -466,9 +456,7 @@ def convert_and_transpose_tensor(tensor, transpose=False):
466456
params["proj_out"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop("proj_out.weight"), transpose=True)
467457

468458
# norm_out
469-
params["norm_out"]["linear"]["kernel"] = convert_and_transpose_tensor(
470-
pt_state_dict.pop("norm_out.linear.weight"), transpose=True
471-
)
459+
params["norm_out"]["linear"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop("norm_out.linear.weight"), transpose=True)
472460

473461
# time_text_embed (Timestep Embedding)
474462
if "time_guidance_embed.timestep_embedder.linear_1.weight" in pt_state_dict:
@@ -506,30 +494,18 @@ def convert_and_transpose_tensor(tensor, transpose=False):
506494
jax_db["attn"]["e_qkv"]["kernel"] = jnp.array(np.concatenate([add_q, add_k, add_v], axis=1), dtype=target_dtype)
507495

508496
# Projections out
509-
jax_db["attn"]["i_proj"]["kernel"] = convert_and_transpose_tensor(
510-
pt_state_dict.pop(prefix + "attn.to_out.0.weight"), transpose=True
511-
)
512-
jax_db["attn"]["e_proj"]["kernel"] = convert_and_transpose_tensor(
513-
pt_state_dict.pop(prefix + "attn.to_add_out.weight"), transpose=True
514-
)
497+
jax_db["attn"]["i_proj"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "attn.to_out.0.weight"), transpose=True)
498+
jax_db["attn"]["e_proj"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "attn.to_add_out.weight"), transpose=True)
515499

516500
# Norm scales
517501
jax_db["attn"]["query_norm"]["scale"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "attn.norm_q.weight"))
518502
jax_db["attn"]["key_norm"]["scale"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "attn.norm_k.weight"))
519-
jax_db["attn"]["encoder_query_norm"]["scale"] = convert_and_transpose_tensor(
520-
pt_state_dict.pop(prefix + "attn.norm_added_q.weight")
521-
)
522-
jax_db["attn"]["encoder_key_norm"]["scale"] = convert_and_transpose_tensor(
523-
pt_state_dict.pop(prefix + "attn.norm_added_k.weight")
524-
)
503+
jax_db["attn"]["encoder_query_norm"]["scale"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "attn.norm_added_q.weight"))
504+
jax_db["attn"]["encoder_key_norm"]["scale"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "attn.norm_added_k.weight"))
525505

526506
# SwiGLU MLPs
527-
jax_db["ff"]["linear_in"]["kernel"] = convert_and_transpose_tensor(
528-
pt_state_dict.pop(prefix + "ff.linear_in.weight"), transpose=True
529-
)
530-
jax_db["ff"]["linear_out"]["kernel"] = convert_and_transpose_tensor(
531-
pt_state_dict.pop(prefix + "ff.linear_out.weight"), transpose=True
532-
)
507+
jax_db["ff"]["linear_in"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "ff.linear_in.weight"), transpose=True)
508+
jax_db["ff"]["linear_out"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop(prefix + "ff.linear_out.weight"), transpose=True)
533509
jax_db["ff_context"]["linear_in"]["kernel"] = convert_and_transpose_tensor(
534510
pt_state_dict.pop(prefix + "ff_context.linear_in.weight"), transpose=True
535511
)
@@ -544,12 +520,8 @@ def convert_and_transpose_tensor(tensor, transpose=False):
544520
s_prefix = f"single_transformer_blocks.{block_idx}."
545521

546522
# Joint projections
547-
jax_sb["linear1"]["kernel"] = convert_and_transpose_tensor(
548-
pt_state_dict.pop(s_prefix + "attn.to_qkv_mlp_proj.weight"), transpose=True
549-
)
550-
jax_sb["linear2"]["kernel"] = convert_and_transpose_tensor(
551-
pt_state_dict.pop(s_prefix + "attn.to_out.weight"), transpose=True
552-
)
523+
jax_sb["linear1"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop(s_prefix + "attn.to_qkv_mlp_proj.weight"), transpose=True)
524+
jax_sb["linear2"]["kernel"] = convert_and_transpose_tensor(pt_state_dict.pop(s_prefix + "attn.to_out.weight"), transpose=True)
553525

554526
# Norm scales
555527
jax_sb["attn"]["query_norm"]["scale"] = convert_and_transpose_tensor(pt_state_dict.pop(s_prefix + "attn.norm_q.weight"))
@@ -585,15 +557,11 @@ def get_pytorch_weight_tensor(key):
585557
max_logging.log("Mapping VAE decoder weights to JAX parameters...")
586558

587559
# post_quant_conv
588-
jax_params["post_quant_conv"]["kernel"] = jnp.array(
589-
get_pytorch_weight_tensor("post_quant_conv.weight").transpose(2, 3, 1, 0)
590-
)
560+
jax_params["post_quant_conv"]["kernel"] = jnp.array(get_pytorch_weight_tensor("post_quant_conv.weight").transpose(2, 3, 1, 0))
591561
jax_params["post_quant_conv"]["bias"] = jnp.array(get_pytorch_weight_tensor("post_quant_conv.bias"))
592562

593563
# decoder.conv_in
594-
jax_params["decoder"]["conv_in"]["kernel"] = jnp.array(
595-
get_pytorch_weight_tensor("decoder.conv_in.weight").transpose(2, 3, 1, 0)
596-
)
564+
jax_params["decoder"]["conv_in"]["kernel"] = jnp.array(get_pytorch_weight_tensor("decoder.conv_in.weight").transpose(2, 3, 1, 0))
597565
jax_params["decoder"]["conv_in"]["bias"] = jnp.array(get_pytorch_weight_tensor("decoder.conv_in.bias"))
598566

599567
# decoder.mid_block
@@ -657,17 +625,13 @@ def get_pytorch_weight_tensor(key):
657625
upsampler_jax = up_block_jax["upsamplers_0"]
658626
upsampler_pt = f"{up_block_pt}.upsamplers.0"
659627

660-
upsampler_jax["conv"]["kernel"] = jnp.array(
661-
get_pytorch_weight_tensor(f"{upsampler_pt}.conv.weight").transpose(2, 3, 1, 0)
662-
)
628+
upsampler_jax["conv"]["kernel"] = jnp.array(get_pytorch_weight_tensor(f"{upsampler_pt}.conv.weight").transpose(2, 3, 1, 0))
663629
upsampler_jax["conv"]["bias"] = jnp.array(get_pytorch_weight_tensor(f"{upsampler_pt}.conv.bias"))
664630

665631
# decoder.conv_norm_out & conv_out
666632
jax_params["decoder"]["conv_norm_out"]["scale"] = jnp.array(get_pytorch_weight_tensor("decoder.conv_norm_out.weight"))
667633
jax_params["decoder"]["conv_norm_out"]["bias"] = jnp.array(get_pytorch_weight_tensor("decoder.conv_norm_out.bias"))
668-
jax_params["decoder"]["conv_out"]["kernel"] = jnp.array(
669-
get_pytorch_weight_tensor("decoder.conv_out.weight").transpose(2, 3, 1, 0)
670-
)
634+
jax_params["decoder"]["conv_out"]["kernel"] = jnp.array(get_pytorch_weight_tensor("decoder.conv_out.weight").transpose(2, 3, 1, 0))
671635
jax_params["decoder"]["conv_out"]["bias"] = jnp.array(get_pytorch_weight_tensor("decoder.conv_out.bias"))
672636

673637
jax_params = jax.tree_util.tree_map(

0 commit comments

Comments
 (0)