Current section

Files

Jump to
bumblebee lib bumblebee text phi3.ex
Raw

lib/bumblebee/text/phi3.ex

defmodule Bumblebee.Text.Phi3 do
alias Bumblebee.Shared
options =
[
vocab_size: [
default: 51200,
doc: """
the vocabulary size of the token embedding. This corresponds to the number of distinct
tokens that can be represented in model input and output
"""
],
max_positions: [
default: 2048,
doc: """
the vocabulary size of the position embedding. This corresponds to the maximum sequence
length that this model can process. Typically this is set to a large value just in case,
such as 512, 1024 or 2048
"""
],
hidden_size: [
default: 2048,
doc: "the dimensionality of hidden layers"
],
intermediate_size: [
default: 8192,
doc: "the dimensionality of intermediate layers"
],
num_blocks: [
default: 24,
doc: "the number of Transformer blocks in the model"
],
num_attention_heads: [
default: 32,
doc: "the number of attention heads for each attention layer in the model"
],
num_key_value_heads: [
default: nil,
doc: """
the number of key-value heads used to implement Grouped Query Attention. If
this value is set to the same as the number of attention heads, it will use
regular MHA. If it's set to 1, it will use MQA, otherwise it uses Grouped Query
Attention
"""
],
attention_window_size: [
default: 262_144,
doc: "window size for both sides of the sliding attention window"
],
activation: [
default: :gelu_approx_tanh,
doc: "the activation function"
],
rotary_embedding_percentage: [
default: 1.0,
doc: "percentage of hidden dimensions to allocate to rotary embeddings"
],
rotary_embedding_base: [
default: 10_000,
doc: "base for computing rotary embedding frequency"
],
rotary_embedding_scaling_strategy: [
default: nil,
doc: """
scaling configuration for rotary embedding. Currently the supported values are:
* `%{type: :longrope, short_factor: list(number()), long_factor: list(number()), original_max_positions: pos_integer()}`
"""
],
layer_norm_epsilon: [
default: 1.0e-12,
doc: "the epsilon used by RMS normalization layers"
],
initializer_scale: [
default: 0.02,
doc:
"the standard deviation of the normal initializer used for initializing kernel parameters"
]
] ++
Shared.common_options([:num_labels, :id_to_label]) ++
Shared.token_options(pad_token_id: 32000)
@moduledoc """
Phi-3 model family.
## Architectures
* `:base` - plain Phi-3 without any head on top
* `:for_causal_language_modeling` - Phi-3 with a language modeling
head. The head returns logits for each token in the original
sequence
* `:for_sequence_classification` - Phi-3 with a sequence
classification head. The head returns logits corresponding to
possible classes
* `:for_token_classification` - Phi-3 with a token classification
head. The head returns logits for each token in the original
sequence
## Inputs
* `"input_ids"` - `{batch_size, sequence_length}`
Indices of input sequence tokens in the vocabulary.
* `"attention_mask"` - `{batch_size, sequence_length}`
Mask indicating which tokens to attend to. This is used to ignore
padding tokens, which are added when processing a batch of sequences
with different length.
* `"position_ids"` - `{batch_size, sequence_length}`
Indices of positions of each input sequence tokens in the position
embeddings.
* `"attention_head_mask"` - `{encoder_num_blocks, encoder_num_attention_heads}`
Mask to nullify selected heads of the self-attention blocks in
the encoder.
* `"input_embeddings"` - `{batch_size, sequence_length, hidden_size}`
Embedded representation of `"input_ids"`, which can be specified
for more control over how `"input_ids"` are embedded than the
model's internal embedding lookup. If `"input_embeddings"` are present,
then `"input_ids"` will be ignored.
* `"cache"`
A container with cached layer results used to speed up sequential
decoding (autoregression). With cache, certain hidden states are
taken from the cache, rather than recomputed on every decoding
pass. The cache should be treated as opaque and initialized with
`Bumblebee.Text.Generation.init_cache/4`.
## Global layer options
#{Shared.global_layer_options_doc([:output_hidden_states, :output_attentions])}
## Configuration
#{Shared.options_doc(options)}
"""
defstruct [architecture: :base] ++ Shared.option_defaults(options)
@behaviour Bumblebee.ModelSpec
@behaviour Bumblebee.Configurable
@behaviour Bumblebee.Text.Generation
import Bumblebee.Utils.Model, only: [join: 2]
alias Bumblebee.Layers
@impl true
def architectures(),
do: [
:base,
:for_causal_language_modeling,
:for_sequence_classification,
:for_token_classification
]
@impl true
def config(spec, opts) do
spec
|> Shared.put_config_attrs(opts)
|> Shared.validate_label_options()
end
@impl true
def input_template(_spec) do
%{
"input_ids" => Nx.template({1, 1}, :s64)
}
end
@impl true
def init_cache(spec, batch_size, max_length, _inputs) do
Layers.Decoder.init_cache(batch_size, max_length,
hidden_size: spec.hidden_size,
decoder_num_attention_heads: spec.num_attention_heads,
decoder_num_blocks: spec.num_blocks
)
end
@impl true
def traverse_cache(_spec, cache, fun) do
Layers.Decoder.traverse_cache(cache, fun)
end
@impl true
def model(%__MODULE__{architecture: :base} = spec) do
inputs = inputs(spec)
inputs
|> core(spec)
|> Layers.output()
end
def model(%__MODULE__{architecture: :for_causal_language_modeling} = spec) do
inputs = inputs(spec)
outputs = core(inputs, spec)
logits = language_modeling_head(outputs.hidden_state, spec, name: "language_modeling_head")
Layers.output(%{
logits: logits,
hidden_states: outputs.hidden_states,
attentions: outputs.attentions,
cache: outputs.cache
})
end
def model(%__MODULE__{architecture: :for_sequence_classification} = spec) do
inputs = inputs(spec)
outputs = core(inputs, spec)
logits =
Axon.dense(outputs.hidden_state, spec.num_labels,
kernel_initializer: kernel_initializer(spec),
name: "sequence_classification_head.output",
use_bias: false
)
pooled_logits =
Layers.if_present inputs["input_ids"] do
Axon.layer(
fn logits, input_ids, _opts ->
indices =
input_ids
|> Nx.not_equal(spec.pad_token_id)
|> Nx.sum(axes: [-1])
|> Nx.subtract(1)
|> Nx.as_type({:s, 64})
Bumblebee.Utils.Nx.batched_take(logits, indices)
end,
[logits, inputs["input_ids"]]
)
else
Layers.take_token(logits, axis: 1, index: -1)
end
Layers.output(%{
logits: pooled_logits,
hidden_states: outputs.hidden_states,
attentions: outputs.attentions,
cache: outputs.cache
})
end
def model(%__MODULE__{architecture: :for_token_classification} = spec) do
inputs = inputs(spec)
outputs = core(inputs, spec)
logits =
outputs.hidden_state
|> Axon.dropout(
rate: 0.1,
name: "token_classification_head.dropout"
)
|> Axon.dense(spec.num_labels,
kernel_initializer: kernel_initializer(spec),
name: "token_classification_head.output"
)
Layers.output(%{
logits: logits,
hidden_states: outputs.hidden_states,
attentions: outputs.attentions
})
end
defp inputs(spec) do
shape = {nil, nil}
hidden_shape = {nil, nil, spec.hidden_size}
attention_head_mask_shape = {spec.num_blocks, spec.num_attention_heads}
Bumblebee.Utils.Model.inputs_to_map([
Axon.input("input_ids", optional: true, shape: shape),
Axon.input("attention_mask", optional: true, shape: shape),
Axon.input("position_ids", optional: true, shape: shape),
Axon.input("attention_head_mask", optional: true, shape: attention_head_mask_shape),
Axon.input("input_embeddings", optional: true, shape: hidden_shape),
Axon.input("cache", optional: true)
])
end
defp core(inputs, spec) do
embeddings =
embedder(
inputs["input_ids"],
inputs["input_embeddings"],
spec,
name: "embedder"
)
position_ids =
Layers.default inputs["position_ids"] do
Layers.default_position_ids(embeddings)
end
decoder_outputs =
decoder(
embeddings,
position_ids,
inputs["attention_mask"],
inputs["attention_head_mask"],
inputs["cache"],
spec,
name: "decoder"
)
hidden_state =
Layers.rms_norm(decoder_outputs.hidden_state,
name: "output_norm",
epsilon: spec.layer_norm_epsilon
)
%{
hidden_state: hidden_state,
hidden_states: Layers.append(decoder_outputs.hidden_states, hidden_state),
attentions: decoder_outputs.attentions,
cache: decoder_outputs.cache
}
end
defp embedder(input_ids, input_embeddings, spec, opts) do
name = opts[:name]
# TODO: Axon needs a way to specify ignoring pad tokens
# in gradient
Layers.default input_embeddings do
Axon.embedding(input_ids, spec.vocab_size, spec.hidden_size,
kernel_initializer: kernel_initializer(spec),
name: join(name, "token_embedding")
)
end
end
defp decoder(
hidden_state,
position_ids,
attention_mask,
attention_head_mask,
cache,
spec,
opts
) do
name = opts[:name]
Layers.Transformer.blocks(hidden_state,
attention_mask: attention_mask,
attention_head_mask: attention_head_mask,
cache: cache,
num_blocks: spec.num_blocks,
num_attention_heads: spec.num_attention_heads,
num_key_value_heads: spec.num_key_value_heads,
hidden_size: spec.hidden_size,
kernel_initializer: kernel_initializer(spec),
layer_norm: &Layers.rms_norm(&1, name: &2, epsilon: spec.layer_norm_epsilon),
ffn:
&gated_ffn(&1, spec.intermediate_size, spec.hidden_size,
name: &2,
activation: spec.activation
),
block_type: :norm_first,
causal: true,
attention_window_size:
spec.attention_window_size && {spec.attention_window_size, spec.attention_window_size},
rotary_embedding: [
position_ids: position_ids,
max_positions: spec.max_positions,
base: spec.rotary_embedding_base,
percentage: spec.rotary_embedding_percentage,
scaling_strategy: spec.rotary_embedding_scaling_strategy
],
query_use_bias: false,
key_use_bias: false,
value_use_bias: false,
output_use_bias: false,
name: join(name, "blocks")
)
end
defp gated_ffn(hidden_state, intermediate_size, output_size, opts) do
name = opts[:name]
activation = opts[:activation]
intermediate =
Axon.dense(hidden_state, intermediate_size,
name: join(name, "intermediate"),
use_bias: false
)
gate = Axon.dense(hidden_state, intermediate_size, name: join(name, "gate"), use_bias: false)
hidden_state = Axon.multiply(intermediate, Axon.activation(gate, activation))
Axon.dense(hidden_state, output_size, name: join(name, "output"), use_bias: false)
end
defp language_modeling_head(hidden_state, spec, opts) do
name = opts[:name]
# TODO: Tie lm-head to word embedding as a spec option
Layers.dense_transposed(hidden_state, spec.vocab_size,
kernel_initializer: kernel_initializer(spec),
name: join(name, "output"),
use_bias: false
)
end
defp kernel_initializer(spec) do
Axon.Initializers.normal(scale: spec.initializer_scale)
end
defimpl Bumblebee.HuggingFace.Transformers.Config do
def load(spec, data) do
import Shared.Converters
scaling_strategy_converter = fn name, value ->
original_max_positions = data["original_max_position_embeddings"]
case value do
%{"type" => type, "long_factor" => long_factor, "short_factor" => short_factor}
when type in ["longrope", "su", "yarn"] and
is_list(long_factor) and is_list(short_factor) and
is_number(original_max_positions) ->
{:ok,
%{
type: :longrope,
long_factor: long_factor,
short_factor: short_factor,
original_max_positions: original_max_positions
}}
_other ->
{:error, "invalid format for #{inspect(name)}, got: #{inspect(value)}"}
end
end
opts =
convert!(data,
vocab_size: {"vocab_size", number()},
max_positions: {"max_position_embeddings", number()},
hidden_size: {"hidden_size", number()},
num_blocks: {"num_hidden_layers", number()},
num_attention_heads: {"num_attention_heads", number()},
num_key_value_heads: {"num_key_value_heads", number()},
attention_window_size: {"sliding_window", optional(number())},
intermediate_size: {"intermediate_size", number()},
activation: {"hidden_act", activation()},
rotary_embedding_percentage: {"partial_rotary_factor", number()},
rotary_embedding_base: {"rope_theta", number()},
rotary_embedding_scaling_strategy:
{"rope_scaling", optional(scaling_strategy_converter)},
initializer_scale: {"initializer_range", number()},
layer_norm_epsilon: {"rms_norm_eps", number()}
) ++ Shared.common_options_from_transformers(data, spec)
@for.config(spec, opts)
end
end
defimpl Bumblebee.HuggingFace.Transformers.Model do
def params_mapping(spec) do
out_template =
{[spec.num_attention_heads, spec.num_key_value_heads, spec.num_key_value_heads], :auto}
%{
"embedder.token_embedding" => "model.embed_tokens",
"decoder.blocks.{n}.self_attention.query" =>
Shared.sliced_dense_params_source(
"model.layers.{n}.self_attn.qkv_proj",
out_template,
0
),
"decoder.blocks.{n}.self_attention.key" =>
Shared.sliced_dense_params_source(
"model.layers.{n}.self_attn.qkv_proj",
out_template,
1
),
"decoder.blocks.{n}.self_attention.value" =>
Shared.sliced_dense_params_source(
"model.layers.{n}.self_attn.qkv_proj",
out_template,
2
),
"decoder.blocks.{n}.self_attention.output" => "model.layers.{n}.self_attn.o_proj",
"decoder.blocks.{n}.self_attention_norm" => "model.layers.{n}.input_layernorm",
"decoder.blocks.{n}.ffn.gate" =>
Shared.sliced_dense_params_source(
"model.layers.{n}.mlp.gate_up_proj",
{[1, 1], :auto},
0
),
"decoder.blocks.{n}.ffn.intermediate" =>
Shared.sliced_dense_params_source(
"model.layers.{n}.mlp.gate_up_proj",
{[1, 1], :auto},
1
),
"decoder.blocks.{n}.ffn.output" => "model.layers.{n}.mlp.down_proj",
"decoder.blocks.{n}.output_norm" => "model.layers.{n}.post_attention_layernorm",
"output_norm" => "model.norm",
"language_modeling_head.output" => "lm_head",
"sequence_classification_head.output" => "score",
"token_classification_head.output" => "classifier"
}
end
end
end