虽然我欣赏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
6条答案
按热度按时间lpwwtiir1#
为了HF兼容性,我认为将这些设置为新的默认值是值得的。大家有什么想法吗?@WoosukKwon@zhuohan123@Yard1
knpiaxh12#
为了与HF兼容,我认为将这些设置为新的默认值是值得的。你怎么看?@WoosukKwon@zhuohan123@Yard1
我认为在我们尝试将默认值设置为OpenAI提供的API之前。你认为哪个更好?OpenAI还是HF?
wko9yo5t3#
OpenAI不允许
top_k
。kmbjn2e34#
我认为我们也应该在OpenAI上标准化。不过,这应该是要记录下来的!
krugob8w5#
OpenAI的超集?因为有很多限制/缺少选项
bihw5rsg6#
是的,我们应该提供一个OpenAI参数/特征的超集,但我们应该使用OpenAI的默认值。