loadEmbeddingTokenizer function

Future<SentencePieceTokenizer> loadEmbeddingTokenizer(
  1. String tokenizerPath
)

Loads the SentencePiece tokenizer at tokenizerPath — a .json (via TokenizerJsonLoader) or a raw SentencePiece .model file.

The returned tokenizer has any padding and truncation the file declares DISABLED: encode() hands back bare content, never a fixed-width row. Width and terminators are the caller's — encodeForEmbedding, encodeForSiglipEmbedding, and the embedding pass that owns seqLen. (A raw .model carries no such blocks, so there the guarantee is free.)

The returned tokenizer is the dependency's, loaded from the file as it always was: its own encode() is unchanged. encodeForEmbedding and encodeForSiglipEmbedding add what this function read from the file, and tokenize as SentencePiece does: the model's user-defined pieces (Gemma's </div>, <mask>, <start_of_turn>, …) matched first and kept whole, and <pad>, <eos>, <bos> and <unk> never produced from text. For a HuggingFace tokenizer.json in Gemma's shape the text between two pieces needs a second tokenizer, loaded from the same file without its added_tokens. It is built the first time one of those two functions runs on the returned tokenizer, by reading tokenizerPath again: a StateError then if the file is gone or has changed since. (The embedding route — loadGemmaSentencePieceEmbeddingTokenizer, loadSiglipSentencePieceEmbeddingTokenizer — builds only that second tokenizer and never the first.)

Throws UnsupportedError for a BPE model with user-defined pieces whose normalizer this package cannot reproduce them under, and FormatException for a tokenizer.json whose added tokens the file alone cannot type (user_defined_pieces.dart says which) — both rather than return ids that differ from SentencePiece's without a sign.

Throws StateError if the resolved dart_sentencepiece_tokenizer accepts the calls that disable those settings and leaves them set anyway. That is a check on the tokenizer's config, not a guarantee about encode()'s output.

Implementation

Future<SentencePieceTokenizer> loadEmbeddingTokenizer(
  String tokenizerPath,
) async {
  // Why the disable lives here and not in each profile.
  //
  // On 1.4.1 a SigLIP 2 `tokenizer.json` (which declares `{Fixed: 64, Right}`)
  // comes back already padded: the loader applies the file's own blocks
  // regardless of the config passed here. Both `encodeForEmbedding` and
  // `encodeForSiglipEmbedding` append their own EOS after whatever they are
  // handed, which puts it past the pad run — every id in range, nothing thrown,
  // and a different vector, since SigLIP pools the LAST position.
  //
  // Undoing that afterwards was possible but was a heuristic over a pipeline
  // nobody checks: it assumed right-padding with id 0, and a file declaring
  // `"direction": "Left"` or another `pad_id` would have slipped through
  // silently. Turning the feature off is the same result without the assumption,
  // and it keeps this package owning its own width rule.
  //
  // `_applyTokenizerSettings` is HF-JSON-only, so on the `.model` arm the two
  // calls are a no-op today. They stay because the contract above is one
  // sentence for both arms, and it should not quietly depend on which ran.
  return _load(tokenizerPath, forEmbedding: false);
}