This model is part of the Ettin suite - the first collection of paired encoder-only and decoder-only models trained with identical data, architecture, and training recipes. Ettin enables fair comparisons between encoder and decoder architectures across multiple scales, providing state-of-the-art performance for open-data models in their respective size categories.
GLUE Average: 88.9 vs 88.4 (Base), 90.8 vs 90.4 (Large)
MTEB v2 English Retrieval: 45.7 vs 43.9 (Base), 48.4 vs 47.0 (Large)
Code Search and Long Context: Superior performance on CodeSearchNet and MLDR
Decoder Tasks (vs. SmolLM2 & Llama 3.2)
Average Score: 46.2 vs 45.2 (SmolLM2-135M)
1B Model: 59.0 vs 56.6 (Llama 3.2-1B)
Generative Tasks: Competitive across all model sizes
Key Finding
Architecture-specific advantages persist: A 400M encoder outperforms a 1B decoder on classification tasks, while a 400M decoder outperforms a 1B encoder on generation tasks.
🚀 Quick Start
Installation
bash
1pip install torch>=1.9.0
2# until the new pip release, install from main to use decoders (transformers>=4.54.X will contain it)3# encoders work with transformers>=4.48.04pip install git+https://github.com/huggingface/transformers.git
Ettin models are designed to provide a foundation for comparing encoder-only and decoder-only architectures. Unlike previous comparisons that were limited by different training data, architectures, and recipes, Ettin models use:
Identical training data - Same high-quality mixture across all models
Open Training Data - Data is available now with batch-level training data for each of the 250+ checkpoints
Matched architectures - Only differing in attention patterns (bidirectional vs causal) and training objectives (MLM vs CLM)
Consistent training recipe - Three-phase training with 2T tokens
Multiple scales - From 17M to 1B parameters
This approach allows for true apples-to-apples comparisons between encoder and decoder models, revealing the inherent strengths of each architecture.
Training Data
The training data is publicly available and split across different phases:
These models demonstrate what happens when you continue training encoders as decoders (and vice versa). Important: Load these models using the architecture they were converted to, not their original architecture.
Encoders Trained from Decoders (Decoder → MLM)
Load as encoders using AutoModel or AutoModelForMaskedLM:
Beyond the final models listed above, we provide access to intermediate training checkpoints for research and analysis purposes. These checkpoints allow you to study model behavior and performance throughout the training process. You can get the checkpoints either in HF format or raw for continued pre-training (e.g. Composer format).
1from transformers import AutoTokenizer, AutoModelForCausalLM
23# Load a specific pretraining checkpoint4model = AutoModelForCausalLM.from_pretrained(5"jhu-clsp/ettin-decoder-400m",6 revision="step590532"# Specific checkpoint tag7)89# Load an extension phase checkpoint10model = AutoModelForCausalLM.from_pretrained(11"jhu-clsp/ettin-decoder-400m",12 revision="ext1000"13)1415# Load a decay phase checkpoint 16model = AutoModelForCausalLM.from_pretrained(17"jhu-clsp/ettin-decoder-400m",18 revision="decay100"19)
This checkpoint availability enables detailed analysis of training dynamics, loss curves, and capability emergence across the complete 2T token training process.
🔬 Research Applications
What Makes Ettin Unique
Ettin provides the first controlled comparison of encoder vs. decoder architectures:
Identical Training Data: Same 2T token mixture across all models
Matched Architectures: Only attention patterns and objectives differ
Open Everything: Training data, model weights, and batch-level training order
Multiple Scales: Fair comparison from 17M to 1B parameters
250+ Checkpoints: Complete training trajectory analysis
Use Cases for Researchers
Architecture Studies: Compare encoder vs decoder capabilities fairly
Training Dynamics: Analyze 250+ checkpoints with batch-level data ordering
Scaling Laws: Study how architectural advantages change with scale
Transfer Learning: Investigate cross-objective training effectiveness
Replication Studies: First open replication of ModernBERT training recipe
Reproducibility
All training artifacts are publicly available:
Training data with exact batch ordering
Model checkpoints every 8.5B tokens
Complete hyperparameter configurations
Training code and evaluation scripts
Training Details
Data: High-quality mixture including DCLM, Dolma v1.7, scientific papers, code, and curated sources totaling 2T+ tokens
Architecture: Transformer with RoPE, GLU activations, and prenorm layers
Training Phases:
Pre-training: 1.7T tokens with diverse data mixture
Mid-training: 250B tokens with higher-quality filtered data and context extension to 8K
Decay phase: 100B tokens with premium data sources
Key Features:
Context length: Up to 8K tokens
Vocabulary: 50,368 tokens (ModernBERT tokenizer)
Deep but efficient architectures following MobileLLM principles
Model Architecture
Parameter
17M
32M
68M
150M
400M
1B
Layers
7
10
19
22
28
28
Hidden Size
256
384
512
768
1024
1792
Intermediate Size
384
576
768
1152
2624
3840
Attention Heads
4
6
8
12
16
28
Usage Examples
Encoder: Masked Language Modeling
Click to expand encoder usage examples
python
1from transformers import AutoTokenizer, AutoModelForMaskedLM
2import torch
34# Load MLM model5tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-encoder-150m")6model = AutoModelForMaskedLM.from_pretrained("jhu-clsp/ettin-encoder-150m")78defpredict_masked_token(text):9 inputs = tokenizer(text, return_tensors="pt")10with torch.no_grad():11 outputs = model(**inputs)1213# Get predictions for [MASK] tokens14 mask_indices = torch.where(inputs["input_ids"]== tokenizer.mask_token_id)15 predictions = outputs.logits[mask_indices]1617# Get top 5 predictions18 top_tokens = torch.topk(predictions,5, dim=-1)19return[tokenizer.decode(token)for token in top_tokens.indices[0]]2021# Example22masked_text ="The capital of France is [MASK]."23predictions = predict_masked_token(masked_text)24print(f"Predictions: {predictions}")
Decoder: Text Generation
Click to expand decoder text generation
python
1from transformers import AutoTokenizer, AutoModelForCausalLM
2import torch
34# Load model and tokenizer 5tokenizer = AutoTokenizer.from_pretrained("jhu-clsp/ettin-decoder-150m")6model = AutoModelForCausalLM.from_pretrained("jhu-clsp/ettin-decoder-150m")78# Set pad token if needed9if tokenizer.pad_token isNone:10 tokenizer.pad_token = tokenizer.eos_token
1112defgenerate_text(prompt, max_length=100, temperature=0.7):13 inputs = tokenizer(prompt, return_tensors="pt")1415with torch.no_grad():16 outputs = model.generate(17 inputs.input_ids,18 max_length=max_length,19 temperature=temperature,20 do_sample=True,21 pad_token_id=tokenizer.eos_token_id,22 num_return_sequences=123)2425return tokenizer.decode(outputs[0], skip_special_tokens=True)2627# Example usage28prompt ="The future of artificial intelligence is"29generated = generate_text(prompt)30print(generated)
Fine-tuning Examples
Encoders
Click to see how to finetune this into a dense embedding model using Sentence Transformers
python
1import argparse
23from datasets import load_dataset
4from sentence_transformers import(5 SentenceTransformer,6 SentenceTransformerTrainer,7 SentenceTransformerTrainingArguments,8)9from sentence_transformers.evaluation import TripletEvaluator
10from sentence_transformers.losses import CachedMultipleNegativesRankingLoss
11from sentence_transformers.training_args import BatchSamplers
1213defmain():14# parse the lr & model name15 parser = argparse.ArgumentParser()16 parser.add_argument("--lr",type=float, default=8e-5)17 parser.add_argument("--model_name",type=str, default="jhu-clsp/ettin-encoder-150m")18 args = parser.parse_args()19 lr = args.lr
20 model_name = args.model_name
21 model_shortname = model_name.split("/")[-1]2223# 1. Load a model to finetune24 model = SentenceTransformer(model_name)2526# 2. Load a dataset to finetune on27 dataset = load_dataset(28"sentence-transformers/msmarco-co-condenser-margin-mse-sym-mnrl-mean-v1",29"triplet-hard",30 split="train",31)32 dataset_dict = dataset.train_test_split(test_size=1_000, seed=12)33 train_dataset = dataset_dict["train"].select(range(1_250_000))34 eval_dataset = dataset_dict["test"]3536# 3. Define a loss function37 loss = CachedMultipleNegativesRankingLoss(model, mini_batch_size=16)# Increase mini_batch_size if you have enough VRAM3839 run_name =f"{model_shortname}-DPR-{lr}"40# 4. (Optional) Specify training arguments41 args = SentenceTransformerTrainingArguments(42# Required parameter:43 output_dir=f"output/{model_shortname}/{run_name}",44# Optional training parameters:45 num_train_epochs=1,46 per_device_train_batch_size=512,47 per_device_eval_batch_size=512,48 warmup_ratio=0.05,49 fp16=False,# Set to False if GPU can't handle FP1650 bf16=True,# Set to True if GPU supports BF1651 batch_sampler=BatchSamplers.NO_DUPLICATES,# (Cached)MultipleNegativesRankingLoss benefits from no duplicates52 learning_rate=lr,53# Optional tracking/debugging parameters:54 save_strategy="steps",55 save_steps=500,56 save_total_limit=2,57 logging_steps=500,58 run_name=run_name,# Used in `wandb`, `tensorboard`, `neptune`, etc. if installed59)6061# 5. (Optional) Create an evaluator & evaluate the base model62 dev_evaluator = TripletEvaluator(63 anchors=eval_dataset["query"],64 positives=eval_dataset["positive"],65 negatives=eval_dataset["negative"],66 name="msmarco-co-condenser-dev",67)68 dev_evaluator(model)6970# 6. Create a trainer & train71 trainer = SentenceTransformerTrainer(72 model=model,73 args=args,74 train_dataset=train_dataset,75 eval_dataset=eval_dataset,76 loss=loss,77 evaluator=dev_evaluator,78)79 trainer.train()8081# 7. (Optional) Evaluate the trained model on the evaluator after training82 dev_evaluator(model)8384# 8. Save the model85 model.save_pretrained(f"output/{model_shortname}/{run_name}/final")8687# 9. (Optional) Push it to the Hugging Face Hub88 model.push_to_hub(run_name, private=False)8990if __name__ =="__main__":91 main()
Click to see how to finetune this into a multi-vector embedding model with PyLate
python
1from datasets import load_dataset
2from pylate import losses, models, utils
3from sentence_transformers import(4 SentenceTransformerTrainer,5 SentenceTransformerTrainingArguments,6)78defmain():9# Load the datasets required for knowledge distillation (train, queries, documents)10 train = load_dataset(11 path="lightonai/ms-marco-en-bge",12 name="train",13)1415 queries = load_dataset(16 path="lightonai/ms-marco-en-bge",17 name="queries",18)1920 documents = load_dataset(21 path="lightonai/ms-marco-en-bge",22 name="documents",23)2425# Set the transformation to load the documents/queries texts using the corresponding ids on the fly26 train.set_transform(27 utils.KDProcessing(queries=queries, documents=documents).transform,28)2930# Define the base model, training parameters, and output directory31 num_train_epochs =132 lr =8e-533 batch_size =1634 accum_steps =135 model_name ="jhu-clsp/ettin-encoder-150m"36 model_shortname = model_name.split("/")[-1]3738# Set the run name for logging and output directory39 run_name =f"{model_shortname}-colbert-KD-{lr}"40 output_dir =f"output/{model_shortname}/{run_name}"4142# Initialize the ColBERT model from the base model43 model = models.ColBERT(model_name_or_path=model_name)4445# Configure the training arguments (e.g., epochs, batch size, learning rate)46 args = SentenceTransformerTrainingArguments(47 output_dir=output_dir,48 num_train_epochs=num_train_epochs,49 per_device_train_batch_size=batch_size,50 fp16=False,# Set to False if you get an error that your GPU can't run on FP1651 bf16=True,# Set to True if you have a GPU that supports BF1652 run_name=run_name,53 logging_steps=10,54 learning_rate=lr,55 gradient_accumulation_steps=accum_steps,56 warmup_ratio=0.05,57)5859# Use the Distillation loss function for training60 train_loss = losses.Distillation(model=model)6162# Initialize the trainer63 trainer = SentenceTransformerTrainer(64 model=model,65 args=args,66 train_dataset=train,67 loss=train_loss,68 data_collator=utils.ColBERTCollator(tokenize_fn=model.tokenize),69)7071# Start the training process72 trainer.train()7374 model.save_pretrained(f"{output_dir}/final")7576if __name__ =="__main__":77 main()78
Click to see how to finetune this into a sparse retrieval model using Sentence Transformers
python
1import logging
23from datasets import load_dataset
45from sentence_transformers import(6 SparseEncoder,7 SparseEncoderModelCardData,8 SparseEncoderTrainer,9 SparseEncoderTrainingArguments,10)11from sentence_transformers.sparse_encoder.evaluation import SparseNanoBEIREvaluator
12from sentence_transformers.sparse_encoder.losses import SparseMultipleNegativesRankingLoss, SpladeLoss
13from sentence_transformers.training_args import BatchSamplers
1415logging.basicConfig(format="%(asctime)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.INFO)1617# 1. Load a model to finetune with 2. (Optional) model card data18model = SparseEncoder(19"jhu-clsp/ettin-encoder-150m",20 model_card_data=SparseEncoderModelCardData(21 language="en",22 license="apache-2.0",23)24)2526# 3. Load a dataset to finetune on27full_dataset = load_dataset("sentence-transformers/natural-questions", split="train").select(range(100_000))28dataset_dict = full_dataset.train_test_split(test_size=1_000, seed=12)29train_dataset = dataset_dict["train"]30eval_dataset = dataset_dict["test"]3132# 4. Define a loss function33loss = SpladeLoss(34 model=model,35 loss=SparseMultipleNegativesRankingLoss(model=model),36 query_regularizer_weight=5e-5,37 document_regularizer_weight=3e-5,38)3940# 5. (Optional) Specify training arguments41run_name ="splade-distilbert-base-uncased-nq"42args = SparseEncoderTrainingArguments(43# Required parameter:44 output_dir=f"models/{run_name}",45# Optional training parameters:46 num_train_epochs=1,47 per_device_train_batch_size=16,48 per_device_eval_batch_size=16,49 learning_rate=2e-5,50 warmup_ratio=0.1,51 fp16=True,# Set to False if you get an error that your GPU can't run on FP1652 bf16=False,# Set to True if you have a GPU that supports BF1653 batch_sampler=BatchSamplers.NO_DUPLICATES,# MultipleNegativesRankingLoss benefits from no duplicate samples in a batch54# Optional tracking/debugging parameters:55 eval_strategy="steps",56 eval_steps=1000,57 save_strategy="steps",58 save_steps=1000,59 save_total_limit=2,60 logging_steps=200,61 run_name=run_name,# Will be used in W&B if `wandb` is installed62)6364# 6. (Optional) Create an evaluator & evaluate the base model65dev_evaluator = SparseNanoBEIREvaluator(dataset_names=["msmarco","nfcorpus","nq"], batch_size=16)6667# 7. Create a trainer & train68trainer = SparseEncoderTrainer(69 model=model,70 args=args,71 train_dataset=train_dataset,72 eval_dataset=eval_dataset,73 loss=loss,74 evaluator=dev_evaluator,75)76trainer.train()7778# 8. Evaluate the model performance again after training79dev_evaluator(model)8081# 9. Save the trained model82model.save_pretrained(f"models/{run_name}/final")8384# 10. (Optional) Push it to the Hugging Face Hub85model.push_to_hub(run_name)86
Click to see how to finetune this into a reranker model using Sentence Transformers
python
1import logging
2import traceback
34import torch
5from datasets import load_dataset
67from sentence_transformers import SentenceTransformer
8from sentence_transformers.cross_encoder import(9 CrossEncoder,10 CrossEncoderModelCardData,11 CrossEncoderTrainer,12 CrossEncoderTrainingArguments,13)14from sentence_transformers.cross_encoder.evaluation import(15 CrossEncoderNanoBEIREvaluator,16 CrossEncoderRerankingEvaluator,17)18from sentence_transformers.cross_encoder.losses import BinaryCrossEntropyLoss
19from sentence_transformers.evaluation import SequentialEvaluator
20from sentence_transformers.util import mine_hard_negatives
2122# Set the log level to INFO to get more information23logging.basicConfig(format="%(asctime)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S", level=logging.INFO)242526defmain():27 model_name ="jhu-clsp/ettin-encoder-150m"2829 train_batch_size =6430 num_epochs =131 num_hard_negatives =5# How many hard negatives should be mined for each question-answer pair3233# 1a. Load a model to finetune with 1b. (Optional) model card data34 model = CrossEncoder(35 model_name,36 model_card_data=CrossEncoderModelCardData(37 language="en",38 license="apache-2.0",39),40)41print("Model max length:", model.max_length)42print("Model num labels:", model.num_labels)4344# 2a. Load the GooAQ dataset: https://huggingface.co/datasets/sentence-transformers/gooaq45 logging.info("Read the gooaq training dataset")46 full_dataset = load_dataset("sentence-transformers/gooaq", split="train").select(range(100_000))47 dataset_dict = full_dataset.train_test_split(test_size=1_000, seed=12)48 train_dataset = dataset_dict["train"]49 eval_dataset = dataset_dict["test"]50 logging.info(train_dataset)51 logging.info(eval_dataset)5253# 2b. Modify our training dataset to include hard negatives using a very efficient embedding model54 embedding_model = SentenceTransformer("sentence-transformers/static-retrieval-mrl-en-v1", device="cpu")55 hard_train_dataset = mine_hard_negatives(56 train_dataset,57 embedding_model,58 num_negatives=num_hard_negatives,# How many negatives per question-answer pair59 margin=0,# Similarity between query and negative samples should be x lower than query-positive similarity60 range_min=0,# Skip the x most similar samples61 range_max=100,# Consider only the x most similar samples62 sampling_strategy="top",# Sample the top negatives from the range63 batch_size=4096,# Use a batch size of 4096 for the embedding model64 output_format="labeled-pair",# The output format is (query, passage, label), as required by BinaryCrossEntropyLoss65 use_faiss=True,66)67 logging.info(hard_train_dataset)6869# 2c. (Optionally) Save the hard training dataset to disk70# hard_train_dataset.save_to_disk("gooaq-hard-train")71# Load again with:72# hard_train_dataset = load_from_disk("gooaq-hard-train")7374# 3. Define our training loss.75# pos_weight is recommended to be set as the ratio between positives to negatives, a.k.a. `num_hard_negatives`76 loss = BinaryCrossEntropyLoss(model=model, pos_weight=torch.tensor(num_hard_negatives))7778# 4a. Define evaluators. We use the CrossEncoderNanoBEIREvaluator, which is a light-weight evaluator for English reranking79 nano_beir_evaluator = CrossEncoderNanoBEIREvaluator(80 dataset_names=["msmarco","nfcorpus","nq"],81 batch_size=train_batch_size,82)8384# 4b. Define a reranking evaluator by mining hard negatives given query-answer pairs85# We include the positive answer in the list of negatives, so the evaluator can use the performance of the86# embedding model as a baseline.87 hard_eval_dataset = mine_hard_negatives(88 eval_dataset,89 embedding_model,90 corpus=full_dataset["answer"],# Use the full dataset as the corpus91 num_negatives=30,# How many documents to rerank92 batch_size=4096,93 include_positives=True,94 output_format="n-tuple",95 use_faiss=True,96)97 logging.info(hard_eval_dataset)98 reranking_evaluator = CrossEncoderRerankingEvaluator(99 samples=[100{101"query": sample["question"],102"positive":[sample["answer"]],103"documents":[sample[column_name]for column_name in hard_eval_dataset.column_names[2:]],104}105for sample in hard_eval_dataset
106],107 batch_size=train_batch_size,108 name="gooaq-dev",109# Realistic setting: only rerank the positives that the retriever found110# Set to True to rerank *all* positives111 always_rerank_positives=False,112)113114# 4c. Combine the evaluators & run the base model on them115 evaluator = SequentialEvaluator([reranking_evaluator, nano_beir_evaluator])116 evaluator(model)117118# 5. Define the training arguments119 short_model_name = model_name if"/"notin model_name else model_name.split("/")[-1]120 run_name =f"reranker-{short_model_name}-gooaq-bce"121 args = CrossEncoderTrainingArguments(122# Required parameter:123 output_dir=f"models/{run_name}",124# Optional training parameters:125 num_train_epochs=num_epochs,126 per_device_train_batch_size=train_batch_size,127 per_device_eval_batch_size=train_batch_size,128 learning_rate=2e-5,129 warmup_ratio=0.1,130 fp16=False,# Set to False if you get an error that your GPU can't run on FP16131 bf16=True,# Set to True if you have a GPU that supports BF16132 dataloader_num_workers=4,133 load_best_model_at_end=True,134 metric_for_best_model="eval_gooaq-dev_ndcg@10",135# Optional tracking/debugging parameters:136 eval_strategy="steps",137 eval_steps=1000,138 save_strategy="steps",139 save_steps=1000,140 save_total_limit=2,141 logging_steps=200,142 logging_first_step=True,143 run_name=run_name,# Will be used in W&B if `wandb` is installed144 seed=12,145)146147# 6. Create the trainer & start training148 trainer = CrossEncoderTrainer(149 model=model,150 args=args,151 train_dataset=hard_train_dataset,152 loss=loss,153 evaluator=evaluator,154)155 trainer.train()156157# 7. Evaluate the final model, useful to include these in the model card158 evaluator(model)159160# 8. Save the final model161 final_output_dir =f"models/{run_name}/final"162 model.save_pretrained(final_output_dir)163164# 9. (Optional) save the model to the Hugging Face Hub!165# It is recommended to run `huggingface-cli login` to log into your Hugging Face account first166try:167 model.push_to_hub(run_name)168except Exception:169 logging.error(170 f"Error uploading model to the Hugging Face Hub:171{traceback.format_exc()}To upload it manually, you can run "
172f"`huggingface-cli login`, followed by loading the model using `model = CrossEncoder({final_output_dir!r})` "173f"and saving it using `model.push_to_hub('{run_name}')`."174)175176177if __name__ =="__main__":178 main()179
🎯 Decoder on Generative Tasks - Using EleutherAI evaluation harness (commit 867413f8677f00f6a817262727cbb041bf36192a) for comprehensive generative task evaluation
Bias Evaluation
⚖️ Gender Bias Evaluation - Comprehensive gender bias testing using Winogender dataset gotcha examples. Tests how well models handle counter-stereotypical pronouns in occupational contexts. Supports both encoder (MLM) and decoder (perplexity) evaluation methods.
Quick Decoder Evaluation Example
bash
1# Clone the specific commit of lm-evaluation-harness2git clone https://github.com/EleutherAI/lm-evaluation-harness.git
3cd lm-evaluation-harness
4git checkout 867413f8677f00f6a817262727cbb041bf36192a
5pip install -e .67# Run evaluation on Ettin decoder8lm_eval --model hf \9 --model_args pretrained=jhu-clsp/ettin-decoder-150m \10 --tasks hellaswag,arc_easy,arc_challenge,winogrande \11 --device cuda:0 \12 --batch_size 8
❓ FAQ
Model Loading Issues
Q: I'm getting an error that ModernBERT-decoder isn't found.A: Make sure you have the latest version of transformers installed:
bash
1# for the latest version until the official pypi release:2pip install git+https://github.com/huggingface/transformers.git
Q: Which model should I choose for my task?A:
Classification/Retrieval/Understanding: Use encoder models
Text Generation/Chat/Completion: Use decoder models
Research on cross-training: Use cross-objective models
Size selection: Start with 150M for experimentation, scale up to 400M or 1B for production
Q: How do I access training checkpoints?A: Each model has multiple git tags for different training stages. Use the revision parameter:
model = AutoModel.from_pretrained("jhu-clsp/ettin-encoder-150m", revision="step500000")
Q: Can I continue training these models?A: Yes! We provide raw checkpoints in the jhu-clsp/ettin-checkpoints dataset that can be loaded into training frameworks.
Q: What's the difference between cross-objective models and regular models?A: Cross-objective models started as one architecture (e.g., decoder) and were continued with a different objective (e.g., MLM). They demonstrate the limitations of cross-training and generally underperform native models.
Q: How do I reproduce the paper results?A: See our evaluation guides:
If you use Ettin models in your research, please cite our work:
bibtex
1@misc{weller2025seqvsseqopen,
2 title={Seq vs Seq: An Open Suite of Paired Encoders and Decoders},
3 author={Orion Weller and Kathryn Ricci and Marc Marone and Antoine Chaffin and Dawn Lawrie and Benjamin Van Durme},
4 year={2025},
5 eprint={2507.11412},
6 archivePrefix={arXiv},
7 primaryClass={cs.CL},
8 url={https://arxiv.org/abs/2507.11412},
9}
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contact: For questions about the models or research, please open an issue or contact the authors.