news 2026/6/20 2:00:57

如何实现Qwen流式输出?Flask异步接口代码实例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何实现Qwen流式输出?Flask异步接口代码实例

如何实现Qwen流式输出?Flask异步接口代码实例

1. 背景与目标

随着大模型轻量化趋势的发展,将小型语言模型部署在资源受限环境(如CPU服务器、边缘设备)成为可能。Qwen1.5-0.5B-Chat作为通义千问系列中参数量最小的对话模型之一,具备响应快、内存占用低、推理门槛低等优势,非常适合用于构建轻量级智能对话服务。

然而,传统Web接口通常采用“请求-响应”模式,用户需等待模型完整生成结果后才能看到输出,体验较差。为提升交互流畅性,本文聚焦于如何在Flask框架下实现Qwen模型的流式输出,让用户像使用ChatGPT一样逐字接收回复,打造类实时对话体验。

本项目基于ModelScope生态完成模型加载与推理,并结合Flask的流式响应能力,提供一个可直接运行的本地化轻量对话系统解决方案。

2. 技术架构与核心原理

2.1 整体架构设计

系统由三部分组成:

  • 前端界面:HTML + JavaScript 构建简易聊天页面,支持消息输入与流式文本渲染。
  • 后端服务:Flask应用提供/chat接口,处理用户输入并返回流式响应。
  • 模型推理层:通过Transformers加载Qwen1.5-0.5B-Chat模型,在CPU上进行推理,利用generate函数配合回调机制实现token级输出。

数据流如下:

用户输入 → Flask接收 → 模型编码 → 逐token生成 → 流式返回 → 前端实时显示

2.2 流式输出的核心机制

要实现“打字机”效果,关键在于服务端持续推送未完成文本,而标准HTTP响应是单次闭合的。为此,我们使用Flask的Response对象配合生成器(generator),将模型每生成一个token的结果即时发送给客户端。

Python中可通过以下方式创建流式响应:

from flask import Response return Response(generate(), mimetype='text/plain')

其中generate()是一个生成器函数,它在模型生成过程中不断yield部分内容。

2.3 ModelScope模型加载策略

Qwen1.5-0.5B-Chat托管于魔塔社区(ModelScope),我们使用其官方SDK自动下载并加载模型:

from modelscope.pipelines import pipeline from modelsoke.utils.constant import Tasks nlp_pipeline = pipeline(task=Tasks.chat, model='qwen/Qwen1.5-0.5B-Chat')

该方式确保模型版本一致性,避免手动管理权重文件带来的兼容性问题。

3. 实现步骤详解

3.1 环境准备与依赖安装

首先创建独立Conda环境并安装必要库:

conda create -n qwen_env python=3.9 conda activate qwen_env pip install torch==2.1.0 transformers==4.36.0 flask jinja2 sentencepiece modelscope

注意:建议使用较新版本的modelscope(≥1.14.0)以支持Qwen1.5系列模型。

3.2 模型初始化与推理封装

定义全局模型加载逻辑,避免每次请求重复加载:

# app.py from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks # 全局变量存储模型管道 chat_pipeline = None def load_model(): global chat_pipeline if chat_pipeline is None: chat_pipeline = pipeline( task=Tasks.chat, model='qwen/Qwen1.5-0.5B-Chat', device='cpu' # 显式指定CPU推理 ) return chat_pipeline

3.3 构建流式生成器函数

这是实现流式输出的核心模块。我们需要自定义生成器,在模型解码过程中逐个获取token并返回:

def generate_stream_response(user_input): global chat_pipeline if chat_pipeline is None: load_model() # 使用generate_with_callback模拟流式生成 def token_callback(text): yield f"data: {text}\n\n" # SSE格式 # 实际上Transformers不直接支持callback,需改用迭代方式 from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained('qwen/Qwen1.5-0.5B-Chat', trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained('qwen/Qwen1.5-0.5B-Chat', trust_remote_code=True, device_map='cpu') inputs = tokenizer(user_input, return_tensors="pt").to('cpu') streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) generation_kwargs = dict(inputs, streamer=streamer, max_new_tokens=512) thread = Thread(target=model.generate, kwargs=generation_kwargs) thread.start() for text in streamer: yield f"data: {text}\n\n" yield "data: [DONE]\n\n"

注:上述代码使用了Hugging Face的TextIteratorStreamer来实现token流捕获,需额外导入相关类。

