Skip to content

Commit 74b09d3

Browse files
test(cuda): pin scaledProdExp bit-identity to composed exp((c·x)·y)
`Buffer.scaledProdExp x y c` is a single fused device kernel for `exp((c·x)·y)`. Add a CUDA kernel-coverage test asserting it is bit-identical — compared by `Float.toBits`, not a tolerance — to the composed form built from the `full` / `mul` / `exp` elementwise kernels, over signed/zero fixtures and a range of scalars. A fast-math `__expf`, a reassociated product, or a dropped float32 cast would fail it. Runs on the CPU stub (`lake build`) and on the GPU (`-K cuda`) alike; wired into the CUDA suite.
1 parent 30aafb6 commit 74b09d3

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/-
2+
Copyright (c) 2026 TorchLean
3+
Released under MIT license as described in the file LICENSE.
4+
Authors: TorchLean Team
5+
-/
6+
7+
module
8+
9+
public import NN.Runtime.Autograd.Engine.Cuda.Buffer
10+
public import Std
11+
12+
/-!
13+
# CUDA Kernel Coverage: scaledProdExp (fused scaled product exponential)
14+
15+
`Buffer.scaledProdExp x y c` computes `exp((c · x) · y)` in a single fused device kernel. This test
16+
pins its defining property: element for element it must be **bit-identical** to the composed form built
17+
from the elementwise `full` / `mul` / `exp` kernels — `exp(((full c) · x) · y)`, the same
18+
left-association and the same `expf`. A silent divergence (a fast-math `__expf`, a reassociated
19+
product, a lost float32 cast) is a regression this catches.
20+
21+
Like the rest of this suite it runs on the CPU stub (`lake build`) and on the GPU (`-K cuda`) alike.
22+
-/
23+
24+
@[expose] public section
25+
26+
namespace Tests
27+
namespace Cuda
28+
namespace ScaledProdExp
29+
30+
open Runtime.Autograd.Cuda
31+
32+
def run : IO Unit := do
33+
IO.println "=== CUDA kernel coverage: scaledProdExp (fused vs composed) ==="
34+
-- Varied, signed, finite fixtures, kept in a range where `exp` stays finite.
35+
let xs : FloatArray := FloatArray.mk #[0.10, -0.20, 0.35, -0.50, 0.75, -0.90, 0.00, 1.00, -1.00, 0.42]
36+
let ys : FloatArray := FloatArray.mk #[0.90, -0.75, 0.50, -0.30, 0.15, -0.05, 1.00, -1.00, 0.25, -0.60]
37+
let n : UInt32 := xs.size.toUInt32
38+
let x := Buffer.ofFloatArray xs
39+
let y := Buffer.ofFloatArray ys
40+
-- Scalars spanning sign and magnitude, including the identity `c = 0`.
41+
for c in ([-2.0, 0.5, 3.25, -0.125, 1.0, 0.0] : List Float) do
42+
let fused := Buffer.scaledProdExp x y c
43+
let composed := Buffer.exp (Buffer.mul (Buffer.mul (Buffer.full n c) x) y)
44+
let af := Buffer.toFloatArray fused
45+
let ac := Buffer.toFloatArray composed
46+
if af.size != ac.size then
47+
throw <| IO.userError s!"scaledProdExp c={c}: size mismatch ({af.size} vs {ac.size})"
48+
let mut mism : Nat := 0
49+
let mut maxDiff : Float := 0.0
50+
for i in [:af.size] do
51+
let vf := af.get! i
52+
let vc := ac.get! i
53+
-- Bit-level comparison: `toBits` distinguishes results that `==` would call equal.
54+
if vf.toBits != vc.toBits then mism := mism + 1
55+
let d := Float.abs (vf - vc)
56+
if d > maxDiff then maxDiff := d
57+
if mism != 0 then
58+
throw <| IO.userError
59+
s!"scaledProdExp c={c}: {mism}/{af.size} elements differ from composed exp((c·x)·y) (max |Δ|={maxDiff})"
60+
IO.println " fused scaledProdExp bit-identical to composed exp((c·x)·y) over all fixtures ✓"
61+
62+
end ScaledProdExp
63+
end Cuda
64+
end Tests

NN/Tests/Runtime/Cuda/Suite.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public import NN.Tests.Runtime.Cuda.MatmulBmm
2121
public import NN.Tests.Runtime.Cuda.Fft
2222
public import NN.Tests.Runtime.Cuda.ViewsBroadcastReduce
2323
public import NN.Tests.Runtime.Cuda.LinearMseConcatSliceGather
24+
public import NN.Tests.Runtime.Cuda.ScaledProdExp
2425
public import NN.Tests.Runtime.Cuda.Stress
2526

2627
/-!
@@ -56,6 +57,7 @@ def run : IO Unit := do
5657
Fft.run
5758
ViewsBroadcastReduce.run
5859
LinearMseConcatSliceGather.run
60+
ScaledProdExp.run
5961
Stress.run
6062
IO.println "=== CUDA kernel coverage suite completed ==="
6163

0 commit comments

Comments
 (0)