Skip to content
Open
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3d463b3
weights for dense
laurilaatu Jan 26, 2026
d678573
hgq2 homogeneous quant fix
calad0i Jan 27, 2026
77258bc
Merge branch 'hgq2_homo_quant' of github.com:calad0i/hls4ml into onea…
laurilaatu Jan 27, 2026
59bd96f
Changes required for oneAPI MHA
laurilaatu Feb 9, 2026
dbb207b
Original weight implementation
laurilaatu Feb 9, 2026
0c59255
Merge branch 'main' of github.com:fastmachinelearning/hls4ml into one…
laurilaatu Feb 9, 2026
51efff0
Restore oneAPI weight placement
laurilaatu Feb 9, 2026
6067bea
pre-commit
laurilaatu Feb 9, 2026
06fda4e
Merge branch 'main' into oneapi_qmha
laurilaatu Feb 10, 2026
bf38a6b
Merge branch 'main' into oneapi_qmha
laurilaatu Feb 13, 2026
e27fd11
Merge branch 'main' into oneapi_qmha
laurilaatu Feb 16, 2026
9f4a448
Merge branch 'main' into oneapi_qmha
laurilaatu Feb 20, 2026
16ca197
softmax multidim templates
laurilaatu Feb 24, 2026
564b692
Merge branch 'oneapi_qmha' of github.com:laurilaatu/hls4ml into oneap…
laurilaatu Feb 24, 2026
974e75a
pre-commit
laurilaatu Feb 24, 2026
060c398
uncomment
laurilaatu Feb 24, 2026
f78558c
Merge branch 'main' into oneapi_qmha
laurilaatu Feb 25, 2026
772b93a
int_inp_t to config
laurilaatu Feb 25, 2026
d2b8921
Merge branch 'oneapi_qmha' of github.com:laurilaatu/hls4ml into oneap…
laurilaatu Feb 25, 2026
a1ad891
Merge branch 'main' into oneapi_qmha
laurilaatu Feb 26, 2026
d65544d
Merge branch 'main' into oneapi_qmha
laurilaatu Mar 16, 2026
2d6a5cc
Merge branch 'main' into oneapi_qmha
laurilaatu Mar 30, 2026
c3a4584
softmax fixed
bugracyln Apr 13, 2026
9b1cf17
Merge branch 'main' into oneapi_qmha
laurilaatu Apr 13, 2026
31b7ad6
table generation cleanup
bugracyln Apr 14, 2026
70b19d1
Merge pull request #4 from bugracyln/smax_fix
laurilaatu Apr 15, 2026
091cd8e
fix overflow for legacy softmax, start looking at others
jmitrevs May 20, 2026
6c9f9a7
fix stable and streaming legacy softmax
jmitrevs May 20, 2026
7a004b2
minor softmax latency fixes
jmitrevs May 20, 2026
9d588f2
Merge branch 'main' into sofmax_fix
jmitrevs Jun 8, 2026
d5e3493
fix copilot review issues (other than adding a test)
jmitrevs Jun 8, 2026
a56a8d6
add test for softmax auto inferrence
jmitrevs Jun 9, 2026
29bdbb3
Merge branch 'main' into oneapi_qmha
laurilaatu Jun 9, 2026
cab4cbc
Fix formatting of inp_norm_t name string
laurilaatu Jun 10, 2026
42ece34
pre-commit for core templates
laurilaatu Jun 10, 2026
7e2798a
pre-commit all
laurilaatu Jun 10, 2026
bd4778e
softmax update
bugracyln Jun 25, 2026
3946858
minor syntax fix
bugracyln Jun 25, 2026
be76917
Merge branch 'oneapi_qmha' into softmax_updated
bugracyln Jun 25, 2026
189f64a
Merge branch 'main' into softmax_updated
jmitrevs Jun 25, 2026
e0aba71
default case handling improvement
bugracyln Jun 28, 2026
584c4f7
minor improvements to default rollback and added comments
bugracyln Jun 28, 2026
711083a
Merge branch 'main' into softmax_updated
jmitrevs Jun 30, 2026
6309fdb
saving before merge
bugracyln Jul 2, 2026
b91a330
Merge branch 'pr-1476' into smax_updates_merged
bugracyln Jul 2, 2026
f990dbd
multidim softmax and other fixes
bugracyln Jul 2, 2026
5bee49a
quick fix to table include logic
bugracyln Jul 2, 2026
134787b
formatting change
bugracyln Jul 2, 2026
a90b79a
suggested fixes and commented sections removed
bugracyln Jul 2, 2026
07ad4a4
formatting quickfix
bugracyln Jul 3, 2026
aa5af6b
minor fix to writer
bugracyln Jul 4, 2026
49d6ccd
Merge branch 'main' into sofmax_fix
jmitrevs Jul 7, 2026
3d7979a
remove quartus reqirement for signed softmax
jmitrevs Jul 7, 2026
aa678e0
add inp_norm_t inferrence
jmitrevs Jul 7, 2026
5dadf1a
fix test_softmax
jmitrevs Jul 8, 2026
d8abde4
pre-commit fix
jmitrevs Jul 8, 2026
55e7454
change randint to rand
jmitrevs Jul 8, 2026
947f195
make sure softmax widths are transfered properly
jmitrevs Jul 9, 2026
10532da
Fix issues with skip, auto
jmitrevs Jul 9, 2026
232b2a3
Merge branch 'main' into sofmax_fix
jmitrevs Jul 9, 2026
84e0710
Merge remote-tracking branch 'jmitrevs/sofmax_fix' into softmax_updated
bugracyln Jul 11, 2026
01d70a4
saving before testing
bugracyln Jul 11, 2026
8d8bc8a
table accuracy improvement and bugfixes
bugracyln Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions hls4ml/backends/fpga/fpga_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,22 +129,33 @@ def __init__(self, name):
ConfigurableAttribute('skip', value_type=bool, default=False, description=descriptions.softmax_skip),
TypeAttribute(
'exp_table',
default=FixedPrecisionType(18, 8, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT),
default=FixedPrecisionType(
18, 8, signed=False, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT
),
description=descriptions.table_type,
),
TypeAttribute(
'inv_table',
default=FixedPrecisionType(18, 8, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT),
default=FixedPrecisionType(
18, 8, signed=False, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT
),
description=descriptions.table_type,
),
TypeAttribute(
'inv_inp',
default=FixedPrecisionType(18, 8, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT),
default=FixedPrecisionType(
18, 8, signed=False, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT
),
description='What the accumulated value is cast to before accessing the inversion table (only in stable)',
),
TypeAttribute(
'accum',
default=FixedPrecisionType(18, 8, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT),
'inp_norm',
default=FixedPrecisionType(
18, 8, signed=False, rounding_mode=RoundingMode.RND, saturation_mode=SaturationMode.SAT
),
description='The internal width used for the exp table lookup (only in stable)',
),
TypeAttribute('accum', description=descriptions.accum_type),
]
self.attribute_map[Softmax] = softmax_attrs

