<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Kernel Space]]></title><description><![CDATA[Substack about GPU and system programming]]></description><link>https://kernelspace.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!sK0f!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F330a19d8-f204-4f07-b080-08941a3f97f0_1856x1856.png</url><title>Kernel Space</title><link>https://kernelspace.substack.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 27 Apr 2026 04:04:44 GMT</lastBuildDate><atom:link href="https://kernelspace.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Dmitry Trifonov]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[kernelspace@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[kernelspace@substack.com]]></itunes:email><itunes:name><![CDATA[Dmitry Trifonov]]></itunes:name></itunes:owner><itunes:author><![CDATA[Dmitry Trifonov]]></itunes:author><googleplay:owner><![CDATA[kernelspace@substack.com]]></googleplay:owner><googleplay:email><![CDATA[kernelspace@substack.com]]></googleplay:email><googleplay:author><![CDATA[Dmitry Trifonov]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Surfacing a 60% performance bug in cuBLAS]]></title><description><![CDATA[Broken FP32 SGEMM dispatcher on RTX GPUs]]></description><link>https://kernelspace.substack.com/p/surfacing-a-60-performance-bug-in</link><guid isPermaLink="false">https://kernelspace.substack.com/p/surfacing-a-60-performance-bug-in</guid><dc:creator><![CDATA[Dmitry Trifonov]]></dc:creator><pubDate>Thu, 09 Apr 2026 22:11:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!swH7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!swH7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!swH7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 424w, https://substackcdn.com/image/fetch/$s_!swH7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 848w, https://substackcdn.com/image/fetch/$s_!swH7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!swH7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!swH7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg" width="1280" height="698" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:698,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!swH7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 424w, https://substackcdn.com/image/fetch/$s_!swH7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 848w, https://substackcdn.com/image/fetch/$s_!swH7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!swH7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc024d326-769a-44e2-9fc2-690f61c7e0a1_1280x698.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Every GPU programmer will tell you that you can&#8217;t beat cuBLAS at matrix multiplication. Matmul is the most popular operation by a large margin, and NVIDIA engineers have squeezed their GPUs dry. Of course, that doesn&#8217;t stop thousands of engineers, including myself, from playing this unfair sport.</p><p>I started this project as a learning exercise: write an FP32 SGEMM kernel for the RTX 5090 (Blackwell, sm_120) using the new TMA hardware introduced in Hopper and reach 80&#8211;90% of cuBLAS performance. That was the plan.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kernelspace.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kernel Space! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>While benchmarking, the <strong>batched-mode numbers on the 5090 were 50&#8211;60% higher than cuBLAS</strong> at sizes from 1024 to 8192. That seemed suspiciously good for a learning exercise. So I profiled with <code>ncu</code> to see what was happening, and found that <strong>cuBLAS was dispatching a tiny </strong><code>simt_sgemm_128x32_8x5</code><strong> kernel</strong> for the entire range of batched workloads, running at only 41% FMA pipe utilization (essentially using only 41% of available compute throughput).</p><p>I double-checked it on other GPUs and found out that the same <code>libcublas.so</code> binary uses a larger <code>simt_sgemm_256x128_8x4</code> kernel at 73% FMA pipe utilization on the RTX PRO 6000, and an even better <code>xmma_gemm</code> family at 82% on the H200. RTX GPUs clearly receive less love from NVIDIA.</p><p>The reference kernel that I wrote using the new Blackwell feature &#8212; TMA (Tensor Memory Accelerator) &#8212; is still interesting. It achieves ~68% FMA pipe utilization with ~300 lines of generated C, whereas CUTLASS&#8217;s hand-tuned kernels require thousands of lines of templates to reach 73%. I will break down the TMA implementation to show how you can hit ~80&#8211;120% of cuBLAS performance with 10x less code than traditional templated approaches</p><p>Data and implementation are available in the <a href="https://github.com/cloudrift-ai/deplodock">DeploDock</a> repository &#8212; GPU and LLM deployment, benchmarking, and optimization framework.</p><h2>The Headlines</h2><p><strong>Single matmul on RTX 5090</strong> &#8212; my kernel matches cuBLAS within 5 percentage points of FMA pipe utilization on every size (256 and 512 omitted; the per-call duration is too short and measurement variance is too high):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ehvw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ehvw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 424w, https://substackcdn.com/image/fetch/$s_!ehvw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 848w, https://substackcdn.com/image/fetch/$s_!ehvw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 1272w, https://substackcdn.com/image/fetch/$s_!ehvw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ehvw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png" width="1400" height="491" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:491,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ehvw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 424w, https://substackcdn.com/image/fetch/$s_!ehvw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 848w, https://substackcdn.com/image/fetch/$s_!ehvw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 1272w, https://substackcdn.com/image/fetch/$s_!ehvw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F414ed090-2dc2-4ad9-92a2-2cd1c3492764_1400x491.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Batched matmul on RTX 5090</strong>&#8211;1.4&#8211;1.7&#215; cuBLAS across the board:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7out!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7out!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 424w, https://substackcdn.com/image/fetch/$s_!7out!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 848w, https://substackcdn.com/image/fetch/$s_!7out!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 1272w, https://substackcdn.com/image/fetch/$s_!7out!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7out!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png" width="1400" height="573" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:573,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!7out!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 424w, https://substackcdn.com/image/fetch/$s_!7out!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 848w, https://substackcdn.com/image/fetch/$s_!7out!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 1272w, https://substackcdn.com/image/fetch/$s_!7out!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe450262a-1895-49c6-bf9b-66c73c3dd50b_1400x573.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The batched table is where it gets weird. Here&#8217;s the same <code>cublasSgemmStridedBatched</code> call on three different sm_90/sm_120 GPUs at batch=8, captured by <code>ncu</code>, showing the dispatched kernel and its FMA pipe utilization:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!24B2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!24B2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 424w, https://substackcdn.com/image/fetch/$s_!24B2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 848w, https://substackcdn.com/image/fetch/$s_!24B2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 1272w, https://substackcdn.com/image/fetch/$s_!24B2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!24B2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png" width="1400" height="579" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:579,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!24B2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 424w, https://substackcdn.com/image/fetch/$s_!24B2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 848w, https://substackcdn.com/image/fetch/$s_!24B2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 1272w, https://substackcdn.com/image/fetch/$s_!24B2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feee3c159-68c8-4744-a733-f28e29caf2aa_1400x579.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It is no surprise that cuBLAS schedules a different kernel for different matrix sizes. Kernels might perform differently on different matrix sizes, so cuBLAS tries to choose the best one. However, the behavior on different GPUs is quite different.</p><ul><li><p><strong>H200 (Hopper, sm_90)</strong> mixes the open-source CUTLASS template family at 1024&#8211;2048 with NVIDIA&#8217;s closed-source <code>xmma_gemm</code> family at 4096+. Within <code>xmma_gemm</code> it picks three different tile sizes (32&#215;32 &#8594; 64&#215;128 &#8594; 128&#215;128) escalating with workload. Peak hits 82% FMA pipe utilization.</p></li><li><p><strong>RTX PRO 6000 Blackwell Max-Q (sm_120)</strong> escalates within the CUTLASS family across three tile sizes (128&#215;64 &#8594; 128&#215;128 &#8594; 256&#215;128), increasing FMA pipe utilization from 64% to 73%. Less sophisticated than H200, but still good. The one bug: at 256 / 512, it falls into a legacy <code>magma_sgemmEx_kernel</code> code path at 32% FMA pipe util. (MAGMA was NVIDIA&#8217;s pre-CUTLASS linear algebra library from the early 2010s, largely absorbed into cuBLAS &#8212; the fact that its kernels still appear in the dispatch path on a 2026 GPU is a window into how deep the legacy debt goes in NVIDIA&#8217;s stack.)</p></li><li><p><strong>RTX 5090 (sm_120)</strong> picks the same <code>simt_sgemm_128x32_8x5</code> kernel for <strong>every</strong> workload from 256&#215;256 (&#8776;microseconds per call) all the way to 8192&#215;8192&#215;8 batches (&#8776;0.5 seconds per call). FMA pipe utilization stuck in the 33&#8211;42% band across the entire 32&#215; range of linear dimensions.</p></li></ul><p>This isn&#8217;t a wrong threshold somewhere in the dispatcher. There&#8217;s no escalation at any threshold. The escalation logic for the 5090 sm_120 batched FP32 path is missing entirely.</p><blockquote><p><em>I haven&#8217;t tested kernels on other RTX GPUs like 5070 or 4090, but it is likely that the bug is present on all consumer GPUs.</em></p></blockquote><h2>What About cuBLASLt and Tensor Cores?</h2><p><a href="https://docs.nvidia.com/cuda/cublas/index.html#cublaslt-api">cuBLASLt</a> is NVIDIA&#8217;s &#8220;lightweight&#8221; BLAS API that exposes more control than plain cuBLAS &#8212; you can query available algorithms, set workspace sizes, and force specific compute modes. A natural question: can cuBLASLt&#8217;s algorithm heuristics route around the 5090 batched-dispatch bug? And what about hybrid approaches using tensor cores with FP32 accumulators?</p><p>I tested all cuBLASLt compute modes at 4096&#215;4096:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YLOP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YLOP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 424w, https://substackcdn.com/image/fetch/$s_!YLOP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 848w, https://substackcdn.com/image/fetch/$s_!YLOP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 1272w, https://substackcdn.com/image/fetch/$s_!YLOP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YLOP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png" width="1400" height="315" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:315,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!YLOP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 424w, https://substackcdn.com/image/fetch/$s_!YLOP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 848w, https://substackcdn.com/image/fetch/$s_!YLOP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 1272w, https://substackcdn.com/image/fetch/$s_!YLOP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4880c6c7-f6c8-4fa9-b93d-7cf4a5a6bace_1400x315.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The FP32 path is <strong>locked to SIMT</strong> regardless of heuristic settings &#8212; cuBLASLt picks a <code>simt_sgemm</code> with a different tile (128&#215;128 vs 256&#215;128) but still cooperative <code>cp.async</code> loading, no TMA, and still no path to the dispatcher&#8217;s broken heuristic. The FAST_TF32/BF16 modes switch to tensor cores (<code>tensorop_s1688gemm</code>) and are 36&#8211;48% faster &#8212; but with reduced input precision. Note all three are <code>cutlass_80</code>-prefixed Ampere-era kernels.</p><p>For strict FP32 accuracy, tensor cores aren&#8217;t an option. TF32 truncates to a 10-bit mantissa; BF16 to a 7-bit mantissa. If your workload tolerates ~0.1% relative error, FAST_TF32 is the pragmatic choice. For exact FP32, the cuBLAS dispatcher bug applies, and there&#8217;s no public API to work around it without writing your own kernel.</p><h2>Where My Kernel Fits In</h2><p>My TMA template hits ~68% FMA pipe utilization on every Blackwell SKU we tested and ~71% on Hopper, which means it is about 10% behind cuBLAS, which chooses an efficient kernel, and 40&#8211;60% ahead of underperforming kernels on RTX 5090.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ccjA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ccjA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 424w, https://substackcdn.com/image/fetch/$s_!ccjA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 848w, https://substackcdn.com/image/fetch/$s_!ccjA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 1272w, https://substackcdn.com/image/fetch/$s_!ccjA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ccjA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png" width="1400" height="756" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:756,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ccjA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 424w, https://substackcdn.com/image/fetch/$s_!ccjA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 848w, https://substackcdn.com/image/fetch/$s_!ccjA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 1272w, https://substackcdn.com/image/fetch/$s_!ccjA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb351068d-60f7-4191-9b1b-b45b2c6754c6_1400x756.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I will introduce my kernel now and the technologies used to make it work. To enjoy the following section of this article, it is good to familiarize yourself with general techniques for optimizing the Matmul kernel on the GPU. The <a href="https://siboehm.com/articles/22/CUDA-MMM">How to Optimize a CUDA Matmul Kernel for cuBLAS-like Performance</a> by Simon Boehm is a good starting point.</p><h2>TMA vs LDGSTS: A Quick Primer</h2><p>If you&#8217;re not deep in GPU programming, here&#8217;s the key distinction.</p><p>High-performance matmul kernels tile the computation: load a block of A and B from global memory (slow, ~1 TB/s) into shared memory (fast, ~20 TB/s), compute the partial products from shared memory, repeat. The bottleneck is the efficient transfer of data from global to shared memory. NVIDIA provides two hardware mechanisms for this &#8212; both <a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#asynchronous-data-copies">asynchronous memory copies</a> that bypass registers and transfer data directly.</p><p><strong>LDGSTS (</strong><code>cp.async</code><strong>)</strong> is the traditional way to load data from global memory into shared memory. Every thread in the block participates: thread 0 loads element 0, thread 1 loads element 1, and so on. It&#8217;s cooperative &#8212; 256 threads each issue their own load instruction, generating 256 individual memory transactions. The hardware coalesces these into efficient cache-line transfers, but each thread still spends instruction slots on address computation, load issuance, and shared memory stores. CUTLASS and cuBLAS have used this approach since Ampere.</p><p><strong>TMA (Tensor Memory Accelerator)</strong> is new hardware introduced in Hopper (sm_90) and refined in Blackwell (sm_120). Instead of 256 threads each loading one element, a single thread issues one <code>cp.async.bulk.tensor.2d</code> command that describes the entire 2D tile &#8212; <em>&#8220;load a 32&#215;224 block of floats starting at row R, column C.&#8221;</em> The TMA hardware unit, separate from the CUDA cores, handles the entire transfer via DMA. The other 255 threads contribute zero instructions &#8212; they can compute while the load happens in the background. Blackwell&#8217;s TMA unit shares the same PTX interface as Hopper&#8217;s; the practical difference I observed is that Blackwell favors larger per-thread tiles (TM=28 is optimal on the 5090 vs TM=8 on the H200), suggesting the sm_120 TMA unit has lower per-issue overhead or better pipelining of concurrent descriptors.</p><p>In principle, TMA should be faster than LDGSTS because it removes per-thread loading overhead. In practice, on the workloads I measured, <strong>TMA and well-tuned LDGSTS land within 5% of each other</strong> at the FMA pipe utilization level. What TMA actually buys you is <strong>kernel implementation simplicity</strong> &#8212; you can write a fully-pipelined SGEMM kernel in ~300 lines of generated C, vs the thousands of lines of templated C++ that CUTLASS needs.</p><h2>The TMA Double-Buffer Architecture</h2><p>On Blackwell, the TMA hardware unit can load a 2D tile from global memory to shared memory with a single PTX instruction. One thread issues <code>cp.async.bulk.tensor.2d</code>, the hardware does the rest.</p><p>The kernel uses this in a double-buffer pipeline:</p><pre><code>Tile 0: [TMA loads buf0] [wait] [compute buf0 + TMA loads buf1]
Tile 1:                         [wait buf1] [compute buf1 + TMA loads buf0]
Tile 2:                                     [wait buf0] [compute buf0 + TMA loads buf1]
...</code></pre><p>While all 256 threads compute FMAs from the current buffer, thread 0 issues a TMA command to fill the other buffer. The TMA hardware runs on a separate path from the CUDA cores &#8212; true parallel execution.</p><p>Here&#8217;s the simplified kernel structure (TM=8 variant, 32 accumulators):</p><pre><code>__global__ __launch_bounds__(256)
void fused_matmul(
    const __grid_constant__ CUtensorMap A_tma,
    const __grid_constant__ CUtensorMap B_tma,
    float* C)
{
    extern __shared__ __align__(128) char dsmem[];
    float* smem = (float*)dsmem;
    // Two mbarriers for double-buffer synchronization
    uint64_t* mbar = (uint64_t*)(dsmem + 2 * STAGE * 4);

    // Shared memory addresses for TMA targets
    const int as0 = __cvta_generic_to_shared(&amp;smem[0]);
    const int bs0 = __cvta_generic_to_shared(&amp;smem[A_SIZE]);
    const int as1 = __cvta_generic_to_shared(&amp;smem[STAGE]);
    const int bs1 = __cvta_generic_to_shared(&amp;smem[STAGE + A_SIZE]);

    // Thread identity
    int tid = threadIdx.y * 32 + threadIdx.x;
    int tr = threadIdx.y * TM, tc = threadIdx.x * 4;
    int bm = blockIdx.y * BM, bn = blockIdx.x * BN;

    // Initialize mbarriers (thread 0 only)
    if (tid == 0) {
        mbarrier_init(mbar[0]); mbarrier_init(mbar[1]);
    }
    __syncthreads();

    float c[TM][4] = {};  // Accumulators
    // Pre-load first tile
    if (tid == 0) {
        mbarrier_expect_tx(mbar[0], BYTES);
        tma_load_2d(as0, &amp;A_tma, /*k=*/0, bm, mbar[0]);
        tma_load_2d(bs0, &amp;B_tma, bn, /*k=*/0, mbar[0]);
    }

    for (int t = 0; t &lt; K/BK; t++) {
        int s = t % 2;  // Current buffer

        // Wait for current tile&#8217;s TMA to complete
        mbarrier_wait(mbar[s], phase[s]);

        // Start loading NEXT tile (overlaps with compute)
        if (tid == 0 &amp;&amp; t + 1 &lt; nt) {
            tma_load_2d(next_buf_a, &amp;A_tma, next_k, bm, next_mbar);
            tma_load_2d(next_buf_b, &amp;B_tma, bn, next_k, next_mbar);
        }

        // Compute: all 256 threads do FMA from shared memory
        float* As = &amp;smem[s * STAGE];
        float* Bs = &amp;smem[s * STAGE + A_SIZE];
        #pragma unroll
        for (int kk = 0; kk &lt; BK; kk++) {
            float b0 = Bs[kk*BN+tc], b1 = Bs[kk*BN+tc+1], ...;
            for (int i = 0; i &lt; TM; i++) {
                float a = As[(tr+i)*BK+kk];
                c[i][0] += a * b0;
                c[i][1] += a * b1;
                // ... 4 FMAs per row
            }
        }
        __syncthreads();
    }

    // Write results to global memory
    for (int i = 0; i &lt; TM; i++)
        store_row(C, bm+tr+i, bn+tc, c[i]);
}</code></pre><p>The generated kernel is denser (with inline PTX for mbarrier and TMA operations), but this captures the architecture. You can find the kernel generator in <code>deplodock/compiler/cuda/lower.py</code> (see <code>_lower_matmul_tma_db</code>).</p><h2>Compile-Time Specialization</h2><p>A useful optimization is to make <strong>M, N, K </strong><code>#define</code><strong> constants</strong>, and not kernel parameters. The runner generates a fresh <code>.cu</code> file for each benchmark invocation with the actual dimensions baked in:</p><pre><code>#define M 8192
#define N 8192
#define K 8192

