十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

《开源大模型食用指南》Kimi-VL-A3B-Thinking 多模态对话助手实战:基于 Flask 的前后端分离 Web 应用搭建

《开源大模型食用指南》Kimi-VL-A3B-Thinking 多模态对话助手实战:基于 Flask 的前后端分离 Web 应用搭建 《开源大模型食用指南》Kimi-VL-A3B-Thinking 多模态对话助手实战基于 Flask 的前后端分离 Web 应用搭建【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm导读本文基于 self-llm 仓库中 01-Kimi-VL-对话助手.md 教程完整讲解如何从零搭建一个基于 Moonshot AIKimi-VL-A3B-Thinking多模态大模型的前后端分离对话助手 Web 应用。你将掌握环境配置、ModelScope 模型下载、Flask 后端实现含多模态输入处理、图像压缩、思考过程保留、多轮对话历史管理以及前端交互设计Canvas 图像压缩、参数实时调节、思考过程折叠展示最终在浏览器中体验图文双模态 可视化推理的对话能力。效果展示整个应用围绕文本 图像双模态输入展开界面支持一次上传最多 2 张图片并配以文本提问。下图依次展示了应用启动时的模型加载状态、用户上传双图提问以及模型给出的对比分析结果一、项目结构与技术选型教程对应的完整参考代码位于仓库的 01-Kimi-VL-对话助手/app 目录结构如下models/Kimi-VL/01-Kimi-VL-对话助手/app/ ├── app.py # Flask 后端模型加载、推理、API 路由 ├── requirements.txt # 依赖清单 ├── templates/ │ └── index.html # 前端聊天界面 └── README.md # 应用说明文档整体架构为典型的前后端分离模式后端Flask 提供GET /渲染前端页面、POST /api/generate生成回复、POST /api/clear_history清空会话历史三个核心路由前端原生 HTML JavaScript负责图片上传压缩、参数滑动条、聊天界面渲染与思考过程折叠展示模型侧通过 Hugging Face Transformers 生态的AutoProcessor、AutoTokenizer、AutoModelForCausalLM加载Kimi-VL-A3B-Thinking其中AutoProcessor专门负责把文本 图像组合输入统一编码为模型可接受的张量。关于该模型的架构细节可进一步阅读仓库中的 02-Kimi-VL-技术报告解读.md。需要说明的是本文聚焦对话助手的工程化搭建Kimi-VL-A3B-Thinking 是 Moonshot AI 发布的多模态大模型支持图像理解与文本推理并会在回复中以◁think▷...◁/think▷标签输出思考过程。二、环境准备2.1 基础环境教程给出的参考环境如下---------------- ubuntu 22.04 python 3.12 cuda 12.4 pytorch 2.6.0 ----------------同时需要保证足够的 GPU 显存在bfloat16精度下加载模型参考显存占用约40GB即最低要求为双卡 RTX 4090 或单卡 A6000。如果使用其他配置需要自行评估显存是否满足device_mapauto切分加载的需求。2.2 安装依赖教程建议先对pip换源加速下载再逐项安装依赖包pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple pip install transformers4.48.2 pip install accelerate1.6.0 pip install flask3.1.0 pip install blobfile3.0.0 pip install pillow10.4.0 pip install modelscope1.22.3参考代码目录下也提供了等价的 requirements.txt可以直接执行pip install -r requirements.txt完成安装torch2.6.0 transformers4.48.2 accelerate1.6.0 flask3.1.0 blobfile3.0.0 pillow10.4.0 modelscope1.22.3其中transformers与accelerate负责模型加载与多卡切分modelscope用于国内环境下拉取模型权重flask承载 Web 服务pillow用于图像解码与缩放blobfile为 Kimi-VL 模型加载所需的依赖。三、模型下载教程推荐使用modelscope的snapshot_download函数下载模型该方法对国内用户非常友好。新建model_download.py文件并写入以下代码# model_download.py from modelscope import snapshot_download model_dir snapshot_download(moonshotai/Kimi-VL-A3B-Thinking, cache_dir请修改我, revisionmaster) print(f模型下载完成保存路径为{model_dir})运行python model_download.py即可开始下载。参数说明第一个参数模型名称moonshotai/Kimi-VL-A3B-Thinkingcache_dir模型下载到本地的保存路径必须修改为实际路径例如/data/models/Kimi-VL-A3B-Thinkingrevision分支版本此处固定为master。注意请记得修改cache_dir为你自己的模型下载路径。下载完成后会打印模型保存路径后续后端配置中需要用到。四、后端实现详解后端代码 app.py 是教程的核心下面按照功能模块逐段剖析。4.1 全局状态与默认参数from flask import Flask, request, jsonify, render_template, session import torch from transformers import AutoTokenizer, AutoModelForCausalLM, AutoProcessor import gc import re import uuid import json import base64 import logging from io import BytesIO from PIL import Image app Flask(__name__) app.secret_key kimi-chatbot-secret-key # 用于session加密 # 修改为合理的值最大100MB app.config[MAX_CONTENT_LENGTH] 100 * 1024 * 1024 # 限制上传文件大小 app.config[MAX_CONTENT_PATH] None # 全局变量存储预加载的模型和tokenizer MODEL_ID 请修改我 tokenizer None model None processor None # 用于存储对话历史的字典 chat_histories {} # 默认值设置 DEFAULT_MAX_NEW_TOKENS 1024 DEFAULT_MAX_HISTORY_LENGTH 10要点MODEL_ID需要修改为上一步snapshot_download返回的本地模型路径或直接填写模型仓库名tokenizer / model / processor三个全局变量在应用启动时预加载避免每次请求重复加载模型chat_histories是一个以chat_id为键的字典用于服务端保存各会话的多轮对话历史默认生成长度 1024 token默认保留最近 10 轮对话历史MAX_CONTENT_LENGTH将单次请求体限制为 100MB防止超大上传拖垮服务。4.2 模型预加载def load_model(): global tokenizer, model, processor print(正在加载模型和tokenizer请稍候...) # 加载processor (用于处理图像和文本) processor AutoProcessor.from_pretrained(MODEL_ID, trust_remote_codeTrue) # 加载tokenizer tokenizer AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_codeTrue) # 加载模型 model AutoModelForCausalLM.from_pretrained( MODEL_ID, device_mapauto, torch_dtypetorch.bfloat16, trust_remote_codeTrue ) print(模型加载完成)关键配置trust_remote_codeTrueKimi-VL 依赖仓库中的自定义代码必须开启device_mapauto由 accelerate 自动将模型分配到可用设备单卡或多卡均可这正是 40GB 显存需求可以被双卡 4090 满足的原因torch_dtypetorch.bfloat16以 bfloat16 半精度加载权重显著降低显存占用。在__main__中模型加载被放进独立线程启动避免阻塞 Flask 主线程的启动if __name__ __main__: # 在另一个线程中预加载模型 import threading threading.Thread(targetload_model).start() app.run(debugTrue, host0.0.0.0, port5000, use_reloaderFalse)注意use_reloaderFalseFlask debug 模式的自动重载会重启进程与线程预加载模型冲突必须关闭。4.3 图像处理base64 解码与压缩前端通过 base64 字符串传递图片后端需要将其还原为 PIL 图像并做压缩优化def base64_to_image(base64_str): if base64, in base64_str: base64_str base64_str.split(base64,)[1] try: image_bytes base64.b64decode(base64_str) image Image.open(BytesIO(image_bytes)) # 获取原始尺寸 original_width, original_height image.size # 压缩大图片如果宽度或高度超过1500像素则按比例缩小 max_size 1500 if original_width max_size or original_height max_size: if original_width original_height: new_width max_size new_height int(original_height * (max_size / original_width)) else: new_height max_size new_width int(original_width * (max_size / original_height)) image image.resize((new_width, new_height), Image.LANCZOS) # 如果是RGBA模式带透明通道转换为RGB if image.mode RGBA: background Image.new(RGB, image.size, (255, 255, 255)) background.paste(image, maskimage.split()[3]) image background return image except Exception as e: # 出错时返回默认灰色图像不中断整体流程 return Image.new(RGB, (100, 100), color(200, 200, 200))该函数做了三件事剥离data:image/...;base64,前缀后解码对超过 1500 像素的长边按比例缩放使用Image.LANCZOS高质量重采样控制送入模型的图像分辨率将带透明通道的 RGBA 图像合成到白色背景上转为 RGB避免透明区域被模型误解。4.4 响应清理保留思考过程、剔除特殊 tokenKimi-VL-A3B-Thinking 的输出包含◁think▷...◁/think▷思考标签以及|eot|、|im_end|、[EOS]等特殊结束标记。clean_response的作用是保留思考标签本身同时清理标签内外所有特殊标记def clean_response(text): 清理模型响应中的特殊token # 先清理常见的结束标记包括|im_end|和[EOS] text re.sub(r\|im_end\|(\s*\[EOS\])?, , text) text re.sub(r\[EOS\], , text) # 保留思考标签 thinking_pattern r◁think▷([\s\S]*?)◁/think▷ if re.search(thinking_pattern, text): # 思考部分的内容 def clean_thinking_content(match): thinking_content match.group(1) thinking_content re.sub(r[\|/]?eot[\|]?, , thinking_content) thinking_content thinking_content.replace(|eot|, ) thinking_content re.sub(r\|im_end\|(\s*\[EOS\])?, , thinking_content) thinking_content re.sub(r\[EOS\], , thinking_content) return f◁think▷{thinking_content}◁/think▷ # 先处理思考标签内的内容 text re.sub(thinking_pattern, clean_thinking_content, text) # 再处理剩余文本中的特殊标记 remaining_text re.sub(thinking_pattern, , text) cleaned_remaining re.sub(r[\|/]?eot[\|]?, , remaining_text) cleaned_remaining cleaned_remaining.replace(|eot|, ) cleaned_remaining re.sub(r\|im_end\|(\s*\[EOS\])?, , cleaned_remaining) cleaned_remaining re.sub(r\[EOS\], , cleaned_remaining) # 替换原文中的思考标签后的部分 text re.sub(r◁/think▷[\s\S]*, f◁/think▷{cleaned_remaining}, text) return text.strip() else: # 无思考标签时统一剔除各类特殊标记 patterns [|eot|, |im_end|, [EOS]] for pattern in patterns: text text.replace(pattern, ) text re.sub(r[\|/]?eot[\|]?, , text) # 匹配 eot, /eot, |eot| 等 return text.strip()思考标签保留下来后前端会将其渲染为可折叠的查看思考过程区域让用户直观看到模型的推理步骤而正文回复则单独展示互不干扰。4.5 核心推理接口/api/generatePOST /api/generate同时支持两种输入模式多模态模式前端通过chat_history表单字段以 JSON 数组传递含图片的消息内容OpenAI 风格type: image/type: text结构纯文本模式直接通过user_input字段传递文本向后兼容纯文字对话。多模态处理路径# 如果前端通过JSON传递了完整的历史记录包含图像 chat_history_json request.form.get(chat_history) if chat_history_json: received_history json.loads(chat_history_json) # 取最后一条用户消息 user_message received_history[-1] user_message_content user_message.get(content, []) # 遍历消息项分离图像与文本 images [] processed_content [] for item in user_message_content: if isinstance(item, dict) and item.get(type) image and image in item: image base64_to_image(item[image]) images.append(image) processed_content.append({type: image, image: fimage_{len(images)-1}}) elif isinstance(item, dict) and item.get(type) text and text in item: processed_content.append({type: text, text: item[text]}) # 使用processor处理多模态输入 messages [{role: user, content: processed_content}] text processor.apply_chat_template(messages, add_generation_promptTrue, return_tensorspt) inputs processor(imagesimages, texttext, return_tensorspt, paddingTrue, truncationTrue).to(model.device) with torch.no_grad(): generated_ids model.generate(**inputs, max_new_tokensmax_new_tokens) # 裁剪掉输入部分仅保留生成的token generated_ids_trimmed [ out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids) ] response processor.batch_decode( generated_ids_trimmed, skip_special_tokensTrue, clean_up_tokenization_spacesFalse )[0] cleaned_response clean_response(response)这段代码的调用链可以这样理解用processor.apply_chat_template把消息列表格式化为模型的对话模板文本再把模板文本与images列表一起交给processor得到input_ids、pixel_values等输入张量model.generate在torch.no_grad()下自回归生成通过输入输出 token 长度差裁剪出新增的生成部分并解码。纯文本处理路径# 传统文本输入处理向后兼容 if not has_input: if not user_input and not request.form: return jsonify({error: 请输入问题或上传图片}), 400 if chat_id not in chat_histories: chat_histories[chat_id] [] chat_histories[chat_id].append({role: user, content: user_input}) # 从历史记录构建消息列表使用前端传递的历史长度 messages chat_histories[chat_id][-max_history_length*2:] # 用户和助手消息各算一条 inputs tokenizer.apply_chat_template(messages, add_generation_promptTrue, return_tensorspt, return_dictTrue) with torch.no_grad(): outputs model.generate(**inputs.to(model.device), max_new_tokensmax_new_tokens) response tokenizer.batch_decode(outputs[:, inputs[input_ids].shape[-1]:]) cleaned_response clean_response(response[0]) chat_histories[chat_id].append({role: assistant, content: cleaned_response})两种路径生成完成后都会将助手回复追加到chat_histories[chat_id]若历史条数超过max_history_length * 2用户与助手各占一半只保留最近max_history_length * 2条防止上下文无限膨胀调用torch.cuda.empty_cache()与gc.collect()释放显存缓存与 Python 对象。参数校验接口接收前端传入的max_new_tokens与max_history_length并做范围约束max_new_tokens max(256, min(max_new_tokens, 2048)) max_history_length max(2, min(max_history_length, 20))即生成长度限制在256 ~ 2048历史轮数限制在2 ~ 20。4.6 会话与历史管理每个浏览器会话通过 Flask 的session分配唯一uuid作为chat_idapp.route(/) def home(): if chat_id not in session: session[chat_id] str(uuid.uuid4()) chat_id session[chat_id] if chat_id not in chat_histories: chat_histories[chat_id] [] return render_template(index.html, chat_idchat_id)清空历史接口则直接置空对应会话的列表app.route(/api/clear_history, methods[POST]) def clear_history(): data request.json chat_id data.get(chat_id, session.get(chat_id)) if chat_id and chat_id in chat_histories: chat_histories[chat_id] [] return jsonify({success: True, message: 聊天历史已清除}) else: return jsonify({success: False, error: 无效的会话ID}), 400服务端会话机制天然支持多用户同时使用互不干扰。五、前端实现要点前端页面位于 templates/index.html以原生 HTML/CSS/JavaScript 实现核心要点如下。5.1 Canvas 图像压缩上传图片后前端先用 Canvas 做一次压缩再发送与后端base64_to_image的二次处理形成双重优化async function compressImage(file, maxWidth 1200, maxHeight 1200, quality 0.7) { // 读取文件 - 加载为 Image - 计算缩放尺寸 - 绘制到 Canvas // 根据文件大小动态调整压缩质量 // 大于3MB - quality 0.5 // 大于1MB - quality 0.6 // PNG 含透明像素时保留 PNG 格式否则一律转 JPEG // 压缩后若仍大于1MB再用 0.4 质量二次压缩 }前端的压缩策略长边超过 1200 像素即缩放按文件大小分档降低 JPEG 质量压缩后仍超 1MB 则强制降至 0.4 质量保证大图也能顺利上传。同时还有如下约束单张图片超过10MB拒绝上传一次最多上传2 张图片发送前若图片总大小超过 5MB会弹出确认提示。5.2 思考过程折叠展示前端通过正则从回复中提取◁think▷标签内容将思考过程渲染为可点击折叠的区域const thinkPattern /◁think▷([\s\S]*?)◁\/think▷([\s\S]*)/; const match content.match(thinkPattern); if (match) { const thinkContent match[1].trim(); // 思考过程 const realResponse match[2].trim(); // 正式回复 // 创建 查看思考过程 折叠头部 可展开的思考内容 正式回复正文 }点击查看思考过程即可展开模型的推理步骤正式回复始终单独展示。5.3 模型加载状态轮询模型在后台线程加载期间前端通过轮询checkModelStatus()探测就绪状态function checkModelStatus() { fetch(/api/generate, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ user_input: , chat_id: chatId }) }) .then(response { if (response.status 503) { setTimeout(checkModelStatus, 5000); // 模型仍在加载5秒后再查 } else { // 模型已就绪隐藏橙色通知条启用输入框/上传/发送按钮 } }) .catch(() setTimeout(checkModelStatus, 5000)); }这与后端模型未加载时返回 503的逻辑呼应模型加载完成前顶部橙色通知条模型正在加载中请稍候...持续显示所有输入控件保持禁用。5.4 参数滑动条与请求发送页面提供两个滑动条值随拖动实时更新并在发送请求时随FormData一并提交模型生成长度上限max_new_tokens范围 256~2048步长 128默认 1024最大对话记忆轮数max_history_length范围 2~20步长 1默认 10。发送时前端只携带最近maxHistoryLength * 2条历史并设置 120 秒的请求超时AbortController超时或网络错误均有对应的中文提示文案。六、运行应用6.1 启动python app.py应用将在http://localhost:5000上运行。启动后模型会在后台自动加载可能需要 1~2 分钟。期间界面顶部会显示模型正在加载中的橙色提示加载完成后提示条消失、输入框与按钮才可用。6.2 使用方法在浏览器中打开 http://localhost:5000等待模型加载完成顶部的橙色通知条消失按需调整参数滑动条生成长度上限256~2048、历史记录长度2~20点击上传图片选择图片支持 JPG、PNG 等常见格式最多 2 张或仅输入文本在输入框中输入问题例如请描述这个图片中的内容点击发送或按 Enter等待模型生成回复含图像时耗时更长若回复含查看思考过程链接可点击展开查看模型的详细分析步骤继续多轮对话模型会记住上下文点击清除对话历史开始新会话。6.3 参数速查表参数默认值允许范围作用max_new_tokens1024256 ~ 2048单次回复生成的最大 token 数越大回复越长、耗时与显存占用越高max_history_length102 ~ 20保留的对话轮数用户与助手各算一条越大上下文越完整、输入越长MODEL_ID需修改—本地模型路径或 ModelScope 模型名MAX_CONTENT_LENGTH100MB—Flask 请求体上限对应单张图片不超过 10MB、一次最多 2 张模型精度bfloat16—加载精度参考显存占用约 40GB6.4 推荐试用的示例问题类型示例问题图像描述请详细描述这张图片中的内容视觉分析这张图片中有哪些物体它们各自的特点是什么图像比较上传两张图后比较这两张图片的异同点内容识别图片中的文字内容是什么场景理解这个场景可能是在什么地方为什么情感分析图片中人物的情绪如何基于什么判断视觉推理根据图片内容推测这可能是什么场合或事件七、注意事项与常见问题显存要求bfloat16 精度下参考显存占用约 40GB建议双卡 4090 或单卡 A6000显存不足时模型无法通过device_mapauto完整加载必须修改两处model_download.py中的cache_dir以及app.py中的MODEL_ID图片限制前端单张图片不超过 10MB、一次最多 2 张、发送前总大小超过 5MB 会提示确认后端MAX_CONTENT_LENGTH为 100MB请求超时含图像请求的浏览器侧超时时间为 120 秒响应过慢请尝试上传更小的图片历史长度默认保留最近 10 轮对话可通过滑动条调整为 2~20防止上下文过长导致显存与耗时上升内存管理每次生成后调用torch.cuda.empty_cache()与gc.collect()释放缓存生成全程使用torch.no_grad()减少内存占用debug 模式必须保持use_reloaderFalse否则 Flask 自动重载会打断后台模型加载线程。结语通过本文你已经在 self-llm 仓库的教程基础上完整掌握了一条多模态大模型 Web 工程化的实践链路从 ModelScope 拉取 Kimi-VL-A3B-Thinking 权重到 Flask 后端处理图文混合输入、保留思考过程、维护多轮会话再到前端 Canvas 压缩与参数实时调节。整个应用同时支持图文双模态与纯文本对话为你后续在此基础上扩展更多多模态应用如自定义视觉问答、图像对比分析工具提供了可直接复用的参考实现——完整代码位于 01-Kimi-VL-对话助手/app可随时对照 README.md 查阅更细致的功能说明。【免费下载链接】self-llm《开源大模型食用指南》针对中国宝宝量身打造的基于Linux环境快速微调全参数/Lora、部署国内外开源大模型LLM/多模态大模型MLLM教程项目地址: https://gitcode.com/GitHub_Trending/se/self-llm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表