Skip to content

Commit e0b5028

Browse files
Merge pull request #2508 from AI-Hypercomputer:aireen/llama4_logits_fix
PiperOrigin-RevId: 820900058
2 parents e0b0cfc + 71e99b2 commit e0b5028

8 files changed

Lines changed: 173 additions & 87 deletions

File tree

src/MaxText/configs/base.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ run_name: ""
1818

1919
model_name: "default" # override config settings to match a specific model. other than the override, nothing should use this!
2020
override_model_config: False # When set to true allows overriding model parameters via CLI for the purpose of debugging/testing.
21-
normalization_layer_epsilon: 1.e-05
21+
normalization_layer_epsilon: 1.e-05 # epsilon value for rmsnorm, layernorm.
2222

2323
################################## CHECKPOINTING ##################################
2424
# Checkpointing makes the following choices in the following order, starting with (1):

src/MaxText/layers/attentions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ def init_rotary_embedding(self):
682682
hidden_size=self.config.hidden_size_for_vit,
683683
num_attention_heads=self.config.num_attention_heads_for_vit,
684684
rope_theta=self.config.rope_theta_for_vit,
685+
fprop_dtype=self.dtype,
685686
rngs=self.rngs,
686687
)
687688
elif self.config.model_name.startswith("llama3.1") or rope_type.startswith("llama3.1"):

