Skip to content

fix(bit_exact): add produce_kif handler for ZeroPadding1D/2D#1482

Open
LarocheC wants to merge 2 commits into
fastmachinelearning:mainfrom
LarocheC:fix/zeropad-produce-kif
Open

fix(bit_exact): add produce_kif handler for ZeroPadding1D/2D#1482
LarocheC wants to merge 2 commits into
fastmachinelearning:mainfrom
LarocheC:fix/zeropad-produce-kif

Conversation

@LarocheC
Copy link
Copy Markdown

Description

ZeroPadding1D and ZeroPadding2D had no _produce_kif handler in hls4ml/model/optimizer/passes/bit_exact.py, so bit-exact propagation through any model containing them raised NotImplementedError. This came up when bringing a TCM-based model with explicit zero-padding (used as a causal-padding alternative for depthwise dilated Conv1D) through convert_from_keras_model.

Fix: padding inserts exact zeros, which fit any signed fixed-point grid with k=0, i=0, f=0. The output kif is therefore the input kif padded with zeros along the spatial axis/axes (1D pads axis 0, 2D pads axes 0 and 1 — channels_last).

The change is local to bit_exact.py: two new @_produce_kif.register handlers plus the corresponding imports.

Type of change

  • Bug fix (non-breaking change that fixes an issue)

Tests

A new pytest module test/pytest/test_bit_exact_zeropadding.py covers three cases:

  1. test_zeropadding1d_produce_kifZeroPadding1D + Conv1D(valid) converts via Vitis backend and hls_model.predict matches keras.Model.predict to atol=1e-2.
  2. test_zeropadding2d_produce_kif — analogous for ZeroPadding2D + Conv2D(valid).
  3. test_causal_via_zeropadding1d_matches_native_causalZeroPadding1D(pad_left=K-1) + Conv1D(valid) produces exactly the same Keras output as Conv1D(padding='causal') (pure-Keras regression guard).

Without the patch, tests 1 and 2 fail at conversion time with NotImplementedError. With the patch, all three pass.

Test Configuration:

  • Python 3.13, Keras 3.14.1 (torch backend on CPU for local verification; tests use model.predict() and so are backend-agnostic).
  • pytest test/pytest/test_bit_exact_zeropadding.py -v → 3 passed.

Checklist

  • I have read the guidelines for contributing.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have made corresponding changes to the documentation. (No user-facing API change; supported-layer list now covers a path that previously raised.)
  • My changes generate no new warnings.
  • I have installed and run pre-commit on the files I edited or added.
  • I have added tests that prove my fix is effective or that my feature works.

ZeroPadding1D and ZeroPadding2D had no _produce_kif handler, so any
bit-exact propagation through a model containing them raised
NotImplementedError. Padding inserts exact zeros that fit any signed
fixed-point grid, so the output kif equals the input kif padded with
zeros along the spatial axes.

Adds three pytest cases:
  * ZeroPadding1D + Conv1D converts and matches Keras bit-exactly
  * ZeroPadding2D + Conv2D converts and matches Keras bit-exactly
  * ZeroPadding1D(K-1) + Conv1D(valid) equals Conv1D(causal), guarding
    against future drift between the two paths
Copy link
Copy Markdown
Contributor

@calad0i calad0i left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor changes needed, good otherwise.

import hls4ml # noqa: E402


def _convert(model, output_dir):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit_exact flow won't be triggered this way. Either use some hgq layers, or use qkeras and set bit_exact=True to trigger it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rewrote the tests to use HGQ2 QConv layers around the ZeroPadding layer so the bit_exact pass actually fires (verified it now hits the handlers; reverting them makes the test raise NotImplementedError). Done in d686d72.

pad_left = int(layer.attributes['pad_left'])
pad_right = int(layer.attributes['pad_right'])
# channels_last: kif shape is (in_width, n_chan); pad axis 0.
pad_shape = ((pad_left, pad_right),) + ((0, 0),) * (k_in.ndim - 1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe assert for channels_last?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added, matching the existing Conv1D/Conv2D handler.

y_keras = model.predict(xin)
y_hls = hls_model.predict(xin).reshape(y_keras.shape)

np.testing.assert_allclose(y_keras, y_hls, atol=1e-2)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can check for array_equal if bit_exact pass fires, if no super large bitwidth is used.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to assert_array_equal now.

…els_last

Address review feedback:

* The previous tests used plain Keras layers, which never engage the
  precision-propagation (bit_exact) pass, so the new ZeroPadding
  produce_kif handlers were not actually exercised. Rebuild the tests
  with HGQ2 QConv layers around a plain Keras ZeroPadding layer, which
  triggers the bit_exact flow and routes kif through the new handlers.
* Since the flow is now bit-exact, assert exact equality
  (assert_array_equal) instead of an approximate tolerance.
* Assert channels_last in both ZeroPadding handlers, matching the
  existing Conv1D/Conv2D handler.
* Drop the pure-Keras causal-equivalence test; it never exercised
  hls4ml and was out of scope for this fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants