背景介绍

在使用大模型推理引擎时,有时除了想要获得generated的text,同时还想获得生成的每个token的logprobability, 获得这个原因可以参考  

https://zhuanlan.zhihu.com/p/673424860https://zhuanlan.zhihu.com/p/673424860

vllm中的logprobs

采用vllm中的offline推理时,可以设置sample_params的logprobs(int)来获得生成过程中每个token。如下:

sampling_params = SamplingParams(
    max_tokens=100,
    temperature=0.7,
    top_p=0.95,
    logprobs=1,
    stop=["\n\n"]
)
outputs = llm.generate(prompts, sampling_params=sampling_params) 

vllm中对logprobs的解释为

  •  logprobs – Number of log probabilities to return per output token. When set to None, no probability is returned. If set to a non-None value, the result includes the log probabilities of the specified number of most likely tokens, as well as the chosen tokens. Note that the implementation follows the OpenAI API: The API will always return the log probability of the sampled token, so there may be up to logprobs+1 elements in the response.

解释: 就是说默认不设置这个参数,返回的内容就不会包含logprobs。但是当被设置为一个int时,就会包含probability前logprobs的token和最终sample出来的token。如果不使用greedy decoding(上面的参数中temperature=0.7, 所以不是greedy decoding, 如果想使用greedy decoding的话,直接将temperature设为0),这两个token可能不一致,所以返回的至多有(logprobs+1)个token及其对应的logprobs,当sample出来的token在前logprobs中,就有logprobs个token了。

问题来了,如果我只想想要sample出来的token及其对应的logprobs,一种非常简单的方法就是将参数logprobs设为0。

好了,如果想获得sample出来的token的logprobs到这里就结束了!具体的代码实现可以看最后的例子!


但是上面引发的另一个问题就是当参数logprobs >= 1时,由于返回的是一个List[Dict]类型,那sample出来的token和probability前logprobs的token的顺序是什么样的呢?vllm中的文档好像并没有明确说明这个细节,我在查了网上资料好像也没有,openai api对应的logprobs返回逻辑好像与这个不一致。

再重述下问题:当sample参数logprobs>1时,由于包含至多(logprobs + 1)个Dict的List, sample出来的token对应的logprobs是在List的最后还是最前面,于是我写了个简单的脚本进行测试,代码如下:

from vllm import LLM, SamplingParams
from pprint import pprint
from dataclasses import asdict
import json

prompts = ["### Instruction: Hello, my name is", "### Instruction: The capital of France is"]  # Sample prompts.
llm = LLM(model="Qwen/Qwen2.5-0.5B-Instruct")  # Create an LLM.
sampling_params = SamplingParams(
    max_tokens=100,
    temperature=0.7,
    top_p=0.95,
    logprobs=1, # there are up to (logprobs + 1) token data
    stop=["\n\n"]
)
outputs = llm.generate(prompts, sampling_params=sampling_params) 

for i, request_output in enumerate(outputs):
    # pprint(dir(output.outputs[0]))
    print(f"Generated text: {request_output.outputs[0].text}")

    
    # Calculate and print the sum of log probabilities
    sum_logprobs = 0
    if request_output.outputs[0].logprobs:

        # write logprobs to file for better readability
        with open(f"logprobs_{i}.json", "w") as f:
            logprobs_list = []
            for logprobs in request_output.outputs[0].logprobs:
                tmp = {}
                for k in logprobs:
                    tmp[k] = asdict(logprobs[k])
                logprobs_list.append(tmp)
            json.dump(logprobs_list, f)

        
        for logprobs in request_output.outputs[0].logprobs:
            if len(logprobs) > 1:
                print(f"logprobs: {logprobs}, type: {type(logprobs)}")
            sum_logprobs += list(logprobs.values())[0].logprob
                
        avg_logprob = sum_logprobs / len(request_output.outputs[0].logprobs)
        print("================================================")
        print(f"Average log probability: {sum_logprobs}")
        print(f"Cumulative log probability: {request_output.outputs[0].cumulative_logprob}")
        print(f"Average log probability: {avg_logprob}")
        print("================================================")
    else:
        print(f"Cumulative log probability: {request_output.outputs[0].cumulative_logprob}")
        print("No log probabilities available")
    

结论: sample出来的token logprobs 位于返回的request_output.output[0].logprobs(List[Dict])的第一个位置

一些重要的想获得的返回数据可以简单地用以下方法获得(但是需要设置logprobs参数,要不然就是None)

- output的logprobs sum: request_output.outputs[0].cumulative_logprob

- output的avg logprobs: request_output.outputs[0].cumulative_logprob / len(request_output.outputs[0].logprobs)

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