Skip to content

Commit b063de6

Browse files
authored
Merge branch 'main' into initialRecurr
2 parents 5c07445 + 4d23e9f commit b063de6

File tree

6 files changed

+86
-86
lines changed

6 files changed

+86
-86
lines changed

hls4ml/backends/fpga/fpga_backend.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,33 @@ def generate_conv2d_line_buffer_fn(
913913

914914
return generated_code
915915

916+
@staticmethod
917+
def permute_config_gen(name: str, shape: tuple[int, ...], perm: tuple[int, ...]):
918+
"""
919+
Generate new shape and perm_strides for a permute operation. Operates by mapping the output index
920+
to input input index by:
921+
- unravel the output index
922+
- map each dimension to the corresponding stride in the input tensor, sum
923+
The operation can be expressed as:
924+
925+
new_shape = tuple(shape[i] for i in perm)
926+
strides = np.cumprod((shapes[1:] + (1,))[::-1])[::-1]
927+
perm_strides = [strides[i] for i in perm]
928+
out[index] = inp[np.dot(np.unravel_index(index, new_shape), perm_strides)]
929+
930+
Args:
931+
name (str): The name of the configuration.
932+
shape (tuple[int, ...]): The shape of the input tensor.
933+
perm (tuple[int, ...]): The permutation of the dimensions.
934+
935+
Returns:
936+
(new_shape, perm_strides) (tuple, tuple): the output shape and permutation strides.
937+
"""
938+
new_shape = tuple(shape[i] for i in perm)
939+
strides = np.cumprod((shape[1:] + (1,))[::-1])[::-1]
940+
perm_strides = tuple(int(strides[i]) for i in perm)
941+
return (new_shape, perm_strides)
942+
916943
@model_optimizer()
917944
def write_hls(self, model):
918945
self.writer.write_hls(model)

hls4ml/backends/oneapi/passes/reshaping_templates.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,17 @@ def format(self, node):
161161

162162
# Transpose templates
163163

164-
transpose_config_template = """struct config{index} : nnet::transpose_config {{
165-
static const unsigned depth = {depth};
166-
static const unsigned height = {height};
167-
static const unsigned width = {width};
168-
static constexpr unsigned perm[3] = {{{perm_str}}};
164+
transpose_config_template = """struct {config_name} : nnet::transpose_config {{
165+
static constexpr unsigned dims = {dims};
166+
static constexpr unsigned N = {N};
167+
static constexpr std::array<unsigned, dims> from_shape = {{{from_shape}}};
168+
static constexpr std::array<unsigned, dims> to_shape = {{{to_shape}}};
169+
static constexpr std::array<unsigned, dims> perm = {{{perm}}};
170+
static constexpr std::array<unsigned, dims> perm_strides = {{{perm_strides}}};
169171
}};\n"""
170172

171-
transpose_function_template = 'nnet::transpose_{dim}<{input_t}, {output_t}, {config}>({input}, {output});'
172-
transpose_task_sequence_template = (
173-
'task_sequence<nnet::transpose_{dim}_stream<{input_pipe}, {output_pipe}, {config}>> {name};'
174-
)
173+
transpose_function_template = 'nnet::transpose<{input_t}, {output_t}, {config}>({input}, {output});'
174+
transpose_task_sequence_template = 'task_sequence<nnet::transpose_stream<{input_pipe}, {output_pipe}, {config}>> {name};'
175175
transpose_include_list = ['nnet_utils/nnet_transpose.h', 'nnet_utils/nnet_transpose_stream.h']
176176

177177

@@ -181,9 +181,20 @@ def __init__(self):
181181
self.template = transpose_config_template
182182

183183
def format(self, node):
184-
params = self._default_config_params(node)
185-
186-
return self.template.format(**params)
184+
shape = tuple(node.get_input_variable().shape)
185+
perm = tuple(node.get_attr('perm'))
186+
name = f'config{node.index}'
187+
188+
new_shape, perm_strides = node.model.config.backend.permute_config_gen(name, shape, perm)
189+
return transpose_config_template.format(
190+
dims=len(shape),
191+
N=np.prod(shape),
192+
from_shape=', '.join(str(x) for x in shape),
193+
perm=', '.join(str(x) for x in perm),
194+
perm_strides=', '.join(str(x) for x in perm_strides),
195+
to_shape=', '.join(str(x) for x in new_shape),
196+
config_name=name,
197+
)
187198

188199

189200
class TransposeFunctionTemplate(FunctionCallTemplate):

hls4ml/backends/vivado/passes/reshaping_templates.py

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from math import prod
2-
31
import numpy as np
42

53
from hls4ml.backends.template import FunctionCallTemplate, LayerConfigTemplate
@@ -127,40 +125,6 @@ def format(self, node):
127125
transpose_function_template = 'nnet::transpose<{input_t}, {output_t}, {config_name}>({input}, {output});'
128126

129127

130-
def permute_config_gen(name: str, shape: tuple[int, ...], perm: tuple[int, ...]):
131-
"""
132-
Generate a configuration string for a permute operation. Operates by mapping the output index to input input index by:
133-
- unravel the output index
134-
- map each dimension to the corresponding stride in the input tensor, sum
135-
The operation can be expressed as:
136-
137-
new_shape = tuple(shape[i] for i in perm)
138-
strides = np.cumprod((shapes[1:] + (1,))[::-1])[::-1]
139-
perm_strides = [strides[i] for i in perm]
140-
out[index] = inp[np.dot(np.unravel_index(index, new_shape), perm_strides)]
141-
142-
Args:
143-
name (str): The name of the configuration.
144-
shape (tuple[int, ...]): The shape of the input tensor.
145-
perm (tuple[int, ...]): The permutation of the dimensions.
146-
147-
Returns:
148-
str: The formatted configuration string for the permute operation.
149-
"""
150-
new_shape = tuple(shape[i] for i in perm)
151-
strides = np.cumprod((shape[1:] + (1,))[::-1])[::-1]
152-
perm_strides = tuple(int(strides[i]) for i in perm)
153-
return transpose_config_template.format(
154-
dims=len(shape),
155-
N=prod(shape),
156-
from_shape=', '.join(str(x) for x in shape),
157-
perm=', '.join(str(x) for x in perm),
158-
perm_strides=', '.join(str(x) for x in perm_strides),
159-
to_shape=', '.join(str(x) for x in new_shape),
160-
config_name=name,
161-
)
162-
163-
164128
class TransposeConfigTemplate(LayerConfigTemplate):
165129
def __init__(self):
166130
super().__init__(Transpose)
@@ -170,7 +134,16 @@ def format(self, node):
170134
shape = tuple(node.get_input_variable().shape)
171135
perm = tuple(node.get_attr('perm'))
172136
name = f'config{node.index}'
173-
return permute_config_gen(name, shape, perm)
137+
new_shape, perm_strides = node.model.config.backend.permute_config_gen(name, shape, perm)
138+
return transpose_config_template.format(
139+
dims=len(shape),
140+
N=np.prod(shape),
141+
from_shape=', '.join(str(x) for x in shape),
142+
perm=', '.join(str(x) for x in perm),
143+
perm_strides=', '.join(str(x) for x in perm_strides),
144+
to_shape=', '.join(str(x) for x in new_shape),
145+
config_name=name,
146+
)
174147

175148

176149
class TransposeFunctionTemplate(FunctionCallTemplate):

hls4ml/templates/oneapi/firmware/nnet_utils/nnet_transpose.h

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,31 @@
44
namespace nnet {
55

66
struct transpose_config {
7-
static const unsigned height = 10;
8-
static const unsigned width = 10;
9-
static const unsigned depth = 10;
10-
static constexpr unsigned perm[3] = {2, 0, 1};
7+
static constexpr unsigned dims = 0;
8+
static constexpr unsigned N = 0;
9+
10+
// Inherited struct should define these
11+
// static constexpr std::array<unsigned, dims> from_shape;
12+
// static constexpr std::array<unsigned, dims> to_shape;
13+
// static constexpr std::array<unsigned, dims> perm;
14+
// static constexpr std::array<unsigned, dims> perm_strides;
1115
};
1216

13-
template <class data_T, class res_T, typename CONFIG_T> void transpose_2d(const data_T &data, res_T &res) {
14-
for (int i = 0; i < CONFIG_T::height; i++) {
15-
#pragma unroll
16-
for (int j = 0; j < CONFIG_T::width; j++) {
17-
res[j * CONFIG_T::height + i] = static_cast<typename res_T::value_type>(data[i * CONFIG_T::width + j]);
18-
}
17+
template <typename CONFIG_T> unsigned transfer_idx(int index) {
18+
// Given output idx in c-order flat array, return input idx
19+
int idx = 0;
20+
for (int i = CONFIG_T::dims - 1; i >= 0; i--) {
21+
idx += (index % CONFIG_T::to_shape[i]) * CONFIG_T::perm_strides[i];
22+
index /= CONFIG_T::to_shape[i];
1923
}
24+
return idx;
2025
}
2126

22-
template <class data_T, class res_T, typename CONFIG_T> void transpose_3d(const data_T &data, res_T &res) {
23-
static constexpr unsigned dim_data[3] = {CONFIG_T::depth, CONFIG_T::height, CONFIG_T::width};
24-
static constexpr unsigned dim_res[3] = {dim_data[CONFIG_T::perm[0]], dim_data[CONFIG_T::perm[1]],
25-
dim_data[CONFIG_T::perm[2]]};
26-
27-
int index_data[3] = {0}, index_res[3] = {0};
28-
29-
for (index_data[0] = 0; index_data[0] < dim_data[0]; index_data[0]++) {
30-
#pragma unroll
31-
for (index_data[1] = 0; index_data[1] < dim_data[1]; index_data[1]++) {
32-
#pragma unroll
33-
for (index_data[2] = 0; index_data[2] < dim_data[2]; index_data[2]++) {
34-
index_res[0] = index_data[CONFIG_T::perm[0]];
35-
index_res[1] = index_data[CONFIG_T::perm[1]];
36-
index_res[2] = index_data[CONFIG_T::perm[2]];
37-
38-
res[index_res[0] * dim_res[1] * dim_res[2] + index_res[1] * dim_res[2] + index_res[2]] =
39-
static_cast<typename res_T::value_type>(
40-
data[index_data[0] * dim_data[1] * dim_data[2] + index_data[1] * dim_data[2] + index_data[2]]);
41-
}
42-
}
27+
template <class data_T, class res_T, typename CONFIG_T> void transpose(const data_T &data, res_T &res) {
28+
#pragma unroll
29+
for (int i = 0; i < CONFIG_T::N; i++) {
30+
int idx = transfer_idx<CONFIG_T>(i);
31+
res[i] = data[idx];
4332
}
4433
}
4534

hls4ml/templates/oneapi/firmware/nnet_utils/nnet_transpose_stream.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33

44
namespace nnet {
55

6-
template <class data_pipe, class res_pipe, typename CONFIG_T> void transpose_2d_stream() {
6+
template <class data_pipe, class res_pipe, typename CONFIG_T> void transpose_stream() {
77

88
using data_T = typename ExtractPipeType<data_pipe>::value_type;
99
using res_T = typename ExtractPipeType<res_pipe>::value_type;
1010

1111
constexpr auto data_size = std::tuple_size<typename ExtractPipeType<data_pipe>::value_type>{};
1212
constexpr auto res_size = std::tuple_size<typename ExtractPipeType<res_pipe>::value_type>{};
1313

14-
[[intel::fpga_register]] typename data_T::value_type data_array[CONFIG_T::height * CONFIG_T::width];
14+
[[intel::fpga_register]] typename data_T::value_type data_array[CONFIG_T::N];
1515

16-
for (int i = 0; i < CONFIG_T::height * CONFIG_T::width / data_size; i++) {
16+
for (int i = 0; i < CONFIG_T::N / data_size; i++) {
1717
[[intel::fpga_register]] data_T in_data = data_pipe::read();
1818

1919
#pragma unroll
@@ -22,12 +22,12 @@ template <class data_pipe, class res_pipe, typename CONFIG_T> void transpose_2d_
2222
}
2323
}
2424

25-
for (int i = 0; i < CONFIG_T::height * CONFIG_T::width / res_size; i++) {
25+
for (int i = 0; i < CONFIG_T::N / res_size; i++) {
2626
[[intel::fpga_register]] res_T out_data;
2727

2828
#pragma unroll
2929
for (int j = 0; j < res_size; j++) {
30-
out_data[j] = typename res_T::value_type(data_array[j * data_size + i]);
30+
out_data[j] = typename res_T::value_type(data_array[transfer_idx<CONFIG_T>(i * res_size + j)]);
3131
}
3232

3333
res_pipe::write(out_data);

test/pytest/test_transpose_concat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def data_highdim():
7272

7373

7474
@pytest.mark.parametrize('io_type', ['io_stream', 'io_parallel'])
75-
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis'])
75+
@pytest.mark.parametrize('backend', ['Vivado', 'Vitis', 'oneAPI'])
7676
def test_highdim_permute(data_highdim, keras_model_highdim, io_type, backend):
7777
X = data_highdim
7878
model = keras_model_highdim

0 commit comments

Comments
 (0)