From 42e9db147250b80522ead846a4977d1f1f55b316 Mon Sep 17 00:00:00 2001 From: Harsh Chauhan Date: Wed, 25 Mar 2026 13:06:14 +0530 Subject: [PATCH 1/5] tmva/sofie: account for dilation in Conv2D same padding calculation --- .../_tmva/_sofie/_parser/_keras/layers/conv.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_sofie/_parser/_keras/layers/conv.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_sofie/_parser/_keras/layers/conv.py index 2a12fc9a832f6..394ebcc978d5f 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_sofie/_parser/_keras/layers/conv.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_tmva/_sofie/_parser/_keras/layers/conv.py @@ -52,8 +52,10 @@ def MakeKerasConv(layer): inputWidth = fInputShape[2] outputHeight = math.ceil(float(inputHeight) / float(fAttrStrides[0])) outputWidth = math.ceil(float(inputWidth) / float(fAttrStrides[1])) - padding_height = max((outputHeight - 1) * fAttrStrides[0] + fAttrKernelShape[0] - inputHeight, 0) - padding_width = max((outputWidth - 1) * fAttrStrides[1] + fAttrKernelShape[1] - inputWidth, 0) + effective_kH = fAttrDilations[0] * (fAttrKernelShape[0] - 1) + 1 + effective_kW = fAttrDilations[1] * (fAttrKernelShape[1] - 1) + 1 + padding_height = max((outputHeight - 1) * fAttrStrides[0] + effective_kH - inputHeight, 0) + padding_width = max((outputWidth - 1) * fAttrStrides[1] + effective_kW - inputWidth, 0) padding_top = math.floor(padding_height / 2) padding_bottom = padding_height - padding_top padding_left = math.floor(padding_width / 2) From eaa2ea74e3945fa67e86d338ae75e2902c46136f Mon Sep 17 00:00:00 2001 From: Harsh Chauhan Date: Sat, 28 Mar 2026 05:10:57 +0530 Subject: [PATCH 2/5] tmva/sofie: fix double-dilation in Conv and add dilated Conv2D test --- tmva/sofie/inc/TMVA/ROperator_Conv.hxx | 16 ++++----- tmva/sofie/test/TestRModelParserKeras.C | 47 +++++++++++++++++++++++++ tmva/sofie/test/generateKerasModels.py | 13 +++++++ 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/tmva/sofie/inc/TMVA/ROperator_Conv.hxx b/tmva/sofie/inc/TMVA/ROperator_Conv.hxx index b4a2bb547c3bd..e50e50103e6cf 100644 --- a/tmva/sofie/inc/TMVA/ROperator_Conv.hxx +++ b/tmva/sofie/inc/TMVA/ROperator_Conv.hxx @@ -490,12 +490,10 @@ public: // << fShapeW[1] << "," << iHeight << "," << iWidth << ","; if (fDim == 1) - out << "1, " << fAttrKernelShape[0] << ",0," << fAttrPads[0] << ",1," << fAttrStrides[0] << ",1," - << fAttrDilations[0]; + out << "1, " << fAttrKernelShape[0] << ",0," << fAttrPads[0] << ",1," << fAttrStrides[0] << ",1," << 1; else // dim ==2 out << fAttrKernelShape[0] << "," << fAttrKernelShape[1] << "," << fAttrPads[0] << "," << fAttrPads[1] - << "," << fAttrStrides[0] << "," << fAttrStrides[1] << "," << fAttrDilations[0] << "," - << fAttrDilations[1]; + << "," << fAttrStrides[0] << "," << fAttrStrides[1] << "," << 1 << "," << 1; out << "," << "tensor_" <= 3 +TEST(RModelParser_Keras, CONV_SAME_DILATED) +#else +TEST(DISABLED_RModelParser_Keras, CONV_SAME_DILATED) +#endif +{ + constexpr float TOLERANCE = DEFAULT_TOLERANCE; + std::vector inputConv2D_SameDilated(64, 1.0f); + + Py_Initialize(); + if (gSystem->AccessPathName("KerasModelConv2D_SameDilated.keras", kFileExists)) + GenerateModels(); + + TMVA::Experimental::RSofieReader r("KerasModelConv2D_SameDilated.keras"); + std::vector outputConv2D_SameDilated = r.Compute(inputConv2D_SameDilated); + + PyObject *main = PyImport_AddModule("__main__"); + PyObject *fGlobalNS = PyModule_GetDict(main); + PyObject *fLocalNS = PyDict_New(); + if (!fGlobalNS) { + throw std::runtime_error("Can't init global namespace for Python"); + } + if (!fLocalNS) { + throw std::runtime_error("Can't init local namespace for Python"); + } + PyRun_String("import os", Py_single_input, fGlobalNS, fLocalNS); + PyRun_String("os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'", Py_single_input, fGlobalNS, fLocalNS); + PyRun_String("from tensorflow.keras.models import load_model", Py_single_input, fGlobalNS, fLocalNS); + PyRun_String("import numpy", Py_single_input, fGlobalNS, fLocalNS); + PyRun_String("model=load_model('KerasModelConv2D_SameDilated.keras')", Py_single_input, fGlobalNS, fLocalNS); + PyRun_String("input=numpy.ones((1,8,8,1))", Py_single_input, fGlobalNS, fLocalNS); + PyRun_String("output=model(input).numpy()", Py_single_input, fGlobalNS, fLocalNS); + PyRun_String("outputSize=output.size", Py_single_input, fGlobalNS, fLocalNS); + std::size_t pOutputConv2DSameDilatedSize = (std::size_t)PyLong_AsLong(PyDict_GetItemString(fLocalNS, "outputSize")); + + // Testing the actual and expected output tensor sizes + EXPECT_EQ(outputConv2D_SameDilated.size(), pOutputConv2DSameDilatedSize); + + PyArrayObject *pConv2DSameDilatedValues = (PyArrayObject *)PyDict_GetItemString(fLocalNS, "output"); + float *pOutputConv2DSameDilated = (float *)PyArray_DATA(pConv2DSameDilatedValues); + + // Testing the actual and expected output tensor values + for (size_t i = 0; i < outputConv2D_SameDilated.size(); ++i) { + EXPECT_LE(std::abs(outputConv2D_SameDilated[i] - pOutputConv2DSameDilated[i]), TOLERANCE); + } +} + TEST(RModelParser_Keras, RESHAPE) { constexpr float TOLERANCE = DEFAULT_TOLERANCE; diff --git a/tmva/sofie/test/generateKerasModels.py b/tmva/sofie/test/generateKerasModels.py index 163e5de5a75e8..0dba786e7b798 100644 --- a/tmva/sofie/test/generateKerasModels.py +++ b/tmva/sofie/test/generateKerasModels.py @@ -93,6 +93,18 @@ def generateConv2DModel_SamePadding(): model.fit(x_train, y_train, verbose=0, epochs=10, batch_size=2) model.save('KerasModelConv2D_Same.keras') +def generateConv2DModel_SamePadding_Dilated(): + model=Sequential() + model.add(Conv2D(4, kernel_size=3, activation="relu", input_shape=(8,8,1), padding="same", dilation_rate=2)) + + randomGenerator=np.random.RandomState(0) + x_train=randomGenerator.rand(1,8,8,1) + y_train=randomGenerator.rand(1,8,8,4) + + model.compile(loss='mean_squared_error', optimizer=SGD(learning_rate=0.01)) + model.fit(x_train, y_train, verbose=0, epochs=10, batch_size=2) + model.save('KerasModelConv2D_SameDilated.keras') + def generateReshapeModel(): model = Sequential() model.add(Conv2D(8, kernel_size=3, activation="relu", input_shape=(4,4,1), padding="same")) @@ -189,6 +201,7 @@ def generateCustomModel(): generateBatchNormModel() generateConv2DModel_ValidPadding() generateConv2DModel_SamePadding() +generateConv2DModel_SamePadding_Dilated() generateReshapeModel() generateConcatModel() generateBinaryOpModel() From ba0c012b33b6055e61c8fd6c3de121ab257bcd82 Mon Sep 17 00:00:00 2001 From: Harsh Chauhan Date: Sun, 29 Mar 2026 16:31:47 +0530 Subject: [PATCH 3/5] format: fix clang-format and ruff formatting issues --- tmva/sofie/inc/TMVA/ROperator_Conv.hxx | 12 +++++------- tmva/sofie/test/generateKerasModels.py | 14 +++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/tmva/sofie/inc/TMVA/ROperator_Conv.hxx b/tmva/sofie/inc/TMVA/ROperator_Conv.hxx index e50e50103e6cf..a430231f0d5f4 100644 --- a/tmva/sofie/inc/TMVA/ROperator_Conv.hxx +++ b/tmva/sofie/inc/TMVA/ROperator_Conv.hxx @@ -502,11 +502,10 @@ public: // channels, d, h, w, k_d, k_h, k_w, pad_d, pad_h, pad_w, stride_d, stride_h, stride_w, // dilation_d, dilation_h, dilation_w, // - << fShapeW[1] << "," << iDepth << "," << iHeight << "," << iWidth << "," - << fAttrKernelShape[0] << "," << fAttrKernelShape[1] << "," << fAttrKernelShape[2] << "," - << fAttrPads[0] << "," << fAttrPads[1] << "," << fAttrPads[2] << "," - << fAttrStrides[0] << "," << fAttrStrides[1] << "," << fAttrStrides[2] << "," - << 1 << "," << 1 << "," << 1 << "," + << fShapeW[1] << "," << iDepth << "," << iHeight << "," << iWidth << "," << fAttrKernelShape[0] << "," + << fAttrKernelShape[1] << "," << fAttrKernelShape[2] << "," << fAttrPads[0] << "," << fAttrPads[1] + << "," << fAttrPads[2] << "," << fAttrStrides[0] << "," << fAttrStrides[1] << "," << fAttrStrides[2] + << "," << 1 << "," << 1 << "," << 1 << "," << "tensor_" << fNX << "_xcol);\n\n "; } // BLAS @@ -561,8 +560,7 @@ public: << fShapeW[1] << "," << iDepth << "," << iHeight << "," << iWidth << "," << fAttrKernelShape[0] << "," << fAttrKernelShape[1] << "," << fAttrKernelShape[2] << "," << fAttrPads[0] << "," << fAttrPads[1] << "," << fAttrPads[2] << "," << fAttrStrides[0] << "," << fAttrStrides[1] << "," << fAttrStrides[2] - << "," << 1 << "," << 1 << "," << 1 << ",tensor_" << fNX - << "_xcol);\n\n "; + << "," << 1 << "," << 1 << "," << 1 << ",tensor_" << fNX << "_xcol);\n\n "; } // BLAS diff --git a/tmva/sofie/test/generateKerasModels.py b/tmva/sofie/test/generateKerasModels.py index 0dba786e7b798..a48847baf9600 100644 --- a/tmva/sofie/test/generateKerasModels.py +++ b/tmva/sofie/test/generateKerasModels.py @@ -94,16 +94,16 @@ def generateConv2DModel_SamePadding(): model.save('KerasModelConv2D_Same.keras') def generateConv2DModel_SamePadding_Dilated(): - model=Sequential() - model.add(Conv2D(4, kernel_size=3, activation="relu", input_shape=(8,8,1), padding="same", dilation_rate=2)) + model = Sequential() + model.add(Conv2D(4, kernel_size=3, activation="relu", input_shape=(8, 8, 1), padding="same", dilation_rate=2)) - randomGenerator=np.random.RandomState(0) - x_train=randomGenerator.rand(1,8,8,1) - y_train=randomGenerator.rand(1,8,8,4) + randomGenerator = np.random.RandomState(0) + x_train = randomGenerator.rand(1, 8, 8, 1) + y_train = randomGenerator.rand(1, 8, 8, 4) - model.compile(loss='mean_squared_error', optimizer=SGD(learning_rate=0.01)) + model.compile(loss="mean_squared_error", optimizer=SGD(learning_rate=0.01)) model.fit(x_train, y_train, verbose=0, epochs=10, batch_size=2) - model.save('KerasModelConv2D_SameDilated.keras') + model.save("KerasModelConv2D_SameDilated.keras") def generateReshapeModel(): model = Sequential() From 31adb8d3f7fb6cdc99e0f63278ee2260c207e15f Mon Sep 17 00:00:00 2001 From: Harsh Chauhan Date: Wed, 15 Apr 2026 22:27:41 +0530 Subject: [PATCH 4/5] ruff format fix --- tmva/sofie/test/generateKerasModels.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tmva/sofie/test/generateKerasModels.py b/tmva/sofie/test/generateKerasModels.py index a48847baf9600..5275bec021ec7 100644 --- a/tmva/sofie/test/generateKerasModels.py +++ b/tmva/sofie/test/generateKerasModels.py @@ -105,6 +105,7 @@ def generateConv2DModel_SamePadding_Dilated(): model.fit(x_train, y_train, verbose=0, epochs=10, batch_size=2) model.save("KerasModelConv2D_SameDilated.keras") + def generateReshapeModel(): model = Sequential() model.add(Conv2D(8, kernel_size=3, activation="relu", input_shape=(4,4,1), padding="same")) From 2b8d00619f4ed8ab3bccac15cd65f8e4d09382cd Mon Sep 17 00:00:00 2001 From: Harsh Chauhan Date: Thu, 23 Apr 2026 02:02:21 +0530 Subject: [PATCH 5/5] [tmva][sofie] add dilated Conv2D test to sofie_keras_parser --- .../pythonizations/test/generate_keras_functional.py | 6 ++++++ .../pythonizations/test/generate_keras_sequential.py | 7 +++++++ bindings/pyroot/pythonizations/test/sofie_keras_parser.py | 1 + 3 files changed, 14 insertions(+) diff --git a/bindings/pyroot/pythonizations/test/generate_keras_functional.py b/bindings/pyroot/pythonizations/test/generate_keras_functional.py index 35fd5e6260128..9ac49f9d62f4a 100644 --- a/bindings/pyroot/pythonizations/test/generate_keras_functional.py +++ b/bindings/pyroot/pythonizations/test/generate_keras_functional.py @@ -91,6 +91,12 @@ def train_and_save(model, name): model = models.Model(inp, out) train_and_save(model, "Conv2D_padding_same") + # Conv2D padding_same with dilation_rate=2 + inp = layers.Input(shape=(8, 8, 3)) + out = layers.Conv2D(4, (3, 3), padding='same', dilation_rate=2, data_format='channels_last', activation='relu')(inp) + model = models.Model(inp, out) + train_and_save(model, "Conv2D_padding_same_dilation") + # Conv2D padding_valid inp = layers.Input(shape=(8, 8, 3)) out = layers.Conv2D(4, (3, 3), padding='valid', data_format='channels_last', activation='elu')(inp) diff --git a/bindings/pyroot/pythonizations/test/generate_keras_sequential.py b/bindings/pyroot/pythonizations/test/generate_keras_sequential.py index 47d10968b8ca5..dfc15fa3414f8 100644 --- a/bindings/pyroot/pythonizations/test/generate_keras_sequential.py +++ b/bindings/pyroot/pythonizations/test/generate_keras_sequential.py @@ -80,6 +80,13 @@ def train_and_save(model, name): ]) train_and_save(model, "Conv2D_padding_same") + # Conv2D padding_same with dilation_rate=2 + model = models.Sequential([ + layers.Input(shape=(8, 8, 3)), + layers.Conv2D(4, (3, 3), padding='same', dilation_rate=2, data_format='channels_last', activation='relu') + ]) + train_and_save(model, "Conv2D_padding_same_dilation") + # Conv2D padding_valid model = models.Sequential([ layers.Input(shape=(8, 8, 3)), diff --git a/bindings/pyroot/pythonizations/test/sofie_keras_parser.py b/bindings/pyroot/pythonizations/test/sofie_keras_parser.py index 81fd80606ec13..3a593564d6cb4 100644 --- a/bindings/pyroot/pythonizations/test/sofie_keras_parser.py +++ b/bindings/pyroot/pythonizations/test/sofie_keras_parser.py @@ -22,6 +22,7 @@ def make_testname(test_case: str): "Conv2D_channels_first", "Conv2D_channels_last", "Conv2D_padding_same", + "Conv2D_padding_same_dilation", "Conv2D_padding_valid", "Dense", "ELU",