Proposal: vLLM scoring fields on /v1/completions (allowed_token_ids, return_tokens_as_token_ids, token-id prompts) for classifier-style clients

Đang mở
#768 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
35/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python
Lĩnh vực
api, backend

Hướng nghiên cứu

Start with python/freetoken/server/api_models.py and python/freetoken/server/openai_api.py, especially CompletionRequest and _completion_unsupported_reason; then trace how completion requests reach the scheduler and how #224 adds logprobs. Implement and validate the proposed scoring fields and token-id prompts, then verify the reproduction returns only allowed labels with token-id logprob keys and rejects invalid inputs.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Proposal

Some clients use a vLLM server as a classifier or scorer, for example AnyJev's VLLMBackend and Tacit's ServerEngine. They send /v1/completions with max_tokens: 1, temperature: 0, logprobs: K, allowed_token_ids: [...] (the label tokens), return_tokens_as_token_ids: true, and often a token-id prompt (list[int] or list[list[int]]). They read the label from the sampled token, or from the token_id:N keys of top_logprobs.

On FreeToken today, logprobs and token-id prompts get a 400, which is correct. allowed_token_ids and return_tokens_as_token_ids are accepted and then ignored (extra="allow", as in #693). Once #224 adds logprobs, these requests with a text prompt will get HTTP 200 with a wrong answer (token-id prompts still get the 400): the sampled token is not restricted to the labels, and top_logprobs cover the whole vocabulary under text keys. The client has no error to notice.

Minimal reproduction (CPU only, PYTHONPATH=python). It shows what main does with these fields today; the fake engine always answers " Paris":

import asyncio, json
from types import SimpleNamespace
from freetoken.message import UserReply
from freetoken.server.openai_api import CompletionRequest, handle_completion

class State:
    config = SimpleNamespace(served_model_name="m", max_output_tokens=None)
    def __init__(self): self.sent = []
    def new_user(self): return 1
    async def send_one(self, msg): self.sent.append(msg)
    async def wait_for_ack(self, uid):
        yield UserReply(uid=1, incremental_output=" Paris", finished=True, finish_reason="length")

async def show(**kw):
    s = State()
    r = await handle_completion(CompletionRequest(model="m", max_tokens=1, temperature=0, **kw), None, s, {})
    out = f"{r.status_code} {json.loads(r.body)['error']['message']}" if hasattr(r, "body") else f"200 text={r['choices'][0]['text']!r}"
    sent = [k for k in vars(s.sent[0].sampling_params) if "allow" in k or "token_id" in k] if s.sent else None
    print(kw, "->", out, "| allowed/token_id fields sent:", sent)

async def main():
    await show(prompt="Is the sky blue? Answer Yes or No:", allowed_token_ids=[9454, 2753])
    await show(prompt="Q:", allowed_token_ids=[9454, 2753], return_tokens_as_token_ids=True)
    await show(prompt="Q:", allowed_token_ids=[9454, 2753], logprobs=2)
    await show(prompt=[[1, 2, 3]])
asyncio.run(main())
{'prompt': 'Is the sky blue? Answer Yes or No:', 'allowed_token_ids': [9454, 2753]} -> 200 text=' Paris' | allowed/token_id fields sent: []
{'prompt': 'Q:', 'allowed_token_ids': [9454, 2753], 'return_tokens_as_token_ids': True} -> 200 text=' Paris' | allowed/token_id fields sent: []
{'prompt': 'Q:', 'allowed_token_ids': [9454, 2753], 'logprobs': 2} -> 400 logprobs is not supported | allowed/token_id fields sent: None
{'prompt': [[1, 2, 3]]} -> 400 OpenAI token-id prompt inputs are not supported; pass text prompt strings instead | allowed/token_id fields sent: None
Cause (current main, cc56b6bd)
  • python/freetoken/server/api_models.py:101-102: CompletionRequest has extra="allow" and does not declare allowed_token_ids or return_tokens_as_token_ids. _completion_unsupported_reason (openai_api.py:655-668) checks only the fields it declares.
  • openai_api.py:656-657 rejects token-id prompts, and CompletionRequest.prompt already types them (list[int] | list[list[int]]).
Proposed design (on top of #224)
  1. allowed_token_ids: a per-row vocabulary mask applied to the logits before sampling, validated as 0 <= id < vocab_size and non-empty (400 otherwise).
  2. Logprobs on a masked row: if a row sends both allowed_token_ids and logprobs, compute its logprobs over the allowed set (renormalized), so the label scores sum to 1. vLLM gives this only with --logprobs-mode processed_logprobs, which applies to the whole server. Deciding per row keeps #224's raw logprobs for every other request.
  3. return_tokens_as_token_ids: return token and the top_logprobs keys as "token_id:N", as vLLM does. This avoids collisions when several ids decode to the same text.
  4. Token-id prompts: accept list[int] / list[list[int]] and send the ids straight to the scheduler. Reject an empty list and ids outside [0, vocab_size) with a 400.
  5. Logprobs cap: #224 caps completions logprobs at 5 (the OpenAI legacy limit). Scoring clients ask for one entry per label. A server cap like vLLM's --max-logprobs (default 20; 32 would cover most label sets) would serve them.
  6. Fields that are set but not implemented: return 400 for vLLM sampling or extension fields that change the result and are not implemented (guided_*, structured_outputs, prompt_logprobs, best_of, use_beam_search, min_tokens, ...), not accept them silently. This adds to the deny-list proposed in #693.

Items 1-4 are what the clients above need to get correct answers; 5 and 6 make the remaining gaps visible.

Environment

Serving nvidia/Qwen3.8-Flash-Next-NVFP4 on an RTX 5080 16 GB (sm_120, driver 615.78.08), torch 2.14.1+cu130; behaviour above checked CPU-only on main cc56b6bd.

Ngôn ngữ chính
Python
Star
14.2k
Fork
1.4k
Merge trung bình
18 giờ 46 phút
Pull request đã merge (30 ngày)
98

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của FlashML-org/FreeToken

Tất cả issue của FlashML-org/FreeToken

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.