__global__ void fused_matmul(...) {
    // nt = K/32 becomes nt = 256 - a literal constant
    // Bounds checks become dead code for aligned sizes
}</code></pre><p>This lets nvcc optimize the tile loop bound, eliminate unreachable branches, and make better register allocation decisions. Moving M/N/K from runtime parameters to compile-time constants improved 1024 from 98% to 101% and 4096 from 100% to 101%.</p><p>Similarly, the write-back bounds checks (<code>if (gr &lt; M)</code>, <code>if (gc &lt; N)</code>) are eliminated via <code>#if</code> when the matrix dimensions are multiples of the tile size:</p><pre><code>#if (M % 224 == 0 &amp;&amp; N % 128 == 0)
#define W(r, v0, v1, v2, v3) { /* no bounds checks */ }
#else
#define W(r, v0, v1, v2, v3) { /* with bounds checks */ }
#endif</code></pre><p>In practice, this compile-time specialization makes sense for a set of common sizes (powers of 2, standard model dimensions) rather than generating a kernel for every possible M/N/K.</p><h2>Size-Adaptive Tile Selection</h2><p>Different matrix sizes have different optimal tile shapes. An adaptive strategy map selects the best configuration per size. These were found empirically by benchmarking various configurations on each GPU. For RTX 5090:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!RTKy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!RTKy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 424w, https://substackcdn.com/image/fetch/$s_!RTKy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 848w, https://substackcdn.com/image/fetch/$s_!RTKy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 1272w, https://substackcdn.com/image/fetch/$s_!RTKy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!RTKy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png" width="1400" height="491" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1c82c666-3741-4dad-9c01-48f150458031_1400x491.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:491,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!RTKy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 424w, https://substackcdn.com/image/fetch/$s_!RTKy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 848w, https://substackcdn.com/image/fetch/$s_!RTKy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 1272w, https://substackcdn.com/image/fetch/$s_!RTKy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c82c666-3741-4dad-9c01-48f150458031_1400x491.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>At TM=28, each thread computes 28&#215;4 = 112 output elements, requiring 112 accumulator registers. The inner loop is fully unrolled (BK=32 iterations), and the compiler uses 241 registers total &#8212; close to the sm_120 limit of 255.</p><h2>CTA Swizzle for L2 Cache Reuse</h2><p>At 16384&#215;16384, the working set is 3 GB &#8212; far beyond the 72 MB L2 cache. Without careful block scheduling, different blocks evict each other&#8217;s L2 lines. I linearize the grid and rasterize in groups of 8 row-tiles:</p><pre><code>const int SWIZ = 8;
int pid = blockIdx.x;
int grp = pid / (gridDim.x * SWIZ);
int rem = pid % (gridDim.x * SWIZ);
int by = grp * SWIZ + rem % SWIZ;   // Row block
int bx = rem / SWIZ;                // Column block</code></pre><p>This ensures 8 adjacent row-blocks run together, maximizing reuse of A-tiles in L2. ncu profiling confirmed this reduced DRAM throughput from 32% to 8.5% &#8212; matching cuBLAS&#8217;s 8.2%.</p><h2>Batched mode</h2><p>Batched mode is a one-line change: <code>blockIdx.z</code> selects the batch element, each batch gets its own TMA descriptor:</p><pre><code>int batch = blockIdx.z;
const CUtensorMap&amp; A_tma = A_tma_array[batch];
// ... same kernel, different data
float* C_batch = C + batch * M * N;</code></pre><h2>NCU Comparison with cuBLAS</h2><p>All measurements: CUDA 13.2.51, cuBLAS 13.3.0, driver 595.58.03, captured by <code>scripts/diagnostics/ncu_compare.sh</code>.</p><p>A quick glossary for the metrics: <strong>IPC</strong> (instructions per cycle) measures how many instructions the SM issues per clock &#8212; higher is better, max ~4.0 on sm_120. <strong>FMA pipe</strong> is the percentage of cycles the fused multiply-add units are active &#8212; this is the actual compute throughput. <strong>Issue active</strong> is the percentage of cycles where at least one warp scheduler successfully issues an instruction &#8212; gaps here mean all warps are stalled.</p><p>For single matmul at 8192:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LdTP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LdTP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 424w, https://substackcdn.com/image/fetch/$s_!LdTP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 848w, https://substackcdn.com/image/fetch/$s_!LdTP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 1272w, https://substackcdn.com/image/fetch/$s_!LdTP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LdTP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png" width="1400" height="1014" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1014,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!LdTP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 424w, https://substackcdn.com/image/fetch/$s_!LdTP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 848w, https://substackcdn.com/image/fetch/$s_!LdTP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 1272w, https://substackcdn.com/image/fetch/$s_!LdTP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfbcfb81-c4e3-44ee-b7ba-b1f508dbf838_1400x1014.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The performance gap maps directly to the FMA pipe gap.</strong> On the 5090 single mode, cuBLAS hits 72.9% FMA pipe utilization vs my 68.0% &#8212; a ~5% gap, which matches the ~5% efficiency gap in the headline tables (cycles_active: 34.8 M vs 37.6 M = 92.6% ratio). On the H200 single mode, cuBLAS hits 79.2% vs my 71.3% &#8212; a ~8% gap, matching the ~10% efficiency gap (cycles_active: 41.1 M vs 45.7 M = 89.9% ratio). It&#8217;s not bandwidth (DRAM throughput is 5&#8211;8%, nowhere near the limit). It&#8217;s not issue-active (both kernels are at 100%). It&#8217;s purely how many of the issued instructions actually land in the FMA pipe.</p><h2>Cross-checking Against the Headline Efficiencies</h2><p>The batched mode comparison is already covered in the headline section. The findings are the same: the performance gap maps directly to the FMA pipe gap. Putting the per-arch FMA pipe numbers next to the observed efficiencies:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DYqB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DYqB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 424w, https://substackcdn.com/image/fetch/$s_!DYqB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 848w, https://substackcdn.com/image/fetch/$s_!DYqB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 1272w, https://substackcdn.com/image/fetch/$s_!DYqB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DYqB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png" width="1400" height="849" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:849,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!DYqB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 424w, https://substackcdn.com/image/fetch/$s_!DYqB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 848w, https://substackcdn.com/image/fetch/$s_!DYqB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 1272w, https://substackcdn.com/image/fetch/$s_!DYqB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd3ab253-abe9-4fc6-9c86-fb7cf0a7d598_1400x849.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The full ncu sweep with raw per-row data is committed in the recipe directory at <code>recipes/sgemm_cublas_vs_tma/ncu/batched_dispatch_finding.md</code>, reproducible with <code>scripts/diagnostics/ncu_compare.sh</code>.</p><h2>We Need to Go Deeper: Beyond PTX</h2><p>The article so far has the headline numbers and the dispatcher-bug evidence. Both are about <em>which</em> kernel runs and how the GPU dispatches it. Neither answers the more refined question: when cuBLAS <em>does</em> dispatch the right kernel &#8212; <code>cutlass_80_simt_sgemm_256x128_8x4_nn_align1</code> on the 5090 single-mode path &#8212; why does it consistently hit ~73% FMA pipe utilization while my generated TMA template gets ~68%? This section is the SASS-level investigation that produced a measured answer, and walks through the reproducible scripts that surface it.</p><p>All numbers below are from running on an RTX 5090, CUDA 13.2.51, cuBLAS 13.3.0, single mode, 8192&#215;8192. The full per-instruction <code>ncu</code> source view, the per-kernel stall histograms, and the inner-loop SASS excerpts are committed in <code>recipes/sgemm_cublas_vs_tma/ncu/scheduling/</code> so you can re-derive them yourself.</p><h2>Static Instruction Histograms</h2><p><code>scripts/diagnostics/sass_analysis.py</code> compiles a fresh <code>tma_db</code> bench binary, runs <code>cuobjdump --dump-sass</code>, and counts opcodes by family. For my <code>fused_matmul</code> (TM=28, BK=32) at 8192&#215;8192:</p><pre><code>3584 FFMA               &#8212; fused multiply-add (the actual compute)
  256 LDS.128           &#8212; 128-bit shared memory loads (float4)
  112 STG.E             &#8212; 32-bit predicated global stores (4 per row &#215; 28 rows,
                          bounds-checked because 8192 % 224 &#8800; 0; an aligned size
                          collapses to 28 STG.E.128 via nvcc auto-vectorization)
   48 CS2R / S2R        &#8212; clock + special-register reads
    4 UTMALDG.2D        &#8212; TMA load commands (the entire loading!)
  143 ISETP.*           &#8212; integer set-predicate (bounds + loop control)
   60 BAR/BSYNC/BSSY    &#8212; block barriers and reconvergence
   30 LDC.*             &#8212; constant loads (kernel params, TMA descriptors)
  169 MOV/IMAD/IADD/LEA &#8212; address arithmetic and reg copies</code></pre><p>cuBLAS&#8217;s <code>cutlass_80_simt_sgemm_256x128_8x4_nn_align1</code> ships as a PTX template inside <code>libcublasLt.so</code> and JIT-compiles at runtime. To see its actual SASS, capture the cubin via <code>ncu</code> while the kernel is running (<code>ncu --set full --print-units base -o profile.ncu-rep ./cublas_probe</code>, then <code>ncu --import profile.ncu-rep --page source --print-source sass</code>). The result has <strong>1152 FFMAs and 256 LDS</strong> in the kernel body &#8212; a third of my FFMA count, because cuBLAS uses a smaller per-thread tile (more CTAs, fewer FMAs per thread). The notable structural fact: <strong>0 shared-memory store instructions in mine, 256 in cuBLAS</strong> (cuBLAS pipelines through smem with <code>cp.async</code> + <code>st.shared</code>, my TMA hardware writes smem directly via DMA). That&#8217;s the one place TMA buys a real instruction-count saving.</p><h2>LDS-to-consumer Scheduling</h2><p>The first hypothesis I tested was the obvious one: maybe ptxas places LDS loads too close to their consumer FFMAs, and the warp scheduler stalls waiting for the load to complete. To check, I extended the diagnostic with <code>scripts/diagnostics/scheduling_analysis.py</code> &#8212; it parses the disassembly, walks each <code>LDS.*</code> forward through the instruction stream, and finds the first downstream <code>FFMA</code> that uses any of the loaded registers. The distance between the load and its consumer is your latency-hiding budget.</p><p>For my <code>fused_matmul</code> at 8192:</p><p>FFMAs between LDS and first consumerCount[0, 5)3[5, 10)1[10, 20)13[20, 40)110[40, 80)117[80, 160)11[160, &#8734;)1</p><p><strong>Median: 40 FFMAs; mean: 44.6. Only 4 of 256 LDS have a consumer within 10 FFMAs.</strong> Blackwell LDS latency is on the order of 30 cycles, and each FFMA is one cycle on the FMA pipe, so ptxas essentially hides LDS latency perfectly. The &#8220;ptxas places LDS too close to consumers&#8221; hypothesis was wrong. <em>That&#8217;s not where the gap is.</em></p><p>Running the same analysis on the cuBLAS kernel (extracted from the <code>ncu</code> source view) gives a completely different shape:</p><p>minecuBLASFFMAs in kernel body35841152LDS instructions256256LDS / FFMA ratio1 per 14<strong>1 per 4.5</strong>Median LDS &#8594; first consumer40 FFMAs<strong>158 FFMAs</strong>Median LDS &#8594; next LDS spacing5 FFMAs<strong>0 FFMAs</strong></p><p>cuBLAS&#8217;s median LDS-to-next-LDS spacing is <strong>zero</strong> &#8212; its LDS instructions are clustered into back-to-back groups. My kernel evenly distributes LDS across the FFMA cluster, with a median of 5 FFMAs between consecutive loads. Both schedules hide LDS latency well (40 vs. 158 FFMAs), but they produce <em>fundamentally different</em> warp behaviors at runtime.</p><p>The difference matters because of how it affects <em>warp staggering</em> across the SM&#8217;s warp schedulers:</p><pre><code>cuBLAS schedule (clustered LDS):
  warp 0: [LDS LDS LDS LDS LDS LDS] [FFMA FFMA FFMA FFMA FFMA FFMA FFMA ...]
  warp 1:        [LDS LDS LDS LDS LDS LDS] [FFMA FFMA FFMA FFMA FFMA ...]
  warp 2:               [LDS LDS LDS LDS LDS LDS] [FFMA FFMA FFMA FFMA ...]
           &#8592; warps naturally stagger: while warp 0 does FFMAs, warp 1 is in
             its LDS cluster, so the FMA pipe sees steady demand from ONE warp
             at a time &#8594; low dispatch_stall

My schedule (interleaved LDS):
  warp 0: [FFMA FFMA FFMA FFMA LDS FFMA FFMA FFMA FFMA LDS FFMA FFMA ...]
  warp 1: [FFMA FFMA FFMA FFMA LDS FFMA FFMA FFMA FFMA LDS FFMA FFMA ...]
  warp 2: [FFMA FFMA FFMA FFMA LDS FFMA FFMA FFMA FFMA LDS FFMA FFMA ...]
           &#8592; all warps are in the SAME phase: they all want the FMA pipe on
             the same cycles &#8594; high dispatch_stall (44% vs 22%)</code></pre><p>You can see the difference in the actual inner-loop SASS excerpts. Here&#8217;s a 30-line slice from cuBLAS&#8217;s <code>cutlass_80_simt_sgemm_256x128</code> (<code>recipes/sgemm_cublas_vs_tma/ncu/scheduling/cublas_inner_loop_excerpt.txt</code>):</p><pre><code>**LDS.128 R132, [R130]**            &#8592; 6 LDS in a row (clustered)
  LDCU.64 UR16, c[0x0][0x3c0]
  SHF.R.U32.HI R30, RZ, 0x1, R131
  IADD.64 R200, R200, UR4
**LDS.128 R140, [R130+0x40]**
  LDCU.64 UR14, c[0x0][0x3e0]
  LOP3.LUT R185, R185, 0xffc, R30, 0xc8, !PT
  IADD.64 R204, R204, UR10
**LDS.128 R144, [R130+0x80]**
  ISETP.NE.AND P0, PT, R189, RZ, PT
  MOV R186, RZ
**LDS.128 R148, [R130+0xc0]**
  ...                              &#8592; then the FFMA burst
**LDS.128 R156, [R130+0x200]**
  FFMA R127, R132, R136, R127      &#8592; consumer arrives 158 FFMAs later in steady state
  FFMA R128, R133, R136, R128
  FFMA R126, R133, R137, R126
  FFMA R125, R132, R137, R125
  FFMA R123, R134, R136, R123
  ... (long FFMA run with occasional single LDS interleaved)</code></pre><p>And here&#8217;s mine (<code>recipes/sgemm_cublas_vs_tma/ncu/scheduling/fused_matmul_inner_loop_excerpt.txt</code>):</p><pre><code>FFMA R140, R36, R144, R159       &#8592; FFMAs running
  FFMA R158, R37, R144, R158
  FFMA R161, R38, R144, R161
  FFMA R160, R39, R144, R160
  FFMA R144, R36, R148, R163
  FFMA R162, R37, R148, R162
  FFMA R165, R38, R148, R165
  FFMA R164, R39, R148, R164
  FFMA R3,   R41, R152, R166
