vllm Better defaults to match Hugging Face

ee7vknir  于 6个月前  发布在  其他
关注(0)|答案(6)|浏览(60)

虽然我欣赏CUDA内核可能导致比特精确度问题的事实,但vLLM的默认设置在基准测试上的表现比使用相应的慢速Huggingface代码差很多。
当使用openai api_endpoint.py脚本时,这成为一个更大的问题——没有简单的方法知道vllm的默认值以及如何传递通常不被补全API接受的参数,这导致我几乎浪费了一整天的时间。希望下面的解决方案能帮助别人避免浪费那么多时间。
为了使贪婪分数尽可能地紧密对齐,以下是一些示例代码,它可以帮助在HumanEval上实现与HF相当的贪婪分数,但大约快20倍。

提供模型

(注意:截至v0.3.0,logprobs计算和使用echo=True存在一个bug。修复方法很简单,但需要手动修补serving_completion.py脚本。我们还使用了一个较大的max-num-seqs,因为如果内存允许,我们希望一次性处理整个数据集。在这个例子中,我将使用一个mistrial 7b指令0.1。)

python -m vllm.entrypoints.openai.api_server \
      --model "${MODEL_PATH}" \
      --trust-remote-code \
      --seed=${SEED} \
      --host="0.0.0.0" \
      --port=${SERVER_PORT} \
      --served-model-name "${MODEL_NAME}" \
      --tensor-parallel-size=${NUM_GPUS} \
      --max-num-seqs=1024

为Humaneval生成解决方案

HF和vLLM之间的主要参数差异是top_k - HF的贪婪默认值为1,而do_sample=True 的默认值为50(奇怪的选择,但可以接受)。另一方面,vLLM有一个模糊的-1作为默认值(我猜这意味着它被禁用,所有logits都被使用?)。这里开始出现问题。如果你使用vLLM端点,当然你可以显式地将top_k设置为1或50,但openai端点通常与openai库一起使用——而那不允许传递top_k。
你必须查找文档中的一个模糊标志extra_body,它接受一个字典作为附加参数传递给请求。哦,好吧。
另一个奇怪的标志是spaces_between_special_tokens - 当默认情况下将其设置为True时,它对分数产生微弱但负面的影响。也许在其他非代码基准测试中有用。
下面脚本的关键部分是

# Setup top k properly (to match HF)
    if generation_config['top_k'] >= 1:
        extra_body = {'top_k': generation_config['top_k'], 'spaces_between_special_tokens': False}
    else:
        extra_body = {'spaces_between_special_tokens': False}

response = client.completions.create(
                model=model_name,
                prompt=prompt_subbatch,
                max_tokens=generation_config['tokens_to_generate'],
                temperature=generation_config['temperature'],
                top_p=generation_config['top_p'],
                n=args.N,
                stream=False,
                stop=generation_config['end_strings'],
                echo=True,
                frequency_penalty=0.0,
                presence_penalty=0.0,
                extra_body=extra_body,
            )

生成解决方案的脚本

"""
Mistral 7b
python temp.py \
--output_path "results/mistral_7b_instruct_humaneval_vllm" \
--N 1 --greedy_decode --batchsize 165 \
--temperature 1.0 --top_p 1.0 --max_len 1280

"""

import argparse
import pprint
import os
from tqdm import tqdm

import torch
import openai
from evalplus.data import get_human_eval_plus, write_jsonl

def generate_prompt(input):
    INSTRUCTION = f"""[INST] Create a Python script for this problem:
{input} [/INST]

### Response:"""
    return INSTRUCTION

if torch.cuda.is_available():
    device = "cuda"
