| // Copyright 2026 The IREE Authors |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #ifndef IREE_TOKENIZER_TOKENIZER_H_ |
| #define IREE_TOKENIZER_TOKENIZER_H_ |
| |
| #include "iree/base/api.h" |
| #include "iree/tokenizer/postprocessor.h" |
| #include "iree/tokenizer/special_tokens.h" |
| #include "iree/tokenizer/types.h" |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| //===----------------------------------------------------------------------===// |
| // Transform Buffer Configuration |
| //===----------------------------------------------------------------------===// |
| |
| // Maximum transform buffer size for one-shot and streaming encode operations. |
| // This limits stack/heap allocation for the internal ring buffer used during |
| // normalization and segmentation. Larger buffers reduce the chance of needing |
| // emergency flush for pathological inputs (thousands of characters without |
| // punctuation), but use more memory. |
| // |
| // The transform buffer uses double-buffer mode, so the logical capacity is half |
| // the allocation. A 64KB allocation provides 32KB logical capacity. |
| // |
| // Maximum transform buffer allocation. |
| // Recommended values: |
| // 8KB: Minimal memory, suitable for most English text |
| // 16KB: Good balance for mixed content and cache locality |
| // |
| // The transform buffer determines the maximum segment size that the segmenter |
| // pipeline processes. Larger buffers allow longer segments, but segments that |
| // exceed L1 data cache (typically 32KB) cause DFA cache misses during |
| // multi-stage pre-tokenization. 16KB (8KB logical capacity after ring buffer |
| // halving) keeps the working set cache-friendly for segmenters with multiple |
| // children (e.g., DeepSeek V3's 4-stage Sequence pre-tokenizer). |
| #ifndef IREE_TOKENIZER_TRANSFORM_BUFFER_MAX_SIZE |
| #define IREE_TOKENIZER_TRANSFORM_BUFFER_MAX_SIZE (16 * 1024) |
| #endif |
| |
| // Normalizer expansion factor for transform buffer sizing. |
| // The ring buffer's logical capacity (allocation / 2) must be large enough to |
| // hold the entire normalized output of a segment. Normalizers can expand input: |
| // the replace normalizer substitutes 1-byte spaces with 3-byte ▁ (U+2581), |
| // so worst-case expansion is 3× for space-heavy text. This multiplier is |
| // applied to the input text size before computing the buffer allocation. |
| // |
| // For models without expanding normalizers, the extra allocation is harmless |
| // (it's a transient per-encode allocation). Increase this if encountering |
| // RESOURCE_EXHAUSTED errors from streaming encode deadlocks. |
| #ifndef IREE_TOKENIZER_TRANSFORM_BUFFER_EXPANSION_FACTOR |
| #define IREE_TOKENIZER_TRANSFORM_BUFFER_EXPANSION_FACTOR 3 |
| #endif |
| |
| // Minimum transform buffer allocation returned by recommended_size(). |
| // Below this threshold, the ring buffer's logical capacity (allocation / 2) |
| // is too small for the segmenter to reliably identify complete segment |
| // boundaries, causing artificial segment splits and sub-optimal BPE merges |
| // (allocation=256 → +8.5% tokens, allocation=1024 → +1.4% tokens, |
| // allocation=2048+ → optimal). The value 4096 (2048 bytes logical capacity) |
| // ensures optimal BPE quality for all practical inputs. |
| // |
| // The encode_state_initialize function accepts any power-of-two allocation |
| // >= 8. This constant only affects recommended_size(), not the hard minimum. |
| #define IREE_TOKENIZER_TRANSFORM_BUFFER_MIN_SIZE 4096 |
| |
| // Tokenizer with builder pattern for construction. |
| // See README.md for architecture, streaming patterns, and design details. |
| // |
| // Usage: |
| // // 1. Create builder (stack-allocated) |
| // iree_tokenizer_builder_t builder; |
| // iree_tokenizer_builder_initialize(allocator, &builder); |
| // |
| // // 2. Format loader populates builder |
| // format_loader_parse_json(&builder, json); // transfers ownership |
| // |
| // // 3. Build tokenizer (transfers ownership from builder) |
| // iree_tokenizer_t* tokenizer = NULL; |
| // IREE_RETURN_IF_ERROR(iree_tokenizer_builder_build(&builder, &tokenizer)); |
| // |
| // // 4. Cleanup builder (no-op after successful build) |
| // iree_tokenizer_builder_deinitialize(&builder); |
| // |
| // // 5. Use tokenizer for encode/decode |
| // iree_tokenizer_encode(tokenizer, text, ...); |
| // |
| // // 6. Free tokenizer when done |
| // iree_tokenizer_free(tokenizer); |
| // |
| // Thread-safe, immutable. |
| typedef struct iree_tokenizer_t iree_tokenizer_t; |
| |
| // Streaming encode state. |
| typedef struct iree_tokenizer_encode_state_t iree_tokenizer_encode_state_t; |
| |
| // Streaming decode state. |
| typedef struct iree_tokenizer_decode_state_t iree_tokenizer_decode_state_t; |
| |
| // Component forward declarations. |
| typedef struct iree_tokenizer_normalizer_t iree_tokenizer_normalizer_t; |
| typedef struct iree_tokenizer_segmenter_t iree_tokenizer_segmenter_t; |
| typedef struct iree_tokenizer_model_t iree_tokenizer_model_t; |
| typedef struct iree_tokenizer_decoder_t iree_tokenizer_decoder_t; |
| typedef struct iree_tokenizer_vocab_t iree_tokenizer_vocab_t; |
| |
| //===----------------------------------------------------------------------===// |
| // Offset Run Tracking (Internal) |
| //===----------------------------------------------------------------------===// |
| |
| // Run-length encoded offset mapping (internal). |
| // Used to track offset changes through length-changing transforms. |
| typedef struct iree_tokenizer_offset_run_t { |
| iree_host_size_t transform_position; // Position in transform buffer. |
| iree_host_size_t original_offset; // Corresponding original input offset. |
| } iree_tokenizer_offset_run_t; |
| |
| // List of offset runs for tracking (pointer + capacity). |
| typedef struct iree_tokenizer_offset_run_list_t { |
| iree_host_size_t capacity; |
| iree_tokenizer_offset_run_t* values; |
| } iree_tokenizer_offset_run_list_t; |
| |
| static inline iree_tokenizer_offset_run_list_t |
| iree_tokenizer_offset_run_list_empty(void) { |
| iree_tokenizer_offset_run_list_t list = {0, NULL}; |
| return list; |
| } |
| |
| //===----------------------------------------------------------------------===// |
| // Encode Flags |
| //===----------------------------------------------------------------------===// |
| |
| // Flags for encode state initialization. |
| enum iree_tokenizer_encode_flag_bits_e { |
| IREE_TOKENIZER_ENCODE_FLAG_NONE = 0u, |
| // Indicates this is the start of input (for Metaspace "First" prepend mode). |
| IREE_TOKENIZER_ENCODE_FLAG_AT_INPUT_START = 1u << 0, |
| // Request offset tracking (requires offset_runs storage at initialize). |
| IREE_TOKENIZER_ENCODE_FLAG_TRACK_OFFSETS = 1u << 1, |
| // Insert special tokens from the postprocessor template (prefix, suffix). |
| // When set, the postprocessor's active template (single or pair) is used |
| // to emit special tokens and assign type_ids to model-produced tokens. |
| // When not set, no special tokens are inserted and type_ids are not written. |
| IREE_TOKENIZER_ENCODE_FLAG_ADD_SPECIAL_TOKENS = 1u << 2, |
| // Disable special token matching in input text. When set, sequences like |
| // <|endoftext|> are tokenized as ordinary text instead of being matched |
| // to special token IDs. Equivalent to tiktoken's encode_ordinary() |
| // (also known as encode(text, disallowed_special=())). |
| IREE_TOKENIZER_ENCODE_FLAG_NO_SPECIAL_TOKEN_MATCHING = 1u << 3, |
| }; |
| typedef uint32_t iree_tokenizer_encode_flags_t; |
| |
| //===----------------------------------------------------------------------===// |
| // Decode Flags |
| //===----------------------------------------------------------------------===// |
| |
| // Flags for decode state initialization. |
| enum iree_tokenizer_decode_flag_bits_e { |
| IREE_TOKENIZER_DECODE_FLAG_NONE = 0u, |
| // Skip tokens with the SPECIAL attribute (BOS/EOS/CLS/SEP/PAD/UNK). |
| // When set, special tokens produce no output text. This matches the default |
| // behavior of HuggingFace's tokenizer.decode(skip_special_tokens=True). |
| // When not set, all tokens are decoded including special tokens. |
| IREE_TOKENIZER_DECODE_FLAG_SKIP_SPECIAL_TOKENS = 1u << 0, |
| }; |
| typedef uint32_t iree_tokenizer_decode_flags_t; |
| |
| //===----------------------------------------------------------------------===// |
| // Builder (Stack-Allocatable) |
| //===----------------------------------------------------------------------===// |
| |
| // Builder for constructing tokenizers. |
| // Stack-allocated by caller, accumulates heap allocations during loading, |
| // then transfers ownership to tokenizer via build(). |
| // |
| // Lifecycle: |
| // initialize() -> set_*() ... -> build() -> deinitialize() |
| // |
| // After successful build(), deinitialize() is a no-op (ownership transferred). |
| // On error, deinitialize() frees any partial state. |
| typedef struct iree_tokenizer_builder_t { |
| iree_allocator_t allocator; |
| |
| // Pipeline components (owned by builder until build() transfers them). |
| // Each set_*() method transfers ownership to the builder. |
| // Optional - NULL for no-op normalizer. |
| iree_tokenizer_normalizer_t* normalizer; |
| iree_tokenizer_segmenter_t* segmenter; // Required. |
| iree_tokenizer_model_t* model; // Required. |
| // Optional - NULL for no-op decode. |
| iree_tokenizer_decoder_t* decoder; |
| |
| // Post-processor (value type, embedded directly). When all template counts |
| // are zero and both flags are false (the zero-initialized state), runtime |
| // postprocessor operations are no-ops. |
| iree_tokenizer_postprocessor_t postprocessor; |
| |
| // Special tokens for pre-normalization matching (value type, embedded). |
| // When empty (zero-initialized), no special token matching is performed. |
| iree_tokenizer_special_tokens_t special_tokens; |
| |
| // Special tokens for post-normalization matching (value type, embedded). |
| // These have normalized=true and are matched after the normalizer runs. |
| // When empty (zero-initialized), no post-norm matching is performed. |
| iree_tokenizer_special_tokens_t special_tokens_post_norm; |
| |
| // Vocabulary (owned by builder until build() transfers it). |
| // The model references but does not own the vocab, so vocab must be stored |
| // separately and freed after the model. |
| iree_tokenizer_vocab_t* vocab; |
| |
| // Number of token strings to batch during decode vocab lookups. |
| // Larger values amortize vtable overhead but use more state storage. |
| // Initialized to IREE_TOKENIZER_DEFAULT_STRING_BATCH_SIZE. |
| iree_host_size_t string_batch_size; |
| } iree_tokenizer_builder_t; |
| |
| // Initializes a builder (stack-allocated by caller). |
| void iree_tokenizer_builder_initialize(iree_allocator_t allocator, |
| iree_tokenizer_builder_t* out_builder); |
| |
| // Cleans up builder state. |
| // No-op if build() succeeded (ownership transferred). |
| // Frees partial state if build() was never called or failed. |
| void iree_tokenizer_builder_deinitialize(iree_tokenizer_builder_t* builder); |
| |
| // Sets the normalizer for the tokenizer. Transfers ownership to the builder. |
| // Optional - if not set, input is copied directly to the transform buffer. |
| void iree_tokenizer_builder_set_normalizer( |
| iree_tokenizer_builder_t* builder, iree_tokenizer_normalizer_t* normalizer); |
| |
| // Sets the segmenter for the tokenizer. Transfers ownership to the builder. |
| // Required - must be set before build(). |
| void iree_tokenizer_builder_set_segmenter( |
| iree_tokenizer_builder_t* builder, iree_tokenizer_segmenter_t* segmenter); |
| |
| // Sets the model for the tokenizer. Transfers ownership to the builder. |
| // Required - must be set before build(). |
| void iree_tokenizer_builder_set_model(iree_tokenizer_builder_t* builder, |
| iree_tokenizer_model_t* model); |
| |
| // Sets the decoder for the tokenizer. Transfers ownership to the builder. |
| // Optional - if not set, decode operations will fail. |
| void iree_tokenizer_builder_set_decoder(iree_tokenizer_builder_t* builder, |
| iree_tokenizer_decoder_t* decoder); |
| |
| // Sets the post-processor for the tokenizer. Copies the value into the builder. |
| // Optional - if not set (zero-initialized), postprocessor operations are no-ops |
| // and no special tokens are inserted during encoding. |
| void iree_tokenizer_builder_set_postprocessor( |
| iree_tokenizer_builder_t* builder, |
| iree_tokenizer_postprocessor_t postprocessor); |
| |
| // Sets the special tokens collection for pre-normalization matching. |
| // Moves ownership of the collection's slab into the builder. After this call, |
| // |special_tokens| is left in an empty state and should not be deinitialized. |
| // Optional - if not set (zero-initialized), no special token matching occurs. |
| void iree_tokenizer_builder_set_special_tokens( |
| iree_tokenizer_builder_t* builder, |
| iree_tokenizer_special_tokens_t* special_tokens); |
| |
| // Sets the special tokens collection for post-normalization matching. |
| // These tokens have normalized=true in HuggingFace and are matched after the |
| // normalizer transforms the input. Moves ownership of the collection's slab |
| // into the builder. After this call, |special_tokens| is left in an empty |
| // state and should not be deinitialized. |
| // Optional - if not set (zero-initialized), no post-norm matching occurs. |
| void iree_tokenizer_builder_set_special_tokens_post_norm( |
| iree_tokenizer_builder_t* builder, |
| iree_tokenizer_special_tokens_t* special_tokens); |
| |
| // Sets the vocabulary for the tokenizer. Transfers ownership to the builder. |
| // Required when using models that need vocabulary lookup (BPE, WordPiece, etc). |
| // The vocab must outlive the model as the model references but does not own it. |
| void iree_tokenizer_builder_set_vocab(iree_tokenizer_builder_t* builder, |
| iree_tokenizer_vocab_t* vocab); |
| |
| // Sets the string batch size for decode operations. |
| // This controls how many token strings are looked up from vocab before feeding |
| // to the decoder. Larger values (up to a few hundred) improve throughput by |
| // amortizing vtable call overhead. |
| void iree_tokenizer_builder_set_string_batch_size( |
| iree_tokenizer_builder_t* builder, iree_host_size_t batch_size); |
| |
| // Builds a tokenizer from the builder. |
| // Transfers ownership of all components from builder to tokenizer. |
| // Builder becomes empty after successful build. |
| iree_status_t iree_tokenizer_builder_build(iree_tokenizer_builder_t* builder, |
| iree_tokenizer_t** out_tokenizer); |
| |
| //===----------------------------------------------------------------------===// |
| // Tokenizer |
| //===----------------------------------------------------------------------===// |
| |
| // Frees a tokenizer and all owned components. |
| void iree_tokenizer_free(iree_tokenizer_t* tokenizer); |
| |
| // Returns the vocabulary owned by the tokenizer. |
| // The returned pointer is valid for the lifetime of the tokenizer. |
| const iree_tokenizer_vocab_t* iree_tokenizer_vocab( |
| const iree_tokenizer_t* tokenizer); |
| |
| // Returns the maximum number of special tokens the post-processor can emit |
| // during a single encode call (prefix + infix + suffix). This is the number |
| // of extra tokens beyond the model-produced content tokens. Returns 0 if no |
| // post-processor is configured. |
| iree_host_size_t iree_tokenizer_max_special_token_count( |
| const iree_tokenizer_t* tokenizer); |
| |
| // Returns the human-readable model type name (e.g., "BPE"). |
| // The returned string_view is valid for the lifetime of the tokenizer. |
| iree_string_view_t iree_tokenizer_model_type_name( |
| const iree_tokenizer_t* tokenizer); |
| |
| //===----------------------------------------------------------------------===// |
| // Batch Encode/Decode (Convenience) |
| //===----------------------------------------------------------------------===// |
| |
| // Encodes |text| to token IDs using |tokenizer|. This is a convenience wrapper |
| // around the streaming encode API equivalent to: initialize -> feed(text) -> |
| // finalize -> deinitialize. |
| // |
| // |flags| controls encoding behavior: |
| // IREE_TOKENIZER_ENCODE_FLAG_ADD_SPECIAL_TOKENS: wraps output with |
| // prefix/suffix special tokens (BOS, EOS, CLS, SEP) per the postprocessor. |
| // AT_INPUT_START and TRACK_OFFSETS (when output.token_offsets is non-NULL) are |
| // set automatically. |
| // |
| // Tokens are written to |output|. If output.token_offsets is non-NULL, byte |
| // ranges mapping each token back to the original input are also written. The |
| // actual number of tokens produced is returned in |out_token_count|. |
| // |
| // |allocator| is used for temporary state storage during encoding and is |
| // released before return. |
| // |
| // Returns IREE_STATUS_RESOURCE_EXHAUSTED if the output would exceed |
| // output.capacity. Callers can retry with a larger buffer. |
| iree_status_t iree_tokenizer_encode(const iree_tokenizer_t* tokenizer, |
| iree_string_view_t text, |
| iree_tokenizer_encode_flags_t flags, |
| iree_tokenizer_token_output_t output, |
| iree_allocator_t allocator, |
| iree_host_size_t* out_token_count); |
| |
| // Decodes |tokens| to text using |tokenizer|. |
| // |
| // |flags| controls decode behavior. SKIP_SPECIAL_TOKENS causes tokens with the |
| // SPECIAL attribute to produce no output (matching HuggingFace's default). |
| // |
| // Up to |text_output.size| bytes will be written to |text_output.data|. The |
| // output is raw UTF-8 bytes and is not NUL-terminated. The actual number of |
| // bytes produced is returned in |out_text_length|. |
| // |
| // |allocator| is used for temporary state storage during decoding and is |
| // released before return. |
| // |
| // Returns IREE_STATUS_RESOURCE_EXHAUSTED if the output would exceed |
| // text_output.size. Callers can retry with a larger buffer. |
| iree_status_t iree_tokenizer_decode(const iree_tokenizer_t* tokenizer, |
| iree_tokenizer_token_id_list_t tokens, |
| iree_tokenizer_decode_flags_t flags, |
| iree_mutable_string_view_t text_output, |
| iree_allocator_t allocator, |
| iree_host_size_t* out_text_length); |
| |
| //===----------------------------------------------------------------------===// |
| // Multi-Item Batch Encode/Decode |
| //===----------------------------------------------------------------------===// |
| |
| // Per-item flags for multi-item batch encoding. |
| enum iree_tokenizer_encode_batch_item_flag_bits_e { |
| IREE_TOKENIZER_ENCODE_BATCH_ITEM_FLAG_NONE = 0u, |
| // Indicates that |text_pair| is present and should be encoded as sequence B. |
| // This distinguishes an absent pair from an intentionally empty second |
| // sequence. |
| IREE_TOKENIZER_ENCODE_BATCH_ITEM_FLAG_HAS_TEXT_PAIR = 1u << 0, |
| }; |
| typedef uint32_t iree_tokenizer_encode_batch_item_flags_t; |
| |
| // Input/output item for multi-item batch encoding. Caller allocates arrays of |
| // these items, fills in the inputs (text, output buffers), and the batch |
| // function fills in out_token_count for each. |
| typedef struct iree_tokenizer_encode_batch_item_t { |
| // Input: text to encode (sequence A). |
| iree_string_view_t text; |
| |
| // Input: optional second text for pair encoding (sequence B). |
| // Only read when |flags| contains HAS_TEXT_PAIR. When ADD_SPECIAL_TOKENS is |
| // also set, the postprocessor's pair template is used instead of the single |
| // template: the output is [prefix] text [infix] text_pair [suffix] with |
| // type_ids assigned per the template's sequence_a_type_id and |
| // sequence_b_type_id. Token offsets for sequence B are relative to |
| // |text_pair|, and special tokens use zero-length offsets. |
| iree_string_view_t text_pair; |
| |
| // Input: per-item options controlling batch encoding. |
| iree_tokenizer_encode_batch_item_flags_t flags; |
| |
| // Input: output buffers for token IDs and optional offsets. |
| iree_tokenizer_token_output_t output; |
| |
| // Output: actual number of tokens written. |
| iree_host_size_t out_token_count; |
| } iree_tokenizer_encode_batch_item_t; |
| |
| // Encodes multiple text items in a single call, reusing internal state and |
| // scratch buffers across items for efficiency. |
| // |
| // |flags| controls encoding behavior (same as iree_tokenizer_encode). |
| // AT_INPUT_START and TRACK_OFFSETS are set automatically per-item. |
| // |
| // Caller provides |state_storage| (at least encode_state_calculate_size bytes) |
| // and |transform_buffer| for scratch space. For offset tracking across all |
| // items, provide |offset_runs| storage; otherwise pass an empty list. |
| // |
| // Each item in |items| must have valid text and output.token_ids pointer. If |
| // any item's output.token_offsets is non-NULL, offset tracking is enabled for |
| // that item (requires offset_runs to have sufficient capacity). |
| // |
| // On success, each item's out_token_count is set to the number of tokens |
| // produced. Returns IREE_STATUS_RESOURCE_EXHAUSTED if any item's output would |
| // exceed its output.capacity; in this case, processing stops at the failing |
| // item and prior items retain their results. |
| iree_status_t iree_tokenizer_encode_batch( |
| const iree_tokenizer_t* tokenizer, |
| iree_tokenizer_encode_batch_item_t* items, iree_host_size_t item_count, |
| iree_tokenizer_encode_flags_t flags, iree_byte_span_t state_storage, |
| iree_byte_span_t transform_buffer, |
| iree_tokenizer_offset_run_list_t offset_runs); |
| |
| // Input/output item for multi-item batch decoding. Caller allocates arrays of |
| // these items, fills in the inputs (tokens, text_output), and the batch |
| // function fills in out_text_length for each. |
| typedef struct iree_tokenizer_decode_batch_item_t { |
| // Input: token IDs to decode. |
| iree_tokenizer_token_id_list_t tokens; |
| // Input: output buffer for text. |
| iree_mutable_string_view_t text_output; |
| // Output: actual number of bytes written. |
| iree_host_size_t out_text_length; |
| } iree_tokenizer_decode_batch_item_t; |
| |
| // Decodes multiple token sequences in a single call, reusing internal state |
| // across items for efficiency. |
| // |
| // |flags| controls decode behavior (same as iree_tokenizer_decode). |
| // |
| // Caller provides |state_storage| (at least decode_state_calculate_size bytes). |
| // Each item in |items| must have valid tokens (or empty if no tokens) and a |
| // valid text_output buffer with capacity in text_output.size. |
| // |
| // On success, each item's out_text_length is set to the number of bytes |
| // produced. Returns IREE_STATUS_RESOURCE_EXHAUSTED if any item's output would |
| // exceed its text_output.size; in this case, processing stops at the failing |
| // item and prior items retain their results. |
| iree_status_t iree_tokenizer_decode_batch( |
| const iree_tokenizer_t* tokenizer, |
| iree_tokenizer_decode_batch_item_t* items, iree_host_size_t item_count, |
| iree_tokenizer_decode_flags_t flags, iree_byte_span_t state_storage); |
| |
| //===----------------------------------------------------------------------===// |
| // Streaming Encode |
| //===----------------------------------------------------------------------===// |
| |
| // Calculates the number of bytes required for encode state storage. Callers |
| // must allocate at least this many bytes and pass them to |
| // iree_tokenizer_encode_state_initialize. |
| // Returns IREE_STATUS_OUT_OF_RANGE on overflow. |
| iree_status_t iree_tokenizer_encode_state_calculate_size( |
| const iree_tokenizer_t* tokenizer, iree_host_size_t* out_size); |
| |
| // Calculates the recommended transform buffer size for encoding text. |
| // The returned size is guaranteed to be: |
| // - A power of two (required by ring buffer mode) |
| // - At least IREE_TOKENIZER_TRANSFORM_BUFFER_MIN_SIZE |
| // - At most IREE_TOKENIZER_TRANSFORM_BUFFER_MAX_SIZE |
| // |
| // The text size is multiplied by |
| // IREE_TOKENIZER_TRANSFORM_BUFFER_EXPANSION_FACTOR to account for normalizer |
| // expansion (e.g., space → ▁ is 1→3 bytes). The ring buffer's logical capacity |
| // is allocation/2, so a 3× multiplier yields 1.5× usable capacity relative to |
| // input — sufficient for typical text where <50% of bytes are subject to |
| // expansion. |
| // |
| // For streaming encode with multiple feed() calls, use the max expected chunk |
| // size as |text_size|. For one-shot encode, use the full text length. |
| static inline iree_host_size_t iree_tokenizer_transform_buffer_recommended_size( |
| iree_host_size_t text_size) { |
| // Account for normalizer expansion (saturating multiply to avoid overflow). |
| iree_host_size_t expanded = |
| text_size <= IREE_TOKENIZER_TRANSFORM_BUFFER_MAX_SIZE / |
| IREE_TOKENIZER_TRANSFORM_BUFFER_EXPANSION_FACTOR |
| ? text_size * IREE_TOKENIZER_TRANSFORM_BUFFER_EXPANSION_FACTOR |
| : IREE_TOKENIZER_TRANSFORM_BUFFER_MAX_SIZE; |
| // Clamp to minimum. |
| if (expanded < IREE_TOKENIZER_TRANSFORM_BUFFER_MIN_SIZE) { |
| expanded = IREE_TOKENIZER_TRANSFORM_BUFFER_MIN_SIZE; |
| } |
| // Round up to next power of two if not already. |
| iree_host_size_t size = iree_host_size_next_power_of_two(expanded); |
| // Clamp to maximum. |
| if (size > IREE_TOKENIZER_TRANSFORM_BUFFER_MAX_SIZE) { |
| size = IREE_TOKENIZER_TRANSFORM_BUFFER_MAX_SIZE; |
| } |
| return size; |
| } |
| |
| // Calculates the transform buffer size for one-shot (batch) encoding. |
| // Unlike the streaming recommended_size (which caps at 64KB for bounded |
| // memory), one-shot encoding uses a buffer proportional to the full input since |
| // it already allocates O(text_size) for output tokens. This avoids deadlocks |
| // when pre-tokenizer segments span more than the streaming buffer cap. |
| static inline iree_host_size_t iree_tokenizer_transform_buffer_oneshot_size( |
| iree_host_size_t text_size) { |
| iree_host_size_t expanded = |
| text_size <= SIZE_MAX / IREE_TOKENIZER_TRANSFORM_BUFFER_EXPANSION_FACTOR |
| ? text_size * IREE_TOKENIZER_TRANSFORM_BUFFER_EXPANSION_FACTOR |
| : SIZE_MAX; |
| if (expanded < IREE_TOKENIZER_TRANSFORM_BUFFER_MIN_SIZE) { |
| expanded = IREE_TOKENIZER_TRANSFORM_BUFFER_MIN_SIZE; |
| } |
| return iree_host_size_next_power_of_two(expanded); |
| } |
| |
| // Initializes encode state for streaming tokenization. The |tokenizer| must |
| // outlive the state. |
| // |
| // Caller provides |state_storage| (at least calculate_size bytes) and |
| // |transform_buffer| for scratch space during normalization. For offset |
| // tracking, provide |offset_runs| storage and set |
| // IREE_TOKENIZER_ENCODE_FLAG_TRACK_OFFSETS in |flags|; otherwise pass an empty |
| // list. |
| // |
| // The initialized state is returned in |out_state|. The state does not own the |
| // storage buffers; the caller is responsible for keeping them valid until |
| // deinitialize is called. |
| iree_status_t iree_tokenizer_encode_state_initialize( |
| const iree_tokenizer_t* tokenizer, iree_byte_span_t state_storage, |
| iree_byte_span_t transform_buffer, |
| iree_tokenizer_offset_run_list_t offset_runs, |
| iree_tokenizer_encode_flags_t flags, |
| iree_tokenizer_encode_state_t** out_state); |
| |
| // Cleans up encode state. Does not free |state_storage| or other buffers |
| // provided at initialization as the caller owns them. |
| void iree_tokenizer_encode_state_deinitialize( |
| iree_tokenizer_encode_state_t* state); |
| |
| // Resets encode state for reuse with a new input, preserving the existing |
| // storage layout. This is more efficient than deinitialize+initialize when |
| // processing multiple inputs with the same tokenizer and buffers. |
| // |
| // |flags| can change between resets (e.g., different offset tracking per item). |
| // The transform_buffer and offset_runs remain as set during initialization. |
| void iree_tokenizer_encode_state_reset(iree_tokenizer_encode_state_t* state, |
| iree_tokenizer_encode_flags_t flags); |
| |
| // Returns true if the encode state has pending data that would produce tokens |
| // on finalize. Useful for determining whether to call finalize or whether the |
| // state is "clean" after processing. This is a lightweight query that does not |
| // modify state. |
| bool iree_tokenizer_encode_state_has_pending( |
| const iree_tokenizer_encode_state_t* state); |
| |
| // Returns an upper bound on the number of tokens that finalize() could produce. |
| // This accounts for all pending data in the pipeline (ring buffer, segments, |
| // model state, deferred special tokens, post-processor). The actual count may |
| // be less due to BPE merges. Use this to size the output buffer for finalize. |
| // |
| // Callers SHOULD use this before finalize() because finalize is non-retryable: |
| // it consumes pipeline state destructively, so RESOURCE_EXHAUSTED from an |
| // undersized buffer is not recoverable. Allocating at least this many tokens |
| // guarantees finalize will succeed. |
| iree_host_size_t iree_tokenizer_encode_state_pending_token_bound( |
| const iree_tokenizer_encode_state_t* state); |
| |
| // Feeds an input |chunk| to the encoder. Tokens that can be definitively |
| // produced are written to |output|. Additional tokens may be produced by |
| // subsequent feed calls or by finalize once all input has been provided. |
| // |
| // Pull-based model: Output buffer capacity drives processing. The encoder |
| // pulls data through the pipeline (normalizer → segmenter → model) to |
| // fill the output buffer. Processing stops when output is full or input |
| // is exhausted. |
| // |
| // Returns: |
| // - |out_bytes_consumed|: Bytes consumed from |chunk|. May be less than |
| // chunk.size if internal buffers (transform_buffer) fill up. Caller should |
| // retry with remaining bytes: chunk.data + *out_bytes_consumed. |
| // - |out_token_count|: Tokens written to |output|. |
| // |
| // The function always makes progress if possible: it will consume input |
| // and/or produce tokens. Returns iree_ok_status() even if not all input |
| // was consumed (check out_bytes_consumed). |
| iree_status_t iree_tokenizer_encode_state_feed( |
| iree_tokenizer_encode_state_t* state, iree_string_view_t chunk, |
| iree_tokenizer_token_output_t output, iree_host_size_t* out_bytes_consumed, |
| iree_host_size_t* out_token_count); |
| |
| // Finalizes encoding by flushing any buffered data through the pipeline. Must |
| // be called after all input chunks have been fed. Any remaining tokens are |
| // written to |output|. |
| // |
| // Returns the number of tokens written in |out_token_count|. Returns |
| // IREE_STATUS_RESOURCE_EXHAUSTED if the output would exceed output.capacity. |
| iree_status_t iree_tokenizer_encode_state_finalize( |
| iree_tokenizer_encode_state_t* state, iree_tokenizer_token_output_t output, |
| iree_host_size_t* out_token_count); |
| |
| //===----------------------------------------------------------------------===// |
| // Streaming Decode |
| //===----------------------------------------------------------------------===// |
| |
| // Recommended output buffer size for decode_state_feed calls. Buffers of at |
| // least this size achieve full decode throughput. Smaller buffers are valid |
| // (minimum 4 bytes per IREE_TOKENIZER_DECODER_MIN_BUFFER_SIZE) but incur |
| // per-call overhead that reduces throughput by up to 12% at 64 bytes. |
| #define IREE_TOKENIZER_DECODE_OUTPUT_RECOMMENDED_SIZE 2048 |
| |
| // Calculates the number of bytes required for decode state storage. Callers |
| // must allocate at least this many bytes and pass them to |
| // iree_tokenizer_decode_state_initialize. |
| // Returns IREE_STATUS_OUT_OF_RANGE on overflow. |
| iree_status_t iree_tokenizer_decode_state_calculate_size( |
| const iree_tokenizer_t* tokenizer, iree_host_size_t* out_size); |
| |
| // Initializes decode state for streaming detokenization. The |tokenizer| must |
| // outlive the state. |
| // |
| // |flags| controls decode behavior (e.g., SKIP_SPECIAL_TOKENS). Flags are |
| // stored in the state and apply to all subsequent feed() calls. |
| // |
| // Caller provides |state_storage| (at least calculate_size bytes). The |
| // initialized state is returned in |out_state|. The state does not own the |
| // storage buffer; the caller is responsible for keeping it valid until |
| // deinitialize is called. |
| iree_status_t iree_tokenizer_decode_state_initialize( |
| const iree_tokenizer_t* tokenizer, iree_tokenizer_decode_flags_t flags, |
| iree_byte_span_t state_storage, iree_tokenizer_decode_state_t** out_state); |
| |
| // Cleans up decode state. Does not free |state_storage| provided at |
| // initialization as the caller owns it. |
| void iree_tokenizer_decode_state_deinitialize( |
| iree_tokenizer_decode_state_t* state); |
| |
| // Feeds |tokens| to the decoder. Text that can be definitively produced is |
| // written to |text_output| as raw UTF-8 bytes (not NUL-terminated). Additional |
| // text may be produced by subsequent feed calls or by finalize once all tokens |
| // have been provided. |
| // |
| // Pull-based model: Output buffer capacity drives processing. The decoder |
| // pulls tokens through the pipeline to fill the text output. Processing |
| // stops when output is full or tokens are exhausted. |
| // |
| // Returns iree_ok_status() always (errors are only from internal pipeline |
| // failures). Progress is indicated by the output parameters: |
| // - |out_tokens_consumed|: Tokens consumed from |tokens|. May be less than |
| // tokens.count if text output fills up. Caller should retry with remaining |
| // tokens: tokens.values + *out_tokens_consumed. |
| // - |out_text_length|: Bytes written to |text_output|. |
| // |
| // Zero-progress case: If both out_tokens_consumed and out_text_length are 0, |
| // the output buffer is genuinely exhausted (the next token's text does not fit |
| // in |text_output|). Callers must check for this to avoid infinite loops. |
| // Provide a larger output buffer or consume the already-written text. |
| iree_status_t iree_tokenizer_decode_state_feed( |
| iree_tokenizer_decode_state_t* state, iree_tokenizer_token_id_list_t tokens, |
| iree_mutable_string_view_t text_output, |
| iree_host_size_t* out_tokens_consumed, iree_host_size_t* out_text_length); |
| |
| // Finalizes decoding by flushing any buffered data through the pipeline. Must |
| // be called after all tokens have been fed. Any remaining text is written to |
| // |text_output| as raw UTF-8 bytes (not NUL-terminated). |
| // |
| // Returns the number of bytes written in |out_text_length|. Returns |
| // IREE_STATUS_RESOURCE_EXHAUSTED if the output would exceed text_output.size. |
| iree_status_t iree_tokenizer_decode_state_finalize( |
| iree_tokenizer_decode_state_t* state, |
| iree_mutable_string_view_t text_output, iree_host_size_t* out_text_length); |
| |
| #ifdef __cplusplus |
| } // extern "C" |
| #endif |
| |
| #endif // IREE_TOKENIZER_TOKENIZER_H_ |