Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions keras_hub/src/utils/transformers/convert_roberta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import numpy as np

from keras_hub.src.models.roberta.roberta_backbone import RobertaBackbone
from keras_hub.src.utils.preset_utils import get_file

backbone_cls = RobertaBackbone

# RoBERTa reserves the first `padding_idx + 1` position embedding rows
# (padding_idx=1 for RoBERTa) and starts real positions at index 2.
_POSITION_OFFSET = 2


def convert_backbone_config(transformers_config):
return {
"vocabulary_size": transformers_config["vocab_size"],
"num_layers": transformers_config["num_hidden_layers"],
"num_heads": transformers_config["num_attention_heads"],
"hidden_dim": transformers_config["hidden_size"],
"intermediate_dim": transformers_config["intermediate_size"],
"dropout": transformers_config["hidden_dropout_prob"],
"max_sequence_length": transformers_config["max_position_embeddings"]
- _POSITION_OFFSET,
}


def transpose_and_reshape(x, shape):
return np.reshape(np.transpose(x), shape)


def convert_weights(backbone, loader, transformers_config):
# Embeddings
loader.port_weight(
keras_variable=backbone.token_embedding.embeddings,
hf_weight_key="roberta.embeddings.word_embeddings.weight",
)
loader.port_weight(
keras_variable=backbone.embeddings.position_embedding.position_embeddings,
hf_weight_key="roberta.embeddings.position_embeddings.weight",
hook_fn=lambda x, _: x[_POSITION_OFFSET:, :],
)
loader.port_weight(
keras_variable=backbone.embeddings_layer_norm.gamma,
hf_weight_key="roberta.embeddings.LayerNorm.weight",
)
loader.port_weight(
keras_variable=backbone.embeddings_layer_norm.beta,
hf_weight_key="roberta.embeddings.LayerNorm.bias",
)

# Attention blocks
for index in range(backbone.num_layers):
encoder_layer = backbone.transformer_layers[index]
hf_prefix = f"roberta.encoder.layer.{index}"

# Attention layers
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.query_dense.kernel,
hf_weight_key=f"{hf_prefix}.attention.self.query.weight",
hook_fn=transpose_and_reshape,
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.query_dense.bias,
hf_weight_key=f"{hf_prefix}.attention.self.query.bias",
hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.key_dense.kernel,
hf_weight_key=f"{hf_prefix}.attention.self.key.weight",
hook_fn=transpose_and_reshape,
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.key_dense.bias,
hf_weight_key=f"{hf_prefix}.attention.self.key.bias",
hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.value_dense.kernel,
hf_weight_key=f"{hf_prefix}.attention.self.value.weight",
hook_fn=transpose_and_reshape,
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.value_dense.bias,
hf_weight_key=f"{hf_prefix}.attention.self.value.bias",
hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.output_dense.kernel,
hf_weight_key=f"{hf_prefix}.attention.output.dense.weight",
hook_fn=transpose_and_reshape,
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer.output_dense.bias,
hf_weight_key=f"{hf_prefix}.attention.output.dense.bias",
hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
)
Comment on lines +56 to +95

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The attention dense layers in Keras's MultiHeadAttention (used by TransformerEncoder) are private attributes prefixed with an underscore (e.g., _query_dense, _key_dense, _value_dense, _output_dense). Accessing them as public attributes (e.g., query_dense) will raise an AttributeError at runtime when loading weights. Please restore the leading underscores to match the Keras implementation and the original checkpoint conversion script.

        # Attention layers
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._query_dense.kernel,
            hf_weight_key=f"{hf_prefix}.attention.self.query.weight",
            hook_fn=transpose_and_reshape,
        )
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._query_dense.bias,
            hf_weight_key=f"{hf_prefix}.attention.self.query.bias",
            hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
        )
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._key_dense.kernel,
            hf_weight_key=f"{hf_prefix}.attention.self.key.weight",
            hook_fn=transpose_and_reshape,
        )
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._key_dense.bias,
            hf_weight_key=f"{hf_prefix}.attention.self.key.bias",
            hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
        )
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._value_dense.kernel,
            hf_weight_key=f"{hf_prefix}.attention.self.value.weight",
            hook_fn=transpose_and_reshape,
        )
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._value_dense.bias,
            hf_weight_key=f"{hf_prefix}.attention.self.value.bias",
            hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
        )
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._output_dense.kernel,
            hf_weight_key=f"{hf_prefix}.attention.output.dense.weight",
            hook_fn=transpose_and_reshape,
        )
        loader.port_weight(
            keras_variable=encoder_layer._self_attention_layer._output_dense.bias,
            hf_weight_key=f"{hf_prefix}.attention.output.dense.bias",
            hook_fn=lambda hf_tensor, shape: np.reshape(hf_tensor, shape),
        )
References
  1. Respect existing repo patterns and ensure robustness by avoiding fragile code that raises runtime exceptions. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

query_dense/key_dense/value_dense/output_dense are public property aliases for the private attrs in Keras's MultiHeadAttention, so the current code is correct.The tests already pass without an AttributeError.

