blob: 19c9daa4b43641636ff941094aa6715aedd43fa9 [file]
// 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
// Decoder interface for token-to-text conversion.
//
// Decoders transform token strings (from vocab lookup) back to text.
// Examples: ByteFallback (byte tokens), Metaspace (▁→space), WordPiece (##).
//
// The decode pipeline is:
// Token IDs → Vocab Lookup → Token Strings → Decoder → Final Text
//
// Design principles:
// - Pull-based: caller controls flow via output buffer size
// - Batched: processes array of token strings, not one at a time
// - Zero allocation: state lives in caller-provided storage
// - Composable: Sequence decoder chains multiple decoders
#ifndef IREE_TOKENIZER_DECODER_H_
#define IREE_TOKENIZER_DECODER_H_
#include "iree/base/api.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct iree_tokenizer_decoder_t iree_tokenizer_decoder_t;
typedef struct iree_tokenizer_decoder_state_t iree_tokenizer_decoder_state_t;
typedef struct iree_tokenizer_decoder_vtable_t iree_tokenizer_decoder_vtable_t;
//===----------------------------------------------------------------------===//
// Token String List (for batched input)
//===----------------------------------------------------------------------===//
// List of token strings for batched decoder input.
// Each string is a token's text representation from vocab lookup.
typedef struct iree_tokenizer_string_list_t {
iree_host_size_t count;
const iree_string_view_t* values;
} iree_tokenizer_string_list_t;
// Creates a string list from an array and count.
static inline iree_tokenizer_string_list_t iree_tokenizer_make_string_list(
const iree_string_view_t* values, iree_host_size_t count) {
iree_tokenizer_string_list_t list = {count, values};
return list;
}
// Returns an empty string list.
static inline iree_tokenizer_string_list_t iree_tokenizer_string_list_empty(
void) {
iree_tokenizer_string_list_t list = {0, NULL};
return list;
}
//===----------------------------------------------------------------------===//
// Decoder Flags
//===----------------------------------------------------------------------===//
// Flags declaring decoder transformation properties.
// Used by Sequence decoder to validate children at construction time.
typedef uint32_t iree_tokenizer_decoder_flags_t;
enum iree_tokenizer_decoder_flags_e {
IREE_TOKENIZER_DECODER_FLAG_NONE = 0,
// Output byte count is always <= input byte count.
// Enables zero-allocation in-place transformation in Sequence decoder.
IREE_TOKENIZER_DECODER_FLAG_SHRINKING = 1u << 0,
};
//===----------------------------------------------------------------------===//
// Decoder Capabilities (for pre-decode optimization)
//===----------------------------------------------------------------------===//
// Capabilities declaring decoder context-dependency.
// Used by tokenizer to determine if decoded token strings can be pre-computed
// at build time, eliminating runtime decoder work during decode.
typedef uint32_t iree_tokenizer_decoder_capability_t;
enum iree_tokenizer_decoder_capability_e {
IREE_TOKENIZER_DECODER_CAPABILITY_NONE = 0,
// Output for each token depends only on that token's text, not on adjacent
// tokens or stream position. Enables full pre-decode at build time.
// Examples: Replace, Strip, Passthrough.
IREE_TOKENIZER_DECODER_CAPABILITY_STATELESS = 1u << 0,
// Output for the first token in a stream differs from subsequent tokens.
// Typically the first token omits a leading space that "rest" tokens include.
// Requires STATELESS to also be set for pre-decode to work.
// Examples: Metaspace (add_prefix_space), WordPiece.
IREE_TOKENIZER_DECODER_CAPABILITY_POSITION_SENSITIVE = 1u << 1,
// Stateless for all tokens EXCEPT byte tokens (<0xHH> pattern).
// Byte tokens require cross-token UTF-8 accumulation (stateful), but
// non-byte tokens are pure passthrough. Enables a hybrid pre-decode path:
// non-byte tokens are pre-decoded at build time, byte tokens are handled
// with an inline accumulator at decode time.
// Examples: ByteFallback.
IREE_TOKENIZER_DECODER_CAPABILITY_STATELESS_EXCEPT_BYTE_TOKENS = 1u << 2,
// Stateless for most tokens (99.3% of GPT-2 vocab), but some produce
// partial UTF-8 bytes that must accumulate across token boundaries.
// Unlike BYTE_TOKENS where byte tokens are identifiable by <0xHH> syntax,
// partial UTF-8 tokens look like normal tokens (e.g., GPT-2's "Ã" maps to
// byte 0xC3 which is a UTF-8 lead byte). The build step trial-decodes each
// token and marks partial ones in a slab-embedded bitmap.
// Examples: ByteLevel (GPT-2 family).
IREE_TOKENIZER_DECODER_CAPABILITY_STATELESS_EXCEPT_PARTIAL_UTF8 = 1u << 3,
};
//===----------------------------------------------------------------------===//
// Decoder Base Type
//===----------------------------------------------------------------------===//
// Base decoder structure. All concrete decoder types embed this at
// offset 0. The vtable provides type-specific operations; state_size is
// cached at creation time for efficient state allocation.
struct iree_tokenizer_decoder_t {
const iree_tokenizer_decoder_vtable_t* vtable; // Must be at offset 0.
// Size of state struct, cached at creation.
iree_host_size_t state_size;
iree_tokenizer_decoder_capability_t capabilities; // Pre-decode properties.
};
// Base streaming state structure. All concrete state types embed this at
// offset 0. The decoder back-pointer enables vtable dispatch from state.
struct iree_tokenizer_decoder_state_t {
const iree_tokenizer_decoder_t* decoder;
};
//===----------------------------------------------------------------------===//
// Decoder VTable
//===----------------------------------------------------------------------===//
// VTable for decoder implementations.
//
// Pull-based processing model:
// - state_process() consumes token strings and writes text output
// - Caller controls batch size via input string count and output buffer
// - Incomplete sequences (e.g., partial byte fallback) buffered in state
// - state_finalize() flushes remaining buffered data
struct iree_tokenizer_decoder_vtable_t {
void (*destroy)(iree_tokenizer_decoder_t* decoder);
iree_status_t (*state_initialize)(const iree_tokenizer_decoder_t* decoder,
void* storage,
iree_tokenizer_decoder_state_t** out_state);
void (*state_deinitialize)(iree_tokenizer_decoder_state_t* state);
iree_status_t (*state_process)(iree_tokenizer_decoder_state_t* state,
iree_tokenizer_string_list_t token_strings,
iree_mutable_string_view_t output,
iree_host_size_t* out_strings_consumed,
iree_host_size_t* out_bytes_written);
iree_status_t (*state_finalize)(iree_tokenizer_decoder_state_t* state,
iree_mutable_string_view_t output,
iree_host_size_t* out_written);
bool (*state_has_pending)(const iree_tokenizer_decoder_state_t* state);
iree_tokenizer_decoder_flags_t flags;
};
//===----------------------------------------------------------------------===//
// Decoder Public API
//===----------------------------------------------------------------------===//
// Initializes base decoder fields. Called by concrete implementations
// after allocating the decoder struct.
void iree_tokenizer_decoder_initialize(
iree_tokenizer_decoder_t* decoder,
const iree_tokenizer_decoder_vtable_t* vtable, iree_host_size_t state_size,
iree_tokenizer_decoder_capability_t capabilities);
// Frees a decoder. Safe to call with NULL.
void iree_tokenizer_decoder_free(iree_tokenizer_decoder_t* decoder);
// Returns the size of state storage required for this decoder.
static inline iree_host_size_t iree_tokenizer_decoder_state_size(
const iree_tokenizer_decoder_t* decoder) {
return decoder->state_size;
}
// Returns the capability flags for this decoder.
static inline iree_tokenizer_decoder_capability_t
iree_tokenizer_decoder_capabilities(const iree_tokenizer_decoder_t* decoder) {
return decoder->capabilities;
}
// Initializes streaming state in caller-provided storage.
// |storage| must be at least iree_tokenizer_decoder_state_size() bytes.
static inline iree_status_t iree_tokenizer_decoder_state_initialize(
const iree_tokenizer_decoder_t* decoder, void* storage,
iree_tokenizer_decoder_state_t** out_state) {
return decoder->vtable->state_initialize(decoder, storage, out_state);
}
// Deinitializes streaming state (does not free storage).
static inline void iree_tokenizer_decoder_state_deinitialize(
iree_tokenizer_decoder_state_t* state) {
if (state && state->decoder) {
state->decoder->vtable->state_deinitialize(state);
}
}
// Minimum output buffer size for decoders. Smaller buffers cannot guarantee
// progress on multi-byte sequences (UTF-8 max 4 bytes, replacement char 3).
#define IREE_TOKENIZER_DECODER_MIN_BUFFER_SIZE 4
// Processes a batch of token strings, writing decoded text.
//
// Reads up to |token_strings.count| strings and writes up to |output.size|
// bytes. Returns strings consumed in |out_strings_consumed| and bytes written
// in |out_bytes_written|.
//
// A token string is "consumed" (counted in |out_strings_consumed|) only if it
// has been fully processed or successfully buffered into internal state. If a
// token cannot be accepted (e.g., output buffer full and no internal buffering
// available), it is not counted. The caller should retry with the unconsumed
// tokens after draining output.
//
// May consume fewer strings than count if the output buffer is full or the
// decoder is waiting for more tokens to complete a sequence (e.g., byte
// fallback). May write fewer bytes than output capacity if input is exhausted
// or the decoder needs more tokens to produce output.
static inline iree_status_t iree_tokenizer_decoder_state_process(
iree_tokenizer_decoder_state_t* state,
iree_tokenizer_string_list_t token_strings,
iree_mutable_string_view_t output, iree_host_size_t* out_strings_consumed,
iree_host_size_t* out_bytes_written) {
return state->decoder->vtable->state_process(
state, token_strings, output, out_strings_consumed, out_bytes_written);
}
// Finalizes the stream, flushing all buffered data. Call after all tokens
// have been provided via state_process(). May need multiple calls if the
// output buffer is too small for buffered data.
static inline iree_status_t iree_tokenizer_decoder_state_finalize(
iree_tokenizer_decoder_state_t* state, iree_mutable_string_view_t output,
iree_host_size_t* out_written) {
return state->decoder->vtable->state_finalize(state, output, out_written);
}
// Returns true if state has pending data that would produce output on
// finalize. Useful for checking if more finalize() calls are needed.
static inline bool iree_tokenizer_decoder_state_has_pending(
const iree_tokenizer_decoder_state_t* state) {
return state->decoder->vtable->state_has_pending(state);
}
#ifdef __cplusplus
} // extern "C"
#endif
#endif // IREE_TOKENIZER_DECODER_H_