Download code/models/tt_transformers/tests/test_model.py from DeAIafdeling/qwen38-flash-next: direct link, hf CLI and curl.
- Browser
- Download file 21.9 kB
-
https://huggingface.co/DeAIafdeling/qwen38-flash-next/resolve/main/code/models/tt_transformers/tests/test_model.py
- Command line
-
hf download hf://DeAIafdeling/qwen38-flash-next/code/models/tt_transformers/tests/test_model.py
-
curl -L -o test_model.py https://huggingface.co/DeAIafdeling/qwen38-flash-next/resolve/main/code/models/tt_transformers/tests/test_model.py
21.9 kB
| # SPDX-FileCopyrightText: © 2023 Tenstorrent USA, Inc. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import os | |
| import pytest | |
| import torch | |
| from loguru import logger | |
| import ttnn | |
| from models.common.utility_functions import comp_allclose, comp_pcc | |
| from models.tt_transformers.tests.decode_test_helpers import decode_step_state, teacher_forced_decode_token | |
| from models.tt_transformers.tt.common import Mode, PagedAttentionConfig, sample_host | |
| from models.tt_transformers.tt.model import Transformer | |
| from models.tt_transformers.tt.model_config import DecodersPrecision, ModelArgs | |
| from models.tt_transformers.tt.prefetcher import Prefetcher | |
| def test_model_inference( | |
| weights, | |
| layers, | |
| max_seq_len, | |
| batch_size, | |
| paged_attention, | |
| page_params, | |
| optimizations, | |
| mesh_device, | |
| reset_seeds, | |
| ensure_gc, | |
| request, | |
| use_prefetcher, | |
| ): | |
| model_name_env = os.getenv("HF_MODEL") | |
| if model_name_env: | |
| if "Mistral-7B" in model_name_env and weights == "instruct": | |
| pytest.skip( | |
| "Skipping Mistral-7B full model test for now. See issue https://github.com/tenstorrent/tt-metal/issues/19806" | |
| ) | |
| if ("Phi-3-mini" in model_name_env or "phi-4" in model_name_env) and weights == "random": | |
| pytest.skip("Skipping Phi-3-mini-128k-instruct for single layer dummy weights test.") | |
| if ("Llama" in model_name_env) and ("Vision" in model_name_env) and (weights == "instruct"): | |
| pytest.skip("Skipping Llama Vision full model test: no CrossAttention functionality in this test.") | |
| run_ref_pt = True # Flag to run reference PyTorch model and compare PCC | |
| dtype = ttnn.bfloat8_b | |
| use_hf_rope = request.config.getoption("--use_hf_rope") | |
| if use_hf_rope: | |
| logger.info("Using HF style rope") | |
| test_id = request.node.callspec.id | |
| mode_accuracy = "accuracy" in test_id | |
| instruct = False # True if weights == "instruct" else False | |
| dummy_weights = True if weights == "random" else False | |
| # KV cache PCC is not measured today: no (weights, layers) pair satisfies both | |
| # conditions, since "quick" is ("random", 1) and "full" is ("instruct", None). | |
| # Unreachable since the flag was introduced, so final_k_cache_pcc / | |
| # final_v_cache_pcc below have never been evaluated. Enabling it (drop the | |
| # dummy-weights term, as the Galaxy copy does) arms those thresholds for every | |
| # model running -k quick and should be done in its own PR. | |
| cache_pcc = layers == 1 and not dummy_weights | |
| # Setup prefetcher | |
| # num_tensors is 5 because we are prefetching qkv + do + ff1 + ff3 + ff2 | |
| num_tensors = 5 if use_prefetcher else 0 | |
| prefetcher = Prefetcher(mesh_device, num_tensors=num_tensors, num_layers=1) if use_prefetcher else None | |
| if use_prefetcher: | |
| prefetcher.init(mode=Mode.DECODE) | |
| model_args = ModelArgs( | |
| mesh_device, | |
| instruct=instruct, | |
| dummy_weights=dummy_weights, | |
| optimizations=optimizations, | |
| max_seq_len=max_seq_len, | |
| max_batch_size=batch_size, | |
| cache_hf=True, | |
| prefetcher=prefetcher, | |
| use_hf_rope=use_hf_rope, | |
| ) | |
| # Define minimum PCC for each iteration | |
| if layers == 1: | |
| pcc = 0.88 if mode_accuracy else 0.86 | |
| else: | |
| pcc = 0.94 if mode_accuracy else 0.86 | |
| model_name = model_args.base_model_name | |
| # Set num_layers for prefetcher if it is not None | |
| if prefetcher is not None: | |
| prefetcher.num_layers = model_args.n_layers | |
| if layers == 1: # quick mode has tight PCC checks for known models | |
| model_name = model_args.base_model_name | |
| # Define tight final PCC thresholds for quick mode | |
| final_model_pcc = { | |
| "Llama-3.1-8B": (0.9649 if model_args.device_name == "N150" else 0.965) if mode_accuracy else 0.954, | |
| "Llama-3.1-70B": 0.973, | |
| # BH Galaxy's corrected second decode step exercises position 1 and | |
| # prompt token 1; the former test repeated position/token 0 and its | |
| # 0.991 threshold was calibrated against that duplicated output. | |
| "Llama-3.2-1B": 0.999 if mode_accuracy else (0.989 if model_args.device_name == "BHGLX" else 0.991), | |
| "Llama-3.2-3B": 0.954 if mode_accuracy else 0.945, | |
| "Llama-3.2-11B": 0.952 if mode_accuracy else 0.940, | |
| "Llama-3.2-90B": 0.971, | |
| "Mistral-7B": 0.95 if mode_accuracy else 0.95, | |
| "Qwen3-32B": 0.88 if mode_accuracy else 0.86, | |
| }.get(model_name, 0.88 if mode_accuracy else 0.86) | |
| final_k_cache_pcc = { | |
| "Llama-3.1-8B": 0.9997, | |
| "Llama-3.1-70B": 0.9997, | |
| "Llama-3.2-1B": 0.9998, | |
| "Llama-3.2-3B": 0.9998, | |
| "Llama-3.2-11B": 0.9995, | |
| "Llama-3.2-90B": 0.9995, | |
| "Mistral-7B": 0.68, | |
| "Qwen3-32B": 0.9995, | |
| }.get(model_name, 0.9995) | |
| final_v_cache_pcc = { | |
| "Llama-3.1-8B": 0.9997, | |
| "Llama-3.1-70B": 0.9997, | |
| "Llama-3.2-1B": 0.9996, | |
| "Llama-3.2-3B": 0.9998, | |
| "Llama-3.2-11B": 0.9996, | |
| "Llama-3.2-90B": 0.9996, | |
| "Mistral-7B": 0.68, | |
| "Qwen3-32B": 0.9995, | |
| }.get(model_name, 0.9995) | |
| quick_iterations = { | |
| "Llama-3.1-8B": 6, | |
| "Llama-3.1-70B": 6, | |
| "Llama-3.2-1B": 2, | |
| "Llama-3.2-3B": 4, | |
| "Llama-3.2-11B": 6, | |
| "Llama-3.2-90B": 6, | |
| "Mistral-7B": 2, | |
| "Qwen3-32B": 6, | |
| }.get(model_name, 6) | |
| iterations = quick_iterations | |
| else: | |
| iterations = 9 | |
| if layers is not None: | |
| model_args.n_layers = layers | |
| state_dict = model_args.load_state_dict() | |
| state_dict_prefix = model_args.get_state_dict_prefix("", None) | |
| reference_state_dict = None | |
| if dummy_weights: | |
| reference_state_dict = { | |
| k[len(state_dict_prefix) :]: v | |
| for k, v in state_dict.items() | |
| if ( | |
| any([f"{state_dict_prefix}layers.{i}." in k for i in range(model_args.n_layers)]) | |
| or any( | |
| [ | |
| f"{state_dict_prefix}{name}" in k | |
| for name in [ | |
| "tok_embeddings.weight", | |
| "learnable_embedding.weight", | |
| "norm.weight", | |
| "output.weight", | |
| ] | |
| ] | |
| ) | |
| ) | |
| } | |
| prompts = ["This is a test"] * model_args.max_batch_size | |
| if dummy_weights: | |
| # "This is a test" encoded prompt | |
| if model_name == "Mistral-7B": | |
| encoded_prompts = [[1619, 1117, 1032, 2137]] * model_args.max_batch_size | |
| else: | |
| encoded_prompts = [[128000, 2028, 374, 264, 1296]] * model_args.max_batch_size | |
| assert not instruct, "Instruct prompt not implemented with dummy weights" | |
| else: | |
| tokenizer = model_args.tokenizer | |
| if instruct: | |
| encoded_prompts = [model_args.encode_prompt(prompt) for prompt in prompts] | |
| else: | |
| encoded_prompts = [model_args.encode_prompt(prompt, instruct=False) for prompt in prompts] | |
| reference_model = None | |
| if run_ref_pt: | |
| reference_model = model_args.reference_transformer(load_checkpoint=not dummy_weights) | |
| if dummy_weights: | |
| reference_model.load_state_dict(reference_state_dict) | |
| # Embedding on host | |
| embd = model_args.reference_embedding(reference_model) | |
| if model_args.is_llama_vision(): | |
| weight = torch.cat( | |
| [ | |
| state_dict[f"{state_dict_prefix}tok_embeddings.weight"], | |
| state_dict[f"{state_dict_prefix}learnable_embedding.weight"], | |
| ], | |
| dim=0, | |
| ) | |
| else: | |
| weight = state_dict[f"{state_dict_prefix}tok_embeddings.weight"] | |
| embd.load_state_dict({"emb.weight": weight}) | |
| generation_start_pos = 0 | |
| generation_length = iterations | |
| page_table_tt = None | |
| paged_attention_config = None | |
| # Prepare page table for paged attention | |
| if paged_attention: | |
| paged_attention_config = PagedAttentionConfig( | |
| block_size=page_params["page_block_size"], | |
| max_num_blocks=page_params["page_max_num_blocks"], | |
| ) | |
| # Implied shuffling of blocks | |
| permutation = torch.randperm(paged_attention_config.max_num_blocks) | |
| # Page table which maps virtual blocks to physical | |
| reverse_permutation = torch.argsort(permutation) | |
| page_table = reverse_permutation.reshape( | |
| model_args.max_batch_size, paged_attention_config.max_num_blocks // model_args.max_batch_size | |
| ) | |
| page_table_tt = ttnn.from_torch( | |
| page_table, | |
| device=mesh_device, | |
| dtype=ttnn.int32, | |
| layout=ttnn.ROW_MAJOR_LAYOUT, | |
| mesh_mapper=ttnn.ShardTensor2dMesh( | |
| mesh_device, | |
| dims=(None, -2) if batch_size > 1 else (None, None), | |
| mesh_shape=model_args.cluster_shape, | |
| ), | |
| ) | |
| # Load TTNN model | |
| tt_model = Transformer( | |
| args=model_args, | |
| mesh_device=mesh_device, | |
| dtype=dtype, | |
| state_dict=state_dict, | |
| weight_cache_path=model_args.weight_cache_path(dtype), | |
| paged_attention_config=paged_attention_config, | |
| prefetcher=prefetcher if use_prefetcher else None, | |
| ) | |
| if use_prefetcher: | |
| tt_model.prefetcher.prefetch() | |
| logger.info("Model and caches loaded.") | |
| if run_ref_pt: | |
| all_tests_pass = True | |
| final_tests_pass = True | |
| kv_cache_tests_pass = True | |
| seqlen = 1 # Generating one token per user at a time | |
| batch = model_args.max_batch_size | |
| # Select the first token from the prompts for initial decoding | |
| encoded_prompts_tensor = torch.tensor(encoded_prompts) # [:,0] | |
| pt_decode_input = embd(encoded_prompts_tensor[:, 0]).view(batch, seqlen, -1) | |
| tt_decode_input = pt_decode_input | |
| # Keep track of generated outputs to print out later | |
| all_outputs = [] | |
| if run_ref_pt: | |
| all_outputs_ref = [] | |
| # Initial positions | |
| current_pos = torch.tensor([generation_start_pos for _ in range(batch)]) | |
| current_pos_tensor = ttnn.from_torch( | |
| current_pos, | |
| device=mesh_device, | |
| dtype=ttnn.int32, | |
| mesh_mapper=ttnn.ShardTensor2dMesh( | |
| mesh_device, | |
| dims=(None, 0) if (model_args.is_galaxy and batch_size > 1) else (None, None), | |
| mesh_shape=model_args.cluster_shape, | |
| ), | |
| ) | |
| for i in range(generation_length): | |
| # Validate the absolute position before executing the model step. | |
| next_position, next_token_index, num_written = decode_step_state( | |
| generation_start_pos, i, len(encoded_prompts[0]), model_args.max_seq_len | |
| ) | |
| logger.info(f"[Model] Generating token {i}") | |
| decode_input = model_args.prepare_residual_tensor_decode( | |
| tt_decode_input, | |
| model_args.get_residual_mem_config(Mode.DECODE, prefetcher), | |
| ) | |
| # Get cos/sin matrices for the current position of each user | |
| rot_mats = tt_model.rope_setup.get_rot_mats(current_pos, prefetcher) | |
| # Models with alternating local/global attention (e.g. Gemma-2) run their | |
| # sliding-window layers on a separate local RoPE. Mirror the real decode | |
| # path (Transformer.ttnn_decode_forward); no-op for models without it. | |
| rot_mats_local = ( | |
| tt_model.rope_local_setup.get_rot_mats(current_pos, prefetcher) | |
| if hasattr(tt_model, "rope_local_setup") | |
| else None | |
| ) | |
| # Run TT model | |
| tt_out = tt_model( | |
| decode_input, | |
| current_pos_tensor, | |
| rot_mats_global=rot_mats, | |
| rot_mats_local=rot_mats_local, | |
| mode=Mode.DECODE, | |
| page_table=page_table_tt, | |
| ) | |
| # Convert ttnn tensor to torch tensor | |
| mesh_composer = ttnn.ConcatMesh2dToTensor( | |
| mesh_device, dims=(3, 1) if model_args.is_galaxy else (1, -1), mesh_shape=model_args.cluster_shape | |
| ) | |
| tt_output_torch = ( | |
| ttnn.to_torch(tt_out, mesh_composer=mesh_composer) | |
| .permute(2, 1, 0, 3) | |
| .squeeze(2)[: model_args.max_batch_size, 0:1, : model_args.vocab_size] | |
| ) | |
| ttnn.deallocate(tt_out) | |
| if run_ref_pt: # Run reference model | |
| # In this test all users have the same position | |
| ref_output = reference_model(pt_decode_input, current_pos[0]) | |
| # Increment position | |
| current_pos = torch.tensor([next_position for _ in range(batch)]) | |
| current_pos_tensor = ttnn.from_torch( | |
| current_pos, | |
| device=mesh_device, | |
| dtype=ttnn.int32, | |
| mesh_mapper=ttnn.ShardTensor2dMesh( | |
| mesh_device, | |
| dims=(None, 0) if (model_args.is_galaxy and batch_size > 1) else (None, None), | |
| mesh_shape=model_args.cluster_shape, | |
| ), | |
| ) | |
| # Append the generated token to the list of outputs | |
| if next_token_index is not None: | |
| # While in "prefill" mode, use the prompt tokens as the output | |
| all_outputs.append(encoded_prompts[0][next_token_index]) # Update list of TT outputs | |
| if run_ref_pt: | |
| all_outputs_ref.append(encoded_prompts[0][next_token_index]) # Update list of ref outputs | |
| tt_decode_input = embd(encoded_prompts_tensor[:, next_token_index]).view(batch, seqlen, -1) | |
| if run_ref_pt: | |
| pt_decode_input = embd(encoded_prompts_tensor[:, next_token_index]).view(batch, seqlen, -1) | |
| else: | |
| # Greedy decode (temperature = 0) the generated token and save it to print out later | |
| reference_token = None | |
| device_token = None | |
| if run_ref_pt: | |
| # Sample from reference model first | |
| _, reference_token = sample_host(ref_output, temperature=0, top_p=0.8) | |
| else: | |
| # If not running reference model, sample from TT model directly | |
| _, device_token = sample_host(tt_output_torch, temperature=0, top_p=0.8) | |
| next_token = teacher_forced_decode_token( | |
| reference_token=reference_token, | |
| device_token=device_token, | |
| ) | |
| next_token_id = next_token.squeeze(1).tolist()[0] | |
| tt_decode_input = embd(next_token) | |
| all_outputs.append(next_token_id) | |
| if run_ref_pt: | |
| pt_decode_input = tt_decode_input | |
| all_outputs_ref.append(next_token_id) | |
| # Measure PCC if also running reference model | |
| if run_ref_pt: | |
| if layers == 1 and i == iterations - 1: # On last iteration in the quick test, set a tighter PCC | |
| passing, pcc_message = comp_pcc(ref_output, tt_output_torch, final_model_pcc) | |
| if not passing: | |
| final_tests_pass = False | |
| else: | |
| passing, pcc_message = comp_pcc(ref_output, tt_output_torch, pcc) | |
| logger.info(comp_allclose(ref_output, tt_output_torch)) | |
| logger.info(f"PCC: {pcc_message}") | |
| if passing: | |
| logger.info("Model Passed!") | |
| else: | |
| logger.warning("Model Failed!") | |
| if not passing: | |
| all_tests_pass = False | |
| # Compare KV caches | |
| if cache_pcc: | |
| for l in range(model_args.n_layers): | |
| pytorch_layer_present = [ | |
| reference_model.cache_k[l].clone().permute(0, 2, 1, 3), # [batch, n_kv_heads, seq, head_dim] | |
| reference_model.cache_v[l].clone().permute(0, 2, 1, 3), # [batch, n_kv_heads, seq, head_dim] | |
| ] | |
| tt_layer_present = [] | |
| if paged_attention: | |
| for layer_past in tt_model.layers[l].attention.layer_past: | |
| tt_layer_present.append( | |
| ttnn.to_torch( | |
| layer_past, | |
| mesh_composer=ttnn.ConcatMesh2dToTensor( | |
| mesh_device, | |
| dims=(1, 3) if model_args.is_galaxy else (0, 1), | |
| mesh_shape=model_args.cluster_shape, | |
| ), | |
| )[reverse_permutation][:, : model_args.n_kv_heads, :, : model_args.head_dim] | |
| .reshape( | |
| model_args.max_batch_size, | |
| paged_attention_config.max_num_blocks // model_args.max_batch_size, | |
| model_args.n_kv_heads, | |
| paged_attention_config.block_size, | |
| model_args.head_dim, | |
| ) | |
| .transpose(1, 2) | |
| .reshape(model_args.max_batch_size, model_args.n_kv_heads, -1, model_args.head_dim)[ | |
| :batch, ... | |
| ] | |
| ) | |
| else: | |
| for layer_past in tt_model.layers[l].attention.layer_past: | |
| tt_layer_present.append( | |
| ttnn.to_torch( | |
| layer_past, | |
| mesh_composer=ttnn.ConcatMesh2dToTensor( | |
| mesh_device, | |
| dims=(1, 0) if model_args.is_galaxy else (0, 1), | |
| mesh_shape=model_args.cluster_shape, | |
| ), | |
| )[:batch, :, :, :] | |
| ) | |
| for kv_cache, (cache_pt, cache_tt) in enumerate(zip(pytorch_layer_present, tt_layer_present)): | |
| cache_pt = cache_pt[:, :, 0:num_written, :] | |
| cache_tt = cache_tt[:, :, generation_start_pos : generation_start_pos + num_written, :] | |
| if ( | |
| layers == 1 and i == iterations - 1 | |
| ): # On last iteration in the quick test, set a tighter PCC | |
| if kv_cache == 0: # K cache | |
| does_pass, output_pcc = comp_pcc(cache_pt, cache_tt, final_k_cache_pcc) | |
| else: # V cache | |
| does_pass, output_pcc = comp_pcc(cache_pt, cache_tt, final_v_cache_pcc) | |
| else: | |
| does_pass, output_pcc = comp_pcc(cache_pt, cache_tt, pcc) | |
| if kv_cache == 0: | |
| logger.info(f"K cache output: {output_pcc}") | |
| else: | |
| logger.info(f"V cache output: {output_pcc}") | |
| if does_pass: | |
| logger.info(f"KV Cache Passed!") | |
| else: | |
| logger.warning(f"KV Cache Failed! PCC value is lower than {pcc}") | |
| kv_cache_tests_pass = False | |
| all_tests_pass = False | |
| if not dummy_weights: | |
| logger.info("[ttnn generation User 0] " + tokenizer.decode(all_outputs).replace("\n", "\\n")) | |
| if run_ref_pt: | |
| logger.info("[Ref generation User 0] " + tokenizer.decode(all_outputs_ref).replace("\n", "\\n")) | |
| if run_ref_pt: | |
| if all_tests_pass: | |
| logger.info(f"All {generation_length} decode iterations Passed!") | |
| else: | |
| logger.warning("One or more iterations of decode had bad PCC") | |
| if layers == 1: | |
| assert ( | |
| final_tests_pass | |
| ), f"PCC value {pcc_message} is lower than {final_model_pcc} for final output. Check Warnings!" | |
| assert kv_cache_tests_pass, f"KV Cache PCC value is lower expected for some of the outputs. Check Warnings!" | |
| assert ( | |
| all_tests_pass | |
| ), f"PCC value {pcc_message} is lower than {pcc} for some of the outputs. Check Warnings!" | |