Repository navigation
[Relax][Frontend][TFLite] Support static hashtable find - #19879
Conversation
There was a problem hiding this comment.
Code Review
This pull request adds support for constant-foldable string-to-int64 HASHTABLE_FIND operations in the Relax TFLite frontend, including decoding constant string tensor buffers and handling HASHTABLE_IMPORT with string keys or values. Feedback focuses on improving robustness and efficiency: catching potential UnicodeDecodeError when decoding string tensors, using np.unique to check for duplicate keys more efficiently, and supporting 1-element default value tensors in HASHTABLE_FIND instead of only strict scalars.
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.
Add a constant-foldable subset for the TFLite resource hashtable family, building on the static HASHTABLE / HASHTABLE_IMPORT import support. - Add a TFLite string-tensor buffer decoder (_get_string_tensor_value) that parses the [count][offsets][utf8 data] layout with bounds and monotonicity validation. This is reusable infrastructure for any future string support. - HASHTABLE_IMPORT now decodes and stores constant keys/values (including string buffers) and rejects duplicate keys, instead of only recording table metadata. - HASHTABLE_FIND now supports the constant string->int64 table subset by folding the lookup to a relax.const. Runtime string queries, int64->string tables, and non-constant tables remain explicitly unsupported. - Reject TensorType.STRING graph inputs in from_tflite with a clear OpNotImplemented instead of a low-level FFI 'unknown dtype string' error. This covers runtime string queries for HASHTABLE_FIND/HASHTABLE_LOOKUP. Relax still has no string tensor type or runtime hashtable op, so general runtime string-keyed lookups remain out of scope.
c75c496 to
9732138
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request adds support for constant-folding a subset of HASHTABLE_FIND operations (specifically constant string-to-int64 tables) in the Relax TFLite frontend. It introduces a helper method to decode constant TFLite string tensor buffers and updates convert_hashtable_import and convert_hashtable_find accordingly, while raising errors for unsupported cases like runtime string queries. Feedback is provided to add shape validation between keys and values in HASHTABLE_IMPORT to prevent silent truncation during lookup.
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.
| if np.unique(keys).size != keys.size: | ||
| raise tvm.error.OpNotImplemented( | ||
| "HASHTABLE_IMPORT with duplicate keys is not supported" | ||
| ) |
There was a problem hiding this comment.
In HASHTABLE_IMPORT, there is no validation to ensure that the keys and values tensors have matching shapes. If a model has mismatched key and value shapes, zip in convert_hashtable_find will silently truncate the elements, leading to unexpected behavior or incorrect constant folding. We should explicitly validate that the shapes of keys and values match before proceeding.
if keys.shape != values.shape:
raise tvm.error.OpNotImplemented(
"HASHTABLE_IMPORT requires keys and values to have the same shape"
)
if np.unique(keys).size != keys.size:
raise tvm.error.OpNotImplemented(
"HASHTABLE_IMPORT with duplicate keys is not supported"
)There was a problem hiding this comment.
This case already be covered by the existing import-time guard just above
this block:
key_shape = self._get_tensor_shape_tuple(key_tensor)
value_shape = self._get_tensor_shape_tuple(value_tensor)
if key_shape != value_shape:
raise tvm.error.OpNotImplemented("HASHTABLE_IMPORT requires keys and values same shape")This runs before the keys and values are stored for HASHTABLE_FIND, so a
mismatched import should be rejected before the later zip path can silently
truncate anything. I do not think an extra keys.shape != values.shape check
changes the frontend behavior here.
Summary
This PR extends the TFLite resource hashtable family from #19519 item G from
"import + size" to a constant-foldable
HASHTABLE_FINDsubset. It builds on thestatic
HASHTABLE/HASHTABLE_IMPORTimport support added in #19639 and theHASHTABLE_LOOKUPconverter from #19654.Relax has no string tensor type and no runtime hashtable operator, so this PR
targets the only subset that can be lowered today: tables whose keys and values
are constants imported in a
CALL_ONCEinit subgraph, queried by a constantstring tensor. In that case the lookup is resolved at import time and emitted as
a
relax.const. Runtime string queries remain explicitly guarded instead ofbeing lowered with incorrect semantics.
Design
Static String Tensor Decoding
The frontend now decodes constant TFLite string tensors through a shared helper
_get_string_tensor_value. It parses the TFLite string-tensor binary layout:The helper validates the buffer length against the declared count, checks that
offsets are in-bounds and monotonically non-decreasing, and verifies the decoded
element count matches the tensor shape. This is reusable infrastructure for any
future TFLite string support, independent of
HASHTABLE_FIND.Hashtable Import State
HASHTABLE_IMPORTnow stores the actual constant keys and values (numeric ordecoded string buffers) in shared conversion state, instead of only recording
table metadata (size, key/value dtype). Duplicate keys are rejected, because the
constant-fold lookup assumes a unique key mapping. The captured table is keyed by
the same
table_id/ handle resolution used byHASHTABLEandHASHTABLE_SIZE,so a
CALL_ONCEinit subgraph and the main graph agree on the same logicaltable.
Constant-Foldable Find
HASHTABLE_FINDresolves the table handle through the importer-local handle mapand the statically imported keys/values. For the supported subset it builds a
Python key -> value map, applies the per-element default value, overwrites hits
from the table, preserves the query tensor shape, and emits the result as a
relax.const. The op produces no runtime Relax computation, which matches thefact that both the table and the query are compile-time constants.
The supported subset is intentionally narrow and guard-first:
string -> int64table imported via a supportedCALL_ONCEHASHTABLE_IMPORTString Graph Input Guard
TensorType.STRINGgraph inputs are now rejected infrom_tflitewith a clearOpNotImplementedinstead of a low-level FFIunknown dtype stringerror, sinceRelax cannot represent a string tensor. This is the path hit by runtime string
queries for
HASHTABLE_FINDand string-valuedHASHTABLE_LOOKUP, so the guardgives a frontend-level diagnostic for both.
Operator Support
HASHTABLE_IMPORTHashtableImportOptionsCALL_ONCEinit, constant keys/values, no duplicate keysHASHTABLE_FINDHashtableFindOptionsrelax.conststring -> int64table + constant string queryNot Included
int64 -> stringfind, which would require string-typed Relax outputs.TensorType.STRINGtensor representation in Relax.These require core Relax support and are out of scope for the frontend.
Tests
The tests manually build minimal TFLite flatbuffers and compare the imported
Relax IR with
tvm.ir.assert_structural_equal. Unsupported patterns usepytest.raises.test_hashtable_call_once_import_find_string_to_int64string -> int64find folds to arelax.consttest_hashtable_call_once_import_find_string_to_int64_2d_querytest_hashtable_call_once_import_find_int64_to_string_unsupportedint64 -> stringtable rejectedtest_hashtable_call_once_import_find_runtime_query_unsupportedtest_hashtable_call_once_import_duplicate_keys_unsupportedtest_hashtable_lookup_string_value_unsupportedOpNotImplementedLocal 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 "hashtable or resource or variable" -q python -m pytest \ tests/python/relax/test_frontend_tflite.py -qResult:
References
HASHTABLE_LOOKUPconverter