搜索
查看: 70|回复: 0

[网站] ChatGPT平替- ChatGLM多用户并行访问部署过程

[复制链接]
发表于 2023-5-8 22:05:47 | 显示全部楼层 |阅读模式
这篇文章主要介绍了ChatGPT平替- ChatGLM多用户并行访问部署,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下: i" q4 c- _! ~/ f  R+ D  N
/ d5 m' i$ f  i$ w2 F1 p; p
* c( s$ Q0 O' ~8 \2 {/ i6 _8 i
+
目录9 r. O; T, F# w' M5 j: f

        ChatGLM对话模型基本环境配置和部署请参考上一篇博文《ChatGPT平替-ChatGLM环境搭建与部署运行》,地址为“https://www.jb51.net/article/283295.htm”。但是,默认部署程序仅支持单用户访问,多用户则需要排队访问。测试过相关的几个Github多用户工程,但是其中一些仍然不满足要求。本节将系统介绍如何实现多用户同时访问ChatGLM的部署接口,包括http、websocket(流式输出,stream)和web页面等方式,主要目录如下所示。

        (1)api.py http多用户并行

        (2)api.py websocket多用户并行(流式输出,stream)

        (3)web_demo.py多用户并行

        本节所涉及程序可根据文中描述自行编写或替换,也可在“https://download.csdn.net/download/suiyingy/87742178”进行下载,文中所有程序均在其中。

2 J6 [' b$ Q$ B3 @3 q) Z- g
1 api.py http多用户并行
! C$ n) d! o: H- T0 b1.1 fastapi并行

        ChatGLM-6B工程的api.py是基于fastapi编写的http post服务程序。具体介绍及调用方式请参考上一篇博文。运行程序后,当多用户同时调用该http接口时,程序需要排队执行,即当前用户指令需要等待上一用户获取结果完成之后才进行执行。

        实现接口并行的关键在于去除create_item的async,相应程序如下所示,该函数段由RdFast小程序自动生成。我们可以根据如下描述编写程序,也可前往“https://download.csdn.net/download/suiyingy/87742178”进行下载,对应下载后的api_http_one_worker.py文件。

  1. #该函数段由RdFast小程序自动生成3 p% ^- V2 Y& f5 [! W
  2. from pydantic import BaseModel& X) v) d5 a7 A9 q: C+ j) z$ O
  3. class User(BaseModel):! v. l1 v% ^- O9 |2 _- J
  4.     prompt: str: L  r* u. W& P1 w! y
  5.     history: list
    8 J- f+ q. }6 T$ u/ [
  6. @app.post("/http/noasync")
    ( T& W$ \9 l, s' Z. i9 w% c  s
  7. def create_item(request: User):
    * v3 ^+ ]! P  a( q; A
  8.     global model, tokenizer
    6 z" p+ w( \0 b- r& I
  9.     json_post_raw = request.dict()
    ' {! R8 }3 r  r/ [
  10.     json_post = json.dumps(json_post_raw)$ |6 |7 D# @6 t( P, a) x& e
  11.     json_post_list = json.loads(json_post)
    8 s: ^1 m2 y9 t" v! a/ e
  12.     prompt = json_post_list.get('prompt')
    4 j0 n" Y) m$ i: Q# ]4 y4 _. m
  13.     history = json_post_list.get('history')
    9 K( k% @1 I8 i' E4 L& I1 ^4 L
  14.     max_length = json_post_list.get('max_length')
    6 u+ g" D7 {5 N
  15.     top_p = json_post_list.get('top_p')" B9 \5 f. D, d
  16.     temperature = json_post_list.get('temperature')$ V- T& P1 N* z
  17.     response, history = model.chat(tokenizer,
    4 c" h1 ^# O4 O7 R5 |: y
  18.                                    prompt,
    & P1 V" J6 n/ j+ F/ A0 F8 y
  19.                                    history=history,
      `, Q  ^, g0 m
  20.                                    max_length=max_length if max_length else 2048,* z1 q3 t2 \% {0 p% R9 N6 J3 D
  21.                                    top_p=top_p if top_p else 0.7,
    3 U: q- p  l: Y' w
  22.                                    temperature=temperature if temperature else 0.95)
    , e- o! t" O0 ]0 B! ?7 z0 S) W
  23.     now = datetime.datetime.now()
    2 S) g0 b& h9 |" `( ^
  24.     time = now.strftime("%Y-%m-%d %H:%M:%S")* E3 u4 }# f& M
  25.     answer = {6 B: l& Q9 H4 {) t3 [1 f7 P
  26.         "response": response,
    # o. n* y. s7 m+ Y. m: E" w, h( a" y
  27.         "history": history,
    7 _  {7 _$ A. S1 t7 V! z
  28.         "status": 200,3 c7 K) R1 D9 r( {0 ^+ H
  29.         "time": time1 Z2 r5 z5 R) H$ C1 ]! G" u
  30.     }
    ( X+ P/ R5 i" V* O/ |0 d/ `
  31.     log = "[" + time + "] " + '", prompt:"' + prompt + '", response:"' + repr(response) + '"') o8 Z8 @+ Q/ S& D) v5 g1 N
  32.     print(log)+ G/ m2 c) @  H! H) k4 J
  33.     torch_gc()
复制代码

/ R, T: R( @: |7 A4 b& F

我们通过输入“你好”来进行测试,并模拟三个用户进行同时访问。修改之前三个用户获取返回结果所需时间分别为2.08s、4.05s和6.02s,而修改之后获取结果所需时间分别为6.73s、6.78和6.88s。修改前程序顺序执行,最后一个用户获取结果所需时间为6.02s。修改后程序是并行执行的,三个用户几乎同时获得访问结果。

        由于模型参数在多个线程之间是共享的,且多线程状态下程序会交替运行,因此多线程状态下获取结果的总时间反而增加了。因此,这种修改并不适合http模式,比较适合于websocket流式输出。模拟多用户调用的测试程序如下所示。

  1. import json
    - m" }6 M+ `/ f
  2. import time
    0 S  ^# a+ C8 n5 G5 {
  3. import requests5 R+ z/ }( |3 z/ X0 s# N; u/ A
  4. import threading
    * {+ `( g' x0 T7 D) H) t; k
  5. def get_ans(id, prompt):# Y! m% s8 X$ q: \; I% V
  6.     t0 = time.time()
    8 Z: n' a( U8 i9 y2 u
  7.     headers = {'Content-Type': 'application/json'}
    / N+ j. L8 b) G' k* @
  8.     url = 'http://IP:Port/http/noasync'" z6 M% W0 i7 @  y/ C! I1 t  f
  9.     data = {'prompt': prompt, 'history': []}
    ' _/ u- q' ~# O
  10.     data = json.dumps(data)
    0 T  W/ e0 {3 k( t2 S+ Y& T
  11.     reponse = requests.post(url=url, data=data, headers=headers): C( ]$ {# m0 i+ m6 s: f
  12.     print(id, '耗时为:', round(time.time() - t0, 2), 's,结果为:', reponse .text)2 O8 {/ d1 z* R4 b) j- ]
  13. if __name__ == '__main__':2 I# ~) L& j) I; b
  14.     t1 = threading.Thread(target=get_ans, args=('线程1', '你好')); ]  A0 c' v* i. v
  15.     t2 = threading.Thread(target=get_ans, args=('线程2', '你好'))
    ; }, {7 A5 M( D+ ?7 O3 v. L0 l( L
  16.     t3 = threading.Thread(target=get_ans, args=('线程3', '你好'))
    6 @6 Y* M0 p9 s  I! P3 n; _
  17.     t1.start()7 g9 P( |$ {. N
  18.     t2.start()6 S1 H: e; ~) r( r( ~5 I
  19.     t3.start()
复制代码

" D, m; a# ]+ L: H1.2 fastapi多线程并行

        fastapi多线程通过启动参数workers来进行控制。如果程序直接将api.py中的workers设置为大于1的数值,即“uvicorn.run(app, host='0.0.0.0', port=8000, workers=2)”,那么会报错“WARNING:  You must pass the application as an import string to enable 'reload' or 'workers'.”,报错后程序退出并停止执行。正确修改为“uvicorn.run('api:app', host='0.0.0.0', port=8000, workers=2)”,其中api表示当前python文件名称。

        这相当于各个线程分别运行api.py文件的app,运行次数由workers决定。从这里可以看到,程序并不能识别’__main__’函数中的变量,因此要把模型定义放在全局位置,如下所示,否则会报错误“NameError: name 'model' is not defined”。

, |6 L  }- D5 v$ R1 i: \5 j5 i  z- b
: Y2 p3 J' [' g( E; P0 W/ q
  1. app = FastAPI()
    # p% Z3 |* d- E, ?* u% r1 ?2 d
  2. tokenizer = AutoTokenizer.from_pretrained("chatglm-6b-int4-qe", trust_remote_code=True)$ ?% E5 S3 L5 f% [" {# {) J
  3. model = AutoModel.from_pretrained("chatglm-6b-int4-qe", trust_remote_code=True).half().cuda()# k9 `7 [3 v; ]
  4. model.eval()
复制代码
/ F* J" _1 g3 P- e1 b0 ^

. A$ O3 w/ Y8 i0 ?( b( T8 K

   在多线程情况下,类似上一节,无论是否使用asnc,程序运行时间基本一致。但是,基本显存会随着线程数量增多而增加。实际运行时,单个线程应需要10GB左右显存,包括模型加载和推理。各个线程数量下的模型加载显存如下所示。而1.1节的方法中单个线程仅需要3939MB。

Workers=1, 7329MB# E/ p5 ?+ i; e% u) D( N6 V
Workers=2, 17875MB* L& Z) w% ~+ K6 u: r7 g0 h8 {
Workers=3, 24843MB
# X8 T- f+ j$ V5 @Workers=4, 31811MB0 ]( w6 M( ]5 T' Z  N) g
Workers=5, 38779MB

        我们可以根据上述描述编写程序,也可前往“https://download.csdn.net/download/suiyingy/87742178”进行下载,对应下载后的api_http_three_worker.py文件。


. g, V5 r5 [5 R2 m/ X0 o1 q1 f2 api.py websocket多用户并行

        Fastapi websocket的创建方法如下所示,该示例程序来源于RdFast小程序自动生成。

  1. #该示例程序来源于RdFast小程序自动生成。6 {& L/ R, Y# y1 g4 Z& c8 b9 W
  2. from fastapi import FastAPI, WebSocket, WebSocketDisconnect5 p, W6 O, y$ m
  3. app = FastAPI()
    0 A& u/ P! v' u
  4. connected_websockets = {}% Z8 y+ ^0 f4 z# \  W; S
  5. @app.websocket("/ws/{client_id}")
    % U* S3 u/ \6 J0 q' ^
  6. async def websocket_endpoint(websocket: WebSocket, client_id: str):
    0 G  F# D1 y1 I, s8 P/ D( [$ @* i
  7.     await websocket.accept()
    & ?; Y3 G) l* z  X9 I) Z) q+ U
  8.     connected_websockets[client_id] = websocket& F- M* b% ]+ ]2 P) e) i% ^" w
  9.     try:, q' A/ l) ]8 M
  10.         while True:
    5 n( {; R* m2 v) o8 X
  11.             # 接收客户端websocket发送过来的信息6 J9 L, u  D/ o' a8 N2 ]6 R
  12.             data = await websocket.receive_text()4 G1 Y! N6 A3 u
  13.             # 将接收到的信息通过所有已连接的websocket广播给其他客户端' Z! R: u0 x% g0 U1 N
  14.             for ws in connected_websockets.values():7 [+ H" l" Q5 Y1 G
  15.                 await ws.send_text(f"Client {client_id}: {data}")
    : g1 T0 j/ L( o. ?# G5 R
  16.     except WebSocketDisconnect:
    5 U" n" {8 w# k* m! Z5 O$ C
  17.         # 连接断开时,从已连接的客户端中移除# |# N5 a1 f" U: q! u
  18.         del connected_websockets[client_id]
复制代码
" K0 N- R1 q6 P+ G& f: d

将上述程序与ChatGLM结合即可实现ChatGLM的websocket api接口。示例程序如下所示:

  1. @app.websocket("/ws/{client_id}")
    , P+ E) ~& U7 {, P8 j* s+ s2 x  b
  2. async def websocket_endpoint(ws: WebSocket, client_id: str):
    % h6 g' ]) A1 h6 ~1 ~. v
  3.     await ws.accept()* ^$ ^' D! k: ]3 t1 {- ]; }4 Z
  4.     print('已连接')% o8 I% X  p8 ?( A) `% F/ |. {" m
  5.     try:
      q7 A' i4 N, o+ u2 ^  h
  6.         while True:
    8 n+ }8 F) [- o0 c0 ?- t- U$ J
  7.             # 接收客户端websocket发送过来的信息: S$ v$ u0 _% N
  8.             data = await ws.receive_text()
    " C7 u$ m8 T; a, V0 [# _) q
  9.             print('收到消息:', data)
    # i* \# i! s5 i# s& z
  10.             resp0 = '', b2 w1 R* E) k8 j3 f, R4 T
  11.             for response, history in model.stream_chat(tokenizer, data, [], max_length= 2048,top_p= 0.7, temperature= 0.95):: @  f1 b" N+ t" j7 j& p% a
  12.                 print('response:', response)
    ( f" W. T$ B- o" q- Y3 d
  13.                 res = response.replace(resp0, '')" F$ r& U2 b+ G
  14.                 resp0 = response
    4 @* G& |# c9 ?3 i6 d3 e/ v2 N4 Q- s
  15.                 await ws.send_text(res)
    9 @3 Y& [+ \7 z+ m
  16.             await ws.send_text('<rdfast stop>')#自定义结束符
    6 }" l6 B8 I6 t( U6 P4 z
  17.     except WebSocketDisconnect:
    ) `& H" K: \0 \9 I
  18.         print('连接已断开')
复制代码

. d: |  `( }- f3 a9 ?, y

  我们可以根据上述描述编写程序,也可前往“https://download.csdn.net/download/suiyingy/87742178”进行下载,对应下载后的api_http_one_worker.py文件。Websocket测试程序如下所示。

  1. from websocket import create_connection" c/ V$ l- k' l+ i/ M) K% z) m
  2. def connect_node(ques):9 H& E5 E' ~( L/ c6 G8 G
  3.     ans = ''& {/ p! A: R" l2 E, i: S
  4.     url = "ws://IP:Port/ws/2"
    5 U4 c! x. G1 ^' ^5 k8 X; V( Q
  5.     ws = create_connection(url)
    ) X, Y9 z( ?8 I( ]. R7 y* q/ p
  6.     ws.send(ques)
    / l" \8 g2 K! V9 e: }1 @
  7.     while True:
    7 b6 Y8 d6 c0 F* Z& v6 p
  8.         try:/ [/ f. X' G5 }
  9.             recv_text = ws.recv()% V$ P) A  h  q- x# I: d: ~
  10.             print(recv_text)9 @0 V9 ]8 K9 ~2 [
  11.             if '<rdfast stop>' in recv_text:8 z. [# [1 ?+ h
  12.                 print('break')
    . `* o: D9 Y4 t8 X9 M
  13.                 break' Z+ z, l" C8 p9 X+ ^& a
  14.             ans += recv_text( H4 D0 J1 w4 ]0 z, D
  15.         except Exception as e:) M+ L; }5 E- b+ |
  16.             print('except: ', str(e))1 w0 V5 S0 S& r0 U
  17.             recv_text = ws.recv()
    8 L6 ~7 j7 p9 f# _1 s' C
  18.             break
    / `# W6 Q/ f8 |& W/ e
  19.     print(ans)( \' F' x9 V' F( W  s) q0 v
  20.     ws.close()9 M/ l) N- G5 H( M, x! b
  21. connect_node('你好')
复制代码
% \2 G: k. ]3 q" c8 K, `" U

类似http接口,使用async时多用户调用websocket将排队获取结果。此时,程序去除async之后无法获取结果。使用1.2中多线程启动方式则可以实现多用户同时获得结果,程序基本一致,也可参考“https://download.csdn.net/download/suiyingy/87742178”的api_http_three_worker.py。

        另外,不同的python包所支持的工作方式不同。例如,websocket-server支持多用户同时调用websocket接口,安装方式为“pip install websocket-server”。运行程序时可能会出现错误提示“KeyError: 'upgrade'”,但这不影响结果获取。websocket-server ChatGLM相应程序见“https://download.csdn.net/download/suiyingy/87742178”的api_ws.py程序。


/ b4 q- V) q, q6 {* L' A* ~" A. U3 web_demo.py多用户并行

        Web_demo.py默认状态下多用户会排队访问,使用下面命令启动时可同时获取结果。concurrency_count表示最多可同时获取结果的用户数,即并发数。max_size表示排队的数量,即最多允许多少个用户处于排队状态。使用时只需将web_demo.py的最后一行替换成如下启动方式即可。Web_demo2.py采用streamlit实现,默认支持多用户同时访问。

  1. demo.queue(/ g: B9 V4 A8 @, k3 b
  2.     concurrency_count=5,7 T. \% ~6 R7 t  s8 F
  3.     max_size=500).launch(share=False,4 n- H, T+ s% K8 y/ Z" h1 A
  4.                 inbrowser=True,' x0 f3 \$ l  z! @! ^' i. i+ h* O
  5.                 server_name="0.0.0.0",. d( t$ R, ?" u
  6.                 server_port=8000)
复制代码

# c3 W- `+ i% R' C

到此这篇关于ChatGPT平替- ChatGLM多用户并行访问部署的文章就介绍到这了。


' @/ p' R5 Z! D2 t9 ~8 w! ~" k: g
% z) K  J4 p( P2 G

0 a4 _' `- W5 s+ K( A" N
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

热议作品
精华帖子排行
精彩推荐

虾皮社区,成立十年了!

站长自己也搞不懂想做个什么,反正就是一直在努力的做!

Copyright © 2007-2019 xp6.org Powered by Discuz

QQ|小黑屋|手机版|Archiver|虾皮社区 ( 鲁ICP备13006813号-1 ) 鲁公网安备 37021102000261号 |网站地图
返回顶部 返回列表