Skip to content

Commit d7cea68

Browse files
committed
Fix interception proxy capturing 0 token ids on current vLLM
vLLM with --return-tokens-as-token-ids encodes the token id in the logprobs "token" field as "token_id:<int>" and does not emit a separate "token_id" key, so the capture read zero token ids. Every env that trains on the captured trace (opencode, pi, ...) then loses the token ids GRPO needs. Parse the id from the token field, still accepting a token_id key. Adds a unit test for both.
1 parent 5298e0d commit d7cea68

2 files changed

Lines changed: 70 additions & 2 deletions

File tree

envs/opencode_env/sandbox/interception.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,14 @@ def _build_turn_record(
458458
token_ids: list[int] = []
459459
per_token_logps: list[float] = []
460460
for entry in content_lp:
461-
tokens.append(entry.get("token", ""))
462-
# OpenAI returns no raw token ids; vLLM returns them as ``token_id``.
461+
token = entry.get("token", "")
462+
tokens.append(token)
463+
# OpenAI returns no raw token ids. vLLM with ``--return-tokens-as-token-ids``
464+
# encodes the id in the ``token`` field as ``"token_id:<int>"``; some builds
465+
# instead expose a separate ``token_id`` key. Accept both.
463466
token_id = entry.get("token_id")
467+
if token_id is None and token.startswith("token_id:"):
468+
token_id = token.split(":", 1)[1]
464469
if token_id is not None:
465470
token_ids.append(int(token_id))
466471
lp = entry.get("logprob")
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
"""The interception proxy must capture per-token ids from a vLLM logprobs response.
8+
9+
vLLM with ``--return-tokens-as-token-ids`` encodes the id in the ``token`` field as
10+
``"token_id:<int>"`` (no separate ``token_id`` key), which the capture must parse so
11+
GRPO trains on real token ids."""
12+
13+
import os
14+
import sys
15+
16+
# Make ``envs/`` importable when running from the repository root.
17+
_REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
18+
if _REPO_ROOT not in sys.path:
19+
sys.path.insert(0, _REPO_ROOT)
20+
_ENVS_DIR = os.path.join(_REPO_ROOT, "envs")
21+
if _ENVS_DIR not in sys.path:
22+
sys.path.insert(0, _ENVS_DIR)
23+
24+
from opencode_env.sandbox.interception import _build_turn_record
25+
26+
27+
def _response(content):
28+
return {"choices": [{"finish_reason": "stop", "logprobs": {"content": content}}]}
29+
30+
31+
def test_captures_ids_from_vllm_token_id_prefix():
32+
# vLLM --return-tokens-as-token-ids: id lives in the ``token`` field as "token_id:N".
33+
content = [
34+
{"token": "token_id:15339", "logprob": -0.1},
35+
{"token": "token_id:1917", "logprob": -0.2},
36+
]
37+
rec = _build_turn_record(
38+
turn_idx=0, request_body={}, response_json=_response(content), latency_s=0.0
39+
)
40+
assert rec.completion_token_ids == [15339, 1917]
41+
assert rec.per_token_logps == [-0.1, -0.2]
42+
43+
44+
def test_captures_ids_from_token_id_key():
45+
# Some builds instead expose a separate integer ``token_id`` key.
46+
content = [
47+
{"token": "hello", "token_id": 42, "logprob": -0.5},
48+
{"token": " world", "token_id": 99, "logprob": -0.6},
49+
]
50+
rec = _build_turn_record(
51+
turn_idx=0, request_body={}, response_json=_response(content), latency_s=0.0
52+
)
53+
assert rec.completion_token_ids == [42, 99]
54+
55+
56+
def test_plain_openai_has_logprobs_but_no_ids():
57+
# A plain OpenAI response (no ids) still yields logprobs, just no token ids.
58+
content = [{"token": "hi", "logprob": -0.3}]
59+
rec = _build_turn_record(
60+
turn_idx=0, request_body={}, response_json=_response(content), latency_s=0.0
61+
)
62+
assert rec.completion_token_ids == []
63+
assert rec.per_token_logps == [-0.3]

0 commit comments

Comments
 (0)