Current section
Files
Jump to
Current section
Files
lib/bumblebee/text/generation/logits_processing.ex
defmodule Bumblebee.Text.Generation.LogitsProcessing do
@moduledoc false
import Nx.Defn
deftransform suppressed_tokens_processor(logits, _context, opts \\ []) do
opts = Keyword.validate!(opts, [:suppressed_token_ids])
indices = opts[:suppressed_token_ids] |> Nx.tensor() |> Nx.new_axis(-1)
values = Nx.broadcast(Nx.Constants.neg_infinity(Nx.type(logits)), {Nx.size(indices)})
Nx.indexed_put(logits, indices, values)
end
defn bos_token_processor(logits, context, opts \\ []) do
opts = keyword!(opts, [:bos_token_id])
bos_token_id = opts[:bos_token_id]
if context.length == 1 do
force_token_id(logits, bos_token_id)
else
logits
end
end
defn eos_token_processor(logits, context, opts \\ []) do
opts = keyword!(opts, [:eos_token_id])
eos_token_id = opts[:eos_token_id]
max_length = Nx.axis_size(context.sequence, 0)
if context.length == max_length - 1 do
force_token_id(logits, eos_token_id)
else
logits
end
end
defn forced_tokens_processor(logits, context, opts \\ []) do
opts = keyword!(opts, [:forced_token_ids])
forced_token_ids(logits, context, opts[:forced_token_ids])
end
deftransformp forced_token_ids(logits, context, forced_token_ids) do
clauses =
for {idx, token_id} <- forced_token_ids do
{Nx.equal(context.length, idx), force_token_id(logits, token_id)}
end
# Note that we can't use defn ifs inside transform, so we build
# the expression directly
Nx.Defn.Expr.cond(clauses, logits)
end
defn min_length_processor(logits, context, opts \\ []) do
opts = keyword!(opts, [:eos_token_ids, :min_length_fun])
eos_token_ids = opts[:eos_token_ids]
min_length_fun = opts[:min_length_fun]
min_length = min_length_fun.(context.input_length)
if context.length < min_length do
ignore_token_ids(logits, eos_token_ids)
else
logits
end
end
defn no_repeat_ngram_processor(logits, context, opts \\ []) do
opts = keyword!(opts, [:ngram_length])
ngram_length = opts[:ngram_length]
if context.length + 1 < ngram_length do
logits
else
# Given a sequence of last {ngram_length - 1} tokens, we look
# for prior occurrences of that sequence and we want to make the
# subsequent token ignored. This way the n-gram is not repeated
# this time around
ngram_but_one_length = ngram_length - 1
last_ngram_but_one =
Nx.slice_along_axis(
context.sequence,
context.length - ngram_but_one_length,
ngram_but_one_length,
axis: 0
)
{_, _, _, _, logits} =
while {i = 0, last_ngram_but_one, sequence = context.sequence, length = context.length,
logits},
i + ngram_but_one_length < length do
ngram_but_one = Nx.slice_along_axis(sequence, i, ngram_but_one_length, axis: 0)
token_id = sequence[i + ngram_but_one_length]
indices = Nx.new_axis(token_id, -1)
match? = Nx.all(ngram_but_one == last_ngram_but_one)
updates = Nx.select(match?, Nx.Constants.neg_infinity(Nx.type(logits)), 0)
logits = Nx.indexed_add(logits, indices, updates)
{i + 1, last_ngram_but_one, sequence, length, logits}
end
logits
end
end
deftransformp force_token_id(logits, token_id) do
logits
|> Nx.fill(Nx.Constants.neg_infinity(), type: Nx.type(logits))
|> Nx.put_slice([token_id], Nx.tensor([0], type: Nx.type(logits)))
end
deftransformp ignore_token_id(logits, token_id) do
Nx.put_slice(
logits,
[token_id],
Nx.broadcast(Nx.Constants.neg_infinity(Nx.type(logits)), {1})
)
end
deftransformp ignore_token_ids(logits, token_ids) do
Enum.reduce(token_ids, logits, &ignore_token_id(&2, &1))
end
defn temperature_processor(logits, _context, opts \\ []) do
opts = keyword!(opts, [:temperature])
temperature = opts[:temperature]
logits / temperature
end
# Processors manipulating the probability distribution
defn top_k_processor(logits, _context, opts \\ []) do
opts = keyword!(opts, [:top_k])
top_k = opts[:top_k]
{top_k_logits, _} = Nx.top_k(logits, k: top_k)
kth_logit = top_k_logits[-1]
Nx.select(logits < kth_logit, Nx.Constants.neg_infinity(Nx.type(logits)), logits)
end
defn top_p_processor(logits, _context, opts \\ []) do
opts = keyword!(opts, [:top_p])
top_p = opts[:top_p]
sorted_idx = Nx.argsort(logits)
cumulative_scores =
logits
|> Nx.take_along_axis(sorted_idx)
|> Axon.Activations.softmax()
|> Nx.cumulative_sum()
ordered_ignore_mask = cumulative_scores <= 1 - top_p
# Arrange the mask back into the original logits order
ignore_mask =
Nx.indexed_put(
Nx.fill(ordered_ignore_mask, 0),
Nx.new_axis(sorted_idx, -1),
Nx.flatten(ordered_ignore_mask)
)
Nx.select(ignore_mask, Nx.Constants.neg_infinity(Nx.type(logits)), logits)
end
defn whisper_timestamp_processor(logits, context, opts \\ []) do
opts =
keyword!(opts, [
:eos_token_id,
:forced_token_ids,
:no_timestamps_token_id,
:timestamp_begin_id
])
eos_token_id = opts[:eos_token_id]
no_timestamps_token_id = opts[:no_timestamps_token_id]
timestamp_begin_id = opts[:timestamp_begin_id]
begin_idx = begin_idx(opts[:forced_token_ids])
# Ensure the no-timestamps token is never taken
logits = ignore_token_id(logits, no_timestamps_token_id)
cond do
context.length < begin_idx ->
logits
context.length == begin_idx ->
# Output the starting timestamp
force_token_id(logits, timestamp_begin_id)
true ->
logits
|> force_timestamp_pair(context, begin_idx, eos_token_id, timestamp_begin_id)
|> maybe_force_timestamp(timestamp_begin_id)
end
end
defnp force_timestamp_pair(logits, context, begin_idx, eos_token_id, timestamp_begin_id) do
# Force timestamp tokens to appear in pairs, end followed by
# start, except directly before the EOS token
prev_was_timestamp? =
if context.length - begin_idx >= 1 do
context.sequence[context.length - 1] >= timestamp_begin_id
else
Nx.tensor(false)
end
# Either second to last was timestamp or is out of range
prev_was_timestamp_start? =
if context.length - begin_idx >= 2 do
context.sequence[context.length - 2] >= timestamp_begin_id
else
Nx.tensor(true)
end
ignore_mask =
Nx.logical_and(
prev_was_timestamp?,
Nx.select(
Nx.broadcast(Nx.new_axis(prev_was_timestamp_start?, -1), Nx.shape(logits)),
# Force non-timestamp
Nx.iota(Nx.shape(logits)) >= timestamp_begin_id,
# Force non-normal token
Nx.iota(Nx.shape(logits)) < eos_token_id
)
)
Nx.select(ignore_mask, Nx.Constants.neg_infinity(Nx.type(logits)), logits)
end
defnp maybe_force_timestamp(logits, timestamp_begin_id) do
# If the total probability of all timestamps exceeds any regular
# token, we force a timestamp
log_probabilities = Axon.Activations.log_softmax(logits)
timestamp_log_probabilities = log_probabilities[timestamp_begin_id..-1//1]
# TODO: use log_sumexp on Axon v0.6.1
# timestamp_log_probability = Axon.Activations.log_sumexp(timestamp_log_probabilities)
timestamp_log_probability = timestamp_log_probabilities |> Nx.exp() |> Nx.sum() |> Nx.log()
token_log_probabilities = log_probabilities[0..(timestamp_begin_id - 1)//1]
max_token_log_probability = Nx.reduce_max(token_log_probabilities)
force_timestamp_mask = timestamp_log_probability > max_token_log_probability
tokens_mask = Nx.iota(Nx.shape(logits)) < timestamp_begin_id
ignore_mask = force_timestamp_mask and tokens_mask
Nx.select(ignore_mask, Nx.Constants.neg_infinity(Nx.type(logits)), logits)
end
deftransformp begin_idx(forced_token_ids) do
case List.last(forced_token_ids) do
nil -> 1
{idx, _token_id} -> idx + 1
end
end
end