Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion renderers/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
from __future__ import annotations

import asyncio
import base64
import json
import logging
import math
from collections.abc import Mapping
from typing import Any, cast

import httpx
import numpy as np
from openai import AsyncOpenAI

from renderers.base import (
Expand Down Expand Up @@ -134,9 +136,26 @@ def parse_generate_response(raw: bytes) -> dict[str, Any]:
return payload


def _parse_packed_completion_logprobs(
packed: Mapping[str, Any], completion_ids: list[int]
) -> list[float]:
"""prime-rl's packed form: base64 float32 ``{data, shape, dtype}``, one per token."""
values = np.frombuffer(base64.b64decode(packed["data"]), dtype=packed["dtype"])
valid = np.isfinite(values) & (values > VLLM_LOGPROB_SENTINEL)
if len(values) != len(completion_ids) or not valid.all():
raise MalformedGenerateResponseError(
f"Engine response choice.completion_logprobs: {len(values)} values for "
f"{len(completion_ids)} completion tokens, or a non-finite or missing (-9999) logprob."
)
Comment thread
cursor[bot] marked this conversation as resolved.
return values.astype(np.float64).tolist()


def _parse_completion_logprobs(
choice: Mapping[str, Any], completion_ids: list[int]
) -> list[float]:
packed = choice.get("completion_logprobs")
if packed is not None:
return _parse_packed_completion_logprobs(packed, completion_ids)
raw_logprobs = choice.get("logprobs")
if not isinstance(raw_logprobs, Mapping):
raise MalformedGenerateResponseError(
Expand Down Expand Up @@ -376,7 +395,8 @@ def _prepare():

routed_experts = choice.get("routed_experts")
# vLLM's native kept-set sampling masks (``--return-sampling-mask``):
# one list of surviving vocab ids per completion token.
# one list of surviving vocab ids per completion token, or (prime-rl's
# server) packed CSR ``{"ids", "counts"}`` base64 int32 arrays.
sampling_mask = choice.get("sampling_mask")

# /inference/v1/generate returns finish_reason in {"stop","length",...} —
Expand Down
27 changes: 27 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,33 @@ def test_generate_preserves_zero_completion_logprob():
assert result["completion_logprobs"] == [0.0, -0.2]


def test_generate_reads_packed_completion_logprobs():
client = _FakeClient()
client.choice.pop("logprobs")
values = np.array([-0.1, 0.0], dtype=np.float32)
client.choice["completion_logprobs"] = {
"data": base64.b64encode(values.tobytes()).decode(),
"shape": [2],
"dtype": "float32",
}

result = _run_generate(client)

assert result["completion_logprobs"] == values.tolist()

client.choice["completion_logprobs"]["data"] = base64.b64encode(
np.array([-0.1, -9999.0], dtype=np.float32).tobytes()
).decode()
with pytest.raises(MalformedGenerateResponseError, match="missing"):
_run_generate(client)

client.choice["completion_logprobs"]["data"] = base64.b64encode(
np.array([-0.1, np.inf], dtype=np.float32).tobytes()
).decode()
with pytest.raises(MalformedGenerateResponseError, match="non-finite"):
_run_generate(client)


class _MalformedToolRenderer(_FakeRenderer):
"""Returns only a malformed tool-call attempt — finish_reason must stay "stop"."""

Expand Down
Loading