-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat(mm): normalized model storage #8584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
psychedelicious
merged 75 commits into
psyche/feat/mm/unknown-model
from
psyche/feat/mm/normalized-model-storage
Oct 8, 2025
Merged
feat(mm): normalized model storage #8584
psychedelicious
merged 75 commits into
psyche/feat/mm/unknown-model
from
psyche/feat/mm/normalized-model-storage
Oct 8, 2025
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Store models in a flat directory structure. Each model is in a dir named its unique key (a UUID). Inside that dir is either the model file or the model dir.
05f9551
to
5e6b52d
Compare
hipsterusername
approved these changes
Sep 19, 2025
- Add concept of match certainty to new probe - Port CLIP Embed models to new API - Fiddle with stuff
Previously, we had a multi-phase strategy to identify models from their files on disk: 1. Run each model config classes' `matches()` method on the files. It checks if the model could possibly be an identified as the candidate model type. This was intended to be a quick check. Break on the first match. 2. If we have a match, run the config class's `parse()` method. It derive some additional model config attrs from the model files. This was intended to encapsulate heavier operations that may require loading the model into memory. 3. Derive the common model config attrs, like name, description, calculate the hash, etc. Some of these are also heavier operations. This strategy has some issues: - It is not clear how the pieces fit together. There is some back-and-forth between different methods and the config base class. It is hard to trace the flow of logic until you fully wrap your head around the system and therefore difficult to add a model architecture to the probe. - The assumption that we could do quick, lightweight checks before heavier checks is incorrect. We often _must_ load the model state dict in the `matches()` method. So there is no practical perf benefit to splitting up the responsibility of `matches()` and `parse()`. - Sometimes we need to do the same checks in `matches()` and `parse()`. In these cases, splitting the logic is has a negative perf impact because we are doing the same work twice. - As we introduce the concept of an "unknown" model config (i.e. a model that we cannot identify, but still record in the db; see #8582), we will _always_ run _all_ the checks for every model. Therefore we need not try to defer heavier checks or resource-intensive ops like hashing. We are going to do them anyways. - There are situations where a model may match multiple configs. One known case are SD pipeline models with merged LoRAs. In the old probe API, we relied on the implicit order of checks to know that if a model matched for pipeline _and_ LoRA, we prefer the pipeline match. But, in the new API, we do not have this implicit ordering of checks. To resolve this in a resilient way, we need to get all matches up front, then use tie-breaker logic to figure out which should win (or add "differential diagnosis" logic to the matchers). - Field overrides weren't handled well by this strategy. They were only applied at the very end, if a model matched successfully. This means we cannot tell the system "Hey, this model is type X with base Y. Trust me bro.". We cannot override the match logic. As we move towards letting users correct mis-identified models (see #8582), this is a requirement. We can simplify the process significantly and better support "unknown" models. Firstly, model config classes now have a single `from_model_on_disk()` method that attempts to construct an instance of the class from the model files. This replaces the `matches()` and `parse()` methods. If we fail to create the config instance, a special exception is raised that indicates why we think the files cannot be identified as the given model config class. Next, the flow for model identification is a bit simpler: - Derive all the common fields up-front (name, desc, hash, etc). - Merge in overrides. - Call `from_model_on_disk()` for every config class, passing in the fields. Overrides are handled in this method. - Record the results for each config class and choose the best one. The identification logic is a bit more verbose, with the special exceptions and handling of overrides, but it is very clear what is happening. The one downside I can think of for this strategy is we do need to check every model type, instead of stopping at the first match. It's a bit less efficient. In practice, however, this isn't a hot code path, and the improved clarity is worth far more than perf optimizations that the end user will likely never notice.
Split the big migration that did all of these things into 3: - Migration 22: Remove unique contraint on base/name/type in models table - Migration 23: Migrate configs to v6.8.0 schemas - Migration 24: Normalize file storage
05cc3b1
into
psyche/feat/mm/unknown-model
9 of 19 checks passed
7 tasks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
api
backend
PRs that change backend files
frontend
PRs that change frontend files
invocations
PRs that change invocations
python
PRs that change python files
python-tests
PRs that change python tests
services
PRs that change app services
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
Normalize model file storage to a flat directory structure.
Currently, models were stored in a nested hierarchy of folders like this:
<models_dir>/<base>/<type>/model_name.ext
for single-file models<models_dir>/<base>/<type>/model_name/**.*
for folder modelsThis has approach has a couple associated unpleasantries:
model.safetensors
. How do we work around that? Well, right now, we throw when a candidate model install would clobber an existing file.This PR normalizes/flattens model storage paths, storing each model in its own dir which is named with the model's unique key:
<models_dir>/<key>/file_name.ext
for single-file models<models_dir>/<key>/**.*
for folder modelsThis neatly addressing those unpleasantries:
models_dir
is not something you should fiddle with.This PR includes the simple code change for new model installs and a rather cautious migration to migrate to the new directory structure and update the model records in the DB. It uses SQLite save points and records all file operations, effectively making the DB and FS operations into a single transaction that can be rolled back.
It also removes some now-unused methods in the MM codebase.
Related Issues / Discussions
Numerous issues scattered throughout discord and GH
QA Instructions
First, test the migration with your
models_dir
at the default setting (e.g. nomodels_dir
ininvokeai.yaml
):cp -r /path/to/invokeai/ /path/to/invokeai_backup/
).databases/
andmodels/
dirs I suppose.models/
should have a UUID folder for each model in there.Next, do the same tests, but move
models_dir
to some other location (e.g.mv /path/to/invokeai/models/ /home/username/models/
and updateinvokeai.yaml
accordingly before starting up the app.I've tested both scenarios and tested invalid model configs in the DB. Everything working fine for me. We don't expect any generation issues, because we aren't doing anything that Invoke didn't already do. It already can load models from any location; we are just shuffling files around.
Merge Plan
n/a
Checklist
What's New
copy (if doing a release after this PR)