**LDS.128 R36, [R15+0x8400]**    &#8592; single LDS in the middle of the cluster
  FFMA R168, R41, R153, R168
  FFMA R167, R41, R154, R167
  FFMA R40,  R41, R155, R40
  FFMA R5,   R45, R152, R172
  ... (continues with one LDS every ~5 FFMAs)</code></pre><p>These are two valid SGEMM schedules. Both feed the FMA pipe. They differ in how the warps stagger.</p><h2>Per-warp stall reasons</h2><p>I captured the per-warp stall reasons from <code>ncu</code>. The script is <code>scripts/diagnostics/ncu_stall_compare.sh</code>; it builds a small probe binary for both kernels at the same shape and extracts the <code>smsp__average_warps_issue_stalled_*_per_issue_active</code> metrics. Each value is &#8220;warps stalled on this reason per issue-active cycle&#8221; &#8212; sums can exceed 100% when multiple warps stall in parallel.</p><p>For 5090 single-mode 8192:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GR1A!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GR1A!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 424w, https://substackcdn.com/image/fetch/$s_!GR1A!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 848w, https://substackcdn.com/image/fetch/$s_!GR1A!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 1272w, https://substackcdn.com/image/fetch/$s_!GR1A!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GR1A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png" width="1400" height="929" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:929,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!GR1A!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 424w, https://substackcdn.com/image/fetch/$s_!GR1A!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 848w, https://substackcdn.com/image/fetch/$s_!GR1A!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 1272w, https://substackcdn.com/image/fetch/$s_!GR1A!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F959e98fe-c92e-4dab-ad78-270285ef050b_1400x929.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There are two large deltas:</p><p><code>dispatch_stall = 44 % vs 22 %.</code> Dispatch stall happens when the warp scheduler has picked a ready warp, but the dispatch unit can&#8217;t accept another instruction this cycle &#8212; typically because some other warp&#8217;s in-flight FFMA has the FMA pipe back-pressured.<strong> My kernel has twice as many dispatch stalls as cuBLAS does, and that&#8217;s the dominant cause of the FMA pipe utilization gap.</strong></p><p><code>short_scoreboard = 20 % vs 12 %.</code> Short scoreboard stalls depend on short-latency operations (LDS reads), during which the scheduler waits for the scoreboard bit to clear. Even though my static LDS-to-consumer distance is 40 FFMAs (more than enough to hide the latency in isolation), the consumers are tightly interleaved into a long FFMA run, so the scoreboard&#8217;s <em>temporal</em> hiding is shorter than the static count suggests.</p><p>Both deltas point to the same root cause: warp-phase<strong> </strong>alignment. With my spread-LDS pattern, all warps are in roughly the same execution phase at the same time &#8212; they all want to execute FFMA instructions on the same cycles. With cuBLAS&#8217;s clustered-LDS pattern, warps stagger naturally: while warp A is draining a long FFMA run, warp B is in its LDS cluster, warp C is finishing a previous FFMA cluster. The warp scheduler always has a <em>different</em> warp to switch to, rather than contending for the FMA pipe.</p><p>The performance gap between my kernel and cublas is caused by <em>the temporal distribution of LDS instructions across warps</em>, which determines whether warps stagger or align and, in turn, how much dispatch-stall pressure piles up on the FMA pipe.</p><h2>A note on the <code>mbarrier.try_wait</code> spin loop</h2><p>A common concern with TMA double-buffer schemes: don&#8217;t threads waste cycles spinning in <code>mbarrier.try_wait</code>? Empirically, no. The TMA transfer for the 45 KB double-buffer slot (<code>bytes=45056</code> in the kernel source) completes well within the FFMA compute phase, so the try-wait spin loop almost always exits on its first read. The <code>dispatch_stall</code> and <code>short_scoreboard</code> numbers above don&#8217;t include any meaningful contribution from try-wait spinning &#8212; both <code>wait = 3.9%</code> and <code>barrier = 7.3%</code> are small compared to the dispatch-side gap.</p><h2>Where is the Limit?</h2><p>The constant 5&#8211;11% gap below cuBLAS&#8217;s best-available kernel (when the dispatcher does its job) shows up on every architecture I tested. I systematically tried to close it. None of these worked:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hp5t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hp5t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 424w, https://substackcdn.com/image/fetch/$s_!hp5t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 848w, https://substackcdn.com/image/fetch/$s_!hp5t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 1272w, https://substackcdn.com/image/fetch/$s_!hp5t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hp5t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png" width="1400" height="855" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:855,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!hp5t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 424w, https://substackcdn.com/image/fetch/$s_!hp5t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 848w, https://substackcdn.com/image/fetch/$s_!hp5t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 1272w, https://substackcdn.com/image/fetch/$s_!hp5t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2258c72-a21b-4d88-98d6-7995fb03d967_1400x855.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The irreducible gap lies in ptxas&#8217;s instruction-scheduling heuristics &#8212; specifically, in how it distributes LDS instructions across the FFMA cluster. As measured in the SASS Deep Dive section above, my generated kernel ends up with <strong>dispatch_stall = 44%</strong> versus cuBLAS&#8217;s <strong>22%</strong>, because my LDS pattern is spread evenly through the FFMA cluster (median spacing 5 FFMAs) while cuBLAS&#8217;s CUTLASS template clusters them into back-to-back groups (median spacing 0).</p><h2>A Note on FP16 / BF16 &#8212; the Mainstream Path</h2><p>The mainstream compute path on modern GPUs is FP16/BF16, possibly with FP32 accumulators for training. That&#8217;s where NVIDIA puts the optimization effort. Pure FP32 SGEMM is no longer the priority &#8212; even though it remains important for scientific computing, numerical simulation, and other use cases that cannot tolerate reduced precision.</p><p>I confirmed that the FP16 path on the 5090 <em>is</em> tensor-core accelerated: an <code>ncu</code> profile of <code>cublasHgemm</code> and <code>cublasGemmEx</code> at 4096&#215;4096 dispatches <code>cutlass_80_tensorop_h16816gemm_...</code> and <code>cutlass_80_tensorop_f16_s16816gemm_...</code> respectively &#8212; both <code>tensorop</code> (HMMA <code>16&#215;8&#215;16</code>), with the SIMT FFMA pipe sitting at &lt;0.2% utilization. So the FP16/BF16 effort is real and visible. The catch: those kernels are <em>also</em> <code>cutlass_80_*</code>-prefixed Ampere forward-ports &#8212; Blackwell&#8217;s incremental tensor-core tuning effort goes into the new low-precision formats (FP8, MXFP4) used by frontier-model training, not into the basic FP16 path. The Ampere kernel reuse on sm_120 isn&#8217;t unique to the unloved FP32 SIMT path; it&#8217;s the dominant pattern across most of cuBLAS&#8217;s compute paths on Blackwell.</p><h2>Benchmark Methodology and Other Results</h2><p>All measurements: 30 iterations (single) or 20 iterations (batched), interleaved with cuBLAS for thermal fairness; the first iteration of every loop is discarded as warmup; median reported. Compiled with <code>nvcc -O3 --fmad=true</code> (no <code>--use_fast_math</code> &#8212; FFMA fusion is preserved, but FTZ/relaxed-div are off, so the comparison is IEEE-clean FP32. RTX 5090 (170 SMs, 32 GB GDDR7), driver 595.58.03, CUDA 13.2.51, cuBLAS 13.3.0. The reproducible recipe is in <code>recipes/sgemm_cublas_vs_tma/</code> &#8212; <code>deplodock bench recipes/sgemm_cublas_vs_tma --local</code>.</p><p>The 256/512 sizes are removed from the single-matmul table. At sub-millisecond per-call durations, the GPU&#8217;s boost clock never engages, and the SM clock bounces around for the duration of the run. The bench runner samples <code>nvidia-smi --query-gpu=clocks.sm</code> around each measurement; one full single-batch sweep looks like:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MZ49!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MZ49!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 424w, https://substackcdn.com/image/fetch/$s_!MZ49!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 848w, https://substackcdn.com/image/fetch/$s_!MZ49!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 1272w, https://substackcdn.com/image/fetch/$s_!MZ49!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MZ49!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png" width="1400" height="652" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:652,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!MZ49!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 424w, https://substackcdn.com/image/fetch/$s_!MZ49!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 848w, https://substackcdn.com/image/fetch/$s_!MZ49!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 1272w, https://substackcdn.com/image/fetch/$s_!MZ49!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff00772d9-114d-473d-a5eb-6290874ec5e0_1400x652.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The clock is not locked to emulate real-world performance. Since my kernel and cuBLAS are interleaved iteration-by-iteration, the <strong>ratio</strong> stays meaningful at whatever clock the governor picks at that instant.</p><p>The headline tables at the top of this article show the full RTX 5090 sweep; the rest of this section covers other hardware.</p><h2>RTX PRO 6000 Blackwell (Max-Q)</h2><p>Same architecture as the 5090 (sm_120), but 188 SMs vs 170, and a lower power budget. Provisioned on CloudRift with the same toolchain (driver 595.58.03 / CUDA 13.2.51), so the comparison is apples-to-apples.</p><p><strong>Pro 6000 single matmul:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tDbC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tDbC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 424w, https://substackcdn.com/image/fetch/$s_!tDbC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 848w, https://substackcdn.com/image/fetch/$s_!tDbC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 1272w, https://substackcdn.com/image/fetch/$s_!tDbC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tDbC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png" width="1400" height="491" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:491,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!tDbC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 424w, https://substackcdn.com/image/fetch/$s_!tDbC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 848w, https://substackcdn.com/image/fetch/$s_!tDbC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 1272w, https://substackcdn.com/image/fetch/$s_!tDbC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F198425dc-a43e-4252-88a6-2fce0f8575cc_1400x491.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Pro 6000 batched matmul</strong> &#8212; note that B=4/8/16 land at ~93&#8211;95% (not 150&#8211;170% like the 5090) because the Pro 6000 dispatcher actually escalates correctly:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vL6N!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vL6N!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 424w, https://substackcdn.com/image/fetch/$s_!vL6N!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 848w, https://substackcdn.com/image/fetch/$s_!vL6N!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 1272w, https://substackcdn.com/image/fetch/$s_!vL6N!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vL6N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png" width="1400" height="583" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9667bc22-0305-4208-9367-748b96e7562a_1400x583.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:583,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!vL6N!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 424w, https://substackcdn.com/image/fetch/$s_!vL6N!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 848w, https://substackcdn.com/image/fetch/$s_!vL6N!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 1272w, https://substackcdn.com/image/fetch/$s_!vL6N!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9667bc22-0305-4208-9367-748b96e7562a_1400x583.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The 512/1024/2048 cells, where I still beat cuBLAS, are the Pro 6000&#8217;s small-size MAGMA fallback bug from the dispatcher table. The 4K+ cells are the constant 5&#8211;7% generator gap.</p><h2>H200</h2><p>TM=8 is optimal at every size on Hopper &#8212; larger thread tiles regress, likely because the first-gen Hopper TMA has more issue pressure than Blackwell&#8217;s refined unit. The H200 is the cleanest control case: when cuBLAS dispatches the right kernel (and on Hopper it always does &#8212; see the dispatcher table), my generator loses by the same constant 8&#8211;11% as everywhere else.</p><p><strong>H200 single matmul:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ITRY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ITRY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 424w, https://substackcdn.com/image/fetch/$s_!ITRY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 848w, https://substackcdn.com/image/fetch/$s_!ITRY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 1272w, https://substackcdn.com/image/fetch/$s_!ITRY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ITRY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png" width="1400" height="501" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:501,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ITRY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 424w, https://substackcdn.com/image/fetch/$s_!ITRY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 848w, https://substackcdn.com/image/fetch/$s_!ITRY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 1272w, https://substackcdn.com/image/fetch/$s_!ITRY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8bb45112-ec98-4f88-8ada-d1cddfdf0b7d_1400x501.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>H200 batched matmul:</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1Vil!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1Vil!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 424w, https://substackcdn.com/image/fetch/$s_!1Vil!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 848w, https://substackcdn.com/image/fetch/$s_!1Vil!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 1272w, https://substackcdn.com/image/fetch/$s_!1Vil!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1Vil!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png" width="1400" height="580" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:580,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!1Vil!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 424w, https://substackcdn.com/image/fetch/$s_!1Vil!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 848w, https://substackcdn.com/image/fetch/$s_!1Vil!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 1272w, https://substackcdn.com/image/fetch/$s_!1Vil!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F685500f8-2830-453c-b10c-a2d31e502f67_1400x580.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>cuBLAS on Hopper hits ~50 TFLOPS across the size range (the H200&#8217;s HBM3e bandwidth easily feeds the SIMT cores), with FMA pipe utilization climbing from 69% at 512 to 82% at 4K+. My TMA template hits ~47 TFLOPS at 71% FMA pipe util across the range &#8212; uniform 8&#8211;11% gap to the well-dispatched cuBLAS baseline.</p><h2>Related Bug Reports</h2><p>After finding all of this, I went looking for prior reports of cuBLAS picking suboptimal kernels for SGEMM on consumer NVIDIA GPUs. <strong>It turns out this isn&#8217;t a new bug type.</strong> Substantially similar reports have surfaced at least twice before, both times reported on NVIDIA&#8217;s developer forums and acknowledged by NVIDIA engineers.</p><p><em><strong><a href="https://forums.developer.nvidia.com/t/is-it-correct-that-my-pascal-card-is-calling-maxwell-gemm-kernels-through-cublas-and-if-so-why-is-cublas-unusably-slow-for-me/63970">Pascal card is calling Maxwell kernels through cublas. It is unusably slow.</a></strong></em> (NVIDIA Developer Forums, 2018) &#8212; closest match. A user with a GTX 1080 Ti (Pascal sm_61) reported <code>cublasSgemmStridedBatched</code> running <code>maxwell_sgemm_128_64_nn</code> kernels, <strong>about 2&#215; slower</strong> than a na&#239;ve hand-rolled kernel for batched workloads.</p><p><em><strong><a href="https://forums.developer.nvidia.com/t/cublas-sgemm-is-slow/51077">cuBLAS sgemm is slow</a></strong></em> (NVIDIA Developer Forums, 2017). Different shape &#8212; extreme aspect ratio, <code>2&#215;23880 &#215; 23880&#215;32</code> &#8212; but the same root cause: cuBLAS&#8217;s dispatcher picked a tiny grid <code>[1,1,1]</code> &#215; block <code>[8,8,1]</code>, leaving the GPU idle.</p><p><strong><a href="https://github.com/vllm-project/vllm/issues/35467">vLLM #35467</a></strong> (2025). vLLM developers report that on certain matmul shapes, <em>&#8220;cuBLAS auto-selects the 7th-best tile (128x136) instead of optimal options, with the heuristic leaving 16% performance on the table.&#8221;</em></p><p><strong>Simon Boehm&#8217;s <a href="https://siboehm.com/articles/22/CUDA-MMM">CUDA matmul worklog</a></strong> (cited indirectly in many places). Notes the structural fact that <em>&#8220;cuBLAS contains not one single implementation of SGEMM, but hundreds of them, and at runtime, based on the dimensions, cuBLAS will pick which kernel to run&#8221;</em> &#8212; and <em>&#8220;cuBLAS may set a too small grid size, which can be identified through profiling tools.&#8221;</em> Published acknowledgment that the heuristic has known holes.</p><h2>Conclusion</h2><p>The headline TMA win on the 5090 turned out to be a <strong>cuBLAS dispatcher bug</strong>, not a hardware advantage &#8212; and, as the <em>Related bug reports</em> section above shows, it&#8217;s the latest instance of a recurring pattern that NVIDIA has acknowledged on its own developer forums since at least 2018. NVIDIA ships a release version of cuBLAS that, on the most popular consumer Blackwell SKU, selects an FP32 SGEMM kernel that runs at ~40% of peak FMA pipe utilization across the entire range of batched workloads. The exact same library binary escalates correctly to a 73% kernel on the RTX PRO 6000 and to an 82% kernel on the H200. It&#8217;s not subtle: the 5090 path picks the wrong kernel 100% of the time across the entire 32&#215; workload range I measured, and the same library has better kernels sitting right there.</p><p>I only verified this on the RTX 5090. The dispatch logic is clearly arch-specific, so it would not surprise me if other consumer RTX cards (5070, 5080, 4090, &#8230;) hit similar bugs in their respective dispatch paths. If you have one of those cards, the diagnostic script that surfaced lives in the repo at <code>scripts/diagnostics/ncu_compare.sh</code>. Three minutes of <code>ncu</code> will tell you whether your batched FP32 workloads are leaving 60% on the floor.</p><p><strong>Don&#8217;t blindly trust cuBLAS on new architectures and/or RTX cards</strong>. Check the kernel name in <code>ncu</code>. If you see something like <code>cutlass_80_simt_sgemm_128x32_8x5</code> running for a workload that should clearly be on a 256&#215;128 kernel, you&#8217;re hitting the bug.</p><p>Separately, the TMA + compile-time specialization technique is worth knowing for its own sake. It produces a fully pipelined SGEMM kernel template in ~300 lines of generated C that hits <strong>~93% of CUTLASS&#8217;s hand-tuned peak FMA pipe utilization</strong> on every Blackwell SKU I tested. TMA might be useful in many other workloads that leverage conventional CUDA cores.</p><h2>Links</h2><ul><li><p>Source code: <a href="https://github.com/cloudrift-ai/deplodock/tree/main/deplodock/compiler/cuda">deplodock/compiler/cuda/</a> &#8212; the compiler that generates the kernels</p></li><li><p>Benchmark script: <a href="https://github.com/cloudrift-ai/deplodock/tree/main/scripts/bench_matmul.py">scripts/bench_matmul.py</a> &#8212; run your own benchmarks</p></li><li><p>Reproducible recipe: <a href="https://github.com/cloudrift-ai/deplodock/tree/main/recipes/sgemm_cublas_vs_tma">recipes/sgemm_cublas_vs_tma/</a> &#8212; <code>deplodock bench recipes/sgemm_cublas_vs_tma --local</code></p></li><li><p>Full per-arch dispatch sweep with raw ncu output: <a href="https://github.com/cloudrift-ai/deplodock/tree/main/recipes/sgemm_cublas_vs_tma/ncu/batched_dispatch_finding.md">recipes/sgemm_cublas_vs_tma/ncu/batched_dispatch_finding.md</a></p></li><li><p>Diagnostic scripts: <a href="https://github.com/cloudrift-ai/deplodock/tree/main/scripts/diagnostics">scripts/diagnostics/</a> &#8212; <code>dump_cublas_kernels.sh</code>, <code>ncu_compare.sh</code>, <code>cublas_loop_vs_strided.cu</code>, <code>sass_analysis.py</code></p></li><li><p>Hardware: RTX 5090 (GB202, sm_120, 32 GB GDDR7, 170 SMs); RTX PRO 6000 Blackwell Max-Q (sm_120, 188 SMs); NVIDIA H200 (GH100, sm_90, 141 GB HBM3e)</p></li><li><p>Software: CUDA 13.2.51, nvcc, cuBLAS 13.3.0, Ubuntu 24.04</p></li></ul><h2>References</h2><ol><li><p>NVIDIA CUDA Programming Guide &#8212; <a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#tensor-memory-access">Tensor Memory Access (TMA)</a></p></li><li><p>NVIDIA CUDA Programming Guide &#8212; <a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#asynchronous-data-copies">Asynchronous Data Copies</a></p></li><li><p>NVIDIA CUTLASS &#8212; <a href="https://github.com/NVIDIA/cutlass">SIMT SGEMM reference</a></p></li><li><p>Simon Boehm &#8212; <a href="https://siboehm.com/articles/22/CUDA-MMM">How to Optimize a CUDA Matmul Kernel for cuBLAS-like Performance</a></p></li><li><p>Modular &#8212; <a href="https://www.modular.com/blog/matrix-multiplication-on-nvidias-blackwell-part-2-using-hardware-features-to-optimize-matmul">Matrix Multiplication on NVIDIA&#8217;s Blackwell</a></p></li><li><p>Lei Mao &#8212; <a href="https://leimao.github.io/blog/CUDA-Shared-Memory-Swizzling/">CUDA Shared Memory Swizzling</a></p></li><li><p>CuAsmRL &#8212; <a href="https://arxiv.org/abs/2501.08071">SASS Optimization via Reinforcement Learning</a></p></li><li><p>Colfax Research &#8212; <a href="https://research.colfax-intl.com/cutlass-tutorial-design-of-a-gemm-kernel/">Efficient GEMM with Pipelining</a></p></li></ol><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kernelspace.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kernel Space! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[GPU Virtualization with VFIO, NVAI Enterprise, and AMD SR-IOV]]></title><description><![CDATA[There is no single GPU virtualization stack that works across all GPUs.]]></description><link>https://kernelspace.substack.com/p/gpu-virtualization-with-vfio-nvai</link><guid isPermaLink="false">https://kernelspace.substack.com/p/gpu-virtualization-with-vfio-nvai</guid><dc:creator><![CDATA[Dmitry Trifonov]]></dc:creator><pubDate>Thu, 09 Apr 2026 15:05:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!F3S8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F3S8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F3S8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 424w, https://substackcdn.com/image/fetch/$s_!F3S8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 848w, https://substackcdn.com/image/fetch/$s_!F3S8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 1272w, https://substackcdn.com/image/fetch/$s_!F3S8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F3S8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp" width="1200" height="670" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:670,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82384,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/webp&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://kernelspace.substack.com/i/193697767?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!F3S8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 424w, https://substackcdn.com/image/fetch/$s_!F3S8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 848w, https://substackcdn.com/image/fetch/$s_!F3S8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 1272w, https://substackcdn.com/image/fetch/$s_!F3S8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9c5b294-a841-43e9-9e16-fb79a6c97a8c_1200x670.webp 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There is no single GPU virtualization stack that works across all GPUs. Datacenter and consumer GPUs require different approaches. There are differences between the virtualization of NVIDIA and AMD cards. Even within NVIDIA&#8217;s own enterprise AI ecosystem, there are multiple virtualization paths. If you&#8217;re building a cloud GPU platform that supports hardware from multiple vendors, you&#8217;re going to end up implementing and maintaining several distinct virtualization strategies.</p><p>At <a href="https://cloudrift.ai/">CloudRift</a>, we support all modern virtualization paths: VFIO passthrough for whole-GPU allocation, NVIDIA MIG with AI Enterprise vGPU for fractional NVIDIA GPUs, and AMD SR-IOV for AMD Instinct cards. This article explains the mechanics behind each one &#8212; the host-side driver lifecycle, the domain XML configuration, and the trade-offs.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kernelspace.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kernel Space! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>I&#8217;ll assume you&#8217;re familiar with basic Linux virtualization concepts (KVM, QEMU, libvirt). If you need a refresher on host-side IOMMU and VFIO setup, see our earlier guide: <a href="https://medium.com/itnext/host-setup-for-qemu-kvm-gpu-passthrough-with-vfio-on-linux-c65bacf2d96b">Host Setup for QEMU/KVM GPU Passthrough with VFIO on Linux</a>.</p><h2>The foundation: QEMU/KVM with libvirt</h2><p>Regardless of GPU vendor, all our VMs run on the same hypervisor stack: QEMU/KVM managed through libvirt. A few configuration choices matter a lot when GPUs are involved.</p><h2>Machine type and PCI topology</h2><p>We use the <code>q35</code> chipset with the <code>pc-q35</code> machine type. Unlike the older <code>i440fx</code>q35 provides native PCIe support, which is essential for GPU passthrough &#8212; GPUs are PCIe devices that expect a PCIe bus topology.</p><pre><code>&lt;type arch=&#8221;x86_64&#8221; machine=&#8221;q35&#8221;&gt;hvm&lt;/type&gt;</code></pre><p>Everything described here works with any Linux distribution, but most of our host providers run Ubuntu, and that&#8217;s what we test against. The examples below assume Ubuntu 22.04 or 24.04.</p><p>Use a modern QEMU and OVMF. The versions shipped with Ubuntu 22.04 LTS (QEMU 6.2, OVMF 2022.02) are too old for reliable GPU passthrough with current hardware. We target QEMU 9.0+, OVMF 2024.02, and libvirt 10.6+, installed from the Canonical Server Backports PPA and Ubuntu Noble packages, respectively. The specific bugs that forced the upgrades: OVMF 2022.02 hangs during boot when initializing RTX GPUs on some platforms, and QEMU 6.2 hits a <code>pci_irq_handler</code> assertion failure during GPU passthrough with AMD MI350X.</p><p>One critical setting is the PCI hole size. Modern data center GPUs (H100, B200) have BARs that can exceed 128 GB. QEMU has internal logic to estimate the PCIe hole size, but we found that it doesn&#8217;t always allocate enough space. The root cause is that PCI BAR allocation involves three parties &#8212; the host kernel (which assigns physical BAR addresses), the guest UEFI firmware (OVMF, which allocates the guest-side PCI address space), and QEMU (which maps between them). Because QEMU doesn&#8217;t have full visibility into how OVMF will lay out the address space, its built-in heuristic can underestimate the required hole, especially with multiple large GPUs.</p><p>We work around this by computing the hole size ourselves based on actual BAR sizes reported by the hardware. In the domain XML, this appears as a <code>&lt;pcihole64&gt;</code> element on the PCIe root controller:</p><pre><code>&lt;controller type=&#8221;pci&#8221; model=&#8221;pcie-root&#8221;&gt;
  &lt;pcihole64 unit=&#8221;G&#8221;&gt;1024&lt;/pcihole64&gt;
&lt;/controller&gt;</code></pre><p>Getting this wrong results in the guest failing to map GPU BARs &#8212; you&#8217;ll see &#8220;BAR X: can&#8217;t assign mem&#8221; errors in <code>dmesg</code> inside the VM.</p><h2>CPU mode</h2><p>We run all VMs in <code>host-passthrough</code> mode with <code>migratable=on</code>:</p><pre><code>&lt;cpu mode=&#8221;host-passthrough&#8221; check=&#8221;none&#8221; migratable=&#8221;on&#8221;/&gt;</code></pre><p>This exposes the host CPU&#8217;s full instruction set to the guest, which is important for GPU workloads that rely on specific CPU features (such as AVX-512 for preprocessing). The <code>migratable=on</code> flag filters out features that would break live migration, though in practice we rarely migrate GPU VMs since the GPU itself isn&#8217;t migratable.</p><h2>PCIe root ports</h2><p>Each GPU (or GPU group, in the case of multi-function devices with audio) gets its own PCIe root port controller. This keeps GPU IOMMU groups clean inside the guest and avoids conflicts:</p><pre><code>&lt;controller type=&#8221;pci&#8221; model=&#8221;pcie-root-port&#8221; index=&#8221;10&#8221; id=&#8221;pcie.10&#8221;&gt;
  &lt;target chassis=&#8221;10&#8221; port=&#8221;0xa&#8221;/&gt;
