Tokenization Models
In pg_tokenizer, a Model maps string tokens (or raw text) to integer token IDs (INT[]). You can choose from four built-in pretrained models, train a custom vocabulary model directly on your PostgreSQL table, import any HuggingFace tokenizer.json, or use a Lindera morphological model.
1. Built-in Pretrained Models
| Model Name | Source / Architecture | Requires text_analyzer? |
Memory Footprint | Best For |
|---|---|---|---|---|
llmlingua2 |
microsoft/llmlingua-2-xlm-roberta-large-meetingbank (XLM-RoBERTa multilingual subword) |
No (forbidden) | ~200 MB (default preloaded) | Multilingual general text out of the box without training a custom vocabulary. |
bert_base_uncased |
google-bert/bert-base-uncased (WordPiece, 30,522 vocab) |
No (forbidden) | ~15 MB | General English text; fast startup and compact vocabulary. |
wiki_tocken |
Snowolf/wiki-tocken (Built-in English Wikipedia token dictionary) |
Yes (required) | ~30 MB | English full-text search combined with custom stemmers, stopwords, and synonyms. |
gemma2b |
google/gemma-2b (SentencePiece BPE, 256k vocab) |
No (forbidden) | ~100 MB | Code, technical identifiers, and multilingual LLM workloads. |
Example: Using bert_base_uncased or llmlingua2
2. Custom Corpus Models (create_custom_model)
For maximum BM25 accuracy and minimal memory footprint, you can build a custom vocabulary model directly from your own table column. Unlike subword LLM tokenizers (which split rare domain words into multiple sub-tokens), a custom model assigns a dedicated 1:1 integer ID to every unique normalized token produced by your text_analyzer—and weighs only a few megabytes in memory.
Option A: One-Step Helper (create_custom_model_tokenizer_and_trigger)
This convenience function creates the custom model, creates the tokenizer, populates the target bm25vector column for all existing rows, and installs a BEFORE INSERT OR UPDATE trigger that automatically learns new vocabulary tokens and updates the bm25vector column when rows change:
Option B: Step-by-Step (create_custom_model Without Trigger)
If you prefer not to attach an automatic trigger (or want to control when embedding is populated):
3. Importing HuggingFace Tokenizers (create_huggingface_model)
You can import any HuggingFace tokenizers JSON specification (tokenizer.json) into PostgreSQL using create_huggingface_model.
In psql, you can fetch tokenizer.json directly from HuggingFace Hub into a variable using \set and pass it to create_huggingface_model:
Or pass the contents of a tokenizer.json file directly as a dollar-quoted string:
4. Lindera Morphological Models (create_lindera_model)
For East Asian languages such as Japanese, Korean, and Chinese that benefit from morphological dictionary analysis, pg_tokenizer integrates lindera.
For builds using Lindera 6.2, [segmenter] dictionary = "embedded://ipadic" is the native dictionary configuration. Existing [segmenter.dictionary] kind = "ipadic" configurations in the catalog and preload files remain supported, as do legacy dictionary and user-dictionary path tables. External compiled dictionaries must be rebuilt with Lindera 6.
Lindera 6.2 embeds the 2025-09-20 IPADIC edition instead of the 2007-08-01 edition used by Lindera 0.42. Token IDs change: regenerate stored Lindera token arrays and rebuild indexes that use them before querying with the upgraded tokenizer. Unknown words continue to be omitted.
If you build pg_tokenizer from source, enable the desired Lindera dictionary features at compile time:
Example configuration using ipadic with iteration mark normalization, NFKC Unicode normalization, character mapping, compound word merging, number normalization, Japanese part-of-speech stop-tags, Katakana stemming, and diacritical mark removal:
5. Model Preloading (add_preload_model)
Large models (llmlingua2 at ~200 MB, gemma2b at ~100 MB, japanese_ipadic) take several seconds and significant RAM to load on first query in each new PostgreSQL backend process. By preloading models during shared_preload_libraries startup, PostgreSQL loads the model once in the postmaster process and shares it across all backend connections via copy-on-write memory (llmlingua2 is preloaded by default):
Restart PostgreSQL after calling add_preload_model(...) (with shared_preload_libraries = 'pg_tokenizer.so' set in postgresql.conf) for preloading to take effect across all backends.
Custom model updates in 0.1.2 (unreleased)
Concurrent writes (0.1.2)
Vocabulary triggers deduplicate and sort new tokens and take a transaction-level advisory lock per model before writing. This serializes writers to the same vocabulary across all rows and statements in a transaction, preventing the opposite token-lock order that caused concurrent batch inserts to deadlock. Ordinary tokenization reads and writes to different models can proceed independently. Keep transactions short; when writing multiple models or locking application rows, use a consistent order and retain normal deadlock/serialization retry handling.
The embedding trigger refreshes its SPI snapshot after the vocabulary trigger, so at READ COMMITTED it can see token IDs committed by a writer it waited for. At REPEATABLE READ or SERIALIZABLE, a conflicting writer can receive SQLSTATE 40001 and must retry the whole transaction. Existing vocabulary IDs are preserved during upgrade; new IDs are internal mappings and are not guaranteed to match a separately created model.
Filtering a corpus (0.1.2)
Set filter in the custom model’s TOML configuration, or pass a seventh filter argument to the convenience helper:
The predicate uses source column names without NEW. or WHERE, and is evaluated with the caller’s privileges. Only TRUE matches; FALSE and NULL do not. Use a deterministic predicate of ordinary source columns, independent of the embedding target column; system columns such as ctid and volatile predicates are not supported. Provide a trusted SQL expression, with schema-qualified functions if needed; corpus selection does not replace row-level security. Empty or invalid expressions raise an error even for an empty table.
The filter applies to the initial vocabulary scan, embedding backfill, and future inserts/updates. Filtered models observe every UPDATE so changing only a predicate column can bring a row into the corpus. Nonmatching rows are left untouched, including existing embeddings; when a row leaves the corpus, its old embedding and vocabulary entries remain. Apply the same predicate when querying the corpus, or clear excluded embeddings in the application. Vocabulary is append-only and is not pruned on UPDATE/DELETE. A matching row with a NULL source has a NULL embedding.
Omitting filter (or passing SQL NULL to the helper) retains the whole-table behavior. Existing models and six-argument helper calls remain supported. Schema-qualified table names and quoted column names are supported; the stored table name is qualified when the model is created. Qualified names take precedence over a literal name containing a dot; use SQL double quotes (for example, '"corpus.docs"') to select the latter explicitly.