<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Checo Cadena</title>
    <description>The latest articles on DEV Community by Checo Cadena (@checocadena).</description>
    <link>https://dev.to/checocadena</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4231094%2F9a0ac3fe-4767-4bf6-8b0a-2564492031e3.png</url>
      <title>DEV Community: Checo Cadena</title>
      <link>https://dev.to/checocadena</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/checocadena"/>
    <language>en</language>
    <item>
      <title>I had an AI agent port Karpathy's GPT-2 trainer from CUDA to SYCL, and made it prove every step</title>
      <dc:creator>Checo Cadena</dc:creator>
      <pubDate>Fri, 06 Nov 2026 20:48:55 +0000</pubDate>
      <link>https://dev.to/checocadena/i-had-an-ai-agent-port-karpathys-gpt-2-trainer-from-cuda-to-sycl-and-made-it-prove-every-step-1b8l</link>
      <guid>https://dev.to/checocadena/i-had-an-ai-agent-port-karpathys-gpt-2-trainer-from-cuda-to-sycl-and-made-it-prove-every-step-1b8l</guid>
      <description>&lt;p&gt;A lot of AI software only runs on NVIDIA GPUs, because it's written in CUDA. SYCL is an open standard (from Khronos, the group behind OpenGL and Vulkan) that lets the same C++ code run on NVIDIA, AMD, Intel and plain CPUs.&lt;/p&gt;

&lt;p&gt;Moving code from CUDA to SYCL is slow, careful work. AI coding agents are now good enough to do a lot of it. The problem is trust: an agent saying "done" is not the same as the code being right.&lt;/p&gt;

&lt;p&gt;So for Meta's Global AI Developer Hackathon I built &lt;strong&gt;Muse Port&lt;/strong&gt;: Meta's Muse model plans and ports real CUDA repositories to SYCL, and nothing counts as done until a check the model never sees confirms the output matches the original.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code and evidence: &lt;a href="https://github.com/checocadena/museport" rel="noopener noreferrer"&gt;https://github.com/checocadena/museport&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;2-minute demo: &lt;a href="https://youtu.be/uBpT5IuxPOY" rel="noopener noreferrer"&gt;https://youtu.be/uBpT5IuxPOY&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scan, no model involved.&lt;/strong&gt; &lt;code&gt;museport scan&lt;/code&gt; reads the whole CUDA repo and lists every kernel, every NVIDIA library call (cuBLAS, cuDNN, NCCL...) and its oneAPI equivalent, and every CUDA-only assumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan.&lt;/strong&gt; Muse writes a migration plan grounded against that scan, so it can't invent files or skip the hard parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port, item by item.&lt;/strong&gt; An agent loop writes SYCL, compiles it, and runs it. A first pass from Intel's SYCLomatic tool is available, and the agent finishes the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hidden checks.&lt;/strong&gt; Each item is graded by a check the model never sees, usually built from the project's own CPU code or its own tests. If it fails, the loop keeps going.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real GPUs.&lt;/strong&gt; Finished ports run on rented NVIDIA GPUs side by side with the original CUDA, and on AMD Instinct GPUs against the reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report.&lt;/strong&gt; The write-up is generated only from committed evidence. Every number links to a log.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The test case: llm.c
&lt;/h2&gt;

&lt;p&gt;I used Andrej Karpathy's &lt;a href="https://github.com/karpathy/llm.c" rel="noopener noreferrer"&gt;llm.c&lt;/a&gt;, which trains GPT-2 in raw CUDA. Its main trainer is 12 files and 4,884 lines of bf16 mixed precision, with cuBLASLt, warp reductions and stochastic rounding. It's real code, not a toy.&lt;/p&gt;

&lt;p&gt;The hidden check was llm.c's own test, at llm.c's own bf16 tolerances, with a guard that fails any port that tries to loosen them.&lt;/p&gt;

