lstmtraining --convert_to_int leaves the ADAM training_flags_ bit set, unlike official int-optimized traineddata
Current Behavior
When fine-tuning an LSTM model starting from a float base model (e.g. tessdata_best/eng.traineddata) via lstmtraining --continue_from <checkpoint> (default ADAM optimizer), then producing an int-optimized traineddata with:
lstmtraining --stop_training --convert_to_int --continue_from <checkpoint> --traineddata <base> --model_output <out>the resulting <out>.traineddata differs from the officially distributed int-optimized models in one network-header byte.
Extracting the .lstm component with combine_tessdata -u <out>.traineddata <prefix>. and inspecting the byte immediately following the "Series" type-name string (network header training_flags_) shows it retains 0x02 (NF_ADAM) — i.e. the flag from the original float/ADAM training is still set — even though the model has been converted to int.
By contrast, extracting the same byte from the officially distributed int eng.traineddata (tesseract-ocr/tessdata) shows 0x00.
I reproduced this as follows:
lstmtraining --continue_from <float checkpoint from tessdata_best> ...(fine-tune with default ADAM optimizer).lstmtraining --stop_training --convert_to_int --continue_from <checkpoint> --traineddata <base traineddata> --model_output outcombine_tessdata -u out.traineddata extracted.- Inspect
extracted.lstm: the byte right after the"Series"string is0x02, not0x00. - Compare with the official int
eng.traineddatafromtessdata— same byte is0x00there.
Expected Behavior
lstmtraining --stop_training --convert_to_int should clear the ADAM-related training_flags_ bit when producing an int-optimized model, so its network header matches the format of the officially distributed int models (as if trained with a non-ADAM / int-native pipeline from the start).
Suggested Fix
In the --convert_to_int code path (network serialization in lstmtraining/Network), clear the ADAM optimizer flag bit in training_flags_ before writing the int-converted network, mirroring the header produced by a native int/non-ADAM training run.
tesseract -v
tesseract 5.5.3
leptonica-1.87.0
libgif 5.2.2 : libjpeg 8d (libjpeg-turbo 3.1.3) : libpng 1.6.58 : libtiff 4.7.2 : zlib 1.2.12 : libwebp 1.6.0 : libopenjp2 2.5.4
Found NEON
Found libarchive 3.8.9 zlib/1.2.12 liblzma/5.8.3 bz2lib/1.0.8 liblz4/1.10.0 libzstd/1.5.7 expat/expat_2.7.4 CommonCrypto/system libb2/system
Found libcurl/8.7.1 SecureTransport (LibreSSL/3.3.6) zlib/1.2.12 nghttp2/1.68.1(Installed via Homebrew.)
Operating System
macOS 26 Tahoe
Other Operating System
Actual installed version reports as macOS 26.5.2 (build 25F84); closest dropdown option selected above.
uname -a
Darwin MAC0040 25.5.0 Darwin Kernel Version 25.5.0: Tue Jun 9 22:26:15 PDT 2026; root:xnu-12377.121.10~1/RELEASE_ARM64_T6031 arm64Compiler
N/A — used prebuilt Homebrew binary, did not compile from source.
CPU
Apple M3 Max (Apple Silicon, arm64)
Virtualization / Containers
N/A
Other Information
This was found while fine-tuning a font-specific digit-recognition model for use with Tesseract.js (a WASM build of Tesseract used in a browser). With the training_flags_ byte left at 0x02, the resulting model crashes when loaded in the WASM build with RuntimeError: missing function: _ZN9tesseract13DotProductSSEEPKfS1_i (a float-path SIMD function not compiled into that particular WASM build). Manually zeroing the byte after --convert_to_int (via combine_tessdata -u / re-pack) produces a traineddata that is byte-identical in this respect to the official int models and works correctly in both native tesseract and the WASM build. I'm not asking for anything WASM-specific to be fixed here — just noting it as the real-world symptom that led to discovering the discrepancy versus the official int traineddata format. Happy to share the full before/after .lstm header bytes or a minimal reproduction script (pure lstmtraining/combine_tessdata CLI calls) if useful.
Source: tesseract-ocr/tesseract