verifiers 0.3.2.dev101__py3-none-any.whl → 0.3.2.dev104__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
verifiers/v1/graph.py CHANGED
@@ -673,8 +673,41 @@ def _attribute_mm(
673
673
  )
674
674
 
675
675
 
676
+ def _replace_placeholder_routing_row(
677
+ trace: Trace, prefix_node_ids: list[int], arr: np.ndarray, off: int
678
+ ) -> None:
679
+ """Replace the prefix's placeholder routing row with the one this turn's prefill forwarded."""
680
+ if not 1 <= off <= arr.shape[0]:
681
+ return
682
+ # Only assistant (`sampled`) nodes are affected: the model forward isn't run on the final
683
+ # generated token for such turns, meaning its routing decisions are fundamentally unavailable.
684
+ # But, because routing needs one row per token, that row instead receives an inaccurate
685
+ # placeholder, attempt to fix up below.
686
+ node_with_placeholder = None
687
+ for nid in reversed(prefix_node_ids):
688
+ if trace.nodes[nid].token_ids:
689
+ node_with_placeholder = trace.nodes[nid]
690
+ break
691
+ if node_with_placeholder is None or not node_with_placeholder.sampled:
692
+ return
693
+ node_rows = node_with_placeholder.routed_experts
694
+ if (
695
+ node_rows is None
696
+ or node_rows.shape[0] == 0
697
+ or node_rows.shape[1:] != arr.shape[1:]
698
+ ):
699
+ return
700
+ # Row `i` of this turn's array is sequence position `start + i`, so the prefix's final position
701
+ # is `arr[off - 1]`. Concatenating widens the node when this turn serialized `uint16`, where an
702
+ # in-place write would truncate.
703
+ node_with_placeholder.routed_experts = np.concatenate(
704
+ [node_rows[:-1], arr[off - 1 : off]], axis=0
705
+ )
706
+
707
+
676
708
  def _attribute_routed_experts(
677
709
  trace: Trace,
710
+ prefix_node_ids: list[int],
678
711
  new_node_ids: list[int],
679
712
  path_len: int,
680
713
  payload: Any,
@@ -683,7 +716,8 @@ def _attribute_routed_experts(
683
716
  payload's array covers the turn's prompt+completion from `payload["start"]` (0 = from token
684
717
  0); the nodes created this turn tile sequence positions `[path_len:]` in creation order, so
685
718
  we hand each node `arr[off : off+len(node.token_ids)]` and advance. Reused-prefix nodes keep
686
- the routing attributed when they were first created. A node whose slice falls outside the
719
+ the routing attributed when they were first created, except for the one position this turn
720
+ corrects (see `_replace_placeholder_routing_row`). A node whose slice falls outside the
687
721
  array (a `start` past `path_len`, e.g. an unexpected prefix-cache delta) is left unset — the
688
722
  branch then reports no routing rather than misaligning."""
689
723
  if payload is None:
@@ -693,6 +727,7 @@ def _attribute_routed_experts(
693
727
  payload["shape"]
694
728
  )
695
729
  off = path_len - int(payload.get("start", 0) or 0)
730
+ _replace_placeholder_routing_row(trace, prefix_node_ids, arr, off)
696
731
  needed = off + sum(len(trace.nodes[nid].token_ids) for nid in new_node_ids)
697
732
  for nid in new_node_ids:
698
733
  n = len(trace.nodes[nid].token_ids)
@@ -701,8 +736,8 @@ def _attribute_routed_experts(
701
736
  # Own only this node's rows; a view would retain the turn's full-context array.
702
737
  trace.nodes[nid].routed_experts = arr[off:end].copy()
703
738
  elif n and arr.shape[0] and 0 <= off and end == needed == arr.shape[0] + 1:
704
- # The engine omits the turn's final position because no forward pass follows it.
705
- # Pad only the final node's suffix instead of copying the full-context array.
739
+ # No forward pass follows the turn's final position, so it gets a placeholder: a
740
+ # copy of the previous row, appended to this node's slice of the array.
706
741
  trace.nodes[nid].routed_experts = np.concatenate(
707
742
  [arr[off:], arr[-1:]], axis=0
708
743
  )
@@ -813,7 +848,7 @@ def _commit_turn(turn: PendingTurn, response: Response) -> int:
813
848
  parent = prefix[-1] if prefix else None
814
849
  # cursor: in prompt_ids, the end of the previous *new* message's tokens
815
850
  cursor: int | None = None
816
- # Track new nodes separately so routed-expert attribution does not need this full path.
851
+ # Track new nodes separately so routed-expert attribution needs only node ids, not this path.
817
852
  new_node_ids: list[int] = []
818
853
  # Materialize the reused message path only for multimodal cursor attribution.
819
854
  mm_path: list[tuple[int, Message]] | None = None
@@ -874,8 +909,9 @@ def _commit_turn(turn: PendingTurn, response: Response) -> int:
874
909
 
875
910
  # Attribute this turn's expert-routing array onto the nodes created this turn (new input
876
911
  # nodes in creation order, then the assistant node), each getting the routing for its tokens.
912
+ # The prefix goes in too, so the position the previous turn could only pad can be corrected.
877
913
  _attribute_routed_experts(
878
- trace, new_node_ids, path_len, tokens.routed_experts if tokens else None
914
+ trace, prefix, new_node_ids, path_len, tokens.routed_experts if tokens else None
879
915
  )
880
916
 
881
917
  # Sampling masks are completion-aligned, so only the sampled node carries them.
@@ -4,12 +4,12 @@ The worker streams a served episode as it grows. Each trace announces its own ch
4
4
  (`Trace.notify`, fired by the rollout at every phase change and by the interception proxy
5
5
  after every recorded turn); the `DeltaStreamer` then diffs the run's live traces against
6
6
  what it has already sent and ships only the new part — the trace header once, then
7
- appended nodes / calls / errors, semantic links landing on earlier nodes, the scalar
7
+ appended nodes / calls / errors, semantic links and routing-row repairs on earlier nodes, the scalar
8
8
  fields whose value changed (timing spans, stop condition, rewards, ...), and the
9
9
  `pending` preview — the messages of the request in flight that no node holds yet, so a
10
10
  watcher sees a tool result before the model has answered it. The `Trace` is
11
- append-only at turn granularity (a turn's nodes are committed complete, with their
12
- tokens), so apart from the preview, which a committed turn repeats, every byte of the
11
+ append-only at turn granularity except for links and the last routing row of a node,
12
+ which the next prefill can repair. Apart from these and the preview, every byte of the
13
13
  episode crosses the wire once and the stream costs about what a single reply would; the
14
14
  reply that ends the run carries only the episode head and per-trace counts the client
15
15
  checks its assembly against. A cursor advances only once its delta is on the wire, so a
@@ -29,8 +29,10 @@ from collections.abc import Awaitable, Callable
29
29
  from typing import TYPE_CHECKING, Any, Self
30
30
 
31
31
  import msgpack
32
+ import numpy as np
32
33
  from pydantic import BaseModel
33
34
 
35
+ from verifiers.v1.graph import _decode_ndarray, _encode_ndarray
34
36
  from verifiers.v1.serve.encoding import msgpack_encoder
35
37
 
36
38
  if TYPE_CHECKING:
@@ -73,7 +75,7 @@ def pack(payload: Any) -> bytes:
73
75
 
74
76
 
75
77
  def unpack(data: bytes) -> Any:
76
- # `links` is keyed by node index (int).
78
+ # Node updates are keyed by node index (int).
77
79
  return msgpack.unpackb(data, raw=False, strict_map_key=False)
78
80
 
79
81
 
@@ -90,6 +92,8 @@ class TraceCursor:
90
92
  self.sent = dict.fromkeys(LIST_FIELDS, 0)
91
93
  self.links: list[int] = []
92
94
  """Per sent node, how many of its semantic links went out with or after it."""
95
+ self.final_rows: dict[int, bytes] = {}
96
+ """Per node that carries routing, its final row as packed when last sent."""
93
97
  self.scalars: dict[str, bytes] = {}
94
98
  self.pending: bytes = pack([])
95
99
 
@@ -157,6 +161,31 @@ class DeltaStreamer:
157
161
  else:
158
162
  self.cursors[trace_id] = cursor
159
163
 
164
+ def _maybe_add_routing_repairs(
165
+ self,
166
+ delta: dict[str, Any],
167
+ trace: Trace,
168
+ cursor: TraceCursor,
169
+ sent_nodes: int,
170
+ ) -> None:
171
+ """Add final rows repaired since they were sent, keyed by node index.
172
+
173
+ Record every node's current row on the cursor. `sent_nodes` is the pre-flush
174
+ count, so a node first sent in this delta is never reported as a repair.
175
+ """
176
+ repairs: dict[int, dict] = {}
177
+ for index, node in enumerate(trace.nodes):
178
+ if node.routed_experts is None:
179
+ continue
180
+ row = _encode_ndarray(node.routed_experts[-1:])
181
+ packed = pack(row)
182
+ if cursor.final_rows.get(index) != packed:
183
+ cursor.final_rows[index] = packed
184
+ if index < sent_nodes:
185
+ repairs[index] = row
186
+ if repairs:
187
+ delta["routing_repairs"] = repairs
188
+
160
189
  def diff(self) -> list[tuple[str, dict, TraceCursor | None]]:
161
190
  """Each trace's delta against its sent cursor, with the cursor as it stands once
162
191
  that delta is sent (None for a discard). Nothing here is committed: `flush`
@@ -183,6 +212,7 @@ class DeltaStreamer:
183
212
  cursor.links[index] = len(node_links)
184
213
  if links:
185
214
  delta["links"] = links
215
+ self._maybe_add_routing_repairs(delta, trace, cursor, cursor.sent["nodes"])
186
216
  for field in LIST_FIELDS:
187
217
  items = getattr(trace, field)
188
218
  sent = cursor.sent[field]
@@ -220,6 +250,20 @@ class EpisodeAssembly:
220
250
  def __init__(self) -> None:
221
251
  self.traces: dict[str, dict] = {}
222
252
 
253
+ def _maybe_apply_routing_repairs(self, delta: dict[str, Any], trace: dict) -> None:
254
+ """Replace repaired final rows in nodes the client already holds.
255
+
256
+ Rebuild each array: a repair can widen its dtype, and decoded rows alias
257
+ the original delta's bytes, which must remain unchanged for consumers.
258
+ """
259
+ for index, row in (delta.get("routing_repairs") or {}).items():
260
+ node = trace["nodes"][int(index)]
261
+ node["routed_experts"] = _encode_ndarray(
262
+ np.concatenate(
263
+ [_decode_ndarray(node["routed_experts"])[:-1], _decode_ndarray(row)]
264
+ )
265
+ )
266
+
223
267
  def apply(self, delta: dict) -> None:
224
268
  trace_id = delta["trace"]
225
269
  if delta.get("discard"):
@@ -234,6 +278,7 @@ class EpisodeAssembly:
234
278
  }
235
279
  for index, links in (delta.get("links") or {}).items():
236
280
  trace["nodes"][int(index)]["semantic_parents"].extend(links)
281
+ self._maybe_apply_routing_repairs(delta, trace)
237
282
  # a later `links` delta grows a node's semantic_parents in place, so the node
238
283
  # is copied: the delta stays as it was when the caller received it
239
284
  if "nodes" in delta:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: verifiers
3
- Version: 0.3.2.dev101
3
+ Version: 0.3.2.dev104
4
4
  Summary: Verifiers: Environments for LLM Reinforcement Learning
5
5
  Project-URL: Homepage, https://github.com/primeintellect-ai/verifiers
6
6
  Project-URL: Documentation, https://github.com/primeintellect-ai/verifiers
@@ -35,7 +35,7 @@ Requires-Dist: msgpack>=1.1.2
35
35
  Requires-Dist: numpy>=2.1.0
36
36
  Requires-Dist: openai<3.0.0,>=2.54.0
37
37
  Requires-Dist: prime-pydantic-config[toml]>=0.4.3
38
- Requires-Dist: prime-runs>=0.1.2
38
+ Requires-Dist: prime-runs>=0.1.3
39
39
  Requires-Dist: prime-sandboxes>=0.2.39
40
40
  Requires-Dist: prime-tunnel>=0.1.8
41
41
  Requires-Dist: pydantic>=2.12.3
@@ -5,7 +5,7 @@ verifiers/v1/agent.py,sha256=rFjoAn9vpRXoyIZTzMgIuGb6HczTSuOenaB-U-x09to,32489
5
5
  verifiers/v1/env.py,sha256=Hon7gcepciMYgki_jN4-l4XCcKRZRYuYm7c17nfeiFE,18520
6
6
  verifiers/v1/episode.py,sha256=Fft0R2W6INAVeJhoE9b8h7L_KQSz7XuxP4FT_XhcsUY,5741
7
7
  verifiers/v1/errors.py,sha256=uQySmCUrjDYFkBIii1vBFPiEcPFPQZ9EGpas_6HvcQI,5279
8
- verifiers/v1/graph.py,sha256=yJufZ9vMCBUshC9w19SpKyf6IJqQ7NHcv8iSdP0w7BU,39009
8
+ verifiers/v1/graph.py,sha256=b3Z3lCnsALnAvY82pDnjhODFYakkeLV_hXgGOvbryrQ,40737
9
9
  verifiers/v1/harness.py,sha256=sXEjVdrS4A2SO35C_uEebBVeUt8VL7dui7IneAp2MRs,14124
10
10
  verifiers/v1/judge.py,sha256=uqn-djkK2_7Q3kOjkDo6ipOvV_HOvuLa9y6AoDjRux8,9618
11
11
  verifiers/v1/rollout.py,sha256=KjfxxIQIqk2X3ZbWUHUn-1nWl04JdYwdVj7ELJtKgbw,24168
@@ -149,7 +149,7 @@ verifiers/v1/runtimes/docker/__init__.py,sha256=2YJtjOVSA4OgxXgeIlFW3Ps01KEfLNT_
149
149
  verifiers/v1/runtimes/docker/egress.py,sha256=iFH_1XotlEM-OCaW7GNu3TWVEq1sqesKYweyVBX8g90,14360
150
150
  verifiers/v1/serve/__init__.py,sha256=wOKuwfugzmDT4yIniwzCXlf4G6tv6Cg8Kc--WMa38Bs,553
151
151
  verifiers/v1/serve/client.py,sha256=E2y2x4qqYveWRWC9VZYQEu19aMEryPWTdXnYzbq5uTA,9829
152
- verifiers/v1/serve/delta.py,sha256=UnDhGqXdJPeRgRJ26j8xkHqU_HtBfk8TdzAvNezaWKA,11525
152
+ verifiers/v1/serve/delta.py,sha256=qhOUOYEt1wVzeaJ9IhjgU6ks5BkYOEtcFfGnuYxFEEs,13501
153
153
  verifiers/v1/serve/encoding.py,sha256=hBZFucAZK9riXOV3DaHcskq9zHTT8gVGkoFVOPfnr30,2499
154
154
  verifiers/v1/serve/pool.py,sha256=bT-FOlIuOiBtOasuPB-p9887tkpEqvQUJBgn1Q45ph0,15912
155
155
  verifiers/v1/serve/server.py,sha256=i6XrY_PMAapkrP54tYX8-K6iGmOmrR7BbQ-3Qhn3w4s,9885
@@ -190,8 +190,8 @@ verifiers/v1/utils/prime.py,sha256=UTYRjp9cbjNb6CVmBHda-1wWZAIIfxNOmTyNuT7_wL4,9
190
190
  verifiers/v1/utils/retries.py,sha256=Y2ZgrAjn-qNkRKZP_RVNL_7EK0iaRcZeCa404lpGYi4,5417
191
191
  verifiers/v1/utils/score.py,sha256=493yJVMw8teCu9JxapxMFPFzI0hNUqdo0Y2nGW4kckk,6200
192
192
  verifiers/v1/utils/version.py,sha256=-obEo_-l9-D8FLef4hYxncOe-uJpxrM1g2Hig_37Sgs,1607
193
- verifiers-0.3.2.dev101.dist-info/METADATA,sha256=t7FmSXg76hCUpvPZ4X17hP-MTCSpo_IIYUqT7nKksjk,4238
194
- verifiers-0.3.2.dev101.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
195
- verifiers-0.3.2.dev101.dist-info/entry_points.txt,sha256=iugElcdWPKbQM7uFF0lZ8iUpHsNr17-BwEAAjJWxV3U,259
196
- verifiers-0.3.2.dev101.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
197
- verifiers-0.3.2.dev101.dist-info/RECORD,,
193
+ verifiers-0.3.2.dev104.dist-info/METADATA,sha256=6pA2Gb_GfaderhuWT55BLeMboFhtpLv-D-jOIjVX_II,4238
194
+ verifiers-0.3.2.dev104.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
195
+ verifiers-0.3.2.dev104.dist-info/entry_points.txt,sha256=iugElcdWPKbQM7uFF0lZ8iUpHsNr17-BwEAAjJWxV3U,259
196
+ verifiers-0.3.2.dev104.dist-info/licenses/LICENSE,sha256=v0RrUsdV3IDoZhrRce297IXS3xMHNJ-_LdLpFAUWb9k,1072
197
+ verifiers-0.3.2.dev104.dist-info/RECORD,,