<?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: Görkem TUNA</title>
    <description>The latest articles on DEV Community by Görkem TUNA (@gundi61).</description>
    <link>https://dev.to/gundi61</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%2F4094315%2F5bc25f2b-ad1f-44c7-86ab-323ef5e110ac.png</url>
      <title>DEV Community: Görkem TUNA</title>
      <link>https://dev.to/gundi61</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gundi61"/>
    <language>en</language>
    <item>
      <title>How I Found an IEEE 754 Violation in an AI Chip Company's Math Kernel</title>
      <dc:creator>Görkem TUNA</dc:creator>
      <pubDate>Tue, 25 Aug 2026 14:01:36 +0000</pubDate>
      <link>https://dev.to/gundi61/how-i-found-an-ieee-754-violation-in-an-ai-chip-companys-math-kernel-524o</link>
      <guid>https://dev.to/gundi61/how-i-found-an-ieee-754-violation-in-an-ai-chip-companys-math-kernel-524o</guid>
      <description>&lt;p&gt;&lt;em&gt;A field report from auditing Tenstorrent's tt-metal SFPU kernels — with the exact method, so you can do it too.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;Tenstorrent — Jim Keller's chip company — ships &lt;a href="https://github.com/tenstorrent/tt-metal" rel="noopener noreferrer"&gt;tt-metal&lt;/a&gt;,&lt;br&gt;
the software stack for their RISC-V-based AI accelerators. Deep in its SFPU (Special Function&lt;br&gt;
Processing Unit) kernels, &lt;code&gt;ttnn.atan2&lt;/code&gt; had this behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# → 1.5708  (π/2, correct per IEEE 754)
&lt;/span&gt;&lt;span class="n"&gt;ttnn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# → 0.0     (wrong)
&lt;/span&gt;&lt;span class="n"&gt;ttnn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# → 3.14159 (π, also wrong — should be +π/2)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a rounding nit. A hard violation of the IEEE 754 special-case table that every&lt;br&gt;
C library, every GPU vendor, and PyTorch implements identically — in an op that&lt;br&gt;
gradient-based navigation, robotics, and phase computation rely on.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why nobody caught it
&lt;/h2&gt;

&lt;p&gt;The existing test suite generates &lt;strong&gt;random inputs in a finite range&lt;/strong&gt;. Random floats never&lt;br&gt;
produce &lt;code&gt;inf&lt;/code&gt;, and essentially never produce exact &lt;code&gt;±0&lt;/code&gt; in the denominator. The special-value&lt;br&gt;
table is precisely the set of inputs your fuzzer will never sample.&lt;/p&gt;

&lt;p&gt;This is the meta-lesson of every kernel bug I have found:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Random testing explores the measure-one set. Bugs live in the measure-zero set.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The hunt
&lt;/h2&gt;

