Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions packages/sie_server/bundles/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ adapters:
- sie_server.adapters.colqwen2
- sie_server.adapters.colqwen3
- sie_server.adapters.colsmol
- sie_server.adapters.cross_encoder.runtime_lora
- sie_server.adapters.grounding_dino.adapter
- sie_server.adapters.gte_sparse_flash
- sie_server.adapters.jina_flash_cross_encoder
Expand Down Expand Up @@ -41,11 +42,14 @@ adapters:
- sie_server.adapters.nli_classification_flash
- sie_server.adapters.gliner
- sie_server.adapters.gliner2.adapter
- sie_server.adapters.gliner2.entity_lora
- sie_server.adapters.gliner_bi
- sie_server.adapters.gliner_union.adapter
- sie_server.adapters.glirel
- sie_server.adapters.gliclass
- sie_server.adapters.gliclass.runtime_lora
- sie_server.adapters.laya.adapter
- sie_server.adapters.laya.runtime_lora
- sie_server.adapters.gliformer.adapter
- sie_server.adapters.donut
- sie_server.adapters.florence2
Expand Down
2 changes: 2 additions & 0 deletions packages/sie_server/bundles/transformers5.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ adapters:
- sie_server.adapters.glm_ocr
- sie_server.adapters.gliner2.classification
- sie_server.adapters.gliner2.decide
- sie_server.adapters.gliner2.decide_lora
- sie_server.adapters.gliner2.entities
- sie_server.adapters.privacy_filter.adapter
- sie_server.adapters.st_sparse_vision.adapter
Expand All @@ -17,6 +18,7 @@ adapters:
- sie_server.adapters.parakeet.adapter
# Perplexity's native calibrated decision readout (Qwen3.8/Qwen3.5 architecture).
- sie_server.adapters.pplx_decider.adapter
- sie_server.adapters.pplx_decider.runtime_lora
# SAM 3 open-vocabulary detection (Sam3Model / Sam3Processor exist only in transformers 5.x).
- sie_server.adapters.sam3.adapter
deps:
Expand Down
24 changes: 24 additions & 0 deletions packages/sie_server/src/sie_server/adapters/_checkpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Resolve native checkpoint paths using the configured local cache policy."""

from collections.abc import Sequence
from pathlib import Path

from huggingface_hub import constants, snapshot_download
from sie_sdk.cache import get_cache_config


def resolve_checkpoint(model_name_or_path: str | Path, *, revision: str | None, allow_patterns: Sequence[str]) -> Path:
"""Keep local paths local and resolve Hub snapshots under the serving cache policy."""
directory = Path(model_name_or_path)
if directory.is_dir():
return directory
config = get_cache_config()
return Path(
snapshot_download(
repo_id=str(model_name_or_path),
revision=revision,
allow_patterns=list(allow_patterns),
cache_dir=config.local_cache,
local_files_only=constants.HF_HUB_OFFLINE or not config.hf_fallback,
)
)
561 changes: 561 additions & 0 deletions packages/sie_server/src/sie_server/adapters/_peft_lora_runtime.py

Large diffs are not rendered by default.

Empty file.
152 changes: 152 additions & 0 deletions packages/sie_server/src/sie_server/adapters/decision_lora/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Lifecycle and selection for explicitly configured native decision LoRA profiles."""

from __future__ import annotations

from abc import abstractmethod
from typing import Any

from sie_server.adapters._base_adapter import BaseAdapter
from sie_server.adapters._peft_lora_runtime import DetachedLoraRuntime
from sie_server.adapters.lora import LoraRuntimeConfig
from sie_server.core.inference_output import ExtractOutput
from sie_server.types.inputs import InvalidInputError, Item
from sie_server.types.lora import InvalidLoraSelectionError, split_lora_selection


