11"""Opt-in LoRA lifecycle for the ordinary sentence-transformers CrossEncoder.
22
33The outer wrapper, tokenizer, classifier and activation are never replaced.
4- Transformers injects ordinary LoRA layers into declared BERT encoder Linear
4+ Transformers injects ordinary LoRA layers into declared BERT/XLM-R encoder Linear
55modules. The worker owns selection/mutation exclusion; only background source
66preparation needs a separate lock here. Resident layers stay outside the model's
77module tree while base is selected. No scoring code calls into this module.
2525from peft .tuners .lora .layer import Linear as LoraLinear
2626from safetensors .torch import load_file
2727from sentence_transformers import CrossEncoder
28- from transformers import BertForSequenceClassification
28+ from transformers import BertForSequenceClassification , XLMRobertaForSequenceClassification
2929
3030from sie_server .adapters .lora import LoraRuntimeConfig , LoraStateUncertainError , LoraUnavailableError
3131from sie_server .adapters .peft_lora_mixin import PEFTLoRAMixin
3232
3333_FILES = ("adapter_config.json" , "adapter_model.safetensors" )
34+ _ENCODER_PREFIXES : dict [type [torch .nn .Module ], str ] = {
35+ BertForSequenceClassification : "bert.encoder." ,
36+ XLMRobertaForSequenceClassification : "roberta.encoder." ,
37+ }
3438_UNSUPPORTED = (
3539 "use_dora" ,
3640 "use_rslora" ,
@@ -93,6 +97,7 @@ def __init__(
9397 self ._active : str | None = None
9498 self ._outer : CrossEncoder | None = None
9599 self ._model : Any = None
100+ self ._encoder_prefix : str | None = None
96101 self ._originals : dict [str , torch .nn .Linear ] = {}
97102 # An ordinary dict deliberately keeps resident adapters outside nn.Module's
98103 # traversal while base is selected. CrossEncoder.predict calls to()/eval()
@@ -108,13 +113,16 @@ def runtime_config(self) -> LoraRuntimeConfig:
108113
109114 def bind (self , outer : CrossEncoder ) -> None :
110115 model = outer .model
111- if type (model ) is not BertForSequenceClassification or getattr (model , "is_quantized" , False ):
116+ encoder_prefix = _ENCODER_PREFIXES .get (type (model ))
117+ if encoder_prefix is None or getattr (model , "is_quantized" , False ):
112118 raise ValueError (
113- "CrossEncoder LoRA currently requires a standard, unquantized BertForSequenceClassification"
119+ "CrossEncoder LoRA requires a standard, unquantized BertForSequenceClassification "
120+ "or XLMRobertaForSequenceClassification"
114121 )
115122 if getattr (model , "peft_config" , None ) or getattr (model , "_hf_peft_config_loaded" , False ):
116123 raise ValueError ("The cross-encoder must start from a base checkpoint without attached adapters" )
117124 self ._outer , self ._model = outer , model
125+ self ._encoder_prefix = encoder_prefix
118126 self ._originals = {name : module for name , module in model .named_modules () if type (module ) is torch .nn .Linear }
119127 self ._grad_flags = [(parameter , parameter .requires_grad ) for parameter in model .parameters ()]
120128 self ._active = None
@@ -160,8 +168,14 @@ def _validate_weights(self, config: LoraConfig, weights: dict[str, torch.Tensor]
160168 expected : dict [str , tuple [int , int ]] = {}
161169 for target in config .target_modules or []:
162170 matches = [name for name in self ._originals if name == target or name .endswith (f".{ target } " )]
163- if not matches or any (not name .startswith ("bert.encoder." ) for name in matches ):
164- raise ValueError (f"LoRA target { target !r} must resolve only to BERT encoder Linear modules" )
171+ if (
172+ self ._encoder_prefix is None
173+ or not matches
174+ or any (not name .startswith (self ._encoder_prefix ) for name in matches )
175+ ):
176+ raise ValueError (
177+ f"LoRA target { target !r} must resolve only to the supported model encoder Linear modules"
178+ )
165179 for name in matches :
166180 module = self ._originals [name ]
167181 expected [f"{ name } .lora_A.weight" ] = (config .r , module .in_features )
@@ -430,6 +444,7 @@ def close(self) -> None:
430444 self ._originals .clear ()
431445 self ._grad_flags .clear ()
432446 self ._outer = self ._model = None
447+ self ._encoder_prefix = None
433448 self ._uncertain = False
434449 if errors :
435450 raise ExceptionGroup ("Failed to remove owned LoRA source snapshots" , errors )
0 commit comments