本文就是一篇非常直白的部署使用教程,并且DeepSeek-R1的部署并没有采用官方指导的vllm,ollama和SGLang的方式,具体原因就是显卡问题,我只有Tesla P40,很遗憾的就是不支持。我也知道vllm等方式非常的方便快捷,没法…故而用fastapi写了一个接口服务,这也重新让我的老掉牙显卡焕发新生机。
首先是官方的部署方式(显卡支持的可直接使用此方法):
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标准格式)等。
如何学习AI大模型?
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
1.AI大模型学习路线图
2.100套AI大模型商业化落地方案
3.100集大模型视频教程
4.200本大模型PDF书籍
5.LLM面试题合集
6.AI产品经理资源合集
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