最新消息 🔥
Byzer-LLM 是一个基于 Ray 的 LLM 全生命周期解决方案,包含预训练、微调、部署和服务。
其独特功能包括:
推荐环境:
## 确保您的 Python 版本是 3.10.11
pip install -r requirements.txt
## 如果没有 Nvidia GPU,请跳过此步骤
pip install vllm==0.3.3
pip install -U byzerllm
ray start --head
如果您的 cuda 版本是 11.8,请参考以下链接安装 vLLM:
https://docs.vllm.ai/en/latest/getting_started/installation.html
关键步骤如下:
# 安装 vLLM with CUDA 11.8。
export VLLM_VERSION=0.2.6
export PYTHON_VERSION=310
pip install https://github.com/vllm-project/vllm/releases/download/v${VLLM_VERSION}/vllm-${VLLM_VERSION}+cu118-cp${PYTHON_VERSION}-cp${PYTHON_VERSION}-manylinux1_x86_64.whl
# 使用 CUDA 11.8 重新安装 PyTorch。
pip uninstall torch -y
pip install torch --upgrade --index-url https://download.pytorch.org/whl/cu118
# 使用 CUDA 11.8 重新安装 xFormers。
pip uninstall xformers -y
pip install --upgrade xformers --index-url https://download.pytorch.org/whl/cu118
仅在 Ubuntu 20.04/22.04 CentOS 8.0 上测试过
如果您的机器是没有安装 GPU 驱动和 Cuda 的裸机,可以使用以下脚本进行设置:
git clone https://gitee.com/allwefantasy/byzer-llm
cd byzer-llm/setup-machine
然后切换到 ROOT 用户,并运行以下脚本:
ROLE=master ./setup-machine.sh
此步骤将创建一个名为 byzerllm 的用户。
然后切换到 byzerllm 用户,并运行以下脚本:
ROLE=master ./setup-machine.sh
这次脚本将自动安装以下软件:
如果需要在 Ray 集群中添加更多工作节点,请在那些工作节点上重复上述步骤。
注意,在工作节点上,ROLE 应设置为 worker。
ROLE=worker ./setup-machine.sh
部署一个模型:
byzerllm deploy --model_path /home/byzerllm/models/openbuddy-llama2-13b64k-v15 \
--pretrained_model_type custom/auto \
--gpu_gpus_per_worker 4 \
--num_workers 1 \
--model llama2_chat
然后您可以与模型聊天:
byzerllm query --model llama2_chat --query "你好"
您可以像这样取消部署模型:
byzerllm undeploy --model llama2_chat
如果需要指定 Ray 地址,请使用 --ray_address。
以下是另一个展示如何部署 SaaS 模型的示例:
byzerllm deploy --pretrained_model_type saas/qianwen \
--cpus_per_worker 0.01 \
--gpus_per_worker 0 \
--num_workers 1 \
--infer_params saas.api_key=xxxxx saas.model=qwen-max \
--model qianwen_short_chat
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
llm = ByzerLLM()
llm.setup_gpus_per_worker(4).setup_num_workers(1)
llm.setup_infer_backend(InferBackend.transformers)
llm.deploy(model_path="/home/byzerllm/models/openbuddy-llama2-13b64k-v15",
pretrained_model_type="custom/llama2",
udf_name="llama2_chat", infer_params={})
llm_client = ByzerLLM()
llm_client.setup_template("llama2_chat", "auto")
v = llm.chat_oai(model="llama2_chat", conversations=[{
"role": "user",
"content": "hello",
}])
print(v[0].output)
以上代码将部署一个 llama2 模型,然后使用该模型推断输入文本。如果使用 transformers 作为推理后端,您需要手动指定 pretrained_model_type,因为 transformers 后端无法自动检测模型类型。
Byzer-LLM 也支持以相同的方式 deploy SaaS 模型。此功能为开源模型和 SaaS 模型提供了统一的接口。以下代码将部署一个 Azure OpenAI 模型,然后使用该模型推断输入文本。
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
llm = ByzerLLM()
llm.setup_gpus_per_worker(0).setup_num_workers(10)
llm.setup_infer_backend(InferBackend.transformers)
llm.deploy(pretrained_model_type="saas/azure_openai",
udf_name="azure_openai",
infer_params={
"saas.api_type": "azure",
"saas.api_key": "xxx",
"saas.api_base": "xxx",
"saas.api_version": "2023-07-01-preview",
"saas.deployment_id": "xxxxxx"
})
llm_client = ByzerLLM()
llm_client.setup_template("azure_openai", "auto")
v = llm.chat_oai(model="azure_openai", conversations=[{
"role": "user",
"content": "hello",
}])
print(v[0].output)
注意,SaaS 模型不需要 GPU,因此我们将 setup_gpus_per_worker 设置为 0,您可以使用 setup_num_workers 控制最大并发数。然而,SaaS 模型本身有最大并发限制,setup_num_workers 仅控制 Byzer-LLM 接受的并发上限。
推荐的方法是在目标机器(例如您的 Web 服务器)上启动一个空的 Ray 工作节点:
ray start --address="xxxxx:6379" --num-gpus=0 --num-cpus=0
然后您可以从 Ray 集群外部连接模型:
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
## 通过我们之前启动的空工作节点连接到 Ray 集群
## 这段代码应该在您的程序中只运行一次
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
## 创建一个新的 ByzerLLM 实例
llm_client = ByzerLLM()
llm_client.setup_template("llama2_chat", "auto")
v = llm.chat_oai(model="llama2_chat", conversations=[{
"role": "user",
"content": "hello",
}])
print(v[0].output)
或者
from dataclasses import asdict
import json
import base64
import byzerllm
from byzerllm.utils.client.types import Templates
byzerllm.connect_cluster(address="ray://CLUSTER_IP:10001")
model_name = "zephyr_7b_chat"
llm = byzerllm.ByzerLLM()
llm.setup_template(model=model_name, template=Templates.default())
llm.setup_default_model_name(model_name)
t = llm.chat_oai(conversations=[{
"role": "user",
"content": "你好"
}])
print(t[0].output)
以下是部署 BGE 嵌入模型的示例:
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
llm = ByzerLLM()
llm.setup_gpus_per_worker(0.4).setup_num_workers(2).setup_infer_backend(InferBackend.Transformers)
llm.deploy(
model_path="/home/byzerllm/models/bge-large-zh",
pretrained_model_type="custom/bge",
udf_name="emb",
infer_params={}
)
然后您可以将任何文本转换为向量:
t = llm.emb("emb", LLMRequest(instruction="wow"))
t[0].output
# output: [-0.005588463973253965,
# -0.01747054047882557,
# -0.040633779019117355,
# ...
# -0.010880181565880775,
# -0.01713103987276554,
# 0.017675869166851044,
# -0.010260719805955887,
# ...]
Byzer-LLM 也支持 SaaS 嵌入模型。以下代码将部署一个百川嵌入模型,然后使用该模型推断输入文本。
import os
os.environ["RAY_DEDUP_LOGS"] = "0"
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
llm = ByzerLLM(verbose=True)
llm.setup_num_workers(1).setup_gpus_per_worker(0)
chat_name = "baichuan_emb"
if llm.is_model_exist(chat_name):
llm.undeploy(udf_name=chat_name)
llm.deploy(model_path="",
pretrained_model_type="saas/baichuan",
udf_name=chat_name,
infer_params={
"saas.api_key": "",
"saas.model": "Baichuan-Text-Embedding"
})
llm.setup_default_emb_model_name(chat_name)
v = llm.emb(None, LLMRequest(instruction="你好"))
print(v.output)
如果您需要使用嵌入重排序模型,可以参考以下用法:
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
llm = ByzerLLM()
llm.setup_gpus_per_worker(0.4).setup_num_workers(2).setup_infer_backend(InferBackend.Transformers)
llm.deploy(
model_path="/Users/wanghan/data/bge-reranker-base",
pretrained_model_type="custom/bge_rerank",
udf_name="emb_rerank",
infer_params={}
)
llm.setup_default_emb_model_name("emb_rerank")
然后您可以通过向重排序器输入问题和段落来获得相关性分数:
sentence_pairs_01 = ['query', 'passage']
t1 = llm.emb_rerank(sentence_pairs=sentence_pairs_01)
print(t1[0].output)
# output [['query', 'passage'], 0.4474925994873047]
sentence_pairs_02 = [['what is panda?', 'hi'], ['what is panda?', 'The giant panda (Ailuropoda melanoleuca), sometimes called a panda bear or simply panda, is a bear species endemic to China.']]
t2 = llm.emb_rerank(sentence_pairs=sentence_pairs_02)
print(t2[0].output)
# output [[['what is panda?', 'The giant panda (Ailuropoda melanoleuca), sometimes called a panda bear or simply panda, is a bear species endemic to China.'], 6.1821160316467285], [['what is panda?', 'hi'], -8.154398918151855]]
如果后端是 InferBackend.transformers,以下是 baichuan2 的示例:
llm.setup_gpus_per_worker(2).setup_num_workers(1).setup_infer_backend(InferBackend.Transformers)
llm.deploy(
model_path=model_location,
pretrained_model_type="custom/baichuan2",
udf_name="baichuan2_13_chat",
infer_params={"quatization": "4"}
)
可用的 quatization 值:
当设置为 true 时,将选择 int4。
如果后端是 InferBackend.VLLM,以下是 Yi 的示例:
如果您需要部署带有量化的模型,您可以像以下代码一样设置 infer_params:
llm.setup_gpus_per_worker(1).setup_num_workers(1).setup_infer_backend(InferBackend.VLLM)
llm.deploy(
model_path="/home/winubuntu/models/Yi-6B-Chat-4bits",
pretrained_model_type="custom/auto",
udf_name="chat",
infer_params={"backend.quantization": "AWQ"}
)
参数 backend.quantization 可以是 GPTQ/AWQ。
支持的pretrained_model_type(开源模型)包括:
支持的 pretrained_model_type(SaaS 模型)包括:
请注意,源自 llama/llama2/startcode 的衍生模型也受到支持。例如,您可以使用 llama 来加载 vicuna 模型。
Byzer-llm 也支持 vLLM 作为推理后端。以下代码将部署一个 vLLM 模型,然后使用该模型推断输入文本。
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
llm = ByzerLLM()
llm.setup_gpus_per_worker(2)
llm.setup_num_workers(1)
llm.setup_infer_backend(InferBackend.VLLM)
llm.deploy(
model_path="/home/byzerllm/models/openbuddy-zephyr-7b-v14.1",
pretrained_model_type="custom/auto",
udf_name="zephyr_chat",
infer_params={}
)
v = llm.chat_oai(model="zephyr_chat", conversations=[{
"role": "user",
"content": "hello",
}])
print(v[0].output)
vLLM 和 transformers 后端之间有一些细微差别:
pretrained_model_type 固定为 custom/auto,因为 vLLM 会自动检测模型类型。setup_infer_backend 指定 InferBackend.VLLM 作为推理后端。如果您使用的 vLLM 版本 > 0.2.7 并遇到以下错误:
Error Type: TASK_EXECUTION_EXCEPTION
self._call_impl(*args, **kwargs)
File "/home/byzerllm/miniconda3/envs/byzerllm-dev2/lib/python3.10/site-packages/torch/nn/modules/module.py", line 1527, in _call_impl
return forward_call(*args, **kwargs)
File "/home/byzerllm/miniconda3/envs/byzerllm-dev2/lib/python3.10/site-packages/vllm/model_executor/layers/vocab_parallel_embedding.py", line 96, in forward
input_mask = ((input_ < self.vocab_start_index) |
TypeError: '<' not supported between instances of 'TensorMetadata' and 'int'
您可以打开 Ray 仪表盘,找到 RayVLLMWorker Actor,进入并检查任务选项卡,点击错误任务以获取上述错误消息。
解决方案是修改 vLLM pip 包中的 $CONDA_ENV/site-packages/vllm/model_executor/parallel_utils/communication_op.py:
将以下代码:
TensorMetadata = namedtuple("TensorMetadata", ["dtype", "size"])
替换为:
class TensorMetadata:
def __init__(self, dtype, size):
self.dtype = dtype
self.size = size
Byzer-llm 也支持 DeepSpeed 作为推理后端。以下代码将部署一个 DeepSpeed 模型,然后使用该模型推断输入文本。
import ray
from byzerllm.utils.client import ByzerLLM, LLMRequest, InferBackend
ray.init(address="auto", namespace="default", ignore_reinit_error=True)
llm = ByzerLLM()
llm.setup_gpus_per_worker(4)
llm.setup_num_workers(1)
llm.setup_infer_backend(InferBackend.DeepSpeed)
llm.deploy(
model_path="/home/byzerllm/models/openbuddy-llama-13b-v5-fp16",
pretrained_model_type="custom/auto",
udf_name="llama_chat",
infer_params={}
)
llm.chat("llama_chat", LLMRequest(instruction="hello world"))[0].output
上述代码与 vLLM 的代码完全相同,只是 InferBackend 为 InferBackend.DeepSpeed。
byzerllm deploy --pretrained_model_type custom/auto \
--infer_backend llama_cpp \
--cpus_per_worker 0.001 \
--gpus_per_worker 0 \
--num_workers 1 \
--worker_concurrency 10 \
--infer_params verbose=true \
--model_path /Users/allwefantasy/Downloads/Meta-Llama-3-8B-Instruct-Q4_K_M.gguf \
--model llama_3_chat
您可以在 --infer_params 中设置 num_gpu_layers=-1 并将 gpu_gpus_per_worker=x 设置为将整个模型加载到 GPU;或设置 num_gpu_layers=0 和 gpu_gpus_per_worker=0 以禁用 GPU 加速。num_gpu_layers 也接受一个正数来指定要使用的 GPU 层数。
您可以使用以下代码启动一个 ByzerLLm OpenAI 兼容的 RESTful API 服务器:
byzerllm serve --ray_address auto --port 8000
默认情况下,服务器监听 8000 端口,您可以使用以下代码测试 API:
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="xxxx"
)
chat_completion = client.chat.completions.create(
model="wenxin_chat",
messages=[{"role": "user", "content": "写一个排序算法"}],
stream=False
)
print(chat_completion.choices[0].message.content)
流式聊天:
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="simple"
)
chat_completion = client.chat.completions.create(
model="wenxin_chat",
messages=[{"role": "user", "content": "写一个排序算法"}],
stream=True
)
for chunk in chat_completion:
print(chunk.choices[0].delta.content or "", end="")
以下是一个基于 QWen 72B 的函数调用简单示例。
部署模型:
import ray
ray.init(address="auto", namespace="default")
llm = ByzerLLM()
model_location = "/home/byzerllm/models/Qwen-72B-Chat"
llm.setup_gpus_per_worker(8).setup_num_workers(1).setup_infer_backend(InferBackend.VLLM)
llm.deploy(
model_path=model_location,
pretrained_model_type="custom/auto",
udf_name=chat_model_name,
infer_params={}
)
llm.setup_default_model_name("chat")
# 从 0.1.24 版本开始
# llm.setup_auto("chat")
meta = llm.get_meta()
llm.setup_max_model_length("chat", meta.get("max_model_len", 32000))
llm.setup_template("chat", Templates.qwen())
尝试创建一些 Python 函数:
from typing import List, Dict, Any, Annotated
import pydantic
import datetime
from dateutil.relativedelta import relativedelta
def compute_date_range(count: Annotated[int, "时间跨度,数值类型"],
unit: Annotated[str, "时间单位,字符串类型", {"enum": ["day", "week", "month", "year"]}]) -> List[str]:
'''
计算日期范围
Args:
count: 时间跨度,数值类型
unit: 时间单位,字符串类型,可选值为 day,week,month,year
'''
now = datetime.datetime.now()
now_str = now.strftime("%Y-%m-%d %H:%M:%S")
if unit == "day":
return [(now - relativedelta(days=count)).strftime("%Y-%m-%d %H:%M:%S"), now_str]
elif unit == "week":
return [(now - relativedelta(weeks=count)).strftime("%Y-%m-%d %H:%M:%S"), now_str]
elif unit == "month":
return [(now - relativedelta(months=count)).strftime("%Y-%m-%d %H:%M:%S"), now_str]
elif unit == "year":
return [(now - relativedelta(years=count)).strftime("%Y-%m-%d %H:%M:%S"), now_str]
return ["", ""]
def compute_now() -> str:
'''
计算当前时间
'''
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
这里我们提供了两个函数:
1. compute_date_range: 根据数量和时间单位计算日期范围
2. compute_now: 获取当前日期
我们将使用模型根据用户的问题调用这些工具。
t = llm.chat_oai([{
"content": '计算当前时间',
"role": "user"
}], tools=[compute_date_range, compute_now], execute_tool=True)
t[0].values
# output: ['2023-12-18 17:30:49']
t = llm.chat_oai([{
"content": '最近三个月趋势',
"role": "user"
}], tools=[compute_date_range, compute_now], execute_tool=True)
t[0].values
# output: [['2023-09-18 17:31:21', '2023-12-18 17:31:21']]
t = llm.chat_oai([{
"content": '最近三天',
"role": "user"
}], tools=[compute_date_range, compute_now], execute_tool=True)
t[0].values
# output: [['2023-12-15 17:23:38', '2023-12-18 17:23:38']]
t = llm.chat_oai([{
"content": '你吃饭了么?',
"role": "user"
}], tools=[compute_date_range, compute_now], execute_tool=True)
if t[0].values:
print(t[0].values[0])
else:
print(t[0].response.output)
# output: '您好,我是一个人工智能语言模型,暂时无法吃饭。'
您可以在 from byzerllm.utils import function_calling_format 中查看默认的提示模板函数。如果默认函数对模型效果不佳,您可以设置自定义函数:
def custom_function_calling_format(prompt: str, tools: List[Callable], tool_choice: Callable) -> str:
pass
llm.setup_function_calling_format_func("chat", custom_function_calling_format)
当您与 LLM 聊天时,可以指定响应类。
import pydantic
class Story(pydantic.BaseModel):
'''
故事
'''
title: str = pydantic.Field(description="故事的标题")
body: str = pydantic.Field(description="故事主体")
t = llm.chat_oai([
{
"content": f'请给我讲个故事,分成两个部分,一个标题,一个故事主体',
"role": "user"
},
], response_class=Story)
t[0].value
# output: Story(title='勇敢的小兔子', body='在一个美丽的森林里,住着一只可爱的小兔子...')
上面的代码将要求 LLM 直接生成 Story 类。然而,有时我们希望 LLM 先生成文本,然后再从文本中提取结构,您可以设置 response_after_chat=True 来启用此行为。但是,这会带来一些性能损失(额外的推理)。
t = llm.chat_oai([
{
"content": f'请给我讲个故事,分成两个部分,一个标题,一个故事主体',
"role": "user"
},
], response_class=Story, response_after_chat=True)
t[0].value
# output: Story(title='月光下的守护者', body='在一个遥远的古...
您可以在 from byzerllm.utils import response_class_format, response_class_format_after_chat 中查看默认的提示模板函数。如果默认函数对模型效果不佳,您可以设置自定义函数:
def custom_response_class_format(prompt: str, cls: pydantic.BaseModel) -> str:
pass
llm.setup_response_class_format_func("chat", custom_response_class_format)
Byzer-llm 也支持函数实现。您可以定义一个空函数,并结合函数内的文档/用户的问题来引导 LLM 实现该函数。
以下是一个简单示例:
from byzerllm.utils.client import code_utils, message_utils
from typing import List, Union, Optional
import pydantic
class Time(pydantic.BaseModel):
time: str = pydantic.Field(..., description="时间,时间格式为 yyyy-MM-dd")
@llm.impl()
def calculate_current_time() -> Time:
'''
计算当前时间
'''
pass
calculate_current_time()
# output: Time(time='2024-01-28')
默认情况下,函数实现会被缓存。
start = time.monotonic()
calculate_current_time()
print(f"first time cost: {time.monotonic() - start}")
start = time.monotonic()
calculate_current_time()
print(f"second time cost: {time.monotonic() - start}")
# output:
# first time cost: 6.067266260739416
# second time cost: 4.347506910562515e-05
您可以使用 llm.clear_impl_cache() 清除缓存。
以下是带参数的函数实现示例:
from byzerllm.utils.client import code_utils, message_utils
from typing import List, Union, Optional, Annotated
import pydantic
from datetime import datetime
class Time(pydantic.BaseModel):
time: str = pydantic.Field(..., description="时间,时间格式为 yyyy-MM-dd")
@llm.impl()
def add_one_day(current_day: Annotated[datetime, "当前日期,类型是datatime.datetime"]) -> Time:
'''
给传入的日期加一天,得到明天的时间
'''
pass
add_one_day(datetime.now())
# output: Time(time='2024-01-29')
带指令的示例:
from byzerllm.utils.client import code_utils, message_utils
from typing import List, Union, Optional
import pydantic
class TimeRange(pydantic.BaseModel):
'''
时间区间
格式需要如下: yyyy-MM-dd
'''
start: str = pydantic.Field(..., description="开始时间.时间格式为 yyyy-MM-dd")
end: str = pydantic.Field(..., description="截止时间.时间格式为 yyyy-MM-dd")
@llm.impl(instruction="去年三月到七月")
def calculate_time_range() -> TimeRange:
'''
计算时间区间,时间格式为 yyyy-MM-dd.
'''
pass
calculate_time_range()
# output: TimeRange(start='2023-03-01', end='2023-07-31')
如果您想用用户的问题替换指令,可以使用以下代码:
from byzerllm.utils.client import code_utils, message_utils
from typing import List, Union, Optional
import pydantic
class TimeRange(pydantic.BaseModel):
'''
时间区间
格式需要如下: yyyy-MM-dd
'''
start: str = pydantic.Field(..., description="开始时间.时间格式为 yyyy-MM-dd")
end: str = pydantic.Field(..., description="截止时间.时间格式为 yyyy-MM-dd")
def calculate_time_range() -> TimeRange:
'''
计算时间区间,时间格式为 yyyy-MM-dd.
'''
pass
llm.impl(instruction="去年三月到七月")(calculate_time_range)()
您可以使用 verbose=True 获取有关函数实现的更多信息:
@llm.impl()
def add_one_day(current_day: Annotated[datetime, "当前日期,类型是datatime.datetime"]) -> Time:
'''
给传入的日期加一天,得到明天的时间
'''
pass
您也可以使用基本的 chat_oai 函数来实现该函数:
```python
class TimeRange(pydantic.BaseModel):
'''
时间区间
格式需要如下: yyyy-MM-dd
'''
start: str = pydantic.Field(..., description="开始时间.时间格式为 yyyy-MM-dd