&lt;p&gt;What happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The port, in one run:&lt;/strong&gt; 125 agent turns and $5.67 of model calls, from the CUDA alone. llm.c's test passes on a CPU, an AMD MI350X and an NVIDIA GPU. That covers logits, all 16 gradient tensors, 10 losses against PyTorch, and a bit-exact checkpoint replay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It was slow:&lt;/strong&gt; 36x slower than CUDA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then the agent tuned it on real GPUs.&lt;/strong&gt; I gave it a &lt;code&gt;gpu_test&lt;/code&gt; tool that builds its current code on a rented GPU and returns llm.c's test result, milliseconds per step next to llm.c's CUDA on the same GPU, and an nsys kernel profile. It profiled, rewrote the slow kernels, measured each change, and fixed two crashes the GPU exposed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where it ended up, as time per training step compared with llm.c's CUDA on the same GPU:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;vs CUDA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Portable SYCL + oneMath, NVIDIA L4 / RTX A4000&lt;/td&gt;
&lt;td&gt;1.40-1.55x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Portable SYCL + oneMath, NVIDIA H100&lt;/td&gt;
&lt;td&gt;2.90x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;With NVIDIA's cuBLAS called through SYCL interop&lt;/td&gt;
&lt;td&gt;1.13-1.21x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AMD Instinct MI350X&lt;/td&gt;
&lt;td&gt;63 ms/step (llm.c's CUDA doesn't build on AMD)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It also ported 19 of llm.c's standalone kernel experiments, a fp32 version reassembled as a working repo (&lt;code&gt;llm.c-sycl&lt;/code&gt;, whose &lt;code&gt;make&lt;/code&gt; builds llm.c's own test), and a second project, llama2.cu, in one run for $0.45, with byte-identical stories on CPU, NVIDIA and AMD.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a port looks like
&lt;/h2&gt;

&lt;p&gt;One real kernel, before and after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cuda"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CUDA (llm.c)&lt;/span&gt;
&lt;span class="k"&gt;__global__&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;residual_forward_kernel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;inp1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;inp2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blockIdx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;blockDim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;threadIdx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;__ldcs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;inp1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;__ldcs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;inp2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// SYCL (as Muse wrote it)&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;residual_forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;launch_1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;sycl&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;__ldcs&lt;/code&gt; is an NVIDIA-only cache hint, so the port drops it. The result is identical everywhere, but that kind of vendor tuning is exactly what you have to add back per target. Which brings me to the honest part.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Portable and correct is not the same as fast.&lt;/strong&gt; The H100 number is the clearest example. The fastest NVIDIA code uses chip-specific features (fused kernels around tensor cores, warp specialization, Hopper's clusters) that portable code can't fully reach today. My bet is that the realistic version of "portable" is a correct portable baseline plus vendor-specific fast paths for the few kernels that dominate runtime. Agents might be what finally makes that second part affordable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hide the checks.&lt;/strong&gt; An agent that can read its test can end up satisfying the test instead of the program, for example by loosening a tolerance. That's why the grading lives somewhere the model can't see, and why a guard fails any port that touches llm.c's tolerances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Publish the failures.&lt;/strong&gt; The one-run bf16 port succeeded in 1 of 2 tries. One tuning run's own loop didn't converge, even though a version it built along the way passes. Speeds are medians of 2-3 runs on rented GPUs whose exact model varies, so compare the ratios. All of that is in the repo, including &lt;a href="https://github.com/checocadena/museport/blob/main/docs/LESSONS.md" rel="noopener noreferrer"&gt;every mistake and its fix&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agents find real upstream bugs.&lt;/strong&gt; Along the way the port hit &lt;code&gt;unimplemented&lt;/code&gt; for bf16 gemm in oneMath's cuBLAS backend, oneAPI's math library. The fix was two lines, and a oneMath reviewer has approved it: &lt;a href="https://github.com/uxlfoundation/oneMath/pull/793" rel="noopener noreferrer"&gt;https://github.com/uxlfoundation/oneMath/pull/793&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/checocadena/museport
&lt;span class="nb"&gt;cd &lt;/span&gt;museport/llm.c-sycl
./dev/download_starter_pack.sh
make                      &lt;span class="c"&gt;# CPU; or: make TARGET=nvidia / TARGET=amd AMD_ARCH=gfx950&lt;/span&gt;
./test_gpt2fp32sycl       &lt;span class="c"&gt;# llm.c's own test: ends with "overall okay: 1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll be talking about this at the oneAPI DevSummit, hosted by the UXL Foundation, on December 1 (online): "Evidence, Not Claims: Teaching Coding Agents the Last Mile of CUDA-to-SYCL Migration."&lt;br&gt;
&lt;a href="https://uxlfoundation.org/events/oneapi-devsummit-hosted-by-uxl-foundation-2026/" rel="noopener noreferrer"&gt;https://uxlfoundation.org/events/oneapi-devsummit-hosted-by-uxl-foundation-2026/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you work on SYCL, oneAPI or GPU kernels, I'd really like to hear where this breaks for you.&lt;/p&gt;

</description>
      <category>cuda</category>
      <category>gpu</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