Expand Down
14 changes: 5 additions & 9 deletions hls4ml/backends/oneapi/oneapi_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
Embedding,
Layer,
SimpleRNN,
Softmax,
)
from hls4ml.model.optimizer import get_backend_passes, layer_optimizer
from hls4ml.model.types import FixedPrecisionType, IntegerPrecisionType, NamedType
Expand Down Expand Up @@ -210,7 +209,11 @@ def build(self, model, build_type='fpga_emu', run=False):
try:
subprocess.run('which icpx', shell=True, cwd=builddir, check=True)
except subprocess.CalledProcessError:
raise RuntimeError('Could not find icpx. Please configure oneAPI appropriately')
print('icpx not found. Trying ahls')
try:
subprocess.run('which ahls', shell=True, cwd=builddir, check=True)
except subprocess.CalledProcessError:
raise RuntimeError('Could not find icpx or ahls. Please configure oneAPI appropriately')
subprocess.run('cmake ..', shell=True, cwd=builddir, check=True)
subprocess.run(f'make {build_type}', shell=True, cwd=builddir, check=True)

Expand Down Expand Up @@ -257,13 +260,6 @@ def init_activation(self, layer):
if layer.get_attr('recurrent_activation') == 'tanh':
layer.set_attr('recurrent_activation', 'dense_tanh')

@layer_optimizer(Softmax)
def init_softmax(self, layer):
if layer.model.config.get_config_value('IOType') == 'io_parallel':
assert len(layer.get_input_variable().shape) == 1, (
'Softmax with io_parallel strategy cannot be used on multidimensional tensors.'
)

