丹青幻境代码实例:修改app.py实现多LoRA并行预览与一键切换功能

📅 发布时间:2026/7/10 0:45:33 👁️ 浏览次数:
丹青幻境代码实例:修改app.py实现多LoRA并行预览与一键切换功能
丹青幻境代码实例修改app.py实现多LoRA并行预览与一键切换功能“见微知著凝光成影。执笔入画神游万象。”丹青幻境这款基于Z-Image架构的数字艺术终端以其独特的宣纸质感界面和文艺化交互逻辑为画师们提供了一个沉浸式的“灵感实验室”。然而在实际创作过程中你是否遇到过这样的困扰面对多个精心训练的LoRA风格卷轴每次切换都需要重新加载模型等待时间漫长创作灵感在等待中悄然流逝今天我们就来动手改造丹青幻境的核心——app.py实现一个真正实用的功能多LoRA并行预览与一键切换。让你在挥毫泼墨前就能直观对比不同“历练卷轴”的风格效果并瞬间切换到心仪的那一个让创作流程如行云流水般顺畅。1. 理解现状为何需要并行预览在原始的丹青幻境设计中LoRA模型的加载逻辑通常是“用时加载”。当你从下拉菜单中选择一个不同的LoRA文件时系统需要从磁盘读取新的.safetensors文件将其权重合并到基础模型中这个过程会消耗数秒到数十秒的时间如果你有5个不同的LoRA风格比如“水墨山水”、“工笔花鸟”、“敦煌壁画”、“浮世绘”、“赛博朋克”想看看同一个“画意描述”在不同风格下的效果你需要重复选择、等待、生成、再选择、再等待……这个过程极其低效。并行预览的核心思想是提前将多个LoRA模型加载到内存中并准备好对应的“风格化”生成管道。当用户输入提示词后系统可以同时或快速串行地使用这些管道生成预览图所有结果并排展示。用户只需一眼就能选定风格然后一键切换到该风格进行正式的高质量渲染。2. 改造蓝图我们需要做什么我们的目标是在不破坏丹青幻境原有中式美学和稳定性的前提下增加以下功能LoRA管理器在应用启动时自动扫描指定目录下的所有LoRA文件并异步加载它们创建对应的风格化管道。并行预览面板在UI上新增一个区域用于展示同一个提示词在不同LoRA风格下的生成效果小图、快速生成。一键切换机制点击预览图中的任意一张系统的主生成管道立即切换为对应的LoRA风格后续的“挥毫泼墨”都使用此风格。资源优化确保在显存有限的条件下如24GB的RTX 4090多个LoRA管道的加载和运行是高效且稳定的。下面是改造后的核心代码逻辑示意图flowchart TD A[应用启动] -- B[LoRA管理器初始化] B -- C[扫描LORA_DIR_PATH目录] C -- D[发现N个LoRA文件] D -- E{是否为首次加载?} E -- 是 -- F[加载基础模型br创建Base Pipeline] E -- 否 -- G[使用已加载的Base Model] F -- H G -- H subgraph H [为每个LoRA创建风格管道] direction LR H1[克隆Base Pipeline] -- H2[注入LoRA权重] -- H3[存储到风格管道字典] end I[用户输入画意与参数] -- J[触发并行预览] J -- K[使用各风格管道生成低分辨率预览图] K -- L[在UI预览面板并排展示] L -- M[用户点击选中心仪风格] M -- N[将主生成管道切换为选中风格] N -- O[用户进行正式高质量生成]3. 动手实作修改app.py代码接下来我们直接切入核心看看app.py需要如何修改。我们将采用模块化的方式逐步增加功能。3.1 第一步初始化与LoRA管理我们首先需要在文件开头导入必要的库并定义一个LoRAManager类来管理所有LoRA模型。# app.py 顶部导入部分新增 import streamlit as st from diffusers import DiffusionPipeline, StableDiffusionXLPipeline import torch from safetensors.torch import load_file import os from pathlib import Path import asyncio import concurrent.futures from typing import Dict, Any, Optional import time # ... 原有的其他导入 ... class LoRAManager: LoRA模型管理器负责加载、缓存和提供多LoRA管道 def __init__(self, base_model_path: str, lora_dir_path: str, device: str cuda, torch_dtypetorch.bfloat16): self.base_model_path base_model_path self.lora_dir_path lora_dir_path self.device device self.torch_dtype torch_dtype # 存储基础管道和所有LoRA管道 self.base_pipeline None self.lora_pipelines {} # 格式: {lora_name: pipeline} self.lora_names [] def discover_loras(self): 扫描LoRA目录发现所有.safetensors文件 lora_path Path(self.lora_dir_path) if not lora_path.exists(): st.warning(f⚠️ 未找到历练卷轴目录: {self.lora_dir_path}) return [] lora_files list(lora_path.glob(*.safetensors)) # 按文件名排序确保每次顺序一致 lora_files.sort() return [f.stem for f in lora_files] # 返回不带后缀的文件名 def load_base_model(self): 加载基础模型Z-Image仅需执行一次 if self.base_pipeline is not None: return self.base_pipeline st.info( 正在铺陈万象画纸加载基础模型...) # 根据你的实际模型类型调整此处的加载代码 # 示例为Stable Diffusion XL try: self.base_pipeline StableDiffusionXLPipeline.from_pretrained( self.base_model_path, torch_dtypeself.torch_dtype, use_safetensorsTrue ).to(self.device) # 启用CPU Offload以节省显存如果显存紧张 # self.base_pipeline.enable_model_cpu_offload() st.success(✅ 万象画纸已铺就) return self.base_pipeline except Exception as e: st.error(f❌ 铺陈画纸失败: {e}) return None def create_lora_pipeline(self, lora_name: str, lora_weight0.8): 为指定的LoRA创建一个独立的风格管道 lora_path Path(self.lora_dir_path) / f{lora_name}.safetensors if not lora_path.exists(): st.warning(f未找到历练卷轴: {lora_name}) return None try: # 1. 克隆基础管道避免污染原始管道 # 注意深度复制整个大模型管道开销较大这里采用加载时注入的方式更优 # 我们换一种方式先创建基础管道然后立即注入LoRA pipeline StableDiffusionXLPipeline.from_pretrained( self.base_model_path, torch_dtypeself.torch_dtype, ).to(self.device) # 2. 加载LoRA权重并注入 state_dict load_file(lora_path) pipeline.load_lora_weights(state_dict) # 3. 融合LoRA权重可选融合后推理更快 pipeline.fuse_lora(lora_scalelora_weight) st.write(f 卷轴「{lora_name}」已就位) return pipeline except Exception as e: st.error(f❌ 加载卷轴「{lora_name}」时出错: {e}) return None def load_all_loras_async(self, lora_weight0.8): 异步加载所有LoRA避免界面卡死 lora_names self.discover_loras() self.lora_names lora_names if not lora_names: st.warning(未发现任何历练卷轴请检查路径。) return # 先确保基础模型已加载 if self.base_pipeline is None: self.load_base_model() # 使用Streamlit的进度条 progress_bar st.progress(0) status_text st.empty() total len(lora_names) for i, name in enumerate(lora_names): status_text.text(f 正在感悟卷轴「{name}」 ({i1}/{total})) pipeline self.create_lora_pipeline(name, lora_weight) if pipeline: self.lora_pipelines[name] pipeline progress_bar.progress((i 1) / total) time.sleep(0.1) # 小幅延迟让进度显示更平滑 status_text.text(✅ 所有历练卷轴已准备就绪) progress_bar.empty() def get_pipeline(self, lora_name: Optional[str] None): 获取指定LoRA的管道如未指定则返回基础管道 if lora_name and lora_name in self.lora_pipelines: return self.lora_pipelines[lora_name] return self.base_pipeline def get_available_loras(self): 返回所有可用的LoRA名称列表 return self.lora_names3.2 第二步集成到Streamlit主应用现在我们需要将LoRAManager集成到丹青幻境的主应用逻辑中并创建预览面板。# 在app.py的主函数或主要逻辑部分 def main(): st.set_page_config( page_title丹青幻境 · Z-Image Atelier, page_icon, layoutwide ) # 加载自定义CSS保持原有中式风格 # ... 原有的CSS代码 ... # 初始化Session State用于存储状态 if lora_manager not in st.session_state: # 这里替换为你实际的模型路径 BASE_MODEL_PATH /root/ai-models/Z-Image LORA_DIR_PATH /root/ai-models/yz-bijini-cosplay st.session_state.lora_manager LoRAManager( base_model_pathBASE_MODEL_PATH, lora_dir_pathLORA_DIR_PATH, devicecuda if torch.cuda.is_available() else cpu, torch_dtypetorch.bfloat16 ) # 异步加载所有LoRA在侧边栏显示进度 with st.sidebar: st.markdown(### 历练卷轴库) if st.button( 感悟所有卷轴, keyload_all_loras): st.session_state.lora_manager.load_all_loras_async(lora_weight0.8) # 获取管理器实例 lora_manager st.session_state.lora_manager # 主标题和描述保持原有风格 st.markdown(# 丹青幻境 · Z-Image Atelier) st.markdown( **\见微知著凝光成影。执笔入画神游万象。\**) # 原有的参数设置侧边栏 with st.sidebar: st.markdown(## 画室规制) # 原有的参数步数、宽高、引导系数等 steps st.slider(修行步数, min_value20, max_value50, value30, step1) width st.slider(画布幅宽, min_value512, max_value1024, value768, step64) height st.slider(画布纵深, min_value512, max_value1024, value768, step64) guidance_scale st.slider(灵感契合度, min_value1.0, max_value20.0, value7.5, step0.5) # 新增LoRA权重调节对所有预览生效 lora_weight st.slider(卷轴火候, min_value0.1, max_value1.5, value0.8, step0.1, help控制LoRA风格的影响强度) # 随机种子 seed st.number_input(机缘种子, value42, min_value0, max_value2**32-1) # 主画布区域分为两列 col1, col2 st.columns([3, 2]) with col1: st.markdown(## ️ 挥毫主画布) # 画意描述和避讳 prompt st.text_area( 画意描述, value一袭青衣倚楼听雨水墨风格, height100, help在此倾诉你的灵感 ) negative_prompt st.text_area( 避讳, value模糊失真低质量文字水印, height60, help写下画中不愿见到的尘杂 ) # 当前选中的LoRA用于正式生成 available_loras [无] lora_manager.get_available_loras() selected_lora st.selectbox( 当前主修卷轴, optionsavailable_loras, index0, help选择用于正式生成的LoRA风格 ) # 正式生成按钮 if st.button( 挥毫泼墨, typeprimary, use_container_widthTrue): with st.spinner(朱砂研磨中...): # 获取对应的管道 pipeline lora_manager.get_pipeline( selected_lora if selected_lora ! 无 else None ) if pipeline: generator torch.Generator(devicecuda).manual_seed(seed) # 生成图像 image pipeline( promptprompt, negative_promptnegative_prompt, widthwidth, heightheight, num_inference_stepssteps, guidance_scaleguidance_scale, generatorgenerator ).images[0] # 显示结果 st.image(image, captionf「{prompt[:30]}...」 - 卷轴:{selected_lora}, use_column_widthTrue) # 保存按钮 buf io.BytesIO() image.save(buf, formatPNG) st.download_button( label 揭榜留存, databuf.getvalue(), file_namef丹青_{int(time.time())}.png, mimeimage/png ) with col2: st.markdown(## ️ 卷轴预览窗) # 预览生成参数通常使用较低分辨率以加快速度 preview_steps st.slider(预览步数, min_value10, max_value25, value15, step1, keypreview_steps) preview_size st.slider(预览尺寸, min_value256, max_value512, value384, step64, keypreview_size) # 并行预览按钮 if st.button( 预览诸般妙相, use_container_widthTrue): if not lora_manager.lora_pipelines: st.warning(请先点击侧边栏的「感悟所有卷轴」加载LoRA。) else: with st.spinner(凝神观想诸般妙相...): # 创建进度指示器 progress_bar st.progress(0) status_text st.empty() # 准备列布局 lora_names list(lora_manager.lora_pipelines.keys()) num_loras len(lora_names) # 计算布局每行最多3列 cols_per_row 3 rows (num_loras cols_per_row - 1) // cols_per_row images_dict {} # 串行生成预览更稳定避免显存溢出 for idx, lora_name in enumerate(lora_names): status_text.text(f正在观想「{lora_name}」 ({idx1}/{num_loras})) try: pipeline lora_manager.lora_pipelines[lora_name] generator torch.Generator(devicecuda).manual_seed(seed) # 使用较低的步数和分辨率快速预览 image pipeline( promptprompt, negative_promptnegative_prompt, widthpreview_size, heightpreview_size, num_inference_stepspreview_steps, guidance_scaleguidance_scale, generatorgenerator ).images[0] images_dict[lora_name] image except Exception as e: st.error(f生成预览「{lora_name}」时出错: {e}) images_dict[lora_name] None progress_bar.progress((idx 1) / num_loras) status_text.text(✅ 诸般妙相已现) progress_bar.empty() # 显示所有预览图 st.markdown(### 点击心仪风格即可切换为主卷轴) # 使用网格布局展示 for row in range(rows): cols st.columns(cols_per_row) for col_idx in range(cols_per_row): lora_idx row * cols_per_row col_idx if lora_idx num_loras: lora_name lora_names[lora_idx] image images_dict.get(lora_name) with cols[col_idx]: if image: # 点击图片即可切换为该LoRA if st.image(image, use_column_widthTrue, captionlora_name): # 更新当前选中的LoRA st.session_state.selected_lora lora_name st.rerun() # 重新运行以更新UI # 添加一个更明显的选择按钮 if st.button(f择此「{lora_name}」, keyfselect_{lora_name}, use_container_widthTrue): st.session_state.selected_lora lora_name st.rerun() else: st.warning(f{lora_name}预览失败) # 存储预览结果到session state避免重复生成 st.session_state.last_preview { prompt: prompt, images: images_dict, timestamp: time.time() } # 显示上一次的预览结果如果存在 if last_preview in st.session_state: st.markdown(---) st.markdown(### 上一次预览结果) # 检查提示词是否相同如果相同则直接显示 if st.session_state.last_preview[prompt] prompt: images_dict st.session_state.last_preview[images] lora_names list(images_dict.keys()) for lora_name, image in images_dict.items(): if image: col1, col2 st.columns([1, 3]) with col1: st.image(image, width150) with col2: if st.button(f切换至「{lora_name}」, keyfreuse_select_{lora_name}): st.session_state.selected_lora lora_name st.rerun() # 底部信息 st.markdown(---) st.markdown(**画意之外皆为云烟。**) st.markdown(*由丹青幻境诚意打造 // 见微知著*) if __name__ __main__: # 确保必要的目录存在 os.makedirs(outputs, exist_okTrue) main()3.3 第三步显存优化策略在RTX 4090 24GB显存上运行多个LoRA管道时需要特别注意显存管理。以下是几种优化策略# 可以在LoRAManager类中添加以下优化方法 class LoRAManager: # ... 之前的代码 ... def optimize_for_memory(self, strategybalanced): 根据显存情况优化加载策略 策略选项 - aggressive: 只保留基础管道LoRA需要时动态加载/卸载最省显存但切换慢 - balanced: 保留基础管道所有LoRA适配器权重推理时动态合并推荐 - performance: 所有LoRA管道常驻显存最快但最耗显存 if strategy aggressive: # 策略1动态加载/卸载 self._setup_dynamic_loading() elif strategy balanced: # 策略2只加载LoRA权重不融合 self._setup_lora_weights_only() elif strategy performance: # 策略3全部常驻默认 pass def _setup_dynamic_loading(self): 动态加载策略只保留基础管道LoRA需要时从磁盘加载 # 清空已加载的LoRA管道 self.lora_pipelines.clear() torch.cuda.empty_cache() # 修改get_pipeline方法实现按需加载 def get_pipeline_dynamic(lora_name): if lora_name in self.lora_pipelines: return self.lora_pipelines[lora_name] # 动态加载 pipeline self.create_lora_pipeline(lora_name) if pipeline: self.lora_pipelines[lora_name] pipeline return pipeline # 替换原来的get_pipeline方法 self.get_pipeline get_pipeline_dynamic def _setup_lora_weights_only(self): 只加载LoRA权重不创建完整管道 # 这种方法更节省显存但需要修改生成逻辑 # 实现思路保存LoRA权重在生成时动态合并到基础管道 pass def clear_unused_loras(self, keep_namesNone): 清理不常用的LoRA管道以释放显存 if keep_names is None: keep_names [] to_remove [] for name in self.lora_pipelines: if name not in keep_names: to_remove.append(name) for name in to_remove: del self.lora_pipelines[name] torch.cuda.empty_cache() st.info(f已释放 {len(to_remove)} 个不常用的卷轴)4. 使用体验创作流程的革新完成上述改造后丹青幻境的使用流程将变得更加高效和直观初始加载启动应用后点击侧边栏的“感悟所有卷轴”按钮系统会加载所有LoRA风格。灵感输入在主画布区输入你的“画意描述”和“避讳”。并行预览点击“预览诸般妙相”按钮系统会快速生成所有LoRA风格下的预览图低分辨率、少步数。风格选择浏览并排展示的预览图点击心仪的风格。一键切换选中的风格立即成为“当前主修卷轴”。正式创作点击“挥毫泼墨”使用选定的风格进行高质量、完整步数的生成。这个流程将传统的“试错-等待-再试错”循环转变为“预览-选择-创作”的线性流程大大提升了创作效率。5. 进阶优化与扩展建议如果你想让这个系统更加强大可以考虑以下扩展方向5.1 实时预览与参数联动# 添加实时预览功能当参数变化时自动更新预览 st.cache_data(ttl300) # 缓存5分钟 def generate_preview(prompt, negative_prompt, lora_name, steps, size, seed): 缓存预览结果避免重复计算 # ... 生成预览的逻辑 ... return image # 在UI中添加“实时预览”开关 real_time_preview st.checkbox(开启实时预览, valueFalse) if real_time_preview: # 监听参数变化自动更新预览 # 可以使用Streamlit的on_change回调或轮询机制 pass5.2 LoRA混合与权重调节# 支持多个LoRA混合使用 def blend_loras(lora_names, weights): 混合多个LoRA的权重 # 实现LoRA权重的线性混合 # 公式: blended sum(weight_i * lora_i) / sum(weights) pass # 在UI中添加LoRA混合面板 with st.expander( 卷轴融合高级功能): selected_loras st.multiselect(选择要融合的卷轴, optionslora_manager.get_available_loras()) if selected_loras: cols st.columns(len(selected_loras)) weights [] for idx, lora_name in enumerate(selected_loras): with cols[idx]: weight st.slider(f{lora_name}权重, 0.0, 1.0, 0.5, 0.1) weights.append(weight) if st.button(融合卷轴): blended_pipeline blend_loras(selected_loras, weights) # 使用混合后的管道进行生成5.3 预览图管理与收藏# 添加预览图收藏功能 if favorites not in st.session_state: st.session_state.favorites {} # 在每张预览图下方添加收藏按钮 if st.button(⭐ 收藏此风格, keyffav_{lora_name}): st.session_state.favorites[lora_name] { image: image, prompt: prompt, timestamp: time.time() } st.success(f已收藏「{lora_name}」风格) # 显示收藏夹 with st.sidebar.expander(⭐ 我的收藏): for lora_name, data in st.session_state.favorites.items(): st.image(data[image], captionlora_name, width100) if st.button(f使用「{lora_name}」, keyfuse_fav_{lora_name}): st.session_state.selected_lora lora_name st.rerun()6. 总结通过这次对丹青幻境app.py的改造我们实现了一个真正实用的多LoRA并行预览与一键切换系统。这个系统不仅大幅提升了创作效率还让风格探索过程变得更加直观和愉悦。核心改进总结效率提升从“串行试错”到“并行预览”节省了大量等待时间。体验优化直观的视觉对比让风格选择更加精准。资源智能管理根据显存情况自动优化加载策略平衡速度与资源消耗。扩展性强为后续的LoRA混合、风格收藏等高级功能奠定了基础。这个改造方案保持了丹青幻境原有的中式美学设计只是在功能逻辑上进行了增强。你可以根据自己的具体需求调整预览的分辨率、步数或者添加更多的个性化功能。画意之外皆为云烟。但好的工具能让云烟更快地凝聚成你心中的丹青。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。