parse_selu lowering loses catastrophic cancellation → silent 0.0 for moderate positive inputs

Aperta Adatta ai principianti
#5,490 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 3 giorni

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
74/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
cpp
Ambito
backend

Direzione di ricerca

Inizia da src/onnx/parse_selu.cpp e confronta il suo lowering con il corpo della funzione di riferimento ONNX Selu collegato nell’issue. Verifica la presenza di test a livello di valore che coprano input positivi grandi e anche BF16 e FP32; aggiungi o aggiorna la copertura affinché rilevi lo zero silenzioso e l’overflow segnalati. Il lavoro è completato quando gli input positivi producono gamma * x senza cancellazione e i test esistenti del parser passano.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

The ONNX Selu parser (src/onnx/parse_selu.cpp) lowers the activation to a
branchless algebraic identity that evaluates exp(x) unconditionally for every
input:

result = (γ/2)·[ (α·eˣ − α + x) − sign(x)·(α·eˣ − α − x) ]

Writing A = α·eˣ − α, the graph computes fl(A + x) − fl(A − x). Once 2x drops
below ulp(A), both terms round to the same floating-point value and their
difference is exactly zero. SELU is monotonically increasing, so returning 0.0
for a large positive x is badly wrong — and it is returned silently, as a finite
number (no error, no NaN).

Versions

Reproduced on MIGraphX 2.17.0 and on 2.15.0 (2.15.0.20250912-17-221-gdd11a7555,
ROCm 7.2.4), on both the CPU ref target and a gfx1151 GPU target. parse_selu.cpp
has had no functional change since Aug 2021, so this likely affects every version in
between.

Onsets (measured)

precision first wrong x silent 0.0 from NaN from
FP32 17 (5.9 % error) 20 89
BF16 6 (32.9 % error) 8 89

Two points worth stressing:

  1. NaN is the least harmful symptom. The dominant failure is the silent-zero
    regime, which starts far earlier (x ≥ 20 FP32, x ≥ 8 BF16). A validation gate that
    only checks isfinite misses all of it.
  2. There is no "FP32 safe to ~709" zone. FP32 and BF16 share an 8-bit exponent, so
    exp overflows at x ≈ 88.7 in both. 709 is the FP64 threshold.

Reproducer (self-contained, no AMD GPU required)

import numpy as np, onnx, migraphx
from onnx import helper, TensorProto

alpha, gamma = 1.6732632423543772, 1.0507009873554805
node  = helper.make_node("Selu", ["input"], ["output"], alpha=alpha, gamma=gamma)
xi    = helper.make_tensor_value_info("input",  TensorProto.FLOAT, [1, 1])
yo    = helper.make_tensor_value_info("output", TensorProto.FLOAT, [1, 1])
model = helper.make_model(helper.make_graph([node], "selu", [xi], [yo]),
                          opset_imports=[helper.make_opsetid("", 13)])
model.ir_version = 7
onnx.save(model, "selu_single_node.onnx")

prog = migraphx.parse_onnx("selu_single_node.onnx")
prog.compile(migraphx.get_target("ref"))   # CPU ref; "gpu" reproduces identically
for v in [1.0, 17.0, 20.0, 50.0, 89.0]:
    out = np.array(prog.run({"input": np.array([[v]], dtype=np.float32)})[-1])
    print(f"x={v:6.1f}  migraphx={out.item():12.4f}  expected={gamma*v:12.4f}")

Observed (FP32, ref target — identical on the gpu target):

x=   1.0  migraphx=      1.0507  expected=      1.0507
x=  17.0  migraphx=     16.8112  expected=     17.8619     <- 5.9 % low
x=  20.0  migraphx=      0.0000  expected=     21.0140     <- silent zero
x=  50.0  migraphx=      0.0000  expected=     52.5350
x=  89.0  migraphx=         nan  expected=     93.5124

Expected behaviour

For x > 0, Selu(x) == gamma * x exactly, for all representable x.

Suggested fix

Lower to the branch-separated form from the official
ONNX Selu reference function body:

Where(x > 0, gamma * x, gamma * alpha * (exp(min(x, 0)) - 1))

The Where keeps the two branches from cancelling, and clamping with min(x, 0)
means exp never sees a positive argument, so the overflow at x ≈ 88.7 disappears as
well. This also aligns the parser with the reference semantics.

Why it is easy to miss

On a real classifier the defect left top-1 accuracy unchanged (argmax is
insensitive to a uniform rescale of one branch) while degrading FP32↔BF16 logit SNR.
Accuracy-only regression tests do not catch it — a value-level (SNR / rel-error)
check is needed.

Lingua principale
C++
Stelle
338
Fork
161
Merge medio
5g 21h
PR unite (30g)
45

Preparare l'ambiente

  • Include un Dockerfile o un file Docker Compose
  • Ha un modello di pull request
  • Nessuna guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ROCm/AMDMIGraphX

Tutte le issue di ROCm/AMDMIGraphX

Issue simili

Altre issue su C++

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.