Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
912ab4a5da | ||
|
|
65dd29a865 | ||
|
|
9f78b30b3d | ||
|
|
96b26bb7f7 | ||
|
|
513ab67214 | ||
|
|
19c761522e | ||
|
|
40d37b7248 |
@@ -0,0 +1 @@
|
||||
github: [adrientoupet, numz]
|
||||
@@ -1,21 +1,201 @@
|
||||
MIT License
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2025 NumZ, AInVFX and Contributors
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
1. Definitions.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2025 seed
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -35,6 +35,27 @@ We're actively working on improvements and new features. To stay informed:
|
||||
- **🔮 Next Model Survey**: We're looking for community input on the next open-source super-powerful generic restoration model. Share your suggestions in [Issue #164](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/issues/164)
|
||||
|
||||
## 🚀 Updates
|
||||
|
||||
**2025.11.13 - Version 2.5.10**
|
||||
|
||||
- **🎯 Fix: Deterministic generation** - Identical images with the same seed now produce identical results across different sessions and batch positions
|
||||
- **🔧 Fix: Model caching with BlockSwap** - Resolved issue where cached DiT models wouldn't properly reload when VAE caching state changed
|
||||
- **💾 Fix: Runner caching optimization** - Runner templates now correctly cache whenever both DiT and VAE are cached, regardless of caching order
|
||||
- **📁 Fix: Case-insensitive model paths** - Extra model paths in YAML config now work regardless of case (seedvr2, SEEDVR2, SeedVR2, etc.)
|
||||
- **🐛 Fix: High resolution tile debug crash** - Fixed "NoneType has no attribute log" error when using maximum resolution with VAE tiling
|
||||
- **📊 Fix: Temporal overlap logging** - Corrected frame count reporting when temporal overlap is automatically adjusted
|
||||
- **🔍 Feature: Enhanced model path debugging** - Added detailed logging to help troubleshoot model loading issues (visible in debug mode)
|
||||
|
||||
**2025.11.12 - Version 2.5.9**
|
||||
|
||||
- **🐛 Fix: Tile debug visualization crash** - Fixed OpenCV error when using VAE tile debug mode on certain systems.
|
||||
- **🍎 Fix: macOS MPS loading error** - Added automatic CPU fallback for MPS allocator issues on certain PyTorch/macOS versions.
|
||||
- **🖥️ Fix: Windows log buffering** - Added flush to print statements for real-time log visibility in ComfyUI on Windows
|
||||
- **📦 Fix: ComfyUI Registry logo** - Updated icon URL to display properly in ComfyUI node registry
|
||||
- **ℹ️ Feature: Version display** - Added version number to node name and CLI/ComfyUI header for better tracking
|
||||
- **💝 Feature: GitHub Sponsors** - Added sponsor button to support project development. Thank you everyone for your support!
|
||||
- **📜 License: Apache 2.0** - Reverted License from MIT to Apache 2.0 to match ByteDance Seed project
|
||||
|
||||
**2025.11.10 - Version 2.5.8**
|
||||
|
||||
- **🐛 Fix (CLI): Windows batch processing duplicate files** - Fixed CLI batch mode processing each file twice on Windows due to case-insensitive filesystem. Improved directory scanning performance by 2-3x
|
||||
@@ -891,4 +912,4 @@ Special thanks to our community contributors including [benjaminherb](https://gi
|
||||
|
||||
## 📜 License
|
||||
|
||||
The code in this repository is released under the MIT license as found in the [LICENSE](LICENSE) file.
|
||||
The code in this repository is released under the Apache 2.0 license as found in the [LICENSE](LICENSE) file.
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "seedvr2_videoupscaler"
|
||||
description = "SeedVR2 official ComfyUI integration: ByteDance-Seed's one-step diffusion-based video/image upscaling with memory-efficient inference"
|
||||
version = "2.5.8"
|
||||
version = "2.5.10"
|
||||
authors = [
|
||||
{name = "numz"},
|
||||
{name = "adrientoupet"}
|
||||
@@ -37,5 +37,5 @@ Forum = "https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/discussions"
|
||||
[tool.comfy]
|
||||
PublisherId = "ainvfx"
|
||||
DisplayName = "ComfyUI-SeedVR2_VideoUpscaler"
|
||||
Icon = "https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/blob/main/docs/seedvr_logo.png"
|
||||
Icon = "https://raw.githubusercontent.com/numz/ComfyUI-SeedVR2_VideoUpscaler/refs/heads/main/docs/seedvr_logo.png"
|
||||
includes = []
|
||||
|
||||
@@ -268,6 +268,10 @@ def encode_all_batches(
|
||||
if step <= 0:
|
||||
step = batch_size
|
||||
temporal_overlap = 0
|
||||
debug.log(f"temporal_overlap >= batch_size, resetting to 0", level="WARNING", category="setup", force=True)
|
||||
|
||||
# Store actual temporal overlap used (may differ from parameter if reset)
|
||||
ctx['actual_temporal_overlap'] = temporal_overlap
|
||||
|
||||
# Calculate number of batches
|
||||
num_encode_batches = 0
|
||||
@@ -306,6 +310,14 @@ def encode_all_batches(
|
||||
runner.vae, ctx['cache_context']['vae_model'], debug
|
||||
)
|
||||
ctx['cache_context']['vae_newly_cached'] = True
|
||||
|
||||
# If both models now cached, cache runner template
|
||||
dit_is_cached = ctx['cache_context']['cached_dit'] or ctx['cache_context']['dit_newly_cached']
|
||||
if dit_is_cached:
|
||||
ctx['cache_context']['global_cache'].set_runner(
|
||||
ctx['cache_context']['dit_id'], ctx['cache_context']['vae_id'],
|
||||
runner, debug
|
||||
)
|
||||
|
||||
# Set deterministic seed for VAE encoding (separate from diffusion noise)
|
||||
# Uses seed + 1,000,000 to avoid collision with upscaling batch seeds
|
||||
@@ -620,6 +632,7 @@ def upscale_all_batches(
|
||||
runner.dit, ctx['cache_context']['dit_model'], debug
|
||||
)
|
||||
ctx['cache_context']['dit_newly_cached'] = True
|
||||
|
||||
# If both models now cached, cache runner template
|
||||
vae_is_cached = ctx['cache_context']['cached_vae'] or ctx['cache_context']['vae_newly_cached']
|
||||
if vae_is_cached:
|
||||
@@ -628,11 +641,6 @@ def upscale_all_batches(
|
||||
runner, debug
|
||||
)
|
||||
|
||||
# Set base seed for DiT noise generation
|
||||
# Ensures deterministic noise across all batches in this upscaling phase
|
||||
set_seed(seed)
|
||||
debug.log(f"Using seed: {seed}", category="dit")
|
||||
|
||||
# Move DiT to GPU for upscaling (no-op if already there)
|
||||
manage_model_device(model=runner.dit, target_device=ctx['dit_device'],
|
||||
model_name="DiT", debug=debug, runner=runner)
|
||||
@@ -646,6 +654,11 @@ def upscale_all_batches(
|
||||
check_interrupt(ctx)
|
||||
|
||||
debug.log(f"Upscaling batch {upscale_idx+1}/{num_valid_latents}", category="generation", force=True)
|
||||
# Reset seed for each batch to ensure identical RNG state
|
||||
# This ensures identical inputs produce identical outputs regardless of batch position
|
||||
set_seed(seed)
|
||||
debug.log(f"Using seed: {seed} for deterministic generation", category="dit")
|
||||
|
||||
debug.start_timer(f"upscale_batch_{upscale_idx+1}")
|
||||
|
||||
# Move to DiT device with correct dtype for upscaling (no-op if already there)
|
||||
@@ -1332,16 +1345,19 @@ def postprocess_all_batches(
|
||||
if total_padding_removed > 0:
|
||||
adjustments.append(f"{total_padding_removed} padding")
|
||||
|
||||
# Use actual temporal overlap from encoding (may have been reset)
|
||||
actual_overlap = ctx.get('actual_temporal_overlap', temporal_overlap)
|
||||
|
||||
# Calculate and include temporal overlap blending info
|
||||
if temporal_overlap > 0:
|
||||
frames_blended = (num_valid_samples - 1) * temporal_overlap
|
||||
if actual_overlap > 0:
|
||||
frames_blended = (num_valid_samples - 1) * actual_overlap
|
||||
adjustments.append(f"{frames_blended} overlap")
|
||||
|
||||
if adjustments:
|
||||
# Add back all removed/blended frames to get true computed count
|
||||
total_computed = frames_before_removal + total_padding_removed
|
||||
if temporal_overlap > 0:
|
||||
total_computed += (num_valid_samples - 1) * temporal_overlap
|
||||
if actual_overlap > 0:
|
||||
total_computed += (num_valid_samples - 1) * actual_overlap
|
||||
frame_info += f" ({total_computed} computed with {' + '.join(adjustments)} removed)"
|
||||
|
||||
debug.log(f"Final output assembled: {frame_info}, Resolution: {Wf}x{Hf}px, Channels: {channels_str}",
|
||||
|
||||
@@ -722,7 +722,7 @@ def _draw_tile_boundaries(image: torch.Tensor, debug: 'Debug', tile_boundaries:
|
||||
annotated_frames = []
|
||||
for frame_idx in range(T):
|
||||
# Convert frame to numpy (handle RGB and RGBA)
|
||||
img = (image[frame_idx].float().cpu().numpy() * 255).astype(np.uint8) # [H, W, C]
|
||||
img = np.ascontiguousarray((image[frame_idx].float().cpu().numpy() * 255).astype(np.uint8)) # [H, W, C]
|
||||
|
||||
# Draw boundary lines inside each tile
|
||||
for idx, tile_info in enumerate(tile_boundaries):
|
||||
|
||||
@@ -115,7 +115,28 @@ def load_quantized_state_dict(checkpoint_path: str, device: torch.device = torch
|
||||
debug.log("This is a one-time installation that will enable loading of .safetensors files",
|
||||
level="INFO", category="info", force=True)
|
||||
raise ImportError(error_msg)
|
||||
state = load_safetensors_file(checkpoint_path, device=device_str)
|
||||
|
||||
# Try direct device loading first (optimal path)
|
||||
try:
|
||||
state = load_safetensors_file(checkpoint_path, device=device_str)
|
||||
except RuntimeError as e:
|
||||
# MPS allocator fallback: some PyTorch/macOS versions have issues with
|
||||
# direct MPS loading (allocation failures, watermark errors, etc.)
|
||||
error_msg = str(e).lower()
|
||||
is_mps_alloc_error = device.type == "mps" and any(
|
||||
keyword in error_msg for keyword in ["watermark", "allocat", "memory"]
|
||||
)
|
||||
|
||||
if is_mps_alloc_error:
|
||||
# Transparent fallback - only log if debug enabled
|
||||
if debug:
|
||||
debug.log("Using CPU intermediate loading for MPS compatibility",
|
||||
category="info", indent_level=1)
|
||||
state = load_safetensors_file(checkpoint_path, device="cpu")
|
||||
# Tensors will be moved to MPS during model.load_state_dict()
|
||||
else:
|
||||
# Re-raise if it's a different error (file corruption, etc.)
|
||||
raise
|
||||
elif checkpoint_path.endswith('.gguf'):
|
||||
validate_gguf_availability(f"load {os.path.basename(checkpoint_path)}", debug)
|
||||
state = _load_gguf_state(
|
||||
|
||||
@@ -6,7 +6,7 @@ Main ComfyUI node for high-quality video upscaling using diffusion models
|
||||
import torch
|
||||
from comfy_api.latest import io
|
||||
from typing import Tuple, Dict, Any, Optional
|
||||
from ..utils.constants import get_base_cache_dir
|
||||
from ..utils.constants import get_base_cache_dir, __version__
|
||||
from ..utils.downloads import download_weight
|
||||
from ..utils.debug import Debug
|
||||
from ..core.generation_phases import (
|
||||
@@ -50,7 +50,7 @@ class SeedVR2VideoUpscaler(io.ComfyNode):
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="SeedVR2VideoUpscaler",
|
||||
display_name="SeedVR2 Video Upscaler",
|
||||
display_name=f"SeedVR2 Video Upscaler (v{__version__})",
|
||||
category="SEEDVR2",
|
||||
description=(
|
||||
"SeedVR2 main upscaling node: processes video frames using DiT and VAE models with diffusion-based enhancement. "
|
||||
|
||||
@@ -1308,7 +1308,8 @@ class VideoAutoencoderKL(diffusers.AutoencoderKL):
|
||||
if H <= tile_h and W <= tile_w:
|
||||
return self.slicing_encode(x)
|
||||
else:
|
||||
self.debug.log(f"Using VAE tiled encoding (Tile: {tile_size}, Overlap: {tile_overlap})", category="vae", force=True, indent_level=1)
|
||||
if self.debug:
|
||||
self.debug.log(f"Using VAE tiled encoding (Tile: {tile_size}, Overlap: {tile_overlap})", category="vae", force=True, indent_level=1)
|
||||
|
||||
# Spatial scale factor (output/latent)
|
||||
scale_factor = self.spatial_downsample_factor
|
||||
@@ -1481,7 +1482,8 @@ class VideoAutoencoderKL(diffusers.AutoencoderKL):
|
||||
if H <= latent_tile_h and W <= latent_tile_w:
|
||||
return self.slicing_decode(z)
|
||||
else:
|
||||
self.debug.log(f"Using VAE tiled decoding (Tile: {tile_size}, Overlap: {tile_overlap})", category="vae", force=True, indent_level=1)
|
||||
if self.debug:
|
||||
self.debug.log(f"Using VAE tiled decoding (Tile: {tile_size}, Overlap: {tile_overlap})", category="vae", force=True, indent_level=1)
|
||||
|
||||
latent_overlap_h = max(0, min((overlap_h // scale_factor), latent_tile_h - 1))
|
||||
latent_overlap_w = max(0, min((overlap_w // scale_factor), latent_tile_w - 1))
|
||||
|
||||
@@ -193,7 +193,7 @@ def apply_block_swap_to_dit(
|
||||
runner._blockswap_active = True
|
||||
|
||||
# Store configuration for debugging and cleanup
|
||||
runner._block_swap_config = {
|
||||
model._block_swap_config = {
|
||||
"blocks_swapped": blocks_to_swap,
|
||||
"swap_io_components": swap_io_components,
|
||||
"total_blocks": total_blocks,
|
||||
@@ -650,11 +650,11 @@ def _protect_model_from_move(
|
||||
|
||||
Wraps model.to() method to prevent other code from accidentally moving
|
||||
the entire model to GPU, which would defeat BlockSwap's memory savings.
|
||||
Allows movement only when explicitly bypassed via runner flag.
|
||||
Allows movement only when explicitly bypassed via model flag.
|
||||
|
||||
Args:
|
||||
model: DiT model to protect
|
||||
runner: VideoDiffusionInfer instance (stored as weak reference)
|
||||
runner: VideoDiffusionInfer instance (for active status check)
|
||||
debug: Debug instance for logging (required)
|
||||
"""
|
||||
if not hasattr(model, '_original_to'):
|
||||
@@ -665,34 +665,46 @@ def _protect_model_from_move(
|
||||
# Define the protected method without closures
|
||||
def protected_model_to(self, device, *args, **kwargs):
|
||||
# Check if protection is temporarily bypassed for offloading
|
||||
# Flag is stored on model itself (not runner) to survive runner recreation
|
||||
if getattr(self, "_blockswap_bypass_protection", False):
|
||||
# Protection bypassed, allow movement
|
||||
if hasattr(self, '_original_to'):
|
||||
return self._original_to(device, *args, **kwargs)
|
||||
|
||||
# Get configured offload device directly from model
|
||||
blockswap_offload_device = "cpu" # default
|
||||
if hasattr(self, "_block_swap_config"):
|
||||
blockswap_offload_device = self._block_swap_config.get("offload_device", "cpu")
|
||||
|
||||
# Check if BlockSwap is currently active via runner weak reference
|
||||
runner_ref = getattr(self, '_blockswap_runner_ref', None)
|
||||
blockswap_is_active = False
|
||||
if runner_ref:
|
||||
runner_obj = runner_ref()
|
||||
if runner_obj and getattr(runner_obj, "_blockswap_bypass_protection", False):
|
||||
# Protection bypassed, allow movement
|
||||
if hasattr(self, '_original_to'):
|
||||
return self._original_to(device, *args, **kwargs)
|
||||
if runner_obj and hasattr(runner_obj, "_blockswap_active"):
|
||||
blockswap_is_active = runner_obj._blockswap_active
|
||||
|
||||
# Check blockswap status using weak reference
|
||||
# Get configured offload device from runner
|
||||
blockswap_offload_device = "cpu" # default
|
||||
if runner_ref:
|
||||
runner_obj = runner_ref()
|
||||
if runner_obj and hasattr(runner_obj, "_block_swap_config"):
|
||||
blockswap_offload_device = runner_obj._block_swap_config.get("offload_device", "cpu")
|
||||
# Block attempts to move model away from configured offload device when active
|
||||
if blockswap_is_active and str(device) != str(blockswap_offload_device):
|
||||
# Get debug instance from runner if available
|
||||
debug_instance = None
|
||||
if runner_ref:
|
||||
runner_obj = runner_ref()
|
||||
if runner_obj and hasattr(runner_obj, 'debug'):
|
||||
debug_instance = runner_obj.debug
|
||||
|
||||
# Block attempts to move model away from configured offload device
|
||||
if str(device) != str(blockswap_offload_device):
|
||||
if runner_obj and hasattr(runner_obj, "_blockswap_active") and runner_obj._blockswap_active:
|
||||
debug.log(f"Blocked attempt to move blockswapped model from {blockswap_offload_device} to {device}",
|
||||
level="WARNING", category="blockswap", force=True)
|
||||
return self
|
||||
if debug_instance:
|
||||
debug_instance.log(
|
||||
f"Blocked attempt to move BlockSwap model from {blockswap_offload_device} to {device}",
|
||||
level="WARNING", category="blockswap", force=True
|
||||
)
|
||||
return self
|
||||
|
||||
# Use original method stored as attribute
|
||||
# Allow movement (either bypass is enabled or target is offload device)
|
||||
if hasattr(self, '_original_to'):
|
||||
return self._original_to(device, *args, **kwargs)
|
||||
else:
|
||||
# This shouldn't happen, but fallback to super().to()
|
||||
# Fallback - shouldn't happen
|
||||
return super(type(self), self).to(device, *args, **kwargs)
|
||||
|
||||
# Bind as a method to the model instance
|
||||
@@ -712,7 +724,13 @@ def set_blockswap_bypass(runner, bypass: bool, debug):
|
||||
if not hasattr(runner, "_blockswap_active") or not runner._blockswap_active:
|
||||
return
|
||||
|
||||
runner._blockswap_bypass_protection = bypass
|
||||
# Get the actual model (handle FP8CompatibleDiT wrapper)
|
||||
model = runner.dit
|
||||
if hasattr(model, "dit_model"):
|
||||
model = model.dit_model
|
||||
|
||||
# Store on model so it survives runner recreation during caching
|
||||
model._blockswap_bypass_protection = bypass
|
||||
|
||||
if bypass:
|
||||
debug.log("BlockSwap protection disabled to allow model DiT offloading", category="success")
|
||||
@@ -741,11 +759,16 @@ def cleanup_blockswap(runner, keep_state_for_cache=False):
|
||||
|
||||
debug = runner.debug
|
||||
|
||||
# Check if there's any BlockSwap state to clean up
|
||||
# Get the actual model (handle FP8CompatibleDiT wrapper)
|
||||
model = runner.dit
|
||||
if hasattr(model, "dit_model"):
|
||||
model = model.dit_model
|
||||
|
||||
# Check if there's any BlockSwap state to clean up (check both runner and model)
|
||||
has_blockswap_state = (
|
||||
hasattr(runner, "_blockswap_active") or
|
||||
hasattr(runner, "_block_swap_config") or
|
||||
hasattr(runner, "_blockswap_bypass_protection")
|
||||
hasattr(model, "_block_swap_config") or
|
||||
hasattr(model, "_blockswap_bypass_protection")
|
||||
)
|
||||
|
||||
if not has_blockswap_state:
|
||||
@@ -757,7 +780,7 @@ def cleanup_blockswap(runner, keep_state_for_cache=False):
|
||||
# Minimal cleanup for caching - just mark as inactive and allow offloading
|
||||
# Everything else stays intact for fast reactivation
|
||||
if hasattr(runner, "_blockswap_active") and runner._blockswap_active:
|
||||
if not getattr(runner, "_blockswap_bypass_protection", False):
|
||||
if not getattr(model, "_blockswap_bypass_protection", False):
|
||||
set_blockswap_bypass(runner=runner, bypass=True, debug=debug)
|
||||
runner._blockswap_active = False
|
||||
debug.log("BlockSwap deactivated for caching (configuration preserved)", category="success")
|
||||
@@ -829,7 +852,7 @@ def cleanup_blockswap(runner, keep_state_for_cache=False):
|
||||
|
||||
# 5. Clean up BlockSwap-specific attributes
|
||||
for attr in ['_blockswap_runner_ref', 'blocks_to_swap', 'main_device',
|
||||
'offload_device', '_blockswap_configured']:
|
||||
'offload_device']:
|
||||
if hasattr(model, attr):
|
||||
delattr(model, attr)
|
||||
|
||||
|
||||
@@ -762,7 +762,7 @@ def _handle_blockswap_model_movement(runner: Any, model: torch.nn.Module,
|
||||
else:
|
||||
# Moving to GPU (reload)
|
||||
# Check if we're in bypass mode (coming from offload)
|
||||
if not getattr(runner, "_blockswap_bypass_protection", False):
|
||||
if not getattr(model, "_blockswap_bypass_protection", False):
|
||||
# Not in bypass mode, blocks are already configured
|
||||
if debug:
|
||||
debug.log(f"{model_name} with BlockSwap active - blocks already distributed across devices, skipping movement", category="general")
|
||||
@@ -787,7 +787,7 @@ def _handle_blockswap_model_movement(runner: Any, model: torch.nn.Module,
|
||||
# Restore blocks to their configured devices
|
||||
if hasattr(model, "blocks") and hasattr(model, "blocks_to_swap"):
|
||||
# Use configured offload_device from BlockSwap config
|
||||
offload_device = runner._block_swap_config.get("offload_device")
|
||||
offload_device = model._block_swap_config.get("offload_device")
|
||||
if not offload_device:
|
||||
raise ValueError("BlockSwap config missing offload_device")
|
||||
|
||||
@@ -801,7 +801,7 @@ def _handle_blockswap_model_movement(runner: Any, model: torch.nn.Module,
|
||||
block.to(offload_device)
|
||||
|
||||
# Handle I/O components
|
||||
if not runner._block_swap_config.get("swap_io_components", False):
|
||||
if not model._block_swap_config.get("swap_io_components", False):
|
||||
# I/O components should be on GPU if not offloaded
|
||||
for name, module in model.named_children():
|
||||
if name != "blocks":
|
||||
@@ -814,10 +814,10 @@ def _handle_blockswap_model_movement(runner: Any, model: torch.nn.Module,
|
||||
|
||||
if debug:
|
||||
# Get actual configuration from runner
|
||||
if hasattr(runner, '_block_swap_config'):
|
||||
blocks_on_gpu = runner._block_swap_config.get('total_blocks', 32) - runner._block_swap_config.get('blocks_swapped', 16)
|
||||
total_blocks = runner._block_swap_config.get('total_blocks', 32)
|
||||
main_device = runner._block_swap_config.get('main_device', 'GPU')
|
||||
if hasattr(model, '_block_swap_config'):
|
||||
blocks_on_gpu = model._block_swap_config.get('total_blocks', 32) - model._block_swap_config.get('blocks_swapped', 16)
|
||||
total_blocks = model._block_swap_config.get('total_blocks', 32)
|
||||
main_device = model._block_swap_config.get('main_device', 'GPU')
|
||||
debug.log(f"BlockSwap blocks restored to configured devices ({blocks_on_gpu}/{total_blocks} blocks on {str(main_device).upper()})", category="success")
|
||||
else:
|
||||
debug.log("BlockSwap blocks restored to configured devices", category="success")
|
||||
|
||||
+26
-4
@@ -3,6 +3,9 @@ Shared constants and utilities for SeedVR2
|
||||
Only includes constants actually used in the codebase
|
||||
"""
|
||||
|
||||
# Version information
|
||||
__version__ = "2.5.10"
|
||||
|
||||
import os
|
||||
import warnings
|
||||
import inspect
|
||||
@@ -52,14 +55,33 @@ def get_base_cache_dir() -> str:
|
||||
|
||||
|
||||
def get_all_model_paths() -> list:
|
||||
"""Get all registered model paths including those from extra_model_paths.yaml"""
|
||||
"""Get all registered model paths including those from extra_model_paths.yaml (case-insensitive)"""
|
||||
try:
|
||||
import folder_paths
|
||||
# Ensure default path is registered first
|
||||
get_base_cache_dir()
|
||||
# Get all paths registered for seedvr2 model type
|
||||
paths = folder_paths.get_folder_paths(SEEDVR2_MODEL_TYPE)
|
||||
return paths if paths else [get_base_cache_dir()]
|
||||
|
||||
# Case-insensitive lookup: search through all registered folder types
|
||||
# This handles any case variation users might use in extra_model_paths.yaml
|
||||
all_paths = []
|
||||
target_lower = SEEDVR2_MODEL_TYPE.lower()
|
||||
|
||||
# folder_paths.folder_names_and_paths is the underlying dict: {type: ([paths], extensions)}
|
||||
if hasattr(folder_paths, 'folder_names_and_paths'):
|
||||
for folder_type, (paths, _) in folder_paths.folder_names_and_paths.items():
|
||||
if folder_type.lower() == target_lower:
|
||||
all_paths.extend(paths)
|
||||
|
||||
# Remove duplicates while preserving order (os.path.normpath handles Windows/Linux path differences)
|
||||
seen = set()
|
||||
unique_paths = []
|
||||
for path in all_paths:
|
||||
normalized = os.path.normpath(path.lower())
|
||||
if normalized not in seen:
|
||||
seen.add(normalized)
|
||||
unique_paths.append(path)
|
||||
|
||||
return unique_paths if unique_paths else [get_base_cache_dir()]
|
||||
except:
|
||||
return [get_base_cache_dir()]
|
||||
|
||||
|
||||
+12
-5
@@ -11,6 +11,7 @@ import gc
|
||||
from typing import Optional, List, Dict, Any, Union
|
||||
from datetime import datetime
|
||||
from ..optimization.memory_manager import get_vram_usage, get_basic_vram_info, get_ram_usage, reset_vram_peak
|
||||
from ..utils.constants import __version__
|
||||
|
||||
|
||||
class Debug:
|
||||
@@ -111,7 +112,7 @@ class Debug:
|
||||
# Add indentation
|
||||
indent = " " * (indent_level * 2)
|
||||
|
||||
print(f"{prefix} {indent}{message}")
|
||||
print(f"{prefix} {indent}{message}", flush=True)
|
||||
|
||||
def print_header(self, cli: bool = False) -> None:
|
||||
"""Print the header with banner - always displayed"""
|
||||
@@ -123,10 +124,16 @@ class Debug:
|
||||
self.log(" ║ ███████ █████ █████ ██ ██ ██ ██ ██████ █████ ║", category="none", force=True)
|
||||
self.log(" ║ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ║", category="none", force=True)
|
||||
self.log(" ║ ███████ ███████ ███████ ██████ ████ ██ ██ ███████ ║", category="none", force=True)
|
||||
if cli:
|
||||
self.log(" ║ 💻 CLI mode © ByteDance Seed · NumZ · AInVFX ║", category="none", force=True)
|
||||
else:
|
||||
self.log(" ║ © ByteDance Seed · NumZ · AInVFX ║", category="none", force=True)
|
||||
|
||||
# Version number with dynamic padding to maintain visual alignment with any version length
|
||||
version_text = f"v{__version__}"
|
||||
prefix = " 💻 CLI mode · " if cli else " "
|
||||
suffix = "© ByteDance Seed · NumZ · AInVFX "
|
||||
emoji_compensation = 1 if cli else 0
|
||||
padding_width = 59 - len(prefix) - len(version_text) - len(suffix) - 2 - emoji_compensation
|
||||
padding = " " * max(1, padding_width)
|
||||
self.log(f" ║{prefix}{version_text}{padding} {suffix}║", category="none", force=True)
|
||||
|
||||
self.log(" ╚══════════════════════════════════════════════════════════╝", category="none", force=True)
|
||||
self.log("", category="none", force=True)
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import time
|
||||
from .model_registry import MODEL_REGISTRY, DEFAULT_VAE
|
||||
from .constants import (
|
||||
get_base_cache_dir,
|
||||
get_all_model_paths,
|
||||
find_model_file,
|
||||
get_validation_cache_path,
|
||||
HUGGINGFACE_BASE_URL,
|
||||
@@ -179,20 +180,36 @@ def download_weight(dit_model: str, vae_model: str, model_dir: Optional[str] = N
|
||||
level="WARNING", category="setup")
|
||||
continue
|
||||
|
||||
# Get model type for logging
|
||||
model_type = "VAE" if model_info.category == "vae" else "DiT"
|
||||
|
||||
# Check if file exists in any registered path first
|
||||
existing_filepath = find_model_file(filename, fallback_dir=cache_dir)
|
||||
|
||||
# Use existing file path if it exists, otherwise download to cache_dir
|
||||
if os.path.exists(existing_filepath):
|
||||
filepath = existing_filepath
|
||||
# Debug log: Model found
|
||||
if debug:
|
||||
debug.log(f"{model_type} model found: {filepath}", category="setup")
|
||||
else:
|
||||
filepath = os.path.join(cache_dir, filename)
|
||||
# Debug log: Model not found, will need to download
|
||||
if debug:
|
||||
searched_paths = get_all_model_paths()
|
||||
debug.log(f"{model_type} model not found: {filename}", category="setup")
|
||||
debug.log(f"Searched in {len(searched_paths)} location(s):", category="setup")
|
||||
for i, path in enumerate(searched_paths, 1):
|
||||
debug.log(f"[{i}] {path}", category="setup", indent_level=1)
|
||||
|
||||
expected_hash = model_info.sha256
|
||||
repo = model_info.repo
|
||||
|
||||
## Quick cache check first
|
||||
if is_file_validated_cached(filepath, cache_dir):
|
||||
# Debug log: Model already validated (using cache)
|
||||
if debug:
|
||||
debug.log(f"{model_type} model already validated (cache): {filepath}", category="setup")
|
||||
continue
|
||||
|
||||
# File exists - validate it
|
||||
@@ -201,6 +218,9 @@ def download_weight(dit_model: str, vae_model: str, model_dir: Optional[str] = N
|
||||
debug.log(f"Validating {filename}...", category="setup", force=True)
|
||||
|
||||
if validate_file(filepath, expected_hash, cache_dir):
|
||||
# Debug log: Model validated successfully
|
||||
if debug:
|
||||
debug.log(f"{model_type} model validated successfully: {filepath}", category="setup")
|
||||
continue
|
||||
else:
|
||||
# File is corrupted
|
||||
|
||||
Reference in New Issue
Block a user