Skip to content

model: Add GTE to Transformers - #48416

Open
harshaljanjani wants to merge 9 commits into
huggingface:mainfrom
harshaljanjani:add-gte
Open

model: Add GTE to Transformers#48416
harshaljanjani wants to merge 9 commits into
huggingface:mainfrom
harshaljanjani:add-gte

Conversation

@harshaljanjani

@harshaljanjani harshaljanjani commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

CPU CI GPU run-slow

What does this PR do?

→ This PR adds GTE to Transformers!
→ Completes the GTE and Snowflake GTE items in #42738 (Snowflake/snowflake-arctic-embed-m-v2.0 ships the same architecture)

Model Checkpoints
Original Implementation
Paper

cc: @vasqu

Code Agent Policy

  • I confirm that this is not a pure code agent PR.

Before submitting

  • This PR adds a new model to Transformers.
  • Did you read the contributor guidelines, specifically the Pull Request section?
  • Was this discussed via a GitHub issue or the forum?
  • Did you make sure to update the documentation with your changes? Here are the documentation guidelines, and here are tips on formatting docstrings.
  • Did you add any necessary tests?
🤖 mlinter review state

@github-actions github-actions Bot left a comment

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.

Model linter — first pass

transformers-mlinter found 1 item(s) in the model files this PR touches. These are structural conventions a maintainer would otherwise flag by hand.

This is automated and advisory — it does not block merging.

rule count what it checks
TRF041 1 A config-gated branch must carry a # CODEPATH: note saying which checkpoints diverge.

Comment thread src/transformers/models/gte/modular_gte.py Outdated
@harshaljanjani
harshaljanjani marked this pull request as ready for review August 30, 2026 15:16

@vasqu vasqu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks very solid, biggest blocker are the conversion needed for the configs atm -> let's rather make PRs on the hub and use the revision to point to it

Comment thread docs/source/en/model_doc/deimv2.md Outdated

*Driven by the simple and effective Dense O2O, DEIM demonstrates faster convergence and enhanced performance. In this work, we extend it with DINOv3 features, resulting in DEIMv2. DEIMv2 spans eight model sizes from X to Atto, covering GPU, edge, and mobile deployment. For the X, L, M, and S variants, we adopt DINOv3-pretrained / distilled backbones and introduce a Spatial Tuning Adapter (STA), which efficiently converts DINOv3's single-scale output into multi-scale features and complements strong semantics with fine-grained details to enhance detection. For ultra-lightweight models (Nano, Pico, Femto, and Atto), we employ HGNetv2 with depth and width pruning to meet strict resource budgets. Together with a simplified decoder and an upgraded Dense O2O, this unified design enables DEIMv2 to achieve a superior performance-cost trade-off across diverse scenarios, establishing new state-of-the-art results. Notably, our largest model, DEIMv2-X, achieves 57.8 AP with only 50.3M parameters, surpassing prior X-scale models that require over 60M parameters for just 56.5 AP. On the compact side, DEIMv2-S is the first sub-10M model (9.71M) to exceed the 50 AP milestone on COCO, reaching 50.9 AP. Even the ultra-lightweight DEIMv2-Pico, with just 1.5M parameters, delivers 38.5 AP-matching YOLOv10-Nano (2.3M) with ~50% fewer parameters.*

This model was contributed by [Harshal Janjani](https://huggingface.co/harshaljanjani).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's move it to a different PR but definitely merging then to credit!

@harshaljanjani harshaljanjani Sep 1, 2026

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.

Raised #48448 🤗

Comment thread docs/source/en/model_doc/gte.md Outdated

## Usage examples

Embeddings are taken from the `[CLS]` token and normalized.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this not for sequence classification then? Or am I missing something?

Might be nicer to just follow the other bert likes a bit with pipeline usage etc

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.

So [CLS] is being used for pooling into an embedding, not classification here, as the model was trained. Classification is a separate model path with an added head. Rewrote it like bert and jina_embeddings_v3

Comment thread docs/source/en/model_doc/gte.md Outdated
Comment on lines +92 to +123
Fine-tuning uses the standard forward and backward pass:

```python
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer

model_id = "harshaljanjani/gte-multilingual-base-hf"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForSequenceClassification.from_pretrained(model_id, num_labels=2, device_map="auto")

inputs = tokenizer(["a positive review", "a negative review"], padding=True, return_tensors="pt").to(model.device)
labels = torch.tensor([1, 0], device=model.device)

loss = model(**inputs, labels=labels).loss
loss.backward()
```

The model is compatible with [`torch.compile`]:

```python
import torch
from transformers import AutoModel, AutoTokenizer

model_id = "harshaljanjani/gte-multilingual-base-hf"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModel.from_pretrained(model_id, device_map="auto")
model = torch.compile(model)

inputs = tokenizer("what is the capital of China?", return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model(**inputs)
```

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think those make more sense for e.g. audio models as they are more special but here we can focus on more casual usage with auto and pipeline

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.

Done :)


