{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# <p style=\"font-family:JetBrains Mono; font-weight:bold; letter-spacing: 2px; font-size:120%; text-align:left;padding: 0px; border-bottom: 3px solid \">Google Slow VS Fast AI Runtime</p>","metadata":{}},{"cell_type":"markdown","source":"## Competition Understanding\n\nAt first glance this competition has a lot of moving parts, making it hard to understand how to approach. In this notebook I'm going through some of my thought processes to understand the competition and it's data.\n\nInitial thoughts:\n- **What are we predicting?** - Predict the runtime length of ML graphs and configurations.\n- **What does the data look like?** - Graph configurations in npz format.\n    - Two \"collection types\": `tile` and `layout` collections\n- **What does the target look like?** - \"Finally, for the layout collections, your job is to predict the order of the indices from best-to-worse configurations (i.e., ones leading to the smallest `d[\"config_runtime\"]`)\"\n- **How are we evaluated?** two evaluation metrics, described below.","metadata":{}},{"cell_type":"markdown","source":"## Imports","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom glob import glob\nfrom pathlib import Path\nfrom tqdm import tqdm\nimport seaborn as sns\nimport os\n","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:40:18.745608Z","iopub.execute_input":"2023-08-31T14:40:18.746022Z","iopub.status.idle":"2023-08-31T14:40:18.752779Z","shell.execute_reply.started":"2023-08-31T14:40:18.745989Z","shell.execute_reply":"2023-08-31T14:40:18.751509Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Structure\n- `npz_all` contains the data.\n    - Two collections types : `layout` and `tile` folders.\n        - `layout` has `nlp` and `xla`\n        - `tile` has `xla`\n    -Each collection type has default/random\n    -The data is split `train/test/valid`","metadata":{}},{"cell_type":"code","source":"!tree -I *.npz /kaggle/input/predict-ai-model-runtime/","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:15:42.099432Z","iopub.execute_input":"2023-08-31T14:15:42.099943Z","iopub.status.idle":"2023-08-31T14:15:44.119309Z","shell.execute_reply.started":"2023-08-31T14:15:42.099910Z","shell.execute_reply":"2023-08-31T14:15:44.117803Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Evaluation Metric\n\n1. For collection `tile:xla`\n    - We use the (1-slowdown) incurred of the top-K predictions to reflect how much slower the top-K configurations predicted by the model is from the actual fastest configuration\n2. For collection `layout:*`\n    - We use the Kendal Tau Correlation (a ranking metric: how well does your model-predicted ranking, correspond to the real ranking of runtimes).","metadata":{}},{"cell_type":"markdown","source":"## EDA","metadata":{}},{"cell_type":"markdown","source":"From the repo we get that:\n​\n> Suppose a `.npz` file stores a graph (representing a kernel) with `n` nodes and `m` edges. In addition, suppose we compile the graph with `c` different configurations, and run each on a TPU. Crucially, the configuration is at the graph-level. Then, the `.npz` file stores the following dictionary (can be loaded with `d = dict(np.load(\"npz/tile/xla/train/<pick 1>.npz\"))`):\n>   - Key `node_feat`: contains `float32` matrix with shape `(n, 140)`. The `u`-th row contains the feature vector for node `u` < `n` (please see Subsection \"Node Features\", below). Nodes are ordered topologically.\n>   - Key `node_opcode` contains `int32` vector with shape `(n, )`. The `u`-th entry stores the op-code for node u (please see the mapping of opcode to instruction name here).\n>   - Key `edge_index` contains `int32` matrix with shape `(m, 2)`. If entry `i` is = `[u, v]` (where `0 <= u, v < n`), then there is a directed edge from node `u` to node `v`, where `u` consumes the output of `v`.\n>   - Key `config_feat` contains `float32` matrix with shape `(c, 24)` with row `j` containing the (graph-level) configuration feature vector (please see Subsection \"Tile Config Features\").\n>   - Keys `config_runtime` and `config_runtime_normalizers`: both are `int64` vectors of length `c`. Entry `j` stores the runtime (in nanoseconds) of the given graph compiled with configuration `j` and a default configuration, respectively. Samples from the same graph may have slightly different `config_runtime_normalizers` because they are measured from different runs on multiple machines.\n> Finally, for the tile collection, your job is to predict the indices of the best configurations (i.e., ones leading to the smallest `d[\"config_runtime\"] / d[\"config_runtime_normalizers\"]`).","metadata":{}},{"cell_type":"markdown","source":">Lets try to look at one file from each collection<br>\n>    - `layout/nlp` : '/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/nlp/default/train/electra_base_batch_size_16_train.npz'\n>    - `layout/xla` : '/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/xla/default/train/resnet_v2_152_batch_64.npz'\n>    - `tile/xla` :   '/kaggle/input/predict-ai-model-runtime/npz_all/npz/tile/xla/train/xception_imagenet_9b1704c883ceb0d.npz'","metadata":{}},{"cell_type":"code","source":"# EDA of albert_en_base_batch_size_16_train.npz\n\n# Function to plot histograms for 1D arrays\ndef plot_histogram(data, title, xlabel, ylabel):\n    plt.figure(figsize=(10, 6))\n    sns.histplot(data, bins=30, kde=True)\n    plt.title(title)\n    plt.xlabel(xlabel)\n    plt.ylabel(ylabel)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:23:23.953039Z","iopub.execute_input":"2023-08-31T14:23:23.953438Z","iopub.status.idle":"2023-08-31T14:23:23.960208Z","shell.execute_reply.started":"2023-08-31T14:23:23.953407Z","shell.execute_reply":"2023-08-31T14:23:23.959003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load the .npz file\nnpz_file_path = '/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/nlp/default/train/electra_base_batch_size_16_train.npz'\nnpz_data = np.load(npz_file_path)\n\n# Plot histogram for node_opcode\nplot_histogram(npz_data['node_opcode'], 'Distribution of Node Opcodes', 'Opcode', 'Frequency')\n\n# Plot histogram for config_runtime\nplot_histogram(npz_data['config_runtime'], 'Distribution of Config Runtime', 'Runtime', 'Frequency')\n\n# Plot some sample histograms for node_feat (first 5 features)\nfor i in range(5):\n    plot_histogram(npz_data['node_feat'][:, i], f'Distribution of Node Feature {i+1}', f'Feature {i+1}', 'Frequency')","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:23:26.134470Z","iopub.execute_input":"2023-08-31T14:23:26.135465Z","iopub.status.idle":"2023-08-31T14:23:29.446823Z","shell.execute_reply.started":"2023-08-31T14:23:26.135426Z","shell.execute_reply":"2023-08-31T14:23:29.445543Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load the .npz file\nnpz_file_path = '/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/xla/default/train/resnet_v2_152_batch_64.npz'\nnpz_data = np.load(npz_file_path)\n\n# Plot histogram for node_opcode\nplot_histogram(npz_data['node_opcode'], 'Distribution of Node Opcodes', 'Opcode', 'Frequency')\n\n# Plot histogram for config_runtime\nplot_histogram(npz_data['config_runtime'], 'Distribution of Config Runtime', 'Runtime', 'Frequency')\n\n# Plot some sample histograms for node_feat (first 5 features)\nfor i in range(5):\n    plot_histogram(npz_data['node_feat'][:, i], f'Distribution of Node Feature {i+1}', f'Feature {i+1}', 'Frequency')","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:26:05.498318Z","iopub.execute_input":"2023-08-31T14:26:05.498858Z","iopub.status.idle":"2023-08-31T14:26:09.166572Z","shell.execute_reply.started":"2023-08-31T14:26:05.498812Z","shell.execute_reply":"2023-08-31T14:26:09.165236Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load the .npz file\nnpz_file_path = '/kaggle/input/predict-ai-model-runtime/npz_all/npz/tile/xla/train/xception_imagenet_9b1704c883ceb0d.npz'\nnpz_data = np.load(npz_file_path)\n\n# Plot histogram for node_opcode\nplot_histogram(npz_data['node_opcode'], 'Distribution of Node Opcodes', 'Opcode', 'Frequency')\n\n# Plot histogram for config_runtime\nplot_histogram(npz_data['config_runtime'], 'Distribution of Config Runtime', 'Runtime', 'Frequency')\n\n# Plot some sample histograms for node_feat (first 5 features)\nfor i in range(5):\n    plot_histogram(npz_data['node_feat'][:, i], f'Distribution of Node Feature {i+1}', f'Feature {i+1}', 'Frequency')","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:27:59.438201Z","iopub.execute_input":"2023-08-31T14:27:59.438645Z","iopub.status.idle":"2023-08-31T14:28:02.089803Z","shell.execute_reply.started":"2023-08-31T14:27:59.438609Z","shell.execute_reply":"2023-08-31T14:28:02.088385Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Insights from basic EDA","metadata":{}},{"cell_type":"markdown","source":"### Node Opcodes (`node_opcode`)\n- The operation codes (opcodes) are distributed in the wide range of 0-100.\n\n### Config Runtime (`config_runtime`)\n- The runtime values are distributed between 2.15 * 10^7 and 2.31 * 10^7. There's no distinct peak, but the data is slightly right-skewed.\n\n### Node Features (`node_feat`)\n- Histograms were plotted for the first 5 features.\n- Features 1 and 5 exhibit a continuous distribution.\n- Features 2 and 3 are all zeros.\n- Feature 4 appears to be categorical (either 0 or 1).\n\nThrough this EDA, we have revealed the basic characteristics and distributions of each feature and target variable. The next step could involve investigating the correlations between features, as well as the relationship between the features and the target variables.","metadata":{}},{"cell_type":"markdown","source":"## Baseline Model","metadata":{}},{"cell_type":"code","source":"!pip install torch-geometric torch-scatter","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:34:47.020050Z","iopub.execute_input":"2023-08-31T14:34:47.020504Z","iopub.status.idle":"2023-08-31T14:39:48.359090Z","shell.execute_reply.started":"2023-08-31T14:34:47.020448Z","shell.execute_reply":"2023-08-31T14:39:48.357705Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Model imports\nimport torch\nfrom torch import nn\nfrom torch import Tensor\nfrom torch_geometric.nn import GCNConv\nfrom torch_geometric.datasets import Planetoid\nfrom torch.utils.data import DataLoader, Dataset\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:39:54.663665Z","iopub.execute_input":"2023-08-31T14:39:54.664134Z","iopub.status.idle":"2023-08-31T14:39:56.981759Z","shell.execute_reply.started":"2023-08-31T14:39:54.664095Z","shell.execute_reply":"2023-08-31T14:39:56.980507Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We can load all the data in the dataframes to make working with it easier\ndef load_df(directory):\n    splits = [\"train\", \"valid\", \"test\"]\n    dfs = dict()\n    \n    for split in splits:\n        path = os.path.join(directory, split)\n        files = os.listdir(path)\n        list_df = []\n        \n        for file in files:\n            d = dict(np.load(os.path.join(path,file)))\n            d['file'] = file\n            list_df.append(d)\n        dfs[split] = pd.DataFrame.from_dict(list_df)\n    return dfs","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:40:02.857705Z","iopub.execute_input":"2023-08-31T14:40:02.859514Z","iopub.status.idle":"2023-08-31T14:40:02.869014Z","shell.execute_reply.started":"2023-08-31T14:40:02.859458Z","shell.execute_reply":"2023-08-31T14:40:02.867566Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If you try to run the following cell completely uncommented the Kaggle kernel will run out of memory and crash, so we will have to study the datasets individually","metadata":{}},{"cell_type":"code","source":"tile_xla = load_df(\"/kaggle/input/predict-ai-model-runtime/npz_all/npz/tile/xla/\")\n#layout_nlp_random = load_df(\"/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/nlp/random/\")\n#layout_nlp_default = load_df(\"/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/nlp/default/\")\n#layout_xla_random = load_df(\"/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/xla/random/\")\n#layout_xla_random = load_df(\"/kaggle/input/predict-ai-model-runtime/npz_all/npz/layout/xla/default/\")","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:40:28.159322Z","iopub.execute_input":"2023-08-31T14:40:28.159793Z","iopub.status.idle":"2023-08-31T14:41:22.378908Z","shell.execute_reply.started":"2023-08-31T14:40:28.159754Z","shell.execute_reply":"2023-08-31T14:41:22.377603Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Define dataset and Model\n\nclass TileDataset(Dataset):\n    def __init__(self, df):\n        self.df = df\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        config_feat = torch.tensor(row['config_feat'].astype(np.float32))\n        node_feat = torch.tensor(row['node_feat'].astype(np.float32))\n        node_opcode = torch.tensor(row['node_opcode'].astype(np.int32))\n        edge_index = torch.tensor(np.swapaxes(row['edge_index'],0,1).astype(np.int32))\n        target = (row['config_runtime']/row['config_runtime_normalizers']).astype(np.float32)\n        # minmax scale the target, we only care about order\n        target = (target-min(target))/(max(target) -min(target))\n        target = torch.tensor(target)\n        return config_feat,node_feat,node_opcode,edge_index,target","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:41:39.485314Z","iopub.execute_input":"2023-08-31T14:41:39.485781Z","iopub.status.idle":"2023-08-31T14:41:39.497504Z","shell.execute_reply.started":"2023-08-31T14:41:39.485744Z","shell.execute_reply":"2023-08-31T14:41:39.496098Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class SimpleModel(torch.nn.Module):\n    def __init__(self, hidden_channels, graph_feats, hidden_dim):\n        super().__init__()\n        op_embedding_dim = 4 # I choose 4-dimensional embedding\n        self.embedding = torch.nn.Embedding(120, #120 different op-codes\n                                            op_embedding_dim,\n                                           )\n        assert len(hidden_channels)>0\n        in_channels = op_embedding_dim+140\n        self.convs = torch.nn.ModuleList()\n        last_dim = hidden_channels[0]\n        self.convs.append(GCNConv(in_channels, hidden_channels[0]))\n        for i in range(len(hidden_channels)-1):\n            self.convs.append(GCNConv(hidden_channels[i], hidden_channels[i+1]))\n            last_dim = hidden_channels[i+1]\n        self.convs.append(GCNConv(last_dim, graph_feats))\n        \n        self.dense = torch.nn.Sequential(nn.Linear(graph_feats+24, 64),\n                                         nn.ReLU(),\n                                         nn.Linear(64, 64),\n                                         nn.ReLU(),\n                                         nn.Linear(64, 1),\n                                        )\n\n    def forward(self, x_cfg: Tensor,x_feat: Tensor, x_op: Tensor, edge_index: Tensor) -> Tensor:\n        \n        #get graph features\n        x = torch.concat([x_feat,self.embedding(x_op)],dim = 1)\n        #pass though conv layers\n        for conv in self.convs:\n            x = conv(x, edge_index).relu()\n        # get 1d graph embedding using average pooling\n        x_graph = torch.mean(x,0)\n        \n        \n        #put graph data into config data\n        x = torch.concat([x_cfg,x_graph.repeat((len(x_cfg),1))],axis=1)\n        #put into dense nn\n        x = torch.flatten(self.dense(x))\n        return x\n\nmodel = SimpleModel(hidden_channels = [16,32,16,48],graph_feats = 64,hidden_dim=64).to(device)","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:41:42.929594Z","iopub.execute_input":"2023-08-31T14:41:42.930996Z","iopub.status.idle":"2023-08-31T14:41:42.992503Z","shell.execute_reply.started":"2023-08-31T14:41:42.930950Z","shell.execute_reply":"2023-08-31T14:41:42.991238Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Lets train one epoch\n\ndataset = TileDataset(tile_xla[\"train\"])\ncriterion = torch.nn.L1Loss()\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001,weight_decay = 0.01)\n\nmodel.train()\npbar = tqdm(range(len(dataset)))\nloss_sum = 0\nn = 0\nfor i in pbar:\n    cfg_ft,nd_ft,nd_op,ind,target = dataset[i]\n    cfg_ft,nd_ft,nd_op,ind,target = cfg_ft.to(device),nd_ft.to(device),nd_op.to(device),ind.to(device),target.to(device)\n    \n    out = model(cfg_ft,nd_ft,nd_op,ind)\n    loss = criterion(out, target)\n    loss.backward()\n    torch.nn.utils.clip_grad_norm_(model.parameters(), 0.01)\n    optimizer.step()\n    \n    loss_sum+=loss.item()\n    n+=1\n    pbar.set_description(f'running loss: {(loss_sum/n):.1f},current loss: {(loss.item()):.1f}')","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:41:48.324698Z","iopub.execute_input":"2023-08-31T14:41:48.325129Z","iopub.status.idle":"2023-08-31T14:45:30.232252Z","shell.execute_reply.started":"2023-08-31T14:41:48.325097Z","shell.execute_reply":"2023-08-31T14:45:30.230414Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Evaluate on validation dataset\n\ndataset = TileDataset(tile_xla[\"valid\"])\ntile_xla_predictions = []\nmodel.eval()\n\npbar = tqdm(range(len(dataset)))\nfor i in pbar:\n    cfg_ft,nd_ft,nd_op,ind,target = dataset[i]\n    cfg_ft,nd_ft,nd_op,ind,target = cfg_ft.to(device),nd_ft.to(device),nd_op.to(device),ind.to(device),target.to(device)\n    \n    out = model(cfg_ft,nd_ft,nd_op,ind)\n    tile_xla_predictions.append(np.argsort(out.detach().numpy())[:5])\n\ndef score_tile(predictions, df):\n    score = 0\n    for i in range(len(df)):\n        predbest = min(df.iloc[i]['config_runtime'][predictions[i]])\n        best = min(df.iloc[i]['config_runtime'])\n        score +=2 - predbest/best\n    score /= len(df)\n    return score\nscore_tile(tile_xla_predictions, tile_xla[\"valid\"])","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:45:35.556588Z","iopub.execute_input":"2023-08-31T14:45:35.557087Z","iopub.status.idle":"2023-08-31T14:45:46.110109Z","shell.execute_reply.started":"2023-08-31T14:45:35.557042Z","shell.execute_reply":"2023-08-31T14:45:46.108641Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Predict (only tile:xla predictions)\n\ndataset = TileDataset(tile_xla[\"test\"])\ntile_xla_predictions = []\nmodel.eval()\npbar = tqdm(range(len(dataset)))\nfor i in pbar:\n    cfg_ft,nd_ft,nd_op,ind,target = dataset[i]\n    cfg_ft,nd_ft,nd_op,ind,target = cfg_ft.to(device),nd_ft.to(device),nd_op.to(device),ind.to(device),target.to(device)\n    \n    out = model(cfg_ft,nd_ft,nd_op,ind)\n    tile_xla_predictions.append(np.argsort(out.detach().numpy())[:5])","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:45:49.614816Z","iopub.execute_input":"2023-08-31T14:45:49.615231Z","iopub.status.idle":"2023-08-31T14:46:02.940943Z","shell.execute_reply.started":"2023-08-31T14:45:49.615200Z","shell.execute_reply":"2023-08-31T14:46:02.939768Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Submission","metadata":{}},{"cell_type":"code","source":"sub = pd.read_csv('/kaggle/input/predict-ai-model-runtime/sample_submission.csv')\nfor i,filename in enumerate(tile_xla[\"test\"]['file'].values):\n    id = 'tile:xla:' +filename[:-4]\n    sub.loc[sub.ID == id,'TopConfigs'] = ';'.join(tile_xla_predictions[i].astype(str))\nsub.to_csv('submission.csv',index=False)\nsub","metadata":{"execution":{"iopub.status.busy":"2023-08-31T14:46:23.366514Z","iopub.execute_input":"2023-08-31T14:46:23.367557Z","iopub.status.idle":"2023-08-31T14:46:23.915527Z","shell.execute_reply.started":"2023-08-31T14:46:23.367513Z","shell.execute_reply":"2023-08-31T14:46:23.914040Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}],"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}}