Feature Extraction
Transformers
Safetensors
modernbert
typed-decisions
decision-index
decision-model
cross-encoder
text-embeddings-inference
Instructions to use tasksource/tasksource-decider-nano with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use tasksource/tasksource-decider-nano with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="tasksource/tasksource-decider-nano")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("tasksource/tasksource-decider-nano") model = AutoModel.from_pretrained("tasksource/tasksource-decider-nano", device_map="auto") - Notebooks
- Google Colab
- Kaggle
v2: shared-state joint cross-encoder (ettin-reranker-150m), Decision Index 0.3 run; v1 kept at tag v1
Browse files- 1_Dense/config.json +0 -7
- 1_Dense/model.safetensors +0 -3
- 2_Dense/config.json +0 -7
- 2_Dense/model.safetensors +0 -3
- 3_Dense/config.json +0 -7
- 3_Dense/model.safetensors +0 -3
- README.md +71 -70
- config.json +14 -7
- config_sentence_transformers.json +0 -54
- decider.py +272 -0
- decider_config.json +32 -0
- decision_index_engine.py +50 -0
- decision_meta.json +0 -37
- model.safetensors +2 -2
- modules.json +0 -26
- sentence_bert_config.json +0 -4
- tokenizer.json +2 -45
- tokenizer_config.json +4 -7
1_Dense/config.json
DELETED
|
@@ -1,7 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"in_features": 768,
|
| 3 |
-
"out_features": 1536,
|
| 4 |
-
"bias": false,
|
| 5 |
-
"activation_function": "torch.nn.modules.linear.Identity",
|
| 6 |
-
"use_residual": true
|
| 7 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1_Dense/model.safetensors
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:7575f7325b112aee78ff749c0f9fca59c827ec1769b7f267ce030a3e0a63bb44
|
| 3 |
-
size 9437360
|
|
|
|
|
|
|
|
|
|
|
|
2_Dense/config.json
DELETED
|
@@ -1,7 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"in_features": 1536,
|
| 3 |
-
"out_features": 768,
|
| 4 |
-
"bias": false,
|
| 5 |
-
"activation_function": "torch.nn.modules.linear.Identity",
|
| 6 |
-
"use_residual": true
|
| 7 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2_Dense/model.safetensors
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:8001a5b0366548829e3029afd3ca769e307fa8bfd60ddd2c06c1ef22dfae824e
|
| 3 |
-
size 9437360
|
|
|
|
|
|
|
|
|
|
|
|
3_Dense/config.json
DELETED
|
@@ -1,7 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"in_features": 768,
|
| 3 |
-
"out_features": 128,
|
| 4 |
-
"bias": false,
|
| 5 |
-
"activation_function": "torch.nn.modules.linear.Identity",
|
| 6 |
-
"use_residual": false
|
| 7 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3_Dense/model.safetensors
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:b8046cc411a84607fdd84b8776c59674c31de51670a29a141a831ca25111563e
|
| 3 |
-
size 393304
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,92 +1,93 @@
|
|
| 1 |
---
|
| 2 |
license: apache-2.0
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
library_name: PyLate
|
| 6 |
-
pipeline_tag: decision-model
|
| 7 |
-
base_model: lightonai/LateOn
|
| 8 |
datasets:
|
| 9 |
- tasksource/tasksource-jev-typed-decisions
|
|
|
|
| 10 |
tags:
|
| 11 |
-
-
|
| 12 |
-
-
|
| 13 |
-
-
|
| 14 |
-
- multi-vector
|
| 15 |
-
- PyLate
|
| 16 |
-
- sentence-transformers
|
| 17 |
---
|
| 18 |
|
| 19 |
-
# tasksource-
|
| 20 |
|
| 21 |
-
|
|
|
|
| 22 |
|
| 23 |
-
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
|
| 40 |
-
|
| 41 |
-
T = 0.31683406233787537 # learned temperature (decision_meta.json: init_temperature)
|
| 42 |
|
| 43 |
-
|
| 44 |
-
ctx = model.encode([f"{state}\nQuestion: {question}"], is_query=False)[0]
|
| 45 |
-
opts = model.encode(options, is_query=True)
|
| 46 |
-
scores = torch.stack([colbert_scores(torch.as_tensor(o)[None], torch.as_tensor(ctx)[None])[0, 0] for o in opts])
|
| 47 |
-
return dict(zip(options, torch.softmax(scores / T, 0).tolist()))
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
```
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
When an option has both a key and a description, the engine renders it as `"key: description"`, or as the description alone when the key is a placeholder like `A` or `option_1`.
|
| 56 |
-
|
| 57 |
-
## Training
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
- **Checkpoint selection:** NLL on a held-out set of unseen Tasksource tasks.
|
| 66 |
-
|
| 67 |
-
## Decision Index results (public index)
|
| 68 |
-
|
| 69 |
-
These are scores on the **public** part of the Decision Index, run with the official kit: full suite, 100% coverage, chance-corrected skill, 0–100.
|
| 70 |
-
|
| 71 |
-
| Edition | Public index (skill) | Raw |
|
| 72 |
-
|---|---|---|
|
| 73 |
-
| 0.3 | **10.84** | 33.12 |
|
| 74 |
-
| 0.2.1 | 10.46 | 32.09 |
|
| 75 |
-
|
| 76 |
-
0.3 areas (skill): Knowledge & Reasoning 2.2 · Language Understanding 5.0 · Retrieval & Classification 23.5 · Tools & Automation 13.6 · Arts & Human Taste 17.7.
|
| 77 |
-
|
| 78 |
-
**This is not the board's headline number.** From 0.3 on, the board ranks a *Full* score:
|
| 79 |
-
|
| 80 |
-
| Part | Weight | Who runs it |
|
| 81 |
-
|---|---|---|
|
| 82 |
-
| Public index | 20% | Anyone, with the kit; the table above |
|
| 83 |
-
| Private tests of the same skills | 50% | Maintainers |
|
| 84 |
-
| Private tasks from new domains | 30% | Maintainers |
|
| 85 |
|
| 86 |
-
|
| 87 |
|
| 88 |
-
##
|
| 89 |
|
| 90 |
-
|
| 91 |
-
-
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
license: apache-2.0
|
| 3 |
+
library_name: transformers
|
| 4 |
+
base_model: cross-encoder/ettin-reranker-150m-v1
|
|
|
|
|
|
|
|
|
|
| 5 |
datasets:
|
| 6 |
- tasksource/tasksource-jev-typed-decisions
|
| 7 |
+
- tasksource/synthetic-typed-decisions
|
| 8 |
tags:
|
| 9 |
+
- typed-decisions
|
| 10 |
+
- decision-index
|
| 11 |
+
- cross-encoder
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# tasksource-decider-nano
|
| 15 |
|
| 16 |
+
This repository was `tasksource/tasksource-jev-nano-v1`. Its `main` (tag `v2`) is now a new model; the previous
|
| 17 |
+
multi-vector model (v1) stays at tag [`v1`](https://huggingface.co/tasksource/tasksource-decider-nano/tree/v1).
|
| 18 |
|
| 19 |
+
A 150M-parameter typed-decision model for the [Decision Index](https://huggingface.co/spaces/multimodalart/jev-decision-index).
|
| 20 |
+
It takes a `state` and typed questions (`choice`, `noul`) and returns a probability for every option. It does not
|
| 21 |
+
generate text.
|
| 22 |
|
| 23 |
+
| | |
|
| 24 |
+
|---|---|
|
| 25 |
+
| Decision Index 0.3, public index | **26.77** |
|
| 26 |
+
| Requests | 140,171 `ok`, 7 `unsupported` (pairs above 8,192 tokens) |
|
| 27 |
+
| Latency, 1× NVIDIA A10 23 GB, one request at a time | median 45 ms · mean 71 ms · p80 71 ms |
|
| 28 |
+
| Complete results | [runs/tasksource-decider-nano](https://huggingface.co/datasets/tasksource/decision-index-results/tree/main/runs/tasksource-decider-nano) |
|
| 29 |
|
| 30 |
+
## Architecture
|
| 31 |
|
| 32 |
+
A joint cross-encoder on [cross-encoder/ettin-reranker-150m-v1](https://huggingface.co/cross-encoder/ettin-reranker-150m-v1)
|
| 33 |
+
(ModernBERT-style encoder, 8,192-token context). Every option is scored against the full state, its question
|
| 34 |
+
and the option text, like the reranker pair `(state, "Question: <instructions>\nOption: <option>")`, by a linear
|
| 35 |
+
head on the mean of the option's hidden states. The scores are softmaxed over each question's options.
|
| 36 |
|
| 37 |
+
The state is encoded **once per request**. All questions and options of a request share it through a tree
|
| 38 |
+
attention mask: the state attends to itself, a question to the state and itself, and an option to the state, its
|
| 39 |
+
question and itself. Rotary positions are those of the single pair, so every option gets the score it would get on its
|
| 40 |
+
own (up to float rounding), while a request with 50 options costs one state encoding instead of 50.
|
| 41 |
|
| 42 |
+
Inference reads every input in full. There is no truncation: a pair above 8,192 tokens raises `Unsupported`.
|
|
|
|
| 43 |
|
| 44 |
+
## Usage
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
+
```python
|
| 47 |
+
from decider import Decider
|
| 48 |
+
model = Decider.from_pretrained("tasksource/tasksource-decider-nano")
|
| 49 |
+
model.answer(state={"review": "The battery died after a week."},
|
| 50 |
+
questions={"q": {"type": "choice", "instructions": "What is the sentiment?",
|
| 51 |
+
"criteria": {"neg": "negative", "pos": "positive"}}})
|
| 52 |
```
|
| 53 |
|
| 54 |
+
Decision Index run (kit 0.3, `9eb2dbe`), from a local copy of this repository:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
+
```bash
|
| 57 |
+
hf download tasksource/tasksource-decider-nano --local-dir tasksource-decider-nano
|
| 58 |
+
cd tasksource-decider-nano
|
| 59 |
+
python -m decision_index pipeline --engine decision_index_engine:DeciderEngine \
|
| 60 |
+
--option repo=. --edition 0.3 --out runs/tasksource-decider-nano
|
| 61 |
+
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
+
`decider.py` and `decision_index_engine.py` are self-contained (PyTorch and transformers only).
|
| 64 |
|
| 65 |
+
## Training
|
| 66 |
|
| 67 |
+
12,000 steps, batches of 8 requests, learning rate 2e-5, inputs up to 2,048 tokens, seed 7. The loss is soft
|
| 68 |
+
cross-entropy over each question's options. The training mix has 240,000 requests:
|
| 69 |
+
|
| 70 |
+
| Share | Source |
|
| 71 |
+
|---:|---|
|
| 72 |
+
| 42% | [tasksource/tasksource-jev-typed-decisions](https://huggingface.co/datasets/tasksource/tasksource-jev-typed-decisions) (658 tasks, at most 600 questions per task) |
|
| 73 |
+
| 30% | *train* splits of public datasets behind Decision Index benchmarks, e.g. MMLU, ANLI, WinoGrande, HellaSwag, ARC, CLINC150, BANKING77, Amazon ESCI, HoVer, RAGTruth, New Yorker caption contest, When2Call, ACOS, iSarcasmEval, NLI4CT, ContractNLI, GSM8K (at most 10,000 requests each) |
|
| 74 |
+
| 12% | tool use: ToolRet train, Glaive function calling, Hermes function calling |
|
| 75 |
+
| 8% | LLM routing: xRouteBench and LLMRouterBench train data |
|
| 76 |
+
| 8% | [tasksource/synthetic-typed-decisions](https://huggingface.co/datasets/tasksource/synthetic-typed-decisions) |
|
| 77 |
+
|
| 78 |
+
No evaluation split is used, and benchmarks without a train split (for example RouterBench) are evaluation only.
|
| 79 |
+
Every training request goes through a firewall against the Decision Index 0.3 suite: an exact match of the
|
| 80 |
+
normalized state or of the (state, question) pair, and a template-insensitive word 6-gram content check, drop the
|
| 81 |
+
request.
|
| 82 |
+
|
| 83 |
+
## Evaluation
|
| 84 |
+
|
| 85 |
+
| Area | Skill | Raw |
|
| 86 |
+
|---|---:|---:|
|
| 87 |
+
| Knowledge & Reasoning | 6.0 | 29.5 |
|
| 88 |
+
| Language Understanding | 28.2 | 47.9 |
|
| 89 |
+
| Retrieval & Classification | 42.0 | 58.2 |
|
| 90 |
+
| Tools & Automation | 44.6 | 49.8 |
|
| 91 |
+
| Arts & Human Taste | 13.9 | 43.5 |
|
| 92 |
+
|
| 93 |
+
Raw index 45.1; kit `9eb2dbe`, edition 0.3, 140,171 requests scored.
|
config.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
| 4 |
],
|
| 5 |
"attention_bias": false,
|
| 6 |
"attention_dropout": 0.0,
|
| 7 |
-
"bos_token_id":
|
|
|
|
| 8 |
"classifier_activation": "gelu",
|
| 9 |
"classifier_bias": false,
|
| 10 |
"classifier_dropout": 0.0,
|
|
@@ -14,14 +15,21 @@
|
|
| 14 |
"deterministic_flash_attn": false,
|
| 15 |
"dtype": "float32",
|
| 16 |
"embedding_dropout": 0.0,
|
| 17 |
-
"eos_token_id":
|
| 18 |
"global_attn_every_n_layers": 3,
|
| 19 |
"gradient_checkpointing": false,
|
| 20 |
"hidden_activation": "gelu",
|
| 21 |
"hidden_size": 768,
|
|
|
|
|
|
|
|
|
|
| 22 |
"initializer_cutoff_factor": 2.0,
|
| 23 |
"initializer_range": 0.02,
|
| 24 |
"intermediate_size": 1152,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
"layer_norm_eps": 1e-05,
|
| 26 |
"layer_types": [
|
| 27 |
"full_attention",
|
|
@@ -48,7 +56,7 @@
|
|
| 48 |
"full_attention"
|
| 49 |
],
|
| 50 |
"local_attention": 128,
|
| 51 |
-
"max_position_embeddings":
|
| 52 |
"mlp_bias": false,
|
| 53 |
"mlp_dropout": 0.0,
|
| 54 |
"model_type": "modernbert",
|
|
@@ -57,15 +65,14 @@
|
|
| 57 |
"num_attention_heads": 12,
|
| 58 |
"num_hidden_layers": 22,
|
| 59 |
"pad_token_id": 50283,
|
| 60 |
-
"position_embedding_type": "
|
| 61 |
-
"repad_logits_with_grad": false,
|
| 62 |
"rope_parameters": {
|
| 63 |
"full_attention": {
|
| 64 |
"rope_theta": 160000.0,
|
| 65 |
"rope_type": "default"
|
| 66 |
},
|
| 67 |
"sliding_attention": {
|
| 68 |
-
"rope_theta":
|
| 69 |
"rope_type": "default"
|
| 70 |
}
|
| 71 |
},
|
|
@@ -74,5 +81,5 @@
|
|
| 74 |
"sparse_prediction": false,
|
| 75 |
"tie_word_embeddings": true,
|
| 76 |
"transformers_version": "5.17.0",
|
| 77 |
-
"vocab_size":
|
| 78 |
}
|
|
|
|
| 4 |
],
|
| 5 |
"attention_bias": false,
|
| 6 |
"attention_dropout": 0.0,
|
| 7 |
+
"bos_token_id": null,
|
| 8 |
+
"causal_mask": false,
|
| 9 |
"classifier_activation": "gelu",
|
| 10 |
"classifier_bias": false,
|
| 11 |
"classifier_dropout": 0.0,
|
|
|
|
| 15 |
"deterministic_flash_attn": false,
|
| 16 |
"dtype": "float32",
|
| 17 |
"embedding_dropout": 0.0,
|
| 18 |
+
"eos_token_id": null,
|
| 19 |
"global_attn_every_n_layers": 3,
|
| 20 |
"gradient_checkpointing": false,
|
| 21 |
"hidden_activation": "gelu",
|
| 22 |
"hidden_size": 768,
|
| 23 |
+
"id2label": {
|
| 24 |
+
"0": "LABEL_0"
|
| 25 |
+
},
|
| 26 |
"initializer_cutoff_factor": 2.0,
|
| 27 |
"initializer_range": 0.02,
|
| 28 |
"intermediate_size": 1152,
|
| 29 |
+
"is_causal": false,
|
| 30 |
+
"label2id": {
|
| 31 |
+
"LABEL_0": 0
|
| 32 |
+
},
|
| 33 |
"layer_norm_eps": 1e-05,
|
| 34 |
"layer_types": [
|
| 35 |
"full_attention",
|
|
|
|
| 56 |
"full_attention"
|
| 57 |
],
|
| 58 |
"local_attention": 128,
|
| 59 |
+
"max_position_embeddings": 7999,
|
| 60 |
"mlp_bias": false,
|
| 61 |
"mlp_dropout": 0.0,
|
| 62 |
"model_type": "modernbert",
|
|
|
|
| 65 |
"num_attention_heads": 12,
|
| 66 |
"num_hidden_layers": 22,
|
| 67 |
"pad_token_id": 50283,
|
| 68 |
+
"position_embedding_type": "sans_pos",
|
|
|
|
| 69 |
"rope_parameters": {
|
| 70 |
"full_attention": {
|
| 71 |
"rope_theta": 160000.0,
|
| 72 |
"rope_type": "default"
|
| 73 |
},
|
| 74 |
"sliding_attention": {
|
| 75 |
+
"rope_theta": 160000.0,
|
| 76 |
"rope_type": "default"
|
| 77 |
}
|
| 78 |
},
|
|
|
|
| 81 |
"sparse_prediction": false,
|
| 82 |
"tie_word_embeddings": true,
|
| 83 |
"transformers_version": "5.17.0",
|
| 84 |
+
"vocab_size": 50368
|
| 85 |
}
|
config_sentence_transformers.json
DELETED
|
@@ -1,54 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"model_type": "ColBERT",
|
| 3 |
-
"__version__": {
|
| 4 |
-
"sentence_transformers": "5.1.2",
|
| 5 |
-
"transformers": "5.17.0",
|
| 6 |
-
"pytorch": "2.7.1+cu126"
|
| 7 |
-
},
|
| 8 |
-
"prompts": {
|
| 9 |
-
"query": "",
|
| 10 |
-
"document": ""
|
| 11 |
-
},
|
| 12 |
-
"default_prompt_name": null,
|
| 13 |
-
"similarity_fn_name": "MaxSim",
|
| 14 |
-
"query_prefix": "[Q] ",
|
| 15 |
-
"document_prefix": "[D] ",
|
| 16 |
-
"query_length": 256,
|
| 17 |
-
"document_length": 2048,
|
| 18 |
-
"attend_to_expansion_tokens": false,
|
| 19 |
-
"skiplist_words": [
|
| 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 |
-
"do_query_expansion": false
|
| 54 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
decider.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Self-contained inference code for a joint typed-decision cross-encoder (ModernBERT-family encoder).
|
| 2 |
+
|
| 3 |
+
Each option of a question is read as one tokenizer pair
|
| 4 |
+
|
| 5 |
+
(state, "Question: <instructions>\\nOption: <option>")
|
| 6 |
+
|
| 7 |
+
and scored by a linear head on the attention-masked mean of the last hidden states; a question's
|
| 8 |
+
probabilities are the softmax over its options' scores. Nothing is truncated: a pair longer than
|
| 9 |
+
`max_length` (default 8,192 tokens, the encoder's native context) raises `TooLong`. All the
|
| 10 |
+
(question, option) pairs of a request are sorted by length and run together in padded batches.
|
| 11 |
+
|
| 12 |
+
Layout "shared" (decider_config.json) reads exactly the same tokens, but writes the state once per
|
| 13 |
+
request: [CLS] state [SEP], then each "Question: <instructions>\nOption:" block once, then each
|
| 14 |
+
" <option> [SEP]" block, with tree attention (state sees state; a question block sees the state and
|
| 15 |
+
itself; an option block sees the state, its question and itself, never other options). RoPE
|
| 16 |
+
positions follow the virtual pair, so the per-pair limit is unchanged. Pooling "opt" averages the
|
| 17 |
+
option block only.
|
| 18 |
+
|
| 19 |
+
from decider import Decider
|
| 20 |
+
model = Decider.from_pretrained("<repo or directory>")
|
| 21 |
+
model.answer(state={"text": "..."}, questions={"q1": {"type": "choice", "instructions": "...",
|
| 22 |
+
"criteria": {"a": "...", "b": "..."}}})
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import json
|
| 28 |
+
from pathlib import Path
|
| 29 |
+
from typing import Any, Dict, List, Optional, Sequence, Tuple
|
| 30 |
+
|
| 31 |
+
import numpy as np
|
| 32 |
+
|
| 33 |
+
FILES = ["config.json", "decider_config.json", "model.safetensors", "tokenizer.json", "tokenizer_config.json",
|
| 34 |
+
"special_tokens_map.json"]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class TooLong(ValueError):
|
| 38 |
+
"""An input does not fit the model's context; inputs are never truncated."""
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def text(x: Any) -> str:
|
| 42 |
+
return x if isinstance(x, str) else json.dumps(x, ensure_ascii=False, separators=(",", ":"))
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def state_text(state: Any) -> str:
|
| 46 |
+
return text(state) if state not in ("", None, {}, []) else "(empty)"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def format_option(key: str, desc: Any) -> str:
|
| 50 |
+
desc_str = text(desc).strip() if desc is not None else ""
|
| 51 |
+
key_str = str(key).strip()
|
| 52 |
+
if not desc_str:
|
| 53 |
+
return key_str
|
| 54 |
+
is_dummy_key = (key_str.lower().startswith(("option_", "k_", "choice_", "item_"))
|
| 55 |
+
or (len(key_str) == 1 and key_str.isalpha())
|
| 56 |
+
or (key_str.startswith("(") and key_str.endswith(")") and len(key_str) <= 4))
|
| 57 |
+
if is_dummy_key:
|
| 58 |
+
return desc_str
|
| 59 |
+
if desc_str.lower() == key_str.lower():
|
| 60 |
+
return key_str
|
| 61 |
+
return f"{key_str}: {desc_str}"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def question_options(q: dict) -> Tuple[List[str], List[str]]:
|
| 65 |
+
"""(answer keys, rendered option texts) for a choice or noul question."""
|
| 66 |
+
crit = q.get("criteria", {}) or {}
|
| 67 |
+
if q.get("type", "choice") == "choice":
|
| 68 |
+
keys = list(crit)
|
| 69 |
+
return keys, [format_option(k, crit[k]) for k in keys]
|
| 70 |
+
descs = (crit.get("false", crit.get("False", "False")), crit.get("true", crit.get("True", "True")))
|
| 71 |
+
return ["false", "true"], [format_option("false", descs[0]), format_option("true", descs[1])]
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def tail_text(question: str, option: str) -> str:
|
| 75 |
+
return f"Question: {question}\nOption: {option}"
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _resolve(path_or_repo: str, revision: Optional[str] = None) -> Path:
|
| 79 |
+
p = Path(path_or_repo)
|
| 80 |
+
if p.is_dir():
|
| 81 |
+
return p
|
| 82 |
+
from huggingface_hub import snapshot_download
|
| 83 |
+
return Path(snapshot_download(path_or_repo, allow_patterns=FILES, revision=revision))
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class Decider:
|
| 87 |
+
def __init__(self, directory: Path, device: Optional[str] = None, max_length: Optional[int] = None,
|
| 88 |
+
token_budget: int = 65536):
|
| 89 |
+
import torch
|
| 90 |
+
import torch.nn as nn
|
| 91 |
+
from safetensors.torch import load_file
|
| 92 |
+
from transformers import AutoConfig, AutoModel, AutoTokenizer
|
| 93 |
+
|
| 94 |
+
self.torch = torch
|
| 95 |
+
self.config = json.loads((directory / "decider_config.json").read_text())
|
| 96 |
+
self.max_length = int(max_length or self.config.get("inference_max_length", 8192))
|
| 97 |
+
self.token_budget = int(token_budget)
|
| 98 |
+
self.tok = AutoTokenizer.from_pretrained(str(directory))
|
| 99 |
+
cfg = AutoConfig.from_pretrained(str(directory))
|
| 100 |
+
net = nn.Module()
|
| 101 |
+
net.backbone = AutoModel.from_config(cfg)
|
| 102 |
+
net.drop = nn.Dropout(0.1)
|
| 103 |
+
net.scorer = nn.Linear(cfg.hidden_size, 1)
|
| 104 |
+
net.load_state_dict(load_file(str(directory / "model.safetensors")), strict=True)
|
| 105 |
+
self.device = torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
|
| 106 |
+
self.net = net.to(self.device).eval()
|
| 107 |
+
self.layout = self.config.get("layout", "pair")
|
| 108 |
+
self.pool = self.config.get("pool", "opt")
|
| 109 |
+
self.window = getattr(cfg, "sliding_window", None)
|
| 110 |
+
|
| 111 |
+
@classmethod
|
| 112 |
+
def from_pretrained(cls, path_or_repo: str, revision: Optional[str] = None, **kw) -> "Decider":
|
| 113 |
+
return cls(_resolve(path_or_repo, revision), **kw)
|
| 114 |
+
|
| 115 |
+
def score_many(self, items: Sequence[Tuple[str, str, Sequence[str]]]) -> List[np.ndarray]:
|
| 116 |
+
"""items: [(state, instructions, option texts)] -> one logit array per item."""
|
| 117 |
+
if self.layout == "shared":
|
| 118 |
+
return self._score_shared(items)
|
| 119 |
+
torch = self.torch
|
| 120 |
+
pairs = [self.tok(s, tail_text(q, o), truncation=False)["input_ids"] for s, q, opts in items for o in opts]
|
| 121 |
+
longest = max(map(len, pairs))
|
| 122 |
+
if longest > self.max_length:
|
| 123 |
+
raise TooLong(f"a (state, question, option) pair needs {longest} tokens > {self.max_length}")
|
| 124 |
+
order = sorted(range(len(pairs)), key=lambda i: len(pairs[i]))
|
| 125 |
+
scores = np.zeros(len(pairs), dtype=np.float64)
|
| 126 |
+
i = 0
|
| 127 |
+
with torch.no_grad():
|
| 128 |
+
while i < len(order):
|
| 129 |
+
j = i
|
| 130 |
+
while j < len(order) and (j - i + 1) * len(pairs[order[j]]) <= max(self.token_budget, len(pairs[order[i]])):
|
| 131 |
+
j += 1
|
| 132 |
+
idx = order[i:j]
|
| 133 |
+
width = len(pairs[idx[-1]])
|
| 134 |
+
ids = torch.full((len(idx), width), self.tok.pad_token_id, dtype=torch.long)
|
| 135 |
+
mask = torch.zeros((len(idx), width), dtype=torch.long)
|
| 136 |
+
for r, k in enumerate(idx):
|
| 137 |
+
ids[r, :len(pairs[k])] = torch.tensor(pairs[k])
|
| 138 |
+
mask[r, :len(pairs[k])] = 1
|
| 139 |
+
ids, mask = ids.to(self.device), mask.to(self.device)
|
| 140 |
+
with torch.autocast(self.device.type, dtype=torch.bfloat16, enabled=self.device.type == "cuda"):
|
| 141 |
+
h = self.net.backbone(input_ids=ids, attention_mask=mask).last_hidden_state
|
| 142 |
+
m = mask.unsqueeze(-1).to(h.dtype)
|
| 143 |
+
z = self.net.scorer((h * m).sum(1) / m.sum(1).clamp_min(1)).squeeze(-1)
|
| 144 |
+
scores[idx] = z.float().cpu().numpy()
|
| 145 |
+
i = j
|
| 146 |
+
out, start = [], 0
|
| 147 |
+
for _, _, opts in items:
|
| 148 |
+
out.append(scores[start:start + len(opts)])
|
| 149 |
+
start += len(opts)
|
| 150 |
+
return out
|
| 151 |
+
|
| 152 |
+
def _score_shared(self, items, row_extra: int = 2048) -> List[np.ndarray]:
|
| 153 |
+
torch, tok = self.torch, self.tok
|
| 154 |
+
rows, oid, prefix = [], 0, {}
|
| 155 |
+
for s, q, opts in items: # one row group per distinct state; rows of max(2P, P + row_extra) tokens
|
| 156 |
+
p = prefix.setdefault(s, tok(s)["input_ids"])
|
| 157 |
+
qi = tok(f"Question: {q}\nOption:", add_special_tokens=False)["input_ids"]
|
| 158 |
+
ob = [tok(" " + o, add_special_tokens=False)["input_ids"] + [tok.sep_token_id] for o in opts]
|
| 159 |
+
longest = len(p) + len(qi) + max(map(len, ob))
|
| 160 |
+
if longest > self.max_length:
|
| 161 |
+
raise TooLong(f"a (state, question, option) pair needs {longest} tokens > {self.max_length}")
|
| 162 |
+
cap = max(2 * len(p), len(p) + row_extra)
|
| 163 |
+
if not rows or rows[-1][0] is not p or rows[-1][2] + len(qi) + len(ob[0]) > cap:
|
| 164 |
+
rows.append([p, [], len(p)])
|
| 165 |
+
k = 0
|
| 166 |
+
while k < len(ob):
|
| 167 |
+
row = rows[-1]
|
| 168 |
+
take, size = [], len(qi)
|
| 169 |
+
while k + len(take) < len(ob) and (not take or row[2] + size + len(ob[k + len(take)]) <= cap):
|
| 170 |
+
take.append((oid + k + len(take), ob[k + len(take)]))
|
| 171 |
+
size += len(take[-1][1])
|
| 172 |
+
if row[1] and row[2] + size > cap:
|
| 173 |
+
rows.append([p, [], len(p)])
|
| 174 |
+
continue
|
| 175 |
+
row[1].append((qi, take))
|
| 176 |
+
row[2] += size
|
| 177 |
+
k += len(take)
|
| 178 |
+
oid += len(ob)
|
| 179 |
+
scores = np.zeros(oid, dtype=np.float64)
|
| 180 |
+
order = sorted(range(len(rows)), key=lambda i: rows[i][2])
|
| 181 |
+
i = 0
|
| 182 |
+
with torch.no_grad():
|
| 183 |
+
while i < len(order):
|
| 184 |
+
j = i + 1
|
| 185 |
+
while (j < len(order) and (j - i + 1) * rows[order[j]][2] <= self.token_budget
|
| 186 |
+
and (j - i + 1) * rows[order[j]][2] ** 2 <= 2 ** 26): # bound dense mask size
|
| 187 |
+
j += 1
|
| 188 |
+
batch = [rows[k] for k in order[i:j]]
|
| 189 |
+
L, B = max(r[2] for r in batch), len(batch)
|
| 190 |
+
ids = torch.full((B, L), tok.pad_token_id, dtype=torch.long)
|
| 191 |
+
pos = torch.zeros((B, L), dtype=torch.long)
|
| 192 |
+
kind = torch.full((B, L), -1, dtype=torch.long)
|
| 193 |
+
qix = torch.full((B, L), -1, dtype=torch.long)
|
| 194 |
+
oix = torch.full((B, L), -1, dtype=torch.long)
|
| 195 |
+
spans = []
|
| 196 |
+
for r, (p, blocks, _) in enumerate(batch):
|
| 197 |
+
ids[r, :len(p)], pos[r, :len(p)], kind[r, :len(p)] = torch.tensor(p), torch.arange(len(p)), 0
|
| 198 |
+
c = len(p)
|
| 199 |
+
for b, (qi, take) in enumerate(blocks):
|
| 200 |
+
n = len(qi)
|
| 201 |
+
ids[r, c:c + n], pos[r, c:c + n] = torch.tensor(qi), torch.arange(len(p), len(p) + n)
|
| 202 |
+
kind[r, c:c + n], qix[r, c:c + n] = 1, b
|
| 203 |
+
c += n
|
| 204 |
+
for o, x in take:
|
| 205 |
+
m = len(x)
|
| 206 |
+
ids[r, c:c + m] = torch.tensor(x)
|
| 207 |
+
pos[r, c:c + m] = torch.arange(len(p) + n, len(p) + n + m)
|
| 208 |
+
kind[r, c:c + m], qix[r, c:c + m], oix[r, c:c + m] = 2, b, o
|
| 209 |
+
spans.append((o, r, c, m, len(p), (r, len(p) + sum(len(bb[0]) + sum(len(y) for _, y in bb[1]) for bb in blocks[:b])), n))
|
| 210 |
+
c += m
|
| 211 |
+
ids, pos, kind, qix, oix = (t.to(self.device) for t in (ids, pos, kind, qix, oix))
|
| 212 |
+
ki, kj = kind[:, :, None], kind[:, None, :]
|
| 213 |
+
allow = (kj == 0) & (ki >= 0)
|
| 214 |
+
allow |= (kj == 1) & (ki >= 1) & (qix[:, :, None] == qix[:, None, :])
|
| 215 |
+
allow |= (kj == 2) & (ki == 2) & (oix[:, :, None] == oix[:, None, :])
|
| 216 |
+
allow |= torch.eye(L, dtype=torch.bool, device=self.device)[None]
|
| 217 |
+
masks = {"full_attention": allow[:, None]}
|
| 218 |
+
if self.window is not None:
|
| 219 |
+
masks["sliding_attention"] = (allow & ((pos[:, :, None] - pos[:, None, :]).abs() <= self.window))[:, None]
|
| 220 |
+
with torch.autocast(self.device.type, dtype=torch.bfloat16, enabled=self.device.type == "cuda"):
|
| 221 |
+
h = self.net.backbone(input_ids=ids, attention_mask=masks, position_ids=pos).last_hidden_state
|
| 222 |
+
h = h.float()
|
| 223 |
+
if self.pool == "opt": # vectorized mean over each option block
|
| 224 |
+
sel = oix >= 0
|
| 225 |
+
flat = oix[sel]
|
| 226 |
+
uniq, inv = torch.unique(flat, return_inverse=True)
|
| 227 |
+
v = torch.zeros(len(uniq), h.shape[-1], device=h.device).index_add_(0, inv, h[sel])
|
| 228 |
+
v = v / torch.bincount(inv).unsqueeze(-1).to(v.dtype)
|
| 229 |
+
with torch.autocast(self.device.type, dtype=torch.bfloat16, enabled=self.device.type == "cuda"):
|
| 230 |
+
z = self.net.scorer(v).squeeze(-1)
|
| 231 |
+
scores[uniq.cpu().numpy()] = z.float().cpu().numpy()
|
| 232 |
+
i = j
|
| 233 |
+
continue
|
| 234 |
+
vecs = []
|
| 235 |
+
for o, r, c, m, plen, (_, qstart), n in spans:
|
| 236 |
+
v = h[r, c:c + m].sum(0)
|
| 237 |
+
if self.pool == "pair":
|
| 238 |
+
v = (v + h[r, :plen].sum(0) + h[r, qstart:qstart + n].sum(0)) / (plen + n + m)
|
| 239 |
+
else:
|
| 240 |
+
v = v / m
|
| 241 |
+
vecs.append(v)
|
| 242 |
+
with torch.autocast(self.device.type, dtype=torch.bfloat16, enabled=self.device.type == "cuda"):
|
| 243 |
+
z = self.net.scorer(torch.stack(vecs)).squeeze(-1)
|
| 244 |
+
scores[[sp[0] for sp in spans]] = z.float().cpu().numpy()
|
| 245 |
+
i = j
|
| 246 |
+
out, start = [], 0
|
| 247 |
+
for _, _, opts in items:
|
| 248 |
+
out.append(scores[start:start + len(opts)])
|
| 249 |
+
start += len(opts)
|
| 250 |
+
return out
|
| 251 |
+
|
| 252 |
+
def answer(self, state: Any, questions: Dict[str, Any]) -> Dict[str, Any]:
|
| 253 |
+
"""Decision Index request -> {"answers": {qid: answer}}. Supports `choice` and `noul`."""
|
| 254 |
+
s = state_text(state)
|
| 255 |
+
parsed = []
|
| 256 |
+
for qid, q in questions.items():
|
| 257 |
+
kind = q.get("type", "choice")
|
| 258 |
+
if kind not in ("choice", "noul") or (kind == "choice" and not q.get("criteria")):
|
| 259 |
+
raise ValueError(f"unsupported question type {kind!r}")
|
| 260 |
+
keys, opts = question_options(q)
|
| 261 |
+
parsed.append((qid, kind, keys, opts, text(q.get("instructions", ""))))
|
| 262 |
+
logits = self.score_many([(s, ins, opts) for _, _, _, opts, ins in parsed])
|
| 263 |
+
answers = {}
|
| 264 |
+
for (qid, kind, keys, _, _), z in zip(parsed, logits):
|
| 265 |
+
p = np.exp(z - z.max())
|
| 266 |
+
p /= p.sum()
|
| 267 |
+
if kind == "noul":
|
| 268 |
+
answers[qid] = {"type": "noul", "noul": float(np.clip(p[1], 0.0, 1.0))}
|
| 269 |
+
else:
|
| 270 |
+
answers[qid] = {"type": "choice", "choice": keys[int(p.argmax())],
|
| 271 |
+
"probabilities": {k: float(v) for k, v in zip(keys, p)}}
|
| 272 |
+
return {"answers": answers}
|
decider_config.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"format": "pair-v1",
|
| 3 |
+
"arch": "joint",
|
| 4 |
+
"base": "cross-encoder/ettin-reranker-150m-v1",
|
| 5 |
+
"train_max_length": 2048,
|
| 6 |
+
"inference_max_length": 8192,
|
| 7 |
+
"step": 12000,
|
| 8 |
+
"train": {
|
| 9 |
+
"arch": "joint",
|
| 10 |
+
"opt_pool": "marker",
|
| 11 |
+
"shuffle_options": false,
|
| 12 |
+
"manifest": "runs/manifests/mix_all.jsonl",
|
| 13 |
+
"extra_manifest": null,
|
| 14 |
+
"extra_frac": 0.0,
|
| 15 |
+
"lr": 2e-05,
|
| 16 |
+
"batch_requests": 8,
|
| 17 |
+
"max_k": 32,
|
| 18 |
+
"seed": 7,
|
| 19 |
+
"total_steps": 12000,
|
| 20 |
+
"max_length": 2048,
|
| 21 |
+
"pack": 0,
|
| 22 |
+
"init": null,
|
| 23 |
+
"init_backbone": null,
|
| 24 |
+
"gc_tokens": 0,
|
| 25 |
+
"layout": "shared",
|
| 26 |
+
"pool": "opt",
|
| 27 |
+
"row_tokens": 8192,
|
| 28 |
+
"tok_budget": 12288
|
| 29 |
+
},
|
| 30 |
+
"layout": "shared",
|
| 31 |
+
"pool": "opt"
|
| 32 |
+
}
|
decision_index_engine.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Decision Index engine for this model (https://github.com/apolinario/decision-index).
|
| 2 |
+
|
| 3 |
+
python -m decision_index run --engine decision_index_engine:DeciderEngine \
|
| 4 |
+
--option repo=<repo or directory> [--option revision=<commit>] --edition 0.3 --out runs/<name>
|
| 5 |
+
|
| 6 |
+
Put this file next to `decider.py` (both are in the model repository) and on PYTHONPATH.
|
| 7 |
+
Kit rules: nothing is truncated and no option is dropped. A (state, question, option) pair longer than
|
| 8 |
+
8,192 tokens (the encoder's native context) raises `Unsupported`. One call = one request: all questions
|
| 9 |
+
of the request are scored together, in length-sorted padded batches.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import sys
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
from decision_index.engines import Engine, Unsupported
|
| 18 |
+
|
| 19 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
| 20 |
+
from decider import Decider, TooLong # noqa: E402
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class DeciderEngine(Engine):
|
| 24 |
+
name = "decider"
|
| 25 |
+
latency = ("In-process request wall time: tokenization plus the encoder passes of one request "
|
| 26 |
+
"(all its question/option pairs batched together); excludes model loading.")
|
| 27 |
+
|
| 28 |
+
def __init__(self, repo: str = str(Path(__file__).resolve().parent), revision: str | None = None,
|
| 29 |
+
device: str | None = None, max_length: int | None = None, token_budget: int = 65536, **options):
|
| 30 |
+
super().__init__(**options)
|
| 31 |
+
self.model = Decider.from_pretrained(repo, revision=revision, device=device, max_length=max_length,
|
| 32 |
+
token_budget=int(token_budget))
|
| 33 |
+
self.repo = repo
|
| 34 |
+
self.provenance = {"repo": repo, "revision": revision or "main", "device": str(self.model.device),
|
| 35 |
+
"dtype": "bfloat16 autocast" if self.model.device.type == "cuda" else "float32",
|
| 36 |
+
"max_length": self.model.max_length, "token_budget": self.model.token_budget,
|
| 37 |
+
"truncation": f"none: pairs above {self.model.max_length} tokens raise Unsupported"}
|
| 38 |
+
|
| 39 |
+
def __call__(self, state, questions):
|
| 40 |
+
try:
|
| 41 |
+
out = self.model.answer(state, questions)
|
| 42 |
+
except TooLong as e:
|
| 43 |
+
raise Unsupported(f"context window: {e}")
|
| 44 |
+
except ValueError as e:
|
| 45 |
+
raise Unsupported(str(e))
|
| 46 |
+
return {"model": self.repo, "answers": out["answers"]}, None
|
| 47 |
+
|
| 48 |
+
def synchronize(self):
|
| 49 |
+
if self.model.device.type == "cuda":
|
| 50 |
+
self.model.torch.cuda.synchronize()
|
decision_meta.json
DELETED
|
@@ -1,37 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"init_temperature": 0.31683406233787537,
|
| 3 |
-
"path_mode": "sq_o",
|
| 4 |
-
"pooling_budget": null,
|
| 5 |
-
"protected_tokens": 1,
|
| 6 |
-
"rel_slots": 2,
|
| 7 |
-
"joint_rel_distinct": false,
|
| 8 |
-
"rel_score_scale": 256.0,
|
| 9 |
-
"role_tokens": false,
|
| 10 |
-
"trimax_rank": null,
|
| 11 |
-
"has_query_gate": false,
|
| 12 |
-
"gate_dim": null,
|
| 13 |
-
"document_length": 2048,
|
| 14 |
-
"query_length": 256,
|
| 15 |
-
"model_max_length": 8192,
|
| 16 |
-
"length_norm_gamma": 0.0,
|
| 17 |
-
"config": {
|
| 18 |
-
"model_name_or_path": "/mnt/nfs_share_magnet2/dsileo/libs/train_jev/runs/multivector/scaling_grouped_p2/step-4000",
|
| 19 |
-
"embedding_dim": 128,
|
| 20 |
-
"state_max_length": 512,
|
| 21 |
-
"query_max_length": 64,
|
| 22 |
-
"init_temperature": 0.3215,
|
| 23 |
-
"learn_temperature": true,
|
| 24 |
-
"path_mode": "sq_o",
|
| 25 |
-
"pooling_budget": null,
|
| 26 |
-
"protected_tokens": 1,
|
| 27 |
-
"rel_slots": 2,
|
| 28 |
-
"joint_rel_distinct": false,
|
| 29 |
-
"rel_score_scale": null,
|
| 30 |
-
"role_tokens": false,
|
| 31 |
-
"trimax_rank": null,
|
| 32 |
-
"query_prefix": "[Q] ",
|
| 33 |
-
"document_prefix": "[D] ",
|
| 34 |
-
"backend": "torch",
|
| 35 |
-
"metadata": {}
|
| 36 |
-
}
|
| 37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a96dcfdf2d4e7da62429ba9f836535adf3a328f30cb51366095ad2c38fb13659
|
| 3 |
+
size 596074556
|
modules.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
| 1 |
-
[
|
| 2 |
-
{
|
| 3 |
-
"idx": 0,
|
| 4 |
-
"name": "0",
|
| 5 |
-
"path": "",
|
| 6 |
-
"type": "sentence_transformers.models.Transformer"
|
| 7 |
-
},
|
| 8 |
-
{
|
| 9 |
-
"idx": 1,
|
| 10 |
-
"name": "1",
|
| 11 |
-
"path": "1_Dense",
|
| 12 |
-
"type": "pylate.models.Dense.Dense"
|
| 13 |
-
},
|
| 14 |
-
{
|
| 15 |
-
"idx": 2,
|
| 16 |
-
"name": "2",
|
| 17 |
-
"path": "2_Dense",
|
| 18 |
-
"type": "pylate.models.Dense.Dense"
|
| 19 |
-
},
|
| 20 |
-
{
|
| 21 |
-
"idx": 3,
|
| 22 |
-
"name": "3",
|
| 23 |
-
"path": "3_Dense",
|
| 24 |
-
"type": "pylate.models.Dense.Dense"
|
| 25 |
-
}
|
| 26 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sentence_bert_config.json
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"max_seq_length": 255,
|
| 3 |
-
"do_lower_case": false
|
| 4 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tokenizer.json
CHANGED
|
@@ -2,18 +2,11 @@
|
|
| 2 |
"version": "1.0",
|
| 3 |
"truncation": {
|
| 4 |
"direction": "Right",
|
| 5 |
-
"max_length":
|
| 6 |
"strategy": "LongestFirst",
|
| 7 |
"stride": 0
|
| 8 |
},
|
| 9 |
-
"padding":
|
| 10 |
-
"strategy": "BatchLongest",
|
| 11 |
-
"direction": "Right",
|
| 12 |
-
"pad_to_multiple_of": null,
|
| 13 |
-
"pad_id": 50284,
|
| 14 |
-
"pad_type_id": 0,
|
| 15 |
-
"pad_token": "[MASK]"
|
| 16 |
-
},
|
| 17 |
"added_tokens": [
|
| 18 |
{
|
| 19 |
"id": 0,
|
|
@@ -1058,42 +1051,6 @@
|
|
| 1058 |
"rstrip": false,
|
| 1059 |
"normalized": true,
|
| 1060 |
"special": false
|
| 1061 |
-
},
|
| 1062 |
-
{
|
| 1063 |
-
"id": 50368,
|
| 1064 |
-
"content": "[Q] ",
|
| 1065 |
-
"single_word": false,
|
| 1066 |
-
"lstrip": false,
|
| 1067 |
-
"rstrip": false,
|
| 1068 |
-
"normalized": true,
|
| 1069 |
-
"special": false
|
| 1070 |
-
},
|
| 1071 |
-
{
|
| 1072 |
-
"id": 50369,
|
| 1073 |
-
"content": "[D] ",
|
| 1074 |
-
"single_word": false,
|
| 1075 |
-
"lstrip": false,
|
| 1076 |
-
"rstrip": false,
|
| 1077 |
-
"normalized": true,
|
| 1078 |
-
"special": false
|
| 1079 |
-
},
|
| 1080 |
-
{
|
| 1081 |
-
"id": 50370,
|
| 1082 |
-
"content": "[REL0]",
|
| 1083 |
-
"single_word": false,
|
| 1084 |
-
"lstrip": false,
|
| 1085 |
-
"rstrip": false,
|
| 1086 |
-
"normalized": false,
|
| 1087 |
-
"special": true
|
| 1088 |
-
},
|
| 1089 |
-
{
|
| 1090 |
-
"id": 50371,
|
| 1091 |
-
"content": "[REL1]",
|
| 1092 |
-
"single_word": false,
|
| 1093 |
-
"lstrip": false,
|
| 1094 |
-
"rstrip": false,
|
| 1095 |
-
"normalized": false,
|
| 1096 |
-
"special": true
|
| 1097 |
}
|
| 1098 |
],
|
| 1099 |
"normalizer": {
|
|
|
|
| 2 |
"version": "1.0",
|
| 3 |
"truncation": {
|
| 4 |
"direction": "Right",
|
| 5 |
+
"max_length": 7999,
|
| 6 |
"strategy": "LongestFirst",
|
| 7 |
"stride": 0
|
| 8 |
},
|
| 9 |
+
"padding": null,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
"added_tokens": [
|
| 11 |
{
|
| 12 |
"id": 0,
|
|
|
|
| 1051 |
"rstrip": false,
|
| 1052 |
"normalized": true,
|
| 1053 |
"special": false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1054 |
}
|
| 1055 |
],
|
| 1056 |
"normalizer": {
|
tokenizer_config.json
CHANGED
|
@@ -2,19 +2,16 @@
|
|
| 2 |
"backend": "tokenizers",
|
| 3 |
"clean_up_tokenization_spaces": true,
|
| 4 |
"cls_token": "[CLS]",
|
| 5 |
-
"is_local":
|
| 6 |
"local_files_only": false,
|
| 7 |
"mask_token": "[MASK]",
|
| 8 |
-
"max_length":
|
| 9 |
"model_input_names": [
|
| 10 |
"input_ids",
|
| 11 |
"attention_mask"
|
| 12 |
],
|
| 13 |
-
"model_max_length":
|
| 14 |
-
"
|
| 15 |
-
"pad_token": "[MASK]",
|
| 16 |
-
"pad_token_type_id": 0,
|
| 17 |
-
"padding_side": "right",
|
| 18 |
"sep_token": "[SEP]",
|
| 19 |
"stride": 0,
|
| 20 |
"tokenizer_class": "TokenizersBackend",
|
|
|
|
| 2 |
"backend": "tokenizers",
|
| 3 |
"clean_up_tokenization_spaces": true,
|
| 4 |
"cls_token": "[CLS]",
|
| 5 |
+
"is_local": false,
|
| 6 |
"local_files_only": false,
|
| 7 |
"mask_token": "[MASK]",
|
| 8 |
+
"max_length": 7999,
|
| 9 |
"model_input_names": [
|
| 10 |
"input_ids",
|
| 11 |
"attention_mask"
|
| 12 |
],
|
| 13 |
+
"model_max_length": 7999,
|
| 14 |
+
"pad_token": "[PAD]",
|
|
|
|
|
|
|
|
|
|
| 15 |
"sep_token": "[SEP]",
|
| 16 |
"stride": 0,
|
| 17 |
"tokenizer_class": "TokenizersBackend",
|