{ "cells": [ { "cell_type": "markdown", "id": "b0c9ae32", "metadata": { "papermill": { "duration": 0.004131, "end_time": "2024-09-06T18:35:13.202776", "exception": false, "start_time": "2024-09-06T18:35:13.198645", "status": "completed" }, "tags": [] }, "source": [ "# HPDC '23: Optimization-Based K-means Clustering on the RAJA Performance Suite: Thicket Tutorial\n", "\n", "Thicket is a python-based toolkit for Exploratory Data Analysis (EDA) of parallel performance data that enables performance optimization and understanding of applications’ performance on supercomputers. It bridges the performance tool gap between being able to consider only a single instance of a simulation run (e.g., single platform, single measurement tool, or single scale) and finding actionable insights in multi-dimensional, multi-scale, multi-architecture, and multi-tool performance datasets.\n", "\n", "## 1. Import Necessary Packages" ] }, { "cell_type": "code", "execution_count": 1, "id": "e76011bf", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:13.211070Z", "iopub.status.busy": "2024-09-06T18:35:13.210937Z", "iopub.status.idle": "2024-09-06T18:35:13.917754Z", "shell.execute_reply": "2024-09-06T18:35:13.917336Z" }, "papermill": { "duration": 0.712027, "end_time": "2024-09-06T18:35:13.918587", "exception": false, "start_time": "2024-09-06T18:35:13.206560", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "application/javascript": [ "var Roundtrip_Obj = {};\n", "var refresh_cycle = false;\n", "var clicked_cell = null;\n", "var cached_cells = Jupyter.notebook.get_cell_elements();\n", "\n", "/**\n", " * @name unindentPyCode\n", " * @description Removes leading indentations from a python code string.\n", " * \n", " * @param {string} code Python code in string form\n", " * @returns Passed code string but with no leading indentations\n", " */\n", "function unindentPyCode(code){\n", " let uicode = code.split('\\n');\n", " let indent = 0;\n", "\n", " uicode.forEach((l,i, arr)=>{\n", " if(i == 0){\n", " indent = l.search(/\\S/);\n", " }\n", " arr[i] = l.slice(indent);\n", " })\n", " uicode = uicode.join('\\n');\n", " return uicode;\n", "}\n", "\n", "/**\n", " * @name buildPythonAssignment\n", " * @description Builds up a python code string which assigns javascript data back into jypyter notebook namespace\n", " * \n", " * @param {string} val This is data assigned back to the python code\n", " * @param {string} py_var This is the variable into which val is assigned\n", " * @param {string} converter This is a definition of a python function which translates data back to the desired format\n", " * @returns The python code to be run in the jupyter shell\n", " */\n", "function buildPythonAssignment(val, py_var, converter){\n", " // console.log(val, py_var, converter);\n", " var holder = `'${val}'`;\n", " var code = `${unindentPyCode(converter.code)}`\n", " code += `\\ntmp = ${holder}`;\n", " code += `\\n${py_var} = ${converter.name}(tmp)`\n", "\n", " return code\n", "}\n", "\n", "/**\n", " * @name manageNewCell\n", " * \n", " * @description Increments all two way bound cell ids by the number of new cells which proceed them. \n", " * Ex. Adding one cell at position 2 will increment a bound cell at position 3 from 3->4. \n", " * \n", " * @param {array} newCells A list of our current cells in the notebook to be compared against cached cells\n", " * @param {} obj The current roundtrip object containing all data bindings\n", " */\n", "function manageNewCell(newCells, obj){\n", " let newIds = [];\n", "\n", " Object.keys(newCells).forEach(function(i){\n", " if(!Object.values(cached_cells).includes(newCells[i]) && !isNaN(i)){\n", " newIds.push(i);\n", " }\n", " });\n", "\n", " //increment all bindings past each new id\n", " for(let js_var in obj){\n", " for(let id of newIds){\n", " for(let key in obj[js_var][\"two_way\"]){\n", " obj[js_var][\"two_way\"][key].forEach((two_way_id, i) => {\n", " if(two_way_id > id){\n", " obj[js_var][\"two_way\"][key][i] += 1;\n", " }\n", " });\n", " }\n", " } \n", " }\n", "\n", " cached_cells = newCells;\n", "}\n", "\n", "function manageDeletedCell(newCells, obj){\n", " let deletedId = null;\n", " \n", " for(i of Object.keys(cachedCells)){\n", " if (cached_cells[i] !== newCells[i]){\n", " deletedId = i;\n", " break;\n", " }\n", " }\n", "\n", "}\n", "\n", "\n", "function bindClickDetectToCells(){\n", " let cells = Jupyter.notebook.get_cell_elements();\n", "\n", " for(let i in Object.keys(cells)){\n", " let cell = cells[i];\n", "\n", " if(cell !== undefined){\n", " cell.addEventListener('mousedown', () => {\n", " clicked_cell = i;\n", " }, true)\n", " }\n", " }\n", "}\n", "\n", "bindClickDetectToCells();\n", "\n", "/**\n", " * @name RT_Handler\n", " * @description A wrapper for our roundtrip object. It is called as a proxy for the\n", " * roundtrip object defined above. This enables us to define custom call backs for\n", " * gets and sets on the roundtrip object. The custom set handles necessary data conversion,\n", " * the registering of two-way bound variables and automatic updating of watched cells. The get\n", " * allows users to interact with the underlying object without worrying about the proxy.\n", " */\n", "var RT_Handler = {\n", " set(obj, prop, value){\n", " //Do cell housekeeping\n", "\n", "\n", " //Initial pass of value into roundtrip object\n", " // from python code; there may be multiple different\n", " // visualizations of the same type we need to catch\n", " if (typeof value === 'object' && value.hasOwnProperty('origin') && value.origin == 'INIT'){\n", " \n", " /**\n", " * In this code block we need to check if there is already a \n", " * an array of id's which are two way bound already defined and \n", " * add to it or remove from it\n", " */\n", " let ida = Jupyter.notebook.get_selected_index()-1;\n", " value.id = ida;\n", " let new_val = value;\n", "\n", " // Block updating bindings while jupyter is running\n", " if(refresh_cycle){\n", " new_val = obj[prop];\n", " new_val.data = value.data;\n", " return Reflect.set(obj, prop, new_val);\n", " }\n", "\n", " /**\n", " * The broad case where we are updating bindings \n", " * on existing data\n", " */\n", " if(obj[prop] != undefined){\n", " new_val = obj[prop];\n", " new_val.data = value.data;\n", " new_val.converter = value.converter;\n", "\n", " // If there is no two way array, create one\n", " // Else push on our new id\n", " if(value.two_way === true){\n", " if(!Object.keys(new_val.two_way).includes(value['python_var'])){\n", " new_val.two_way[value['python_var']] = [];\n", " }\n", "\n", " let pybinding = new_val.two_way[value['python_var']];\n", "\n", " if(!pybinding.includes(value.id)){\n", " pybinding.push(value.id);\n", " }\n", "\n", " }\n", "\n", " //Deregister a cell id from being two-way bound now\n", " else if(value.two_way === false && Object.keys(new_val.two_way).includes(value['python_var'])){\n", " let pybinding = new_val.two_way[value['python_var']];\n", " const index = pybinding.indexOf(value.id);\n", " \n", " if (index > -1) {\n", " pybinding.splice(index, 1);\n", " }\n", " }\n", " }\n", "\n", " //Initalize a new two-way object if\n", " // one did not exist\n", " else{\n", " if(new_val.two_way == true){\n", " new_val.two_way = {};\n", " new_val.two_way[value['python_var']] = [value.id];\n", " }\n", " else{\n", " new_val.two_way = {};\n", " }\n", " delete new_val.id;\n", " delete new_val.from_py;\n", " delete new_val.python_var;\n", " }\n", "\n", " return Reflect.set(obj, prop, new_val);\n", " }\n", " //Assignment from javascript code\n", " else {\n", " // TODO: make the py/js data identification object a\n", " // formal class\n", " if(obj[prop] === undefined){\n", " obj[prop] = {\n", " two_way: {},\n", " origin: \"JS\",\n", " data: null,\n", " python_var: \"\",\n", " converter: null,\n", " type: typeof(value)\n", " }\n", " }\n", "\n", " var execable_cells = [];\n", " let origin = 'STANDARD';\n", " let python_var = '';\n", "\n", " if (typeof value === 'object' && \n", " value.hasOwnProperty('origin') && \n", " value.origin == 'PYASSIGN'){\n", "\n", " origin = value.origin;\n", " python_var = value.python_var;\n", " value = value.data;\n", " }\n", "\n", " //TODO: Replace with imported, webpacked D3\n", " require(['https://d3js.org/d3.v4.min.js'], function(d3) {\n", "\n", " // When 2 way bound this calls automatically when something changes\n", " if (obj[prop] !== undefined && Object.keys(obj[prop][\"two_way\"]).length > 0){\n", "\n", " let current_cell = Number(clicked_cell);\n", " let py_var = '';\n", "\n", " //ust set the data without updating if our current cell is not two way bound\n", " if(origin == 'STANDARD'){\n", " let found = false;\n", " for(let key in obj[prop][\"two_way\"]){\n", " if (obj[prop][\"two_way\"][key].includes(current_cell)){\n", " found = true;\n", " py_var = key;\n", " }\n", " }\n", "\n", " if(!found){\n", " return Reflect.set(obj[prop], \"data\", value);\n", " }\n", " }\n", "\n", "\n", " if(origin == 'PYASSIGN'){\n", " py_var = python_var;\n", " }\n", "\n", "\n", " /**\n", " * We now have a list of registered cells we can execute.\n", " * So we look through our javascript variables to see if they\n", " * are bound to the same py variable as our current assignment\n", " * TODO: Make this list update when cells are moved up or down\n", " */\n", "\n", " for(let js_var in obj){\n", " let boundpyvars = Object.keys(obj[js_var][\"two_way\"]);\n", "\n", " if(boundpyvars.includes(py_var)){\n", " let clls = obj[js_var][\"two_way\"][py_var].filter(x => x != current_cell );\n", " execable_cells = execable_cells.concat(clls);\n", " }\n", " }\n", "\n", " if(origin == 'STANDARD'){\n", " // TODO:THROW AN ERROR IF CONVERTER == NONE\n", " const code = buildPythonAssignment(value, py_var, obj[prop][\"converter\"]);\n", " \n", " //TODO: Turn this into a function that manages error reporting and printing\n", " Jupyter.notebook.kernel.execute(code, { \n", " shell:{\n", " reply: function(r){\n", " //consider putting this in a reserved jupyter variable\n", " if(r.content.status == 'error'){\n", " console.error(`${r.content.ename} in JS->Python coversion:\\n ${r.content.evalue}`)\n", " }\n", " }\n", " }\n", " });\n", " }\n", "\n", " refresh_cycle = true;\n", " Jupyter.notebook.execute_cells(execable_cells);\n", "\n", " /**\n", " * Test every half second to see if some of the\n", " * jupyter cells are still running. Avoids a race condition\n", " * where incorrect ids were stored in our roundtrip object.\n", " */\n", " const test_running = function(){\n", " let runtest = d3.selectAll(\".running\");\n", " if(runtest.empty()){\n", " refresh_cycle = false;\n", " return;\n", " }\n", " else{\n", " setTimeout(test_running, 500);\n", " }\n", " }\n", "\n", " test_running();\n", " }\n", "\n", " });\n", " } \n", "\n", " return Reflect.set(obj[prop], \"data\", value);\n", " },\n", " get(obj, prop, reciever){\n", " let ret = obj[prop].data\n", " return ret; \n", " }\n", "}\n", "\n", "window.Roundtrip = new Proxy(Roundtrip_Obj, RT_Handler);\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import os\n", "import sys\n", "import re\n", "from itertools import product\n", "\n", "from IPython.display import display\n", "from IPython.display import HTML\n", "from IPython.display import Markdown\n", "import numpy as np\n", "import pandas as pd\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "import matplotlib as mpl\n", "import matplotlib.cm as cm\n", "from sklearn.cluster import KMeans\n", "from sklearn.decomposition import PCA\n", "from sklearn.preprocessing import StandardScaler\n", "from sklearn.metrics import silhouette_samples, silhouette_score\n", "import hatchet as ht\n", "from hatchet import QueryMatcher\n", "\n", "import thicket as th\n", "from thicket import Thicket" ] }, { "cell_type": "code", "execution_count": 2, "id": "6586fbfb", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:13.927236Z", "iopub.status.busy": "2024-09-06T18:35:13.926996Z", "iopub.status.idle": "2024-09-06T18:35:13.929877Z", "shell.execute_reply": "2024-09-06T18:35:13.929368Z" }, "papermill": { "duration": 0.008212, "end_time": "2024-09-06T18:35:13.930684", "exception": false, "start_time": "2024-09-06T18:35:13.922472", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "# Disable the warnings that Pandas 3 and NumPy will produce\n", "import warnings\n", "warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n", "warnings.filterwarnings(\"ignore\", category=FutureWarning) " ] }, { "cell_type": "markdown", "id": "ff9038d9", "metadata": { "papermill": { "duration": 0.003901, "end_time": "2024-09-06T18:35:13.938757", "exception": false, "start_time": "2024-09-06T18:35:13.934856", "status": "completed" }, "tags": [] }, "source": [ "## 2. Utility Functions for Ingesting and Manipulating Performance Data" ] }, { "cell_type": "code", "execution_count": 3, "id": "1c86f0eb", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:13.946994Z", "iopub.status.busy": "2024-09-06T18:35:13.946871Z", "iopub.status.idle": "2024-09-06T18:35:13.953789Z", "shell.execute_reply": "2024-09-06T18:35:13.953331Z" }, "papermill": { "duration": 0.011929, "end_time": "2024-09-06T18:35:13.954385", "exception": false, "start_time": "2024-09-06T18:35:13.942456", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def get_perf_leaves(thicket):\n", " \"\"\"Get data associated with only the leaf nodes of the thicket.\n", " \n", " Keyword Arguments:\n", " thicket -- thicket object\n", " \"\"\"\n", " query = QueryMatcher().match(\n", " \".\",\n", " lambda row: len(row.index.get_level_values(\"node\")[0].children) == 0\n", " )\n", " new_thicket = thicket.copy()\n", " matches = query.apply(thicket)\n", " idx_names = new_thicket.dataframe.index.names\n", " new_thicket.dataframe.reset_index(inplace=True)\n", " new_thicket.dataframe = new_thicket.dataframe.loc[new_thicket.dataframe[\"node\"].isin(matches)]\n", " new_thicket.dataframe.set_index(idx_names, inplace=True)\n", " squashed_gf = new_thicket.squash()\n", " new_thicket.graph = squashed_gf.graph\n", " new_thicket.statsframe.graph = squashed_gf.graph\n", " \n", " # NEW: need to update the performance data and the statsframe with the remaining (re-indexed) nodes.\n", " # The dataframe is internally updated in squash(), so we can easily just save it to our thicket perfdata.\n", " # For the statsframe, we'll have to come up with a better way eventually, but for now, we'll just create\n", " # a new statsframe the same way we do when we create a new thicket. \n", " new_thicket.dataframe = squashed_gf.dataframe \n", " subset_df = new_thicket.dataframe[\"name\"].reset_index().drop_duplicates(subset=[\"node\"])\n", " new_thicket.statsframe = ht.GraphFrame(\n", " graph=squashed_gf.graph,\n", " dataframe=pd.DataFrame(\n", " index=subset_df[\"node\"],\n", " data={\"name\": subset_df[\"name\"].values},\n", " ),\n", " )\n", " return new_thicket\n", "\n", "def apply_query_to_thicket(thicket, query):\n", " \"\"\"Applies query to thicket object.\n", " \n", " Keyword Arguments:\n", " thicket -- thicket object\n", " query -- thicket query\n", " \"\"\"\n", " new_thicket = thicket.copy()\n", " matches = query.apply(thicket)\n", " idx_names = new_thicket.dataframe.index.names\n", " new_thicket.dataframe.reset_index(inplace=True)\n", " new_thicket.dataframe = new_thicket.dataframe.loc[new_thicket.dataframe[\"node\"].isin(matches)]\n", " new_thicket.dataframe.set_index(idx_names, inplace=True)\n", " squashed_gf = new_thicket.squash()\n", " new_thicket.graph = squashed_gf.graph\n", " new_thicket.statsframe.graph = squashed_gf.graph\n", " \n", " # NEW: need to update the performance data and the statsframe with the remaining (re-indexed) nodes.\n", " # The dataframe is internally updated in squash(), so we can easily just save it to our thicket perfdata.\n", " # For the statsframe, we'll have to come up with a better way eventually, but for now, we'll just create\n", " # a new statsframe the same way we do when we create a new thicket. \n", " new_thicket.dataframe = squashed_gf.dataframe \n", " subset_df = new_thicket.dataframe[\"name\"].reset_index().drop_duplicates(subset=[\"node\"])\n", " new_thicket.statsframe = ht.GraphFrame(\n", " graph=squashed_gf.graph,\n", " dataframe=pd.DataFrame(\n", " index=subset_df[\"node\"],\n", " data={\"name\": subset_df[\"name\"].values},\n", " ),\n", " )\n", " return new_thicket\n", "\n", "def get_node(thicket, node_name):\n", " \"\"\"Get data associated with only the node corresponding to node_name.\n", " \n", " Keyword Arguments:\n", " thicket -- thicket object\n", " node_name -- (str) name of a node\n", " \"\"\"\n", " query = QueryMatcher().match(\n", " \".\",\n", " lambda row: row.index.get_level_values(\"node\")[0].frame[\"name\"] == node_name\n", " ) \n", "\n", " return apply_query_to_thicket(thicket, query)\n", "\n", "def get_nodes_in_group(thicket, group_prefix):\n", " \"\"\"Get data associated with kernels with the specified name prefix\n", " \n", " Keyword Arguments:\n", " thicket -- thicket object\n", " group_prefix -- prefix to satisfy startswith condition\n", " \"\"\"\n", " nodes_thicket = get_perf_leaves(thicket)\n", " query = QueryMatcher().match(\n", " \".\",\n", " lambda row: row.index.get_level_values(\"node\")[0].frame[\"name\"].startswith(group_prefix)\n", " )\n", " return apply_query_to_thicket(nodes_thicket, query)" ] }, { "cell_type": "code", "execution_count": 4, "id": "cc7affa3", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:13.961764Z", "iopub.status.busy": "2024-09-06T18:35:13.961633Z", "iopub.status.idle": "2024-09-06T18:35:13.965701Z", "shell.execute_reply": "2024-09-06T18:35:13.965477Z" }, "papermill": { "duration": 0.008239, "end_time": "2024-09-06T18:35:13.966376", "exception": false, "start_time": "2024-09-06T18:35:13.958137", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def check_for_optimization_level(val):\n", " match = re.search(r\"(?P-O[0123]).*\", val)\n", " if match is None:\n", " raise ValueError(\"Could not find opt level in {}\".format(val))\n", " return match.group(\"opt_level\")\n", "\n", "def check_for_optimization_level_int(val):\n", " match = re.search(r\"-O(?P[0-3]).*\", val)\n", " if match is None:\n", " raise ValueError(\"Could not find opt level in {}\".format(val))\n", " return match.group(\"opt_level\")\n", "\n", "def add_metadata_to_thicket(thicket):\n", " \"\"\"Insert additional metadata columns to the performance data table.\n", " \n", " Keyword Arguments:\n", " thicket -- thicket object\n", " \"\"\"\n", " thicket.metadata[\"opt_level\"] = thicket.metadata[\"rajaperf_compiler_options\"].apply(\n", " check_for_optimization_level\n", " )\n", " thicket.metadata[\"opt_level_int\"] = thicket.metadata[\"rajaperf_compiler_options\"].apply(\n", " check_for_optimization_level_int\n", " )\n", " thicket.metadata_column_to_perfdata(\"opt_level\")\n", " thicket.metadata_column_to_perfdata(\"opt_level_int\")\n", " thicket.metadata_column_to_perfdata(\"compiler\")\n", " return [\"opt_level\", \"opt_level_int\", \"compiler\"], thicket" ] }, { "cell_type": "code", "execution_count": 5, "id": "5d602c2d", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:13.972996Z", "iopub.status.busy": "2024-09-06T18:35:13.972884Z", "iopub.status.idle": "2024-09-06T18:35:13.976764Z", "shell.execute_reply": "2024-09-06T18:35:13.976502Z" }, "papermill": { "duration": 0.007759, "end_time": "2024-09-06T18:35:13.977327", "exception": false, "start_time": "2024-09-06T18:35:13.969568", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def collect_ensemble_data(directories, metrics, exc_time_metric=\"time (exc)\", profile_level_name=\"profile\"):\n", " \"\"\"Create a thicket from multiple profiles with specified metric columns.\n", " \n", " Keyword Arguments:\n", " directories -- list of paths to profiles\n", " metrics -- perf metrics\n", " \"\"\"\n", " real_metrics = metrics.copy()\n", " if \"name\" not in real_metrics:\n", " real_metrics.append(\"name\")\n", " if \"nid\" not in real_metrics:\n", " real_metrics.append(\"nid\")\n", " if exc_time_metric not in real_metrics:\n", " real_metrics.append(exc_time_metric)\n", " subthickets = []\n", " for directory in directories:\n", " thicket = Thicket.from_caliperreader(directory, disable_tqdm=True)\n", " thicket.dataframe = thicket.dataframe[[*real_metrics]]\n", " thicket.exc_metrics = [m for m in thicket.dataframe.columns if m in thicket.exc_metrics]\n", " thicket.inc_metrics = [m for m in thicket.dataframe.columns if m in thicket.inc_metrics]\n", " if thicket.default_metric not in thicket.dataframe.columns:\n", " if exc_time_metric in thicket.dataframe.columns:\n", " thicket.default_metric = exc_time_metric\n", " else:\n", " raise ValueError(\"Could not set default metric\")\n", " subthickets.append(thicket)\n", " return Thicket.concat_thickets(axis=\"index\", thickets=subthickets, disable_tqdm=True)" ] }, { "cell_type": "code", "execution_count": 6, "id": "d1880c5e", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:13.983449Z", "iopub.status.busy": "2024-09-06T18:35:13.983348Z", "iopub.status.idle": "2024-09-06T18:35:13.986703Z", "shell.execute_reply": "2024-09-06T18:35:13.986477Z" }, "papermill": { "duration": 0.007077, "end_time": "2024-09-06T18:35:13.987229", "exception": false, "start_time": "2024-09-06T18:35:13.980152", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def calc_speedup_by_opt_level(df, opt_level_metric=\"opt_level\", name_metric=\"name\",\n", " exc_time_metric=\"time (exc)\", min_opt_level=\"-O0\"):\n", " \"\"\"Calculate speedup based on optimization level (e.g., O0/O1, O0/O2, etc.).\n", " \n", " Keyword Arguments:\n", " df -- performace data table\n", " \"\"\"\n", " index_names = df.index.names\n", " df.reset_index(inplace=True)\n", " min_opt_groups = df.groupby(\n", " by=name_metric\n", " )\n", " min_opt_times = {}\n", " for group_name, group in min_opt_groups:\n", " min_time = group.loc[group[opt_level_metric] == min_opt_level, exc_time_metric].iloc[0]\n", " min_opt_times[group_name] = min_time\n", " \n", " def calc_speedup(row):\n", " base_time = min_opt_times[row[name_metric]]\n", " ret_val = base_time / row[exc_time_metric]\n", " return ret_val\n", " \n", " speedup_col = df.apply(calc_speedup, axis=\"columns\")\n", " df[\"speedup\"] = speedup_col\n", " \n", " df.set_index(index_names, inplace=True)\n", " \n", " return df" ] }, { "cell_type": "markdown", "id": "a811cb0a", "metadata": { "papermill": { "duration": 0.003121, "end_time": "2024-09-06T18:35:13.993214", "exception": false, "start_time": "2024-09-06T18:35:13.990093", "status": "completed" }, "tags": [] }, "source": [ "## 3. Utility Functions for KMeans" ] }, { "cell_type": "code", "execution_count": 7, "id": "0534d891", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:13.999202Z", "iopub.status.busy": "2024-09-06T18:35:13.999115Z", "iopub.status.idle": "2024-09-06T18:35:14.001815Z", "shell.execute_reply": "2024-09-06T18:35:14.001556Z" }, "papermill": { "duration": 0.006331, "end_time": "2024-09-06T18:35:14.002353", "exception": false, "start_time": "2024-09-06T18:35:13.996022", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def build_clustering_df(gf, metrics):\n", " tmp_df = gf.dataframe.reset_index()\n", " real_metrics = metrics\n", " if \"name\" in tmp_df.columns and \"name\" not in metrics:\n", " real_metrics.append(\"name\")\n", " return tmp_df[[*metrics]]" ] }, { "cell_type": "code", "execution_count": 8, "id": "cddd1f09", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.008452Z", "iopub.status.busy": "2024-09-06T18:35:14.008365Z", "iopub.status.idle": "2024-09-06T18:35:14.012831Z", "shell.execute_reply": "2024-09-06T18:35:14.012623Z" }, "papermill": { "duration": 0.008029, "end_time": "2024-09-06T18:35:14.013295", "exception": false, "start_time": "2024-09-06T18:35:14.005266", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def normalize_data(df, metrics, **kwargs):\n", " \"\"\"Creates normalized versions of the specified metrics.\n", " \n", " Keyword Arguments:\n", " df -- performace data table\n", " metrics -- perf metrics\n", " \"\"\"\n", " scaler = StandardScaler()\n", " data = df[[*metrics]].to_numpy()\n", " new_data = scaler.fit_transform(data)\n", " for col in range(new_data.shape[1]):\n", " df[\"{}_norm\".format(metrics[col])] = new_data[:, col]\n", " return df\n", "\n", "\n", "def cluster_data(df, x_metric, y_metric, n_clusters=8, use_y_for_label=True):\n", " \"\"\"Conducts a KMeans clustering of data from the DataFrame.\n", " \n", " Keyword Arguments:\n", " df -- performace data table\n", " n_clusters -- no. of clusters\n", " \"\"\"\n", " print(x_metric, y_metric)\n", " km = KMeans(n_clusters=n_clusters)\n", " pts = df[[x_metric, y_metric]].to_numpy()\n", " cluster_labels = km.fit_predict(pts)\n", " label_metric = y_metric if use_y_for_label else x_metric\n", " if label_metric.endswith(\"_norm\"):\n", " label_metric = label_metric[:-len(\"_norm\")]\n", " cluster_label = \"clusters\" if label_metric.startswith(\"pca\") else \"{}_cluster\".format(label_metric)\n", " df[\"{}_id\".format(cluster_label)] = cluster_labels\n", " df[cluster_label] = [\"Cluster {}\".format(l) for l in cluster_labels]\n", " return cluster_label, km.cluster_centers_, df" ] }, { "cell_type": "code", "execution_count": 9, "id": "06bcf566", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.019641Z", "iopub.status.busy": "2024-09-06T18:35:14.019539Z", "iopub.status.idle": "2024-09-06T18:35:14.026832Z", "shell.execute_reply": "2024-09-06T18:35:14.026592Z" }, "papermill": { "duration": 0.011301, "end_time": "2024-09-06T18:35:14.027393", "exception": false, "start_time": "2024-09-06T18:35:14.016092", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "# Modified version of the code from\n", "# https://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html\n", "def silhouette_test(df, x_metric, y_metric):\n", " \"\"\"Silhouette analysis on KMeans clustering.\n", " \n", " Keyword Arguments:\n", " df -- performace data table\n", " \"\"\"\n", " range_n_clusters = [2, 3, 4, 5, 6]\n", " \n", " pts = df[[x_metric, y_metric]].to_numpy()\n", "\n", " for n_clusters in range_n_clusters:\n", " # Create a subplot with 1 row and 2 columns\n", " fig, (ax1, ax2) = plt.subplots(1, 2)\n", " fig.set_size_inches(18, 7)\n", "\n", " # The 1st subplot is the silhouette plot\n", " # The silhouette coefficient can range from -1, 1 but in this example all\n", " # lie within [-0.1, 1]\n", " ax1.set_xlim([-0.1, 1])\n", " # The (n_clusters+1)*10 is for inserting blank space between silhouette\n", " # plots of individual clusters, to demarcate them clearly.\n", " ax1.set_ylim([0, len(pts) + (n_clusters + 1) * 10])\n", "\n", " # Initialize the clusterer with n_clusters value and a random generator\n", " # seed of 10 for reproducibility.\n", " clusterer = KMeans(n_clusters=n_clusters, random_state=10)\n", " cluster_labels = clusterer.fit_predict(pts)\n", "\n", " # The silhouette_score gives the average value for all the samples.\n", " # This gives a perspective into the density and separation of the formed\n", " # clusters\n", " silhouette_avg = silhouette_score(pts, cluster_labels)\n", " print(\n", " \"For n_clusters =\",\n", " n_clusters,\n", " \"The average silhouette_score is :\",\n", " silhouette_avg,\n", " )\n", "\n", " # Compute the silhouette scores for each sample\n", " sample_silhouette_values = silhouette_samples(pts, cluster_labels)\n", " \n", " y_lower = 10\n", " for i in range(n_clusters):\n", " # Aggregate the silhouette scores for samples belonging to\n", " # cluster i, and sort them\n", " ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]\n", "\n", " ith_cluster_silhouette_values.sort()\n", "\n", " size_cluster_i = ith_cluster_silhouette_values.shape[0]\n", " y_upper = y_lower + size_cluster_i\n", "\n", " color = cm.nipy_spectral(float(i) / n_clusters)\n", " ax1.fill_betweenx(\n", " np.arange(y_lower, y_upper),\n", " 0,\n", " ith_cluster_silhouette_values,\n", " facecolor=color,\n", " edgecolor=color,\n", " alpha=0.7,\n", " )\n", "\n", " # Label the silhouette plots with their cluster numbers at the middle\n", " ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))\n", "\n", " # Compute the new y_lower for next plot\n", " y_lower = y_upper + 10 # 10 for the 0 samples\n", "\n", " ax1.set_title(\"The silhouette plot for the various clusters.\")\n", " ax1.set_xlabel(\"The silhouette coefficient values\")\n", " ax1.set_ylabel(\"Cluster label\")\n", "\n", " # The vertical line for average silhouette score of all the values\n", " ax1.axvline(x=silhouette_avg, color=\"red\", linestyle=\"--\")\n", "\n", " ax1.set_yticks([]) # Clear the yaxis labels / ticks\n", " ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])\n", "\n", " # 2nd Plot showing the actual clusters formed\n", " colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)\n", " ax2.scatter(\n", " pts[:, 0],\n", " pts[:, 1],\n", " marker=\".\",\n", " s=30,\n", " lw=0,\n", " alpha=0.7,\n", " c=colors,\n", " edgecolor=\"k\"\n", " )\n", "\n", " # Labeling the clusters\n", " centers = clusterer.cluster_centers_\n", " # Draw white circles at cluster centers\n", " ax2.scatter(\n", " centers[:, 0],\n", " centers[:, 1],\n", " marker=\"o\",\n", " c=\"white\",\n", " alpha=1,\n", " s=200,\n", " edgecolor=\"k\",\n", " )\n", "\n", " for i, c in enumerate(centers):\n", " ax2.scatter(\n", " c[0],\n", " c[1],\n", " marker=\"$%d$\" % i,\n", " alpha=1,\n", " s=50,\n", " edgecolor=\"k\"\n", " )\n", "\n", " ax2.set_title(\"The visualization of the clustered data.\")\n", " ax2.set_xlabel(\"Feature space for the 1st feature\")\n", " ax2.set_ylabel(\"Feature space for the 2nd feature\")\n", "\n", " plt.suptitle(\n", " \"Silhouette analysis for KMeans clustering on sample data with n_clusters = %d\"\n", " % n_clusters,\n", " fontsize=14,\n", " fontweight=\"bold\",\n", " )\n", "\n", " plt.show()" ] }, { "cell_type": "code", "execution_count": 10, "id": "3b990cfa", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.033823Z", "iopub.status.busy": "2024-09-06T18:35:14.033730Z", "iopub.status.idle": "2024-09-06T18:35:14.038902Z", "shell.execute_reply": "2024-09-06T18:35:14.038644Z" }, "papermill": { "duration": 0.009073, "end_time": "2024-09-06T18:35:14.039443", "exception": false, "start_time": "2024-09-06T18:35:14.030370", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def plot_clusters(df, x_metric, y_metric, hue_metric, marker_style_metric=None, \n", " swap_marker_and_hue=False, cluster_centers=None, use_y_for_label=True,\n", " **kwargs):\n", " \"\"\"Plot cluster with specific configuration.\n", " \n", " Keyword Arguments:\n", " df -- performace data table\n", " \"\"\"\n", " hue = hue_metric\n", " df = df.sort_values(\n", " [\n", " \"opt_level_int\",\n", " hue\n", " ],\n", " )\n", " ax = None\n", " if marker_style_metric is not None:\n", " if swap_marker_and_hue:\n", " hue_order = sorted(df[marker_style_metric].unique().tolist())\n", " style_order = sorted(df[hue].unique().tolist())\n", " ax = sns.scatterplot(\n", " data=df,\n", " x=x_metric,\n", " y=y_metric,\n", " hue=marker_style_metric,\n", " style=hue,\n", " legend=\"auto\",\n", " hue_order=hue_order,\n", " style_order=style_order,\n", " **kwargs\n", " )\n", " else:\n", " hue_order = sorted(df[hue].unique().tolist())\n", " style_order = sorted(df[marker_style_metric].unique().tolist())\n", " ax = sns.scatterplot(\n", " data=df,\n", " x=x_metric,\n", " y=y_metric,\n", " hue=hue,\n", " style=marker_style_metric,\n", " legend=\"auto\",\n", " hue_order=hue_order,\n", " style_order=style_order,\n", " **kwargs\n", " )\n", " else:\n", " hue_order = sorted(df[hue].unique().tolist())\n", " ax = sns.scatterplot(\n", " data=df,\n", " x=x_metric,\n", " y=y_metric,\n", " hue=hue,\n", " legend=\"auto\",\n", " hue_order=hue_order,\n", " **kwargs\n", " )\n", " return ax" ] }, { "cell_type": "code", "execution_count": 11, "id": "2c4c9966", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.045566Z", "iopub.status.busy": "2024-09-06T18:35:14.045473Z", "iopub.status.idle": "2024-09-06T18:35:14.047695Z", "shell.execute_reply": "2024-09-06T18:35:14.047437Z" }, "papermill": { "duration": 0.005867, "end_time": "2024-09-06T18:35:14.048213", "exception": false, "start_time": "2024-09-06T18:35:14.042346", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "def plot_lines_by_profile(df, x_metric, y_metric, grouping_metric, axes, legend=\"auto\"):\n", " \"\"\"Plot line with specific configuration.\"\"\"\n", " sns.lineplot(\n", " data=df,\n", " x=x_metric,\n", " y=y_metric,\n", " style=grouping_metric,\n", " ax=axes,\n", " legend=legend,\n", " color=\"#c0c0c0\"\n", " )" ] }, { "cell_type": "markdown", "id": "c922e597", "metadata": { "papermill": { "duration": 0.002898, "end_time": "2024-09-06T18:35:14.054107", "exception": false, "start_time": "2024-09-06T18:35:14.051209", "status": "completed" }, "tags": [] }, "source": [ "## 4. Set Variables to be Used for All Clustering\n", "\n", "Set `directories` to be a list of the directories containing the `.cali` files to load" ] }, { "cell_type": "code", "execution_count": 12, "id": "a25f953f", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.060335Z", "iopub.status.busy": "2024-09-06T18:35:14.060233Z", "iopub.status.idle": "2024-09-06T18:35:14.062778Z", "shell.execute_reply": "2024-09-06T18:35:14.062544Z" }, "papermill": { "duration": 0.006215, "end_time": "2024-09-06T18:35:14.063288", "exception": false, "start_time": "2024-09-06T18:35:14.057073", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "root = \"../data/quartz\"\n", "dirs_ordered = [\n", " \"GCC_10.3.1_BaseSeq_08388608/O0/GCC_1031_BaseSeq_O0_8388608_01.cali\",\n", " \"GCC_10.3.1_BaseSeq_08388608/O1/GCC_1031_BaseSeq_O1_8388608_01.cali\",\n", " \"GCC_10.3.1_BaseSeq_08388608/O2/GCC_1031_BaseSeq_O2_8388608_01.cali\",\n", " \"GCC_10.3.1_BaseSeq_08388608/O3/GCC_1031_BaseSeq_O3_8388608_01.cali\"\n", "]\n", "directories = [\"{}/{}\".format(root, d) for d in dirs_ordered]" ] }, { "cell_type": "markdown", "id": "f345aff7", "metadata": { "papermill": { "duration": 0.002963, "end_time": "2024-09-06T18:35:14.069494", "exception": false, "start_time": "2024-09-06T18:35:14.066531", "status": "completed" }, "tags": [] }, "source": [ "Set `topdown_cols` to be a list containing the Topdown metrics to be used" ] }, { "cell_type": "code", "execution_count": 13, "id": "9c32973c", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.084486Z", "iopub.status.busy": "2024-09-06T18:35:14.084384Z", "iopub.status.idle": "2024-09-06T18:35:14.086844Z", "shell.execute_reply": "2024-09-06T18:35:14.086531Z" }, "papermill": { "duration": 0.006323, "end_time": "2024-09-06T18:35:14.087389", "exception": false, "start_time": "2024-09-06T18:35:14.081066", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "topdown_cols=[\n", " \"Retiring\",\n", " \"Frontend bound\",\n", " \"Backend bound\",\n", " \"Bad speculation\",\n", "]" ] }, { "cell_type": "markdown", "id": "e479a3a6", "metadata": { "papermill": { "duration": 0.003294, "end_time": "2024-09-06T18:35:14.094595", "exception": false, "start_time": "2024-09-06T18:35:14.091301", "status": "completed" }, "tags": [] }, "source": [ "## 5. Thicket Clustering\n", "\n", "Collect the performance data from Quartz. Then, normalize and cluster the 4 high-level topdown metrics (i.e., retiring, frontend bound, backend bound, and bad speculation) with respect to time. Finally, plot the clusters and produce a dataframe containing the node name, metrics, cluster ID, and optimization level.\n", "\n", "Set variables used for clustering:" ] }, { "cell_type": "code", "execution_count": 14, "id": "10cfa497", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.102554Z", "iopub.status.busy": "2024-09-06T18:35:14.102460Z", "iopub.status.idle": "2024-09-06T18:35:14.104712Z", "shell.execute_reply": "2024-09-06T18:35:14.104497Z" }, "papermill": { "duration": 0.005846, "end_time": "2024-09-06T18:35:14.105228", "exception": false, "start_time": "2024-09-06T18:35:14.099382", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "# If desired, set max_time_variance_node to the node to focus on for the clustering\n", "max_time_variance_node = \"Stream\"\n", "# Specify the number of clusters to create for each topdown metric\n", "n_clusters_retiring = 3\n", "n_clusters_frontend_bound = 4\n", "n_clusters_backend_bound = 3\n", "n_clusters_bad_speculation = 4\n", "# If desired, scale the topdown metrics by this factor before clustering\n", "topdown_scaling_factor = 1\n", "# If true, use sklearn's StandardScaler to create normalized metrics\n", "use_normalization = True" ] }, { "cell_type": "markdown", "id": "9315af2a", "metadata": { "papermill": { "duration": 0.00276, "end_time": "2024-09-06T18:35:14.110822", "exception": false, "start_time": "2024-09-06T18:35:14.108062", "status": "completed" }, "tags": [] }, "source": [ "Read data into a single Thicket" ] }, { "cell_type": "code", "execution_count": 15, "id": "e439c1a1", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.116709Z", "iopub.status.busy": "2024-09-06T18:35:14.116621Z", "iopub.status.idle": "2024-09-06T18:35:14.226822Z", "shell.execute_reply": "2024-09-06T18:35:14.226330Z" }, "papermill": { "duration": 0.114052, "end_time": "2024-09-06T18:35:14.227658", "exception": false, "start_time": "2024-09-06T18:35:14.113606", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "thicket = collect_ensemble_data(directories, topdown_cols)" ] }, { "cell_type": "markdown", "id": "c4ea7b97", "metadata": { "papermill": { "duration": 0.003407, "end_time": "2024-09-06T18:35:14.234919", "exception": false, "start_time": "2024-09-06T18:35:14.231512", "status": "completed" }, "tags": [] }, "source": [ "**NOTE:** Only run one of the next three cells\n", "\n", "Cell 1: Query the Thicket object to get data associated with only the node corresponding to `max_time_variance_node`\n", "\n", "Cell 2: Query the Thicket object to get data associated with only the leaf nodes\n", "\n", "Cell 3: Query the Thicket object to get data associated with kernels with the specified name prefix" ] }, { "cell_type": "code", "execution_count": 16, "id": "aea168dc", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.242988Z", "iopub.status.busy": "2024-09-06T18:35:14.242856Z", "iopub.status.idle": "2024-09-06T18:35:14.269131Z", "shell.execute_reply": "2024-09-06T18:35:14.268624Z" }, "papermill": { "duration": 0.031206, "end_time": "2024-09-06T18:35:14.269853", "exception": false, "start_time": "2024-09-06T18:35:14.238647", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "single_node_thicket = get_node(thicket, max_time_variance_node)" ] }, { "cell_type": "code", "execution_count": 17, "id": "11be2ef5", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.280119Z", "iopub.status.busy": "2024-09-06T18:35:14.279980Z", "iopub.status.idle": "2024-09-06T18:35:14.683941Z", "shell.execute_reply": "2024-09-06T18:35:14.683641Z" }, "papermill": { "duration": 0.410821, "end_time": "2024-09-06T18:35:14.684660", "exception": false, "start_time": "2024-09-06T18:35:14.273839", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "single_node_thicket = get_perf_leaves(thicket)" ] }, { "cell_type": "code", "execution_count": 18, "id": "cf9b54ef", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:14.691360Z", "iopub.status.busy": "2024-09-06T18:35:14.691271Z", "iopub.status.idle": "2024-09-06T18:35:15.160852Z", "shell.execute_reply": "2024-09-06T18:35:15.160341Z" }, "papermill": { "duration": 0.473807, "end_time": "2024-09-06T18:35:15.161792", "exception": false, "start_time": "2024-09-06T18:35:14.687985", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "single_node_thicket = get_nodes_in_group(thicket, \"Stream\")" ] }, { "cell_type": "markdown", "id": "20d3e2cb", "metadata": { "papermill": { "duration": 0.003716, "end_time": "2024-09-06T18:35:15.169872", "exception": false, "start_time": "2024-09-06T18:35:15.166156", "status": "completed" }, "tags": [] }, "source": [ "Add important metadata columns into the performance data" ] }, { "cell_type": "code", "execution_count": 19, "id": "5fdffa2b", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.178978Z", "iopub.status.busy": "2024-09-06T18:35:15.178789Z", "iopub.status.idle": "2024-09-06T18:35:15.185904Z", "shell.execute_reply": "2024-09-06T18:35:15.185479Z" }, "papermill": { "duration": 0.012735, "end_time": "2024-09-06T18:35:15.186724", "exception": false, "start_time": "2024-09-06T18:35:15.173989", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "metadata_cols, single_node_thicket = add_metadata_to_thicket(single_node_thicket)" ] }, { "cell_type": "markdown", "id": "ba80ae38", "metadata": { "papermill": { "duration": 0.007083, "end_time": "2024-09-06T18:35:15.197437", "exception": false, "start_time": "2024-09-06T18:35:15.190354", "status": "completed" }, "tags": [] }, "source": [ "Calculate speedup based on optimization level (e.g., O0/O1, O0/O2, etc.)" ] }, { "cell_type": "code", "execution_count": 20, "id": "1703fcef", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.205840Z", "iopub.status.busy": "2024-09-06T18:35:15.205718Z", "iopub.status.idle": "2024-09-06T18:35:15.210858Z", "shell.execute_reply": "2024-09-06T18:35:15.210435Z" }, "papermill": { "duration": 0.010286, "end_time": "2024-09-06T18:35:15.211612", "exception": false, "start_time": "2024-09-06T18:35:15.201326", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "single_node_thicket.dataframe = calc_speedup_by_opt_level(single_node_thicket.dataframe)" ] }, { "cell_type": "markdown", "id": "337a8cfa", "metadata": { "papermill": { "duration": 0.003786, "end_time": "2024-09-06T18:35:15.219084", "exception": false, "start_time": "2024-09-06T18:35:15.215298", "status": "completed" }, "tags": [] }, "source": [ "Extract the important data into a DataFrame better formatted for clustering" ] }, { "cell_type": "code", "execution_count": 21, "id": "bc244ead", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.227062Z", "iopub.status.busy": "2024-09-06T18:35:15.226922Z", "iopub.status.idle": "2024-09-06T18:35:15.235117Z", "shell.execute_reply": "2024-09-06T18:35:15.234594Z" }, "papermill": { "duration": 0.013423, "end_time": "2024-09-06T18:35:15.235919", "exception": false, "start_time": "2024-09-06T18:35:15.222496", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
opt_levelopt_level_intcompilerRetiringFrontend boundBackend boundBad speculationspeedupname
0-O00g++-10.3.10.0577560.0072830.9347450.0002151.000000Stream_ADD
1-O22g++-10.3.10.0564470.0069570.9364930.0001031.015401Stream_ADD
2-O11g++-10.3.10.0567570.0070640.9362310.0000001.007056Stream_ADD
3-O33g++-10.3.10.0569020.0068090.9359970.0002920.994071Stream_ADD
4-O00g++-10.3.10.0502300.0009320.9484210.0004171.000000Stream_COPY
5-O22g++-10.3.10.0505110.0007830.9484390.0002671.022479Stream_COPY
6-O11g++-10.3.10.0500610.0009320.9488700.0001371.004292Stream_COPY
7-O33g++-10.3.10.0501000.0007390.9490180.0001430.998211Stream_COPY
8-O00g++-10.3.10.0852350.0091520.9052750.0003371.000000Stream_DOT
9-O22g++-10.3.10.0830240.0091740.9078570.0000001.005844Stream_DOT
10-O11g++-10.3.10.0838230.0093100.9064850.0003811.004005Stream_DOT
11-O33g++-10.3.10.0843160.0090450.9064150.0002240.991317Stream_DOT
12-O00g++-10.3.10.0677400.0076020.9244280.0002291.000000Stream_MUL
13-O22g++-10.3.10.0665280.0073750.9260110.0000861.023686Stream_MUL
14-O11g++-10.3.10.0663690.0074010.9263590.0000001.005575Stream_MUL
15-O33g++-10.3.10.0670280.0071620.9257040.0001050.998667Stream_MUL
16-O00g++-10.3.10.0573870.0071980.9351090.0003071.000000Stream_TRIAD
17-O22g++-10.3.10.0575090.0067340.9355010.0002561.029841Stream_TRIAD
18-O11g++-10.3.10.0567600.0067550.9364560.0000301.010570Stream_TRIAD
19-O33g++-10.3.10.0571960.0069850.9355240.0002951.000759Stream_TRIAD
\n", "
" ], "text/plain": [ " opt_level opt_level_int compiler Retiring Frontend bound \\\n", "0 -O0 0 g++-10.3.1 0.057756 0.007283 \n", "1 -O2 2 g++-10.3.1 0.056447 0.006957 \n", "2 -O1 1 g++-10.3.1 0.056757 0.007064 \n", "3 -O3 3 g++-10.3.1 0.056902 0.006809 \n", "4 -O0 0 g++-10.3.1 0.050230 0.000932 \n", "5 -O2 2 g++-10.3.1 0.050511 0.000783 \n", "6 -O1 1 g++-10.3.1 0.050061 0.000932 \n", "7 -O3 3 g++-10.3.1 0.050100 0.000739 \n", "8 -O0 0 g++-10.3.1 0.085235 0.009152 \n", "9 -O2 2 g++-10.3.1 0.083024 0.009174 \n", "10 -O1 1 g++-10.3.1 0.083823 0.009310 \n", "11 -O3 3 g++-10.3.1 0.084316 0.009045 \n", "12 -O0 0 g++-10.3.1 0.067740 0.007602 \n", "13 -O2 2 g++-10.3.1 0.066528 0.007375 \n", "14 -O1 1 g++-10.3.1 0.066369 0.007401 \n", "15 -O3 3 g++-10.3.1 0.067028 0.007162 \n", "16 -O0 0 g++-10.3.1 0.057387 0.007198 \n", "17 -O2 2 g++-10.3.1 0.057509 0.006734 \n", "18 -O1 1 g++-10.3.1 0.056760 0.006755 \n", "19 -O3 3 g++-10.3.1 0.057196 0.006985 \n", "\n", " Backend bound Bad speculation speedup name \n", "0 0.934745 0.000215 1.000000 Stream_ADD \n", "1 0.936493 0.000103 1.015401 Stream_ADD \n", "2 0.936231 0.000000 1.007056 Stream_ADD \n", "3 0.935997 0.000292 0.994071 Stream_ADD \n", "4 0.948421 0.000417 1.000000 Stream_COPY \n", "5 0.948439 0.000267 1.022479 Stream_COPY \n", "6 0.948870 0.000137 1.004292 Stream_COPY \n", "7 0.949018 0.000143 0.998211 Stream_COPY \n", "8 0.905275 0.000337 1.000000 Stream_DOT \n", "9 0.907857 0.000000 1.005844 Stream_DOT \n", "10 0.906485 0.000381 1.004005 Stream_DOT \n", "11 0.906415 0.000224 0.991317 Stream_DOT \n", "12 0.924428 0.000229 1.000000 Stream_MUL \n", "13 0.926011 0.000086 1.023686 Stream_MUL \n", "14 0.926359 0.000000 1.005575 Stream_MUL \n", "15 0.925704 0.000105 0.998667 Stream_MUL \n", "16 0.935109 0.000307 1.000000 Stream_TRIAD \n", "17 0.935501 0.000256 1.029841 Stream_TRIAD \n", "18 0.936456 0.000030 1.010570 Stream_TRIAD \n", "19 0.935524 0.000295 1.000759 Stream_TRIAD " ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = build_clustering_df(single_node_thicket, metadata_cols + topdown_cols + [\"speedup\"])\n", "df" ] }, { "cell_type": "markdown", "id": "f91e0e6d", "metadata": { "papermill": { "duration": 0.004184, "end_time": "2024-09-06T18:35:15.244139", "exception": false, "start_time": "2024-09-06T18:35:15.239955", "status": "completed" }, "tags": [] }, "source": [ "**This cell is optional!**\n", "\n", "If desired, run this cell to create normalized versions of the specified metrics" ] }, { "cell_type": "code", "execution_count": 22, "id": "1ea23bf3", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.252049Z", "iopub.status.busy": "2024-09-06T18:35:15.251901Z", "iopub.status.idle": "2024-09-06T18:35:15.260788Z", "shell.execute_reply": "2024-09-06T18:35:15.260402Z" }, "papermill": { "duration": 0.013881, "end_time": "2024-09-06T18:35:15.261659", "exception": false, "start_time": "2024-09-06T18:35:15.247778", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
opt_levelopt_level_intcompilerRetiringFrontend boundBackend boundBad speculationspeedupnameRetiring_normFrontend bound_normBackend bound_normBad speculation_normspeedup_norm
0-O00g++-10.3.10.0577560.0072830.9347450.0002151.000000Stream_ADD-0.4519570.3575310.3044060.187331-0.566466
1-O22g++-10.3.10.0564470.0069570.9364930.0001031.015401Stream_ADD-0.5629960.2425170.428783-0.6942270.994632
2-O11g++-10.3.10.0567570.0070640.9362310.0000001.007056Stream_ADD-0.5367000.2802670.410141-1.5049450.148736
3-O33g++-10.3.10.0569020.0068090.9359970.0002920.994071Stream_ADD-0.5244000.1903020.3934910.793402-1.167452
4-O00g++-10.3.10.0502300.0009320.9484210.0004171.000000Stream_COPY-1.090369-1.8831231.2775131.777283-0.566466
5-O22g++-10.3.10.0505110.0007830.9484390.0002671.022479Stream_COPY-1.066533-1.9356911.2787940.5966261.711995
6-O11g++-10.3.10.0500610.0009320.9488700.0001371.004292Stream_COPY-1.104705-1.8831231.309461-0.426611-0.131445
7-O33g++-10.3.10.0501000.0007390.9490180.0001430.998211Stream_COPY-1.101397-1.9512141.319992-0.379385-0.747844
8-O00g++-10.3.10.0852350.0091520.9052750.0003371.000000Stream_DOT1.8790201.016920-1.7925141.147599-0.566466
9-O22g++-10.3.10.0830240.0091740.9078570.0000001.005844Stream_DOT1.6914661.024682-1.608793-1.5049450.025889
10-O11g++-10.3.10.0838230.0093100.9064850.0003811.004005Stream_DOT1.7592431.072663-1.7064171.493925-0.160496
11-O33g++-10.3.10.0843160.0090450.9064150.0002240.991317Stream_DOT1.8010630.979170-1.7113980.258170-1.446604
12-O00g++-10.3.10.0677400.0076020.9244280.0002291.000000Stream_MUL0.3949610.470075-0.4296940.297526-0.566466
13-O22g++-10.3.10.0665280.0073750.9260110.0000861.023686Stream_MUL0.2921500.389989-0.317057-0.8280341.834334
14-O11g++-10.3.10.0663690.0074010.9263590.0000001.005575Stream_MUL0.2786630.399162-0.292295-1.504945-0.001373
15-O33g++-10.3.10.0670280.0071620.9257040.0001050.998667Stream_MUL0.3345640.314842-0.338901-0.678484-0.701590
16-O00g++-10.3.10.0573870.0071980.9351090.0003071.000000Stream_TRIAD-0.4832580.3275430.3303060.911468-0.566466
17-O22g++-10.3.10.0575090.0067340.9355010.0002561.029841Stream_TRIAD-0.4729090.1638420.3581980.5100442.458264
18-O11g++-10.3.10.0567600.0067550.9364560.0000301.010570Stream_TRIAD-0.5364450.1712510.426151-1.2688130.504867
19-O33g++-10.3.10.0571960.0069850.9355240.0002951.000759Stream_TRIAD-0.4994600.2523950.3598350.817015-0.489583
\n", "
" ], "text/plain": [ " opt_level opt_level_int compiler Retiring Frontend bound \\\n", "0 -O0 0 g++-10.3.1 0.057756 0.007283 \n", "1 -O2 2 g++-10.3.1 0.056447 0.006957 \n", "2 -O1 1 g++-10.3.1 0.056757 0.007064 \n", "3 -O3 3 g++-10.3.1 0.056902 0.006809 \n", "4 -O0 0 g++-10.3.1 0.050230 0.000932 \n", "5 -O2 2 g++-10.3.1 0.050511 0.000783 \n", "6 -O1 1 g++-10.3.1 0.050061 0.000932 \n", "7 -O3 3 g++-10.3.1 0.050100 0.000739 \n", "8 -O0 0 g++-10.3.1 0.085235 0.009152 \n", "9 -O2 2 g++-10.3.1 0.083024 0.009174 \n", "10 -O1 1 g++-10.3.1 0.083823 0.009310 \n", "11 -O3 3 g++-10.3.1 0.084316 0.009045 \n", "12 -O0 0 g++-10.3.1 0.067740 0.007602 \n", "13 -O2 2 g++-10.3.1 0.066528 0.007375 \n", "14 -O1 1 g++-10.3.1 0.066369 0.007401 \n", "15 -O3 3 g++-10.3.1 0.067028 0.007162 \n", "16 -O0 0 g++-10.3.1 0.057387 0.007198 \n", "17 -O2 2 g++-10.3.1 0.057509 0.006734 \n", "18 -O1 1 g++-10.3.1 0.056760 0.006755 \n", "19 -O3 3 g++-10.3.1 0.057196 0.006985 \n", "\n", " Backend bound Bad speculation speedup name Retiring_norm \\\n", "0 0.934745 0.000215 1.000000 Stream_ADD -0.451957 \n", "1 0.936493 0.000103 1.015401 Stream_ADD -0.562996 \n", "2 0.936231 0.000000 1.007056 Stream_ADD -0.536700 \n", "3 0.935997 0.000292 0.994071 Stream_ADD -0.524400 \n", "4 0.948421 0.000417 1.000000 Stream_COPY -1.090369 \n", "5 0.948439 0.000267 1.022479 Stream_COPY -1.066533 \n", "6 0.948870 0.000137 1.004292 Stream_COPY -1.104705 \n", "7 0.949018 0.000143 0.998211 Stream_COPY -1.101397 \n", "8 0.905275 0.000337 1.000000 Stream_DOT 1.879020 \n", "9 0.907857 0.000000 1.005844 Stream_DOT 1.691466 \n", "10 0.906485 0.000381 1.004005 Stream_DOT 1.759243 \n", "11 0.906415 0.000224 0.991317 Stream_DOT 1.801063 \n", "12 0.924428 0.000229 1.000000 Stream_MUL 0.394961 \n", "13 0.926011 0.000086 1.023686 Stream_MUL 0.292150 \n", "14 0.926359 0.000000 1.005575 Stream_MUL 0.278663 \n", "15 0.925704 0.000105 0.998667 Stream_MUL 0.334564 \n", "16 0.935109 0.000307 1.000000 Stream_TRIAD -0.483258 \n", "17 0.935501 0.000256 1.029841 Stream_TRIAD -0.472909 \n", "18 0.936456 0.000030 1.010570 Stream_TRIAD -0.536445 \n", "19 0.935524 0.000295 1.000759 Stream_TRIAD -0.499460 \n", "\n", " Frontend bound_norm Backend bound_norm Bad speculation_norm \\\n", "0 0.357531 0.304406 0.187331 \n", "1 0.242517 0.428783 -0.694227 \n", "2 0.280267 0.410141 -1.504945 \n", "3 0.190302 0.393491 0.793402 \n", "4 -1.883123 1.277513 1.777283 \n", "5 -1.935691 1.278794 0.596626 \n", "6 -1.883123 1.309461 -0.426611 \n", "7 -1.951214 1.319992 -0.379385 \n", "8 1.016920 -1.792514 1.147599 \n", "9 1.024682 -1.608793 -1.504945 \n", "10 1.072663 -1.706417 1.493925 \n", "11 0.979170 -1.711398 0.258170 \n", "12 0.470075 -0.429694 0.297526 \n", "13 0.389989 -0.317057 -0.828034 \n", "14 0.399162 -0.292295 -1.504945 \n", "15 0.314842 -0.338901 -0.678484 \n", "16 0.327543 0.330306 0.911468 \n", "17 0.163842 0.358198 0.510044 \n", "18 0.171251 0.426151 -1.268813 \n", "19 0.252395 0.359835 0.817015 \n", "\n", " speedup_norm \n", "0 -0.566466 \n", "1 0.994632 \n", "2 0.148736 \n", "3 -1.167452 \n", "4 -0.566466 \n", "5 1.711995 \n", "6 -0.131445 \n", "7 -0.747844 \n", "8 -0.566466 \n", "9 0.025889 \n", "10 -0.160496 \n", "11 -1.446604 \n", "12 -0.566466 \n", "13 1.834334 \n", "14 -0.001373 \n", "15 -0.701590 \n", "16 -0.566466 \n", "17 2.458264 \n", "18 0.504867 \n", "19 -0.489583 " ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = normalize_data(df, topdown_cols + [\"speedup\"])\n", "df" ] }, { "cell_type": "markdown", "id": "bc0832fc", "metadata": { "papermill": { "duration": 0.003958, "end_time": "2024-09-06T18:35:15.269878", "exception": false, "start_time": "2024-09-06T18:35:15.265920", "status": "completed" }, "tags": [] }, "source": [ "Cluster the topdown metrics with respect to exclusive time.\n", "\n", "If `topdown_scaling_factor` is set to any value besides 1, the topdown metrics will be multiplied by that value before clustering and divided by that value after clustering.\n", "\n", "If `use_normalization` is true, use the normalized metrics calculated by `normalize_data` for clustering instead of the vanilla versions.\n", "\n", "If `silhouette_mode` (in the next cell) is true, don't cluster. Instead run a Silhouette analysis on the data to find the correct K values." ] }, { "cell_type": "code", "execution_count": 23, "id": "8e8f0f53", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.278750Z", "iopub.status.busy": "2024-09-06T18:35:15.278634Z", "iopub.status.idle": "2024-09-06T18:35:15.280869Z", "shell.execute_reply": "2024-09-06T18:35:15.280455Z" }, "papermill": { "duration": 0.007623, "end_time": "2024-09-06T18:35:15.281628", "exception": false, "start_time": "2024-09-06T18:35:15.274005", "status": "completed" }, "tags": [] }, "outputs": [], "source": [ "silhouette_mode = False" ] }, { "cell_type": "code", "execution_count": 24, "id": "9c1f94a1", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.290427Z", "iopub.status.busy": "2024-09-06T18:35:15.290321Z", "iopub.status.idle": "2024-09-06T18:35:15.410948Z", "shell.execute_reply": "2024-09-06T18:35:15.410328Z" }, "papermill": { "duration": 0.125686, "end_time": "2024-09-06T18:35:15.411695", "exception": false, "start_time": "2024-09-06T18:35:15.286009", "status": "completed" }, "scrolled": false, "tags": [] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "speedup_norm Retiring_norm\n", "speedup_norm Frontend bound_norm\n", "speedup_norm Backend bound_norm\n", "speedup_norm Bad speculation_norm\n" ] } ], "source": [ "cluster_centers = []\n", "cluster_labels = []\n", "pairs = list(product([\"speedup\"], topdown_cols))\n", "for x, y in pairs:\n", " nc = 8\n", " if y == \"Retiring\":\n", " nc = n_clusters_retiring\n", " elif y == \"Frontend bound\":\n", " nc = n_clusters_frontend_bound\n", " elif y == \"Backend bound\":\n", " nc = n_clusters_backend_bound\n", " elif y == \"Bad speculation\":\n", " nc = n_clusters_bad_speculation\n", " df[y] = df[y] * topdown_scaling_factor\n", " if silhouette_mode:\n", " x_met = x\n", " y_met = y\n", " if use_normalization:\n", " x_met = \"{}_norm\".format(x_met)\n", " y_met = \"{}_norm\".format(y_met)\n", " print(\"Printing Silhouette Test for: {} vs {}\".format(x_met, y_met), end=\"\\n\\n\")\n", " silhouette_test(df, x_met, y_met)\n", " else:\n", " if use_normalization:\n", " clab, cc, df = cluster_data(df, \"{}_norm\".format(x), \"{}_norm\".format(y), n_clusters=nc)\n", " else:\n", " clab, cc, df = cluster_data(df, x, y, n_clusters=nc)\n", " df[y] = df[y] / topdown_scaling_factor\n", " cc = [(sc[0], sc[1]/topdown_scaling_factor) for sc in cc]\n", " cluster_centers.append(cc)\n", " cluster_labels.append(clab)" ] }, { "cell_type": "markdown", "id": "95d78248", "metadata": { "papermill": { "duration": 0.003697, "end_time": "2024-09-06T18:35:15.420250", "exception": false, "start_time": "2024-09-06T18:35:15.416553", "status": "completed" }, "tags": [] }, "source": [ "These two cells simply print out the post-clustering DataFrame and the centroids" ] }, { "cell_type": "code", "execution_count": 25, "id": "a062f169", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.429848Z", "iopub.status.busy": "2024-09-06T18:35:15.429618Z", "iopub.status.idle": "2024-09-06T18:35:15.450475Z", "shell.execute_reply": "2024-09-06T18:35:15.449930Z" }, "papermill": { "duration": 0.026974, "end_time": "2024-09-06T18:35:15.451292", "exception": false, "start_time": "2024-09-06T18:35:15.424318", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
opt_levelopt_level_intcompilerRetiringFrontend boundBackend boundBad speculationspeedupnameRetiring_norm...Bad speculation_normspeedup_normRetiring_cluster_idRetiring_clusterFrontend bound_cluster_idFrontend bound_clusterBackend bound_cluster_idBackend bound_clusterBad speculation_cluster_idBad speculation_cluster
3-O33g++-10.3.10.0569020.0068090.9359970.0002920.994071Stream_ADD-0.524400...0.793402-1.1674520Cluster 00Cluster 00Cluster 02Cluster 2
0-O00g++-10.3.10.0577560.0072830.9347450.0002151.000000Stream_ADD-0.451957...0.187331-0.5664660Cluster 00Cluster 00Cluster 02Cluster 2
2-O11g++-10.3.10.0567570.0070640.9362310.0000001.007056Stream_ADD-0.536700...-1.5049450.1487360Cluster 00Cluster 00Cluster 00Cluster 0
1-O22g++-10.3.10.0564470.0069570.9364930.0001031.015401Stream_ADD-0.562996...-0.6942270.9946322Cluster 21Cluster 11Cluster 11Cluster 1
7-O33g++-10.3.10.0501000.0007390.9490180.0001430.998211Stream_COPY-1.101397...-0.379385-0.7478440Cluster 02Cluster 20Cluster 00Cluster 0
4-O00g++-10.3.10.0502300.0009320.9484210.0004171.000000Stream_COPY-1.090369...1.777283-0.5664660Cluster 02Cluster 20Cluster 02Cluster 2
6-O11g++-10.3.10.0500610.0009320.9488700.0001371.004292Stream_COPY-1.104705...-0.426611-0.1314450Cluster 02Cluster 20Cluster 00Cluster 0
5-O22g++-10.3.10.0505110.0007830.9484390.0002671.022479Stream_COPY-1.066533...0.5966261.7119952Cluster 23Cluster 31Cluster 13Cluster 3
11-O33g++-10.3.10.0843160.0090450.9064150.0002240.991317Stream_DOT1.801063...0.258170-1.4466041Cluster 10Cluster 02Cluster 22Cluster 2
8-O00g++-10.3.10.0852350.0091520.9052750.0003371.000000Stream_DOT1.879020...1.147599-0.5664661Cluster 10Cluster 02Cluster 22Cluster 2
10-O11g++-10.3.10.0838230.0093100.9064850.0003811.004005Stream_DOT1.759243...1.493925-0.1604961Cluster 10Cluster 02Cluster 22Cluster 2
9-O22g++-10.3.10.0830240.0091740.9078570.0000001.005844Stream_DOT1.691466...-1.5049450.0258891Cluster 10Cluster 02Cluster 20Cluster 0
15-O33g++-10.3.10.0670280.0071620.9257040.0001050.998667Stream_MUL0.334564...-0.678484-0.7015900Cluster 00Cluster 00Cluster 00Cluster 0
12-O00g++-10.3.10.0677400.0076020.9244280.0002291.000000Stream_MUL0.394961...0.297526-0.5664660Cluster 00Cluster 00Cluster 02Cluster 2
14-O11g++-10.3.10.0663690.0074010.9263590.0000001.005575Stream_MUL0.278663...-1.504945-0.0013730Cluster 00Cluster 00Cluster 00Cluster 0
13-O22g++-10.3.10.0665280.0073750.9260110.0000861.023686Stream_MUL0.292150...-0.8280341.8343342Cluster 21Cluster 11Cluster 11Cluster 1
16-O00g++-10.3.10.0573870.0071980.9351090.0003071.000000Stream_TRIAD-0.483258...0.911468-0.5664660Cluster 00Cluster 00Cluster 02Cluster 2
19-O33g++-10.3.10.0571960.0069850.9355240.0002951.000759Stream_TRIAD-0.499460...0.817015-0.4895830Cluster 00Cluster 00Cluster 02Cluster 2
18-O11g++-10.3.10.0567600.0067550.9364560.0000301.010570Stream_TRIAD-0.536445...-1.2688130.5048670Cluster 00Cluster 00Cluster 01Cluster 1
17-O22g++-10.3.10.0575090.0067340.9355010.0002561.029841Stream_TRIAD-0.472909...0.5100442.4582642Cluster 21Cluster 11Cluster 13Cluster 3
\n", "

20 rows Ă— 22 columns

\n", "
" ], "text/plain": [ " opt_level opt_level_int compiler Retiring Frontend bound \\\n", "3 -O3 3 g++-10.3.1 0.056902 0.006809 \n", "0 -O0 0 g++-10.3.1 0.057756 0.007283 \n", "2 -O1 1 g++-10.3.1 0.056757 0.007064 \n", "1 -O2 2 g++-10.3.1 0.056447 0.006957 \n", "7 -O3 3 g++-10.3.1 0.050100 0.000739 \n", "4 -O0 0 g++-10.3.1 0.050230 0.000932 \n", "6 -O1 1 g++-10.3.1 0.050061 0.000932 \n", "5 -O2 2 g++-10.3.1 0.050511 0.000783 \n", "11 -O3 3 g++-10.3.1 0.084316 0.009045 \n", "8 -O0 0 g++-10.3.1 0.085235 0.009152 \n", "10 -O1 1 g++-10.3.1 0.083823 0.009310 \n", "9 -O2 2 g++-10.3.1 0.083024 0.009174 \n", "15 -O3 3 g++-10.3.1 0.067028 0.007162 \n", "12 -O0 0 g++-10.3.1 0.067740 0.007602 \n", "14 -O1 1 g++-10.3.1 0.066369 0.007401 \n", "13 -O2 2 g++-10.3.1 0.066528 0.007375 \n", "16 -O0 0 g++-10.3.1 0.057387 0.007198 \n", "19 -O3 3 g++-10.3.1 0.057196 0.006985 \n", "18 -O1 1 g++-10.3.1 0.056760 0.006755 \n", "17 -O2 2 g++-10.3.1 0.057509 0.006734 \n", "\n", " Backend bound Bad speculation speedup name Retiring_norm \\\n", "3 0.935997 0.000292 0.994071 Stream_ADD -0.524400 \n", "0 0.934745 0.000215 1.000000 Stream_ADD -0.451957 \n", "2 0.936231 0.000000 1.007056 Stream_ADD -0.536700 \n", "1 0.936493 0.000103 1.015401 Stream_ADD -0.562996 \n", "7 0.949018 0.000143 0.998211 Stream_COPY -1.101397 \n", "4 0.948421 0.000417 1.000000 Stream_COPY -1.090369 \n", "6 0.948870 0.000137 1.004292 Stream_COPY -1.104705 \n", "5 0.948439 0.000267 1.022479 Stream_COPY -1.066533 \n", "11 0.906415 0.000224 0.991317 Stream_DOT 1.801063 \n", "8 0.905275 0.000337 1.000000 Stream_DOT 1.879020 \n", "10 0.906485 0.000381 1.004005 Stream_DOT 1.759243 \n", "9 0.907857 0.000000 1.005844 Stream_DOT 1.691466 \n", "15 0.925704 0.000105 0.998667 Stream_MUL 0.334564 \n", "12 0.924428 0.000229 1.000000 Stream_MUL 0.394961 \n", "14 0.926359 0.000000 1.005575 Stream_MUL 0.278663 \n", "13 0.926011 0.000086 1.023686 Stream_MUL 0.292150 \n", "16 0.935109 0.000307 1.000000 Stream_TRIAD -0.483258 \n", "19 0.935524 0.000295 1.000759 Stream_TRIAD -0.499460 \n", "18 0.936456 0.000030 1.010570 Stream_TRIAD -0.536445 \n", "17 0.935501 0.000256 1.029841 Stream_TRIAD -0.472909 \n", "\n", " ... Bad speculation_norm speedup_norm Retiring_cluster_id \\\n", "3 ... 0.793402 -1.167452 0 \n", "0 ... 0.187331 -0.566466 0 \n", "2 ... -1.504945 0.148736 0 \n", "1 ... -0.694227 0.994632 2 \n", "7 ... -0.379385 -0.747844 0 \n", "4 ... 1.777283 -0.566466 0 \n", "6 ... -0.426611 -0.131445 0 \n", "5 ... 0.596626 1.711995 2 \n", "11 ... 0.258170 -1.446604 1 \n", "8 ... 1.147599 -0.566466 1 \n", "10 ... 1.493925 -0.160496 1 \n", "9 ... -1.504945 0.025889 1 \n", "15 ... -0.678484 -0.701590 0 \n", "12 ... 0.297526 -0.566466 0 \n", "14 ... -1.504945 -0.001373 0 \n", "13 ... -0.828034 1.834334 2 \n", "16 ... 0.911468 -0.566466 0 \n", "19 ... 0.817015 -0.489583 0 \n", "18 ... -1.268813 0.504867 0 \n", "17 ... 0.510044 2.458264 2 \n", "\n", " Retiring_cluster Frontend bound_cluster_id Frontend bound_cluster \\\n", "3 Cluster 0 0 Cluster 0 \n", "0 Cluster 0 0 Cluster 0 \n", "2 Cluster 0 0 Cluster 0 \n", "1 Cluster 2 1 Cluster 1 \n", "7 Cluster 0 2 Cluster 2 \n", "4 Cluster 0 2 Cluster 2 \n", "6 Cluster 0 2 Cluster 2 \n", "5 Cluster 2 3 Cluster 3 \n", "11 Cluster 1 0 Cluster 0 \n", "8 Cluster 1 0 Cluster 0 \n", "10 Cluster 1 0 Cluster 0 \n", "9 Cluster 1 0 Cluster 0 \n", "15 Cluster 0 0 Cluster 0 \n", "12 Cluster 0 0 Cluster 0 \n", "14 Cluster 0 0 Cluster 0 \n", "13 Cluster 2 1 Cluster 1 \n", "16 Cluster 0 0 Cluster 0 \n", "19 Cluster 0 0 Cluster 0 \n", "18 Cluster 0 0 Cluster 0 \n", "17 Cluster 2 1 Cluster 1 \n", "\n", " Backend bound_cluster_id Backend bound_cluster \\\n", "3 0 Cluster 0 \n", "0 0 Cluster 0 \n", "2 0 Cluster 0 \n", "1 1 Cluster 1 \n", "7 0 Cluster 0 \n", "4 0 Cluster 0 \n", "6 0 Cluster 0 \n", "5 1 Cluster 1 \n", "11 2 Cluster 2 \n", "8 2 Cluster 2 \n", "10 2 Cluster 2 \n", "9 2 Cluster 2 \n", "15 0 Cluster 0 \n", "12 0 Cluster 0 \n", "14 0 Cluster 0 \n", "13 1 Cluster 1 \n", "16 0 Cluster 0 \n", "19 0 Cluster 0 \n", "18 0 Cluster 0 \n", "17 1 Cluster 1 \n", "\n", " Bad speculation_cluster_id Bad speculation_cluster \n", "3 2 Cluster 2 \n", "0 2 Cluster 2 \n", "2 0 Cluster 0 \n", "1 1 Cluster 1 \n", "7 0 Cluster 0 \n", "4 2 Cluster 2 \n", "6 0 Cluster 0 \n", "5 3 Cluster 3 \n", "11 2 Cluster 2 \n", "8 2 Cluster 2 \n", "10 2 Cluster 2 \n", "9 0 Cluster 0 \n", "15 0 Cluster 0 \n", "12 2 Cluster 2 \n", "14 0 Cluster 0 \n", "13 1 Cluster 1 \n", "16 2 Cluster 2 \n", "19 2 Cluster 2 \n", "18 1 Cluster 1 \n", "17 3 Cluster 3 \n", "\n", "[20 rows x 22 columns]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.sort_values(by=[\"name\", \"speedup\"])" ] }, { "cell_type": "code", "execution_count": 26, "id": "2cb3f41b", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.465800Z", "iopub.status.busy": "2024-09-06T18:35:15.465648Z", "iopub.status.idle": "2024-09-06T18:35:15.469779Z", "shell.execute_reply": "2024-09-06T18:35:15.469380Z" }, "papermill": { "duration": 0.013938, "end_time": "2024-09-06T18:35:15.470450", "exception": false, "start_time": "2024-09-06T18:35:15.456512", "status": "completed" }, "tags": [] }, "outputs": [ { "data": { "text/plain": [ "[[(-0.4042957384725225, -0.4433752753596362),\n", " (-0.5369190461812909, 1.7826978227050168),\n", " (1.749806261598802, -0.4525719966261081)],\n", " [(-0.42718993699028074, 0.5274463486959997),\n", " (1.762410152328975, 0.26544919660540234),\n", " (-0.48191862184059486, -1.9058198610436912),\n", " (1.7119945894082833, -1.9356905397331174)],\n", " [(-0.4042957384725225, 0.42253368260272484),\n", " (1.749806261598802, 0.4371796258076567),\n", " (-0.5369190461812909, -1.704780673615863)],\n", " [(-0.2346044647104745, -0.9998856205690471),\n", " (1.1112777501485172, -0.9303580190338078),\n", " (-0.6773849736286504, 0.8537464731912041),\n", " (2.085129150237462, 0.5533347608974337)]]" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cluster_centers" ] }, { "cell_type": "markdown", "id": "f0965157", "metadata": { "papermill": { "duration": 0.004349, "end_time": "2024-09-06T18:35:15.479350", "exception": false, "start_time": "2024-09-06T18:35:15.475001", "status": "completed" }, "tags": [] }, "source": [ "## 6. Plot the Clusters as Scatterplots\n", "\n", "The legends will not be printed, but they will be dumped to file." ] }, { "cell_type": "code", "execution_count": 27, "id": "27ce46ff", "metadata": { "execution": { "iopub.execute_input": "2024-09-06T18:35:15.488801Z", "iopub.status.busy": "2024-09-06T18:35:15.488689Z", "iopub.status.idle": "2024-09-06T18:35:15.834806Z", "shell.execute_reply": "2024-09-06T18:35:15.834311Z" }, "papermill": { "duration": 0.352059, "end_time": "2024-09-06T18:35:15.835567", "exception": false, "start_time": "2024-09-06T18:35:15.483508", "status": "completed" }, "scrolled": false, "tags": [] }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" }, { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "sns_handles = None\n", "sns_labels = None\n", "\n", "is_first = True\n", "\n", "for clab, mets, ccs in zip(cluster_labels, pairs, cluster_centers):\n", " scatter_palette = sns.color_palette(\"colorblind\", n_colors=4).as_hex()\n", " tmp = scatter_palette[1]\n", " scatter_palette[1] = scatter_palette[2]\n", " scatter_palette[2] = tmp\n", " ax = plot_clusters(\n", " df,\n", " mets[0],\n", " mets[1],\n", " hue_metric=\"opt_level\",\n", " marker_style_metric=\"{}_cluster\".format(mets[1]),\n", " swap_marker_and_hue=False,\n", " palette=scatter_palette,\n", " s=3*mpl.rcParams[\"lines.markersize\"]**2\n", " )\n", " ax.tick_params(\n", " axis=\"both\",\n", " which=\"major\",\n", " labelsize=1.5*ax.get_xticklabels()[0].get_fontsize()\n", " )\n", " xlab = ax.xaxis.get_label()\n", " ax.set_xlabel(\n", " \"Speedup\",#xlab.get_text(),\n", " fontfamily=xlab.get_fontfamily(),\n", " fontsize=1.5*xlab.get_fontsize(),\n", " fontstyle=xlab.get_fontstyle(),\n", " fontvariant=xlab.get_fontvariant(),\n", " fontweight=xlab.get_fontweight(),\n", " )\n", " ylab = ax.yaxis.get_label()\n", " ax.set_ylabel(\n", " ylab.get_text(),\n", " fontfamily=ylab.get_fontfamily(),\n", " fontsize=1.5*ylab.get_fontsize(),\n", " fontstyle=ylab.get_fontstyle(),\n", " fontvariant=ylab.get_fontvariant(),\n", " fontweight=ylab.get_fontweight(),\n", " )\n", " ax.set_xlim(0.99, 1.04)\n", " ax.set_ylim(-0.05, 1.0)\n", " plot_lines_by_profile(df, mets[0], mets[1], \"name\", ax)\n", " legend = ax.legend(loc=\"center left\", bbox_to_anchor=(1.25, 0.5), ncol=2)\n", " legend.get_texts()[0].set_text(\"Optimization Level\")\n", " legend.get_texts()[5].set_text(\"Clusters\")\n", " legend_fig = legend.figure\n", " legend_fig.canvas.draw()\n", " bbox = legend.get_window_extent().transformed(legend_fig.dpi_scale_trans.inverted())\n", " legend.remove()\n", " plt.tight_layout()\n", " if is_first:\n", " sns_handles, sns_labels = ax.get_legend_handles_labels()\n", " is_first = False\n", " plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.9.12" }, "papermill": { "default_parameters": {}, "duration": 3.831036, "end_time": "2024-09-06T18:35:16.268159", "environment_variables": {}, "exception": null, "input_path": "02_thicket_rajaperf_clustering.ipynb", "output_path": "02_thicket_rajaperf_clustering.ipynb", "parameters": {}, "start_time": "2024-09-06T18:35:12.437123", "version": "2.5.0" } }, "nbformat": 4, "nbformat_minor": 5 }