Skip to content

[tmva][sofie] Pool operator ignores ceil_mode attribute, always uses floor division for output shape #21774

@harz05

Description

@harz05

Check duplicate issues.

  • Checked for duplicates

Description

In tmva/sofie/inc/TMVA/ROperator_Pool.hxx, the ceil_mode attribute is read from the ONNX node and stored in fAttrCeilMode,but it is never used when computing the output shape in DoShapeInference().

The output size is always computed with floor division:

size_t output1 = (input1 + pad1 - fAttrKernelShape[0]) / fAttrStrides[0] + 1;

The ONNX spec requires that when ceil_mode=1, ceiling division is used :
output = ceil((input + pad - kernel) / stride + 1)
As a result, any model using MaxPool or AveragePool with ceil_mode=1 silently produces a wrong (smaller) output shape and drops elements that should be present.

Reproducer

Run the following script with- python3 reproducer.py
Requirements: ROOT built with SOFIE enabled, Python packages: onnx, onnxruntime, numpy

import numpy as np
import onnx
from onnx import helper, TensorProto
import onnxruntime as rt
import tempfile, os
import ROOT

# MaxPool: input (1,1,5,5), kernel=2x2, stride=2x2, ceil_mode=1
# (5-2)/2 + 1 = 2.5  →  floor=2, ceil=3
# Expected output shape: (1,1,3,3). SOFIE produces (1,1,2,2).

input_data = np.arange(25, dtype=np.float32).reshape(1,1,5,5)

graph = helper.make_graph(
    [helper.make_node('MaxPool', inputs=['X'], outputs=['Y'],
        kernel_shape=[2,2], strides=[2,2], ceil_mode=1)],
    'maxpool_ceil',
    inputs=[helper.make_tensor_value_info('X', TensorProto.FLOAT, [1,1,5,5])],
    outputs=[helper.make_tensor_value_info('Y', TensorProto.FLOAT, None)]
)
model = helper.make_model(graph, opset_imports=[helper.make_opsetid('', 11)])
model.ir_version = 7

tmpdir = tempfile.mkdtemp()
onnx_path = os.path.join(tmpdir, 'maxpool_ceil.onnx')
onnx.save(model, onnx_path)

# OnnxRuntime reference
ort_out = rt.InferenceSession(onnx_path).run(None, {'X': input_data})[0]
print("OnnxRuntime output shape:", ort_out.shape) # (1,1,3,3) i.e. 9 elements
print("OnnxRuntime output:", ort_out.flatten())

# SOFIE
ROOT.gSystem.Load('libROOTTMVASofieParser')
parser = ROOT.TMVA.Experimental.SOFIE.RModelParser_ONNX()
rmodel = parser.Parse(onnx_path)
rmodel.Generate()
rmodel.OutputGenerated(os.path.join(tmpdir, 'maxpool_ceil.hxx'))
ROOT.gInterpreter.Declare(f'#include "{tmpdir}/maxpool_ceil.hxx"')
sess = ROOT.TMVA_SOFIE_maxpool_ceil.Session()
sofie_out = np.array(sess.infer(input_data.flatten()))
print("SOFIE output shape: ", sofie_out.shape)      # (4,); wrong, should be 9
print("SOFIE output:", sofie_out)
print("Shape mismatch:", sofie_out.size != ort_out.size)

Observed:

  • OnnxRuntime output shape: (1,1,3,3) i.e. 9 elements: 6 8 9 16 18 19 21 23 24
  • SOFIE output size: 4: wrong shape (1,1,2,2): 6 8 16 18. Last row and column silently dropped

ROOT version

6.39.01

Installation method

Built from source with -Dtmva-sofie=ON -Dbuiltin_protobuf=ON

Operating system

Linux (Ubuntu 22.04)

Additional context

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions