Skip to content

[Relax][Frontend][TFLite] Support dynamic DYNAMIC_UPDATE_SLICE starts#19881

Open
Aharrypotter wants to merge 2 commits into
apache:mainfrom
Aharrypotter:tflite-dus-dynamic-start-19412
Open

[Relax][Frontend][TFLite] Support dynamic DYNAMIC_UPDATE_SLICE starts#19881
Aharrypotter wants to merge 2 commits into
apache:mainfrom
Aharrypotter:tflite-dus-dynamic-start-19412

Conversation

@Aharrypotter

Copy link
Copy Markdown
Contributor

Summary

This PR adds Relax TFLite frontend support for runtime (dynamic) start indices
in STABLEHLO_DYNAMIC_UPDATE_SLICE, addressing the DYNAMIC_UPDATE_SLICE item
from #19412 section B.

_convert_stablehlo_dynamic_update_slice (added in #19587) previously raised
OpNotImplemented when the start-index scalars were runtime (non-constant)
values, handling only compile-time-constant starts. Models that compute the
update offset at runtime could therefore not be imported. This PR makes the
dynamic-start path work, with StableHLO clamping semantics, without adding a new
Relax op. The change is limited to this converter and its test.

Design

Dynamic start indices via scatter_nd

The existing static path already lowers STABLEHLO_DYNAMIC_UPDATE_SLICE to
relax.op.scatter_nd, building the scatter index grid at compile time with
numpy.indices. scatter_nd accepts a general runtime indices tensor and
returns the data (operand) shape unchanged, so the dynamic case needs no new
op and introduces no symbolic dimensions — only the index grid is built
in-graph instead of in NumPy.

For runtime starts, the converter builds the index grid per axis a (rank is
statically known from the operand/update shapes):

  • clamp the start to [0, operand_dim - update_dim] with relax.op.maximum /
    relax.op.minimum — StableHLO clamps out-of-range starts rather than erroring;
  • idx = arange(update_dim) + clamped_start;
  • reshape idx to broadcast on axis a and broadcast_to the update shape;
  • expand_dims a trailing index axis.

concat over the axes produces an int64 index tensor of shape
(*update_shape, rank), which is fed to the same
relax.op.scatter_nd(operand, indices, update, "update") call the static path
uses.

The static (constant-start) path is unchanged, including its compile-time
out-of-bounds rejection.

Operator Support

Operator TFLite inputs Relax lowering Supported subset
STABLEHLO_DYNAMIC_UPDATE_SLICE operand, update, N scalar start indices relax.op.scatter_nd with a NumPy index grid (constant starts) or an in-graph arange + clamp index grid (runtime starts) static operand/update shapes; constant or runtime start indices

Not Included

  • Dynamic (non-static) operand or update shapes — the index grid is built from
    the statically known update shape, so operand/update shapes must be static.
    Runtime start indices are supported; runtime tensor shapes are not.

Tests

The dynamic-start test compiles the imported module and runs it on the Relax VM,
comparing the output against a NumPy reference; it includes an out-of-range start
to exercise clamping. The static structural-equal and out-of-bounds tests are
unchanged.

Test Coverage
test_stablehlo_dynamic_update_slice constant start indices, structural-equal (existing)
test_stablehlo_dynamic_update_slice_dynamic_starts runtime start indices, compile + run, including an out-of-range start that is clamped
test_stablehlo_dynamic_update_slice_out_of_bounds_unsupported constant-start path rejects out-of-bounds updates (existing)

Local validation:

python -m ruff format --check \
  python/tvm/relax/frontend/tflite/tflite_frontend.py \
  tests/python/relax/test_frontend_tflite.py

python -m ruff check \
  python/tvm/relax/frontend/tflite/tflite_frontend.py \
  tests/python/relax/test_frontend_tflite.py

python -m pytest \
  tests/python/relax/test_frontend_tflite.py -k dynamic_update_slice -q

python -m pytest \
  tests/python/relax/test_frontend_tflite.py -q

Result:

ruff format --check: 2 files already formatted
ruff check: All checks passed
dynamic_update_slice tests: 3 passed, 555 deselected
full TFLite pytest: 558 passed

References

_convert_stablehlo_dynamic_update_slice previously raised OpNotImplemented
when the start-index scalars were runtime (non-constant) values, only
handling compile-time-constant starts. This was the DYNAMIC_UPDATE_SLICE
"partial implementation" item tracked in apache#19412.

scatter_nd already accepts a general runtime indices tensor, so no new op is
needed. Add a dynamic branch that builds the index grid in-graph: per axis,
arange(update_dim) + clamp(start, 0, operand_dim - update_dim), broadcast to
the update shape and concatenated on a trailing axis. Clamping matches
StableHLO, which clamps out-of-range starts rather than erroring. The static
path (compile-time index grid, out-of-bounds rejection) is unchanged.

Replace the "not supported" test with a compile-and-run test that exercises
both an in-range start and an out-of-range start (verifying clamping).

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request adds support for converting STABLEHLO_DYNAMIC_UPDATE_SLICE with dynamic start indices in the TVM Relax TFLite frontend by lowering it to relax.op.scatter_nd and building the index grid dynamically at runtime. It also adds a test case to verify correct execution and clamping behavior. Feedback suggests improving robustness by explicitly passing start, stop, and step constants to relax.op.arange to avoid type mismatches, and adding compile-time validation to ensure update slice dimensions do not exceed operand dimensions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread python/tvm/relax/frontend/tflite/tflite_frontend.py Outdated
Comment thread python/tvm/relax/frontend/tflite/tflite_frontend.py
Apply Gemini Code Assist review feedback to the dynamic-start
DYNAMIC_UPDATE_SLICE converter:

- Validate at import time that update dimensions do not exceed operand
  dimensions, matching StableHLO's precondition and avoiding negative
  clamp bounds.
- Pass explicit start/stop/step to relax.op.arange for clarity and to
  avoid relying on default-argument dtype behavior.

@tlopex tlopex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you change the test to use tvm.ir.assert_structural_equal due to the CI pressure?
Also we'd better use structural check for frontend tests whenever possible in the future

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