JavRedstone/pope-repro-artifacts / code /freq_analysis.ipynb
JavRedstone's picture
download
raw
15.8 kB
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"Model Analysis: This notebook loads a pretrained model checkpoint, runs inference on new text and records activations (queries and keys) for plotting."
]
},
{
"cell_type": "markdown",
"id": "1",
"metadata": {},
"source": [
"1. Imports"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import re\n",
"import pickle\n",
"from contextlib import nullcontext\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import torch\n",
"import tiktoken\n",
"from model import GPTConfig, GPT, CausalSelfAttention"
]
},
{
"cell_type": "markdown",
"id": "3",
"metadata": {},
"source": [
"2. Config and pytorch setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"init_from = 'resume' # either 'resume' (from an out_dir) or a gpt2 variant (e.g. 'gpt2-xl')\n",
"base_dir = '/data/'\n",
"out_dir = 'final-owt-ckpts/' # ignored if init_from is not 'resume'\n",
"ckpt_fname = 'gpt2-774M-rope-ckpt.pt' # ignored if init_from is not 'resume'\n",
"start = \"FILE:data/shakespeare_sonnet/input.txt\" # \"\\n\" or \"<|endoftext|>\" or etc. Can also specify a file, use as: \"FILE:prompt.txt\"\n",
"# model\n",
"n_layer = 36\n",
"n_head = 20\n",
"n_embd = 1280\n",
"dropout = 0.0 # for pretraining 0 is good, for finetuning try 0.1+\n",
"norm_type = 'rmsnorm' # 'layernorm' or 'rmsnorm'\n",
"pos_type = 'rope' # 'sinusoidal', 'rope', 'nope', 'popev1', 'popev1b', 'popev1bg', 'popev2', 'alibi'\n",
"base_freq = 10000 # base frequency for rotary positional encoding\n",
"rotate_fraction = 1.0\n",
"thetab_init = 'zero'\n",
"bias = False # do we use bias inside LayerNorm and Linear layers?\n",
"complex_flash = True\n",
"# sampling\n",
"num_samples = 1 # number of samples to draw\n",
"max_new_tokens = 500 # number of tokens generated in each sample\n",
"temperature = 0.8 # 1.0 = no change, < 1.0 = less random, > 1.0 = more random, in predictions\n",
"top_k = 200 # retain only the top_k most likely tokens, clamp others to have 0 probability\n",
"seed = 1337\n",
"device = 'cuda' # examples: 'cpu', 'cuda', 'cuda:0', 'cuda:1', etc.\n",
"dtype = 'bfloat16' if torch.cuda.is_available() and torch.cuda.is_bf16_supported() else 'float16' # 'float32' or 'bfloat16' or 'float16'\n",
"compile = False # use PyTorch 2.0 to compile the model to be faster\n",
"# exec(open('configurator.py').read()) # overrides from command line or config file\n",
"\n",
"np.random.seed(seed)\n",
"torch.manual_seed(seed)\n",
"torch.cuda.manual_seed(seed)\n",
"torch.backends.cuda.matmul.allow_tf32 = True # allow tf32 on matmul\n",
"torch.backends.cudnn.allow_tf32 = True # allow tf32 on cudnn\n",
"device_type = 'cuda' if 'cuda' in device else 'cpu' # for later use in torch.autocast\n",
"ptdtype = {'float32': torch.float32, 'bfloat16': torch.bfloat16, 'float16': torch.float16}[dtype]\n",
"ctx = nullcontext() if device_type == 'cpu' else torch.amp.autocast(device_type=device_type, dtype=ptdtype)"
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"3. Load model, enable qk storage and load tokenizer"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6",
"metadata": {},
"outputs": [],
"source": [
"# model init\n",
"if init_from == 'resume':\n",
" # init from a model saved in a specific directory\n",
" ckpt_path = os.path.join(os.path.join(base_dir, out_dir, ckpt_fname))\n",
" checkpoint = torch.load(ckpt_path, map_location=device)\n",
" gptconf = GPTConfig(**checkpoint['model_args'])\n",
" model = GPT(gptconf)\n",
" state_dict = checkpoint['model']\n",
" unwanted_prefix = '_orig_mod.'\n",
" for k,v in list(state_dict.items()):\n",
" if k.startswith(unwanted_prefix):\n",
" state_dict[k[len(unwanted_prefix):]] = state_dict.pop(k)\n",
" model.load_state_dict(state_dict, strict=False)\n",
"elif init_from.startswith('gpt2'):\n",
" # init from a given GPT-2 model\n",
" model = GPT.from_pretrained(init_from, dict(dropout=0.0))\n",
"\n",
"model.eval()\n",
"model.to(device)\n",
"if compile:\n",
" model = torch.compile(model) # requires PyTorch 2.0 (optional)\n",
"\n",
"# enable flags to store qk vectors in all attn layers\n",
"for module in model.transformer.modules():\n",
" if isinstance(module, CausalSelfAttention):\n",
" module.enable_qk_storage()\n",
" # module.enable_attn_scores_storage()\n",
"\n",
"# look for the meta pickle in case it is available in the dataset folder\n",
"load_meta = False\n",
"if init_from == 'resume' and 'config' in checkpoint and 'dataset' in checkpoint['config']: # older checkpoints might not have these...\n",
" meta_path = os.path.join('data', checkpoint['config']['dataset'], 'meta.pkl')\n",
" load_meta = os.path.exists(meta_path)\n",
"if load_meta:\n",
" print(f\"Loading meta from {meta_path}...\")\n",
" with open(meta_path, 'rb') as f:\n",
" meta = pickle.load(f)\n",
" # TODO want to make this more general to arbitrary encoder/decoder schemes\n",
" stoi, itos = meta['stoi'], meta['itos']\n",
" encode = lambda s: [stoi[c] for c in s]\n",
" decode = lambda l: ''.join([itos[i] for i in l])\n",
"else:\n",
" # ok let's assume gpt-2 encodings by default\n",
" print(\"No meta.pkl found, assuming GPT-2 encodings...\")\n",
" enc = tiktoken.get_encoding(\"gpt2\")\n",
" encode = lambda s: enc.encode(s, allowed_special={\"<|endoftext|>\"})\n",
" decode = lambda l: enc.decode(l)"
]
},
{
"cell_type": "markdown",
"id": "7",
"metadata": {},
"source": [
"4. Preprocess prompt"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8",
"metadata": {},
"outputs": [],
"source": [
"# encode the beginning of the prompt\n",
"if start.startswith('FILE:'):\n",
" with open(start[5:], 'r', encoding='utf-8') as f:\n",
" start = f.read()\n",
" # This pattern looks for a Roman numeral at the beginning of a line\n",
" # The (?m) flag enables multi-line mode, so '^' matches the start of each line\n",
" pattern = re.compile(r'(?m)(?=^[IVXLCDM]+\\n)')\n",
" # filter(None, ...) removes any empty strings from the result\n",
" sonnets = list(filter(None, pattern.split(start)))\n",
"\n",
"start_ids = [encode(sonnet) for sonnet in sonnets]\n",
"start_ids = [torch.tensor(s, dtype=torch.long, device=device)[None, ...] for s in start_ids]"
]
},
{
"cell_type": "markdown",
"id": "9",
"metadata": {},
"source": [
"5. Run inference and record activations"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10",
"metadata": {},
"outputs": [],
"source": [
"qk_store = {'queries': [], 'keys': []}\n",
"avg_over_n = 10 # avg q/k over this many samples\n",
"# sampled_start_ids = random.sample(start_ids, avg_over_n)\n",
"# run generation\n",
"with torch.no_grad():\n",
" with ctx:\n",
" for x in start_ids[10:10+avg_over_n]:\n",
" y = model(x)\n",
" # layer-wise queries and keys\n",
" layer_qk_store = {'queries': [], 'keys': []}\n",
" for module in model.transformer.modules():\n",
" if isinstance(module, CausalSelfAttention):\n",
" # query [1, nh, T, hs]\n",
" queries, keys = module.get_stored_qk()\n",
" # get 2-dim chunks for RoPE\n",
" if pos_type == 'rope':\n",
" bs, nh, T, hs = queries.shape # q, k shape: [bs, nh, T, hs]\n",
" queries = queries.reshape(bs, nh, T, hs // 2, 2)\n",
" keys = keys.reshape(bs, nh, T, hs // 2, 2)\n",
" # compute L2 norm\n",
" queries = queries.norm(dim=-1, p=2)\n",
" keys = keys.norm(dim=-1, p=2)\n",
" elif pos_type in ['popev1', 'popev1b']:\n",
" assert queries.is_complex() and keys.is_complex()\n",
" queries = queries.abs()\n",
" keys = keys.abs()\n",
" # avg over sequence dimension\n",
" queries = queries.mean(dim=-2, keepdim=False)\n",
" keys = keys.mean(dim=-2, keepdim=False)\n",
" layer_qk_store['queries'].append(queries)\n",
" layer_qk_store['keys'].append(keys)\n",
" # queries across all layers [1, nl, nh, hs]\n",
" qk_store['queries'].append(torch.stack(layer_qk_store['queries'], dim=0))\n",
" qk_store['keys'].append(torch.stack(layer_qk_store['keys'], dim=0))\n",
"# average over all samples: queries [B, nl, 1, nh, hs] -> [nl, nh, hs]\n",
"qk_store['queries'] = torch.stack(qk_store['queries'], dim=0).squeeze().mean(dim=0, keepdim=False)\n",
"qk_store['keys'] = torch.stack(qk_store['keys'], dim=0).squeeze().mean(dim=0, keepdim=False)"
]
},
{
"cell_type": "markdown",
"id": "11",
"metadata": {},
"source": [
"6. Plot frequency usage across all attn layers"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "12",
"metadata": {},
"outputs": [],
"source": [
"def plot_freqs_of_layers(ax, data, title):\n",
" # plot \n",
" im = ax.imshow(\n",
" data.to(torch.float16).cpu().numpy().transpose(), \n",
" aspect='auto', \n",
" origin='lower', \n",
" cmap='viridis', \n",
" interpolation='nearest'\n",
" )\n",
" # set title\n",
" ax.set_title(title, fontsize=16, pad=10)\n",
"\n",
" # configure x-axis (Layers)\n",
" ax.set_xlabel(\"Layer\", fontsize=14, labelpad=10)\n",
" x_ticks = np.arange(0, n_layer)\n",
" x_tick_labels = [str(i + 1) for i in x_ticks]\n",
" ax.set_xticks(x_ticks)\n",
" ax.set_xticklabels(x_tick_labels, fontsize=12)\n",
"\n",
" # configure y-axis (Frequencies)\n",
" ax.set_yticks([]) # Remove default numeric y-ticks\n",
" # x-coordinate set to -2.8 to place the text to the left of the heatmap.\n",
" y_axis_size = n_embd // (n_head * 2) if pos_type == 'rope' else n_embd // n_head\n",
" ax.text(-1, y_axis_size * 0.25, \"Low Frequencies\",\n",
" ha='center', va='center', rotation=90, fontsize=14)\n",
" ax.text(-1, y_axis_size * 0.75, \"High Frequencies\",\n",
" ha='center', va='center', rotation=90, fontsize=14)\n",
"\n",
" # configure color bar\n",
" cbar = fig.colorbar(im, ax=ax, pad=0.02)\n",
" cbar.set_label(\"Mean norm\", fontsize=14, labelpad=5)\n",
" cbar.ax.tick_params(labelsize=12)\n",
" return im\n",
"\n",
"# queries [nl, nh, hs]\n",
"mean_q_per_layer = qk_store['queries'].mean(dim=1)\n",
"mean_k_per_layer = qk_store['keys'].mean(dim=1)\n",
"# init plot\n",
"plt.style.use('seaborn-v0_8-white')\n",
"plt.rcParams['font.family'] = 'serif'\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 7), sharey=True)\n",
"im1 = plot_freqs_of_layers(ax1, mean_q_per_layer, f\"Query frequency usage per layer\")\n",
"im2 = plot_freqs_of_layers(ax2, mean_k_per_layer, f\"Key frequency usage per layer\")\n",
"\n",
"fig.tight_layout()\n",
"# plt.show()\n",
"plt.savefig('final-qk-viz/all_layers/774M-rope-1.png', dpi=300)"
]
},
{
"cell_type": "markdown",
"id": "13",
"metadata": {},
"source": [
"7. Plot frequency usage of specific attn layer"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "14",
"metadata": {},
"outputs": [],
"source": [
"def plot_freqs_of_heads(ax, data, title):\n",
" data_to_plot = data.to(torch.float16).cpu().numpy().transpose()\n",
" # create heatmap\n",
" im = ax.imshow(\n",
" data_to_plot,\n",
" aspect='auto',\n",
" origin='lower',\n",
" cmap='viridis',\n",
" interpolation='nearest'\n",
" )\n",
"\n",
" # customize axes, labels and ticks\n",
" ax.set_title(title, fontsize=16, pad=10)\n",
"\n",
" # configure x-axis (Attention Heads)\n",
" ax.set_xlabel(\"Attention Head\", fontsize=14, labelpad=10)\n",
" # Set x-ticks to correspond to head numbers (1 to 16)\n",
" x_ticks = np.arange(0, n_head)\n",
" x_tick_labels = [str(i + 1) for i in x_ticks]\n",
" ax.set_xticks(x_ticks)\n",
" ax.set_xticklabels(x_tick_labels, fontsize=12)\n",
"\n",
" # configure y-axis (Frequencies)\n",
" ax.set_yticks([]) # Remove default numeric y-ticks\n",
" # Use ax.text to place custom labels for frequency ranges\n",
" y_axis_size = n_embd // 2 if pos_type == 'rope' else n_embd\n",
" ax.text(-1, y_axis_size // n_head * 0.25, \"Low Frequencies\",\n",
" ha='center', va='center', rotation=90, fontsize=14)\n",
" ax.text(-1, y_axis_size // n_head * 0.75, \"High Frequencies\",\n",
" ha='center', va='center', rotation=90, fontsize=14)\n",
"\n",
" # configure color bar\n",
" cbar = fig.colorbar(im, ax=ax, pad=0.02)\n",
" cbar.set_label(\"Mean norm\", fontsize=14, labelpad=5)\n",
" cbar.ax.tick_params(labelsize=12)\n",
" return im\n",
"\n",
"# plotting the queries and keys for the selected layer\n",
"selected_layer = 11\n",
"# queries [L, nh, hs]\n",
"selected_layer_q = qk_store['queries'][selected_layer]\n",
"selected_layer_k = qk_store['keys'][selected_layer]\n",
"\n",
"# init plot\n",
"plt.style.use('seaborn-v0_8-white')\n",
"plt.rcParams['font.family'] = 'serif'\n",
"\n",
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 7), sharey=True)\n",
"im1 = plot_freqs_of_heads(ax1, selected_layer_q, f\"Query frequency usage in Layer {selected_layer+1}\")\n",
"im2 = plot_freqs_of_heads(ax2, selected_layer_k, f\"Key frequency usage in Layer {selected_layer+1}\")\n",
"fig.tight_layout()\n",
"plt.savefig(f'final-qk-viz/all_heads/124M-rope-l{selected_layer+1}-1.png', dpi=300)\n",
"# plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "15",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "pytorch",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

Xet Storage Details

Size:
15.8 kB
·
Xet hash:
28c27a412346ae38dd9906c0e2ab2a3913fe5ef40b8d97e9c7ad9ae69515a525

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.