Packages

Fast Hugging Face tokenizer.json, OpenAI tiktoken, and SentencePiece model bindings for Elixir via the IREE tokenizer runtime

Current section

Files

Jump to
iree_tokenizers lib iree tokenizers model bpe.ex
Raw

lib/iree/tokenizers/model/bpe.ex

defmodule IREE.Tokenizers.Model.BPE do
@moduledoc """
BPE model spec compatible with `IREE.Tokenizers.Tokenizer.init/1`.
"""
alias IREE.Tokenizers.Model
@type options :: [
cache_capacity: number(),
dropout: float(),
unk_token: String.t(),
continuing_subword_prefix: String.t(),
end_of_word_suffix: String.t(),
fuse_unk: boolean(),
byte_fallback: boolean()
]
@spec init(%{String.t() => integer()}, [{String.t(), String.t()}], options()) ::
{:ok, Model.t()}
def init(vocab, merges, opts \\ []) when is_map(vocab) and is_list(merges) do
opts =
Keyword.validate!(opts,
cache_capacity: 10_000,
dropout: nil,
unk_token: nil,
continuing_subword_prefix: nil,
end_of_word_suffix: nil,
fuse_unk: false,
byte_fallback: false
)
spec = %{
"vocab" => Map.new(vocab),
"merges" => Enum.map(merges, fn {left, right} -> [left, right] end),
"unk_token" => opts[:unk_token],
"continuing_subword_prefix" => opts[:continuing_subword_prefix],
"end_of_word_suffix" => opts[:end_of_word_suffix],
"fuse_unk" => opts[:fuse_unk],
"byte_fallback" => opts[:byte_fallback]
}
info = %{
"model_type" => "BPE",
"vocab_size" => map_size(vocab),
"merge_count" => length(merges),
"unk_token" => opts[:unk_token],
"continuing_subword_prefix" => opts[:continuing_subword_prefix],
"end_of_word_suffix" => opts[:end_of_word_suffix],
"fuse_unk" => opts[:fuse_unk],
"byte_fallback" => opts[:byte_fallback]
}
{:ok, %Model{type: "BPE", spec: spec, info: info}}
end
@spec empty() :: {:ok, Model.t()}
def empty, do: init(%{}, [])
@spec from_file(String.t(), String.t(), options()) :: {:ok, Model.t()} | {:error, term()}
def from_file(vocab_path, merges_path, opts \\ []) do
with {:ok, vocab_json} <- File.read(vocab_path),
{:ok, vocab} <- Jason.decode(vocab_json),
true <- is_map(vocab) or {:error, :invalid_vocab},
{:ok, merges_text} <- File.read(merges_path) do
merges =
merges_text
|> String.split("\n", trim: true)
|> Enum.reject(&String.starts_with?(&1, "#"))
|> Enum.map(fn line ->
case String.split(line, ~r/\s+/, trim: true) do
[left, right] -> {left, right}
_ -> raise ArgumentError, "invalid merges line: #{inspect(line)}"
end
end)
init(vocab, merges, opts)
else
{:error, reason} -> {:error, reason}
false -> {:error, :invalid_vocab}
error -> {:error, error}
end
rescue
error in [ArgumentError, Jason.DecodeError] -> {:error, error}
end
end