|
13 | 13 | "\n", |
14 | 14 | "This analysis is called Orojenesis, and it is introduced in \"Mind the Gap: Attainable\n", |
15 | 15 | "Data Movement and Operational Intensity Bounds for Tensor Algorithms\" by Qijing Huang,\n", |
16 | | - "Po-An Tsai, Joel S. Emer, Angshuman Parashar.\n", |
| 16 | + "Po-An Tsai, Joel S. Emer, Angshuman Parashar. The paper includes additional analysis and\n", |
| 17 | + "example use cases for this tradeoff.\n", |
17 | 18 | "\n", |
18 | 19 | "Our plan to do this analysis is the following:\n", |
19 | 20 | "- Set up a simple architecture with a main memory and a global buffer\n", |
|
59 | 60 | "metadata": {}, |
60 | 61 | "outputs": [], |
61 | 62 | "source": [ |
62 | | - "# import accelforge as af\n", |
63 | | - "# from pathlib import Path\n", |
| 63 | + "import accelforge as af\n", |
| 64 | + "from pathlib import Path\n", |
64 | 65 | "\n", |
65 | | - "# examples_dir = Path(\"../../examples\")\n", |
| 66 | + "examples_dir = Path(\"../../examples\")\n", |
66 | 67 | "\n", |
67 | | - "# spec = af.Spec.from_yaml(\n", |
68 | | - "# af.examples.arches.orojenesis,\n", |
69 | | - "# af.examples.workloads.matmuls,\n", |
70 | | - "# jinja_parse_data={\"N_EINSUMS\": 1, \"M\": 4096, \"KN\": 4096},\n", |
71 | | - "# )\n", |
72 | | - "# spec.mapper.metrics = af.Metrics.ENERGY | af.Metrics.RESOURCE_USAGE" |
73 | | - ] |
74 | | - }, |
75 | | - { |
76 | | - "cell_type": "code", |
77 | | - "execution_count": null, |
78 | | - "id": "724ba697", |
79 | | - "metadata": {}, |
80 | | - "outputs": [], |
81 | | - "source": [ |
82 | | - "# # af.set_n_parallel_jobs(1)\n", |
83 | | - "# results = spec.map_workload_to_arch()" |
| 68 | + "spec = af.Spec.from_yaml(\n", |
| 69 | + " af.examples.arches.orojenesis,\n", |
| 70 | + " af.examples.workloads.matmuls,\n", |
| 71 | + " jinja_parse_data={\"N_EINSUMS\": 1, \"M\": 4096, \"KN\": 4096},\n", |
| 72 | + ")\n", |
| 73 | + "spec.mapper.metrics = af.Metrics.ENERGY | af.Metrics.RESOURCE_USAGE\n", |
| 74 | + "results = spec.map_workload_to_arch()" |
84 | 75 | ] |
85 | 76 | }, |
86 | 77 | { |
87 | 78 | "cell_type": "markdown", |
88 | 79 | "id": "0c06b6c5", |
89 | 80 | "metadata": {}, |
90 | 81 | "source": [ |
91 | | - "Let's plot the results." |
| 82 | + "Let's plot the results. We can see that, on a log-log scale, the curve appears to be\n", |
| 83 | + "linear." |
92 | 84 | ] |
93 | 85 | }, |
94 | 86 | { |
|
98 | 90 | "metadata": {}, |
99 | 91 | "outputs": [], |
100 | 92 | "source": [ |
101 | | - "# import matplotlib.pyplot as plt\n", |
102 | | - "\n", |
103 | | - "# results.data.sort_values(\"Total<SEP>energy\", ascending=True, inplace=True)\n", |
104 | | - "# plt.plot(\n", |
105 | | - "# [x * spec.arch.find(\"GlobalBuffer\").size for x in results.resource_usage()[\"GlobalBuffer\"]],\n", |
106 | | - "# results.energy()\n", |
107 | | - "# )\n", |
108 | | - "# plt.xlabel(\"Global Buffer Size (bits)\")\n", |
109 | | - "# plt.ylabel(\"Lowest-Attainable DRAM Accesses (bits)\")\n", |
110 | | - "# plt.xscale(\"log\")\n", |
111 | | - "# plt.yscale(\"log\")\n", |
112 | | - "# plt.show()\n", |
113 | | - "# # Plotting runoff to the right\n", |
114 | | - "# # arxiv and let timeloop team know\n", |
115 | | - "# # restricted imperfect factorization for spatial fanouts" |
| 93 | + "import matplotlib.pyplot as plt\n", |
| 94 | + "\n", |
| 95 | + "results.data.sort_values(\"Total<SEP>energy\", inplace=True)\n", |
| 96 | + "fig, ax = plt.subplots(figsize=(10, 10))\n", |
| 97 | + "ax.plot(\n", |
| 98 | + " [x * spec.arch.find(\"GlobalBuffer\").size for x in results.resource_usage()[\"GlobalBuffer\"]],\n", |
| 99 | + " results.energy(),\n", |
| 100 | + " marker=\"o\",\n", |
| 101 | + " drawstyle=\"steps-pre\"\n", |
| 102 | + ")\n", |
| 103 | + "ax.set_xlabel(\"On-Chip Memory Size (bits)\")\n", |
| 104 | + "ax.set_ylabel(\"Lowest-Attainable Off-Chip Accesses (bits)\")\n", |
| 105 | + "ax.set_xscale(\"log\")\n", |
| 106 | + "ax.set_yscale(\"log\")\n", |
| 107 | + "plt.show()" |
116 | 108 | ] |
117 | 109 | }, |
118 | 110 | { |
119 | 111 | "cell_type": "markdown", |
120 | 112 | "id": "5bee35e9", |
121 | 113 | "metadata": {}, |
122 | 114 | "source": [ |
| 115 | + "### Fused vs. Unfused\n", |
| 116 | + "\n", |
123 | 117 | "Let's also do a comparison of how the fusion affects this curve. We'll use the same\n", |
124 | | - "architecture, but this time with a larger workload." |
| 118 | + "architecture, but this time with a larger workload. We'll run a GPT-3 6.7B attention\n", |
| 119 | + "head with 8192 tokens.\n", |
| 120 | + "\n", |
| 121 | + "Note: Depending on your machine, this may take 3-15 minutes to run." |
125 | 122 | ] |
126 | 123 | }, |
127 | 124 | { |
|
143 | 140 | " jinja_parse_data={\"N_TOKENS\": 8192}\n", |
144 | 141 | ")\n", |
145 | 142 | "spec.mapper.metrics = af.Metrics.ENERGY | af.Metrics.RESOURCE_USAGE\n", |
146 | | - "# spec.mapper.max_pmapping_templates_per_einsum = 8\n", |
147 | | - "\n", |
148 | 143 | "\n", |
149 | 144 | "# FUSED\n", |
150 | | - "af.set_n_parallel_jobs(1)\n", |
| 145 | + "af.set_n_parallel_jobs(32)\n", |
151 | 146 | "spec.arch.find(\"MainMemory\").tensors.keep = \"~Intermediates\"\n", |
152 | 147 | "spec.arch.find(\"MainMemory\").tensors.may_keep = \"All\"\n", |
153 | | - "results_fused = spec.map_workload_to_arch(einsum_names=[\"Q\"])\n", |
154 | | - "\n", |
| 148 | + "results_fused = spec.map_workload_to_arch()\n", |
155 | 149 | "\n", |
156 | | - "# # UNFUSED\n", |
| 150 | + "# UNFUSED\n", |
157 | 151 | "spec.arch.find(\"MainMemory\").tensors.keep = \"All\"\n", |
158 | | - "results_unfused = spec.map_workload_to_arch(einsum_names=[\"Q\"])#einsum_names=[\"K\", \"QK\"])\n", |
| 152 | + "results_unfused = spec.map_workload_to_arch()" |
| 153 | + ] |
| 154 | + }, |
| 155 | + { |
| 156 | + "cell_type": "markdown", |
| 157 | + "id": "dbb84dfd", |
| 158 | + "metadata": {}, |
| 159 | + "source": [ |
| 160 | + "Let's plot the results. \n", |
| 161 | + "\n", |
| 162 | + "\n", |
| 163 | + "We can see that, for small on-chip memory sizes, there is an approximately-linear\n", |
| 164 | + "relationship between on-chip memory size and the number of off-chip accesses.\n", |
| 165 | + "Additionally in this region, fusion has negligible benefit. These are because the\n", |
| 166 | + "on-chip memory is too small to fully reuse the tensors of any given Einsum, so off-chip\n", |
| 167 | + "accesses are dominated by refetching tensors.\n", |
159 | 168 | "\n", |
160 | | - "# BUG C: Initial stride appearing in the model output when stride == initial" |
| 169 | + "As we increase on-chip memory size, the curve begins to flatten out, and fusion begins\n", |
| 170 | + "to have a larger effect. These are because the available on-chip memory is large enough\n", |
| 171 | + "to store full tensors, which enables multiple fusion opportunities:\n", |
| 172 | + "\n", |
| 173 | + "- Untiled fusion: We can generate a full output tensor, keep it on-chip, then use it for\n", |
| 174 | + " the next Einsum\n", |
| 175 | + "- Tiled fusion: We can keep a full weight tensor on-chip, while consuming input tensor\n", |
| 176 | + " and sending output tensors one-tile-at-a-time from and to other Einsums without going\n", |
| 177 | + " off-chip.\n", |
| 178 | + "\n", |
| 179 | + "Also note that fusion tends to be less helpful if we're refetching tensors multiple\n", |
| 180 | + "times from off-chip, because fusion can save, at most, one fetch of a tensor per Einsum\n", |
| 181 | + "(exchanging with another Einsum happens once). If we're refetching tensors multiple\n", |
| 182 | + "times, we're likely better off using all available capacity to minimize refetches.\n", |
| 183 | + "\n", |
| 184 | + "The curve slowly flattens out, rather than quickly, because each Einsum has a different\n", |
| 185 | + "shape, and some Einsums enter the no-refetch, fusion-helpful region at smaller on-chip\n", |
| 186 | + "memory sizes." |
161 | 187 | ] |
162 | 188 | }, |
163 | 189 | { |
|
170 | 196 | "results_unfused.resource_usage()\n", |
171 | 197 | "results_unfused.columns\n", |
172 | 198 | "import matplotlib.pyplot as plt\n", |
| 199 | + "import numpy as np\n", |
| 200 | + "\n", |
| 201 | + "fig, ax = plt.subplots(figsize=(10, 10))\n", |
173 | 202 | "\n", |
174 | | - "results_fused.data.sort_values(\"Total<SEP>energy\", ascending=True, inplace=True)\n", |
175 | | - "plt.plot(\n", |
176 | | - " results_fused.resource_usage()[\"GlobalBuffer\"],\n", |
| 203 | + "results_fused.data.sort_values(\"Total<SEP>energy\", inplace=True)\n", |
| 204 | + "ax.plot(\n", |
| 205 | + " np.array(results_fused.resource_usage()[\"GlobalBuffer\"]) * spec.arch.find(\"GlobalBuffer\").size,\n", |
177 | 206 | " results_fused.energy(),\n", |
178 | 207 | " label=\"Fused\",\n", |
| 208 | + " marker=\"o\",\n", |
| 209 | + " drawstyle=\"steps-pre\"\n", |
179 | 210 | ")\n", |
180 | 211 | "\n", |
181 | | - "results_unfused.data.sort_values(\"Total<SEP>energy\", ascending=True, inplace=True)\n", |
182 | | - "plt.plot(\n", |
183 | | - " results_unfused.resource_usage()[\"GlobalBuffer\"],\n", |
| 212 | + "results_unfused.data.sort_values(\"Total<SEP>energy\", inplace=True)\n", |
| 213 | + "ax.plot(\n", |
| 214 | + " np.array(results_unfused.resource_usage()[\"GlobalBuffer\"]) * spec.arch.find(\"GlobalBuffer\").size,\n", |
184 | 215 | " results_unfused.energy(),\n", |
185 | 216 | " label=\"Unfused\",\n", |
| 217 | + " marker=\"o\",\n", |
| 218 | + " drawstyle=\"steps-pre\"\n", |
186 | 219 | ")\n", |
187 | | - "plt.xlabel(\"Global Buffer Size (bits)\")\n", |
188 | | - "plt.ylabel(\"Lowest-Attainable DRAM Accesses (bits)\")\n", |
189 | | - "plt.xscale(\"log\")\n", |
190 | | - "plt.yscale(\"log\")\n", |
191 | | - "plt.legend()\n", |
192 | | - "plt.show()\n", |
193 | | - "# results_fused.resource_usage(\"GlobalBuffer\")\n" |
| 220 | + "ax.set_xlabel(\"On-Chip Memory Size (bits)\")\n", |
| 221 | + "ax.set_ylabel(\"Lowest-Attainable Off-Chip Accesses (bits)\")\n", |
| 222 | + "ax.set_xscale(\"log\")\n", |
| 223 | + "ax.set_yscale(\"log\")\n", |
| 224 | + "ax.legend()\n", |
| 225 | + "plt.show()" |
194 | 226 | ] |
195 | 227 | } |
196 | 228 | ], |
197 | 229 | "metadata": { |
198 | 230 | "kernelspec": { |
199 | | - "display_name": "venv", |
| 231 | + "display_name": "Python 3", |
200 | 232 | "language": "python", |
201 | 233 | "name": "python3" |
202 | 234 | }, |
|
210 | 242 | "name": "python", |
211 | 243 | "nbconvert_exporter": "python", |
212 | 244 | "pygments_lexer": "ipython3", |
213 | | - "version": "3.12.3" |
| 245 | + "version": "3.12.11" |
214 | 246 | } |
215 | 247 | }, |
216 | 248 | "nbformat": 4, |
|
0 commit comments