一、引言
之前在我的第5篇热榜第一🏆文章【机器学习】Qwen-VL:基于FastAPI私有化部署你的第一个AI多模态大模型中对Qwen-VL如何基于FastAPI封装私有化接口进行了讲述,评论区有人问到如何基于GLM-4V进行私有化接口部署。那今天我们就基于FastAPI,讲述如何封装GLM-4V私有化接口。
OpenAI兼容的API接口(OpenAI-API-compatible):是个很重要的接口规范,由大模型王者OpenAI制定,简单来说就是接口名、传参方式、参数格式统一仿照OpenAI的接口方式,这样可以降低使用接口的学习与改造,做到多厂商、多模型兼容。
本文基于FastAPI简单实现了一个遵照OpenAI兼容接口的Qwen-VL服务端和客户端接口,用于交流学习,如有问题与建议欢迎大家留言指正!
GLM-4是清华智谱AI的第4代产品,重点强调的是ALL Tools工具调用能力,并于2024年6月5日开源了GLM-4-9B版本,包括GLM-4-9B、GLM-4-9B-Chat、GLM-4-9B-Chat-1M以及对应支持1120x1120像素的多模态模型GLM-4V-9B。今天重点对GLM-4V-9B进行介绍,并给出基于FastAPI私有化部署方式。
该模型采用了与CogVLM2相似的架构设计,能够处理高达1120 x 1120分辨率的输入,并通过降采样技术有效减少了token的开销。为了减小部署与计算开销,GLM-4V-9B没有引入额外的视觉专家模块,采用了直接混合文本和图片数据的方式进行训练,在保持文本性能的同时提升多模态能力,模型架构如图:
通过之前的文章中讲述的使用transformers查看model结构的方法,查看模型结构如下,包含以下几个部分
ChatGLMForConditionalGeneration(
(transformer): ChatGLMModel(
(embedding): Embedding(
(word_embeddings): Embedding(151552, 4096)
)
(rotary_pos_emb): RotaryEmbedding()
(encoder): GLMTransformer(
(layers): ModuleList(
(0-39): 40 x GLMBlock(
(input_layernorm): RMSNorm()
(self_attention): SelfAttention(
(query_key_value): Linear(in_features=4096, out_features=4608, bias=True)
(core_attention): CoreAttention(
(attention_dropout): Dropout(p=0.0, inplace=False)
)
(dense): Linear(in_features=4096, out_features=4096, bias=False)
)
(post_attention_layernorm): RMSNorm()
(mlp): MLP(
(dense_h_to_4h): Linear(in_features=4096, out_features=27392, bias=False)
(dense_4h_to_h): Linear(in_features=13696, out_features=4096, bias=False)
)
)
)
(final_layernorm): RMSNorm()
)
(output_layer): Linear(in_features=4096, out_features=151552, bias=False)
(vision): EVA2CLIPModel(
(patch_embedding): PatchEmbedding(
(proj): Conv2d(3, 1792, kernel_size=(14, 14), stride=(14, 14))
(position_embedding): Embedding(6401, 1792)
)
(transformer): Transformer(
(layers): ModuleList(
(0-62): 63 x TransformerLayer(
(input_layernorm): LayerNorm((1792,), eps=1e-06, elementwise_affine=True)
(attention): Attention(
(query_key_value): Linear(in_features=1792, out_features=5376, bias=True)
(dense): Linear(in_features=1792, out_features=1792, bias=True)
(output_dropout): Dropout(p=0.0, inplace=False)
)
(mlp): MLP(
(activation_fn): GELUActivation()
(fc1): Linear(in_features=1792, out_features=15360, bias=True)
(fc2): Linear(in_features=15360, out_features=1792, bias=True)
)
(post_attention_layernorm): LayerNorm((1792,), eps=1e-06, elementwise_affine=True)
)
)
)
(linear_proj): GLU(
(linear_proj): Linear(in_features=4096, out_features=4096, bias=False)
(norm1): LayerNorm((4096,), eps=1e-05, elementwise_affine=True)
(act1): GELU(approximate='none')
(dense_h_to_4h): Linear(in_features=4096, out_features=13696, bias=False)
(gate_proj): Linear(in_features=4096, out_features=13696, bias=False)
(dense_4h_to_h): Linear(in_features=13696, out_features=4096, bias=False)
)
(conv): Conv2d(1792, 4096, kernel_size=(2, 2), stride=(2, 2))
)
)
)
搭建1个FastAPI服务依赖fastapi、pydantic、uvicorn三个库:
FastAPI是一个现代、快速(高性能)的Web框架,用于构建API,用Python编写。它基于标准的Python类型提示,提供自动的交互式文档和数据验证。
代码示例:
# 导入FastAPI模块
from fastapi import FastAPI
# 创建一个FastAPI实例
app = FastAPI()
# 定义一个路径操作函数
@app.get("/")
async def root():
# 返回一个JSON响应
return {"message": "Hello World"}
# 运行应用
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
这段代码创建了一个简单的FastAPI应用,当访问根路径/
时,会返回一个包含"Hello World"的消息。可以使用uvicorn
运行这个应用,它是一个ASGI服务器,FastAPI是基于ASGI构建的
uvicorn
是一个ASGI(Asynchronous Server Gateway Interface)服务器,用于运行现代的异步Python Web应用,如FastAPI。以下是如何使用uvicorn
运行一个FastAPI应用的步骤:
假设你有一个名为main.py
的文件,其中包含你的FastAPI应用:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
可以使用以下命令运行你的应用:
uvicorn main:app --reload
这里的main
是你的Python文件名(不包括.py扩展名),app
是你的FastAPI实例的变量名。--reload
标志告诉uvicorn
在代码更改时自动重新加载应用,这对于开发非常有用。
Pydantic
是一个Python库,用于数据验证和设置管理。它被广泛用于FastAPI中,用于定义请求和响应模型,以进行数据验证和解析。
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
tags: list = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
item = Item(**items["foo"])
print(item)
# 输出: Item(name='Foo', description=None, price=50.2, tax=None, tags=[])
from fastapi import FastAPI
from pydantic import BaseModel
import uvicorn
import requests
from io import BytesIO
from modelscope import snapshot_download
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
from PIL import Image
model_dir = snapshot_download('ZhipuAI/glm-4v-9b')
#model_dir = snapshot_download('qwen/Qwen-VL-Chat')
device = "auto"
tokenizer = AutoTokenizer.from_pretrained(model_dir,trust_remote_code=True)
glm4_vl = AutoModelForCausalLM.from_pretrained(model_dir, device_map=device, trust_remote_code=True,torch_dtype=torch.float16).eval()
# 创建FastAPI应用实例
app = FastAPI()
# 定义请求体模型,与OpenAI API兼容
class ChatCompletionRequest(BaseModel):
model: str
messages: list
max_tokens: int = 50
temperature: float = 1.0
# 文本生成函数
def generate_text(model: str, messages: list, max_tokens: int, temperature: float):
query = messages[0]["content"][0]["text"]
image_url = messages[0]["content"][1]["image_url"]["url"]
image_get_response = requests.get(image_url)
image = None
if image_get_response.status_code == 200:
# 将二进制数据转换为Image对象
image = Image.open(BytesIO(image_get_response.content)).convert("RGB")
# 现在你可以使用image对象进行进一步的处理
else:
print("Failed to download image")
print(query,image_url,image)
response, history = glm4_vl.chat(tokenizer,image=image, query=query, history=None,temperature=temperature,max_length=max_tokens)
return response
# 定义路由和处理函数,与OpenAI API兼容
@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest):
# 调用自定义的文本生成函数
response = generate_text(request.model, request.messages, request.max_tokens, request.temperature)
return {"choices": [{"message": {"content": response}}],"model": request.model}
# 启动FastAPI应用
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8888)
使用CUDA_VISIBLE_DEVICES=2 python run_api_glm4vl.py启动,指定卡2运行api服务。
显存占用28.35G(模型尺寸13B,根据我们之前提过很多次的经验,推理模型显存占用=模型尺寸*2=13*2=26G,差不多。Qwen-VL模型占用18.74G,整整多了10G显存)
import requests
import json
# 定义请求的URL
url = "http://0.0.0.0:8888/v1/chat/completions"
# 定义请求体
data = {
"model": "glm-4v",
"messages":[{"role":"user","content":[{"type":"text","text":"这是什么?"},{"type":"image_url","image_url":{"url":"https://img1.baidu.com/it/u=1369931113,3388870256&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1703696400&t=f3028c7a1dca43a080aeb8239f09cc2f"}}]}],
"max_tokens": 1024,
"temperature": 0.5
}
# 将字典转换为JSON格式
headers = {'Content-Type': 'application/json'}
data_json = json.dumps(data)
# 发送POST请求
response = requests.post(url, data=data_json, headers=headers)
# 检查响应状态码
if response.status_code == 200:
# 如果响应成功,打印响应内容
print(response.json())
else:
# 如果响应失败,打印错误信息
print(f"Error: {response.status_code}, {response.text}")
requests:采用requests库进行请求,requests
是一个在Python中用于发送HTTP请求的库。它允许你发送各种类型的HTTP请求,如GET、POST、PUT、DELETE等,以及处理响应。requests
库的一个主要优点是它的易用性和简洁的API。
请求体data定义:完全模仿OpenAI API请求结构,服务端也根据此结构规范处理。
headers请求头:接口请求格式为JSON,采用json.dumps可以将字典型的data转换为json字符串,用于请求时采用json格式传输。更多json用法可以参考之前的文章
将以上客户端代码放入post_api.py中,采用python post_api.py调用服务端接口。
比如传入的图片为:
glm-4v输出为
“这是一张展示海滨风景的图片。图中可以看到一片宁静的海洋,海面上有几块岩石露出水面。天空呈现出深浅不一的蓝色,其中散布着一些白云。在远处,可以看到陆地和大海的交界线,以及一些小岛或陆地突起。整个场景给人一种宁静、宽广的感觉。”
qwen-vl输出为
“这是海面,可以看到远处的海平线和海岸线。天空中飘着美丽的云彩。”
看起来glm-4v的效果要好一些,主要原因:
卷吧!期待Qwen2-VL的诞生!
本文首先在引言中强调了一下OpenAI兼容API的重要性,希望引起读者重视,其次介绍了GLM-4V的原理与模型结构,最后简要讲了下FastAPI以及搭配组件,并基于FastAPI封装了OpenAI兼容API的GLM-4V大模型服务端接口,并给出了客户端实现。本文内容在工作中非常实用,希望大家能有所收获并与我交流。