&lt;/controller&gt;</code></pre><p>On consumer GPU rigs (RTX 4090, 5090), we use a <strong>flat topology</strong>: all GPUs share the same bus, and each GPU gets a different slot. On multi-GPU servers (8&#215;H100, 8&#215;B200), we use a <strong>deep topology</strong> &#8212; each GPU sits behind its own PCIe root port on a separate bus.</p><p><strong>Flat topology</strong> (consumer GPUs &#8212; faster to allocate, smaller PCIe hole):</p><pre><code>&lt;!-- All GPUs on bus 0x00, varying slot --&gt;
&lt;hostdev mode=&#8221;subsystem&#8221; type=&#8221;pci&#8221; managed=&#8221;no&#8221; multifunction=&#8221;on&#8221;&gt;
  &lt;source&gt;&lt;address domain=&#8221;0x0000&#8221; bus=&#8221;0x82&#8221; slot=&#8221;0x00&#8221; function=&#8221;0x00&#8221;/&gt;&lt;/source&gt;
  &lt;address type=&#8221;pci&#8221; bus=&#8221;0x00&#8221; slot=&#8221;0x10&#8221; function=&#8221;0&#8221;/&gt;  &lt;!-- GPU 0 --&gt;
&lt;/hostdev&gt;
&lt;hostdev mode=&#8221;subsystem&#8221; type=&#8221;pci&#8221; managed=&#8221;no&#8221; multifunction=&#8221;on&#8221;&gt;
  &lt;source&gt;&lt;address domain=&#8221;0x0000&#8221; bus=&#8221;0xa2&#8221; slot=&#8221;0x00&#8221; function=&#8221;0x00&#8221;/&gt;&lt;/source&gt;
  &lt;address type=&#8221;pci&#8221; bus=&#8221;0x00&#8221; slot=&#8221;0x11&#8221; function=&#8221;0&#8221;/&gt;  &lt;!-- GPU 1 --&gt;
&lt;/hostdev&gt;</code></pre><p><strong>Deep topology</strong> (data center GPUs &#8212; one root port per GPU):</p><pre><code>&lt;!-- Each GPU behind its own root port, on its own bus --&gt;
&lt;controller type=&#8221;pci&#8221; model=&#8221;pcie-root-port&#8221; index=&#8221;1&#8221; id=&#8221;pcie.1&#8221;/&gt;
&lt;hostdev mode=&#8221;subsystem&#8221; type=&#8221;pci&#8221; managed=&#8221;no&#8221;&gt;
  &lt;source&gt;&lt;address domain=&#8221;0x0000&#8221; bus=&#8221;0x03&#8221; slot=&#8221;0x00&#8221; function=&#8221;0x00&#8221;/&gt;&lt;/source&gt;
  &lt;address type=&#8221;pci&#8221; bus=&#8221;0x01&#8221; slot=&#8221;0x00&#8221; function=&#8221;0&#8221;/&gt;  &lt;!-- GPU 0, bus 1 --&gt;
&lt;/hostdev&gt;
&lt;controller type=&#8221;pci&#8221; model=&#8221;pcie-root-port&#8221; index=&#8221;2&#8221; id=&#8221;pcie.2&#8221;/&gt;
&lt;hostdev mode=&#8221;subsystem&#8221; type=&#8221;pci&#8221; managed=&#8221;no&#8221;&gt;
  &lt;source&gt;&lt;address domain=&#8221;0x0000&#8221; bus=&#8221;0x13&#8221; slot=&#8221;0x00&#8221; function=&#8221;0x00&#8221;/&gt;&lt;/source&gt;
  &lt;address type=&#8221;pci&#8221; bus=&#8221;0x02&#8221; slot=&#8221;0x00&#8221; function=&#8221;0&#8221;/&gt;  &lt;!-- GPU 1, bus 2 --&gt;
&lt;/hostdev&gt;</code></pre><p>The flat layout is preferable when possible &#8212; it requires fewer PCIe root port controllers, reduces the PCIe hole requirement, and is faster for QEMU to set up. But data center GPUs work more reliably behind dedicated root ports.</p><h2>ROM BAR</h2><p>We always disable ROM BAR for passthrough GPUs:</p><pre><code>&lt;rom bar=&#8221;off&#8221;/&gt;</code></pre><p>Without this, OVMF (the UEFI firmware) tries to load the GPU&#8217;s option ROM during boot. With newer GPU firmware, this can cause hangs or extremely slow boot times. Since we&#8217;re not using the GPU for console output (cloud VMs use serial/VNC), there&#8217;s no reason to load the ROM.</p><h2>NVIDIA: full GPU passthrough via VFIO</h2><p>This is the straightforward mode. One physical GPU goes to one VM. It&#8217;s what we use for consumer GPUs (RTX 4090, RTX 5090, RTX PRO 6000) and for data center GPUs when the tenant needs the whole card.</p><p>Fair warning: NVIDIA&#8217;s documentation around GPU virtualization is a maze. There&#8217;s VFIO passthrough, mdev (mediated devices), vGPU, SR-IOV, MIG &#8212; and the terminology overlaps in confusing ways across different driver generations and product lines. If you&#8217;re making changes to your virtualization stack, expect to speak with NVIDIA&#8217;s support team. The docs alone won&#8217;t get you there.</p><h2>Using host and guest GPUs simultaneously</h2><p>One scenario worth mentioning: what if you want to use some GPUs on the host (e.g., for LLM inference) while simultaneously passing others through to VMs? This is possible, but only with the open-source NVIDIA kernel driver stack. You can leave the host GPUs bound to the open <code>nvidia</code> driver and bind the passthrough GPUs <code>vfio-pci</code> independently. NVIDIA AI Enterprise doesn&#8217;t support this mixed mode, at least according to the response to our support inquiry in February 2026.</p><p>The tricky part is the driver lifecycle on the host. NVIDIA&#8217;s kernel driver is notoriously sticky, and you have to fully release the GPU before VFIO can claim it. Here&#8217;s the sequence:</p><p><strong>Preparation (host &#8594; VFIO):</strong></p><ol><li><p>Load the <code>vfio-pci</code> kernel module</p></li><li><p>Stop the processes holding the GPU, like the DCGM exporter (if running &#8212; it holds a handle on the GPU)</p></li><li><p>Disable persistence mode and stop <code>nvidia-persistenced</code></p></li><li><p>Unload NVIDIA kernel modules (<code>nvidia-uvm</code>, <code>nvidia-drm</code>, <code>nvidia-modeset</code>, <code>nvidia</code>)</p></li><li><p>Unbind each GPU (and its audio function) from the <code>nvidia</code> driver</p></li><li><p>Bind each device to <code>vfio-pci</code></p></li><li><p>Wait for VFIO initialization (the device files under <code>/dev/vfio/</code> need a moment to appear)</p></li></ol><p>If any step fails &#8212; especially the module unload &#8212; something is still holding a reference to the GPU. Common culprits: an orphaned <code>nvidia-smi</code> process, a monitoring daemon, or a zombie compute process.</p><p><strong>Return (VFIO &#8594; host):</strong></p><p>When the VM shuts down, we reverse the process:</p><ol><li><p>Rebind the GPU to the <code>nvidia</code> driver</p></li><li><p>Rebind the audio function to <code>snd_hda_intel</code></p></li><li><p>Start <code>nvidia-persistenced</code></p></li><li><p>Re-enable persistence mode</p></li><li><p>Verify CUDA readiness with a quick device query. After rebinding, the GPU may appear in <code>nvidia-smi</code> but still fail when accessed from a Docker container (you will get &#8220;no CUDA-capable device&#8221;). Running a short CUDA initialization on the host &#8220;warms up&#8221; the device and ensures the driver state is fully consistent.</p></li></ol><h2>Domain XML for VFIO passthrough</h2><p>The GPU appears as a <code>&lt;hostdev&gt;</code> element with <code>managed="no"</code> &#8212; We handle driver binding ourselves rather than letting libvirt do it, because the multi-step NVIDIA teardown requires more control than libvirt&#8217;s managed mode provides:</p><pre><code>&lt;hostdev mode=&#8221;subsystem&#8221; type=&#8221;pci&#8221; managed=&#8221;no&#8221; multifunction=&#8221;on&#8221;&gt;
  &lt;driver name=&#8221;vfio&#8221;/&gt;
  &lt;source&gt;
    &lt;address domain=&#8221;0x0000&#8221; bus=&#8221;0xc2&#8221; slot=&#8221;0x00&#8221; function=&#8221;0x00&#8221;/&gt;
  &lt;/source&gt;
  &lt;address type=&#8221;pci&#8221; domain=&#8221;0x0000&#8221; bus=&#8221;0x00&#8221; slot=&#8221;0x13&#8221; function=&#8221;0&#8221;/&gt;
  &lt;rom bar=&#8221;off&#8221;/&gt;
&lt;/hostdev&gt;</code></pre><p>The <code>multifunction="on"</code> The attribute is relevant for consumer GPUs (RTX series), which have a companion HDA audio device at function 0x1. Both functions need to be in the same multifunction group for the guest to enumerate them correctly. Data center GPUs (H100, B200) don&#8217;t have an audio function, so this attribute isn&#8217;t needed for them.</p><h2>NVIDIA: fractional GPUs with MIG and vGPU</h2><p>NVIDIA&#8217;s Multi-Instance GPU (MIG) technology lets you partition a single GPU into isolated instances, each with its own compute units, memory, and memory bandwidth.</p><h2>How MIG works</h2><p>MIG is available on data center GPUs (A100, H100, H200, B200) and creates hardware-level partitions. Unlike time-sharing (MPS), MIG instances receive dedicated compute slices.</p><p>Each GPU supports a set of profiles. For example, an H100 with 80 GB HBM3 memory can be split into:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AI-H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AI-H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 424w, https://substackcdn.com/image/fetch/$s_!AI-H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 848w, https://substackcdn.com/image/fetch/$s_!AI-H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 1272w, https://substackcdn.com/image/fetch/$s_!AI-H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AI-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png" width="1400" height="484" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:484,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!AI-H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 424w, https://substackcdn.com/image/fetch/$s_!AI-H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 848w, https://substackcdn.com/image/fetch/$s_!AI-H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 1272w, https://substackcdn.com/image/fetch/$s_!AI-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc4a8feaf-7428-4fdc-adf3-4b199b601e35_1400x484.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The <code>7g.80gb</code> profile gives you the entire GPU but through the MIG/vGPU pathway instead of VFIO passthrough. We use this when the node is configured for NVIDIA AI Enterprise licensing and all VMs need to go through the vGPU stack, even single-tenant ones.</p><p>An important constraint: you can&#8217;t arbitrarily slice GPUs. Only specific profile combinations are allowed per GPU. The H100 has 7 GPC (Graphics Processing Cluster) slices, and profiles must tile across them without overlap. Here are the <a href="https://docs.nvidia.com/datacenter/tesla/mig-user-guide/supported-mig-profiles.html">valid placement configurations</a> for the H100 80GB:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6J57!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6J57!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 424w, https://substackcdn.com/image/fetch/$s_!6J57!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 848w, https://substackcdn.com/image/fetch/$s_!6J57!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 1272w, https://substackcdn.com/image/fetch/$s_!6J57!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6J57!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png" width="1400" height="508" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:508,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!6J57!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 424w, https://substackcdn.com/image/fetch/$s_!6J57!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 848w, https://substackcdn.com/image/fetch/$s_!6J57!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 1272w, https://substackcdn.com/image/fetch/$s_!6J57!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff696a767-67eb-43cf-991e-a0f5d9a62f41_1400x508.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Configs 1&#8211;11 use <code>1g.10gb</code> profiles, while configs 12-19 mix in <code>1g.20gb</code> &#8212; double the memory, same compute, but consuming memory from 2 GPC slices, so fewer fit. There&#8217;s also a <code>1g.10gb+me</code> variant that includes media engines (NVDEC/NVJPG).</p><p>Profile names and memory sizes vary by GPU model &#8212; H200 uses <code>1g.18gb</code>, <code>2g.35gb</code>, <code>3g.71gb</code>, <code>7g.141gb</code>, etc., but the slice tiling rules are the same.</p><h2>MIG device creation</h2><p>Creating a MIG instance requires several steps:</p><ol><li><p><strong>Ensure the NVIDIA driver is loaded</strong> on the host (MIG is managed by the host driver, not VFIO)</p></li><li><p><strong>Enable MIG mode</strong> on the target GPU via <code>nvidia-smi</code></p></li><li><p><strong>Query available profiles</strong> to get the GPU Instance Profile ID</p></li><li><p><strong>Create a GPU Instance (GI)</strong> using the profile ID <code>nvidia-smi mig -i0 -cgi 14</code></p></li><li><p><strong>Create a Compute Instance (CI)</strong> within the GI (or use <code>-C</code> with <code>-cgi</code> to do both in one step): <code>nvidia-smi mig -i0 -cgi 14 -C</code></p></li><li><p><strong>Retrieve the vGPU BDF</strong> &#8212; each MIG instance has an associated SR-IOV Virtual Function. You can find the VF&#8217;s BDF by reading the symlinks under <code>/sys/bus/pci/devices/&lt;GPU_BDF&gt;/virtfnN</code>, then writing the appropriate vGPU type to the VF&#8217;s <code>nvidia/current_vgpu_type</code> sysfs file. The resulting VF BDF is what gets passed to the VM via libvirt.</p></li></ol><p>The key difference from full passthrough is that the host NVIDIA driver stays loaded and manages the GPU. The MIG instance appears as a virtual device that gets passed to the guest via libvirt&#8217;s <code>&lt;hostdev&gt;</code> mechanism. This, in turn, allows using the host to access GPUs for specific purposes, for example, tracking utilization using NVIDIA DCGM.</p><h2>Domain XML for MIG vGPU passthrough</h2><p>The MIG vGPU device is attached to the VM the same way as a full GPU &#8212; via a <code>&lt;hostdev&gt;</code> element. The difference is the source address: instead of the physical GPU&#8217;s BDF, you use the VF BDF that was assigned to the MIG instance:</p><pre><code>&lt;hostdev mode=&#8221;subsystem&#8221; type=&#8221;pci&#8221; managed=&#8221;no&#8221;&gt;
  &lt;driver name=&#8221;vfio&#8221;/&gt;
  &lt;source&gt;
    &lt;address domain=&#8221;0x0000&#8221; bus=&#8221;0xdb&#8221; slot=&#8221;0x00&#8221; function=&#8221;0x04&#8221;/&gt;
  &lt;/source&gt;
  &lt;address type=&#8221;pci&#8221; domain=&#8221;0x0000&#8221; bus=&#8221;0x01&#8221; slot=&#8221;0x00&#8221; function=&#8221;0&#8221;/&gt;
  &lt;rom bar=&#8221;off&#8221;/&gt;
&lt;/hostdev&gt;</code></pre><p>Note there&#8217;s no <code>multifunction="on"</code> here &#8212; MIG vGPU devices don&#8217;t have a companion audio function. Otherwise, the XML is identical to VFIO passthrough: <code>managed="no"</code>, explicit <code>&lt;driver name="vfio"/&gt;</code>, and ROM BAR disabled.</p><h2>NVIDIA AI Enterprise guest stack</h2><p>Inside the VM, we install the NVIDIA AI Enterprise (NVAI) driver package rather than the standard driver. It can be downloaded from the NVIDIA licensing portal after purchasing a license. The NVAI guest driver is specifically designed for vGPU environments:</p><ul><li><p><strong>Proprietary DKMS modules</strong> &#8212; the open-source NVIDIA driver doesn&#8217;t support vGPU. We explicitly remove any <code>nvidia/x.y.z-open</code> DKMS modules and install the proprietary <code>nvidia/x.y.z</code> modules</p></li><li><p><code>nvidia-gridd</code> &#8212; the grid daemon that handles vGPU license checkout from a license server</p></li><li><p><code>nvidia-persistenced</code> &#8212; keeps the driver loaded even when no GPU applications are running</p></li><li><p><strong>CUDA toolkit</strong> &#8212; installed in the guest for compute workloads</p></li></ul><p>The guest image is built with these components baked in, so the VM boots with full GPU support ready. No driver installation on first boot.</p><h2>MIG cleanup</h2><p>When a VM is destroyed, we track its MIG instance IDs (stored in the domain&#8217;s libvirt metadata) and destroy the corresponding GPU and Compute instances:</p><pre><code>nvidia-smi mig -i 0 -dgi -gi 5
# Successfully destroyed GPU instance ID 5 from GPU 0</code></pre><p>This releases the GPU slices back to the pool for the next tenant.</p><h2>Licensing costs</h2><p>NVIDIA AI Enterprise licensing is not cheap. At the time of writing, it costs roughly $4,500 per GPU per year (<a href="https://docs.nvidia.com/ai-enterprise/planning-resource/licensing-guide/latest/pricing.html">source</a>). A whopping $36,000/year for the 8xGPU server. Over the H100 server&#8217;s 4-year lifetime, it will increase your cost to own by 50% (you can save some by using a 3-year or lifetime subscription, at the cost of a higher upfront). This is one reason we primarily use VFIO passthrough for GPUs and only route through the vGPU stack when fractional allocation is needed. If a tenant rents a whole GPU, VFIO passthrough avoids the licensing overhead entirely.</p><h2>AMD: SR-IOV with GIM and ROCm</h2><p>AMD takes a slightly different approach to GPU virtualization. Interestingly, NVIDIA&#8217;s vGPU stack also uses SR-IOV under the hood on supported data center GPUs &#8212; the host driver calls <code>/usr/lib/nvidia/sriov-manage</code> to enable SR-IOV Virtual Functions, and MIG instances are mapped onto those VFs. But NVIDIA layers its own abstractions on top: you create MIG instances first, then associate them with VFs and set a vGPU type via sysfs. AMD&#8217;s approach is more direct &#8212; PCIe SR-IOV (Single Root I/O Virtualization) is the primary interface, the same technology network cards have used for years. The GIM driver creates VFs, and you pass them through with <code>managed="yes"</code>. No intermediate abstraction.</p><blockquote><p><em>Of the three virtualization modes we support, AMD SR-IOV was by far the easiest to implement. Standard PCIe mechanism, managed passthrough, no nvidia-smi invocations, no licensing hurdles, all images are easy to download and install, and it supports fractional GPU allocation out of the box. Good job, AMD.</em></p></blockquote><h2>How AMD SR-IOV differs</h2><p>With SR-IOV, the GPU&#8217;s Physical Function (PF) stays bound to the <code>amdgpu</code> driver on the host at all times. The GIM (GPU Instance Manager) driver creates Virtual Functions (VFs), each representing a slice of the GPU. These VFs are what get passed to VMs.</p><p>This means:</p><ul><li><p><strong>No runtime driver switching</strong> &#8212; the <code>amdgpu</code> kernel module must be loaded when the host boots and stays loaded.</p></li><li><p><strong>The host driver manages partitioning</strong> &#8212; similar in spirit to MIG, but implemented at the PCIe level rather than via a vendor-specific API.</p></li><li><p><strong>VFs are standard PCI devices</strong> &#8212; they show up in <code>lspci</code>, have their own BDF addresses, and can be managed with standard PCI tooling.</p></li></ul><p>In SPX (Single-GPU Instance) mode, each Physical Function exposes exactly one Virtual Function. This is equivalent to passing the entire GPU to a single VM, but via the SR-IOV path rather than full VFIO passthrough.</p><h2>Managed passthrough</h2><p>Because AMD&#8217;s SR-IOV VFs are standard PCI virtual functions, we can let libvirt handle the VFIO binding automatically (<code>managed="yes"</code>)</p><pre><code>&lt;hostdev mode=&#8221;subsystem&#8221; type=&#8221;pci&#8221; managed=&#8221;yes&#8221;&gt;
  &lt;source&gt;
    &lt;address domain=&#8221;0x0000&#8221; bus=&#8221;0x09&#8221; slot=&#8221;0x01&#8221; function=&#8221;0x0&#8221;/&gt;
  &lt;/source&gt;
  &lt;address type=&#8221;pci&#8221; domain=&#8221;0x0000&#8221; bus=&#8221;0x00&#8221; slot=&#8221;0x10&#8221; function=&#8221;0&#8221;/&gt;
  &lt;rom bar=&#8221;off&#8221;/&gt;
&lt;/hostdev&gt;</code></pre><p>Notice two differences from the NVIDIA XML:</p><ol><li><p><code>managed="yes"</code> &#8212; libvirt handles binding the VF to <code>vfio-pci</code> and unbinding it when the VM stops</p></li><li><p><strong>No </strong><code>&lt;driver name="vfio"/&gt;</code><strong> element</strong> &#8212; not needed when managed mode is active</p></li></ol><h2>ROCm guest stack</h2><p>The guest-side setup for AMD involves the ROCm (Radeon Open Compute) software stack:</p><ul><li><p><strong>HWE kernel</strong> &#8212; the default Ubuntu 24.04 kernel (6.8) is too old for ROCm&#8217;s <code>amdgpu</code> DKMS module. We install the Hardware Enablement kernel (6.11+) so DKMS can build against a compatible kernel.</p></li><li><p><code>amdgpu-install</code> &#8212; AMD&#8217;s bootstrapper that sets up signed APT repositories. All packages are verified via AMD&#8217;s GPG key.</p></li><li><p><strong>ROCm toolkit with DKMS</strong> &#8212; the compute stack and kernel module for GPU access inside the guest.</p></li><li><p><strong>AMD SMI</strong> &#8212; AMD&#8217;s equivalent of <code>nvidia-smi</code> for GPU monitoring and management.</p></li><li><p><strong>Group configuration</strong> &#8212; we ensure users are automatically added to the <code>render</code> and <code>video</code> groups so they can access the GPU device files without root.</p></li></ul><p>Like our NVIDIA images, the ROCm stack is pre-installed in the guest image.</p><h2>Fractional GPU allocation</h2><p>AMD&#8217;s SR-IOV implementation supports fractional GPU allocation natively &#8212; unlike NVIDIA, which requires a separate MIG mechanism. The GIM driver on the host can create multiple Virtual Functions per Physical Function, each representing a partition of the GPU&#8217;s compute and memory resources.</p><p>The number and size of VFs are configured at the driver level through partition modes. AMD Instinct GPUs (MI300X, MI350X) support several:</p><ul><li><p><strong>SPX</strong> (Single Partition eXtension) &#8212; 1 VF per PF. Whole GPU to one VM. This is what we currently use.</p></li><li><p><strong>DPX</strong> (Dual Partition) &#8212; 2 VFs per PF. Each VF gets half the GPU&#8217;s compute and memory.</p></li><li><p><strong>QPX</strong> (Quad Partition) &#8212; 4 VFs per PF.</p></li><li><p><strong>CPX</strong> (Core Partition) &#8212; up to 8 VFs per PF on MI300X (one per XCD die).</p></li></ul><p>Compared to NVIDIA MIG, AMD&#8217;s partitioning is simpler to reason about: the mode is set at the driver/firmware level, the VFs appear as standard PCIe devices, and there are no profile compatibility constraints to worry about. The trade-off is less granularity &#8212; you can&#8217;t mix partition sizes on the same GPU the way MIG allows (e.g., one 3/7 instance and one 4/7 instance).</p><p>From the libvirt perspective, there&#8217;s no difference between a whole-GPU VF and a fractional VF. Both are standard PCIe virtual functions, and both use <code>managed="yes"</code>and both use the same XML structure.</p><h2>Comparison</h2><p>Here&#8217;s how the three approaches stack up:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AUDJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AUDJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 424w, https://substackcdn.com/image/fetch/$s_!AUDJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 848w, https://substackcdn.com/image/fetch/$s_!AUDJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 1272w, https://substackcdn.com/image/fetch/$s_!AUDJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AUDJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png" width="1400" height="693" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:693,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!AUDJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 424w, https://substackcdn.com/image/fetch/$s_!AUDJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 848w, https://substackcdn.com/image/fetch/$s_!AUDJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 1272w, https://substackcdn.com/image/fetch/$s_!AUDJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ea91b72-5384-4e77-a81b-d7592b5edff9_1400x693.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Closing thoughts</h2><p>GPU virtualization is harder than CPU or network virtualization because GPUs were designed for bare-metal performance, not sharing. Both NVIDIA and AMD have made significant progress, but have taken slightly different approaches: NVIDIA offers more flexibility (flexible MIG profile combinations), while AMD offers greater simplicity (standard SR-IOV; virtual functions are managed by the driver).</p><p>If you want to try it out, you can rent GPU VMs on <a href="https://cloudrift.ai/">CloudRift</a> &#8212; we have NVIDIA RTX, H100, H200, and B200, as well as AMD Instinct machines available.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kernelspace.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kernel Space! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Host Setup for QEMU KVM GPU Passthrough with VFIO on Linux]]></title><description><![CDATA[From &#8220;black magic&#8221; to reproducible results]]></description><link>https://kernelspace.substack.com/p/host-setup-for-qemu-kvm-gpu-passthrough</link><guid isPermaLink="false">https://kernelspace.substack.com/p/host-setup-for-qemu-kvm-gpu-passthrough</guid><dc:creator><![CDATA[Dmitry Trifonov]]></dc:creator><pubDate>Thu, 09 Apr 2026 04:51:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!6J0Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GPU passthrough shouldn&#8217;t feel like sorcery. If you&#8217;ve ever lost a weekend to half-working configs, random resets, or a guest that only boots when the moon is right, this guide is for you. I have pulled lots of hair while hardening the <a href="https://cloudrift.ai">CloudRift</a> VM service for a variety of consumer (RTX 4090, 5090, PRO 6000) and data center (H100, B200) GPUs, so writing this guide<br>to help you avoid common pitfalls.</p><p>I&#8217;ll focus specifically on the host node configuration for GPU passthrough. Thus, this guide is relevant regardless of whether you&#8217;re using Proxmox or plain libvirt/QEMU. The provided instructions have been tested on Ubuntu 22.04 and 24.04 with various NVIDIA GPUs.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kernelspace.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kernel Space! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>To keep this guide manageable, I won&#8217;t delve into lower-level details, such as specific domain XML tricks, Linux kernel builds, or GPU firmware flashing. In most cases, you don&#8217;t need to fiddle with those.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6J0Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6J0Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6J0Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6J0Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6J0Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6J0Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg" width="1400" height="1050" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1050,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!6J0Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 424w, https://substackcdn.com/image/fetch/$s_!6J0Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 848w, https://substackcdn.com/image/fetch/$s_!6J0Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!6J0Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdcc96e15-6365-42df-9156-2d3c8fea7161_1400x1050.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>An 8-GPU rig assembled by <a href="https://www.neuralrack.ai/">NeuralRack</a> &#8212; a typical VM GPU-rental rig.</p><h2>1. Remove NVIDIA drivers</h2><p>The first step is to remove the NVIDIA drivers. It is not required, but NVIDIA drivers tend to cause issues with passthrough in one way or another, so it&#8217;s better to remove them altogether.</p><blockquote><p>If you&#8217;re configuring your own work PC with mutiple GPUs, skip this step as without NVIDIA drivers you won&#8217;t be able to run UI applications. In this case, the passthrough robustness is likely not a priority for you. However, I strongly recommend removing NVIDIA drivers on headless servers.</p></blockquote><p>If the NVIDIA driver is installed from the repository, you can remove it using the following commands:</p><pre><code>sudo apt-get remove --purge &#8216;^nvidia-.*&#8217;
sudo apt autoremove</code></pre><p>If you&#8217;ve installed the driver using the RUN file, remove it using:</p><pre><code>sudo /usr/bin/nvidia-uninstall</code></pre><p>Remove configs if any.</p><pre><code>sudo rm -rf /etc/X11/xorg.conf
sudo rm -rf /etc/modprobe.d/nvidia*.conf
sudo rm -rf /lib/modprobe.d/nvidia*.conf</code></pre><p>Reboot the system after driver removal.</p><pre><code>sudo reboot</code></pre><h2>2. Check BIOS, IOMMU Support, and IOMMU Group Assignment</h2><p>The next step is to check virtualization and IOMMU support. We need to check four things:</p><ol><li><p>Virtualization is enabled (AMD-Vi / Intel VT-D options are enabled in BIOS). If present, enable &#8220;Above 4G decoding&#8221; and &#8220;Resizable BAR (ReBAR)&#8221; options in BIOS as well.</p></li><li><p>IOMMU is active (groups exist).</p></li><li><p>Each GPU and its audio function are isolated in their own IOMMU group.</p></li><li><p>GPU groups contain only GPU/video-audio functions and PCI bridges &#8212; no NICs, NVMe, SATA, etc.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KK04!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KK04!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 424w, https://substackcdn.com/image/fetch/$s_!KK04!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 848w, https://substackcdn.com/image/fetch/$s_!KK04!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 1272w, https://substackcdn.com/image/fetch/$s_!KK04!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KK04!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png" width="797" height="594" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:594,&quot;width&quot;:797,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!KK04!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 424w, https://substackcdn.com/image/fetch/$s_!KK04!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 848w, https://substackcdn.com/image/fetch/$s_!KK04!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 1272w, https://substackcdn.com/image/fetch/$s_!KK04!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff49b0b2f-abb6-47b3-96f0-dec203ab2a0a_797x594.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Enable IOMMU in Bios</p><p>You can use the following handy-dandy script to check those preconditions.</p><blockquote><p>AI goes overboard when generating helper scripts, doesn&#8217;t it? I can&#8217;t complain, though. It provides a lot of useful information.</p></blockquote><pre><code>#!/usr/bin/env bash
# VFIO host sanity check: IOMMU support + GPU-containing groups

set -u  # don&#8217;t use -e so greps that find nothing don&#8217;t abort

# --- helpers ---------------------------------------------------------------
have() { command -v &#8220;$1&#8221; &gt;/dev/null 2&gt;&amp;1; }

read_klog() {
  if have journalctl; then journalctl -k -b 0 2&gt;/dev/null
  else dmesg 2&gt;/dev/null
  fi
}

trim() { sed -e &#8216;s/^[[:space:]]*//&#8217; -e &#8216;s/[[:space:]]*$//&#8217;; }

# --- 1) CPU vendor + boot flags -------------------------------------------
CPU_VENDOR=&#8221;$(
  (lscpu 2&gt;/dev/null | awk -F: &#8216;/Vendor ID/{print $2}&#8217; | trim) ||
  (grep -m1 &#8216;vendor_id&#8217; /proc/cpuinfo 2&gt;/dev/null | awk &#8216;{print $3}&#8217;)
)&#8221;
[ -z &#8220;${CPU_VENDOR}&#8221; ] &amp;&amp; CPU_VENDOR=&#8221;(unknown)&#8221;

CMDLINE=&#8221;$(cat /proc/cmdline 2&gt;/dev/null || echo &#8216;&#8217;)&#8221;
HAS_INTEL_FLAG=$(echo &#8220;$CMDLINE&#8221; | grep -q &#8216;intel_iommu=on&#8217; &amp;&amp; echo yes || echo no)
HAS_AMD_FLAG=$(echo &#8220;$CMDLINE&#8221; | grep -q &#8216;amd_iommu=on&#8217; &amp;&amp; echo yes || echo no)
HAS_PT_FLAG=$(echo &#8220;$CMDLINE&#8221; | grep -q &#8216;iommu=pt&#8217; &amp;&amp; echo yes || echo no)

# --- 2) Kernel log signals ------------------------------------------------
KLOG=&#8221;$(read_klog)&#8221;

DISABLED_MSG=$(echo &#8220;$KLOG&#8221; | egrep -i &#8216;IOMMU.*disabled by BIOS|DMAR:.*disabled|AMD-Vi:.*disabled&#8217; || true)
ENABLED_MSG=$(echo &#8220;$KLOG&#8221; | egrep -i &#8216;DMAR: IOMMU enabled|AMD-Vi:.*IOMMU.*enabled|IOMMU: .*enabled&#8217; || true)
IR_MSG=$(echo &#8220;$KLOG&#8221; | egrep -i &#8216;Interrupt remapping enabled&#8217; || true)

# --- 3) IOMMU groups presence --------------------------------------------
GROUPS_DIR=&#8221;/sys/kernel/iommu_groups&#8221;
GROUP_COUNT=0
if [ -d &#8220;$GROUPS_DIR&#8221; ]; then
  GROUP_COUNT=$(find &#8220;$GROUPS_DIR&#8221; -mindepth 1 -maxdepth 1 -type d 2&gt;/dev/null | wc -l | awk &#8216;{print $1}&#8217;)
fi

# Heuristic: active if groups exist (&gt;0). Logs help explain state.
IOMMU_ACTIVE=&#8221;no&#8221;
[ &#8220;$GROUP_COUNT&#8221; -gt 0 ] &amp;&amp; IOMMU_ACTIVE=&#8221;yes&#8221;

# --- 4) Report summary ----------------------------------------------------
echo &#8220;=== IOMMU Summary ===&#8221;
echo &#8220;CPU vendor           : $CPU_VENDOR&#8221;
echo &#8220;Kernel cmdline       : $CMDLINE&#8221;
echo &#8220;Boot flags           : intel_iommu=$HAS_INTEL_FLAG  amd_iommu=$HAS_AMD_FLAG  iommu=pt=$HAS_PT_FLAG&#8221;
echo &#8220;Groups directory     : $GROUPS_DIR  (exists: $([ -d &#8220;$GROUPS_DIR&#8221; ] &amp;&amp; echo yes || echo no))&#8221;
echo &#8220;IOMMU group count    : $GROUP_COUNT&#8221;
echo &#8220;Kernel says enabled  : $([ -n &#8220;$ENABLED_MSG&#8221; ] &amp;&amp; echo yes || echo no)&#8221;
echo &#8220;Interrupt remapping  : $([ -n &#8220;$IR_MSG&#8221; ] &amp;&amp; echo yes || echo no)&#8221;
echo &#8220;Kernel says disabled : $([ -n &#8220;$DISABLED_MSG&#8221; ] &amp;&amp; echo yes || echo no)&#8221;
echo &#8220;IOMMU ACTIVE?        : $IOMMU_ACTIVE&#8221;
echo

if [ -n &#8220;$ENABLED_MSG&#8221; ]; then
  echo &#8220;--- Kernel enable lines ---&#8221;
  echo &#8220;$ENABLED_MSG&#8221;
  echo
fi
if [ -n &#8220;$DISABLED_MSG&#8221; ]; then
  echo &#8220;--- Kernel disable lines ---&#8221;
  echo &#8220;$DISABLED_MSG&#8221;
  echo
fi

# --- 5) Original: list only GPU-containing groups -------------------------
echo &#8220;=== GPU-Containing IOMMU Groups ===&#8221;
if [ ! -d &#8220;$GROUPS_DIR&#8221; ] || [ &#8220;$GROUP_COUNT&#8221; -eq 0 ]; then
  echo &#8220;(no IOMMU groups found)&#8221;
else
  declare -A GPU_COUNT_BY_GROUP=()
  group_warnings=()

  for g in &#8220;$GROUPS_DIR&#8221;/*; do
    [ -d &#8220;$g&#8221; ] || continue
    group_num=$(basename &#8220;$g&#8221;)
    gpu_found=false
    device_lines=&#8221;&#8220;
    non_gpu_non_bridge=false
    gpu_count_in_this_group=0

    for d in &#8220;$g&#8221;/devices/*; do
      [ -e &#8220;$d&#8221; ] || continue
      pci_addr=$(basename &#8220;$d&#8221;)
      # -nns prints class code [XXXX] and vendor:device [vvvv:dddd]
      line=$(lspci -nns &#8220;$pci_addr&#8221; 2&gt;/dev/null || echo &#8220;$pci_addr (unlisted)&#8221;)
      device_lines+=&#8221;$line&#8221;$&#8217;\n&#8217;

      # Extract first [...] which is the class code, e.g. 0300, 0302, 0403, 0604, 0600
      class_code=$(echo &#8220;$line&#8221; | awk -F&#8217;[][]&#8217; &#8216;{print $2}&#8217;)

      # Detect GPUs / 3D controllers and their HDA audio functions
      if echo &#8220;$line&#8221; | grep -qE &#8216;VGA compatible controller|3D controller&#8217;; then
        gpu_found=true
        gpu_count_in_this_group=$((gpu_count_in_this_group+1))
      fi

      # Allowlist: 0300(VGA), 0302(3D), 0403(HDA audio), 0600(host bridge), 0604(PCI bridge)
      case &#8220;$class_code&#8221; in
        0300|0302|0403|0600|0604) : ;;
        *) non_gpu_non_bridge=true ;;
      esac
    done

    if $gpu_found; then
      echo &#8220;IOMMU Group $group_num:&#8221;
      echo &#8220;$device_lines&#8221;

      # Track GPUs per group
      GPU_COUNT_BY_GROUP[&#8221;$group_num&#8221;]=$gpu_count_in_this_group

      # Warn if unexpected devices share the group with the GPU
      if $non_gpu_non_bridge; then
        group_warnings+=(&#8221;WARN: Group $group_num contains non-GPU, non-audio, non-bridge devices (consider different slot/CPU root complex or ACS).&#8221;)
      fi
    fi
  done

  # Post-checks
  # 1) Each GPU should be alone (one GPU per group)
  shared_groups=()
  for gnum in &#8220;${!GPU_COUNT_BY_GROUP[@]}&#8221;; do
    if [ &#8220;${GPU_COUNT_BY_GROUP[$gnum]}&#8221; -gt 1 ]; then
      shared_groups+=(&#8221;$gnum&#8221;)
    fi
  done

  if [ &#8220;${#shared_groups[@]}&#8221; -gt 0 ]; then
    echo
    echo &#8220;WARN: Multiple GPUs share these IOMMU groups: ${shared_groups[*]} (prefer one GPU per group for VFIO).&#8221;
  fi

  # 2) Any non-bridge co-residents?
  if [ &#8220;${#group_warnings[@]}&#8221; -gt 0 ]; then
    echo
    printf &#8220;%s\n&#8221; &#8220;${group_warnings[@]}&#8221;
  fi
fi</code></pre><p>Here is what a good summary should look like:</p><pre><code>=== IOMMU Summary ===
CPU vendor           : AuthenticAMD
Kernel cmdline       : BOOT_IMAGE=/boot/vmlinuz-6.8.0-71-generic root=/dev/mapper/vgroot-lvroot ro systemd.unified_cgroup_hierarchy=false default_hugepagesz=1G hugepages=576 hugepagesz=1G nomodeset video=efifb:off iommu=pt pci=realloc pcie_aspm=off amd_iommu=on vfio-pci.ids=10de:0000,10de:204b,10de:22e8,10de:2bb1 modprobe.blacklist=nouveau,nvidia,nvidiafb,snd_hda_intel
Boot flags           : intel_iommu=no  amd_iommu=yes  iommu=pt=yes
Groups directory     : /sys/kernel/iommu_groups  (exists: yes)
IOMMU group count    : 57
Kernel says enabled  : no
Interrupt remapping  : no
Kernel says disabled : no
IOMMU ACTIVE?        : yes

=== GPU-Containing IOMMU Groups ===
IOMMU Group 13:
c1:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2bb1] (rev a1)
c1:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:22e8] (rev a1)

IOMMU Group 16:
c6:00.0 PCI bridge [0604]: ASPEED Technology, Inc. AST1150 PCI-to-PCI Bridge [1a03:1150] (rev 06)
c7:00.0 VGA compatible controller [0300]: ASPEED Technology, Inc. ASPEED Graphics Family [1a03:2000] (rev 52)

IOMMU Group 27:
81:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2bb1] (rev a1)
81:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:22e8] (rev a1)

IOMMU Group 42:
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2bb1] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:22e8] (rev a1)

IOMMU Group 54:
41:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2bb1] (rev a1)
41:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:22e8] (rev a1)</code></pre><p>As we can see, IOMMU support is enabled, and all GPUs and their corresponding audio devices are in separate IOMMU groups.</p><p>Sometimes you may see PCI bridges in the GPU IOMMU group. This is normal.</p><pre><code>=== GPU-Containing IOMMU Groups ===
IOMMU Group 13:
40:01.0 Host bridge [0600]: Advanced Micro Devices, Inc. [AMD] Starship/Matisse PCIe Dummy Host Bridge [1022:1482]
40:01.1 PCI bridge [0604]: Advanced Micro Devices, Inc. [AMD] Starship/Matisse GPP Bridge [1022:1483]
41:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2b85] (rev a1)
41:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:22e8] (rev a1)

IOMMU Group 32:
20:03.0 Host bridge [0600]: Advanced Micro Devices, Inc. [AMD] Starship/Matisse PCIe Dummy Host Bridge [1022:1482]
20:03.1 PCI bridge [0604]: Advanced Micro Devices, Inc. [AMD] Starship/Matisse GPP Bridge [1022:1483]
25:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2b85] (rev a1)
25:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:22e8] (rev a1)</code></pre><h2>3. Leverage 1G Huge Pages</h2><p>This step is optional. However, if you have more than 512GB of RAM on your system, it is highly encouraged. From experience, aside from providing performance benefit, 1 GB huge pages make the VM startup much more reliable on high-memory systems.</p><p><strong>Rule of thumb</strong></p><ul><li><p><strong>&lt; 128 GB RAM</strong>: usually skip (benefit is small).</p></li><li><p><strong>128&#8211;512 GB</strong>: optional; can reduce latency jitter.</p></li><li><p><strong>&gt; 512 GB</strong>: recommended for reliability and predictable performance.</p></li></ul><p><strong>Why 1 GiB pages help</strong></p><ul><li><p>Fewer page-table walks &#8594; fewer TLB misses.</p></li><li><p>Lower page management overhead.</p></li><li><p>More predictable VM start times on large RAM allocations.</p></li></ul><h3>3.1 Check Huge Page Support</h3><p>To confirm 1G huge page support on your system, check the pdpe1gb CPU flag.</p><pre><code>grep -m1 pdpe1gb /proc/cpuinfo &gt;/dev/null &amp;&amp; echo &#8220;&#10003; CPU supports 1GiB pages&#8221; || echo &#8220;&#10007; No 1GiB page support&#8221;</code></pre><h3>3.2 Allocate Huge Pages</h3><p>Determine how much memory you want to reserve for the VMs. You need to reserve that much memory for huge pages plus a buffer.</p><blockquote><p>Note that the memory reserved for huge pages will not be usable on the host system.</p></blockquote><p>For example, if you want to dedicate 2000GB to virtual machines with an 80GB buffer, you would need 2080 huge pages.</p><p>I use the following empirically validated table to determine the huge page configuration on a high-memory multi-GPU system.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eeNG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eeNG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 424w, https://substackcdn.com/image/fetch/$s_!eeNG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 848w, https://substackcdn.com/image/fetch/$s_!eeNG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 1272w, https://substackcdn.com/image/fetch/$s_!eeNG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eeNG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png" width="1150" height="500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:1150,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!eeNG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 424w, https://substackcdn.com/image/fetch/$s_!eeNG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 848w, https://substackcdn.com/image/fetch/$s_!eeNG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 1272w, https://substackcdn.com/image/fetch/$s_!eeNG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3bbccfb-5314-43bb-99bc-d23925e693f1_1150x500.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>Is there a reliable formula to determine the huge page buffer size? Good question. If you know one, let me know in the comments. It makes sense that we need to leave some memory for the system, but it feels that the gap between memory dedicated for VM allocation and the number of huge pages is not necessary. After VM startup we&#8217;ll see that the system has allocated the exact number of requested huge pages, so why do we need a buffer and how big should it be? Is it because of the fragmentation? Empirically, I&#8217;ve confirmed that it is needed. Without a buffer I was occasionally running into OOM errors.</p></blockquote><p>Run the following command to allocate 2000 pages (it will take a while):</p><pre><code>echo 2000 | sudo tee /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages</code></pre><p>To check that huge pages were allocated, run</p><pre><code>grep -i huge /proc/meminfo</code></pre><p>Look at Hugepagesize and Hugetlb. They tell the huge page size and the total amount of RAM allocated for huge pages. You should see output like this:</p><pre><code>AnonHugePages:     79872 kB
ShmemHugePages:        0 kB
FileHugePages:         0 kB
HugePages_Total:    2080
HugePages_Free:     1580
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:    1048576 kB
Hugetlb:        2181038080 kB</code></pre><p>To deallocate, invoke:</p><pre><code>echo 0 | sudo tee /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages</code></pre><h3>3.3 Make Huge Pages Persistent</h3><p>Edit the <em>/etc/default/grub</em> file and modify the line containing <em>GRUB_CMDLINE_LINUX</em>. <br><br>Add <em>default_hugepagesz=1G hugepagesz=1G hugepages=&lt;num&gt;</em> to the <em>GRUB_CMDLINE_LINUX</em> options.</p><p>The <em>&lt;num&gt;</em> is the number of huge pages to allocate. For example:</p><pre><code>GRUB_CMDLINE_LINUX=&#8221;... default_hugepagesz=1G hugepagesz=1G hugepages=200&#8221;</code></pre><blockquote><p><strong>Be careful. If you specify more huge pages than the system can allocate, the machine will not boot.</strong></p></blockquote><p>Update the GRUB changes, reboot, and verify that huge pages are allocated (or do this in the end).</p><pre><code>sudo update-grub
sudo reboot</code></pre><h3>3.4 (Optional) Mount Huge Page Table</h3><p>Many systems already have <em>/dev/hugepages</em>. If not, or if you want a dedicated mount:</p><pre><code>sudo mkdir -p /mnt/hugepages-1G
sudo mount -t hugetlbfs -o pagesize=1G none /mnt/hugepages-1G</code></pre><p>Check that the mount point is present by running</p><pre><code>hugetlbfs /dev/hugepages hugetlbfs rw,nosuid,nodev,relatime,pagesize=1024M 0 0
hugetlbfs /mnt/hugepages-1G hugetlbfs rw,relatime,pagesize=1024M 0 0</code></pre><p>To persist &#8212; invoke:</p><pre><code>echo &#8220;none /mnt/hugepages-1G hugetlbfs pagesize=1G 0 0&#8221; | sudo tee -a /etc/fs</code></pre><h3>3.5 Configure your Virtualization Software to use Huge Pages</h3><p>Neither Proxmox nor libvirt is using huge pages by default. <br><br>To use them in libvirt, you need to add the following section to the domain XML:</p><pre><code>&lt;memoryBacking&gt;
  &lt;hugepages&gt;
    &lt;page size=&#8217;1048576&#8217; unit=&#8217;KiB&#8217;/&gt;
  &lt;/hugepages&gt;
  &lt;locked/&gt;
&lt;/memoryBacking&gt;</code></pre><p>In Proxmox CLI, you do it as follows:</p><pre><code>qm set &lt;vmid&gt; --hugepages 1024   # use 1GiB pages
qm set &lt;vmid&gt; --keephugepages 1  # optional: keep reserved after shutdown</code></pre><h2>4. Bind to VFIO Early</h2><p>For maximum stability, have VFIO claim the GPU at boot so no runtime driver swaps occur (Proxmox/libvirt will otherwise bind/unbind around VM start/stop).</p><blockquote><p>I strongly recommend this step on headless servers. However, for your local PC setup you might need to resort to dynamic binding as VFIO driver will claim your main GPU.</p></blockquote><h3>4.1 Identify the PCI IDs to bind</h3><p>First, you need to determine the PCI vendor ID and device ID for your GPUs.</p><p>List all NVIDIA functions (display + audio, and any auxiliary functions):</p><pre><code>lspci -nn | grep -i nvidia</code></pre><p>Example (RTX 5090):</p><pre><code>01:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:2b85] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation Device [10de:22e8] (rev a1)</code></pre><h3>4.2 Give VFIO first claim</h3><p>Add the following lines to <em>GRUB_CMDLINE_LINUX_DEFAULT</em> in<em> /etc/default/grub</em>, replacing the PCI vendor ID and device ID with the appropriate values. Keep other options if needed.</p><pre><code>GRUB_CMDLINE_LINUX_DEFAULT=&#8221;modprobe.blacklist=nouveau,nvidia,nvidiafb,snd_hda_intel vfio-pci.ids=10de:2b85,10de:22e8 ...&#8221;</code></pre><p>Proxmox is likely using systemd-boot by default instead of GRUB. <a href="https://pve.proxmox.com/pve-docs/pve-admin-guide.html#sysboot">Check the bootloader</a> you&#8217;re using and adjust the kernel command line accordingly.</p><blockquote><p>Many online manuals suggest adding VFIO modules to /etc/modprobe.d/vfio.conf, but this approach has not always worked for me. I recommend early binding via the kernel command line.</p></blockquote><h3>4.3 Ensure VFIO is in the initramfs</h3><p>We need to make sure that VFIO modules are loaded early in the boot process. To achieve this, we include them in the <em>initramfs</em>.</p><pre><code>sudo tee -a /etc/initramfs-tools/modules &gt;/dev/null &lt;&lt;&#8217;EOF&#8217;
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
EOF</code></pre><h3>4.4 Reboot and verify</h3><p>Update GRUB, initramfs, and reboot.</p><pre><code>sudo update-initramfs -u -k all
sudo update-grub
sudo reboot</code></pre><p>After reboot, check that VFIO drivers are in use. You can use</p><pre><code>lspci -k | grep -A 2 -i nvidia</code></pre><p>You should see VFIO drivers in use.</p><pre><code>81:00.0 VGA compatible controller: NVIDIA Corporation Device 2b85 (rev a1)
    Subsystem: Gigabyte Technology Co., Ltd Device 416f
    Kernel driver in use: vfio-pci
    Kernel modules: nvidiafb, nouveau
81:00.1 Audio device: NVIDIA Corporation Device 22e8 (rev a1)
    Subsystem: NVIDIA Corporation Device 0000
    Kernel driver in use: vfio-pci
    Kernel modules: snd_hda_intel</code></pre><blockquote><p>To be fair, there was one machine where this technique to bind VFIO failed. The system was aggressively binding snd_hda_intel driver to the GPU audio function. However, this method worked for me in all other cases.</p></blockquote><h2>5. Other GRUB Options</h2><p>Here is a summary of other kernel command line options that you may want to consider, along with my thoughts on each.</p><ul><li><p><strong>pci=realloc</strong>: Reallocate PCI resources forces the kernel to reassign PCI bus resources (MMIO/IOBARs) from scratch, ignoring what the firmware/BIOS assigned. It helps avoid issues when the BIOS didn&#8217;t allocate enough space for devices (common with large GPUs or multiple devices). Fixes &#8220;BAR can&#8217;t be assigned&#8221; or &#8220;resource busy&#8221; errors. <em><strong>This option is helpful. I also like to include it in the guest OS kernel parameters. It occasionally helps to work around BAR allocation issues. However, there is no need to list it unless the system has PCI device enumeration issues.</strong></em></p></li><li><p><strong>iommu=pt</strong>: IOMMU passthrough mode tells the kernel to enable the IOMMU but use pass-through mode for DMA mappings by default.<br>For VFIO GPU passthrough &#8212; allows the device to access physical memory directly with minimal performance penalty. <em><strong>I haven&#8217;t had a chance to test the performance gains, so I can only say that this option didn&#8217;t cause any issues.</strong></em></p></li><li><p><strong>pcie_aspm=off</strong>: Disable PCIe Active State Power Management, which is a power-saving feature that reduces PCIe link power in idle states. Some PCIe devices (especially GPUs) have trouble retraining links or waking from ASPM low-power states, leading to hangs or device-inaccessible errors. This option was introduced to my configurations after I lost a lot of time on the <a href="https://medium.com/@dmitrytrifonov/bug-bounty-nvidia-reset-bug-fd3c6c99d860">Reset Bug</a>. <em><strong>It didn&#8217;t help. I don&#8217;t consider this option helpful at the moment, but I am still evaluating it.</strong></em></p></li><li><p><strong>nomodeset</strong>: Disable kernel mode setting (KMS) for all GPUs; prevents DRM drivers from taking over the console. <strong>This option is intended for use with headless servers only. It can break desktop/console output. I typically use it since we&#8217;re working with headless servers only.</strong></p></li><li><p><strong>video=efifb:off</strong>: Disables the firmware EFI framebuffer, so simpledrm/efifb won&#8217;t grab the boot GPU before VFIO claims it. <strong>This option is outdated and has no effect on systems with modern kernels. I list it for completeness.</strong></p></li><li><p><strong>intel_iommu=on</strong> / <strong>amd_iommu=on</strong>: Enable IOMMU support for Intel and AMD. <strong>These are enabled by default, so there is no need to add them to kernel parameters.</strong></p></li></ul><p>Here is how the typical kernel command line should look on a headless server with over 500GB of RAM.</p><pre><code>nomodeset
modprobe.blacklist=nouveau,nvidia,nvidiafb,snd_hda_intel
vfio-pci.ids=10de:2b85,10de:22e8
default_hugepagesz=1G hugepagesz=1G hugepages=400</code></pre><h2>Conclusion</h2><p>The VFIO GPU passthrough is a finicky process. It is sensitive to host hardware and software configuration. However, with enough diligence, you can make it robust and reliable. <strong>I strongly believe in this approach and rely on VFIO GPU passthrough as the primary tool for our GPU rental service at <a href="https://cloudrift.ai/">cloudrift.ai</a>.</strong></p><p>I hope this guide helped you to improve your homelab or data center setup. If you notice inaccuracies or have suggestions, please don&#8217;t hesitate to let me know, so we can work together to improve the workflow.</p><p>Final host checklist:</p><ul><li><p>Enable IOMMU, Above 4G, and (where applicable) ReBAR in the BIOS.</p></li><li><p>Verify clean IOMMU groups; each GPU (+ audio) isolated.</p></li><li><p>Bind to vfio-pci early.</p></li><li><p>Size huge pages (1 GiB on high-RAM hosts) and confirm in <em>/proc/meminfo</em>.</p></li><li><p>Configure other kernel command-line options as needed.</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://kernelspace.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Kernel Space! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Evolution of GPU Programming]]></title><description><![CDATA[From Smart Pixels to the Backbone of an AI-driven World]]></description><link>https://kernelspace.substack.com/p/evolution-of-gpu-programming</link><guid isPermaLink="false">https://kernelspace.substack.com/p/evolution-of-gpu-programming</guid><dc:creator><![CDATA[Dmitry Trifonov]]></dc:creator><pubDate>Tue, 07 Apr 2026 02:12:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5e_y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every decade GPUs reinvented themselves &#8212; from drawing triangles to generating worlds, and now, reasoning with language. I have realized that throughout my entire programming journey, I have been working closely with GPUs and tried countless ways to program them. From writing pixel shaders in GLSL to implementing real-time 3D scanning algorithms in OpenCL to optimizing deep learning models in PyTorch and Tensorflow. So what can be a better way to share my experience than to write a blog post about the evolution of GPU programming, full of <strong>nostalgia and memes</strong>?</p><p>A lot has changed in the GPU programming landscape over the years. New programming models, new frameworks, and new hardware architectures have emerged. There is no point in studying them nowadays; however, the evolutionary path of GPU programming is quite interesting. If you&#8217;re an AI expert or a developer in another field, it can help you broaden your expertise or provide the necessary inspiration to dive into the world of GPU programming. It can give you new ideas to address current problems, especially given that some of the issues we face today in AI were already faced by graphics programmers 25 years ago.</p><p>Here is a mildly entertaining, nostalgia-induced journey through the history of GPU programming from making brick walls that look bumpy in 2000 to optimizing attention mechanisms in LLM models in 2025. Feel free to skip the code snippets if you&#8217;re not interested in programming or are already familiar with the material and would rather enjoy the story.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5e_y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5e_y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5e_y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5e_y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5e_y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5e_y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg" width="519" height="480" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:480,&quot;width&quot;:519,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!5e_y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5e_y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5e_y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5e_y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2a2772cc-d048-48d1-96c5-be08183ed672_519x480.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Smart Pixels</h2><p>In the early 2000s, the GPUs were used exclusively for visualization, and the rendering pipeline was completely fixed-function. It was akin to HTML, where you would predefine your scene: geometry, textures, position of lights and camera, and the GPU would take care of rendering it. You could, of course, customize it on the fly, but only in a limited way, by changing parameters of the predefined functions, and this customization has happened entirely on the CPU side.</p><p>Here is a simple example of rendering a triangle using old-school OpenGL, taken from <a href="https://cs.lmu.edu/~ray/notes/openglexamples/">here</a>.</p><pre><code>// Set every pixel in the frame buffer to the current clear color.
glClear(GL_COLOR_BUFFER_BIT);

// Drawing is done by specifying a sequence of vertices. The way these
// vertices are connected. GL_POLYGON constructs a filled polygon.
glBegin(GL_POLYGON);
  glColor3f(1, 0, 0); glVertex3f(-0.6, -0.75, 0.5);
  glColor3f(0, 1, 0); glVertex3f(0.6, -0.75, 0);
  glColor3f(0, 0, 1); glVertex3f(0, 0.75, 0);
glEnd();

// Flush drawing command buffer to make drawing happen as soon as possible.
glFlush();</code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aUcV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aUcV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 424w, https://substackcdn.com/image/fetch/$s_!aUcV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 848w, https://substackcdn.com/image/fetch/$s_!aUcV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 1272w, https://substackcdn.com/image/fetch/$s_!aUcV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aUcV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png" width="400" height="282" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:282,&quot;width&quot;:400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!aUcV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 424w, https://substackcdn.com/image/fetch/$s_!aUcV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 848w, https://substackcdn.com/image/fetch/$s_!aUcV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 1272w, https://substackcdn.com/image/fetch/$s_!aUcV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc269935d-8f26-400f-9d18-ae38ac3a7029_400x282.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Rendering a triangle with OpenGL</figcaption></figure></div><p>The idea that you can actually program how pixels are rendered on the screen was quite revolutionary in the early 2000s.</p><p>My first interaction with these ideas was through <a href="https://gamedev-ru.translate.goog/code/articles/?id=4155&amp;_x_tr_sl=ru&amp;_x_tr_tl=en&amp;_x_tr_hl=en&amp;_x_tr_pto=wapp">an article</a> from 2001 on a popular Russian game-development website about the <a href="https://registry.khronos.org/OpenGL/extensions/NV/NV_register_combiners.txt">NV_register_combiners</a> extension for OpenGL. Surprisingly, the article is still available online.</p><p>This extension enabled you to program how the final color of a pixel is computed from various inputs, such as texture colors and lighting, allowing you to create more complex visual effects. This computation is performed on the GPU, enabling real-time performance. It was akin to running a small assembly program on the GPU for each pixel being rendered.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ilyt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ilyt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 424w, https://substackcdn.com/image/fetch/$s_!Ilyt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 848w, https://substackcdn.com/image/fetch/$s_!Ilyt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 1272w, https://substackcdn.com/image/fetch/$s_!Ilyt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ilyt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png" width="1400" height="556" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:556,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Ilyt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 424w, https://substackcdn.com/image/fetch/$s_!Ilyt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 848w, https://substackcdn.com/image/fetch/$s_!Ilyt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 1272w, https://substackcdn.com/image/fetch/$s_!Ilyt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd9d4cc8a-a2a9-4469-a3f2-386d4213448d_1400x556.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Rendering small surface details with NV register combiners.</figcaption></figure></div><p>Graphics developers were fascinated by this idea, as it enabled them to increase the visual fidelity of the scenes dramatically. Shortly after, the <a href="https://www.khronos.org/opengl/wiki/Core_Language_(GLSL)">GLSL</a> was conceptualized and formally introduced in 2004, allowing the writing of more complex shaders (small programs that define how to manipulate geometry or pixels) in a C-like language.</p><blockquote><p>Are you feeling GPU poor? Imagine that it was even worse back then! Every new generation of GPUs introduced new features and capabilities, dramatically increasing the visual fidelity of games. Having a new GPU was a prerequisite for playing the latest and greatest games. For those into computer graphics, the frustration of the wait and the excitement of getting the new card were doubled! Luckily, I could trick my parents into buying me a new card, because it supported <strong>SHADERS</strong>! Which, of course, were essential to advance my computer science education. Having the ability to play Oblivion on high settings was just a nice bonus.</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kupP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kupP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kupP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kupP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kupP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kupP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg" width="1080" height="1230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1230,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!kupP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kupP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kupP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kupP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F682f45b1-2648-450d-b5b9-bf37b0291866_1080x1230.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The &#8220;ugly&#8221; by today&#8217;s standards non-remastered Oblivion looked gorgeous in 2006. It has won the &#8220;Best Graphics&#8221; and &#8220;Best Technology&#8221; awards from several gaming websites. Notice how the bump mapping algorithm was leveraged to create the illusion of bumps on the wall and wrinkles on the emperor's face. In the original version, the wall is entirely flat, and wrinkles are not modelled in the geometry &#8212; image from <a href="https://www.reddit.com/r/videogames/comments/1k5e1ig/early_screenshot_comparison_of_tes_iv_oblivion/">Reddit</a>.</figcaption></figure></div><p>Here is an example of a simple GLSL program from <a href="https://www.rastertek.com/gl4linuxtut20.html">rastertek.com</a> to perform bump mapping, the effect achieved by perturbing the surface normals of a texture to simulate small-scale bumps and wrinkles on the surface of an object.</p><pre><code>in vec2 texCoord;
in vec3 normal;
in vec3 tangent;
in vec3 binormal;

void main(void)
{
    // Sample the pixel color from the texture using the sampler at this texture coordinate location.
    vec4 textureColor = texture(shaderTexture1, texCoord);

    // Sample the pixel from the normal map.
    vec4 bumpMap = texture(shaderTexture2, texCoord);

    // Expand the range of the normal value from (0, +1) to (-1, +1).
    vec3 bumpMap = (bumpMap * 2.0f) - 1.0f;

    // Calculate the normal from the data in the normal map.
    bumpNormal = (bumpMap.x * tangent) + (bumpMap.y * binormal) + (bumpMap.z * normal);

    // Normalize the resulting bump normal.
    bumpNormal = normalize(bumpNormal);

    // Calculate the amount of light on this pixel based on the normal map value.
    float lightIntensity = clamp(dot(bumpNormal, -lightDirection), 0.0f, 1.0f);

    // Determine the final amount of diffuse color based on the diffuse color combined with the light intensity.
    outputColor =  clamp((diffuseLightColor * lightIntensity), 0.0f, 1.0f);

    // Combine the final light color with the texture color.
    outputColor = outputColor * textureColor;
}</code></pre><blockquote><p>What do all these <code>in vec3</code> variables mean? These are the inputs to the shader program. Those are specified per vertex and interpolated across the surface of the triangle being rendered. The interpolation is done by GPU hardware and fed into the shader program for each pixel being rendered. This way, you can have different values for each pixel, allowing for more complex effects. This allows for parallelization of the computation across all pixels being rendered, as each pixel can be processed independently.</p></blockquote><p>Shaders quickly progressed from simple pixel color manipulation to complex effects simulating shadows, reflections, and refractions. Graphics programmers were especially obsessed with simulating complex surface details without increasing the geometric complexity of the scene. The deepest point of this rabbit hole was a <a href="https://learnopengl.com/Advanced-Lighting/Parallax-Mapping">Parallax Occlusion Mapping</a> technique, which performs a type of ray-marching in a pixel shader, i.e., traversing space to determine the intersection of a ray with a surface defined by a heightmap texture. This way, a completely flat surface can appear to have complex 3D details.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JhyO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JhyO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JhyO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JhyO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JhyO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JhyO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg" width="418" height="417" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:417,&quot;width&quot;:418,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!JhyO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JhyO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JhyO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JhyO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc936ecf6-7856-42aa-bdab-ffcab40db060_418x417.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Parallax Occlusion Mapping technique. The cube&#8217;s surface is entirely flat, but it appears to have details &#8212; image from <a href="https://doc.babylonjs.com/features/featuresDeepDive/materials/using/parallaxMapping">babylon.js</a>.</figcaption></figure></div><h2>GPUs as General Purpose Computers</h2><p>At this point, you may wonder about LLMs, deep learning, and the ability to perform general-purpose computations on GPUs. However, take a look at the shader program above. It is just like a piece of C code. Why can&#8217;t we use that to perform arbitrary computations on the GPU? Indeed, we can, and people have been doing so since the early 2000s. However, we need to address one problem first. How do we get data in and out of the GPU?</p><p>Getting data in is pretty straightforward. We can encode our data as a texture or geometry and upload it to the GPU. But how do we get data out? To help with that, we can use techniques like <a href="http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-14-render-to-texture/">render to texture</a>. It allows us to render the output of our shader program to a texture instead of the screen. Then we can read that texture back to the CPU.</p><blockquote><p>For those not familiar with computer graphics terms. Texture is just an image. In computer graphics, textures are used to store image data that can be applied to the surface of 3D models to give them color and detail. A texture is typically a 2D array of pixels, where each pixel contains color information (e.g., RGB values) and sometimes additional information like alpha (transparency) or normal vectors for bump mapping.</p></blockquote><p>This technique is actually even older than shaders themselves, as it was used in the pre-shader era to create effects like dynamic reflections and shadows. For example, to create a reflection effect, you can render the scene from the point of view of a reflected camera (e.g., below the water surface) to a texture, and then use that texture to render the water surface. You can use a pixel shader to distort the texture coordinates, simulating water ripples.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E4qA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E4qA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 424w, https://substackcdn.com/image/fetch/$s_!E4qA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 848w, https://substackcdn.com/image/fetch/$s_!E4qA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 1272w, https://substackcdn.com/image/fetch/$s_!E4qA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E4qA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png" width="1400" height="434" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:434,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!E4qA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 424w, https://substackcdn.com/image/fetch/$s_!E4qA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 848w, https://substackcdn.com/image/fetch/$s_!E4qA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 1272w, https://substackcdn.com/image/fetch/$s_!E4qA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F56529d37-e894-44e2-ad1b-58692027ef02_1400x434.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A schematic visualization of how the render-to-texture technique can be used to simulate reflections</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YQN3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YQN3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 424w, https://substackcdn.com/image/fetch/$s_!YQN3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 848w, https://substackcdn.com/image/fetch/$s_!YQN3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!YQN3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YQN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg" width="640" height="513" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:513,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!YQN3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 424w, https://substackcdn.com/image/fetch/$s_!YQN3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 848w, https://substackcdn.com/image/fetch/$s_!YQN3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!YQN3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F14cb8320-c082-46ca-a591-0674f131d598_640x513.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">An example of a water reflection effect achieved via the render-to-texture technique. Apparently, I was too lazy to fix the face orientation on the yacht model at the time of making that demo.</figcaption></figure></div><p>Some ingenious people figured out that you can use this technique to perform arbitrary computations on the GPU by encoding your input data as a texture, writing a shader program to perform the calculation, rendering the output to a texture, and then reading that texture back to the CPU.</p><p>What can you achieve with this technique? Everything you can with CUDA today. A popular technique in early GPGPU was to use ping-pong rendering, where two textures are alternated for reading and writing. This way, you can compute, take your input texture, compute some function on it, write the result to the output texture, then use that output texture as input for the following computation, and so on. This way, you can build complex computations by chaining together multiple shader programs. And you don&#8217;t have to work with images specifically. You can encode any data as a texture, e.g., a 2D array of floats, a 3D volume of voxels, a graph, and so on.</p><p>For example, the Fast Fourier Transform (FFT) algorithm can be implemented using shaders and the render-to-texture technique. Here is an example of a GPU-based FFT implementation from <a href="https://developer.nvidia.com/gpugems/gpugems2/part-vi-simulation-and-numerical-algorithms/chapter-48-medical-image-reconstruction">GPU Gems 2</a>, along with its medical image reconstruction.</p><p>Here is how a fragment shader for a single FFT pass looks. It is similar to the CUDA kernel you would write today, as shown below. It is essentially a function that is invoked for each pixel of the output texture. It reads data from the input textures, performs some computation, and writes the result as the color of the pixel, which is then stored in the output texture.</p><pre><code>void FragmentProgram(in float4 TexCoordRect
                     : TEXCOORD0, out float4 sColor0
                     : COLOR0, out float4 sColor1
                     : COLOR1, out float4 sColor2
                     : COLOR2, out float4 sColor3
                     : COLOR3, uniform samplerRECT Real1,
                       uniform samplerRECT Imag1, uniform samplerRECT Real2,
                       uniform samplerRECT Imag2,
                       uniform samplerRECT ButterflyLookupI,
                       uniform samplerRECT ButterflyLookupWR,
                       uniform samplerRECT ButterflyLookupWI)
{
  // Read in butterfly indices
  float4 i = texRECT(ButterflyLookupI, TexCoordRect.xy);
  // Read in scrambling coordinates
  float4 WR = texRECT(ButterflyLookupWR, TexCoordRect.xy);
  // Read in weights
  float4 WI = texRECT(ButterflyLookupWI, TexCoordRect.xy);
  
  // Perform the butterfly operation, storing results in the output colors
  float2 Res;
  float2 r1 = float2(i.x, TexCoordRect.y);
  float2 r2 = float2(i.w, TexCoordRect.y);
  float4 InputX1 = texRECT(Real1, r1);
  float4 InputY1 = texRECT(Imag1, r1);
  float4 InputX2 = texRECT(Real1, r2);
  float4 InputY2 = texRECT(Imag1, r2);
  Res.x = WR.x * InputX2.x - WI.x * InputY2.x;
  Res.y = WI.x * InputX2.x + WR.x * InputY2.x;
  sColor0.x = InputX1.x + Res.x;
  sColor1.x = InputY1.x + Res.y;
  float4 InputX1_ = texRECT(Real2, r1);
  float4 InputY1_ = texRECT(Imag2, r1);
  float4 InputX2_ = texRECT(Real2, r2);
  float4 InputY2_ = texRECT(Imag2, r2);
  Res.x = WR.x * InputX2_.x - WI.x * InputY2_.x;
  Res.y = WI.x * InputX2_.x + WR.x * InputY2_.x;
  sColor2.x = InputX1_.x + Res.x;
  sColor3.x = InputY1_.x + Res.y;
}</code></pre><blockquote><p>The code above is written in <a href="https://www.khronos.org/opengl/wiki/cg">Cg</a> language. It is an early attempt by Nvidia to dominate the graphics computing market&#8230;<em> </em>I meant to say,<em> </em>simplify shader programming. Luckily, nobody cared about it, and the market relied on a more universally supported GLSL and HLSL languages.</p></blockquote><p>I was fascinated by these developments! This technique unlocked a remarkable number of new applications in computer graphics, science, and the medical field, among others. Personally, I&#8217;ve used it to implement advanced graphics effects. Here is an example of <a href="http://www.uraldev.ru/articles/35/page/2">using FFT to generate a complex water surface</a>. This technique was used in the movie <a href="https://en.wikipedia.org/wiki/Titanic_(1997_film)">Titanic</a> and in some advanced games, such as <a href="https://en.wikipedia.org/wiki/Assassin%27s_Creed">Assassin&#8217;s Creed</a>.</p><p>Are any of those articles worth reading? Of course, not. I want to demonstrate how I used Web-Archive to recover some old articles that are no longer available online and add a meme image to the post.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!x7M0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!x7M0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 424w, https://substackcdn.com/image/fetch/$s_!x7M0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 848w, https://substackcdn.com/image/fetch/$s_!x7M0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!x7M0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!x7M0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg" width="600" height="450" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:450,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!x7M0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 424w, https://substackcdn.com/image/fetch/$s_!x7M0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 848w, https://substackcdn.com/image/fetch/$s_!x7M0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!x7M0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4e643112-406f-4c33-a491-fafbb8e1bbf0_600x450.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Enter the CUDA</h2><p>Although the technique of using shaders for general-purpose computations was quite powerful, it was still somewhat limited. The programming model was not very friendly, as you had to encode your data as textures or other graphics primitives. The render-to-texture approach involves rendering a rectangular area of the entire screen, ensuring that all rendered pixels align precisely with the texels of the output texture. It was easy to misconfigure the graphics pipeline, such as forgetting to turn off texture filtering, which would lead to incorrect results.</p><p>All of these details were quite distracting and made it hard to focus on the actual computation, especially for non-graphics programmers. Thus, NVIDIA introduced CUDA in 2007, which provided a C-like programming model for writing general-purpose computations on NVIDIA GPUs.</p><p>The programming model is similar to the shader programming model, as you still write a kernel function that is executed in parallel by many threads. Each thread is identified by its 1D, 2D, or 3D index, which you can use to compute the memory address of the data you want to process. In the shader programming model, you would do that using texture coordinates or other varying variables, while you would use thread indices. However, all the scaffolding of setting up the graphics pipeline, managing textures, framebuffers, and so on, is eliminated. You can allocate memory on the GPU, copy data to it, launch a kernel, and copy the results back.</p><p>Here is how the FFT kernel from above would look in CUDA. Again, feel free to skip if you&#8217;re here for the story.</p><pre><code>// Helper function to perform a complex multiply and add operation.
__device__ float2 butterfly_op(float2 a, float2 b, float2 twiddle) {
    // Perform complex multiplication and addition
    float2 temp_result;
    temp_result.x = b.x * twiddle.x - b.y * twiddle.y;
    temp_result.y = b.y * twiddle.x + b.x * twiddle.y;
    return a + temp_result;
}

__global__ void fft_stage_kernel(
    // Input data arrays (now using float2 for complex numbers)
    float2 *d_input1,
    float2 *d_input2,

    // Combined butterfly lookup tables (now float2 for complex twiddle factors)
    float *d_butterflyLookupI,
    float2 *d_butterflyTwiddles,

    // Output data arrays (now using float2)
    float2 *d_out1,
    float2 *d_out2,

    int width,
    int height
) {
    int tx = blockIdx.x * blockDim.x + threadIdx.x;
    int ty = blockIdx.y * blockDim.y + threadIdx.y;

    if (tx &gt;= width || ty &gt;= height) {
        return;
    }

    int index = ty * width + tx;

    // Read butterfly lookup index and complex twiddle factor
    int lookup_i = (int)d_butterflyLookupI[index];
    float2 twiddle_factor = d_butterflyTwiddles[index];

    // Read input data using combined float2 arrays
    int r1_idx = ty * width + tx;
    int r2_idx = ty * width + lookup_i;

    float2 input1 = d_input1[r1_idx];
    float2 input2 = d_input1[r2_idx];

    // Perform the butterfly operation for the first pair of inputs
    d_out1[index] = butterfly_op(input1, input2, twiddle_factor);

    // Process the second pair of data arrays
    float2 input1_prime = d_input2[r1_idx];
    float2 input2_prime = d_input2[r2_idx];

    // Perform the second butterfly operation
    d_out2[index] = butterfly_op(input1_prime, input2_prime, twiddle_factor);
}</code></pre><blockquote><p>I was waiting to get my hands on a GPU that supported CUDA, again. I was earning money, so there was no need to trick my parent anymore, but high-end PC upgrades were still a considerable expense, and you needed to do them often. My first CUDA-capable GPU was the 8800GT, a GPU from the most legendary series of all time. It leveraged entirely new architecture and has introduced CUDA. In addition, asingle 8800 GTX card was able to outperform two previous-generation 7900 GTX cards in SLI and had comparable power consumption and price ($599 &#8212; hold your tears in your eyes). When will we see such leaps in performance and value again, Mr. Leather-jacket CEO?</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N3D3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N3D3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N3D3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N3D3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N3D3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N3D3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg" width="640" height="508" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:508,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!N3D3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 424w, https://substackcdn.com/image/fetch/$s_!N3D3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 848w, https://substackcdn.com/image/fetch/$s_!N3D3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!N3D3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84aaaff1-c7d1-4a0a-a3f3-8f0e6e5937d2_640x508.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">An entry-level GPU in 2030 with an MSRP of $8799. Image from <a href="https://www.reddit.com/r/pcmasterrace/comments/iktxws/in_a_stunning_move_jensen_huang_also_announces/">Reddit</a>.</figcaption></figure></div><h2>CUDA Moat?</h2><p>As a <strong>true open-source warrior</strong>, I did not use CUDA and relied on OpenCL instead for my work. It was not as well-supported as CUDA: debuggers and other tools were not so advanced, there were more glitches, and you could get slightly better performance out of CUDA on NVIDIA hardware. However, its drawbacks were outweighed by the fact that it was an open standard and worked on both AMD and Intel GPUs, so CUDA was far from being a monopoly at that time.</p><p>At my job, I was using OpenCL to implement an algorithm for real-time 3D scanning. The <a href="https://www.artec3d.com/portable-3d-scanners/artec-eva">Artec Eva</a> is a professional 3D scanner used for medical or industrial applications. Real-time 3D scanning involves a significant amount of GPU computation to process the input video stream, identify your position with respect to the environment (similar algorithms are employed as in self-driving cars), fuse all the input data into a single 3D model, and display it on the screen. All of this had to happen in real-time, so the user could see the result immediately and adjust their position if needed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ue8p!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ue8p!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ue8p!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ue8p!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ue8p!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ue8p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg" width="1200" height="675" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:675,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ue8p!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ue8p!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ue8p!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ue8p!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F623205d2-8084-442c-9e3f-b76cc98063a3_1200x675.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Real-time scanning of a 3D object with an Artec scanner. Scanner localization, data fusion, and visualization are performed in real-time on a GPU using OpenCL.</figcaption></figure></div><p>Real-time scanning of a 3D object with an Artec scanner. Scanner localization, data fusion, and visualization are performed in real-time on a GPU using OpenCL.</p><p>I opted for OpenCL, which was a brave choice back then and possibly a bad product decision at the time, as when you buy a $12000 3D scanner, you can afford a decent GPU and not worry about vendor lock-in. However, over time, as GPUs became more powerful and it became possible to run the pipeline on a laptop GPU, specifically <a href="https://en.wikipedia.org/wiki/Microsoft_Surface">Microsoft Surface</a> tablet, the choice of OpenCL has become more relevant. Now, an operator had a lightweight display in their hands and could walk around the object being scanned. At least, this is what I tell myself to feel better about my choice &#128517;</p><div id="youtube2-deyP0j45U5c" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;deyP0j45U5c&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/deyP0j45U5c?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>In addition to OpenCL, there were many other hardware-agnostic GPGPU frameworks to choose from, including <a href="https://halide-lang.org/">Halide</a>, <a href="https://arrayfire.com/">ArrayFire</a>, and <a href="https://numba.pydata.org/">Numba</a>. So, all things considered, the open-source and open-standard ecosystem was a fair contender to CUDA back then, and CUDA hasn&#8217;t had its moat yet.</p><h2>Deep Learning Revolution</h2><p>The new GPU programming capabilities unlocked by CUDA/OpenCL have enabled numerous new applications in computer graphics, science, and medical fields, among others. However, the popularization of deep learning (this is how we&#8217;ve called AI before ChatGPT came along) is arguably the most noticeable outcome.</p><p>Many think that thanks to AI, the GPUs have become the central compute platform. In fact, it is the other way around. Thanks to GPUs, we have AI in the first place. Deep convolutional neural networks have been known since 90s. In 2012, a graduate student, Alex Krizhevsky, motivated by Ilya Sutskever, trained a deep convolutional neural network under the guidance of Geoffrey Hinton using a couple of GeForce GPUs to enter the <a href="https://en.wikipedia.org/wiki/ImageNet#ImageNet_Challenge">ImageNet challenge</a>. The model was called <a href="https://en.wikipedia.org/wiki/AlexNet">AlexNet</a>, and the dataset consisted of 1.2 million images belonging to 1,000 categories.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YnI4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YnI4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 424w, https://substackcdn.com/image/fetch/$s_!YnI4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 848w, https://substackcdn.com/image/fetch/$s_!YnI4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 1272w, https://substackcdn.com/image/fetch/$s_!YnI4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YnI4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png" width="770" height="978" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:978,&quot;width&quot;:770,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!YnI4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 424w, https://substackcdn.com/image/fetch/$s_!YnI4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 848w, https://substackcdn.com/image/fetch/$s_!YnI4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 1272w, https://substackcdn.com/image/fetch/$s_!YnI4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0b450b99-0a54-4287-91d5-e464de6d48e7_770x978.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The obligatory xkcd meme. The <a href="https://xkcd.com/2347/">original</a>.</figcaption></figure></div><p>The results? They have obliterated the state-of-the-art computer vision models at the time, demonstrating a whopping 9.4% increase in accuracy over the previous state-of-the-art. This was a game-changer. It has triggered a deep learning revolution, where all breakthroughs in computer vision, natural language processing, and other fields were achieved using deep learning models trained on GPUs.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oZGF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oZGF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 424w, https://substackcdn.com/image/fetch/$s_!oZGF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 848w, https://substackcdn.com/image/fetch/$s_!oZGF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!oZGF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oZGF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg" width="665" height="419" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:419,&quot;width&quot;:665,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!oZGF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 424w, https://substackcdn.com/image/fetch/$s_!oZGF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 848w, https://substackcdn.com/image/fetch/$s_!oZGF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!oZGF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F560027cf-01cc-4c6c-b668-11deeeb198c2_665x419.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">The best ImageNet challenge results in 2010 and 2011, compared against all results in 2012, including AlexNet. Image from Pinecone&#8217;s article: <a href="https://www.pinecone.io/learn/series/image-search/imagenet/">AlexNet and ImageNet: The Birth of Deep Learning</a></figcaption></figure></div><h2>The Array Programming Model</h2><p>GPU computing has caused great upheaval in the machine learning field, while the latter has retaliated by drastically changing the way we program GPUs. The programming model has shifted from writing kernels that operate on individual elements of an array to writing code that operates on entire arrays (tensors) at once.</p><p>The reason for this is that deep-learning frameworks like Tensorflow or PyTorch were inspired not by graphics programming, but scientific computing frameworks like <a href="https://numpy.org/">NumPy</a> and <a href="https://www.mathworks.com/products/matlab.html">MATLAB</a>. The programming model differs significantly from those of CUDA and OpenCL. Instead of writing kernels that operate on individual elements of an array, you write code that operates on entire arrays (tensors) at once. The framework breaks down the operations into smaller pieces that can be executed in parallel on the GPU. This programming model, known as <a href="https://en.wikipedia.org/wiki/Array_programming">array programming</a>, dates back to the 60s with the development of languages like <a href="https://en.wikipedia.org/wiki/APL_(programming_language)">APL</a> and <a href="https://en.wikipedia.org/wiki/Fortran">Fortran</a>.</p><blockquote><p>I am skipping the first and popular at the time declarative deep learning framework <a href="https://en.wikipedia.org/wiki/Caffe_(software)">Caffe</a>. It was suitable for defining a large number of models, but it was not appropriate for expressing arbitrary computations on tensors.</p></blockquote><p>This programming model has one tremendous advantage. It is much easier to reason about the code, as you don&#8217;t have to think about how to parallelize the computation. You write code that operates on entire arrays, and the framework takes care of the rest. It made GPU programming accessible to a much wider audience, as you didn&#8217;t have to be a GPU programming expert to write code that runs on the GPU. It is so convenient that many GPU programming experts, myself included, have switched to using these frameworks for their work. It allows you to express your ideas much more concisely and focus on the problem at hand, rather than the intricacies of GPU programming. Additionally, frameworks like PyTorch and Tensorflow come with an automatic differentiation engine, which allows you to compute gradients of your functions automatically. This is especially useful for training neural networks, but it can also be applied to other applications.</p><p>Here is a simple numpy program. Even without knowing numpy, you can figure out what it does. It creates a couple of arrays, performs some basic operations on them, and prints the results.</p><pre><code>import numpy as np

# Create a 1-dimensional array from a Python list
array1d = np.array([1, 2, 3, 4, 5])

# Create a 2-dimensional array (matrix)
array2d = np.array([[10, 20, 30], [40, 50, 60]])

# Element-wise addition
sum_array = array1d + 5

# Element-wise multiplication
product_array = array1d * 2

# Sum of all elements in an array
total_sum = np.sum(array1d)

# Mean of elements in an array
mean_value = np.mean(array1d)

# Accessing elements
print(&#8221;First element of array1d:&#8221;, array1d[0])
print(&#8221;Element at row 0, column 1 of array2d:&#8221;, array2d[0, 1])</code></pre><h2>Why Programming GPUs is Hard?</h2><p>With the convenience of the array programming model comes a significant drawback. It is hard to optimize the code for performance. To understand the reason, we first need to consider why it is hard to optimize code for GPUs in the first place.</p><p>There are several reasons why GPU programming is a complex task. Still, the primary limitation is that memory bandwidth heavily restricts GPUs, so GPU architects have introduced numerous complex mechanisms to hide the latency of memory accesses and maximize the utilization of available bandwidth. Developers need to understand these mechanisms and write code that leverages them. This is not an easy task, as it requires a deep understanding of the GPU architecture and the specific details of the memory hierarchy.</p><p>Think about the following example. The most powerful CPU at the time of writing is <a href="https://www.amd.com/en/products/processors/server/epyc/9005-series/amd-epyc-9965.html">AMD EPYC 9965</a>. It offers a whopping 192 cores and 384 threads. The per-socket memory bandwidth is about 614 GB/s. However, its number of cores pales in comparison with the most powerful GPU, which is <a href="https://www.nvidia.com/en-us/data-center/dgx-b200/">NVIDIA B200</a> at the time of writing. It offers 16,896 CUDA cores and up to 8TB/s of memory bandwidth per GPU.</p><p>Now, you might see the problem: each CPU core has about 3.2 GB/s of memory bandwidth, while each GPU core has only about 0.47 GB/s of memory bandwidth. This means that each GPU core must perform significantly more work to hide the latency of memory accesses and make the best use of the available bandwidth. The situation with consumer GPUs is even worse, e.g. the <a href="https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5090/">RTX 5090</a> has 21,760 CUDA cores and 1,792 GB/s of memory bandwidth, which gives only about 0.082 GB/s per core. This means that GPUs must perform significantly more computations per memory access to achieve optimal performance.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c6xP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c6xP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!c6xP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!c6xP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!c6xP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c6xP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg" width="1160" height="1200" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1200,&quot;width&quot;:1160,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!c6xP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 424w, https://substackcdn.com/image/fetch/$s_!c6xP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 848w, https://substackcdn.com/image/fetch/$s_!c6xP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!c6xP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff77b0d49-27c0-4435-842f-89bf28545a48_1160x1200.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>The relationship between compute power and memory bandwidth in the GPU computing world is referred to as the ALU-to-memory ratio, which represents the number of operations a GPU core can perform per memory access. For GPUs, this ratio is much higher than for CPUs. It can be dozens or even hundreds of operations per memory access.</p><p>The same problem exists for all other parallel computing platforms, such as TPUs, neural processors, and FPGAs. The memory bandwidth per processing unit is always much lower than that of a CPU core. Between 2017 and 2022, I was optimizing neural network inference at Apple for their custom neural processors. We have shipped models such as Animoji, FaceID, Portrait mode, and numerous models that run on Apple Vision Pro. For each of these models, we&#8217;ve had to ensure there is no swapping of data between the on-chip memory and DRAM, as the memory bandwidth was the main bottleneck.</p></blockquote><p>To work around this limitation, GPUs employ several techniques, such as using <strong>shared memory</strong> &#8212;a small amount of memory shared among a group of threads. This allows threads to cooperate and share data without accessing global memory, which is significantly slower. Another technique is to use <strong>memory coalescing</strong>, which enables threads to access memory in a way that minimizes the number of memory transactions. This is achieved by ensuring that threads access contiguous memory locations, which allows the GPU to fetch multiple data elements in a single memory transaction. GPU cores also have access to <strong>more registers</strong> than CPU cores, which can also be used to store intermediate data. However, registers are shared among cores (threads in a workgroup), so if you&#8217;re using too many, some cores will be turned off.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xzTp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xzTp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 424w, https://substackcdn.com/image/fetch/$s_!xzTp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 848w, https://substackcdn.com/image/fetch/$s_!xzTp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!xzTp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xzTp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg" width="825" height="857" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:857,&quot;width&quot;:825,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!xzTp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 424w, https://substackcdn.com/image/fetch/$s_!xzTp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 848w, https://substackcdn.com/image/fetch/$s_!xzTp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!xzTp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd85745e6-8699-45a0-b8e0-79c0a7c61db3_825x857.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">GPU-machine memory hierarchy for NVIDIA Fermi (2010) architecture &#8212; transfer speeds on modern GPUs are about 5&#8211;10 times faster, but relationships are similar. Illustration from the publication <a href="https://www.researchgate.net/publication/51927898_Accelerating_Radio_Astronomy_Cross-Correlation_with_Graphics_ProcessingUnits">Accelerating Radio Astronomy Cross-Correlation with Graphics Processing Units</a></figcaption></figure></div><p>Enough complex terms! If you&#8217;re to take out one thing from this post, it is this: <strong>the most effective way to optimize a GPU program is to perform more computations per memory access</strong>. In other words, <strong>ensure that data doesn&#8217;t leave the GPU core for as long as possible</strong>. Let&#8217;s pin this and come back to the array programming model and the performance issues that it introduces.</p><h2>I Love PyTorch! What Can Possibly be Wrong with It?</h2><p>Let&#8217;s take a look at how a simple CUDA kernel to perform an array operation like A*B + C would look. Here, <code>A</code>, <code>B,</code> and <code>C</code> are large arrays (tensors) and the operation is performed element-wise, e.g., <code>[1, 2, 3] * [2, 2, 2] + [1, 1, 1] = [3, 5, 7]</code></p><pre><code>__global__ void array_op(const float *A, const float *B, const float *C, float *D, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx &lt; N) {
        D[idx] = A[idx] * B[idx] + C[idx];
    }
}</code></pre><p>This kernel is straightforward. Each thread computes a single element of the output array <code>D</code> by reading the corresponding elements from the input arrays <code>A</code>, <code>B,</code> and <code>C</code>.</p><p>Now, let&#8217;s take a look at how the same operation would look in PyTorch.</p><pre><code>import torch

A = torch.randn(1000000, device=&#8217;cuda&#8217;)
B = torch.randn(1000000, device=&#8217;cuda&#8217;)
C = torch.randn(1000000, device=&#8217;cuda&#8217;)
D = A * B + C</code></pre><p>If you naively translate PyTorch operations, such as element-wise multiplication and addition, to CUDA, which is how it is actually done in practice, you would get two kernels: one for multiplication and one for addition. The runtime would launch a kernel to perform element-wise multiplication, store the result in a temporary array <code>A1</code>, and then launch another kernel to perform element-wise addition using <code>A1</code> and <code>B</code> to produce the final tensor <code>C</code>.</p><pre><code>__global__ void array_mul(const float *A, const float *B, float *E, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx &lt; N) {
        E[idx] = A[idx] * B[idx];
    }
}

__global__ void array_add(const float *E, const float *C, float *D, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx &lt; N) {
        D[idx] = E[idx] + C[idx];
    }
}</code></pre><p>You can see the problem now:</p><ul><li><p>In the original example, we fetch data once from memory and perform two operations (multiplication and addition) on it.</p></li><li><p>In the PyTorch example, we fetch data twice from memory and perform only one operation (multiplication or addition) on it.</p></li></ul><p>Given that our program is completely memory-bound, the PyTorch version will be practically <strong>twice as slow as the CUDA version</strong>, because it performs half the number of operations per memory access. If we add one more unfused element-wise operation, it will be <strong>three times as slow</strong>, and so on.</p><p>You may wonder, can&#8217;t we generate a single fused kernel that performs both operations simultaneously? The answer is yes, we can. In fact, both PyTorch and TensorFlow have a mechanism to do that. However, this is not an easy problem to solve in a general way. PyTorch officially supports more than 1200 operations that can be performed on tensors. The number of possible combinations of these operations is astronomical. Many of these operations are not even element-wise, e.g., matrix multiplication, convolutions, reductions, and so on. It is a complex problem to solve in a general way. For PyTorch, it is challenging, as it is a dynamic framework, i.e., the computation graph is built on-the-fly as the code is executed. This makes it difficult to analyze the entire computation graph and determine which operations can be fused.</p><p>This problem remains unsolved in a general way to date, as you&#8217;ll see when we discuss <a href="https://github.com/Dao-AILab/flash-attention">Flash Attention</a> in the context of LLM inference.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Obyn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Obyn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Obyn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Obyn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Obyn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Obyn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg" width="720" height="709" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:709,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Obyn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Obyn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Obyn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Obyn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93039b95-deb8-41a2-8f69-b5881751bf6b_720x709.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>NVidia Domination</h2><p>The Deep Learning revolution has dramatically changed the GPU programming landscape. The array programming model has made GPU programming accessible to a much wider audience, as you don&#8217;t have to be a GPU programming expert to write code that runs on the GPU. However, it has also introduced new challenges, such as optimizing memory access patterns and fusing operations to achieve optimal performance.</p><p>This has created a strong moat for NVIDIA. Although CUDA was just one of the many GPGPU frameworks available at the time, the CUDA ecosystem had a great deal more to offer the community. For example, it had CUDNN, a highly optimized library for deep learning primitives such as convolutions, pooling, and normalization. This library was used by all major deep learning frameworks like TensorFlow and PyTorch to achieve good performance on NVIDIA GPUs. Additionally, NVIDIA has invested heavily in optimizing its hardware for deep learning workloads, for example, by introducing Tensor Cores, which are specialized hardware units designed for performing matrix multiplications and convolutions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Wa_x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wa_x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Wa_x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Wa_x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Wa_x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wa_x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg" width="742" height="500" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:500,&quot;width&quot;:742,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Wa_x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Wa_x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Wa_x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Wa_x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b32cbc5-5fe1-424d-85c1-156eb27bc9f8_742x500.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Link to the <a href="https://imgflip.com/i/60t85x">original</a></figcaption></figure></div><p>In the Deep Learning age, the NVIDIA GPUs have become the <strong>de facto standard for deep learning workloads</strong>. All major deep learning frameworks like PyTorch and Tensorflow were built on top of CUDNN, initially not even offering the option to use other backends like OpenCL or ROCm. All research has been done on NVIDIA hardware, as it was the only hardware that supported the tools they were using. This has created a strong network effect, as everyone was using NVIDIA hardware, so everyone was optimizing their code for NVIDIA hardware, which made NVIDIA hardware even more attractive.</p><blockquote><p>From 2010 to the present, I have exclusively owned NVIDIA GPUs. Even though some AMD models were offering more value, the need to be able to perform AI-related work has always steered me into the Team Green camp.</p></blockquote><p>Ironically, as innovative as CUDA was, the moat was created not by CUDA itself, but by <strong>the army of NVIDIA engineers who have optimized CUDNN and other libraries for deep learning workloads</strong>. There was simply no good algorithm to optimize computational graphs in a general way, so NVIDIA engineers have hand-optimized the most common patterns that appear in deep learning workloads.</p><blockquote><p>There are many attempts to come up with an automatic way to optimize computational graphs or at least to come up with a universal, hardware-agnostic AI stack that makes the optimization process easier, like <a href="https://www.tensorflow.org/xla">XLA</a> from Google, <a href="https://tvm.apache.org/">TVM</a> from the Apache Foundation, <a href="https://mlir.llvm.org/">MLIR</a> from LLVM or <a href="https://www.modular.com/max">MAX</a> from Modular AI. However, none of these attempts have been able to beat hand-optimized libraries like CUDNN on NVIDIA hardware on a large enough number of real-world use cases and establish a strong enough network effect.</p></blockquote><h2>The AI Era &#8212; Bigger is Better</h2><p>History doesn&#8217;t repeat itself, but it often rhymes. The computational power of GPUs has triggered the deep learning evolution. We&#8217;ve used the same algorithms that were known since the 90s, but now we can train much larger models on much larger datasets. The same thing happened with LLMs. The <a href="https://arxiv.org/abs/1706.03762">transformer architecture</a> was known since 2017, but it was only in 2020 that we saw the first large-scale transformer models like GPT-3 and BERT. The reason for that is that training these models requires a lot of computational power and memory bandwidth. OpenAI has trained GPT-3 on a cluster of 10,000 GPUs. The largest model, GPT-3, has 175 billion parameters and was trained on a dataset of 570GB of text data. The training process took several weeks and cost several million dollars (and probably raised global temperature by a degree or so).</p><p>How did AI affect the GPU programming landscape? Not much, actually. The same array programming model is used for training and inference of LLMs. The same challenges of optimizing memory access patterns and fusing operations to achieve good performance still exist. However, the scale of the models has increased dramatically, which has introduced new challenges, like distributing the model across multiple GPUs and optimizing communication between GPUs.</p><p>The large scale of the models has also introduced new challenges for inference. The models are so large that they don&#8217;t fit into the memory of a single GPU. For example, GPT-3 requires about 700GB of memory to store the model parameters, which is much larger than the memory of even the most powerful GPUs available today. This has led to the development of techniques such as model parallelism, where the model is split across multiple GPUs, and pipeline parallelism, where different parts of the model are executed on separate GPUs in a pipelined manner.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gy9M!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gy9M!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 424w, https://substackcdn.com/image/fetch/$s_!gy9M!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 848w, https://substackcdn.com/image/fetch/$s_!gy9M!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!gy9M!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gy9M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg" width="640" height="753" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:753,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!gy9M!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 424w, https://substackcdn.com/image/fetch/$s_!gy9M!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 848w, https://substackcdn.com/image/fetch/$s_!gy9M!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!gy9M!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa553f95b-afbe-4a7b-9ee3-af1f98584ab4_640x753.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Link to the <a href="https://www.reddit.com/r/memes/comments/1g52kiy/fixed_that_for_you/">original</a></figcaption></figure></div><h2>The Case of Flash Attention</h2><p>One of the most essential operations in transformer models is the attention mechanism. The attention mechanism allows the model to focus on different parts of the input sequence when making predictions. The attention mechanism is implemented using a series of matrix multiplications and softmax operations (see the rightmost diagram in the image below).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YldV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YldV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 424w, https://substackcdn.com/image/fetch/$s_!YldV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 848w, https://substackcdn.com/image/fetch/$s_!YldV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 1272w, https://substackcdn.com/image/fetch/$s_!YldV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YldV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png" width="1280" height="716" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:716,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!YldV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 424w, https://substackcdn.com/image/fetch/$s_!YldV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 848w, https://substackcdn.com/image/fetch/$s_!YldV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 1272w, https://substackcdn.com/image/fetch/$s_!YldV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feace2d66-7de6-4e11-b5cb-bb0a959f8e85_1280x716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Attention mechanism in transformers. Illustration from the original <a href="https://arxiv.org/abs/1706.03762">paper</a>.</figcaption></figure></div><p>The softmax operation involves computing the exponential of each element in the input matrix, summing them up, and then dividing each component by the sum.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0N2O!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0N2O!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 424w, https://substackcdn.com/image/fetch/$s_!0N2O!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 848w, https://substackcdn.com/image/fetch/$s_!0N2O!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 1272w, https://substackcdn.com/image/fetch/$s_!0N2O!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0N2O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png" width="967" height="212" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:212,&quot;width&quot;:967,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!0N2O!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 424w, https://substackcdn.com/image/fetch/$s_!0N2O!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 848w, https://substackcdn.com/image/fetch/$s_!0N2O!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 1272w, https://substackcdn.com/image/fetch/$s_!0N2O!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e84a-ecbc-4818-b21f-8557f74c13a9_967x212.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Looks challenging to optimize, right? How can we reduce the number of memory accesses here? The naive implementation would involve reading the input matrices from memory, multiplying them together, storing the result in a temporary matrix, reading the temporary matrix from memory, computing the exponential of each element, summing them up, and then dividing each component by the sum. This would involve a lot of memory accesses and would be very slow. And it is slow!</p><p>However, previously I have mentioned that GPUs come with a bit of fast on-chip memory called shared memory (SRAM in hardware terms&#8212; static random access memory). It is a small amount of memory that is shared between a block of GPU cores. This memory is much faster than the global memory (GDDR or HBM) and can be used to store intermediate results. The original Flash Attention implementation was implemented and benchmarked on H100, which has 80GB of HBM memory and 192KB of shared memory per SM. The SRAM speed was about 19TB/s, and the HBM speed was about 1.5&#8211;2.0TB/s.</p><p>The authors of Flash Attention have devised a method to partition computations in a way that allows intermediate results to fit into shared memory, enabling them to perform the entire attention computation with fewer trips to global memory. This is achieved by partitioning the input matrices into smaller tiles, performing calculations on these tiles (including matrix multiplications and softmax operations), and streaming the results back into global memory. The result is a significant speedup over the naive implementation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!okt-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!okt-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 424w, https://substackcdn.com/image/fetch/$s_!okt-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 848w, https://substackcdn.com/image/fetch/$s_!okt-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 1272w, https://substackcdn.com/image/fetch/$s_!okt-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!okt-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png" width="1400" height="573" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df111acf-db22-4139-ab3c-845b7e30894b_1400x573.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:573,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!okt-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 424w, https://substackcdn.com/image/fetch/$s_!okt-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 848w, https://substackcdn.com/image/fetch/$s_!okt-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 1272w, https://substackcdn.com/image/fetch/$s_!okt-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf111acf-db22-4139-ab3c-845b7e30894b_1400x573.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Left: FlashAttention uses tiling to prevent materialization of the large &#119873; &#215; &#119873; attention matrix (dotted box) on (relatively) slow GPU HBM. In the outer loop (red arrows), FlashAttention loops through blocks of the K and V matrices and loads them to fast on-chip SRAM. In each block, FlashAttention loops over blocks of Q matrix (blue arrows), loading them to SRAM, and writing the output of the attention computation back to HBM. Right: Speedup over the PyTorch implementation of attention on GPT-2. FlashAttention does not read and write the large &#119873; &#215; &#119873; attention matrix to HBM, resulting in an 7.6&#215; speedup on the attention computation. Illustration from the original <a href="https://arxiv.org/abs/2205.14135">paper</a>.</p><h2>Conclusion</h2><p>The GPU programming landscape has changed dramatically over the past two decades. The introduction of CUDA and OpenCL has made GPU programming accessible to a much wider audience, triggering the deep learning revolution that, in turn, has changed the way we program GPUs. The array programming model has made it easier to write code that runs on the GPU, but it has also introduced new challenges, such as optimizing memory access patterns and fusing operations to achieve optimal performance.</p><p>Now, when you&#8217;re a certified GPU programming expert, enjoy the last meme and get your GPU cranking!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oGie!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oGie!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 424w, https://substackcdn.com/image/fetch/$s_!oGie!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 848w, https://substackcdn.com/image/fetch/$s_!oGie!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!oGie!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oGie!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg" width="720" height="562" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:562,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!oGie!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 424w, https://substackcdn.com/image/fetch/$s_!oGie!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 848w, https://substackcdn.com/image/fetch/$s_!oGie!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!oGie!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F43635cd8-2a9a-4b32-bdb4-0c338074bfad_720x562.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">If you frequently run into this issue &#8212; check out my GPU rental service <a href="https://www.cloudrift.ai/">CloudRift</a>.</figcaption></figure></div>]]></content:encoded></item></channel></rss>