src/MaxText/layers/decoders.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,6 @@ def __call__(
775775
"nope_layer_interval": self.config.nope_layer_interval,
776776
"interleave_moe_layer_step": self.config.interleave_moe_layer_step,
777777
}
778-
broadcast_args += (bidirectional_mask,)
779778
y, _ = self.scan_decoder_layers(
780779
cfg,
781780
RemattedBlockLayer,
@@ -825,7 +824,6 @@ def __call__(
825824
"is_nope_layer": llama4.determine_is_nope_layer(lyr, self.config.nope_layer_interval),
826825
"is_moe_layer": llama4.determine_is_moe_layer(lyr, self.config.interleave_moe_layer_step),
827826
}
828-
layer_call_kwargs = {"bidirectional_mask": bidirectional_mask}
829827
if cfg.decoder_block == DecoderBlockType.GPT_OSS:
830828
layer_kwargs = {"attention_type": gpt_oss.get_attention_type(layer_id=lyr)}
831829
layer = RemattedBlockLayer(

src/MaxText/layers/gemma3.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,13 +413,19 @@ def __init__(
413413
self.rngs = rngs
414414
self.seq_len = (self.config.image_size_for_vit // self.config.patch_size_for_vit) ** 2
415415

416-
self.LayerNorm_0 = nnx.LayerNorm(num_features=self.config.hidden_size_for_vit, rngs=self.rngs)
416+
self.LayerNorm_0 = nnx.LayerNorm(
417+
num_features=self.config.hidden_size_for_vit, epsilon=self.config.normalization_layer_epsilon, rngs=self.rngs
418+
)
417419
self.MultiHeadDotProductAttention_0 = Attention(
418420
config=self.config,
419421
num_query_heads=self.config.num_attention_heads_for_vit,
420422
num_kv_heads=self.config.num_attention_heads_for_vit,
421423
head_dim=self.config.hidden_size_for_vit // self.config.num_attention_heads_for_vit,
422424
max_target_length=self.seq_len,
425+
float32_qk_product=self.config.float32_qk_product,
426+
float32_logits=self.config.float32_logits,
427+
dtype=self.config.dtype_mm,
428+
weight_dtype=self.config.weight_dtype,
423429
mesh=self.mesh,
424430
attention_kernel="dot_product",
425431
inputs_q_shape=(self.config.per_device_batch_size, self.seq_len, self.config.hidden_size_for_vit),
@@ -434,7 +440,9 @@ def __init__(
434440
is_vision=True,
435441
rngs=self.rngs,
436442
)
437-
self.LayerNorm_1 = nnx.LayerNorm(num_features=self.config.hidden_size_for_vit, rngs=self.rngs)
443+
self.LayerNorm_1 = nnx.LayerNorm(
444+
num_features=self.config.hidden_size_for_vit, epsilon=self.config.normalization_layer_epsilon, rngs=self.rngs
445+
)
438446
self.MlpBlockViT_0 = MlpBlockViT(
439447
block_id=self.block_id,
440448
config=self.config,
@@ -479,7 +487,9 @@ def __init__(
479487
rngs=self.rngs,
480488
)
481489
setattr(self, layer_name, layer)
482-
self.encoder_norm = nnx.LayerNorm(num_features=self.config.hidden_size_for_vit, rngs=self.rngs)
490+
self.encoder_norm = nnx.LayerNorm(
491+
num_features=self.config.hidden_size_for_vit, epsilon=self.config.normalization_layer_epsilon, rngs=self.rngs
492+
)
483493

484494
def __call__(self, x: jax.Array, deterministic: bool = True) -> jax.Array:
485495
# TODO(aireenmei, hengtaoguo): add if-scan branch to enable scan support for vision encoder

src/MaxText/layers/llama4.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def __call__(self, inputs: Array) -> Array:
7777
window_strides=[self.config.patch_size_for_vit, self.config.patch_size_for_vit],
7878
padding="VALID",
7979
dimension_numbers=("NCHW", "HWIO", "NCHW"),
80+
precision=lax.Precision(self.config.matmul_precision),
81+
preferred_element_type=self.config.dtype_mm,
8082
)
8183

8284
patches = patches.reshape(
@@ -442,7 +444,6 @@ def __call__(
442444
decoder_positions,
443445
deterministic,
444446
model_mode,
445-
bidirectional_mask=None,
446447
slot: None | int = None,
447448
page_state: None | page_manager.PageState = None,
448449
previous_chunk=None,
@@ -467,7 +468,6 @@ def __call__(
467468
slot=slot,
468469
page_state=page_state,
469470
previous_chunk=previous_chunk,
470-
bidirectional_mask=bidirectional_mask,
471471
)
472472
attention_lnx = nn.with_logical_constraint(attention_lnx, self.activation_axis_names)
473473
intermediate_inputs = inputs + attention_lnx
@@ -561,7 +561,6 @@ def __call__(
561561
decoder_positions,
562562
deterministic,
563563
model_mode,
564-
bidirectional_mask=None,
565564
slot: None | int = None,
566565
page_state: None | page_manager.PageState = None,
567566
previous_chunk=None,
@@ -582,7 +581,6 @@ def __call__(
582581
previous_chunk=previous_chunk,
583582
page_state=page_state,
584583
slot=slot,
585-
bidirectional_mask=bidirectional_mask,
586584
)
587585
if cfg.scan_layers:
588586
y = y[0]
@@ -611,7 +609,9 @@ def __init__(self, config: Config, mesh: Mesh, *, rngs: nnx.Rngs = None):
611609
self.config.hidden_size_for_vit,
612610
)
613611

614-
self.input_layer_norm = nnx.LayerNorm(num_features=self.config.hidden_size_for_vit, epsilon=1e-5, rngs=self.rngs)
612+
self.input_layer_norm = nnx.LayerNorm(
613+
num_features=self.config.hidden_size_for_vit, epsilon=self.config.normalization_layer_epsilon, rngs=self.rngs
614+
)
615615
self.self_attention_vision = Attention(
616616
config=self.config,
617617
num_query_heads=self.config.num_attention_heads_for_vit,
@@ -623,6 +623,8 @@ def __init__(self, config: Config, mesh: Mesh, *, rngs: nnx.Rngs = None):
623623
inputs_kv_shape=self.hidden_states_shape,
624624
float32_qk_product=self.config.float32_qk_product,
625625
float32_logits=self.config.float32_logits,
626+
dtype=self.config.dtype_mm,
627+
weight_dtype=self.config.weight_dtype,
626628
mesh=self.mesh,
627629
dropout_rate=0,
628630
name="self_attention_vision",
@@ -640,7 +642,7 @@ def __init__(self, config: Config, mesh: Mesh, *, rngs: nnx.Rngs = None):
640642
rngs=self.rngs,
641643
)
642644
self.post_attention_layer_norm = nnx.LayerNorm(
643-
num_features=self.config.hidden_size_for_vit, epsilon=1e-5, rngs=self.rngs
645+
num_features=self.config.hidden_size_for_vit, epsilon=self.config.normalization_layer_epsilon, rngs=self.rngs
644646
)
645647
self.Llama4VisionMLP_0 = Llama4VisionMLP(config=self.config, rngs=self.rngs)
646648

@@ -722,10 +724,16 @@ def __init__(self, config: Config, mesh: Mesh, *, rngs: nnx.Rngs = None):
722724
self.initializer(self.rngs.params(), (self.num_patches, self.config.hidden_size_for_vit), self.config.dtype_mm)
723725
)
724726
self.layernorm_pre = nnx.LayerNorm(
725-
num_features=self.config.hidden_size_for_vit, dtype=self.config.dtype_mm, rngs=self.rngs
727+
num_features=self.config.hidden_size_for_vit,
728+
epsilon=self.config.normalization_layer_epsilon,
729+
dtype=self.config.dtype_mm,
730+
rngs=self.rngs,
726731
)
727732
self.layernorm_post = nnx.LayerNorm(
728-
num_features=self.config.hidden_size_for_vit, dtype=self.config.dtype_mm, rngs=self.rngs
733+
num_features=self.config.hidden_size_for_vit,
734+
epsilon=self.config.normalization_layer_epsilon,
735+
dtype=self.config.dtype_mm,
736+
rngs=self.rngs,
729737
)
730738

731739
self.Llama4UnfoldConvolution_0 = Llama4UnfoldConvolution(config=self.config, rngs=self.rngs)
@@ -762,7 +770,7 @@ def __call__(
762770
class_embedding = jnp.broadcast_to(
763771
class_embedding_expanded, (hidden_states.shape[0], 1, self.config.hidden_size_for_vit)
764772
)
765-
hidden_states = jnp.concatenate([class_embedding, hidden_states], axis=1)
773+
hidden_states = jnp.concatenate([hidden_states, class_embedding], axis=1)
766774

767775
# Add positional embedding
768776
hidden_states += self.positional_embedding_vlm

src/MaxText/scratch_code/generate_hf_golden_logits.py

Lines changed: 78 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,27 @@
2424
For multimodal models, use --image-paths argument to provide image path(s),\
2525
use --apply-chat-template=true if use HF chat template to format image+prompt.\
2626
When using chat template, the prompt should not contain image placeholders.
27-
27+
For multimodal logits, since the model is not suppose to generate image, the logits correspond to images \
28+
tokens can be close to 0, using --output-format=pickle is recommended to preserve precision.
29+
2830
More examples:
2931
python3 -m MaxText.scratch_code.generate_hf_golden_logits --model-id=meta-llama/Llama-4-Scout-17B-16E \
3032
--output-path=golden_Llama-4-Scout-17B-16E_vision.jsonl --prompts='Describe this image.' \
31-
--apply-chat-template=true --gcs-bucket=<bucket> --hf-model-path=<hf_checkpoint_path> \
32-
--image-paths=src/MaxText/test_assets/test_image.jpg
33+
--apply-chat-template --gcs-bucket=<bucket> --hf-model-path=<hf_checkpoint_path> \
34+
--image-paths=src/MaxText/test_assets/test_image.jpg --output-format=pickle
3335
3436
python3 -m MaxText.scratch_code.generate_hf_golden_logits --model-id=google/gemma-3-4b-it \
3537
--output-path=golden_gemma-3-4b-it_vision.jsonl --prompts='<start_of_image>' \
36-
--apply-chat-template=false --gcs-bucket=<bucket> --hf-model-path=<hf_checkpoint_path> \
38+
--gcs-bucket=<bucket> --hf-model-path=<hf_checkpoint_path> \
3739
--image-paths=src/MaxText/test_assets/test_image.jpg
3840
"""
3941

4042
import torch
4143
import argparse
42-
from transformers import AutoTokenizer, AutoProcessor, AutoModelForCausalLM
44+
from transformers import AutoTokenizer, AutoProcessor
4345
import jsonlines
46+
import pickle
47+
import numpy as np
4448
from google.cloud import storage
4549
from PIL import Image
4650

@@ -55,78 +59,84 @@ def upload_blob(bucket_name, source_file_name, destination_blob_name):
5559
blob.upload_from_filename(source_file_name)
5660

5761

58-
def save_golden_logits(model_id, output_path, prompt_texts, apply_chat_template, gcs_bucket, hf_model_path, image_paths):
62+
def save_golden_logits(
63+
model_id, output_path, prompt_texts, apply_chat_template, gcs_bucket, hf_model_path, image_paths, output_format
64+
):
5965
"""save golden logits"""
6066
if hf_model_path is None:
6167
hf_model_path = model_id
68+
69+
if model_id.startswith("meta-llama/Llama-4"):
70+
from transformers import Llama4ForConditionalGeneration # pylint: disable=import-outside-toplevel
71+
72+
model_class = Llama4ForConditionalGeneration
73+
else:
74+
from transformers import AutoModelForCausalLM # pylint: disable=import-outside-toplevel
75+
76+
model_class = AutoModelForCausalLM
77+
6278
tokenizer = AutoTokenizer.from_pretrained(model_id)
63-
model = AutoModelForCausalLM.from_pretrained(
79+
print(f"loading model from {hf_model_path}")
80+
model = model_class.from_pretrained(
6481
hf_model_path,
6582
torch_dtype=torch.float32,
6683
trust_remote_code=True,
6784
)
6885

6986
all_data_to_save = []
7087
for i, prompt_text in enumerate(prompt_texts):
71-
# Encode the prompt text
88+
# 1. Prepare inputs for the model and base data for saving
89+
data_to_save = {"prompt": prompt_text}
7290
if image_paths:
73-
try:
74-
image = Image.open(image_paths[i])
75-
except Exception as e:
76-
raise e
77-
image = image.convert("RGB")
78-
# TODO (aireenmei): remove this when Llama-4 supports dynamic image shapes.
91+
image = Image.open(image_paths[i]).convert("RGB")
7992
if model_id.startswith("meta-llama/Llama-4"):
8093
image = image.resize((336, 336))
81-
processor = AutoProcessor.from_pretrained(model_id, token=True)
94+
processor = AutoProcessor.from_pretrained(model_id, token=True) if image_paths else None
8295
if apply_chat_template:
83-
messages = [
84-
{
85-
"role": "user",
86-
"content": [
87-
{"type": "image"},
88-
{"type": "text", "text": prompt_text},
89-
],
90-
},
91-
]
96+
messages = [{"role": "user", "content": [{"type": "image"}, {"type": "text", "text": prompt_text}]}]
9297
formatted_prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
9398
inputs = processor(text=formatted_prompt, images=image, return_tensors="pt")
9499
else:
95100
formatted_prompt = prompt_text
96101
inputs = processor(text=formatted_prompt, images=image, return_tensors="pt", add_special_tokens=False)
97-
with torch.no_grad():
98-
outputs = model(**inputs)
99-
logits = outputs.logits.cpu().numpy().astype("float32")
100-
101-
data_to_save = {
102-
"prompt": prompt_text,
103-
"formatted_prompt": formatted_prompt,
104-
"tokens": inputs["input_ids"].tolist()[0],
105-
"attention_mask": inputs["attention_mask"].tolist()[0],
106-
"image_path": image_paths[i],
107-
"pixel_values": inputs["pixel_values"].tolist()[0],
108-
"logits": logits.tolist()[0],
109-
}
102+
103+
inputs["pixel_values"] = inputs["pixel_values"].to(torch.float32)
104+
print(f"{apply_chat_template=} {formatted_prompt=}")
105+
data_to_save.update({"formatted_prompt": formatted_prompt, "image_path": image_paths[i]})
110106
else:
111107
input_ids = tokenizer.encode(prompt_text, return_tensors="pt")
112-
# Get the logits for the prompt + completion
113-
with torch.no_grad():
114-
outputs = model(input_ids)
115-
logits = outputs.logits.cpu().numpy().astype("float32")
116-
117-
# Prepare data to be saved
118-
data_to_save = {
119-
"prompt": prompt_text,
120-
"tokens": input_ids.tolist()[0],
121-
"logits": logits.tolist()[0], # Convert numpy array to list for JSON serialization
122-
}
108+
inputs = {"input_ids": input_ids}
109+
110+
# 2. Run inference
111+
with torch.no_grad():
112+
outputs = model(**inputs)
113+
logits = outputs.logits.cpu().numpy().astype("float32")
114+
115+
# 3. Populate final data dictionary with tensors from inputs and logits
116+
for key, value in inputs.items():
117+
new_key = "tokens" if key == "input_ids" else key
118+
data_to_save[new_key] = value.cpu().numpy()[0]
119+
data_to_save["logits"] = logits[0]
120+
123121
print(f"Token length is {len(data_to_save['tokens'])} for prompt: {prompt_text}")
124122
print(f"raw ids: {data_to_save['tokens']}")
123+
124+
# 4. Convert numpy arrays to lists if format is json
125+
if output_format == "json":
126+
for key, value in data_to_save.items():
127+
if isinstance(value, np.ndarray):
128+
data_to_save[key] = value.tolist()
129+
125130
all_data_to_save.append(data_to_save)
126131

127-
with jsonlines.open(output_path, "w") as f:
128-
f.write_all(all_data_to_save)
129-
print(f"File is stored locally at {output_path}.")
132+
# 5. Save the collected data
133+
if output_format == "json":
134+
with jsonlines.open(output_path, "w") as f:
135+
f.write_all(all_data_to_save)
136+
elif output_format == "pickle":
137+
with open(output_path, "wb") as f:
138+
pickle.dump(all_data_to_save, f)
139+
print(f"File is stored locally at {output_path}.")
130140

131141
if gcs_bucket:
132142
upload_blob(gcs_bucket, output_path, f"golden-logits/{model_id}/{output_path}")
@@ -140,10 +150,8 @@ def main(raw_args=None) -> None:
140150
parser.add_argument("--prompts", type=str, required=True, help="A semicolon-separated list of prompts.")
141151
parser.add_argument(
142152
"--apply-chat-template",
143-
type=bool,
144-
required=False,
145-
default=False,
146-
help="Whether to apply chat template from the HF processor. Used for image+text input.",
153+
action="store_true",
154+
help="Apply chat template from the HF processor. Use for image+text input. Pass this flag to enable; omit to disable.",
147155
)
148156
parser.add_argument(
149157
"--gcs-bucket", type=str, required=False, default=None, help="A GCS bucket to store logits, without gs://."
@@ -154,6 +162,13 @@ def main(raw_args=None) -> None:
154162
parser.add_argument(
155163
"--image-paths", type=str, required=False, default=None, help="A semicolon-separated list of image_paths."
156164
)
165+
parser.add_argument(
166+
"--output-format",
167+
type=str,
168+
required=False,
169+
default="json",
170+
help="The output format for the golden logits. (json, pickle)",
171+
)
157172
args = parser.parse_args(raw_args)
158173
prompts = args.prompts.split(";")
159174
image_paths = args.image_paths.split(";") if args.image_paths else []
@@ -164,7 +179,14 @@ def main(raw_args=None) -> None:
164179
if args.apply_chat_template:
165180
assert image_paths, "apply_chat_template is only used for image+text input, so image_paths must be provided."
166181
save_golden_logits(
167-
args.model_id, args.output_path, prompts, args.apply_chat_template, args.gcs_bucket, args.hf_model_path, image_paths
182+
args.model_id,
183+
args.output_path,
184+
prompts,
185+
args.apply_chat_template,
186+
args.gcs_bucket,
187+
args.hf_model_path,
188+
image_paths,
189+
args.output_format,
168190
)
169191

170192

0 commit comments

Comments
 (0)