class DecisionLoraAdapter(BaseAdapter):
"""Cooperative base for opt-in profiles; native adapter classes never import it.

Each concrete class precedes its ordinary adapter in the MRO. The raw model
still owns inference; the separate controller owns reversible LoRA layers.
Base CUDA graphs are retained, but an adapted forward always runs eagerly.
"""

_lora: DetachedLoraRuntime

@abstractmethod
def _lora_model(self) -> Any:
"""Return the exact native serving owner after load."""

@abstractmethod
def _validate_decision_request(
self,
items: list[Item],
*,
labels: list[str] | None,
output_schema: dict[str, Any] | None,
instruction: str | None,
options: dict[str, Any],
) -> None:
"""Check native task intent before a missing adapter can be fetched."""

def _request_selection(self, options: dict[str, Any] | None) -> tuple[str | None, dict[str, Any]]:
if options is not None and not isinstance(options, dict):
raise InvalidInputError("Decision options must be an object")
try:
selection, opts = split_lora_selection(options or {})
except InvalidLoraSelectionError as error:
raise InvalidInputError(str(error)) from error
selected = selection.value
if selected is not None:
try:
source = self._lora.source(selected)
if "lora_id" in selection.fields and source != selected:
raise ValueError("Legacy lora_id must name the declared source, not a served alias")
except ValueError as error:
raise InvalidInputError(str(error)) from error
return selected, opts

def validate_extract_request(
self,
items: list[Item],
*,
labels: list[str] | None = None,
output_schema: dict[str, Any] | None = None,
instruction: str | None = None,
options: dict[str, Any] | None = None,
) -> None:
_, opts = self._request_selection(options)
self._validate_decision_request(
items, labels=labels, output_schema=output_schema, instruction=instruction, options=opts
)

def load(self, device: str) -> None:
try:
super().load(device)
self._lora.bind(self._lora_model())
except BaseException:
self.unload()
raise

def unload(self) -> None:
try:
self._lora.close()
finally:
super().unload()

def extract(
self,
items: list[Item],
*,
labels: list[str] | None = None,
output_schema: dict[str, Any] | None = None,
instruction: str | None = None,
options: dict[str, Any] | None = None,
prepared_items: list[Any] | None = None,
) -> ExtractOutput:
selected, opts = self._request_selection(options)
self._validate_decision_request(
items, labels=labels, output_schema=output_schema, instruction=instruction, options=opts
)
with self._lora.lock:
self._lora.select(selected)
graphs = getattr(self, "_graphs", None)
if selected is not None:
if graphs is not None:
self._graphs = None
primed = getattr(self, "_primed", None)
if primed is not None:
primed.clear()
try:
return super().extract(
items,
labels=labels,
output_schema=output_schema,
instruction=instruction,
options=opts,
prepared_items=prepared_items,
)
finally:
try:
self._lora.select(None)
finally:
if graphs is not None:
self._graphs = graphs
if selected is not None:
primed = getattr(self, "_primed", None)
if primed is not None:
primed.clear()

def supports_lora(self) -> bool:
return True

def supports_hot_lora_reload(self) -> bool:
return True

def lora_runtime_config(self) -> LoraRuntimeConfig:
return self._lora.runtime_config()

def fetch_lora(self, lora_path: str, revision: str | None = None) -> str:
return self._lora.fetch(lora_path, revision)

def discard_lora_source(self, lora_path: str) -> None:
self._lora.discard(lora_path)

def load_lora(self, lora_path: str, revision: str | None = None, *, source: str | None = None) -> int:
return self._lora.load(lora_path, revision, source=source)

def unload_lora(self, lora_name: str) -> None:
self._lora.unload(lora_name)

