diff --git a/invokeai/app/invocations/cogview4_text_encoder.py b/invokeai/app/invocations/cogview4_text_encoder.py index c6ef1663cf..3b5b1dc73f 100644 --- a/invokeai/app/invocations/cogview4_text_encoder.py +++ b/invokeai/app/invocations/cogview4_text_encoder.py @@ -6,6 +6,7 @@ from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField from invokeai.app.invocations.model import GlmEncoderField from invokeai.app.invocations.primitives import CogView4ConditioningOutput from invokeai.app.services.shared.invocation_context import InvocationContext +from invokeai.backend.model_manager.load.model_cache.utils import get_effective_device from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( CogView4ConditioningInfo, ConditioningFieldData, @@ -46,10 +47,18 @@ class CogView4TextEncoderInvocation(BaseInvocation): prompt = [self.prompt] # TODO(ryand): Add model inputs to the invocation rather than hard-coding. + glm_text_encoder_info = context.models.load(self.glm_encoder.text_encoder) with ( - context.models.load(self.glm_encoder.text_encoder).model_on_device() as (_, glm_text_encoder), + glm_text_encoder_info.model_on_device() as (_, glm_text_encoder), context.models.load(self.glm_encoder.tokenizer).model_on_device() as (_, glm_tokenizer), ): + repaired_tensors = glm_text_encoder_info.repair_required_tensors_on_device() + device = get_effective_device(glm_text_encoder) + if repaired_tensors > 0: + context.logger.warning( + f"Recovered {repaired_tensors} required GLM tensor(s) onto {device} after a partial device mismatch." + ) + context.util.signal_progress("Running GLM text encoder") assert isinstance(glm_text_encoder, GlmModel) assert isinstance(glm_tokenizer, PreTrainedTokenizerFast) @@ -85,9 +94,7 @@ class CogView4TextEncoderInvocation(BaseInvocation): device=text_input_ids.device, ) text_input_ids = torch.cat([pad_ids, text_input_ids], dim=1) - prompt_embeds = glm_text_encoder( - text_input_ids.to(glm_text_encoder.device), output_hidden_states=True - ).hidden_states[-2] + prompt_embeds = glm_text_encoder(text_input_ids.to(device), output_hidden_states=True).hidden_states[-2] assert isinstance(prompt_embeds, torch.Tensor) return prompt_embeds diff --git a/invokeai/app/invocations/flux2_klein_text_encoder.py b/invokeai/app/invocations/flux2_klein_text_encoder.py index 6ca307ebf0..b44e782c8a 100644 --- a/invokeai/app/invocations/flux2_klein_text_encoder.py +++ b/invokeai/app/invocations/flux2_klein_text_encoder.py @@ -25,6 +25,7 @@ from invokeai.app.invocations.fields import ( from invokeai.app.invocations.model import Qwen3EncoderField from invokeai.app.invocations.primitives import FluxConditioningOutput from invokeai.app.services.shared.invocation_context import InvocationContext +from invokeai.backend.model_manager.load.model_cache.utils import get_effective_device from invokeai.backend.patches.layer_patcher import LayerPatcher from invokeai.backend.patches.lora_conversions.flux_lora_constants import FLUX_LORA_T5_PREFIX from invokeai.backend.patches.model_patch_raw import ModelPatchRaw @@ -100,7 +101,12 @@ class Flux2KleinTextEncoderInvocation(BaseInvocation): tokenizer_info = context.models.load(self.qwen3_encoder.tokenizer) (_, tokenizer) = exit_stack.enter_context(tokenizer_info.model_on_device()) - device = text_encoder.device + repaired_tensors = text_encoder_info.repair_required_tensors_on_device() + device = get_effective_device(text_encoder) + if repaired_tensors > 0: + context.logger.warning( + f"Recovered {repaired_tensors} required Qwen3 tensor(s) onto {device} after a partial device mismatch." + ) # Apply LoRA models lora_dtype = TorchDevice.choose_bfloat16_safe_dtype(device) diff --git a/invokeai/app/invocations/z_image_text_encoder.py b/invokeai/app/invocations/z_image_text_encoder.py index 06718c4897..c3405d6dc8 100644 --- a/invokeai/app/invocations/z_image_text_encoder.py +++ b/invokeai/app/invocations/z_image_text_encoder.py @@ -16,6 +16,7 @@ from invokeai.app.invocations.fields import ( from invokeai.app.invocations.model import Qwen3EncoderField from invokeai.app.invocations.primitives import ZImageConditioningOutput from invokeai.app.services.shared.invocation_context import InvocationContext +from invokeai.backend.model_manager.load.model_cache.utils import get_effective_device from invokeai.backend.patches.layer_patcher import LayerPatcher from invokeai.backend.patches.lora_conversions.z_image_lora_constants import Z_IMAGE_LORA_QWEN3_PREFIX from invokeai.backend.patches.model_patch_raw import ModelPatchRaw @@ -76,11 +77,17 @@ class ZImageTextEncoderInvocation(BaseInvocation): tokenizer_info = context.models.load(self.qwen3_encoder.tokenizer) with ExitStack() as exit_stack: - (_, text_encoder) = exit_stack.enter_context(text_encoder_info.model_on_device()) + (cached_weights, text_encoder) = exit_stack.enter_context(text_encoder_info.model_on_device()) (_, tokenizer) = exit_stack.enter_context(tokenizer_info.model_on_device()) - # Use the device that the text_encoder is actually on - device = text_encoder.device + # Use the device that the text encoder is effectively executing on, and repair any required tensors left on + # the CPU by a previous interrupted run. + repaired_tensors = text_encoder_info.repair_required_tensors_on_device() + device = get_effective_device(text_encoder) + if repaired_tensors > 0: + context.logger.warning( + f"Recovered {repaired_tensors} required Qwen3 tensor(s) onto {device} after a partial device mismatch." + ) # Apply LoRA models to the text encoder lora_dtype = TorchDevice.choose_bfloat16_safe_dtype(device) @@ -90,6 +97,7 @@ class ZImageTextEncoderInvocation(BaseInvocation): patches=self._lora_iterator(context), prefix=Z_IMAGE_LORA_QWEN3_PREFIX, dtype=lora_dtype, + cached_weights=cached_weights, ) ) diff --git a/invokeai/backend/model_manager/load/load_base.py b/invokeai/backend/model_manager/load/load_base.py index a4004afba7..b972969a68 100644 --- a/invokeai/backend/model_manager/load/load_base.py +++ b/invokeai/backend/model_manager/load/load_base.py @@ -14,6 +14,9 @@ import torch from invokeai.app.services.config import InvokeAIAppConfig from invokeai.backend.model_manager.configs.factory import AnyModelConfig from invokeai.backend.model_manager.load.model_cache.cache_record import CacheRecord +from invokeai.backend.model_manager.load.model_cache.cached_model.cached_model_with_partial_load import ( + CachedModelWithPartialLoad, +) from invokeai.backend.model_manager.load.model_cache.model_cache import ModelCache from invokeai.backend.model_manager.taxonomy import AnyModel, SubModelType @@ -80,6 +83,13 @@ class LoadedModelWithoutConfig: """Return the model without locking it.""" return self._cache_record.cached_model.model + def repair_required_tensors_on_device(self) -> int: + """Repair required tensors that should be resident on the cached model's execution device.""" + cached_model = self._cache_record.cached_model + if not isinstance(cached_model, CachedModelWithPartialLoad): + return 0 + return cached_model.repair_required_tensors_on_compute_device() + class LoadedModel(LoadedModelWithoutConfig): """Context manager object that mediates transfer from RAM<->VRAM.""" diff --git a/invokeai/backend/model_manager/load/model_cache/cached_model/cached_model_with_partial_load.py b/invokeai/backend/model_manager/load/model_cache/cached_model/cached_model_with_partial_load.py index f80b017ba7..328978b45b 100644 --- a/invokeai/backend/model_manager/load/model_cache/cached_model/cached_model_with_partial_load.py +++ b/invokeai/backend/model_manager/load/model_cache/cached_model/cached_model_with_partial_load.py @@ -149,6 +149,27 @@ class CachedModelWithPartialLoad: """Unload all weights from VRAM.""" return self.partial_unload_from_vram(self.total_bytes()) + @torch.no_grad() + def repair_required_tensors_on_compute_device(self) -> int: + """Repair required non-autocast tensors that were left off the compute device. + + This can happen if an interrupted run leaves the model in a partially inconsistent state. Any repaired device + movement invalidates the cached VRAM accounting. + """ + cur_state_dict = self._model.state_dict() + keys_to_repair = { + key + for key in self._keys_in_modules_that_do_not_support_autocast + if cur_state_dict[key].device.type != self._compute_device.type + } + if len(keys_to_repair) == 0: + return 0 + + self._load_state_dict_with_device_conversion(cur_state_dict, keys_to_repair, self._compute_device) + self._move_non_persistent_buffers_to_device(self._compute_device) + self._cur_vram_bytes = None + return len(keys_to_repair) + def _load_state_dict_with_device_conversion( self, state_dict: dict[str, torch.Tensor], keys_to_convert: set[str], target_device: torch.device ): diff --git a/invokeai/frontend/web/public/locales/fi.json b/invokeai/frontend/web/public/locales/fi.json index f03c6f1aa1..54e5a66660 100644 --- a/invokeai/frontend/web/public/locales/fi.json +++ b/invokeai/frontend/web/public/locales/fi.json @@ -4,7 +4,8 @@ "uploadImage": "Lataa kuva", "invokeProgressBar": "Invoken edistymispalkki", "nextImage": "Seuraava kuva", - "previousImage": "Edellinen kuva" + "previousImage": "Edellinen kuva", + "uploadImages": "Lähetä Kuva(t)" }, "common": { "languagePickerLabel": "Kielen valinta", @@ -29,5 +30,28 @@ "galleryImageSize": "Kuvan koko", "gallerySettings": "Gallerian asetukset", "autoSwitchNewImages": "Vaihda uusiin kuviin automaattisesti" + }, + "modelManager": { + "t5Encoder": "T5-kooderi", + "qwen3Encoder": "Qwen3-kooderi", + "zImageVae": "VAE (valinnainen)", + "zImageQwen3Encoder": "Qwen3-kooderi (valinnainen)", + "zImageQwen3SourcePlaceholder": "Pakollinen, jos VAE/Enkooderi on tyhjä", + "flux2KleinVae": "VAE (valinnainen)", + "flux2KleinQwen3Encoder": "Qwen3-kooderi (valinnainen)" + }, + "auth": { + "login": { + "title": "Kirjaudu sisään InvokeAI:hin", + "password": "Salasana", + "passwordPlaceholder": "Salasana", + "signIn": "Kirjaudu sisään", + "signingIn": "Kirjaudutaan sisään...", + "loginFailed": "Kirjautuminen epäonnistui. Tarkista käyttäjätunnuksesi tiedot." + }, + "setup": { + "title": "Tervetuloa InvokeAI:hin", + "subtitle": "Määritä ensimmäiseksi järjestelmänvalvojan tili" + } } } diff --git a/invokeai/frontend/web/public/locales/it.json b/invokeai/frontend/web/public/locales/it.json index d17d36d5c0..7a6dafe4c7 100644 --- a/invokeai/frontend/web/public/locales/it.json +++ b/invokeai/frontend/web/public/locales/it.json @@ -3139,6 +3139,11 @@ "back": "Indietro", "cannotDeleteSelf": "Non puoi eliminare il tuo account", "cannotDeactivateSelf": "Non puoi disattivare il tuo account" + }, + "passwordStrength": { + "weak": "Password debole", + "moderate": "Password moderata", + "strong": "Password forte" } } } diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx index c321317a34..19b0278353 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItemsBooleanSubMenu.tsx @@ -9,7 +9,8 @@ import { rasterLayerGlobalCompositeOperationChanged } from 'features/controlLaye import type { CanvasEntityIdentifier, CompositeOperation } from 'features/controlLayers/store/types'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { CgPathBack, CgPathCrop, CgPathExclude, CgPathFront, CgPathIntersect } from 'react-icons/cg'; +import { CgPathBack, CgPathExclude, CgPathFront, CgPathIntersect } from 'react-icons/cg'; +import { PiIntersectSquareBold } from 'react-icons/pi'; export const RasterLayerMenuItemsBooleanSubMenu = memo(() => { const { t } = useTranslation(); @@ -48,7 +49,7 @@ export const RasterLayerMenuItemsBooleanSubMenu = memo(() => { const disabled = isBusy || !entityIdentifierBelowThisOne; return ( - }> + }>