Skip to content

Commit 63acf34

Browse files
Bug Fix for Operand Shape Mismatch in BatchNorm Fusion (PyTorch) (#1045)
* fixed operand dimension mismatch error in bn_fuse.py * moved test file to test/ * added channels_last_conversion to config * updating PR to match contribution guidelines. pre-commit has been run and test case has been moved from standalone file to existing pytests * fix shape of bias tensor in pytorch if zero, add additional batchnorm tests * consistent uses of ' * reverting changes in test_batchnorm_pytorch.py from merge * reverting changes in test_batchnorm_pytorch.py from merge --------- Co-authored-by: Jan-Frederik Schulte <jschulte@cern.ch>
1 parent 7f28502 commit 63acf34

File tree

2 files changed

+96
-2
lines changed

2 files changed

+96
-2
lines changed

hls4ml/model/layers.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,13 @@ def add_bias(self, quantizer=None):
258258
precision = None
259259
type_name = None
260260
if data is None:
261-
data = np.zeros(self.get_output_variable().shape[-1])
261+
if 'data_format' in self.attributes:
262+
if self.attributes['data_format'] == 'channels_first':
263+
data = np.zeros(self.get_output_variable().shape[0])
264+
elif self.attributes['data_format'] == 'channels_last':
265+
data = np.zeros(self.get_output_variable().shape[-1])
266+
else:
267+
data = np.zeros(self.get_output_variable().shape[-1])
262268
precision = IntegerPrecisionType(width=1, signed=False)
263269
type_name = 'bias{index}_t'
264270
quantizer = None # Don't quantize non-existant bias

test/pytest/test_batchnorm_pytorch.py

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,22 @@
1313
atol = 5e-3
1414

1515

16-
@pytest.fixture(scope='module')
16+
@pytest.fixture
1717
def data():
1818
np.random.seed(0)
1919
X = np.random.rand(100, in_shape)
2020
return X
2121

2222

23+
@pytest.fixture(scope='module')
24+
def fusion_data():
25+
n_batch = 2
26+
n_in = 2
27+
size_in_height = 32
28+
X = np.random.rand(n_batch, n_in, size_in_height)
29+
return X
30+
31+
2332
@pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream'])
2433
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult'])
2534
def test_batchnorm(data, backend, io_type):
@@ -41,3 +50,82 @@ def test_batchnorm(data, backend, io_type):
4150
pytorch_prediction = model(torch.Tensor(data)).detach().numpy()
4251
hls_prediction = hls_model.predict(data)
4352
np.testing.assert_allclose(pytorch_prediction, hls_prediction, rtol=0, atol=atol, verbose=True)
53+
54+
55+
atol = 5e-2
56+
57+
58+
class BatchNorm_w_Fusion(nn.Module):
59+
def __init__(self, filters, momentum):
60+
super().__init__()
61+
self.conv1 = nn.Conv1d(
62+
int(filters),
63+
filters,
64+
kernel_size=3,
65+
stride=1,
66+
padding=1,
67+
bias=False,
68+
)
69+
self.bn1 = nn.BatchNorm1d(filters)
70+
self.relu1 = nn.ReLU()
71+
72+
def forward(self, x):
73+
x = self.conv1(x)
74+
x = self.bn1(x)
75+
x = self.relu1(x)
76+
return x
77+
78+
79+
@pytest.mark.parametrize('io_type', ['io_parallel', 'io_stream'])
80+
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'Quartus', 'Catapult'])
81+
def test_batchnorm_fusion(fusion_data, backend, io_type):
82+
n_in = 2
83+
momentum = 0.99
84+
size_in_height = 32
85+
filters = n_in
86+
87+
# see above for model definition
88+
model = BatchNorm_w_Fusion(filters, momentum)
89+
# Important to set model to eval to fix batchnorm behavior
90+
model.eval()
91+
# generating config
92+
pytorch_prediction = model(torch.Tensor(fusion_data)).detach().numpy()
93+
94+
# We do not have an implementation of a transpose for io_stream, need to transpose inputs and outputs outside of hls4ml
95+
if io_type == 'io_stream':
96+
fusion_data = np.ascontiguousarray(fusion_data.transpose(0, 2, 1))
97+
config = hls4ml.utils.config_from_pytorch_model(model, channels_last_conversion='internal', transpose_outputs=False)
98+
else:
99+
config = hls4ml.utils.config_from_pytorch_model(model, channels_last_conversion='full', transpose_outputs=True)
100+
101+
config['Model']['Strategy'] = 'Resource'
102+
103+
default_precision = 'ac_fixed<32, 1, true>' if backend == 'Quartus' else 'ac_fixed<32, 1>'
104+
105+
config['Model']['Precision'] = default_precision
106+
107+
# conversion
108+
output_dir = str(test_root_path / f'hls4mlprj_block_{backend}_{io_type}')
109+
hls_model = hls4ml.converters.convert_from_pytorch_model(
110+
model,
111+
(None, n_in, size_in_height),
112+
hls_config=config,
113+
output_dir=output_dir,
114+
backend=backend,
115+
io_type=io_type,
116+
)
117+
118+
# compiling model
119+
hls_model.compile()
120+
121+
if io_type == 'io_stream':
122+
hls_prediction = np.transpose(
123+
np.reshape(
124+
hls_model.predict(fusion_data),
125+
(pytorch_prediction.shape[0], pytorch_prediction.shape[2], pytorch_prediction.shape[1]),
126+
),
127+
(0, 2, 1),
128+
)
129+
else:
130+
hls_prediction = np.reshape(hls_model.predict(fusion_data), pytorch_prediction.shape)
131+
np.testing.assert_allclose(pytorch_prediction, hls_prediction, rtol=0, atol=atol, verbose=True)

0 commit comments

Comments
 (0)