完整导入如下:

from threading import Thread from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer

3.4 Flask路由与流式接口实现

配置Flask应用,注册/chat流式接口和/主页路由:

from flask import Flask, request, render_template, Response app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') # 简单HTML页面 @app.route('/chat', methods=['POST']) def chat(): user_message = request.json.get('message', '') return Response( generate_stream_response(user_message), mimetype='text/event-stream' # 使用SSE协议 )

3.5 前端页面实现(支持流式渲染)

创建templates/index.html,使用JavaScript监听SSE流并动态更新DOM:

<!DOCTYPE html> <html> <head> <title>Qwen1.5-0.5B-Chat 轻量对话</title> <style> #chat-box { width: 80%; height: 400px; border: 1px solid #ccc; margin: 20px auto; padding: 10px; overflow-y: auto; font-family: Arial, sans-serif; } #input-area { display: flex; width: 80%; margin: 0 auto; } #message-input { flex: 1; padding: 10px; font-size: 16px; } button { padding: 10px 20px; font-size: 16px; } </style> </head> <body> <h1 style="text-align:center;">🧠 Qwen1.5-0.5B-Chat 对话系统</h1> <div id="chat-box"></div> <div id="input-area"> <input type="text" id="message-input" placeholder="请输入您的问题..." /> <button onclick="sendMessage()">发送</button> </div> <script> const chatBox = document.getElementById('chat-box'); let eventSource = null; function sendMessage() { const input = document.getElementById('message-input'); const message = input.value.trim(); if (!message) return; // 显示用户消息 chatBox.innerHTML += `<p><strong>你:</strong>${message}</p>`; // 发起POST请求并开启SSE流 fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }) }).then(() => { // 不等待响应体,直接建立SSE连接 if (eventSource) eventSource.close(); eventSource = new EventSource('/chat'); let reply = ''; eventSource.onmessage = function(event) { if (event.data === '[DONE]') { eventSource.close(); chatBox.scrollTop = chatBox.scrollHeight; return; } reply += event.data; chatBox.innerHTML = chatBox.innerHTML.replace(/<p><strong>AI:<\/strong>(.*?)<\/p>/, `<p><strong>AI:</strong>${reply}</p>`) || `<p><strong>AI:</strong>${reply}</p>`; chatBox.scrollTop = chatBox.scrollHeight; }; }); input.value = ''; } </script> </body> </html>

3.6 完整启动脚本

整合所有组件,编写主程序入口:

# app.py from flask import Flask, request, render_template, Response from threading import Thread from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer import torch app = Flask(__name__) tokenizer = None model = None chat_pipeline = None def load_model(): global tokenizer, model if model is not None: return model_dir = 'qwen/Qwen1.5-0.5B-Chat' tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_dir, trust_remote_code=True, torch_dtype=torch.float32, device_map='cpu' ) def generate_stream_response(prompt): load_model() inputs = tokenizer(prompt, return_tensors="pt").to('cpu') streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) generation_kwargs = { "input_ids": inputs["input_ids"], "max_new_tokens": 512, "temperature": 0.7, "do_sample": True, "streamer": streamer, } thread = Thread(target=model.generate, kwargs=generation_kwargs) thread.start() for text in streamer: yield f"data: {text}\n\n" yield "data: [DONE]\n\n" @app.route('/') def index(): return render_template('index.html') @app.route('/chat', methods=['POST']) def chat(): user_message = request.json.get('message', '') return Response(generate_stream_response(user_message), mimetype='text/event-stream') if __name__ == '__main__': app.run(host='0.0.0.0', port=8080, threaded=True)

4. 性能优化与实践建议

4.1 CPU推理加速技巧

尽管Qwen1.5-0.5B-Chat可在CPU运行,但仍可通过以下方式提升响应速度:

  • 降低精度:使用torch.float16bfloat16(若支持)
  • 启用ONNX Runtime:将模型导出为ONNX格式,利用ORT优化推理
  • 减少max_new_tokens:限制生成长度防止过长输出阻塞流

示例(半精度加载):

model = AutoModelForCausalLM.from_pretrained( 'qwen/Qwen1.5-0.5B-Chat', torch_dtype=torch.float16, device_map='cpu' )

注意:CPU对FP16支持有限,某些情况下反而变慢,需实测验证。

4.2 内存控制策略