@layer_optimizer(Embedding)
Comment thread
calad0i marked this conversation as resolved.
def init_embed(self, layer):
if layer.attributes['n_in'] is None:
Expand Down
62 changes: 59 additions & 3 deletions hls4ml/backends/oneapi/passes/core_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,42 @@ def format(self, node):

softmax_config_template = """struct {type}_config{index} : nnet::activ_config {{
static constexpr unsigned n_in = {n_in};

// For multi-dim softmax
static const unsigned n_slice = {n_slice};
static const unsigned n_outer = {n_outer};
static const unsigned n_inner = {n_inner};

// For legacy softmax
typedef {table_t.name} table_t;
static constexpr unsigned table_size = {table_size};

static constexpr unsigned exp_table_size = {exp_table_size};
static constexpr unsigned inv_table_size = {inv_table_size};
static constexpr unsigned io_type = nnet::{iotype};
static constexpr unsigned reuse_factor = {reuse};

static constexpr nnet::softmax_implementation implementation = nnet::softmax_implementation::{implementation};
typedef {smax_accum_t} accum_t;
typedef {exp_table_t.name} exp_table_t;
typedef {inv_table_t.name} inv_table_t;
typedef {inv_table_t.name} inv_table_t;"""

softmax_config_table_template = """

using {exp_table_name}_arr_t = nnet::array<exp_table_t, exp_table_size>;
using {inv_table_name}_arr_t = nnet::array<inv_table_t, inv_table_size>;
static constexpr const {exp_table_name}_arr_t exp_table = {exp_table_name};
static constexpr const {inv_table_name}_arr_t invert_table = {inv_table_name};
Comment thread
jmitrevs marked this conversation as resolved.
}};\n"""

softmax_config_table_template_stable = """
typedef {inv_inp_t.name} inv_inp_t;
typedef {inp_norm_t.name} inp_norm_t;

using {exp_table_name}_arr_t = nnet::array<exp_table_t, exp_table_size>;
using {inv_table_name}_arr_t = nnet::array<inv_table_t, inv_table_size>;
static constexpr const {exp_table_name}_arr_t exp_table = {exp_table_name};
static constexpr const {inv_table_name}_arr_t invert_table = {inv_table_name};
}};\n"""

activ_function_template = 'nnet::{activation}<{input_t}, {output_t}, {config}>({input}, {output});'
Expand All @@ -219,7 +249,32 @@ def __init__(self):

def format(self, node):
params = self._default_config_params(node)
params['type'] = node.get_attr('activation')
params['type'] = node.get_attr('activation').lower()

if (params['type'] == 'softmax') or (params['type'] == 'softmax_multidim'):
# If no table size is specified, assume default size of 1024
params.setdefault('exp_table_size', params['table_size'])
params.setdefault('inv_table_size', params['table_size'])

# This is for non-quantised layers where table size is not a layer attribute
if node.get_attr('exp_table_size', -1) == -1:
node.set_attr('exp_table_size', params['exp_table_size'])

if node.get_attr('inv_table_size', -1) == -1:
node.set_attr('inv_table_size', params['inv_table_size'])

params.setdefault('exp_scale', 1.0)
params.setdefault('parallelization_factor', -1)

n_slice = params['n_in'] // params['n_inner'] // params['n_outer']
params['n_slice'] = n_slice

params['exp_table_name'] = node.name + '_exp_table'
params['inv_table_name'] = node.name + '_inv_table'
params['smax_accum_t'] = params['accum_t'].name

if params['implementation'] == 'stable':
self.template = softmax_config_template + softmax_config_table_template_stable

return self.template.format(**params)

Expand Down Expand Up @@ -251,7 +306,7 @@ def format(self, node):
class SoftmaxConfigTemplate(ActivationConfigTemplate):
def __init__(self):
super(ActivationConfigTemplate, self).__init__(Softmax) # Skip ActivationConfigTemplate's __init__
self.template = softmax_config_template
self.template = softmax_config_template + softmax_config_table_template


class ActivationFunctionTemplate(FunctionCallTemplate):
Expand Down Expand Up @@ -304,6 +359,7 @@ def format(self, node):
params = self._default_function_params(node)
params['activation'] = node.get_attr('activation').lower()
params['config'] = f'{node.get_attr("activation")}_config{node.index}'

return self.template.format(**params)


Expand Down
26 changes: 20 additions & 6 deletions hls4ml/converters/keras_v3/hgq2/softmax.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@
from keras import KerasTensor


