MeTask-Rain commited on
Commit
068cb14
·
verified ·
1 Parent(s): 238f5ab

Serving package (same as GitHub chenyuwang166-oss/MetaDecision-31B-Rain@6b8ed2d0)

Browse files
service/.gitattributes ADDED
@@ -0,0 +1 @@
 
 
1
+ * -text
service/.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ runs/
2
+ __pycache__/
3
+ *.pyc
4
+ state/
5
+ *.log
service/ADAPTER_MANIFEST.json ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_config_normalization": {
3
+ "changed_fields": [
4
+ "base_model_name_or_path",
5
+ "revision"
6
+ ],
7
+ "rule": "Only these keys differ from the selected checkpoint adapter_config.json.",
8
+ "serving_values": {
9
+ "base_model_name_or_path": "google/gemma-4-31B-it-qat-q4_0-unquantized",
10
+ "revision": "1e4d8beecacb8b7590c1d8bedd7335f687bf311f"
11
+ }
12
+ },
13
+ "files": {
14
+ "adapter_config.json": {
15
+ "sha256": "140bb00c832d117f57d29650dabe50e992b2e5064e908aaa29230b3ce19ca2c4",
16
+ "size": 22953
17
+ },
18
+ "adapter_model.safetensors": {
19
+ "sha256": "2b9568998ffed5d133223ffdff03056be297285c643122db4d45365d0bd86970",
20
+ "size": 494595208
21
+ }
22
+ },
23
+ "key_value_alignment": {
24
+ "added_count": 20,
25
+ "added_tensors": [
26
+ "base_model.model.model.language_model.layers.11.self_attn.v_proj.lora_A.weight",
27
+ "base_model.model.model.language_model.layers.11.self_attn.v_proj.lora_B.weight",
28
+ "base_model.model.model.language_model.layers.17.self_attn.v_proj.lora_A.weight",
29
+ "base_model.model.model.language_model.layers.17.self_attn.v_proj.lora_B.weight",
30
+ "base_model.model.model.language_model.layers.23.self_attn.v_proj.lora_A.weight",
31
+ "base_model.model.model.language_model.layers.23.self_attn.v_proj.lora_B.weight",
32
+ "base_model.model.model.language_model.layers.29.self_attn.v_proj.lora_A.weight",
33
+ "base_model.model.model.language_model.layers.29.self_attn.v_proj.lora_B.weight",
34
+ "base_model.model.model.language_model.layers.35.self_attn.v_proj.lora_A.weight",
35
+ "base_model.model.model.language_model.layers.35.self_attn.v_proj.lora_B.weight",
36
+ "base_model.model.model.language_model.layers.41.self_attn.v_proj.lora_A.weight",
37
+ "base_model.model.model.language_model.layers.41.self_attn.v_proj.lora_B.weight",
38
+ "base_model.model.model.language_model.layers.47.self_attn.v_proj.lora_A.weight",
39
+ "base_model.model.model.language_model.layers.47.self_attn.v_proj.lora_B.weight",
40
+ "base_model.model.model.language_model.layers.5.self_attn.v_proj.lora_A.weight",
41
+ "base_model.model.model.language_model.layers.5.self_attn.v_proj.lora_B.weight",
42
+ "base_model.model.model.language_model.layers.53.self_attn.v_proj.lora_A.weight",
43
+ "base_model.model.model.language_model.layers.53.self_attn.v_proj.lora_B.weight",
44
+ "base_model.model.model.language_model.layers.59.self_attn.v_proj.lora_A.weight",
45
+ "base_model.model.model.language_model.layers.59.self_attn.v_proj.lora_B.weight"
46
+ ],
47
+ "applied": true,
48
+ "layers": [
49
+ 5,
50
+ 11,
51
+ 17,
52
+ 23,
53
+ 29,
54
+ 35,
55
+ 41,
56
+ 47,
57
+ 53,
58
+ 59
59
+ ],
60
+ "rule": "v_proj.lora_A/B = byte copy of k_proj.lora_A/B in every attention_k_eq_v layer; all source tensors and their data unchanged"
61
+ },
62
+ "kind": "unmerged_lora_serving_adapter",
63
+ "lora": {
64
+ "lora_alpha": 32,
65
+ "merged": false,
66
+ "peft_type": "LORA",
67
+ "r": 16,
68
+ "stored_tensors": {
69
+ "dtypes": {
70
+ "F32": 840
71
+ },
72
+ "k_eq_v_layers_without_v_proj_lora": [],
73
+ "layers": 60,
74
+ "module_count": 420,
75
+ "module_prefix": "model.language_model.",
76
+ "rank": 16,
77
+ "targets": [
78
+ "down_proj",
79
+ "gate_proj",
80
+ "k_proj",
81
+ "o_proj",
82
+ "q_proj",
83
+ "up_proj",
84
+ "v_proj"
85
+ ],
86
+ "tensor_count": 840,
87
+ "v_proj_copied_from_k_proj_layers": [
88
+ 5,
89
+ 11,
90
+ 17,
91
+ 23,
92
+ 29,
93
+ 35,
94
+ 41,
95
+ 47,
96
+ 53,
97
+ 59
98
+ ],
99
+ "variant": "key_value_aligned"
100
+ },
101
+ "target_modules": [
102
+ "q_proj",
103
+ "k_proj",
104
+ "v_proj",
105
+ "o_proj",
106
+ "gate_proj",
107
+ "up_proj",
108
+ "down_proj"
109
+ ]
110
+ },
111
+ "protocol": "adapter_arm_31b_v1",
112
+ "schema_version": 1,
113
+ "served_base": {
114
+ "files_byte_identical": true,
115
+ "model_sha256": "ca750c75ea690276947dfc6d3c775668282e9180e4c56f180bfad912fed606a5",
116
+ "repo_id": "google/gemma-4-31B-it-qat-w4a16-ct",
117
+ "revision": "52f3f65bc7a02d555763bc923bd1d9094898219d"
118
+ },
119
+ "source": {
120
+ "adapter_model_sha256": "9dbf1acc152ef1a327b934b9674c232eec1129bee0fad51707021ad16285fc27",
121
+ "checkpoint_adapter_config_sha256": "52553a60f6155af2707d05d11d17a4e0a3d20c35233ea9261fdc56a7e7b4c4ca",
122
+ "checkpoint_complete_sha256": "056e81fa6b71062de77b6a640ffdb91c3680a16ffeb0154a6a10cf58b4c325fb",
123
+ "checkpoint_step": 150,
124
+ "run_identity_sha256": "7c94956f79b06b60a1066ebbf094e346ade3c70e9b900888563d878b2850b2b3",
125
+ "selection_sha256": "621fde9159b24e773298511418d3373dcd2da45bed535327c5be01facf218994",
126
+ "training_manifest_sha256": "ced7511dd24f895ad18a65616736ddbad4a4a906c3c015879e7d8148a49fa879"
127
+ },
128
+ "subdir": "adapter",
129
+ "training_base": {
130
+ "repo_id": "google/gemma-4-31B-it-qat-q4_0-unquantized",
131
+ "revision": "1e4d8beecacb8b7590c1d8bedd7335f687bf311f"
132
+ },
133
+ "variant": "key_value_aligned"
134
+ }
service/DISCLOSURE.md ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MetaDecision-31B-Rain: disclosure
2
+
3
+ ## Model
4
+
5
+ - Base model: `google/gemma-4-31B-it`. The weights used are Google's official
6
+ quantization-aware-training (QAT) releases of this model:
7
+ - training base: `google/gemma-4-31B-it-qat-q4_0-unquantized` at revision
8
+ `1e4d8beecacb8b7590c1d8bedd7335f687bf311f` (BF16);
9
+ - served base: `google/gemma-4-31B-it-qat-w4a16-ct` at revision
10
+ `52f3f65bc7a02d555763bc923bd1d9094898219d`, Google's compressed-tensors W4A16 checkpoint
11
+ (4-bit weights, group size 32, symmetric; activations unquantized), used unchanged. We did not
12
+ quantize anything.
13
+ - Own trained weights: the LoRA adapter in `adapter/` (PEFT format), rank 16, alpha 32, on the
14
+ language model's q/k/v/o/gate/up/down projections, trained by us in BF16 on the training base.
15
+ - The adapter is not merged: vLLM 0.31.0 loads it with `enable_lora` and `max_lora_rank=16` and
16
+ applies it to every request on top of the W4A16 base.
17
+ - In the 10 full-attention layers (5, 11, …, 59) Gemma 4 computes the attention values with the key
18
+ projection (`attention_k_eq_v`); these layers have no v_proj, and vLLM serves them with separate
19
+ key and value slots. `adapter/adapter_model.safetensors` therefore contains the trained checkpoint
20
+ tensors unchanged plus, for these 10 layers, a v_proj LoRA that is a byte copy of the trained
21
+ k_proj LoRA (20 tensors), so that the update applies to keys and values as in training.
22
+ - The base files and the adapter are exactly those of `MeTask-Rain/metask-jev-rain-31B-trained`
23
+ (same SHA-256 for every file, `MODEL_MANIFEST.json`); no weight was trained or changed for this
24
+ entry. It differs from that entry only in the serving rules below.
25
+
26
+ ## Data
27
+
28
+ - The adapter is the one of `MeTask-Rain/metask-jev-rain-31B-trained`, trained on our own generated
29
+ data as disclosed for that entry.
30
+ - Inherited from the earlier service of these weights, unchanged: the per-type temperatures of the
31
+ first calibration step, fitted on the 231 public JevBench items, and the drafting prompt limit
32
+ (840 tokens, below), chosen using the 231 public items together with our own development data.
33
+ Fits and choices made on these public items are in-sample. The yes/no confidence constant 0.898
34
+ (first-step answers carry 0.898 or 0.102) is the yes/no accuracy of our own held-out development
35
+ data (1,419 yes/no items), rounded to 3 decimals; its value does not depend on any leaderboard
36
+ result.
37
+ - The serving parameters new in this entry (drafting threshold and share, draft length, the re-read
38
+ rule and the post-calibration below) were fitted on our own independently built development set.
39
+ The 9 s deadline and the drafting chunk settings are fixed rules, not fitted.
40
+ - The 231 public items were also used for the validation run of the workflow in README.md; no
41
+ parameter of this entry was chosen from that run.
42
+
43
+ ## Serving
44
+
45
+ - Each decision is first read in one prompt pass: the answer distribution comes from the next-token
46
+ log-probabilities of the answer forms. If the gap between the two highest answer scores of this
47
+ pass is below a threshold (fitted so that 52.1% of the decisions of our development set
48
+ that can take the drafting pass fall below it), the decision may take a second, drafting pass: the
49
+ model writes at most 192 tokens of reasoning (the instruction asks for at most
50
+ 50 words), then the answer distribution is read again. If the draft ends without an
51
+ answer line, an answer line is appended and the answer is read after the draft.
52
+ - Deadline: a decision that takes the drafting pass has a deadline of 9 s from the
53
+ moment the service starts answering the request. Drafting runs in chunks sized so that the answer
54
+ can still be read before the deadline (an engine call in progress is always completed, never
55
+ abandoned); a draft that reaches its time budget is ended with an answer line, and a drafting pass
56
+ that cannot be read in time returns the first-pass answer (with a `warning` note, also listed in
57
+ `partial_errors`).
58
+ - Decisions whose drafting prompt (the first-pass prompt plus the drafting instruction) is
59
+ longer than 840 tokens are answered in one pass. Inputs longer than 6,653 tokens never take the drafting
60
+ pass and are answered from a context shortened to at most 6,653 tokens by the service's existing
61
+ extraction rule; an input is not refused because of its length.
62
+ - Drafting share (slow-path quota 49%): a counter kept below the service's state
63
+ directory admits a drafting pass only while drafting decisions stay
64
+ at most 49% of all decisions so far.
65
+ - Re-read: a choice decision answered in one pass although its length allowed the drafting pass (for example because the drafting share or the cost fuse refused it), whose first-pass margin (the log-score difference between its two highest answers) is below 4, is read once more in one pass with the options in reverse order (each option keeps its text and label). The two answer distributions are averaged by label. The second read is included in the decision's token usage and in the cost fuse; it is not a drafting pass and does not count toward the drafting share.
66
+ - Cost fuse: from the 101st decision on, when the running engine cost per decision reaches 90% of
67
+ USD 0.0646 per 1,000 decisions, drafting passes are limited to the share that brings the running
68
+ cost back to 85% of that limit (none while first-pass decisions alone cost more than 85% of it),
69
+ until the running cost falls to 85% of the limit. The fuse prices engine token counts at
70
+ USD 0.0598 per million tokens, input and output alike (the GPU-time basis of the
71
+ leaderboard's cost rule for this architecture). The fuse never changes the first pass.
72
+ - Answers after a drafting pass use the same first-step calibration parameters (per-type
73
+ temperatures and yes/no confidence) as first-pass answers.
74
+ - Post-calibration: after the first-step calibration, the reported probabilities are transformed per type, without any model call or token (a temperature t replaces the probabilities p by p^(1/t), renormalised; parameters fitted on our own independently built development set; values in `config.json`, block `post_calibration`). yes/no: temperature 0.5 (the first-step 0.898/0.102 become 0.987/0.013); score: all probability mass is put on the most probable level. The answer label is recomputed from the transformed probabilities; types not listed are unchanged.
75
+
76
+ ## Run conditions and usage accounting
77
+
78
+ - vLLM 0.31.0 with `VLLM_BATCH_INVARIANT=1` (also set by the launcher for serving) and
79
+ `VLLM_USE_FLASHINFER_SAMPLER=0`, one request at a time; model loading and warm-up are excluded.
80
+ - Cost basis: measured engine token counts (input tokens minus cached prefix tokens; generated
81
+ tokens).
service/FREEZE.json ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "config": {
3
+ "change": "package.finalized: false -> true (single textual replacement)",
4
+ "frozen_sha256": "94d5765076019628a0dc736f79e470eefeea72661895599ea2670ef391b0fed0",
5
+ "prepared_sha256": "0c84b933ac60e43394dbd748da21cb27bf91fe640cac7674acc3d02b80e8de11"
6
+ },
7
+ "frozen_utc": "2026-10-30T20:10:25Z",
8
+ "model_identity_sha256": "0513d121db967ec1a6aaccf627d3c42149d092dd027298da5b53684b030821ea",
9
+ "prepared_files": {
10
+ ".gitattributes": "705fd4d6451a31d36b3df7de96f83f30ac976c9b4a6d1e51671d8e2f33e2d0da",
11
+ ".gitignore": "4254e460a0ff807e7fd68e104efa8935a769377f09876b8b4fec0fb0b544a0e6",
12
+ "ADAPTER_MANIFEST.json": "6c40a7534f5d2b87cc8591748f85c7958f0259821c98659a60e25308904aa430",
13
+ "DISCLOSURE.md": "f50f14b50c9399ebf1cbf8867a8f807374448e0fbc9218dd3f3cd4a350a36135",
14
+ "LICENSE": "199f78d6c6e3b98c1601e4407aa092af57e42842d195409569c199b0393afac9",
15
+ "MODEL_CARD.md": "311bf55c3eda8fb9e464c0870d662c87fc99ae8f9d1a38fa64e12b9cfed8b59f",
16
+ "MODEL_MANIFEST.json": "d82f71ba20a912a3d5fe1ef8a5a2f8e004be16e7c67df2ee5308864b6832eb5f",
17
+ "PIPELINE_PROVENANCE.json": "b069a1fdbe6b56867a84ecaad10ce7521cfe135cead029a002c83383f8bf6f44",
18
+ "README.md": "91995c8d1a63816e14ec73a3d2c9d25993072592c98ddbbd073ac211d1f445c5",
19
+ "SERVICE_LOCK.json": "f777656d2cd1d40d1df1bb8a9d55661175663564f5ad5c1de1db57d9be72af3b",
20
+ "attention.py": "9c887f4e5f8ea2858f79a629e684ea71b07359d49d2a0bf31b5f993cb367e547",
21
+ "backends.py": "e0613ebb8e05fd22673e0502a131dcc33a5af563998cf5a12143878131518f93",
22
+ "bench.py": "7f158bcb4dcd90395c21535527abe4283d6ca5dab92ded0be47cc352f022f0cd",
23
+ "calibration.json": "5b60ca8516b93982724145bc2227404c4c53a6f5657b4bf2155108c2f001685f",
24
+ "config.json": "0c84b933ac60e43394dbd748da21cb27bf91fe640cac7674acc3d02b80e8de11",
25
+ "core.py": "fb4169ed1c6c9d69e406e9a33d69a71cc49aa48e0155d9544bb9f3bf41690a17",
26
+ "fit_report.json": "fc20f2f631c98f043f7c02d4cadbe8122580ee400377eef5420d93621111248b",
27
+ "item_deadline.py": "8d3d637ab3db1be9769e26900b4fc46964e48425a05bb92402a2b504ae66ff3a",
28
+ "launch.py": "d8f966264dfa5d977ef6444364e1eb4128cf34ed1b60bc94a45f2649f6fce2d3",
29
+ "model_setup.py": "6c7cd992039327178b72247b8b0f44214a5a00e43abcc0f583130dfe91586dd7",
30
+ "pkg_check.py": "0e8bf9d4c723f6205e25244a28cae31f4534d8356e1cc1a1390dad199b2904c0",
31
+ "post_calibration.py": "62a89b2b87cee77322333774a6e081f28646804c926af0f0c0e798a2018b3ffe",
32
+ "prepare_model.sh": "454a0090ce0187bf6f4b4825a537183cf095c503592a7f673f7f57329258dc44",
33
+ "quota.py": "8b039bc30c13e6c5a63668f3064fbb311358f9295922a817ec26f09893936f8d",
34
+ "reconcile_usage.py": "a381dca414201652f254f5101f08d4cbba27b69c3abeb50e6dad86e0e151aba7",
35
+ "requirements.lock": "4536b444d83518a5d696ebf262aa9d3da156fe2d898f96582e4ab52d70cb4e4d",
36
+ "requirements.measured.lock": "888ec6b0d3fad1a17e9323c1ab9321040303c79df3c9fe43e6f755cf2ab24b91",
37
+ "selftest.py": "08d5cfceb703748b407ce0223dee2807bf49f27b4f9bbd28c43e9738673699e6",
38
+ "selftest.sh": "d0cc8cc9068667aec3525861e0d61ac8fc9bb617b90f7fee7818af9dc0af154e",
39
+ "serve.sh": "13008367a9b37988de9c7acfa1d2340ca7dea502fd352887fc61783749d279c3",
40
+ "server.py": "dbce4f70c2c684ac8ea5069a9dfa1d30aa33a8e1c33d274d91fd3c2a953c6951"
41
+ },
42
+ "replaced_documents": {},
43
+ "repo_id": "MeTask-Rain/metask-jev-rain-31B-trained",
44
+ "revision": "9392335f5a17395ecd70049f85b1909d53500144",
45
+ "schema": "metadecision_31b_freeze_v1",
46
+ "serving_selection": {
47
+ "freeze_sha256": "6e5325eb9713ed73ed9fc94992b67b1179ea60d6969757af61d5c8860d2bb22e",
48
+ "protocol_sha256": "1c279f3d780a0a522a217c38f74892e9c7019be5012409a9c83bbd9dbc179771",
49
+ "readout_sha256": "8cc627afcf00b3b9ba9edafe004adc8294d45c62bec8a2dc5b3f593cac4ea0a2",
50
+ "selected": "serving rules selected on our own development set",
51
+ "selection_sha256": "cc1070b2d79a088983cdb5c300adc7c1ed5c25232003830a9d1a8611e4df6820"
52
+ },
53
+ "status": "FROZEN"
54
+ }
service/LICENSE ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MeTask (Hangzhou MeTask Technology Co., Ltd.)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ Model weights are not part of this repository. This service loads Google's
24
+ official Gemma 4 31B W4A16 base and our unmerged LoRA adapter. See MODEL_CARD.md
25
+ and DISCLOSURE.md for the model sources and applicable weight licensing.
service/MANIFEST.sha256 ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 705fd4d6451a31d36b3df7de96f83f30ac976c9b4a6d1e51671d8e2f33e2d0da .gitattributes
2
+ 4254e460a0ff807e7fd68e104efa8935a769377f09876b8b4fec0fb0b544a0e6 .gitignore
3
+ 6c40a7534f5d2b87cc8591748f85c7958f0259821c98659a60e25308904aa430 ADAPTER_MANIFEST.json
4
+ f50f14b50c9399ebf1cbf8867a8f807374448e0fbc9218dd3f3cd4a350a36135 DISCLOSURE.md
5
+ e2cfe584c132b9b3fd666bbabae51dccca455aaad541aad649ccb92741f75d1c FREEZE.json
6
+ 199f78d6c6e3b98c1601e4407aa092af57e42842d195409569c199b0393afac9 LICENSE
7
+ 311bf55c3eda8fb9e464c0870d662c87fc99ae8f9d1a38fa64e12b9cfed8b59f MODEL_CARD.md
8
+ d82f71ba20a912a3d5fe1ef8a5a2f8e004be16e7c67df2ee5308864b6832eb5f MODEL_MANIFEST.json
9
+ b069a1fdbe6b56867a84ecaad10ce7521cfe135cead029a002c83383f8bf6f44 PIPELINE_PROVENANCE.json
10
+ 91995c8d1a63816e14ec73a3d2c9d25993072592c98ddbbd073ac211d1f445c5 README.md
11
+ f777656d2cd1d40d1df1bb8a9d55661175663564f5ad5c1de1db57d9be72af3b SERVICE_LOCK.json
12
+ 9c887f4e5f8ea2858f79a629e684ea71b07359d49d2a0bf31b5f993cb367e547 attention.py
13
+ e0613ebb8e05fd22673e0502a131dcc33a5af563998cf5a12143878131518f93 backends.py
14
+ 7f158bcb4dcd90395c21535527abe4283d6ca5dab92ded0be47cc352f022f0cd bench.py
15
+ 5b60ca8516b93982724145bc2227404c4c53a6f5657b4bf2155108c2f001685f calibration.json
16
+ 94d5765076019628a0dc736f79e470eefeea72661895599ea2670ef391b0fed0 config.json
17
+ fb4169ed1c6c9d69e406e9a33d69a71cc49aa48e0155d9544bb9f3bf41690a17 core.py
18
+ fc20f2f631c98f043f7c02d4cadbe8122580ee400377eef5420d93621111248b fit_report.json
19
+ 8d3d637ab3db1be9769e26900b4fc46964e48425a05bb92402a2b504ae66ff3a item_deadline.py
20
+ d8f966264dfa5d977ef6444364e1eb4128cf34ed1b60bc94a45f2649f6fce2d3 launch.py
21
+ 6c7cd992039327178b72247b8b0f44214a5a00e43abcc0f583130dfe91586dd7 model_setup.py
22
+ 0e8bf9d4c723f6205e25244a28cae31f4534d8356e1cc1a1390dad199b2904c0 pkg_check.py
23
+ 62a89b2b87cee77322333774a6e081f28646804c926af0f0c0e798a2018b3ffe post_calibration.py
24
+ 454a0090ce0187bf6f4b4825a537183cf095c503592a7f673f7f57329258dc44 prepare_model.sh
25
+ 8b039bc30c13e6c5a63668f3064fbb311358f9295922a817ec26f09893936f8d quota.py
26
+ a381dca414201652f254f5101f08d4cbba27b69c3abeb50e6dad86e0e151aba7 reconcile_usage.py
27
+ 4536b444d83518a5d696ebf262aa9d3da156fe2d898f96582e4ab52d70cb4e4d requirements.lock
28
+ 888ec6b0d3fad1a17e9323c1ab9321040303c79df3c9fe43e6f755cf2ab24b91 requirements.measured.lock
29
+ 08d5cfceb703748b407ce0223dee2807bf49f27b4f9bbd28c43e9738673699e6 selftest.py
30
+ d0cc8cc9068667aec3525861e0d61ac8fc9bb617b90f7fee7818af9dc0af154e selftest.sh
31
+ 13008367a9b37988de9c7acfa1d2340ca7dea502fd352887fc61783749d279c3 serve.sh
32
+ dbce4f70c2c684ac8ea5069a9dfa1d30aa33a8e1c33d274d91fd3c2a953c6951 server.py
service/MODEL_CARD.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ license_link: https://ai.google.dev/gemma/docs/gemma_4_license
4
+ base_model: google/gemma-4-31B-it
5
+ pipeline_tag: text-generation
6
+ tags:
7
+ - gemma4
8
+ - compressed-tensors
9
+ - lora
10
+ - peft
11
+ - vllm
12
+ - jevbench
13
+ ---
14
+
15
+ # MetaDecision-31B-Rain
16
+
17
+ Decision service for JevBench. Base model:
18
+ [`google/gemma-4-31B-it`](https://huggingface.co/google/gemma-4-31B-it), used through Google's
19
+ official quantization-aware-training (QAT) releases of it. The model has two parts that are loaded
20
+ together and **not merged**; both are the files of
21
+ [`MeTask-Rain/metask-jev-rain-31B-trained`](https://huggingface.co/MeTask-Rain/metask-jev-rain-31B-trained/tree/2feff076f07711fe2fefe7a6fdcedfe4132b773e),
22
+ unchanged and pinned by SHA-256 in `MODEL_MANIFEST.json` of the service code. This entry uses the same weights as that
23
+ entry and changes only the serving rules.
24
+
25
+ | Part | Location in that model repository | What it is |
26
+ |---|---|---|
27
+ | Base | repository root | The files of [`google/gemma-4-31B-it-qat-w4a16-ct`](https://huggingface.co/google/gemma-4-31B-it-qat-w4a16-ct) at revision `52f3f65bc7a02d555763bc923bd1d9094898219d`, byte for byte: Google's official compressed-tensors W4A16 checkpoint (4-bit weights, group size 32, symmetric). |
28
+ | Adapter | `adapter/` | Our LoRA (PEFT format): rank 16, alpha 32, on the language model's q/k/v/o/gate/up/down projections, trained by us in BF16 on [`google/gemma-4-31B-it-qat-q4_0-unquantized`](https://huggingface.co/google/gemma-4-31B-it-qat-q4_0-unquantized) at revision `1e4d8beecacb8b7590c1d8bedd7335f687bf311f`. |
29
+
30
+ `adapter/adapter_model.safetensors` contains the trained checkpoint tensors unchanged plus, for the 10 full-attention layers (5, 11, …, 59) that compute values with the key projection, a v_proj LoRA that is a byte copy of the trained k_proj LoRA (20 tensors), so that vLLM applies the update to keys and values as in training.
31
+
32
+ ## Serving
33
+
34
+ The service code (`service/` in the Hugging Face repository `MeTask-Rain/MetaDecision-31B-Rain`, mirrored at https://github.com/chenyuwang166-oss/MetaDecision-31B-Rain) starts vLLM 0.31.0 on a folder holding the files of that weight
35
+ repository, with `enable_lora`, `max_lora_rank=16`, and the `adapter/` folder as the single LoRA,
36
+ applied to every request. Each decision is read from the next-token log-probabilities of the answer
37
+ forms in one prompt pass; up to 49% of decisions then take a second, short drafting pass
38
+ (at most 192 tokens) under a 9 s deadline, a choice decision answered in one pass although its length allowed the drafting pass is read a second time with the options in reverse order when its first-pass margin is below 4, and the reported probabilities get a per-type post-calibration (DISCLOSURE.md).
39
+
40
+ Equivalent vLLM usage of the model:
41
+
42
+ ```python
43
+ from vllm import LLM
44
+ from vllm.lora.request import LoRARequest
45
+ llm = LLM(model=MODEL_DIR, dtype="bfloat16", enable_lora=True, max_lora_rank=16)
46
+ adapter = LoRARequest("adapter", 1, MODEL_DIR + "/adapter")
47
+ # llm.generate(prompts, sampling_params, lora_request=adapter)
48
+ ```
49
+
50
+ Facts on data and the serving rules are in `DISCLOSURE.md` of the service code (`service/DISCLOSURE.md` in the
51
+ Hugging Face repository).
52
+
53
+ Contact: chenyuwang166@gmail.com
service/MODEL_MANIFEST.json ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter": {
3
+ "adapter_manifest_sha256": "6c40a7534f5d2b87cc8591748f85c7958f0259821c98659a60e25308904aa430",
4
+ "alpha": 32,
5
+ "files": {
6
+ "adapter_config.json": {
7
+ "sha256": "140bb00c832d117f57d29650dabe50e992b2e5064e908aaa29230b3ce19ca2c4",
8
+ "size": 22953
9
+ },
10
+ "adapter_model.safetensors": {
11
+ "sha256": "2b9568998ffed5d133223ffdff03056be297285c643122db4d45365d0bd86970",
12
+ "size": 494595208
13
+ }
14
+ },
15
+ "format": "peft-lora",
16
+ "merged": false,
17
+ "rank": 16,
18
+ "stored_tensors": {
19
+ "dtypes": {
20
+ "F32": 840
21
+ },
22
+ "k_eq_v_layers_without_v_proj_lora": [],
23
+ "layers": 60,
24
+ "module_count": 420,
25
+ "module_prefix": "model.language_model.",
26
+ "rank": 16,
27
+ "targets": [
28
+ "down_proj",
29
+ "gate_proj",
30
+ "k_proj",
31
+ "o_proj",
32
+ "q_proj",
33
+ "up_proj",
34
+ "v_proj"
35
+ ],
36
+ "tensor_count": 840,
37
+ "v_proj_copied_from_k_proj_layers": [
38
+ 5,
39
+ 11,
40
+ 17,
41
+ 23,
42
+ 29,
43
+ 35,
44
+ 41,
45
+ 47,
46
+ 53,
47
+ 59
48
+ ],
49
+ "variant": "key_value_aligned"
50
+ },
51
+ "subdir": "adapter",
52
+ "target_modules": [
53
+ "down_proj",
54
+ "gate_proj",
55
+ "k_proj",
56
+ "o_proj",
57
+ "q_proj",
58
+ "up_proj",
59
+ "v_proj"
60
+ ],
61
+ "training_base": "google/gemma-4-31B-it-qat-q4_0-unquantized",
62
+ "training_revision": "1e4d8beecacb8b7590c1d8bedd7335f687bf311f",
63
+ "variant": "key_value_aligned"
64
+ },
65
+ "arm": "trained",
66
+ "excluded_repository_documents": [
67
+ "README.md",
68
+ ".gitattributes"
69
+ ],
70
+ "files": {
71
+ "chat_template.jinja": {
72
+ "sha256": "ae53464bf3be25802b3a5b37def7fd89667067d7577049b3b2d74c4d8de4c6d4",
73
+ "size": 18683
74
+ },
75
+ "config.json": {
76
+ "sha256": "b100d85e571c25b688e82919d819253063a1defb0e6cee3b9b1c1fbad99bd0c2",
77
+ "size": 18711
78
+ },
79
+ "generation_config.json": {
80
+ "sha256": "70110aa886fa7ee25ce47346d102dd29ca5f967500490b4362fc72068e1db70a",
81
+ "size": 208
82
+ },
83
+ "model.safetensors": {
84
+ "sha256": "1b9b1d622a93f02c0d33f98e502f233b5d707443af6ddc464ed0bf5498506c20",
85
+ "size": 23265352448
86
+ },
87
+ "processor_config.json": {
88
+ "sha256": "32bdf45d2ad4cc29a0822ddd157a182de76644f0419a6228d151495256e9813c",
89
+ "size": 1689
90
+ },
91
+ "tokenizer.json": {
92
+ "sha256": "cc8d3a0ce36466ccc1278bf987df5f71db1719b9ca6b4118264f45cb627bfe0f",
93
+ "size": 32169626
94
+ },
95
+ "tokenizer_config.json": {
96
+ "sha256": "b8045a4576903e86903291d5cbdd4adfc8859e9ce3c98621bdbd957f73ed394b",
97
+ "size": 3728
98
+ }
99
+ },
100
+ "format": "compressed-tensors",
101
+ "identity_sha256": "0513d121db967ec1a6aaccf627d3c42149d092dd027298da5b53684b030821ea",
102
+ "pricing_base_model": "google/gemma-4-31B-it",
103
+ "publish_authority": "repository owner",
104
+ "repo_id": "MeTask-Rain/metask-jev-rain-31B-trained",
105
+ "revision": "9392335f5a17395ecd70049f85b1909d53500144",
106
+ "served_base": {
107
+ "files_byte_identical": true,
108
+ "model_sha256": "ca750c75ea690276947dfc6d3c775668282e9180e4c56f180bfad912fed606a5",
109
+ "repo_id": "google/gemma-4-31B-it-qat-w4a16-ct",
110
+ "revision": "52f3f65bc7a02d555763bc923bd1d9094898219d"
111
+ },
112
+ "status": "evaluated_service_mirror",
113
+ "training_base": {
114
+ "repo_id": "google/gemma-4-31B-it-qat-q4_0-unquantized",
115
+ "revision": "1e4d8beecacb8b7590c1d8bedd7335f687bf311f"
116
+ }
117
+ }
service/PIPELINE_PROVENANCE.json ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_reference": "model-subdir:adapter",
3
+ "arm": "trained",
4
+ "core_patch": null,
5
+ "input_budget": 6653,
6
+ "measured_environment": {
7
+ "entries": 201,
8
+ "equal_to_source_package_measured_lock": false,
9
+ "files": {
10
+ "requirements.lock": "4536b444d83518a5d696ebf262aa9d3da156fe2d898f96582e4ab52d70cb4e4d",
11
+ "requirements.measured.lock": "888ec6b0d3fad1a17e9323c1ab9321040303c79df3c9fe43e6f755cf2ab24b91"
12
+ },
13
+ "freeze_sha256": "888ec6b0d3fad1a17e9323c1ab9321040303c79df3c9fe43e6f755cf2ab24b91",
14
+ "source": "pip freeze of the measurement service environment"
15
+ },
16
+ "model_key": "metask-gemma4-31b-lora-w4a16",
17
+ "model_name": "MetaDecision-31B-Rain",
18
+ "notes": [
19
+ "fit_report.json, calibration.json and service_policy.base_fit / fit_n describe the first-step calibration of the earlier service of these weights (unchanged); the serving rules of this entry are config.json routing / generation / item_deadline / item_rules / post_calibration",
20
+ "weights.revision is the model manifest revision; evaluated_service_revision pins the same model files"
21
+ ],
22
+ "protocol": "adapter_arm_31b_v3",
23
+ "quota": 0.49,
24
+ "reference_source_files": {
25
+ "attention.py": "9c887f4e5f8ea2858f79a629e684ea71b07359d49d2a0bf31b5f993cb367e547",
26
+ "backends.py": "e0613ebb8e05fd22673e0502a131dcc33a5af563998cf5a12143878131518f93",
27
+ "bench.py": "7f158bcb4dcd90395c21535527abe4283d6ca5dab92ded0be47cc352f022f0cd",
28
+ "core.py": "fb4169ed1c6c9d69e406e9a33d69a71cc49aa48e0155d9544bb9f3bf41690a17",
29
+ "model_setup.py": "6c7cd992039327178b72247b8b0f44214a5a00e43abcc0f583130dfe91586dd7",
30
+ "quota.py": "8b039bc30c13e6c5a63668f3064fbb311358f9295922a817ec26f09893936f8d",
31
+ "reconcile_usage.py": "a381dca414201652f254f5101f08d4cbba27b69c3abeb50e6dad86e0e151aba7",
32
+ "server.py": "2cb085c1bf0f68758097afee55ecad2ef7ecd76dcced00c1b3f927b49151e213"
33
+ },
34
+ "route": "unmerged LoRA adapter (adapter/) on the official W4A16 compressed-tensors base",
35
+ "served_base": {
36
+ "repo_id": "google/gemma-4-31B-it-qat-w4a16-ct",
37
+ "revision": "52f3f65bc7a02d555763bc923bd1d9094898219d"
38
+ },
39
+ "served_mode": "routed",
40
+ "server_patch": {
41
+ "change": "install_extensions(): item_deadline with item_rules, then post_calibration",
42
+ "patched_server_sha256": "dbce4f70c2c684ac8ea5069a9dfa1d30aa33a8e1c33d274d91fd3c2a953c6951",
43
+ "reference_server_sha256": "2cb085c1bf0f68758097afee55ecad2ef7ecd76dcced00c1b3f927b49151e213"
44
+ },
45
+ "service_policy": {
46
+ "base_fit": {
47
+ "draft": 256,
48
+ "q": 0.056
49
+ },
50
+ "core_patch": null,
51
+ "cost_fuse": {
52
+ "arm": 0.9,
53
+ "enabled": true,
54
+ "release": 0.85,
55
+ "warmup": 100
56
+ },
57
+ "cost_fuse_prices": {
58
+ "input_per_million": 0.059819178473256074,
59
+ "output_per_million": 0.059819178473256074
60
+ },
61
+ "draft": {
62
+ "max_new_tokens": 192,
63
+ "reasoning_words": 50
64
+ },
65
+ "fit_n": 231,
66
+ "fit_protocol": "31b-adapter-arm-v3-routed",
67
+ "fuse": 0.49,
68
+ "item_deadline": {
69
+ "chunk_tokens": 32,
70
+ "ewma_alpha": 0.3,
71
+ "item_seconds": 9.0,
72
+ "min_chunk_tokens": 8,
73
+ "readout_reserve_init_s": 1.0,
74
+ "readout_reserve_min_s": 0.3,
75
+ "readout_safety": 1.5,
76
+ "response_reserve_s": 0.35,
77
+ "token_safety": 1.25,
78
+ "warmup_repeats": 3,
79
+ "warmup_tokens": 33
80
+ },
81
+ "item_rules": {
82
+ "reread_tau": 4.0,
83
+ "slow_types": [
84
+ "choice",
85
+ "noul",
86
+ "score"
87
+ ]
88
+ },
89
+ "margin_threshold": 6.625023671062799,
90
+ "mode": "routed",
91
+ "noul_p_cal": 0.898,
92
+ "post_calibration": {
93
+ "choice": {
94
+ "config": {
95
+ "debias": false,
96
+ "temperature": false
97
+ },
98
+ "params": {}
99
+ },
100
+ "noul": {
101
+ "config": {
102
+ "floor": null,
103
+ "temperature": "type"
104
+ },
105
+ "params": {
106
+ "T": 0.5
107
+ }
108
+ },
109
+ "score": {
110
+ "config": {
111
+ "mode_weight": 1.0,
112
+ "temperature": false
113
+ },
114
+ "params": {}
115
+ }
116
+ },
117
+ "post_calibration_active": true,
118
+ "protocol": "adapter_arm_31b_v3",
119
+ "quota": 0.49,
120
+ "slow_excluded_types": [],
121
+ "slow_max_prompt_tokens": 840,
122
+ "threshold_rule": "eligible_items",
123
+ "unparsed_policy": "force_answer",
124
+ "v3_params_sha256": "b28ed73bb5a36e99a280a47a6b846e0c5b2de54f34a96efa4a10754b71ea7573"
125
+ },
126
+ "source_files": {
127
+ "attention.py": "9c887f4e5f8ea2858f79a629e684ea71b07359d49d2a0bf31b5f993cb367e547",
128
+ "backends.py": "e0613ebb8e05fd22673e0502a131dcc33a5af563998cf5a12143878131518f93",
129
+ "bench.py": "7f158bcb4dcd90395c21535527abe4283d6ca5dab92ded0be47cc352f022f0cd",
130
+ "core.py": "fb4169ed1c6c9d69e406e9a33d69a71cc49aa48e0155d9544bb9f3bf41690a17",
131
+ "item_deadline.py": "8d3d637ab3db1be9769e26900b4fc46964e48425a05bb92402a2b504ae66ff3a",
132
+ "model_setup.py": "6c7cd992039327178b72247b8b0f44214a5a00e43abcc0f583130dfe91586dd7",
133
+ "post_calibration.py": "62a89b2b87cee77322333774a6e081f28646804c926af0f0c0e798a2018b3ffe",
134
+ "quota.py": "8b039bc30c13e6c5a63668f3064fbb311358f9295922a817ec26f09893936f8d",
135
+ "reconcile_usage.py": "a381dca414201652f254f5101f08d4cbba27b69c3abeb50e6dad86e0e151aba7",
136
+ "server.py": "dbce4f70c2c684ac8ea5069a9dfa1d30aa33a8e1c33d274d91fd3c2a953c6951"
137
+ },
138
+ "weights": {
139
+ "evaluated_service_revision": "2feff076f07711fe2fefe7a6fdcedfe4132b773e",
140
+ "identity_sha256": "0513d121db967ec1a6aaccf627d3c42149d092dd027298da5b53684b030821ea",
141
+ "note": "same model files as the earlier service of these weights; not copied",
142
+ "repo_id": "MeTask-Rain/metask-jev-rain-31B-trained",
143
+ "revision": "9392335f5a17395ecd70049f85b1909d53500144"
144
+ }
145
+ }
service/README.md ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MetaDecision-31B-Rain
2
+
3
+ A self-hosted decision service for JevBench. It serves `POST /v1/systemone` for the unchanged
4
+ `typesafe` adapter of the official harness, and `GET /health`.
5
+
6
+ The model weights are exactly those of
7
+ [`MeTask-Rain/metask-jev-rain-31B-trained`](https://huggingface.co/MeTask-Rain/metask-jev-rain-31B-trained/tree/2feff076f07711fe2fefe7a6fdcedfe4132b773e)
8
+ (the same files, pinned by SHA-256 in `MODEL_MANIFEST.json`): Google's official W4A16 checkpoint
9
+ `google/gemma-4-31B-it-qat-w4a16-ct` (unchanged) and our LoRA adapter, loaded together and never
10
+ merged. `MODEL_MANIFEST.json` names that repository's earlier revision `9392335f5a17395ecd70049f85b1909d53500144`, which
11
+ holds the same model files. This entry changes only how the service uses them:
12
+
13
+ - a larger share of decisions may take a short drafting pass (slow-path quota 49%, at most 192 drafted tokens);
14
+ - a decision that takes the drafting pass has a deadline of 9 s: the draft is stopped in time for its answer to be read, and a drafting pass that cannot be read in time returns the first-pass answer;
15
+ - a choice decision answered in one pass although its length allowed the drafting pass, whose first-pass margin is below 4, is read once more with the options in reverse order, and the two answer distributions are averaged;
16
+ - the reported probabilities get a per-type post-calibration fitted on our own development set (no model call);
17
+ - the cost fuse prices engine tokens at the leaderboard's GPU-time rate for this architecture (USD 0.0598 per million tokens).
18
+
19
+ Facts and serving rules are in [DISCLOSURE.md](DISCLOSURE.md).
20
+
21
+ ## Requirements
22
+
23
+ - One NVIDIA GPU used only by this service: compute capability 8.0 or newer, NVIDIA driver 580 or
24
+ newer, 80 GB or more of GPU memory. Validated: steps 2-6 below, run with this package and the model files downloaded in step 2, completed the 231 public items on one NVIDIA A100 80GB (231/231, zero failed). The service starts vLLM with
25
+ `gpu_memory_utilization=0.94` (it reserves 94% of the GPU memory), `max_model_len=6656`,
26
+ `enable_lora` and `max_lora_rank=16`. The model files take 23.3 GB (base) plus 495 MB (adapter).
27
+ The commands use the host's GPU 0 (`--gpus '"device=0"'`).
28
+ - Docker with the NVIDIA Container Toolkit.
29
+ - On the host: Git and Python with pip; step 2 installs the Hugging Face command line tool `hf`.
30
+ - Runtime image `vllm/vllm-openai@sha256:a4a4c0437bf7240089da5f08aa370c4aee17ae5290f7a3b468825ee26c4c3a6b`
31
+ (Python 3.12.3, vLLM 0.31.0, torch 2.13.0+cu130), the linux/amd64 image of vLLM release
32
+ `v0.31.0`. Nothing is installed and the service has no network access.
33
+ - Model folder: the model files of https://huggingface.co/MeTask-Rain/metask-jev-rain-31B-trained/tree/2feff076f07711fe2fefe7a6fdcedfe4132b773e
34
+ as regular files (step 2). The repository root holds the official base files; `adapter/` holds
35
+ the adapter (`adapter_config.json`, `adapter_model.safetensors`). A Hugging Face cache
36
+ `snapshots/<revision>` directory holds links and cannot be mounted on its own. At startup the
37
+ service checks the SHA-256 of the small model files, the size of the weight file and the full
38
+ SHA-256 of both adapter files (`MODEL_MANIFEST.json`), and refuses other weight, tokenizer,
39
+ processor or template files in the folder; it never writes to the folder.
40
+ - Code: this repository (download command in step 1).
41
+ - Harness: https://github.com/fstandhartinger/jevbench/tree/bb05a335bc809e61b20c0f745d25499a82b326fc
42
+
43
+ ## Run
44
+
45
+ Copy the commands as they are, in the same Bash terminal.
46
+
47
+ 1. Obtain the code and harness, and create a fresh work directory. Run from a writable parent
48
+ directory; the new directories must not already contain another run:
49
+
50
+ ```bash
51
+ git clone https://github.com/chenyuwang166-oss/MetaDecision-31B-Rain.git
52
+ PKG="$(pwd)/MetaDecision-31B-Rain"
53
+ git clone https://github.com/fstandhartinger/jevbench.git jevbench
54
+ git -C jevbench checkout bb05a335bc809e61b20c0f745d25499a82b326fc
55
+ JEVBENCH="$(pwd)/jevbench"
56
+ MODEL="$(pwd)/model-31b"
57
+ WORK="$(pwd)/work-31b-run1"
58
+ mkdir "$MODEL" "$WORK"
59
+ TASKS=/jevbench/datasets/public/easy.jsonl,/jevbench/datasets/public/original.jsonl,/jevbench/datasets/public/hard.jsonl
60
+ ```
61
+
62
+ 2. Install the download tool, fetch the pinned weights, and pull the pinned runtime image:
63
+
64
+ ```bash
65
+ python3 -m pip install huggingface_hub
66
+ hf download MeTask-Rain/metask-jev-rain-31B-trained --revision 2feff076f07711fe2fefe7a6fdcedfe4132b773e --local-dir "$MODEL" --exclude 'service/*'
67
+ docker pull vllm/vllm-openai@sha256:a4a4c0437bf7240089da5f08aa370c4aee17ae5290f7a3b468825ee26c4c3a6b
68
+ ```
69
+
70
+ 3. Start the service:
71
+
72
+ ```bash
73
+ docker run -d --name metadecision31b --init --gpus '"device=0"' --network none --read-only --shm-size 8g \
74
+ -v "$PKG":/pkg:ro -v "$MODEL":/model:ro -v "$WORK":/work -v "$JEVBENCH":/jevbench:ro \
75
+ --entrypoint bash vllm/vllm-openai@sha256:a4a4c0437bf7240089da5f08aa370c4aee17ae5290f7a3b468825ee26c4c3a6b \
76
+ /pkg/serve.sh --model /model --state-dir /work/state --run-id run1
77
+ ```
78
+
79
+ 4. Follow the log until the service is ready:
80
+
81
+ ```bash
82
+ docker logs -f metadecision31b
83
+ ```
84
+
85
+ When a line starting with `{"status": "ok"` appears, press Ctrl-C (this ends only the log
86
+ display) and check health before step 5:
87
+
88
+ ```bash
89
+ docker exec metadecision31b python3 -c 'import urllib.request; print(urllib.request.urlopen("http://localhost:8000/health").read().decode())'
90
+ ```
91
+
92
+ If the log ends before that line, the container has exited: the reason is in
93
+ `docker logs metadecision31b` and in `$WORK/state/run1-startup/attempt-*.log`; go to step 6.
94
+ After correcting the cause, use a new `WORK` and restart from step 3.
95
+
96
+ 5. Run the official harness in the same container, then summarize:
97
+
98
+ ```bash
99
+ docker exec -e PYTHONPATH=/jevbench metadecision31b python3 -B -m jevbench.cli run --adapter typesafe \
100
+ --endpoint http://localhost:8000 --model MetaDecision-31B-Rain --key-env '' --tasks "$TASKS" \
101
+ --results /work/run1/results.jsonl --ledger /work/run1/ledger.jsonl --raw-dir /work/run1/raw \
102
+ --manifest /work/run1/manifest.json --cap-usd 15 --price-in-per-m 0.09 --price-out-per-m 0.34
103
+ docker exec -e PYTHONPATH=/jevbench metadecision31b python3 -B -m jevbench.cli summarize --tasks "$TASKS" \
104
+ --results /work/run1/results.jsonl --ledger /work/run1/ledger.jsonl
105
+ ```
106
+
107
+ 6. Stop and remove the container:
108
+
109
+ ```bash
110
+ docker stop -t 40 metadecision31b && docker rm metadecision31b
111
+ ```
112
+
113
+ Each evaluation uses a new `WORK` and a new container. A run name that already exists below the
114
+ state directory is refused at startup ("use a new --state-dir"); with these commands that means a
115
+ new `WORK`.
116
+
117
+ ## What to expect
118
+
119
+ - Health line: `"status": "ok"`, `"warnings": []`, `"model_key": "metask-gemma4-31b-lora-w4a16"`,
120
+ `"mode": "routed"`, `"quantization_kernel": "compressed-tensors"`, `"prefix_caching": true`,
121
+ `"supports_slow_session": true`, and `threshold_identity.adapter` equal to `/model/adapter`, the
122
+ verified adapter folder.
123
+ - Startup: `launch.py` verifies the model folder and the adapter, writes the run configuration
124
+ (with the adapter folder filled in) to `$WORK/state/run1-startup/config.runtime.json`, and starts
125
+ vLLM with `enable_lora` and `max_lora_rank=16`; it sets `VLLM_BATCH_INVARIANT=1`,
126
+ `VLLM_USE_FLASHINFER_SAMPLER=0` and `VLLM_WORKER_MULTIPROC_METHOD=spawn`. Each startup attempt
127
+ waits at most 1800 s for readiness: the first lets vLLM select the attention backend; only if the
128
+ engine fails to start does a second attempt request `TRITON_ATTN`. Before serving, the service
129
+ times a few short synthetic drafts to size its drafting chunks.
130
+ - Each decision is first read in one prompt pass. At most 49% of decisions take a second,
131
+ drafting pass (at most 192 generated tokens) before the answer is read again; such a
132
+ decision is planned to finish within its 9 s deadline, counted from the moment the
133
+ service starts answering the request (an engine call in progress is completed, never abandoned;
134
+ DISCLOSURE.md states the rules and the cost fuse). Inputs longer than 6,653 tokens are answered in
135
+ one pass from a context shortened to at most 6,653 tokens by the service's extraction rule.
136
+ - Writes: the service writes files only below `$WORK/state` (plus shared memory in `/dev/shm`) and
137
+ the harness only below `$WORK/run1`. The package and model mounts are read-only. Cache and
138
+ configuration path variables (`HF_HOME`, `XDG_*`, `TRITON_*`, `TORCH_HOME` and similar) must not
139
+ be passed to the container: a value outside `/work/state` is refused at startup. No script of
140
+ this package deletes directories recursively.
141
+ - Responses: an item the service cannot process returns HTTP 422 without answer and without usage;
142
+ after an engine failure, and from the third consecutive item failure on, the service answers
143
+ every `POST /v1/systemone` with HTTP 503 and `/health` reports `"status": "error"`. No substitute
144
+ answer is ever returned. A decision whose drafting pass could not be read before its deadline is
145
+ answered (HTTP 200) with its valid first-pass answer and carries a `warning` note, also listed in
146
+ `partial_errors`.
147
+
148
+ The health response keeps `threshold_identity.quant: "awq"` as a historical configuration alias;
149
+ the actual backend is **compressed-tensors W4A16**, reported by `quantization_kernel`.
150
+
151
+ ## Package checks (offline, no GPU or network)
152
+
153
+ ```bash
154
+ cd "$PKG"
155
+ python3 -B pkg_check.py # package integrity, configuration, adapter contract, serving policy
156
+ python3 -B selftest.py --offline # synthetic software tests, including long-input compression
157
+ ```
158
+
159
+ `MODEL_MANIFEST.json` pins the weight repository, revision and every model and adapter file;
160
+ `ADAPTER_MANIFEST.json` records the adapter's origin by SHA-256; `FREEZE.json` records the freeze
161
+ of this configuration; `MANIFEST.sha256` binds all package files. `MODEL_CARD.md` describes the
162
+ model.
163
+
164
+ Contact: chenyuwang166@gmail.com
service/SERVICE_LOCK.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_files": {
3
+ "adapter_config.json": {
4
+ "sha256": "140bb00c832d117f57d29650dabe50e992b2e5064e908aaa29230b3ce19ca2c4",
5
+ "size": 22953
6
+ },
7
+ "adapter_model.safetensors": {
8
+ "sha256": "2b9568998ffed5d133223ffdff03056be297285c643122db4d45365d0bd86970",
9
+ "size": 494595208
10
+ }
11
+ },
12
+ "arm": "trained",
13
+ "calibration_sha256": "5b60ca8516b93982724145bc2227404c4c53a6f5657b4bf2155108c2f001685f",
14
+ "complete": true,
15
+ "config_file_sha256": "0c84b933ac60e43394dbd748da21cb27bf91fe640cac7674acc3d02b80e8de11",
16
+ "fit_report_sha256": "fc20f2f631c98f043f7c02d4cadbe8122580ee400377eef5420d93621111248b",
17
+ "protocol": "adapter_arm_31b_v3",
18
+ "q": 0.49,
19
+ "serving": {
20
+ "draft": {
21
+ "max_new_tokens": 192,
22
+ "reasoning_words": 50
23
+ },
24
+ "fuse": 0.49,
25
+ "item_deadline_sha256": "3633ad26b201ba9200d6bb606852a80cef33a0c58adc0709f15f4bc8a49d9148",
26
+ "item_rules": {
27
+ "reread_tau": 4.0,
28
+ "slow_types": [
29
+ "choice",
30
+ "noul",
31
+ "score"
32
+ ]
33
+ },
34
+ "post_calibration_sha256": "02cbf05b38bb627acd416f64e66a5cea6a70ce3a7055f1976fcf54e965396951",
35
+ "quota": 0.49,
36
+ "selection": {
37
+ "freeze_sha256": "6e5325eb9713ed73ed9fc94992b67b1179ea60d6969757af61d5c8860d2bb22e",
38
+ "protocol_sha256": "1c279f3d780a0a522a217c38f74892e9c7019be5012409a9c83bbd9dbc179771",
39
+ "readout_sha256": "8cc627afcf00b3b9ba9edafe004adc8294d45c62bec8a2dc5b3f593cac4ea0a2",
40
+ "selected": "serving rules selected on our own development set",
41
+ "selection_sha256": "cc1070b2d79a088983cdb5c300adc7c1ed5c25232003830a9d1a8611e4df6820"
42
+ },
43
+ "threshold": 6.625023671062799
44
+ },
45
+ "serving_identity_sha256": "0513d121db967ec1a6aaccf627d3c42149d092dd027298da5b53684b030821ea",
46
+ "status": "frozen",
47
+ "variant": "key_value_aligned"
48
+ }
service/attention.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Report the attention backend named in the engine log; the requested setting is reported separately."""
2
+ import os
3
+ from pathlib import Path
4
+ import re
5
+
6
+ # vLLM 0.31 logs "Using AttentionBackendEnum.<NAME> backend."; older formats name the backend in prose.
7
+ _PATTERN = re.compile(r'\bUsing\s+(?:AttentionBackendEnum\.([A-Za-z0-9_]+)|([A-Za-z][A-Za-z0-9_ -]*?))\s+backend\b', re.I)
8
+ _ALIASES = {'FLASH_ATTENTION': 'FLASH_ATTN', 'TRITON_ATTENTION': 'TRITON_ATTN',
9
+ 'FLASH_INFER': 'FLASHINFER', 'FLASHINFER_ATTENTION': 'FLASHINFER'}
10
+
11
+
12
+ def attention_health():
13
+ """Backend names found in the engine log of this service process (JEV_ATTENTION_LOG).
14
+
15
+ The requested value comes from JEV_ATTENTION_BACKEND; 'auto' means vLLM selected the backend.
16
+ A request is never reported as the active backend.
17
+ """
18
+ requested = os.environ.get('JEV_ATTENTION_BACKEND') or 'auto'
19
+ text = ''
20
+ path = os.environ.get('JEV_ATTENTION_LOG')
21
+ if path:
22
+ try:
23
+ text = Path(path).read_text(encoding='utf-8', errors='replace')
24
+ except OSError:
25
+ pass
26
+ text = re.sub(r'\x1b\[[0-9;]*m', '', text)
27
+ names = []
28
+ for enum_name, prose in _PATTERN.findall(text):
29
+ raw = (enum_name or prose).strip().upper().replace('-', '_').replace(' ', '_')
30
+ if not enum_name and 'ATTN' not in raw and 'ATTENTION' not in raw:
31
+ continue # "Using X backend" lines about other kernels
32
+ names.append(_ALIASES.get(raw, raw.removesuffix('_ATTENTION')))
33
+ names = list(dict.fromkeys(names))
34
+ return {'attention_backend': names[-1] if names else None,
35
+ 'attention_backends': names,
36
+ 'attention_backend_requested': requested,
37
+ 'attention_backend_source': 'engine_log' if names else 'unreported'}
service/backends.py ADDED
@@ -0,0 +1,691 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Lazy model backends; importing this module never imports or loads a model.
2
+
3
+ Both backends return unmasked next-token scores. vLLM returns raw log
4
+ probabilities, which differ from logits by one common additive constant; the
5
+ form-set logsumexp and subsequent label temperature are invariant to it.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import math
11
+ import logging
12
+ import json
13
+ import hashlib
14
+ import os
15
+ import re
16
+ import threading
17
+ import time
18
+ from pathlib import Path
19
+ from typing import Any, Iterable
20
+
21
+
22
+ class BackendError(RuntimeError):
23
+ """An unavailable or incompatible backend, with no approximation fallback."""
24
+
25
+
26
+ def _candidate_ids(values: Iterable[int]) -> list[int]:
27
+ ids = list(dict.fromkeys(values))
28
+ if not ids or any(isinstance(i, bool) or not isinstance(i, int) or i < 0 for i in ids):
29
+ raise ValueError("candidate_ids must contain nonnegative integer token IDs")
30
+ return ids
31
+
32
+
33
+ def _max_new_tokens(value: int) -> int:
34
+ if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
35
+ raise ValueError("max_new_tokens must be a positive integer")
36
+ return value
37
+
38
+
39
+ def _check_scores(scores: dict[int, float]) -> dict[int, float]:
40
+ if any(math.isnan(value) or value == math.inf for value in scores.values()):
41
+ raise BackendError("backend returned NaN or positive infinite candidate scores")
42
+ return scores
43
+
44
+
45
+ def generation_audit(config):
46
+ """Compare explicit service settings; never silently inherit checkpoint policy."""
47
+ report={'verified':False,'status':'unavailable','differences':{},'intended_overrides':{},'sha256':None}
48
+ model_path=config['model'].get('path')
49
+ path=Path(os.path.expanduser(model_path))/'generation_config.json' if model_path else None
50
+ if path is not None and path.is_file():
51
+ try:
52
+ data=path.read_bytes();reference=json.loads(data)
53
+ if not isinstance(reference,dict):raise ValueError('Expected mapping')
54
+ report['sha256']=hashlib.sha256(data).hexdigest()
55
+ def safe(v):
56
+ if v is None or isinstance(v,bool) or isinstance(v,(int,float)) and math.isfinite(v):return v
57
+ if isinstance(v,list):return [safe(x) for x in v]
58
+ return 'unsupported value'
59
+ for key,value in config['generation'].items():
60
+ if key not in ('seed','reasoning_words','max_new_tokens','wall_timeout_seconds') and key in reference and reference[key]!=value:
61
+ difference={'checkpoint':safe(reference[key]),'configured':safe(value)}
62
+ declared=config.get('generation_reference',{}).get('intended_overrides',{}).get(key,{})
63
+ intended=(isinstance(declared,dict) and declared.get('reason')
64
+ and 'value' in declared and type(declared['value']) is type(value)
65
+ and declared['value']==value and config['generation'].get('do_sample') is False)
66
+ target='intended_overrides' if intended else 'differences'
67
+ if intended:difference['reason']=declared['reason']
68
+ report[target][key]=difference
69
+ report.update(verified=not report['differences'],status='mismatch' if report['differences'] else 'matched_explicit_keys')
70
+ except (OSError,ValueError,TypeError):report['status']='unreadable'
71
+ if not report['verified']:logging.warning('Generation reference %s; explicit configured parameters remain active',report['status'])
72
+ return report
73
+
74
+
75
+ def install_forward_counter(backend):
76
+ backend.forward_counters={'forward_tokens':0,'prefill_tokens':0,'uncounted_forwards':0}
77
+ def count_forward(module,args,kwargs):
78
+ try:
79
+ ids=kwargs.get('input_ids',args[0] if args else None)
80
+ if ids is None:ids=kwargs.get('inputs_embeds')
81
+ if ids is None:raise ValueError('No input tensor')
82
+ n=int(ids.shape[1]);past=kwargs.get('past_key_values')
83
+ empty=past is None or (callable(getattr(past,'get_seq_length',None)) and past.get_seq_length()==0)
84
+ backend.forward_counters['forward_tokens']+=n
85
+ if empty:backend.forward_counters['prefill_tokens']+=n
86
+ except Exception:
87
+ backend.forward_counters['uncounted_forwards']+=1
88
+ logging.warning('HF forward count unavailable; usage estimated')
89
+ target=backend.model.get_base_model() if callable(getattr(backend.model,'get_base_model',None)) else backend.model
90
+ hook=getattr(target,'register_forward_pre_hook',None)
91
+ backend.stats_available=callable(hook)
92
+ if backend.stats_available:backend.stats_hook=hook(count_forward,with_kwargs=True)
93
+ else:logging.warning('HF forward hook unavailable; usage estimated')
94
+
95
+
96
+ def runtime_info():
97
+ """Versions on the numeric path and the visible GPU, reported in /health info.runtime.
98
+
99
+ The GPU name comes from vLLM's platform interface (NVML), so this process creates no CUDA
100
+ context. triton_module is the version of the triton module the process actually imported.
101
+ """
102
+ import importlib.metadata
103
+ import platform
104
+ import sys
105
+ info = {"python": platform.python_version()}
106
+ for name in ("vllm", "torch", "transformers", "tokenizers", "triton", "compressed-tensors"):
107
+ try:
108
+ info[name] = importlib.metadata.version(name)
109
+ except importlib.metadata.PackageNotFoundError:
110
+ info[name] = None
111
+ info["triton_module"] = getattr(sys.modules.get("triton"), "__version__", None)
112
+ try:
113
+ import torch
114
+ info["torch_cuda"] = torch.version.cuda
115
+ except Exception:
116
+ info["torch_cuda"] = None
117
+ try:
118
+ from vllm.platforms import current_platform
119
+ info["gpu"] = current_platform.get_device_name()
120
+ except Exception:
121
+ info["gpu"] = None
122
+ return info
123
+
124
+
125
+ class TokenizerMixin:
126
+ tokenizer: Any
127
+
128
+ def scan_digit_tokens(self):
129
+ vocab=self.tokenizer.get_vocab() if hasattr(self.tokenizer,'get_vocab') else {}
130
+ self.digit_start_ids={token for token in set(vocab.values())
131
+ if re.match(r'^\d',self.decode([token]).lstrip())}
132
+
133
+ def encode(self, text: str) -> list[int]:
134
+ return list(self.tokenizer.encode(text, add_special_tokens=False))
135
+
136
+ def decode(self, ids: Iterable[int]) -> str:
137
+ return self.tokenizer.decode(list(ids), skip_special_tokens=True)
138
+
139
+ def prompt_ids(self, system: str, text: str, enable_thinking: bool = False) -> list[int]:
140
+ # The instructions and the context share one user turn, as in all measured runs.
141
+ messages = [{"role": "user", "content": system + "\n\n" + text}]
142
+ try:
143
+ prompt = self.tokenizer.apply_chat_template(
144
+ messages, tokenize=False, add_generation_prompt=True,
145
+ enable_thinking=enable_thinking,
146
+ )
147
+ except TypeError as exc:
148
+ if "enable_thinking" not in str(exc):
149
+ raise
150
+ prompt = self.tokenizer.apply_chat_template(
151
+ messages, tokenize=False, add_generation_prompt=True,
152
+ )
153
+ return self.encode(prompt)
154
+
155
+
156
+ class TransformersBackend(TokenizerMixin):
157
+ """Direct single-forward readout, and a separate bounded draft generation."""
158
+
159
+ readout_output_tokens = 0
160
+
161
+ def __init__(self, config: dict[str, Any]):
162
+ model_cfg = config["model"]
163
+ self.generation = dict(config["generation"])
164
+ self.generation_reference=generation_audit(config)
165
+ self.marker = config.get("prompt", {}).get("answer_marker", "ANSWER:")
166
+ if model_cfg["quant"] not in {"int8", "bf16"}:
167
+ raise ValueError("model.quant must be int8 or bf16")
168
+ if not model_cfg["path"]:
169
+ raise ValueError("model.path is required before loading a model")
170
+ try:
171
+ import torch
172
+ import transformers
173
+ except ImportError as exc:
174
+ raise BackendError("transformers backend requires torch and transformers") from exc
175
+ self.torch = torch
176
+ torch.manual_seed(self.generation["seed"])
177
+ # expanduser keeps a Hub identifier's slash intact on Windows.
178
+ path = os.path.expanduser(model_cfg["path"])
179
+ cfg = transformers.AutoConfig.from_pretrained(path)
180
+ declared_quant = getattr(cfg, "quantization_config", None)
181
+ if declared_quant:
182
+ if not isinstance(declared_quant, dict):
183
+ declared_quant = declared_quant.to_dict()
184
+ if model_cfg["quant"] == "bf16":
185
+ raise BackendError("bf16 requires an unquantized checkpoint; base config declares quantization")
186
+ if (declared_quant.get("quant_method") != "bitsandbytes"
187
+ or not declared_quant.get("load_in_8bit")
188
+ or declared_quant.get("load_in_4bit")):
189
+ raise BackendError("int8 requires an unquantized base or a BitsAndBytes 8bit checkpoint")
190
+ archs = list(getattr(cfg, "architectures", None) or [])
191
+ if any("Qwen3_5ForConditionalGeneration" in name for name in archs):
192
+ model_cls = getattr(transformers, "Qwen3_5ForConditionalGeneration", None)
193
+ if model_cls is None:
194
+ raise BackendError("installed transformers lacks Qwen3_5ForConditionalGeneration")
195
+ else:
196
+ model_cls = transformers.AutoModelForCausalLM
197
+ kwargs = {
198
+ "dtype": torch.bfloat16,
199
+ "device_map": model_cfg["device_map"],
200
+ "low_cpu_mem_usage": True,
201
+ "attn_implementation": model_cfg["attn_implementation"],
202
+ }
203
+ if model_cfg["quant"] == "int8":
204
+ kwargs["quantization_config"] = transformers.BitsAndBytesConfig(load_in_8bit=True)
205
+ self.tokenizer = transformers.AutoTokenizer.from_pretrained(path)
206
+ self.scan_digit_tokens()
207
+ self.model = model_cls.from_pretrained(path, **kwargs).eval()
208
+ if model_cfg.get("adapter"):
209
+ try:
210
+ from peft import PeftModel
211
+ except ImportError as exc:
212
+ raise BackendError("--adapter requires peft for the transformers backend") from exc
213
+ self.model = PeftModel.from_pretrained(
214
+ self.model, str(Path(model_cfg["adapter"]).expanduser()),
215
+ ).eval()
216
+ # A fresh config prevents checkpoint-specific suppression, forced tokens,
217
+ # sampling filters or minimum-length policies from silently changing the
218
+ # configured draft. Preserve special-token metadata, including EOS lists.
219
+ special = {}
220
+ for name in ("bos_token_id", "eos_token_id", "pad_token_id"):
221
+ for source in (getattr(self.model, "generation_config", None),
222
+ getattr(self.model, "config", None), self.tokenizer):
223
+ value = getattr(source, name, None)
224
+ if value is not None:
225
+ special[name] = value
226
+ break
227
+ self.prefix_caching=False
228
+ install_forward_counter(self)
229
+ self.draft_generation_config = transformers.GenerationConfig(**special, **{k:v for k,v in self.generation.items()
230
+ if k not in ("reasoning_words","seed","max_new_tokens","wall_timeout_seconds")})
231
+ eos = special.get("eos_token_id", [])
232
+ self.eos_ids = eos if isinstance(eos, list) else [eos]
233
+
234
+ def engine_stats(self):
235
+ return {'source':'hf_forward_hook','prefix_caching':False,
236
+ 'counters':dict(self.forward_counters) if self.stats_available else {}}
237
+
238
+ def open_session(self, ids):
239
+ return CachedSession(self, ids)
240
+
241
+ def slow_session(self, ids, unparsed_policy, meter, max_new_tokens=None):
242
+ count=_max_new_tokens(max_new_tokens or self.generation['max_new_tokens'])
243
+ tensor=self._input(ids)
244
+ meter['prompt']+=len(ids); meter['engine_submitted']+=len(ids)
245
+ meter['engine_input']=meter.get('engine_input',0)+len(ids)
246
+ meter['engine_calls']=meter.get('engine_calls',0)+1
247
+ meter['hf_prefill_tokens']=meter.get('hf_prefill_tokens',0)+len(ids)
248
+ class MeterStreamer:
249
+ first=True
250
+ def put(inner,value):
251
+ if inner.first: inner.first=False; return
252
+ n=value.shape[-1]
253
+ meter['output']+=n; meter['draft']+=n
254
+ meter['engine_generated']=meter.get('engine_generated',0)+n
255
+ if not getattr(inner,'counted',False):
256
+ meter['generate_nonempty_calls']=meter.get('generate_nonempty_calls',0)+1; inner.counted=True
257
+ def end(inner): pass
258
+ streamer=MeterStreamer()
259
+ g=self.generation
260
+ kwargs=dict(input_ids=tensor,attention_mask=self.torch.ones_like(tensor),
261
+ generation_config=self.draft_generation_config,max_new_tokens=count,
262
+ max_time=g.get("wall_timeout_seconds",60),
263
+ do_sample=g['do_sample'],num_beams=1,num_return_sequences=1,
264
+ repetition_penalty=g['repetition_penalty'],use_cache=True,
265
+ return_dict_in_generate=True,stop_strings=[self.marker],tokenizer=self.tokenizer,
266
+ streamer=streamer,pad_token_id=self.tokenizer.pad_token_id or self.tokenizer.eos_token_id)
267
+ if g['do_sample']: kwargs.update(temperature=g['temperature'],top_p=g['top_p'],top_k=g['top_k'])
268
+ check_deadline(meter)
269
+ try:
270
+ with self.torch.inference_mode(): output=self.model.generate(**kwargs)
271
+ except Exception:
272
+ meter['engine_usage_unknown']=True
273
+ raise
274
+ check_deadline(meter)
275
+ generated=output.sequences[0,len(ids):].tolist()
276
+ # Transformers streams sampled IDs even when the next forward raises.
277
+ if meter['draft']!=len(generated): raise BackendError('Generation streamer accounting mismatch')
278
+ parsed=bool(re.search(r'(?m)^[ \t]*'+re.escape(self.marker)+r'$',self.decode(generated)))
279
+ if not parsed and unparsed_policy=='fast': return None,False
280
+ cache=output.past_key_values
281
+ if cache is None or not generated: raise BackendError('Generation returned no reusable cache')
282
+ session=object.__new__(CachedSession)
283
+ session.backend,session.cache,session.length=self,cache,len(ids)+len(generated)-1
284
+ session._log_probs=None
285
+ session.meter=meter
286
+ # Standard generate cache is one sampled token behind sequences.
287
+ session.advance(generated[-1:])
288
+ if not parsed:
289
+ forced=self.encode('\n'+self.marker)
290
+ meter['output']+=len(forced);meter['forced']+=len(forced)
291
+ session.advance(forced)
292
+ return session,parsed
293
+
294
+ def _input(self, ids: Iterable[int]):
295
+ values = list(ids)
296
+ if not values:
297
+ raise ValueError("prompt token IDs cannot be empty")
298
+ embedding = self.model.get_input_embeddings()
299
+ device = embedding.weight.device
300
+ if getattr(device, "type", None) == "meta":
301
+ device = self.model.device
302
+ return self.torch.tensor([values], dtype=self.torch.long, device=device)
303
+
304
+ def next_logits(self, ids: Iterable[int], candidate_ids: Iterable[int]) -> dict[int, float]:
305
+ candidates = _candidate_ids(candidate_ids)
306
+ tensor = self._input(ids)
307
+ with self.torch.inference_mode():
308
+ try:
309
+ output = self.model(input_ids=tensor, use_cache=False, logits_to_keep=1)
310
+ except TypeError as exc:
311
+ if "logits_to_keep" not in str(exc):
312
+ raise
313
+ output = self.model(input_ids=tensor, use_cache=False)
314
+ logits = output.logits[0, -1, :]
315
+ if max(candidates) >= logits.shape[0]:
316
+ raise BackendError("candidate token ID exceeds the model output vocabulary")
317
+ index = self.torch.tensor(candidates, dtype=self.torch.long, device=logits.device)
318
+ selected = logits.index_select(0, index).float().cpu().tolist()
319
+ return _check_scores(dict(zip(candidates, selected)))
320
+
321
+ def generate(self, ids: Iterable[int], max_new_tokens: int) -> list[int]:
322
+ count = _max_new_tokens(max_new_tokens)
323
+ tensor = self._input(ids)
324
+ generation = self.generation
325
+ pad_id = self.tokenizer.pad_token_id
326
+ if pad_id is None:
327
+ pad_id = self.tokenizer.eos_token_id
328
+ kwargs = {
329
+ "input_ids": tensor,
330
+ "attention_mask": self.torch.ones_like(tensor),
331
+ "generation_config": self.draft_generation_config,
332
+ "max_new_tokens": count,
333
+ "do_sample": generation["do_sample"],
334
+ "num_beams": 1,
335
+ "num_return_sequences": 1,
336
+ "repetition_penalty": generation["repetition_penalty"],
337
+ "use_cache": True,
338
+ "return_dict_in_generate": False,
339
+ "pad_token_id": pad_id,
340
+ }
341
+ if generation["do_sample"]:
342
+ kwargs.update(temperature=generation["temperature"],
343
+ top_p=generation["top_p"], top_k=generation["top_k"])
344
+ with self.torch.inference_mode():
345
+ output = self.model.generate(**kwargs)
346
+ return output[0, tensor.shape[1]:].tolist()
347
+
348
+
349
+ class VLLMBackend(TokenizerMixin):
350
+ """Exact candidate readout through explicit raw logprob token IDs.
351
+
352
+ The engine produces one probe token to expose the next-token distribution;
353
+ callers must include readout_output_tokens in measured output-token usage.
354
+ Normal probes gather only explicit candidate IDs. Full-vocabulary output
355
+ is reserved for one logged recovery probe if a backend omits candidates.
356
+ This implementation supports bf16 and an explicitly checked AWQ checkpoint.
357
+ It rejects int8 because vLLM's
358
+ BnB loader settings do not establish equivalence to the reference int8 base.
359
+ """
360
+
361
+ readout_output_tokens = 1
362
+ engine_metering = True
363
+
364
+ @staticmethod
365
+ def _awq_quant_arg(model_cfg):
366
+ """AWQ profile: pass quantization='awq' for classic AWQ checkpoints; let vLLM auto-detect
367
+ compressed-tensors (W4A16) checkpoints, whose config.json declares quant_method."""
368
+ if model_cfg["quant"]!="awq": return None
369
+ try:
370
+ import json as _json
371
+ cfg=_json.loads(Path(os.path.join(os.path.expanduser(model_cfg["path"]),"config.json")).read_text(encoding="utf-8"))
372
+ qm=((cfg.get("quantization_config") or {}).get("quant_method") or "").lower()
373
+ if qm in ("compressed-tensors","compressed_tensors"): return None
374
+ except Exception:
375
+ pass
376
+ return "awq"
377
+
378
+ def __init__(self, config: dict[str, Any]):
379
+ model_cfg = config["model"]
380
+ if model_cfg["quant"] not in ("bf16","awq"):
381
+ raise BackendError("vllm backend supports bf16 or AWQ; use transformers for int8")
382
+ if not model_cfg["path"]:
383
+ raise ValueError("model.path is required before loading a model")
384
+ options = dict(model_cfg["vllm_options"])
385
+ if config.get("limits"):
386
+ options.setdefault("max_model_len",config["limits"]["physical_max_prompt_tokens"]+2048)
387
+ reserved = {
388
+ "model", "tokenizer", "dtype", "quantization", "quantization_config",
389
+ "max_logprobs", "logprobs_mode", "seed", "enable_lora",
390
+ "generation_config", "override_generation_config", "logits_processors",
391
+ "skip_tokenizer_init", "speculative_config", "enable_prefix_caching", "disable_log_stats",
392
+ "attention_backend",
393
+ }
394
+ overlap = sorted(reserved.intersection(options))
395
+ if overlap:
396
+ raise ValueError("model.vllm_options cannot override readout contract: " + ", ".join(overlap))
397
+ try:
398
+ from vllm import LLM, SamplingParams
399
+ except ImportError as exc:
400
+ raise BackendError("vllm backend requires an importable compatible vllm installation") from exc
401
+ self.SamplingParams = SamplingParams
402
+ self.marker=config.get("prompt",{}).get("answer_marker","ANSWER:")
403
+ self.lock=threading.RLock()
404
+ self.generation = dict(config["generation"])
405
+ self.generation_reference=generation_audit(config)
406
+ self.lora_request = None
407
+ if model_cfg.get("adapter"):
408
+ try:
409
+ from vllm.lora.request import LoRARequest
410
+ self.lora_request = LoRARequest(
411
+ "adapter", 1, str(Path(model_cfg["adapter"]).expanduser()),
412
+ )
413
+ except (ImportError, TypeError, ValueError) as exc:
414
+ raise BackendError("installed vllm does not support the requested LoRA adapter API") from exc
415
+ # Unset means vLLM selects the attention backend (auto). The supervisor sets
416
+ # JEV_ATTENTION_BACKEND=TRITON_ATTN only for its second startup attempt.
417
+ requested_attention = os.environ.get("JEV_ATTENTION_BACKEND")
418
+ if requested_attention:
419
+ options["attention_backend"] = requested_attention
420
+ try:
421
+ # These are correctness invariants, not configurable routing policy.
422
+ self.engine = LLM(
423
+ model=os.path.expanduser(model_cfg["path"]), dtype="bfloat16",
424
+ quantization=self._awq_quant_arg(model_cfg), seed=self.generation["seed"],
425
+ max_logprobs=-1, logprobs_mode="raw_logprobs",
426
+ generation_config="vllm", enable_lora=bool(self.lora_request), disable_log_stats=False,
427
+ enable_prefix_caching=config.get("model",{}).get("enable_prefix_caching",True), **options,
428
+ )
429
+ # quantization=None permits checkpoint auto-detection in vLLM. Do
430
+ # not label that run bf16 if the checkpoint activates quantization.
431
+ active_model_cfg = self.engine.llm_engine.model_config
432
+ if model_cfg["quant"]=="bf16" and active_model_cfg.quantization is not None:
433
+ raise BackendError("bf16 requires an unquantized checkpoint; vllm activated quantization")
434
+ if model_cfg['quant']=='awq' and active_model_cfg.quantization not in ('awq','awq_marlin','compressed-tensors'):
435
+ raise BackendError('AWQ profile requires active AWQ quantization')
436
+ self.quantization_kernel=active_model_cfg.quantization
437
+ cache_cfg=getattr(getattr(self.engine.llm_engine,'vllm_config',None),'cache_config',None)
438
+ self.prefix_caching=getattr(cache_cfg,'enable_prefix_caching',None)
439
+ self.runtime_info=runtime_info()
440
+ self.tokenizer = self.engine.get_tokenizer()
441
+ self.scan_digit_tokens()
442
+ self.probe_params = SamplingParams(
443
+ max_tokens=1, temperature=1.0, top_p=1.0, top_k=-1,
444
+ min_p=0.0, presence_penalty=0.0, frequency_penalty=0.0,
445
+ repetition_penalty=1.0, logprobs=-1, detokenize=False,
446
+ seed=self.generation["seed"],
447
+ )
448
+ except Exception as exc:
449
+ raise BackendError(
450
+ "vllm initialization failed; selected precision/architecture/adapter and full-vocabulary "
451
+ "raw_logprobs support are required. No approximate fallback is used."
452
+ ) from exc
453
+
454
+ def engine_stats(self):
455
+ counters={}
456
+ accepted={'prompt_tokens','generation_tokens','prefix_cache_queries','prefix_cache_hits','num_preemptions','request_success'}
457
+ for metric in self.engine.get_metrics():
458
+ name=metric.name.removeprefix('vllm:').removesuffix('_total')
459
+ if name not in accepted: continue
460
+ value=getattr(metric,'value',None)
461
+ if isinstance(value,(int,float)) and not isinstance(value,bool):
462
+ counters[name]=counters.get(name,0)+value
463
+ return {'source':'vllm_metrics','prefix_caching':self.prefix_caching,'counters':counters}
464
+
465
+ def _completion(self, ids: Iterable[int], sampling_params: Any, meter=None, kind="probes"):
466
+ check_deadline(meter)
467
+ values = list(ids)
468
+ if not values:
469
+ raise ValueError("prompt token IDs cannot be empty")
470
+ kwargs = {"use_tqdm": False}
471
+ if self.lora_request is not None:
472
+ kwargs["lora_request"] = self.lora_request
473
+ try:
474
+ if meter is not None: meter['engine_submitted']+=len(values)
475
+ # TokensPrompt is a TypedDict; its runtime representation is this dict.
476
+ with self.lock:
477
+ check_deadline(meter)
478
+ result = self.engine.generate(
479
+ [{"prompt_token_ids": values}], sampling_params, **kwargs,
480
+ )
481
+ except TimeoutError:
482
+ raise
483
+ except Exception as exc:
484
+ if meter is not None: meter['engine_usage_unknown']=True
485
+ raise BackendError("vllm request failed; verify its architecture, adapter and logprobs support") from exc
486
+ if len(result) != 1 or len(result[0].outputs) != 1:
487
+ raise BackendError("vllm did not return exactly one completion")
488
+ if meter is not None:
489
+ n=len(result[0].outputs[0].token_ids)
490
+ meter['output']+=n;meter[kind]+=n
491
+ cached=getattr(result[0],'num_cached_tokens',None)
492
+ if cached is None: meter['cached_unknown']=True
493
+ else: meter['cached']+=int(cached)
494
+ meter['engine_calls']=meter.get('engine_calls',0)+1
495
+ meter['engine_generated']=meter.get('engine_generated',0)+n
496
+ meter['engine_input']=meter.get('engine_input',0)+len(values)-int(cached or 0)
497
+ check_deadline(meter)
498
+ return result[0].outputs[0]
499
+
500
+ def next_logits(self, ids: Iterable[int], candidate_ids: Iterable[int], meter=None) -> dict[int, float]:
501
+ candidates = _candidate_ids(candidate_ids)
502
+ values = list(ids)
503
+ scores = {}
504
+ # v0.31 supports explicit gather from RAW logprobs (before masks).
505
+ # Chunking preserves the same full-vocabulary normalizer for all IDs.
506
+ for start in range(0, len(candidates), 128):
507
+ wanted = candidates[start:start + 128]
508
+ kwargs = dict(max_tokens=1, temperature=1.0, top_p=1.0, top_k=-1,
509
+ repetition_penalty=1.0, detokenize=False,
510
+ seed=self.generation['seed'])
511
+ try:
512
+ params = self.SamplingParams(**kwargs, allowed_token_ids=wanted,
513
+ logprob_token_ids=wanted, logprobs=len(wanted))
514
+ except TypeError:
515
+ params = self.SamplingParams(**kwargs, logprobs=min(50, max(len(wanted), 1)))
516
+ output = self._completion(values, params, meter)
517
+ distribution = output.logprobs[0] if output.logprobs else None
518
+ if len(output.token_ids) != 1:
519
+ raise BackendError('Probe must produce exactly one token')
520
+ if distribution is None or any(t not in distribution for t in wanted):
521
+ logging.warning('Candidate readout incomplete; one full-vocabulary recovery probe')
522
+ if meter is not None:
523
+ meter.setdefault('warnings', []).append('candidate_full_vocab_recovery')
524
+ output = self._completion(values, self.probe_params, meter)
525
+ distribution = output.logprobs[0] if output.logprobs else None
526
+ if len(output.token_ids) != 1 or distribution is None or any(t not in distribution for t in candidates):
527
+ raise BackendError('vllm omitted candidate token logprobs after recovery')
528
+ return _check_scores({t: float(distribution[t].logprob) for t in candidates})
529
+ scores.update({t: float(distribution[t].logprob) for t in wanted})
530
+ return _check_scores(scores)
531
+
532
+ def open_session(self, ids, meter=None):
533
+ return VLLMPrefixSession(self,ids,meter if meter is not None else new_meter())
534
+
535
+ def slow_session(self, ids, unparsed_policy, meter, max_new_tokens=None):
536
+ count=_max_new_tokens(max_new_tokens or self.generation['max_new_tokens'])
537
+ g=self.generation; sampling=g['do_sample']
538
+ params=self.SamplingParams(max_tokens=count,temperature=g['temperature'] if sampling else 0.0,
539
+ top_p=g['top_p'] if sampling else 1.0,top_k=(g['top_k'] or -1) if sampling else -1,
540
+ repetition_penalty=g['repetition_penalty'],seed=g['seed'],
541
+ stop=[self.marker],include_stop_str_in_output=True)
542
+ meter['prompt']+=len(ids)
543
+ out=self._completion(ids,params,meter,'draft')
544
+ gen=list(out.token_ids)
545
+ parsed=(getattr(out,'stop_reason',None)==self.marker and
546
+ getattr(out,'finish_reason',None)=='stop' and
547
+ bool(re.search(r'(?m)^[ \t]*'+re.escape(self.marker)+r'$',self.decode(gen))))
548
+ if not parsed:
549
+ if unparsed_policy=='fast': return None,False
550
+ forced=self.encode('\n'+self.marker);gen+=forced
551
+ meter['output']+=len(forced);meter['forced']+=len(forced)
552
+ return VLLMPrefixSession(self,list(ids)+gen,meter),parsed
553
+
554
+ def generate(self, ids: Iterable[int], max_new_tokens: int) -> list[int]:
555
+ count = _max_new_tokens(max_new_tokens)
556
+ generation = self.generation
557
+ sampling = generation["do_sample"]
558
+ # HF uses top_k=0 for unrestricted sampling; vLLM uses top_k=-1.
559
+ top_k = generation["top_k"] if generation["top_k"] > 0 else -1
560
+ params = self.SamplingParams(
561
+ max_tokens=count, temperature=generation["temperature"] if sampling else 0.0,
562
+ top_p=generation["top_p"] if sampling else 1.0,
563
+ top_k=top_k if sampling else -1,
564
+ repetition_penalty=generation["repetition_penalty"],
565
+ seed=generation["seed"], detokenize=False,
566
+ )
567
+ output = self._completion(ids, params)
568
+ if len(output.token_ids) > count:
569
+ raise BackendError("vllm generated more tokens than max_new_tokens")
570
+ return list(output.token_ids)
571
+
572
+
573
+ def create_backend(config: dict[str, Any]):
574
+ name = config["model"]["backend"]
575
+ if name == "transformers":
576
+ return TransformersBackend(config)
577
+ if name == "vllm":
578
+ return VLLMBackend(config)
579
+ raise ValueError("model.backend must be transformers or vllm")
580
+
581
+ class CachedSession:
582
+ """Single prefill followed exclusively by KV-backed token continuations.
583
+
584
+ Branches own their cache, including recurrent attention state. Models that
585
+ cannot copy/cache fail into the service's fast path rather than re-prefill.
586
+ """
587
+ def __init__(self, backend, ids):
588
+ self.backend = backend
589
+ self.cache = None
590
+ self.length = 0
591
+ self.advance(list(ids))
592
+
593
+ def advance(self, ids):
594
+ if not ids:
595
+ return
596
+ backend = self.backend
597
+ meter=getattr(self,'meter',None)
598
+ if meter is not None:
599
+ check_deadline(meter)
600
+ meter['engine_input']+=len(ids);meter['engine_submitted']+=len(ids)
601
+ kwargs = dict(input_ids=backend._input(ids), use_cache=True, logits_to_keep=1)
602
+ if self.cache is not None:
603
+ kwargs['past_key_values'] = self.cache
604
+ try:
605
+ with backend.torch.inference_mode():
606
+ output = backend.model(**kwargs)
607
+ except Exception:
608
+ if meter is not None:meter['engine_usage_unknown']=True
609
+ raise
610
+ self.cache = output.past_key_values
611
+ if self.cache is None:
612
+ raise BackendError('Model did not return a reusable cache')
613
+ self.length += len(ids)
614
+ self._logits = output.logits[0, -1, :].float()
615
+ self._log_probs = None
616
+
617
+ def top_token(self):
618
+ return int(self._logits.argmax().item())
619
+
620
+ def log_probs(self, ids):
621
+ if self._log_probs is None:
622
+ self._log_probs=self._logits.log_softmax(dim=-1)
623
+ index=self.backend.torch.tensor(list(ids),dtype=self.backend.torch.long,device=self._logits.device)
624
+ return _check_scores(dict(zip(ids,self._log_probs.index_select(0,index).cpu().tolist())))
625
+
626
+ def non_digit_logprob(self, digit_ids):
627
+ if self._log_probs is None: self._log_probs=self._logits.log_softmax(dim=-1)
628
+ ids=[i for i in digit_ids if i<self._logits.shape[0]]
629
+ if not ids: return 0.0
630
+ torch=self.backend.torch
631
+ index=torch.tensor(ids,dtype=torch.long,device=self._logits.device)
632
+ # Sum excluded mass on device; return one scalar, never vocab logits.
633
+ mass=self._log_probs.index_select(0,index).exp().sum().clamp(max=1.0)
634
+ return float(torch.log1p(-mass).item())
635
+
636
+ def fork(self):
637
+ import copy
638
+ branch = object.__new__(type(self))
639
+ branch.backend, branch.length = self.backend, self.length
640
+ branch.meter=getattr(self,'meter',None)
641
+ branch.cache = copy.deepcopy(self.cache)
642
+ branch._logits = self._logits
643
+ branch._log_probs = self._log_probs
644
+ return branch
645
+
646
+
647
+ def check_deadline(meter):
648
+ if meter and (meter.get('cancel_event') is not None and meter['cancel_event'].is_set() or
649
+ meter.get('deadline') is not None and time.monotonic()>=meter['deadline']):
650
+ raise TimeoutError('Slow deadline exceeded')
651
+
652
+
653
+ def new_meter():
654
+ return dict(prompt=0,output=0,cached=0,engine_submitted=0,draft=0,forced=0,probes=0,
655
+ engine_calls=0,engine_generated=0,engine_input=0,generate_nonempty_calls=0,hf_prefill_tokens=0)
656
+
657
+
658
+ class VLLMPrefixSession:
659
+ """Immutable prefix branches; APC owns KV storage and recomputation."""
660
+ handles_meter=True
661
+ def __init__(self,backend,ids,meter):
662
+ self.backend,self.ids,self.meter=backend,list(ids),meter
663
+ self._logits=None
664
+
665
+ @property
666
+ def logits(self):
667
+ if self._logits is None:
668
+ out=self.backend._completion(self.ids,self.backend.probe_params,self.meter)
669
+ if not out.logprobs or out.logprobs[0] is None:
670
+ raise BackendError('Missing full-vocabulary probe distribution')
671
+ self._logits=_check_scores({t:float(lp.logprob) for t,lp in out.logprobs[0].items()})
672
+ return self._logits
673
+
674
+ def log_probs(self,ids):
675
+ return self.backend.next_logits(self.ids, ids, self.meter)
676
+
677
+ def non_digit_logprob(self, digit_ids):
678
+ if not digit_ids: return 0.0
679
+ scores = self.log_probs(sorted(digit_ids))
680
+ mass = min(1.0, math.fsum(math.exp(v) for v in scores.values()))
681
+ return math.log1p(-mass) if mass < 1 else -math.inf
682
+
683
+ def fork(self):
684
+ branch=VLLMPrefixSession(self.backend,self.ids,self.meter)
685
+ branch._logits=self._logits
686
+ return branch
687
+
688
+ def advance(self,ids):
689
+ # These are candidate continuation tokens, separate from engine probes.
690
+ self.ids+=list(ids);self._logits=None
691
+ self.meter['output']+=len(ids);self.meter['probes']+=len(ids)
service/bench.py ADDED
@@ -0,0 +1,795 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Sequential, model-free HTTP benchmark client for the /v1/systemone service (maintainer tool).
2
+
3
+ All outputs are created with exclusive mode at an absolute --output-prefix outside the package.
4
+ """
5
+
6
+ import argparse
7
+ import json
8
+ import math
9
+ import statistics
10
+ import time
11
+ import urllib.parse
12
+ import urllib.request
13
+ from pathlib import Path
14
+
15
+
16
+ ROOT = Path(__file__).resolve().parent
17
+ PUBLIC_FILES = (("easy.jsonl", "easy"), ("original.jsonl", "standard"), ("hard.jsonl", "hard"))
18
+ TYPES = ("noul", "choice", "score")
19
+ ALIASES = {"boolean": "noul", "enum": "choice"}
20
+ QUESTION_NAME = "decision"
21
+ # Local service calls never go through an HTTP proxy.
22
+ _OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}))
23
+
24
+
25
+ def canonical_type(value):
26
+ value = ALIASES.get(value, value)
27
+ if value not in TYPES:
28
+ raise ValueError("unsupported question type")
29
+ return value
30
+
31
+
32
+ def _read_rows(path):
33
+ with Path(path).expanduser().open(encoding="utf-8-sig") as source:
34
+ for line_number, line in enumerate(source, 1):
35
+ if not line.strip():
36
+ continue
37
+ try:
38
+ row = json.loads(line)
39
+ except (ValueError, TypeError) as exc:
40
+ raise ValueError(f"invalid JSONL at line {line_number}") from exc
41
+ if not isinstance(row, dict):
42
+ raise ValueError(f"JSONL line {line_number} must be an object")
43
+ yield row
44
+
45
+
46
+ def normalize_task(task, tier=None, require_expected=False):
47
+ """Keep the source's label order, including choice tie-breaking order."""
48
+ if not isinstance(task.get("id"), str) or not task["id"]:
49
+ raise ValueError("task id must be a nonempty string")
50
+ if "state" not in task or not isinstance(task.get("question"), dict):
51
+ raise ValueError("task needs state and question")
52
+ question = dict(task["question"])
53
+ qtype = canonical_type(question.get("type"))
54
+ question["request_type"] = question.get("request_type",question["type"])
55
+ question["type"] = qtype
56
+ instructions = question.get("instructions", question.get("description"))
57
+ if not isinstance(instructions, str) or not instructions.strip():
58
+ raise ValueError("question needs nonempty instructions or description")
59
+ question["instructions"] = instructions
60
+ question.pop("description", None)
61
+ criteria = question.get("criteria")
62
+ if qtype == "noul":
63
+ if criteria is not None and not isinstance(criteria, dict):
64
+ raise ValueError("noul criteria must be an object")
65
+ labels = ["no", "yes"]
66
+ elif qtype == "score":
67
+ if not isinstance(criteria, list) or not criteria:
68
+ raise ValueError("score criteria must be a nonempty list")
69
+ labels = [str(index) for index in range(len(criteria))]
70
+ else:
71
+ labels = task.get("labels", question.get("labels"))
72
+ if labels is None and isinstance(criteria, dict):
73
+ labels = list(criteria)
74
+ if labels is None and isinstance(criteria, list):
75
+ labels = criteria
76
+ if not isinstance(labels, list) or not 1 <= len(labels) <= 676:
77
+ raise ValueError("choice requires between 1 and 676 labels")
78
+ labels = list(labels)
79
+ if any(not isinstance(label, str) or not label for label in labels):
80
+ raise ValueError("choice labels must be nonempty strings")
81
+ if len(set(labels)) != len(labels):
82
+ raise ValueError("choice labels must be distinct")
83
+ question["labels"] = labels
84
+ expected = task.get("expected")
85
+ if expected is not None:
86
+ if qtype == "noul" and isinstance(expected, bool):
87
+ expected = "yes" if expected else "no"
88
+ elif qtype == "score":
89
+ expected = str(expected)
90
+ if expected not in labels:
91
+ raise ValueError("expected answer is not a valid label")
92
+ elif require_expected:
93
+ raise ValueError("public source task is missing expected answer")
94
+ result = dict(task)
95
+ result.update(question=question, type=qtype, labels=labels, expected=expected)
96
+ result["tier"] = tier if tier is not None else task.get("tier", "custom")
97
+ return result
98
+
99
+
100
+ def load_jsonl_tasks(path):
101
+ tasks = [normalize_task(row) for row in _read_rows(path)]
102
+ _require_unique(tasks)
103
+ if not tasks:
104
+ raise ValueError("input contains no tasks")
105
+ return tasks
106
+
107
+
108
+ def _require_unique(rows):
109
+ seen = set()
110
+ for row in rows:
111
+ if row["id"] in seen:
112
+ raise ValueError("duplicate task id")
113
+ seen.add(row["id"])
114
+
115
+
116
+ def load_public_tasks(index, public, expected_count):
117
+ """Use only id/type/tier from the index; all task content comes from public."""
118
+ metadata = []
119
+ for cached in _read_rows(index):
120
+ # Do not use cached expected labels, logits, predictions or prompt content.
121
+ item = {key: cached.get(key) for key in ("id", "type", "tier")}
122
+ if not isinstance(item["id"], str) or not item["id"]:
123
+ raise ValueError("index id must be a nonempty string")
124
+ item["type"] = canonical_type(item["type"])
125
+ if item["tier"] not in {tier for _, tier in PUBLIC_FILES}:
126
+ raise ValueError("index tier is not a public tier")
127
+ metadata.append(item)
128
+ _require_unique(metadata)
129
+ if len(metadata) != expected_count:
130
+ raise ValueError("index task count does not match expected_public_count")
131
+ by_id = {}
132
+ for filename, tier in PUBLIC_FILES:
133
+ for row in _read_rows(Path(public).expanduser() / filename):
134
+ if "tier" in row and row["tier"] != tier:
135
+ raise ValueError("public task tier conflicts with its source file")
136
+ task = normalize_task(row, tier=tier, require_expected=True)
137
+ if task["id"] in by_id:
138
+ raise ValueError("duplicate public task id")
139
+ by_id[task["id"]] = task
140
+ selected = []
141
+ for meta in metadata:
142
+ task = by_id.get(meta["id"])
143
+ if task is None:
144
+ raise ValueError("indexed task missing from public sources")
145
+ if task["type"] != meta["type"] or task["tier"] != meta["tier"]:
146
+ raise ValueError("indexed type or tier does not match public source")
147
+ selected.append(task)
148
+ return selected
149
+
150
+
151
+ def request_payload(task, original_types=False, model=None, label_order=False):
152
+ question=task['question']
153
+ q={'type':question['type'],'instructions':question['instructions']}
154
+ if question.get('criteria') is not None: q['criteria']=question['criteria']
155
+ if original_types:q['type']=question.get('request_type',question['type'])
156
+ if label_order and task['type']=='choice': q['labels']=list(task['labels'])
157
+ return {'state':task['state'],'model':model,'questions':{QUESTION_NAME:q}}
158
+
159
+
160
+ def endpoint_url(endpoint):
161
+ parts = urllib.parse.urlsplit(endpoint)
162
+ if parts.scheme not in {"http", "https"} or not parts.netloc or parts.query or parts.fragment:
163
+ raise ValueError("endpoint must be an HTTP base URL or the systemone route")
164
+ if parts.username or parts.password:
165
+ raise ValueError("endpoint must not contain credentials")
166
+ path = parts.path.rstrip("/")
167
+ if path not in {"", "/v1/systemone"}:
168
+ raise ValueError("endpoint path must be empty or /v1/systemone")
169
+ return urllib.parse.urlunsplit((parts.scheme, parts.netloc, "/v1/systemone", "", ""))
170
+
171
+
172
+ def post_json(endpoint, payload, timeout):
173
+ request = urllib.request.Request(endpoint_url(endpoint),
174
+ data=json.dumps(payload).encode("utf-8"),
175
+ headers={"Content-Type": "application/json"}, method="POST")
176
+ with _OPENER.open(request, timeout=timeout) as response:
177
+ return json.load(response)
178
+
179
+
180
+ def parse_prediction(response, task):
181
+ if not isinstance(response, dict) or not isinstance(response.get("answers"), dict):
182
+ raise ValueError("response must contain answers")
183
+ if not isinstance(response.get("model"), str) or not response["model"]:
184
+ raise ValueError("response must identify its model")
185
+ answer = response["answers"].get(QUESTION_NAME)
186
+ if not isinstance(answer, dict) or canonical_type(answer.get("type")) != task["type"]:
187
+ raise ValueError("response question type mismatch")
188
+ if answer.get("warning") or response.get("partial_errors"):
189
+ raise ValueError("degraded API response")
190
+ probabilities = answer.get("probabilities")
191
+ if task["type"]=="noul" and isinstance(probabilities,dict) and set(probabilities)=={"true","false"}:
192
+ probabilities={"yes":probabilities["true"],"no":probabilities["false"]}
193
+ if not isinstance(probabilities, dict) or set(probabilities) != set(task["labels"]):
194
+ raise ValueError("response probabilities must have exactly the requested labels")
195
+ values = list(probabilities.values())
196
+ if any(isinstance(value, bool) or not isinstance(value, (int, float))
197
+ or not math.isfinite(value) or value < 0 or value > 1 for value in values):
198
+ raise ValueError("response contains invalid probabilities")
199
+ if not math.isclose(sum(values), 1.0, rel_tol=1e-6, abs_tol=1e-6):
200
+ raise ValueError("response probabilities must sum to one")
201
+ # Match official score_task: lexicographically smallest label wins ties.
202
+ return max(sorted(task["labels"]), key=lambda label: probabilities[label]), probabilities
203
+
204
+
205
+ def _token_count(value):
206
+ return value if isinstance(value, int) and not isinstance(value, bool) and value >= 0 else None
207
+
208
+
209
+ def _safe_usage_json(value):
210
+ """Keep malformed optional metadata from making result JSON unwritable."""
211
+ if value is None or isinstance(value, (str, bool, int)):
212
+ return value, False
213
+ if isinstance(value, float):
214
+ return (value, False) if math.isfinite(value) else (None, True)
215
+ if isinstance(value, list):
216
+ parts = [_safe_usage_json(item) for item in value]
217
+ return [item for item, _ in parts], any(changed for _, changed in parts)
218
+ if isinstance(value, dict):
219
+ result, changed = {}, False
220
+ for key, item in value.items():
221
+ if not isinstance(key, str):
222
+ changed = True
223
+ continue
224
+ result[key], item_changed = _safe_usage_json(item)
225
+ changed = changed or item_changed
226
+ return result, changed
227
+ return None, True
228
+
229
+
230
+ def extract_usage(response):
231
+ usage = response.get("usage") if isinstance(response, dict) else None
232
+ usage = usage if isinstance(usage, dict) else {}
233
+ usage, sanitized = _safe_usage_json(usage)
234
+ questions = usage.get("questions", {})
235
+ detail = questions.get(QUESTION_NAME, {}) if isinstance(questions, dict) else {}
236
+ detail = detail if isinstance(detail, dict) else {}
237
+ prompt = _token_count(usage.get("input_tokens", usage.get("prompt_tokens")))
238
+ completion = _token_count(usage.get("output_tokens", usage.get("completion_tokens")))
239
+ total = _token_count(usage.get("total_tokens"))
240
+ n_new_tokens = _token_count(detail.get("n_new_tokens"))
241
+ complete = prompt is not None and completion is not None and total is not None
242
+ complete = complete and total == prompt + completion
243
+ complete = complete and n_new_tokens is not None
244
+ if usage.get("accounting") is None:complete=complete and n_new_tokens<=completion if complete else False
245
+ complete = complete and not sanitized and not detail.get("usage_estimated", False) and not usage.get("usage_estimated",False)
246
+ path = detail.get("path")
247
+ if not isinstance(path, str) or path not in {"fast", "slow"}:
248
+ path = None
249
+ router_score = detail.get("router_score")
250
+ if isinstance(router_score, bool) or not isinstance(router_score, (int, float)) or not math.isfinite(router_score):
251
+ router_score = None
252
+ return {"usage": usage, "prompt_tokens": prompt, "completion_tokens": completion,
253
+ "total_tokens": total, "usage_complete": bool(complete), "usage_sanitized": sanitized,
254
+ "n_new_tokens": n_new_tokens, "path": path, "router_score": router_score,
255
+ "fast_prompt_tokens":detail.get("fast_prompt_tokens"), "prefix_caching":response.get("prefix_caching"),
256
+ "fast_scores":detail.get("fast_scores",detail.get("raw_label_scores")),
257
+ "slow_scores":detail.get("slow_scores",detail.get("slow_raw_label_scores")),
258
+ "slow_raw_label_scores":detail.get("slow_raw_label_scores"),
259
+ "calibration_block":detail.get("calibration_block"),"threshold_identity":detail.get("threshold_identity"),
260
+ "budget_tier":detail.get("budget_tier"), "cost_fuse":detail.get("cost_fuse"),
261
+ "raw_label_scores":detail.get("raw_label_scores"), "fast_margin":detail.get("fast_margin"), "margin_kind":detail.get("margin_kind")}
262
+
263
+
264
+ def run_benchmark(tasks, endpoint, config, client=post_json, clock=time.perf_counter, writer=None, concurrency=1):
265
+ """One request per task, no retries; ordered rows, latency includes each call."""
266
+ if type(concurrency) is not int or concurrency<1: raise ValueError('concurrency must be positive')
267
+ if concurrency>1:
268
+ from concurrent.futures import ThreadPoolExecutor
269
+ def one(task): return run_benchmark([task],endpoint,config,client,clock)[0]
270
+ with ThreadPoolExecutor(max_workers=concurrency) as pool:
271
+ rows=list(pool.map(one,tasks))
272
+ for row in rows: row["concurrency"]=concurrency
273
+ if writer is not None:
274
+ for row in rows: writer.write(json.dumps(row,ensure_ascii=False,allow_nan=False)+'\n')
275
+ writer.flush()
276
+ return rows
277
+ rows = []
278
+ timeout = config["bench"]["timeout_seconds"]
279
+ for task in tasks:
280
+ row = {"request_order":"labels" if config.get("bench",{}).get("label_order",False) else "criteria", "id": task["id"], "type": task["type"], "tier": task.get("tier"),
281
+ "benchmark_set":task.get("benchmark_set"), "concurrency":concurrency, "expected": task.get("expected"), "labels": task["labels"], "pred": None,
282
+ "probabilities": None, "ok": False, "error": None, "model": None}
283
+ row.update(extract_usage({}))
284
+ start = clock()
285
+ try:
286
+ response = client(endpoint, request_payload(task,config.get("bench",{}).get("original_types",False),config["model"]["name"],config.get("bench",{}).get("label_order",False)), timeout)
287
+ except Exception as exc:
288
+ # Exception text can include an endpoint or credentials; never save it.
289
+ row["error"] = {"type": type(exc).__name__, "message": "request failed"}
290
+ row["latency_seconds"] = clock() - start
291
+ else:
292
+ row["latency_seconds"] = clock() - start
293
+ row.update(extract_usage(response))
294
+ if isinstance(response, dict) and isinstance(response.get("model"), str):
295
+ row["model"] = response["model"]
296
+ try:
297
+ row["pred"], row["probabilities"] = parse_prediction(response, task)
298
+ row["ok"] = True
299
+ except (ValueError, KeyError, TypeError) as exc:
300
+ row["error"] = {"type": type(exc).__name__, "message": "invalid API response"}
301
+ row["correct"] = bool(row["ok"] and row["pred"] == row["expected"]) if row["expected"] is not None else None
302
+ rows.append(row)
303
+ if writer is not None:
304
+ writer.write(json.dumps(row, ensure_ascii=False, allow_nan=False) + "\n")
305
+ writer.flush()
306
+ return rows
307
+
308
+
309
+ def percentile(values, fraction):
310
+ if not values:
311
+ return None
312
+ ordered = sorted(values)
313
+ rank = (len(ordered) - 1) * fraction
314
+ low, high = math.floor(rank), math.ceil(rank)
315
+ return ordered[low] + (ordered[high] - ordered[low]) * (rank - low)
316
+
317
+
318
+ def summarize(rows, config, expected_count=None):
319
+ count = len(rows)
320
+ expected_count = count if expected_count is None else expected_count
321
+ successes = sum(bool(row["ok"]) for row in rows)
322
+ complete = count > 0 and count == expected_count and successes == count
323
+ usage_complete = count > 0 and all(row["usage_complete"] for row in rows)
324
+ routing_complete = count > 0 and all(row["path"] in {"fast", "slow"} for row in rows)
325
+ new_tokens_complete = count > 0 and all(row["n_new_tokens"] is not None for row in rows)
326
+ latencies = [row["latency_seconds"] for row in rows]
327
+ p50 = statistics.median(latencies) if latencies else None
328
+ price_key = config["pricing"]["model_key"]
329
+ observed_models = sorted({row["model"] for row in rows if isinstance(row.get("model"), str) and row["model"]})
330
+ price_model_matches = count > 0 and all(config["pricing"].get("submission_models",{}).get(row.get("model"),row.get("model")) == price_key for row in rows)
331
+ prices = config["pricing"]["models"].get(price_key, {})
332
+ input_price = prices.get("input_per_million")
333
+ output_price = prices.get("output_per_million")
334
+ price_known = all(isinstance(value, (int, float)) and not isinstance(value, bool)
335
+ and math.isfinite(value) and value >= 0 for value in (input_price, output_price))
336
+ total_prompt = sum(row["prompt_tokens"] for row in rows) if usage_complete else None
337
+ total_completion = sum(row["completion_tokens"] for row in rows) if usage_complete else None
338
+ cost = (total_prompt * input_price + total_completion * output_price) / 1_000_000 if usage_complete and price_known and price_model_matches else None
339
+ cost_per_1000 = cost / count * 1000 if cost is not None else None
340
+ by_type = {}
341
+ for qtype in TYPES:
342
+ subset = [row for row in rows if row["type"] == qtype]
343
+ labeled = [row for row in subset if row["expected"] is not None]
344
+ correct = sum(row["correct"] is True for row in labeled)
345
+ by_type[qtype] = {"count": len(subset), "labeled_count": len(labeled), "correct": correct,
346
+ "accuracy": correct / len(subset) if subset and len(labeled) == len(subset) else None}
347
+ limits = config["limits"]
348
+ scale=config.get('bench',{}).get('latency_scale',1)
349
+ offset=config.get('bench',{}).get('latency_offset_seconds',0)
350
+ scaled_p50=p50*scale+offset if p50 is not None else None
351
+ margin=config.get('pricing',{}).get('margin',0)
352
+ if not isinstance(margin,(float,int)) or not 0<=margin<1: raise ValueError('pricing.margin must be in [0,1)')
353
+ cost_limit=limits['cost_per_1000_decisions_usd']*(1-margin)
354
+ eligible = None
355
+ if complete and usage_complete and routing_complete and new_tokens_complete and cost_per_1000 is not None:
356
+ eligible = scaled_p50 <= limits["median_latency_seconds"] and cost_per_1000 <= cost_limit
357
+ slow_share = sum(row["path"] == "slow" for row in rows) / count if routing_complete else None
358
+ fast_q = 0.5 / (1 - slow_share) if slow_share is not None and slow_share < 1 else None
359
+ fast_latencies = [row["latency_seconds"] for row in rows if row["path"] == "fast" and row["ok"]]
360
+ path_stats = {}
361
+ for path in ("fast", "slow"):
362
+ subset = [row for row in rows if row["path"] == path and row["ok"]]
363
+ times = [row["latency_seconds"] for row in subset]
364
+ path_stats[path] = {"count":len(subset), "p50":percentile(times, .5),
365
+ "p75":percentile(times,.75),"mean":statistics.mean(times) if times else None,
366
+ "scaled_p75":percentile(times,.75)*scale+offset if times else None,
367
+ "scaled_mean":statistics.mean(times)*scale+offset if times else None,
368
+ "p90":percentile(times, .9), "p99":percentile(times, .99),
369
+ 'average_prompt_tokens':statistics.mean(r['prompt_tokens'] for r in subset) if subset and all(r['prompt_tokens'] is not None for r in subset) else None,
370
+ 'average_completion_tokens':statistics.mean(r['completion_tokens'] for r in subset) if subset and all(r['completion_tokens'] is not None for r in subset) else None}
371
+ tail = config.get("bench", {}).get("long_tail", {"count":23, "prompt_tokens":79000, "total_decisions":1624})
372
+ tail_kept = min(tail["prompt_tokens"], limits.get("max_prompt_tokens", 16000))
373
+ tail_cost = tail["count"] * tail_kept * input_price / 1e6 if price_known else None
374
+ projection = {"count":tail["count"], "original_prompt_tokens_each":tail["prompt_tokens"],
375
+ "kept_prompt_tokens_each":tail_kept, "path":"fast", "output_tokens_each":0,
376
+ "estimated_cost_usd":tail_cost,
377
+ "untruncated_fast_cost_usd":tail["count"] * tail["prompt_tokens"] * input_price / 1e6 if price_known else None,
378
+ "assumption":"fast-only long-tail projection; final physical guard can discard protected content",
379
+ "projected_cost_per_1000_decisions_usd":
380
+ ((tail["total_decisions"]-tail["count"]) * cost/count + tail_cost) / tail["total_decisions"] * 1000
381
+ if cost is not None and count and tail_cost is not None else None}
382
+ cost_basis = {'A':cost_per_1000,'B':None,'C':None}
383
+ accounting_details = [row.get('usage',{}).get('prompt_tokens_details') for row in rows]
384
+ if cost is not None and count and all(isinstance(d,dict) and
385
+ all(type(d.get(k)) is int and d[k]>=0 for k in ('cached_tokens','engine_submitted_tokens'))
386
+ and d['cached_tokens']<=d['engine_submitted_tokens'] for d in accounting_details):
387
+ submitted=sum(d['engine_submitted_tokens'] for d in accounting_details)
388
+ cached=sum(d['cached_tokens'] for d in accounting_details)
389
+ cost_basis['B']=(submitted*input_price+total_completion*output_price)/1e6/count*1000
390
+ if all(d.get('cached_tokens_known',True) for d in accounting_details):
391
+ cost_basis['C']=((submitted-cached)*input_price+total_completion*output_price)/1e6/count*1000
392
+ for name,legacy in [('engine','C'),('each_once','A'),('submitted','B')]:
393
+ values=[r.get('usage',{}).get('alternatives',{}).get(name) for r in rows]
394
+ if cost is not None and count and all(isinstance(v,dict) for v in values):
395
+ cost_basis[legacy]=sum(v['input_tokens']*input_price+v['output_tokens']*output_price for v in values)/1e6/count*1000
396
+ cost_basis[name]=cost_basis[legacy]
397
+ tail_tiers={}
398
+ for budget in tail.get('projection_budgets',[]):
399
+ extra=tail['count']*budget*input_price/1e6 if price_known else None
400
+ tail_tiers[str(budget)]={'count':tail['count'],'prompt_tokens_each':budget,
401
+ 'fast_only_estimated_cost_usd':extra,
402
+ 'projected_cost_per_1000_by_basis':{k:((tail['total_decisions']-tail['count'])*v/1000+extra)
403
+ /tail['total_decisions']*1000 if v is not None and extra is not None else None for k,v in cost_basis.items()}}
404
+ speed_rows=[r for r in rows if r.get('benchmark_set')=='open-set' and r.get('tier') in ('standard','judge') and r.get('concurrency',1)==1]
405
+ speed={'count':len(speed_rows),'scope':'explicit open-set standard+judge serial',
406
+ 'scaled_p50':None,'scaled_p95':None,'complete':bool(speed_rows) and all(r['ok'] for r in speed_rows)}
407
+ if speed['complete']:
408
+ speed.update(scaled_p50=percentile([r['latency_seconds'] for r in speed_rows],.5)*scale+offset,
409
+ scaled_p95=percentile([r['latency_seconds'] for r in speed_rows],.95)*scale+offset)
410
+ q_latency=percentile(fast_latencies,fast_q) if fast_q is not None and fast_q<=1 else None
411
+ budget_stats={}
412
+ for tier in ('normal','hard','long'):
413
+ subset=[r for r in rows if r.get('budget_tier')==tier]
414
+ labeled=[r for r in subset if r.get('correct') is not None]
415
+ known=bool(subset) and price_known and all(r['usage_complete'] for r in subset)
416
+ budget_stats[tier]={'count':len(subset),'accuracy':sum(r['correct'] for r in labeled)/len(labeled) if labeled else None,
417
+ 'cost_usd':sum(r['prompt_tokens']*input_price+r['completion_tokens']*output_price for r in subset)/1e6 if known else None}
418
+ by_tier={}
419
+ for tier in sorted({str(r.get('tier')) for r in rows}):
420
+ subset=[r for r in rows if str(r.get('tier'))==tier];labeled=[r for r in subset if r.get('correct') is not None]
421
+ by_tier[tier]={'count':len(subset),'accuracy':sum(r['correct'] for r in labeled)/len(labeled) if labeled else None}
422
+ p_projection=project_cost(rows,config,config.get('bench',{}).get('projection_prompt_tokens'))
423
+ accuracy_only=config.get('bench',{}).get('accuracy_only',False)
424
+ if accuracy_only:eligible=None
425
+ return {"accuracy_only":accuracy_only,"by_tier":by_tier,"P_projection":p_projection,
426
+ "cost_gate":('UNKNOWN' if cost_per_1000 is None or accuracy_only else 'PASS' if cost_per_1000<=.85*cost_limit else 'WARN' if cost_per_1000<=cost_limit else 'FAIL'),
427
+ "p75_latency_seconds":percentile(latencies,.75),"mean_latency_seconds":statistics.mean(latencies) if latencies else None,
428
+ "scaled_p75_latency_seconds":percentile(latencies,.75)*scale+offset if latencies else None,
429
+ "scaled_mean_latency_seconds":statistics.mean(latencies)*scale+offset if latencies else None,
430
+ "by_budget_tier":budget_stats,"official_speed_subset":speed,"fast_required_quantile_meets_raw_limit":q_latency<=limits.get('raw_median_latency_seconds',.54) if q_latency is not None else None,
431
+ "count": count, "expected_count": expected_count, "successful_count": successes,
432
+ "failed_count": count - successes, "complete": complete,
433
+ "by_path":path_stats, "long_tail_projection":projection,
434
+ "cost_per_1000_by_basis":cost_basis, "long_tail_by_budget":tail_tiers,
435
+ "fast_required_quantile":fast_q,
436
+ "fast_required_quantile_latency_seconds":percentile(fast_latencies, fast_q) if fast_q is not None and fast_q <= 1 else None,
437
+ "fast_quantile_assumption":"all slow decisions exceed median target; successful fast samples only",
438
+ "successful_p50_latency_seconds":percentile([r["latency_seconds"] for r in rows if r["ok"]], .5),
439
+ "latency_scale":scale,"latency_offset_seconds":offset,"scaled_p50_latency_seconds":scaled_p50,
440
+ "pricing_margin":margin,"cost_limit_after_margin":cost_limit,
441
+ "cost_headroom_usd_per_1000":cost_limit-cost_per_1000 if cost_per_1000 is not None else None,
442
+ "latency_scope": "all_attempts_including_failures", "p50_latency_seconds": p50,
443
+ "p99_latency_seconds": percentile(latencies, 0.99),
444
+ "slow_count": sum(row["path"] == "slow" for row in rows),
445
+ "slow_share": sum(row["path"] == "slow" for row in rows) / count if routing_complete else None,
446
+ "usage_complete": usage_complete, "routing_complete": routing_complete,
447
+ "average_prompt_tokens": total_prompt / count if usage_complete else None,
448
+ "average_completion_tokens": total_completion / count if usage_complete else None,
449
+ "average_total_tokens": (total_prompt + total_completion) / count if usage_complete else None,
450
+ "average_new_tokens": sum(row["n_new_tokens"] for row in rows) / count if new_tokens_complete else None,
451
+ "pricing_model_key": price_key, "observed_models": observed_models,
452
+ "price_model_matches": price_model_matches, "reference_prices": prices,
453
+ "estimated_total_cost_usd": cost, "estimated_cost_per_1000_decisions_usd": cost_per_1000,
454
+ "by_type": by_type, "limits": limits, "local_reference_limits_met": eligible,
455
+ "qualification_note": "Local measurements only; evaluator hardware measurements determine admission."}
456
+
457
+
458
+ def project_cost(rows,config,P):
459
+ if P is None:return None
460
+ if isinstance(P,bool) or not isinstance(P,(int,float)) or not math.isfinite(P) or P<=0:raise ValueError('P must be positive')
461
+ prices=config['pricing']['models'][config['pricing']['model_key']]
462
+ known=all(isinstance(prices.get(k),(int,float)) and prices[k]>=0 for k in ('input_per_million','output_per_million'))
463
+ result={'P':P,'by_basis':{},'assumption':'APC: additive input overhead; no APC: proportional input; output unchanged; long tail added'}
464
+ cap=config['limits']['cost_per_1000_decisions_usd']*(1-config['pricing']['margin'])
465
+ tail=config['bench'].get('long_tail',{'count':23,'total_decisions':1624})
466
+ for basis in ('engine','each_once','submitted'):
467
+ values=[];outputs=[];valid=bool(rows) and known and all(r.get('usage_complete') for r in rows)
468
+ modes=set()
469
+ if valid:
470
+ for r in rows:
471
+ usage=r.get('usage',{}).get('alternatives',{}).get(basis);f=r.get('fast_prompt_tokens')
472
+ if not usage or not isinstance(f,(int,float)) or f<=0:valid=False;break
473
+ apc=r.get('prefix_caching')
474
+ if basis=='engine' and apc is None:valid=False;break
475
+ additive=basis=='engine' and apc is True
476
+ values.append(max(0,P+usage['input_tokens']-f) if additive else usage['input_tokens']*P/f)
477
+ outputs.append(usage['output_tokens']);modes.add('additive' if additive else 'ratio')
478
+ cost=None
479
+ if valid:
480
+ regular=(statistics.mean(values)*prices['input_per_million']+statistics.mean(outputs)*prices['output_per_million'])/1e6
481
+ long_input=config['limits']['max_prompt_tokens'];probe=1 if config['model']['backend']=='vllm' else 0
482
+ long_cost=(long_input*prices['input_per_million']+probe*prices['output_per_million'])/1e6
483
+ cost=((tail['total_decisions']-tail['count'])*regular+tail['count']*long_cost)/tail['total_decisions']*1000
484
+ result['by_basis'][basis]={'cost_per_1000':cost,'method':sorted(modes),
485
+ 'gate':'UNKNOWN' if cost is None else 'PASS' if cost<=.85*cap else 'WARN' if cost<=cap else 'FAIL'}
486
+ return result
487
+
488
+
489
+ def margin_threshold_for_share(margins,target):
490
+ if not margins or not 0<target<1 or any(not math.isfinite(v) or v<0 for v in margins):
491
+ raise ValueError('Need finite nonnegative margins and target in (0,1)')
492
+ ordered=sorted(margins); k=math.ceil(target*len(ordered)); lower=ordered[k-1]
493
+ higher=next((v for v in ordered[k:] if v>lower),None)
494
+ # JSON-safe equivalent of infinity when all remaining margins are tied.
495
+ threshold=lower+(higher-lower)/2 if higher is not None else math.nextafter(lower,math.inf)
496
+ if not math.isfinite(threshold): raise ValueError("No finite threshold can include the boundary tie")
497
+ return threshold,sum(v<threshold for v in margins)/len(margins)
498
+
499
+
500
+ def binomial_ci(successes,n):
501
+ if not n: return None
502
+ z=1.959963984540054; p=successes/n; d=1+z*z/n
503
+ center=(p+z*z/(2*n))/d
504
+ radius=z*math.sqrt(p*(1-p)/n+z*z/(4*n*n))/d
505
+ return [max(0,center-radius),min(1,center+radius)]
506
+
507
+
508
+ def margin_auroc(rows):
509
+ positive=[-r['fast_margin'] for r in rows if r.get('correct') is False]
510
+ negative=[-r['fast_margin'] for r in rows if r.get('correct') is True]
511
+ if not positive or not negative: return None
512
+ return sum((a>b)+.5*(a==b) for a in positive for b in negative)/(len(positive)*len(negative))
513
+
514
+
515
+ def calibrate_margin(rows,target,health):
516
+ if health.get('mode')!='fast_only': raise ValueError('Margin calibration requires fast_only service')
517
+ if not rows or any(r.get('request_order')!='criteria' or not r['ok'] or not r['usage_complete'] or r.get('path')!='fast'
518
+ or isinstance(r.get('fast_margin'),bool) or not isinstance(r.get('fast_margin'),(int,float))
519
+ or not math.isfinite(r['fast_margin']) for r in rows):
520
+ raise ValueError('Every calibration task must succeed with complete margin and usage')
521
+ kinds={r.get('margin_kind') for r in rows}
522
+ if len(kinds)!=1 or next(iter(kinds)) not in ('logratio','probdiff'):
523
+ raise ValueError('Inconsistent margin kind')
524
+ threshold,achieved=margin_threshold_for_share([r['fast_margin'] for r in rows],target)
525
+ def stats(subset):
526
+ n=len(subset); selected=sum(r['fast_margin']<threshold for r in subset)
527
+ result={'n':n,'selected':selected,'share':selected/n if n else None,
528
+ 'binomial_ci95':binomial_ci(selected,n),'auroc_fast_error':margin_auroc(subset)}
529
+ for side in ('below','above'):
530
+ labeled=[r for r in subset if r.get('correct') is not None and (r['fast_margin']<threshold)==(side=='below')]
531
+ result[side+'_fast_accuracy']=sum(r['correct'] for r in labeled)/len(labeled) if labeled else None
532
+ return result
533
+ identity=health.get('threshold_identity',{})
534
+ if any(k not in identity for k in ('model_key','quant','adapter','prompt_sha','backend','margin_kind','max_new_tokens','max_prompt_tokens','request_contract')):
535
+ raise ValueError('Health lacks threshold identity')
536
+ return dict(identity,request_contract='typesafe.criteria.v1',margin_kind=next(iter(kinds)),target=target,threshold=threshold,
537
+ achieved=achieved,n=len(rows),placeholder=False,overall=stats(rows),
538
+ by_type={k:stats([r for r in rows if r['type']==k]) for k in sorted({r['type'] for r in rows})},
539
+ by_tier={str(k):stats([r for r in rows if r['tier']==k]) for k in sorted({r['tier'] for r in rows},key=str)})
540
+
541
+
542
+ def compare_order_accuracy(criteria_rows,labels_rows):
543
+ left={r['id']:r for r in criteria_rows};right={r['id']:r for r in labels_rows}
544
+ ids=[i for i in left if i in right and left[i]['type']=='choice' and left[i].get('expected') is not None]
545
+ if not ids or any(not left[i]['ok'] or not right[i]['ok'] for i in ids):
546
+ return {'count':len(ids),'criteria_accuracy':None,'labels_accuracy':None,'labels_minus_criteria':None}
547
+ a=sum(left[i]['correct'] for i in ids)/len(ids);b=sum(right[i]['correct'] for i in ids)/len(ids)
548
+ return {'count':len(ids),'criteria_accuracy':a,'labels_accuracy':b,'labels_minus_criteria':b-a}
549
+
550
+
551
+ def calibrate_temperature(rows):
552
+ # Only replay raw scores from the official request order; never calibrate
553
+ # already floored/temperature-scaled probabilities.
554
+ if not rows or any(not r['ok'] or r.get('request_order')!='criteria' or not isinstance(r.get('raw_label_scores'),dict) or r.get('expected') is None for r in rows):
555
+ raise ValueError('Temperature fit needs successful labeled criteria-order raw scores')
556
+ result={}
557
+ grid=[2**(i/8) for i in range(-32,41)]
558
+ for kind in TYPES:
559
+ subset=[r for r in rows if r['type']==kind]
560
+ if not subset: result[kind]={'count':0,'fitted':False};continue
561
+ if kind=='noul':
562
+ correct=sum(max(r['raw_label_scores'],key=r['raw_label_scores'].get)==r['expected'] for r in subset)
563
+ result[kind]={'count':len(subset),'p_cal':max(.5,min(1-1e-6,correct/len(subset))),'fitted':True};continue
564
+ def loss(t):
565
+ total=0
566
+ for r in subset:
567
+ scores=r['raw_label_scores'];top=max(scores.values())
568
+ total+=math.log(sum(math.exp((v-top)/t) for v in scores.values()))-(scores[r['expected']]-top)/t
569
+ return total/len(subset)
570
+ t=min(grid,key=loss);result[kind]={'count':len(subset),'temperature':t,'nll':loss(t),'fitted':True}
571
+ return {'request_contract':'typesafe.criteria.v1','by_type':result,'status':'candidate; held-out validation required'}
572
+
573
+
574
+ def fit_calibration(fast_rows, routed_rows, model_key):
575
+ import copy
576
+ if not fast_rows or not routed_rows:raise ValueError('Both fast and routed rows are required')
577
+ for rows in (fast_rows,routed_rows):
578
+ if len({r.get('id') for r in rows})!=len(rows):raise ValueError('Duplicate calibration IDs')
579
+ for r in rows:
580
+ identity=r.get('threshold_identity') or {}
581
+ if identity.get('model_key')!=model_key:raise ValueError('Calibration model identity mismatch')
582
+ if not r.get('ok') or r.get('request_order')!='criteria' or r.get('expected') is None:
583
+ raise ValueError('Calibration requires successful labeled criteria-order rows')
584
+ keys=('model_key','quant','adapter','backend','prompt_sha','margin_kind','max_prompt_tokens','request_contract')
585
+ identity=fast_rows[0]['threshold_identity']
586
+ if any(any(r['threshold_identity'].get(k)!=identity.get(k) for k in keys) for r in fast_rows+routed_rows):
587
+ raise ValueError('Mixed calibration identities')
588
+ if any(r.get('path')!='fast' or r.get('calibration_block') not in ('fast',None) for r in fast_rows):
589
+ raise ValueError('fast-run must contain only fast decisions')
590
+ def prepared(rows,field):
591
+ result=[]
592
+ for r in rows:
593
+ scores=r.get(field)
594
+ if scores is None and field=='fast_scores':scores=r.get('raw_label_scores')
595
+ if not isinstance(scores,dict) or r['expected'] not in scores or any(isinstance(v,bool) or not isinstance(v,(int,float)) or not math.isfinite(v) for v in scores.values()):
596
+ raise ValueError('Missing or invalid pre-temperature label scores')
597
+ result.append(dict(r,raw_label_scores=scores))
598
+ return result
599
+ sources={'fast':prepared(fast_rows,'fast_scores'),
600
+ 'slow':prepared([r for r in routed_rows if r.get('calibration_block')=='slow'],'slow_scores'),
601
+ 'fallback':prepared([r for r in routed_rows if r.get('calibration_block')=='fallback'],'fast_scores')}
602
+ blocks={}
603
+ for name,rows in sources.items():
604
+ fit=calibrate_temperature(rows)['by_type'] if rows else {}
605
+ block={'placeholder':False,'status':'candidate; held-out validation required',
606
+ 'temperature_by_kind':{'choice':1.,'score':1.,'noul':'one_bin'},'noul_calibration':{'p_cal':.5},'by_type':{}}
607
+ for kind in TYPES:
608
+ item=fit.get(kind,{'count':0,'fitted':False});n=item['count']
609
+ meta=dict(item,status='insufficient' if n<20 else 'candidate')
610
+ if name=='fallback' and n<20:
611
+ source=blocks['fast'];meta['inherited_from']='fast'
612
+ if kind=='noul':block['noul_calibration']=copy.deepcopy(source['noul_calibration'])
613
+ else:block['temperature_by_kind'][kind]=source['temperature_by_kind'][kind]
614
+ elif item.get('fitted'):
615
+ if kind=='noul':block['noul_calibration']['p_cal']=item['p_cal']
616
+ else:block['temperature_by_kind'][kind]=item['temperature']
617
+ if n<20:block['placeholder']=True
618
+ block['by_type'][kind]=meta
619
+ blocks[name]=block
620
+ return {'status':'candidate; no production overwrite; validate held-out data',
621
+ 'models':{model_key:dict(blocks,placeholder=any(b['placeholder'] for b in blocks.values()),
622
+ fitted_for=identity)}}
623
+
624
+
625
+ def read_run_rows(prefix):
626
+ if __package__:from .core import external_path
627
+ else:from core import external_path
628
+ path=external_path(prefix)
629
+ if path.suffix!='.jsonl':path=path.with_name(path.name+'.jsonl')
630
+ return [json.loads(line) for line in path.read_text(encoding='utf-8-sig').splitlines() if line.strip()]
631
+
632
+
633
+ def write_candidate(path,value):
634
+ if __package__:from .core import external_output
635
+ else:from core import external_output
636
+ target=external_output(path)
637
+ with target.open('x',encoding='utf-8') as out:json.dump(value,out,ensure_ascii=False,indent=2,allow_nan=False)
638
+ return target
639
+
640
+
641
+ def apply_margin_candidate(config, report, tier=None):
642
+ import copy
643
+ if __package__:from .core import apply_profile,threshold_identity,apply_budget_tier
644
+ else:from core import apply_profile,threshold_identity,apply_budget_tier
645
+ result=copy.deepcopy(config);key=report.get('model_key')
646
+ result['model']['model_key']=key;apply_profile(result);apply_budget_tier(result,tier)
647
+ identity=threshold_identity(result)
648
+ keys=('model_key','quant','adapter','backend','prompt_sha','margin_kind','max_prompt_tokens','request_contract')
649
+ if report.get('placeholder',True) or any(report.get(k)!=identity[k] for k in keys):raise ValueError('Margin candidate identity mismatch')
650
+ value=report['threshold']
651
+ if not isinstance(value,(int,float)) or not math.isfinite(value) or value<0:raise ValueError('Invalid threshold')
652
+ fit={k:report[k] for k in keys};fit.update(placeholder=False,max_new_tokens=identity['max_new_tokens'],reasoning_words=identity['reasoning_words'])
653
+ target=result['profiles'][key]['routing']
654
+ target.update(margin_threshold=value,margin_threshold_fitted_for=fit)
655
+ result['routing'].update(margin_threshold=value,margin_threshold_fitted_for=fit)
656
+ return result
657
+
658
+
659
+ def get_health(endpoint,timeout,path="/health"):
660
+ parts=urllib.parse.urlsplit(endpoint)
661
+ url=urllib.parse.urlunsplit((parts.scheme,parts.netloc,path,'',''))
662
+ with _OPENER.open(url,timeout=timeout) as response:
663
+ return json.load(response)
664
+
665
+
666
+ def output_paths(prefix, calibration_only=False):
667
+ if __package__:from .core import external_output
668
+ else:from core import external_output
669
+ candidate = external_output(prefix)
670
+ if calibration_only:
671
+ target=candidate.with_name(candidate.name+'.margin_calibration.json')
672
+ if target.exists(): raise FileExistsError('Calibration output exists')
673
+ return target
674
+ rows_path = candidate.with_name(candidate.name + ".jsonl")
675
+ summary_path = candidate.with_name(candidate.name + ".summary.json")
676
+ if rows_path.exists() or summary_path.exists():
677
+ raise FileExistsError("output already exists; choose a new prefix")
678
+ return rows_path, summary_path
679
+
680
+
681
+ def main(argv=None):
682
+ parser = argparse.ArgumentParser(description=__doc__)
683
+ parser.add_argument("--config", type=Path, default=ROOT / "config.json")
684
+ source = parser.add_mutually_exclusive_group(required=False)
685
+ source.add_argument("--public", type=Path)
686
+ source.add_argument("--data", type=Path)
687
+ parser.add_argument("--index", type=Path)
688
+ parser.add_argument("--endpoint", help="Runtime HTTP endpoint; never written to output")
689
+ parser.add_argument("--output-prefix", help="Absolute path prefix outside the package (required for runs)")
690
+ parser.add_argument("--price-model", help="Key in config.pricing.models")
691
+ parser.add_argument('--original-types',action='store_true')
692
+ parser.add_argument('--concurrency',type=int,nargs='+')
693
+ parser.add_argument('--calibrate-margin',action='store_true')
694
+ parser.add_argument('--engine-stats',action='store_true')
695
+ parser.add_argument('--compare-label-order',action='store_true')
696
+ parser.add_argument('--target-slow-share',type=float)
697
+ parser.add_argument('--P',type=float)
698
+ parser.add_argument('--fit-calibration',action='store_true')
699
+ parser.add_argument('--fast-run',type=Path)
700
+ parser.add_argument('--routed-run',type=Path)
701
+ parser.add_argument('--output',type=Path)
702
+ parser.add_argument('--model-key')
703
+ parser.add_argument('--apply-margin',type=Path)
704
+ parser.add_argument('--tier',choices=('normal','hard','long'))
705
+ args = parser.parse_args(argv)
706
+ config_path = args.config.expanduser().resolve()
707
+ config = json.loads(config_path.read_text(encoding="utf-8-sig"))
708
+ if args.fit_calibration:
709
+ if not all((args.fast_run,args.routed_run,args.output,args.model_key)):parser.error('Fit needs fast-run, routed-run, output and model-key')
710
+ write_candidate(args.output,fit_calibration(read_run_rows(args.fast_run),read_run_rows(args.routed_run),args.model_key))
711
+ return 0
712
+ if args.apply_margin:
713
+ if not args.output:parser.error('Apply margin needs a new output config')
714
+ write_candidate(args.output,apply_margin_candidate(config,json.loads(args.apply_margin.read_text(encoding='utf-8')),args.tier))
715
+ return 0
716
+ if not args.endpoint or not (args.public or args.data):parser.error('Benchmark needs endpoint and public or data')
717
+ if not args.output_prefix:parser.error('Benchmark needs --output-prefix (absolute, outside the package)')
718
+ if args.P is not None:config['bench']['projection_prompt_tokens']=args.P
719
+ if args.original_types: config['bench']['original_types']=True
720
+ if args.price_model:
721
+ config["pricing"]["model_key"] = args.price_model
722
+ if args.public:
723
+ index = args.index or Path(config["bench"]["index"])
724
+ if not index.is_absolute():
725
+ index = config_path.parent / index
726
+ tasks = load_public_tasks(index, args.public, config["bench"]["expected_public_count"])
727
+ else:
728
+ if args.index:
729
+ parser.error("--index requires --public")
730
+ tasks = load_jsonl_tasks(args.data)
731
+ levels=args.concurrency or config['bench']['concurrency']
732
+ if any(n<1 for n in levels): parser.error('Concurrency must be positive')
733
+ if args.calibrate_margin and config['bench'].get('original_types'):parser.error('Calibration requires the official request types')
734
+ if args.calibrate_margin and levels!=[1]: parser.error('Margin calibration is serial')
735
+ if args.calibrate_margin and args.engine_stats:parser.error('Use a separate serial run for engine reconciliation')
736
+ endpoint = endpoint_url(args.endpoint)
737
+ paths = output_paths(args.output_prefix,calibration_only=args.calibrate_margin)
738
+ health=get_health(endpoint,config['bench']['timeout_seconds'])
739
+ if __package__: from .core import apply_profile
740
+ else: from core import apply_profile
741
+ config['model']['model_key']=health['model_key']
742
+ apply_profile(config)
743
+ if args.tier:
744
+ if __package__:from .core import apply_budget_tier
745
+ else:from core import apply_budget_tier
746
+ apply_budget_tier(config,args.tier)
747
+ if args.price_model: config['pricing']['model_key']=args.price_model
748
+ if args.calibrate_margin:
749
+ if health.get('mode')!='fast_only': parser.error('Requires fast_only service')
750
+ target=args.target_slow_share if args.target_slow_share is not None else config['bench']['target_slow_share']
751
+ calibration_path=paths
752
+ # Existing-file check precedes inference; exclusive creation prevents races.
753
+ rows=run_benchmark(tasks,endpoint,config)
754
+ report=calibrate_margin(rows,target,health)
755
+ report['temperature_fit']=calibrate_temperature(rows)
756
+ if args.compare_label_order:
757
+ import copy
758
+ other=copy.deepcopy(config);other['bench']['label_order']=True
759
+ report['order_accuracy']=compare_order_accuracy(rows,run_benchmark(tasks,endpoint,other))
760
+ with calibration_path.open('x',encoding='utf-8') as output:
761
+ json.dump(report,output,ensure_ascii=False,indent=2,allow_nan=False)
762
+ print(json.dumps(report,ensure_ascii=False,indent=2,allow_nan=False))
763
+ return 0
764
+ stats_path=paths[0].with_name(paths[0].stem+'.engine_stats.json')
765
+ if args.engine_stats and stats_path.exists():raise FileExistsError('Engine snapshot exists')
766
+ # Claim both paths before requests, using exclusive creation for preservation.
767
+ with paths[0].open("x", encoding="utf-8") as writer, paths[1].open("x", encoding="utf-8") as summary_writer:
768
+ summaries={}
769
+ snapshots={"health":health,"by_concurrency":{}}
770
+ stats_path=paths[0].with_name(paths[0].stem+".engine_stats.json")
771
+ if args.engine_stats and stats_path.exists(): raise FileExistsError("Engine snapshot exists")
772
+ for n in dict.fromkeys(levels):
773
+ if args.engine_stats: before=get_health(endpoint,config['bench']['timeout_seconds'],'/engine_stats')
774
+ rows=run_benchmark(tasks,endpoint,config,concurrency=n)
775
+ if args.engine_stats: snapshots['by_concurrency'][str(n)]={'before':before,'after':get_health(endpoint,config['bench']['timeout_seconds'],'/engine_stats')}
776
+ for row in rows:
777
+ row['concurrency']=n
778
+ row['prefix_caching']=health.get('prefix_caching')
779
+ writer.write(json.dumps(row,ensure_ascii=False,allow_nan=False)+'\n')
780
+ writer.flush()
781
+ summaries[str(n)]=summarize(rows,config,expected_count=len(tasks))
782
+ if args.compare_label_order:
783
+ import copy
784
+ other=copy.deepcopy(config);other['bench']['label_order']=True
785
+ summaries[str(n)]['order_accuracy']=compare_order_accuracy(rows,run_benchmark(tasks,endpoint,other,concurrency=n))
786
+ if args.engine_stats:
787
+ with stats_path.open('x',encoding='utf-8') as output: json.dump(snapshots,output,indent=2,allow_nan=False)
788
+ summary=dict(summaries[str(levels[0])],by_concurrency=summaries)
789
+ summary_writer.write(json.dumps(summary, ensure_ascii=False, indent=2, allow_nan=False) + "\n")
790
+ print(json.dumps(summary, ensure_ascii=False, indent=2, allow_nan=False))
791
+ return 0 if all(v["complete"] for v in summary["by_concurrency"].values()) else 1
792
+
793
+
794
+ if __name__ == "__main__":
795
+ raise SystemExit(main())
service/calibration.json ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "models": {
3
+ "metask-gemma4-31b-lora-w4a16": {
4
+ "fallback": {
5
+ "by_type": {
6
+ "choice": {
7
+ "count": 139,
8
+ "fitted": true,
9
+ "inherited": true,
10
+ "inherited_from": "fast",
11
+ "nll": 0.268809886282054,
12
+ "temperature": 1.2968395546510096
13
+ },
14
+ "noul": {
15
+ "constant_source": "service parameter (fit_report.json noul_constant)",
16
+ "count": 74,
17
+ "fitted": true,
18
+ "fitted_p_cal": 0.9459459459459459,
19
+ "inherited": true,
20
+ "inherited_from": "fast",
21
+ "p_cal": 0.898
22
+ },
23
+ "score": {
24
+ "count": 18,
25
+ "fitted": true,
26
+ "inherited": true,
27
+ "inherited_from": "fast",
28
+ "nll": 0.26993267192031306,
29
+ "temperature": 1.189207115002721
30
+ }
31
+ },
32
+ "noul_calibration": {
33
+ "p_cal": 0.898
34
+ },
35
+ "placeholder": false,
36
+ "status": "temperatures: public231 in-sample fast fit (unchanged); yes/no constant: service parameter, see fit_report.json noul_constant",
37
+ "temperature_by_kind": {
38
+ "choice": 1.2968395546510096,
39
+ "noul": "one_bin",
40
+ "score": 1.189207115002721
41
+ }
42
+ },
43
+ "fast": {
44
+ "by_type": {
45
+ "choice": {
46
+ "count": 139,
47
+ "fitted": true,
48
+ "nll": 0.268809886282054,
49
+ "temperature": 1.2968395546510096
50
+ },
51
+ "noul": {
52
+ "constant_source": "service parameter (fit_report.json noul_constant)",
53
+ "count": 74,
54
+ "fitted": true,
55
+ "fitted_p_cal": 0.9459459459459459,
56
+ "p_cal": 0.898
57
+ },
58
+ "score": {
59
+ "count": 18,
60
+ "fitted": true,
61
+ "nll": 0.26993267192031306,
62
+ "temperature": 1.189207115002721
63
+ }
64
+ },
65
+ "noul_calibration": {
66
+ "p_cal": 0.898
67
+ },
68
+ "placeholder": false,
69
+ "status": "temperatures: public231 in-sample fast fit (unchanged); yes/no constant: service parameter, see fit_report.json noul_constant",
70
+ "temperature_by_kind": {
71
+ "choice": 1.2968395546510096,
72
+ "noul": "one_bin",
73
+ "score": 1.189207115002721
74
+ }
75
+ },
76
+ "fitted_for": {
77
+ "adapter": "model-subdir:adapter",
78
+ "backend": "vllm",
79
+ "margin_kind": "logratio",
80
+ "max_new_tokens": 256,
81
+ "max_prompt_tokens": 6653,
82
+ "model_key": "metask-gemma4-31b-lora-w4a16",
83
+ "prompt_sha": "e18ed7a06eb52f1b351458f384710a2c240aaf2b4f64d916b776c6d18ca3f938",
84
+ "quant": "awq",
85
+ "reasoning_words": 160,
86
+ "request_contract": "typesafe.criteria.v1"
87
+ },
88
+ "placeholder": false,
89
+ "slow": {
90
+ "by_type": {
91
+ "choice": {
92
+ "applied_rule": "slow samples of a type < 30: the fast parameter of that type (temperature or yes/no confidence) is copied unchanged",
93
+ "count": 0,
94
+ "fast_count": 139,
95
+ "fitted": false,
96
+ "inherited": true,
97
+ "inherited_from": "fast",
98
+ "status": "inherited"
99
+ },
100
+ "noul": {
101
+ "applied_rule": "slow samples of a type < 30: the fast parameter of that type (temperature or yes/no confidence) is copied unchanged",
102
+ "constant_source": "service parameter (fit_report.json noul_constant)",
103
+ "count": 0,
104
+ "fast_count": 74,
105
+ "fitted": false,
106
+ "fitted_p_cal": 0.9459459459459459,
107
+ "inherited": true,
108
+ "inherited_from": "fast",
109
+ "p_cal": 0.898,
110
+ "status": "inherited"
111
+ },
112
+ "score": {
113
+ "applied_rule": "slow samples of a type < 30: the fast parameter of that type (temperature or yes/no confidence) is copied unchanged",
114
+ "count": 0,
115
+ "fast_count": 18,
116
+ "fitted": false,
117
+ "inherited": true,
118
+ "inherited_from": "fast",
119
+ "status": "inherited"
120
+ }
121
+ },
122
+ "held_out_validated": false,
123
+ "noul_calibration": {
124
+ "p_cal": 0.898
125
+ },
126
+ "placeholder": false,
127
+ "status": "temperatures: public231 in-sample; slow types with fewer than 30 slow samples inherit fast (unchanged); yes/no constant: service parameter, see fit_report.json noul_constant",
128
+ "temperature_by_kind": {
129
+ "choice": 1.2968395546510096,
130
+ "noul": "one_bin",
131
+ "score": 1.189207115002721
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
service/config.json ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bench": {
3
+ "concurrency": [
4
+ 1
5
+ ],
6
+ "expected_public_count": 231,
7
+ "latency_offset_seconds": 0.15,
8
+ "latency_scale": 2.0,
9
+ "long_tail": {
10
+ "count": 23,
11
+ "projection_budgets": [
12
+ 8192
13
+ ],
14
+ "prompt_tokens": 79000,
15
+ "total_decisions": 1624
16
+ },
17
+ "original_types": false,
18
+ "output_prefix": "31b-exploration",
19
+ "target_slow_share": 0.49,
20
+ "timeout_seconds": 120
21
+ },
22
+ "calibration_file": "calibration.json",
23
+ "generation": {
24
+ "bad_words_ids": null,
25
+ "begin_suppress_tokens": null,
26
+ "do_sample": false,
27
+ "forced_bos_token_id": null,
28
+ "forced_decoder_ids": null,
29
+ "forced_eos_token_id": null,
30
+ "length_penalty": 1.0,
31
+ "max_new_tokens": 192,
32
+ "min_length": 0,
33
+ "min_new_tokens": 0,
34
+ "no_repeat_ngram_size": 0,
35
+ "reasoning_words": 50,
36
+ "renormalize_logits": false,
37
+ "repetition_penalty": 1.0,
38
+ "seed": 0,
39
+ "suppress_tokens": null,
40
+ "temperature": 1.0,
41
+ "top_k": 0,
42
+ "top_p": 1.0,
43
+ "wall_timeout_seconds": 60
44
+ },
45
+ "generation_reference": {
46
+ "intended_overrides": {
47
+ "do_sample": {
48
+ "reason": "Frozen greedy decoding; checkpoint sampling policy is intentionally overridden.",
49
+ "value": false
50
+ },
51
+ "suppress_tokens": {
52
+ "reason": "All reported measurements were taken without checkpoint token suppression; drafts only, fast-path candidate scoring unaffected.",
53
+ "value": null
54
+ },
55
+ "temperature": {
56
+ "reason": "Frozen greedy decoding; checkpoint sampling policy is intentionally overridden.",
57
+ "value": 1.0
58
+ },
59
+ "top_k": {
60
+ "reason": "Frozen greedy decoding; checkpoint sampling policy is intentionally overridden.",
61
+ "value": 0
62
+ },
63
+ "top_p": {
64
+ "reason": "Frozen greedy decoding; checkpoint sampling policy is intentionally overridden.",
65
+ "value": 1.0
66
+ }
67
+ }
68
+ },
69
+ "interface": {
70
+ "boolean_keys": "request"
71
+ },
72
+ "item_deadline": {
73
+ "chunk_tokens": 32,
74
+ "ewma_alpha": 0.3,
75
+ "item_seconds": 9.0,
76
+ "min_chunk_tokens": 8,
77
+ "readout_reserve_init_s": 1.0,
78
+ "readout_reserve_min_s": 0.3,
79
+ "readout_safety": 1.5,
80
+ "response_reserve_s": 0.35,
81
+ "token_safety": 1.25,
82
+ "warmup_repeats": 3,
83
+ "warmup_tokens": 33
84
+ },
85
+ "item_rules": {
86
+ "reread_tau": 4.0,
87
+ "slow_types": [
88
+ "choice",
89
+ "noul",
90
+ "score"
91
+ ]
92
+ },
93
+ "limits": {
94
+ "compression_reserve_tokens": 32,
95
+ "cost_per_1000_decisions_usd": 0.0646,
96
+ "estimated_chars_per_token": 4,
97
+ "max_prompt_tokens": 6653,
98
+ "median_latency_seconds": 1.0,
99
+ "oom_retry_prompt_tokens": 4096,
100
+ "physical_max_prompt_tokens": 6653,
101
+ "precompression_trigger_chars_per_token": 8,
102
+ "raw_median_latency_seconds": 0.425,
103
+ "state_chunk_tokens": 200,
104
+ "state_head_tokens": 512,
105
+ "state_tail_tokens": 512,
106
+ "truncation_head_frac": 0.6
107
+ },
108
+ "model": {
109
+ "adapter": "model-subdir:adapter",
110
+ "attn_implementation": "sdpa",
111
+ "backend": "vllm",
112
+ "device_map": {
113
+ "": 0
114
+ },
115
+ "enable_prefix_caching": true,
116
+ "model_key": "metask-gemma4-31b-lora-w4a16",
117
+ "name": "MetaDecision-31B-Rain",
118
+ "path": "",
119
+ "quant": "awq",
120
+ "version": "MetaDecision-31B-Rain",
121
+ "vllm_expected_version": "0.31.0",
122
+ "vllm_gpu_memory_utilization": 0.94,
123
+ "vllm_max_model_len": 6656,
124
+ "vllm_options": {
125
+ "gpu_memory_utilization": 0.94,
126
+ "max_lora_rank": 16,
127
+ "max_model_len": 6656,
128
+ "max_num_seqs": 1
129
+ }
130
+ },
131
+ "package": {
132
+ "finalized": true,
133
+ "nonformal_fitted": true,
134
+ "sample_only": false,
135
+ "version": "MetaDecision-31B-Rain"
136
+ },
137
+ "pipeline": {
138
+ "arm": "trained",
139
+ "model_identity_sha256": "0513d121db967ec1a6aaccf627d3c42149d092dd027298da5b53684b030821ea",
140
+ "protocol": "adapter_arm_31b_v3"
141
+ },
142
+ "post_calibration": {
143
+ "choice": {
144
+ "config": {
145
+ "debias": false,
146
+ "temperature": false
147
+ },
148
+ "params": {}
149
+ },
150
+ "noul": {
151
+ "config": {
152
+ "floor": null,
153
+ "temperature": "type"
154
+ },
155
+ "params": {
156
+ "T": 0.5
157
+ }
158
+ },
159
+ "score": {
160
+ "config": {
161
+ "mode_weight": 1.0,
162
+ "temperature": false
163
+ },
164
+ "params": {}
165
+ }
166
+ },
167
+ "pricing": {
168
+ "margin": 0.0,
169
+ "model_key": "31B",
170
+ "models": {
171
+ "31B": {
172
+ "input_per_million": 0.059819178473256074,
173
+ "output_per_million": 0.059819178473256074
174
+ }
175
+ },
176
+ "submission_models": {
177
+ "MetaDecision-31B-Rain": "31B",
178
+ "metask-gemma4-31b-lora-w4a16": "31B",
179
+ "metask-jev-rain-31B-trained": "31B"
180
+ }
181
+ },
182
+ "profiles": {
183
+ "metask-gemma4-31b-lora-w4a16": {
184
+ "bench": {
185
+ "target_slow_share": 0.49
186
+ },
187
+ "generation": {
188
+ "bad_words_ids": null,
189
+ "begin_suppress_tokens": null,
190
+ "do_sample": false,
191
+ "forced_bos_token_id": null,
192
+ "forced_decoder_ids": null,
193
+ "forced_eos_token_id": null,
194
+ "length_penalty": 1.0,
195
+ "max_new_tokens": 192,
196
+ "min_length": 0,
197
+ "min_new_tokens": 0,
198
+ "no_repeat_ngram_size": 0,
199
+ "reasoning_words": 50,
200
+ "renormalize_logits": false,
201
+ "repetition_penalty": 1.0,
202
+ "seed": 0,
203
+ "suppress_tokens": null,
204
+ "temperature": 1.0,
205
+ "top_k": 0,
206
+ "top_p": 1.0,
207
+ "wall_timeout_seconds": 60
208
+ },
209
+ "limits": {
210
+ "max_prompt_tokens": 6653,
211
+ "physical_max_prompt_tokens": 6653
212
+ },
213
+ "model": {
214
+ "backend": "vllm",
215
+ "name": "MetaDecision-31B-Rain",
216
+ "quant": "awq"
217
+ },
218
+ "pricing": {
219
+ "model_key": "31B"
220
+ },
221
+ "routing": {
222
+ "cost_fuse": {
223
+ "arm": 0.9,
224
+ "enabled": true,
225
+ "release": 0.85,
226
+ "warmup": 100
227
+ },
228
+ "fuse": 0.49,
229
+ "long_item_slow_policy": "fast_only",
230
+ "margin_kind": "logratio",
231
+ "margin_threshold": 6.625023671062799,
232
+ "margin_threshold_fitted_for": {
233
+ "achieved_share": 0.5207547169811321,
234
+ "adapter": "model-subdir:adapter",
235
+ "backend": "vllm",
236
+ "fitted_on": "our own independently built development set (fit split): fast-path margins of the earlier service routing, eligible_items rule (the lowest-margin share of the decisions that can take the drafting pass)",
237
+ "margin_kind": "logratio",
238
+ "max_new_tokens": 192,
239
+ "max_prompt_tokens": 6653,
240
+ "model_key": "metask-gemma4-31b-lora-w4a16",
241
+ "n": 530,
242
+ "placeholder": false,
243
+ "prompt_sha": "e18ed7a06eb52f1b351458f384710a2c240aaf2b4f64d916b776c6d18ca3f938",
244
+ "quant": "awq",
245
+ "reasoning_words": 50,
246
+ "request_contract": "typesafe.criteria.v1"
247
+ },
248
+ "mode": "routed",
249
+ "quota": 0.49,
250
+ "slow_max_prompt_tokens": 840,
251
+ "target_slow_share": 0.49,
252
+ "tier": "normal",
253
+ "tiers": {
254
+ "hard": {
255
+ "fast_only": false,
256
+ "max_new_tokens": 192,
257
+ "reasoning_words": 50
258
+ },
259
+ "long": {
260
+ "fast_only": true,
261
+ "max_prompt_tokens": 6653
262
+ },
263
+ "normal": {
264
+ "fast_only": false,
265
+ "max_prompt_tokens": 6653
266
+ }
267
+ },
268
+ "unparsed_policy": "force_answer"
269
+ }
270
+ }
271
+ },
272
+ "prompt": {
273
+ "answer_marker": "ANSWER:",
274
+ "cot_instructions": {
275
+ "choice": "First reason step by step in at most {reasoning_words} words, tracking any intermediate state explicitly. Then output a final line `ANSWER: <letter>`.",
276
+ "noul": "First reason step by step in at most {reasoning_words} words, tracking any intermediate state explicitly. Then output a final line of the form `ANSWER: yes` or `ANSWER: no`.",
277
+ "score": "First reason step by step in at most {reasoning_words} words, tracking any intermediate state explicitly. Then output a final line `ANSWER: <number>`."
278
+ },
279
+ "enable_thinking": false,
280
+ "system": "You are a careful, decisive judge. Read the context, apply every rule, definition, threshold, exception and date it contains, and answer the question. Reply with exactly one word and nothing else."
281
+ },
282
+ "routing": {
283
+ "cost_fuse": {
284
+ "arm": 0.9,
285
+ "enabled": true,
286
+ "release": 0.85,
287
+ "warmup": 100
288
+ },
289
+ "fuse": 0.49,
290
+ "long_item_slow_policy": "fast_only",
291
+ "margin_kind": "logratio",
292
+ "margin_threshold": 6.625023671062799,
293
+ "margin_threshold_fitted_for": {
294
+ "achieved_share": 0.5207547169811321,
295
+ "adapter": "model-subdir:adapter",
296
+ "backend": "vllm",
297
+ "fitted_on": "our own independently built development set (fit split): fast-path margins of the earlier service routing, eligible_items rule (the lowest-margin share of the decisions that can take the drafting pass)",
298
+ "margin_kind": "logratio",
299
+ "max_new_tokens": 192,
300
+ "max_prompt_tokens": 6653,
301
+ "model_key": "metask-gemma4-31b-lora-w4a16",
302
+ "n": 530,
303
+ "placeholder": false,
304
+ "prompt_sha": "e18ed7a06eb52f1b351458f384710a2c240aaf2b4f64d916b776c6d18ca3f938",
305
+ "quant": "awq",
306
+ "reasoning_words": 50,
307
+ "request_contract": "typesafe.criteria.v1"
308
+ },
309
+ "mode": "routed",
310
+ "quota": 0.49,
311
+ "resume": false,
312
+ "run_id": null,
313
+ "slow_max_prompt_tokens": 840,
314
+ "state_dir": null,
315
+ "target_slow_share": 0.49,
316
+ "tier": "normal",
317
+ "tiers": {
318
+ "hard": {
319
+ "fast_only": false,
320
+ "max_new_tokens": 192,
321
+ "reasoning_words": 50
322
+ },
323
+ "long": {
324
+ "fast_only": true,
325
+ "max_prompt_tokens": 6653
326
+ },
327
+ "normal": {
328
+ "fast_only": false,
329
+ "max_prompt_tokens": 6653
330
+ }
331
+ },
332
+ "unparsed_policy": "force_answer"
333
+ },
334
+ "server": {
335
+ "bind": "127.0.0.1",
336
+ "max_questions": 64,
337
+ "max_request_bytes": 16777216,
338
+ "port": 8000,
339
+ "socket_timeout_seconds": 120,
340
+ "warmup": true,
341
+ "warmup_slow_tokens": 2
342
+ },
343
+ "usage": {
344
+ "basis": "engine"
345
+ }
346
+ }
service/core.py ADDED
@@ -0,0 +1,1197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Rendering, candidate aggregation and service logic, without model imports."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import math
6
+ import re
7
+ import string
8
+ from pathlib import Path
9
+
10
+ import threading
11
+ if __package__:
12
+ from .backends import new_meter, check_deadline
13
+ from .quota import QuotaStateError
14
+ else:
15
+ from backends import new_meter, check_deadline
16
+ from quota import QuotaStateError
17
+
18
+ ROOT = Path(__file__).resolve().parent
19
+ YES_FORMS = ["yes", "Yes", "YES", " yes", " Yes", " YES", "true", "True", " true", " True", "Y", " Y"]
20
+ NO_FORMS = ["no", "No", "NO", " no", " No", " NO", "false", "False", " false", " False", "N", " N"]
21
+ ALIASES = {"boolean": "noul", "enum": "choice", "noul": "noul", "choice": "choice", "score": "score"}
22
+
23
+
24
+ class ItemFailure(RuntimeError):
25
+ """Inference for one item failed; the service answers HTTP 422 and keeps running."""
26
+
27
+
28
+ class ServiceFailure(RuntimeError):
29
+ """Service-level failure; the service answers HTTP 503."""
30
+
31
+
32
+ # The third consecutive item failure is treated as service-level (HTTP 503, service
33
+ # marked failed), so the harness stop rule for consecutive infrastructure errors applies.
34
+ ITEM_FAILURE_LIMIT = 3
35
+
36
+
37
+ def unrecoverable_engine_error(exc):
38
+ """True if the __cause__/__context__ graph of exc holds an unrecoverable vLLM engine error.
39
+
40
+ Classes are matched by name, so the check needs no vLLM import: EngineDeadError, or a
41
+ vLLM VLLMServerError subclass other than the per-request EngineGenerateError.
42
+ """
43
+ pending, seen = [exc], set()
44
+ while pending:
45
+ current = pending.pop()
46
+ if current is None or id(current) in seen:
47
+ continue
48
+ seen.add(id(current))
49
+ classes = type(current).__mro__
50
+ if isinstance(current, ServiceFailure) or any(cls.__name__ == 'EngineDeadError' for cls in classes):
51
+ return True
52
+ vllm_classes = {cls.__name__ for cls in classes
53
+ if cls.__module__ == 'vllm' or cls.__module__.startswith('vllm.')}
54
+ if 'VLLMServerError' in vllm_classes and 'EngineGenerateError' not in vllm_classes:
55
+ return True
56
+ pending.extend((current.__cause__, current.__context__))
57
+ return False
58
+
59
+
60
+ def external_path(path):
61
+ """Resolve an absolute path that is neither inside the package nor an ancestor of it."""
62
+ candidate = Path(path)
63
+ if not candidate.is_absolute():
64
+ raise ValueError("Path must be absolute: " + str(path))
65
+ result = candidate.resolve()
66
+ if result == ROOT or result.is_relative_to(ROOT) or ROOT.is_relative_to(result):
67
+ raise ValueError("Path must be outside the package and must not contain it: " + str(result))
68
+ return result
69
+
70
+
71
+ def external_output(path):
72
+ """Validated output location outside the package; callers create files with mode 'x'."""
73
+ result = external_path(path)
74
+ result.parent.mkdir(parents=True, exist_ok=True)
75
+ return result
76
+
77
+
78
+ def load_json(path):
79
+ def reject(value):
80
+ raise ValueError("Non-finite JSON value")
81
+ return json.loads(Path(path).read_text(encoding="utf-8"), parse_constant=reject)
82
+
83
+
84
+ def load_config(path=None):
85
+ config = load_json(path or ROOT / "config.json")
86
+ apply_profile(config)
87
+ validate_config(config)
88
+ return config
89
+
90
+
91
+ def apply_profile(config):
92
+ import copy
93
+ profile=config.get('profiles',{}).get(config['model']['model_key'])
94
+ if profile is None: raise ValueError('No profile for model_key')
95
+ for section,values in profile.items():
96
+ config.setdefault(section,{}).update(copy.deepcopy(values))
97
+ return apply_budget_tier(config)
98
+
99
+
100
+ def apply_budget_tier(config, tier=None):
101
+ r=config['routing'];tier=tier or r.get('tier','normal')
102
+ if tier not in ('normal','hard','long'):raise ValueError('Unknown quota tier')
103
+ r['tier']=tier
104
+ tiers=r.get('tiers',{})
105
+ values=tiers.get(tier,{})
106
+ for key in ('reasoning_words','max_new_tokens'):
107
+ if key in tiers.get('hard',{}):config['generation'][key]=tiers['hard'][key]
108
+ if 'max_prompt_tokens' in values:config['limits']['max_prompt_tokens']=values['max_prompt_tokens']
109
+ for key in ('reasoning_words','max_new_tokens'):
110
+ if key in values:config['generation'][key]=values[key]
111
+ # Forced long is a runtime admission switch, preserving fast identity.
112
+ return config
113
+
114
+
115
+ def finite_number(value, name, *, low=None, high=None, positive=False):
116
+ if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
117
+ raise ValueError(f"{name} must be a finite number")
118
+ if (low is not None and value < low) or (high is not None and value > high) or (positive and value <= 0):
119
+ raise ValueError(f"{name} is outside its allowed range")
120
+ return value
121
+
122
+
123
+ def validate_config(c):
124
+ if __package__: from .quota import cost_settings
125
+ else: from quota import cost_settings
126
+ cost_settings(c)
127
+ if c['model']['model_key'] not in c.get('profiles',{}):raise ValueError('No profile for model_key')
128
+ if c.get('usage',{}).get('basis','engine') not in ('engine','each_once','submitted'):
129
+ raise ValueError('Invalid usage basis')
130
+ m, r, g, p, s = (c[k] for k in ("model", "routing", "generation", "prompt", "server"))
131
+ if m["backend"] not in ("transformers", "vllm") or m["quant"] not in ("int8", "bf16", "awq"):
132
+ raise ValueError("Unsupported backend or precision")
133
+ if m['backend']=='vllm':
134
+ neutral={'suppress_tokens':None,'begin_suppress_tokens':None,'bad_words_ids':None,
135
+ 'forced_bos_token_id':None,'forced_eos_token_id':None,'forced_decoder_ids':None,
136
+ 'no_repeat_ngram_size':0,'min_new_tokens':0,'min_length':0,'length_penalty':1.0,'renormalize_logits':False}
137
+ if any(g.get(k,v)!=v for k,v in neutral.items()):raise ValueError('Unsupported vLLM decode override')
138
+ if r["mode"] not in ("routed", "fast_only", "uniform"):
139
+ raise ValueError("Unsupported routing mode")
140
+ tiers=r.get('tiers',{})
141
+ if tiers:
142
+ if set(tiers)!={'normal','hard','long'} or r.get('tier','normal') not in tiers:raise ValueError('Require normal/hard/long tiers')
143
+ if tiers['long'].get('fast_only') is not True:raise ValueError('Long tier must be fast only')
144
+ for tier in ('normal','long'):
145
+ value=tiers[tier].get('max_prompt_tokens')
146
+ if type(value) is not int or not 0<value<=c['limits']['physical_max_prompt_tokens']:raise ValueError('Invalid tier input budget')
147
+ for key in ('reasoning_words','max_new_tokens'):
148
+ if type(tiers['hard'].get(key)) is not int or tiers['hard'][key]<=0:raise ValueError('Invalid hard draft budget')
149
+ finite_number(r["margin_threshold"], "margin_threshold", low=0)
150
+ if r["margin_kind"]=="probdiff" and r["margin_threshold"]>1:
151
+ raise ValueError("probdiff threshold must be <=1")
152
+ if r["margin_kind"] not in ("logratio", "probdiff"):
153
+ raise ValueError("Invalid margin_kind")
154
+ if r.get('long_item_slow_policy','fast_only') not in ('fast_only','allow'):
155
+ raise ValueError('Invalid long item policy')
156
+ if r["unparsed_policy"] not in ("fast", "force_answer"):
157
+ raise ValueError("Invalid unparsed_policy")
158
+ finite_number(r["quota"], "quota", low=0, high=1)
159
+ finite_number(r["fuse"], "fuse", low=0, high=1)
160
+ if r["quota"] > r["fuse"]:
161
+ raise ValueError("quota must not exceed fuse")
162
+ if r["mode"] == "uniform" and (r["quota"] != 1 or r["fuse"] != 1):
163
+ raise ValueError("uniform requires explicit quota=1 and fuse=1; it is an unbounded draft experiment")
164
+ for key in ("reasoning_words", "max_new_tokens"):
165
+ if type(g[key]) is not int or g[key] <= 0:
166
+ raise ValueError(f"{key} must be a positive integer")
167
+ if type(g["seed"]) is not int or type(g["do_sample"]) is not bool:
168
+ raise ValueError("Invalid generation seed or do_sample")
169
+ finite_number(g.get("wall_timeout_seconds",60),"slow wall timeout",positive=True)
170
+ finite_number(g["temperature"], "generation temperature", positive=True)
171
+ finite_number(g["top_p"], "top_p", positive=True, high=1)
172
+ finite_number(g["repetition_penalty"], "repetition_penalty", positive=True)
173
+ if type(g["top_k"]) is not int or g["top_k"] < 0:
174
+ raise ValueError("top_k must be a nonnegative integer")
175
+ for key in ("max_request_bytes", "max_questions", "port"):
176
+ if type(s[key]) is not int or s[key] <= 0:
177
+ raise ValueError(f"{key} must be a positive integer")
178
+ if s["port"] > 65535:
179
+ raise ValueError("Invalid port")
180
+ finite_number(s["socket_timeout_seconds"], "socket timeout", positive=True)
181
+ if not isinstance(p["answer_marker"], str) or not p["answer_marker"].strip() or "\n" in p["answer_marker"]:
182
+ raise ValueError("answer_marker must be a nonempty single line")
183
+ if type(p["enable_thinking"]) is not bool or not isinstance(p["system"], str):
184
+ raise ValueError("Invalid prompt settings")
185
+ for template in p["cot_instructions"].values():
186
+ template.format(reasoning_words=g["reasoning_words"])
187
+ for value in (c["limits"]["max_prompt_tokens"], r["slow_max_prompt_tokens"]):
188
+ if type(value) is not int or value <= 0:
189
+ raise ValueError("Token limits must be positive integers")
190
+ limits=c['limits']
191
+ for name in ('physical_max_prompt_tokens','oom_retry_prompt_tokens','state_chunk_tokens','precompression_trigger_chars_per_token','estimated_chars_per_token'):
192
+ if type(limits[name]) is not int or limits[name]<=0: raise ValueError('Invalid compression limit: '+name)
193
+ for name in ('state_head_tokens','state_tail_tokens','compression_reserve_tokens'):
194
+ if type(limits[name]) is not int or limits[name]<0: raise ValueError('Invalid compression limit: '+name)
195
+ if limits['physical_max_prompt_tokens']<limits['max_prompt_tokens']:
196
+ raise ValueError('Physical maximum must cover soft prompt budget')
197
+ finite_number(c["limits"]["truncation_head_frac"], "head fraction", low=0, high=1)
198
+ if c.get("interface", {}).get("boolean_keys", "request") not in ("request", "true_false", "yes_no"):
199
+ raise ValueError("Unknown boolean key style")
200
+
201
+
202
+ class UnsupportedRequest(ValueError):
203
+ """Structurally valid request outside the supported semantic domain (422)."""
204
+
205
+
206
+ def choice_codes(count):
207
+ if count<=26:return list(string.ascii_uppercase[:count])
208
+ if count>676:raise UnsupportedRequest('At most 676 choices supported')
209
+ return [a+b for a in string.ascii_uppercase for b in string.ascii_uppercase][:count]
210
+
211
+
212
+ def normalize_question(question):
213
+ if not isinstance(question, dict):
214
+ raise ValueError("Each question must be an object")
215
+ qtype = ALIASES.get(question.get("type"))
216
+ if qtype is None:
217
+ raise UnsupportedRequest("Unsupported question type")
218
+ instructions = question.get("instructions", question.get("description"))
219
+ if not isinstance(instructions, str) or not instructions.strip():
220
+ raise ValueError("Question needs instructions or description")
221
+ criteria = question.get("criteria")
222
+ q = {"request_type": question["type"], "type": qtype, "instructions": instructions, "criteria": criteria}
223
+ labels = None
224
+ if qtype == "noul":
225
+ if criteria is not None and not isinstance(criteria, dict):
226
+ raise ValueError("noul criteria must be an object")
227
+ elif qtype == "score":
228
+ if not isinstance(criteria,list): raise ValueError("score criteria must be an ordered list")
229
+ if not criteria: raise UnsupportedRequest("score criteria is empty")
230
+ else:
231
+ labels = question.get("labels")
232
+ if labels is None:
233
+ labels = list(criteria) if isinstance(criteria, (dict, list)) else None
234
+ if not isinstance(labels,list): raise ValueError("choice needs labels or ordered criteria")
235
+ if not 1<=len(labels)<=676: raise UnsupportedRequest("choice needs 1..676 labels")
236
+ if any(not isinstance(x, str) or not x for x in labels) or len(set(labels)) != len(labels):
237
+ raise ValueError("choice labels must be distinct nonempty strings")
238
+ if criteria is not None and not isinstance(criteria, (dict, list)):
239
+ raise ValueError("choice criteria must be an object or list")
240
+ return q, labels
241
+
242
+
243
+ def normalize_request(payload, max_questions):
244
+ if not isinstance(payload, dict) or "state" not in payload:
245
+ raise ValueError("Request needs state and questions")
246
+ questions = payload.get("questions")
247
+ if not isinstance(questions, dict) or not 1 <= len(questions) <= max_questions:
248
+ raise ValueError("questions must be a nonempty object within max_questions")
249
+ tasks = []
250
+ for name, question in questions.items():
251
+ if not isinstance(name, str) or not name:
252
+ raise ValueError("Question names must be nonempty strings")
253
+ q, labels = normalize_question(question)
254
+ tasks.append((name, {"state": payload["state"], "question": q, "labels": labels}))
255
+ return tasks
256
+
257
+
258
+ def letter_forms(c):
259
+ return [c, " " + c, c.lower(), " " + c.lower(), c + ".", "(" + c]
260
+
261
+
262
+ def digit_forms(d):
263
+ return [d, " " + d, d + "."]
264
+
265
+
266
+ def render(task):
267
+ # Source-faithful copy of cache_hidden_v2.render; do not deduplicate forms.
268
+ q = task["question"]; qtype = q["type"]; crit = q.get("criteria")
269
+ state = task["state"] if isinstance(task["state"], str) else json.dumps(task["state"], ensure_ascii=False)
270
+ cq = "Context:\n" + state + "\n\nQuestion: " + q["instructions"]
271
+ if qtype == "noul":
272
+ labels = ["no", "yes"]; c = crit or {}; groups = {"yes": YES_FORMS, "no": NO_FORMS}
273
+ parts = [cq] + (["Answer yes if: " + str(c.get("true", "Yes")) + "\nAnswer no if: " + str(c.get("false", "No"))] if (c.get("true") or c.get("false")) else []) + ["Answer with one word: yes or no."]
274
+ elif qtype == "score":
275
+ labels = [str(i) for i in range(len(crit))]; groups = {l: digit_forms(l) for l in labels}
276
+ parts = [cq, "Levels:\n" + "\n".join(str(i) + ": " + str(t) for i, t in enumerate(crit)), "Answer with one number from 0 to " + str(len(crit) - 1) + "."]
277
+ else:
278
+ labels = list(task["labels"]); rubric = crit if isinstance(crit, dict) else {}; codes = choice_codes(len(labels))
279
+ groups = {l: letter_forms(c) for c, l in zip(codes, labels)}
280
+ parts = [cq, "Options:\n" + "\n".join(c + ". " + str(l) + ((": " + str(rubric[l])) if rubric.get(l) else "") for c, l in zip(codes, labels)), "Answer with the letter of the best option."]
281
+ return labels, "\n\n".join(parts), groups
282
+
283
+
284
+ def parse_answer(generation, qtype="noul", labels=None):
285
+ """Audit only: keep the explicit-last and standalone fallbacks of the measured parser."""
286
+ if qtype == "noul":
287
+ matches = re.findall(r"ANSWER:\s*(yes|no)", generation, re.I) or re.findall(r"\b(yes|no)\b", generation, re.I)
288
+ return matches[-1].lower() if matches else None
289
+ if qtype == "choice":
290
+ mapping = dict(zip(choice_codes(len(labels or [])), labels or []))
291
+ explicit, fallback = r"ANSWER:\s*([A-Z]{1,2})\b", r"\b([A-Z]{1,2})\b"
292
+ elif qtype == "score":
293
+ mapping = {str(label): str(label) for label in (labels or [])}
294
+ explicit, fallback = r"ANSWER:\s*(\d+)", r"(?<![\w.+-])(\d+)(?!\w|\.\d)"
295
+ else:
296
+ raise ValueError("Unsupported question type")
297
+ matches = re.findall(explicit, generation)
298
+ if matches and matches[-1] in mapping:
299
+ return mapping[matches[-1]]
300
+ matches = [symbol for symbol in re.findall(fallback, generation) if symbol in mapping]
301
+ return mapping[matches[-1]] if matches else None
302
+
303
+
304
+ def readout_prefix(generation, marker):
305
+ matches = list(re.finditer(r"(?m)^[ \t]*" + re.escape(marker), generation))
306
+ if matches:
307
+ return generation[:matches[-1].end()], False
308
+ return generation.rstrip() + "\n" + marker, True
309
+
310
+
311
+ def logsumexp(values):
312
+ top = max(values)
313
+ if top == -math.inf: return top
314
+ return top + math.log(sum(math.exp(x - top) for x in values))
315
+
316
+
317
+ def candidate_ids(backend, groups):
318
+ # Keep multiplicity even when two textual forms map to the same token id.
319
+ result = {}
320
+ for label, forms in groups.items():
321
+ tokens = [backend.encode(form) for form in forms]
322
+ result[label] = [ids[0] for ids in tokens if len(ids) == 1]
323
+ if not any(result.values()):
324
+ raise ValueError("Tokenizer has no single-token candidate forms")
325
+ return result
326
+
327
+
328
+ def aggregate(logits, groups):
329
+ for token in {t for ids in groups.values() for t in ids}:
330
+ if token not in logits or not math.isfinite(logits[token]):
331
+ raise RuntimeError("Backend omitted a candidate or returned a non-finite logit")
332
+ return {label: logsumexp([logits[t] for t in ids]) if ids else -1e9 for label, ids in groups.items()}
333
+
334
+
335
+ def validate_calibration(calibration, model_key, path="fast"):
336
+ model = calibration.get("models", {}).get(model_key)
337
+ if model is None:
338
+ raise ValueError("Calibration lacks selected model_key")
339
+ block = model.get(path)
340
+ if block is None:
341
+ raise ValueError("Calibration lacks selected route")
342
+ for kind in ("choice", "score"):
343
+ finite_number(block["temperature_by_kind"][kind], kind + " temperature", positive=True)
344
+ if block["temperature_by_kind"]["noul"] != "one_bin":
345
+ raise ValueError("noul calibration must be one_bin")
346
+ finite_number(block["noul_calibration"]["p_cal"], "p_cal", low=0, high=1)
347
+ return block
348
+
349
+
350
+ def probabilities(labels, logits, qtype, block):
351
+ if qtype == "noul":
352
+ # Deterministic tie: earliest in render's label order, namely no.
353
+ winner = max(labels, key=logits.__getitem__)
354
+ p_cal = min(1 - 1e-6, max(0.5, block["noul_calibration"]["p_cal"]))
355
+ return {label: p_cal if label == winner else 1 - p_cal for label in labels}
356
+ temperature = block["temperature_by_kind"][qtype]
357
+ # Subtract before dividing to avoid overflow from small positive T.
358
+ top = max(logits.values())
359
+ weights = {label: math.exp((logits[label] - top) / temperature) for label in labels}
360
+ total = sum(weights.values())
361
+ floored = {label: max(1e-6, weights[label] / total) for label in labels}
362
+ return {label: value / sum(floored.values()) for label, value in floored.items()}
363
+
364
+
365
+ # Rule sections are protected conservatively. Unknown text outside a recognized
366
+ # section is narrative; explicit rule/policy/definition/criteria subtrees are sacred.
367
+ _RULE = re.compile(r'(?im)^\s*(?:R\d+\s*[::]|(?:rules?|polic(?:y|ies)|definitions?|criteria|规则|規則|定义|定義|政策|判定标准)\s*[::])')
368
+
369
+ _RULE_KEY = re.compile(r'^\s*(?:rules?|polic(?:y|ies)|definitions?|criteria|application_rule_set|规则|規則|定义|定義|政策|标准|判定标准)\s*$', re.I)
370
+
371
+ def contains_rule_subtree(value):
372
+ if isinstance(value,str):return bool(_RULE.search(value))
373
+ if isinstance(value,dict):return any(_RULE_KEY.fullmatch(str(k)) or contains_rule_subtree(v) for k,v in value.items())
374
+ if isinstance(value,list):return any(contains_rule_subtree(v) for v in value)
375
+ return False
376
+
377
+
378
+ OMISSION = '\n[... state middle omitted ...]\n'
379
+
380
+
381
+ def lexical_terms(text):
382
+ words = re.findall(r'[a-zA-Z_][\w-]*|\d+(?:\.\d+)?|[\u4e00-\u9fff]', text.lower())
383
+ # Character bigrams preserve Chinese query signal without a segmenter.
384
+ words += re.findall(r'(?=([\u4e00-\u9fff]{2}))', text)
385
+ return set(words)
386
+
387
+
388
+ def shrink_state(state, keep_chars, head_fraction=0.6, *, backend=None, query='',
389
+ head_tokens=512, tail_tokens=512, chunk_tokens=200):
390
+ """Extract query-relevant chunks in original order, protecting explicit rules.
391
+
392
+ The budget is tokens when a backend is supplied, otherwise characters.
393
+ A contiguous omitted range produces exactly one marker.
394
+ """
395
+ encode = backend.encode if backend else lambda x: list(x)
396
+ decode = backend.decode if backend else lambda x: ''.join(x)
397
+ query_terms = lexical_terms(query)
398
+ important = {w for w in query_terms if any(c.isdigit() for c in w)}
399
+ important |= {w.lower() for w in re.findall(r'\b(?:[A-Z][a-z]+|[A-Z][A-Z0-9_]+)\b',query)}
400
+ def text_shrink(text, budget):
401
+ if len(encode(text)) <= budget:
402
+ return text
403
+ # Slice original paragraphs including their delimiters; never make a
404
+ # delimiter into an independent compressible paragraph.
405
+ spans=[]; rule_section=False
406
+ for match in re.finditer(r'.+?(?:\n[ \t]*\n|\Z)',text,re.S):
407
+ part=match.group()
408
+ heading=re.match(r'^\s*(?:#{1,6}\s*)?([^\n::]{1,60})[::]\s*(?:\n|$)',part)
409
+ if heading:
410
+ title=heading.group(1)
411
+ if _RULE_KEY.search(title): rule_section=True
412
+ elif re.fullmatch(r'(?i)narrative|events?|history|state|context|data|observations?|背景|叙述|事件|状态|记录',title.strip()): rule_section=False
413
+ protected=rule_section or bool(_RULE.search(part))
414
+ if spans and spans[-1][1]==protected: spans[-1]=(spans[-1][0]+part,protected)
415
+ else: spans.append((part,protected))
416
+ merged=spans
417
+ encoded=[(part,keep,encode(part)) for part,keep in merged]
418
+ total=sum(len(ids) for _,_,ids in encoded)
419
+ head_end=min(head_tokens,total);tail_start=max(head_end,total-tail_tokens)
420
+ blocks=[];cursor=0
421
+ for part,protected,ids in encoded:
422
+ if protected:
423
+ blocks.append((part,len(ids),True));cursor+=len(ids)
424
+ continue
425
+ # Sentence/newline boundaries first, word boundaries for oversized
426
+ # sentences. Never decode an arbitrary token slice into a block.
427
+ sentences=re.findall(r'.*?(?:[.!?。!?](?=\s|$)|\n|$)',part,re.S)
428
+ pieces=[]
429
+ for sentence in sentences:
430
+ if not sentence:continue
431
+ if len(encode(sentence))<=chunk_tokens:pieces.append(sentence)
432
+ else:
433
+ words=re.findall(r'\s+|\S+\s*',sentence)
434
+ chunk=''
435
+ for word in words:
436
+ if chunk and len(encode(chunk+word))>chunk_tokens:
437
+ pieces.append(chunk);chunk=''
438
+ chunk+=word
439
+ if chunk:pieces.append(chunk)
440
+ chunk=''
441
+ def add(value):
442
+ nonlocal cursor
443
+ size=len(encode(value))
444
+ edge=cursor<head_end or cursor+size>tail_start
445
+ blocks.append((value,size,edge));cursor+=size
446
+ for piece in pieces:
447
+ if chunk and len(encode(chunk+piece))>chunk_tokens:
448
+ add(chunk);chunk=''
449
+ chunk+=piece
450
+ if chunk:add(chunk)
451
+ selected={i for i,(_,_,keep) in enumerate(blocks) if keep}
452
+ marker_cost=len(encode(OMISSION))
453
+ used=sum(blocks[i][1] for i in selected)+marker_cost*(len(selected)+1)
454
+ ranked=[]
455
+ for i,(part,size,keep) in enumerate(blocks):
456
+ terms=lexical_terms(part); overlap=query_terms & terms
457
+ score=(len(overlap)+2*len(important & terms))/max(1,math.sqrt(len(terms)))
458
+ if not keep: ranked.append((-score,i))
459
+ for _,i in sorted(ranked):
460
+ if used+blocks[i][1]+marker_cost <= budget:
461
+ selected.add(i); used+=blocks[i][1]+marker_cost
462
+ def join(indices):
463
+ out=[]; omitted=False
464
+ for i,(part,_,_) in enumerate(blocks):
465
+ if i in indices:
466
+ if omitted: out.append(OMISSION)
467
+ out.append(part); omitted=False
468
+ else: omitted=True
469
+ if omitted: out.append(OMISSION)
470
+ return ''.join(out)
471
+ result=join(selected)
472
+ # Recover conservative omission-marker reserves using adjacent blocks.
473
+ while len(encode(result))<.8*budget:
474
+ neighbors=sorted({j for i in selected for j in (i-1,i+1) if 0<=j<len(blocks) and j not in selected})
475
+ added=False
476
+ for j in neighbors:
477
+ candidate=join(selected|{j})
478
+ if len(encode(candidate))<=budget:
479
+ selected.add(j);result=candidate;added=True
480
+ if not added:break
481
+ if len(encode(result)) <= budget: return result
482
+ # Protected material survives the soft budget. For an unusually small
483
+ # budget shrink only narrative edges globally, retaining the rules.
484
+ if any(keep for _,keep in merged):
485
+ protected_text=''.join(part for part,keep in merged if keep)
486
+ allowance=max(0,budget-len(encode(protected_text))-marker_cost*(len(merged)+1))
487
+ if not allowance: return protected_text
488
+ n=sum(len(encode(part)) for part,keep in merged if not keep)
489
+ h=int(allowance*head_fraction);t=allowance-h
490
+ out=[];offset=0;omitted=False
491
+ for part,keep in merged:
492
+ if keep:
493
+ if omitted:out.append(OMISSION);omitted=False
494
+ out.append(part);continue
495
+ ids=encode(part)
496
+ head=ids[:max(0,min(len(ids),h-offset))]
497
+ tail=ids[max(0,n-t-offset):] if offset+len(ids)>n-t else []
498
+ if head:
499
+ if omitted:out.append(OMISSION);omitted=False
500
+ out.append(decode(head))
501
+ if len(head)+len(tail)<len(ids):omitted=True
502
+ if tail:
503
+ if omitted:out.append(OMISSION);omitted=False
504
+ out.append(decode(tail))
505
+ offset+=len(ids)
506
+ if omitted:out.append(OMISSION)
507
+ return ''.join(out)
508
+ ids=encode(text); available=max(0,budget-marker_cost)
509
+ h=int(available*head_fraction); t=available-h
510
+ return decode(ids[:h])+OMISSION+(decode(ids[-t:]) if t else '')
511
+
512
+ if isinstance(state,str): return text_shrink(state,max(0,keep_chars))
513
+ if isinstance(state,dict):
514
+ protected={k:v for k,v in state.items() if _RULE_KEY.search(str(k)) or
515
+ (not isinstance(v,(dict,list)) and len(str(v))<=200)}
516
+ rest=[k for k in state if k not in protected]
517
+ remaining=max(0,keep_chars-len(encode(json.dumps(protected,ensure_ascii=False))))
518
+ sizes={k:len(encode(json.dumps(state[k],ensure_ascii=False))) for k in rest}
519
+ total=max(1,sum(sizes.values()))
520
+ return {k:v if k in protected else shrink_state(v,int(remaining*sizes[k]/total),head_fraction,
521
+ backend=backend,query=query,head_tokens=head_tokens,tail_tokens=tail_tokens,chunk_tokens=chunk_tokens)
522
+ for k,v in state.items()}
523
+ if isinstance(state,list):
524
+ if len(encode(json.dumps(state,ensure_ascii=False)))<=keep_chars: return state
525
+ if not state: return state
526
+ # Budget whole entries, protect explicit rule entries; preserve both ends.
527
+ selected={0,len(state)-1}|{i for i,v in enumerate(state) if contains_rule_subtree(v)}
528
+ ranked=sorted(range(1,len(state)-1),key=lambda i:(-len(lexical_terms(str(state[i])) & query_terms),i))
529
+ used=sum(len(encode(json.dumps(state[i],ensure_ascii=False))) for i in selected)
530
+ for i in ranked:
531
+ size=len(encode(json.dumps(state[i],ensure_ascii=False)))
532
+ if i not in selected and used+size+len(OMISSION)<keep_chars: selected.add(i); used+=size
533
+ protected_entries={i for i in selected if isinstance(state[i],str) and _RULE.search(state[i])}
534
+ protected_size=sum(len(encode(json.dumps(state[i],ensure_ascii=False))) for i in protected_entries)
535
+ narrative_size=max(1,sum(len(encode(json.dumps(state[i],ensure_ascii=False))) for i in selected-protected_entries))
536
+ free=max(0,keep_chars-protected_size-len(selected)*len(OMISSION))
537
+ result=[]; omitted=False
538
+ for i,v in enumerate(state):
539
+ if i in selected:
540
+ if omitted: result.append(OMISSION.strip())
541
+ if i not in protected_entries and len(str(v))>200:
542
+ size=len(encode(json.dumps(v,ensure_ascii=False)))
543
+ v=shrink_state(v,int(free*size/narrative_size),head_fraction,backend=backend,
544
+ query=query,head_tokens=head_tokens,tail_tokens=tail_tokens,chunk_tokens=chunk_tokens)
545
+ result.append(v);omitted=False
546
+ else: omitted=True
547
+ return result
548
+ return state
549
+
550
+
551
+ def prepare_task(task, backend, config):
552
+ def prompts(value):
553
+ labels,text,groups=render(value)
554
+ instruction=config['prompt']['cot_instructions'][value['question']['type']].format(
555
+ reasoning_words=config['generation']['reasoning_words']).replace('ANSWER:',config['prompt']['answer_marker'])
556
+ system,think=config['prompt']['system'],config['prompt']['enable_thinking']
557
+ return labels,groups,backend.prompt_ids(system,text,think),backend.prompt_ids(system,text+'\n'+instruction,think)
558
+ limits=config['limits']; budget=limits['max_prompt_tokens'];normal_budget=budget
559
+ long_budget=min(budget,config['routing'].get('tiers',{}).get('long',{}).get('max_prompt_tokens',budget))
560
+ query=task['question']['instructions']+' '+json.dumps(task['question'].get('criteria'),ensure_ascii=False)+' '+str(task.get('labels') or '')
561
+ raw=task['state'] if isinstance(task['state'],str) else json.dumps(task['state'],ensure_ascii=False)
562
+ precompressed=len(raw)>limits['precompression_trigger_chars_per_token']*budget
563
+ source=task
564
+ if precompressed:budget=long_budget
565
+ if precompressed:
566
+ ratio=limits['estimated_chars_per_token']
567
+ source=dict(task,state=shrink_state(task['state'],budget*ratio,limits['truncation_head_frac'],query=query,
568
+ head_tokens=limits['state_head_tokens']*ratio,tail_tokens=limits['state_tail_tokens']*ratio,
569
+ chunk_tokens=limits['state_chunk_tokens']*ratio))
570
+ original=prompts(source); original_count=len(original[2])
571
+ long_input=precompressed or original_count>normal_budget
572
+ if long_input:budget=long_budget
573
+ current,prepared=source,original
574
+ refilled=False
575
+ if precompressed and original_count<.8*budget:
576
+ fixed=len(prompts(dict(task,state=''))[2])
577
+ refill=shrink_state(task['state'],max(0,budget-fixed-limits['compression_reserve_tokens']),
578
+ limits['truncation_head_frac'],backend=backend,query=query,
579
+ head_tokens=limits['state_head_tokens'],tail_tokens=limits['state_tail_tokens'],chunk_tokens=limits['state_chunk_tokens'])
580
+ candidate=dict(task,state=refill);candidate_prompts=prompts(candidate)
581
+ if len(candidate_prompts[2])<=budget:
582
+ current,prepared=candidate,candidate_prompts;refilled=True
583
+ if original_count>budget:
584
+ fixed=len(prompts(dict(task,state=''))[2])
585
+ query=task['question']['instructions']+' '+json.dumps(task['question'].get('criteria'),ensure_ascii=False)+' '+str(task.get('labels') or '')
586
+ allowance=max(0,budget-fixed-limits['compression_reserve_tokens'])
587
+ for _ in range(3):
588
+ current=dict(task,state=shrink_state(source['state'],allowance,limits['truncation_head_frac'],
589
+ backend=backend,query=query,head_tokens=limits['state_head_tokens'],
590
+ tail_tokens=limits['state_tail_tokens'],chunk_tokens=limits['state_chunk_tokens']))
591
+ prepared=prompts(current); size=len(prepared[2])
592
+ if size<=budget: break
593
+ allowance=max(0,allowance-(size-budget)-limits['compression_reserve_tokens'])
594
+ # Compression cannot remove sacred rule/question/option material. The
595
+ # final physical guard always bounds tensors, with explicit metadata.
596
+ kept=len(prepared[2]); physical=limits['physical_max_prompt_tokens']
597
+ hard_cut=kept>physical
598
+ if hard_cut:
599
+ def cut(ids):
600
+ h=int(physical*limits['truncation_head_frac']);t=physical-h
601
+ return ids if len(ids)<=physical else ids[:h]+ids[-t:]
602
+ prepared=prepared[:2]+(cut(prepared[2]),cut(prepared[3]))
603
+ return current,prepared,{'original_prompt_tokens':original_count if not precompressed else None,
604
+ 'original_prompt_tokens_estimate':math.ceil(len(raw)/limits['estimated_chars_per_token']) if precompressed else original_count,
605
+ 'long_input':long_input,'character_precompressed':precompressed,'adjacent_refill':refilled,
606
+ 'kept_prompt_tokens':len(prepared[2]),
607
+ 'slow_prompt_exceeds_budget':len(prepared[3])>min(budget,config['routing']['slow_max_prompt_tokens']),
608
+ 'truncated':current['state']!=task['state'] or hard_cut,
609
+ 'protected_content_exceeds_budget':kept>budget,'physical_truncated':hard_cut}
610
+
611
+
612
+ def sequence_scores(session, backend, groups, meter=None):
613
+ """Full-vocabulary chain probabilities; copies branch KV, never prompt prefill.
614
+
615
+ Keep every form (including repeated tokenizations) with its original weight.
616
+ """
617
+ cache = {(): session}
618
+ continuation_tokens = 0
619
+ normalized = {}
620
+ path_scores = {(): 0.0}
621
+ def score(ids):
622
+ nonlocal continuation_tokens
623
+ prefix = ()
624
+ for token in ids:
625
+ check_deadline(meter)
626
+ child = prefix + (token,)
627
+ if child not in path_scores:
628
+ parent = cache[prefix]
629
+ if prefix not in normalized:
630
+ wanted=sorted({encoded[len(prefix)] for forms in groups.values() for form in forms
631
+ if (encoded:=backend.encode(form))[:len(prefix)]==list(prefix) and len(encoded)>len(prefix)})
632
+ if hasattr(parent,'log_probs'):
633
+ normalized[prefix]=parent.log_probs(wanted)
634
+ else:
635
+ logits=parent.logits; lse=logsumexp(list(logits.values()))
636
+ normalized[prefix]={t:logits[t]-lse for t in wanted if t in logits}
637
+ value=normalized[prefix].get(token)
638
+ if value is None or not math.isfinite(value): raise RuntimeError('Missing candidate token')
639
+ path_scores[child]=path_scores[prefix]+value
640
+ prefix = child
641
+ if prefix not in cache and len(prefix) < len(ids):
642
+ branch = cache[prefix[:-1]].fork()
643
+ if meter is not None and not getattr(branch, 'handles_meter', False):
644
+ meter['output'] += 1
645
+ meter['probes'] += 1
646
+ if getattr(branch,'meter',None) is None:
647
+ meter['engine_input']=meter.get('engine_input',0)+1; meter['engine_submitted']+=1
648
+ branch.advance([token])
649
+ continuation_tokens += 1
650
+ cache[prefix] = branch
651
+ return path_scores[prefix]
652
+ digit_ids=getattr(backend,'digit_start_ids',None)
653
+ if digit_ids is None:
654
+ # Synthetic tokenizer fallback only; real backends scan the vocabulary.
655
+ digit_ids={ids[0] for d in string.digits if len(ids:=backend.encode(d))==1}
656
+ terminals={}
657
+ def termination(ids):
658
+ nonlocal continuation_tokens
659
+ key=tuple(ids)
660
+ if key not in terminals:
661
+ if key not in cache:
662
+ branch=cache[key[:-1]].fork()
663
+ if meter is not None and not getattr(branch,'handles_meter',False):
664
+ meter['output']+=1;meter['probes']+=1
665
+ if getattr(branch,'meter',None) is None:
666
+ meter['engine_input']=meter.get('engine_input',0)+1;meter['engine_submitted']+=1
667
+ branch.advance([key[-1]]);continuation_tokens+=1;cache[key]=branch
668
+ se=cache[key]
669
+ if hasattr(se,'non_digit_logprob'):
670
+ value=se.non_digit_logprob(digit_ids)
671
+ else:
672
+ logits=se.logits; z=logsumexp(list(logits.values()))
673
+ allowed=[v for t,v in logits.items() if t not in digit_ids]
674
+ value=logsumexp(allowed)-z if allowed else -math.inf
675
+ terminals[key]=value
676
+ return terminals[key]
677
+ result={}
678
+ for label,forms in groups.items():
679
+ is_prefix=label.isdigit() and any(other!=label and other.startswith(label) for other in groups)
680
+ values=[]
681
+ for form in forms:
682
+ ids=backend.encode(form)
683
+ if not ids: continue
684
+ value=score(ids)
685
+ if is_prefix and form.strip().isdigit(): value+=termination(ids)
686
+ values.append(value)
687
+ result[label]=logsumexp(values)
688
+ return result, continuation_tokens
689
+
690
+
691
+ def slow_readout(backend, ids, groups, config, meter):
692
+ """One autoregressive session: one prompt prefill, cached draft/marker/readout."""
693
+ if hasattr(backend,'slow_session'):
694
+ session,parsed=backend.slow_session(ids,config['routing']['unparsed_policy'],meter)
695
+ if session is None: return None,meter['draft'],False,False,0
696
+ # Marginalize textual whitespace forms; no full-vocabulary CPU copy.
697
+ scores,probes=sequence_scores(session,backend,groups,meter)
698
+ return scores,meter['draft']+meter['forced'],not parsed,False,probes
699
+ meter['prompt'] += len(ids)
700
+ meter['engine_submitted'] += len(ids)
701
+ meter['engine_input']=meter.get('engine_input',0)+len(ids)
702
+ meter['engine_calls']=meter.get('engine_calls',0)+1
703
+ session = backend.open_session(ids)
704
+ import random
705
+ rng = random.Random(config['generation']['seed'])
706
+ generated = []
707
+ marker = config['prompt']['answer_marker']
708
+ found = False
709
+ for _ in range(config['generation']['max_new_tokens']):
710
+ check_deadline(meter)
711
+ logits = dict(session.logits)
712
+ g = config['generation']
713
+ for seen in set(generated):
714
+ value = logits[seen]
715
+ logits[seen] = value * g['repetition_penalty'] if value < 0 else value / g['repetition_penalty']
716
+ token = max(logits, key=logits.get)
717
+ if g['do_sample']:
718
+ ordered = sorted(logits, key=logits.get, reverse=True)
719
+ if g['top_k']:
720
+ ordered = ordered[:g['top_k']]
721
+ weights = [math.exp((logits[t]-logits[token])/g['temperature']) for t in ordered]
722
+ mass, cutoff = 0, sum(weights)*g['top_p']
723
+ end = 0
724
+ while end < len(weights) and mass < cutoff:
725
+ mass += weights[end]
726
+ end += 1
727
+ token = rng.choices(ordered[:end], weights=weights[:end])[0]
728
+ generated.append(token)
729
+ meter['output'] += 1
730
+ meter['draft'] += 1
731
+ meter['engine_generated']=meter.get('engine_generated',0)+1
732
+ session.advance([token])
733
+ text = backend.decode(generated)
734
+ if re.search(r'(?m)^[ \t]*' + re.escape(marker) + r'$', text):
735
+ found = True
736
+ break
737
+ if token in getattr(backend, 'eos_ids', ()):
738
+ break
739
+ if not found and config['routing']['unparsed_policy'] == 'fast':
740
+ return None, len(generated), False, False, 0
741
+ if not found:
742
+ suffix = backend.encode('\n' + marker)
743
+ meter['output'] += len(suffix)
744
+ meter['forced'] += len(suffix)
745
+ meter['engine_input']=meter.get('engine_input',0)+len(suffix);meter['engine_submitted']+=len(suffix)
746
+ session.advance(suffix)
747
+ generated.extend(suffix)
748
+ # Whitespace is a form branch, never a top-1 destructive advance.
749
+ whitespace = False
750
+ scores, probes = sequence_scores(session, backend, groups, meter)
751
+ return scores, len(generated), not found, whitespace, probes
752
+
753
+
754
+ def label_margin(labels, scores, kind="logratio"):
755
+ """Aggregated label distribution at T=1, before any calibration/floor."""
756
+ if len(labels) < 2:
757
+ return math.inf
758
+ values = sorted((scores[label] for label in labels), reverse=True)
759
+ if kind == 'logratio':
760
+ return values[0] - values[1]
761
+ if kind != 'probdiff':
762
+ raise ValueError('Invalid margin kind')
763
+ weights = [math.exp(v-values[0]) for v in values]
764
+ return (weights[0]-weights[1])/sum(weights)
765
+
766
+
767
+ def fast_readout(backend, ids, labels, groups, meter):
768
+ meter["prompt"]+=len(ids)
769
+ if not getattr(backend,"engine_metering",False):
770
+ meter["engine_submitted"]+=len(ids);meter['engine_input']+=len(ids)
771
+ meter['hf_prefill_tokens']+=len(ids)
772
+ try:
773
+ tokens = candidate_ids(backend, groups)
774
+ except ValueError:
775
+ tokens = {label: [] for label in labels}
776
+ probes = 0
777
+ if any(not values for values in tokens.values()):
778
+ session=backend.open_session(ids,meter=meter) if getattr(backend,'engine_metering',False) else backend.open_session(ids)
779
+ if hasattr(session,'cache'): session.meter=meter
780
+ scores, probes = sequence_scores(session, backend, groups, meter)
781
+ else:
782
+ wanted = sorted({t for values in tokens.values() for t in values})
783
+ logits=backend.next_logits(ids,wanted,meter=meter) if getattr(backend,'engine_metering',False) else backend.next_logits(ids,wanted)
784
+ scores = aggregate(logits, tokens)
785
+ if not getattr(backend,'engine_metering',False):
786
+ meter['output']+=backend.readout_output_tokens;meter['probes']+=backend.readout_output_tokens
787
+ meter['engine_generated']+=backend.readout_output_tokens
788
+ return scores, probes + backend.readout_output_tokens
789
+
790
+
791
+ def release_failed_session(backend):
792
+ import gc
793
+ gc.collect()
794
+ torch=getattr(backend,'torch',None)
795
+ cuda=getattr(torch,'cuda',None)
796
+ if cuda is not None:
797
+ try: cuda.empty_cache()
798
+ except Exception: pass
799
+
800
+
801
+ def fast_attempt_with_retry(backend,task,config,ids,labels,groups,meter):
802
+ try:
803
+ scores,out=fast_readout(backend,ids,labels,groups,meter)
804
+ return scores,out,None
805
+ except Exception as exc:
806
+ if unrecoverable_engine_error(exc):
807
+ raise
808
+ cuda=getattr(getattr(backend,'torch',None),'cuda',None)
809
+ oom_type=getattr(cuda,'OutOfMemoryError',MemoryError)
810
+ if not isinstance(exc,(MemoryError,oom_type)) and 'out of memory' not in str(exc).lower():
811
+ raise
812
+ # Leave the failed forward traceback before clearing allocator state.
813
+ release_failed_session(backend)
814
+ import copy
815
+ reduced=copy.deepcopy(config)
816
+ budget=min(config['limits']['oom_retry_prompt_tokens'],max(1,config['limits']['max_prompt_tokens']//2))
817
+ reduced['limits']['max_prompt_tokens']=budget
818
+ reduced['limits']['physical_max_prompt_tokens']=budget
819
+ _,(_,new_groups,new_ids,_),length=prepare_task(task,backend,reduced)
820
+ scores,out=fast_readout(backend,new_ids,labels,new_groups,meter)
821
+ return scores,out,length
822
+
823
+
824
+ def slow_attempt(backend,ids,groups,config,meter):
825
+ # Catch in this frame, then leave except before collecting. The failed
826
+ # readout/generate traceback (and its KV references) no longer exists.
827
+ try:
828
+ return slow_readout(backend,ids,groups,config,meter)
829
+ except Exception as exc:
830
+ if unrecoverable_engine_error(exc):
831
+ raise ServiceFailure('unrecoverable engine failure') from exc
832
+ timed_out=isinstance(exc,TimeoutError)
833
+ release_failed_session(backend)
834
+ if timed_out: raise TimeoutError('Slow deadline exceeded')
835
+ raise RuntimeError('Slow session failed')
836
+
837
+
838
+ def bounded_slow_attempt(backend,ids,groups,config,meter,model_lock, *, on_service_failure=None):
839
+ """Bound response wait; an in-flight synchronous engine call drains under lock.
840
+
841
+ No worker may start another engine call after cancellation. Timeout usage is
842
+ explicitly estimated and reconciliation rejects the run. An unrecoverable engine
843
+ error is reported through on_service_failure even after the response timed out.
844
+ """
845
+ import queue,time
846
+ result=queue.Queue(maxsize=1)
847
+ timeout=config['generation'].get('wall_timeout_seconds',60)
848
+ meter['cancel_event']=threading.Event();meter['deadline']=time.monotonic()+timeout
849
+ def work():
850
+ try:
851
+ with model_lock:
852
+ check_deadline(meter)
853
+ value=slow_attempt(backend,ids,groups,config,meter)
854
+ check_deadline(meter)
855
+ result.put((True,value))
856
+ except Exception as exc:
857
+ fatal=unrecoverable_engine_error(exc)
858
+ if fatal and on_service_failure is not None:
859
+ on_service_failure()
860
+ result.put((False,ServiceFailure if fatal else type(exc)))
861
+ worker=threading.Thread(target=work,daemon=True);worker.start()
862
+ try: ok,value=result.get(timeout=timeout)
863
+ except queue.Empty:
864
+ meter['cancel_event'].set();meter['engine_usage_unknown']=True
865
+ raise TimeoutError('Slow wall timeout; in-flight call draining')
866
+ if not ok:
867
+ if value is ServiceFailure: raise ServiceFailure('unrecoverable engine failure')
868
+ if value is TimeoutError: raise TimeoutError('Slow wall timeout')
869
+ raise RuntimeError('Slow session failed')
870
+ return value
871
+
872
+
873
+ def threshold_identity(config):
874
+ import hashlib
875
+ import inspect
876
+ # The renderer source binds label layout, and settings bind both prompts.
877
+ canonical=json.dumps({'prompt':{k:config['prompt'][k] for k in ('system','enable_thinking')},'render':inspect.getsource(render),'choice_codes':inspect.getsource(choice_codes),
878
+ 'generation':{k:v for k,v in config['generation'].items() if k not in ('max_new_tokens','reasoning_words','wall_timeout_seconds')}},sort_keys=True,ensure_ascii=False)
879
+ m=config['model']
880
+ return dict(model_key=m['model_key'],quant=m['quant'],adapter=m.get('adapter'),
881
+ prompt_sha=hashlib.sha256(canonical.encode('utf-8')).hexdigest(),backend=m['backend'],
882
+ margin_kind=config['routing']['margin_kind'],max_new_tokens=config['generation']['max_new_tokens'],
883
+ reasoning_words=config['generation']['reasoning_words'],
884
+ max_prompt_tokens=config['limits']['max_prompt_tokens'],request_contract='typesafe.criteria.v1')
885
+
886
+
887
+ def validate_threshold(config,allow_placeholder=False):
888
+ identity=threshold_identity(config)
889
+ fit=config['routing'].get('margin_threshold_fitted_for',{})
890
+ reasons=[]
891
+ if fit.get('placeholder',True): reasons.append('placeholder')
892
+ if any(fit.get(k)!=identity[k] for k in ('model_key','quant','adapter','prompt_sha','margin_kind','max_prompt_tokens','request_contract')):
893
+ reasons.append('identity mismatch')
894
+ if fit.get('backend')!=identity['backend']: reasons.append('backend mismatch')
895
+ if reasons and config['routing']['mode']=='routed' and not allow_placeholder:
896
+ raise ValueError('Unfitted margin threshold: '+', '.join(reasons))
897
+ return ['margin threshold degraded: '+', '.join(reasons)] if reasons else []
898
+
899
+
900
+ class Service:
901
+ def __init__(self, config, calibration, backend, counter, *, allow_placeholder_threshold=False):
902
+ validate_config(config)
903
+ threshold_warnings=validate_threshold(config,allow_placeholder_threshold)
904
+ self.model_lock = threading.RLock()
905
+ self._failure_lock = threading.Lock()
906
+ self.item_failure_streak = 0
907
+ self.dead = False
908
+ self.config, self.backend, self.counter = config, backend, counter
909
+ counter.configure_cost(config)
910
+ key = config['model']['model_key']
911
+ self.calibration = {path: validate_calibration(calibration, key, path) for path in ('fast', 'slow')}
912
+ model = calibration['models'][key]
913
+ self.warnings = ['calibration placeholder: refit required'] if (
914
+ model.get('placeholder') or any(b.get('placeholder') for b in self.calibration.values())) else []
915
+ self.warnings.extend(threshold_warnings)
916
+ if not config['routing'].get('cost_fuse',{}).get('enabled',True):
917
+ self.warnings.append('cost fuse explicitly disabled')
918
+ if 'fallback' in model:
919
+ self.calibration['fallback']=validate_calibration(calibration,key,'fallback')
920
+ else:
921
+ self.calibration['fallback']=self.calibration['fast']
922
+ self.warnings.append('fallback calibration missing: using fast block')
923
+
924
+ def _mark_dead(self):
925
+ with self._failure_lock:
926
+ self.dead = True
927
+
928
+ def is_dead(self):
929
+ """Service failed: an unrecoverable engine error, the engine's own dead flag, or the
930
+ third consecutive item failure. Reading the flag performs no engine call."""
931
+ if self.dead:
932
+ return True
933
+ resource = self.backend
934
+ for name in ('engine', 'llm_engine', 'engine_core', 'resources'):
935
+ resource = getattr(resource, name, None)
936
+ if getattr(resource, 'engine_dead', False) is True:
937
+ self._mark_dead()
938
+ return self.dead
939
+
940
+ def _raise_if_service_failure(self, exc):
941
+ if unrecoverable_engine_error(exc) or self.is_dead():
942
+ self._mark_dead()
943
+ raise ServiceFailure('unrecoverable engine failure') from exc
944
+
945
+ def _item_failed(self):
946
+ with self._failure_lock:
947
+ self.item_failure_streak += 1
948
+ if self.item_failure_streak >= ITEM_FAILURE_LIMIT:
949
+ self.dead = True
950
+ return self.item_failure_streak
951
+
952
+ def _item_succeeded(self):
953
+ with self._failure_lock:
954
+ self.item_failure_streak = 0
955
+
956
+ def health(self):
957
+ dead = self.is_dead()
958
+ warnings = list(self.warnings)
959
+ if dead:
960
+ warnings.append('service failure')
961
+ reference=getattr(self.backend,'generation_reference',None)
962
+ if reference and not reference['verified']:warnings.append('generation reference '+reference['status'])
963
+ if hasattr(self.backend,'forward_counters') and not getattr(self.backend,'stats_available',False):warnings.append('HF forward hook unavailable; usage estimated')
964
+ if getattr(self.backend,'forward_counters',{}).get('uncounted_forwards',0):warnings.append('HF uncounted forwards; usage estimated')
965
+ if getattr(self.counter, 'memory_only', False):
966
+ warnings.append('quota persistence unavailable: memory counts, fast only')
967
+ return {'status': 'error' if dead else 'warning' if warnings else 'ok', 'warnings': warnings,
968
+ 'info': {'intended_overrides': (reference or {}).get('intended_overrides',{})},
969
+ 'model_key': self.config['model']['model_key'], 'mode':self.config['routing']['mode'],
970
+ 'quota_tier':self.config['routing'].get('tier','normal'), 'budget_tiers':self.config['routing'].get('tiers',{}),
971
+ 'threshold_identity':threshold_identity(self.config),
972
+ 'cost_fuse':self.counter.cost_fuse.snapshot(self.config['routing']['quota']),
973
+ 'accuracy_only':self.config.get('bench',{}).get('accuracy_only',False),
974
+ 'generation_reference':({k:v for k,v in reference.items() if k!='intended_overrides'} if reference else reference),'quantization_kernel':getattr(self.backend,'quantization_kernel',None),
975
+ 'prefix_caching':getattr(self.backend,'prefix_caching',None),
976
+ 'supports_slow_session':callable(getattr(self.backend,'slow_session',None)) or callable(getattr(self.backend,'open_session',None))}
977
+
978
+ def engine_stats(self):
979
+ with self.model_lock:
980
+ return self.backend.engine_stats()
981
+
982
+ def warmup(self):
983
+ ids = self.backend.prompt_ids(self.config['prompt']['system'], 'Ready?', False)
984
+ self.backend.next_logits(ids, [self.backend.encode('yes')[0]])
985
+ self.backend.generate(ids, 1)
986
+ meter=new_meter()
987
+ with self.model_lock:
988
+ if callable(getattr(self.backend,'slow_session',None)):
989
+ session,_=self.backend.slow_session(ids,'force_answer',meter,
990
+ max_new_tokens=self.config['server']['warmup_slow_tokens'])
991
+ if session is not None:
992
+ branch=session.fork();branch.advance(self.backend.encode('yes'))
993
+ if hasattr(branch,'log_probs'): branch.log_probs([self.backend.encode('yes')[0]])
994
+ else: _=branch.logits
995
+ del branch,session
996
+ elif callable(getattr(self.backend,'open_session',None)):
997
+ import copy
998
+ warm=copy.deepcopy(self.config)
999
+ warm['generation']['max_new_tokens']=self.config['server']['warmup_slow_tokens']
1000
+ warm['routing']['unparsed_policy']='force_answer'
1001
+ slow_attempt(self.backend,ids,{'yes':['yes',' yes']},warm,meter)
1002
+
1003
+ def _one(self, task):
1004
+ with self.counter.accounting():
1005
+ if self.is_dead():
1006
+ raise ServiceFailure('service failure')
1007
+ try:
1008
+ answer,detail=self._one_impl(task)
1009
+ if self.is_dead():
1010
+ raise ServiceFailure('service failure')
1011
+ detail['threshold_identity']=threshold_identity(self.config)
1012
+ detail['cost_fuse']=self.counter.record_cost(detail['usage_bases'][detail['accounting']],detail['path']=='slow')
1013
+ try:
1014
+ json.dumps([answer,detail],allow_nan=False)
1015
+ except ValueError as exc:
1016
+ raise ItemFailure('non-finite value in response') from exc
1017
+ except ItemFailure as exc:
1018
+ if self._item_failed() >= ITEM_FAILURE_LIMIT:
1019
+ raise ServiceFailure('consecutive item failures') from exc
1020
+ raise
1021
+ except ServiceFailure:
1022
+ self._mark_dead()
1023
+ raise
1024
+ self._item_succeeded()
1025
+ return answer,detail
1026
+
1027
+ def _one_impl(self, task):
1028
+ c, backend = self.config, self.backend
1029
+ uncounted_before=getattr(backend,'forward_counters',{}).get('uncounted_forwards',0)
1030
+ labels, _, groups = render(task)
1031
+ qtype = task['question']['type']
1032
+ detail = {'path':'fast', 'route_reason':'error', 'router_score':0.0,
1033
+ 'prompt_tokens':0, 'completion_tokens':0, 'n_new_tokens':0,
1034
+ 'prompt_tokens_details':{'cached_tokens':0,'engine_submitted_tokens':0},
1035
+ 'fast_scores':None,'slow_scores':None,'budget_tier':'normal',
1036
+ 'readout_output_tokens':0, 'forced_answer':False, 'draft_prediction':None}
1037
+ warning = None
1038
+ meters = []
1039
+ try:
1040
+ kept_task, (_, groups, fast_ids, slow_ids), length = prepare_task(task, backend, c)
1041
+ detail.update(length)
1042
+ long_item=length.get('long_input',False) or c['routing'].get('tier')=='long' or length.get('slow_prompt_exceeds_budget',False) or any(length[k] for k in ('truncated','character_precompressed','protected_content_exceeds_budget','physical_truncated'))
1043
+ hard=long_item if c['routing'].get('long_item_slow_policy','fast_only')=='fast_only' else (
1044
+ length['character_precompressed'] or (length['original_prompt_tokens'] or 0)>c['routing']['slow_max_prompt_tokens'] or length['protected_content_exceeds_budget'])
1045
+ hard=hard or c['routing'].get('tier')=='long' or length.get('slow_prompt_exceeds_budget',False)
1046
+ detail['long_item']=long_item
1047
+ r = c['routing']
1048
+ fast_scores = None
1049
+ fast_meter=new_meter()
1050
+ meters.append(fast_meter)
1051
+ try:
1052
+ with self.model_lock:
1053
+ fast_scores, out, retry_length = fast_attempt_with_retry(backend,task,c,fast_ids,labels,groups,fast_meter)
1054
+ except Exception as exc:
1055
+ self._raise_if_service_failure(exc)
1056
+ raise ItemFailure('item could not be processed') from exc
1057
+ if retry_length is not None:
1058
+ detail.update(retry_length)
1059
+ detail['fast_oom_retry']=True
1060
+ hard=True
1061
+ detail['prompt_tokens']+=fast_meter['prompt']
1062
+ detail['completion_tokens']+=fast_meter['output']
1063
+ detail['readout_output_tokens']+=fast_meter['probes']
1064
+ detail['prompt_tokens_details'].update(cached_tokens=fast_meter['cached'],engine_submitted_tokens=fast_meter['engine_submitted'])
1065
+ if fast_meter.get('engine_usage_unknown'): detail['usage_estimated']=True
1066
+ if fast_meter.get('cached_unknown'): detail['prompt_tokens_details']['cached_tokens_known']=False
1067
+ detail['fast_prompt_tokens']=fast_meter['prompt']
1068
+ detail['raw_label_scores']=detail['fast_scores']=fast_scores
1069
+ margin = label_margin(labels, fast_scores, r['margin_kind']) if fast_scores is not None else None
1070
+ detail.update(fast_margin=margin if margin is None or math.isfinite(margin) else None,
1071
+ fast_margin_infinite=margin == math.inf, margin_kind=r['margin_kind'])
1072
+ wants_slow = fast_scores is not None and not hard and (
1073
+ r['mode'] == 'uniform' or (r['mode'] == 'routed' and margin < r['margin_threshold']))
1074
+ wants_slow = wants_slow and (callable(getattr(backend,'slow_session',None)) or callable(getattr(backend,'open_session',None)))
1075
+ detail['budget_tier']='long' if hard else 'hard' if wants_slow else 'normal'
1076
+ detail['admission_quota'],detail['admission_quota_source']=self.counter.cost_fuse.quota(r['quota'])
1077
+ with self.counter.decision(wants_slow, quota=detail['admission_quota']) as decision:
1078
+ detail.update(path=decision.path, route_reason='oom_retry' if detail.get('fast_oom_retry') else 'length' if hard else decision.reason,
1079
+ quota_at_reservation=decision.snapshot)
1080
+ scores, block = fast_scores, 'fast'
1081
+ if decision.path == 'slow':
1082
+ meter = new_meter()
1083
+ meters.append(meter)
1084
+ try:
1085
+ scores, count, forced, whitespace, probes = bounded_slow_attempt(backend,slow_ids,groups,c,meter,self.model_lock,on_service_failure=self._mark_dead)
1086
+ detail['slow_raw_label_scores']=detail['slow_scores']=scores
1087
+ block = 'slow' if scores is not None else 'fallback'
1088
+ detail['route'] = ('slow_forced_answer' if forced else 'slow') if scores is not None else 'slow_unparsed_fast'
1089
+ if scores is None:
1090
+ scores = fast_scores
1091
+ detail.update(n_new_tokens=count, forced_answer=forced, whitespace_step=whitespace)
1092
+ except Exception as exc:
1093
+ self._raise_if_service_failure(exc)
1094
+ scores, block = fast_scores, 'fallback'
1095
+ warning = 'slow_failed: fast answer kept'
1096
+ detail.update(fallback='slow_to_fast', route='slow_timeout_fast' if isinstance(exc,TimeoutError) else 'slow_failed_fast')
1097
+ if isinstance(exc,TimeoutError):
1098
+ warning='slow_timeout: fast answer kept'
1099
+ detail['usage_estimated']=True
1100
+ # Detach response accounting from the draining worker.
1101
+ meter=dict(meter);meters[-1]=meter
1102
+ finally:
1103
+ if meter.get('engine_usage_unknown'): detail['usage_estimated']=True
1104
+ if meter.get('cached_unknown'): detail['prompt_tokens_details']['cached_tokens_known']=False
1105
+ detail['prompt_tokens'] += meter['prompt']
1106
+ detail['completion_tokens'] += meter['output']
1107
+ detail['n_new_tokens'] = meter['draft'] + meter['forced']
1108
+ detail['readout_output_tokens'] += meter['probes']
1109
+ detail['prompt_tokens_details']['cached_tokens'] += meter['cached']
1110
+ detail['prompt_tokens_details']['engine_submitted_tokens'] += meter['engine_submitted']
1111
+ detail['calibration_block']=block
1112
+ if scores is None:
1113
+ raise ItemFailure('no label scores')
1114
+ probs = probabilities(labels, scores, qtype, self.calibration.get(block, self.calibration['fast']))
1115
+ detail['quota_after_completion'] = decision.snapshot
1116
+ except (ItemFailure, ServiceFailure, QuotaStateError):
1117
+ raise
1118
+ except Exception as exc:
1119
+ # No substitute answer: an engine failure is service-level (HTTP 503); any other
1120
+ # failure fails this item (HTTP 422), which the harness scores as wrong.
1121
+ self._raise_if_service_failure(exc)
1122
+ raise ItemFailure('item could not be processed') from exc
1123
+ if (getattr(backend,'forward_counters',{}).get('uncounted_forwards',0)>uncounted_before
1124
+ or hasattr(backend,'forward_counters') and not getattr(backend,'stats_available',False)):
1125
+ detail['usage_estimated']=True
1126
+ warning='HF count unavailable: usage estimated'
1127
+ detail['backend_warnings']=[w for m in meters for w in m.get('warnings',[])]
1128
+ if any(m.get('cached_unknown') for m in meters):detail['usage_estimated']=True
1129
+ submitted=sum(m['engine_submitted'] for m in meters)
1130
+ cached=sum(m['cached'] for m in meters)
1131
+ generated=sum(m.get('engine_generated',m['output']) for m in meters)
1132
+ engine_input=sum(m.get('engine_input',m['engine_submitted']-m['cached']) for m in meters)
1133
+ if detail.get('usage_estimated'):
1134
+ engine_input=max(engine_input,submitted)+c['generation']['max_new_tokens']
1135
+ generated+=c['generation']['max_new_tokens']
1136
+ detail['input_tokens_details']=dict(cached_tokens=cached,engine_submitted_tokens=submitted,
1137
+ engine_calls=sum(m.get('engine_calls',0) for m in meters),engine_generated_tokens=generated,
1138
+ cached_tokens_known=not any(m.get('cached_unknown') for m in meters),
1139
+ generate_nonempty_calls=sum(m.get('generate_nonempty_calls',0) for m in meters),
1140
+ hf_prefill_tokens=sum(m.get('hf_prefill_tokens',0) for m in meters))
1141
+ detail['usage_bases']={
1142
+ 'engine':dict(input_tokens=engine_input,output_tokens=generated),
1143
+ 'each_once':dict(input_tokens=detail['prompt_tokens'],output_tokens=detail['completion_tokens']),
1144
+ 'submitted':dict(input_tokens=submitted,output_tokens=detail['completion_tokens'])}
1145
+ if detail.get('usage_estimated'):
1146
+ for values in detail['usage_bases'].values():
1147
+ values['input_tokens']=max(values['input_tokens'],engine_input)
1148
+ values['output_tokens']=max(values['output_tokens'],generated)
1149
+ detail['accounting']=c.get('usage',{}).get('basis','engine')
1150
+ detail['kept_prompt_tokens_max'] = detail.get('kept_prompt_tokens', 0)
1151
+ detail['total_tokens'] = detail['prompt_tokens'] + detail['completion_tokens']
1152
+ detail['prediction'] = max(labels, key=probs.get)
1153
+ request_type = task['question']['request_type']
1154
+ if qtype == 'noul':
1155
+ style = c.get('interface', {}).get('boolean_keys', 'request')
1156
+ if style == 'true_false' or (style == 'request' and request_type == 'boolean'):
1157
+ probs = {'false':probs['no'], 'true':probs['yes']}
1158
+ answer = {'type':request_type, 'probabilities':probs}
1159
+ if qtype == 'noul': answer['noul'] = float(probs.get('yes', probs.get('true')))
1160
+ elif qtype == 'choice': answer['choice'] = detail['prediction']
1161
+ if warning:
1162
+ answer['warning'] = warning
1163
+ return answer, detail
1164
+
1165
+ def answer(self, payload):
1166
+ if self.is_dead():
1167
+ raise ServiceFailure('service failure')
1168
+ tasks = normalize_request(payload, self.config['server']['max_questions'])
1169
+ answers, details, errors, noul, choice = {}, {}, {}, {}, {}
1170
+ for name, task in tasks:
1171
+ answer, detail = self._one(task)
1172
+ answers[name], details[name] = answer, detail
1173
+ if 'warning' in answer:
1174
+ errors[name] = answer['warning']
1175
+ if task['question']['type'] == 'noul':
1176
+ noul[name] = answer['probabilities'].get('true', answer['probabilities'].get('yes'))
1177
+ else:
1178
+ choice[name] = detail['prediction']
1179
+ basis=self.config.get('usage',{}).get('basis','engine')
1180
+ alternatives={b:{k:sum(d['usage_bases'][b][k] for d in details.values())
1181
+ for k in ('input_tokens','output_tokens')} for b in ('engine','each_once','submitted')}
1182
+ p,o=alternatives[basis]['input_tokens'],alternatives[basis]['output_tokens']
1183
+ token_details={k:sum(d['input_tokens_details'][k] for d in details.values()) for k in
1184
+ ('cached_tokens','engine_submitted_tokens','engine_calls','engine_generated_tokens','generate_nonempty_calls','hf_prefill_tokens')}
1185
+ token_details['cached_tokens_known']=all(d['input_tokens_details']['cached_tokens_known'] for d in details.values())
1186
+ result={'answers':answers,'model':self.config['model']['name'],
1187
+ 'usage':dict(input_tokens=p,output_tokens=o,prompt_tokens=p,completion_tokens=o,total_tokens=p+o,
1188
+ cost_fuse=next(reversed(details.values()))['cost_fuse'],
1189
+ accounting=basis,usage_estimated=any(d.get('usage_estimated',False) for d in details.values()),
1190
+ alternatives=alternatives,questions=details,input_tokens_details=token_details,
1191
+ prompt_tokens_details=token_details,token_accounting=basis)}
1192
+ for key, values in (('noul',noul),('choice',choice)):
1193
+ if values:
1194
+ result[key] = next(iter(values.values())) if len(tasks) == 1 else values
1195
+ if errors:
1196
+ result['partial_errors'] = errors
1197
+ return result
service/fit_report.json ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adapter_files": {
3
+ "adapter_config.json": {
4
+ "sha256": "140bb00c832d117f57d29650dabe50e992b2e5064e908aaa29230b3ce19ca2c4",
5
+ "size": 22953
6
+ },
7
+ "adapter_model.safetensors": {
8
+ "sha256": "2b9568998ffed5d133223ffdff03056be297285c643122db4d45365d0bd86970",
9
+ "size": 494595208
10
+ }
11
+ },
12
+ "arm": "trained",
13
+ "complete": true,
14
+ "config_sha256": "5cebe511ef103d5104539d3ffa34435274c6a3bc08dbf40d5f80f9ec0ce3455a",
15
+ "draft": 256,
16
+ "elapsed_seconds": 0.0,
17
+ "engine_lora": {
18
+ "lora_dtype": "torch.bfloat16",
19
+ "max_lora_rank": 16,
20
+ "max_loras": 1
21
+ },
22
+ "fast_rows_source": {
23
+ "fit_report_sha256": "252cb2baf2ecb21a7892cb34b9334c1bad5f59fe7b924dd7f868addf3f27081e",
24
+ "protocol": "adapter_arm_31b_v1",
25
+ "records_sha256": "5e118133ce686b2d12b8d8a58b6e68560221c106eec54438dcfe1110e7ecfe7e",
26
+ "role": "q0 public231 fast-only collection of this arm",
27
+ "service_lock_sha256": "50a4c56c5c9fbfbde9c127ad0247736fa16355bc8deae90b5311f3ba19a03d44"
28
+ },
29
+ "fit_protocol": "31b-adapter-arm-v3-routed",
30
+ "input_budget": 6653,
31
+ "isolation_sha256": "802bfec07914c4c10dbdd35fdfdced0ef88945b06717252038da35b0b0d9bf99",
32
+ "model_sha256": "ddb0a8178194de6d3a3ed6e5a00ea01d6d89c143d43d169a68ad9561636cf4ff",
33
+ "n": 231,
34
+ "noul_constant": {
35
+ "applied_to": [
36
+ "fast",
37
+ "slow",
38
+ "fallback"
39
+ ],
40
+ "fitted_on": "the 74 yes/no items of the 231 public items, in-sample fast fit",
41
+ "fitted_values": {
42
+ "fallback": 0.9459459459459459,
43
+ "fast": 0.9459459459459459,
44
+ "slow": 0.9459459459459459
45
+ },
46
+ "outputs": [
47
+ 0.898,
48
+ 0.102
49
+ ],
50
+ "params_sha256": "b28ed73bb5a36e99a280a47a6b846e0c5b2de54f34a96efa4a10754b71ea7573",
51
+ "provenance": {
52
+ "development": {
53
+ "accuracy": 0.897815362931642,
54
+ "n": 1419,
55
+ "standard_error": 0.010166783957967924,
56
+ "standard_error_kind": "clustered by source group (663 groups)"
57
+ },
58
+ "result_sha256": "6cd7105b25050ece8ab7e99cd437db38368f6169bb452908785e413a3cdbe26a",
59
+ "rule": "Rule (v3, own development data only): the yes/no accuracy of our own held-out development data, rounded to 3 decimals. No leaderboard result of any kind was used for this constant.",
60
+ "rule_sha256": "be93d0ba00dd1a268f8722955111622ab71f0035ba66c60db86c4e88d35f0dbc"
61
+ },
62
+ "rule": "Rule (v3, own development data only): the yes/no accuracy of our own held-out development data, rounded to 3 decimals. No leaderboard result of any kind was used for this constant.",
63
+ "value": 0.898
64
+ },
65
+ "numeric_config": {
66
+ "NVIDIA_TF32_OVERRIDE": "0",
67
+ "VLLM_BATCH_INVARIANT": "1",
68
+ "cuda_matmul_allow_tf32": false,
69
+ "cudnn_allow_tf32": false,
70
+ "dtype": "bfloat16",
71
+ "engine_lora": {
72
+ "lora_dtype": "torch.bfloat16",
73
+ "max_lora_rank": 16,
74
+ "max_loras": 1
75
+ },
76
+ "postload_verified": {
77
+ "NVIDIA_TF32_OVERRIDE": "0",
78
+ "TORCH_ALLOW_TF32_CUBLAS_OVERRIDE": null,
79
+ "cuda_matmul_allow_tf32": false,
80
+ "cudnn_allow_tf32": false,
81
+ "float32_matmul_precision": "highest"
82
+ },
83
+ "process_environment": {
84
+ "DO_NOT_TRACK": "1",
85
+ "HF_DATASETS_OFFLINE": "1",
86
+ "HF_HUB_DISABLE_TELEMETRY": "1",
87
+ "HF_HUB_OFFLINE": "1",
88
+ "NVIDIA_TF32_OVERRIDE": "0",
89
+ "TRANSFORMERS_OFFLINE": "1",
90
+ "VLLM_BATCH_INVARIANT": "1",
91
+ "VLLM_NO_USAGE_STATS": "1",
92
+ "VLLM_USE_FLASHINFER_SAMPLER": "0",
93
+ "VLLM_WORKER_MULTIPROC_METHOD": "spawn"
94
+ },
95
+ "startup": {
96
+ "applied_environment": {
97
+ "DO_NOT_TRACK": "1",
98
+ "PYTHONUNBUFFERED": "1",
99
+ "VLLM_HOST_IP": "127.0.0.1",
100
+ "VLLM_NO_USAGE_STATS": "1",
101
+ "VLLM_USE_FLASHINFER_SAMPLER": "0"
102
+ },
103
+ "removed_attention_environment": {
104
+ "JEV_ATTENTION_BACKEND": null,
105
+ "VLLM_ATTENTION_BACKEND": null
106
+ },
107
+ "startup_environment_policy": {
108
+ "operator_cache_environment_preserved": true,
109
+ "removed_attention_variables": [
110
+ "VLLM_ATTENTION_BACKEND",
111
+ "JEV_ATTENTION_BACKEND"
112
+ ],
113
+ "reused_function": "child_environment",
114
+ "source": "reference launch.py",
115
+ "source_sha256": "2136bcb3330a80ad3f7e73ee5c99633a41e26fa922f5c8aef6f7df696fb7ce11"
116
+ }
117
+ },
118
+ "torch_version": "2.13.0+cu130",
119
+ "versions": {
120
+ "compressed-tensors": "0.17.0",
121
+ "tokenizers": "0.23.2",
122
+ "torch": "2.13.0+cu130",
123
+ "transformers": "5.17.0",
124
+ "triton": "3.7.1",
125
+ "vllm": "0.31.0"
126
+ }
127
+ },
128
+ "phases": [
129
+ "q0 public231 fast-only rows of the arm (reused, hash-bound)",
130
+ "offline routed fit of the v3 slow setting",
131
+ "v3 yes/no constant in the fast, slow and fallback blocks"
132
+ ],
133
+ "protocol": "adapter_arm_31b_v3",
134
+ "public_data_sha256": "d7ac378b817b9c32c2128a83bb8699f5770439503f7204c79572e2749319b5d4",
135
+ "public_fit_only": true,
136
+ "public_identity": {
137
+ "contains_public_benchmark": true,
138
+ "contains_sealed_benchmark": false,
139
+ "data_sha256": "8e7b2fec1275413649d2894670cbe3ea4a734c768a01ff833697123b2b44562c",
140
+ "n": 231,
141
+ "order_sha256": "3cac6d401ca18059628f0f48c8d7f974cbbea020f39f28109eaf84bd1a0f22e1",
142
+ "origin": "public231_crlf",
143
+ "public_sha256": {
144
+ "easy.jsonl": "314cd493a4c080fca5ad90e1c8a7c2b3f24137e66dc06f8fceaf51ea54f38467",
145
+ "hard.jsonl": "8c8f1efb5a04b7016f9c8cd8a70a485f58a70a918aac3e3c1107f41172ca91b6",
146
+ "original.jsonl": "b2abe9ac8cabd953a3bc452abd4b229da42ca6d0e647bf93f7638076f42db19f"
147
+ },
148
+ "public_source_verified": true,
149
+ "role": "service_fit_public",
150
+ "usage_scope": "service_fit_only"
151
+ },
152
+ "public_selection_score_exported": false,
153
+ "q": 0.056,
154
+ "records_sha256": "5e118133ce686b2d12b8d8a58b6e68560221c106eec54438dcfe1110e7ecfe7e",
155
+ "role": "service_fit_public",
156
+ "routing": {
157
+ "cost_fuse": {
158
+ "arm": 0.9,
159
+ "enabled": true,
160
+ "release": 0.85,
161
+ "warmup": 100
162
+ },
163
+ "excluded_types": [],
164
+ "mode": "routed",
165
+ "quota": 0.056,
166
+ "slow_draft": {
167
+ "max_new_tokens": 256,
168
+ "reasoning_words": 160
169
+ },
170
+ "slow_max_prompt_tokens": 840,
171
+ "slow_share_fuse": 0.2,
172
+ "target_slow_share": 0.056,
173
+ "threshold_rule": "bench.calibrate_margin(public231 rows that can draft, ceil(q x 231) of them)",
174
+ "threshold_rule_name": "eligible_items",
175
+ "unparsed_policy": "force_answer"
176
+ },
177
+ "selection_sha256": "621fde9159b24e773298511418d3373dcd2da45bed535327c5be01facf218994",
178
+ "serving_identity_sha256": "0513d121db967ec1a6aaccf627d3c42149d092dd027298da5b53684b030821ea",
179
+ "slow_calibration": {
180
+ "by_type": {
181
+ "choice": {
182
+ "fast_value": 1.2968395546510096,
183
+ "inherited": true,
184
+ "slow_samples": 0,
185
+ "value": 1.2968395546510096
186
+ },
187
+ "noul": {
188
+ "constant": "service parameter",
189
+ "fast_value": 0.898,
190
+ "fitted_fast_value": 0.9459459459459459,
191
+ "inherited": true,
192
+ "slow_samples": 0,
193
+ "value": 0.898
194
+ },
195
+ "score": {
196
+ "fast_value": 1.189207115002721,
197
+ "inherited": true,
198
+ "slow_samples": 0,
199
+ "value": 1.189207115002721
200
+ }
201
+ },
202
+ "routed_collection": {
203
+ "calibration_routed_quota": 0.1,
204
+ "collected": false,
205
+ "max_possible_slow_samples": 23,
206
+ "reason": "at the calibration-routed quota 0.10 the quota counter admits at most 23 of 231 public decisions to the slow path, fewer than 30 for any type, so every slow type inherits the fast parameter whatever such a collection shows; it is therefore not run"
207
+ },
208
+ "rule": "slow samples of a type < 30: the fast parameter of that type (temperature or yes/no confidence) is copied unchanged"
209
+ },
210
+ "source_code_sha256": "51269609aa077981a1a5e0bc5775a3d3aad5c1df04f6329b4c199fa2c5477d05",
211
+ "threshold": {
212
+ "achieved": 0.05627705627705628,
213
+ "eligible_below": 13,
214
+ "eligible_n": 181,
215
+ "n": 231,
216
+ "rule": "eligible_items",
217
+ "target": 0.056,
218
+ "threshold": 2.250016468674351
219
+ },
220
+ "v3_params": {
221
+ "cost_fuse_arm": 0.9,
222
+ "cost_fuse_release": 0.85,
223
+ "draft_max_new_tokens": 256,
224
+ "draft_reasoning_words": 160,
225
+ "exclude_noul": false,
226
+ "noul_p_cal": 0.898,
227
+ "noul_p_cal_provenance": {
228
+ "development": {
229
+ "accuracy": 0.897815362931642,
230
+ "n": 1419,
231
+ "standard_error": 0.010166783957967924,
232
+ "standard_error_kind": "clustered by source group (663 groups)"
233
+ },
234
+ "result_sha256": "6cd7105b25050ece8ab7e99cd437db38368f6169bb452908785e413a3cdbe26a",
235
+ "rule": "Rule (v3, own development data only): the yes/no accuracy of our own held-out development data, rounded to 3 decimals. No leaderboard result of any kind was used for this constant.",
236
+ "rule_sha256": "be93d0ba00dd1a268f8722955111622ab71f0035ba66c60db86c4e88d35f0dbc"
237
+ },
238
+ "schema": "adapter_arm_31b_v3_params_v1",
239
+ "slow_max_prompt_tokens": 840,
240
+ "slow_quota": 0.056,
241
+ "threshold_rule": "eligible_items",
242
+ "unparsed_policy": "force_answer"
243
+ },
244
+ "v3_params_sha256": "b28ed73bb5a36e99a280a47a6b846e0c5b2de54f34a96efa4a10754b71ea7573",
245
+ "variant": "key_value_aligned"
246
+ }
service/item_deadline.py ADDED
@@ -0,0 +1,551 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Per-decision deadline for the drafting pass, plus two item rules (installed by server.py when
2
+ config.json has an `item_deadline` block, with the `item_rules` block; no package file is modified at
3
+ runtime).
4
+
5
+ One hard deadline per request (the harness sends one decision per request): the moment the service
6
+ starts answering the request + item_seconds. The draft is generated greedily in chunks of at most
7
+ `chunk_tokens`; each chunk is sized by a conservative speed model (a short synthetic warm-up at
8
+ startup, updated online) so that it ends before deadline - response reserve - readout reserve.
9
+ No engine call is abandoned in flight, so every call is metered exactly, and the model lock is
10
+ released when the decision returns.
11
+ - Draft budget used up without the answer marker: the marker is appended, exactly as when
12
+ max_new_tokens is reached.
13
+ - Readout that cannot finish before the deadline: the first-pass answer is returned (route
14
+ slow_timeout_fast) and the decision's usage is reconciled exactly from its meters.
15
+ - An answer marker that straddles a chunk boundary ends the draft at the token that completes it,
16
+ where one-shot generation would have stopped.
17
+ Decisions that do not take the drafting pass are unaffected by the deadline.
18
+
19
+ Item rules (wrap_service):
20
+ - slow_types: question types that may take the drafting pass (None = all). For any other type the
21
+ routing threshold is 0 for that decision: it never requests the drafting pass and the quota
22
+ counter never counts it.
23
+ - reread_tau: a choice decision answered in one pass although its length allowed the drafting pass
24
+ (budget tier not 'long', not a long item), whose first-pass margin is below tau, is read once
25
+ more, with the options in reverse order (each label keeps its criterion); the two answer
26
+ distributions are averaged by label. The second read is metered into the
27
+ decision's usage (so the cost fuse sees it) and is not a quota decision. If it fails (not an
28
+ engine failure) the first-pass answer is kept and `reread_failed` is recorded.
29
+ Controller.truncated_readout is a diagnostic used only by offline measurement; serving never calls it.
30
+ """
31
+ import math
32
+ import re
33
+ import threading
34
+ import time
35
+ from contextlib import contextmanager
36
+
37
+ DEFAULTS = dict(item_seconds=9.0, response_reserve_s=0.35, readout_reserve_init_s=1.0,
38
+ readout_reserve_min_s=0.3, readout_safety=1.5, chunk_tokens=32, min_chunk_tokens=8,
39
+ token_safety=1.25, ewma_alpha=0.3, warmup_tokens=33, warmup_repeats=3)
40
+
41
+ _local = threading.local()
42
+
43
+
44
+ def need(ok, code):
45
+ if not ok:
46
+ raise RuntimeError(code)
47
+
48
+
49
+ def settings_from(values=None):
50
+ s = dict(DEFAULTS)
51
+ s.update(values or {})
52
+ need(set(s) == set(DEFAULTS), 'DEADLINE_SETTINGS_KEYS')
53
+ for key in ('item_seconds', 'response_reserve_s', 'readout_reserve_init_s', 'readout_reserve_min_s',
54
+ 'readout_safety', 'token_safety', 'ewma_alpha'):
55
+ x = s[key]
56
+ need(isinstance(x, (int, float)) and not isinstance(x, bool) and math.isfinite(x) and x > 0,
57
+ 'DEADLINE_SETTING_' + key.upper())
58
+ for key in ('chunk_tokens', 'min_chunk_tokens', 'warmup_tokens', 'warmup_repeats'):
59
+ need(type(s[key]) is int and s[key] > 0, 'DEADLINE_SETTING_' + key.upper())
60
+ need(s['token_safety'] >= 1 and s['readout_safety'] >= 1 and s['ewma_alpha'] <= 1, 'DEADLINE_SAFETY')
61
+ need(s['min_chunk_tokens'] <= s['chunk_tokens'] and s['warmup_tokens'] >= 2 and s['warmup_repeats'] >= 2,
62
+ 'DEADLINE_TOKEN_LIMITS')
63
+ need(s['item_seconds'] > s['response_reserve_s'] + s['readout_reserve_init_s'], 'DEADLINE_RESERVES')
64
+ return s
65
+
66
+
67
+ class SpeedModel:
68
+ """Conservative chunk planner: seconds(n) = call_s + n * token_s * token_safety."""
69
+
70
+ def __init__(self, token_s, call_s, settings):
71
+ need(all(isinstance(x, float) and math.isfinite(x) and x > 0 for x in (token_s, call_s)),
72
+ 'DEADLINE_SPEED')
73
+ self.s = settings
74
+ self.lock = threading.Lock()
75
+ self.warm_token_s = token_s
76
+ self.token_s = token_s
77
+ self.call_s = call_s
78
+ self.ewma = None
79
+ self.readout = {}
80
+ self.chunks_observed = 0
81
+
82
+ def fit_tokens(self, seconds):
83
+ if not math.isfinite(seconds) or seconds <= self.call_s:
84
+ return 0
85
+ return int(math.floor((seconds - self.call_s) / (self.token_s * self.s['token_safety'])))
86
+
87
+ def observe_chunk(self, generated, seconds):
88
+ if generated < 4 or not math.isfinite(seconds) or seconds <= 0:
89
+ return
90
+ per = max(0., seconds - self.call_s) / generated
91
+ with self.lock:
92
+ a = self.s['ewma_alpha']
93
+ self.ewma = per if self.ewma is None else a * per + (1 - a) * self.ewma
94
+ self.token_s = max(self.warm_token_s, self.ewma)
95
+ self.chunks_observed += 1
96
+
97
+ def readout_reserve(self, kind):
98
+ v = self.readout.get(kind)
99
+ if v is None:
100
+ return self.s['readout_reserve_init_s']
101
+ return max(self.s['readout_reserve_min_s'], self.s['readout_safety'] * v)
102
+
103
+ def observe_readout(self, kind, seconds):
104
+ if not math.isfinite(seconds) or seconds < 0:
105
+ return
106
+ with self.lock:
107
+ a = self.s['ewma_alpha']
108
+ old = self.readout.get(kind)
109
+ self.readout[kind] = seconds if old is None else a * seconds + (1 - a) * old
110
+
111
+ def snapshot(self):
112
+ return dict(token_s=self.token_s, warm_token_s=self.warm_token_s, call_s=self.call_s,
113
+ chunks_observed=self.chunks_observed, readout={k: v for k, v in sorted(self.readout.items())})
114
+
115
+
116
+ class ItemPlan:
117
+ def __init__(self, controller, start, max_new_tokens, kind):
118
+ s = controller.settings
119
+ self.c = controller
120
+ self.start = start
121
+ self.kind = kind
122
+ self.max_new_tokens = max_new_tokens
123
+ self.hard = start + s['item_seconds']
124
+ self.meter_deadline = self.hard - s['response_reserve_s']
125
+ self.meters = []
126
+ self.chunks = []
127
+ self.budget_stop = False
128
+ self.straddle = False
129
+ self.forced = False
130
+ self.draft_tokens = 0
131
+ self.slow_started = None
132
+ self.draft_end_at = None
133
+ self.readout_start = None
134
+ self.readout_s = None
135
+ self.timeout_phase = None
136
+ self.reconciled = False
137
+ # draft bookkeeping (memory only; never serialised)
138
+ self._prompt_ids = None
139
+ self._draft_ids = None
140
+ self._by_marker = False
141
+ self._groups = None
142
+ self._config = None
143
+ self._slow_meter = None
144
+
145
+ def draft_deadline(self):
146
+ return self.meter_deadline - self.c.speed.readout_reserve(self.kind)
147
+
148
+ def exact(self):
149
+ return all(not m.get('engine_usage_unknown') and not m.get('cached_unknown') for m in self.meters)
150
+
151
+ def audit(self, now):
152
+ def rel(t):
153
+ return None if t is None else round(t - self.start, 6)
154
+ speed = self.c.speed
155
+ return dict(schema='item-deadline-v1', item_seconds=self.c.settings['item_seconds'], elapsed_s=rel(now),
156
+ slow_started_s=rel(self.slow_started), draft_end_s=rel(self.draft_end_at),
157
+ readout_s=None if self.readout_s is None else round(self.readout_s, 6),
158
+ draft_tokens=self.draft_tokens, chunks=len(self.chunks),
159
+ chunk_tokens=[c['generated'] for c in self.chunks][:64],
160
+ chunk_seconds=[round(c['seconds'], 6) for c in self.chunks][:64],
161
+ chunk_end_s=[round(c['end'] - self.start, 6) for c in self.chunks][:64],
162
+ chunk_cum_tokens=[c['cum_tokens'] for c in self.chunks][:64],
163
+ chunk_cum_input=[c['cum_input'] for c in self.chunks][:64],
164
+ chunk_cum_generated=[c['cum_generated'] for c in self.chunks][:64],
165
+ draft_by_marker=bool(self._by_marker),
166
+ budget_stop=self.budget_stop, straddle=self.straddle, forced=self.forced,
167
+ timeout_phase=self.timeout_phase, reconciled=self.reconciled, exact=self.exact(),
168
+ max_new_tokens=self.max_new_tokens, token_s=round(speed.token_s, 6),
169
+ call_s=round(speed.call_s, 6), readout_reserve_s=round(speed.readout_reserve(self.kind), 6))
170
+
171
+
172
+ def reconcile(detail, meters):
173
+ """Usage arithmetic of core.py _one_impl, without the timeout estimate."""
174
+ submitted = sum(m['engine_submitted'] for m in meters)
175
+ cached = sum(m['cached'] for m in meters)
176
+ generated = sum(m.get('engine_generated', m['output']) for m in meters)
177
+ engine_input = sum(m.get('engine_input', m['engine_submitted'] - m['cached']) for m in meters)
178
+ detail['usage_estimated'] = False
179
+ detail['input_tokens_details'].update(
180
+ cached_tokens=cached, engine_submitted_tokens=submitted, engine_generated_tokens=generated,
181
+ engine_calls=sum(m.get('engine_calls', 0) for m in meters), cached_tokens_known=True)
182
+ detail['usage_bases'] = {
183
+ 'engine': dict(input_tokens=engine_input, output_tokens=generated),
184
+ 'each_once': dict(input_tokens=detail['prompt_tokens'], output_tokens=detail['completion_tokens']),
185
+ 'submitted': dict(input_tokens=submitted, output_tokens=detail['completion_tokens'])}
186
+
187
+
188
+ def marker_completion(decode, previous, new, marker):
189
+ """Smallest j such that the marker is complete in decode(previous+new[:j]); None if absent."""
190
+ if marker not in decode(list(previous) + list(new)):
191
+ return None
192
+ for j in range(1, len(new) + 1):
193
+ if marker in decode(list(previous) + list(new[:j])):
194
+ return j
195
+ return None
196
+
197
+
198
+ @contextmanager
199
+ def request_clock(start=None):
200
+ previous = getattr(_local, 'request_start', None)
201
+ _local.request_start = time.monotonic() if start is None else start
202
+ try:
203
+ yield
204
+ finally:
205
+ _local.request_start = previous
206
+
207
+
208
+ def active_plan():
209
+ return getattr(_local, 'plan', None)
210
+
211
+
212
+ def _install_core(core):
213
+ if getattr(core, '_deadline_core', None):
214
+ return
215
+ original_bounded = core.bounded_slow_attempt
216
+ original_new_meter = core.new_meter
217
+
218
+ def bounded_slow_attempt(backend, ids, groups, config, meter, model_lock, *, on_service_failure=None):
219
+ plan = getattr(_local, 'plan', None)
220
+ if plan is None:
221
+ return original_bounded(backend, ids, groups, config, meter, model_lock,
222
+ on_service_failure=on_service_failure)
223
+ return plan.c.bounded(plan, backend, ids, groups, config, meter, model_lock, on_service_failure)
224
+
225
+ def new_meter():
226
+ m = original_new_meter()
227
+ plan = getattr(_local, 'plan', None)
228
+ if plan is not None:
229
+ plan.meters.append(m)
230
+ return m
231
+
232
+ core.bounded_slow_attempt = bounded_slow_attempt
233
+ core.new_meter = new_meter
234
+ core._deadline_core = dict(bounded_slow_attempt=original_bounded, new_meter=original_new_meter)
235
+
236
+
237
+ def _install_backend(backend):
238
+ if getattr(backend, '_deadline_backend', None):
239
+ return
240
+ original = backend.slow_session
241
+
242
+ def slow_session(ids, unparsed_policy, meter, max_new_tokens=None):
243
+ plan = getattr(_local, 'plan', None)
244
+ if plan is None:
245
+ return original(ids, unparsed_policy, meter, max_new_tokens=max_new_tokens)
246
+ need(plan.c.backend is backend, 'DEADLINE_BACKEND_MISMATCH')
247
+ return plan.c.chunked_session(plan, ids, unparsed_policy, meter, max_new_tokens)
248
+
249
+ backend.slow_session = slow_session
250
+ backend._deadline_backend = dict(slow_session=original)
251
+
252
+
253
+ def measure_speed(backend, settings, system_prompt, clock=time.monotonic):
254
+ """Synthetic prompt only. Paired 1/N exact-length greedy calls; conservative maxima."""
255
+ g = backend.generation
256
+ ids = backend.prompt_ids(system_prompt, 'Ready?', False)
257
+ n_tokens = settings['warmup_tokens']
258
+ pairs = []
259
+
260
+ def run(n):
261
+ params = backend.SamplingParams(max_tokens=n, min_tokens=n, temperature=0.0, top_p=1.0, top_k=-1,
262
+ repetition_penalty=g['repetition_penalty'], seed=g['seed'],
263
+ detokenize=False)
264
+ t0 = clock()
265
+ out = backend._completion(ids, params, None, 'draft')
266
+ dt = clock() - t0
267
+ need(len(out.token_ids) == n, 'DEADLINE_WARMUP_LENGTH')
268
+ return dt
269
+
270
+ for _ in range(settings['warmup_repeats']):
271
+ one = run(1)
272
+ many = run(n_tokens)
273
+ pairs.append((one, many))
274
+ call_s = float(max(x for x, _ in pairs))
275
+ token_s = float(max(max((y - x) / (n_tokens - 1), y / n_tokens) for x, y in pairs))
276
+ token_s = max(token_s, 1e-6)
277
+ call_s = max(call_s, 1e-6)
278
+ return SpeedModel(token_s, call_s, settings), dict(pairs_s=pairs, tokens=n_tokens)
279
+
280
+
281
+ class Controller:
282
+ def __init__(self, core, backends, backend, settings, speed, clock=time.monotonic, warmup=None):
283
+ self.core, self.backends, self.backend = core, backends, backend
284
+ self.settings, self.speed, self.clock = settings, speed, clock
285
+ self.warmup = warmup
286
+
287
+ # ---- per item ---------------------------------------------------------------------------
288
+ def slow_attempt(self, backend, ids, groups, config, meter):
289
+ """core.slow_attempt semantics (same exception mapping), except that the deadline
290
+ path skips release_failed_session's gc.collect(): on vLLM there is no allocator state to
291
+ clear, and a full collection would only delay the fast answer past the item cap."""
292
+ core = self.core
293
+ failed = None
294
+ try:
295
+ return core.slow_readout(backend, ids, groups, config, meter)
296
+ except TimeoutError:
297
+ failed = 'timeout'
298
+ except Exception as exc:
299
+ if core.unrecoverable_engine_error(exc):
300
+ raise core.ServiceFailure('unrecoverable engine failure') from exc
301
+ failed = 'error'
302
+ if failed == 'timeout':
303
+ raise TimeoutError('Slow deadline exceeded')
304
+ core.release_failed_session(backend)
305
+ raise RuntimeError('Slow session failed')
306
+
307
+ def bounded(self, plan, backend, ids, groups, config, meter, model_lock, on_service_failure):
308
+ core = self.core
309
+ plan._groups, plan._config, plan._slow_meter = groups, config, meter
310
+ meter['deadline'] = plan.meter_deadline
311
+ plan.slow_started = self.clock()
312
+ try:
313
+ with model_lock:
314
+ core.check_deadline(meter)
315
+ value = self.slow_attempt(backend, ids, groups, config, meter)
316
+ core.check_deadline(meter)
317
+ if plan.readout_start is not None:
318
+ plan.readout_s = self.clock() - plan.readout_start
319
+ self.speed.observe_readout(plan.kind, plan.readout_s)
320
+ return value
321
+ except Exception as exc:
322
+ if core.unrecoverable_engine_error(exc) and on_service_failure is not None:
323
+ on_service_failure()
324
+ if isinstance(exc, TimeoutError):
325
+ plan.timeout_phase = 'readout' if plan.readout_start is not None else 'draft'
326
+ raise
327
+
328
+ def chunked_session(self, plan, ids, unparsed_policy, meter, max_new_tokens=None):
329
+ backend, s, speed = self.backend, self.settings, self.speed
330
+ g = backend.generation
331
+ need(g['do_sample'] is False and g['repetition_penalty'] == 1.0, 'DEADLINE_CHUNKS_REQUIRE_GREEDY')
332
+ count = self.backends._max_new_tokens(max_new_tokens or plan.max_new_tokens)
333
+ marker = backend.marker
334
+ prompt = list(ids)
335
+ gen = []
336
+ by_marker = False
337
+ counted_prompt = False
338
+ while len(gen) < count:
339
+ now = self.clock()
340
+ if now >= plan.meter_deadline:
341
+ raise TimeoutError('item deadline before chunk')
342
+ left = count - len(gen)
343
+ n = min(s['chunk_tokens'], left, speed.fit_tokens(plan.draft_deadline() - now))
344
+ if n < min(s['min_chunk_tokens'], left):
345
+ if not gen:
346
+ raise TimeoutError('no draft budget')
347
+ plan.budget_stop = True
348
+ break
349
+ params = backend.SamplingParams(max_tokens=n, temperature=0.0, top_p=1.0, top_k=-1,
350
+ repetition_penalty=g['repetition_penalty'], seed=g['seed'],
351
+ stop=[marker], include_stop_str_in_output=True)
352
+ if not counted_prompt:
353
+ meter['prompt'] += len(prompt) # the backend's slow_session counts the prompt once
354
+ counted_prompt = True
355
+ t0 = self.clock()
356
+ out = backend._completion(prompt + gen, params, meter, 'draft')
357
+ t1 = self.clock()
358
+ dt = t1 - t0
359
+ new = list(out.token_ids)
360
+ speed.observe_chunk(len(new), dt)
361
+ plan.chunks.append(dict(n=n, generated=len(new), seconds=dt, end=t1, cum_tokens=len(gen) + len(new),
362
+ cum_input=int(meter.get('engine_input', 0)),
363
+ cum_generated=int(meter.get('engine_generated', 0))))
364
+ if getattr(out, 'stop_reason', None) == marker and getattr(out, 'finish_reason', None) == 'stop':
365
+ gen += new
366
+ by_marker = True
367
+ break
368
+ j = marker_completion(backend.decode, gen, new, marker)
369
+ if j is not None:
370
+ # One-shot generation would have stopped at the token completing the marker. The
371
+ # extra tokens of this chunk were generated and stay metered (exact cost).
372
+ gen += new[:j]
373
+ by_marker = True
374
+ plan.straddle = True
375
+ break
376
+ gen += new
377
+ if getattr(out, 'finish_reason', None) == 'stop' or len(new) < n:
378
+ break # EOS: the draft ended without the marker
379
+ plan.draft_tokens = len(gen)
380
+ plan.draft_end_at = self.clock()
381
+ plan._prompt_ids, plan._draft_ids, plan._by_marker = list(prompt), list(gen), by_marker
382
+ parsed = by_marker and bool(re.search(r'(?m)^[ \t]*' + re.escape(marker) + r'$', backend.decode(gen)))
383
+ if not parsed:
384
+ if unparsed_policy == 'fast':
385
+ return None, False
386
+ forced = backend.encode('\n' + marker)
387
+ gen += forced
388
+ meter['output'] += len(forced)
389
+ meter['forced'] += len(forced)
390
+ plan.forced = True
391
+ plan.readout_start = self.clock()
392
+ return self.backends.VLLMPrefixSession(backend, prompt + gen, meter), parsed
393
+
394
+ # ---- truncated-draft readout (diagnostic for offline measurement; not used when serving) ----
395
+ def truncated_readout(self, plan, n_tokens, labels, qtype, calibration_block):
396
+ """Readout of this item's draft cut at n_tokens (no marker within them) + the forced answer
397
+ marker, on a fresh meter: what a one-shot cap of n_tokens would have read out (greedy drafts
398
+ are prefix-identical). Returns (probabilities dict, meter, seconds). Caller checks eligibility."""
399
+ need(plan._draft_ids is not None and len(plan._draft_ids) > n_tokens, 'DEADLINE_TRUNC_NOT_LONGER')
400
+ backend, core = self.backend, self.core
401
+ forced = backend.encode('\n' + backend.marker)
402
+ meter = self.backends.new_meter()
403
+ meter['output'] += len(forced)
404
+ meter['forced'] += len(forced)
405
+ t0 = self.clock()
406
+ session = self.backends.VLLMPrefixSession(backend, plan._prompt_ids + plan._draft_ids[:n_tokens] + forced, meter)
407
+ scores, _ = core.sequence_scores(session, backend, plan._groups, meter)
408
+ probs = core.probabilities(labels, scores, qtype, calibration_block)
409
+ return probs, meter, self.clock() - t0
410
+
411
+ # ---- low-margin choice re-read ------------------------------------------------------------
412
+ def reread_reading(self, service, task):
413
+ """One fast read of a choice task with the option order reversed (label -> criterion pairs kept;
414
+ render re-letters the options), the service's 'fast' calibration. Returns (probabilities by label, meter,
415
+ seconds). The meter is created through core.new_meter, so inside an item it joins the item's meters."""
416
+ core = self.core
417
+ need(task['question']['type'] == 'choice' and task.get('labels'), 'DEADLINE_REREAD_TASK')
418
+ t2 = dict(task, labels=list(reversed(task['labels'])))
419
+ meter = core.new_meter()
420
+ t0 = self.clock()
421
+ _, (labels2, groups2, fast_ids2, _), _ = core.prepare_task(t2, self.backend, service.config)
422
+ with service.model_lock:
423
+ scores2, _, _ = core.fast_attempt_with_retry(self.backend, t2, service.config, fast_ids2, labels2, groups2, meter)
424
+ need(scores2 is not None, 'DEADLINE_REREAD_SCORES')
425
+ probs = core.probabilities(labels2, scores2, 'choice', service.calibration['fast'])
426
+ return probs, meter, self.clock() - t0
427
+
428
+ @staticmethod
429
+ def reread_due(task, detail, tau):
430
+ m = detail.get('fast_margin')
431
+ return (tau is not None and tau > 0 and task['question']['type'] == 'choice' and detail.get('path') == 'fast'
432
+ and detail.get('budget_tier') != 'long' and not detail.get('long_item')
433
+ and isinstance(m, (int, float)) and math.isfinite(m) and m < tau)
434
+
435
+ def apply_reread(self, service, task, answer_value, detail):
436
+ """Online re-read: average the fast answer with the reversed-order read; add the read to the item's
437
+ usage (every basis) and token details; record it in detail['reread']."""
438
+ probs, meter, seconds = self.reread_reading(service, task)
439
+ first = answer_value['probabilities']
440
+ need(set(first) == set(probs), 'DEADLINE_REREAD_LABELS')
441
+ mixed = {k: (first[k] + probs[k]) / 2 for k in first}
442
+ winner = max(task['labels'], key=mixed.get)
443
+ engine_input = meter.get('engine_input', meter['engine_submitted'] - meter['cached'])
444
+ generated = meter.get('engine_generated', meter['output'])
445
+ bases = detail['usage_bases']
446
+ bases['engine']['input_tokens'] += engine_input
447
+ bases['engine']['output_tokens'] += generated
448
+ bases['each_once']['input_tokens'] += meter['prompt']
449
+ bases['each_once']['output_tokens'] += meter['output']
450
+ bases['submitted']['input_tokens'] += meter['engine_submitted']
451
+ bases['submitted']['output_tokens'] += meter['output']
452
+ d = detail['input_tokens_details']
453
+ d['cached_tokens'] += meter['cached']
454
+ d['engine_submitted_tokens'] += meter['engine_submitted']
455
+ d['engine_generated_tokens'] += generated
456
+ d['engine_calls'] += meter.get('engine_calls', 0)
457
+ if meter.get('cached_unknown'):
458
+ d['cached_tokens_known'] = False
459
+ if meter.get('engine_usage_unknown') or meter.get('cached_unknown'):
460
+ detail['usage_estimated'] = True
461
+ detail['prompt_tokens'] += meter['prompt']
462
+ detail['completion_tokens'] += meter['output']
463
+ detail['total_tokens'] = detail['prompt_tokens'] + detail['completion_tokens']
464
+ detail['readout_output_tokens'] += meter['probes']
465
+ detail['prediction'] = winner
466
+ detail['reread'] = dict(input_tokens=int(engine_input), output_tokens=int(generated), seconds=seconds,
467
+ order='reversed')
468
+ answer_value = dict(answer_value, probabilities=mixed)
469
+ answer_value['choice'] = winner
470
+ return answer_value, detail
471
+
472
+ # ---- per service ------------------------------------------------------------------------
473
+ def wrap_service(self, service, slow_types=None, reread_tau=None):
474
+ """slow_types: question types allowed on the slow path (None = all); reread_tau: low-margin choice
475
+ re-read threshold (None or 0 = off)."""
476
+ need(service.backend is self.backend, 'DEADLINE_SERVICE_BACKEND')
477
+ need(not getattr(service, '_deadline_wrapped', False), 'DEADLINE_ALREADY_WRAPPED')
478
+ need(slow_types is None or (set(slow_types) <= {'choice', 'noul', 'score'} and len(slow_types) > 0), 'DEADLINE_SLOW_TYPES')
479
+ need(reread_tau is None or (isinstance(reread_tau, (int, float)) and math.isfinite(reread_tau) and reread_tau >= 0),
480
+ 'DEADLINE_REREAD_TAU')
481
+ original_one, original_answer = service._one_impl, service.answer
482
+ clock = self.clock
483
+ allowed = None if slow_types is None else frozenset(slow_types)
484
+
485
+ def answer(payload):
486
+ owned = getattr(_local, 'request_start', None) is None
487
+ if owned:
488
+ _local.request_start = clock()
489
+ try:
490
+ return original_answer(payload)
491
+ finally:
492
+ if owned:
493
+ _local.request_start = None
494
+
495
+ def one(task):
496
+ need(getattr(_local, 'plan', None) is None, 'DEADLINE_NESTED_ITEM')
497
+ start = getattr(_local, 'request_start', None)
498
+ plan = ItemPlan(self, clock() if start is None else start,
499
+ service.config['generation']['max_new_tokens'], task['question']['type'])
500
+ service._deadline_last = plan
501
+ _local.plan = plan
502
+ routing = service.config['routing']
503
+ switched_off = allowed is not None and task['question']['type'] not in allowed
504
+ saved = routing['margin_threshold']
505
+ try:
506
+ if switched_off:
507
+ routing['margin_threshold'] = 0.0 # margins are >= 0: never requests slow, never counted
508
+ try:
509
+ answer_value, detail = original_one(task)
510
+ finally:
511
+ routing['margin_threshold'] = saved
512
+ if detail.get('route') == 'slow_timeout_fast' and plan.exact():
513
+ reconcile(detail, plan.meters)
514
+ plan.reconciled = True
515
+ if self.reread_due(task, detail, reread_tau):
516
+ try:
517
+ answer_value, detail = self.apply_reread(service, task, answer_value, detail)
518
+ except Exception as exc:
519
+ if self.core.unrecoverable_engine_error(exc):
520
+ raise self.core.ServiceFailure('unrecoverable engine failure') from exc
521
+ detail['reread_failed'] = type(exc).__name__ # fast answer kept, as without re-read
522
+ detail['item_deadline'] = plan.audit(clock())
523
+ detail['item_deadline']['slow_type_off'] = switched_off
524
+ return answer_value, detail
525
+ finally:
526
+ _local.plan = None
527
+
528
+ service._one_impl = one
529
+ service.answer = answer
530
+ service._deadline_wrapped = True
531
+ service._deadline_slow_types = None if allowed is None else sorted(allowed)
532
+ service._deadline_reread_tau = reread_tau
533
+ return service
534
+
535
+
536
+ def install(core, backends, backend, settings=None, system_prompt=None, clock=time.monotonic, speed=None):
537
+ """Process-wide, idempotent dispatchers + one controller for this backend."""
538
+ s = settings_from(settings)
539
+ existing = getattr(backend, '_deadline_controller', None)
540
+ if existing is not None:
541
+ need(existing.settings == s, 'DEADLINE_SETTINGS_CHANGED')
542
+ return existing
543
+ _install_core(core)
544
+ warm = None
545
+ if speed is None:
546
+ need(isinstance(system_prompt, str), 'DEADLINE_SYSTEM_PROMPT')
547
+ speed, warm = measure_speed(backend, s, system_prompt, clock)
548
+ _install_backend(backend)
549
+ controller = Controller(core, backends, backend, s, speed, clock, warm)
550
+ backend._deadline_controller = controller
551
+ return controller
service/launch.py ADDED
@@ -0,0 +1,456 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Foreground supervisor: runtime and model checks, startup attempts, readiness, termination.
2
+
3
+ The service writes files only below --state-dir (plus shared memory in /dev/shm):
4
+ <state-dir>/<run-id>-startup/ attempts.jsonl, attempt-N.log, state-N/ (quota state), tmp/, home/
5
+ <state-dir>/cache/ compilation caches shared by runs that use the same state directory
6
+ """
7
+ import argparse
8
+ from contextlib import contextmanager
9
+ import hashlib
10
+ import importlib.metadata
11
+ import json
12
+ import os
13
+ from pathlib import Path
14
+ import re
15
+ import signal
16
+ import socket
17
+ import subprocess
18
+ import sys
19
+ import tempfile
20
+ import threading
21
+ import time
22
+ from datetime import datetime, timezone
23
+ import urllib.request
24
+
25
+ ROOT=Path(__file__).resolve().parent
26
+ IMAGE='vllm/vllm-openai@sha256:a4a4c0437bf7240089da5f08aa370c4aee17ae5290f7a3b468825ee26c4c3a6b'
27
+ # Model identity: official W4A16 CT base files + our unmerged adapter (MODEL_MANIFEST.json).
28
+ _MANIFEST=json.loads((ROOT/'MODEL_MANIFEST.json').read_text(encoding='utf-8'))
29
+ MODEL_KEY='metask-gemma4-31b-lora-w4a16'
30
+ MODEL_REVISION=_MANIFEST['repo_id']+'@'+str(_MANIFEST.get('revision') or 'unpublished')
31
+ # q=0.056: routed; the quota counter below --state-dir caps the share of slow (drafting) decisions.
32
+ SERVED_MODE='routed'
33
+ VERSIONS={'vllm':'0.31.0','torch':'2.13.0','transformers':'5.17.0','tokenizers':'0.23.2',
34
+ 'triton':'3.7.1','compressed-tensors':'0.17.0'}
35
+ MODEL_SHA256={n:v['sha256'] for n,v in _MANIFEST['files'].items() if not n.endswith('.safetensors')}
36
+ SHARD_BYTES={n:v['size'] for n,v in _MANIFEST['files'].items() if n.endswith('.safetensors')}
37
+ SHARD_SHA256={n:v['sha256'] for n,v in _MANIFEST['files'].items() if n.endswith('.safetensors')}
38
+ # Unmerged LoRA: <model>/adapter holds exactly these files; configs name it by ADAPTER_REF.
39
+ ADAPTER_REF='model-subdir:adapter'
40
+ _ADAPTER=_MANIFEST.get('adapter')
41
+ ADAPTER_SUBDIR=(_ADAPTER or {}).get('subdir') or 'adapter'
42
+ ADAPTER_FILES={n:dict(v) for n,v in ((_ADAPTER or {}).get('files') or {}).items()}
43
+ RUN_ID=re.compile(r'[A-Za-z0-9][A-Za-z0-9._-]{0,63}')
44
+ BINDS=('127.0.0.1','0.0.0.0')
45
+ STARTUP_TIMEOUT=1800
46
+ # vLLM places engine IPC sockets in TMPDIR; Unix socket paths are limited to 107 bytes.
47
+ MAX_TMP_PATH=70
48
+ # Startup attempts: vLLM's own choice (auto), then TRITON_ATTN. This checkpoint mixes attention
49
+ # head sizes 256 and 512; where FlashAttention 4 is unavailable (for example compute capability
50
+ # 8.x, the measured RTX 4090) vLLM 0.31 resolves auto to TRITON_ATTN, and FLASH_ATTN cannot start.
51
+ ATTEMPTS=(None,'TRITON_ATTN')
52
+ # Model folder entries a loader may read; among them only the pinned files are accepted.
53
+ LOADER_SUFFIXES=('.json','.safetensors','.bin','.pt','.pth','.gguf','.model','.tiktoken','.jinja','.py','.txt')
54
+ # Inherited path variables must stay inside --state-dir; unset ones default below state/cache or HOME.
55
+ PATH_VARIABLES=('XDG_CACHE_HOME','VLLM_CACHE_ROOT','TRITON_CACHE_DIR','TORCHINDUCTOR_CACHE_DIR','HF_HOME',
56
+ 'CUDA_CACHE_PATH','NUMBA_CACHE_DIR','XDG_CONFIG_HOME','XDG_DATA_HOME','XDG_STATE_HOME',
57
+ 'VLLM_CONFIG_ROOT','TRITON_HOME','FLASHINFER_WORKSPACE_BASE','TORCH_HOME','TORCH_EXTENSIONS_DIR',
58
+ 'MPLCONFIGDIR','HF_HUB_CACHE','HUGGINGFACE_HUB_CACHE','TRANSFORMERS_CACHE','HF_ASSETS_CACHE')
59
+
60
+
61
+
62
+ def check_preparation(config=None,allow_nonformal=False):
63
+ c=json.loads(Path(config or ROOT/'config.json').read_text(encoding='utf-8'))
64
+ if not c.get('package',{}).get('nonformal_fitted') or c['package'].get('sample_only'):
65
+ raise ValueError('Service fit pending; preparation placeholders cannot serve')
66
+ if not _MANIFEST.get('identity_sha256') or not _MANIFEST['files'] or not ADAPTER_FILES:
67
+ raise ValueError('Model identity pending')
68
+ if c.get('pipeline',{}).get('model_identity_sha256')!=_MANIFEST['identity_sha256']:
69
+ raise ValueError('Configuration and model manifest identities differ')
70
+ if c['model'].get('adapter')!=ADAPTER_REF:
71
+ raise ValueError('Configuration adapter reference differs from the model manifest')
72
+ if c.get('routing',{}).get('mode')!=SERVED_MODE:
73
+ raise ValueError('Configuration mode differs from the served fast-only mode')
74
+ if not allow_nonformal:
75
+ if not re.fullmatch('[0-9a-f]{40}',str(_MANIFEST.get('revision') or '')) or _MANIFEST['repo_id'].startswith('OWNER/'):
76
+ raise ValueError('The model manifest lacks the own Hugging Face repository and full revision')
77
+ if not c['package'].get('finalized') or not (ROOT/'FREEZE.json').is_file():
78
+ raise ValueError('Package has not been frozen (no FREEZE.json); use a frozen release package')
79
+
80
+
81
+ def check_adapter(model):
82
+ """Read-only check of <model>/adapter: exactly the pinned files, regular, pinned size and full SHA-256."""
83
+ if _ADAPTER is None:
84
+ return None
85
+ if not ADAPTER_FILES:
86
+ raise ValueError('Adapter identity pending: rebuild with the staged model folder')
87
+ folder=Path(model)/ADAPTER_SUBDIR
88
+ if folder.is_symlink() or not folder.is_dir():
89
+ raise ValueError(f'Missing adapter folder {ADAPTER_SUBDIR}/ in the model folder; it must hold '+', '.join(sorted(ADAPTER_FILES)))
90
+ names={p.name for p in folder.iterdir()}
91
+ if names!=set(ADAPTER_FILES):
92
+ raise ValueError(f'{ADAPTER_SUBDIR}/ must contain exactly: '+', '.join(sorted(ADAPTER_FILES)))
93
+ for name,entry in sorted(ADAPTER_FILES.items()):
94
+ path=_model_file(folder,name)
95
+ if path.stat().st_size!=entry['size'] or sha256(path)!=entry['sha256']:
96
+ raise ValueError(f'Adapter file differs from {MODEL_REVISION}: {name}')
97
+ return folder.resolve()
98
+
99
+
100
+ def materialize_adapter(config,adapter):
101
+ """Replace ADAPTER_REF by the verified adapter folder in model.adapter and the threshold identity."""
102
+ c=json.loads(json.dumps(config))
103
+ blocks=[c.get('routing',{})]+[p.get('routing',{}) for p in c.get('profiles',{}).values()]
104
+ if adapter is None:
105
+ if c['model'].get('adapter') is not None or any((p.get('model') or {}).get('adapter') is not None
106
+ for p in c.get('profiles',{}).values()):
107
+ raise ValueError('Configuration names an adapter, but this arm serves none')
108
+ return c
109
+ if c['model'].get('adapter')!=ADAPTER_REF:
110
+ raise ValueError('Adapter configuration must use the model-relative adapter reference')
111
+ value=str(adapter)
112
+ c['model']['adapter']=value
113
+ for profile in c.get('profiles',{}).values():
114
+ model=profile.get('model',{})
115
+ if 'adapter' in model:
116
+ if model['adapter']!=ADAPTER_REF:raise ValueError('Profile names a different adapter')
117
+ model['adapter']=value
118
+ for block in blocks:
119
+ fit=block.get('margin_threshold_fitted_for')
120
+ if isinstance(fit,dict) and 'adapter' in fit:
121
+ if fit['adapter']!=ADAPTER_REF:raise ValueError('Threshold was fitted for a different adapter reference')
122
+ fit['adapter']=value
123
+ return c
124
+
125
+
126
+ def write_runtime_config(startup,config):
127
+ """The materialized per-run configuration lives only below --state-dir."""
128
+ path=Path(startup)/'config.runtime.json'
129
+ with path.open('x',encoding='utf-8',newline='\n') as out:
130
+ json.dump(config,out,ensure_ascii=False,indent=2,allow_nan=False)
131
+ out.write('\n')
132
+ return path
133
+
134
+
135
+ def check_python(version_info=None):
136
+ version=tuple((version_info or sys.version_info)[:2])
137
+ if version<(3,10):raise ValueError('Python >= 3.10 required')
138
+
139
+
140
+ def check_versions():
141
+ found={}
142
+ for name,expected in VERSIONS.items():
143
+ try:actual=importlib.metadata.version(name)
144
+ except importlib.metadata.PackageNotFoundError:
145
+ raise ValueError(f'{name} is not installed; run in {IMAGE}') from None
146
+ if actual.split('+',1)[0]!=expected:
147
+ raise ValueError(f'{name} {actual} found, {expected} required; run in {IMAGE}')
148
+ found[name]=actual
149
+ return found
150
+
151
+
152
+ def _absolute(value,flag):
153
+ path=Path(value)
154
+ if not path.is_absolute():raise ValueError(f'{flag} must be an absolute path')
155
+ return path.resolve()
156
+
157
+
158
+ def _overlap(a,b):
159
+ return a==b or a.is_relative_to(b) or b.is_relative_to(a)
160
+
161
+
162
+ def check_paths(model,state_dir):
163
+ model=_absolute(model,'--model');state=_absolute(state_dir,'--state-dir')
164
+ for path,flag in ((model,'--model'),(state,'--state-dir')):
165
+ if _overlap(path,ROOT):raise ValueError(f'{flag} must be outside the package and must not contain it')
166
+ if _overlap(model,state):raise ValueError('--model and --state-dir must not contain each other')
167
+ return model,state
168
+
169
+
170
+ def sha256(path):
171
+ digest=hashlib.sha256()
172
+ with Path(path).open('rb') as source:
173
+ for chunk in iter(lambda:source.read(1<<20),b''):digest.update(chunk)
174
+ return digest.hexdigest()
175
+
176
+
177
+ MODEL_FOLDER_RULE=('MODEL must contain regular files '
178
+ '(a Hugging Face cache snapshots/<sha> directory cannot be mounted alone)')
179
+
180
+
181
+ def _model_file(model,name):
182
+ path=model/name
183
+ if path.is_symlink():
184
+ try:path.resolve(strict=True)
185
+ except (OSError,RuntimeError): # RuntimeError: a link loop before Python 3.13
186
+ raise ValueError(f'Model file is a dangling or looping symbolic link: {name}; {MODEL_FOLDER_RULE}') from None
187
+ raise ValueError(f'Model file is a symbolic link: {name}; {MODEL_FOLDER_RULE}')
188
+ if not path.is_file():raise ValueError('Missing model file: '+name)
189
+ return path
190
+
191
+
192
+ def check_model(model,full=False):
193
+ """Read-only identity check of the model folder against MODEL_REVISION; nothing is written.
194
+
195
+ The pinned files must be regular files with the pinned SHA-256 (shards: size, or full hash).
196
+ Any other entry a loader may read (weights, tokenizer, processor or template files) is refused.
197
+ """
198
+ model=Path(model)
199
+ if not MODEL_SHA256 or not SHARD_BYTES:raise ValueError('Model identity pending')
200
+ if not model.is_dir():raise ValueError('Model folder not found: '+str(model))
201
+ pinned={*MODEL_SHA256,*SHARD_BYTES,'PREPARATION.json'}
202
+ for entry in sorted(model.iterdir()):
203
+ name=entry.name
204
+ if name not in pinned and (name.lower().endswith(LOADER_SUFFIXES) or name.lower().startswith('tokenizer')):
205
+ raise ValueError(f'Unexpected file in model folder: {name}; use the files of {MODEL_REVISION} only')
206
+ for name,expected in MODEL_SHA256.items():
207
+ path=_model_file(model,name)
208
+ if sha256(path)!=expected:raise ValueError(f'Model file differs from {MODEL_REVISION}: {name}')
209
+ for name,size in SHARD_BYTES.items():
210
+ path=_model_file(model,name)
211
+ if path.stat().st_size!=size:raise ValueError(f'Model shard size differs from {MODEL_REVISION}: {name}')
212
+ if full and sha256(path)!=SHARD_SHA256[name]:raise ValueError(f'Model shard differs from {MODEL_REVISION}: {name}')
213
+ check_adapter(model)
214
+
215
+
216
+ EXISTING_RUN='run_id already exists in --state-dir; use a new --state-dir (with the README commands: a new WORK)'
217
+
218
+
219
+ def prepare_state(state,run_id=None):
220
+ """Claim <state>/<run-id>-startup with exclusive creation; nothing is overwritten or removed."""
221
+ state=Path(state)
222
+ rid=run_id or datetime.now(timezone.utc).strftime('run-%Y%m%dT%H%M%S%fZ')
223
+ if not RUN_ID.fullmatch(rid):raise ValueError('run_id must match [A-Za-z0-9][A-Za-z0-9._-]{0,63}')
224
+ startup=state/(rid+'-startup')
225
+ if len(os.fsencode(startup/'tmp'))>MAX_TMP_PATH:
226
+ raise ValueError(f'--state-dir path is too long for engine sockets; keep {startup/"tmp"} within {MAX_TMP_PATH} bytes')
227
+ try:state.mkdir(parents=True,exist_ok=True)
228
+ except OSError:raise ValueError('State directory is not writable: '+str(state)) from None
229
+ if (state/rid).exists() or startup.exists():raise ValueError(EXISTING_RUN)
230
+ cache=state/'cache'
231
+ if cache.is_symlink() or (cache.exists() and not cache.is_dir()):
232
+ raise ValueError(f'{cache} must be a directory, not a link or a file')
233
+ try:
234
+ startup.mkdir()
235
+ for name in ('tmp','home'):(startup/name).mkdir()
236
+ cache.mkdir(exist_ok=True)
237
+ with (startup/'attempts.jsonl').open('x',encoding='utf-8') as probe:
238
+ probe.flush();os.fsync(probe.fileno())
239
+ except FileExistsError:raise ValueError(EXISTING_RUN) from None
240
+ except OSError:raise ValueError('State directory is not writable: '+str(state)) from None
241
+ if cache.is_symlink() or not cache.resolve().is_relative_to(state.resolve()):
242
+ raise ValueError(f'{cache} must be a directory inside --state-dir, not a link')
243
+ return rid,startup
244
+
245
+
246
+ def check_environment(base,state):
247
+ """Refuse inherited cache or configuration paths outside --state-dir; nothing is written.
248
+
249
+ An inherited path variable is accepted only if it is absolute and inside --state-dir, so
250
+ writes cannot leave the state directory. start() runs this before claiming the run name.
251
+ """
252
+ root=Path(state).resolve()
253
+ for key in PATH_VARIABLES:
254
+ if key not in base:continue
255
+ value=Path(base[key])
256
+ if not value.is_absolute() or not value.resolve().is_relative_to(root):
257
+ raise ValueError(f'{key}={base[key]} is outside --state-dir; do not set cache or configuration paths')
258
+
259
+
260
+ def child_environment(base,state,startup):
261
+ """Engine environment: offline, no telemetry, every write location below --state-dir."""
262
+ check_environment(base,state)
263
+ env=dict(base)
264
+ env['VLLM_BATCH_INVARIANT']='1'
265
+ env['VLLM_WORKER_MULTIPROC_METHOD']='spawn'
266
+ # vLLM 0.31 no longer reads the legacy attention variable; an inherited value would only log a warning.
267
+ env.pop('VLLM_ATTENTION_BACKEND',None);env.pop('JEV_ATTENTION_BACKEND',None)
268
+ env.update(VLLM_USE_FLASHINFER_SAMPLER='0',VLLM_NO_USAGE_STATS='1',DO_NOT_TRACK='1',
269
+ HF_HUB_OFFLINE='1',TRANSFORMERS_OFFLINE='1',PYTHONDONTWRITEBYTECODE='1',PYTHONUNBUFFERED='1',
270
+ VLLM_HOST_IP='127.0.0.1',HOME=str(startup/'home'),TMPDIR=str(startup/'tmp'))
271
+ cache=Path(state)/'cache'
272
+ for key,sub in (('XDG_CACHE_HOME',None),('VLLM_CACHE_ROOT','vllm'),('TRITON_CACHE_DIR','triton'),
273
+ ('TORCHINDUCTOR_CACHE_DIR','inductor'),('HF_HOME','hf'),('CUDA_CACHE_PATH','nv'),
274
+ ('NUMBA_CACHE_DIR','numba')):
275
+ env.setdefault(key,str(cache/sub if sub else cache))
276
+ return env
277
+
278
+
279
+ def validate_health(health, no_cache=False):
280
+ bad=health.get('warnings',[])
281
+ if bad or health.get('status','ok')!='ok' or health.get('prefix_caching') is not (not no_cache) or health.get('supports_slow_session') is not True:
282
+ raise RuntimeError('Service readiness rejected: health warnings or cache/session capability; health='+json.dumps(health)[:1500])
283
+
284
+
285
+ def start(model, port, mode='routed', config=None, no_cache=False, state_dir=None, run_id=None, bind='127.0.0.1', allow_nonformal=False):
286
+ check_preparation(config,allow_nonformal)
287
+ check_python()
288
+ check_versions()
289
+ if bind not in BINDS:raise ValueError('--bind must be one of '+', '.join(BINDS))
290
+ if state_dir is None:raise ValueError('--state-dir is required')
291
+ model,state=check_paths(model,state_dir)
292
+ check_environment(os.environ,state) # an input check: refused before anything is written
293
+ check_model(model)
294
+ from model_setup import report_processor_configs
295
+ report_processor_configs(model)
296
+ cfg=Path(config or ROOT/'config.json').resolve()
297
+ c=json.loads(cfg.read_text(encoding='utf-8'))
298
+ if mode=='routed' and not c.get('package',{}).get('finalized') and not allow_nonformal:
299
+ raise ValueError('Real fitted parameters must be finalized before routed serving')
300
+ try:
301
+ with socket.socket() as probe:
302
+ probe.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
303
+ probe.bind((bind,port))
304
+ except OSError:raise ValueError(f'Port {port} on {bind} is not available') from None
305
+ runtime=materialize_adapter(c,check_adapter(model))
306
+ rid,startup=prepare_state(state,run_id)
307
+ cfg=write_runtime_config(startup,runtime)
308
+ tempfile.tempdir=str(startup/'tmp')
309
+ env=child_environment(os.environ,state,startup)
310
+ args=[sys.executable,'-B',str(ROOT/'server.py'),'--config',str(cfg),'--model',str(model),
311
+ '--bind',bind,'--port',str(port),'--backend','vllm','--quant','awq',
312
+ '--mode',mode,'--model-key',MODEL_KEY,'--state-dir',str(state),'--run-id',rid]
313
+ if no_cache:args.append('--no-prefix-caching')
314
+ return start_attempts(args,env,startup,port,mode,no_cache)
315
+
316
+
317
+ def start_attempts(args,env,startup,port,mode='routed',no_cache=False,timeout=STARTUP_TIMEOUT):
318
+ """auto first; TRITON_ATTN only if the engine fails to start. Each attempt has its own deadline."""
319
+ startup=Path(startup)
320
+ opener=urllib.request.build_opener(urllib.request.ProxyHandler({})) # readiness never uses a proxy
321
+ def event(attempt,backend,result,log=None):
322
+ # Best effort: a record that cannot be written or printed never blocks starting or stopping.
323
+ record=dict(attempt=attempt,requested_backend=backend or 'auto',result=result)
324
+ if log is not None:record['log']=str(log)
325
+ line=json.dumps(record)
326
+ try:
327
+ with (startup/'attempts.jsonl').open('a',encoding='utf-8',newline='\n') as out:
328
+ out.write(line+'\n')
329
+ except OSError:pass
330
+ try:print(line,flush=True)
331
+ except OSError:pass
332
+ for attempt,backend in enumerate(ATTEMPTS,1):
333
+ child_env=dict(env)
334
+ child_env.pop('VLLM_ATTENTION_BACKEND',None);child_env.pop('JEV_ATTENTION_BACKEND',None)
335
+ if backend:child_env['JEV_ATTENTION_BACKEND']=backend
336
+ log_path=startup/f'attempt-{attempt}.log'
337
+ child_env['JEV_ATTENTION_LOG']=str(log_path)
338
+ child_args=list(args)
339
+ # Separate quota state per attempt: a failed warmup never counts toward the served run.
340
+ child_args[child_args.index('--state-dir')+1]=str(startup/f'state-{attempt}')
341
+ event(attempt,backend,'starting',log_path)
342
+ proc=None
343
+ try:
344
+ with log_path.open('xb') as out:
345
+ proc=subprocess.Popen(child_args,cwd=ROOT,env=child_env,
346
+ stdout=out,stderr=subprocess.STDOUT,start_new_session=True)
347
+ deadline=time.monotonic()+timeout
348
+ while time.monotonic()<deadline:
349
+ logs=log_path.read_text(encoding='utf-8',errors='replace')
350
+ if 'EngineCore failed to start' in logs:
351
+ event(attempt,backend,'failed: EngineCore failed to start',log_path)
352
+ break
353
+ if proc.poll() is not None:
354
+ event(attempt,backend,'failed: exited before readiness',log_path)
355
+ break
356
+ try:
357
+ with opener.open(f'http://127.0.0.1:{port}/health',timeout=2) as response:
358
+ health=json.load(response)
359
+ ready=response.status==200 and health['mode']==mode and health['model_key']==MODEL_KEY
360
+ except (OSError,ValueError,KeyError):
361
+ ready=False
362
+ if ready:
363
+ # Recheck after the HTTP round trip; failure takes precedence.
364
+ if proc.poll() is not None or 'EngineCore failed to start' in log_path.read_text(encoding='utf-8',errors='replace'):
365
+ event(attempt,backend,'failed: engine stopped during readiness',log_path)
366
+ break
367
+ validate_health(health,no_cache)
368
+ event(attempt,backend,'ready',log_path)
369
+ print(json.dumps(health,ensure_ascii=False),flush=True)
370
+ return proc
371
+ time.sleep(1)
372
+ else:
373
+ raise TimeoutError(f'Health readiness timed out after {timeout} s; see {log_path}')
374
+ except BaseException as exc:
375
+ # Stop the server first; the record follows even if a stop request arrives meanwhile.
376
+ try:
377
+ if proc is not None:stop(proc)
378
+ finally:
379
+ event(attempt,backend,'aborted: '+type(exc).__name__,log_path)
380
+ raise
381
+ stop(proc)
382
+ raise RuntimeError(f'Both attention backend attempts (auto, TRITON_ATTN) failed before readiness; see {startup}')
383
+
384
+
385
+ KILL_WAIT=60
386
+
387
+
388
+ @contextmanager
389
+ def cleanup_signals():
390
+ """Hold SIGTERM, SIGINT and SIGHUP while child processes are being stopped.
391
+
392
+ A signal that arrives meanwhile is recorded, not acted on; once the previous handlers are
393
+ restored it is raised as KeyboardInterrupt, so a stop request is never lost.
394
+ """
395
+ saved={};pending=[]
396
+ def hold(number,frame):pending.append(number)
397
+ try:
398
+ if threading.current_thread() is threading.main_thread():
399
+ for name in ('SIGTERM','SIGINT','SIGHUP'):
400
+ number=getattr(signal,name,None)
401
+ if number is not None:saved[number]=signal.signal(number,hold)
402
+ yield
403
+ finally:
404
+ for number,handler in saved.items():
405
+ if handler is not None:signal.signal(number,handler)
406
+ if pending:raise KeyboardInterrupt
407
+
408
+
409
+ def stop(proc):
410
+ """SIGTERM to the server's process group, up to 30 s, then SIGKILL to the group and a wait of
411
+ at most KILL_WAIT s; a signal during this sequence is raised afterwards (cleanup_signals)."""
412
+ with cleanup_signals():
413
+ try:os.killpg(proc.pid,signal.SIGTERM)
414
+ except ProcessLookupError:pass
415
+ try:
416
+ try:proc.wait(timeout=30)
417
+ except subprocess.TimeoutExpired:pass
418
+ finally:
419
+ # Reap descendants left in the process group before rebinding on retry.
420
+ try:os.killpg(proc.pid,signal.SIGKILL)
421
+ except ProcessLookupError:pass
422
+ try:proc.wait(timeout=KILL_WAIT)
423
+ except subprocess.TimeoutExpired:
424
+ try:print(f'launch: warning: process group {proc.pid} still running {KILL_WAIT} s after SIGKILL',
425
+ file=sys.stderr,flush=True)
426
+ except OSError:pass
427
+
428
+
429
+ def main(argv=None):
430
+ ap=argparse.ArgumentParser(description=__doc__,formatter_class=argparse.RawDescriptionHelpFormatter)
431
+ ap.add_argument('--model',required=True,help='absolute path of the model folder (read only)')
432
+ ap.add_argument('--state-dir',required=True,help='absolute path; the service writes only below it')
433
+ ap.add_argument('--run-id',help='new name for this run; default run-<UTC timestamp>')
434
+ ap.add_argument('--port',type=int,default=8000)
435
+ ap.add_argument('--bind',choices=BINDS,default='127.0.0.1')
436
+ ap.add_argument('--allow-nonformal',action='store_true',help='Local fitted engineering package only; never signs release')
437
+ a=ap.parse_args(argv)
438
+ def interrupted(*_):raise KeyboardInterrupt
439
+ for name in ('SIGTERM','SIGINT','SIGHUP'):
440
+ number=getattr(signal,name,None)
441
+ if number is not None:signal.signal(number,interrupted)
442
+ proc=None
443
+ try:
444
+ proc=start(a.model,a.port,mode=SERVED_MODE,state_dir=a.state_dir,run_id=a.run_id,bind=a.bind,allow_nonformal=a.allow_nonformal)
445
+ return proc.wait()
446
+ except KeyboardInterrupt:return 130
447
+ except (ValueError,RuntimeError,OSError) as exc:
448
+ print('launch: error: '+str(exc),file=sys.stderr,flush=True)
449
+ return 1
450
+ finally:
451
+ if proc:
452
+ # A stop request during this final stop is already being carried out.
453
+ try:stop(proc)
454
+ except KeyboardInterrupt:pass
455
+
456
+ if __name__=='__main__':raise SystemExit(main())
service/model_setup.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Report a missing processor config in the model folder; never downloads or copies."""
2
+ from pathlib import Path
3
+
4
+ # Gemma 4 keeps its processor settings in processor_config.json; the checkpoint has no
5
+ # preprocessor_config.json or video_preprocessor_config.json and needs neither.
6
+ NAMES=('processor_config.json',)
7
+
8
+ def report_processor_configs(model_dir):
9
+ """Print nothing for a complete model folder; return the missing names."""
10
+ p=Path(model_dir)
11
+ missing=[n for n in NAMES if not (p/n).is_file()]
12
+ if missing:
13
+ print('[model_setup] processor config not in the model folder: '+', '.join(missing),flush=True)
14
+ return missing
service/pkg_check.py ADDED
@@ -0,0 +1,455 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Offline package checks: integrity, model identity, configuration, adapter contract, serving
2
+ policy, calibration and disclosure items. No model is executed and nothing is downloaded.
3
+
4
+ A preparation package (config package.finalized false) and a frozen package
5
+ (finalized true plus FREEZE.json) are both checked. The freeze may change only
6
+ package.finalized in config.json and replace README.md, DISCLOSURE.md and
7
+ MODEL_CARD.md; every other file keeps the bytes of the prepared package. A Git
8
+ working copy of the package (a `.git` directory next to the files) is accepted.
9
+
10
+ SERVICE_POLICY (written by the builder from the selected serving parameters) states what this
11
+ package serves: the routing mode, threshold and quota, the drafting budget and its prompt limit, the
12
+ per-decision deadline and the item rules (types that may draft, low-margin choice re-read), the
13
+ post-calibration, the unparsed policy, the cost-fuse lines and prices and the yes/no confidence
14
+ constant of the base calibration. Every check below compares the configuration, calibration and fit
15
+ records with it, and the guards hold whatever it says.
16
+ """
17
+ import argparse
18
+ import ast
19
+ import copy
20
+ import hashlib
21
+ import json
22
+ from pathlib import Path
23
+ import re
24
+
25
+ ROOT=Path(__file__).resolve().parent
26
+ MODEL_KEY='metask-gemma4-31b-lora-w4a16'
27
+ MODEL_NAME='MetaDecision-31B-Rain'
28
+ ADAPTER_REF='model-subdir:adapter'
29
+ ADAPTER_SUBDIR='adapter'
30
+ ADAPTER_FILES=('adapter_config.json','adapter_model.safetensors')
31
+ TARGETS=('q_proj','k_proj','v_proj','o_proj','gate_proj','up_proj','down_proj')
32
+ LORA=dict(rank=16,alpha=32,target_modules=sorted(TARGETS))
33
+ PRICING_BASE='google/gemma-4-31B-it'
34
+ TRAINING_BASE='google/gemma-4-31B-it-qat-q4_0-unquantized'
35
+ TRAINING_REVISION='1e4d8beecacb8b7590c1d8bedd7335f687bf311f'
36
+ SERVED_BASE='google/gemma-4-31B-it-qat-w4a16-ct'
37
+ SERVED_REVISION='52f3f65bc7a02d555763bc923bd1d9094898219d'
38
+ SERVICE_POLICY={'protocol': 'adapter_arm_31b_v3', 'mode': 'routed', 'quota': 0.49, 'fuse': 0.49, 'margin_threshold': 6.625023671062799, 'fit_protocol': '31b-adapter-arm-v3-routed', 'fit_n': 231, 'base_fit': {'q': 0.056, 'draft': 256}, 'noul_p_cal': 0.898, 'draft': {'max_new_tokens': 192, 'reasoning_words': 50}, 'slow_max_prompt_tokens': 840, 'unparsed_policy': 'force_answer', 'slow_excluded_types': [], 'cost_fuse': {'arm': 0.9, 'enabled': True, 'release': 0.85, 'warmup': 100}, 'cost_fuse_prices': {'input_per_million': 0.059819178473256074, 'output_per_million': 0.059819178473256074}, 'item_deadline': {'item_seconds': 9.0, 'response_reserve_s': 0.35, 'readout_reserve_init_s': 1.0, 'readout_reserve_min_s': 0.3, 'readout_safety': 1.5, 'chunk_tokens': 32, 'min_chunk_tokens': 8, 'token_safety': 1.25, 'ewma_alpha': 0.3, 'warmup_tokens': 33, 'warmup_repeats': 3}, 'item_rules': {'slow_types': ['choice', 'noul', 'score'], 'reread_tau': 4.0}, 'post_calibration': {'choice': {'config': {'debias': False, 'temperature': False}, 'params': {}}, 'noul': {'config': {'temperature': 'type', 'floor': None}, 'params': {'T': 0.5}}, 'score': {'config': {'temperature': False, 'mode_weight': 1.0}, 'params': {}}}, 'post_calibration_active': True, 'core_patch': None, 'v3_params_sha256': 'b28ed73bb5a36e99a280a47a6b846e0c5b2de54f34a96efa4a10754b71ea7573', 'threshold_rule': 'eligible_items'}
39
+ INPUT_BUDGET=6653
40
+ MAX_MODEL_LEN=6656
41
+ GPU_MEMORY_UTILIZATION=0.94
42
+ MAX_LORA_RANK=16
43
+ PROTOCOL='adapter_arm_31b_v3'
44
+ FIT_PROTOCOLS={'fast_only':'31b-adapter-arm-v3-q0','routed':'31b-adapter-arm-v3-routed'}
45
+ # The reference cost fuse of the fast-only configuration; a routed package states its own lines in SERVICE_POLICY.
46
+ COST_FUSE={'enabled':True,'warmup':100,'arm':0.85,'release':0.8}
47
+ NOUL_P_CAL_RANGE=(0.81,0.95)
48
+ SLOW_READOUT_MARGIN=32
49
+ DRAFTING_WALL_TIMEOUT_SECONDS=60
50
+ SLOW_QUOTA_MAX=0.49
51
+ BASE_FUSE=0.2
52
+ ITEM_SECONDS_MAX=9.5
53
+ KINDS=('choice','noul','score')
54
+ SERVED_MODE=(SERVICE_POLICY or {}).get('mode')
55
+ REQUIRED=('core.py backends.py quota.py attention.py model_setup.py server.py bench.py item_deadline.py post_calibration.py '
56
+ 'reconcile_usage.py launch.py serve.sh selftest.py selftest.sh pkg_check.py '
57
+ 'prepare_model.sh config.json calibration.json MODEL_MANIFEST.json ADAPTER_MANIFEST.json '
58
+ 'PIPELINE_PROVENANCE.json DISCLOSURE.md README.md MODEL_CARD.md '
59
+ 'requirements.lock requirements.measured.lock LICENSE MANIFEST.sha256').split()
60
+ RUNTIME=('core.py','backends.py','quota.py','attention.py','model_setup.py','server.py','launch.py','item_deadline.py','post_calibration.py')
61
+ REFERENCE_CODE=('core.py','backends.py','quota.py','attention.py','model_setup.py','server.py','bench.py','reconcile_usage.py')
62
+ FREEZE_SCHEMA='metadecision_31b_freeze_v1'
63
+ FREEZE_DOCUMENTS=('README.md','DISCLOSURE.md','MODEL_CARD.md')
64
+ CONFIG_CHANGE='package.finalized: false -> true (single textual replacement)'
65
+ FROZEN_FLAG,PREPARED_FLAG=b'"finalized": true',b'"finalized": false'
66
+ TEXT_SUFFIXES=('.py','.md','.json','.sh','.lock','.txt')
67
+ def percent(value):
68
+ """'6%' for 0.06, '2.5%' for 0.025, '6.25%' for 0.0625: the service's quota and fuse lines as written in the
69
+ documents, exact (never rounded)."""
70
+ from decimal import Decimal
71
+ text=format((Decimal(repr(float(value)))*100).normalize(),'f')
72
+ return (text.rstrip('0').rstrip('.') if '.' in text else text)+'%'
73
+
74
+ def policy_disclosure(policy):
75
+ """Policy facts DISCLOSURE.md must state: the yes/no constant; routed: the drafting pass, its quota, budget,
76
+ prompt limit, 60 s limit and the cost-fuse lines."""
77
+ if not policy:return ()
78
+ items=('%.3f'%policy['noul_p_cal'],)
79
+ if policy['mode']!='routed':return items
80
+ pct=percent(policy['quota'])
81
+ items+=('drafting pass','slow-path quota '+pct,'at most '+pct+' of all decisions',
82
+ 'at most %d tokens'%policy['draft']['max_new_tokens'],
83
+ 'longer than {:,} tokens'.format(policy['slow_max_prompt_tokens']),
84
+ 'deadline of %s s'%format(policy['item_deadline']['item_seconds'],'g'),'Cost fuse',
85
+ percent(policy['cost_fuse']['arm']),percent(policy['cost_fuse']['release']))
86
+ rules=policy.get('item_rules') or {}
87
+ if rules.get('reread_tau'):items+=('reverse order','below %s'%format(rules['reread_tau'],'g'))
88
+ if set(rules.get('slow_types') or KINDS)!=set(KINDS):items+=('only these question types',)
89
+ if policy.get('post_calibration_active'):items+=('Post-calibration',)
90
+ return items
91
+
92
+ # Necessary disclosure: base repo@revision, own weights, quantization, unmerged adapter,
93
+ # input budget and the cost/latency measurement conditions, plus the policy facts.
94
+ DISCLOSURE_REQUIRED=(PRICING_BASE,TRAINING_BASE,TRAINING_REVISION,SERVED_BASE,SERVED_REVISION,
95
+ 'rank 16','alpha 32','W4A16','not merged','max_lora_rank=16','6,653',
96
+ 'VLLM_BATCH_INVARIANT=1','engine token')+policy_disclosure(SERVICE_POLICY)
97
+ # README requirements: tested GPU and memory, the adapter route's vLLM options, the download tool, the mode.
98
+ README_REQUIRED=('NVIDIA A100 80GB','gpu_memory_utilization=0.94','max_model_len=6656','enable_lora',
99
+ 'max_lora_rank=16','The model files take','`hf`','hf download')+(
100
+ ('`"mode": "%s"`'%SERVED_MODE,) if SERVED_MODE else ())
101
+
102
+ def read(name):
103
+ return json.loads((ROOT/name).read_text(encoding='utf-8'))
104
+
105
+ def sha(path):
106
+ h=hashlib.sha256()
107
+ with path.open('rb') as f:
108
+ for block in iter(lambda:f.read(1<<20),b''):h.update(block)
109
+ return h.hexdigest()
110
+
111
+ def canonical(value):
112
+ return hashlib.sha256(json.dumps(value,ensure_ascii=False,sort_keys=True,separators=(',',':'),allow_nan=False).encode()).hexdigest()
113
+
114
+ def identity_sha256(m):
115
+ """Identity of the model folder: official root files + the unmerged adapter/ files."""
116
+ identity={'format':m['format'],'files':m['files']}
117
+ if m.get('adapter') is not None:
118
+ a=m['adapter']
119
+ identity['adapter']={'format':a['format'],'subdir':a['subdir'],'files':a['files']}
120
+ return canonical(identity)
121
+
122
+ def check_manifest(root=ROOT):
123
+ expected={}
124
+ for line in (root/'MANIFEST.sha256').read_text(encoding='utf-8').splitlines():
125
+ digest,name=line.split(' ',1)
126
+ if not re.fullmatch('[0-9a-f]{64}',digest) or Path(name).name!=name or name in expected:
127
+ raise ValueError('Unsafe or duplicate manifest entry')
128
+ p=root/name
129
+ if p.is_symlink() or not p.is_file() or sha(p)!=digest:
130
+ raise ValueError('Missing, linked or changed package member: '+name)
131
+ expected[name]=digest
132
+ actual={p.name for p in root.iterdir() if p.is_file() and p.name!='MANIFEST.sha256'}
133
+ if actual!=set(expected):raise ValueError('Manifest membership differs')
134
+ for p in root.iterdir():
135
+ if p.is_symlink() or (p.is_dir() and p.name not in ('__pycache__','.git')):
136
+ raise ValueError('Unexpected package directory or symlink')
137
+ return expected
138
+
139
+ def front_matter(text):
140
+ """Minimal YAML front-matter reader for top-level 'key: value' lines."""
141
+ lines=text.splitlines()
142
+ if not lines or lines[0].strip()!='---':raise ValueError('Model card lacks YAML front matter')
143
+ values={}
144
+ for line in lines[1:]:
145
+ if line.strip()=='---':return values
146
+ match=re.fullmatch(r'([A-Za-z_][A-Za-z0-9_]*):\s*(\S.*)?',line)
147
+ if match:values[match[1]]=(match[2] or '').strip()
148
+ raise ValueError('Unterminated model card front matter')
149
+
150
+ def check_adapter_contract(c,m):
151
+ adapter=m.get('adapter')
152
+ profiles=[(p.get('model') or {}) for p in c.get('profiles',{}).values()]
153
+ if (not isinstance(adapter,dict) or adapter.get('format')!='peft-lora' or adapter.get('subdir')!=ADAPTER_SUBDIR
154
+ or not isinstance(adapter.get('files'),dict)):
155
+ raise ValueError('The manifest must pin the unmerged adapter/ folder')
156
+ if adapter['files'] and (set(adapter['files'])!=set(ADAPTER_FILES) or adapter.get('merged') is not False
157
+ or adapter.get('rank')!=LORA['rank'] or adapter.get('alpha')!=LORA['alpha']
158
+ or adapter.get('target_modules')!=LORA['target_modules'] or adapter.get('variant') not in ('trained_original','key_value_aligned')
159
+ or adapter.get('training_base')!=TRAINING_BASE or adapter.get('training_revision')!=TRAINING_REVISION):
160
+ raise ValueError('Adapter manifest differs from the trained r16/alpha32 unmerged LoRA contract')
161
+ if c['model'].get('adapter')!=ADAPTER_REF or any('adapter' in p and p['adapter']!=ADAPTER_REF for p in profiles):
162
+ raise ValueError('Configuration must reference the model-relative adapter')
163
+ options=c['model'].get('vllm_options',{})
164
+ if options.get('max_lora_rank')!=MAX_LORA_RANK or 'enable_lora' in options:
165
+ raise ValueError('vLLM max_lora_rank=16 required; enable_lora is backend-owned')
166
+ record=read('ADAPTER_MANIFEST.json')
167
+ if (sha(ROOT/'ADAPTER_MANIFEST.json')!=adapter.get('adapter_manifest_sha256') or record.get('files')!=adapter['files']
168
+ or record.get('variant')!=adapter.get('variant') or (record.get('lora') or {}).get('merged') is not False
169
+ or (record.get('served_base') or {}).get('repo_id')!=SERVED_BASE
170
+ or (record.get('served_base') or {}).get('revision')!=SERVED_REVISION):
171
+ raise ValueError('ADAPTER_MANIFEST.json does not bind the pinned adapter files')
172
+
173
+ def policy_problems(policy):
174
+ """Guards that hold whatever the builder wrote: mode, quota, yes/no constant, drafting budget and context."""
175
+ if not isinstance(policy,dict):return ['service policy pending']
176
+ problems=[]
177
+ p=policy.get('noul_p_cal')
178
+ if isinstance(p,bool) or not isinstance(p,float) or not NOUL_P_CAL_RANGE[0]<=p<=NOUL_P_CAL_RANGE[1]:
179
+ problems.append('yes/no constant outside [0.81, 0.95]')
180
+ if policy.get('protocol')!=PROTOCOL or policy.get('fit_protocol')!=FIT_PROTOCOLS.get(policy.get('mode')):
181
+ problems.append('protocol or fit protocol')
182
+ q=policy.get('quota')
183
+ if policy.get('mode')=='routed':
184
+ problems+=v4_policy_problems(policy)
185
+ if policy.get('mode')=='fast_only':
186
+ if q!=0 or policy.get('cost_fuse')!=COST_FUSE or policy.get('slow_excluded_types'):
187
+ problems.append('fast-only policy with a quota, other cost fuse or excluded types')
188
+ elif policy.get('mode')=='routed':
189
+ fuse,draft=policy.get('cost_fuse') or {},policy.get('draft') or {}
190
+ if isinstance(q,bool) or not isinstance(q,float) or not 0<q<=SLOW_QUOTA_MAX:problems.append('quota')
191
+ if (set(fuse)!={'enabled','warmup','arm','release'} or fuse['enabled'] is not True or fuse['warmup']!=100
192
+ or not 0.5<=fuse['release']<fuse['arm']<=0.9):problems.append('cost fuse lines')
193
+ length,prompt=draft.get('max_new_tokens'),policy.get('slow_max_prompt_tokens')
194
+ if (type(length) is not int or type(prompt) is not int or type(draft.get('reasoning_words')) is not int
195
+ or prompt+length+SLOW_READOUT_MARGIN>MAX_MODEL_LEN or prompt>INPUT_BUDGET):
196
+ problems.append('drafting budget outside the vLLM context')
197
+ if policy.get('unparsed_policy') not in ('fast','force_answer'):problems.append('unparsed policy')
198
+ if policy.get('slow_excluded_types') not in ([],['noul']):problems.append('excluded types')
199
+ if bool(policy.get('slow_excluded_types'))!=(policy.get('core_patch') is not None):
200
+ problems.append('excluded slow types need exactly the v3 core patch')
201
+ else:
202
+ problems.append('mode')
203
+ return problems
204
+
205
+ def v4_policy_problems(policy):
206
+ """Fuse, cost-fuse prices, per-decision deadline, item rules and post-calibration of a routed package."""
207
+ problems=[]
208
+ q=policy.get('quota')
209
+ if not isinstance(q,float) or policy.get('fuse')!=max(BASE_FUSE,q):problems.append('fuse must be max(0.2, quota)')
210
+ t=policy.get('margin_threshold')
211
+ if isinstance(t,bool) or not isinstance(t,float) or not 0<=t<float('inf'):problems.append('margin threshold')
212
+ prices=policy.get('cost_fuse_prices') or {}
213
+ if (set(prices)!={'input_per_million','output_per_million'}
214
+ or any(isinstance(v,bool) or not isinstance(v,float) or not 0<v<float('inf') for v in prices.values())):
215
+ problems.append('cost-fuse prices')
216
+ try:
217
+ import item_deadline
218
+ s=item_deadline.settings_from(policy.get('item_deadline'))
219
+ if s!=policy.get('item_deadline') or not 0<s['item_seconds']<=ITEM_SECONDS_MAX:problems.append('item deadline')
220
+ except Exception:
221
+ problems.append('item deadline')
222
+ rules=policy.get('item_rules')
223
+ if (not isinstance(rules,dict) or set(rules)!={'slow_types','reread_tau'} or not isinstance(rules['slow_types'],list)
224
+ or not rules['slow_types'] or not set(rules['slow_types'])<=set(KINDS)
225
+ or len(set(rules['slow_types']))!=len(rules['slow_types'])
226
+ or isinstance(rules['reread_tau'],bool) or not isinstance(rules['reread_tau'],float)
227
+ or not 0<=rules['reread_tau']<float('inf')):
228
+ problems.append('item rules')
229
+ try:
230
+ import post_calibration
231
+ block=post_calibration.validate(policy.get('post_calibration'))
232
+ if bool(post_calibration.active(block))!=bool(policy.get('post_calibration_active')):problems.append('post-calibration flag')
233
+ except Exception:
234
+ problems.append('post-calibration')
235
+ return problems
236
+
237
+ def check_serving_policy(c):
238
+ """SERVICE_POLICY's mode, quota, draft, prompt limit and cost fuse; 6653 input budget everywhere server.py can
239
+ apply it; 31B context/memory; the served configuration is server.py's fixed point."""
240
+ from core import apply_profile, apply_budget_tier
241
+ problems=policy_problems(SERVICE_POLICY)
242
+ if problems:raise ValueError('Service policy invalid: '+', '.join(problems))
243
+ policy=SERVICE_POLICY
244
+ r,limits,options,g=c['routing'],c['limits'],c['model'].get('vllm_options',{}),c['generation']
245
+ q,draft=policy['quota'],policy['draft']
246
+ if (r['mode']!=policy['mode'] or r['quota']!=q or r['target_slow_share']!=q or c['bench'].get('target_slow_share')!=q
247
+ or r['fuse']!=policy['fuse'] or c['usage']!={'basis':'engine'} or r.get('cost_fuse')!=policy['cost_fuse']):
248
+ raise ValueError('Changed serving mode, quota, usage or cost-fuse convention')
249
+ if (r['margin_threshold']!=policy['margin_threshold']
250
+ or c['pricing']['models'][c['pricing']['model_key']]!=policy['cost_fuse_prices']
251
+ or c.get('item_deadline')!=policy['item_deadline'] or c.get('item_rules')!=policy['item_rules']
252
+ or c.get('post_calibration')!=policy['post_calibration']):
253
+ raise ValueError('Changed threshold, cost-fuse prices, deadline, item rules or post-calibration')
254
+ if (r['margin_threshold']!=0 if policy['mode']=='fast_only' else not 0<r['margin_threshold']<float('inf')):
255
+ raise ValueError('Changed serving threshold')
256
+ if ((g['max_new_tokens'],g['reasoning_words'])!=(draft['max_new_tokens'],draft['reasoning_words'])
257
+ or (r['tiers']['hard'].get('max_new_tokens'),r['tiers']['hard'].get('reasoning_words'))!=(draft['max_new_tokens'],draft['reasoning_words'])
258
+ or r['slow_max_prompt_tokens']!=policy['slow_max_prompt_tokens'] or r['unparsed_policy']!=policy['unparsed_policy']
259
+ or r.get('slow_excluded_types',[])!=policy['slow_excluded_types']
260
+ or ('slow_excluded_types' in r and not r['slow_excluded_types'])):
261
+ raise ValueError('Changed drafting budget, prompt limit, unparsed policy or excluded types')
262
+ if policy['mode']=='routed' and (r.get('long_item_slow_policy')!='fast_only'
263
+ or g.get('wall_timeout_seconds')!=DRAFTING_WALL_TIMEOUT_SECONDS):
264
+ raise ValueError('Changed routed serving, drafting timeout or long-item policy')
265
+ if (limits['max_prompt_tokens'],limits['physical_max_prompt_tokens'])!=(INPUT_BUDGET,INPUT_BUDGET) \
266
+ or any(r['tiers'][t]['max_prompt_tokens']!=INPUT_BUDGET for t in ('normal','long')) \
267
+ or limits['oom_retry_prompt_tokens']>INPUT_BUDGET:
268
+ raise ValueError('Input budget must be 6653 for every prompt limit')
269
+ if options.get('max_model_len')!=MAX_MODEL_LEN or options.get('gpu_memory_utilization')!=GPU_MEMORY_UTILIZATION:
270
+ raise ValueError('vLLM context 6656 and GPU memory 0.94 required')
271
+ served=copy.deepcopy(c)
272
+ apply_profile(served)
273
+ apply_budget_tier(served,None)
274
+ served['routing']['mode']=SERVED_MODE
275
+ if served!=c:
276
+ raise ValueError('server.py would apply a different configuration than the measured one')
277
+ import core
278
+ if policy['slow_excluded_types'] and getattr(core,'SLOW_TYPE_EXCLUSION',None)!='routing.slow_excluded_types':
279
+ raise ValueError('Excluded slow types need the patched core.py')
280
+
281
+ def check_calibration_policy(cal):
282
+ """The yes/no constant of SERVICE_POLICY in the fast, slow and fallback blocks; drafting-pass and fallback
283
+ answers use the first-pass temperatures (stated in DISCLOSURE.md)."""
284
+ blocks=cal['models'][MODEL_KEY]
285
+ p=SERVICE_POLICY['noul_p_cal']
286
+ for side in ('fast','slow','fallback'):
287
+ if blocks[side]['noul_calibration']['p_cal']!=p or blocks[side]['by_type']['noul'].get('p_cal')!=p:
288
+ raise ValueError('Calibration block does not carry the stated yes/no constant: '+side)
289
+ if blocks[side]['temperature_by_kind']!=blocks['fast']['temperature_by_kind']:
290
+ raise ValueError('DISCLOSURE.md states that drafting-pass answers use the first-pass calibration')
291
+
292
+ def prepared_config_bytes(data):
293
+ """Undo the freeze's single textual change of config.json."""
294
+ if data.count(FROZEN_FLAG)!=1 or PREPARED_FLAG in data:
295
+ raise ValueError('Frozen config.json must differ from the prepared one only in package.finalized')
296
+ return data.replace(FROZEN_FLAG,PREPARED_FLAG)
297
+
298
+ def check_freeze(c,m,members):
299
+ """None for a preparation package; the verified FREEZE.json record for a frozen one."""
300
+ path=ROOT/'FREEZE.json'
301
+ if not c['package'].get('finalized'):
302
+ if path.exists():raise ValueError('FREEZE.json in a package that is not finalized')
303
+ return None
304
+ if not path.is_file():raise ValueError('Finalized package without FREEZE.json')
305
+ f=read('FREEZE.json')
306
+ if f.get('schema')!=FREEZE_SCHEMA or f.get('status')!='FROZEN':
307
+ raise ValueError('Unknown freeze record')
308
+ if not c['package'].get('nonformal_fitted') or c['package'].get('sample_only'):
309
+ raise ValueError('Only the fitted package can be frozen')
310
+ if not re.fullmatch('[0-9a-f]{40}',str(m.get('revision') or '')) or m['repo_id'].startswith('OWNER/'):
311
+ raise ValueError('Frozen package lacks the own repository and full revision')
312
+ if (f.get('repo_id'),f.get('revision'),f.get('model_identity_sha256'))!=(m['repo_id'],m['revision'],m['identity_sha256']):
313
+ raise ValueError('Freeze record names another repository, revision or model')
314
+ prepared=f.get('prepared_files')
315
+ replaced=f.get('replaced_documents') or {}
316
+ if not isinstance(prepared,dict) or set(members)-{'FREEZE.json'}!=set(prepared) or set(replaced)-set(FREEZE_DOCUMENTS):
317
+ raise ValueError('Frozen package membership differs from the prepared package')
318
+ for name,digest in members.items():
319
+ if name=='FREEZE.json':continue
320
+ if name=='config.json':
321
+ before=hashlib.sha256(prepared_config_bytes((ROOT/name).read_bytes())).hexdigest()
322
+ if before!=prepared[name] or f.get('config')!={'prepared_sha256':before,'frozen_sha256':digest,'change':CONFIG_CHANGE}:
323
+ raise ValueError('Frozen config.json is not the recorded freeze of the prepared config')
324
+ elif name in replaced:
325
+ if replaced[name]!={'prepared_sha256':prepared[name],'frozen_sha256':digest}:
326
+ raise ValueError('Replaced document differs from the freeze record: '+name)
327
+ elif digest!=prepared[name]:
328
+ raise ValueError('Freeze changed a file other than config.json and the documents: '+name)
329
+ return f
330
+
331
+ def disclosure_problems(root=ROOT):
332
+ """Necessary disclosure items that DISCLOSURE.md (and the model card base_model) must state."""
333
+ text=(root/'DISCLOSURE.md').read_text(encoding='utf-8',errors='replace')
334
+ problems=['DISCLOSURE.md lacks: '+item for item in DISCLOSURE_REQUIRED if item not in text]
335
+ readme=(root/'README.md').read_text(encoding='utf-8',errors='replace')
336
+ problems+=['README.md lacks: '+item for item in README_REQUIRED if item not in readme]
337
+ card=(root/'MODEL_CARD.md').read_text(encoding='utf-8',errors='replace')
338
+ try:
339
+ values=front_matter(card)
340
+ except ValueError as exc:
341
+ return problems+[str(exc)]
342
+ if values.get('base_model')!=PRICING_BASE:
343
+ problems.append('MODEL_CARD.md front matter base_model must be '+PRICING_BASE)
344
+ for item in (TRAINING_BASE,TRAINING_REVISION,SERVED_BASE,SERVED_REVISION):
345
+ if item not in card:problems.append('MODEL_CARD.md lacks: '+item)
346
+ return problems
347
+
348
+ def checks():
349
+ for name in REQUIRED:
350
+ if not (ROOT/name).is_file():raise ValueError('Missing '+name)
351
+ members=check_manifest()
352
+ for p in ROOT.glob('*.py'):ast.parse(p.read_text(encoding='utf-8'))
353
+ c,m,prov=read('config.json'),read('MODEL_MANIFEST.json'),read('PIPELINE_PROVENANCE.json')
354
+ from core import validate_config, validate_threshold, validate_calibration
355
+ import launch
356
+ validate_config(c)
357
+ if c['model']['model_key']!=MODEL_KEY or c['model']['name']!=MODEL_NAME or set(c['profiles'])!={MODEL_KEY}:
358
+ raise ValueError('Wrong trained model key')
359
+ if c['pipeline']['arm']!='trained' or prov.get('arm')!='trained' or m.get('arm')!='trained':
360
+ raise ValueError('Arm differs between config, manifest and provenance')
361
+ if c['pipeline']['model_identity_sha256']!=m['identity_sha256']:
362
+ raise ValueError('Model identity mismatch')
363
+ if m.get('revision') is not None and not re.fullmatch('[0-9a-f]{40}',m['revision']):
364
+ raise ValueError('Abbreviated/mutable revision rejected')
365
+ if m['repo_id'].startswith('google/'):
366
+ raise ValueError('Own HF repository required')
367
+ if (m.get('served_base')or{}).get('repo_id')!=SERVED_BASE or (m.get('served_base')or{}).get('revision')!=SERVED_REVISION:
368
+ raise ValueError('Served base must be the official W4A16 CT revision')
369
+ check_adapter_contract(c,m)
370
+ check_serving_policy(c)
371
+ frozen=check_freeze(c,m,members)
372
+ complete=bool(m['files']) and bool(m['adapter']['files'])
373
+ if complete and identity_sha256(m)!=m['identity_sha256']:raise ValueError('Manifest identity hash differs')
374
+ if not complete and m['identity_sha256'] is not None:raise ValueError('Incomplete model has an identity')
375
+ cal=read('calibration.json')
376
+ if set(cal['models'])!={MODEL_KEY}:raise ValueError('Foreign calibration model')
377
+ for side in ('fast','slow','fallback'):validate_calibration(cal,MODEL_KEY,side)
378
+ check_calibration_policy(cal)
379
+ fitted=c['package'].get('nonformal_fitted',False)
380
+ if fitted:
381
+ if validate_threshold(c):raise ValueError('Threshold identity differs from the configuration')
382
+ # The served configuration is the materialized one; its threshold identity must still hold.
383
+ if validate_threshold(launch.materialize_adapter(c,Path('/materialized/adapter'))):
384
+ raise ValueError('Materialized threshold identity differs')
385
+ lock=read('SERVICE_LOCK.json')
386
+ report=read('fit_report.json')
387
+ if (lock['protocol']!=prov.get('protocol') or lock['protocol']!=PROTOCOL
388
+ or lock['serving_identity_sha256']!=m['identity_sha256'] or lock['arm']!='trained'
389
+ or lock.get('adapter_files')!=m['adapter']['files'] or lock.get('q')!=SERVICE_POLICY['quota']):
390
+ raise ValueError('Fit lock identity mismatch')
391
+ actual={'config_file':frozen['config']['prepared_sha256'] if frozen else sha(ROOT/'config.json'),
392
+ 'calibration':sha(ROOT/'calibration.json'),'fit_report':sha(ROOT/'fit_report.json')}
393
+ for stem,digest in actual.items():
394
+ if lock[stem+'_sha256']!=digest:raise ValueError('Fit hash mismatch')
395
+ base=SERVICE_POLICY['base_fit']
396
+ if (report.get('fit_protocol')!=SERVICE_POLICY['fit_protocol'] or report.get('n')!=SERVICE_POLICY['fit_n']
397
+ or report.get('q')!=base['q']
398
+ or (report.get('noul_constant') or {}).get('value')!=SERVICE_POLICY['noul_p_cal']
399
+ or report.get('draft')!=base['draft']):
400
+ raise ValueError('Wrong fitting method')
401
+ else:
402
+ if not c['package']['sample_only'] or not cal['models'][MODEL_KEY].get('placeholder'):
403
+ raise ValueError('Unfitted package must clearly carry placeholders')
404
+ if (c['package']['finalized'] and frozen is None) or prov.get('release_ready') or prov.get('official_submission_authorized'):
405
+ raise ValueError('Only the recorded freeze may finalize; the builder never authorizes a release')
406
+ for name in REFERENCE_CODE+('item_deadline.py','post_calibration.py'):
407
+ if sha(ROOT/name)!=prov['source_files'][name]:raise ValueError('Request code changed: '+name)
408
+ patch_record=prov.get('core_patch')
409
+ if patch_record!=SERVICE_POLICY.get('core_patch'):raise ValueError('Core patch record differs from the policy')
410
+ if patch_record is None:
411
+ if prov['source_files']['core.py']!=prov['reference_source_files']['core.py']:raise ValueError('Reference core.py changed')
412
+ else:
413
+ raise ValueError('This package serves the unchanged core.py')
414
+ server_patch=prov.get('server_patch') or {}
415
+ if (sha(ROOT/'server.py')!=server_patch.get('patched_server_sha256')
416
+ or prov['reference_source_files']['server.py']!=server_patch.get('reference_server_sha256')):
417
+ raise ValueError('server.py is not the declared extension patch of the reference server.py')
418
+ if any(prov['source_files'][n]!=prov['reference_source_files'][n] for n in REFERENCE_CODE if n not in ('core.py','server.py')):
419
+ raise ValueError('Reference request code changed')
420
+ if 'install_extensions(service, c)' not in (ROOT/'server.py').read_text(encoding='utf-8'):
421
+ raise ValueError('server.py does not install the serving extensions')
422
+ for name in RUNTIME:
423
+ text=(ROOT/name).read_text(encoding='utf-8')
424
+ if re.search(r'\brmtree\s*\(|\b(?:snapshot_download|hf_hub_download)\s*\(',text):
425
+ raise ValueError('Runtime downloads or recursive deletion found')
426
+ launch_text=(ROOT/'launch.py').read_text(encoding='utf-8')
427
+ for token in ("HF_HUB_OFFLINE='1'","TRANSFORMERS_OFFLINE='1'","env['VLLM_BATCH_INVARIANT']='1'","env['VLLM_WORKER_MULTIPROC_METHOD']='spawn'",
428
+ "VLLM_USE_FLASHINFER_SAMPLER='0'","def check_adapter(","def materialize_adapter(",
429
+ " check_adapter(model)\n","cfg=write_runtime_config(startup,runtime)","config.runtime.json",
430
+ "SERVED_MODE='%s'"%SERVED_MODE,"mode=SERVED_MODE","(ROOT/'FREEZE.json').is_file()"):
431
+ if token not in launch_text:raise ValueError('Offline/invariant/adapter/mode/freeze startup setting missing: '+token.strip())
432
+ if launch.ADAPTER_REF!=ADAPTER_REF or launch.MODEL_KEY!=MODEL_KEY or launch.SERVED_MODE!=c['routing']['mode']:
433
+ raise ValueError('Launcher adapter reference, model key or mode differs')
434
+ for p in ROOT.glob('*.sh'):
435
+ if re.search(r'\brm\s+-[^\n]*r|\brmtree\b',p.read_text(encoding='utf-8')):
436
+ raise ValueError('Recursive deletion in shell')
437
+ serve=(ROOT/'serve.sh').read_text(encoding='utf-8')
438
+ if 'VLLM_USE_FLASHINFER_SAMPLER=0' not in serve or 'VLLM_BATCH_INVARIANT=1' not in serve or 'VLLM_WORKER_MULTIPROC_METHOD=spawn' not in serve:
439
+ raise ValueError('serve.sh must set VLLM_USE_FLASHINFER_SAMPLER=0, VLLM_BATCH_INVARIANT=1 and VLLM_WORKER_MULTIPROC_METHOD=spawn')
440
+ problems=disclosure_problems()
441
+ if problems:raise ValueError('Necessary disclosure incomplete: '+'; '.join(problems))
442
+ pins=dict(line.split('==',1) for line in (ROOT/'requirements.lock').read_text().splitlines() if '==' in line and not line.startswith('#'))
443
+ for name,version in launch.VERSIONS.items():
444
+ if pins.get(name,'').split('+')[0]!=version:raise ValueError('Runtime version pin mismatch')
445
+ return {'status':'FROZEN_PACKAGE_CHECK_PASS' if frozen else 'PREPARATION_CHECK_PASS','model_execution':False,'network':False,
446
+ 'adapter':'unmerged adapter/ ('+m['adapter'].get('variant','pending')+')','served_mode':SERVED_MODE,
447
+ 'input_budget':INPUT_BUDGET,'service_fit':'frozen' if frozen else 'fitted_not_frozen' if fitted else 'pending',
448
+ 'serving_extensions':['item_deadline','item_rules','post_calibration']}
449
+
450
+ def main():
451
+ argparse.ArgumentParser(description=__doc__).parse_args()
452
+ print(json.dumps(checks(),ensure_ascii=False,indent=2))
453
+ return 0
454
+
455
+ if __name__=='__main__':raise SystemExit(main())
service/post_calibration.py ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Per-type post-calibration of the reported probabilities (no model call, no tokens).
2
+
3
+ server.py installs it when config.json has a `post_calibration` block. For each question type the
4
+ block holds the options chosen on the service's own fitting data and their fitted parameters:
5
+ choice: debias (divide out the mean probability of each option position, per option count, then
6
+ renormalise), then a temperature;
7
+ noul: a temperature, then a decision-side floor (P(yes) is moved to at least `floor` on the
8
+ side it already favours, or to at most 1 - floor);
9
+ score: a temperature, then a reading weight w (probability mass w moved onto the most probable
10
+ level; w = 1 puts all mass on it).
11
+ The temperature option is off, one value per type ("type"), or one value per type and route cell
12
+ ("route": fast, slow, slow_timeout_fast, fast_reread; a cell without its own value uses the type
13
+ value). A temperature t replaces the probabilities p by p^(1/t), renormalised. A type without an
14
+ entry, or with every option off, and a question with a single option or level, are returned exactly
15
+ as the service computed them. The answer label (`choice`, `noul`, the prediction) is recomputed from
16
+ the calibrated probabilities.
17
+ """
18
+ import math
19
+
20
+ KINDS = ('choice', 'noul', 'score')
21
+ OPTIONS = {'choice': ('debias', 'temperature'), 'noul': ('temperature', 'floor'),
22
+ 'score': ('temperature', 'mode_weight')}
23
+ ROUTE_BUCKETS = ('fast', 'slow', 'slow_timeout_fast', 'fast_reread')
24
+ TEMPERATURE_MODES = (False, True, 'type', 'route')
25
+ T_MIN, T_MAX = 0.5, 8.0
26
+
27
+
28
+ def need(ok, code):
29
+ if not ok:
30
+ raise RuntimeError(code)
31
+
32
+
33
+ def finite(x):
34
+ return isinstance(x, (int, float)) and not isinstance(x, bool) and math.isfinite(x)
35
+
36
+
37
+ # ---------------------------------------------------------------- transforms
38
+ def distribution(p):
39
+ need(len(p) >= 2 and all(type(v) in (float, int) and math.isfinite(v) and 0 <= v <= 1 for v in p), 'BAD_PROBS')
40
+ need(abs(sum(p)-1) < 1e-6, 'BAD_PROB_SUM')
41
+ return list(p)
42
+
43
+
44
+ def softmax(scores):
45
+ need(scores and all(math.isfinite(x) for x in scores), 'BAD_LOGITS')
46
+ z = max(scores)
47
+ w = [math.exp(x-z) for x in scores]
48
+ return [x/sum(w) for x in w]
49
+
50
+
51
+ def temperature(p, t):
52
+ distribution(p)
53
+ need(math.isfinite(t) and t >= .5, 'BAD_TEMPERATURE')
54
+ return list(p) if t == 1 else softmax([math.log(max(x, 1e-300))/t for x in p])
55
+
56
+
57
+ def _argmax(p):
58
+ return max(range(len(p)), key=p.__getitem__)
59
+
60
+
61
+ def debias(p, prior):
62
+ """Option-position prior divided out, renormalised."""
63
+ need(len(prior) == len(p) and all(w > 0 for w in prior), 'CAL_PRIOR')
64
+ q = [x / w for x, w in zip(p, prior)]
65
+ s = sum(q)
66
+ return [x / s for x in q]
67
+
68
+
69
+ def noul_floor(p, floor):
70
+ """Labels no/yes: P(yes) is committed to >= floor on the favoured side (<= 1 - floor on the other)."""
71
+ need(len(p) == 2 and .8 < floor < 1, 'CAL_NOUL_FLOOR')
72
+ if p[1] >= floor or p[1] <= 1 - floor:
73
+ return list(p)
74
+ y = floor if p[1] >= .5 else 1 - floor
75
+ return [1 - y, y]
76
+
77
+
78
+ def score_mode(p, weight):
79
+ """Weight 0 keeps the distribution; weight 1 puts all mass on the most probable level."""
80
+ need(0 <= weight <= 1, 'CAL_SCORE_WEIGHT')
81
+ if weight == 0:
82
+ return list(p)
83
+ m = _argmax(p)
84
+ return [(1 - weight) * x + (weight if i == m else 0.) for i, x in enumerate(p)]
85
+
86
+
87
+ def n_active(cfg):
88
+ return sum(bool(v) for v in cfg.values())
89
+
90
+
91
+ def apply_one(kind, cfg, params, p, bucket='fast'):
92
+ q = list(p)
93
+ if cfg.get('debias') and str(len(q)) in params.get('prior', {}):
94
+ q = debias(q, params['prior'][str(len(q))])
95
+ mode = cfg.get('temperature')
96
+ if mode:
97
+ need(mode in (True, 'type', 'route'), 'CAL_TEMPERATURE_MODE')
98
+ t = params['route_T'].get(bucket, params['T']) if mode == 'route' else params['T']
99
+ q = temperature(q, t)
100
+ if kind == 'noul' and cfg.get('floor'):
101
+ q = noul_floor(q, cfg['floor'])
102
+ if kind == 'score' and cfg.get('mode_weight'):
103
+ q = score_mode(q, cfg['mode_weight'])
104
+ return distribution(q)
105
+
106
+
107
+ def route_bucket(detail):
108
+ """Route cell of one answered decision, from the service's own per-decision record:
109
+ slow_timeout_fast = first-pass answer served after an admitted drafting pass (deadline, failure or
110
+ unparsed draft; the 'fallback' calibration block); slow = answer read after the drafting pass
111
+ (appended answer line included); fast_reread = one-pass answer with the reversed-order re-read
112
+ averaged in; fast = everything else."""
113
+ if detail.get('route') in ('slow_timeout_fast', 'slow_failed_fast', 'slow_unparsed_fast') \
114
+ or detail.get('calibration_block') == 'fallback':
115
+ return 'slow_timeout_fast'
116
+ if detail.get('path') == 'slow' or detail.get('calibration_block') == 'slow' \
117
+ or detail.get('route') in ('slow', 'slow_forced_answer'):
118
+ return 'slow'
119
+ if isinstance(detail.get('reread'), dict):
120
+ return 'fast_reread'
121
+ return 'fast'
122
+
123
+
124
+ # ---------------------------------------------------------------- configuration
125
+ def _temperature_value(t):
126
+ return finite(t) and T_MIN <= t <= T_MAX
127
+
128
+
129
+ def validate(block):
130
+ """The config.json block: {type: {"config": {option: value},
131
+ "params": {"T": float, "route_T": {cell: float}, "route_counts": {cell: int}, "prior": {...}}}}."""
132
+ need(isinstance(block, dict) and set(block) <= set(KINDS), 'POSTCAL_TYPES')
133
+ for kind, entry in block.items():
134
+ need(isinstance(entry, dict) and set(entry) == {'config', 'params'}, 'POSTCAL_ENTRY')
135
+ cfg, params = entry['config'], entry['params']
136
+ need(isinstance(cfg, dict) and set(cfg) <= set(OPTIONS[kind]), 'POSTCAL_OPTIONS')
137
+ need(isinstance(params, dict) and set(params) <= {'T', 'prior', 'route_T', 'route_counts'}, 'POSTCAL_PARAMS')
138
+ need('debias' not in cfg or type(cfg['debias']) is bool, 'POSTCAL_SWITCH')
139
+ mode = cfg.get('temperature', False)
140
+ need(any(mode is m or (isinstance(m, str) and mode == m) for m in TEMPERATURE_MODES), 'POSTCAL_TEMPERATURE_MODE')
141
+ if mode:
142
+ need(_temperature_value(params.get('T')), 'POSTCAL_TEMPERATURE')
143
+ if mode == 'route':
144
+ cells = params.get('route_T')
145
+ need(isinstance(cells, dict) and set(cells) <= set(ROUTE_BUCKETS)
146
+ and all(_temperature_value(v) for v in cells.values()), 'POSTCAL_ROUTE_TEMPERATURE')
147
+ else:
148
+ need('route_T' not in params, 'POSTCAL_ROUTE_TEMPERATURE_UNUSED')
149
+ counts = params.get('route_counts')
150
+ need(counts is None or (isinstance(counts, dict) and set(counts) <= set(ROUTE_BUCKETS)
151
+ and all(type(v) is int and v >= 0 for v in counts.values())), 'POSTCAL_ROUTE_COUNTS')
152
+ if cfg.get('debias'):
153
+ prior = params.get('prior')
154
+ need(isinstance(prior, dict) and all(
155
+ k.isdigit() and int(k) >= 2 and isinstance(v, list) and len(v) == int(k)
156
+ and all(finite(w) and w > 0 for w in v) for k, v in prior.items()), 'POSTCAL_PRIOR')
157
+ if cfg.get('floor') is not None:
158
+ need(finite(cfg['floor']) and .8 < cfg['floor'] < 1, 'POSTCAL_FLOOR')
159
+ if 'mode_weight' in cfg:
160
+ need(finite(cfg['mode_weight']) and 0 <= cfg['mode_weight'] <= 1, 'POSTCAL_MODE_WEIGHT')
161
+ return block
162
+
163
+
164
+ def active(block):
165
+ """{type: [active option names]} for the types this block changes."""
166
+ return {kind: sorted(k for k, v in entry['config'].items() if v)
167
+ for kind, entry in sorted(block.items()) if n_active(entry['config'])}
168
+
169
+
170
+ # ---------------------------------------------------------------- service hook
171
+ def wrap_service(service, block, core):
172
+ """Calibrate each answer after the service computed it (same order as the measured setup:
173
+ after the deadline wrapper and its item rules, before the answer is serialised)."""
174
+ policy = validate(block)
175
+ need(not getattr(service, '_post_calibration_wrapped', False), 'POSTCAL_ALREADY_WRAPPED')
176
+ original = service._one_impl
177
+
178
+ def one(task):
179
+ answer, detail = original(task)
180
+ kind = task['question']['type']
181
+ entry = policy.get(kind)
182
+ if entry is None or not n_active(entry['config']):
183
+ return answer, detail
184
+ labels = core.render(task)[0]
185
+ if len(labels) < 2:
186
+ return answer, detail # one option or level: nothing to calibrate
187
+ probs = answer['probabilities']
188
+ if set(probs) == set(labels):
189
+ keys = list(labels)
190
+ elif kind == 'noul' and set(probs) == {'false', 'true'}:
191
+ keys = ['false', 'true'] # the service's no/yes order
192
+ else:
193
+ raise core.ItemFailure('post-calibration label mismatch')
194
+ bucket = route_bucket(detail)
195
+ try:
196
+ q = apply_one(kind, entry['config'], entry['params'], [probs[k] for k in keys], bucket)
197
+ except (RuntimeError, ValueError, ZeroDivisionError, OverflowError) as exc:
198
+ raise core.ItemFailure('post-calibration failed') from exc
199
+ calibrated = dict(zip(keys, q))
200
+ answer['probabilities'] = {k: calibrated[k] for k in probs}
201
+ by_label = dict(zip(labels, q))
202
+ detail['prediction'] = max(labels, key=by_label.get)
203
+ if kind == 'noul':
204
+ answer['noul'] = float(calibrated.get('yes', calibrated.get('true')))
205
+ elif kind == 'choice':
206
+ answer['choice'] = detail['prediction']
207
+ detail['post_calibration'] = dict(options=sorted(k for k, v in entry['config'].items() if v), route=bucket)
208
+ return answer, detail
209
+
210
+ service._one_impl = one
211
+ service._post_calibration_wrapped = True
212
+ return service
service/prepare_model.sh ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # NETWORKED maintainer preparation, never called by serve.sh.
3
+ # Downloads the pinned revision: the official base files at the folder root and the unmerged LoRA in adapter/.
4
+ set -euo pipefail
5
+ PKG=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
6
+ exec "${PYTHON:-python3}" -B - "$PKG" "${1:?new absolute external model directory}" <<'PY'
7
+ import json,re,sys
8
+ from pathlib import Path
9
+ pkg=Path(sys.argv[1]).resolve(); dest=Path(sys.argv[2])
10
+ m=json.loads((pkg/'MODEL_MANIFEST.json').read_text())
11
+ if not dest.is_absolute():raise SystemExit('Use an absolute destination')
12
+ dest=dest.resolve()
13
+ if dest.exists() or dest.is_relative_to(pkg) or pkg.is_relative_to(dest):raise SystemExit('Use a new separate model directory')
14
+ if m['repo_id'].startswith('OWNER/') or not re.fullmatch('[0-9a-f]{40}',str(m.get('revision') or '')):
15
+ raise SystemExit('Set the own Hugging Face repository and a complete 40-character revision in MODEL_MANIFEST.json')
16
+ if not m['files'] or not m['adapter']['files']:raise SystemExit('Model hash manifest is pending')
17
+ patterns=[*m['files'],*(m['adapter']['subdir']+'/'+name for name in m['adapter']['files'])]
18
+ from huggingface_hub import snapshot_download
19
+ snapshot_download(repo_id=m['repo_id'],revision=m['revision'],local_dir=dest,allow_patterns=patterns)
20
+ sys.path.insert(0,str(pkg))
21
+ from launch import check_model
22
+ check_model(dest,full=True)
23
+ print('Downloaded base and adapter files match the pinned manifest; no inference or submission performed.')
24
+ PY
service/quota.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Durable, conservative per-decision slow-path quota.
2
+
3
+ The lock spans inference: a slow attempt is charged before work starts, while
4
+ the denominator grows only after successful work. A crash therefore cannot
5
+ create quota credit. One counter file represents one global service history.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from contextlib import contextmanager
10
+ from dataclasses import dataclass
11
+ from decimal import Decimal
12
+ import errno
13
+ import json
14
+ import math
15
+ import os
16
+ from pathlib import Path
17
+ import tempfile
18
+ import threading
19
+ import time
20
+ from typing import Iterator
21
+
22
+
23
+ class QuotaStateError(RuntimeError):
24
+ """Counter storage is unreadable or invalid; inference must fail closed."""
25
+
26
+
27
+ @dataclass
28
+ class QuotaDecision:
29
+ path: str
30
+ reason: str
31
+ snapshot: dict
32
+
33
+
34
+ class _PathLock:
35
+ def __init__(self) -> None:
36
+ self.mutex = threading.RLock()
37
+ self.local = threading.local()
38
+
39
+
40
+ _REGISTRY_MUTEX = threading.Lock()
41
+ _PATH_LOCKS: dict[str, _PathLock] = {}
42
+ _ROOT = Path(__file__).resolve().parent
43
+
44
+
45
+ @contextmanager
46
+ def _os_lock(path: Path) -> Iterator[None]:
47
+ # Keep the lock file permanently: unlinking it would let another process
48
+ # lock a different inode while an existing process still holds this lock.
49
+ with path.open("a+b") as handle:
50
+ if os.name == "nt":
51
+ import msvcrt
52
+
53
+ handle.seek(0, os.SEEK_END)
54
+ if handle.tell() == 0:
55
+ handle.write(b"\0")
56
+ handle.flush()
57
+ while True:
58
+ handle.seek(0)
59
+ try:
60
+ msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
61
+ break
62
+ except OSError as exc:
63
+ if exc.errno not in (errno.EACCES, errno.EAGAIN, errno.EDEADLK):
64
+ raise
65
+ time.sleep(0.05)
66
+ try:
67
+ yield
68
+ finally:
69
+ handle.seek(0)
70
+ msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
71
+ else:
72
+ import fcntl
73
+
74
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
75
+ try:
76
+ yield
77
+ finally:
78
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
79
+
80
+
81
+ def _fsync_directory(path: Path) -> None:
82
+ if os.name == "nt":
83
+ return # Windows does not expose a directory fsync via os.open.
84
+ descriptor = os.open(str(path), os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
85
+ try:
86
+ try:
87
+ os.fsync(descriptor)
88
+ except OSError as exc:
89
+ if exc.errno not in (errno.EINVAL, errno.ENOTSUP):
90
+ raise
91
+ finally:
92
+ os.close(descriptor)
93
+
94
+
95
+ def cost_settings(config):
96
+ settings = config['routing'].get('cost_fuse', {'enabled': False})
97
+ prices = config['pricing']['models'][config['pricing']['model_key']]
98
+ known = all(isinstance(prices.get(k), (int, float)) and not isinstance(prices[k], bool)
99
+ and math.isfinite(prices[k]) and prices[k] >= 0
100
+ for k in ('input_per_million', 'output_per_million'))
101
+ if settings.get('enabled', True) and not known and config['routing']['mode'] == 'routed':
102
+ raise ValueError('Routed cost fuse requires known profile prices; explicitly use --cost-fuse off')
103
+ return settings, prices, known
104
+
105
+
106
+ class CostFuse:
107
+ def __init__(self, limit_per_1000, *, warmup=100, arm=.85, release=.80, state=None, enabled=True):
108
+ if not math.isfinite(limit_per_1000) or limit_per_1000 <= 0 or not 0 < release < arm <= 1:
109
+ raise ValueError('Invalid cost fuse limits')
110
+ if type(warmup) is not int or warmup < 100: raise ValueError('Cost warmup must be at least 100')
111
+ self.limit = limit_per_1000 / 1000
112
+ self.warmup, self.arm, self.release, self.enabled = warmup, arm, release, enabled
113
+ self.s = dict(n=0, cost=0., n_slow=0, cost_slow=0., engaged=False, transitions=0) if state is None else dict(state)
114
+ v=self.s
115
+ if (any(type(v.get(k)) is not int or v[k]<0 for k in ('n','n_slow','transitions'))
116
+ or v['n_slow']>v['n'] or type(v.get('engaged')) is not bool
117
+ or any(not isinstance(v.get(k),(int,float)) or not math.isfinite(v[k]) or v[k]<0 for k in ('cost','cost_slow'))
118
+ or v['cost_slow']>v['cost']+1e-12): raise QuotaStateError('Invalid cost fuse state')
119
+
120
+ def state(self):
121
+ return 'off' if not self.enabled else 'observe' if self.s['n'] < self.warmup else 'engaged' if self.s['engaged'] else 'idle'
122
+
123
+ def quota(self, tier_quota):
124
+ s=self.s
125
+ if self.state()!='engaged': return tier_quota,'tier'
126
+ n_fast=s['n']-s['n_slow']
127
+ fast=(s['cost']-s['cost_slow'])/n_fast if n_fast else 0.
128
+ slow=s['cost_slow']/s['n_slow'] if s['n_slow'] else None
129
+ # If even fast exceeds target, remove slow admissions; never alter fast.
130
+ q=0. if fast>=self.release*self.limit else (
131
+ max(0.,(self.release*self.limit-fast)/(slow-fast)) if slow is not None and slow>fast else tier_quota)
132
+ return (q,'cost') if q<tier_quota else (tier_quota,'tier')
133
+
134
+ def record(self, cost_usd, slow):
135
+ if not math.isfinite(cost_usd) or cost_usd<0: raise ValueError('Invalid observed cost')
136
+ s=self.s;s['n']+=1;s['cost']+=cost_usd
137
+ if slow:s['n_slow']+=1;s['cost_slow']+=cost_usd
138
+ if not self.enabled or s['n']<self.warmup:return
139
+ running=s['cost']/s['n'];old=s['engaged']
140
+ if not old and running>=self.arm*self.limit:s['engaged']=True
141
+ elif old and running<=self.release*self.limit:s['engaged']=False
142
+ if old!=s['engaged']:s['transitions']+=1
143
+
144
+ def snapshot(self, quota):
145
+ q,source=self.quota(quota)
146
+ return dict(state=self.state(),n=self.s['n'],running_cost_per_1000=self.s['cost']/self.s['n']*1000 if self.s['n'] else 0.,
147
+ effective_quota=q,quota_source=source,transitions=self.s['transitions'],cap_per_1000=self.limit*1000)
148
+
149
+
150
+ def configure_cost(counter, config):
151
+ settings,prices,known=cost_settings(config)
152
+ counter.cost_prices=prices if known else None
153
+ counter.cost_options=dict(limit_per_1000=config['limits']['cost_per_1000_decisions_usd']*(1-config['pricing']['margin']),
154
+ warmup=settings.get('warmup',100),arm=settings.get('arm',.85),release=settings.get('release',.80),
155
+ enabled=settings.get('enabled',True) and known and config['routing']['mode']=='routed')
156
+ counter.cost_fuse=CostFuse(**counter.cost_options)
157
+ counter.tier_quota=config['routing']['quota']
158
+
159
+
160
+ class QuotaCounter:
161
+ """Serialize decisions across threads/processes sharing ``state_path``.
162
+
163
+ ``quota`` and ``fuse`` must come from the caller's configuration. Uniform
164
+ experiments can explicitly use quota=fuse=1; an ordinary routed service
165
+ should use its configured limits. Persisted slow attempts include failures.
166
+ """
167
+
168
+ def __init__(self, state_path: str | Path, *, quota: float, fuse: float):
169
+ if (
170
+ isinstance(quota, bool)
171
+ or isinstance(fuse, bool)
172
+ or not math.isfinite(quota)
173
+ or not math.isfinite(fuse)
174
+ or not 0 <= quota <= fuse <= 1
175
+ ):
176
+ raise ValueError("Require finite 0 <= quota <= fuse <= 1")
177
+ self.quota = float(quota)
178
+ self.fuse = float(fuse)
179
+ self._quota = Decimal(str(quota))
180
+ self._fuse = Decimal(str(fuse))
181
+ target = Path(state_path)
182
+ if not target.is_absolute():
183
+ raise QuotaStateError("Quota state path must be absolute")
184
+ # Symbolic links are resolved first; state and lock must stay beside each other, outside the package.
185
+ self.state_path = target.resolve()
186
+ if self.state_path.is_relative_to(_ROOT) or _ROOT.is_relative_to(self.state_path):
187
+ raise QuotaStateError("Quota state must be separate from the package")
188
+ self.lock_path = self.state_path.with_name(self.state_path.name + ".lock")
189
+ if self.lock_path.resolve().parent != self.state_path.parent:
190
+ raise QuotaStateError("Quota lock must stay inside its state directory")
191
+ key = os.path.normcase(str(self.state_path))
192
+ with _REGISTRY_MUTEX:
193
+ self._lock = _PATH_LOCKS.setdefault(key, _PathLock())
194
+ self.state_path.parent.mkdir(parents=True, exist_ok=True)
195
+ with self._exclusive():
196
+ if self.state_path.exists():
197
+ self._read()
198
+ else:
199
+ self._write({"version": 2, "total": 0, "slow": 0})
200
+
201
+ @contextmanager
202
+ def _exclusive(self) -> Iterator[None]:
203
+ with self._lock.mutex:
204
+ depth = getattr(self._lock.local, "depth", 0)
205
+ if depth:
206
+ self._lock.local.depth = depth + 1
207
+ try:
208
+ yield
209
+ finally:
210
+ self._lock.local.depth = depth
211
+ else:
212
+ with _os_lock(self.lock_path):
213
+ self._lock.local.depth = 1
214
+ try:
215
+ yield
216
+ finally:
217
+ self._lock.local.depth = 0
218
+
219
+ def _read(self) -> dict:
220
+ try:
221
+ with self.state_path.open("r", encoding="utf-8") as handle:
222
+ state = json.load(handle)
223
+ except (OSError, ValueError) as exc:
224
+ raise QuotaStateError("Cannot read quota state; refusing to reset it") from exc
225
+ if (
226
+ not isinstance(state, dict)
227
+ or type(state.get("version")) is not int
228
+ or state["version"] not in (1, 2)
229
+ or any(type(state.get(key)) is not int or state[key] < 0 for key in ("total", "slow"))
230
+ ):
231
+ raise QuotaStateError("Invalid quota state; refusing to reset it")
232
+ # slow may exceed total after a failed or interrupted slow attempt.
233
+ return state
234
+
235
+ def _write(self, state: dict) -> None:
236
+ temporary: str | None = None
237
+ try:
238
+ with tempfile.NamedTemporaryFile(
239
+ mode="w", encoding="utf-8", dir=self.state_path.parent,
240
+ prefix=self.state_path.name + ".", suffix=".tmp", delete=False,
241
+ ) as handle:
242
+ temporary = handle.name
243
+ json.dump(state, handle, sort_keys=True, allow_nan=False)
244
+ handle.write("\n")
245
+ handle.flush()
246
+ os.fsync(handle.fileno())
247
+ os.replace(temporary, self.state_path)
248
+ temporary = None
249
+ _fsync_directory(self.state_path.parent)
250
+ except OSError as exc:
251
+ raise QuotaStateError("Cannot persist quota state; refusing inference") from exc
252
+ finally:
253
+ if temporary is not None:
254
+ try:
255
+ Path(temporary).unlink(missing_ok=True)
256
+ except OSError:
257
+ pass
258
+
259
+ @staticmethod
260
+ def _snapshot(state: dict) -> dict:
261
+ total, slow = state["total"], state["slow"]
262
+ return {
263
+ "total": total,
264
+ "slow": slow,
265
+ "slow_fraction": slow / total if total else float(slow > 0),
266
+ }
267
+
268
+ def snapshot(self) -> dict:
269
+ with self._exclusive():
270
+ return self._snapshot(self._read())
271
+
272
+ def configure_cost(self, config):
273
+ configure_cost(self, config)
274
+ with self._exclusive():self.cost_fuse=CostFuse(**self.cost_options,state=self._read().get('cost_fuse'))
275
+
276
+ @contextmanager
277
+ def accounting(self):
278
+ with self._exclusive():
279
+ state=self._read()
280
+ self.cost_fuse=CostFuse(**self.cost_options,state=state.get('cost_fuse'))
281
+ yield
282
+
283
+ def record_cost(self, usage, slow):
284
+ with self._exclusive():
285
+ before=self.cost_fuse.snapshot(self.tier_quota)
286
+ if self.cost_prices is not None:
287
+ p=self.cost_prices
288
+ self.cost_fuse.record((usage['input_tokens']*p['input_per_million']+usage['output_tokens']*p['output_per_million'])/1e6,slow)
289
+ after=self.cost_fuse.snapshot(self.tier_quota)
290
+ state=self._read();state.update(version=2,cost_fuse=self.cost_fuse.s);self._write(state)
291
+ if any(before[k]!=after[k] for k in ('state','effective_quota')):
292
+ print('cost_fuse '+json.dumps(after,sort_keys=True),flush=True)
293
+ return after
294
+
295
+ @contextmanager
296
+ def decision(self, wants_slow: bool, quota=None) -> Iterator[QuotaDecision]:
297
+ """Reserve, run caller work, then commit only on normal context exit.
298
+
299
+ Use ``with counter.decision(wants_slow) as decision`` and run inference
300
+ inside that context. Propagate failures out of the block: swallowing an
301
+ inference failure would incorrectly record a successful decision.
302
+ """
303
+ with self._exclusive():
304
+ if getattr(self._lock.local, "active_decision", False):
305
+ raise RuntimeError("Nested quota decisions are not supported")
306
+ self._lock.local.active_decision = True
307
+ try:
308
+ state = self._read()
309
+ total, slow = state["total"], state["slow"]
310
+ if Decimal(slow) > self._fuse * total:
311
+ path, reason = "fast", "fuse"
312
+ elif not wants_slow:
313
+ path, reason = "fast", "router"
314
+ elif Decimal(slow + 1) > self._fuse * (total + 1):
315
+ path, reason = "fast", "fuse"
316
+ elif Decimal(slow + 1) > min(self._quota, Decimal(str(quota)) if quota is not None else self._quota) * (total + 1):
317
+ path, reason = "fast", "quota"
318
+ else:
319
+ path, reason = "slow", "eligible"
320
+ state["slow"] += 1
321
+ self._write(state)
322
+ decision = QuotaDecision(path, reason, self._snapshot(state))
323
+ yield decision
324
+ state["total"] += 1
325
+ self._write(state)
326
+ decision.snapshot = self._snapshot(state)
327
+ finally:
328
+ self._lock.local.active_decision = False
329
+
330
+ class RuntimeCounter:
331
+ """Run-scoped durable counter: startup IO failures abort; later IO failures use fast only."""
332
+ def __init__(self, config):
333
+ import logging
334
+ import uuid
335
+ routing = config['routing']
336
+ self.run_id = routing.get('run_id') or uuid.uuid4().hex
337
+ if Path(self.run_id).name != self.run_id or self.run_id in ('.', '..'):
338
+ raise ValueError('run_id must be a single directory name')
339
+ self.memory_only = False
340
+ self._mutex = threading.RLock()
341
+ self._state = {'total':0, 'slow':0}
342
+ self.durable = None
343
+ if not routing.get('state_dir') or not Path(routing['state_dir']).is_absolute():
344
+ raise QuotaStateError('An absolute quota state directory is required; refusing startup')
345
+ state = Path(routing['state_dir']).resolve()
346
+ if state.is_relative_to(_ROOT) or _ROOT.is_relative_to(state):
347
+ raise QuotaStateError('Quota state directory must be separate from the package')
348
+ directory = (state / self.run_id).resolve()
349
+ if directory.parent != state:
350
+ raise QuotaStateError('Run directory must stay inside the state directory')
351
+ for name in ('run.json', 'quota.json', 'quota.json.lock'):
352
+ if (directory / name).resolve().parent != directory:
353
+ raise QuotaStateError('Run files must stay inside the run directory')
354
+ fingerprint = {k:routing[k] for k in ('mode','quota','fuse')}
355
+ fingerprint.update(model_key=config['model']['model_key'], run_id=self.run_id,
356
+ tier=routing.get('tier'), tiers=routing.get('tiers'), cost_fuse=routing.get('cost_fuse'),
357
+ pricing=config.get('pricing'), cost_cap=config['limits']['cost_per_1000_decisions_usd'],
358
+ basis=config.get('usage',{}).get('basis','engine'))
359
+ try:
360
+ if routing.get('resume'):
361
+ if not routing.get('run_id'):
362
+ raise ValueError('resume requires an explicit run_id')
363
+ if not (directory/'quota.json').is_file() or not (directory/'run.json').is_file():
364
+ raise ValueError('Resume state is missing')
365
+ if json.loads((directory/'run.json').read_text(encoding='utf-8')) != fingerprint:
366
+ raise ValueError('Run fingerprint mismatch')
367
+ else:
368
+ if directory.exists():
369
+ raise ValueError('run_id already exists; select a new run_id or explicit resume')
370
+ directory.mkdir(parents=True, exist_ok=False)
371
+ with (directory/'run.json').open('x', encoding='utf-8') as handle:
372
+ handle.write(json.dumps(fingerprint))
373
+ self.durable = QuotaCounter(directory/'quota.json', quota=routing['quota'], fuse=routing['fuse'])
374
+ self._state = self.durable.snapshot()
375
+ except (OSError, QuotaStateError):
376
+ if routing.get('resume'):
377
+ raise ValueError('Resume state cannot be read or validated') from None
378
+ raise QuotaStateError('Quota persistence unavailable; refusing startup') from None
379
+
380
+ def configure_cost(self, config):
381
+ configure_cost(self, config)
382
+ if self.durable is not None:
383
+ self.durable.configure_cost(config)
384
+ self.cost_fuse=self.durable.cost_fuse
385
+
386
+ @contextmanager
387
+ def accounting(self):
388
+ import logging
389
+ with self._mutex:
390
+ context=None
391
+ if not self.memory_only:
392
+ try:
393
+ context=self.durable.accounting();context.__enter__()
394
+ self.cost_fuse=self.durable.cost_fuse
395
+ except (OSError,QuotaStateError):
396
+ context=None;self.memory_only=True
397
+ logging.warning('Cost state unavailable; memory counts and fast only')
398
+ try:yield
399
+ except BaseException as exc:
400
+ if context is not None:context.__exit__(type(exc),exc,exc.__traceback__)
401
+ raise
402
+ else:
403
+ if context is not None:context.__exit__(None,None,None)
404
+
405
+ def record_cost(self, usage, slow):
406
+ import logging
407
+ before=self.cost_fuse.s['n']
408
+ if not self.memory_only:
409
+ try:return self.durable.record_cost(usage,slow)
410
+ except (OSError,QuotaStateError):
411
+ self.memory_only=True
412
+ logging.warning('Cost commit unavailable; memory counts and fast only')
413
+ # A failed disk commit may already have updated this shared fuse object.
414
+ if self.cost_prices is not None and self.cost_fuse.s['n']==before:
415
+ p=self.cost_prices
416
+ self.cost_fuse.record((usage['input_tokens']*p['input_per_million']+usage['output_tokens']*p['output_per_million'])/1e6,slow)
417
+ return self.cost_fuse.snapshot(self.tier_quota)
418
+
419
+ def snapshot(self):
420
+ with self._mutex:
421
+ return dict(self._state)
422
+
423
+ @contextmanager
424
+ def decision(self, wants_slow, quota=None):
425
+ import logging
426
+ with self._mutex:
427
+ context = None
428
+ if not self.memory_only:
429
+ try:
430
+ context = self.durable.decision(wants_slow, quota=quota)
431
+ decision = context.__enter__()
432
+ self._state = dict(decision.snapshot)
433
+ except (OSError, QuotaStateError):
434
+ context = None
435
+ self.memory_only = True
436
+ logging.warning('Quota persistence failed; using memory counts and fast only')
437
+ if context is None:
438
+ decision = QuotaDecision('fast', 'memory_only', dict(self._state))
439
+ try:
440
+ yield decision
441
+ except BaseException as exc:
442
+ if context is not None:
443
+ context.__exit__(type(exc), exc, exc.__traceback__)
444
+ raise
445
+ else:
446
+ self._state['total'] += 1
447
+ if context is not None:
448
+ try:
449
+ context.__exit__(None, None, None)
450
+ self._state = dict(decision.snapshot)
451
+ except (OSError, QuotaStateError):
452
+ self.memory_only = True
453
+ logging.warning('Quota commit failed; using memory counts and fast only')
454
+ decision.snapshot = dict(self._state)
service/reconcile_usage.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Usage reconciliation before release (maintainer tool): reads bench outputs and engine counter
2
+ snapshots only; sends no inference requests and reads no request content.
3
+
4
+ python3 -B reconcile_usage.py --run /abs/out/fast [--run-off /abs/out/routed_off] [--tol 0.001]
5
+ Exit code: 0 passed; 1 an identity does not hold; 2 a precondition is not met (missing data or counters).
6
+ """
7
+ import argparse, json, sys, math
8
+ from pathlib import Path
9
+
10
+ from core import external_path
11
+
12
+ COUNTERS = ('prompt_tokens', 'generation_tokens', 'prefix_cache_queries', 'prefix_cache_hits',
13
+ 'num_preemptions', 'request_success')
14
+ FIELDS = ('input', 'output', 'total', 'cached', 'submitted', 'calls', 'generated')
15
+
16
+
17
+ def resolve(prefix):
18
+ """Run prefixes are absolute paths outside the package (see bench.py --output-prefix)."""
19
+ return external_path(prefix)
20
+
21
+
22
+ def load(prefix):
23
+ p = resolve(prefix)
24
+ rows = [json.loads(x) for x in p.with_name(p.name + '.jsonl').read_text(encoding='utf-8').splitlines() if x.strip()]
25
+ stats = json.loads(p.with_name(p.name + '.engine_stats.json').read_text(encoding='utf-8'))
26
+ rows = [r for r in rows if r.get('concurrency', 1) == 1] # reconcile the serial level only
27
+ if not rows:
28
+ sys.exit(2)
29
+ return p, rows, stats
30
+
31
+
32
+ def delta(stats):
33
+ level = stats['by_concurrency']['1']
34
+ before, after = level['before']['counters'], level['after']['counters']
35
+ missing = [k for k in COUNTERS if k not in before or k not in after]
36
+ if missing:
37
+ raise ValueError('engine counters missing: '+','.join(missing))
38
+ if any(isinstance(v,bool) or not isinstance(v,(int,float)) or not math.isfinite(v) for d in (before,after) for v in (d[k] for k in COUNTERS)):raise ValueError('Invalid counter')
39
+ return {k: after[k] - before[k] for k in COUNTERS}
40
+
41
+
42
+ def row_usage(row):
43
+ u = row.get('usage') or {}
44
+ if not isinstance(u,dict):return False,{}
45
+ d = u.get('input_tokens_details') or {}
46
+ if not isinstance(d,dict):return False,{}
47
+ v = dict(input=u.get('input_tokens'), output=u.get('output_tokens'), total=u.get('total_tokens'),
48
+ cached=d.get('cached_tokens'), submitted=d.get('engine_submitted_tokens'),
49
+ calls=d.get('engine_calls'), generated=d.get('engine_generated_tokens'))
50
+ ok = (row.get('ok') and u.get('accounting') == 'engine' and not u.get('usage_estimated')
51
+ and d.get('cached_tokens_known', True) and all(type(v[k]) is int and v[k] >= 0 for k in FIELDS))
52
+ return bool(ok), v
53
+
54
+
55
+ def close(x, y, tol):
56
+ return abs(x - y) <= tol * max(1, abs(y))
57
+
58
+
59
+ def reconcile_one(rows, stats, tol):
60
+ fails, parsed = [], [row_usage(r) for r in rows]
61
+ if not rows or len({r.get('id') for r in rows})!=len(rows) or any(r.get('concurrency',1)!=1 for r in rows) or not all(ok for ok, _ in parsed):
62
+ return {'precondition': 'all rows ok, engine basis, exact usage'}, ['precondition']
63
+ U = [v for _, v in parsed]
64
+ S = {k: sum(v[k] for v in U) for k in FIELDS}
65
+ if S['generated']!=S['output']:fails.append('generated/output identity broken')
66
+ level=stats.get('by_concurrency',{}).get('1',{})
67
+ source=level.get('after',{}).get('source')
68
+ if source=='hf_forward_hook':
69
+ try:
70
+ before,after=level['before']['counters'],level['after']['counters']
71
+ if after.get('uncounted_forwards',0)>before.get('uncounted_forwards',0):
72
+ return {'precondition':'HF uncounted forwards'},['precondition']
73
+ D={k:after[k]-before[k] for k in ('prefill_tokens','forward_tokens')}
74
+ ds=[r['usage']['input_tokens_details'] for r in rows]
75
+ prefill=sum(d['hf_prefill_tokens'] for d in ds)
76
+ calls=sum(d['generate_nonempty_calls'] for d in ds)
77
+ except (KeyError,TypeError): return {'precondition':'HF counters missing'},['precondition']
78
+ if S['cached'] or any(v['input']!=v['submitted'] or v['total']!=v['input']+v['output'] for v in U): fails.append('HF row identity broken')
79
+ if D['prefill_tokens']!=prefill: fails.append('HF initial prefill mismatch')
80
+ if D['forward_tokens']!=S['input']+S['output']-calls: fails.append('HF forward identity broken')
81
+ return dict(sums=S,engine_delta=D,counter_mode='hf_forward'),fails
82
+ try: D = delta(stats)
83
+ except (KeyError,ValueError,TypeError): return {'precondition':'engine counters missing'},['precondition']
84
+ if any(v<0 for v in D.values()): return {'precondition':'counter reset'},['precondition']
85
+ if D['num_preemptions']:
86
+ return {'precondition':'preemption during run'},['precondition']
87
+ if D['request_success'] != S['calls']:
88
+ return {'precondition':'stray, warmup or missed engine calls'},['precondition']
89
+ bad = [r['id'] for r, v in zip(rows, U) if v['input'] != v['submitted'] - v['cached'] or v['total'] != v['input'] + v['output']]
90
+ if bad:
91
+ fails.append('row identity broken: %s' % bad[:5])
92
+ if close(S['submitted'], D['prompt_tokens'], tol):
93
+ mode, prefill = 'full_prompt', D['prompt_tokens'] - D['prefix_cache_hits']
94
+ elif close(S['submitted'] - S['cached'], D['prompt_tokens'], tol):
95
+ mode, prefill = 'computed_only', D['prompt_tokens']
96
+ else:
97
+ mode, prefill = 'unknown', None
98
+ fails.append('sum(engine_submitted) matches neither engine prompt-counter definition')
99
+ if not close(S['cached'], D['prefix_cache_hits'], tol):
100
+ fails.append('sum(cached_tokens) != delta prefix_cache_hits')
101
+ if prefill is not None and not close(S['input'], prefill, tol): # identity E1
102
+ fails.append('E1: sum(usage.input_tokens) != engine prefill')
103
+ if not close(S['output'], D['generation_tokens'], tol):
104
+ fails.append('sum(usage.output_tokens) != delta generation_tokens')
105
+ return dict(sums=S, engine_delta=D, counter_mode=mode, engine_prefill=prefill), fails
106
+
107
+
108
+ def reconcile_pair(on_rows, off_rows, off_stats, tol):
109
+ fails = []
110
+ if off_stats.get('health', {}).get('prefix_caching') is not False:
111
+ fails.append('off run did not disable prefix caching')
112
+ if not all(row_usage(r)[0] for r in on_rows+off_rows): return {},['precondition']
113
+ if len({r['id'] for r in on_rows})!=len(on_rows) or len({r['id'] for r in off_rows})!=len(off_rows): return {},['precondition']
114
+ on = {r['id']: row_usage(r)[1] for r in on_rows}
115
+ off = {r['id']: row_usage(r)[1] for r in off_rows}
116
+ if any(v['cached'] for v in off.values()):
117
+ fails.append('off run reports cached tokens')
118
+ matched = [i for i in on if i in off and on[i]['submitted'] == off[i]['submitted'] and on[i]['output'] == off[i]['output']]
119
+ coverage = len(matched) / max(1, len(on),len(off))
120
+ if coverage < 0.99:
121
+ fails.append('decode drift: only %.1f%% rows identical between runs' % (100 * coverage))
122
+ diff = sum(off[i]['input'] - on[i]['input'] for i in matched)
123
+ cached = sum(on[i]['cached'] for i in matched)
124
+ if not close(diff, cached, tol): # identity E2
125
+ fails.append('E2: sum(input_off) - sum(input_on) != sum(cached_on)')
126
+ return dict(matched=len(matched), coverage=coverage, input_diff=diff, cached_on=cached), fails
127
+
128
+
129
+ def main(argv=None):
130
+ ap = argparse.ArgumentParser(description=__doc__)
131
+ ap.add_argument('--run', required=True)
132
+ ap.add_argument('--run-off')
133
+ ap.add_argument('--tol', type=float, default=0.001)
134
+ a = ap.parse_args(argv)
135
+ if not 0<=a.tol<=0.001: ap.error('tol must be within [0, 0.001]')
136
+ try: path, rows, stats = load(a.run)
137
+ except (OSError,ValueError,KeyError) as exc:
138
+ print('precondition: '+str(exc), file=sys.stderr); return 2
139
+ report, fails = reconcile_one(rows, stats, a.tol)
140
+ if a.run_off:
141
+ try: _, off_rows, off_stats = load(a.run_off)
142
+ except (OSError,ValueError,KeyError) as exc:
143
+ print('precondition: '+str(exc), file=sys.stderr); return 2
144
+ off_report, off_fails = reconcile_one(off_rows, off_stats, a.tol)
145
+ pair, pair_fails = reconcile_pair(rows, off_rows, off_stats, a.tol)
146
+ report.update(off=off_report, pair=pair)
147
+ fails += ['off: ' + f for f in off_fails] + pair_fails
148
+ report.update(tolerance=a.tol, passed=not fails, submission_ready=bool(a.run_off) and not fails, failures=fails)
149
+ with path.with_name(path.name + '.reconcile.json').open('x', encoding='utf-8') as out: # exclusive create, never overwrite
150
+ json.dump(report, out, ensure_ascii=False, indent=2, allow_nan=False)
151
+ print(json.dumps({'passed': not fails, 'failures': fails}, ensure_ascii=False))
152
+ return 0 if not fails else (2 if any('precondition' in f for f in fails) else 1)
153
+
154
+
155
+ if __name__ == '__main__':
156
+ raise SystemExit(main())
service/requirements.lock ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Measured version lock; versions only, no artifact hashes.
2
+ # All measured entries retained; torch CUDA build made explicit.
3
+ --extra-index-url https://download.pytorch.org/whl/cu130
4
+ agent-detector==2.0.0
5
+ aiohappyeyeballs==2.7.1
6
+ aiohttp==3.14.3
7
+ aiosignal==1.4.0
8
+ annotated-doc==0.0.5
9
+ annotated-types==0.8.0
10
+ anthropic==1.11.0
11
+ anyio==4.15.1
12
+ apache-tvm-ffi==0.1.11
13
+ astor==0.8.1
14
+ async-timeout==5.0.1
15
+ attrs==26.1.0
16
+ backports.strenum==1.3.1
17
+ blake3==1.0.10
18
+ cachetools==7.2.0
19
+ cbor2==6.1.5
20
+ certifi==2026.7.22
21
+ cffi==2.1.1
22
+ charset-normalizer==3.5.2
23
+ click==8.5.0
24
+ cloudpickle==3.1.2
25
+ compressed-tensors==0.17.0
26
+ cryptography==50.0.2
27
+ cuda-bindings==13.4.3
28
+ cuda-core==1.2.1
29
+ cuda-pathfinder==1.8.3
30
+ cuda-python==13.4.1
31
+ cuda-tile==1.6.0
32
+ cuda-toolkit==13.0.3.0
33
+ depyf==0.20.0
34
+ detect-installer==0.2.1
35
+ dill==0.4.1
36
+ dnspython==2.8.0
37
+ docstring_parser==0.18.0
38
+ einops==0.8.2
39
+ email-validator==2.3.0
40
+ exceptiongroup==1.3.1
41
+ fastapi==0.136.3
42
+ fastapi-cli==0.0.32
43
+ fastapi-cloud-cli==0.26.0
44
+ fastar==0.12.0
45
+ fastsafetensors==0.4.0
46
+ filelock==4.0.12
47
+ flashinfer-python==0.7.0.post1
48
+ frozenlist==1.8.0
49
+ fsspec==2026.9.0
50
+ googleapis-common-protos==1.75.5
51
+ grpcio==1.84.0
52
+ h11==0.16.0
53
+ hf-xet==1.6.0
54
+ httpcore==1.0.9
55
+ httpcore2==2.13.1
56
+ httptools==0.8.0
57
+ httpx==0.28.1
58
+ httpx2==2.13.1
59
+ huggingface_hub==1.33.0
60
+ humming-kernels==0.1.16
61
+ idna==3.20
62
+ ijson==3.5.1
63
+ instanttensor==0.2.0
64
+ interegular==0.3.3
65
+ Jinja2==3.1.6
66
+ jiter==0.17.0
67
+ jmespath==1.1.0
68
+ jsonschema==4.26.0
69
+ jsonschema-specifications==2025.9.1
70
+ lark==1.2.2
71
+ llguidance==1.7.6
72
+ llvmlite==0.47.0
73
+ lm-format-enforcer==0.11.3
74
+ loguru==0.7.3
75
+ markdown-it-py==4.2.0
76
+ MarkupSafe==3.0.4
77
+ mcp==2.3.0
78
+ mcp-types==2.3.0
79
+ mdurl==0.1.2
80
+ mistral_common==1.12.0
81
+ ml_dtypes==0.6.0
82
+ model-hosting-container-standards==0.1.16
83
+ mpmath==1.3.0
84
+ msgspec==0.22.0
85
+ multidict==6.9.1
86
+ nccl-extensions==0.1.0
87
+ nccl4py==0.6.0
88
+ networkx==3.4.2
89
+ ninja==1.13.2
90
+ numba==0.65.0
91
+ numpy==2.2.6
92
+ nvidia-cublas==13.1.1.3
93
+ nvidia-cuda-cccl==13.3.4.3.1
94
+ nvidia-cuda-crt==13.4.92
95
+ nvidia-cuda-cupti==13.0.85
96
+ nvidia-cuda-nvcc==13.4.92
97
+ nvidia-cuda-nvdisasm==13.4.92
98
+ nvidia-cuda-nvrtc==13.0.88
99
+ nvidia-cuda-runtime==13.0.96
100
+ nvidia-cudnn-cu13==9.20.0.48
101
+ nvidia-cudnn-frontend==1.30.0
102
+ nvidia-cufft==12.0.0.61
103
+ nvidia-cufile==1.15.1.6
104
+ nvidia-curand==10.4.0.35
105
+ nvidia-cusolver==12.0.4.66
106
+ nvidia-cusparse==12.6.3.3
107
+ nvidia-cusparselt-cu13==0.8.1
108
+ nvidia-cutlass-dsl==4.7.1
109
+ nvidia-cutlass-dsl-libs-base==4.7.1
110
+ nvidia-cutlass-dsl-libs-core==4.7.1
111
+ nvidia-cutlass-dsl-libs-cu12==4.7.1
112
+ nvidia-cutlass-dsl-libs-cu13==4.7.1
113
+ nvidia-ml-py==13.615.71
114
+ nvidia-nccl-cu13==2.29.7
115
+ nvidia-nvjitlink==13.4.92
116
+ nvidia-nvshmem-cu13==3.4.5
117
+ nvidia-nvtx==13.0.85
118
+ nvidia-nvvm==13.4.92
119
+ nvtx==0.2.15
120
+ openai==3.24.0
121
+ opencv-python-headless==5.0.0.93
122
+ opentelemetry-api==1.45.0
123
+ opentelemetry-exporter-http-transport==0.66b0
124
+ opentelemetry-exporter-otlp==1.45.0
125
+ opentelemetry-exporter-otlp-common==0.66b0
126
+ opentelemetry-exporter-otlp-proto-common==1.45.0
127
+ opentelemetry-exporter-otlp-proto-grpc==1.45.0
128
+ opentelemetry-exporter-otlp-proto-http==1.45.0
129
+ opentelemetry-proto==1.45.0
130
+ opentelemetry-sdk==1.45.0
131
+ opentelemetry-semantic-conventions==0.66b0
132
+ opentelemetry-semantic-conventions-ai==0.5.1
133
+ oss-harmony==0.0.11
134
+ outlines_core==0.2.14
135
+ packaging==26.3
136
+ partial-json-parser==0.2.1.1.post7
137
+ pillow==12.3.0
138
+ prometheus-fastapi-instrumentator==8.1.0
139
+ prometheus_client==0.26.0
140
+ propcache==0.5.4
141
+ protobuf==7.36.2
142
+ psutil==7.2.2
143
+ py-cpuinfo==9.0.0
144
+ pybase64==1.5.1
145
+ pycountry==26.2.16
146
+ pycparser==3.0
147
+ pydantic==2.13.5
148
+ pydantic-extra-types==2.11.1
149
+ pydantic-settings==2.15.0
150
+ pydantic_core==2.46.5
151
+ Pygments==2.21.0
152
+ PyJWT==2.15.1
153
+ pynvvideocodec==2.0.4
154
+ python-dotenv==1.2.4
155
+ python-json-logger==4.2.0
156
+ python-multipart==0.0.32
157
+ PyYAML==6.0.3
158
+ pyzmq==27.2.0
159
+ quack-kernels==0.6.5
160
+ referencing==0.37.0
161
+ regex==2026.9.29
162
+ requests==2.34.2
163
+ rich==15.0.0
164
+ rich-toolkit==0.20.5
165
+ rignore==0.8.1
166
+ rpds-py==0.30.0
167
+ safetensors==0.8.0
168
+ sentencepiece==0.2.2
169
+ sentry-sdk==2.71.0
170
+ setproctitle==1.3.8
171
+ shellingham==1.5.4
172
+ sniffio==1.3.1
173
+ sse-starlette==3.5.0
174
+ starlette==1.7.0
175
+ supervisor==4.3.0
176
+ sympy==1.14.0
177
+ tabulate==0.10.0
178
+ tiktoken==0.14.0
179
+ tilelang==0.1.12
180
+ tokenizers==0.23.2
181
+ tokenspeed-mla==0.1.8
182
+ tokenspeed-triton==3.8.10.post20260920
183
+ tomli==2.4.1
184
+ torch==2.13.0+cu130
185
+ torch_c_dlpack_ext==0.1.5
186
+ torchaudio==2.11.0+cu130
187
+ torchcodec==0.17.0+cu130
188
+ torchvision==0.28.0+cu130
189
+ tqdm==4.70.1
190
+ transformers==5.17.0
191
+ triton==3.7.1
192
+ truststore==0.10.4
193
+ typer==0.27.2
194
+ typing-inspection==0.4.4
195
+ typing_extensions==4.16.0
196
+ urllib3==2.8.0
197
+ uvicorn==0.54.0
198
+ uvloop==0.23.0
199
+ vllm==0.31.0
200
+ watchfiles==1.3.0
201
+ websockets==16.1.1
202
+ xgrammar==0.2.7
203
+ yarl==1.25.1
204
+ z3-solver==4.15.4.0
service/requirements.measured.lock ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ agent-detector==2.0.0
2
+ aiohappyeyeballs==2.7.1
3
+ aiohttp==3.14.3
4
+ aiosignal==1.4.0
5
+ annotated-doc==0.0.5
6
+ annotated-types==0.8.0
7
+ anthropic==1.11.0
8
+ anyio==4.15.1
9
+ apache-tvm-ffi==0.1.11
10
+ astor==0.8.1
11
+ async-timeout==5.0.1
12
+ attrs==26.1.0
13
+ backports.strenum==1.3.1
14
+ blake3==1.0.10
15
+ cachetools==7.2.0
16
+ cbor2==6.1.5
17
+ certifi==2026.7.22
18
+ cffi==2.1.1
19
+ charset-normalizer==3.5.2
20
+ click==8.5.0
21
+ cloudpickle==3.1.2
22
+ compressed-tensors==0.17.0
23
+ cryptography==50.0.2
24
+ cuda-bindings==13.4.3
25
+ cuda-core==1.2.1
26
+ cuda-pathfinder==1.8.3
27
+ cuda-python==13.4.1
28
+ cuda-tile==1.6.0
29
+ cuda-toolkit==13.0.3.0
30
+ depyf==0.20.0
31
+ detect-installer==0.2.1
32
+ dill==0.4.1
33
+ dnspython==2.8.0
34
+ docstring_parser==0.18.0
35
+ einops==0.8.2
36
+ email-validator==2.3.0
37
+ exceptiongroup==1.3.1
38
+ fastapi==0.136.3
39
+ fastapi-cli==0.0.32
40
+ fastapi-cloud-cli==0.26.0
41
+ fastar==0.12.0
42
+ fastsafetensors==0.4.0
43
+ filelock==4.0.12
44
+ flashinfer-python==0.7.0.post1
45
+ frozenlist==1.8.0
46
+ fsspec==2026.9.0
47
+ googleapis-common-protos==1.75.5
48
+ grpcio==1.84.0
49
+ h11==0.16.0
50
+ hf-xet==1.6.0
51
+ httpcore==1.0.9
52
+ httpcore2==2.13.1
53
+ httptools==0.8.0
54
+ httpx==0.28.1
55
+ httpx2==2.13.1
56
+ huggingface_hub==1.33.0
57
+ humming-kernels==0.1.16
58
+ idna==3.20
59
+ ijson==3.5.1
60
+ instanttensor==0.2.0
61
+ interegular==0.3.3
62
+ Jinja2==3.1.6
63
+ jiter==0.17.0
64
+ jmespath==1.1.0
65
+ jsonschema==4.26.0
66
+ jsonschema-specifications==2025.9.1
67
+ lark==1.2.2
68
+ llguidance==1.7.6
69
+ llvmlite==0.47.0
70
+ lm-format-enforcer==0.11.3
71
+ loguru==0.7.3
72
+ markdown-it-py==4.2.0
73
+ MarkupSafe==3.0.4
74
+ mcp==2.3.0
75
+ mcp-types==2.3.0
76
+ mdurl==0.1.2
77
+ mistral_common==1.12.0
78
+ ml_dtypes==0.6.0
79
+ model-hosting-container-standards==0.1.16
80
+ mpmath==1.3.0
81
+ msgspec==0.22.0
82
+ multidict==6.9.1
83
+ nccl-extensions==0.1.0
84
+ nccl4py==0.6.0
85
+ networkx==3.4.2
86
+ ninja==1.13.2
87
+ numba==0.65.0
88
+ numpy==2.2.6
89
+ nvidia-cublas==13.1.1.3
90
+ nvidia-cuda-cccl==13.3.4.3.1
91
+ nvidia-cuda-crt==13.4.92
92
+ nvidia-cuda-cupti==13.0.85
93
+ nvidia-cuda-nvcc==13.4.92
94
+ nvidia-cuda-nvdisasm==13.4.92
95
+ nvidia-cuda-nvrtc==13.0.88
96
+ nvidia-cuda-runtime==13.0.96
97
+ nvidia-cudnn-cu13==9.20.0.48
98
+ nvidia-cudnn-frontend==1.30.0
99
+ nvidia-cufft==12.0.0.61
100
+ nvidia-cufile==1.15.1.6
101
+ nvidia-curand==10.4.0.35
102
+ nvidia-cusolver==12.0.4.66
103
+ nvidia-cusparse==12.6.3.3
104
+ nvidia-cusparselt-cu13==0.8.1
105
+ nvidia-cutlass-dsl==4.7.1
106
+ nvidia-cutlass-dsl-libs-base==4.7.1
107
+ nvidia-cutlass-dsl-libs-core==4.7.1
108
+ nvidia-cutlass-dsl-libs-cu12==4.7.1
109
+ nvidia-cutlass-dsl-libs-cu13==4.7.1
110
+ nvidia-ml-py==13.615.71
111
+ nvidia-nccl-cu13==2.29.7
112
+ nvidia-nvjitlink==13.4.92
113
+ nvidia-nvshmem-cu13==3.4.5
114
+ nvidia-nvtx==13.0.85
115
+ nvidia-nvvm==13.4.92
116
+ nvtx==0.2.15
117
+ openai==3.24.0
118
+ opencv-python-headless==5.0.0.93
119
+ opentelemetry-api==1.45.0
120
+ opentelemetry-exporter-http-transport==0.66b0
121
+ opentelemetry-exporter-otlp==1.45.0
122
+ opentelemetry-exporter-otlp-common==0.66b0
123
+ opentelemetry-exporter-otlp-proto-common==1.45.0
124
+ opentelemetry-exporter-otlp-proto-grpc==1.45.0
125
+ opentelemetry-exporter-otlp-proto-http==1.45.0
126
+ opentelemetry-proto==1.45.0
127
+ opentelemetry-sdk==1.45.0
128
+ opentelemetry-semantic-conventions==0.66b0
129
+ opentelemetry-semantic-conventions-ai==0.5.1
130
+ oss-harmony==0.0.11
131
+ outlines_core==0.2.14
132
+ packaging==26.3
133
+ partial-json-parser==0.2.1.1.post7
134
+ pillow==12.3.0
135
+ prometheus-fastapi-instrumentator==8.1.0
136
+ prometheus_client==0.26.0
137
+ propcache==0.5.4
138
+ protobuf==7.36.2
139
+ psutil==7.2.2
140
+ py-cpuinfo==9.0.0
141
+ pybase64==1.5.1
142
+ pycountry==26.2.16
143
+ pycparser==3.0
144
+ pydantic==2.13.5
145
+ pydantic-extra-types==2.11.1
146
+ pydantic-settings==2.15.0
147
+ pydantic_core==2.46.5
148
+ Pygments==2.21.0
149
+ PyJWT==2.15.1
150
+ pynvvideocodec==2.0.4
151
+ python-dotenv==1.2.4
152
+ python-json-logger==4.2.0
153
+ python-multipart==0.0.32
154
+ PyYAML==6.0.3
155
+ pyzmq==27.2.0
156
+ quack-kernels==0.6.5
157
+ referencing==0.37.0
158
+ regex==2026.9.29
159
+ requests==2.34.2
160
+ rich==15.0.0
161
+ rich-toolkit==0.20.5
162
+ rignore==0.8.1
163
+ rpds-py==0.30.0
164
+ safetensors==0.8.0
165
+ sentencepiece==0.2.2
166
+ sentry-sdk==2.71.0
167
+ setproctitle==1.3.8
168
+ shellingham==1.5.4
169
+ sniffio==1.3.1
170
+ sse-starlette==3.5.0
171
+ starlette==1.7.0
172
+ supervisor==4.3.0
173
+ sympy==1.14.0
174
+ tabulate==0.10.0
175
+ tiktoken==0.14.0
176
+ tilelang==0.1.12
177
+ tokenizers==0.23.2
178
+ tokenspeed-mla==0.1.8
179
+ tokenspeed-triton==3.8.10.post20260920
180
+ tomli==2.4.1
181
+ torch==2.13.0+cu130
182
+ torch_c_dlpack_ext==0.1.5
183
+ torchaudio==2.11.0+cu130
184
+ torchcodec==0.17.0+cu130
185
+ torchvision==0.28.0+cu130
186
+ tqdm==4.70.1
187
+ transformers==5.17.0
188
+ triton==3.7.1
189
+ truststore==0.10.4
190
+ typer==0.27.2
191
+ typing-inspection==0.4.4
192
+ typing_extensions==4.16.0
193
+ urllib3==2.8.0
194
+ uvicorn==0.54.0
195
+ uvloop==0.23.0
196
+ vllm==0.31.0
197
+ watchfiles==1.3.0
198
+ websockets==16.1.1
199
+ xgrammar==0.2.7
200
+ yarl==1.25.1
201
+ z3-solver==4.15.4.0
service/selftest.py ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Synthetic offline selftest. Never imports a model runtime or opens a socket."""
2
+ import argparse
3
+ import copy
4
+ import hashlib
5
+ import json
6
+ import math
7
+ import re
8
+ import socket
9
+ import tempfile
10
+ import unittest
11
+ from pathlib import Path
12
+ from unittest.mock import patch
13
+ import backends
14
+ import core
15
+ import launch
16
+ import pkg_check
17
+
18
+
19
+ class WordTokenizer:
20
+ """Reversible synthetic tokenizer: one token per whitespace-prefixed word."""
21
+ def __init__(self):
22
+ self.ids, self.pieces = {}, []
23
+
24
+ def _id(self, piece):
25
+ if piece not in self.ids:
26
+ self.ids[piece] = len(self.pieces)
27
+ self.pieces.append(piece)
28
+ return self.ids[piece]
29
+
30
+ def encode(self, text, add_special_tokens=False):
31
+ return [self._id(p) for p in re.findall(r'\s*\S+|\s+', text)]
32
+
33
+ def decode(self, ids, skip_special_tokens=True):
34
+ return ''.join(self.pieces[i] for i in ids)
35
+
36
+ def apply_chat_template(self, messages, tokenize=False, add_generation_prompt=True, **_):
37
+ return '<turn>user ' + messages[0]['content'] + ' <turn>model'
38
+
39
+
40
+ def synthetic_task(words):
41
+ """Invented rules + narrative of about `words` tokens; a yes/no question about the rules."""
42
+ rules = 'Rules:\nR1: A crate is late if it reaches dock B after day 30.\nR2: Late crates are refunded.\n\n'
43
+ sentences, day = [], 0
44
+ while sum(len(s.split()) for s in sentences) < words:
45
+ day += 1
46
+ sentences.append('Day %d: the courier moved crate C%d from dock A to dock B without delay.' % (day, day))
47
+ state = rules + 'Events:\n' + ' '.join(sentences)
48
+ payload = {'state': state, 'questions': {'q': {'type': 'noul', 'instructions': 'Is crate C7 refunded?'}}}
49
+ return core.normalize_request(payload, 1)[0][1]
50
+
51
+
52
+ class SelfTest(unittest.TestCase):
53
+ def test_package_integrity(self):
54
+ self.assertIn(pkg_check.checks()['status'], ('PREPARATION_CHECK_PASS', 'FROZEN_PACKAGE_CHECK_PASS'))
55
+
56
+ def test_pending_cannot_serve(self):
57
+ config = pkg_check.read('config.json')
58
+ if not config['package'].get('nonformal_fitted'):
59
+ with self.assertRaisesRegex(ValueError, 'fit pending'):
60
+ launch.check_preparation(allow_nonformal=True)
61
+ if config['package'].get('finalized'):
62
+ launch.check_preparation() # a frozen package starts formally, without any extra switch
63
+ else:
64
+ with self.assertRaises(ValueError):
65
+ launch.check_preparation()
66
+
67
+ def test_offline_environment(self):
68
+ state = Path(Path.cwd().anchor) / 'jev-31b-offline-state'
69
+ env = launch.child_environment({}, state, state / 'startup')
70
+ for key in ('HF_HUB_OFFLINE', 'TRANSFORMERS_OFFLINE', 'VLLM_BATCH_INVARIANT'):
71
+ self.assertEqual(env[key], '1')
72
+ self.assertEqual(env['VLLM_USE_FLASHINFER_SAMPLER'], '0')
73
+ self.assertTrue(Path(env['TMPDIR']).is_relative_to(state))
74
+
75
+ def test_served_mode_is_the_stated_policy(self):
76
+ policy = pkg_check.SERVICE_POLICY
77
+ self.assertEqual(launch.SERVED_MODE, policy['mode'])
78
+ c = pkg_check.read('config.json')
79
+ self.assertEqual((c['routing']['mode'], c['routing']['quota'], c['routing']['target_slow_share']),
80
+ (policy['mode'], policy['quota'], policy['quota']))
81
+ self.assertEqual(pkg_check.policy_problems(policy), [])
82
+
83
+ def test_yes_no_constant(self):
84
+ p = pkg_check.SERVICE_POLICY['noul_p_cal']
85
+ self.assertTrue(0.81 <= p <= 0.95)
86
+ cal = pkg_check.read('calibration.json')['models'][pkg_check.MODEL_KEY]
87
+ for side in ('fast', 'slow', 'fallback'):
88
+ block = core.validate_calibration({'models': {'m': cal}}, 'm', side)
89
+ self.assertEqual(block['noul_calibration']['p_cal'], p)
90
+ out = core.probabilities(['no', 'yes'], {'no': 0.2, 'yes': 1.5}, 'noul', block)
91
+ self.assertEqual(out, {'no': 1 - p, 'yes': p})
92
+ out = core.probabilities(['no', 'yes'], {'no': 1.5, 'yes': 0.2}, 'noul', block)
93
+ self.assertEqual(out, {'no': p, 'yes': 1 - p})
94
+ for p_bad in (0.80, 0.951):
95
+ policy = dict(pkg_check.SERVICE_POLICY, noul_p_cal=p_bad)
96
+ self.assertIn('yes/no constant outside [0.81, 0.95]', pkg_check.policy_problems(policy))
97
+
98
+ def test_drafting_budget_inside_the_context(self):
99
+ policy = pkg_check.SERVICE_POLICY
100
+ c = pkg_check.read('config.json')
101
+ served = copy.deepcopy(c)
102
+ core.apply_profile(served)
103
+ core.apply_budget_tier(served, None)
104
+ identity = core.threshold_identity(served)
105
+ self.assertEqual((identity['max_new_tokens'], identity['reasoning_words']),
106
+ (policy['draft']['max_new_tokens'], policy['draft']['reasoning_words']))
107
+ self.assertEqual(served['generation']['max_new_tokens'], policy['draft']['max_new_tokens'])
108
+ self.assertEqual(served['routing']['slow_max_prompt_tokens'], policy['slow_max_prompt_tokens'])
109
+ if policy['mode'] == 'routed':
110
+ self.assertLessEqual(policy['slow_max_prompt_tokens'] + policy['draft']['max_new_tokens']
111
+ + pkg_check.SLOW_READOUT_MARGIN, c['model']['vllm_options']['max_model_len'])
112
+ wide = dict(policy, slow_max_prompt_tokens=6653)
113
+ self.assertIn('drafting budget outside the vLLM context', pkg_check.policy_problems(wide))
114
+
115
+ def test_slow_type_exclusion(self):
116
+ """Excluded types (v3 core patch) never draft; the other types do (uniform mode, synthetic readout)."""
117
+ policy = pkg_check.SERVICE_POLICY
118
+ c = pkg_check.read('config.json')
119
+ excluded = policy['slow_excluded_types']
120
+ self.assertEqual(c['routing'].get('slow_excluded_types', []), excluded)
121
+ if not excluded:
122
+ return
123
+ self.assertEqual(getattr(core, 'SLOW_TYPE_EXCLUSION', None), 'routing.slow_excluded_types')
124
+
125
+ class DraftingBackend:
126
+ def slow_session(self, ids, unparsed_policy, meter, max_new_tokens=None):
127
+ return None, False # a draft without answer marker: the first-pass answer is kept
128
+
129
+ def fast(backend, task, config, ids, labels, groups, meter):
130
+ return {label: -float(index) for index, label in enumerate(labels)}, 0, None
131
+ length = {key: False for key in ('truncated', 'character_precompressed', 'protected_content_exceeds_budget',
132
+ 'physical_truncated', 'long_input', 'slow_prompt_exceeds_budget')}
133
+ length['original_prompt_tokens'] = 1
134
+ cal = pkg_check.read('calibration.json')
135
+ with tempfile.TemporaryDirectory() as tmp:
136
+ config = copy.deepcopy(c)
137
+ core.apply_profile(config)
138
+ config['routing'].update(state_dir=str(Path(tmp).resolve()), run_id='exclusion', resume=False,
139
+ mode='uniform', quota=1., fuse=1.)
140
+ import quota
141
+ service = core.Service(config, cal, DraftingBackend(), quota.RuntimeCounter(config))
142
+ routes = {}
143
+ with patch('core.prepare_task', return_value=(None, (None, {}, [1], [1]), length)), \
144
+ patch('core.fast_attempt_with_retry', side_effect=fast):
145
+ for kind, question in (('noul', {'type': 'noul', 'instructions': 'Is it late?'}),
146
+ ('choice', {'type': 'choice', 'instructions': 'Which one?',
147
+ 'criteria': {'red': 'r', 'blue': 'b'}})):
148
+ response = service.answer({'state': 'synthetic', 'questions': {'q': question}})
149
+ routes[kind] = response['usage']['questions']['q']
150
+ for kind in ('choice', 'noul', 'score'):
151
+ if kind not in routes:
152
+ continue
153
+ with self.subTest(kind=kind):
154
+ if kind in excluded:
155
+ self.assertEqual((routes[kind]['path'], routes[kind]['route_reason']), ('fast', 'type_excluded'))
156
+ else:
157
+ self.assertEqual(routes[kind]['path'], 'slow')
158
+ bad = copy.deepcopy(c)
159
+ bad['routing']['slow_excluded_types'] = ['noul', 'noul']
160
+ with self.assertRaisesRegex(ValueError, 'slow_excluded_types'):
161
+ core.validate_config(bad)
162
+
163
+ def test_threshold_identity(self):
164
+ c = pkg_check.read('config.json')
165
+ expected = core.threshold_identity(c)
166
+ self.assertEqual(expected['model_key'], pkg_check.MODEL_KEY)
167
+ self.assertEqual(expected['max_prompt_tokens'], pkg_check.INPUT_BUDGET)
168
+ c['routing']['margin_threshold_fitted_for'] = {'placeholder': False, **expected}
169
+ self.assertEqual(core.validate_threshold(c), [])
170
+ c['routing']['margin_threshold_fitted_for']['model_key'] = 'wrong'
171
+ if c['routing']['mode'] == 'routed':
172
+ with self.assertRaisesRegex(ValueError, 'identity mismatch'):
173
+ core.validate_threshold(c) # routed serving refuses a threshold fitted for another identity
174
+ else:
175
+ self.assertTrue(core.validate_threshold(c))
176
+
177
+ def test_adapter_reference_materializes_into_served_identity(self):
178
+ c = pkg_check.read('config.json')
179
+ identity = core.threshold_identity(c)
180
+ c['routing']['margin_threshold_fitted_for'] = {'placeholder': False, **identity}
181
+ c['profiles'][pkg_check.MODEL_KEY]['routing'] = copy.deepcopy(c['routing'])
182
+ folder = Path(tempfile.gettempdir()) / 'synthetic-model' / 'adapter'
183
+ self.assertEqual(c['model']['adapter'], launch.ADAPTER_REF)
184
+ served = launch.materialize_adapter(c, folder)
185
+ self.assertEqual(served['model']['adapter'], str(folder))
186
+ self.assertEqual(served['routing']['margin_threshold_fitted_for']['adapter'], str(folder))
187
+ self.assertEqual(core.validate_threshold(served), [])
188
+ self.assertEqual(c['model']['adapter'], launch.ADAPTER_REF)
189
+ with self.assertRaises(ValueError):
190
+ launch.materialize_adapter(c, None)
191
+ raw = copy.deepcopy(c)
192
+ raw['model']['adapter'] = str(folder)
193
+ with self.assertRaises(ValueError):
194
+ launch.materialize_adapter(raw, folder)
195
+
196
+ def test_adapter_folder_is_exactly_pinned(self):
197
+ with tempfile.TemporaryDirectory() as tmp:
198
+ model = Path(tmp)
199
+ folder = model / 'adapter'
200
+ folder.mkdir()
201
+ payload = {'adapter_config.json': b'{"r": 16}\n', 'adapter_model.safetensors': b'synthetic weights'}
202
+ pinned = {}
203
+ for name, data in payload.items():
204
+ (folder / name).write_bytes(data)
205
+ pinned[name] = {'sha256': hashlib.sha256(data).hexdigest(), 'size': len(data)}
206
+ with patch.object(launch, 'ADAPTER_FILES', pinned):
207
+ self.assertEqual(launch.check_adapter(model), folder.resolve())
208
+ (folder / 'extra.json').write_text('{}')
209
+ with self.assertRaisesRegex(ValueError, 'exactly'):
210
+ launch.check_adapter(model)
211
+ (folder / 'extra.json').unlink()
212
+ (folder / 'adapter_model.safetensors').write_bytes(b'tampered weights!')
213
+ with self.assertRaisesRegex(ValueError, 'differs'):
214
+ launch.check_adapter(model)
215
+ with patch.object(launch, 'ADAPTER_FILES', {}):
216
+ with self.assertRaisesRegex(ValueError, 'pending'):
217
+ launch.check_adapter(model)
218
+
219
+ def test_input_budget_6653_everywhere(self):
220
+ c = pkg_check.read('config.json')
221
+ served = copy.deepcopy(c)
222
+ core.apply_profile(served)
223
+ core.apply_budget_tier(served, None)
224
+ self.assertEqual(served['limits']['max_prompt_tokens'], 6653)
225
+ self.assertEqual(served['limits']['physical_max_prompt_tokens'], 6653)
226
+ for tier in ('normal', 'long'):
227
+ self.assertEqual(served['routing']['tiers'][tier]['max_prompt_tokens'], 6653)
228
+ self.assertLessEqual(6653 + 3, c['model']['vllm_options']['max_model_len'])
229
+
230
+ def test_long_inputs_are_compressed_not_refused(self):
231
+ """80k- and 7k-token synthetic inputs are answered from at most 6653 prompt tokens."""
232
+ c = pkg_check.read('config.json')
233
+ core.apply_profile(c)
234
+ backend = backends.TokenizerMixin()
235
+ backend.tokenizer = WordTokenizer()
236
+ for words, precompressed in ((80000, True), (7000, False)):
237
+ task = synthetic_task(words)
238
+ _, prepared, length = core.prepare_task(task, backend, c)
239
+ self.assertTrue(length['long_input'])
240
+ self.assertEqual(length['character_precompressed'], precompressed)
241
+ if not precompressed:
242
+ self.assertGreater(length['original_prompt_tokens'], 6653)
243
+ self.assertLessEqual(length['kept_prompt_tokens'], 6653)
244
+ self.assertLessEqual(len(prepared[2]), 6653)
245
+ self.assertFalse(length['physical_truncated'])
246
+ self.assertFalse(length['protected_content_exceeds_budget'])
247
+ task = synthetic_task(3000)
248
+ _, prepared, length = core.prepare_task(task, backend, c)
249
+ self.assertFalse(length['long_input'])
250
+ self.assertFalse(length['truncated'])
251
+
252
+ def test_readout_probability_contract(self):
253
+ block = {'temperature_by_kind': {'choice': 1., 'score': 1., 'noul': 'one_bin'}, 'noul_calibration': {'p_cal': .81}}
254
+ for kind, labels in [('choice', ['A', 'B', 'C']), ('score', ['1', '2', '3']), ('noul', ['no', 'yes'])]:
255
+ logits = {label: float(i) for i, label in enumerate(labels)}
256
+ p = core.probabilities(labels, logits, kind, block)
257
+ self.assertEqual(set(p), set(labels))
258
+ self.assertAlmostEqual(sum(p.values()), 1.)
259
+ self.assertTrue(all(math.isfinite(v) and 0 <= v <= 1 for v in p.values()))
260
+ self.assertEqual(max(labels, key=p.__getitem__), labels[-1])
261
+
262
+ def test_readout_rejects_missing_and_nonfinite(self):
263
+ for logits in ({0: 0.}, {0: 0., 1: float('nan')}, {0: 0., 1: float('inf')}):
264
+ with self.assertRaises(RuntimeError):
265
+ core.aggregate(logits, {'a': [0], 'b': [1]})
266
+
267
+ def test_config_does_not_accept_invalid_quota(self):
268
+ c = copy.deepcopy(pkg_check.read('config.json'))
269
+ c['routing']['quota'] = .9
270
+ with self.assertRaises(ValueError):
271
+ core.validate_config(c)
272
+
273
+
274
+ def main():
275
+ p = argparse.ArgumentParser(description=__doc__)
276
+ p.add_argument('--offline', action='store_true', help='default; compatibility flag')
277
+ p.parse_args()
278
+ with patch.object(socket, 'socket', side_effect=AssertionError('network forbidden in offline selftest')):
279
+ result = unittest.TextTestRunner(verbosity=2).run(unittest.defaultTestLoader.loadTestsFromTestCase(SelfTest))
280
+ print(json.dumps({'status': 'PASS' if result.wasSuccessful() else 'FAIL', 'tests': result.testsRun,
281
+ 'synthetic': True, 'model_called': False, 'network': False}, ensure_ascii=False))
282
+ return 0 if result.wasSuccessful() else 1
283
+
284
+
285
+ if __name__ == '__main__':
286
+ raise SystemExit(main())
service/selftest.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ PKG=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
4
+ export VLLM_USE_FLASHINFER_SAMPLER=0 HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1
5
+ export PYTHONDONTWRITEBYTECODE=1
6
+ exec "${PYTHON:-python3}" -B "$PKG/selftest.py" --offline "$@"
service/serve.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Start the service in the foreground. MODEL must hold the base files and adapter/ (see README.md).
3
+ # bash serve.sh --model /abs/model --state-dir /abs/state --run-id NAME [--port 8000] [--bind 127.0.0.1]
4
+ set -euo pipefail
5
+ PKG=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
6
+ export VLLM_USE_FLASHINFER_SAMPLER=0 VLLM_NO_USAGE_STATS=1 DO_NOT_TRACK=1
7
+ export HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 PYTHONDONTWRITEBYTECODE=1
8
+ export VLLM_BATCH_INVARIANT=1 VLLM_WORKER_MULTIPROC_METHOD=spawn
9
+ exec "${PYTHON:-python3}" -B "$PKG/launch.py" "$@"
service/server.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Standard-library HTTP adapter. No model is loaded until main()."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
7
+ from pathlib import Path
8
+
9
+ if __package__:
10
+ from .core import ItemFailure, ServiceFailure, UnsupportedRequest, Service, ROOT, load_json, validate_config, apply_profile, validate_calibration, validate_threshold
11
+ else:
12
+ from core import ItemFailure, ServiceFailure, UnsupportedRequest, Service, ROOT, load_json, validate_config, apply_profile, validate_calibration, validate_threshold
13
+
14
+ BINDS = ("127.0.0.1", "0.0.0.0")
15
+
16
+
17
+ def make_handler(service):
18
+ def service_dead():
19
+ check = getattr(service, 'is_dead', None)
20
+ return check() if callable(check) else getattr(service, 'dead', False)
21
+
22
+ class Handler(BaseHTTPRequestHandler):
23
+ protocol_version='HTTP/1.1'
24
+
25
+ def handle_expect_100(self):
26
+ if self.command == 'POST' and service_dead():
27
+ self.close_connection=True
28
+ self.respond(503, {'error':'service failure'})
29
+ return False
30
+ try:
31
+ n=int(self.headers.get('Content-Length','0'))
32
+ if n<=0 or n>service.config['server']['max_request_bytes'] or self.headers.get('Transfer-Encoding'):
33
+ raise ValueError
34
+ except (ValueError,TypeError):
35
+ self.close_connection=True
36
+ self.respond(400,{'error':'Invalid request size'})
37
+ return False
38
+ self.send_response_only(100)
39
+ self.end_headers()
40
+ return True
41
+
42
+ def setup(self):
43
+ super().setup()
44
+ self.connection.settimeout(service.config["server"]["socket_timeout_seconds"])
45
+
46
+ def log_message(self, format, *args):
47
+ # Avoid logging request contents, model paths or network endpoints.
48
+ pass
49
+
50
+ def respond(self, status, value):
51
+ data = json.dumps(value, ensure_ascii=False, allow_nan=False).encode("utf-8")
52
+ self.send_response(status)
53
+ self.send_header("Content-Type", "application/json; charset=utf-8")
54
+ self.send_header("Content-Length", str(len(data)))
55
+ self.end_headers()
56
+ self.wfile.write(data)
57
+
58
+ def do_GET(self):
59
+ if self.path.split('?', 1)[0].rstrip('/') == '/health':
60
+ if __package__:
61
+ from .attention import attention_health
62
+ else:
63
+ from attention import attention_health
64
+ health=service.health()
65
+ # Attention backend and runtime versions are information, never warnings.
66
+ info=health.setdefault('info',{})
67
+ info.update(attention_health())
68
+ info['runtime']=getattr(service.backend,'runtime_info',None)
69
+ self.respond(200, health)
70
+ elif self.path.split('?',1)[0].rstrip('/') == '/engine_stats':
71
+ # Read-only engine counters for maintainer usage reconciliation; not used by evaluation.
72
+ try: self.respond(200,service.engine_stats())
73
+ except Exception: self.respond(503,{'error':'Engine counters unavailable'})
74
+ else:
75
+ self.respond(404, {'error':'Unknown route'})
76
+
77
+ def do_POST(self):
78
+ # After an unrecoverable engine error or the third consecutive item failure,
79
+ # every POST is answered 503 without reading or processing the request.
80
+ if service_dead():
81
+ self.close_connection=True
82
+ self.respond(503, {'error':'service failure'})
83
+ return
84
+ if self.path.split("?", 1)[0].rstrip("/") != "/v1/systemone":
85
+ self.close_connection=True
86
+ self.respond(404, {"error": "Unknown route"})
87
+ return
88
+ try:
89
+ if self.headers.get("Transfer-Encoding"):
90
+ raise ValueError("Transfer-Encoding is unsupported")
91
+ length = int(self.headers.get("Content-Length", "0"))
92
+ if length <= 0 or length > service.config["server"]["max_request_bytes"]:
93
+ raise ValueError("Invalid request size")
94
+ def reject(value):
95
+ raise ValueError("Non-finite JSON value")
96
+ raw = self.rfile.read(length)
97
+ if len(raw) != length:
98
+ raise ValueError("Incomplete request body")
99
+ payload = json.loads(raw.decode("utf-8"), parse_constant=reject)
100
+ # Validate before inference so model ValueError is never a client error.
101
+ if __package__:
102
+ from .core import normalize_request
103
+ else:
104
+ from core import normalize_request
105
+ normalize_request(payload, service.config["server"]["max_questions"])
106
+ except UnsupportedRequest:
107
+ self.close_connection=True
108
+ self.respond(422,{"error":"Unsupported systemone request"})
109
+ return
110
+ except (ValueError, TypeError, UnicodeError, OSError):
111
+ self.close_connection=True
112
+ self.respond(400, {"error": "Invalid systemone request"})
113
+ return
114
+ # Failures never produce a substitute answer: 422 for one item, 503 for the service.
115
+ try:
116
+ result = service.answer(payload)
117
+ json.dumps(result, allow_nan=False)
118
+ except ItemFailure as exc:
119
+ print("item failure: "+type(exc.__cause__ or exc).__name__, flush=True)
120
+ self.respond(422, {"error": "item could not be processed"})
121
+ return
122
+ except ServiceFailure as exc:
123
+ print("service failure: "+str(exc), flush=True)
124
+ self.respond(503, {"error": "service failure"})
125
+ return
126
+ except Exception as exc:
127
+ print("service failure: "+type(exc).__name__, flush=True)
128
+ self.respond(503, {"error": "service failure"})
129
+ return
130
+ self.respond(200, result)
131
+
132
+ return Handler
133
+
134
+
135
+ def install_extensions(service, config):
136
+ """Serving extensions named in config.json, installed on the running service in this order:
137
+ item_deadline (per-decision deadline of the drafting pass, with the item rules of the
138
+ `item_rules` block: the question types that may take the drafting pass and the low-margin choice
139
+ re-read), then post_calibration (per-type calibration of the reported probabilities). A
140
+ configuration without these blocks serves the unchanged service."""
141
+ if __package__:
142
+ from . import backends, core, item_deadline, post_calibration
143
+ else:
144
+ import backends, core, item_deadline, post_calibration
145
+ if config.get('item_deadline') is not None:
146
+ rules = config.get('item_rules') or {}
147
+ types = rules.get('slow_types')
148
+ service.warmup() # the drafting speed model is timed on a warmed engine, as in the measured setup
149
+ item_deadline.install(core, backends, service.backend, config['item_deadline'],
150
+ system_prompt=config['prompt']['system']).wrap_service(
151
+ service, slow_types=None if types is None or set(types) == {'choice', 'noul', 'score'} else types,
152
+ reread_tau=rules.get('reread_tau') or None)
153
+ if config.get('post_calibration') is not None:
154
+ post_calibration.wrap_service(service, config['post_calibration'], core)
155
+ return service
156
+
157
+
158
+ class QuietHTTPServer(ThreadingHTTPServer):
159
+ # Request workers must not keep a stopped service alive while draining.
160
+ daemon_threads = True
161
+
162
+ def handle_error(self, request, client_address):
163
+ # The base implementation prints a peer endpoint on disconnected clients.
164
+ print("HTTP connection failed", flush=True)
165
+
166
+
167
+ def main(argv=None):
168
+ parser = argparse.ArgumentParser(description=__doc__)
169
+ parser.add_argument("--config", type=Path, default=ROOT / "config.json")
170
+ parser.add_argument("--model")
171
+ parser.add_argument("--quant", choices=("int8", "bf16", "awq"))
172
+ parser.add_argument("--adapter")
173
+ parser.add_argument("--backend", choices=("transformers", "vllm"))
174
+ parser.add_argument("--bind", choices=BINDS, help="Listening address; defaults to server.bind in config")
175
+ parser.add_argument("--port", type=int)
176
+ parser.add_argument("--mode", choices=("routed", "uniform", "fast_only"))
177
+ parser.add_argument("--calibration", type=Path)
178
+ parser.add_argument('--state-dir', type=Path, help='Absolute directory for run state (required)')
179
+ parser.add_argument('--run-id')
180
+ parser.add_argument('--resume', action='store_true')
181
+ parser.add_argument('--model-key')
182
+ parser.add_argument('--quota-tier',choices=('normal','hard','long'))
183
+ parser.add_argument('--cost-fuse',choices=('on','off'))
184
+ parser.add_argument('--no-prefix-caching',action='store_true')
185
+ parser.add_argument('--allow-placeholder-threshold',action='store_true')
186
+ args = parser.parse_args(argv)
187
+ if args.state_dir is None or not args.state_dir.is_absolute():
188
+ parser.error("--state-dir must be given as an absolute path")
189
+ c = load_json(args.config)
190
+ for arg, field in (("model", "path"), ("quant", "quant"), ("adapter", "adapter"), ("backend", "backend")):
191
+ if getattr(args, arg) is not None:
192
+ c["model"][field] = getattr(args, arg)
193
+ for key in ("bind", "port"):
194
+ if getattr(args, key) is not None:
195
+ c["server"][key] = getattr(args, key)
196
+ if args.mode is not None:
197
+ c["routing"]["mode"] = args.mode
198
+ if args.state_dir is not None:
199
+ c['routing']['state_dir'] = str(args.state_dir)
200
+ if args.run_id:
201
+ c['routing']['run_id'] = args.run_id
202
+ c['routing']['resume'] = args.resume
203
+ if args.model_key:
204
+ c['model']['model_key'] = args.model_key
205
+ apply_profile(c)
206
+ if __package__: from .core import apply_budget_tier
207
+ else: from core import apply_budget_tier
208
+ apply_budget_tier(c,args.quota_tier)
209
+ if args.mode is not None:c["routing"]["mode"]=args.mode
210
+ if args.quant is not None and args.quant!=c['model']['quant']:parser.error('Precision conflicts with model profile')
211
+ if args.backend is not None and args.backend!=c['model']['backend']:parser.error('Backend conflicts with model profile')
212
+ if args.no_prefix_caching: c["model"]["enable_prefix_caching"]=False
213
+ if args.cost_fuse:c['routing']['cost_fuse']['enabled']=args.cost_fuse=='on'
214
+ validate_config(c)
215
+ validate_threshold(c,args.allow_placeholder_threshold)
216
+ if not c["model"]["path"] or not c["server"]["bind"]:
217
+ parser.error("Set --model and --bind, or their config fields")
218
+ calibration = load_json(args.calibration or ROOT / c["calibration_file"])
219
+ validate_calibration(calibration, c["model"]["model_key"])
220
+ if __package__:
221
+ from .backends import create_backend
222
+ from .quota import RuntimeCounter
223
+ else:
224
+ from backends import create_backend
225
+ from quota import RuntimeCounter
226
+ counter = RuntimeCounter(c)
227
+ service = Service(c, calibration, create_backend(c), counter, allow_placeholder_threshold=args.allow_placeholder_threshold)
228
+ install_extensions(service, c)
229
+ if c["server"].get("warmup", True):
230
+ service.warmup()
231
+ httpd = QuietHTTPServer((c["server"]["bind"], c["server"]["port"]), make_handler(service))
232
+ print("systemone service ready", flush=True)
233
+ try:
234
+ httpd.serve_forever()
235
+ except KeyboardInterrupt:
236
+ pass
237
+ finally:
238
+ httpd.server_close()
239
+
240
+
241
+ if __name__ == "__main__":
242
+ main()