← Research index
INDEPENDENT NUMERICAL AUDIT7 September 2026

ONNX reference LpNormalization uses a signed sum for p=1

The input [1, -1] becomes [0, 0], while the exact L1-normalized result and ONNX Runtime both return [0.5, -0.5].

Xamit Kadirbekov
Xamit KadirbekovIndependent verification · GERO Research
Numerical auditONNXNormalization
STATUS · VERIFIED ORDINARY CORRECTNESS DEFECTReproduced on ONNX 1.22.0 and current main; correction PR #8427 adds absolute values and a negative-input regression.
UPSTREAM RECORD onnx/onnx#8427 ↗

Claim under test

ONNX defines LpNormalization as division by an Lp norm along a selected axis. For p=1, the divisor is the sum of absolute values:

||x||₁ = Σᵢ |xᵢ|

The sign of a component must not reduce the norm contributed by another component.

Minimal counterexample

For axis=0, p=1, and float32 input [1, -1]:

ONNX ReferenceEvaluator   [ 0.0,  0.0]
ONNX Runtime CPU          [ 0.5, -0.5]
exact x / Σ|x|            [ 0.5, -0.5]

The exact L1 norm is |1| + |-1| = 2. The reference implementation instead forms the signed sum 1 + (-1) = 0; its zero-norm guard then replaces the result with a zero vector.

Two further cases isolate the same cause:

input [-1,-2]  reference [ 1/3,  2/3]  exact [-1/3, -2/3]
input [ 1,-3]  reference [-1/2,  3/2]  exact [ 1/4, -3/4]

The second reference output even has L1 norm 2, so it is not normalized.

Cause

The current implementation computes:

norm = np.power(np.power(x, p).sum(axis=axis), 1.0 / p)

For p=2, squaring removes the sign. For p=1, it does not. The definition requires np.abs(x) before exponentiation.

The existing p=1 backend examples use only non-negative inputs, so signed summation and absolute summation happen to agree in those tests.

Independent verification

  • Exact arithmetic gives [1/2, -1/2] for the minimal input.
  • NumPy x / np.sum(np.abs(x)) gives [0.5, -0.5] in float32.
  • ONNX Runtime 1.29.0 returns the same result on the CPU provider.
  • ONNX ReferenceEvaluator 1.22.0 returns [0, 0].
  • The formula remained present on ONNX main commit 1fdd92cf2f70f7a00c55d9153d243829d9349a96, checked 2026-09-07.
  • Searches across open and closed ONNX issues and pull requests using four relevant phrasings found no duplicate.

The executable reproducer is published at gero-onnx-reference-lpnormalization-audit.

Impact and boundary

This defect affects the Python ReferenceEvaluator when p=1 and the reduced axis contains negative values. It does not implicate ONNX Runtime, which matched the exact result in every tested case. The p=2 path is not implicated.

A reference evaluator is used as an oracle by backend authors and conformance tests. Here, a correct runtime can disagree with the official reference, and a consumer of onnx.reference can receive a vector with wrong signs, magnitudes, or normalization.

This is an ordinary numerical-correctness defect, not a security finding. No deployed-system impact or loss is claimed.

Proposed correction

Compute the norm from absolute values:

norm = np.power(np.power(np.abs(x), p).sum(axis=axis), 1.0 / p)

A regression test must include a negative p=1 input; [1, -1] distinguishes the two formulas with an exact result.

Upstream status

The one-line correction and a negative-input backend regression test were submitted as ONNX pull request #8427.