Text Classification
Transformers
ONNX
Safetensors
English
reflex_decision
feature-extraction
dual-encoder
multiple-choice
dynamic-labels
research
custom_code
reflex
Eval Results (legacy)
Instructions to use gai-labs/reflex-1 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use gai-labs/reflex-1 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="gai-labs/reflex-1", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("gai-labs/reflex-1", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 7,241 Bytes
5e054a6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | """Request validation and the token layout used to train Reflex-1."""
from collections.abc import Mapping, Sequence
from typing import Any
import torch
def questions_of(request: Mapping[str, Any]) -> list[Mapping[str, Any]]:
if not isinstance(request, Mapping):
raise ValueError("A request must be an object")
if not isinstance(request.get("state"), str):
raise ValueError("state must be a string")
if "questions" in request:
if "question" in request or "options" in request:
raise ValueError("Use either questions or a single question/options pair")
questions = request["questions"]
if not isinstance(questions, list) or not questions:
raise ValueError("questions must be a nonempty list")
else:
questions = [request]
for question in questions:
if not isinstance(question, Mapping):
raise ValueError("Each question must be an object")
text = question.get("question")
if not isinstance(text, str) or not text.strip():
raise ValueError("question must be a nonempty string")
options = question.get("options")
if not isinstance(options, list) or not 1 <= len(options) <= 255:
raise ValueError("options must be a list of 1 to 255 strings")
if any(not isinstance(option, str) or not option.strip() for option in options):
raise ValueError("Every option must be a nonempty string")
return questions
class RequestEncoder:
"""Preserve the original two-tokenizer budget and packed-question layout.
Tokenization is performed for every request. There is no cross-request token
or embedding cache. State truncation is reported to the caller.
"""
def __init__(self, context_tokenizer, option_tokenizer):
self.context_tokenizer = context_tokenizer
self.option_tokenizer = option_tokenizer
self.max_state_tokens = 512
self.max_request_tokens = 2048
self.max_option_tokens = 512
for name in ("cls_token_id", "sep_token_id", "mask_token_id", "pad_token_id"):
if getattr(context_tokenizer, name, None) is None:
raise ValueError(f"Context tokenizer requires {name}")
if option_tokenizer.pad_token_id is None:
raise ValueError("Option tokenizer requires pad_token_id")
def encode(self, request: Mapping[str, Any]) -> dict[str, Any]:
questions = questions_of(request)
tok = self.context_tokenizer
def tokens(text):
return tok.encode(text, add_special_tokens=False)
context_prefix = tokens("CONTEXT: ")
question_prefix = tokens("\nQUESTION: ")
answer_prefix = tokens("\nANSWER: [unused0] ")
packed = []
for question in questions:
qt = tokens(question["question"])
opts = [tokens(option) for option in question["options"]]
if not qt or any(not option for option in opts):
raise ValueError("Question and option text must produce tokens")
packed.append((qt, opts))
# Keep both budget checks: changing either would change trained input truncation.
required = 2 + len(context_prefix) + sum(
len(qt) + len(question_prefix) + len(answer_prefix) + 2
+ sum(map(len, opts)) for qt, opts in packed
)
if required > self.max_request_tokens:
raise ValueError("Questions and choices exceed the 2048-token request budget")
state = tokens(request["state"])
retained = state[:min(self.max_state_tokens, self.max_request_tokens - required)]
ids = [tok.cls_token_id] + context_prefix + retained + [tok.sep_token_id]
positions, owners, groups, option_tokens = [], [], [], []
for index, ((qt, _), question) in enumerate(zip(packed, questions)):
ids += question_prefix + qt + answer_prefix
positions.append(len(ids))
ids += [tok.mask_token_id, tok.sep_token_id]
group = []
for option in question["options"]:
encoded = self.option_tokenizer.encode(option, add_special_tokens=True)
if len(encoded) > self.max_option_tokens:
raise ValueError("An option exceeds the 512-token option encoder limit")
group.append(len(option_tokens))
option_tokens.append(encoded)
owners.append(index)
groups.append(group)
if len(ids) + sum(map(len, option_tokens)) > self.max_request_tokens:
raise ValueError("Context and complete options exceed the 2048-token request budget")
return {
"ids": ids, "positions": positions, "owners": owners,
"groups": groups, "option_tokens": option_tokens,
"state_tokens": len(state), "retained_state_tokens": len(retained),
}
def batch(self, records: Sequence[dict[str, Any]], device="cpu") -> dict[str, Any]:
if not records:
raise ValueError("A batch must contain at least one request")
count = len(records)
length = max(len(record["ids"]) for record in records)
questions = max(len(record["positions"]) for record in records)
options = max(len(record["owners"]) for record in records)
ids = torch.full((count, length), self.context_tokenizer.pad_token_id, dtype=torch.long)
attention = torch.zeros_like(ids)
positions = torch.zeros((count, questions), dtype=torch.long)
owners = torch.zeros((count, options), dtype=torch.long)
valid = torch.zeros((count, options), dtype=torch.bool)
indices = torch.zeros((count, options), dtype=torch.long)
unique, lookup = [], {}
for index, record in enumerate(records):
n = len(record["ids"])
ids[index, :n] = torch.tensor(record["ids"])
attention[index, :n] = 1
positions[index, :len(record["positions"])] = torch.tensor(record["positions"])
owners[index, :len(record["owners"])] = torch.tensor(record["owners"])
valid[index, :len(record["owners"])] = True
for oi, tokens in enumerate(record["option_tokens"]):
key = tuple(tokens)
if key not in lookup:
lookup[key] = len(unique)
unique.append(tokens)
indices[index, oi] = lookup[key]
label_length = max(map(len, unique))
label_ids = torch.full(
(len(unique), label_length), self.option_tokenizer.pad_token_id, dtype=torch.long
)
label_mask = torch.zeros_like(label_ids)
for index, tokens in enumerate(unique):
label_ids[index, :len(tokens)] = torch.tensor(tokens)
label_mask[index, :len(tokens)] = 1
tensors = {
"input_ids": ids, "attention_mask": attention, "positions": positions,
"owners": owners, "option_valid": valid, "option_indices": indices,
"label_ids": label_ids, "label_mask": label_mask,
}
return {**{key: value.to(device) for key, value in tensors.items()},
"groups": [record["groups"] for record in records]}
|