- GTE uses RoPE, so for correct positional encoding either use right padding (the default), or use left padding and prepare `position_ids` accordingly.
- `type_vocab_size` differs across checkpoints. `Alibaba-NLP/gte-base-en-v1.5` sets it to `0`, in which case no token type embedding is created and `token_type_ids` are ignored.
- The `gte-*-v1.5` and `gte-multilingual-*` checkpoints apply static NTK scaling on top of RoPE. It is expressed as a `linear` [`~modeling_rope_utils.RopeParameters`] entry whose `rope_theta` is the base scaled by the NTK factor.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Will see but ig we register in post init properly?

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.

Here, with the Hub PRs the configs now carry rope_parameters directly as you suggested :)

rope_theta = kwargs.pop("rope_theta", self.default_theta)

# GTE's static NTK scaling is exactly a linear scaling of `base * factor`.
if rope_scaling is not None and rope_scaling["type"] == "ntk":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ah yes ok perf

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we link an example for which model

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.

Resolved :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we are already submitting PRs with rope parameters directly, then we can directly do that there instead of overriding this method, no?

Comment on lines +171 to +173
def _init_weights(self, module):
# None of the inherited buffer initialisations apply, GTE keeps no such buffers.
PreTrainedModel._init_weights(self, module)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lets use attribute error instead then to not inherit anything

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.

Tried it, the converter only pops class attributes so a method name raises KeyError: '_init_weights' here

class GteForMaskedLM(JinaEmbeddingsV3ForMaskedLM):
_tied_weights_keys = {"lm_head.decoder.weight": "gte.embeddings.word_embeddings.weight"}

def __init__(self, config: GteConfig):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this override is not needed no? at max, maybe self.gte

@harshaljanjani harshaljanjani Sep 1, 2026

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 parent assigns the backbone to self.roberta which the converter can't rename, so super().__init__ + self.gte creates both backbones :(
Same as nomic

GteForMultipleChoice,
GteForQuestionAnswering,
GteForSequenceClassification,
GteForTokenClassification,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Imo we could reduce a bit as in nomic bert iirc. not all the for are super used so could make maintenance a bit easier

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.

Done, dropped ForQuestionAnswering and ForMultipleChoice so the set matches Nomic's four.

Comment thread tests/models/gte/test_modeling_gte.py Outdated
class GteModelIntegrationTest(unittest.TestCase):
sentences = ["Plants create oxygen.", "Photosynthesis is a process where plants create oxygen."]

# TODO: Point these back at Alibaba-NLP and Snowflake once their configs declare `model_type: "gte"`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we have to submit conversions either way, we could also just add the rope parameters directly to the configs no?

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.

Comment thread tests/models/gte/test_modeling_gte.py Outdated
sentences = ["Plants create oxygen.", "Photosynthesis is a process where plants create oxygen."]

# TODO: Point these back at Alibaba-NLP and Snowflake once their configs declare `model_type: "gte"`.
# NOTE: The upstream repos carry an `auto_map`, so `Auto*` resolves them to that remote code

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hmm but that shouldnt be the case 🤔 shouldnt we need to pass trust_remote_code=True for that to happen

You could use a revision to make both live at the same repo, i.e. add what we need for the transformers integration

@harshaljanjani harshaljanjani Sep 1, 2026

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.

Ah, my note was misleading apologies! Also applied the revision param here and everywhere with TODOs

@harshaljanjani
harshaljanjani requested a review from vasqu September 1, 2026 06:21
@harshaljanjani harshaljanjani mentioned this pull request Sep 1, 2026
4 tasks

@vasqu vasqu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Ok my only bigger ask is to just make the ntk remotely into rope parameters directly as well, the rest is smaller in nature