def set_active_lora(self, lora_name: str | None) -> None:
self._lora.select(lora_name)
111 changes: 111 additions & 0 deletions packages/sie_server/src/sie_server/adapters/gliclass/runtime_lora.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Opt-in declared runtime LoRA for native GLiClass uni-encoder decisions."""

from __future__ import annotations

from pathlib import Path
from typing import Any

from transformers import BertModel, DebertaV2Model, ModernBertModel

from sie_server.adapters._peft_lora_runtime import DetachedLoraRuntime
from sie_server.adapters.decision_lora.adapter import DecisionLoraAdapter
from sie_server.adapters.gliclass import GLiClassAdapter
from sie_server.types.inputs import InvalidInputError, Item


class GLiClassLora(DetachedLoraRuntime):
"""Adapt only ordinary encoder projections, retaining the native scoring head."""

def _model_targets(self, model: Any) -> tuple[str, ...]:
from gliclass.model import GLiClassModel, GLiClassUniEncoder

if type(model) is not GLiClassModel or type(model.model) is not GLiClassUniEncoder:
raise ValueError("GLiClass LoRA requires the native uni-encoder model")
encoder = model.model.encoder_model
if type(encoder) is ModernBertModel:
return ("model.encoder_model.layers.",)
if type(encoder) in (BertModel, DebertaV2Model):
return ("model.encoder_model.encoder.layer.",)
raise ValueError("GLiClass LoRA supports ordinary BERT, DeBERTaV2 and ModernBERT encoders")


class GLiClassLoraAdapter(DecisionLoraAdapter, GLiClassAdapter):
"""Native labels and grouped decisions with explicit runtime LoRA declarations."""

def __init__(
self,
model_name_or_path: str | Path,
*,
lora_paths: dict[str, str],
lora_revisions: dict[str, str | None] | None = None,
max_loaded_loras: int = 10,
**kwargs: Any,
) -> None:
allowed = {
"classification_type",
"threshold",
"max_seq_length",
"compute_precision",
"revision",
"cuda_graphs",
"modernbert_flash",
}
if kwargs.keys() - allowed:
raise ValueError(f"GLiClass LoRA does not accept load options {sorted(kwargs.keys() - allowed)!r}")
self._lora = GLiClassLora(lora_paths, lora_revisions, max_loaded_loras)
super().__init__(model_name_or_path, **kwargs)

def _lora_model(self) -> Any:
return self._require_pipe().model

def _validate_decision_request(
self,
items: list[Item],
*,
labels: list[str] | None,
output_schema: dict[str, Any] | None,
instruction: str | None,
options: dict[str, Any],
) -> None:
allowed = {
"label_groups",
"group_encoding",
"classification_type",
"threshold",
"examples",
"cuda_graphs",
"overflow_policy",
}
if set(options) - allowed:
raise InvalidInputError("Unsupported GLiClass decision option")
if output_schema is not None:
raise InvalidInputError("GLiClass uses labels or options.label_groups, not output_schema")
groups = self._validate_label_groups(options.get("label_groups"))
encoding = self._validate_group_encoding(options, groups)
classification_type = self._validate_classification_type(
options.get("classification_type", self._classification_type)
)
if groups is None:
normalized_labels = self._validate_labels(labels)
elif labels:
raise InvalidInputError("GLiClass accepts either labels or options.label_groups, not both")
else:
normalized_labels = self._flatten_label_groups(groups, classification_type)
if groups is not None and encoding == "separate":
self._check_group_count(groups)
for _, group_labels in groups:
self._check_label_size(group_labels)
else:
self._check_label_size(normalized_labels)
prompt = self._validate_instruction(instruction)
examples = self._validate_examples(options.get("examples"), normalized_labels, groups)
self._check_context_size(prompt, examples)
self._validate_threshold(options.get("threshold", self._threshold))
self._request_cuda_graphs(options)
policy = options.get("overflow_policy", "default")
if not isinstance(policy, str) or policy not in {"default", "truncate_text", "error"}:
raise InvalidInputError("GLiClass overflow_policy must be default, truncate_text or error")
if not items:
raise InvalidInputError("GLiClass LoRA requires at least one item")
for item in items:
self._extract_text(item)
Loading
Loading