import time import os import re import json import random from contextlib import nullcontext import numpy as np from tokenizers import Tokenizer import torch from modules.semantic import GPTConfig, GPT from torch.profiler import profile, record_function, ProfilerActivity activities = [ProfilerActivity.CPU] if torch.cuda.is_available(): activities.append(ProfilerActivity.CUDA) data_dir = None do_full_profile = True eval_steps = 50 dataset_names = "youtube,foreign,genius,podcasts" filename_pattern = "semantic_{dataset}_{set}.bin" filename_pattern_meta = "semantic_{dataset}_meta_{set}.json" filename_tokenizer = "bert_tokenizer.json" text_codebook_size = 119_547 text_vocab_size = 119_552 assert text_vocab_size - text_codebook_size >= 3 # to accommodate pad tokens etc text_pad_token = text_codebook_size semantic_codebook_size = 10_000 semantic_vocab_size = 10_048 n_semantic_codebooks = 8 assert semantic_vocab_size - semantic_codebook_size >= 3 # to accommodate pad tokens etc semantic_pad_token = semantic_codebook_size infer_token = semantic_codebook_size + 1 custom_seed_offset = 0 gradient_accumulation_steps = 1 # used to simulate larger batch sizes batch_size = 12 # if gradient_accumulation_steps > 1, this is the micro-batch size block_size = 4096 # model n_layer = 12 n_head = 12 n_embd = 768 dropout = 0.0 # for pretraining 0 is good, for finetuning try 0.1+ bias = False # do we use bias inside LayerNorm and Linear layers? # adamw optimizer learning_rate = 1e-3 # max learning rate max_iters = 100000 # total number of training iterations weight_decay = 1e-1 beta1 = 0.9 beta2 = 0.95 grad_clip = 1.0 # clip gradients at this value, or disable if == 0.0 # system device = "cuda" # examples: "cpu", "cuda", "cuda:0", "cuda:1" etc., or try "mps" on macbooks dtype = "bfloat16" # "float32", "bfloat16", or "float16" (implements a GradScaler) compile = True # use PyTorch 2.0 to compile the model to be faster # ----------------------------------------------------------------------------- config_keys = [ k for k, v in globals().items() if not k.startswith("_") and isinstance(v, (int, float, bool, str)) ] exec(open("configurator.py").read()) # overrides from command line or config file config = {k: globals()[k] for k in config_keys} # will be useful for logging # ----------------------------------------------------------------------------- seed_offset = 0 seed_offset += custom_seed_offset torch.manual_seed(1337 + seed_offset) random.seed(6006 + seed_offset) torch.backends.cuda.matmul.allow_tf32 = True # allow tf32 on matmul torch.backends.cudnn.allow_tf32 = True # allow tf32 on cudnn device_type = "cuda" if "cuda" in device else "cpu" # for later use in torch.autocast # note: float16 data type will automatically use a GradScaler ptdtype = { "float32": torch.float32, "bfloat16": torch.bfloat16, "float16": torch.float16, }[dtype] ctx = ( nullcontext() if device_type == "cpu" else torch.amp.autocast(device_type=device_type, dtype=ptdtype) ) # load data print("loading data...") dataset_names = [s.strip() for s in dataset_names.split(",")] val_data = [] val_data_metas = [] val_data_weights = [] for name in dataset_names: fn = filename_pattern.replace("{dataset}", name).replace("{set}", "val") mm = np.memmap(os.path.join(data_dir, fn), dtype=np.uint16, mode="r") assert mm[: 10 * block_size].max() <= semantic_codebook_size mm = mm.reshape(-1, 8, block_size) val_data.append(mm) val_data_weights.append(len(mm)) fn_metas = filename_pattern_meta.replace("{dataset}", name).replace("{set}", "val") with open(os.path.join(data_dir, fn_metas)) as f: metas = json.load(f) val_data_metas.append(metas) assert len(mm) == len(metas) tokenizer = Tokenizer.from_file(os.path.join(data_dir, filename_tokenizer)) def tokenize(text): return tokenizer.encode(text, add_special_tokens=False).ids def get_sample(dataset_idx=None, n_text_context=256, use_fake_data=False): if use_fake_data: x_text_arr = np.zeros(256).astype(np.int64) + 119_547 y_semantic_arr = np.zeros((8, 4096 - 256)).astype(np.int64) + 10_000 x_semantic_arr = y_semantic_arr.copy() x_semantic_arr[:, 0] = 10_001 return x_text_arr, x_semantic_arr, y_semantic_arr if dataset_idx is None: dataset_idx = random.choices(list(range(len(val_data))), weights=val_data_weights, k=1)[0] data = val_data[dataset_idx] data_metas = val_data_metas[dataset_idx] idx = random.randint(0, len(data) - 1) # build semantic y_semantic_arr_list = [] for n in range(n_semantic_codebooks): arr = np.pad( data[idx][n, : block_size - n_text_context - n], (n, 0), constant_values=semantic_pad_token, mode="constant", ).astype(np.uint16) y_semantic_arr_list.append(arr) y_semantic_arr = np.vstack(y_semantic_arr_list) x_semantic_arr = np.hstack( [np.array([[infer_token]] * n_semantic_codebooks), y_semantic_arr[:, :-1]] ) assert x_semantic_arr.shape == (n_semantic_codebooks, block_size - n_text_context) assert y_semantic_arr.shape == (n_semantic_codebooks, block_size - n_text_context) # build text meta = data_metas[idx] x_text_arr = np.array([text_pad_token] * n_text_context) # metas can be text, score, tags, language # and also dataset p_cond = 0.3 pretext_segments = [] if random.random() < p_cond: pretext_segments.append(f"dataset:{dataset_names[dataset_idx].lower()}") if "language" in meta and random.random() < p_cond: pretext_segments.append(f"language:{meta['language'].lower()}") if "score" in meta and random.random() < p_cond: pretext_segments.append(f"score:{meta['score']}") if "tags" in meta and len(meta["tags"]) > 0 and random.random() < p_cond: tags = meta["tags"] if len(tags) > 0 and len(tags[0]) == 1: # bugfix for ["p", "o", "p"] tags = "".join(tags).split(",") random.shuffle(tags) tags = tags[: random.randint(1, len(tags))] tag_str = ",".join(tags) pretext_segments.append(f"tags:{tag_str.lower()}") text = "" if len(pretext_segments) > 0: text = "{" + ";".join(pretext_segments) + "}" if "text" in meta and random.random() < p_cond: if random.random() < 0.75: # remove newlines completely (eg lyrics) tts_text = re.sub(r"\s+", " ", meta["text"]) else: tts_text = meta["text"] text += " " + tts_text.strip() text = text.strip() text_tokens = tokenize(text) x_text_arr[: len(text_tokens)] = text_tokens[: len(x_text_arr)] assert x_text_arr.shape == (n_text_context,) return x_text_arr, x_semantic_arr, y_semantic_arr def get_batch(dataset_idx=None): # TODO: make this adjustable n_text_context = 256 x_text_list = [] x_semantic_list = [] y_list = [] for _ in range(batch_size): x_text, x_semantic, y = get_sample(dataset_idx=dataset_idx, n_text_context=n_text_context) x_text_list.append(torch.from_numpy(x_text.astype(np.int64))) x_semantic_list.append(torch.from_numpy(x_semantic.astype(np.int64))) y_list.append(torch.from_numpy(y.astype(np.int64))) x_text = torch.stack(x_text_list) x_semantic = torch.stack(x_semantic_list) y = torch.stack(y_list) if device_type == "cuda": # pin arrays x,y, which allows us to move them to GPU asynchronously (non_blocking=True) x_text, x_semantic, y = ( x_text.pin_memory().to(device), x_semantic.pin_memory().to(device), y.pin_memory().to(device), ) else: x_text, x_semantic, y = x_text.to(device), x_semantic.to(device), y.to(device) del x_text_list, x_semantic_list, y_list return x_text, x_semantic, y # init these up here, can override if init_from="resume" (i.e. from a checkpoint) iter_num = 0 # model init model_args = dict( n_layer=n_layer, n_head=n_head, n_embd=n_embd, block_size=block_size, bias=bias, dropout=dropout, text_vocab_size=text_vocab_size, semantic_vocab_size=semantic_vocab_size, n_semantic_codebooks=n_semantic_codebooks, ) # init a new model from scratch print("Initializing a new model from scratch") gptconf = GPTConfig(**model_args) model = GPT(gptconf) model.to(device) # initialize a GradScaler. If enabled=False scaler is a no-op scaler = torch.cuda.amp.GradScaler(enabled=(dtype == "float16")) # optimizer optimizer = model.configure_optimizers(weight_decay, learning_rate, (beta1, beta2), device_type) # compile the model if compile: print("compiling the model... (takes a ~minute)") model = torch.compile(model) X_text, X_semantic, Y = get_batch() print("Warmup...") model.train() for n in range(50): for nn in range(gradient_accumulation_steps): with ctx: loss = model(X_text, X_semantic, targets=Y) loss = loss / gradient_accumulation_steps scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() optimizer.zero_grad(set_to_none=True) torch.cuda.synchronize() print("Checking dataload overhead...") model.train() t0 = time.time() for n in range(eval_steps): for nn in range(gradient_accumulation_steps): with ctx: loss = model(X_text, X_semantic, targets=Y) loss = loss / gradient_accumulation_steps X_text, X_semantic, Y = get_batch() scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() optimizer.zero_grad(set_to_none=True) torch.cuda.synchronize() print(round(time.time() - t0, 1), "seconds with dataload") t0 = time.time() for n in range(eval_steps): for nn in range(gradient_accumulation_steps): with ctx: loss = model(X_text, X_semantic, targets=Y) loss = loss / gradient_accumulation_steps scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() optimizer.zero_grad(set_to_none=True) torch.cuda.synchronize() print(round(time.time() - t0, 1), "seconds without dataload") if do_full_profile: print("Profiling Infer...") model.eval() with profile(activities=activities, record_shapes=False) as prof_infer: with record_function(" Infer"): for n in range(eval_steps): for nn in range(gradient_accumulation_steps): with ctx, torch.no_grad(): _ = model(X_text, X_semantic) torch.cuda.synchronize() print(prof_infer.key_averages().table(sort_by="cuda_time_total", row_limit=10)) print("Profiling Train...") model.train() with profile(activities=activities, record_shapes=False) as prof_train: with record_function(" Train"): for n in range(eval_steps): for nn in range(gradient_accumulation_steps): with ctx: loss = model(X_text, X_semantic, targets=Y) loss = loss / gradient_accumulation_steps scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() optimizer.zero_grad(set_to_none=True) torch.cuda.synchronize() print(prof_train.key_averages().table(sort_by="cuda_time_total", row_limit=10)) with open("out.txt", "w") as f: f.write(prof_infer.key_averages().table(sort_by="cuda_time_total")) f.write("\n" * 5) f.write(prof_train.key_averages().table(sort_by="cuda_time_total"))