山东大学创新项目实训(4)AIGC架构设计、LoRA训练模型搭建与可视化故事板层构想

本周工作:本周开始AIGC部分的工作。我负责对框架的搭建、LoRA训练模型的搭建以及其中可视化故事板层的实现。其中框架搭建、训练模型搭建已完工,可视化故事板层的实现正在进一步开发中。

本周迎来了我们项目最重要的部分:AIGC模块,也是我个人觉得最难的部分。为此,我查阅了很多资料和文献、也看了很多教学视频。

一、AIGC模块架构

我们项目的基础功能是用户选择几个单词后,DeepSeek生成和这几个单词有关的一个英文小故事,然后生成四页的漫画分镜描述,将这些信息传给微调过的Stable Diffusion模型,然后由Stable Diffusion模型生成一幅四格漫画,虽然看着这个功能似乎挺简单的,但是在实际开发过程中,我还是费了较大的精力。一开始设计的架构是这样的:

我初步的想法是直接在领域层实现两个模型的交互,一边接收DeepSeek生成的故事,然后将故事直接用作Stable Diffusion模型的prompt。但是这样简单地传递整个故事给Stable Diffusion效果非常差,因为Stable Diffusionm模型更擅长处理一系列单独的词,而不是自然语言。

于是我考虑将领域层分解为两个模块,一个更负责接收完整故事,并将其拆解为四个分镜描述,一个负责将四个描述转换为四个prompt表单,传给Stable Diffusion模型。但是,我很快发现,将拆解为四个分镜描述这里必须再次调用Deepseek API实现,这样的话这个模块将涉及多次与Deepseek的交互,并且故事生成和图像生成的处理混在一起,Story → Prompt 类似的逻辑在多个地方反复写了好几遍,非常不清晰。

于是我想到,需要单独的一层负责把故事变成镜头语言。可以在领域层和表示层之间加一层“可视化故事板层”,专门处理语义理解到视觉语境的转换,即Story → Prompt。这一层将负责调用Deepseek接口,拆分故事为四个分镜描述,并将描述转换为供Stable Diffusion使用的prompt。这样一来,可视化数据板层充当了“翻译官”的角色,Deepseek写的故事Stable Diffusion看不懂,可视化数据板层作为翻译官把故事转成“画面描述”。

解决了prompt的问题后还需要解决绘图风格问题,Stable Diffusion基底模型具有不稳定的特点,想要保证每次画出来的四幅漫画风格、人物特征一致,就需要用到LoRA微调技术。

大体思路为预训练好若干漫画风格和人物特征的LoRA模型,并将其存放于服务器数据库中,每次选用固定的一个风格模型和几个人物模型生成一组四幅漫画。在Stable Diffusion标准工作流模型的基底模型加载层和CLIP层之间添加一层LoRA层,专门负责处理这一逻辑。

 完整架构:

二、LoRA训练与推理模型搭建

1.训练模型

训练模型下载:

kijai/ComfyUI-FluxTrainer

kijai/ComfyUI-KJNodes: Various custom nodes for ComfyUI

rgthree/rgthree-comfy: Making ComfyUI more comfortable!

载入训练数据:使用数据添加器载入训练数据,支持不同分辨率

载入模型并初始化:载入Flux模型,设置统一桶分辨率,并在StringConstantMultiline模块中设置prompt

训练:使用LoRA模型迭代训练,并在每500次迭代完后预览图像

完整工作流:(2000次迭代)

2.推理模型

只需要在标准工作流(详情见山东大学创新项目实训(2)本地部署Stable Diffusion模型-CSDN博客)的加载层和CLIP层之间添加一层LoRA层:

参考:

DiffSensei: Bridging Multi-Modal LLMs and Diffusion Models for Customized Manga Generation

使用 ComfyUI 进行本地 FLUX.1 LoRA 的训练 - 知乎

一文读懂:LoRA实现大模型LLM微调-阿里云开发者社区

三、数据层实现

定义API交互逻辑:

interface DeepSeekApiService {
    @POST("generate_story")
    suspend fun generateStory(@Body request: StoryRequest): StoryResponse
}

data class StoryRequest(
    val keywords: List<String>
)

data class StoryResponse(
    val story: String
)

class DeepSeekRemoteSource(private val apiService: DeepSeekApiService) {
    suspend fun getStory(keywords: List<String>): StoryResponse {
        return apiService.generateStory(StoryRequest(keywords))
    }
}

调用Deepseek API:

class DeepSeekService(
    private val apiBase: String = "http://10.2.8.77:3000/v1",
    private val apiKey: String = "sk-xxxxxxx",
    private val model: String = "DeepSeek-R1"
)

网络通信逻辑:

private val client = OkHttpClient.Builder()
        .connectTimeout(60, TimeUnit.SECONDS)
        .readTimeout(60, TimeUnit.SECONDS)
        .build()
    fun streamResponse(prompt: String): Flow<String> = callbackFlow {
        val url = "$apiBase/chat/completions"
        val payload = JSONObject().apply {
            put("model", model)
            put("messages", JSONArray().apply {
                put(JSONObject().apply {
                    put("role", "user")
                    put("content", prompt)
                })
            })
            put("temperature", 0.7)
            put("max_tokens", 1024)
            put("stream", true)
        }
        val request = withContext(Dispatchers.IO) {
            Request.Builder()
                .url(url)
                .header("Authorization", "Bearer $apiKey")
                .header("Content-Type", "application/json")
                .post(payload.toString().toRequestBody())
                .build()
        }
        val eventSourceFactory = EventSources.createFactory(client)
        val finalResponse = StringBuilder()  
        val eventSourceListener = object : EventSourceListener() {
            override fun onEvent(
                eventSource: EventSource,
                id: String?,
                type: String?,
                data: String
            ) 

四、领域层实现

功能封装:

interface GenerateComicUseCase {
    suspend fun execute(keywords: List<String>): ComicResult
}

data class ComicResult(
    val story: String,
    val frames: List<StoryboardFrame>
)

class StoryboardProcessor {
    fun processStory(story: String): List<StoryboardFrame> {

        ...

    }
}

接收完整故事:

class GenerateComicUseCaseImpl(
    private val deepSeekRemoteSource: DeepSeekRemoteSource,
    private val storyboardProcessor: StoryboardProcessor,
    private val sdApiService: StableDiffusionApiService
) : GenerateComicUseCase {
    override suspend fun execute(keywords: List<String>): ComicResult {
        val storyResponse = deepSeekRemoteSource.getStory(keywords)
        val frames = storyboardProcessor.processStory(storyResponse.story)
        val images = sdApiService.generateImages(frames)
        return ComicResult(storyResponse.story, frames)
    }
}

五、可视化故事板层的实现

开发中...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dt23333

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值