ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Civitai 模型合并(Model Merge)设计指南:跨 Model 迁移 ModelVersion 与历史数据折叠的完整方案

Civitai 模型合并(Model Merge)设计指南:跨 Model 迁移 ModelVersion 与历史数据折叠的完整方案 Civitai 模型合并Model Merge设计指南跨 Model 迁移 ModelVersion 与历史数据折叠的完整方案【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai本文基于仓库内设计文档 docs/model-merge-plan.md状态设计阶段尚未实现撰写于 2026-08-25并结合当前仓库源码与测试逐一印证。文章面向希望理解如何把一个Model的版本与全部历史数据并入另一个Model这一系统级操作的开发者你将掌握 Postgres 各表的迁移策略分类、ClickHouse 为何不可变更、Engagement 折叠策略、tombstone 方案的边界以及一份可直接落地的构建计划与 Wan 案例实测数据。背景为什么需要合并模型这个操作在 API-only 生态即模型权重托管在外部 API、本平台仅承载卡片与生成入口的生态中每发布一个新版本就会新建一张模型卡片——Wan Video 2.5、Wan Video 2.7、Wan 2.7 Image等。这带来两个问题历史从零开始每张新卡片的关注数follower、评测review、评论comment全部清零用户与社区积累的反馈被割裂选择器泛滥生成器的模型选择器model picker中堆满近乎重复的卡片。期望的最终状态是每种媒体类型、每个系列只保留一张卡片——例如Wan Video (api)、Wan Image (api)——新版本直接落在该卡片上。要达成这个状态需要一个代码库目前不存在的操作把ModelVersion迁移到不同的Model并将源模型的历史数据折叠进目标模型。现状缺口为什么现有代码帮不上忙文档明确指出仓库中从未在创建后更新过ModelVersion.modelId。两个最接近的能力都存在硬边界mergeVersions明确拒绝跨模型合并见 src/server/services/model-version.service.ts#L3184。该函数只接受一个modelId且做了严格校验——targetVersionId与所有sourceVersionIds都必须属于同一个模型否则抛出throwBadRequestErrorTarget version does not belong to this model. / One or more source versions do not belong to this model.。它本质上是在同一模型内把多个版本的文件、描述折叠进目标版本并阻止带有效 monetization 或早购early access的版本被合并为保护买家资金这些读门直接从主库dbWrite读取而非走缓存副本。transferModelOwnership只改Model.userId见 src/server/services/model.service.ts#L5376。它把整批模型的归属转移给目标用户并联动迁移PaidAccess.ownerId付费访问的归属者、重挂受影响 Post/Image 的userId但不动版本归属。此外整个仓库不存在任何 redirect、alias 或 merge 表——没有Model.mergedIntoId之类的字段也没有可查询的合并记录。有利因素生成体系按版本version键控而非按模型键控合并方案能成立的关键前提是生成generation是按modelVersionId解析的而不是按modelId。文档特别澄清了一个容易误读的细节EcosystemSettings.defaults.model.id看起来像Model的 id但实际上存的是modelVersionId已对照数据库核实。在 packages/civitai-shared/src/basemodel.constants.ts 的ecosystemSettings中可以直观看到这种形态例如SD1生态的defaults: { model: { id: 128713 } }、SDXL的defaults: { model: { id: 128078 } }——这些 id 指向的是版本。每个生态条目、graph handler 以及getGenerationBaseModel的调用链都通过版本 id 解析因此把一个版本从源模型移到目标模型不会改变生成器的任何行为。两个例外需要手动处理硬编码的 Wan模型id 只存在于两个文件src/shared/constants/ecosystem-seo.constants.tsfeaturedModels 中的modelId/versionId对src/utils/training.ts一处 AIR 字符串内。任何一次合并都需要手工编辑这两处。另一个有利因素媒体类型是 base-model 属性。type: image | video挂在 base-model 记录上按版本解析同一家族的图像发布与视频发布天然应该分属两张卡片——常量层已经把它们建模为独立的生态ecosystem。Postgres 迁移清单一张表一种策略文档强调以下清单全部通过查询核实而非推断。核心决策规则只有一条当且仅当该表对modelId存在唯一约束、且没有任何外键引用该表的id时采用 insertdelete否则原地UPDATE。这条规则可以用查询校验而不是靠记忆。1. 直接UPDATEmodelId无唯一约束ModelVersion版本本身modelId 重排indexCommentResourceReviewVaultItem。⚠️ResourceReview.modelId是必填字段且极易遗漏它只在 upsert 时写入一次、从不重算。漏掉它模型的评分就会永远记在源模型头上。2.INSERT … ON CONFLICT DO NOTHING再DELETEmodelId有唯一约束批量UPDATE会撞唯一约束而失败而这些表的 id 又没有外键引用可以安全地插新删旧表唯一键冲突处理ModelEngagement(userId, modelId)见下文 engagement 政策SavedModel(modelId, userId)跳过重复ModelInterest(userId, modelId)跳过重复TagsOnModelsVote(tagId, modelId, userId)跳过重复TagsOnModels(modelId, tagId)跳过ModelTag分数从投票重算ModelReport(reportId, modelId)仅当一条举报同时覆盖两个模型时才冲突ModelBaseModelMetric(modelId, baseModel)冲突时求和不能丢弃ModelMetricDaily(modelId, modelVersionId, type, date)永不冲突——版本 id 互不相交在SELECT中要显式携带createdAt/addedById/note/status避免列默认值重写历史。3.CollectionItem会咬人的例外CollectionItemScore.collectionItemId引用CollectionItem(id)且为ON DELETE CASCADE——如果对CollectionItem采用插入新行 删除旧行大赛评审分数会被静默摧毁不会报任何错级联直接带走。推荐处理不冲突的行原地UPDATE保住 id分数随之保留只有冲突的行才 insertdelete对冲突行断言CollectionItemScore为空否则拒绝合并。4. 永不 insertdelete有东西挂在 id 上ResourceReviewResourceReviewReaction、ResourceReviewReport、Thread.reviewId都引用它CommentComment.parentId自引用还有CommentReaction、CommentReport。两者对modelId都没有唯一约束规则一已自动把它们路由到UPDATE。5. 需要人工介入一行一模型没有合并语义表约束为什么棘手ThreadUNIQUE(modelId)结构性硬阻塞两个根线程无法共存合并意味着重挂CommentV2行、重写rootThreadId/parentThreadId链ModelDescriptionPK(modelId)两份描述只能活一份ModelFlagPK(modelId)审核状态需要按位或而非二选一静默丢弃一侧是安全回退Model行本身—poi、minor、nsfw、sfwOnly、allowCommercialUse、licenses、mode、status、availability、lockedProperties需要人工裁决。GenerationCoverage的谓词读取的是新父模型的m.poi/m.allowCommercialUse/m.type合并瞬间覆盖范围可能无声翻转6. 必须重新排队不能手工改这些都不会自愈ModelMetric其受影响集合查询以ModelVersionMetric.updatedAt为键而modelId变更永远不会触碰该时间戳所以两侧都不会重算——必须把两个 id 都推入 metric 队列Meilisearch 模型索引prepareBatches按Model.updatedAt选择同样不受影响——为两个 id 排队SearchIndexUpdateModelRank_New、Model.nsfwLevelupdateModelNsfwLevels、Model.lastVersionAtupdateModelLastVersionAtRedis 缓存dataForModelsCache、resourceDataCache、imageResourcesCache。所有这些必须在事务之外执行——仓库的no-io-in-transaction规则见 eslint-local-rules.js禁止在事务回调内做外部 I/O因为它会消耗事务超时预算。7. 免费项所有视图GenerationCoverage、GenerationCoverage2、ImageResourceHelper、PostResourceHelper、ModelHash、ModelTag、ModelReportStat——全部视图无需处理。ClickHouse不要动它ClickHouse 中只有7 张表带modelId其余全部以modelVersionId为键、随版本迁移自动跟随合并不改变版本 id表行数文档实测是否可变modelVersionEvents2.06B排序键含modelId禁ALTER UPDATEdaily_downloads156M同上daily_downloads_unique156M同上modelEngagements22.4M同上modelEvents6.49M同上resourceReviews1.13M同上daily_runs363K同上partnerEvents—唯一可变更但这不重要没有任何生产指标按modelId读 ClickHouse。ModelMetric是从ModelVersionMetric通过实时的ModelVersion.modelId联表重建的下载量按modelVersionId读daily_downloads_unique生成与收益按modelVersionId读orchestration.*。唯一按模型键的读取是评论发现comment discovery其计数来自 Postgres。因此ClickHouse 中陈旧的modelId只影响 ad-hoc 分析和仪表盘。在读取时解析而不是改写历史——那些事件行在写入时是正确的。Engagement 折叠政策2026-08-25 定稿的政策源Hide→丢弃绝不插入目标。宁可多展示用户可重新隐藏的内容也不静默隐藏一个用户从未隐藏、也无从发现的模型源Mute→ 同样丢弃理由相同源Notify/Favorite→ 携带ON CONFLICT DO NOTHING目标行永不修改如果有人隐藏了目标模型、同时关注了源模型目标的Hide保持原样、源关注被丢弃——与任何其他去重行为一致。⚠️关于no-pk-addressed-engagement-write守卫该守卫禁止.modelEngagement.delete/update/upsert(这种按主键寻址的写入要求deleteMany/updateMany在where中显式携带type——恰好是本政策需要的形态见 src/server/services/tests/no-pk-addressed-engagement-write.test.ts 中的用例如dbWrite.modelEngagement.deleteMany({ where: { userId, modelId, type } })通过、dbWrite.modelEngagement.delete({ where: { userId_modelId } })被拦截。但该守卫的解析器无法看到构建在变量中的where而 manifest 驱动的合并恰恰是这种形态。因此必须直接为 engagement 政策写测试不能把守卫变绿当作覆盖。为什么 tombstone墓碑不够Model.mergedIntoId自引用列值得保留——它服务于无法改写的入站引用书签、外部链接、ecosystem-seo.constants.ts中的 id、缓存了模型 id 的 API 客户端。成本仅为一列可空字段、无 join模型页本来就按 id 加载行。但它不能替代移动行。一个没有任何可见版本的模型会在四个独立点被静默丢弃model.service.ts 中无版本即弹出再次无首个版本即弹出再次无图片即弹出collection.service.ts 在水合hydration时丢弃该项。永远不会渲染出破损卡片——但这也意味着Liked Models 本身就是一个 Bookmark 模式的集合走同一条丢弃路径。把点赞留在墓碑上它会从唯一能移除它的 UI 中消失而ModelEngagement与CollectionItem行永远留存仓库中没有HiddenModelsSection——账户设置只覆盖隐藏标签和用户不覆盖模型。逃生舱user.toggleFavorite与user.toggleNotifyModel都是protectedProcedure只接受{ modelId }且toggleNotifyModel接受包括Hide在内的任意 engagement 类型见 src/server/controllers/user.controller.ts#L1020 与 src/server/routers/user.router.ts#L260因此搁浅的行可以无需页面、纯脚本清理。顺带发现的一个既有 bug与合并无关collection item 的 SQL 不 joinModelVersion导致项目在水合丢弃之前就分页——limit 1的游标数学跑在过滤前集合上。任何含无版本模型的集合都会出现短页、条目数与渲染不符。这个问题在今天独立存在。构建计划预估可复用方案约2–3 天一次性脚本加预检约1 天。部件工作量Manifest 执行器约半天预检 / 冲突报告约 2 小时失效invalidation约 1 小时Model.mergedIntoId 301约半天测试约半天到 1 天Manifest 必须由 schema 派生并被守卫覆盖。schema.full.prismapackages/civitai-db-schema/prisma/schema.full.prisma中有63 处modelId引用手写 manifest 只能覆盖今天的表没人告诉你在新增下一张表后合并会静默丢行。方案加一个约定测试convention test——解析 schema、找出每个带modelId字段的模型、断言其被分类为update/insertdelete/requeue/ignore/needs-human之一。这与no-pk-addressed-engagement-write是同一套路手工枚举的测试抓住已存在的表守卫抓住下一张。在同一提交中接入test:lint-rules该脚本定义于 package.json运行一组no-*约定测试。写审计 blob没有撤销undo。记录每个被移动和被删除的行、按合并键组织——被删除的 collection items 和被丢弃的Hide行否则消失后没有任何记录。放在主应用而不是apps/moderator合并约 30% SQL、70% 失效逻辑而所有失效 helper 都是主应用的 server 代码SvelteKit 页面调用它们都得回调主应用。将model.merge与model.mergePreflight构建为moderator-scoped 的 tRPC proceduresmoderator 应用页面日后可做薄调用层。先从 preflight 开始只读、零写入、可对任意模型对调用。它独立验证下文的数字并告诉你通用工具是否值得做完。Wan 案例实测2026-08-25目标形态1992179→ 重命名为Wan Video (api)2516180→Wan Image (api)。图像侧是纯重命名、零迁移。真正需要的合并只有一个2516027 → 1992179。1817671Wan Video 2.2携带真实权重保持原位不动。2516027 → 1992179的机械工作量1 个版本移动2828005、28 条评论、231 条评测、125 条 engagement17 条去重、187 个 collection items21 条去重、1 个标签、2 行ModelBaseModelMetric。这对模型的需人工类别全部为空阻塞项计数Thread行0——三个 Wan 模型的评论全在遗留Comment表ModelFlag0ModelDescription0冲突项上的CollectionItemScore0混合类型 engagement 对014 对 Notify/Notify、2 对 Hide/Hide、1 对 Mute/Mute分歧的Model策略列0——17 项检查全部一致同区域、与合并无关的已知数据异味WanImage27被归在familyId: 5注释为 Wan Video Family且ModelBaseModelMetric携带一条游离的Wan 2.7 Video正确的应为Wan Video 2.7。结论模型合并是一个一次性正确、否则历史静默丢失的系统级操作。本文梳理的决策规则唯一约束决定 update 还是 insertdelete、CollectionItemScore级联陷阱、ClickHouse 只读原则、engagement 折叠政策与 schema 派生 manifest 的守卫策略共同构成了可落地的合并框架。对 Wan 家族的实测表明多数场景是纯重命名或单次合并且关键阻塞项全部为空——先跑只读 preflight即可判定通用工具是否值得投入。【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表