else:
    device = "cpu"

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('--host', type=str, default='127.0.0.1', help='')
    parser.add_argument('--port', type=int, default=5000, help='')
    parser.add_argument('--output_path', type=str, help="")
    parser.add_argument('--start_index', type=int, default=0, help="")
    parser.add_argument('--end_index', type=int, default=164, help="")
    parser.add_argument('--temperature', type=float, default=1.0, help="")
    parser.add_argument('--top_p', type=float, default=1.0, help="")
    parser.add_argument('--N', type=int, default=1, help="")
    parser.add_argument('--max_len', type=int, default=1280, help="")
    parser.add_argument('--greedy_decode', action='store_true', help='')
    parser.add_argument('--batchsize', type=int, default=50, help='')
    parser.add_argument('--overwrite', action='store_true', help='')

    # Model specification
    args = parser.parse_args()

    argsdict = vars(args)
    print(pprint.pformat(argsdict))

    problems = get_human_eval_plus()

    task_ids = sorted(problems.keys())[args.start_index: args.end_index]
    prompts = [problems[task_id]['prompt'] for task_id in task_ids]
    num_samples = len(prompts)
    print("Number of samples: {}".format(num_samples))

    generation_config = {
        "tokens_to_generate": args.max_len,
        "temperature": args.temperature,
        "top_k": 1 if args.greedy_decode else 50,  # match HF
        "top_p": args.top_p,
        "greedy": True if args.greedy_decode else False,
        'end_strings': ['</s>', 'Explain:', 'Explanation:', 'Example:'],  # Early exit the explanations, not needed
    }

    if generation_config['greedy']:
        print("Greedy generation set - setting temperature, top_p and top_k to 1.0")
        generation_config['temperature'] = 1.0
        generation_config['top_p'] = 1.0
        generation_config['top_k'] = 1

    # Setup OpenAI
    client = openai.OpenAI(api_key='EMPTY', base_url=f"http://{args.host}:{args.port}/v1")
    model_list = client.models.list()
    model_name = model_list.data[0].id
    print("Model name:", model_name)

    # Setup top k properly (to match HF)
    if generation_config['top_k'] >= 1:
        extra_body = {'top_k': generation_config['top_k'], 'spaces_between_special_tokens': False}
    else:
        extra_body = {'spaces_between_special_tokens': False}

    basedir = os.path.expanduser(args.output_path)
    if not os.path.exists(basedir):
        os.makedirs(basedir, exist_ok=True)

    # Greedy batch generation
    if generation_config['greedy']:
        print("Generating completions for greedy decoding...")
        prog = tqdm(range(num_samples), ncols=0, total=num_samples)
        for i in range(0, num_samples, args.batchsize):
            output_files = []
            for j in range(args.batchsize):
                if i + j >= args.end_index:
                    break

                output_file = args.output_path + '/{}.jsonl'.format(args.start_index + i + j)

                if os.path.exists(output_file) and not args.overwrite:
                    print(f'Skip {output_file} as it already exists')
                    continue

                output_files.append(output_file)

            if len(output_files) == 0:
                continue

            prompt_subbatch = []
            ids_subbatch = []
            for j in range(args.batchsize):
                if i + j < args.end_index:
                    prompt = prompts[i+j].replace('    ', '\t')
                    prompt_subbatch.append(generate_prompt(prompt))

                    idx = task_ids[i + j]
                    ids_subbatch.append(idx)

            response = client.completions.create(
                model=model_name,
                prompt=prompt_subbatch,
                max_tokens=generation_config['tokens_to_generate'],
                temperature=generation_config['temperature'],
                top_p=generation_config['top_p'],
                n=args.N,
                stream=False,
                stop=generation_config['end_strings'],
                echo=True,
                frequency_penalty=0.0,
                presence_penalty=0.0,
                extra_body=extra_body,
            )

            # Extract completions
            gen_seqs = []
            for choice in response.choices:
                gen_seqs.append(choice.text)

            if gen_seqs is not None:
                for k, gen_seq in enumerate(gen_seqs):
                    idx = ids_subbatch[k]
                    output_file = output_files[k]

                    completion_seqs = []
                    completion_seq = gen_seq[len(prompt_subbatch[k]):]
                    completion_seq = completion_seq.replace('\t', '    ')
                    all_code = gen_seq.replace('\t', '    ')

                    completion_seqs.append(
                        {'task_id': idx,
                         'completion': completion_seq,
                         'all_code': all_code,
                         }
                    )

                    print("Saving results to {}".format(output_file))
                    write_jsonl(output_file, completion_seqs)

                print()
                prog.update(len(gen_seqs))

        print("Finished!")
        return

if __name__ == '__main__':
    main()

就是这样了,这两个小标志几乎可以让huggingface与openai端点和openai库具有99%相似的贪婪评估。
建议如果可能的话,在文档中添加一个关于top_k标志以及如何将其传递给openai的简短说明的注解,这样将来其他人就不必像我一样寻找这些参数了。
顺便说一下 @simon-mo@zhuohan123

lpwwtiir

lpwwtiir1#

为了HF兼容性,我认为将这些设置为新的默认值是值得的。大家有什么想法吗?@WoosukKwon@zhuohan123@Yard1

knpiaxh1

knpiaxh12#

为了与HF兼容,我认为将这些设置为新的默认值是值得的。你怎么看?@WoosukKwon@zhuohan123@Yard1
我认为在我们尝试将默认值设置为OpenAI提供的API之前。你认为哪个更好?OpenAI还是HF?

wko9yo5t

wko9yo5t3#

OpenAI不允许top_k

kmbjn2e3

kmbjn2e34#

我认为我们也应该在OpenAI上标准化。不过,这应该是要记录下来的!

krugob8w

krugob8w5#

OpenAI的超集?因为有很多限制/缺少选项

bihw5rsg

bihw5rsg6#

是的,我们应该提供一个OpenAI参数/特征的超集,但我们应该使用OpenAI的默认值。

相关问题