|
| 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 |
0 commit comments