項目介紹:
Firefly(流螢)?是yangjianxin開發(fā)的開源的中文大語言模型項目,本文主要實現(xiàn)將此模型部署到http服務器上,語言實現(xiàn):python,本項目為雙創(chuàng)項目后端部分代碼(本人根據(jù)firefly訓練代碼修改+微調的模型暫不方便開源),樣例模型改用firefly1b4模型
項目環(huán)境:
1.pytorch:2.0.1+cpu
2.transformers:4.29.1
3.httpserver庫
例外:requests庫(如果不接其他api不需要)
模型下載:YeungNLP (YeungNLP) (huggingface.co)
下載后新建model文件夾將下載的所有文件放入文件夾,如下圖所示
打開config.json,將torch_dtype的值改為int8,可以有效降低卡頓(尤其適用于cpu版本)
硬件環(huán)境:
由于是模型的使用,除了推理的時候不會很吃Cpu/Gpu,加載模型比較吃內存,目前經(jīng)過測試發(fā)現(xiàn)實際運行,8G可以勉強運行模型,但是有大概率導致整機卡死,建議至少達到12G內存
項目開發(fā)環(huán)境:Cpu:i58400,內存:16G(此配置下運行模型再跑androidstudio+非androidstudio自帶的模擬器也是搓搓有余的)
代碼部分:
1.導入包:
print("導入requests庫中...")
import requests
print("導入http庫中...")
import http.server
print("導入json庫中...")
import json
print("導入os庫中...")
import os
print("導入time庫中...")
import time
print("導入urllib庫中...")
import urllib
import random
from urllib import parse
print("導入transformers庫中...")
from transformers import BloomTokenizerFast, BloomForCausalLM
print("導包完成=====================")
2.RequestHandlerImpl類部分(httpserver)?
class RequestHandlerImpl(http.server.BaseHTTPRequestHandler):
def do_GET(self):
get_str=""
get_cmd=self.requestline[5:self.requestline.find("HTTP/1.1")]
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
get_str=checkget(get_cmd,self.headers)
if get_str=="":get_str= "Hello World\n"
self.wfile.write(get_str.encode("utf-8"))
def do_POST(self):
req_body = self.rfile.read(int(self.headers["Content-Length"])).decode()
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
get_str=checkpost(self.path,req_body)
self.wfile.write(get_str.encode("utf-8"))
3.項目函數(shù)部分(由于是app后端有接入其他接口):
def get_answer(text):
print("得到新問題",text)
input_ids = tokenizer(text, return_tensors="pt").input_ids
input_ids = input_ids.to(device)
outputs = model.generate(input_ids, max_new_tokens=200, do_sample=True, top_p=0.85, temperature=0.35,repetition_penalty=1.2, eos_token_id=tokenizer.eos_token_id)
rets = tokenizer.batch_decode(outputs)
output = rets[0].strip().replace(text, "").replace('</s>', "")
return format(output)
def get_list(parm): #新聞類接口,可以發(fā)布
parm=parm[1:]
get_tx=parm.split("&")
name="福州"
page="0"
for i in range(0, len(get_tx)):
if get_tx[i][0:5]=="name=":
name=get_tx[i][5:]
if get_tx[i][0:5]=="page=":
page=get_tx[i][5:].replace(' ', '')
url = "https://v.api.aa1.cn/api/api-tplist/go.php/api/News/local_news?name=" + name + "&page=" + page
print(url)
response = requests.get(url)
content = response.text
return content
def get_top(): #百度熱搜接口
url ='https://v.api.aa1.cn/api/topbaidu/index.php'
response = requests.get(url)
content = response.text
return content
def get_weather(): #天氣類接口(付費的)
url ='http://apis.juhe.cn/simpleWeather/query?city=%E7%A6%8F%E5%B7%9E&key=需要自己加上'
response = requests.get(url)
content = response.text
return content
def login(up): #登錄接口
get_tx=up.split("&")
un=""
pw=""
code=0
for i in range(0, len(get_tx)):
if get_tx[i][0:5]=="user=":
un=get_tx[i][5:]
if get_tx[i][0:5]=="pass=":
pw=get_tx[i][5:].replace(' ', '')
print(un)
print(pw)
f=open('libaray/uw', encoding='gbk') #加載type字符庫
for line in f:
get_tx=line.split(",")
if un==get_tx[0] and pw==get_tx[1].replace('\n', ''):
dic = {'code': 200, 'msg': "登錄成功","token":token}
break
else:
dic = {'code': 201, 'msg': "用戶名或密碼錯誤"}
f.close()
print(dic)
return json.dumps(dic, sort_keys=True,ensure_ascii= False,indent=4, separators=(',', ':'))
def register(up): #登錄接口
get_tx=up.split("&")
uw=""
pw=""
code=0
for i in range(0, len(get_tx)): #這里和登錄類似,可以封裝起來,目的是獲取傳來的用戶,密碼
if get_tx[i][0:5]=="user=":
un=get_tx[i][5:]
if get_tx[i][0:5]=="pass=":
pw=get_tx[i][5:].replace(' ', '')
print(un)
print(pw)
#加載uw密碼庫,后續(xù)可以寫成load函數(shù),在加載時候開啟
f=open('libaray/uw', encoding='gbk')
for line in f:
get_tx=line.split(",")
if un==get_tx[0]:
dic = {'code': 201, 'msg':"用戶已存在"}
return json.dumps(dic, sort_keys=True,ensure_ascii= False,indent=4, separators=(',', ':'))
f.close()
f=open('libaray/uw','a+')
f.write(un+","+pw+"\n")
dic = {'code': 200, 'msg':"注冊成功"}
f.close()
return json.dumps(dic, sort_keys=True,ensure_ascii= False,indent=4, separators=(',', ':'))
def checkpost(path,get_cmd): #查看post進來的數(shù)據(jù)
if path=="/login":
return login(get_cmd)
if path=="/register":
return register(get_cmd)
def checkhead(head): #檢查需要加密的接口,傳進來的頭
print(token == head.get("Authorization"))
if token == head.get("Authorization"):
return True
else:
return False
def checkget(get_cmd="",head=""): #查看get進來的數(shù)據(jù)
if get_cmd[0:9]=="question=":
if checkhead(head):
dic = {'code': 200, 'msg':get_answer(parse.unquote(get_cmd[9:])),"prompt":urllib.parse.unquote(get_cmd[9:])}
return json.dumps(dic, sort_keys=True,ensure_ascii= False,indent=4, separators=(',', ':'))
else:
dic = {'code': 401, 'msg':"沒有權限"}
return json.dumps(dic, sort_keys=True,ensure_ascii= False,indent=4, separators=(',', ':'))
if get_cmd[0:4]=="list":
if checkhead(head):
return get_list(get_cmd[4:])
else:
dic = {'code': 401, 'msg':"沒有權限"}
return json.dumps(dic, sort_keys=True,ensure_ascii= False,indent=4, separators=(',', ':'))
if get_cmd[0:6]=="gettop":
if checkhead(head):
return get_top()
else:
dic = {'code': 401, 'msg':"沒有權限"}
return json.dumps(dic, sort_keys=True,ensure_ascii= False,indent=4, separators=(',', ':'))
if get_cmd[0:7]=="weather": #免費api接口
return get_weather()
if get_cmd[0:6]=="login?":
gcmd=get_cmd[6:]
return login(gcmd)
main:
?
print("加載tokenizer中")
tokenizer = BloomTokenizerFast.from_pretrained('model/') #路徑以文件夾下的model為例
print("加載model中")
model = BloomForCausalLM.from_pretrained('model/')
model.eval()
device="cpu"
model = model.to(device) #用cuda或者cpu
print("tlc機器人已啟動")
token=''.join(random.sample('abcdefghijklmnopqrstuvwxyzABCDEGHIJKLMNOPQRSTWVUXYZ!@#$%&',39))
print("加密為token=" + token) #這句加入是方便測試
local_ip="10.1.136.73" #local ip為服務器ip
server_address = (local_ip, 19999)
httpd = http.server.HTTPServer(server_address, RequestHandlerImpl)
httpd.serve_forever()
接口測試:
1.運行代碼:
運行代碼后,如果提示如下圖所示就是沒有問題的了,可以看到有一個token=xxxx的參數(shù),這個參數(shù)是隨機生成的臨時token,目前設定是每次啟動服務端生成一次,這里為了方便演示打印出來,實際需要登錄接口來獲取,后續(xù)可以注釋掉
2.測試接口是否可用:
如下圖所示在postman輸入http://10.1.136.73:19999/question=<s>你好</s></s>
由于輸出到模型的數(shù)據(jù)被格式化成<s></s>的形式,為方便客戶端傳遞歷史對話作為promat,我沒有在python格式化字符串,而是在客戶端里實現(xiàn)。
彈出401的提示是我們加入頭,無法通過驗證,但是可以證明http服務端可以正常跑起來
?
3.加入頭繼續(xù)驗證:
在head加入?yún)?shù)名為Authorization,參數(shù)值為臨時生成的token的head再次運行。發(fā)現(xiàn)已可行,如下圖所示,發(fā)現(xiàn)客戶端正常返回json
?code參數(shù):為200時正常,其他不正常,prompt是傳入的值,msg是出來的值。
多輪對話:
在實際測試中發(fā)現(xiàn)Firefly1b4的版本也是可以支持多輪對話的,但是效果的確會差些,我們只需要在外部把數(shù)據(jù)格式化成以下的形式:
<s>問題1</s></s>回答1</s></s>問題2</s></s>回答2</s></s>問題3</s></s>回答3</s></s>
以下是效果案例:
傳入的promat為<s>你知道北京嗎</s></s>北京市是中國的首都,位于中國北方。</s></s>那里有什么美食</s></s>烤鴨、炸醬面、豆汁、涮羊肉、豆腐腦等等。</s></s>有什么娛樂的地方</s></s>
輸出為長城、故宮博物院、頤和園、天壇、圓明園等。
?
Developed?by 福州機電工程職業(yè)技術學校 wh
郵箱聯(lián)系方式:xiaohui032901@foxmail.com文章來源:http://www.zghlxwxcb.cn/news/detail-522570.html
qq聯(lián)系方式:2151335401、3135144152文章來源地址http://www.zghlxwxcb.cn/news/detail-522570.html
到了這里,關于從0開始,部署基于yangjianxin開發(fā)的流螢(Firefly)中文對話式大語言模型的http服務端的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!