本文就是一篇非常直白的部署使用教程,并且DeepSeek-R1的部署并没有采用官方指导的vllm,ollama和SGLang的方式,具体原因就是显卡问题,我只有Tesla P40,很遗憾的就是不支持。我也知道vllm等方式非常的方便快捷,没法…故而用fastapi写了一个接口服务,这也重新让我的老掉牙显卡焕发新生机
前排提示,文末有大模型AGI-CSDN独家资料包哦!
。
首先是官方的部署方式(显卡支持的可直接使用此方法):
vllm serve deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --tensor-parallel-size 2 --max-model-len 32768 --enforce-eager python3 -m sglang.launch_server --model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --trust-remote-code --tp 2
首先这里先安装open-webui,直接使用docker镜像,别折腾环境这种浪费时间,没有太多意义的事情:
docker run -d -p 3000:8080 --gpus all --add-host=host.docker.internal:host-gateway -v d:\jgogo\open-webui\backend\:/app/backend/ --name open-webui --restart always ghcr.io/open-webui/o pen-webui:cuda
在上面命令中有一处不一样的地方,就是-v
命令,我事先下载镜像后将内部的工程文件拷贝出来,在从本地映射到镜像内,方便后续代码改动同步,命令:
docker cp open-webui:/app/backend ./
镜像pull完毕后会直接运行,本地地址为:http://127.0.0.1:3000
,一开始会让你注册一个管理员账号,进去后界面如下:
接着就是DeepSeek-R1服务API开发,直接上代码(太长,贴上关键代码,其他自行补全),接口是符合opanai标准格式的,并且使用流式输出,这里流式输出有一个bug,代码中使用了fastapi和uvicorn,但是流式返回的StreamingResponse,open-webui收到后无法显示,因为内容是字节流,貌似无法解析(有可能我没用对),所以最后做了for,屏蔽了yield,变成非流式返回:
def parse_messages(messages): """ 解析原始请求体 Args: messages: 原始请求消息 Returns: 解析后的消息 """ if all(m.role != "user"for m in messages): raise HTTPException( status_code=400, detail=f"Invalid request: Expecting at least one user message.", ) messages = copy.deepcopy(messages) _messages = messages messages = [] image_data = None for m_idx, m in enumerate(_messages): role, content, func_call, image_str = m.role, m.content, m.function_call, m.image if content: content = content.lstrip("\n").rstrip() if role == "function": if (len(messages) == 0) or (messages[-1].role != "assistant"): raise HTTPException( status_code=400, detail=f"Invalid request: Expecting role assistant before role function.", ) messages[-1].content += f"\nObservation: {content}" if m_idx == len(_messages) - 1: messages[-1].content += "\nThought:" elif role == "assistant": if len(messages) == 0: raise HTTPException( status_code=400, detail=f"Invalid request: Expecting role user before role assistant.", ) if messages[-1].role == "user": messages.append( ChatMessage(role="assistant", content=content.lstrip("\n").rstrip()) ) else: messages[-1].content += content elif role == "user": t_content = content.lstrip("\n").rstrip() messages.append( ChatMessage(role="user", content=t_content, image=image_str) ) else: raise HTTPException( status_code=400, detail=f"Invalid request: Incorrect role {role}." ) query = "" if messages[-1].role == "user": query = messages[-1].content.replace("<image>", "").strip() image_data = messages[0].image messages = messages[:-1] image = None img_path = None if image_data: img_name = hashlib.md5(f"{query}_{int(time.time() * 1000)}".encode("utf-8")).hexdigest() img_path = f"./assets/cache/imgs/{img_name}.jpg" with open(img_path, "wb") as fp: fp.write(base64.b64decode(image_data)) image = Image.open(img_path).convert('RGB') logger.info(f"输入图像大小为: {image.height} x {image.width}") else: logger.warning("图片上传数据为空,请排查问题。") if len(messages) % 2 != 0: raise HTTPException(status_code=400, detail="Invalid request") if len(messages): msg = [] for i in range(0, len(messages), 2): if messages[i].role == "user" and messages[i + 1].role == "assistant": if i == 0: msg.append({"role": messages[i].role, "content": messages[i].content}) msg.append({"role": messages[i + 1].role, "content": messages[i + 1].content}) else: raise HTTPException( status_code=400, detail="Invalid request: Expecting exactly one user (or function) role before every assistant role.", ) msg.append({"role": "user", "content": query}) else: msg = [{"role": "user", "content": query}] return query, msg, img_path @app.get("/v1/models", response_model=ModelList) async def list_models(): global model_args model_card = ModelCard(id="DeepSeek-R1-7B") return ModelList(data=[model_card]) @app.post("/v1/chat/completions", response_model=ChatCompletionResponse) async def create_chat_completion(request: ChatCompletionRequest): global model, tokenizer start_time = time.time() query, query_msg, _ = parse_messages(request.messages) logger.info(f"{datetime.datetime.now()} - 原始输入数据: {query} ----- {query_msg}") input_ids = tokenizer.apply_chat_template(query_msg, tokenize=False, add_generation_prompt=True) model_inputs = tokenizer(input_ids, return_tensors="pt").to(model.device) streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) generation_kwargs = dict(model_inputs, temperature=request.temperature, streamer=streamer, top_p=request.top_p, top_k=request.top_k, max_new_tokens=8192, repetition_penalty=request.repetition_penalty ) Thread(target=model.generate, kwargs=generation_kwargs).start() # 流式返回 # async def generate_data(): # for res in streamer: # yield res response = "" for res in streamer: response += res logger.info(f"最终返回结果: {response} - 耗时: {time.time() - start_time} - Token数:{len(tokenizer.tokenize(response))}") torch.cuda.empty_cache() choice_data = ChatCompletionResponseChoice( index=0, message=ChatMessage(role="assistant", content=response), finish_reason="stop", ) return ChatCompletionResponse( model=request.model, choices=[choice_data], object="chat.completion" ) # 流式返回 # return StreamingResponse(generate_data(), media_type="text/event-stream") def _get_args(): parser = ArgumentParser() parser.add_argument( "-c", "--checkpoint-path", type=str, default="/home/jgogo/llms/DeepSeek-R1-Distill-Qwen-7B", help="Checkpoint name or path, default to %(default)r", ) parser.add_argument( "--cpu-only", action="store_true", help="Run demo with CPU only" ) parser.add_argument( "--server-port", type=int, default=8010, help="Demo server port." ) parser.add_argument( "--server-name", type=str, default="0.0.0.0", help="Demo server name. Default: 127.0.0.1, which is only visible from the local computer." " If you want other computers to access your server, use 0.0.0.0 instead.", ) return parser.parse_args() if __name__ == "__main__": args = _get_args() if args.cpu_only: device_map = "cpu" else: device_map = "cuda:0" model = AutoModelForCausalLM.from_pretrained( args.checkpoint_path, device_map=device_map, trust_remote_code=True, ).to(dtype=torch.float16) tokenizer = AutoTokenizer.from_pretrained(args.checkpoint_path, trust_remote_code=True) model.eval() model.generation_config = GenerationConfig.from_pretrained( args.checkpoint_path, trust_remote_code=True, resume_download=True, )
接口测试代码:
import json import requests url = "http://127.0.0.1:8010/v1/chat/completions" datas = { "model": "DeepSeek-R1-7B", "temperature": 0.6, # "top_k": 20, # "top_p": 0.2, # "repetition_penalty": 1.05, "messages": [ { "role": "user", "content": "你是谁", }, ], "max_tokens": 8192, } response = requests.post(url, data=json.dumps(datas)) print(response.text)
最后在命令行运行本程序,这个环境就稀松平常了吧,即可在open-webui中配置接口,其中192.168.124.2是我本机内网地址,换成自己的即可。如下图所示:
其实很简单,下面是测试效果:
细心的朋友应该注意到,接口代码中有图片的相关信息,这是因为我这边也接入了相关多模态大模型,改动了一些内部代码,目前可支持MiniCPM-V系列,InternVL系列((均为openai标准格式)等
读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用
对于0基础小白入门:
如果你是零基础小白,想快速入门大模型是可以考虑的。
一方面是学习时间相对较短,学习内容更全面更集中。
二方面是可以根据这些资料规划好学习计划和方向。
包括:大模型学习线路汇总、学习阶段,大模型实战案例,大模型学习视频,人工智能、机器学习、大模型书籍PDF。带你从零基础系统性的学好大模型!
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费
】🆓
👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费
】🆓