{"id":876,"date":"2026-06-11T00:00:00","date_gmt":"2026-06-11T00:00:00","guid":{"rendered":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/"},"modified":"2026-06-12T04:00:01","modified_gmt":"2026-06-12T04:00:01","slug":"torch-mlp-fusion","status":"publish","type":"post","link":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/","title":{"rendered":"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP"},"content":{"rendered":"<p><br \/>\n<\/p>\n<div>\n<img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/thumbnail.png\" alt=\"Thumbnail of the blog post\"\/><\/p>\n<p>Within the first a part of this collection &#8220;Profiling in PyTorch&#8221;, we used torch.add(torch.matmul(x, w), b) to discover ways to learn PyTorch profiler traces. We additionally mentioned a number of different subjects that got here our means &#8211; the CPU dispatch chain, launch overhead, the distinction between an overhead-bound and a compute-bound regime, and a few internals of torch.compile.<\/p>\n<p>Within the second iteration (this weblog submit), we climb one rung up the ladder. We exchange the hand-written matmul-add pair with an nn.Linear (with bias=True). That is the constructing block each deep studying mannequin makes use of. We then stack three of them (particular to our instance), with an activation in between, to kind a Multilayer Perceptron (MLP) block.<\/p>\n<blockquote class=\"note\">\n<p>The scripts for this weblog submit reside right here: 02_linear.py, 03_simple_mlp.py, and 03_kernels_mlp.py. Like earlier than, it helps to open them in a separate tab and stroll by means of the code as you learn. We use an NVIDIA A100-SXM4-80GB GPU to run the scripts. It&#8217;s very easy to arrange a GPU on the Hugging Face infrastructure and experiment with the scripts utilizing Dev Mode with Areas. One may additionally run the scripts with the Hugging Face Jobs pipeline.<\/p>\n<\/blockquote>\n<p>Earlier than we start, a fast recap of two concepts we&#8217;ll lean on repeatedly:<\/p>\n<p>A GPU kernel is a program that runs in parallel on many threads of the GPU.<br \/>\nThe CPU schedules and launches these kernels. A lot of the PyTorch overhead you see in a profiler hint is that this scheduling work.<\/p>\n<h2 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tFrom matmul-add to Linear<br \/>\n\t<\/span><br \/>\n<\/h2>\n<p>nn.Linear is a module wrapper across the identical matrix multiplication and addition we already profiled in Half 1. The one distinction is that it owns its weight and bias as parameters and exposes a ahead methodology that PyTorch customers have grown aware of.<\/p>\n<p>linear_layer = nn.Linear(in_dim, out_dim, bias=<span class=\"hljs-literal\">True<\/span>)<br \/>\ny = linear_layer(x)<\/p>\n<p>The operation at hand might be written as:<\/p>\n<p>y = x @ w.T + b<\/p>\n<p>The place x is the enter, w is the load and b is the bias. Let&#8217;s run 02_linear.py and verify the profile.<\/p>\n<p>uv run 02_linear.py &#8211;batch 1024 &#8211;in_dim 32 &#8211;out_dim 64<br \/>\nuvx trace-util traces -b traces<\/p>\n<blockquote class=\"tip\">\n<p>trace-util is a utility that may sync your traces to a Hugging Face bucket after which present the Preffeto URLs in your terminal.<\/p>\n<\/blockquote>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/linear-profile-trace.png\" alt=\"PyTorch profiler trace of an `nn.Linear` forward pass: three short Profile Steps and `linear_fwd` annotations on the CPU lane, a tiny kernel on the GPU lane, and a long `cudaDeviceSynchronize` bar at the end\"\/><\/p>\n<p>Determine 1: Profiler hint of nn.Linear<\/p>\n<\/div>\n<p>Determine 1 exhibits the profiler hint of a ahead name of the linear layer. We hint the ahead name of the linear layer with an analogous schedule setup because the earlier traces, with wait=1, warmup=1 and energetic=3. This is the reason we see three Profile Steps within the CPU and GPU lanes.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tWhat&#8217;s the transpose doing?<br \/>\n\t<\/span><br \/>\n<\/h3>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/transpose-cpu-dispatch.png\" alt=\"Zoomed in CPU dispatch chain showing the aten::t transpose op nested before aten::addmm inside aten::linear, with no matching activity on the GPU lane\"\/><\/p>\n<p>Determine 2: The transpose CPU row<\/p>\n<\/div>\n<p>If we zoom into the profiler hint, as we do in Determine 2, we discover an aten::t (transpose) op earlier than the aten::addmm (multiplication and addition) op. We will already work out that nn.Linear transposes the load parameter after which multiplies it with the enter. That is the rationale we see an aten::t op.<\/p>\n<p>An necessary factor to note is that aten::t does probably not copy or reorganize information: it solely rewrites tensor metadata (form and stride) on the CPU to characterize the transposed matrix. It doesn&#8217;t launch a kernel on the GPU. One can confirm this two methods: by trying on the GPU lane within the hint, or by checking the aten::t row within the profiler desk and the time it took on CUDA.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tWhy are there no separate mul and add kernels?<br \/>\n\t<\/span><br \/>\n<\/h3>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/no-aten-add.png\" alt=\"Profiler trace of the linear layer with the dispatch chain highlighted, showing aten::linear, aten::t and aten::addmm but no separate aten::add op\"\/><\/p>\n<p>Determine 3: No aten::add within the profile of a linear layer<\/p>\n<\/div>\n<p>There isn&#8217;t a aten::add (the bias addition) within the dispatch chain of the linear layer, as seen in Determine 3. It&#8217;s because the bias addition has been folded into the matrix multiplication kernel, utilizing what is known as an epilogue.<\/p>\n<p>An epilogue is a small computation {that a} GEMM (GEneral Matrix Multiply) kernel does on the very finish, simply earlier than it writes its outcome again to HBM (Excessive Bandwidth Reminiscence, the GPU&#8217;s predominant reminiscence). Including a bias, making use of an activation, or scaling by a relentless are all traditional epilogues. The purpose of an epilogue is to keep away from loading or writing to HBM a second time, since reminiscence visitors makes an operation costly.<\/p>\n<p>nn.Linear calls torch.nn.purposeful.linear, which, in flip, calls aten::linear. aten::linear appears on the inputs, notices {that a} bias was handed, and dispatches aten::addmm(bias, x, weight) as an alternative of doing a matmul and an add individually. addmm computes:<\/p>\n<p>out = x @ weight.T + bias<\/p>\n<p>The cuBLAS GEMM kernel that runs on the GPU has a bias-add variant inbuilt, and that is the kernel aten::addmm picks. The add by no means seems as a separate kernel as a result of it&#8217;s a part of the matmul kernel&#8217;s writeback, which is precisely what an epilogue is.<\/p>\n<p>That is the second to note one thing refined. The kernel you noticed in Half 1 underneath &#8211;compile (addmm) is the kernel that keen nn.Linear already makes use of. There&#8217;s nothing left for torch.compile to fuse right here, which is the following factor we&#8217;ll confirm.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tCan &#8211;compile assist a single Linear?<br \/>\n\t<\/span><br \/>\n<\/h3>\n<p>Let&#8217;s compile the ahead name and take a look at the profiler hint. (The profiler hint is visualized within the subsequent part)<\/p>\n<p>uv run 02_linear.py &#8211;batch 1024 &#8211;in_dim 32 &#8211;out_dim 64 &#8211;compile<br \/>\nuvx trace-util traces -b traces<\/p>\n<p>In the event you evaluate the keen and compiled traces for a single nn.Linear&#8217;s ahead, you will see:<\/p>\n<p>The identical cuBLAS GEMM kernel on the GPU.<br \/>\nThe identical aten::addmm op on the CPU.<br \/>\nA couple of further rows on the CPU lane distinctive to compile.<\/p>\n<p>That is price internalizing. A typical reflex is to succeed in for torch.compile at any time when a mannequin feels sluggish. For a single GEMM-with-bias, compile has little or no to do. This isn&#8217;t a bug, that is simply that compile wants multiple operation to presumably do any fusing. Let&#8217;s show that by taking a look at an MLP.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tThe place did the transpose go? Kernel layouts and pre-ops<br \/>\n\t<\/span><br \/>\n<\/h3>\n<p>A cautious reader of the 2 traces (keen vs compile) will discover that the keen CPU dispatch chain has extra in it than the compiled one.<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/eager.png\" alt=\"Eager CPU dispatch chain with the aten::t transpose and aten::addmm boxed separately under aten::linear\"\/><\/p>\n<p>Determine 4: Keen dispatch chain the place aten::linear walks by means of aten::t (transpose) after which aten::addmm<\/p>\n<\/div>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/compile.png\" alt=\"Compiled CPU dispatch chain showing a Torch-Compiled Region and a single aten::addmm call, with no transpose op\"\/><\/p>\n<p>Determine 5: Compiled dispatch chain the place aten::addmm is known as instantly, with no transpose<\/p>\n<\/div>\n<p>The keen CPU dispatch chain inside aten::linear is aten::t adopted by aten::addmm (Determine 4). To know what aten::t truly does, we want a fast detour into strides and views.<\/p>\n<p>A tensor shops its information as one flat, contiguous run of numbers in reminiscence. The form and stride are metadata that sit on high of that run and inform PyTorch the best way to stroll it: a stride of (s0, s1) means &#8220;step s0 parts to maneuver one row, step s1 to maneuver one column&#8221;. Change the metadata and also you get a distinct view of the identical uncooked information, with no copy:<\/p>\n<p><span class=\"hljs-meta\">&gt;&gt;&gt; <\/span>M = torch.tensor([[<span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">1<\/span>],<br \/>\n<span class=\"hljs-meta\">&#8230; <\/span>                  [<span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">3<\/span>],<br \/>\n<span class=\"hljs-meta\">&#8230; <\/span>                  [<span class=\"hljs-number\">4<\/span>, <span class=\"hljs-number\">5<\/span>]])<br \/>\n<span class=\"hljs-meta\">&gt;&gt;&gt; <\/span>M.form, M.stride()<br \/>\n(torch.Measurement([<span class=\"hljs-number\">3<\/span>, <span class=\"hljs-number\">2<\/span>]), (<span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">1<\/span>))   <\/p>\n<p><span class=\"hljs-meta\">&gt;&gt;&gt; <\/span>T = M.t()<br \/>\n<span class=\"hljs-meta\">&gt;&gt;&gt; <\/span>T.form, T.stride()<br \/>\n(torch.Measurement([<span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">3<\/span>]), (<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">2<\/span>))<br \/>\n<span class=\"hljs-meta\">&gt;&gt;&gt; <\/span>T<br \/>\ntensor([[<span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">4<\/span>],<br \/>\n        [<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">3<\/span>, <span class=\"hljs-number\">5<\/span>]])<br \/>\n<span class=\"hljs-meta\">&gt;&gt;&gt; <\/span>T.flatten()<br \/>\ntensor([<span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">4<\/span>, <span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">3<\/span>, <span class=\"hljs-number\">5<\/span>])<\/p>\n<p>M.t() didn&#8217;t transfer a single quantity. It returned a brand new view whose strides are swapped, so studying it row-by-row now walks the unique buffer 0, 1, 2, 3, 4, 5 in transposed order. The underlying information is an identical; solely the metadata differs.<\/p>\n<p>That is precisely what aten::t does contained in the linear layer: it doesn&#8217;t allocate a brand new tensor or copy any information, it produces a view of the load with rewritten strides.<\/p>\n<p>As we are able to see in Determine 5, compile didn&#8217;t take away a GPU kernel: it eliminated the CPU overhead of dispatching that view. Inductor traced by means of the view chain at compile time, computed the ensuing strides as soon as, and emitted a direct aten::addmm name with these strides hard-coded. A couple of microseconds of CPU work disappear whereas the GPU does an identical math. <\/p>\n<p>As one would anticipate, when the enter information violates the strides precomputed by the compiler, it is going to throw an error.<\/p>\n<p>In the event you take a look at the GPU lane in each traces, there&#8217;s precisely one kernel per ahead, and it&#8217;s the identical kernel each occasions:<\/p>\n<p>cutlass_80_wmma_tensorop_bf16_s161616gemm_bf16_32x32_32x1_tn_align8<\/p>\n<p>If no transpose kernel ran, who taught the GEMM to learn the load matrix in transposed order? The reply is within the kernel&#8217;s identify. Have a look at the suffix:<\/p>\n<p>cutlass_80_wmma_tensorop_bf16_s161616gemm_bf16_32x32_32x1_tn_align8<br \/>\n                                                          ^^<\/p>\n<p>That tn is the structure descriptor. cuBLAS and CUTLASS precompile a separate kernel binary for every mixture of enter layouts.<\/p>\n<p>n (non-transposed) and t (transposed) describe how a kernel walks its enter throughout the interior loop. The dispatcher&#8217;s job is to have a look at the enter strides, resolve which suffix mixture matches, and decide the fitting precompiled kernel.<\/p>\n<blockquote class=\"tip\">\n<p>The kernel identify in a profiler hint is a hash dump of the kernel&#8217;s identification. If two runs present the identical kernel identify, the GPU is doing the identical work. In the event that they differ (e.g., _tn_ vs _nn_, bf16 vs fp16, or s16816gemm vs s161616gemm) then the GPU is doing totally different work, and the dispatcher took a distinct department. Studying to learn this identify is among the most helpful habits when evaluating traces.<\/p>\n<\/blockquote>\n<h2 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tStacking three Linears: the MLP<br \/>\n\t<\/span><br \/>\n<\/h2>\n<p>On this part, we&#8217;ll profile a Multilayer Perceptron (MLP). To make this extra attention-grabbing, we&#8217;ll profile a feed-forward community with the GeGLU activation variant (which is sort of closely utilized in apply). That is additionally our means of paying tribute to one of many best strains ever written within the historical past of deep studying analysis (Determine 6).<\/p>\n<p><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title class_\">SimpleGeGLUMLP<\/span>(nn.Module):<br \/>\n    <span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title function_\">__init__<\/span>(<span class=\"hljs-params\">self, dim, hidden<\/span>):<br \/>\n        <span class=\"hljs-built_in\">tremendous<\/span>().__init__()<br \/>\n        self.gate_proj = nn.Linear(dim, hidden, bias=<span class=\"hljs-literal\">False<\/span>)<br \/>\n        self.up_proj = nn.Linear(dim, hidden, bias=<span class=\"hljs-literal\">False<\/span>)<br \/>\n        self.down_proj = nn.Linear(hidden, dim, bias=<span class=\"hljs-literal\">False<\/span>)<\/p>\n<p>    <span class=\"hljs-keyword\">def<\/span> <span class=\"hljs-title function_\">ahead<\/span>(<span class=\"hljs-params\">self, x<\/span>):<br \/>\n        g = self.gate_proj(x)<br \/>\n        u = self.up_proj(x)<br \/>\n        h = F.gelu(g, approximate=<span class=\"hljs-string\">&#8220;tanh&#8221;<\/span>)<br \/>\n        m = h * u<br \/>\n        y = self.down_proj(m)<br \/>\n        <span class=\"hljs-keyword\">return<\/span> y<\/p>\n<p>One can find your complete script right here: 03_simple_mlp.py. Execute it like so:<\/p>\n<p>uv run 03_simple_mlp.py &#8211;batch 64 &#8212;<span class=\"hljs-built_in\">seq<\/span> 128 &#8211;dim 768 &#8211;hidden 3072<br \/>\nuvx trace-util traces -b traces<\/p>\n<p>Earlier than we open the hint, let&#8217;s assume collectively about what we should always anticipate to see. The ahead perform does a good quantity of computation, however most of it&#8217;s already acquainted to us.<\/p>\n<p>We must always anticipate three aten::linear dispatches, one for every nn.Linear layer. We must also anticipate two pointwise kernel launches, one for the GeLU and one for the multiplication. Forming this expectation earlier than trying is the one most helpful behavior within the profiling journey: you learn the hint to substantiate or break a guess, to not kind one from scratch.<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/simple-mlp-eager.png\" alt=\"Profiler trace of the GeGLU MLP forward pass, with five boxed groups on the CPU lane labelled linear, linear, gelu, mul, linear\"\/><\/p>\n<p>Determine 7: The profiler hint for a GeGLU MLP<\/p>\n<\/div>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/occupancy-queries.png\" alt=\"Occupancy Queries highlighted in the linear projection traces\"\/><\/p>\n<p>Determine 8: The occupancy queries highlighted within the linear projection CPU lane<\/p>\n<\/div>\n<p>From Determine 7 we are able to pat ourselves on the again, as our instinct was right. Per ahead go (one mlp_fwd), the GPU runs precisely 5 kernels. Determine 8 highlights the &#8220;occupancy question&#8221; as seen within the CPU lane for the linear projection layers.<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p>Op<br \/>\nCPU op<br \/>\nGPU kernel<br \/>\nlaunches<\/p>\n<p>gate_proj<br \/>\naten::linear<br \/>\nampere_bf16_s16816gemm_bf16_128x128_&#8230;<br \/>\noccupancy question + cudaLaunchKernel<\/p>\n<p>up_proj<br \/>\naten::linear<br \/>\nampere_bf16_s16816gemm_bf16_128x128_&#8230;<br \/>\noccupancy question + cudaLaunchKernel<\/p>\n<p>gelu<br \/>\naten::gelu<br \/>\nvectorized_elementwise_kernel&lt;4, GeluCUDAKernelImpl&#8230;&gt;<br \/>\ncudaLaunchKernel<\/p>\n<p>h * u<br \/>\naten::mul<br \/>\nvectorized_elementwise_kernel&lt;4, &#8230;MulFunctor&#8230;&gt;<br \/>\ncudaLaunchKernel<\/p>\n<p>down_proj<br \/>\naten::linear<br \/>\nampere_bf16_s16816gemm_bf16_128x256_&#8230;<br \/>\noccupancy question + cudaLaunchKernel<\/p>\n<\/div>\n<p>The three GEMMs every do an additional cudaOccupancyMaxActiveBlocksPerMultiprocessor name earlier than the launch. We now have a separate part on this in Half 1, yow will discover it right here. That&#8217;s cuBLAS sizing the grid. The pointwise ops (GeLU and mul) launch instantly, with no occupancy question. So &#8220;a linear&#8221; is definitely question + launch, whereas &#8220;a pointwise op&#8221; is simply launch.<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/simple-mlp-table.png\" alt=\"Profiler table for the GeGLU MLP listing op names and their CUDA times, where metadata ops like aten::transpose and aten::as_strided show 0.000us of CUDA time\"\/><\/p>\n<p>Determine 9: The desk exhibits that some ops launch zero kernels<\/p>\n<\/div>\n<p>The aten::t, aten::transpose, aten::reshape, aten::view, aten::as_strided, and aten::_unsafe_view ops launch zero kernels. They present 0.000us of CUDA time within the desk (Determine 9) as a result of they solely rewrite tensor metadata (form and stride) on the CPU. A reader scanning the desk sees round six op names per linear, however solely certainly one of them (mm) ever reaches the GPU.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tWhy are there two varieties of GEMM kernels?<br \/>\n\t<\/span><br \/>\n<\/h3>\n<p>The MLP flattens [batch, seq, dim] to [batch * seq, dim] for the matmul. In our command-line invocation we used 64 for batch and 128 for seq, in order that&#8217;s the place the 8192 (batch * seq = 64 * 128) under comes from.<\/p>\n<p>From the hint:<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p>Linear<br \/>\naten::mm enter dims<br \/>\nM\u00b7Okay\u00b7N<br \/>\ncuBLAS kernel<br \/>\navg CUDA<\/p>\n<p>gate_proj<br \/>\n[8192,768] x [768,3072]<br \/>\n8192\u00b7768\u00b73072<br \/>\n\u2026128&#215;128\u2026stages_32x5_tn<br \/>\n0.19ms<\/p>\n<p>up_proj<br \/>\n[8192,768] x [768,3072]<br \/>\n8192\u00b7768\u00b73072<br \/>\n\u2026128&#215;128\u2026stages_32x5_tn<br \/>\n0.19ms<\/p>\n<p>down_proj<br \/>\n[8192,3072] x [3072,768]<br \/>\n8192\u00b73072\u00b7768<br \/>\n\u2026128&#215;256\u2026stages_64x3_tn<br \/>\n0.17ms<\/p>\n<\/div>\n<p>All three GEMMs have the identical FLOP depend, 2\u00b78192\u00b7768\u00b73072 \u2248 38.7 GFLOP every, but down_proj is about 10% sooner. Similar work, totally different form (N=768 as an alternative of 3072), so cuBLAS picks a distinct tile (128\u00d7256, with a deeper stages_64x3 pipeline) that will get higher reuse for that form.<\/p>\n<blockquote class=\"note\">\n<p>If you wish to study extra about tiling in depth, right here is a superb useful resource to get began with.<\/p>\n<\/blockquote>\n<p>That is precisely why the desk had two GEMM rows (Determine 9): the 128&#215;128 row is gate+up and the 128&#215;256 row is down.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tWhat does torch.compile do?<br \/>\n\t<\/span><br \/>\n<\/h3>\n<p>Earlier than compiling the ahead methodology and visualizing it, let&#8217;s do the psychological train once more of asking ourselves what we anticipate to see within the hint. It is a enjoyable experiment, and an necessary one to repeat each time you profile one thing your self. All the time construct in your instinct, and the second one thing doesn&#8217;t match, cease and work out why.<\/p>\n<p>uv run 03_simple_mlp.py &#8211;batch 64 &#8212;<span class=\"hljs-built_in\">seq<\/span> 128 &#8211;dim 768 &#8211;hidden 3072 &#8211;compile<br \/>\nuvx trace-util traces -b traces<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/simple-mlp-compile-trace.png\" alt=\"Profiler trace of the compiled GeGLU MLP showing three aten::mm calls and one fused triton kernel on the CPU lane, labelled mm, mm, fused, mm\"\/><\/p>\n<p>Determine 10: The profiler hint for the compiled GeGLU MLP<\/p>\n<\/div>\n<p>In keen mode, every nn.Linear was expanded into a series of dispatcher ops (aten::linear \u2192 aten::t \u2192 aten::transpose \u2192 aten::matmul \u2192 aten::reshape \u2192 aten::mm). These are the high-level wrappers that ATen walks by means of earlier than reaching the actual GEMM. torch.compile removes that chain.<\/p>\n<p>By the point the compiled graph runs, there isn&#8217;t a linear, no matmul, no transpose or reshape and people metadata ops have been folded into how mm is known as. We will see three naked aten::mm exterior calls (Determine 10). The proof that it&#8217;s the identical GEMM is that the kernel names are byte-for-byte an identical to keen: &#8230;128&#215;128&#8230;stages_32x5_tn for gate and up, and &#8230;128&#215;256&#8230;stages_64x3_tn for down.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tThe fused Triton kernel<br \/>\n\t<\/span><br \/>\n<\/h3>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/fused.png\" alt=\"Compiled MLP trace with the triton_poi_fused__unsafe_view_gelu_mul_0 kernel boxed on the CPU lane, replacing the separate gelu and mul kernels from the eager run\"\/><\/p>\n<p>Determine 11: The fused Triton kernel<\/p>\n<\/div>\n<p>That is the headline of the entire compile lesson. The 2 keen pointwise kernels (GeLU and mul) plus a reshape collapsed into one kernel, triton_poi_fused__unsafe_view_gelu_mul_0 (Determine 11). Let&#8217;s decode the identify:<\/p>\n<p>triton: generated by Inductor&#8217;s Triton backend (not cuBLAS, not ATen).<br \/>\npoi: pointwise (Inductor tags pointwise kernels poi, reductions pink, and protracted reductions per).<br \/>\nfused__unsafe_view_gelu_mul: the ops it merged: the _unsafe_view (reshape), the GeLU, and the mul.<br \/>\n0: the distinctive id throughout the graph.<\/p>\n<p>Why is that this a win? In keen mode, the intermediate h = gelu(g) is a full [8192, 3072] bf16 tensor (round 50 MB) that the GeLU kernel writes to HBM and the mul kernel instantly reads again. Fusion retains it in registers (reminiscence that resides contained in the chip and are nearer than the HBM). The Triton kernel reads g and u as soon as, computes gelu(g) * u, and writes the outcome as soon as. One entire spherical journey of the intermediate by means of world reminiscence is gone.<\/p>\n<h2 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tLet&#8217;s use hand tuned kernels<br \/>\n\t<\/span><br \/>\n<\/h2>\n<p>To this point we now have let PyTorch (keen) and the compiler (torch.compile) decide our kernels. Now we plug in a kernel {that a} human skilled wrote and tuned by hand. We use the LigerGEGLUMLP layer, that we are able to simply fetch from the Hugging Face Hub with the kernels library.<\/p>\n<p><span class=\"hljs-keyword\">from<\/span> kernels <span class=\"hljs-keyword\">import<\/span> get_kernel<\/p>\n<p>kernels_layers = get_kernel(<span class=\"hljs-string\">&#8220;kernels-community\/liger-kernels&#8221;<\/span>, model=<span class=\"hljs-number\">1<\/span>).layers<br \/>\nkernels_geglu_mlp = kernels_layers.LigerGEGLUMLP(Config()).to(machine, dtype=torch.bfloat16).<span class=\"hljs-built_in\">eval<\/span>()<\/p>\n<p>The complete script is right here: 03_kernels_mlp.py.<\/p>\n<p>uv run 03_kernels_mlp.py &#8211;batch 64 &#8212;<span class=\"hljs-built_in\">seq<\/span> 128 &#8211;dim 768 &#8211;hidden 3072<br \/>\nuvx trace-util traces -b traces<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/kernels-profile.png\" alt=\"Profiler trace of the LigerGEGLUMLP forward pass showing three aten::linear groups and a single LigerGELUMulFunction group on the CPU lane\"\/><\/p>\n<p>Determine 12: The profiler hint for the LigerGEGLUMLP layer<\/p>\n<\/div>\n<p>Determine 12 exhibits the profile for the LigerGEGLUMLP layer utilizing the Liger kernels from the Hub.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tWhy use the kernels library<br \/>\n\t<\/span><br \/>\n<\/h3>\n<p>Writing kernels in Triton or CUDA is one downside and transport them is one other. The kernel needs to be compiled in your precise mixture of GPU structure, CUDA model, and PyTorch model. That is the step that often breaks (&#8220;works on my machine&#8221;, lacking nvcc, mistaken Triton model).<\/p>\n<p>The kernels library strikes that construct step off your machine. get_kernel(&#8220;kernels-community\/liger-kernels&#8221;, model=1) downloads a pre-built, version-pinned kernel package deal from the Hugging Face Hub and caches it regionally (right here underneath ~\/.cache\/&#8230;kernels-community&#8211;liger-kernels). The advantages are:<\/p>\n<p>The kernels are compiled as soon as, in CI, for a lot of architectures and model combos. You obtain the fitting binary as an alternative of compiling it your self.<br \/>\nmodel=1 pins the precise construct, so everybody operating your script will get the identical kernel. There isn&#8217;t a &#8220;it obtained slower after I up to date a package deal&#8221;.<br \/>\nThe package deal exposes a .layers attribute with drop-in nn.Modules (like LigerGEGLUMLP). You swap your module for theirs and nothing else in your mannequin modifications.<\/p>\n<h3 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tWhy tuned kernels are higher<br \/>\n\t<\/span><br \/>\n<\/h3>\n<p>Once we say &#8220;tuned&#8221;, we imply two concrete issues, and each are seen within the hint.<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/compile-preops.png\" alt=\"Compiled MLP trace with the TorchDynamo, prologue and guard pre-ops boxed on the CPU lane before the compiled graph runs\"\/><\/p>\n<p>Determine 13: The compiled run pays for pre-ops (Dynamo, guards, prologue) earlier than any GEMM runs<\/p>\n<\/div>\n<div class=\"max-w-full overflow-auto\">\n<p><img decoding=\"async\" src=\"https:\/\/huggingface.co\/datasets\/huggingface\/documentation-images\/resolve\/main\/blog\/torch-mlp-fusion\/no-preops.png\" alt=\"LigerGEGLUMLP trace with an empty box where the compile pre-ops would be, showing the hand-written kernel has no Dynamo or guard overhead\"\/><\/p>\n<p>Determine 14: The Liger kernel has no pre-ops \u2014 the field the place they&#8217;d be is empty<\/p>\n<\/div>\n<p>The fusion is baked in. The LigerGEGLUMLP ahead is down_proj(LigerGELUMulFunction.apply(gate_proj(x), up_proj(x))). The LigerGELUMulFunction runs a single Triton kernel, _geglu_tanh_forward_kernel, that computes gelu(gate) * up in a single go. That is precisely what we noticed from torch.compile, the place the intermediate by no means makes a round-trip by means of HBM. We get it right here with out the compiler, as proven in Figures 13 and 14 (no Dynamo guards, no compile latency, no recompilation threat).<\/p>\n<p>The launch parameters have been chosen for the {hardware}. The kernel doesn&#8217;t guess its block measurement at random. Liger&#8217;s calculate_settings picks them from the column depend.<\/p>\n<p>It&#8217;s price being trustworthy concerning the trade-off right here, as a result of the uncooked numbers might be deceptive. The Liger kernel runs in 92.8 \u00b5s, whereas Inductor&#8217;s fused kernel from the compile run was 89.4 \u00b5s. At first look the hand-written kernel appears barely slower, however that comparability hides the associated fee that makes it worthwhile.<\/p>\n<p>torch.compile specializes for a static form. Inductor&#8217;s 89.4 \u00b5s kernel is quick exactly as a result of it was generated for this precise [8192, 3072] downside. Change the batch measurement, the sequence size, or the hidden dimension, Dynamo re-traces, and also you pay the compile price yet again to get a brand new specialised kernel.<\/p>\n<p>So the actual alternative just isn&#8217;t &#8220;sluggish human kernel vs quick compiled kernel&#8221;. It&#8217;s a quick generic kernel vs a kernel specialised for one explicit enter form. The Liger kernel takes one set of launch parameters and runs them for any form with no recompilation. It offers up the previous few microseconds that per-shape specialization would purchase, in alternate for being strong to altering shapes.<\/p>\n<h2 class=\"relative group flex items-baseline\">\n<p>\t<span><br \/>\n\t\tConclusion<br \/>\n\t<\/span><br \/>\n<\/h2>\n<p>The desk under collects what every step modified on the GPU and what it left untouched.<\/p>\n<div class=\"max-w-full overflow-auto\">\n<p>Setup<br \/>\nWhat modified<br \/>\nWhat stayed the identical<\/p>\n<p>Keen nn.Linear<br \/>\nBaseline: bias add is already folded into the GEMM epilogue (addmm), so it&#8217;s one cuBLAS kernel, not a matmul plus an add<br \/>\n\u2014<\/p>\n<p>Compiled nn.Linear<br \/>\nA couple of CPU dispatch ops (the aten::t view bookkeeping) disappear<br \/>\nSimilar single cuBLAS GEMM kernel, byte-for-byte. Compile has nothing to fuse<\/p>\n<p>Keen MLP<br \/>\n5 GPU kernels: 3 GEMMs + a GeLU + a mul. The [8192, 3072] intermediate makes a full round-trip by means of HBM<br \/>\nEvery GEMM remains to be the identical bias-free cuBLAS kernel as a standalone linear<\/p>\n<p>Compiled MLP<br \/>\nGeLU + mul + reshape collapse into one fused Triton kernel; the intermediate stays in registers. Pays compile pre-ops (Dynamo, guards)<br \/>\nThe three GEMMs are untouched with an identical cuBLAS kernel names<\/p>\n<p>Liger MLP<br \/>\nSimilar fusion, however baked right into a hand-written Triton kernel with hardware-tuned launch params with no Dynamo, guards, or compile latency<br \/>\nThe three GEMMs are nonetheless the identical cuBLAS kernels<\/p>\n<\/div>\n<p>If there&#8217;s one behavior to hold ahead, it&#8217;s the one we practiced earlier than each hint: guess first, then look. State what you anticipate the hint to comprise, open it, and deal with any mismatch as probably the most attention-grabbing factor on the display screen.<\/p>\n<p>This was the second cease within the Profiling in PyTorch collection. Within the subsequent submit we&#8217;ll maintain climbing the ladder, transferring from this MLP block in the direction of the eye block and, finally, a full mannequin.<\/p>\n<p>Due to Noe Flandre and Pedro Gabriel Gengo Louren\u00e7o for his or her opinions on the early draft of the submit!<\/p>\n<\/div>\n<p><br \/>\n<br \/><a href=\"https:\/\/huggingface.co\/blog\/torch-mlp-fusion\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Within the first a part of this collection &#8220;Profiling in PyTorch&#8221;, we used torch.add(torch.matmul(x, w), b) to discover ways to learn PyTorch profiler traces. We additionally mentioned a number of different subjects that got here our means &#8211; the CPU dispatch chain, launch overhead, the distinction between an overhead-bound and a compute-bound regime, and a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":878,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"fifu_image_url":"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png","fifu_image_alt":"","jnews-multi-image_gallery":[],"jnews_single_post":[],"jnews_primary_category":[],"jnews_override_bookmark_settings":[],"jnews_social_meta":[],"jnews_override_counter":[],"footnotes":""},"categories":[5],"tags":[1229,1230,1228,1227,1225,1226],"class_list":["post-876","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-developer-ai-open-source-ecosystem","tag-fused","tag-mlp","tag-nn-linear","tag-part","tag-profiling","tag-pytorch"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP - Future News 24<\/title>\n<meta name=\"description\" content=\"We\u2019re on a journey to advance and democratize artificial intelligence through open source and open science.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP - Future News 24\" \/>\n<meta property=\"og:description\" content=\"We\u2019re on a journey to advance and democratize artificial intelligence through open source and open science.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/\" \/>\n<meta property=\"og:site_name\" content=\"Future News 24\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-11T00:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-12T04:00:01+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png\" \/>\n<meta name=\"author\" content=\"Future News 24\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Future News 24\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/\"},\"author\":{\"name\":\"Future News 24\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#\\\/schema\\\/person\\\/cecad1bde21cfc357cf70128144d6c83\"},\"headline\":\"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP\",\"datePublished\":\"2026-06-11T00:00:00+00:00\",\"dateModified\":\"2026-06-12T04:00:01+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/\"},\"wordCount\":3697,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/huggingface.co\\\/blog\\\/assets\\\/torch-mlp-fusion\\\/thumbnail.png\",\"keywords\":[\"Fused\",\"MLP\",\"nn.Linear\",\"Part\",\"Profiling\",\"PyTorch\"],\"articleSection\":[\"Developer AI &amp; Open-Source Ecosystem\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/\",\"url\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/\",\"name\":\"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP - Future News 24\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/huggingface.co\\\/blog\\\/assets\\\/torch-mlp-fusion\\\/thumbnail.png\",\"datePublished\":\"2026-06-11T00:00:00+00:00\",\"dateModified\":\"2026-06-12T04:00:01+00:00\",\"description\":\"We\u2019re on a journey to advance and democratize artificial intelligence through open source and open science.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#primaryimage\",\"url\":\"https:\\\/\\\/huggingface.co\\\/blog\\\/assets\\\/torch-mlp-fusion\\\/thumbnail.png\",\"contentUrl\":\"https:\\\/\\\/huggingface.co\\\/blog\\\/assets\\\/torch-mlp-fusion\\\/thumbnail.png\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/2026\\\/06\\\/11\\\/torch-mlp-fusion\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/futurenews24.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#website\",\"url\":\"https:\\\/\\\/futurenews24.com\\\/\",\"name\":\"Future News 24\",\"description\":\"The Smart Hub for AI and Next-Gen Innovation\",\"publisher\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/futurenews24.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#organization\",\"name\":\"Future News 24\",\"url\":\"https:\\\/\\\/futurenews24.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/futurenews24.com\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/fn24-favicon.png\",\"contentUrl\":\"https:\\\/\\\/futurenews24.com\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/fn24-favicon.png\",\"width\":250,\"height\":250,\"caption\":\"Future News 24\"},\"image\":{\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/futurenews24.com\\\/#\\\/schema\\\/person\\\/cecad1bde21cfc357cf70128144d6c83\",\"name\":\"Future News 24\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d57f07142d73cb5503ab2446ea7bc9ef3d0a5ba378d64a6157692311e42bf097?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d57f07142d73cb5503ab2446ea7bc9ef3d0a5ba378d64a6157692311e42bf097?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/d57f07142d73cb5503ab2446ea7bc9ef3d0a5ba378d64a6157692311e42bf097?s=96&d=mm&r=g\",\"caption\":\"Future News 24\"},\"sameAs\":[\"https:\\\/\\\/futurenews24.com\"],\"url\":\"https:\\\/\\\/futurenews24.com\\\/index.php\\\/author\\\/mridulpahuja20\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP - Future News 24","description":"We\u2019re on a journey to advance and democratize artificial intelligence through open source and open science.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/","og_locale":"en_US","og_type":"article","og_title":"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP - Future News 24","og_description":"We\u2019re on a journey to advance and democratize artificial intelligence through open source and open science.","og_url":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/","og_site_name":"Future News 24","article_published_time":"2026-06-11T00:00:00+00:00","article_modified_time":"2026-06-12T04:00:01+00:00","og_image":[{"url":"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png","type":"","width":"","height":""}],"author":"Future News 24","twitter_card":"summary_large_image","twitter_image":"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png","twitter_misc":{"Written by":"Future News 24","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#article","isPartOf":{"@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/"},"author":{"name":"Future News 24","@id":"https:\/\/futurenews24.com\/#\/schema\/person\/cecad1bde21cfc357cf70128144d6c83"},"headline":"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP","datePublished":"2026-06-11T00:00:00+00:00","dateModified":"2026-06-12T04:00:01+00:00","mainEntityOfPage":{"@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/"},"wordCount":3697,"commentCount":0,"publisher":{"@id":"https:\/\/futurenews24.com\/#organization"},"image":{"@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#primaryimage"},"thumbnailUrl":"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png","keywords":["Fused","MLP","nn.Linear","Part","Profiling","PyTorch"],"articleSection":["Developer AI &amp; Open-Source Ecosystem"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/","url":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/","name":"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP - Future News 24","isPartOf":{"@id":"https:\/\/futurenews24.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#primaryimage"},"image":{"@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#primaryimage"},"thumbnailUrl":"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png","datePublished":"2026-06-11T00:00:00+00:00","dateModified":"2026-06-12T04:00:01+00:00","description":"We\u2019re on a journey to advance and democratize artificial intelligence through open source and open science.","breadcrumb":{"@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#primaryimage","url":"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png","contentUrl":"https:\/\/huggingface.co\/blog\/assets\/torch-mlp-fusion\/thumbnail.png"},{"@type":"BreadcrumbList","@id":"https:\/\/futurenews24.com\/index.php\/2026\/06\/11\/torch-mlp-fusion\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/futurenews24.com\/"},{"@type":"ListItem","position":2,"name":"Profiling in PyTorch (Half 2): From nn.Linear to a Fused MLP"}]},{"@type":"WebSite","@id":"https:\/\/futurenews24.com\/#website","url":"https:\/\/futurenews24.com\/","name":"Future News 24","description":"The Smart Hub for AI and Next-Gen Innovation","publisher":{"@id":"https:\/\/futurenews24.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/futurenews24.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/futurenews24.com\/#organization","name":"Future News 24","url":"https:\/\/futurenews24.com\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/futurenews24.com\/#\/schema\/logo\/image\/","url":"https:\/\/futurenews24.com\/wp-content\/uploads\/2026\/06\/fn24-favicon.png","contentUrl":"https:\/\/futurenews24.com\/wp-content\/uploads\/2026\/06\/fn24-favicon.png","width":250,"height":250,"caption":"Future News 24"},"image":{"@id":"https:\/\/futurenews24.com\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/futurenews24.com\/#\/schema\/person\/cecad1bde21cfc357cf70128144d6c83","name":"Future News 24","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/d57f07142d73cb5503ab2446ea7bc9ef3d0a5ba378d64a6157692311e42bf097?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/d57f07142d73cb5503ab2446ea7bc9ef3d0a5ba378d64a6157692311e42bf097?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d57f07142d73cb5503ab2446ea7bc9ef3d0a5ba378d64a6157692311e42bf097?s=96&d=mm&r=g","caption":"Future News 24"},"sameAs":["https:\/\/futurenews24.com"],"url":"https:\/\/futurenews24.com\/index.php\/author\/mridulpahuja20\/"}]}},"_links":{"self":[{"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/posts\/876","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/comments?post=876"}],"version-history":[{"count":1,"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/posts\/876\/revisions"}],"predecessor-version":[{"id":877,"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/posts\/876\/revisions\/877"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/media\/878"}],"wp:attachment":[{"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/media?parent=876"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/categories?post=876"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/futurenews24.com\/index.php\/wp-json\/wp\/v2\/tags?post=876"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}