Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 70 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Tests

on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: pytest (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-py${{ matrix.python-version }}-pip-${{ hashFiles('pyproject.toml') }}
restore-keys: |
${{ runner.os }}-py${{ matrix.python-version }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .
pip install pytest pytest-asyncio

- name: Run pytest
run: pytest -v --tb=short

lint:
name: ruff
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install ruff
run: pip install ruff

- name: ruff check
run: ruff check gliclass

- name: ruff format --check
run: ruff format --check gliclass
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ gradio_cached_examples/
test.ipynb
demo1.py
.gradio/
uv.lock

# Distribution / packaging
.Python
Expand Down Expand Up @@ -57,6 +58,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
.ruff_cache/
cover/

# Translations
Expand Down
60 changes: 58 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,64 @@ for predict in results:
print(f"{predict['label']} => {predict['score']:.3f}")
```

### 🚀 Production Serving

Deploy GLiClass with Ray Serve for production workloads with dynamic batching and memory-aware processing.

#### Installation

```bash
pip install gliclass[serve]
```

#### Quick Start

```bash
# Default model
python -m gliclass.serve

# Specify model and port
python -m gliclass.serve --model knowledgator/gliclass-edge-v3.0 --port 8000

# With config file
python -m gliclass.serve --config serve_configs/serve_config.yaml
```

#### Python Client

```python
from gliclass.serve import GLiClassClient

client = GLiClassClient(url="http://localhost:8000/gliclass")

result = client.classify(
text="This is a great product!",
labels=["positive", "negative", "neutral"],
threshold=0.3,
)
print(result) # [{"label": "positive", "score": 0.95}, ...]
```

#### HTTP API

The HTTP endpoint processes one text per request.

```bash
curl -X POST http://localhost:8000/gliclass \
-H "Content-Type: application/json" \
-d '{
"texts": "This is a great product!",
"labels": ["positive", "negative", "neutral"],
"threshold": 0.3
}'

# Response: [{"label": "positive", "score": 0.95}, ...]
```

**Note:** For batch processing multiple texts, use the `ZeroShotClassificationPipeline` directly instead of the serving API.

See `serve_configs/serve_config.yaml` for full configuration options.

### 🎯 Key Use Cases

- **Sentiment Analysis:** Rapidly classify texts as positive, negative, or neutral.
Expand Down Expand Up @@ -281,8 +339,6 @@ config = GLiClassModelConfig(
)
```

Gotcha — here’s a **much leaner, README-style version**, no fluff, just what matters 👇

---

### Flash Attention Backends
Expand Down
14 changes: 12 additions & 2 deletions gliclass/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
from .model import GLiClassModel, GLiClassBiEncoder, GLiClassUniEncoder, GLiClassEncoderDecoderCLS
from .config import GLiClassModelConfig
from .pipeline import ZeroShotClassificationPipeline, BiEncoderZeroShotClassificationPipeline, ZeroShotClassificationWithChunkingPipeline
from .pipeline import (
ZeroShotClassificationPipeline,
BiEncoderZeroShotClassificationPipeline,
ZeroShotClassificationWithChunkingPipeline,
)

__version__ = "0.1.18"
__version__ = "0.1.18"

# Serve module (optional import)
try:
from . import serve
except ImportError:
serve = None
69 changes: 32 additions & 37 deletions gliclass/config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from transformers import AutoConfig
from transformers.configuration_utils import PretrainedConfig
from transformers.utils import logging
from transformers.models.auto import CONFIG_MAPPING
from transformers.configuration_utils import PretrainedConfig

from .utils import is_module_available

IS_TURBOT5 = is_module_available('turbot5')
IS_TURBOT5 = is_module_available("turbot5")

if IS_TURBOT5:
from turbot5.model.config import T5Config
Expand All @@ -22,50 +22,48 @@ class GLiClassModelConfig(PretrainedConfig):

def __init__(
self,
encoder_config = None,
encoder_config=None,
encoder_model=None,
label_model_config=None,
label_model_name=None,
class_token_index = -1,
text_token_index = -1,
example_token_index = -1,
class_token_index=-1,
text_token_index=-1,
example_token_index=-1,
ignore_index=-100,
hidden_size=None,
projector_hidden_act="gelu",
vocab_size=None,
problem_type='single_label_classification',
problem_type="single_label_classification",
max_num_classes=25,
use_lstm=False,
initializer_range=0.03,
scorer_type='simple',
scorer_type="simple",
scorer_num_heads=16,
scorer_mlp_hidden_size=1024,
scorer_attn_dropout=0.1,
pooling_strategy='first',
pooling_strategy="first",
class_token_pooling="first",
focal_loss_alpha=0.5,
focal_loss_gamma=2,
focal_loss_reduction=None,
logit_scale_init_value=2.6592,
normalize_features=False,
extract_text_features=False,
max_labels_alloc: str = 'dynamic',
max_labels_alloc: str = "dynamic",
contrastive_loss_coef=0,
architecture_type = 'uni-encoder',
prompt_first = False,
squeeze_layers = False,
layer_wise = False,
encoder_layer_id = -1,
embed_class_token = True,
dropout = 0.1,
use_segment_embeddings = False,
architecture_type="uni-encoder",
prompt_first=False,
squeeze_layers=False,
layer_wise=False,
encoder_layer_id=-1,
embed_class_token=True,
dropout=0.1,
use_segment_embeddings=False,
**kwargs,
):
if isinstance(encoder_config, dict):
encoder_config["model_type"] = (encoder_config["model_type"]
if "model_type" in encoder_config
else "deberta-v2")
if encoder_config['model_type'] == 't5':
encoder_config["model_type"] = encoder_config.get("model_type", "deberta-v2")
if encoder_config["model_type"] == "t5":
encoder_config = T5Config(**encoder_config)
elif encoder_config["model_type"] in CONFIG_MAPPING:
encoder_config = CONFIG_MAPPING[encoder_config["model_type"]](**encoder_config)
Expand All @@ -83,9 +81,7 @@ def __init__(

if label_model_name is not None:
if isinstance(label_model_config, dict):
label_model_config["model_type"] = (label_model_config["model_type"]
if "model_type" in label_model_config
else "deberta-v2")
label_model_config["model_type"] = label_model_config.get("model_type", "deberta-v2")
label_model_config = CONFIG_MAPPING[label_model_config["model_type"]](**label_model_config)
elif label_model_config is None:
label_model_config = CONFIG_MAPPING["deberta-v2"]()
Expand All @@ -104,40 +100,40 @@ def __init__(
self.vocab_size = self.encoder_config.vocab_size
else:
self.vocab_size = vocab_size

if class_token_index == -1:
self.class_token_index = self.vocab_size
else:
self.class_token_index = class_token_index

if text_token_index == -1:
self.text_token_index = self.vocab_size+1
self.text_token_index = self.vocab_size + 1
else:
self.text_token_index = text_token_index

if example_token_index == -1:
self.example_token_index = self.vocab_size+2
self.example_token_index = self.vocab_size + 2
else:
self.example_token_index = example_token_index

self.ignore_index = ignore_index
self.projector_hidden_act = projector_hidden_act
self.problem_type = problem_type
self.max_num_classes = max_num_classes
self.initializer_range=initializer_range
self.initializer_range = initializer_range
self.scorer_type = scorer_type
self.scorer_num_heads = scorer_num_heads
self.scorer_mlp_hidden_size = scorer_mlp_hidden_size
self.scorer_attn_dropout = scorer_attn_dropout
self.pooling_strategy=pooling_strategy
self.class_token_pooling=class_token_pooling
self.pooling_strategy = pooling_strategy
self.class_token_pooling = class_token_pooling
self.use_lstm = use_lstm
self.focal_loss_alpha=focal_loss_alpha
self.focal_loss_gamma=focal_loss_gamma
self.focal_loss_alpha = focal_loss_alpha
self.focal_loss_gamma = focal_loss_gamma
self.focal_loss_reduction = focal_loss_reduction
self.contrastive_loss_coef=contrastive_loss_coef
self.contrastive_loss_coef = contrastive_loss_coef
self.logit_scale_init_value = logit_scale_init_value
self.normalize_features=normalize_features
self.normalize_features = normalize_features
self.extract_text_features = extract_text_features
self.max_labels_alloc = max_labels_alloc
self.architecture_type = architecture_type
Expand All @@ -150,4 +146,3 @@ def __init__(
self.dropout = dropout
self.use_segment_embeddings = use_segment_embeddings
super().__init__(**kwargs)

Loading
Loading