def fixed_quantizer_to_hls4ml_t(q: 'FixedPointQuantizerBase', take_max=False):
def fixed_quantizer_to_hls4ml_t(q: 'FixedPointQuantizerBase', take_max=False, force_unsigned=False):
from keras import ops

k, i, f = q.kif

if force_unsigned:
k = 0.0
i += 1.0

k = ops.convert_to_numpy(k)
i = ops.convert_to_numpy(i)
f = ops.convert_to_numpy(f)
Expand Down Expand Up @@ -78,10 +83,12 @@ def handle(
exp_oq = layer.exp_table.oq.quantizer
inv_oq = layer.inv_table.oq.quantizer
inv_iq = layer.inv_table.iq.quantizer

assert isinstance(exp_oq, FixedPointQuantizerBase), 'Only fixed-point quantizer is supported for exp_table'
exp_table_t = fixed_quantizer_to_hls4ml_t(exp_oq)
inv_table_t = fixed_quantizer_to_hls4ml_t(inv_oq)
inv_inp_t = fixed_quantizer_to_hls4ml_t(inv_iq)
# Enforce unsigned quantisers on all three types
exp_table_t = fixed_quantizer_to_hls4ml_t(exp_oq, force_unsigned=True)
inv_table_t = fixed_quantizer_to_hls4ml_t(inv_oq, force_unsigned=True)
inv_inp_t = fixed_quantizer_to_hls4ml_t(inv_iq, force_unsigned=True)
exp_scale = layer.input_scaler

inv_table_size = 2**inv_inp_t.width
Expand All @@ -99,13 +106,19 @@ def handle(
else:
raise ValueError(f'Too many inputs for softmax layer {layer.name}: expected 1 or 2, got {len(in_tensors)}')

# For masked implementation assume first input is the tensor we are operating on
activation = 'softmax'
if len(in_tensors[0].shape[1:]) > 1:
if (1 not in in_tensors[0].shape[1:]) or (len(in_tensors[0].shape[1:]) > 2):
activation = 'softmax_multidim'

config = {}
config.update(self.default_config)
config.update(
{
'axis': ax,
'n_in': n_in,
'activation': 'softmax',
'activation': activation,
'n_outer': n_outer,
'n_inner': n_inner,
'implementation': impl,
Expand All @@ -122,7 +135,8 @@ def handle(
)

if layer.stable:
inp_norm_t = fixed_quantizer_to_hls4ml_t(layer.exp_table.iq.quantizer)
# Force unsigned since norm >= 0
inp_norm_t = fixed_quantizer_to_hls4ml_t(layer.exp_table.iq.quantizer, force_unsigned=True)
config['inp_norm_t'] = inp_norm_t

return (config,)
23 changes: 23 additions & 0 deletions hls4ml/model/optimizer/passes/infer_precision.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ def _infer_precision(self, node, types_to_infer):

if node_class in ['PReLU']:
return self._infer_prelu_act_precision(node, types_to_infer)

if node_class in ['Softmax']:
return self._infer_softmax_precision(node, types_to_infer)
# What about quantized activation layer? Setting it to 'auto' manually will break it here. We should prevent
# this in config_from_* functions

Expand Down Expand Up @@ -605,6 +608,26 @@ def _infer_prelu_act_precision(self, node, types_to_infer):

return inferred_types

def _infer_softmax_precision(self, node, types_to_infer):
inferred_types = []

# for softmax, the table parameters have a default setting, so they don't need to be inferred
# here. We never expect them to be of type auto.

# For result, we leave it to be set externally (model default if not set). We expect it to
# likely be the output value, in which case the output format would determine it's precision.
# Therefore, only the accum is configured here

if 'accum_t' in types_to_infer:
exp_w = node.types['exp_table_t'].precision.width
exp_i = node.types['exp_table_t'].precision.integer
exp_s = node.types['exp_table_t'].precision.signed
ceillog = math.ceil(np.log2(node.get_attr('n_in')))
node.types['accum_t'].precision = FixedPrecisionType(exp_w + ceillog, exp_i + ceillog, signed=exp_s)
inferred_types.append('accum_t')

return inferred_types


def _get_precision_from_constant(value: int | float, max_width=8):
"""A utility function to find a fixed type to store the constant
Expand Down
Loading
Loading