7676from ..stdlib .requirements import ALoraRequirement , LLMaJRequirement
7777from ..telemetry .context import generate_request_id , with_context
7878from ._options import resolve_model_options
79- from .adapters import AdapterMixin , IntrinsicAdapter , LocalHFAdapter
79+ from .adapters import (
80+ AdapterMixin ,
81+ EmbeddedActivationRequest ,
82+ EmbeddedBinding ,
83+ IntrinsicAdapter ,
84+ LocalHFAdapter ,
85+ )
8086from .adapters ._core import (
8187 Adapter as _AdapterCore ,
8288 IOContract ,
8389 LocalFileBinding ,
8490 WeightsBinding ,
8591)
86- from .adapters .adapter import AdapterInput
92+ from .adapters .adapter import AdapterInput , EmbeddedIntrinsicAdapter
8793from .backend import FormatterBackend
8894from .cache import Cache , SimpleLRUCache
8995from .model_ids import ModelIdentifier
@@ -339,6 +345,11 @@ class LocalHFBackend(FormatterBackend, AdapterMixin):
339345 tokenizer/model/device; if provided, `model_id` is not used for loading.
340346 default_to_constraint_checking_alora (bool): If `False`, aLoRA constraint
341347 checking is deactivated; mainly for benchmarking and debugging.
348+ load_embedded_adapters (bool): If `True`, register adapter functions
349+ embedded in the Granite Switch checkpoint named by `adapter_source`
350+ (or `model_id` when `adapter_source` is not set).
351+ adapter_source (str | None): Local checkpoint directory or Hugging Face
352+ Hub repository used to discover embedded adapter functions.
342353 model_options (dict | None): Default model options for generation requests.
343354
344355 Attributes:
@@ -363,6 +374,8 @@ def __init__(
363374 cache : Cache | None = None ,
364375 custom_config : TransformersTorchConfig | None = None ,
365376 default_to_constraint_checking_alora : bool = True ,
377+ load_embedded_adapters : bool = False ,
378+ adapter_source : str | None = None ,
366379 model_options : dict | None = None ,
367380 ):
368381 """Load model weights from the given model ID, or from a custom config if provided."""
@@ -454,8 +467,12 @@ def __init__(
454467 )
455468
456469 # Adapters can be made known to the backend (added) and loaded.
457- self ._added_adapters : dict [str , LocalHFAdapter | LocalFileBinding ] = {}
470+ self ._added_adapters : dict [
471+ str , LocalHFAdapter | LocalFileBinding | EmbeddedIntrinsicAdapter
472+ ] = {}
458473 self ._loaded_adapters : dict [str , LocalHFAdapter | LocalFileBinding ] = {}
474+ self ._adapter_source = adapter_source
475+ self ._uses_embedded_adapters = load_embedded_adapters
459476
460477 self ._generation_lock = threading .RLock ()
461478 """Forces generation requests to be non-concurrent, and guards adapter
@@ -469,6 +486,9 @@ def __init__(
469486 A plain `Lock` deadlocks on that same-thread re-acquisition (#1465).
470487 """
471488
489+ if load_embedded_adapters :
490+ self .register_embedded_adapter_model (self ._adapter_source or self ._model_id )
491+
472492 def _make_dc_cache (self , toks , ** model_options ):
473493 dc = DynamicCache ()
474494 with torch .no_grad ():
@@ -605,6 +625,27 @@ def _generate_with_adapter_lock(self, generate_func: Callable, *args, **kwargs):
605625 _assert_correct_adapters ("" , self ._model )
606626 return out
607627
628+ def _generate_embedded_with_generation_lock (
629+ self , generate_func : Callable [..., _T ], * args : Any , ** kwargs : Any
630+ ) -> _T :
631+ """Run embedded-adapter generation while serialising model access.
632+
633+ Embedded adapter functions select their control token while rendering
634+ the chat template. They must not use the PEFT lifecycle, but local
635+ Transformers generation still shares the backend's model and therefore
636+ remains serialised with every other generation path.
637+
638+ Args:
639+ generate_func: The synchronous generation callable to invoke.
640+ *args: Positional arguments forwarded to `generate_func`.
641+ **kwargs: Keyword arguments forwarded to `generate_func`.
642+
643+ Returns:
644+ Whatever `generate_func` returns.
645+ """
646+ with self ._generation_lock :
647+ return generate_func (* args , ** kwargs )
648+
608649 def _generate_intrinsic_with_adapter_scope (
609650 self ,
610651 adapter : IntrinsicAdapter ,
@@ -713,7 +754,9 @@ async def _generate_from_intrinsic(
713754 ValueError: If no adapter is registered for the requested intrinsic,
714755 or if the context contains images or audio; `LocalHFBackend`
715756 does not support multimodal inputs.
716- TypeError: If the adapter isn't an IntrinsicAdapter.
757+ TypeError: If the adapter is neither an `IntrinsicAdapter` nor an
758+ `EmbeddedIntrinsicAdapter`, or if an embedded adapter does not
759+ have an `EmbeddedBinding`.
717760 """
718761 if not ctx .is_chat_context :
719762 raise Exception ("Does not yet support non-chat contexts." )
@@ -770,9 +813,10 @@ async def _generate_from_intrinsic(
770813
771814 # TODO: Code below this point is mostly specific to RagIntrinsics
772815 # It should be refactored into a specific adapter.transform() function.
773- if not isinstance (adapter , IntrinsicAdapter ):
816+ if not isinstance (adapter , ( IntrinsicAdapter , EmbeddedIntrinsicAdapter ) ):
774817 raise TypeError (
775- f"LocalHFBackend only supports IntrinsicAdapters, got: { type (adapter ).__name__ } "
818+ "LocalHFBackend only supports IntrinsicAdapter or "
819+ f"EmbeddedIntrinsicAdapter, got: { type (adapter ).__name__ } "
776820 )
777821
778822 intrinsic_config = adapter .config
@@ -809,9 +853,26 @@ async def _generate_from_intrinsic(
809853 # so we will have to invalidate the cache on our side. This requires
810854 # us having specific caching for each Component/Message.
811855
856+ rewritten_request = rewritten .model_dump ()
857+ if isinstance (adapter , EmbeddedIntrinsicAdapter ):
858+ if not isinstance (adapter .weights , EmbeddedBinding ):
859+ raise TypeError (
860+ "EmbeddedIntrinsicAdapter.weights must be an EmbeddedBinding; "
861+ f"got { type (adapter .weights ).__name__ } . Activation cannot proceed."
862+ )
863+ extra_body = rewritten_request .setdefault ("extra_body" , {})
864+ if not isinstance (extra_body , dict ):
865+ raise TypeError (
866+ "Embedded adapter generation requires extra_body to be a dict."
867+ )
868+ await adapter .weights .apply_activation (
869+ EmbeddedActivationRequest (extra_body = extra_body , api_params = {}),
870+ adapter .identity ,
871+ )
872+
812873 generate_input , other_input = (
813874 granite_formatters .base .util .chat_completion_request_to_transformers_inputs ( # type: ignore
814- rewritten ,
875+ rewritten_request ,
815876 self ._tokenizer ,
816877 self ._model ,
817878 ll_tokenizer = self ._llguidance_tokenizer ,
@@ -871,16 +932,27 @@ def __getattr__(self_proxy, name: str) -> Any:
871932
872933 model_arg = _CapturingModelProxy () # type: ignore[assignment]
873934
874- chat_response = asyncio .to_thread (
875- self ._generate_intrinsic_with_adapter_scope ,
876- adapter ,
877- granite_formatters .base .util .generate_with_transformers , # type: ignore
878- # Passed as args/kwargs to generate.
879- self ._tokenizer ,
880- model_arg ,
881- generate_input ,
882- other_input ,
883- )
935+ if isinstance (adapter , IntrinsicAdapter ):
936+ chat_response = asyncio .to_thread (
937+ self ._generate_intrinsic_with_adapter_scope ,
938+ adapter ,
939+ granite_formatters .base .util .generate_with_transformers , # type: ignore
940+ # Passed as args/kwargs to generate.
941+ self ._tokenizer ,
942+ model_arg ,
943+ generate_input ,
944+ other_input ,
945+ )
946+ else :
947+ chat_response = asyncio .to_thread (
948+ self ._generate_embedded_with_generation_lock ,
949+ granite_formatters .base .util .generate_with_transformers , # type: ignore
950+ # Passed as args/kwargs to generate.
951+ self ._tokenizer ,
952+ model_arg ,
953+ generate_input ,
954+ other_input ,
955+ )
884956
885957 output = ModelOutputThunk (None )
886958 output ._gen .start = datetime .datetime .now ()
@@ -2081,32 +2153,36 @@ def _filter_for_chat_template(
20812153 @property
20822154 def base_model_name (self ):
20832155 """Returns the base_model_id of the model used by the backend. For example, `granite-3.3-8b-instruct` for `ibm-granite/granite-3.3-8b-instruct`."""
2084- return self ._model_id .split ("/" )[ 1 ]
2156+ return self ._model_id .rsplit ("/" , maxsplit = 1 )[ - 1 ]
20852157
20862158 def add_adapter (self , adapter : AdapterInput ) -> None :
2087- """Register a LoRA/aLoRA adapter with this backend so it can be loaded later .
2159+ """Register an adapter function with this backend.
20882160
20892161 Downloads the adapter weights (via `adapter.get_local_hf_path`) and records
20902162 the adapter in the backend's registry. The adapter must not already be
20912163 registered with a different backend.
20922164
20932165 Accepts the full `AdapterInput` union to honour the mixin contract, but
2094- only the LocalFile/PEFT reality is supported here — other realities are
2095- rejected at runtime rather than narrowing the signature.
2166+ LocalFile/PEFT and Embedded/Granite Switch realities are supported.
2167+ Embedded adapter functions are already present in the model, so they
2168+ are registered without downloading or loading PEFT weights.
20962169
20972170 Args:
20982171 adapter (AdapterInput): The adapter to register. Must be a
2099- `LocalHFAdapter` or `LocalFileBinding`; other adapter realities
2100- are rejected.
2172+ `LocalHFAdapter`, `LocalFileBinding`, or
2173+ `EmbeddedIntrinsicAdapter`; other adapter realities are
2174+ rejected.
21012175
21022176 Raises:
2103- TypeError: If `adapter` is not a `LocalHFAdapter` or `LocalFileBinding` .
2177+ TypeError: If `adapter` is not a supported local or embedded adapter .
21042178 Exception: If `adapter` has already been added to a different backend.
21052179 """
2106- if not isinstance (adapter , (LocalHFAdapter , LocalFileBinding )):
2180+ if not isinstance (
2181+ adapter , (LocalHFAdapter , LocalFileBinding , EmbeddedIntrinsicAdapter )
2182+ ):
21072183 raise TypeError (
2108- f "LocalHFBackend requires a LocalHFAdapter or LocalFileBinding; got "
2109- f"{ type (adapter ).__name__ } ."
2184+ "LocalHFBackend requires a LocalHFAdapter, LocalFileBinding, or "
2185+ f"EmbeddedIntrinsicAdapter; got { type (adapter ).__name__ } ."
21102186 )
21112187 if adapter .backend is not None :
21122188 if adapter .backend is self :
@@ -2131,10 +2207,41 @@ def add_adapter(self, adapter: AdapterInput) -> None:
21312207 )
21322208 return None
21332209
2210+ if isinstance (adapter , EmbeddedIntrinsicAdapter ):
2211+ adapter .backend = self
2212+ if not isinstance (adapter .weights , EmbeddedBinding ):
2213+ raise TypeError (
2214+ "EmbeddedIntrinsicAdapter.weights must be an EmbeddedBinding; "
2215+ f"got { type (adapter .weights ).__name__ } ."
2216+ )
2217+ adapter .weights .source = self .base_model_name
2218+ self ._added_adapters [adapter .qualified_name ] = adapter
2219+ return
2220+
21342221 adapter .path = adapter .get_local_hf_path (self .base_model_name )
21352222 adapter .backend = self
21362223 self ._added_adapters [adapter .qualified_name ] = adapter
21372224
2225+ def register_embedded_adapter_model (
2226+ self , source : str , * , revision : str = "main" , cache_dir : str | None = None
2227+ ) -> list [str ]:
2228+ """Register embedded adapter functions from a Granite Switch checkpoint.
2229+
2230+ Args:
2231+ source: Local checkpoint directory or Hugging Face Hub repository ID.
2232+ revision: Git revision when loading from the Hub.
2233+ cache_dir: Cache directory for Hub downloads.
2234+
2235+ Returns:
2236+ Names of the registered adapter functions.
2237+ """
2238+ adapters = EmbeddedIntrinsicAdapter .from_source (
2239+ source , revision = revision , cache_dir = cache_dir
2240+ )
2241+ for adapter in adapters :
2242+ self .add_adapter (adapter )
2243+ return [adapter .intrinsic_name for adapter in adapters ]
2244+
21382245 def load_peft_adapter (self , adapter_qualified_name : str ) -> None :
21392246 """Load a previously registered adapter into the underlying Hugging Face model.
21402247
@@ -2154,6 +2261,11 @@ def load_peft_adapter(self, adapter_qualified_name: str) -> None:
21542261 raise ValueError (
21552262 f"could not load adapter { adapter_qualified_name } for backend { self } : adapter was not previously added"
21562263 )
2264+ if isinstance (adapter , EmbeddedIntrinsicAdapter ):
2265+ raise TypeError (
2266+ f"cannot load embedded adapter { adapter_qualified_name } through PEFT; "
2267+ "it is activated by the chat template"
2268+ )
21572269
21582270 try :
21592271 # self._model is a HF PreTrainedModel — .load_adapter() here is
0 commit comments