Civitai 付费模型加载Paid Model Loading决策档案解析从开放问题到已定方案【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitaiCivitai 的付费模型加载Paid Model Loading功能允许任何模型被加载进生成集群并保证 48 小时驻留而本文剖析的是该功能最关键的“决策注册表”——docs/features/paid-model-loading-decisions.md。这份文档把每一个尚未拍板的开放问题集中在一处明确标注「谁拍板、什么条件算关闭」防止用户在功能上线后替团队发现漏洞。读完本文你将理解该功能全部决策项的来龙去脉、速率限制与覆盖模型的真实数字、以及哪些判断已经固化进src/下的源码。决策档案的定位契约、清单与覆盖模型的分工在 Civitai 仓库中付费模型加载Phase A即服务端管线加一个 mod-only 测试页已经构建完成四份文档各司其职文档角色paid-model-loading.md功能契约与已定决策orchestrator 契约、信号路径、速率限制paid-model-loading-checklist.md工作状态清单Phase 0–3、orchestrator 状态核对paid-model-loading-coverage.md覆盖模型coverage model及其审计数据paid-model-loading-decisions.md本文主题所有仍然开放的决策档案里只存放真正开放的问题已经回答的归入「Decided, do not relitigate」已定案不再复议属于工作而非判断的进入 checklist。每个开放条目带一个dev:块一句话陈述问题并留出就地回答的空间——就地回答即可不需要开会没有回答的条目团队会为它发布一个默认值而每条都写明了默认值是什么。关键背景Phase A 没有阻塞项。§1 与 §2 的大部分问题已于 2026-09-08 由 Justin 回答仍开放的只有两个构建问题、Phase A 的追认项、以及一个留给 Koen 的新问题K3。§1 阻塞上线的问题六条全部已答1.1 无RentCivit许可证的模型——拒绝Justin 的原话「没有RentCivit许可证的模型我们目前允许站内生成吗我假设不允许。如果RentCivit为 false 就应该拒绝。」数据证实主路径是对的零个被覆盖的版本缺少RentCivit——按分支逐一测量、再对视图做端到端核对均如此。实现上选择「拒绝任何不在GenerationCoverage中的内容」而非直接检查许可证两者今天选出的是同一集合但视图还携带了两个跳过许可证检查的分支因此以覆盖为门槛等于继承规则本身而不是保留对规则的第二种意见。细节见 paid-model-loading-coverage.md。1.2 永远加载不完的负载——退款Justin「对失败的加载退款不过这应该通过 orchestrator 发生。」⚠️后半句是假设不是已确认的行为。CalculateCost今天返回零所以没有任何 prepare 被收过费退款路径从未运行过。orchestrator 是否会对失败或超时的prepareResource退款是 K3 的问题并且必须在定价上线之前回答——而不是之后。1.3 我们承诺什么——最初的 48 小时Justin「『另一个 controller 持有它』意味着模型仍下载在我们的服务器上、可用于生成。我认为最初的承诺就够了。」这个解读让驱逐eviction的顾虑变得无害副本可以移动可用性不会消失。因此表面文案承诺的就是最初设计的 48 小时。这关闭了 2026-09-07 因 Koen 的回答表明「驻留residency确实存在」而重新打开的问题。1.4 进度信号发给买家本人而不是发到 topic2026-09-08 拍板修正了文档先前记录的设计。加载回调原本指向model-version:id信号的组group任何在关注该模型的人都会收到进度。 这有泄漏风险orchestrator 直接把它的WorkflowStepEvent投递到 signals 服务——我们不在路径中间、无法改写它——而workflowId是userId-timestamp见workflowOwnerId。组广播会告诉每个关注该模型的人谁为这次加载付了钱。回调现在指向/users/{userId}/signals/并由一个测试钉死断言 URL 包含/users/且不含/groups/。对应实现见 orchestrator.utils.ts 中的getResourceLoadCallbacksexport function getResourceLoadCallbacks(userId: number): ArrayWorkflowCallback | undefined { if (!env.SIGNALS_ENDPOINT) return; return [ { url: ${env.SIGNALS_ENDPOINT}/users/${userId}/signals/${SignalMessages.ResourceLoadUpdate}, type: [step:*], }, ]; }后果旁观者收不到实时进度改为在就绪时被告知见 1.5。1.5 通知是返回时的 toast真正的通知属于 Phase 22026-09-08 拍板。浏览器把正在等待的负载放在localStorage里——包括它自己发起的和它选择关注的。每次页面加载都会排空drain这个队列已完成的弹出一条必须手动关闭的 toast 然后移除已不可能完成的移除其余保持订阅。排空逻辑挂在应用全局因此无论用户下次落在哪个页面完成的加载都会被报告。让「它总是会排空」成立的关键是上限ceiling。「完成 / 消失 / 加载中」不覆盖「失败」或「完成后被驱逐」的负载——两者都读回为unavailable与「排队中」无法区分。没有期限这种条目会被永久重新订阅。条目在 48 小时过期与驻留策略一致。配套实现是src/store/resource-load.store.ts持久化、自排空、48h 上限和挂在AppHeader的ResourceLoadDrain。已接受限制这只能在用户回来时、在该浏览器里触达他。不同设备或被清空的浏览器什么也收不到——这是可接受的Justin2026-09-08。触达不再回来的用户是Phase 2的目标——为需要的用户提供 API 级通知。1.6 已购加载有持久记录而且不在我们这边2026-09-08 拍板。submitResourceLoad给每个负载打上resource-load标签queryWorkflows({ token, tags })返回该用户的工作流——持久、跨设备、无需站点侧存储。所以localStorage不是「买了什么」的记录它只是某个浏览器「在关注什么」的列表。Redis 曾被考虑并否决必须存活数小时、还要驱动通知的东西不该放在可被驱逐的存储里。这在 resource-load.service.ts 的submitResourceLoad中可见——提交时携带tags: [resource-load]。⚠️getMyLoads流程尚未构建且有一个未知数orchestrator 保留已完成工作流多久这决定了该列表能往回看多远。值得与 K3 一起问 Koen。§2 阻塞具体构建工作的问题2.1 C14——从 mod 测试页开始Justin「我们从我要的测试页开始。这个页面允许我一个 mod请求加载模型、查看已加载的模型、并在加载时获得状态更新。这应该已经被记录下来了。」它已经构建并记录在案——/moderator/resource-load对应 checklist 中的 Phase 1.5。所以 C14 由一个已存在的东西回答不需要独立的 demo 客户端也不需要平台级铺开。2.2 只支持 Checkpoint——不是LoRA 优先Justin「模型加载只适用于 checkpoints。由于模型体积的原因checkpoints 有独立的加载系统。LoRA 通常不够大不用担心。」这推翻了本文档集此前携带的建议。LoRA 优先的论点LoRA 不需要改视图、因此是最小的切片解决的是错误的问题体积才是加载器存在的原因而 LoRA 没有这个问题。文档集中所有「LoRA 优先」的建议都是错的已被移除。后果2.3 不是可选项而是关键路径。2.3CoveredCheckpoint退出历史舞台Justin「理论上 coveredCheckpoint 不应再影响生成。如果 CoveredCheckpoint 只用于生成那它就应该消失。所以 checkpoint 模型的 canGenerate 不应再以拍卖系统的 CoveredCheckpoint 为条件。」条件成立CoveredCheckpoint有四个用途、全部属于生成其中getCheckpointGenerationCoverage是零调用者的死代码。移除它作为合取项 conjunct使被覆盖的 checkpoint 扩大约两个数量级——数字见 coverage。⚠️ 后续审计发现相邻的表是相反情形EcosystemCheckpoints必须保留因为 63 个 checkpoint 默认模型中有 62 个经由它覆盖、经由CoveredCheckpoint的为零。详见 coverage 文档。⚠️2026-09-09 修订。CoveredCheckpoint不再作为合取项——Justin 的条件成立——但它在迁移20260909180000_generation_coverage_next_safetensor_checkpoints中作为**析取项disjunct**回归豁免 6 个拍卖驻留 checkpoint 不满足新的 SafeTensor 要求。拍卖成员身份不再决定覆盖它只是充当驻留的替身直到 C11 退役该任务然后随之删除。2.7 Diffusers 可加载——除了 checkpointsJustin 于 2026-09-08 裁定 Diffusers 可加载第一个GenerationCoverageNext迁移把它从每个类型的排除格式列表中移除。但加载器只服务 SafeTensor因此 checkpoint 分支于 2026-09-09 被收窄——174 个 Diffusers checkpoint 落在失去覆盖的 2,242 个版本之中。Diffusers 对 LoRA/TI/VAE/LoCon/DoRA/Upscaler 不受影响。开放Justin 是否接受这次收窄作为实现约束还是希望加载器长出 Diffusers 支持。拍板人Justin。关闭条件他在此处回答或迁移带着收窄原样应用到生产。dev:加载器今天只能服务 SafeTensor。OK 把 Diffusers/GGUF/PickleTensor checkpoints 从覆盖中剔除2,242 个版本占 checkpoint 生成的 0.43%还是让加载器学会它们在源码层面这一规则体现为 resource-load.service.ts 的checkLoadable权重文件类型走LOADABLE_FILE_TYPES [Model, Pruned Model, Diffusion Model, UNet, Negative, VAE]允许列表format是自由文本且经常未设置所以格式用允许列表、拒绝列表无法承诺加载器只见到 SafeTensor并且仅在modelType Checkpoint时强制metadata?.format SafeTensor。拒绝原因通过UNLOADABLE_MESSAGESresource-load.schema.ts区分no-weights与unsupported-format两种话术——前者是「外部提供方运行、没有可加载文件」后者是「生成器只能加载 SafeTensor而该版本没有」。2.4 C4 webhook——现在不做2026-09-08 关闭不做。它的两个存在理由同一天消失直接回调 signals 无法做的两件事——触发完成通知C9以及让旁观者看到负载而不泄露谁付了钱——现在都不需要一跳C9 是Phase 2目标旁观者不需要实时进度他们只需在下一次访问时被告知就绪而 localStorage 排空通过getState询问做到了。不构建它还避免了一个每 10 秒、跨越整个集群、每个进行中的下载都会触发的端点。⚠️Phase 2 到来时它会回来。真正的通知必须从某个地方发出而那个地方是本功能否则不具备的服务端时刻。届时重新打开此问题而不是发明第二个机制。2.5 速率限制数字差一off-by-one要做的决定配置数字应该读作多少。rateLimit()比较attempts limit所以当前的 3 / 6 / 10 实际允许4 / 7 / 11。共享的比较逻辑「不能在此处修复」是已定案要在两个剩余选项中取哪个没有定。选项写成 2 / 5 / 9 使实际上限为 3 / 6 / 10或保留数字并公开记录「3 意味着 4」。建议保留数字写下来。这些档位故意很低、本就要上调此量级的差一只是噪音——但未记录的差一是以后的坑。源码确认了这一判断resource-load.router.ts 的注释明确写着「The comparison isattempts limit, so every nonzero number permits one more than it says — 3/hour is really 4 (limit: 0short-circuits and is exact).」负责人关闭 C10 的人。关闭条件数字被重编号或做出保留的决定。2.6 常量与GenerationBaseModel之间的 17 个基础模型缺口要做的决定basemodel.constants.ts与数据库哪个错了。17 个基础模型在常量里声明了生成支持、却没有GenerationBaseModel行表中还有 5 行是常量未声明的。这 17 个今天仅因EcosystemCheckpoints覆盖了它们的默认模型而可生成——允许列表从未了解到它们另一张表悄悄补偿了。完整清单见 coverage。它影响什么对付费加载没有影响——付费加载无论如何都以GenerationBaseModel为门槛。它重要是因为两个事实来源不一致且无人检测——一个「guard 形」问题。负责人无主。关闭条件行被补上或常量停止声称生成支持或有测试把两者钉在一起。§3 Phase A 期间已在代码里定下的事——追认或推翻这些已经活在已构建的代码里且都未被既有文档覆盖。每一条都是为继续推进而做的判断现在推翻都不贵一旦有表面依赖它们就都变难了。3.1 第五个状态unknown当 orchestrator 回答本构建不认识的状态、或完全没有availability时服务报告unknown而不是并入unsupported。购买路径对两者都拒绝。为什么合并会使「集群永远不会托管它」与「我们读不懂回答」无法区分。前者是永久的后者可以重试而支持会话需要区分二者。在 resource-load.schema.ts 中体现为ResourceLoadAvailability ResourceAvailability | { status: unknown }并在parseAvailability中解析失败即回退unknown。3.2estimate返回{ cost, priced }当 orchestrator 报价为零时即 C2 之前一直都是priced为 false。为什么没有它真实的零与占位符的零渲染完全相同——这正是「免费模型加载」会意外上线的样子。mod 页面在priced: false时显示警告。实现见estimateResourceLoadreturn { ...state, cost, priced: cost 0 }。3.3 哪个 Buzz 账户付钱submit和estimate传递currencies: getAllowedAccountTypes(ctx.features)——与拍卖路径相同的推导因此在 green 上域货币是正确的。为什么它是决策它在任何地方都没有被规定。如果付费加载本应只允许从特定账户类型支付这里就是需要改的一行。resource-load.router.ts 中estimate与submit两个 mutation 均使用该推导。dev:这三条都活在代码里、今天推翻都很便宜。说「行」它们就不再是决策否则指名是哪一条。§4 留给 Koen 的问题C2——定价唯一还留在 Koen 手里的东西。PrepareResourceHandler.CalculateCost返回空成本所以whatIf报告 0、站点的估价流程没有数字可显示。购买路径上的其他一切都已构建完毕、只等它。这不是决策也不是新请求——记录在此是为了让这份文件展示他持有的全部。2026-09-01 的一段邻题上下文给追查的人「整个 orchestrator 的定价部分是一团乱麻、很多功能层层叠加让我非理性地不愿碰它但我同意你的推理会把它放进清单。」K3 orchestrator 是否自动退还失败的 prepareJustin 决定「永不完成的加载要退款」并期望 orchestrator 来做「不过这应该通过 orchestrator 发生」。没有任何东西确认这一点CalculateCost返回零所以没有 prepare 被收过费、退款从未被实践过。PrepareResourceJob有 24 小时的MaxTimeout而实验室通话测得的带宽下大型 checkpoint 很可能触及它。它影响什么退款是 orchestrator 的还是我们的。如果是我们的那是未界定范围的工作必须与定价一起落地而不是之后。dev:Koen —— 当prepareResource步骤失败或撞上 24h 超时费用是自动退还还是消费者必须自行撤销与 C2 同一个对话因为在 prepare 真正开始计费之前两者都无法被观察。已回答K1 与 K22026-09-04 / 2026-09-07两个回答都改变了本文件所述内容故保留作为记录。K1——48 小时驻留是计划中的吗在哪它不在计划中它已经存在。spine controllers 在驱逐资源前互相检查拒绝驱逐任何不足 48h 老的资源除非另一个 spine controller 持有它。PrepareResourceJob把资源送进数据中心之后该策略守护它的生命周期——ClusterAwareEvictionPolicy.cscivitai-spine-controller仓库。PinModelJob是遗留物「别去看它。」本注册表曾把它当作预期原语那是错的。K2——step:preparing是故意不公告的吗不是。Koen本意是preparing与scheduled是步骤状态、绝不是工作流状态但它们在回调枚举中的缺席是 2024 年一次无注释改动的规格限制。他已把缺失的事件类型加进规格。step:*仍然正确未来的civitai/client发布应携带这些类型。不算决策在别处跟踪C2——定价与计费。属于 Koen且不是配置开关——见上文。驻留Residency。已构建且不属于我们——spine controllers 强制它。属于我们的是副本问题1.3。从决策到源码几个可验证的实现锚点决策文档刻意「不包含实现细节」但仓库源码提供了逐条对应的落点便于读者交叉验证拒绝路径统一在resolveLoadableresource-load.service.ts依次拒绝「不可生成」!eligible即isGenerationEligible组合覆盖与生态类型支持、「不可加载」UNLOADABLE_MESSAGES、unsupported、unknown、已availableorchestrator 会对已驻留资源瞬间完成 prepare用户会白付钱。生成资格单一推导isGenerationEligiblepackages/civitai-shared/src/generation-eligibility.ts是covered !isGenerationDisabled(flags) isBaseModelGenerationSupported(baseModel, modelType)的唯一组合点——覆盖视图的类型分支是应用到每个基础模型的扁平列表单看covered会多报 736 个版本。成员门槛 vs 配额assertCanRequestLoadresource-load.router.ts是门槛——rateLimit()的limit: 0自由行虽然拒绝自由用户但该中间件对 mod 以及在 dev/test/preview 中整体短路预览构建上只有门槛能挡住免费账户。负载状态读取不缓存getResourceLoadState有意绕过modelVersionResourceCache——那个缓存持有同一份ResourceInfo但 TTL 一天、且把availability丢弃了看起来「已经有了」实则是陷阱。AIR 构造modelVersionToAirsrc/server/utils/resource-air.ts从bustOrchestratorModelCache与modelVersionResourceCache的拷贝中抽出仅当调用方加载了文件时才从主文件带出fileType——带文件与不带文件的调用方问的是两个不同的 AIR。结语决策注册表的工作方式这份档案的价值在于纪律每个开放条目都有一句话问题、影响什么、选项、谁拍板、什么关闭它以及默认值已答条目保留回答因为它们改变了要构建的东西纯工作项被赶去 checklist。Phase A 已经构建完毕dev:块逐条可搜索——这正是让「开放决策」不变成「上线后发现」的机制。【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