# Attention layer norm.
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer_norm.gamma,
hf_weight_key=f"{hf_prefix}.attention.output.LayerNorm.weight",
)
loader.port_weight(
keras_variable=encoder_layer._self_attention_layer_norm.beta,
hf_weight_key=f"{hf_prefix}.attention.output.LayerNorm.bias",
)
# MLP layers
loader.port_weight(
keras_variable=encoder_layer._feedforward_intermediate_dense.kernel,
hf_weight_key=f"{hf_prefix}.intermediate.dense.weight",
hook_fn=lambda hf_tensor, _: np.transpose(hf_tensor, axes=(1, 0)),
)
loader.port_weight(
keras_variable=encoder_layer._feedforward_intermediate_dense.bias,
hf_weight_key=f"{hf_prefix}.intermediate.dense.bias",
)
loader.port_weight(
keras_variable=encoder_layer._feedforward_output_dense.kernel,
hf_weight_key=f"{hf_prefix}.output.dense.weight",
hook_fn=lambda hf_tensor, _: np.transpose(hf_tensor, axes=(1, 0)),
)
loader.port_weight(
keras_variable=encoder_layer._feedforward_output_dense.bias,
hf_weight_key=f"{hf_prefix}.output.dense.bias",
)
# Output layer norm.
loader.port_weight(
keras_variable=encoder_layer._feedforward_layer_norm.gamma,
hf_weight_key=f"{hf_prefix}.output.LayerNorm.weight",
)
loader.port_weight(
keras_variable=encoder_layer._feedforward_layer_norm.beta,
hf_weight_key=f"{hf_prefix}.output.LayerNorm.bias",
)


def convert_tokenizer(cls, preset, **kwargs):
vocab_file = get_file(preset, "vocab.json")
merges_file = get_file(preset, "merges.txt")
return cls(
vocabulary=vocab_file,
merges=merges_file,
**kwargs,
)
33 changes: 33 additions & 0 deletions keras_hub/src/utils/transformers/convert_roberta_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest

from keras_hub.src.models.backbone import Backbone
from keras_hub.src.models.roberta.roberta_backbone import RobertaBackbone
from keras_hub.src.models.roberta.roberta_text_classifier import (
RobertaTextClassifier,
)
from keras_hub.src.models.text_classifier import TextClassifier
from keras_hub.src.tests.test_case import TestCase


class TestRobertaConverter(TestCase):
@pytest.mark.extra_large
def test_convert_tiny_preset(self):
model = RobertaTextClassifier.from_preset(
"hf://FacebookAI/roberta-base", num_classes=2
)
Comment on lines +14 to +17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test test_convert_tiny_preset is intended to verify the converter with a lightweight preset, but it currently loads the full-sized FacebookAI/roberta-base model (125M parameters, ~500MB download). This makes the unit test extremely slow and resource-intensive. Please use a tiny random model from Hugging Face, such as hf://hf-internal-testing/tiny-random-RobertaModel, to keep the test fast and lightweight.

Suggested change
def test_convert_tiny_preset(self):
model = RobertaTextClassifier.from_preset(
"hf://FacebookAI/roberta-base", num_classes=2
)
def test_convert_tiny_preset(self):
model = RobertaTextClassifier.from_preset(
"hf://hf-internal-testing/tiny-random-RobertaModel", num_classes=2
)
References
  1. When a test is set up, the test inputs should be small to allow for fast testing. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the model size is under 500 mb, it should be fine. also tried the suggested preset anyways, there was an error.

prompt = "That movies was terrible."
model.predict([prompt])

@pytest.mark.large
def test_class_detection(self):
model = TextClassifier.from_preset(
"hf://FacebookAI/roberta-base",
num_classes=2,
load_weights=False,
)
self.assertIsInstance(model, RobertaTextClassifier)
model = Backbone.from_preset(
"hf://FacebookAI/roberta-base",
load_weights=False,
)
self.assertIsInstance(model, RobertaBackbone)
3 changes: 3 additions & 0 deletions keras_hub/src/utils/transformers/preset_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from keras_hub.src.utils.transformers import convert_qwen3_5_moe
from keras_hub.src.utils.transformers import convert_qwen3_moe
from keras_hub.src.utils.transformers import convert_qwen_moe
from keras_hub.src.utils.transformers import convert_roberta
from keras_hub.src.utils.transformers import convert_sam3
from keras_hub.src.utils.transformers import convert_smollm3
from keras_hub.src.utils.transformers import convert_t5gemma
Expand Down Expand Up @@ -102,6 +103,8 @@ def __init__(self, preset, config):
self.converter = convert_qwen3_5_moe
elif model_type == "qwen3_5":
self.converter = convert_qwen3_5
elif model_type == "roberta":
self.converter = convert_roberta
elif model_type == "sam3_video":
self.converter = convert_sam3
elif model_type == "xlm-roberta":
Expand Down
Loading
Loading