对于低内存环境(如2GB以内),建议:

  • 设置low_cpu_mem_usage=True
  • 启用offload_folder临时卸载参数
  • 避免并发请求
model = AutoModelForCausalLM.from_pretrained( 'qwen/Qwen1.5-0.5B-Chat', low_cpu_mem_usage=True, offload_folder="./offload", device_map='cpu' )

4.3 并发与线程安全注意事项

当前实现中,模型共享于全局变量,多用户同时访问可能导致冲突。生产环境中应考虑:

  • 使用队列机制串行处理请求
  • 或为每个请求分配独立上下文(成本较高)

简单防并发方案:

import threading lock = threading.Lock() @app.route('/chat', methods=['POST']) def chat(): with lock: user_message = request.json.get('message', '') return Response(generate_stream_response(user_message), mimetype='text/event-stream')

5. 总结

5. 总结

本文详细介绍了如何基于Qwen1.5-0.5B-Chat模型和Flask框架构建一个支持流式输出的轻量级对话服务。主要内容包括:

  • 利用ModelScope生态快速加载官方开源模型,保障模型来源可靠性;
  • 通过TextIteratorStreamer与多线程技术实现token级流式生成;
  • 使用SSE(Server-Sent Events)协议在前端实现“打字机”式逐字输出;
  • 提供完整的前后端代码结构,支持本地一键部署;
  • 给出了CPU环境下的性能优化与内存控制建议。

该项目充分体现了小模型在边缘侧部署的价值:无需GPU、内存占用低、响应及时,适合嵌入式设备、私有化部署、教育演示等多种场景。

未来可扩展方向包括:

  • 添加对话历史记忆功能
  • 支持语音输入/输出
  • 集成RAG实现知识增强问答
  • 封装为Docker镜像便于分发

获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 19:40:01

DLSS Swapper完整指南:免费提升游戏性能的终极解决方案

DLSS Swapper完整指南&#xff1a;免费提升游戏性能的终极解决方案 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 还在为游戏中的卡顿和画面撕裂而烦恼吗&#xff1f;想要轻松管理不同版本的DLSS文件却不知从何入手&a…

作者头像 李华
网站建设 2026/6/15 13:45:17

Topit窗口置顶神器:彻底告别Mac多窗口切换烦恼

Topit窗口置顶神器&#xff1a;彻底告别Mac多窗口切换烦恼 【免费下载链接】Topit Pin any window to the top of your screen / 在Mac上将你的任何窗口强制置顶 项目地址: https://gitcode.com/gh_mirrors/to/Topit 你是否曾经在编程时为了查看API文档而频繁切换窗口&a…

作者头像 李华
网站建设 2026/6/18 11:04:33

GLM-ASR-Nano-2512优化教程:模型推理速度提升秘籍

GLM-ASR-Nano-2512优化教程&#xff1a;模型推理速度提升秘籍 1. 引言 1.1 技术背景与业务需求 随着语音识别技术在智能客服、会议转录、内容创作等场景的广泛应用&#xff0c;对高效、低延迟的自动语音识别&#xff08;ASR&#xff09;系统的需求日益增长。GLM-ASR-Nano-25…

作者头像 李华
网站建设 2026/6/15 13:08:27

YimMenu终极配置与完整指南:从新手到高手的进阶之路

YimMenu终极配置与完整指南&#xff1a;从新手到高手的进阶之路 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/YimMen…

作者头像 李华
网站建设 2026/6/19 22:36:21

同事甩锅、需求难沟通?程序员提升情商,比学新框架更紧急

尽管您可能认为软件工程是一个主要重视逻辑、知识和解决问题的领域&#xff0c;但还有另一种同样重要的智能&#xff1a;情商。越来越多的雇主正在寻找具有“软技能”的编码人员&#xff0c;例如能够与团队良好合作、同情同事和客户以及缓和情绪状况的能力。所有这些技能都需要…

作者头像 李华
网站建设 2026/6/9 21:09:43

蓝屏模拟器深度解析:安全实现系统故障模拟的架构设计与实践

蓝屏模拟器深度解析&#xff1a;安全实现系统故障模拟的架构设计与实践 【免费下载链接】BluescreenSimulator Bluescreen Simulator for Windows 项目地址: https://gitcode.com/gh_mirrors/bl/BluescreenSimulator 蓝屏模拟器作为一款专业的Windows系统故障模拟工具&a…

作者头像 李华