Skip to content

feat: activate Focal Loss, add Dice Loss + span-width weighting, OpenVINO INT8 pipeline - #361

Open
ALI-AL-MARJANI wants to merge 5 commits into
urchade:mainfrom
ALI-AL-MARJANI:feat/focal-dice-loss-openvino
Open

feat: activate Focal Loss, add Dice Loss + span-width weighting, OpenVINO INT8 pipeline#361
ALI-AL-MARJANI wants to merge 5 commits into
urchade:mainfrom
ALI-AL-MARJANI:feat/focal-dice-loss-openvino

Conversation

@ALI-AL-MARJANI

Copy link
Copy Markdown

Summary

1. Loss functions (gliner/modeling/loss_functions.py, base.py, trainer.py)

focal_loss_with_logits already exists in the codebase but is disabled by default
(alpha=-1, gamma=0). This PR:

  • Exposes loss_type, focal_loss_alpha, focal_loss_gamma in TrainingArguments so
    users can activate it
  • Adds span_dice_loss() — span-level Dice Loss adapted from Li et al. (ACL 2020),
    applied element-wise over the (B, L×K, T) logit tensor with ignore_index masking
  • Adds use_span_width_weight flag: positive spans of width k receive w(k) = 1 + log(k+1) — zero inference overhead

Motivation: WNUT-17 has 187× more negative spans than positive entities (0.53%
positive ratio). BCE's gradient is dominated by trivial negatives. Focal α=0.25 delivers
+0.99 pp WNUT-17 F1; Dice delivers +0.70 pp.

2. Bug fixes (gliner/utils.py, gliner/modeling/encoder.py, gliner/model.py,

gliner/onnx/model.py)

  • is_module_available() changed from __import__() to importlib.util.find_spec()
    prevents optional packages (peft, tensorflow) from being eagerly imported, which caused
    OpenMP deadlocks on macOS ARM
  • encoder.py: kwargs.pop("token_lengths", None) prevents crash on bi-encoder models
    that pass this GLiNER-internal kwarg to HuggingFace forward methods
  • Lazy import of Trainer/TrainingArguments in model.py — avoids importing
    torch.distributed at module load time

3. OpenVINO INT8 pipeline (scripts/convert_to_openvino.py)

New script: ONNX → OpenVINO IR → INT8 weight compression via
nncf.compress_weights(INT8_ASYM).

  • 2.35× CPU speedup, 4× model size reduction, no accuracy degradation
  • Uses weight-only compression (not activation quantization) because GLiNER's ONNX graph
    contains If nodes with dynamic rank that the CPU plugin rejects during calibration-based
    quantization

Benchmark

Config WNUT-17 F1 Δ
BCE baseline 50.09%
Focal α=0.25, γ=2 51.08% +0.99 pp
Dice Loss 50.79% +0.70 pp
Backend Latency Speedup Size
PyTorch FP32 59ms 721MB
OpenVINO INT8 25ms 2.35× 181MB

Model: knowledgator/gliner-bi-small-v1.0, 200 fine-tuning steps on CoNLL-2003, eval on
WNUT-17.

@Ingvarstep
Ingvarstep self-requested a review May 31, 2026 17:27

@Ingvarstep Ingvarstep left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for your great contribution. I have a few suggestions:

  1. First, use importing utils so the project is consistent from this perspective;
  2. Move ablation scripts into scripts/dice_loss_study, or something like that;

Comment thread gliner/onnx/model.py Outdated
import numpy as np
import torch
import onnxruntime as ort
try:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use

def is_module_available(module_name):
instead, so we have consistency over the repo.

Ali322O and others added 3 commits July 24, 2026 14:23
Addresses review feedback from Ingvarstep on urchade#361:

- gliner/onnx/model.py: replaced the local `try/except ImportError` around
  `import onnxruntime` with the same is_module_available() pattern used
  elsewhere in the codebase (gliner/modeling/encoder.py's IS_PEFT/IS_LLM2VEC
  etc.), for consistency.
