|
| 1 | +import json |
| 2 | +from aiu_fms_testing_utils.testing.validation import ( |
| 3 | + LogitsExtractorHook, |
| 4 | + extract_validation_information, |
| 5 | +) |
| 6 | +from fms.models import get_model |
| 7 | +from transformers import AutoTokenizer |
| 8 | +# from concurrent.futures import ThreadPoolExecutor |
| 9 | +# Ideally we want this script to fetch data in parallel |
| 10 | +# But it's proving harder than initially thought |
| 11 | +# Making it work for now, making it fast is second step |
| 12 | + |
| 13 | +import argparse |
| 14 | +import torch |
| 15 | + |
| 16 | + |
| 17 | +def load_jsonl(path): |
| 18 | + """ |
| 19 | + Loads a JSONL file. |
| 20 | + - If field is None: returns a list of dicts (one per line). |
| 21 | + - If field is a string: returns a list of obj[field] (only non-None values). |
| 22 | + """ |
| 23 | + data = [] |
| 24 | + with open(path, "r", encoding="utf-8") as f: |
| 25 | + for idx, line in enumerate(f): |
| 26 | + line = line.strip() |
| 27 | + if not line: |
| 28 | + continue |
| 29 | + try: |
| 30 | + obj = json.loads(line) |
| 31 | + except (ValueError, json.JSONDecodeError) as e: |
| 32 | + print(f"Failed to parse line {idx} in {path}: {e}") |
| 33 | + data.append(obj) |
| 34 | + return data |
| 35 | + |
| 36 | + |
| 37 | +parser = argparse.ArgumentParser( |
| 38 | + description="Script which will save CPU validation data" |
| 39 | +) |
| 40 | +parser.add_argument( |
| 41 | + "--attention_type", |
| 42 | + type=str, |
| 43 | + default="paged", |
| 44 | + choices=["paged", "paged_fp8"], |
| 45 | + help="The attention type to use", |
| 46 | +) |
| 47 | +parser.add_argument( |
| 48 | + "--model_variant", |
| 49 | + type=str, |
| 50 | + default="ibm-ai-platform/micro-g3.3-8b-instruct-1b", |
| 51 | + help="The model id or path to use for this test. Note: must be a huggingface format", |
| 52 | +) |
| 53 | +parser.add_argument( |
| 54 | + "--max_new_tokens", |
| 55 | + type=int, |
| 56 | + default=8, |
| 57 | + help="set this if you want to change the number of tokens generated per sequence (1 prefill + max_new_tokens-1 decodes). Note: If this value is larger than 64, this may result in switching decode programs mid generation", |
| 58 | +) |
| 59 | +parser.add_argument( |
| 60 | + "--max_workers", |
| 61 | + type=int, |
| 62 | + default=8, |
| 63 | + help="max workers to run in parallel", |
| 64 | +) |
| 65 | +parser.add_argument( |
| 66 | + "--dataset_path", |
| 67 | + type=str, |
| 68 | + help="path to dataset", |
| 69 | +) |
| 70 | +args = parser.parse_args() |
| 71 | +max_new_tokens = args.max_new_tokens |
| 72 | +is_fp8 = "fp8" in args.attention_type |
| 73 | +model_variant = args.model_variant |
| 74 | +tokenizer = AutoTokenizer.from_pretrained(model_variant) |
| 75 | +model_path_kwargs = {"variant": model_variant} |
| 76 | +validation_model = get_model( |
| 77 | + architecture="hf_pretrained", |
| 78 | + device_type="cpu", |
| 79 | + data_type=None if is_fp8 else torch.bfloat16, |
| 80 | + fused_weights=False, |
| 81 | + **model_path_kwargs, |
| 82 | +) |
| 83 | + |
| 84 | +# get the input ids for the validation |
| 85 | +dataset = load_jsonl(args.dataset_path) |
| 86 | + |
| 87 | + |
| 88 | +def process_row(row): |
| 89 | + id = row["id"] |
| 90 | + prompt_text = row["prompt"] |
| 91 | + input_ids = tokenizer.encode(prompt_text) |
| 92 | + print("fetching cpu validation info for id: ", id) |
| 93 | + with torch.no_grad(): |
| 94 | + cpu_validation_info = extract_validation_information( |
| 95 | + validation_model, |
| 96 | + torch.tensor(input_ids).unsqueeze(0), |
| 97 | + max_new_tokens, |
| 98 | + LogitsExtractorHook(), |
| 99 | + attn_algorithm="math", |
| 100 | + ) |
| 101 | + return {"id": id, "input_ids": input_ids, "validation": cpu_validation_info} |
| 102 | + |
| 103 | + |
| 104 | +# See comment above |
| 105 | +# with ThreadPoolExecutor(max_workers=args.max_workers) as executor: |
| 106 | +# results = list(executor.map(process_row, dataset)) |
| 107 | + |
| 108 | +# save the results |
| 109 | +validation_info = {} |
| 110 | +for row in dataset: |
| 111 | + result = process_row(row) |
| 112 | + # for result in results: |
| 113 | + tokens = result["validation"].get_info("tokens") |
| 114 | + generated_tokens_tensor = tokens[0][-max_new_tokens:] |
| 115 | + generated_tokens = [token.item() for token in generated_tokens_tensor] |
| 116 | + logits = result["validation"].get_info("logits") |
| 117 | + top_logprobs = [] |
| 118 | + for step_num, logits_for_step in enumerate(logits[0]): |
| 119 | + logprob_for_step = torch.nn.functional.log_softmax(logits_for_step, dim=-1) |
| 120 | + values, indices = torch.topk(logprob_for_step, k=100) |
| 121 | + # in case we want to save a new tensor? |
| 122 | + # but this will also take memory |
| 123 | + # top_logprobs = torch.full_like(logprobs, float('-inf')) |
| 124 | + # top_logprobs.scatter_(1, indices, values) |
| 125 | + top_logprob_dict = { |
| 126 | + int(idx): float(val) for idx, val in zip(indices[0], values[0]) |
| 127 | + } |
| 128 | + top_logprobs.append(top_logprob_dict) |
| 129 | + validation_info[result["id"]] = { |
| 130 | + "logprobs": top_logprobs, |
| 131 | + "tokens": generated_tokens, |
| 132 | + "text": tokenizer.decode(generated_tokens), |
| 133 | + } |
| 134 | + with open(f"{result['id']}_cpu_validation_info.json", "w") as f: |
| 135 | + json.dump(validation_info, f, indent=4) |
| 136 | + print(f"Done for {result['id']}") |
| 137 | + |
| 138 | + |
| 139 | +# save the final result |
| 140 | +with open("cpu_validation_info.json", "w") as f: |
| 141 | + json.dump(validation_info, f, indent=4) |
| 142 | +print("all done!") |
0 commit comments