&lt;p&gt;I audited the kernel against a table I wrote &lt;em&gt;before&lt;/em&gt; reading the code — the oracle comes&lt;br&gt;
first, or you will unconsciously excuse whatever the code does. For &lt;code&gt;atan2(y, x)&lt;/code&gt; the&lt;br&gt;
interesting cells are &lt;code&gt;y ∈ {±0, ±∞}&lt;/code&gt; crossed with &lt;code&gt;x ∈ {±0, ±∞, finite}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then read the kernel as an algorithm, not as code. The SFPU implementation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;computes &lt;code&gt;min = min(|x|,|y|)&lt;/code&gt;, &lt;code&gt;max = max(|x|,|y|)&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;reduces to &lt;code&gt;atan(a)&lt;/code&gt; on &lt;code&gt;[0,1]&lt;/code&gt; where &lt;code&gt;a = min/max&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;applies quadrant corrections,&lt;/li&gt;
&lt;li&gt;and keeps a &lt;strong&gt;rescue branch&lt;/strong&gt;: "if both inputs are zero, return ±0".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rescue was guarded by &lt;code&gt;min == 0&lt;/code&gt;. Now trace &lt;code&gt;y = +inf, x = +0&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;min = 0&lt;/code&gt;, &lt;code&gt;max = inf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;|y| ≥ |x|&lt;/code&gt; branch correctly computes &lt;code&gt;r = π/2 − 0 = π/2&lt;/code&gt; ✅&lt;/li&gt;
&lt;li&gt;The rescue checks &lt;code&gt;min == 0&lt;/code&gt; → &lt;strong&gt;true&lt;/strong&gt; → &lt;code&gt;r = 0&lt;/code&gt; ❌&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rescue was written for "both zero" but its condition only tests &lt;em&gt;one&lt;/em&gt; of the two.&lt;br&gt;
Since &lt;code&gt;0 ≤ min ≤ max&lt;/code&gt;, the correct both-zero condition is &lt;code&gt;max == 0&lt;/code&gt; — which also implies&lt;br&gt;
&lt;code&gt;min == 0&lt;/code&gt;, and never fires for the infinite case.&lt;/p&gt;
&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;One comparison, three architecture copies (Wormhole, Blackhole, Quasar — math kernels are&lt;br&gt;
duplicated per hardware target, and a fix that misses a copy is incomplete):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-v_if(min == 0.0f) {
&lt;/span&gt;&lt;span class="gi"&gt;+v_if(max == 0.0f) {
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a deterministic regression test pinning the four &lt;code&gt;inf/zero&lt;/code&gt; cells against the torch&lt;br&gt;
golden — the random-range test that would have caught this costs ten lines.&lt;/p&gt;

&lt;p&gt;The fix was reviewed by &lt;strong&gt;three Tenstorrent engineers&lt;/strong&gt; and&lt;br&gt;
&lt;a href="https://github.com/tenstorrent/tt-metal/pull/54240" rel="noopener noreferrer"&gt;merged into main&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general method
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write the oracle first.&lt;/strong&gt; The special-value table, from the standard — before reading
a line of kernel code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the kernel as an algorithm.&lt;/strong&gt; Strip the intrinsics: it is range reduction →
polynomial → exponent reconstruction → sign fix-up. Reconstruct it in plain fp32.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand-trace every special value&lt;/strong&gt; through the model: &lt;code&gt;±0, ±inf, NaN, ±1, ±INT_MIN,
boundary−ε, boundary+ε, boundary-exact&lt;/code&gt;. One divergent cell is a finding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix minimally, everywhere.&lt;/strong&gt; All architecture copies. Mirror the codebase's idioms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic regression test&lt;/strong&gt; with the exact edge values.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I have since catalogued four more recurring patterns from auditing 15+ kernels — clamping&lt;br&gt;
the range-reduction &lt;em&gt;driver&lt;/em&gt; instead of guarding the &lt;em&gt;output&lt;/em&gt;, boundary strictness&lt;br&gt;
mismatches vs the reference, order-of-operations overflow, and &lt;code&gt;INT_MIN&lt;/code&gt; sign-magnitude&lt;br&gt;
conversion edges. The full playbook:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/gundi61/kernel-audit-playbook" rel="noopener noreferrer"&gt;github.com/gundi61/kernel-audit-playbook&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The meta-lesson
&lt;/h2&gt;

&lt;p&gt;The highest-value bugs in 2026 are not behind random fuzzing — the agents already run&lt;br&gt;
fuzzers. They are behind &lt;strong&gt;specification-first reading&lt;/strong&gt;: pick the table the hardware&lt;br&gt;
implicitly promises (IEEE 754, a reference implementation's docs), trace the implementation&lt;br&gt;
against it by hand, and test exactly the cells a sampler cannot reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The measure-zero set is where the money is.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found, fixed and merged as&lt;br&gt;
&lt;a href="https://github.com/tenstorrent/tt-metal/issues/54241" rel="noopener noreferrer"&gt;issue #54241&lt;/a&gt; +&lt;br&gt;
&lt;a href="https://github.com/tenstorrent/tt-metal/pull/54240" rel="noopener noreferrer"&gt;PR #54240&lt;/a&gt; in tenstorrent/tt-metal.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>cpp</category>
      <category>hardware</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
