Aharrypotter opened a new pull request, #19812:
URL: https://github.com/apache/tvm/pull/19812
## Summary
This PR adds Relax TFLite frontend support for the TFLite builtin `RFFT2D`
operator (issue #19519 item C — FFT / complex operators). It is the
follow-up to upstream PR #19763, which already merged the `REAL` / `IMAG` /
`COMPLEX_ABS` subset of item C; this PR completes the subset with `RFFT2D`
itself.
`RFFT2D` computes a 2D real FFT over the last two input axes and returns a
real/imag pair tensor of shape `[..., H, W // 2 + 1, 2]`. Relax does not have
a native complex64 dtype, so the pair output is represented as a `float32`
tensor with a trailing axis of size 2, matching the convention PR #19763
established for `REAL` / `IMAG` / `COMPLEX_ABS`. `RFFT2D` accepts a real
`float32` input and emits a `float32` pair output of the same dtype; the
frontend does not need to materialize any COMPLEX64 in-memory representation
of its own.
The supported subset is the static no-padding / no-truncation path:
- the input's last two dimensions must match `fft_length`
- `fft_length` must be a length-2 integer constant (int32 or int64)
- the output shape is `input_shape[:-2] + (H, W // 2 + 1, 2)`
- sparse inputs are rejected
## Design
### Dispatch Between Two TIR Kernels
`convert_rfft2d` selects one of two TIR primfuncs at lowering time, based on
whether the spatial axes are powers of two:
```python
if _is_power_of_2(height) and _is_power_of_2(width):
prim_func = _build_tflite_rfft2d_fft_primfunc(input_shape,
relax_output_shape)
else:
prim_func = _build_tflite_rfft2d_primfunc(input_shape,
relax_output_shape)
```
Both kernels share the same `call_tir` contract, so downstream code is
kernel-agnostic.
#### DFT Reference Kernel (`_build_tflite_rfft2d_primfunc`)
A naive O(B · H · W · H · W) DFT over the last two input axes. The outer
(batch, out_y, out_x) iteration is structured as S-TIR spatial axes so a
downstream `tir.schedule` pass can parallelize it. Trig and accumulation are
in float32; the result agrees with `np.fft.rfft2` to about `1e-5` absolute
tolerance for typical input sizes. This kernel is the fallback for odd or
non-power-of-2 spatial sizes.
#### Cooley-Tukey FFT Kernel (`_build_tflite_rfft2d_fft_primfunc`)
An O(B · H · W · (log2(H) + log2(W))) radix-2 Cooley-Tukey FFT, dispatched
when both `H` and `W` are positive powers of 2. The primfunc source is
generated as a TIR string at construction time and registered in `linecache`
so `tirx.parser` can resolve it. The bit-reversal permutation and butterfly
stages are precomputed in Python and inlined as direct scratch-buffer
assignments, so all loop bounds and twiddle factors are compile-time
literals:
1. Copy the real input into `scratch_real`; initialize `scratch_imag` to 0.
2. For each batch and each row, run an in-place 1D FFT of length `W` along
the width axis using scratch buffers.
3. For each batch and each column, run an in-place 1D FFT of length `H`
along the height axis with stride `W`.
4. Write the first `W // 2 + 1` complex bins per row to the output pair
representation.
The bit-reversal swap pairs and butterfly stage bodies are precomputed at
primfunc-construction time because `tirx.parser` does not currently accept
runtime `T.serial` bounds, and twiddle factors must be `T.float32` literals
to avoid `Undefined variable` errors. The fake linecache filename
`<tflite_rfft2d_fft_primfunc H=8 W=8 outW=5>` is dimension-aware, so
generated-source stack traces are readable.
### COMPLEX64 Pair Representation Helpers
The frontend represents TFLite COMPLEX64 tensors as float32 real/imag pairs
with a trailing axis of size 2 (since Relax has no native complex64 dtype).
Four small helpers centralize the rule so future complex ops can plug in
without re-implementing the pair-axis layout:
- `_is_tflite_complex64_type` — checks whether a TFLite tensor type is
`COMPLEX64`.
- `_unwrap_tflite_tensor` — unwraps a `TensorWrapper` to the raw
`tflite.Tensor`.
- `_get_relax_tensor_dtype` — returns the Relax dtype used to represent a
TFLite tensor (`"float32"` for COMPLEX64, otherwise the standard mapping).
- `_get_relax_tensor_shape` — returns the Relax shape (TFLite shape with a
trailing `(2,)` axis appended for COMPLEX64).
The 3 callers that construct Relax parameters from TFLite metadata
(`_get_static_tensor_shape_dtype` / `_set_subgraph_input_params` /
`_get_tensor_param`) now go through these helpers. The pair-axis invariant
is documented on `get_tensor_value` and `get_tensor_shape`, which both
return the *raw TFLite* shape (no pair axis) for downstream callers that
need to compare against the model's declared output shape.
### Boundary Validation
`convert_rfft2d` validates rank, dtype, fft_length shape, integer-ness,
positivity, fft_length == input spatial shape, output shape agreement, and
the absence of sparse inputs before emitting the `call_tir`. Edge cases
(sparse, non-integer fft_length, zero/negative fft_length, mismatched
fft_length, 1×1 spatial size) each produce a targeted `OpNotImplemented`
diagnostic.
## Operator Support
| Operator | TFLite options | Relax lowering | Supported subset |
|---|---|---|---|
| `RFFT2D` | input `float32`, constant length-2 integer `fft_length`,
`COMPLEX64` output | `call_tir` to a generated TIR kernel | static
no-padding/no-truncation; `H`, `W` arbitrary; Cooley-Tukey dispatched when both
are powers of 2 |
## Safety Checks
- Non-float32 input raises `OpNotImplemented("RFFT2D input must be
float32")`.
- Non-COMPLEX64 output raises `OpNotImplemented("RFFT2D output must be
COMPLEX64")`.
- Sparse inputs raise `OpNotImplemented("RFFT2D does not support sparse
inputs")`.
- Non-constant `fft_length` raises `OpNotImplemented("RFFT2D requires a
constant fft_length")`.
- Non-integer `fft_length` raises `OpNotImplemented("RFFT2D fft_length must
be an integer tensor")`.
- Wrong-shape `fft_length` (not length 2) raises `OpNotImplemented("RFFT2D
fft_length must be a length-2 tensor")`.
- Non-positive `fft_length` raises `OpNotImplemented("RFFT2D fft_length must
be positive")`.
- `fft_length` not matching the input's last two dims raises
`OpNotImplemented("RFFT2D currently supports fft_length matching the input
spatial shape")`.
- Mismatched output shape raises `OpNotImplemented("RFFT2D output shape does
not match fft_length")`.
- Input rank < 2 raises `OpNotImplemented("RFFT2D input rank must be at
least 2")`.
## Not Included
- `RFFT2D` with `fft_length` not matching the input's last two dimensions
(padding / truncation path).
- Other complex-data operators: `REAL` / `IMAG` / `COMPLEX_ABS` are already
handled by upstream PR #19763 and are out of scope for this PR.
- A frontend guard that rejects COMPLEX64 inputs flowing into non-complex
ops. With PR #19763 providing the only other complex ops, models that
contain only `RFFT2D` (with float32 input / COMPLEX64 output) and the
three upstream ops are fully supported. A generic COMPLEX64 guard was
intentionally not added here to keep this PR scoped to `RFFT2D`.
- User-override validation for `shape_dict` / `dtype_dict` on COMPLEX64
inputs. After PR #19763, the frontend no longer auto-appends a
trailing pair axis to user overrides; a user passing the natural TFLite
shape without the pair axis will now fall through to the standard
metadata mismatch path.
- Higher-precision FFT kernel (e.g. SIMD). The float32 twiddle / float32
accumulation paths match `np.fft.rfft2` to `~1e-4` on the Cooley-Tukey
path; large spatial dimensions may need a future optimized lowering or
backend-specific implementation.
## Tests
The tests manually build minimal TFLite flatbuffers, run the frontend, and
compare against `np.fft.rfft2`. Edge cases raise `OpNotImplemented`. The
DFT-path tests use `atol=1e-5`; the FFT-path tests use `atol=1e-4` because
twiddle factors are float32 literals.
| Test | Path | Shape | Coverage |
|---|---|---|---|
| `test_rfft2d_static_pair_output` | DFT | `[2, 4]` | Baseline 2D, even
width; also asserts Relax script contains the `tflite_rfft2d` kernel name and
pair-output struct-info |
| `test_rfft2d_static_pair_output_with_batch` | DFT | `[2, 2, 4]` | Leading
batch dims preserved |
| `test_rfft2d_odd_width_pair_output` | DFT | `[3, 5]` | Odd width → `W // 2
+ 1` output bins |
| `test_rfft2d_int64_fft_length` | DFT | `[2, 4]` | INT64 fft_length
constant (TFLite schema allows either int32 or int64) |
| `test_rfft2d_4d_input_pair_output` | DFT | `[2, 3, 4, 5]` | 4D input with
batch and odd width |
| `test_rfft2d_minimal_1x1_pair_output` | DFT | `[1, 1]` | Edge case:
trivial 1×1 FFT |
| `test_rfft2d_mismatched_fft_length_unsupported` | — | `[2, 4]` (fft=`[4,
4]`) | fft_length != input spatial shape guard |
| `test_rfft2d_fft_path_4x4` | **FFT** | `[4, 4]` | Smallest power-of-two
(4×4) where both row and column FFTs do real work |
| `test_rfft2d_fft_path_8x8` | **FFT** | `[8, 8]` | Square 8×8 power-of-two |
| `test_rfft2d_fft_path_16x16` | **FFT** | `[16, 16]` | Larger FFT, kernel
scaling check |
| `test_rfft2d_fft_path_2x2x4x8` | **FFT** | `[2, 2, 4, 8]` | 4D
power-of-two with batch |
Local validation:
```bash
python -m py_compile \
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 rfft2d -v
```
Result:
```text
py_compile: passed
ruff check: All checks passed
rfft2d tests: 11 passed
```
## References
- Issue [#19519](https://github.com/apache/tvm/issues/19519) item C: FFT /
complex operators (`RFFT2D`, `REAL`, `IMAG`, `COMPLEX_ABS`).
- Upstream PR #19763: `[Relax][Frontend][TFLite] Add support for
FFT/complex operators: REAL, IMAG, COMPLEX_ABS` (merge commit `9d6e1cf0`).
This PR's RFFT2D output pair layout matches the COMPLEX64 pair
representation PR #19763 established for `REAL` / `IMAG` / `COMPLEX_ABS`,
so downstream ops from PR #19763 can consume RFFT2D output directly.
- Tracking issue [#19764](https://github.com/apache/tvm/issues/19764) for
the longer-term native `relax.op.signal.rfft2d` / registered TOPI backend
path. This PR is a frontend-local lowering for TFLite `RFFT2D`, not the
native Relax signal op tracked there.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]