Afterwards, I will ask internally for contacts so we may merge the remote hub PRs 🫡

Comment thread docs/source/en/model_doc/gte.md Outdated
task="feature-extraction",
model="Alibaba-NLP/gte-multilingual-base",
revision="refs/pr/31",
device=0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
device=0

I think we use device map auto on pipelines so can be removed

Comment thread docs/source/en/model_doc/gte.md Outdated
"Alibaba-NLP/gte-multilingual-base",
revision="refs/pr/31",
device_map="auto",
attn_implementation="sdpa"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
attn_implementation="sdpa"

same as its default


[[autodoc]] GteForSequenceClassification
- forward

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

super nit lol

rope_theta = kwargs.pop("rope_theta", self.default_theta)

# GTE's static NTK scaling is exactly a linear scaling of `base * factor`.
if rope_scaling is not None and rope_scaling["type"] == "ntk":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we are already submitting PRs with rope parameters directly, then we can directly do that there instead of overriding this method, no?

Comment on lines +174 to +177
@torch.no_grad()
def _init_weights(self, module):
# None of the inherited buffer initialisations apply, GTE keeps no such buffers.
PreTrainedModel._init_weights(self, module)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
@torch.no_grad()
def _init_weights(self, module):
# None of the inherited buffer initialisations apply, GTE keeps no such buffers.
PreTrainedModel._init_weights(self, module)
def _init_weights(self, **super_kwargs):
raise AttributeError("Uses base super call")

pretty sure this should work, can you retry?

@vasqu

vasqu commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

@harshaljanjani thanks a lot, just checked most PRs on the hub. Imo we could maybe simplify by quite a bit when we use default_rope_theta = 16_000 on the config

That would handle most of the PRs need for writing rope parameters explicitly (and would reduce my attempts at contacting orgs 😬)

Other than that, if we do not reach them we could still get them in even without the hub PRs

  1. Your ntk conversion and general rope conversion could be handled re rope theta
  2. Unsure about the model type tbh

@harshaljanjani

Copy link
Copy Markdown
Contributor Author

@vasqu A couple of clarifying questions.

→ I tried default_theta. Since the checkpoints set rope_theta explicitly and that takes priority, I'm uncertain whether default_theta can be a good complete replacement for the Hub PRs. Having said that, it's probably useful to set default_theta to 160000 instead of the existing 10000, which is a bad fallback for these checkpoints, changed that.
→ I'm uncertain again whether we can subsume the NTK part into rope_theta, it's a rescale plus a division by a constant, and the closest single theta I could find is still off. A bigger issue if we try to remove the Hub PRs altogether is model_type. With just model_type fixed, the four non-NTK ones already load and match, snowflake-arctic-embed-m-v2.0 needs nothing at all since it's already gte. The issue then becomes the four NTK ones, which raise KeyError: 'ntk', so they'd need convert_rope_params_to_dict back.

Please do let me know what you think about this!

@vasqu

vasqu commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

This is not to exchange every hub PR tbh but at least make it easier for us on a few models, e.g. as you mentioned NTK and different rope thetas likely still need their hub PRs

For example, with this default, we can theoretically still work with the snowflake implementation without the hub PR

@harshaljanjani

Copy link
Copy Markdown
Contributor Author

I understand, thank you for the clarification, pushed the changes! The PRs are down from the previous 8 to just the 4 NTK ones. The PRs that are important now:

https://huggingface.co/Alibaba-NLP/gte-multilingual-base/discussions/31
https://huggingface.co/Alibaba-NLP/gte-base-en-v1.5/discussions/17
https://huggingface.co/Alibaba-NLP/gte-large-en-v1.5/discussions/26
https://huggingface.co/Alibaba-NLP/gte-multilingual-reranker-base/discussions/23

code_revision = kwargs.pop("code_revision", None)

config_dict, unused_kwargs = PreTrainedConfig.get_config_dict(pretrained_model_name_or_path, **kwargs)
# GTE checkpoints ship the `new` model type, and the ones without NTK scaling need nothing else to load natively

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yea no please revert this, we definitely need to update those models at least on the hub

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Perf, trying to contact the alibaba team so gotta be a bit patient for now 🤗

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: auto, gte

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 33594411416:2
Result: success | Jobs: 16 | Tests: 185,363 | Failures: 0 | Duration: 16h 30m

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants