ReNikud Plus — Hebrew Grapheme-to-Phoneme Inference
Convert unvocalized Hebrew text into IPA for TTS, speech technology, and spoken-language research. Trained without any benchmark-derived data; on 126k words of unseen podcast speech it scores 93.5% word accuracy.
Benchmark
| word accuracy | RenikudPlus | int8 | Gemini | ReNikud | Phonikud |
|---|---|---|---|---|---|
| OVERALL | 86.2 | 85.9 | 84.6 | 78.2 | 66.4 |
| Gender | 99.3 | 99.3 | 77.0 | 59.9 | 37.5 |
| Min. Stress Pairs | 90.7 | 90.7 | 88.0 | 80.7 | 78.7 |
| Stress Homographs | 90.6 | 90.6 | 92.6 | 79.8 | 78.3 |
| Names | 80.0 | 79.3 | 75.3 | 67.3 | 68.0 |
| Acronyms | 79.6 | 79.0 | 79.0 | 56.6 | 37.5 |
| Slang | 76.9 | 76.9 | 71.2 | 59.0 | 41.7 |
| Penultimate Stress | 85.4 | 86.1 | 89.4 | 82.1 | 60.9 |
| Rare Phonemes | 55.6 | 53.0 | 57.6 | 41.1 | 19.9 |
| Foreign | 78.7 | 78.1 | 71.6 | 56.8 | 36.1 |
| Colloquial | 52.3 | 51.7 | 25.2 | 54.3 | 9.3 |
| ILSpeech-test | 93.2 | 93.1 | 96.0 | 92.5 | 85.5 |
Install
From PyPI:
pip install 'renikud-plus[cpu]' # CPU inference
pip install 'renikud-plus[gpu]' # CUDA inference
Pick the extra that matches the machine. ONNX Runtime is not a hard dependency
on purpose: onnxruntime and onnxruntime-gpu are separate distributions that
unpack into the same onnxruntime/ directory, so pip installs both happily
and whichever it writes last wins. If this package depended on the CPU build,
it would silently disable CUDA for anyone who also asked for onnxruntime-gpu
— and because pip's install order is topological rather than file order, they
could not fix it by reordering their requirements. Nothing errors; sessions just
quietly run on CPU.
If you already manage ONNX Runtime yourself, plain pip install renikud-plus
leaves your build untouched.
From this repo (no PyPI):
uv sync
Then run the example:
uv run python examples/basic.py
The ONNX weights (~310 MB) and the rescoring datastore download automatically
from Hugging Face on first use and are cached locally. No separate
hf download step is required.
Usage
from renikud_onnx import G2P
g2p = G2P() # downloads notmax123/RenikudPlus model.onnx + datastore.json if needed
print(g2p.phonemize("שלום לכולם"))
# → ʃalˈom lekulˈam
Pass a local path if you already have the weights:
g2p = G2P("model.onnx") # or model_int8.onnx — 4× smaller, ~0.3 pt overall
For a gender-conditioned ONNX model, pass speaker and target_speaker as
0 (unknown), 1 (male), or 2 (female):
g2p.phonemize("היא רצה", speaker=2, target_speaker=2)
What runs around the model
- Exact-MAP cascade decode (default). Consonant, vowel and stress are
decoded jointly under
E(c,v,s) = log P(c) + log P(v|c) + log P(s|c,v), with per-letter legality and "stress needs a vowel" as hard constraints, so the one-stress-per-word choice can flip the vowel and consonant too. Passexact_map=Falsefor the old greedy argmax. - Attested-reading rescorer, on by default whenever
datastore.jsonis found beside the weights (it is, after the automatic download). A trust gate attau=0.2over corpus-attested readings;datastore=Noneswitches it off. - Force lexicon —
G2P(..., lexicon={"סבתא": "sˈavta"})or a TSV path. Off unless passed; wins wherever it matches. - Hebrew number front end, from
hebrew-num2words(installed as a dependency). Digits never appear in training text, so they are expanded to words first — gender-, construct- and context-aware ("המחיר 1250 שקלים"→hameχˈiʁ ʔˈelef matˈajim veχamiʃˈim ʃkalˈim), with clock time, date, year, percent, decimal and identifier readings.number_norm="off"skips it. Its API is re-exported, sofrom renikud_onnx import normalize_numbersstill works. - Long inputs. Text past the encoder's 2,046-character window is split on
sentence, then comma, then word boundaries and decoded window by window; the
pieces are contiguous, so concatenation is lossless. A
[א-ת]-in-output backstop warns (or raises,on_hebrew_leak="raise") if raw Hebrew survives into the IPA.
Vowelized (pointed) input
Input may carry niqqud or cantillation, in either of two modes.
niqqud="strip" (default). The marks are dropped before tokenization, so
pointed text decodes exactly like the same text unpointed — and, in particular,
cannot disambiguate anything:
g2p.phonemize("שָׁלוֹם לְכֻּלָּם") # → ʃalˈom lekulˈam, identical to the bare form
niqqud="use". The points are read as evidence. Each vowel sign, dagesh qal
and shin/sin dot is pinned into the exact-MAP energy as a hard constraint, so
the model predicts only what the pointing leaves open:
g2p = G2P(niqqud="use") # or per call: g2p.phonemize(text, niqqud="use")
g2p.phonemize("סֵפֶר") # sˈefeʁ book
g2p.phonemize("סַפָּר") # sˈapaʁ barber
g2p.phonemize("סָפַר") # sˈafaʁ counted
g2p.phonemize("סִפֵּר") # sˈipeʁ told
All four are sˈefeʁ under "strip" — the skeleton ספר cannot tell them apart.
Two signs stay ambiguous on purpose and are left to the model: qamats, since
qamats qatan (/o/, as in כָּל → kˈol) is written with the same sign in most
pointed text, and shva, since shva na is /e/ while shva nah is nothing.
Unmarked letters inside a pointed word are read the way pointed text implies —
a bare ו is the consonant (a vowel vav would carry holam or dagesh), a bare י
after hiriq or tsere is a mater, a word-final bare א/ה is silent.
Caveats. The mode needs the exact-MAP decode (it raises with exact_map=False).
Niqqud does not mark stress, so stress stays the model's own call — and where
the pointing overrules its reading of the skeleton, its stress can still reflect
the reading it preferred (סַפָּר → sˈapaʁ, not sapˈaʁ). A constraint that
would leave a letter with no legal reading at all is dropped rather than
enforced, so partially or sloppily pointed text degrades to the default decode
instead of breaking.
On 24 hand-checked pointed words, "use" gets 24/24 vowels and consonants
right against 13/24 for "strip"; including stress, 19/24 against 13/24. On the
seven pointed possessive-suffix words from the field report (בְּנֹתָיו,
צִדְקֹתָיו, קֳדָשָׁיו, …) it matches the reference reading 7/7.
Niqqud output
vocalize renders the same predictions as pointed Hebrew (niqqud) instead of
IPA — for TTS engines that read niqqud natively but ignore phoneme markup. It
accepts the same speaker / target_speaker arguments.
print(g2p.vocalize("שלום לכולם"))
# → שַׁלוֹם לֶכּוּלַם
Niqqud has no stress mark, so predicted stress is not represented in this output
(it is in phonemize). Diacritization is phonetically faithful but not
publication-grade — e.g. shva in clusters is omitted. The IPA-side rescorer and
force lexicon rewrite IPA strings and so do not apply here.
Citation
@misc{melichov2026renikud,
title={ReNikud: Audio-Supervised Hebrew Grapheme-to-Phoneme Conversion},
author={Maxim Melichov and Yakov Kolani and Morris Alper},
year={2026},
url={https://arxiv.org/pdf/2606.20179},
}
Metadata
Release files for renikud-plus 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| renikud_plus-0.5.0.tar.gz | 26.0 kB | Without provenance | Details for renikud_plus-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| renikud_plus-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for renikud_plus-0.5.0-py3-none-any.whl |
Total release size: 52.7 kB
Release files / renikud_plus-0.5.0.tar.gz
| Download URL | renikud_plus-0.5.0.tar.gz |
|---|---|
| Size | 26.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
598ea6f77679fd77ded67d989fa2446c507ede4434879be3f9dcde3310c691a2
|
|
BLAKE2b-256 checksum How to use checksums |
d15134c4fa9e23ffa5de07e1b69ab2795a78ca8f58254d733a0df48bd494dc12
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.13
|
Release files / renikud_plus-0.5.0-py3-none-any.whl
| Download URL | renikud_plus-0.5.0-py3-none-any.whl |
|---|---|
| Size | 26.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
369fe82ccd3539e536ce71465b032952005466a2601c93ce08587abb956a1374
|
|
BLAKE2b-256 checksum How to use checksums |
45f90f3c50e8f1b8bced99844c9c9fbbc56b50fcaf46f8ab9f7aebd6504a2694
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.13
|
Security for renikud-plus 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.