Skip to content

Token Classification and Span Extraction

mlxcel runs two kinds of per-token output model offline, each behind its own subcommand:

Shape Subcommand Labels Output
Token classification mlxcel tag fixed by the checkpoint (id2label, BIO or BIOES) tagged character spans and a redacted copy of the text
Span extraction mlxcel extract chosen per request (--labels, --labels-json, --classify) label-keyed span lists, or one class per task

The two shapes are separate because they take different inputs: a fixed-label classifier handed request labels would have to ignore them silently. Passing a checkpoint to the other subcommand fails with a message naming the right one.

Neither shape is served over HTTP. mlx-vlm, the reference implementation, defines no route for them, so there is no upstream schema to match.

Supported Families

Family Detection Kind Decode
BERT / XLM-RoBERTa token classifier model_type bert, xlm-roberta or xlm_roberta, architectures[0] ending in ForTokenClassification, non-empty id2label bert_token_classifier per-token argmax
OpenAI Privacy Filter model_type openai_privacy_filter, non-empty id2label openai_privacy_filter constrained BIOES Viterbi (default), per-token argmax with --argmax
GLiNER2.5 span extractor model_type gliner2 or gliner2_5, or an architecture naming Gliner2ForSchemaExtraction or BoundaryExtractor gliner2_5 sigmoid span scores over a shared candidate pool, overlaps resolved per label

Detection reads config.json only and is separate from the generation, embedding, and rerank dispatch. mlxcel embed still loads a BertForTokenClassification checkpoint as a BERT embedder, and mlxcel generate refuses a token classifier with a message naming mlxcel tag.

mlxcel tag

mlxcel tag -m nativ-community/rampart-mlx-fp16 --keep CITY,STATE,ZIP_CODE \
  "I'm Sarah Connor, 1984 Cyberdyne Ave, Los Angeles CA 90012, sarah@sky.net"
mlxcel tag -m mlx-community/openai-privacy-filter-bf16 "Alice Smith can be reached at alice@example.com."
Flag Description
-m, --model <PATH_OR_REPO_ID> Checkpoint directory or HuggingFace repo-id, resolved and downloaded like mlxcel generate -m.
TEXT The text to tag. - reads it from stdin.
--argmax Force per-token decoding on a checkpoint whose default decoder is Viterbi.
--replacement <STR> The string every redacted span becomes, instead of its placeholder.
--keep <L1,L2> Entity labels reported in spans but left in redacted_text. Unknown labels are ignored.
--operating-point <NAME> Calibration operating point of a Viterbi-decoded checkpoint (default default).
--models-dir <PATH> Model-store root for repo-id resolution.

The JSON document goes to stdout and a one-line summary (span count, kind, model_type) goes to stderr:

{
  "text": "Alice Smith can be reached at alice@example.com.",
  "spans": [
    {"label": "private_person", "start": 0, "end": 11, "text": "Alice Smith", "placeholder": "<PRIVATE_PERSON>"},
    {"label": "private_email", "start": 30, "end": 47, "text": "alice@example.com", "placeholder": "<PRIVATE_EMAIL>"}
  ],
  "redacted_text": "<PRIVATE_PERSON> can be reached at <PRIVATE_EMAIL>."
}

Only redacted_text is redacted. text and every spans[].text still hold the original values, so pass on redacted_text alone when the output leaves a trusted context.

Long text is never truncated. The tokenizer must report character offsets, and the ids are split into windows of the model's context size, wrapped in the checkpoint's special tokens, scored, decoded, and concatenated. Character ranges come from the offsets with surrounding whitespace trimmed, and a word split across a window boundary still forms one span. Offsets are Unicode scalar indices into text, matching Python string indexing.

A placeholder is < plus the label upper-cased, with every run of non-alphanumeric characters replaced by _, plus >. --replacement overrides it.

OpenAI Privacy Filter

openai/privacy-filter is a 1.4B-parameter bidirectional sparse-MoE encoder that tags PII with BIOES labels. mlxcel runs it through the same pipeline as mlxcel tag with the upstream settings: 128000-token windows, no special tokens, and no word grouping.

The default decoder is a constrained Viterbi path over the BIOES lattice, which is why the output never contains a label path the span builder has to repair. --argmax switches to independent per-token decoding.

viterbi_calibration.json in the checkpoint may carry named operating points, each a set of six additive log-domain biases on the lattice transitions. --operating-point selects one; the published checkpoints ship a single default point with every bias at zero, and the flag is refused for checkpoints that do not decode with Viterbi.

mlxcel extract

mlxcel extract -m fastino/gliner2.5-small-v1 --labels company,person,location \
  --include-confidence --include-spans "Apple hired Sam in New York."
mlxcel extract -m fastino/gliner2.5-small-v1 \
  --classify '{"sentiment": ["positive", "neutral", "negative"]}' "The launch was excellent."

Exactly one of --labels, --labels-json, and --classify is required.

Flag Default Description
-m, --model <PATH_OR_REPO_ID> required GLiNER2.5 checkpoint.
--labels <L1,L2> - Comma-separated entity labels.
--labels-json <JSON> - Label map, where a value is a description or null.
--classify <JSON> - Classification tasks, as {"<task>": [labels]} or an object with labels, prompt, label_descriptions, multi_label, and threshold.
--threshold <F> 0.5 Minimum entity score, and the default multi-label threshold.
--include-confidence off Add each span's confidence to the output.
--include-spans off Add each span's start and end offsets.
--max-len <N> checkpoint value GLiNER2.5 only. Longest text window, an integer in 1..=32768.
--word-splitter <whitespace\|char> whitespace GLiNER2.5 only. char splits one word per non-space character, for scripts without word spacing such as CJK.
--models-dir <PATH> global store Model-store root for repo-id resolution (env MLXCEL_MODELS_DIR).

Entity output is {"entities": {"<label>": [...]}} with one key per requested label, in request order. --classify prints {"<task>": "<label>"}, or a label list for a multi-label task.