-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[tmva][sofie] Pool operator ignores ceil_mode attribute, always uses floor division for output shape #21774
Description
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