Uh oh!
There was an error while loading. Please reload this page.
XLS backend - #1475
Conversation
… pass, merge of dense_relu written as an opt pass
This fixes two test cases in test_softmax.py (one of them still fails due to another error) TODO: check layer.class_name == 'Input' instead of taking layers[0]?
This fixes DSLX compilation error in test_softmax.py
# Conflicts: # docs/requirements.txt # hls4ml/backends/__init__.py # hls4ml/model/graph.py # hls4ml/report/__init__.py # test/pytest/test_activations.py # test/pytest/test_keras_api.py # test/pytest/test_softmax.py
These things were removed in fastmachinelearning#1321
See fastmachinelearning#1443 Setting 'strategy' for Softmax layer did not affect anything, and the code always chose the default implementation=stable. TODO: all backends fail when implementation=latency (low accuracy, probably due to overflow).
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…vado after that. XLS backend should not have anything vendor-specific. It is up to user to choose the tools for the next stages (RTL -> IP conversion). Removed all Vivado references from XLS, leaving only build_prj.tcl (Vivado script) as an example.
…loats use quantizers.get_fixed_quantizer_np()
# Conflicts: # pyproject.toml # test/pytest/ci-template.yml
Used e.g. in test_qkeras.py
TODO: currently it fails because XLS does not support unsigned types.
…om BuildAttr, refactor xls_writer. Custom attributes (input and output variables, weights etc.) are not needed anymore. Now we wrap existing attributes. Some former attributes are now computed on the fly by functions xls_layer_util.py. In DSLX code, now we use existing TensorVariable names when possible (e.g. in function arguments). We also do not specify input types separately - we import them from other layers.
XLS converts unsigned types to signed.
Use stable implementations for softplus and sigmoid, clip exp(x) for softmax_latency. Also use math.logp1(x) and math.expm1(x) when possible to increase accuracy.
- Move XLS weights definition out of XLSWeightVarConverter - Remove xls_ prefix when not needed (i.e. everywhere except for wrapper classes) - class XLSDefinitionBase
Uh oh!
There was an error while loading. Please reload this page.
| def xls_func_name(node: Layer) -> XLSQualifiedName: | ||
| match node.class_name: |
There was a problem hiding this comment.
Could we do something like f'{node.class_name}{node.index}' to save the whole case-match?
There was a problem hiding this comment.
No, here we need qualified names for library functions, e.g. activations::softmax_stable or pooling::global_pooling_2d.
We could put all layer functions into one giant DSLX module (note that DSLX does not allow re-exports), but that's ugly. Also simply returning node.class_name and removing ValueError would lead to more obscure DSLX errors if layer is not supported.
| match class_name: | ||
| case 'BatchNormalization': | ||
| # NB: we need flattening because sometimes the weights can be e.g. | ||
| # (1,1,1,n_filt) instead of (n_filt,) | ||
| # We'll throw an error if there are several dimensions larger than 1. | ||
| data = data.flatten() | ||
| n_filt = node.get_attr('n_filt') | ||
| if n_filt == -1: | ||
| n_filt = input_var.shape[-1] | ||
| expected_shape = (n_filt,) | ||
| case 'Conv1D': | ||
| expected_shape = tuple(node.get_attr(x) for x in ['filt_width', 'n_chan', 'n_filt']) | ||
| case 'DepthwiseConv1D': | ||
| expected_shape = tuple(node.get_attr(x) for x in ['filt_width', 'n_chan', 'depth_multiplier']) | ||
| case 'Conv2D': | ||
| expected_shape = tuple(node.get_attr(x) for x in ['filt_height', 'filt_width', 'n_chan', 'n_filt']) | ||
| case 'DepthwiseConv2D': | ||
| expected_shape = tuple( | ||
| node.get_attr(x) for x in ['filt_height', 'filt_width', 'n_chan', 'depth_multiplier'] | ||
| ) | ||
| case 'Dense': | ||
| # Transpose the weights so that we can call dot_prod(x, w[i]) in nnet_utils/dense.x | ||
| data = data.T | ||
| expected_shape = (output_var.shape[0], input_var.shape[0]) | ||
| case 'PReLU': | ||
| expected_shape = (input_var.shape[0],) | ||
| case _: | ||
| raise ValueError(f'Unsupported weights for layer {node.class_name}') | ||
| if expected_shape is not None: | ||
| assert shape_tuple(data.shape) == expected_shape, ( | ||
| f'Weights shape mismatch: expected {expected_shape}, got {data.shape}' | ||
| ) | ||
There was a problem hiding this comment.
Asserting in variable definition is probably a bit misplaced, and we likely don't really need to assert these outside the corresponding layer specific handlers. Maybe remove?
There was a problem hiding this comment.
Ideally yes, everything should be handled earlier. But if something goes wrong either with XLS or with layer handling, as it does in PyTorch Linear layer case:
hls4ml/test/pytest/test_pytorch_api.py
Lines 659 to 662 in 9fda567
then assertion error
AssertionError: Weights shape mismatch: expected (1, 5, 3), got (1, 3, 5)
is more readable than XLS error
layer_6_linear.x:50:5-50:27 TypeInferenceError: type mismatch: FixedPoint<16, -10>[5] vs. FixedPoint<16, -10>[LAYER6_OUT_DIM_1]. The body of function
transform_2ddoes not actually return the function's declared return type, which isFixedPoint<16, -10>[LAYER6_OUT_DIM_1][LAYER6_OUT_DIM_0]
| self._top_function_lib = ctypes.cdll.LoadLibrary(lib_name) | ||
| def _get_top_function(self, x): | ||
| def _get_top_function(self, x, *args, **kwargs): |
There was a problem hiding this comment.
If warp predict() directly, this part could be removed.
There was a problem hiding this comment.
The problem is that _get_top_function is called inside _predict.
Lines 881 to 882 in 2ec9ef3
I see two options here:
- Keep
get_top_functionhook. - Remove
get_top_functionhook, liftself._get_top_function(x)call from_predict()topredict(). Then everypredict()implementation would need to make an extraself._get_top_function(x)call:
defpredict(self, x)
# <...>top_function, ctype=self._get_top_function(x)
returnself._predict(x, top_function, ctype)Option 1 seems lees intrusive to me, although it adds three extra lines of code.
ctype is not used in XLS, but it is required by ModelGraph._predict() API
# Conflicts: # test/pytest/test_softmax.py
vasdommes
commented
Aug 12, 2026
This became visible because of my fixes for #1443 (enable P.S. Other CI failures seem to be due to timeout (probably unrelated to XLS, same tests work on my machine). |
Description
This PR adds XLS backend. It is based on PR #1343, with most of the code rewritten and new features added.
Google XLS is an open-source (Apache 2) High Level Syntesis toolchain that produces an RTL (Verilog or SystemVerilog) design from a high-level description (DSLX or C++).
Adding XLS as a new hls4ml backend allows to generate RTL without vendor-specific dependencies and benefit from the developments that XLS brings to HLS field.
XLS workflow
XLS backend performs the following transformations:
write(): hls4ml representation -> DSLX projectcompile(): DSLX -> XLS IR -> Optimized XLS IRbuild(): Optimized XLS IR -> (System)Verilog-> IP(UPD: Vivado call removed, now we produce only Verilog.)DSLX -> IR -> (System)Verilog conversion is done by XLS.
IP can generated from Verilog file manually e.g. via Vivado or another vendor.
XLS features
XLS backend supports the following layers:
Input,ApplyAlpha,BatchNormalization,Dense,Conv1D,DepthwiseConv1D,Conv2D,DepthwiseConv2D,Pooling1D,Pooling2D,GlobalPooling1D,GlobalPooling2D,Merge,Concatenate,Dot,Activation,HardActivation,ParametrizedActivation,PReLU,Reshape,Softmax,Transpose,TernaryTanh.You can override default codegen options as follows:
DSLX standard library has only signed FixedPoint type (similar to
ap_fixed). Thus, unsigned types are not supported.Currently, XLS backend implements only
IOType: io_parallel.Strategyis ignored.All operations are fully unrolled.
io_streamcould be implemented via DSLX procs. @calad0i and I are going to work on that after finishing this PR.Other changes
I made some minor changes in non-XLS code:
test_softmax.pydoes not testargmaxandlatencyimplementations;latencyfails #1443, since it was needed to test all softmax implementations in XLS.ModelGraphto call custombackend.get_top_function(). This is needed for XLS because it uses optimized XLS IR file instead of.solibrary generated by other backends.docs/ir/attributes.rst. Aside from adding XLS, this commit some other missing layers and attributes.Dependencies
XLS backend uses xls-python to access XLS API. It is enabled by dependency group
xls:xls-pythoncomes with batteries (libxls.soand DSLX standard library) included, no separate XLS installation is required.The code has been tested for the version
xls-python=0.1.9875.Known issues
XLS doesn't work with
Denselayer imported from PyTorchLinearlayer because of shape mismatch: PyTorch storesLinearweights as(out_features, in_features), while hls4mlDenselayers use the Keras-style layout(in_features, out_features).Repro: add XLS backend to test_pytorch_api.py/test_squeeze and run the test.
Note that the weights in this test are constant, and other backends flatten them without checking shape.
So, it is unclear whether they handle this situation correctly or not.
Type of change
Tests
XLS has been added to the following tests:
test_activations.py,test_auto_precision.py,test_binary_cnn.py,test_causalpadding.py,test_depthconv1d.py,test_depthconv2d.py,test_keras_api.py,test_keras_v3_api.py,test_merge.py,test_multi_dense.py,test_pointwiseconv.py,test_pooling.py,test_pytorch_api.py,test_reshape.py,test_sepconv1d.py,test_sepconv2d.py,test_softmax.py.Test Configuration
Add
xlsdependency, e.g.and run tests, e.g.:
Notes on performance
Some test cases are very slow for XLS (e.g. ~30 minutes vs ~10 seconds on other backends).
This happens because XLS generates (in
model.compile()) and uses (inmodel.predict()) an optimized XLS IR code, where all loops are fully unrolled. The resulting file can be huge and thus slow for the likes ofConv2D.During development, I made test faster by reducing dimensions in some tests.
For example, in test_keras_api.py/test_conv2d I replaced
with
I haven't pushed such changes, but that could be one of the ways of speeding things up.UPD: I reduced XLS dimensions in many tests, see 8578a62 and 75a04fb.
Checklist
pre-commiton the files I edited or added.