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
Download encoding_reflex.py from gai-labs/reflex-1: direct link, hf CLI and curl.
- Browser
- Download file 7.24 kB
-
https://huggingface.co/gai-labs/reflex-1/resolve/main/encoding_reflex.py
- Command line
-
hf download hf://gai-labs/reflex-1/encoding_reflex.py
-
curl -L -o encoding_reflex.py https://huggingface.co/gai-labs/reflex-1/resolve/main/encoding_reflex.py
7.24 kB
| """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]} | |