149 lines
5.1 KiB
Python
149 lines
5.1 KiB
Python
"""Compare first-token logits between `FlexGemma4Inference._prefill` and
|
|
vanilla `Gemma4ForCausalLM.forward` on the same prompt. Intended as a
|
|
one-shot correctness check; not part of the benchmark matrix.
|
|
|
|
Run:
|
|
CUDA_VISIBLE_DEVICES=2 python scripts/benchmarks/verify_gemma4_numerics.py
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import torch
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(HERE))
|
|
|
|
from gemma4_flex_inference import ( # noqa: E402
|
|
FlexGemma4Inference,
|
|
Sequence,
|
|
_require_gemma4,
|
|
)
|
|
|
|
|
|
def main():
|
|
Gemma4ForCausalLM, Gemma4Config, Gemma4TextConfig = _require_gemma4()
|
|
from transformers.models.gemma4.modeling_gemma4 import (
|
|
Gemma4ForConditionalGeneration,
|
|
)
|
|
from transformers import AutoTokenizer
|
|
|
|
name = "unsloth/gemma-4-E2B-it"
|
|
tok = AutoTokenizer.from_pretrained(name)
|
|
if tok.pad_token is None:
|
|
tok.pad_token = tok.eos_token
|
|
|
|
full_cfg = Gemma4Config.from_pretrained(name)
|
|
text_cfg = full_cfg.text_config
|
|
|
|
# Untouched HF reference: `Gemma4ForConditionalGeneration.forward` --
|
|
# the same class everyone else would load via AutoModelForCausalLM
|
|
# for Gemma-4. No patching, no shell, no flex attention.
|
|
ref_raw = Gemma4ForConditionalGeneration.from_pretrained(
|
|
name, dtype = torch.bfloat16, attn_implementation = "eager"
|
|
).to("cuda")
|
|
ref_raw.eval()
|
|
|
|
# Shell copy used by `gemma4_flex_inference.main()`: we deep-copy the
|
|
# loaded multimodal model, drop the vision + audio towers, and move
|
|
# the language_model into a Gemma4ForCausalLM wrapper so PEFT and
|
|
# state-dict hashing treat it as a decoder-only model. The flex path
|
|
# then patches its attention forwards on this shell. We keep the
|
|
# shell around both as (a) the model that gets flex-patched and
|
|
# (b) a sanity check that the shell itself matches the raw HF path.
|
|
full = Gemma4ForConditionalGeneration.from_pretrained(
|
|
name, dtype = torch.bfloat16, attn_implementation = "eager"
|
|
)
|
|
lang = full.model.language_model
|
|
full.model.vision_tower = None
|
|
full.model.audio_tower = None
|
|
full.model.embed_vision = None
|
|
full.model.embed_audio = None
|
|
|
|
shell = Gemma4ForCausalLM(text_cfg)
|
|
shell.model = lang
|
|
shell.lm_head.weight = lang.embed_tokens.weight
|
|
shell = shell.to(torch.bfloat16).to("cuda")
|
|
shell.eval()
|
|
del full
|
|
|
|
# Deep-copy so Flex's attention patching doesn't mutate the shell.
|
|
flex_model = copy.deepcopy(shell)
|
|
|
|
prompt = "The quick brown fox jumps over"
|
|
ids = tok(prompt, return_tensors = "pt")["input_ids"].to("cuda")
|
|
print(f"prompt len = {ids.shape[1]}")
|
|
|
|
with torch.inference_mode():
|
|
# `Gemma4ForConditionalGeneration.forward` applies
|
|
# `final_logit_softcapping` internally.
|
|
ref_logits = ref_raw(input_ids = ids, use_cache = False).logits[0, -1, :].float()
|
|
shell_logits = shell(ids, use_cache = False).logits[0, -1, :].float()
|
|
print(
|
|
f"raw Gemma4ForConditionalGeneration: mean {ref_logits.mean():.4f}, "
|
|
f"std {ref_logits.std():.4f}, argmax {int(ref_logits.argmax())} "
|
|
f"({tok.decode([int(ref_logits.argmax())])!r})"
|
|
)
|
|
print(
|
|
f"shell Gemma4ForCausalLM(text_cfg) : mean {shell_logits.mean():.4f}, "
|
|
f"std {shell_logits.std():.4f}, argmax {int(shell_logits.argmax())}"
|
|
)
|
|
# Dispose of the raw multimodal model before we build FlexGemma4Inference.
|
|
del ref_raw
|
|
torch.cuda.empty_cache()
|
|
|
|
# Flex path.
|
|
inf = FlexGemma4Inference(
|
|
flex_model,
|
|
tok,
|
|
max_batch_size = 4,
|
|
max_seq_length = 256,
|
|
n_pages = 64,
|
|
page_size = 64,
|
|
max_new_tokens = 1,
|
|
decode_kernel_options = {"BLOCK_M": 16, "BLOCK_N": 16},
|
|
prefill_kernel_options = {
|
|
"FORCE_USE_FLEX_ATTENTION": True,
|
|
"BLOCK_M": 32,
|
|
"BLOCK_N": 32,
|
|
},
|
|
fa4_prefill = False,
|
|
)
|
|
seq = Sequence(text = prompt, max_new_tokens = 1)
|
|
inf.tokenize([seq])
|
|
bi = inf.page_table.allocate()
|
|
inf.page_table.reserve(
|
|
bi,
|
|
torch.tensor([bi], device = "cuda", dtype = torch.long),
|
|
seq.total_length,
|
|
)
|
|
seq.batch_idx = bi
|
|
with torch.inference_mode():
|
|
flex_logits = inf._prefill([seq])[0].float()
|
|
print(
|
|
f"flex last-token logits : mean {flex_logits.mean():.4f}, "
|
|
f"std {flex_logits.std():.4f}, argmax {int(flex_logits.argmax())} "
|
|
f"({tok.decode([int(flex_logits.argmax())])!r})"
|
|
)
|
|
|
|
def report(tag, a, b):
|
|
diff = (a - b).abs()
|
|
top_a = set(a.topk(10).indices.tolist())
|
|
top_b = set(b.topk(10).indices.tolist())
|
|
print(
|
|
f" {tag:14s} max {diff.max().item():.3e} mean {diff.mean().item():.3e} "
|
|
f"argmax={int(a.argmax()) == int(b.argmax())} top-10={len(top_a & top_b)}/10"
|
|
)
|
|
|
|
print("vs raw Gemma4ForConditionalGeneration:")
|
|
report("shell vs raw", shell_logits, ref_logits)
|
|
report("flex vs raw", flex_logits, ref_logits)
|
|
print("vs shell (Gemma4ForCausalLM wrapper):")
|
|
report("flex vs shell", flex_logits, shell_logits)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|