149 lines
5.2 KiB
Python
149 lines
5.2 KiB
Python
#!/usr/bin/env python
|
||
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
|
||
#
|
||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
# you may not use this file except in compliance with the License.
|
||
# You may obtain a copy of the License at
|
||
#
|
||
# http://www.apache.org/licenses/LICENSE-2.0
|
||
#
|
||
# Unless required by applicable law or agreed to in writing, software
|
||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
from __future__ import annotations
|
||
|
||
import copy
|
||
import json
|
||
import os
|
||
import socket
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
import unittest
|
||
|
||
import pytest
|
||
import requests
|
||
|
||
from paddlenlp.transformers import LlamaTokenizer
|
||
|
||
|
||
def is_port_in_use(port):
|
||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||
s.settimeout(0.01)
|
||
try:
|
||
s.bind(("localhost", port))
|
||
return False
|
||
except socket.error:
|
||
return True
|
||
|
||
|
||
class UITest(unittest.TestCase):
|
||
def setUp(self):
|
||
# start web ui
|
||
self.flask_port = self.available_free_port()
|
||
self.port = self.available_free_port([self.flask_port])
|
||
self.model_path = "__internal_testing__/micro-random-llama"
|
||
command = (
|
||
"cd ./llm && PYTHONPATH=../:$PYTHONPATH"
|
||
+ " {python} predict/flask_server.py --model_name_or_path {model_path} "
|
||
+ '--port {port} --flask_port {flask_port} --src_length 1024 --dtype "float16"'
|
||
).format(flask_port=self.flask_port, port=self.port, model_path=self.model_path, python=sys.executable)
|
||
current_env = copy.copy(os.environ.copy())
|
||
current_env.pop("http_proxy", None)
|
||
current_env.pop("https_proxy", None)
|
||
os.environ["http_proxy"] = ""
|
||
os.environ["https_proxy"] = ""
|
||
|
||
self.ui_process = subprocess.Popen(command, shell=True, stdout=sys.stdout, stderr=sys.stderr, env=current_env)
|
||
self.tokenizer = LlamaTokenizer.from_pretrained(self.model_path)
|
||
return super().setUp()
|
||
|
||
def tearDown(self):
|
||
self.ui_process.terminate()
|
||
|
||
def available_free_port(self, exclude=None):
|
||
exclude = exclude or []
|
||
for port in range(8000, 10000):
|
||
if port in exclude:
|
||
continue
|
||
if is_port_in_use(port):
|
||
continue
|
||
return port
|
||
|
||
raise ValueError("can not get available port in [8000, 8200]")
|
||
|
||
def wait_until_server_is_ready(self):
|
||
while True:
|
||
if is_port_in_use(self.flask_port) and is_port_in_use(self.port):
|
||
break
|
||
print("waiting for server ...")
|
||
time.sleep(1)
|
||
|
||
def get_gradio_ui_result(self, *args, **kwargs):
|
||
_, _, file = self.client.predict(*args, **kwargs)
|
||
with open(file, "r", encoding="utf-8") as f:
|
||
content = json.load(f)
|
||
return content[-1]["utterance"]
|
||
|
||
@pytest.mark.timeout(4 * 60)
|
||
def test_argument(self):
|
||
self.wait_until_server_is_ready()
|
||
|
||
def get_response(data):
|
||
res = requests.post(f"http://localhost:{self.flask_port}/v1/chat/completions", json=data, stream=True)
|
||
result_ = ""
|
||
for line in res.iter_lines():
|
||
if not line:
|
||
continue
|
||
decoded_line = line.decode("utf-8").strip()
|
||
# 如果返回行以 "data:" 开头,则去除该前缀
|
||
if decoded_line.startswith("data:"):
|
||
data_str = decoded_line[len("data:") :].strip()
|
||
else:
|
||
data_str = decoded_line
|
||
if data_str == "[DONE]":
|
||
break
|
||
chunk = json.loads(data_str)
|
||
# 根据 OpenAI 的流式返回,每个 chunk 在 choices[0]["delta"] 中包含回复增量
|
||
delta = chunk["choices"][0]["delta"].get("content", "")
|
||
result_ += delta
|
||
return result_
|
||
|
||
# 测试用例1:greedy search 模式(top_p 为1.0)
|
||
data = {
|
||
"messages": [{"role": "user", "content": "你好"}],
|
||
"temperature": 1.0,
|
||
"max_tokens": 20,
|
||
"top_p": 1.0,
|
||
"stream": True,
|
||
}
|
||
result_1 = get_response(data)
|
||
|
||
# 测试用例2:采样模式(top_p 为 0.7)
|
||
data = {
|
||
"messages": [{"role": "user", "content": "你好"}],
|
||
"temperature": 1.0,
|
||
"max_tokens": 20,
|
||
"top_p": 0.7,
|
||
"stream": True,
|
||
}
|
||
result_2 = get_response(data)
|
||
|
||
# 对生成文本的长度进行简单检测
|
||
assert 10 <= len(self.tokenizer.tokenize(result_1)) <= 50
|
||
assert 10 <= len(self.tokenizer.tokenize(result_2)) <= 50
|
||
|
||
# 测试用例3:更长的 max_tokens 参数
|
||
data = {
|
||
"messages": [{"role": "user", "content": "你好"}],
|
||
"temperature": 1.0,
|
||
"max_tokens": 100,
|
||
"top_p": 0.7,
|
||
"stream": True,
|
||
}
|
||
result_3 = get_response(data)
|
||
assert result_3 != result_2
|
||
assert 70 <= len(self.tokenizer.tokenize(result_3)) <= 150
|