- Moved scripts/{baseline_eval,convert_to_openvino,train_ablation,
  visualize_results}.py into scripts/dice_loss_study/ -- they're four
  numbered steps (Step 1-4) of one self-contained ablation study pipeline,
  not independent general-purpose scripts. Updated each script's own
  docstring usage example to the new path. No cross-script imports and no
  __file__-relative path logic exist between them, so the move doesn't
  change how they locate the shared results/ directory (all paths are
  CWD-relative, assuming repo-root invocation, unchanged).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…x ruff

Found while addressing the consistency review comment, beyond the two
explicitly requested files:

- gliner/model.py had onnxruntime imported twice: an unguarded try/except
  at the top of the file (dead code, immediately shadowed by the existing
  is_module_available()-gated import further down) and the real one.
  Removed the redundant one.
- The existing is_module_available()-gated block had been changed to set
  ONNX_AVAILABLE = True unconditionally, even without onnxruntime
  installed, while _check_onnx_export_preconditions still raised
  "onnxruntime is not available" behind a now-dead `if not ONNX_AVAILABLE`
  check. Traced the actual export call chain
  (export_to_onnx -> _run_torch_onnx_export): it only uses
  torch.onnx.export, never `ort` -- onnxruntime is only needed to later
  *load*/*run* an exported model, not to export one. So the underlying
  behavior change (allow export without onnxruntime) was correct; the
  dead/misleading check was the bug. Removed the check, restored
  ONNX_AVAILABLE to truthfully reflect is_module_available("onnxruntime").
- The lazy Trainer/TrainingArguments import (avoids a torch.distributed
  import deadlock on macOS ARM) had dropped create_training_args'
  entire docstring (all Args: entries) and both methods' type hints in the
  process. Restored the docstring verbatim and the return-type hints via
  `if TYPE_CHECKING: from .training import Trainer, TrainingArguments` +
  string forward-refs, so IDEs/static analysis get full types back without
  reintroducing the eager import.
- Moved the lazy-import helper out of the middle of the top-level import
  block (was causing every import after it to trip E402).

Ruff (gliner/config.py, gliner/model.py, gliner/modeling/base.py,
gliner/onnx/model.py, gliner/training/trainer.py): same PLR0917
version-drift issue as the other two branches, plus a few genuinely
introduced by this PR's own diff -- missing docstring args for the new
loss_type/dice_gamma params, an unused unpacked variable, two lazy imports
needing noqa, and numpy/torch movable into a TYPE_CHECKING block in
gliner/onnx/model.py (that file already has `from __future__ import
annotations`; every use is in a type hint, none at runtime).

317 passed, 1 skipped, ruff check gliner clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ALI-AL-MARJANI

Copy link
Copy Markdown
Author
  • Consistency: gliner/onnx/model.py now uses is_module_available() instead of the local try/except around the onnxruntime import. While at it, found the same onnxruntime import duplicated in gliner/model.py itself (an unguarded try/except shadowed by the existing is_module_available()-gated block further down) — removed the dead one. Also found that block had been changed to hardcode ONNX_AVAILABLE = True even without onnxruntime installed; traced the actual export_to_onnx() call chain and confirmed it only needs torch.onnx.export, never ort — so the behavior was actually correct, just left a dead/misleading precondition check behind. Removed that, restored ONNX_AVAILABLE to truthfully reflect availability.

  • Scripts: moved baseline_eval.py, convert_to_openvino.py, train_ablation.py, visualize_results.py into scripts/dice_loss_study/ — they're four numbered steps of one self-contained pipeline, not independent scripts.

Also merged latest main (52 commits — the branch was quite stale) to pick up the streaming feature and clear the merge conflict. Along the way restored a docstring that had been accidentally dropped from create_training_args during the lazy-import fix for the macOS deadlock, and fixed the ruff errors introduced by that version drift (same story as the other two PRs — pip install ruff with no version pin, ended up on 0.16.0 mid-review).
Ruff clean, 340 tests passing. Let me know if this all looks right

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants