Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +6 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/.github/FUNDING.yml +1 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/.github/workflows/publish.yml +27 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/.gitignore +26 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/CONTRIBUTING.md +34 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/LICENSE +201 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/README.md +1053 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/__init__.py +9 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/configs_3b/main.yaml +91 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/configs_7b/main.yaml +88 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/demo_01.jpg +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/demo_02.jpg +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/dit_model_loader.png +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/seedvr_logo.png +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/torch_compile_settings.png +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/usage_01.png +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/usage_02.png +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/vae_model_loader.png +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/video_upscaler.png +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_4K_image_upscale.jpg +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_4K_image_upscale.json +1 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_HD_video_upscale.jpg +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_HD_video_upscale.json +1 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_simple_image_upscale.jpg +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_simple_image_upscale.json +1 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Eyes_212x120.mp4 +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Mustache_640x360.mp4 +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Sadhu_320x478.png +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/inference_cli.py +1712 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/neg_emb.pt +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/pos_emb.pt +3 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/pyproject.toml +40 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/requirements.txt +14 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/__init__.py +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/__init__.py +0 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/cache.py +47 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/config.py +134 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/decorators.py +130 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/__init__.py +56 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/config.py +75 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/samplers/base.py +108 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/samplers/euler.py +99 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/schedules/base.py +131 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/schedules/lerp.py +55 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/timesteps/base.py +72 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/timesteps/sampling/trailing.py +50 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/types.py +59 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/utils.py +84 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/distributed/__init__.py +37 -0
- v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/distributed/advanced.py +223 -0
.gitattributes
CHANGED
|
@@ -296,3 +296,9 @@ v3-nodes/ComfyUI-RMBG/example_workflows/V3.0.0_nodes.jpg filter=lfs diff=lfs mer
|
|
| 296 |
v3-nodes/ComfyUI-RMBG/example_workflows/YOLO_Node.jpg filter=lfs diff=lfs merge=lfs -text
|
| 297 |
v3-nodes/ComfyUI-RMBG/example_workflows/florence2_node.jpg filter=lfs diff=lfs merge=lfs -text
|
| 298 |
v3-nodes/ComfyUI-RMBG/models/sam3/perflib/tests/assets/masks.tiff filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
v3-nodes/ComfyUI-RMBG/example_workflows/YOLO_Node.jpg filter=lfs diff=lfs merge=lfs -text
|
| 297 |
v3-nodes/ComfyUI-RMBG/example_workflows/florence2_node.jpg filter=lfs diff=lfs merge=lfs -text
|
| 298 |
v3-nodes/ComfyUI-RMBG/models/sam3/perflib/tests/assets/masks.tiff filter=lfs diff=lfs merge=lfs -text
|
| 299 |
+
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/demo_01.jpg filter=lfs diff=lfs merge=lfs -text
|
| 300 |
+
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/demo_02.jpg filter=lfs diff=lfs merge=lfs -text
|
| 301 |
+
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/usage_01.png filter=lfs diff=lfs merge=lfs -text
|
| 302 |
+
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/usage_02.png filter=lfs diff=lfs merge=lfs -text
|
| 303 |
+
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Mustache_640x360.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 304 |
+
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Sadhu_320x478.png filter=lfs diff=lfs merge=lfs -text
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/.github/FUNDING.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
github: [adrientoupet, numz]
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/.github/workflows/publish.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Publish to Comfy registry
|
| 2 |
+
on:
|
| 3 |
+
workflow_dispatch:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
paths:
|
| 8 |
+
- "pyproject.toml"
|
| 9 |
+
|
| 10 |
+
permissions:
|
| 11 |
+
issues: write
|
| 12 |
+
|
| 13 |
+
jobs:
|
| 14 |
+
publish-node:
|
| 15 |
+
name: Publish Custom Node to registry
|
| 16 |
+
runs-on: ubuntu-latest
|
| 17 |
+
if: ${{ github.repository_owner == 'numz' }}
|
| 18 |
+
steps:
|
| 19 |
+
- name: Check out code
|
| 20 |
+
uses: actions/checkout@v4
|
| 21 |
+
with:
|
| 22 |
+
submodules: true
|
| 23 |
+
- name: Publish Custom Node
|
| 24 |
+
uses: Comfy-Org/publish-node-action@v1
|
| 25 |
+
with:
|
| 26 |
+
## Add your own personal access token to your Github Repository secrets and reference it here.
|
| 27 |
+
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git/*
|
| 2 |
+
**/__pycache__/
|
| 3 |
+
tests/
|
| 4 |
+
.vscode/
|
| 5 |
+
.cursor/
|
| 6 |
+
benchmark/
|
| 7 |
+
advanced_optimizations.py
|
| 8 |
+
BENCHMARK_*
|
| 9 |
+
environment.yml
|
| 10 |
+
install_safetensors.py
|
| 11 |
+
manage_quantized_models.py
|
| 12 |
+
quantization_config.py
|
| 13 |
+
quantize_model.py
|
| 14 |
+
quantize_vae.py
|
| 15 |
+
README_*.md
|
| 16 |
+
run_*.py
|
| 17 |
+
vram_diagnostic.py
|
| 18 |
+
test_*.py
|
| 19 |
+
VRAM_OPTIMIZATIONS_SUMMARY.md
|
| 20 |
+
seedvr2.py
|
| 21 |
+
src/core/isolated_generation.py
|
| 22 |
+
src/core/subprocess_runner.py
|
| 23 |
+
models/video_vae_v3_mine_bad/
|
| 24 |
+
src/processing/
|
| 25 |
+
TILE_VAE*
|
| 26 |
+
.DS_Store
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to ComfyUI-SeedVR2_VideoUpscaler
|
| 2 |
+
|
| 3 |
+
Thank you for your interest in contributing to ComfyUI-SeedVR2_VideoUpscaler! We appreciate your effort and to help us incorporate your contribution in the best way possible, please follow the following contribution guidelines.
|
| 4 |
+
|
| 5 |
+
## Reporting Bugs
|
| 6 |
+
|
| 7 |
+
If you find a bug in the project, we encourage you to report it. Here's how:
|
| 8 |
+
|
| 9 |
+
1. First, check the [existing Issues](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/issues) to see if the issue has already been reported. If it has, please add a comment to the existing issue rather than creating a new one.
|
| 10 |
+
2. If you can't find an existing issue that matches your bug, create a new issue. Make sure to include as many details as possible so we can understand and reproduce the problem.
|
| 11 |
+
|
| 12 |
+
## Proposing Changes
|
| 13 |
+
|
| 14 |
+
We welcome code contributions from the community. Here's how to propose changes:
|
| 15 |
+
|
| 16 |
+
1. Fork this repository to your own GitHub account.
|
| 17 |
+
2. Create a new branch on your fork for your changes.
|
| 18 |
+
3. Make your changes in this branch.
|
| 19 |
+
4. When you are ready, submit a pull request to the **`main`** branch.
|
| 20 |
+
|
| 21 |
+
We use the GitHub Flow workflow.
|
| 22 |
+
|
| 23 |
+
Before submitting a pull request, please make sure your code adheres to the project's coding conventions and it has passed all tests. If you are adding features, please also add appropriate tests.
|
| 24 |
+
|
| 25 |
+
## Contact
|
| 26 |
+
|
| 27 |
+
If you have any questions or need help, please reach out to the developers:
|
| 28 |
+
|
| 29 |
+
- **NumZ**: Discord NumZ#7184
|
| 30 |
+
- **adrientoupet** from AInVFX: [YouTube Channel](https://www.youtube.com/@AInVFX)
|
| 31 |
+
|
| 32 |
+
You can also open an issue on GitHub for general questions and discussions.
|
| 33 |
+
|
| 34 |
+
Thank you again for your contribution !
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "{}"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright 2025 seed
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/README.md
ADDED
|
@@ -0,0 +1,1053 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ComfyUI-SeedVR2_VideoUpscaler
|
| 2 |
+
|
| 3 |
+
[](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler)
|
| 4 |
+
|
| 5 |
+
Official release of [SeedVR2](https://github.com/ByteDance-Seed/SeedVR) for ComfyUI that enables high-quality video and image upscaling.
|
| 6 |
+
|
| 7 |
+
Can run as **Multi-GPU standalone CLI** too, see [🖥️ Run as Standalone](#-run-as-standalone-cli) section.
|
| 8 |
+
|
| 9 |
+
[](https://youtu.be/MBtWYXq_r60)
|
| 10 |
+
|
| 11 |
+

|
| 12 |
+
|
| 13 |
+

|
| 14 |
+
|
| 15 |
+
## 📋 Quick Access
|
| 16 |
+
|
| 17 |
+
- [🆙 Future Work](#-future-work)
|
| 18 |
+
- [🚀 Release Notes](#-release-notes)
|
| 19 |
+
- [🎯 Features](#-features)
|
| 20 |
+
- [🔧 Requirements](#-requirements)
|
| 21 |
+
- [📦 Installation](#-installation)
|
| 22 |
+
- [📖 Usage](#-usage)
|
| 23 |
+
- [🖥️ Run as Standalone](#️-run-as-standalone-cli)
|
| 24 |
+
- [⚠️ Limitations](#️-limitations)
|
| 25 |
+
- [🤝 Contributing](#-contributing)
|
| 26 |
+
- [🙏 Credits](#-credits)
|
| 27 |
+
- [📜 License](#-license)
|
| 28 |
+
|
| 29 |
+
## 🆙 Future Work
|
| 30 |
+
|
| 31 |
+
We're actively working on improvements and new features. To stay informed:
|
| 32 |
+
|
| 33 |
+
- **📌 Track Active Development**: Visit [Issues](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/issues) to see active development, report bugs, and request new features
|
| 34 |
+
- **💬 Join the Community**: Learn from others, share your workflows, and get help in the [Discussions](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/discussions)
|
| 35 |
+
- **🔮 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)
|
| 36 |
+
|
| 37 |
+
## 🚀 Release Notes
|
| 38 |
+
|
| 39 |
+
**2025.12.24 - Version 2.5.24**
|
| 40 |
+
|
| 41 |
+
- **🍎 Fix: MPS memory leak regression** - Restored MPS cache clearing after VAE encode/decode operations that was accidentally removed during code cleanup in v2.5.23
|
| 42 |
+
|
| 43 |
+
**2025.12.24 - Version 2.5.23**
|
| 44 |
+
|
| 45 |
+
- **🔒 Security: Prevent code execution in model loading** - Added protection against malicious .pth files by restricting deserialization to tensors only
|
| 46 |
+
- **🎥 Fix: FFmpeg video writer reliability** - Resolved ffmpeg process hanging issues by redirecting stderr and adding buffer flush, with improved error messages for debugging *(thanks [@thehhmdb](https://github.com/thehhmdb))*
|
| 47 |
+
- **⚡ Fix: GGUF VAE model support** - Enabled automatic weight dequantization for convolution operations, making GGUF-quantized VAE models fully functional *(thanks [@naxci1](https://github.com/naxci1))*
|
| 48 |
+
- **🛡️ Fix: VAE slicing edge cases** - Protected against division by zero crashes when using small split sizes with high temporal downsampling *(thanks [@naxci1](https://github.com/naxci1))*
|
| 49 |
+
- **🎨 Fix: LAB color transfer precision** - Resolved dtype mismatch errors during video upscaling by ensuring consistent float types before matrix operations
|
| 50 |
+
- **🔧 Fix: PyTorch 2.9+ compatibility** - Extended Conv3d memory workaround to all PyTorch 2.9+ versions, fixing 3x VRAM usage on newer PyTorch releases
|
| 51 |
+
- **📦 Fix: Bitsandbytes compatibility** - Added ValueError exception handling for Intel Gaudi version detection failures on non-Gaudi systems
|
| 52 |
+
- **🍎 MPS: Memory optimization** - Reduced memory usage during encode/decode operations on Apple Silicon *(thanks [@s-cerevisiae](https://github.com/s-cerevisiae))*
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
**2025.12.13 - Version 2.5.22**
|
| 56 |
+
|
| 57 |
+
- **🎬 CLI: FFmpeg video backend with 10-bit support** - New `--video_backend ffmpeg` and `--10bit` flags enable x265 encoding with 10-bit color depth, reducing banding artifacts in gradients compared to 8-bit OpenCV output *(based on PR by [@thehhmdb](https://github.com/thehhmdb) - thank you!)*
|
| 58 |
+
- **🍎 Fix: MPS bicubic upscaling compatibility** - Added CPU fallback for bicubic+antialias interpolation on PyTorch versions before 2.8.0, resolving RGBA alpha upscaling errors on Apple Silicon
|
| 59 |
+
- **⚡ Fix: Cross-platform histogram matching** - Replaced scatter_ operation with argsort+index_select for improved reliability across CUDA, ROCm, and MPS backends
|
| 60 |
+
- **🧹 MPS: Remove sync overhead** - Reverted unnecessary `torch.mps.synchronize()` calls introduced in v2.5.21 for consistent behavior with CUDA pipeline
|
| 61 |
+
|
| 62 |
+
**2025.12.12 - Version 2.5.21**
|
| 63 |
+
|
| 64 |
+
- **🛠️ Fix: GGUF dequantization error on MPS** - Resolved shape mismatch error introduced in 2.5.20 by skipping GGUF quantized buffers in precision conversion - these must remain in packed format for on-the-fly dequantization during inference
|
| 65 |
+
- **🍎 MPS: Eliminate CPU sync overhead** - Skip unnecessary CPU tensor offload on Apple Silicon unified memory architecture, preventing sync stalls that caused slowdowns. Input images and output video now stay on MPS device throughout the pipeline
|
| 66 |
+
- **⚡ MPS: Preload text embeddings** - Load text embeddings before Phase 1 encoding to avoid sync stall at Phase 2 start, improving timing accuracy and throughput
|
| 67 |
+
- **🧹 MPS: Optimized model cleanup** - Skip redundant CPU movement before model deletion on unified memory
|
| 68 |
+
|
| 69 |
+
**2025.12.12 - Version 2.5.20**
|
| 70 |
+
|
| 71 |
+
- **⚡ Expanded attention backends** - Full support for Flash Attention 2 (Ampere+), Flash Attention 3 (Hopper+), SageAttention 2, and SageAttention 3 (Blackwell/RTX 50xx), with automatic fallback chains to PyTorch SDPA when unavailable *(based on PR by [@naxci1](https://github.com/naxci1) - thank you!)*
|
| 72 |
+
- **🍎 macOS/Apple Silicon compatibility** - Replaced MPS autocast with explicit dtype conversion throughout VAE and DiT pipelines, resolving hangs and crashes on M-series Macs. BlockSwap now auto-disables with warning (unified memory makes it meaningless)
|
| 73 |
+
- **🛡️ Flash Attention graceful fallback** - Added compatibility shims for corrupted or partially installed flash_attn/xformers DLLs, preventing startup crashes
|
| 74 |
+
- **🛡️ AMD ROCm: bitsandbytes conflict fix** - Prevent kernel registration errors when diffusers attempts to re-import broken bitsandbytes installations
|
| 75 |
+
- **📦 ComfyUI Manager: macOS classifier fix** - Removed NVIDIA CUDA classifier causing false "GPU not supported" warnings on macOS
|
| 76 |
+
- **📚 Documentation updates** - Updated README with attention backend details, BlockSwap macOS notes, and clarified model caching descriptions
|
| 77 |
+
|
| 78 |
+
**2025.12.10 - Version 2.5.19**
|
| 79 |
+
|
| 80 |
+
- **🎨 New header logo design** - Refreshed ASCII art banner *(thanks [@naxci1](https://github.com/naxci1))*
|
| 81 |
+
- **🧹 Remove dead flash attention wrapper** - Removed legacy code from FP8CompatibleDiT; FlashAttentionVarlen already handles backend switching via its `attention_mode` attribute
|
| 82 |
+
- **🛡️ Fix graceful fallback from flash-attn** - Add compatibility shims for corrupted flash_attn/xformers DLLs, preventing startup crashes when CUDA extensions are broken
|
| 83 |
+
- **📊 Improved VRAM tracking** - Separate allocated vs reserved memory tracking, Windows-only overflow detection (WDDM paging behavior)
|
| 84 |
+
- **♻️ Centralize backend detection** - Unified `is_mps_available()`, `is_cuda_available()`, `get_gpu_backend()` helpers across codebase
|
| 85 |
+
- **🔄 Revert 2.5.14 VRAM limit enforcement** - Removed `set_per_process_memory_fraction` call; Overflow detection and warnings remain.
|
| 86 |
+
|
| 87 |
+
**2025.12.09 - Version 2.5.18**
|
| 88 |
+
|
| 89 |
+
- **🚀 CLI: Streaming mode for long videos** - New `--chunk_size` flag processes videos in memory-bounded chunks, enabling arbitrarily long videos without RAM limits. Works with model caching (`--cache_dit`/`--cache_vae`) for chunk-to-chunk reuse *(inspired by [disk02](https://github.com/disk02) PR contribution)*
|
| 90 |
+
- **⚡ CLI: Multi-GPU streaming** - Each GPU now streams its segment internally with independent model caching, improving memory efficiency and enabling `--temporal_overlap` blending at GPU boundaries
|
| 91 |
+
- **🔧 CLI: Fix large video MemoryError** - Shared memory transfer replaces numpy pickling, preventing crashes on high-resolution/long video outputs *(inspired by [FurkanGozukara](https://github.com/FurkanGozukara) PR contribution)*
|
| 92 |
+
|
| 93 |
+
**2025.12.05 - Version 2.5.17**
|
| 94 |
+
|
| 95 |
+
- **🔧 Fix: Older GPU compatibility (GTX 970, etc.)** - Runtime bf16 CUBLAS probe replaces compute capability heuristics, correctly detecting unsupported GPUs without affecting RTX 20XX
|
| 96 |
+
|
| 97 |
+
**2025.12.05 - Version 2.5.16**
|
| 98 |
+
|
| 99 |
+
- **🔧 Fix: Older GPU compatibility (GTX 970, etc.)** - Automatic fallback for GPUs without bfloat16 support
|
| 100 |
+
- **🐛 Fix: Quality regression** - Reverted bfloat16 detection that was causing artifact issues
|
| 101 |
+
- **📋 Debug: Environment info display** - Shows system info in debug mode to help with issue reporting
|
| 102 |
+
- **📚 Docs: Simplified contribution workflow** - Streamlined to main branch only
|
| 103 |
+
|
| 104 |
+
**2025.12.03 - Version 2.5.15**
|
| 105 |
+
|
| 106 |
+
- **🍎 Fix: MPS compatibility** - Disable antialias for MPS tensors and fix bfloat16 arange issues
|
| 107 |
+
- **⚡ Fix: Autocast device type** - Use proper device type attribute to prevent autocast errors
|
| 108 |
+
- **📊 Memory: Accurate VRAM tracking** - Use max_memory_reserved for more precise peak reporting
|
| 109 |
+
- **🔧 Fix: Triton compatibility** - Add shim for bitsandbytes 0.45+ / triton 3.0+ (fixes PyTorch 2.7 installation errors)
|
| 110 |
+
|
| 111 |
+
**2025.12.01 - Version 2.5.14**
|
| 112 |
+
|
| 113 |
+
- **🍎 Fix: MPS device comparison** - Normalize device strings to prevent unnecessary tensor movements
|
| 114 |
+
- **📊 Memory: VRAM swap detection** - Peak stats now show GPU+swap breakdown when overflow occurs, with warning when swap detected
|
| 115 |
+
- **🛡️ Memory: Enforce physical VRAM limit** - PyTorch now OOMs instead of silently swapping to shared memory (prevents extreme slowdowns on Windows)
|
| 116 |
+
|
| 117 |
+
**2025.11.30 - Version 2.5.13**
|
| 118 |
+
|
| 119 |
+
- **🔧 Fix: PyTorch 2.7+ triton import error** - Resolved installation crash caused by triton.ops import chain on newer triton versions
|
| 120 |
+
- **💾 Fix: OOM on float32 conversion for long videos** - Graceful fallback to native dtype when insufficient memory for float32 conversion
|
| 121 |
+
- **🍎 Fix: CLI watermark error on macOS** - Resolved MPS-related watermark processing crash on Apple Silicon
|
| 122 |
+
|
| 123 |
+
**2025.11.28 - Version 2.5.12**
|
| 124 |
+
|
| 125 |
+
- **🐛 Fix: Color artifacts regression** - Reverted in-place tensor operations in video transform pipeline that caused color artifacts on some images
|
| 126 |
+
|
| 127 |
+
**2025.11.28 - Version 2.5.11**
|
| 128 |
+
|
| 129 |
+
- **⚡ Feature: CUDNN attention backend** - Added support for PyTorch 2.3+ CUDNN_ATTENTION backend with automatic fallback for older versions (thanks @eadwu)
|
| 130 |
+
- **💾 Fix: Memory spike for long videos** - VAE decode now streams directly to pre-allocated tensor, eliminating OOM errors during long video processing
|
| 131 |
+
- **🎨 Fix: LAB color correction artifacts** - Resolved tile boundary artifacts using wavelet reconstruction preprocessing
|
| 132 |
+
- **🎨 Fix: Color reference misalignment** - Fixed color correction frame alignment with temporal overlap
|
| 133 |
+
- **🍎 Fix: MPS detection reliability** - Switched to canonical `torch.backends.mps.is_available()` API for consistent Apple Silicon detection
|
| 134 |
+
- **🖥️ Fix: Mac subprocess error** - CLI now uses direct processing on Mac to avoid MPS allocator failures in child processes
|
| 135 |
+
- **🖥️ Fix: Multi-GPU device assignment** - CUDA_VISIBLE_DEVICES now set before spawn for proper worker inheritance
|
| 136 |
+
- **📊 Fix: BlockSwap logging** - Now shows effective/total blocks (e.g., 32/32) instead of raw requested value
|
| 137 |
+
- **🔧 Feature: Auto bfloat16 detection** - Automatically detects bfloat16 support to prevent CUBLAS errors on older GPUs
|
| 138 |
+
- **📊 Feature: Peak RAM tracking** - Added RAM usage alongside VRAM in debug summary
|
| 139 |
+
- **⚡ Performance: In-place tensor ops** - Reduced memory allocation overhead with in-place operations throughout pipeline
|
| 140 |
+
- **📖 Docs: Multi-GPU clarification** - Clarified frame-level parallelism behavior expectations for multi-GPU setups
|
| 141 |
+
|
| 142 |
+
**2025.11.13 - Version 2.5.10**
|
| 143 |
+
|
| 144 |
+
- **🎯 Fix: Deterministic generation** - Identical images with the same seed now produce identical results across different sessions and batch positions
|
| 145 |
+
- **🔧 Fix: Model caching with BlockSwap** - Resolved issue where cached DiT models wouldn't properly reload when VAE caching state changed
|
| 146 |
+
- **💾 Fix: Runner caching optimization** - Runner templates now correctly cache whenever both DiT and VAE are cached, regardless of caching order
|
| 147 |
+
- **📁 Fix: Case-insensitive model paths** - Extra model paths in YAML config now work regardless of case (seedvr2, SEEDVR2, SeedVR2, etc.)
|
| 148 |
+
- **🐛 Fix: High resolution tile debug crash** - Fixed "NoneType has no attribute log" error when using maximum resolution with VAE tiling
|
| 149 |
+
- **📊 Fix: Temporal overlap logging** - Corrected frame count reporting when temporal overlap is automatically adjusted
|
| 150 |
+
- **🔍 Feature: Enhanced model path debugging** - Added detailed logging to help troubleshoot model loading issues (visible in debug mode)
|
| 151 |
+
|
| 152 |
+
**2025.11.12 - Version 2.5.9**
|
| 153 |
+
|
| 154 |
+
- **🐛 Fix: Tile debug visualization crash** - Fixed OpenCV error when using VAE tile debug mode on certain systems.
|
| 155 |
+
- **🍎 Fix: macOS MPS loading error** - Added automatic CPU fallback for MPS allocator issues on certain PyTorch/macOS versions.
|
| 156 |
+
- **🖥️ Fix: Windows log buffering** - Added flush to print statements for real-time log visibility in ComfyUI on Windows
|
| 157 |
+
- **📦 Fix: ComfyUI Registry logo** - Updated icon URL to display properly in ComfyUI node registry
|
| 158 |
+
- **ℹ️ Feature: Version display** - Added version number to node name and CLI/ComfyUI header for better tracking
|
| 159 |
+
- **💝 Feature: GitHub Sponsors** - Added sponsor button to support project development. Thank you everyone for your support!
|
| 160 |
+
- **📜 License: Apache 2.0** - Reverted License from MIT to Apache 2.0 to match ByteDance Seed project
|
| 161 |
+
|
| 162 |
+
**2025.11.10 - Version 2.5.8**
|
| 163 |
+
|
| 164 |
+
- **🐛 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
|
| 165 |
+
- **📁 Fix(CLI): Output folder location** - Output files now created in sensible locations: batch mode creates `{folder_name}_upscaled/` sibling folder with original filenames preserved; single file mode adds `_upscaled` suffix in same directory. All logs now show absolute paths for clarity
|
| 166 |
+
- **🎨 Fix(CLI): RGBA alpha channel support** - PNG images with transparency are now properly detected and preserved through the upscaling pipeline, matching ComfyUI behavior
|
| 167 |
+
|
| 168 |
+
**2025.11.10 - Version 2.5.7**
|
| 169 |
+
|
| 170 |
+
- **🔧 Fix: Conv3d workaround compatibility** - Enhanced platform detection and added graceful fallback to prevent errors on PyTorch dev builds and AMD ROCm systems
|
| 171 |
+
|
| 172 |
+
**2025.11.09 - Version 2.5.6**
|
| 173 |
+
|
| 174 |
+
- 🎨 **Fix: Restored natural look for 7b model** - Corrected torch.compile optimization that was causing overly plastic/ high-specular appearance in upscaled videos with 7b model.
|
| 175 |
+
|
| 176 |
+
- 💾 **Memory: Fixed RAM leak for long videos** - On-demand reconstruction with lightweight batch indices instead of storing full transformed videos, fixed release_tensor_memory to handle CPU/CUDA/MPS consistently, and refactored batch processing helpers
|
| 177 |
+
|
| 178 |
+
**2025.11.08 - Version 2.5.4**
|
| 179 |
+
|
| 180 |
+
- 🎨 **Fix: AdaIN color correction** - Replace `.view()` with `.reshape()` to handle non-contiguous tensors after spatial padding, resolving "view size is not compatible with input tensor's size and stride" error
|
| 181 |
+
- 🔴 **Fix: AMD ROCm compatibility** - Add cuDNN availability check in Conv3d workaround to prevent "ATen not compiled with cuDNN support" error on ROCm systems (AMD GPUs on Windows/Linux)
|
| 182 |
+
|
| 183 |
+
**2025.11.08 - Version 2.5.3**
|
| 184 |
+
|
| 185 |
+
- 🍎 **Fix: Apple Silicon MPS device handling** - Corrected MPS device enumeration to use `"mps"` instead of `"mps:0"`, resolving invalid device errors on M-series Macs
|
| 186 |
+
- 🪟 **Fix: torch.mps AttributeError on Windows** - Add defensive checks for `torch.mps.is_available()` to handle PyTorch versions where the method doesn't exist on non-Mac platforms
|
| 187 |
+
|
| 188 |
+
**2025.11.07 - Version 2.5.0** 🎉
|
| 189 |
+
|
| 190 |
+
⚠️ **BREAKING CHANGE**: This is a major update requiring workflow recreation. All nodes and CLI parameters have been redesigned for better usability and consistency. Watch the latest video from [AInVFX](https://www.youtube.com/@AInVFX) for a deep dive and check out the [usage](#-usage) section.
|
| 191 |
+
|
| 192 |
+
**📦 Official Release**: Now available on main branch with ComfyUI Manager support for easy installation and automatic version tracking. Updated dependencies and local imports prevent conflicts with other ComfyUI custom nodes.
|
| 193 |
+
|
| 194 |
+
### 🎨 ComfyUI Improvements
|
| 195 |
+
|
| 196 |
+
- **Four-Node Modular Architecture**: Split into dedicated nodes for DiT model, VAE model, torch.compile settings, and main upscaler for granular control
|
| 197 |
+
- **Global Model Cache**: Models now shared across multiple upscaler instances with automatic config updates - no more redundant loading
|
| 198 |
+
- **ComfyUI V3 Migration**: Full compatibility with ComfyUI V3 stateless node design
|
| 199 |
+
- **RGBA Support**: Native alpha channel processing with edge-guided upscaling for clean transparency
|
| 200 |
+
- **Improved Memory Management**: Streaming architecture prevents VRAM spikes regardless of video length
|
| 201 |
+
- **Flexible Resolution Support**: Upscale to any resolution divisible by 2 with lossless padding approach (replaced restrictive cropping)
|
| 202 |
+
- **Enhanced Parameters**: Added `uniform_batch_size`, `temporal_overlap`, `prepend_frames`, and `max_resolution` for better control
|
| 203 |
+
|
| 204 |
+
### 🖥️ CLI Enhancements
|
| 205 |
+
|
| 206 |
+
- **Batch Directory Processing**: Process entire folders of videos/images with model caching for efficiency
|
| 207 |
+
- **Single Image Support**: Direct image upscaling without video conversion
|
| 208 |
+
- **Smart Output Detection**: Auto-detects output format (MP4/PNG) based on input type
|
| 209 |
+
- **Enhanced Multi-GPU**: Improved workload distribution with temporal overlap blending
|
| 210 |
+
- **Unified Parameters**: CLI and ComfyUI now use identical parameter names for consistency
|
| 211 |
+
- **Better UX**: Auto-display help, validation improvements, progress tracking, and cleaner output
|
| 212 |
+
|
| 213 |
+
### ⚡ Performance & Optimization
|
| 214 |
+
|
| 215 |
+
- **torch.compile Support**: 20-40% DiT speedup and 15-25% VAE speedup with full graph compilation
|
| 216 |
+
- **Optimized BlockSwap**: Adaptive memory clearing (5% threshold), separate I/O component handling, reduced overhead
|
| 217 |
+
- **Enhanced VAE Tiling**: Tensor offload support for accumulation buffers, separate encode/decode configuration
|
| 218 |
+
- **Native Dtype Pipeline**: Eliminated unnecessary conversions, maintains bfloat16 precision throughout for speed and quality
|
| 219 |
+
- **Optimized Tensor Operations**: Replaced einops rearrange with native PyTorch ops for 2-5x faster transforms
|
| 220 |
+
|
| 221 |
+
### 🎯 Quality Improvements
|
| 222 |
+
|
| 223 |
+
- **LAB Color Correction**: New perceptual color transfer method with superior color accuracy (now default)
|
| 224 |
+
- **Additional Color Methods**: HSV saturation matching, wavelet adaptive, and hybrid approaches
|
| 225 |
+
- **Deterministic Generation**: Seed-based reproducibility with phase-specific seeding strategy
|
| 226 |
+
- **Better Temporal Consistency**: Hann window blending for smooth transitions between batches
|
| 227 |
+
|
| 228 |
+
### 💾 Memory Management
|
| 229 |
+
|
| 230 |
+
- **Smarter Offloading**: Independent device configuration for DiT, VAE, and tensors (CPU/GPU/none)
|
| 231 |
+
- **Four-Phase Pipeline**: Completes each phase (encode→upscale→decode→postprocess) for all batches before moving to next, minimizing model swaps
|
| 232 |
+
- **Better Cleanup**: Phase-specific resource management with proper tensor memory release
|
| 233 |
+
- **Peak VRAM Tracking**: Per-phase memory monitoring with summary display
|
| 234 |
+
|
| 235 |
+
### 🔧 Technical Improvements
|
| 236 |
+
|
| 237 |
+
- **GGUF Quantization Support**: Added full GGUF support for 4-bit/8-bit inference on low-VRAM systems
|
| 238 |
+
- **Improved GGUF Handling**: Fixed VRAM leaks, torch.compile compatibility, non-persistent buffers
|
| 239 |
+
- **Apple Silicon Support**: Full MPS (Metal Performance Shaders) support for Apple Silicon Macs
|
| 240 |
+
- **AMD ROCm Compatibility**: Conditional FSDP imports for PyTorch ROCm 7+ support
|
| 241 |
+
- **Conv3d Memory Workaround**: Fixes PyTorch 2.9+ cuDNN memory bug (3x usage reduction)
|
| 242 |
+
- **Flash Attention Optional**: Graceful fallback to SDPA when flash-attn unavailable
|
| 243 |
+
|
| 244 |
+
### 📚 Code Quality
|
| 245 |
+
|
| 246 |
+
- **Modular Architecture**: Split monolithic files into focused modules (generation_phases, model_configuration, etc.)
|
| 247 |
+
- **Comprehensive Documentation**: Extensive docstrings with type hints across all modules
|
| 248 |
+
- **Better Error Handling**: Early validation, clear error messages, installation instructions
|
| 249 |
+
- **Consistent Logging**: Unified indentation, better categorization, concise messages
|
| 250 |
+
|
| 251 |
+
**2025.08.07**
|
| 252 |
+
|
| 253 |
+
- 🎯 **Unified Debug System**: New structured logging with categories, timers, and memory tracking. `enable_debug` now available on main node
|
| 254 |
+
- ⚡ **Smart FP8 Optimization**: FP8 models now keep native FP8 storage, converting to BFloat16 only for arithmetic - faster and more memory efficient than FP16
|
| 255 |
+
- 📦 **Model Registry**: Multi-repo support (numz/ & AInVFX/), auto-discovery of user models, added mixed FP8 variants to fix 7B artifacts
|
| 256 |
+
- 💾 **Model Caching**: `cache_model` moved to main node, fixed memory leaks with proper RoPE/wrapper cleanup
|
| 257 |
+
- 🧹 **Code Cleanup**: New modular structure (`constants.py`, `model_registry.py`, `debug.py`), removed legacy code
|
| 258 |
+
- 🚀 **Performance**: Better memory management with `torch.cuda.ipc_collect()`, improved RoPE handling
|
| 259 |
+
|
| 260 |
+
**2025.07.17**
|
| 261 |
+
|
| 262 |
+
- 🛠️ Add 7B sharp Models: add 2 new 7B models with sharpen output
|
| 263 |
+
|
| 264 |
+
**2025.07.11**
|
| 265 |
+
|
| 266 |
+
- 🎬 Complete tutorial released: Adrien from [AInVFX](https://www.youtube.com/@AInVFX) created an in-depth ComfyUI SeedVR2 guide covering everything from basic setup to advanced BlockSwap techniques for running on consumer GPUs. Perfect for understanding memory optimization and upscaling of image sequences with alpha channel! [Watch the tutorial](#-usage)
|
| 267 |
+
|
| 268 |
+
**2025.09.07**
|
| 269 |
+
|
| 270 |
+
- 🛠️ Blockswap Integration: Big thanks to [Adrien Toupet](https://github.com/adrientoupet) from [AInVFX](https://www.youtube.com/@AInVFX) for this :), useful for low VRAM users (see [usage](#-usage) section)
|
| 271 |
+
|
| 272 |
+
**2025.07.03**
|
| 273 |
+
|
| 274 |
+
- 🛠️ Can run as **standalone mode** with **Multi GPU** see [🖥️ Run as Standalone](#run-as-standalone-cli)
|
| 275 |
+
|
| 276 |
+
**2025.06.30**
|
| 277 |
+
|
| 278 |
+
- 🚀 Speed Up the process and less VRAM used
|
| 279 |
+
- 🛠️ Fixed memory leak on 3B models
|
| 280 |
+
- ❌ Can now interrupt process if needed
|
| 281 |
+
- ✅ Refactored the code for better sharing with the community, feel free to propose pull requests
|
| 282 |
+
- 🛠️ Removed flash attention dependency (thanks to [luke2642](https://github.com/Luke2642) !!)
|
| 283 |
+
|
| 284 |
+
**2025.06.24**
|
| 285 |
+
|
| 286 |
+
- 🚀 Speed up the process until x4
|
| 287 |
+
|
| 288 |
+
**2025.06.22**
|
| 289 |
+
|
| 290 |
+
- 💪 FP8 compatibility !
|
| 291 |
+
- 🚀 Speed Up all Process
|
| 292 |
+
- 🚀 less VRAM consumption (Stay high, batch_size=1 for RTX4090 max, I'm trying to fix that)
|
| 293 |
+
- 🛠️ Better benchmark coming soon
|
| 294 |
+
|
| 295 |
+
**2025.06.20**
|
| 296 |
+
|
| 297 |
+
- 🛠️ Initial push
|
| 298 |
+
|
| 299 |
+
## 🎯 Features
|
| 300 |
+
|
| 301 |
+
### Core Capabilities
|
| 302 |
+
- **High-Quality Diffusion-Based Upscaling**: One-step diffusion model for video and image enhancement
|
| 303 |
+
- **Temporal Consistency**: Maintains coherence across video frames with configurable batch processing
|
| 304 |
+
- **Multi-Format Support**: Handles RGB and RGBA (alpha channel) for both videos and images
|
| 305 |
+
- **Any Video Length**: Suitable for any video length
|
| 306 |
+
|
| 307 |
+
### Model Support
|
| 308 |
+
- **Multiple Model Variants**: 3B and 7B parameter models with different precision options
|
| 309 |
+
- **FP16, FP8, and GGUF Quantization**: Choose between full precision (FP16), mixed precision (FP8), or heavily quantized GGUF models for different VRAM requirements
|
| 310 |
+
- **Automatic Model Downloads**: Models are automatically downloaded from HuggingFace on first use
|
| 311 |
+
|
| 312 |
+
### Memory Optimization
|
| 313 |
+
- **BlockSwap Technology**: Dynamically swap transformer blocks between GPU and CPU memory to run large models on limited VRAM
|
| 314 |
+
- **VAE Tiling**: Process large resolutions with tiled encoding/decoding to reduce VRAM usage
|
| 315 |
+
- **Intelligent Offloading**: Offload models and intermediate tensors to CPU or secondary GPUs between processing phases
|
| 316 |
+
- **GGUF Quantization Support**: Run models with 4-bit or 8-bit quantization for extreme VRAM savings
|
| 317 |
+
|
| 318 |
+
### Performance Features
|
| 319 |
+
- **torch.compile Integration**: Optional 20-40% DiT speedup and 15-25% VAE speedup with PyTorch 2.0+ compilation
|
| 320 |
+
- **Multi-GPU CLI**: Distribute workload across multiple GPUs with automatic temporal overlap blending
|
| 321 |
+
- **Model Caching**: Keep models loaded between generations for single-GPU directory processing or multi-GPU streaming
|
| 322 |
+
- **Flexible Attention Backends**: Choose between PyTorch SDPA (stable, always available), Flash Attention 2/3, or SageAttention 2/3 for faster computation on supported hardware
|
| 323 |
+
|
| 324 |
+
### Quality Control
|
| 325 |
+
- **Advanced Color Correction**: Five methods including LAB (recommended for highest fidelity), wavelet, wavelet adaptive, HSV, and AdaIN
|
| 326 |
+
- **Noise Injection Controls**: Fine-tune input and latent noise scales for artifact reduction at high resolutions
|
| 327 |
+
- **Configurable Resolution Limits**: Set target and maximum resolutions with automatic aspect ratio preservation
|
| 328 |
+
|
| 329 |
+
### Workflow Features
|
| 330 |
+
- **ComfyUI Integration**: Four dedicated nodes for complete control over the upscaling pipeline
|
| 331 |
+
- **Standalone CLI**: Command-line interface for batch processing and automation
|
| 332 |
+
- **Debug Logging**: Comprehensive debug mode with memory tracking, timing information, and processing details
|
| 333 |
+
- **Progress Reporting**: Real-time progress updates during processing
|
| 334 |
+
|
| 335 |
+
## 🔧 Requirements
|
| 336 |
+
|
| 337 |
+
### Hardware
|
| 338 |
+
|
| 339 |
+
With the current optimizations (tiling, BlockSwap, GGUF quantization), SeedVR2 can run on a wide range of hardware:
|
| 340 |
+
|
| 341 |
+
- **Minimal VRAM** (8GB or less): Use GGUF Q4_K_M models with BlockSwap and VAE tiling enabled
|
| 342 |
+
- **Moderate VRAM** (12-16GB): Use FP8 models with BlockSwap or VAE tiling as needed
|
| 343 |
+
- **High VRAM** (24GB+): Use FP16 models for best quality and speed without memory optimizations
|
| 344 |
+
|
| 345 |
+
### Software
|
| 346 |
+
|
| 347 |
+
- **ComfyUI**: Latest version recommended
|
| 348 |
+
- **Python**: 3.12+ (Python 3.12 and 3.13 tested and recommended)
|
| 349 |
+
- **PyTorch**: 2.0+ for torch.compile support (optional but recommended)
|
| 350 |
+
- **Triton**: Required for torch.compile with inductor backend (optional)
|
| 351 |
+
- **Flash Attention / SageAttention**: Flash Attention 2 (Ampere+), Flash Attention 3 (Hopper+), SageAttention 2 or SageAttention 3 (Blackwell) provide faster attention computation on supported hardware (optional, falls back to PyTorch SDPA)
|
| 352 |
+
|
| 353 |
+
## 📦 Installation
|
| 354 |
+
|
| 355 |
+
### Option 1: ComfyUI Manager (Recommended)
|
| 356 |
+
|
| 357 |
+
1. Open ComfyUI Manager in your ComfyUI interface
|
| 358 |
+
2. Click "Custom Nodes Manager"
|
| 359 |
+
3. Search for "ComfyUI-SeedVR2_VideoUpscaler"
|
| 360 |
+
4. Click "Install" and restart ComfyUI
|
| 361 |
+
|
| 362 |
+
**Registry Link**: [ComfyUI Registry - SeedVR2 Video Upscaler](https://registry.comfy.org/nodes/seedvr2_videoupscaler)
|
| 363 |
+
|
| 364 |
+
### Option 2: Manual Installation
|
| 365 |
+
|
| 366 |
+
1. **Clone the repository** into your ComfyUI custom nodes directory:
|
| 367 |
+
```bash
|
| 368 |
+
cd ComfyUI
|
| 369 |
+
git clone https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler.git custom_nodes/seedvr2_videoupscaler
|
| 370 |
+
```
|
| 371 |
+
|
| 372 |
+
2. **Install dependencies using standalone Python**:
|
| 373 |
+
```bash
|
| 374 |
+
# Install requirements (from same ComfyUI directory)
|
| 375 |
+
# Windows:
|
| 376 |
+
.venv\Scripts\python.exe -m pip install -r custom_nodes\seedvr2_videoupscaler\requirements.txt
|
| 377 |
+
# Linux/macOS:
|
| 378 |
+
.venv/bin/python -m pip install -r custom_nodes/seedvr2_videoupscaler/requirements.txt
|
| 379 |
+
```
|
| 380 |
+
|
| 381 |
+
3. **Restart ComfyUI**
|
| 382 |
+
|
| 383 |
+
### Model Installation
|
| 384 |
+
|
| 385 |
+
Models will be **automatically downloaded** on first use and saved to `ComfyUI/models/SEEDVR2`.
|
| 386 |
+
|
| 387 |
+
You can also manually download models from:
|
| 388 |
+
- Main models available at [numz/SeedVR2_comfyUI](https://huggingface.co/numz/SeedVR2_comfyUI/tree/main) and [AInVFX/SeedVR2_comfyUI](https://huggingface.co/AInVFX/SeedVR2_comfyUI/tree/main)
|
| 389 |
+
- Additional GGUF models available at [cmeka/SeedVR2-GGUF](https://huggingface.co/cmeka/SeedVR2-GGUF/tree/main)
|
| 390 |
+
|
| 391 |
+
## 📖 Usage
|
| 392 |
+
|
| 393 |
+
### 🎬 Video Tutorials
|
| 394 |
+
|
| 395 |
+
#### Latest Version Deep Dive (Recommended)
|
| 396 |
+
|
| 397 |
+
Complete walkthrough of version 2.5 by Adrien from [AInVFX](https://www.youtube.com/@AInVFX), covering the new 4-node architecture, GGUF support, memory optimizations, and production workflows:
|
| 398 |
+
|
| 399 |
+
[](https://youtu.be/MBtWYXq_r60)
|
| 400 |
+
|
| 401 |
+
This comprehensive tutorial covers:
|
| 402 |
+
- Installing v2.5 through ComfyUI Manager and troubleshooting conflicts
|
| 403 |
+
- Understanding the new 4-node modular architecture and why we rebuilt it
|
| 404 |
+
- Running 7B models on 8GB VRAM with GGUF quantization
|
| 405 |
+
- Configuring BlockSwap, VAE tiling, and torch.compile for your hardware
|
| 406 |
+
- Image and video upscaling workflows with alpha channel support
|
| 407 |
+
- CLI for batch processing and multi-GPU rendering
|
| 408 |
+
- Memory optimization strategies for different VRAM levels
|
| 409 |
+
- Real production tips and the critical batch_size formula (4n+1)
|
| 410 |
+
|
| 411 |
+
#### Previous Version Tutorial
|
| 412 |
+
|
| 413 |
+
For reference, here's the original tutorial covering the initial release:
|
| 414 |
+
|
| 415 |
+
[](https://youtu.be/I0sl45GMqNg)
|
| 416 |
+
|
| 417 |
+
*Note: This tutorial covers the previous single-node architecture. While the UI has changed significantly in v2.5, the core concepts about BlockSwap and memory management remain valuable.*
|
| 418 |
+
|
| 419 |
+
### Node Setup
|
| 420 |
+
|
| 421 |
+
SeedVR2 uses a modular node architecture with four specialized nodes:
|
| 422 |
+
|
| 423 |
+
#### 1. SeedVR2 (Down)Load DiT Model
|
| 424 |
+
|
| 425 |
+

|
| 426 |
+
|
| 427 |
+
Configure the DiT (Diffusion Transformer) model for video upscaling.
|
| 428 |
+
|
| 429 |
+
**Parameters:**
|
| 430 |
+
|
| 431 |
+
- **model**: Choose your DiT model
|
| 432 |
+
- **3B Models**: Faster, lower VRAM requirements
|
| 433 |
+
- `seedvr2_ema_3b_fp16.safetensors`: FP16 (best quality)
|
| 434 |
+
- `seedvr2_ema_3b_fp8_e4m3fn.safetensors`: FP8 8-bit (good quality)
|
| 435 |
+
- `seedvr2_ema_3b-Q4_K_M.gguf`: GGUF 4-bit quantized (acceptable quality)
|
| 436 |
+
- `seedvr2_ema_3b-Q8_0.gguf`: GGUF 8-bit quantized (good quality)
|
| 437 |
+
- **7B Models**: Higher quality, higher VRAM requirements
|
| 438 |
+
- `seedvr2_ema_7b_fp16.safetensors`: FP16 (best quality)
|
| 439 |
+
- `seedvr2_ema_7b_fp8_e4m3fn_mixed_block35_fp16.safetensors`: FP8 with last block in FP16 to reduce artifacts (good quality)
|
| 440 |
+
- `seedvr2_ema_7b-Q4_K_M.gguf`: GGUF 4-bit quantized (acceptable quality)
|
| 441 |
+
- `seedvr2_ema_7b_sharp_*`: Sharp variants for enhanced detail
|
| 442 |
+
|
| 443 |
+
- **device**: GPU device for DiT inference (e.g., `cuda:0`)
|
| 444 |
+
|
| 445 |
+
- **offload_device**: Device to offload DiT model when not actively processing
|
| 446 |
+
- `none`: Keep model on inference device (fastest, highest VRAM)
|
| 447 |
+
- `cpu`: Offload to system RAM (reduces VRAM)
|
| 448 |
+
- `cuda:X`: Offload to another GPU (good balance if available)
|
| 449 |
+
|
| 450 |
+
- **cache_model**: Keep DiT model loaded on offload_device between workflow runs
|
| 451 |
+
- Useful for batch processing to avoid repeated loading
|
| 452 |
+
- Requires offload_device to be set
|
| 453 |
+
|
| 454 |
+
- **blocks_to_swap**: BlockSwap memory optimization
|
| 455 |
+
- `0`: Disabled (default)
|
| 456 |
+
- `1-32`: Number of transformer blocks to swap for 3B model
|
| 457 |
+
- `1-36`: Number of transformer blocks to swap for 7B model
|
| 458 |
+
- Higher values = more VRAM savings but slower processing
|
| 459 |
+
- Requires offload_device to be set and different from device
|
| 460 |
+
|
| 461 |
+
- **swap_io_components**: Offload input/output embeddings and normalization layers
|
| 462 |
+
- Additional VRAM savings when combined with blocks_to_swap
|
| 463 |
+
- Requires offload_device to be set and different from device
|
| 464 |
+
|
| 465 |
+
- **attention_mode**: Attention computation backend
|
| 466 |
+
- `sdpa`: PyTorch scaled_dot_product_attention (default, always available)
|
| 467 |
+
- `flash_attn_2`: Flash Attention 2 (Ampere+, requires flash-attn package)
|
| 468 |
+
- `flash_attn_3`: Flash Attention 3 (Hopper+, requires flash-attn with FA3 support)
|
| 469 |
+
- `sageattn_2`: SageAttention 2 (requires sageattention package)
|
| 470 |
+
- `sageattn_3`: SageAttention 3 (Blackwell/RTX 50xx, requires sageattn3 package)
|
| 471 |
+
|
| 472 |
+
- **torch_compile_args**: Connect to SeedVR2 Torch Compile Settings node for 20-40% speedup
|
| 473 |
+
|
| 474 |
+
**BlockSwap Explained:**
|
| 475 |
+
|
| 476 |
+
BlockSwap enables running large models on GPUs with limited VRAM by dynamically swapping transformer blocks between GPU and CPU memory during inference.
|
| 477 |
+
|
| 478 |
+
> **Note:** BlockSwap is not available on macOS. Apple Silicon Macs use unified memory architecture where GPU and CPU share the same memory pool, making BlockSwap meaningless. The option will be automatically disabled with a warning if requested on macOS.
|
| 479 |
+
|
| 480 |
+
Here's how it works:
|
| 481 |
+
|
| 482 |
+
- **What it does**: Keeps only the currently-needed transformer blocks on the GPU, while storing the rest on CPU or another device
|
| 483 |
+
- **When to use it**: When you get OOM (Out of Memory) errors during the upscaling phase
|
| 484 |
+
- **How to configure**:
|
| 485 |
+
1. Set `offload_device` to `cpu` or another GPU
|
| 486 |
+
2. Start with `blocks_to_swap=16` (half the blocks)
|
| 487 |
+
3. If still getting OOM, increase to 24 or 32 (3B) / 36 (7B)
|
| 488 |
+
4. Enable `swap_io_components` for maximum VRAM savings
|
| 489 |
+
5. If you have plenty of VRAM, decrease or set to 0 for faster processing
|
| 490 |
+
|
| 491 |
+
**Example Configuration for Low VRAM (8GB)**:
|
| 492 |
+
- model: `seedvr2_ema_3b-Q8_0.gguf`
|
| 493 |
+
- device: `cuda:0`
|
| 494 |
+
- offload_device: `cpu`
|
| 495 |
+
- blocks_to_swap: `32`
|
| 496 |
+
- swap_io_components: `True`
|
| 497 |
+
|
| 498 |
+
#### 2. SeedVR2 (Down)Load VAE Model
|
| 499 |
+
|
| 500 |
+

|
| 501 |
+
|
| 502 |
+
Configure the VAE (Variational Autoencoder) model for encoding/decoding video frames.
|
| 503 |
+
|
| 504 |
+
**Parameters:**
|
| 505 |
+
|
| 506 |
+
- **model**: VAE model selection
|
| 507 |
+
- `ema_vae_fp16.safetensors`: Default and recommended
|
| 508 |
+
|
| 509 |
+
- **device**: GPU device for VAE inference (e.g., `cuda:0`)
|
| 510 |
+
|
| 511 |
+
- **offload_device**: Device to offload VAE model when not actively processing
|
| 512 |
+
- `none`: Keep model on inference device (default, fastest)
|
| 513 |
+
- `cpu`: Offload to system RAM (reduces VRAM)
|
| 514 |
+
- `cuda:X`: Offload to another GPU (good balance if available)
|
| 515 |
+
|
| 516 |
+
- **cache_model**: Keep VAE model loaded on offload_device between workflow runs
|
| 517 |
+
- Requires offload_device to be set
|
| 518 |
+
|
| 519 |
+
- **encode_tiled**: Enable tiled encoding to reduce VRAM usage during encoding phase
|
| 520 |
+
- Enable if you see OOM errors during the "Encoding" phase in debug logs
|
| 521 |
+
|
| 522 |
+
- **encode_tile_size**: Encoding tile size in pixels (default: 1024)
|
| 523 |
+
- Applied to both height and width
|
| 524 |
+
- Lower values reduce VRAM but may increase processing time
|
| 525 |
+
|
| 526 |
+
- **encode_tile_overlap**: Encoding tile overlap in pixels (default: 128)
|
| 527 |
+
- Reduces visible seams between tiles
|
| 528 |
+
|
| 529 |
+
- **decode_tiled**: Enable tiled decoding to reduce VRAM usage during decoding phase
|
| 530 |
+
- Enable if you see OOM errors during the "Decoding" phase in debug logs
|
| 531 |
+
|
| 532 |
+
- **decode_tile_size**: Decoding tile size in pixels (default: 1024)
|
| 533 |
+
|
| 534 |
+
- **decode_tile_overlap**: Decoding tile overlap in pixels (default: 128)
|
| 535 |
+
|
| 536 |
+
- **torch_compile_args**: Connect to SeedVR2 Torch Compile Settings node for 15-25% speedup
|
| 537 |
+
|
| 538 |
+
**VAE Tiling Explained:**
|
| 539 |
+
|
| 540 |
+
VAE tiling processes large resolutions in smaller tiles to reduce VRAM requirements. Here's how to use it:
|
| 541 |
+
|
| 542 |
+
1. **Run without tiling first** and monitor the debug logs (enable `enable_debug` on main node)
|
| 543 |
+
2. **If OOM during "Encoding" phase**:
|
| 544 |
+
- Enable `encode_tiled`
|
| 545 |
+
- If still OOM, reduce `encode_tile_size` (try 768, 512, etc.)
|
| 546 |
+
3. **If OOM during "Decoding" phase**:
|
| 547 |
+
- Enable `decode_tiled`
|
| 548 |
+
- If still OOM, reduce `decode_tile_size`
|
| 549 |
+
4. **Adjust overlap** (default 128) if you see visible seams in output (increase it) or processing times are too slow (decrease it).
|
| 550 |
+
|
| 551 |
+
**Example Configuration for High Resolution (4K)**:
|
| 552 |
+
- encode_tiled: `True`
|
| 553 |
+
- encode_tile_size: `1024`
|
| 554 |
+
- encode_tile_overlap: `128`
|
| 555 |
+
- decode_tiled: `True`
|
| 556 |
+
- decode_tile_size: `1024`
|
| 557 |
+
- decode_tile_overlap: `128`
|
| 558 |
+
|
| 559 |
+
#### 3. SeedVR2 Torch Compile Settings (Optional)
|
| 560 |
+
|
| 561 |
+

|
| 562 |
+
|
| 563 |
+
Configure torch.compile optimization for 20-40% DiT speedup and 15-25% VAE speedup.
|
| 564 |
+
|
| 565 |
+
**Requirements:**
|
| 566 |
+
- PyTorch 2.0+
|
| 567 |
+
- Triton (for inductor backend)
|
| 568 |
+
|
| 569 |
+
**Parameters:**
|
| 570 |
+
|
| 571 |
+
- **backend**: Compilation backend
|
| 572 |
+
- `inductor`: Full optimization with Triton kernel generation and fusion (recommended)
|
| 573 |
+
- `cudagraphs`: Lightweight wrapper using CUDA graphs, no kernel optimization
|
| 574 |
+
|
| 575 |
+
- **mode**: Optimization level (compilation time vs runtime performance)
|
| 576 |
+
- `default`: Fast compilation with good speedup (recommended for development)
|
| 577 |
+
- `reduce-overhead`: Lower overhead, optimized for smaller models
|
| 578 |
+
- `max-autotune`: Slowest compilation, best runtime performance (recommended for production)
|
| 579 |
+
- `max-autotune-no-cudagraphs`: Like max-autotune but without CUDA graphs
|
| 580 |
+
|
| 581 |
+
- **fullgraph**: Compile entire model as single graph without breaks
|
| 582 |
+
- `False`: Allow graph breaks for better compatibility (default, recommended)
|
| 583 |
+
- `True`: Enforce no breaks for maximum optimization (may fail with dynamic shapes)
|
| 584 |
+
|
| 585 |
+
- **dynamic**: Handle varying input shapes without recompilation
|
| 586 |
+
- `False`: Specialize for exact input shapes (default)
|
| 587 |
+
- `True`: Create dynamic kernels that adapt to shape variations (enable when processing different resolutions or batch sizes)
|
| 588 |
+
|
| 589 |
+
- **dynamo_cache_size_limit**: Max cached compiled versions per function (default: 64)
|
| 590 |
+
- Higher = more memory, lower = more recompilation
|
| 591 |
+
|
| 592 |
+
- **dynamo_recompile_limit**: Max recompilation attempts before falling back to eager mode (default: 128)
|
| 593 |
+
- Safety limit to prevent compilation loops
|
| 594 |
+
|
| 595 |
+
**Usage:**
|
| 596 |
+
1. Add this node to your workflow
|
| 597 |
+
2. Connect its output to the `torch_compile_args` input of DiT and/or VAE loader nodes
|
| 598 |
+
3. First run will be slow (compilation), subsequent runs will be much faster
|
| 599 |
+
|
| 600 |
+
**When to use:**
|
| 601 |
+
- torch.compile only makes sense when processing **multiple batches, long videos, or many tiles**
|
| 602 |
+
- For single images or short clips, the compilation time outweighs the speed improvement
|
| 603 |
+
- Best suited for batch processing workflows or long videos
|
| 604 |
+
|
| 605 |
+
**Recommended Settings:**
|
| 606 |
+
- For development/testing: `mode=default`, `backend=inductor`, `fullgraph=False`
|
| 607 |
+
- For production: `mode=max-autotune`, `backend=inductor`, `fullgraph=False`
|
| 608 |
+
|
| 609 |
+
#### 4. SeedVR2 Video Upscaler (Main Node)
|
| 610 |
+
|
| 611 |
+

|
| 612 |
+
|
| 613 |
+
Main upscaling node that processes video frames using DiT and VAE models.
|
| 614 |
+
|
| 615 |
+
**Required Inputs:**
|
| 616 |
+
|
| 617 |
+
- **image**: Input video frames as image batch (RGB or RGBA format)
|
| 618 |
+
- **dit**: DiT model configuration from SeedVR2 (Down)Load DiT Model node
|
| 619 |
+
- **vae**: VAE model configuration from SeedVR2 (Down)Load VAE Model node
|
| 620 |
+
|
| 621 |
+
**Parameters:**
|
| 622 |
+
|
| 623 |
+
- **seed**: Random seed for reproducible generation (default: 42)
|
| 624 |
+
- Same seed with same inputs produces identical output
|
| 625 |
+
|
| 626 |
+
- **resolution**: Target resolution for shortest edge in pixels (default: 1080)
|
| 627 |
+
- Maintains aspect ratio automatically
|
| 628 |
+
|
| 629 |
+
- **max_resolution**: Maximum resolution for any edge (default: 0 = no limit)
|
| 630 |
+
- Automatically scales down if exceeded to prevent OOM
|
| 631 |
+
|
| 632 |
+
- **batch_size**: Frames per batch (default: 5)
|
| 633 |
+
- **CRITICAL REQUIREMENT**: Must follow the **4n+1 formula** (1, 5, 9, 13, 17, 21, 25, ...)
|
| 634 |
+
- **Why this matters**: The model uses these frames for temporal consistency calculations
|
| 635 |
+
- **Minimum 5 for temporal consistency**: Use 1 only for single images or when temporal consistency isn't needed
|
| 636 |
+
- **Match shot length ideally**: For best results, set batch_size to match your shot length (e.g., batch_size=21 for a 20-frame shot)
|
| 637 |
+
- **VRAM impact**: Higher batch_size = better quality and speed but requires more VRAM
|
| 638 |
+
- **If you get OOM with batch_size=5**: Try optimization techniques first (model offloading, BlockSwap, GGUF models...) before reducing batch_size or input resolution, as these directly impact quality
|
| 639 |
+
|
| 640 |
+
**uniform_batch_size** (default: False)
|
| 641 |
+
- Pads the final batch to match `batch_size` for uniform processing
|
| 642 |
+
- Prevents temporal artifacts when the last batch is significantly smaller than others
|
| 643 |
+
- Example: 45 frames with `batch_size=33` creates [33, 33] instead of [33, 12]
|
| 644 |
+
- Recommended when using large batch sizes and video length is not a multiple of `batch_size`
|
| 645 |
+
- Increases VRAM usage slightly but ensures consistent temporal coherence across all batches
|
| 646 |
+
|
| 647 |
+
- **temporal_overlap**: Overlapping frames between batches (default: 0)
|
| 648 |
+
- Used for blending between batches to reduce temporal artifacts
|
| 649 |
+
- Range: 0-16 frames
|
| 650 |
+
|
| 651 |
+
- **prepend_frames**: Frames to prepend (default: 0)
|
| 652 |
+
- Prepends reversed frames to reduce artifacts at video start
|
| 653 |
+
- Automatically removed after processing
|
| 654 |
+
- Range: 0-32 frames
|
| 655 |
+
|
| 656 |
+
- **color_correction**: Color correction method (default: "wavelet")
|
| 657 |
+
- **`lab`**: Full perceptual color matching with detail preservation (recommended for highest fidelity to original)
|
| 658 |
+
- **`wavelet`**: Frequency-based natural colors, preserves details well
|
| 659 |
+
- **`wavelet_adaptive`**: Wavelet base + targeted saturation correction
|
| 660 |
+
- **`hsv`**: Hue-conditional saturation matching
|
| 661 |
+
- **`adain`**: Statistical style transfer
|
| 662 |
+
- **`none`**: No color correction
|
| 663 |
+
|
| 664 |
+
- **input_noise_scale**: Input noise injection scale 0.0-1.0 (default: 0.0)
|
| 665 |
+
- Adds noise to input frames to reduce artifacts at very high resolutions
|
| 666 |
+
- Try 0.1-0.3 if you see artifacts with high output resolutions
|
| 667 |
+
|
| 668 |
+
- **latent_noise_scale**: Latent space noise scale 0.0-1.0 (default: 0.0)
|
| 669 |
+
- Adds noise during diffusion process, can soften excessive detail
|
| 670 |
+
- Use if input_noise doesn't help, try 0.05-0.15
|
| 671 |
+
|
| 672 |
+
- **offload_device**: Device for storing intermediate tensors between processing phases (default: "cpu")
|
| 673 |
+
- `none`: Keep all tensors on inference device (fastest but highest VRAM)
|
| 674 |
+
- `cpu`: Offload to system RAM (recommended for long videos, slower transfers)
|
| 675 |
+
- `cuda:X`: Offload to another GPU (good balance if available, faster than CPU)
|
| 676 |
+
|
| 677 |
+
- **enable_debug**: Enable detailed debug logging (default: False)
|
| 678 |
+
- Shows memory usage, timing information, and processing details
|
| 679 |
+
- **Highly recommended** for troubleshooting OOM issues
|
| 680 |
+
|
| 681 |
+
**Output:**
|
| 682 |
+
- Upscaled video frames with color correction applied
|
| 683 |
+
- Format (RGB/RGBA) matches input
|
| 684 |
+
- Range [0, 1] normalized for ComfyUI compatibility
|
| 685 |
+
|
| 686 |
+
### Typical Workflow Setup
|
| 687 |
+
|
| 688 |
+
**Basic Workflow (High VRAM - 24GB+)**:
|
| 689 |
+
```
|
| 690 |
+
Load Video Frames
|
| 691 |
+
↓
|
| 692 |
+
SeedVR2 Load DiT Model
|
| 693 |
+
├─ model: seedvr2_ema_3b_fp16.safetensors
|
| 694 |
+
└─ device: cuda:0
|
| 695 |
+
↓
|
| 696 |
+
SeedVR2 Load VAE Model
|
| 697 |
+
├─ model: ema_vae_fp16.safetensors
|
| 698 |
+
└─ device: cuda:0
|
| 699 |
+
↓
|
| 700 |
+
SeedVR2 Video Upscaler
|
| 701 |
+
├─ batch_size: 21
|
| 702 |
+
└─ resolution: 1080
|
| 703 |
+
↓
|
| 704 |
+
Save Video/Frames
|
| 705 |
+
```
|
| 706 |
+
|
| 707 |
+
**Low VRAM Workflow (8-12GB)**:
|
| 708 |
+
```
|
| 709 |
+
Load Video Frames
|
| 710 |
+
↓
|
| 711 |
+
SeedVR2 Load DiT Model
|
| 712 |
+
├─ model: seedvr2_ema_3b-Q8_0.gguf
|
| 713 |
+
├─ device: cuda:0
|
| 714 |
+
├─ offload_device: cpu
|
| 715 |
+
├─ blocks_to_swap: 32
|
| 716 |
+
└─ swap_io_components: True
|
| 717 |
+
↓
|
| 718 |
+
SeedVR2 Load VAE Model
|
| 719 |
+
├─ model: ema_vae_fp16.safetensors
|
| 720 |
+
├─ device: cuda:0
|
| 721 |
+
├─ encode_tiled: True
|
| 722 |
+
└─ decode_tiled: True
|
| 723 |
+
↓
|
| 724 |
+
SeedVR2 Video Upscaler
|
| 725 |
+
├─ batch_size: 5
|
| 726 |
+
└─ resolution: 720
|
| 727 |
+
↓
|
| 728 |
+
Save Video/Frames
|
| 729 |
+
```
|
| 730 |
+
|
| 731 |
+
**High Performance Workflow (24GB+ with torch.compile)**:
|
| 732 |
+
```
|
| 733 |
+
Load Video Frames
|
| 734 |
+
↓
|
| 735 |
+
SeedVR2 Torch Compile Settings
|
| 736 |
+
├─ mode: max-autotune
|
| 737 |
+
└─ backend: inductor
|
| 738 |
+
↓
|
| 739 |
+
SeedVR2 Load DiT Model
|
| 740 |
+
├─ model: seedvr2_ema_7b_sharp_fp16.safetensors
|
| 741 |
+
├─ device: cuda:0
|
| 742 |
+
└─ torch_compile_args: connected
|
| 743 |
+
↓
|
| 744 |
+
SeedVR2 Load VAE Model
|
| 745 |
+
├─ model: ema_vae_fp16.safetensors
|
| 746 |
+
├─ device: cuda:0
|
| 747 |
+
└─ torch_compile_args: connected
|
| 748 |
+
↓
|
| 749 |
+
SeedVR2 Video Upscaler
|
| 750 |
+
├─ batch_size: 81
|
| 751 |
+
└─ resolution: 1080
|
| 752 |
+
↓
|
| 753 |
+
Save Video/Frames
|
| 754 |
+
```
|
| 755 |
+
|
| 756 |
+
## 🖥️ Run as Standalone (CLI)
|
| 757 |
+
|
| 758 |
+
The standalone CLI provides powerful batch processing capabilities with multi-GPU support and sophisticated optimization options.
|
| 759 |
+
|
| 760 |
+
### Prerequisites
|
| 761 |
+
|
| 762 |
+
Choose the appropriate setup based on your installation:
|
| 763 |
+
|
| 764 |
+
#### Option 1: Already Have ComfyUI with SeedVR2 Installed
|
| 765 |
+
|
| 766 |
+
If you've already installed SeedVR2 as part of ComfyUI (via [ComfyUI installation](#-installation)), you can use the CLI directly:
|
| 767 |
+
|
| 768 |
+
```bash
|
| 769 |
+
# Navigate to your ComfyUI directory
|
| 770 |
+
cd ComfyUI
|
| 771 |
+
|
| 772 |
+
# Run the CLI using standalone Python (display help message)
|
| 773 |
+
# Windows:
|
| 774 |
+
.venv\Scripts\python.exe custom_nodes\seedvr2_videoupscaler\inference_cli.py --help
|
| 775 |
+
# Linux/macOS:
|
| 776 |
+
.venv/bin/python custom_nodes/seedvr2_videoupscaler/inference_cli.py --help
|
| 777 |
+
```
|
| 778 |
+
|
| 779 |
+
**Skip to [Command Line Usage](#command-line-usage) below.**
|
| 780 |
+
|
| 781 |
+
#### Option 2: Standalone Installation (Without ComfyUI)
|
| 782 |
+
|
| 783 |
+
If you want to use the CLI without ComfyUI installation, follow these steps:
|
| 784 |
+
|
| 785 |
+
1. **Install [uv](https://docs.astral.sh/uv/getting-started/installation/)** (modern Python package manager):
|
| 786 |
+
```bash
|
| 787 |
+
# Windows
|
| 788 |
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
| 789 |
+
|
| 790 |
+
# macOS and Linux
|
| 791 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 792 |
+
```
|
| 793 |
+
|
| 794 |
+
2. **Clone the repository**:
|
| 795 |
+
```bash
|
| 796 |
+
git clone https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler.git seedvr2_videoupscaler
|
| 797 |
+
cd seedvr2_videoupscaler
|
| 798 |
+
```
|
| 799 |
+
|
| 800 |
+
3. **Create virtual environment and install dependencies**:
|
| 801 |
+
```bash
|
| 802 |
+
# Create virtual environment with Python 3.13
|
| 803 |
+
uv venv --python 3.13
|
| 804 |
+
|
| 805 |
+
# Activate virtual environment
|
| 806 |
+
# Windows:
|
| 807 |
+
.venv\Scripts\activate
|
| 808 |
+
# Linux/macOS:
|
| 809 |
+
source .venv/bin/activate
|
| 810 |
+
|
| 811 |
+
# Install PyTorch with CUDA support
|
| 812 |
+
# Check command line based on your environment: https://pytorch.org/get-started/locally/
|
| 813 |
+
uv pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu130
|
| 814 |
+
|
| 815 |
+
# Install SeedVR2 requirements
|
| 816 |
+
uv pip install -r requirements.txt
|
| 817 |
+
|
| 818 |
+
# Run the CLI (display help message)
|
| 819 |
+
# Windows:
|
| 820 |
+
.venv\Scripts\python.exe inference_cli.py --help
|
| 821 |
+
# Linux/macOS:
|
| 822 |
+
.venv/bin/python inference_cli.py --help
|
| 823 |
+
```
|
| 824 |
+
|
| 825 |
+
### Command Line Usage
|
| 826 |
+
|
| 827 |
+
The CLI provides comprehensive options for single-GPU, multi-GPU, and batch processing workflows.
|
| 828 |
+
|
| 829 |
+
**Basic Usage Examples:**
|
| 830 |
+
|
| 831 |
+
```bash
|
| 832 |
+
# Basic image upscaling
|
| 833 |
+
python inference_cli.py image.jpg
|
| 834 |
+
|
| 835 |
+
# Basic video upscaling with temporal consistency
|
| 836 |
+
python inference_cli.py video.mp4 --resolution 720 --batch_size 33
|
| 837 |
+
|
| 838 |
+
# Streaming mode for long videos (memory-efficient) with 10-bit video output (requires FFMPEG)
|
| 839 |
+
# Processes video in chunks of 330 frames to avoid loading entire video into RAM
|
| 840 |
+
# Use --temporal_overlap to ensure smooth transitions between chunks
|
| 841 |
+
python inference_cli.py long_video.mp4 \
|
| 842 |
+
--resolution 1080 \
|
| 843 |
+
--batch_size 33 \
|
| 844 |
+
--chunk_size 330 \
|
| 845 |
+
--temporal_overlap 3 \
|
| 846 |
+
--video_backend ffmpeg \
|
| 847 |
+
--10bit
|
| 848 |
+
|
| 849 |
+
# Multi-GPU processing with temporal overlap
|
| 850 |
+
python inference_cli.py video.mp4 \
|
| 851 |
+
--cuda_device 0,1 \
|
| 852 |
+
--resolution 1080 \
|
| 853 |
+
--batch_size 81 \
|
| 854 |
+
--uniform_batch_size \
|
| 855 |
+
--temporal_overlap 3 \
|
| 856 |
+
--prepend_frames 4
|
| 857 |
+
|
| 858 |
+
# Memory-optimized for low VRAM (8GB)
|
| 859 |
+
python inference_cli.py image.png \
|
| 860 |
+
--dit_model seedvr2_ema_3b-Q8_0.gguf \
|
| 861 |
+
--resolution 1080 \
|
| 862 |
+
--blocks_to_swap 32 \
|
| 863 |
+
--swap_io_components \
|
| 864 |
+
--dit_offload_device cpu \
|
| 865 |
+
--vae_offload_device cpu
|
| 866 |
+
|
| 867 |
+
# High resolution with VAE tiling
|
| 868 |
+
python inference_cli.py video.mp4 \
|
| 869 |
+
--resolution 1440 \
|
| 870 |
+
--batch_size 31 \
|
| 871 |
+
--uniform_batch_size \
|
| 872 |
+
--temporal_overlap 3 \
|
| 873 |
+
--vae_encode_tiled \
|
| 874 |
+
--vae_decode_tiled
|
| 875 |
+
|
| 876 |
+
# Batch directory processing with model caching
|
| 877 |
+
python inference_cli.py media_folder/ \
|
| 878 |
+
--output processed/ \
|
| 879 |
+
--cuda_device 0 \
|
| 880 |
+
--cache_dit \
|
| 881 |
+
--cache_vae \
|
| 882 |
+
--dit_offload_device cpu \
|
| 883 |
+
--vae_offload_device cpu \
|
| 884 |
+
--resolution 1080 \
|
| 885 |
+
--max_resolution 1920
|
| 886 |
+
```
|
| 887 |
+
|
| 888 |
+
### Command Line Arguments
|
| 889 |
+
|
| 890 |
+
**Input/Output:**
|
| 891 |
+
- `<input>`: Input file (.mp4, .avi, .png, .jpg, etc.) or directory
|
| 892 |
+
- `--output`: Output path (default: auto-generated in 'output/' directory)
|
| 893 |
+
- `--output_format`: Output format: 'mp4' (video) or 'png' (image sequence). Default: auto-detect from input type
|
| 894 |
+
- `--video_backend`: Video encoder backend: 'opencv' (default) or 'ffmpeg' (requires ffmpeg in PATH)
|
| 895 |
+
- `--10bit`: Save 10-bit video with x265 codec and yuv420p10le pixel format (reduces banding in gradients). Without this flag, ffmpeg uses x264 (yuv420p) for maximum compatibility. Requires --video_backend ffmpeg
|
| 896 |
+
- `--model_dir`: Model directory (default: ./models/SEEDVR2)
|
| 897 |
+
|
| 898 |
+
**Model Selection:**
|
| 899 |
+
- `--dit_model`: DiT model to use. Options: 3B/7B with fp16/fp8/GGUF variants (default: 3B FP8)
|
| 900 |
+
|
| 901 |
+
**Processing Parameters:**
|
| 902 |
+
- `--resolution`: Target short-side resolution in pixels (default: 1080)
|
| 903 |
+
- `--max_resolution`: Maximum resolution for any edge. Scales down if exceeded. 0 = no limit (default: 0)
|
| 904 |
+
- `--batch_size`: Frames per batch (must follow 4n+1: 1, 5, 9, 13, 17, 21...). Ideally matches shot length for best temporal consistency (default: 5)
|
| 905 |
+
- `--seed`: Random seed for reproducibility (default: 42)
|
| 906 |
+
- `--skip_first_frames`: Skip N initial frames (default: 0)
|
| 907 |
+
- `--load_cap`: Maximum total frames to load from video. 0 = load all (default: 0)
|
| 908 |
+
- `--chunk_size`: Frames per chunk for streaming mode. When > 0, processes video in memory-bounded chunks of N frames, writing each chunk before loading the next. Essential for long videos that would otherwise exceed RAM. Use with `--temporal_overlap` for seamless chunk transitions. 0 = load all frames at once (default: 0)
|
| 909 |
+
- `--prepend_frames`: Prepend N reversed frames to reduce start artifacts (auto-removed) (default: 0)
|
| 910 |
+
- `--temporal_overlap`: Frames to overlap between batches/GPUs for smooth blending (default: 0)
|
| 911 |
+
|
| 912 |
+
**Quality Control:**
|
| 913 |
+
- `--color_correction`: Color correction method: 'lab' (perceptual, recommended), 'wavelet', 'wavelet_adaptive', 'hsv', 'adain', or 'none' (default: lab)
|
| 914 |
+
- `--input_noise_scale`: Input noise injection scale (0.0-1.0). Reduces artifacts at high resolutions (default: 0.0)
|
| 915 |
+
- `--latent_noise_scale`: Latent space noise scale (0.0-1.0). Softens details if needed (default: 0.0)
|
| 916 |
+
|
| 917 |
+
**Memory Management:**
|
| 918 |
+
- `--dit_offload_device`: Device to offload DiT model: 'none' (keep on GPU), 'cpu', or 'cuda:X' (default: none)
|
| 919 |
+
- `--vae_offload_device`: Device to offload VAE model: 'none', 'cpu', or 'cuda:X' (default: none)
|
| 920 |
+
- `--blocks_to_swap`: Number of transformer blocks to swap (0=disabled, 3B: 0-32, 7B: 0-36). Requires dit_offload_device (default: 0). Not available on macOS.
|
| 921 |
+
- `--swap_io_components`: Offload I/O components for additional VRAM savings. Requires dit_offload_device. Not available on macOS.
|
| 922 |
+
|
| 923 |
+
**VAE Tiling:**
|
| 924 |
+
- `--vae_encode_tiled`: Enable VAE encode tiling to reduce VRAM during encoding
|
| 925 |
+
- `--vae_encode_tile_size`: VAE encode tile size in pixels (default: 1024)
|
| 926 |
+
- `--vae_encode_tile_overlap`: VAE encode tile overlap in pixels (default: 128)
|
| 927 |
+
- `--vae_decode_tiled`: Enable VAE decode tiling to reduce VRAM during decoding
|
| 928 |
+
- `--vae_decode_tile_size`: VAE decode tile size in pixels (default: 1024)
|
| 929 |
+
- `--vae_decode_tile_overlap`: VAE decode tile overlap in pixels (default: 128)
|
| 930 |
+
- `--tile_debug`: Visualize tiles: 'false' (default), 'encode', or 'decode'
|
| 931 |
+
|
| 932 |
+
**Performance Optimization:**
|
| 933 |
+
- `--allow_vram_overflow`: Allow VRAM overflow to system RAM. Prevents OOM but may cause severe slowdown
|
| 934 |
+
- `--attention_mode`: Attention backend: 'sdpa' (default), 'flash_attn_2' (Ampere+), 'flash_attn_3' (Hopper+), 'sageattn_2', or 'sageattn_3' (Blackwell)
|
| 935 |
+
- `--compile_dit`: Enable torch.compile for DiT model (20-40% speedup, requires PyTorch 2.0+ and Triton)
|
| 936 |
+
- `--compile_vae`: Enable torch.compile for VAE model (15-25% speedup, requires PyTorch 2.0+ and Triton)
|
| 937 |
+
- `--compile_backend`: Compilation backend: 'inductor' (full optimization) or 'cudagraphs' (lightweight) (default: inductor)
|
| 938 |
+
- `--compile_mode`: Optimization level: 'default', 'reduce-overhead', 'max-autotune', 'max-autotune-no-cudagraphs' (default: default)
|
| 939 |
+
- `--compile_fullgraph`: Compile entire model as single graph (faster but less flexible) (default: False)
|
| 940 |
+
- `--compile_dynamic`: Handle varying input shapes without recompilation (default: False)
|
| 941 |
+
- `--compile_dynamo_cache_size_limit`: Max cached compiled versions per function (default: 64)
|
| 942 |
+
- `--compile_dynamo_recompile_limit`: Max recompilation attempts before fallback (default: 128)
|
| 943 |
+
|
| 944 |
+
**Model Caching (batch processing):**
|
| 945 |
+
- `--cache_dit`: Keep DiT model in memory between generations. Works with single-GPU directory processing or multi-GPU streaming (`--chunk_size`). Requires `--dit_offload_device`
|
| 946 |
+
- `--cache_vae`: Keep VAE model in memory between generations. Works with single-GPU directory processing or multi-GPU streaming (`--chunk_size`). Requires `--vae_offload_device`
|
| 947 |
+
|
| 948 |
+
**Multi-GPU:**
|
| 949 |
+
- `--cuda_device`: CUDA device id(s). Single id (e.g., '0') or comma-separated list '0,1' for multi-GPU
|
| 950 |
+
|
| 951 |
+
**Debugging:**
|
| 952 |
+
- `--debug`: Enable verbose debug logging
|
| 953 |
+
|
| 954 |
+
### Multi-GPU Processing Explained
|
| 955 |
+
|
| 956 |
+
The CLI's multi-GPU mode uses **frame-level parallelism**: the video is split into chunks and each GPU processes its chunk independently through all 4 phases (encode → upscale → decode → postprocess). This is ideal for long videos where you want to reduce total processing time by dividing the workload.
|
| 957 |
+
|
| 958 |
+
**How it works:**
|
| 959 |
+
1. Video frames are split evenly across GPUs (e.g., 100 frames on 2 GPUs → 50 frames each)
|
| 960 |
+
2. Each GPU loads its own copy of the models and processes its chunk independently
|
| 961 |
+
3. When `--temporal_overlap` is set, chunks include overlapping frames for seamless blending
|
| 962 |
+
4. Results are concatenated (and blended at overlap regions) into the final video
|
| 963 |
+
|
| 964 |
+
**Example for 100 frames on 2 GPUs with temporal_overlap=4:**
|
| 965 |
+
```
|
| 966 |
+
GPU 0: Frames 0-53 (50 base + 4 overlap at end, processed as independent video)
|
| 967 |
+
GPU 1: Frames 50-99 (50 frames, 4 overlap at start, processed as independent video)
|
| 968 |
+
Result: Frames 0-99 with smooth blending at the transition point
|
| 969 |
+
```
|
| 970 |
+
|
| 971 |
+
**Important considerations:**
|
| 972 |
+
- Each GPU processes its chunk as a separate video with its own batch splitting
|
| 973 |
+
- `batch_size` controls batching *within* each GPU's chunk, not across GPUs
|
| 974 |
+
- For short videos (< 100 frames), single GPU is often more efficient due to model loading overhead
|
| 975 |
+
- Multi-GPU doubles VRAM usage (each GPU loads full models) but roughly halves processing time
|
| 976 |
+
|
| 977 |
+
**When to use multi-GPU:**
|
| 978 |
+
- Long videos (100+ frames) where splitting provides significant time savings
|
| 979 |
+
- When you have multiple GPUs with sufficient VRAM each
|
| 980 |
+
|
| 981 |
+
**When to use single GPU:**
|
| 982 |
+
- Short videos where model loading overhead outweighs parallel gains
|
| 983 |
+
- When you want all frames processed together for maximum temporal coherence
|
| 984 |
+
|
| 985 |
+
**Best practices:**
|
| 986 |
+
- Set `--temporal_overlap` to 2-4 frames for smooth blending between GPU chunks
|
| 987 |
+
- Higher overlap = smoother transitions but more redundant processing
|
| 988 |
+
- Use `--prepend_frames` to reduce artifacts at video start
|
| 989 |
+
- For optimal quality on short videos, use single GPU with `batch_size` matching your shot length
|
| 990 |
+
|
| 991 |
+
## ⚠️ Limitations
|
| 992 |
+
|
| 993 |
+
### Model Limitations
|
| 994 |
+
|
| 995 |
+
**Batch Size Constraint**: The model requires batch_size to follow the **4n+1 formula** (1, 5, 9, 13, 17, 21, 25, ...) due to temporal consistency architecture. All frames in a batch are processed together for temporal coherence, then batches can be blended using temporal_overlap. Ideally, set batch_size to match your shot length for optimal quality.
|
| 996 |
+
|
| 997 |
+
### Performance Considerations
|
| 998 |
+
|
| 999 |
+
**VAE Bottleneck**: Even with optimized DiT upscaling (BlockSwap, GGUF, torch.compile), the VAE encoding/decoding stages can be the bottleneck, especially for high resolutions. The VAE is slow. Use large batch_size to mitigate this.
|
| 1000 |
+
|
| 1001 |
+
**VRAM Usage**: While the integration now supports low VRAM systems (8GB or less with proper optimization), VRAM usage varies based on:
|
| 1002 |
+
- Input/output resolution (larger = more VRAM)
|
| 1003 |
+
- Batch size (higher = more VRAM but better temporal consistency and speed)
|
| 1004 |
+
- Model choice (FP16 > FP8 > GGUF in VRAM usage)
|
| 1005 |
+
- Optimization settings (BlockSwap, VAE tiling significantly reduce VRAM)
|
| 1006 |
+
|
| 1007 |
+
**Speed**: Processing speed depends on:
|
| 1008 |
+
- GPU capabilities (compute performance, VRAM bandwidth, and architecture generation)
|
| 1009 |
+
- Model size (3B faster than 7B)
|
| 1010 |
+
- Batch size (larger batch sizes are faster per frame due to better GPU utilization)
|
| 1011 |
+
- Optimization settings (torch.compile provides significant speedup)
|
| 1012 |
+
- Resolution (higher resolutions are slower)
|
| 1013 |
+
|
| 1014 |
+
### Best Practices
|
| 1015 |
+
|
| 1016 |
+
1. **Start with debug enabled** to understand where VRAM is being used
|
| 1017 |
+
2. **For OOM errors during encoding**: Enable VAE encode tiling and reduce tile size
|
| 1018 |
+
3. **For OOM errors during upscaling**: Enable BlockSwap and increase blocks_to_swap
|
| 1019 |
+
4. **For OOM errors during decoding**: Enable VAE decode tiling and reduce tile size
|
| 1020 |
+
- **If still getting OOM after trying all above**: Reduce batch_size or resolution
|
| 1021 |
+
5. **For best quality**: Use higher batch_size matching your shot length, FP16 models, and LAB color correction
|
| 1022 |
+
6. **For speed**: Use FP8/GGUF models, enable torch.compile, and use Flash Attention if available
|
| 1023 |
+
7. **Test settings with a short clip first** before processing long videos
|
| 1024 |
+
|
| 1025 |
+
## 🤝 Contributing
|
| 1026 |
+
|
| 1027 |
+
Contributions are welcome! We value community input and improvements.
|
| 1028 |
+
|
| 1029 |
+
For detailed contribution guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
| 1030 |
+
|
| 1031 |
+
**Quick Start:**
|
| 1032 |
+
|
| 1033 |
+
1. Fork the repository
|
| 1034 |
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
| 1035 |
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
| 1036 |
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
| 1037 |
+
5. Open a Pull Request to the **main** branch
|
| 1038 |
+
|
| 1039 |
+
**Get Help:**
|
| 1040 |
+
- YouTube: [AInVFX Channel](https://www.youtube.com/@AInVFX)
|
| 1041 |
+
- GitHub [Issues](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/issues): For bug reports and feature requests
|
| 1042 |
+
- GitHub [Discussions](https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/discussions): For questions and community support
|
| 1043 |
+
- Discord: adrientoupet & NumZ#7184
|
| 1044 |
+
|
| 1045 |
+
## 🙏 Credits
|
| 1046 |
+
|
| 1047 |
+
This ComfyUI implementation is a collaborative project by **[NumZ](https://github.com/numz)** and **[AInVFX](https://www.youtube.com/@AInVFX)** (Adrien Toupet), based on the original [SeedVR2](https://github.com/ByteDance-Seed/SeedVR) by ByteDance Seed Team.
|
| 1048 |
+
|
| 1049 |
+
Special thanks to our community contributors including [naxci1](https://github.com/naxci1), [thehhmdb](https://github.com/thehhmdb), [s-cerevisiae](https://github.com/s-cerevisiae), [benjaminherb](https://github.com/benjaminherb), [cmeka](https://github.com/cmeka), [FurkanGozukara](https://github.com/FurkanGozukara), [JohnAlcatraz](https://github.com/JohnAlcatraz), [lihaoyun6](https://github.com/lihaoyun6), [Luchuanzhao](https://github.com/Luchuanzhao), [Luke2642](https://github.com/Luke2642), [proxyid](https://github.com/proxyid), [q5sys](https://github.com/q5sys), and many others for their improvements, bug fixes, and testing.
|
| 1050 |
+
|
| 1051 |
+
## 📜 License
|
| 1052 |
+
|
| 1053 |
+
The code in this repository is released under the Apache 2.0 license as found in the [LICENSE](LICENSE) file.
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
ComfyUI-SeedVR2_VideoUpscaler
|
| 3 |
+
Official SeedVR2 integration for ComfyUI
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from .src.optimization.compatibility import ensure_triton_compat # noqa: F401
|
| 7 |
+
from .src.interfaces import comfy_entrypoint, SeedVR2Extension
|
| 8 |
+
|
| 9 |
+
__all__ = ["comfy_entrypoint", "SeedVR2Extension"]
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/configs_3b/main.yaml
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__object__:
|
| 2 |
+
path: projects.video_diffusion_sr.train
|
| 3 |
+
name: VideoDiffusionTrainer
|
| 4 |
+
|
| 5 |
+
dit:
|
| 6 |
+
model:
|
| 7 |
+
__object__:
|
| 8 |
+
path: "dit_3b.nadit"
|
| 9 |
+
name: "NaDiT"
|
| 10 |
+
args: "as_params"
|
| 11 |
+
vid_in_channels: 33
|
| 12 |
+
vid_out_channels: 16
|
| 13 |
+
vid_dim: 2560
|
| 14 |
+
vid_out_norm: fusedrms
|
| 15 |
+
txt_in_dim: 5120
|
| 16 |
+
txt_in_norm: fusedln
|
| 17 |
+
txt_dim: ${.vid_dim}
|
| 18 |
+
emb_dim: ${eval:'6 * ${.vid_dim}'}
|
| 19 |
+
heads: 20
|
| 20 |
+
head_dim: 128 # llm-like
|
| 21 |
+
expand_ratio: 4
|
| 22 |
+
norm: fusedrms
|
| 23 |
+
norm_eps: 1.0e-05
|
| 24 |
+
ada: single
|
| 25 |
+
qk_bias: False
|
| 26 |
+
qk_norm: fusedrms
|
| 27 |
+
patch_size: [1, 2, 2]
|
| 28 |
+
num_layers: 32 # llm-like
|
| 29 |
+
mm_layers: 10
|
| 30 |
+
mlp_type: swiglu
|
| 31 |
+
msa_type: None
|
| 32 |
+
block_type: ${eval:'${.num_layers} * ["mmdit_sr"]'} # space-full
|
| 33 |
+
window: ${eval:'${.num_layers} * [(4,3,3)]'} # space-full
|
| 34 |
+
window_method: ${eval:'${.num_layers} // 2 * ["720pwin_by_size_bysize","720pswin_by_size_bysize"]'} # space-full
|
| 35 |
+
rope_type: mmrope3d
|
| 36 |
+
rope_dim: 128
|
| 37 |
+
compile: False
|
| 38 |
+
gradient_checkpoint: True
|
| 39 |
+
fsdp:
|
| 40 |
+
sharding_strategy: _HYBRID_SHARD_ZERO2
|
| 41 |
+
|
| 42 |
+
ema:
|
| 43 |
+
decay: 0.9998
|
| 44 |
+
|
| 45 |
+
vae:
|
| 46 |
+
model:
|
| 47 |
+
__object__:
|
| 48 |
+
path: "video_vae_v3.modules.attn_video_vae"
|
| 49 |
+
name: "VideoAutoencoderKLWrapper"
|
| 50 |
+
args: "as_params"
|
| 51 |
+
freeze_encoder: False
|
| 52 |
+
gradient_checkpoint: True # Disabled to prevent VRAM leaks in inference
|
| 53 |
+
slicing:
|
| 54 |
+
split_size: 4
|
| 55 |
+
memory_device: same
|
| 56 |
+
memory_limit:
|
| 57 |
+
conv_max_mem: 0.5
|
| 58 |
+
norm_max_mem: 0.5
|
| 59 |
+
checkpoint: ema_vae_fp16.safetensors
|
| 60 |
+
scaling_factor: 0.9152
|
| 61 |
+
compile: False
|
| 62 |
+
grouping: False
|
| 63 |
+
dtype: float16
|
| 64 |
+
|
| 65 |
+
diffusion:
|
| 66 |
+
schedule:
|
| 67 |
+
type: lerp
|
| 68 |
+
T: 1000.0
|
| 69 |
+
sampler:
|
| 70 |
+
type: euler
|
| 71 |
+
prediction_type: v_lerp
|
| 72 |
+
timesteps:
|
| 73 |
+
training:
|
| 74 |
+
type: logitnormal
|
| 75 |
+
loc: 0.0
|
| 76 |
+
scale: 1.0
|
| 77 |
+
sampling:
|
| 78 |
+
type: uniform_trailing
|
| 79 |
+
steps: 50
|
| 80 |
+
transform: True
|
| 81 |
+
loss:
|
| 82 |
+
type: v_lerp
|
| 83 |
+
cfg:
|
| 84 |
+
scale: 7.5
|
| 85 |
+
rescale: 0
|
| 86 |
+
|
| 87 |
+
condition:
|
| 88 |
+
i2v: 0.0
|
| 89 |
+
v2v: 0.0
|
| 90 |
+
sr: 1.0
|
| 91 |
+
noise_scale: 0.25
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/configs_7b/main.yaml
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__object__:
|
| 2 |
+
path: projects.video_diffusion_sr.train
|
| 3 |
+
name: VideoDiffusionTrainer
|
| 4 |
+
|
| 5 |
+
dit:
|
| 6 |
+
model:
|
| 7 |
+
__object__:
|
| 8 |
+
path: "dit_7b.nadit"
|
| 9 |
+
name: "NaDiT"
|
| 10 |
+
args: "as_params"
|
| 11 |
+
vid_in_channels: 33
|
| 12 |
+
vid_out_channels: 16
|
| 13 |
+
vid_dim: 3072
|
| 14 |
+
txt_in_dim: 5120
|
| 15 |
+
txt_dim: ${.vid_dim}
|
| 16 |
+
emb_dim: ${eval:'6 * ${.vid_dim}'}
|
| 17 |
+
heads: 24
|
| 18 |
+
head_dim: 128 # llm-like
|
| 19 |
+
expand_ratio: 4
|
| 20 |
+
norm: fusedrms
|
| 21 |
+
norm_eps: 1e-5
|
| 22 |
+
ada: single
|
| 23 |
+
qk_bias: False
|
| 24 |
+
qk_rope: True
|
| 25 |
+
qk_norm: fusedrms
|
| 26 |
+
patch_size: [1, 2, 2]
|
| 27 |
+
num_layers: 36 # llm-like
|
| 28 |
+
shared_mlp: False
|
| 29 |
+
shared_qkv: False
|
| 30 |
+
mlp_type: normal
|
| 31 |
+
block_type: ${eval:'${.num_layers} * ["mmdit_sr"]'} # space-full
|
| 32 |
+
window: ${eval:'${.num_layers} * [(4,3,3)]'} # space-full
|
| 33 |
+
window_method: ${eval:'${.num_layers} // 2 * ["720pwin_by_size_bysize","720pswin_by_size_bysize"]'} # space-full
|
| 34 |
+
compile: False
|
| 35 |
+
gradient_checkpoint: True
|
| 36 |
+
fsdp:
|
| 37 |
+
sharding_strategy: _HYBRID_SHARD_ZERO2
|
| 38 |
+
|
| 39 |
+
ema:
|
| 40 |
+
decay: 0.9998
|
| 41 |
+
|
| 42 |
+
vae:
|
| 43 |
+
model:
|
| 44 |
+
__object__:
|
| 45 |
+
path: "video_vae_v3.modules.attn_video_vae"
|
| 46 |
+
name: "VideoAutoencoderKLWrapper"
|
| 47 |
+
args: "as_params"
|
| 48 |
+
freeze_encoder: False
|
| 49 |
+
# gradient_checkpoint: True
|
| 50 |
+
slicing:
|
| 51 |
+
split_size: 4
|
| 52 |
+
memory_device: same
|
| 53 |
+
memory_limit:
|
| 54 |
+
conv_max_mem: 0.5
|
| 55 |
+
norm_max_mem: 0.5
|
| 56 |
+
checkpoint: ema_vae_fp16.safetensors
|
| 57 |
+
scaling_factor: 0.9152
|
| 58 |
+
compile: False
|
| 59 |
+
grouping: False
|
| 60 |
+
dtype: float16
|
| 61 |
+
|
| 62 |
+
diffusion:
|
| 63 |
+
schedule:
|
| 64 |
+
type: lerp
|
| 65 |
+
T: 1000.0
|
| 66 |
+
sampler:
|
| 67 |
+
type: euler
|
| 68 |
+
prediction_type: v_lerp
|
| 69 |
+
timesteps:
|
| 70 |
+
training:
|
| 71 |
+
type: logitnormal
|
| 72 |
+
loc: 0.0
|
| 73 |
+
scale: 1.0
|
| 74 |
+
sampling:
|
| 75 |
+
type: uniform_trailing
|
| 76 |
+
steps: 50
|
| 77 |
+
transform: True
|
| 78 |
+
loss:
|
| 79 |
+
type: v_lerp
|
| 80 |
+
cfg:
|
| 81 |
+
scale: 7.5
|
| 82 |
+
rescale: 0
|
| 83 |
+
|
| 84 |
+
condition:
|
| 85 |
+
i2v: 0.0
|
| 86 |
+
v2v: 0.0
|
| 87 |
+
sr: 1.0
|
| 88 |
+
noise_scale: 0.25
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/demo_01.jpg
ADDED
|
Git LFS Details
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/demo_02.jpg
ADDED
|
Git LFS Details
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/dit_model_loader.png
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/seedvr_logo.png
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/torch_compile_settings.png
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/usage_01.png
ADDED
|
Git LFS Details
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/usage_02.png
ADDED
|
Git LFS Details
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/vae_model_loader.png
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/docs/video_upscaler.png
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_4K_image_upscale.jpg
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_4K_image_upscale.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"e2c8e6ec-9e8a-4a81-a240-7e7d2befc2bf","revision":0,"last_node_id":20,"last_link_id":19,"nodes":[{"id":19,"type":"SeedVR2TorchCompileSettings","pos":[-366.25065217175177,401.8303561599628],"size":[307.671484375,178],"flags":{},"order":0,"mode":4,"inputs":[{"localized_name":"backend","name":"backend","type":"COMBO","widget":{"name":"backend"},"link":null},{"localized_name":"mode","name":"mode","type":"COMBO","widget":{"name":"mode"},"link":null},{"localized_name":"fullgraph","name":"fullgraph","type":"BOOLEAN","widget":{"name":"fullgraph"},"link":null},{"localized_name":"dynamic","name":"dynamic","type":"BOOLEAN","widget":{"name":"dynamic"},"link":null},{"localized_name":"dynamo_cache_size_limit","name":"dynamo_cache_size_limit","type":"INT","widget":{"name":"dynamo_cache_size_limit"},"link":null},{"localized_name":"dynamo_recompile_limit","name":"dynamo_recompile_limit","type":"INT","widget":{"name":"dynamo_recompile_limit"},"link":null}],"outputs":[{"localized_name":"TORCH_COMPILE_ARGS","name":"TORCH_COMPILE_ARGS","type":"TORCH_COMPILE_ARGS","links":[18,19]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2TorchCompileSettings"},"widgets_values":["inductor","default",false,false,64,128]},{"id":18,"type":"Note","pos":[420.71483854947945,-81.0108307454865],"size":[210,94.05179298404067],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Enable to upscale alpha/mask channel along with RGB channel (for RGBA inputs)"],"color":"#432","bgcolor":"#653"},{"id":16,"type":"LoadImage","pos":[80.5773583147188,-182.2697173004957],"size":[274.080078125,314],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[14]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":[17]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"LoadImage"},"widgets_values":["Sadhu_320x478.png","image"]},{"id":14,"type":"SeedVR2LoadDiTModel","pos":[64.4234520647185,212.7072358245033],"size":[307.6646484375,202],"flags":{},"order":4,"mode":0,"inputs":[{"localized_name":"torch_compile_args","name":"torch_compile_args","shape":7,"type":"TORCH_COMPILE_ARGS","link":18},{"localized_name":"model","name":"model","type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"blocks_to_swap","name":"blocks_to_swap","shape":7,"type":"INT","widget":{"name":"blocks_to_swap"},"link":null},{"localized_name":"swap_io_components","name":"swap_io_components","shape":7,"type":"BOOLEAN","widget":{"name":"swap_io_components"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"cache_model","name":"cache_model","shape":7,"type":"BOOLEAN","widget":{"name":"cache_model"},"link":null},{"localized_name":"attention_mode","name":"attention_mode","shape":7,"type":"COMBO","widget":{"name":"attention_mode"},"link":null}],"outputs":[{"localized_name":"SEEDVR2_DIT","name":"SEEDVR2_DIT","type":"SEEDVR2_DIT","links":[11]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2LoadDiTModel"},"widgets_values":["seedvr2_ema_7b_sharp_fp16.safetensors","cuda:0",36,false,"cpu",false,"sdpa"]},{"id":13,"type":"SeedVR2LoadVAEModel","pos":[60.33985831471847,499.8798920745057],"size":[312.866796875,298],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"torch_compile_args","name":"torch_compile_args","shape":7,"type":"TORCH_COMPILE_ARGS","link":19},{"localized_name":"model","name":"model","type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"encode_tiled","name":"encode_tiled","shape":7,"type":"BOOLEAN","widget":{"name":"encode_tiled"},"link":null},{"localized_name":"encode_tile_size","name":"encode_tile_size","shape":7,"type":"INT","widget":{"name":"encode_tile_size"},"link":null},{"localized_name":"encode_tile_overlap","name":"encode_tile_overlap","shape":7,"type":"INT","widget":{"name":"encode_tile_overlap"},"link":null},{"localized_name":"decode_tiled","name":"decode_tiled","shape":7,"type":"BOOLEAN","widget":{"name":"decode_tiled"},"link":null},{"localized_name":"decode_tile_size","name":"decode_tile_size","shape":7,"type":"INT","widget":{"name":"decode_tile_size"},"link":null},{"localized_name":"decode_tile_overlap","name":"decode_tile_overlap","shape":7,"type":"INT","widget":{"name":"decode_tile_overlap"},"link":null},{"localized_name":"tile_debug","name":"tile_debug","shape":7,"type":"COMBO","widget":{"name":"tile_debug"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"cache_model","name":"cache_model","shape":7,"type":"BOOLEAN","widget":{"name":"cache_model"},"link":null}],"outputs":[{"localized_name":"SEEDVR2_VAE","name":"SEEDVR2_VAE","type":"SEEDVR2_VAE","links":[10]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2LoadVAEModel"},"widgets_values":["ema_vae_fp16.safetensors","cuda:0",true,1024,128,true,1024,128,"false","cpu",false]},{"id":10,"type":"SeedVR2VideoUpscaler","pos":[843.8992333147189,191.3224701995034],"size":[270,386],"flags":{},"order":7,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":16},{"localized_name":"dit","name":"dit","type":"SEEDVR2_DIT","link":11},{"localized_name":"vae","name":"vae","type":"SEEDVR2_VAE","link":10},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"resolution","name":"resolution","type":"INT","widget":{"name":"resolution"},"link":null},{"localized_name":"max_resolution","name":"max_resolution","type":"INT","widget":{"name":"max_resolution"},"link":null},{"localized_name":"batch_size","name":"batch_size","type":"INT","widget":{"name":"batch_size"},"link":null},{"localized_name":"uniform_batch_size","name":"uniform_batch_size","type":"BOOLEAN","widget":{"name":"uniform_batch_size"},"link":null},{"localized_name":"color_correction","name":"color_correction","type":"COMBO","widget":{"name":"color_correction"},"link":null},{"localized_name":"temporal_overlap","name":"temporal_overlap","shape":7,"type":"INT","widget":{"name":"temporal_overlap"},"link":null},{"localized_name":"prepend_frames","name":"prepend_frames","shape":7,"type":"INT","widget":{"name":"prepend_frames"},"link":null},{"localized_name":"input_noise_scale","name":"input_noise_scale","shape":7,"type":"FLOAT","widget":{"name":"input_noise_scale"},"link":null},{"localized_name":"latent_noise_scale","name":"latent_noise_scale","shape":7,"type":"FLOAT","widget":{"name":"latent_noise_scale"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"enable_debug","name":"enable_debug","shape":7,"type":"BOOLEAN","widget":{"name":"enable_debug"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[12]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2VideoUpscaler"},"widgets_values":[42,"fixed",4096,4096,1,false,"lab",0,0,0,0,"cpu",false]},{"id":17,"type":"JoinImageWithAlpha","pos":[449.4979840435381,-180.32741340177097],"size":[176.86484375,46],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":14},{"localized_name":"alpha","name":"alpha","type":"MASK","link":17}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[16]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"JoinImageWithAlpha"},"widgets_values":[]},{"id":15,"type":"SaveImage","pos":[1167.7234520647182,192.0920014495034],"size":[270,270],"flags":{},"order":8,"mode":0,"inputs":[{"localized_name":"images","name":"images","type":"IMAGE","link":12},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.68"},"widgets_values":["ComfyUI"]},{"id":20,"type":"Note","pos":[-353.44461861972593,641.0883773827939],"size":[290.1922351462714,97.95592567265356],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Enable torch.compile optimization. First batch has compilation overhead, but speeds up subsequent batches. Best for long videos, not single images."],"color":"#432","bgcolor":"#653"}],"links":[[10,13,0,10,2,"SEEDVR2_VAE"],[11,14,0,10,1,"SEEDVR2_DIT"],[12,10,0,15,0,"IMAGE"],[14,16,0,17,0,"IMAGE"],[16,17,0,10,0,"IMAGE"],[17,16,1,17,1,"MASK"],[18,19,0,14,0,"TORCH_COMPILE_ARGS"],[19,19,0,13,0,"TORCH_COMPILE_ARGS"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.6934334949441353,"offset":[1011.3669791186508,462.16171611318566]}},"version":0.4}
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_HD_video_upscale.jpg
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_HD_video_upscale.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"e2c8e6ec-9e8a-4a81-a240-7e7d2befc2bf","revision":0,"last_node_id":24,"last_link_id":25,"nodes":[{"id":17,"type":"JoinImageWithAlpha","pos":[415.46463925106394,-269.3527671179127],"size":[176.86484375,46],"flags":{},"order":7,"mode":4,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":21},{"localized_name":"alpha","name":"alpha","type":"MASK","link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[16]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"JoinImageWithAlpha"},"widgets_values":[]},{"id":19,"type":"SeedVR2TorchCompileSettings","pos":[-369.50847923647007,315.0949953354577],"size":[307.671484375,178],"flags":{},"order":0,"mode":4,"inputs":[{"localized_name":"backend","name":"backend","type":"COMBO","widget":{"name":"backend"},"link":null},{"localized_name":"mode","name":"mode","type":"COMBO","widget":{"name":"mode"},"link":null},{"localized_name":"fullgraph","name":"fullgraph","type":"BOOLEAN","widget":{"name":"fullgraph"},"link":null},{"localized_name":"dynamic","name":"dynamic","type":"BOOLEAN","widget":{"name":"dynamic"},"link":null},{"localized_name":"dynamo_cache_size_limit","name":"dynamo_cache_size_limit","type":"INT","widget":{"name":"dynamo_cache_size_limit"},"link":null},{"localized_name":"dynamo_recompile_limit","name":"dynamo_recompile_limit","type":"INT","widget":{"name":"dynamo_recompile_limit"},"link":null}],"outputs":[{"localized_name":"TORCH_COMPILE_ARGS","name":"TORCH_COMPILE_ARGS","type":"TORCH_COMPILE_ARGS","links":[18,19]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2TorchCompileSettings"},"widgets_values":["inductor","default",false,false,64,128]},{"id":20,"type":"Note","pos":[-358.9923959032341,553.3187800066305],"size":[290.1922351462714,97.95592567265356],"flags":{},"order":1,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Enable torch.compile optimization. First batch has compilation overhead, but speeds up subsequent batches. Best for long videos, not single images."],"color":"#432","bgcolor":"#653"},{"id":21,"type":"LoadVideo","pos":[-201.66033140975628,-266.86977401462417],"size":[274.080078125,228.1700439453125],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"file","name":"file","type":"COMBO","widget":{"name":"file"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"VIDEO","name":"VIDEO","type":"VIDEO","links":[20]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"LoadVideo"},"widgets_values":["Mustache_640x360.mp4","image"]},{"id":18,"type":"Note","pos":[417.45701148476115,-167.7461915699906],"size":[210,94.05179298404067],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Enable + connect input alpha to upscale alpha/mask channel along with RGB channel (for RGBA inputs)"],"color":"#432","bgcolor":"#653"},{"id":24,"type":"CreateVideo","pos":[1171.8133710326213,102.33318716823825],"size":[270,78],"flags":{},"order":9,"mode":0,"inputs":[{"localized_name":"images","name":"images","type":"IMAGE","link":22},{"localized_name":"audio","name":"audio","shape":7,"type":"AUDIO","link":25},{"localized_name":"fps","name":"fps","type":"FLOAT","widget":{"name":"fps"},"link":24}],"outputs":[{"localized_name":"VIDEO","name":"VIDEO","type":"VIDEO","links":[23]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"CreateVideo"},"widgets_values":[30]},{"id":23,"type":"SaveVideo","pos":[1499.476781650457,101.92559527677722],"size":[270,249.875],"flags":{},"order":10,"mode":0,"inputs":[{"localized_name":"video","name":"video","type":"VIDEO","link":23},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null},{"localized_name":"format","name":"format","type":"COMBO","widget":{"name":"format"},"link":null},{"localized_name":"codec","name":"codec","type":"COMBO","widget":{"name":"codec"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"SaveVideo"},"widgets_values":["video/ComfyUI","auto","auto"]},{"id":22,"type":"GetVideoComponents","pos":[149.28727188425574,-267.41033914727126],"size":[185.17734375,66],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"video","name":"video","type":"VIDEO","link":20}],"outputs":[{"localized_name":"images","name":"images","type":"IMAGE","links":[21]},{"localized_name":"audio","name":"audio","type":"AUDIO","links":[25]},{"localized_name":"fps","name":"fps","type":"FLOAT","links":[24]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"GetVideoComponents"},"widgets_values":[]},{"id":13,"type":"SeedVR2LoadVAEModel","pos":[57.08203125,413.14453125],"size":[312.866796875,298],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"torch_compile_args","name":"torch_compile_args","shape":7,"type":"TORCH_COMPILE_ARGS","link":19},{"localized_name":"model","name":"model","type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"encode_tiled","name":"encode_tiled","shape":7,"type":"BOOLEAN","widget":{"name":"encode_tiled"},"link":null},{"localized_name":"encode_tile_size","name":"encode_tile_size","shape":7,"type":"INT","widget":{"name":"encode_tile_size"},"link":null},{"localized_name":"encode_tile_overlap","name":"encode_tile_overlap","shape":7,"type":"INT","widget":{"name":"encode_tile_overlap"},"link":null},{"localized_name":"decode_tiled","name":"decode_tiled","shape":7,"type":"BOOLEAN","widget":{"name":"decode_tiled"},"link":null},{"localized_name":"decode_tile_size","name":"decode_tile_size","shape":7,"type":"INT","widget":{"name":"decode_tile_size"},"link":null},{"localized_name":"decode_tile_overlap","name":"decode_tile_overlap","shape":7,"type":"INT","widget":{"name":"decode_tile_overlap"},"link":null},{"localized_name":"tile_debug","name":"tile_debug","shape":7,"type":"COMBO","widget":{"name":"tile_debug"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"cache_model","name":"cache_model","shape":7,"type":"BOOLEAN","widget":{"name":"cache_model"},"link":null}],"outputs":[{"localized_name":"SEEDVR2_VAE","name":"SEEDVR2_VAE","type":"SEEDVR2_VAE","links":[10]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2LoadVAEModel"},"widgets_values":["ema_vae_fp16.safetensors","cuda:0",true,1024,128,true,768,128,"false","cpu",false]},{"id":10,"type":"SeedVR2VideoUpscaler","pos":[840.6414062499999,104.58710937499997],"size":[270,386],"flags":{},"order":8,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":16},{"localized_name":"dit","name":"dit","type":"SEEDVR2_DIT","link":11},{"localized_name":"vae","name":"vae","type":"SEEDVR2_VAE","link":10},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"resolution","name":"resolution","type":"INT","widget":{"name":"resolution"},"link":null},{"localized_name":"max_resolution","name":"max_resolution","type":"INT","widget":{"name":"max_resolution"},"link":null},{"localized_name":"batch_size","name":"batch_size","type":"INT","widget":{"name":"batch_size"},"link":null},{"localized_name":"uniform_batch_size","name":"uniform_batch_size","type":"BOOLEAN","widget":{"name":"uniform_batch_size"},"link":null},{"localized_name":"color_correction","name":"color_correction","type":"COMBO","widget":{"name":"color_correction"},"link":null},{"localized_name":"temporal_overlap","name":"temporal_overlap","shape":7,"type":"INT","widget":{"name":"temporal_overlap"},"link":null},{"localized_name":"prepend_frames","name":"prepend_frames","shape":7,"type":"INT","widget":{"name":"prepend_frames"},"link":null},{"localized_name":"input_noise_scale","name":"input_noise_scale","shape":7,"type":"FLOAT","widget":{"name":"input_noise_scale"},"link":null},{"localized_name":"latent_noise_scale","name":"latent_noise_scale","shape":7,"type":"FLOAT","widget":{"name":"latent_noise_scale"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"enable_debug","name":"enable_debug","shape":7,"type":"BOOLEAN","widget":{"name":"enable_debug"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[22]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2VideoUpscaler"},"widgets_values":[42,"fixed",1080,0,33,true,"lab",3,0,0,0,"cpu",false]},{"id":14,"type":"SeedVR2LoadDiTModel","pos":[61.165625,125.97187500000004],"size":[307.6646484375,202],"flags":{},"order":4,"mode":0,"inputs":[{"localized_name":"torch_compile_args","name":"torch_compile_args","shape":7,"type":"TORCH_COMPILE_ARGS","link":18},{"localized_name":"model","name":"model","type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"blocks_to_swap","name":"blocks_to_swap","shape":7,"type":"INT","widget":{"name":"blocks_to_swap"},"link":null},{"localized_name":"swap_io_components","name":"swap_io_components","shape":7,"type":"BOOLEAN","widget":{"name":"swap_io_components"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"cache_model","name":"cache_model","shape":7,"type":"BOOLEAN","widget":{"name":"cache_model"},"link":null},{"localized_name":"attention_mode","name":"attention_mode","shape":7,"type":"COMBO","widget":{"name":"attention_mode"},"link":null}],"outputs":[{"localized_name":"SEEDVR2_DIT","name":"SEEDVR2_DIT","type":"SEEDVR2_DIT","links":[11]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2LoadDiTModel"},"widgets_values":["seedvr2_ema_3b_fp16.safetensors","cuda:0",32,false,"cpu",false,"sdpa"]}],"links":[[10,13,0,10,2,"SEEDVR2_VAE"],[11,14,0,10,1,"SEEDVR2_DIT"],[16,17,0,10,0,"IMAGE"],[18,19,0,14,0,"TORCH_COMPILE_ARGS"],[19,19,0,13,0,"TORCH_COMPILE_ARGS"],[20,21,0,22,0,"VIDEO"],[21,22,0,17,0,"IMAGE"],[22,10,0,24,0,"IMAGE"],[23,24,0,23,0,"VIDEO"],[24,22,2,24,2,"FLOAT"],[25,22,1,24,1,"AUDIO"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.8441465687687482,"offset":[739.339356050958,625.120941260255]}},"version":0.4}
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_simple_image_upscale.jpg
ADDED
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/SeedVR2_simple_image_upscale.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"id":"e2c8e6ec-9e8a-4a81-a240-7e7d2befc2bf","revision":0,"last_node_id":20,"last_link_id":19,"nodes":[{"id":16,"type":"LoadImage","pos":[529.6515751584279,98.66389637266899],"size":[274.080078125,314],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[14]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":[17]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"LoadImage"},"widgets_values":["example.png","image"]},{"id":17,"type":"JoinImageWithAlpha","pos":[867.7966831594896,98.31620737975611],"size":[176.86484375,46],"flags":{},"order":4,"mode":4,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":14},{"localized_name":"alpha","name":"alpha","type":"MASK","link":17}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[16]}],"properties":{"cnr_id":"comfy-core","ver":"0.3.68","Node name for S&R":"JoinImageWithAlpha"},"widgets_values":[]},{"id":19,"type":"SeedVR2TorchCompileSettings","pos":[82.82356467195704,682.7639698331246],"size":[307.671484375,178],"flags":{},"order":1,"mode":4,"inputs":[{"localized_name":"backend","name":"backend","type":"COMBO","widget":{"name":"backend"},"link":null},{"localized_name":"mode","name":"mode","type":"COMBO","widget":{"name":"mode"},"link":null},{"localized_name":"fullgraph","name":"fullgraph","type":"BOOLEAN","widget":{"name":"fullgraph"},"link":null},{"localized_name":"dynamic","name":"dynamic","type":"BOOLEAN","widget":{"name":"dynamic"},"link":null},{"localized_name":"dynamo_cache_size_limit","name":"dynamo_cache_size_limit","type":"INT","widget":{"name":"dynamo_cache_size_limit"},"link":null},{"localized_name":"dynamo_recompile_limit","name":"dynamo_recompile_limit","type":"INT","widget":{"name":"dynamo_recompile_limit"},"link":null}],"outputs":[{"localized_name":"TORCH_COMPILE_ARGS","name":"TORCH_COMPILE_ARGS","type":"TORCH_COMPILE_ARGS","links":[18,19]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2TorchCompileSettings"},"widgets_values":["inductor","default",false,false,64,128]},{"id":13,"type":"SeedVR2LoadVAEModel","pos":[509.41407515842775,780.813505747667],"size":[312.866796875,298],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"torch_compile_args","name":"torch_compile_args","shape":7,"type":"TORCH_COMPILE_ARGS","link":19},{"localized_name":"model","name":"model","type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"encode_tiled","name":"encode_tiled","shape":7,"type":"BOOLEAN","widget":{"name":"encode_tiled"},"link":null},{"localized_name":"encode_tile_size","name":"encode_tile_size","shape":7,"type":"INT","widget":{"name":"encode_tile_size"},"link":null},{"localized_name":"encode_tile_overlap","name":"encode_tile_overlap","shape":7,"type":"INT","widget":{"name":"encode_tile_overlap"},"link":null},{"localized_name":"decode_tiled","name":"decode_tiled","shape":7,"type":"BOOLEAN","widget":{"name":"decode_tiled"},"link":null},{"localized_name":"decode_tile_size","name":"decode_tile_size","shape":7,"type":"INT","widget":{"name":"decode_tile_size"},"link":null},{"localized_name":"decode_tile_overlap","name":"decode_tile_overlap","shape":7,"type":"INT","widget":{"name":"decode_tile_overlap"},"link":null},{"localized_name":"tile_debug","name":"tile_debug","shape":7,"type":"COMBO","widget":{"name":"tile_debug"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"cache_model","name":"cache_model","shape":7,"type":"BOOLEAN","widget":{"name":"cache_model"},"link":null}],"outputs":[{"localized_name":"SEEDVR2_VAE","name":"SEEDVR2_VAE","type":"SEEDVR2_VAE","links":[10]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2LoadVAEModel"},"widgets_values":["ema_vae_fp16.safetensors","cuda:0",false,1024,128,false,1024,128,"false","none",false]},{"id":14,"type":"SeedVR2LoadDiTModel","pos":[513.4976689084277,493.64084949766976],"size":[307.6646484375,202],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"torch_compile_args","name":"torch_compile_args","shape":7,"type":"TORCH_COMPILE_ARGS","link":18},{"localized_name":"model","name":"model","type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"device","name":"device","type":"COMBO","widget":{"name":"device"},"link":null},{"localized_name":"blocks_to_swap","name":"blocks_to_swap","shape":7,"type":"INT","widget":{"name":"blocks_to_swap"},"link":null},{"localized_name":"swap_io_components","name":"swap_io_components","shape":7,"type":"BOOLEAN","widget":{"name":"swap_io_components"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"cache_model","name":"cache_model","shape":7,"type":"BOOLEAN","widget":{"name":"cache_model"},"link":null},{"localized_name":"attention_mode","name":"attention_mode","shape":7,"type":"COMBO","widget":{"name":"attention_mode"},"link":null}],"outputs":[{"localized_name":"SEEDVR2_DIT","name":"SEEDVR2_DIT","type":"SEEDVR2_DIT","links":[11]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2LoadDiTModel"},"widgets_values":["seedvr2_ema_3b_fp8_e4m3fn.safetensors","cuda:0",0,false,"none",false,"sdpa"]},{"id":18,"type":"Note","pos":[869.7890553931869,199.9227829276786],"size":[210,94.05179298404067],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Enable to upscale alpha/mask channel along with RGB channel (for RGBA inputs)"],"color":"#432","bgcolor":"#653"},{"id":20,"type":"Note","pos":[93.33964800519301,920.9877545042978],"size":[290.1922351462714,97.95592567265356],"flags":{},"order":3,"mode":0,"inputs":[],"outputs":[],"properties":{},"widgets_values":["Enable torch.compile optimization. First batch has compilation overhead, but speeds up subsequent batches. Best for long videos, not single images."],"color":"#432","bgcolor":"#653"},{"id":10,"type":"SeedVR2VideoUpscaler","pos":[1292.9734501584262,472.2560838726697],"size":[270,386],"flags":{},"order":7,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":16},{"localized_name":"dit","name":"dit","type":"SEEDVR2_DIT","link":11},{"localized_name":"vae","name":"vae","type":"SEEDVR2_VAE","link":10},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"resolution","name":"resolution","type":"INT","widget":{"name":"resolution"},"link":null},{"localized_name":"max_resolution","name":"max_resolution","type":"INT","widget":{"name":"max_resolution"},"link":null},{"localized_name":"batch_size","name":"batch_size","type":"INT","widget":{"name":"batch_size"},"link":null},{"localized_name":"uniform_batch_size","name":"uniform_batch_size","type":"BOOLEAN","widget":{"name":"uniform_batch_size"},"link":null},{"localized_name":"color_correction","name":"color_correction","type":"COMBO","widget":{"name":"color_correction"},"link":null},{"localized_name":"temporal_overlap","name":"temporal_overlap","shape":7,"type":"INT","widget":{"name":"temporal_overlap"},"link":null},{"localized_name":"prepend_frames","name":"prepend_frames","shape":7,"type":"INT","widget":{"name":"prepend_frames"},"link":null},{"localized_name":"input_noise_scale","name":"input_noise_scale","shape":7,"type":"FLOAT","widget":{"name":"input_noise_scale"},"link":null},{"localized_name":"latent_noise_scale","name":"latent_noise_scale","shape":7,"type":"FLOAT","widget":{"name":"latent_noise_scale"},"link":null},{"localized_name":"offload_device","name":"offload_device","shape":7,"type":"COMBO","widget":{"name":"offload_device"},"link":null},{"localized_name":"enable_debug","name":"enable_debug","shape":7,"type":"BOOLEAN","widget":{"name":"enable_debug"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[12]}],"properties":{"aux_id":"ainvfx/ComfyUI-SeedVR2_VideoUpscaler","ver":"690cc39379c1481159ddd451368dbf2295930fc6","Node name for S&R":"SeedVR2VideoUpscaler"},"widgets_values":[42,"randomize",1080,0,1,false,"lab",0,0,0,0,"cpu",false]},{"id":15,"type":"SaveImage","pos":[1616.797668908428,473.0256151226697],"size":[270,270],"flags":{},"order":8,"mode":0,"inputs":[{"localized_name":"images","name":"images","type":"IMAGE","link":12},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.68"},"widgets_values":["ComfyUI"]}],"links":[[10,13,0,10,2,"SEEDVR2_VAE"],[11,14,0,10,1,"SEEDVR2_DIT"],[12,10,0,15,0,"IMAGE"],[14,16,0,17,0,"IMAGE"],[16,17,0,10,0,"IMAGE"],[17,16,1,17,1,"MASK"],[18,19,0,14,0,"TORCH_COMPILE_ARGS"],[19,19,0,13,0,"TORCH_COMPILE_ARGS"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.9229599817706443,"offset":[268.65279462308837,183.8749573202125]}},"version":0.4}
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Eyes_212x120.mp4
ADDED
|
Binary file (20.5 kB). View file
|
|
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Mustache_640x360.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:21581bc8454e234e3d3f833172bb358215f47d33cfcf5fc12f3dd0dff3319d1d
|
| 3 |
+
size 115496
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/example_workflows/example_inputs/Sadhu_320x478.png
ADDED
|
Git LFS Details
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/inference_cli.py
ADDED
|
@@ -0,0 +1,1712 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
SeedVR2 Video Upscaler - Standalone CLI Interface
|
| 4 |
+
|
| 5 |
+
Command-line interface for high-quality upscaling using SeedVR2 diffusion models.
|
| 6 |
+
Supports single and multi-GPU processing with advanced memory optimization.
|
| 7 |
+
|
| 8 |
+
Key Features:
|
| 9 |
+
• Multi-GPU Processing: Automatic workload distribution across multiple GPUs with
|
| 10 |
+
temporal overlap blending for seamless transitions
|
| 11 |
+
• Streaming Mode: Memory-efficient processing of long videos in chunks, avoiding
|
| 12 |
+
full video loading into RAM while maintaining temporal consistency
|
| 13 |
+
• Memory Optimization: BlockSwap for limited VRAM, VAE tiling for large resolutions,
|
| 14 |
+
intelligent tensor offloading between processing phases
|
| 15 |
+
• Performance: Torch.compile integration, BFloat16 compute pipeline,
|
| 16 |
+
efficient model caching for batch and streaming processing
|
| 17 |
+
• Flexibility: Multiple output formats (MP4/PNG), advanced color correction methods,
|
| 18 |
+
directory batch processing with auto-format detection
|
| 19 |
+
• Quality Control: Temporal overlap blending, frame prepending for artifact reduction,
|
| 20 |
+
configurable noise scales for detail preservation
|
| 21 |
+
|
| 22 |
+
Architecture:
|
| 23 |
+
The CLI implements a 4-phase processing pipeline:
|
| 24 |
+
1. Encode: VAE encoding with optional input noise and tiling
|
| 25 |
+
2. Upscale: DiT transformer upscaling with latent space diffusion
|
| 26 |
+
3. Decode: VAE decoding with optional tiling
|
| 27 |
+
4. Postprocess: Color correction and temporal blending
|
| 28 |
+
|
| 29 |
+
Usage:
|
| 30 |
+
python inference_cli.py video.mp4 --resolution 1080
|
| 31 |
+
For complete usage examples, run: python inference_cli.py --help
|
| 32 |
+
|
| 33 |
+
Requirements:
|
| 34 |
+
• Python 3.10+
|
| 35 |
+
• PyTorch 2.4+ with CUDA 12.1+ (NVIDIA) or MPS (Apple Silicon)
|
| 36 |
+
• 16GB+ VRAM recommended (8GB minimum with BlockSwap)
|
| 37 |
+
• OpenCV, NumPy for video I/O
|
| 38 |
+
|
| 39 |
+
Model Support:
|
| 40 |
+
• 3B models: seedvr2_ema_3b_fp16.safetensors (default), _fp8_e4m3fn/GGUF variants
|
| 41 |
+
• 7B models: seedvr2_ema_7b_fp16.safetensors, _fp8_e4m3fn/GGUF variants
|
| 42 |
+
• VAE: ema_vae_fp16.safetensors (shared across all models)
|
| 43 |
+
• Auto-downloads from HuggingFace on first run with SHA256 validation
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
# Standard library imports
|
| 47 |
+
import sys
|
| 48 |
+
import os
|
| 49 |
+
import argparse
|
| 50 |
+
import time
|
| 51 |
+
import platform
|
| 52 |
+
import multiprocessing as mp
|
| 53 |
+
from typing import Dict, Any, List, Optional, Tuple, Literal, Generator
|
| 54 |
+
from datetime import datetime
|
| 55 |
+
from pathlib import Path
|
| 56 |
+
|
| 57 |
+
# Set up path before any other imports to fix module resolution
|
| 58 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 59 |
+
if script_dir not in sys.path:
|
| 60 |
+
sys.path.insert(0, script_dir)
|
| 61 |
+
|
| 62 |
+
# Set environment variable so all spawned processes can find modules
|
| 63 |
+
os.environ['PYTHONPATH'] = script_dir + ':' + os.environ.get('PYTHONPATH', '')
|
| 64 |
+
|
| 65 |
+
# Ensure safe CUDA usage with multiprocessing
|
| 66 |
+
if mp.get_start_method(allow_none=True) != 'spawn':
|
| 67 |
+
mp.set_start_method('spawn', force=True)
|
| 68 |
+
|
| 69 |
+
# Configure platform-specific memory management before heavy imports
|
| 70 |
+
# Must be set BEFORE import torch
|
| 71 |
+
if platform.system() == "Darwin":
|
| 72 |
+
# MPS allocator requires: low_watermark <= high_watermark
|
| 73 |
+
# Setting both to 0.0 disables PyTorch memory limits, letting macOS manage memory
|
| 74 |
+
os.environ.setdefault("PYTORCH_MPS_HIGH_WATERMARK_RATIO", "0.0")
|
| 75 |
+
os.environ.setdefault("PYTORCH_MPS_LOW_WATERMARK_RATIO", "0.0")
|
| 76 |
+
else:
|
| 77 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "backend:cudaMallocAsync")
|
| 78 |
+
|
| 79 |
+
# Pre-parse arguments that must be handled before torch import
|
| 80 |
+
_pre_parser = argparse.ArgumentParser(add_help=False)
|
| 81 |
+
_pre_parser.add_argument("--cuda_device", type=str, default=None)
|
| 82 |
+
_pre_args, _ = _pre_parser.parse_known_args()
|
| 83 |
+
|
| 84 |
+
if _pre_args.cuda_device is not None:
|
| 85 |
+
device_list_env = [x.strip() for x in _pre_args.cuda_device.split(',') if x.strip()!='']
|
| 86 |
+
|
| 87 |
+
# Skip validation if CUDA_VISIBLE_DEVICES is already set (worker process)
|
| 88 |
+
if os.environ.get("CUDA_VISIBLE_DEVICES") is None:
|
| 89 |
+
# Temporary torch import for CUDA device validation only
|
| 90 |
+
# Must happen before setting CUDA_VISIBLE_DEVICES and before main torch import
|
| 91 |
+
import torch as _torch_check
|
| 92 |
+
if _torch_check.cuda.is_available():
|
| 93 |
+
available_count = _torch_check.cuda.device_count()
|
| 94 |
+
invalid_devices = [d for d in device_list_env if not d.isdigit() or int(d) >= available_count]
|
| 95 |
+
if invalid_devices:
|
| 96 |
+
print(f"❌ [ERROR] Invalid CUDA device ID(s): {', '.join(invalid_devices)}. "
|
| 97 |
+
f"Available devices: 0-{available_count-1} (total: {available_count})")
|
| 98 |
+
sys.exit(1)
|
| 99 |
+
else:
|
| 100 |
+
print("❌ [ERROR] CUDA is not available on this system. Cannot use --cuda_device argument.")
|
| 101 |
+
sys.exit(1)
|
| 102 |
+
|
| 103 |
+
# Set CUDA_VISIBLE_DEVICES for single GPU after validation
|
| 104 |
+
if len(device_list_env) == 1:
|
| 105 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = device_list_env[0]
|
| 106 |
+
|
| 107 |
+
# Heavy dependency imports after environment configuration
|
| 108 |
+
import torch
|
| 109 |
+
import cv2
|
| 110 |
+
import numpy as np
|
| 111 |
+
import subprocess
|
| 112 |
+
import shutil
|
| 113 |
+
|
| 114 |
+
# Project imports
|
| 115 |
+
from src.utils.downloads import download_weight
|
| 116 |
+
from src.utils.model_registry import get_available_dit_models, DEFAULT_DIT, DEFAULT_VAE
|
| 117 |
+
from src.utils.constants import SEEDVR2_FOLDER_NAME
|
| 118 |
+
from src.core.generation_utils import (
|
| 119 |
+
setup_generation_context,
|
| 120 |
+
prepare_runner,
|
| 121 |
+
compute_generation_info,
|
| 122 |
+
log_generation_start,
|
| 123 |
+
blend_overlapping_frames,
|
| 124 |
+
load_text_embeddings,
|
| 125 |
+
script_directory
|
| 126 |
+
)
|
| 127 |
+
from src.core.generation_phases import (
|
| 128 |
+
encode_all_batches,
|
| 129 |
+
upscale_all_batches,
|
| 130 |
+
decode_all_batches,
|
| 131 |
+
postprocess_all_batches
|
| 132 |
+
)
|
| 133 |
+
from src.utils.debug import Debug
|
| 134 |
+
from src.optimization.memory_manager import clear_memory, get_gpu_backend, is_cuda_available
|
| 135 |
+
debug = Debug(enabled=False) # Will be enabled via --debug CLI flag
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
# =============================================================================
|
| 139 |
+
# FFMPEG Class
|
| 140 |
+
# =============================================================================
|
| 141 |
+
|
| 142 |
+
class FFMPEGVideoWriter:
|
| 143 |
+
"""
|
| 144 |
+
Video writer using ffmpeg subprocess for encoding with 10-bit support.
|
| 145 |
+
|
| 146 |
+
Provides cv2.VideoWriter-compatible interface (write, isOpened, release) while
|
| 147 |
+
using ffmpeg for encoding. Enables 10-bit output (yuv420p10le with x265) which
|
| 148 |
+
reduces banding artifacts in gradients compared to 8-bit opencv output.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
path: Output video file path
|
| 152 |
+
width: Frame width in pixels
|
| 153 |
+
height: Frame height in pixels
|
| 154 |
+
fps: Frames per second
|
| 155 |
+
use_10bit: If True, uses x265 codec with yuv420p10le pixel format.
|
| 156 |
+
If False, uses x264 with yuv420p (default: False)
|
| 157 |
+
|
| 158 |
+
Raises:
|
| 159 |
+
RuntimeError: If ffmpeg is not found in system PATH
|
| 160 |
+
|
| 161 |
+
Note:
|
| 162 |
+
Frames must be passed to write() in BGR format (same as cv2.VideoWriter).
|
| 163 |
+
Internally converts to RGB for ffmpeg rawvideo input.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
def __init__(self, path: str, width: int, height: int, fps: float, use_10bit: bool = False):
|
| 167 |
+
pix_fmt = 'yuv420p10le' if use_10bit else 'yuv420p'
|
| 168 |
+
codec = 'libx265' if use_10bit else 'libx264'
|
| 169 |
+
|
| 170 |
+
self.proc = subprocess.Popen(
|
| 171 |
+
['ffmpeg', '-y', '-f', 'rawvideo', '-pix_fmt', 'rgb24',
|
| 172 |
+
'-s', f'{width}x{height}', '-r', str(fps), '-i', '-',
|
| 173 |
+
'-c:v', codec, '-pix_fmt', pix_fmt, '-preset', 'medium', '-crf', '12', path],
|
| 174 |
+
stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
def write(self, frame_bgr: np.ndarray):
|
| 178 |
+
if not self.isOpened():
|
| 179 |
+
raise RuntimeError("FFMPEGVideoWriter: ffmpeg process is not running")
|
| 180 |
+
|
| 181 |
+
frame_rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
|
| 182 |
+
try:
|
| 183 |
+
self.proc.stdin.write(frame_rgb.astype(np.uint8).tobytes())
|
| 184 |
+
self.proc.stdin.flush() # Critical: prevent buffering issues
|
| 185 |
+
except BrokenPipeError:
|
| 186 |
+
raise RuntimeError(
|
| 187 |
+
"FFMPEGVideoWriter: ffmpeg process terminated unexpectedly. "
|
| 188 |
+
"Check video path, codec support, and disk space."
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
def isOpened(self) -> bool:
|
| 192 |
+
return self.proc is not None and self.proc.poll() is None
|
| 193 |
+
|
| 194 |
+
def release(self):
|
| 195 |
+
if self.proc:
|
| 196 |
+
try:
|
| 197 |
+
self.proc.stdin.close()
|
| 198 |
+
except Exception:
|
| 199 |
+
pass # Ignore errors on close
|
| 200 |
+
|
| 201 |
+
self.proc.wait()
|
| 202 |
+
|
| 203 |
+
if self.proc.returncode != 0:
|
| 204 |
+
debug.log(
|
| 205 |
+
f"ffmpeg exited with code {self.proc.returncode}. "
|
| 206 |
+
"Check output file for corruption.",
|
| 207 |
+
level="WARNING", force=True, category="file"
|
| 208 |
+
)
|
| 209 |
+
self.proc = None
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
# =============================================================================
|
| 213 |
+
# Device Management Helpers
|
| 214 |
+
# =============================================================================
|
| 215 |
+
|
| 216 |
+
def _device_id_to_name(device_id: str, platform_type: str = None) -> str:
|
| 217 |
+
"""
|
| 218 |
+
Convert device ID to full device name.
|
| 219 |
+
|
| 220 |
+
Args:
|
| 221 |
+
device_id: Device ID ("0", "1") or special value ("cpu", "none")
|
| 222 |
+
platform_type: Override platform type ("cuda", "mps", "cpu")
|
| 223 |
+
|
| 224 |
+
Returns:
|
| 225 |
+
Full device name ("cuda:0", "mps:0", "cpu", "none")
|
| 226 |
+
"""
|
| 227 |
+
if device_id in ("cpu", "none"):
|
| 228 |
+
return device_id
|
| 229 |
+
|
| 230 |
+
if platform_type is None:
|
| 231 |
+
platform_type = get_gpu_backend()
|
| 232 |
+
|
| 233 |
+
# MPS typically doesn't use indices
|
| 234 |
+
if platform_type == "mps":
|
| 235 |
+
return "mps"
|
| 236 |
+
|
| 237 |
+
return f"{platform_type}:{device_id}"
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def _parse_offload_device(offload_arg: str, platform_type: str = None, cache_enabled: bool = False) -> Optional[str]:
|
| 241 |
+
"""
|
| 242 |
+
Parse offload device argument to full device name.
|
| 243 |
+
|
| 244 |
+
Args:
|
| 245 |
+
offload_arg: Offload device argument ("none", "cpu", "0", "1", or "cuda:1")
|
| 246 |
+
platform_type: Override platform type
|
| 247 |
+
cache_enabled: If True and offload_arg is "none", default to "cpu"
|
| 248 |
+
|
| 249 |
+
Returns:
|
| 250 |
+
Full device name or None
|
| 251 |
+
"""
|
| 252 |
+
if offload_arg == "none":
|
| 253 |
+
# If caching enabled but no offload device specified, default to CPU
|
| 254 |
+
return "cpu" if cache_enabled else None
|
| 255 |
+
|
| 256 |
+
if offload_arg == "cpu":
|
| 257 |
+
return "cpu"
|
| 258 |
+
|
| 259 |
+
# If already a full device name (cuda:1, mps:0), return as-is
|
| 260 |
+
if ":" in offload_arg:
|
| 261 |
+
return offload_arg
|
| 262 |
+
|
| 263 |
+
# Otherwise treat as device ID
|
| 264 |
+
return _device_id_to_name(offload_arg, platform_type)
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
# =============================================================================
|
| 268 |
+
# Constants
|
| 269 |
+
# =============================================================================
|
| 270 |
+
|
| 271 |
+
# Supported file extensions
|
| 272 |
+
VIDEO_EXTENSIONS = {'.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv', '.m4v'}
|
| 273 |
+
IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.tif', '.webp'}
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
# =============================================================================
|
| 277 |
+
# Video I/O Functions
|
| 278 |
+
# =============================================================================
|
| 279 |
+
|
| 280 |
+
def get_media_files(directory: str) -> List[str]:
|
| 281 |
+
"""
|
| 282 |
+
Get all video and image files from directory, sorted alphabetically.
|
| 283 |
+
|
| 284 |
+
Args:
|
| 285 |
+
directory: Path to directory to scan
|
| 286 |
+
|
| 287 |
+
Returns:
|
| 288 |
+
Sorted list of file paths (strings) matching video or image extensions
|
| 289 |
+
"""
|
| 290 |
+
valid_extensions = VIDEO_EXTENSIONS | IMAGE_EXTENSIONS
|
| 291 |
+
path = Path(directory)
|
| 292 |
+
|
| 293 |
+
# Get all files and filter by extension (case-insensitive)
|
| 294 |
+
files = [f for f in path.iterdir() if f.is_file() and f.suffix.lower() in valid_extensions]
|
| 295 |
+
|
| 296 |
+
return sorted([str(f) for f in files])
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def extract_frames_from_image(image_path: str) -> Tuple[torch.Tensor, float]:
|
| 300 |
+
"""
|
| 301 |
+
Extract single frame from image file and convert to tensor format.
|
| 302 |
+
|
| 303 |
+
Reads image using OpenCV, converts BGR to RGB, normalizes to [0,1] range,
|
| 304 |
+
and formats as single-frame video tensor for consistent processing.
|
| 305 |
+
|
| 306 |
+
Args:
|
| 307 |
+
image_path: Path to input image file
|
| 308 |
+
|
| 309 |
+
Returns:
|
| 310 |
+
Tuple containing:
|
| 311 |
+
- frames_tensor: Single frame as tensor [1, H, W, C], Float16, range [0,1] (C=3 for RGB, C=4 for RGBA)
|
| 312 |
+
- fps: Default FPS value (30.0) for image-to-video conversion
|
| 313 |
+
|
| 314 |
+
Raises:
|
| 315 |
+
FileNotFoundError: If image file doesn't exist
|
| 316 |
+
ValueError: If image cannot be opened
|
| 317 |
+
"""
|
| 318 |
+
debug.log(f"Loading image: {image_path}", category="file")
|
| 319 |
+
|
| 320 |
+
if not os.path.exists(image_path):
|
| 321 |
+
raise FileNotFoundError(f"Image file not found: {image_path}")
|
| 322 |
+
|
| 323 |
+
# Read image with alpha channel preserved
|
| 324 |
+
frame = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
|
| 325 |
+
if frame is None:
|
| 326 |
+
raise ValueError(f"Cannot open image file: {image_path}")
|
| 327 |
+
|
| 328 |
+
# Convert BGR(A) to RGB(A) based on channel count
|
| 329 |
+
if frame.shape[2] == 4:
|
| 330 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2RGBA)
|
| 331 |
+
debug.log(f"Detected RGBA image (alpha channel preserved)", category="file")
|
| 332 |
+
else:
|
| 333 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 334 |
+
|
| 335 |
+
# Convert to float32 and normalize
|
| 336 |
+
frame = frame.astype(np.float32) / 255.0
|
| 337 |
+
|
| 338 |
+
# Convert to tensor [1, H, W, C]
|
| 339 |
+
frames_tensor = torch.from_numpy(frame[None, ...]).to(torch.float16)
|
| 340 |
+
|
| 341 |
+
debug.log(f"Image tensor shape: {frames_tensor.shape}, dtype: {frames_tensor.dtype}", category="memory")
|
| 342 |
+
|
| 343 |
+
return frames_tensor, 30.0 # Default FPS for images
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
def get_input_type(input_path: str) -> Literal['video', 'image', 'directory', 'unknown']:
|
| 347 |
+
"""
|
| 348 |
+
Determine input type from file path.
|
| 349 |
+
|
| 350 |
+
Args:
|
| 351 |
+
input_path: Path to input file or directory
|
| 352 |
+
|
| 353 |
+
Returns:
|
| 354 |
+
Input type: 'video', 'image', 'directory', or 'unknown'
|
| 355 |
+
|
| 356 |
+
Raises:
|
| 357 |
+
FileNotFoundError: If input path doesn't exist
|
| 358 |
+
"""
|
| 359 |
+
path = Path(input_path)
|
| 360 |
+
|
| 361 |
+
if not path.exists():
|
| 362 |
+
raise FileNotFoundError(f"Input path not found: {input_path}")
|
| 363 |
+
|
| 364 |
+
if path.is_dir():
|
| 365 |
+
return 'directory'
|
| 366 |
+
|
| 367 |
+
ext = path.suffix.lower()
|
| 368 |
+
if ext in VIDEO_EXTENSIONS:
|
| 369 |
+
return "video"
|
| 370 |
+
elif ext in IMAGE_EXTENSIONS:
|
| 371 |
+
return "image"
|
| 372 |
+
else:
|
| 373 |
+
return "unknown"
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def generate_output_path(input_path: str, output_format: str, output_dir: Optional[str] = None,
|
| 377 |
+
input_type: Optional[str] = None, from_directory: bool = False) -> str:
|
| 378 |
+
"""
|
| 379 |
+
Generate output path based on input path and format.
|
| 380 |
+
|
| 381 |
+
Args:
|
| 382 |
+
input_path: Source file path
|
| 383 |
+
output_format: "mp4" or "png"
|
| 384 |
+
output_dir: Optional output directory (overrides default behavior)
|
| 385 |
+
input_type: Optional input type ("image", "video", "directory")
|
| 386 |
+
from_directory: True if processing files from a directory (batch mode)
|
| 387 |
+
|
| 388 |
+
Returns:
|
| 389 |
+
Absolute output path (file for single image/video, directory for sequences)
|
| 390 |
+
"""
|
| 391 |
+
input_path_obj = Path(input_path)
|
| 392 |
+
input_name = input_path_obj.stem
|
| 393 |
+
|
| 394 |
+
# Determine base directory and whether to add suffix
|
| 395 |
+
if output_dir:
|
| 396 |
+
# User specified output directory - use as-is, no suffix
|
| 397 |
+
base_dir = Path(output_dir)
|
| 398 |
+
add_suffix = False
|
| 399 |
+
elif from_directory:
|
| 400 |
+
# Batch mode: create sibling folder with _upscaled, keep original filenames
|
| 401 |
+
original_dir = input_path_obj.parent
|
| 402 |
+
base_dir = original_dir.parent / f"{original_dir.name}_upscaled"
|
| 403 |
+
add_suffix = False
|
| 404 |
+
else:
|
| 405 |
+
# Single file mode: output to same directory with _upscaled suffix
|
| 406 |
+
base_dir = input_path_obj.parent
|
| 407 |
+
add_suffix = True
|
| 408 |
+
|
| 409 |
+
# Build filename with optional suffix
|
| 410 |
+
file_suffix = "_upscaled" if add_suffix else ""
|
| 411 |
+
|
| 412 |
+
# Generate output path based on format
|
| 413 |
+
if output_format == "png":
|
| 414 |
+
if input_type == "image":
|
| 415 |
+
output_path = base_dir / f"{input_name}{file_suffix}.png"
|
| 416 |
+
else:
|
| 417 |
+
output_path = base_dir / f"{input_name}{file_suffix}"
|
| 418 |
+
else:
|
| 419 |
+
output_path = base_dir / f"{input_name}{file_suffix}.mp4"
|
| 420 |
+
|
| 421 |
+
return str(output_path.resolve())
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
def process_single_file(input_path: str, args: argparse.Namespace, device_list: List[str],
|
| 425 |
+
output_path: Optional[str] = None, format_auto_detected: bool = False,
|
| 426 |
+
runner_cache: Optional[Dict[str, Any]] = None) -> int:
|
| 427 |
+
"""
|
| 428 |
+
Process a single video or image file with optional model caching.
|
| 429 |
+
|
| 430 |
+
For videos, supports streaming mode (chunk_size > 0) which processes in memory-bounded
|
| 431 |
+
chunks with temporal overlap for seamless transitions between chunks.
|
| 432 |
+
|
| 433 |
+
Args:
|
| 434 |
+
input_path: Path to input file
|
| 435 |
+
args: Command-line arguments with all processing settings
|
| 436 |
+
device_list: List of GPU device IDs as strings
|
| 437 |
+
output_path: Optional explicit output path (auto-generated if None)
|
| 438 |
+
format_auto_detected: Whether output format was auto-detected
|
| 439 |
+
runner_cache: Optional cache dict for model reuse across multiple files
|
| 440 |
+
|
| 441 |
+
Returns:
|
| 442 |
+
Number of frames written to output
|
| 443 |
+
"""
|
| 444 |
+
input_type = get_input_type(input_path)
|
| 445 |
+
|
| 446 |
+
if input_type == "unknown":
|
| 447 |
+
debug.log(f"Skipping unsupported file: {input_path}", level="WARNING", category="file", force=True)
|
| 448 |
+
return 0
|
| 449 |
+
|
| 450 |
+
debug.log(f"Processing {input_type}: {Path(input_path).name}", category="generation", force=True)
|
| 451 |
+
|
| 452 |
+
# Generate or validate output path
|
| 453 |
+
if output_path is None:
|
| 454 |
+
output_path = generate_output_path(input_path, args.output_format, input_type=input_type)
|
| 455 |
+
elif not Path(output_path).suffix or (args.output_format == "png" and input_type != "image"):
|
| 456 |
+
# No extension or PNG sequence → treat as directory, generate filename
|
| 457 |
+
output_path = generate_output_path(input_path, args.output_format,
|
| 458 |
+
output_dir=output_path, input_type=input_type)
|
| 459 |
+
|
| 460 |
+
# Show format with auto-detection indicator
|
| 461 |
+
format_prefix = "Auto-detected" if format_auto_detected else "Requested"
|
| 462 |
+
debug.log(f"{format_prefix} output format: {args.output_format}", category="info", force=True, indent_level=1)
|
| 463 |
+
|
| 464 |
+
# === VIDEO PROCESSING ===
|
| 465 |
+
if input_type == "video":
|
| 466 |
+
if not os.path.exists(input_path):
|
| 467 |
+
raise FileNotFoundError(f"Video file not found: {input_path}")
|
| 468 |
+
|
| 469 |
+
cap = cv2.VideoCapture(input_path)
|
| 470 |
+
if not cap.isOpened():
|
| 471 |
+
raise ValueError(f"Cannot open video file: {input_path}")
|
| 472 |
+
|
| 473 |
+
fps = cap.get(cv2.CAP_PROP_FPS) or 30.0
|
| 474 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 475 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 476 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 477 |
+
|
| 478 |
+
debug.log(f"Video info: {total_frames} frames, {width}x{height}, {fps:.2f} FPS", category="info")
|
| 479 |
+
|
| 480 |
+
# Skip initial frames
|
| 481 |
+
if args.skip_first_frames > 0:
|
| 482 |
+
debug.log(f"Skipping first {args.skip_first_frames} frames", category="info")
|
| 483 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, args.skip_first_frames)
|
| 484 |
+
|
| 485 |
+
# Calculate frames to process (apply load_cap if set)
|
| 486 |
+
frames_to_process = total_frames - args.skip_first_frames
|
| 487 |
+
if args.load_cap > 0:
|
| 488 |
+
frames_to_process = min(frames_to_process, args.load_cap)
|
| 489 |
+
|
| 490 |
+
# Early exit for empty/exhausted video
|
| 491 |
+
if frames_to_process <= 0:
|
| 492 |
+
debug.log(f"No frames to process after skipping {args.skip_first_frames} of {total_frames}",
|
| 493 |
+
level="WARNING", category="file", force=True)
|
| 494 |
+
cap.release()
|
| 495 |
+
return 0
|
| 496 |
+
|
| 497 |
+
# Streaming mode: process in chunks
|
| 498 |
+
chunk_size = args.chunk_size if args.chunk_size > 0 else frames_to_process
|
| 499 |
+
streaming = args.chunk_size > 0
|
| 500 |
+
total_chunks = (frames_to_process + chunk_size - 1) // chunk_size # ceiling division
|
| 501 |
+
|
| 502 |
+
if streaming:
|
| 503 |
+
debug.log(f"Streaming mode: chunks of {chunk_size} frames, overlap={args.temporal_overlap}",
|
| 504 |
+
category="info", force=True, indent_level=1)
|
| 505 |
+
|
| 506 |
+
is_png = args.output_format == "png"
|
| 507 |
+
video_writer = None
|
| 508 |
+
overlap = args.temporal_overlap
|
| 509 |
+
frames_written = 0
|
| 510 |
+
chunk_idx = 0
|
| 511 |
+
base_name = Path(input_path).stem
|
| 512 |
+
|
| 513 |
+
# Multi-GPU: workers stream their own segments
|
| 514 |
+
if len(device_list) > 1:
|
| 515 |
+
cap.release() # Workers will reopen
|
| 516 |
+
video_info = {
|
| 517 |
+
'video_path': input_path,
|
| 518 |
+
'start_frame': args.skip_first_frames,
|
| 519 |
+
'frames_to_process': frames_to_process,
|
| 520 |
+
}
|
| 521 |
+
result = _gpu_processing(None, device_list, args, video_info=video_info)
|
| 522 |
+
|
| 523 |
+
# Save result
|
| 524 |
+
if is_png:
|
| 525 |
+
save_frames_to_image(result, output_path, base_name)
|
| 526 |
+
else:
|
| 527 |
+
video_writer = save_frames_to_video(result, output_path, fps,
|
| 528 |
+
video_backend=args.video_backend, use_10bit=args.use_10bit)
|
| 529 |
+
if video_writer is not None:
|
| 530 |
+
video_writer.release()
|
| 531 |
+
|
| 532 |
+
frames_written = result.shape[0]
|
| 533 |
+
|
| 534 |
+
# Single GPU: stream in main process
|
| 535 |
+
else:
|
| 536 |
+
chunk_count = 0
|
| 537 |
+
for result in _stream_video_chunks(
|
| 538 |
+
cap=cap,
|
| 539 |
+
frames_to_process=frames_to_process,
|
| 540 |
+
chunk_size=chunk_size,
|
| 541 |
+
overlap=overlap,
|
| 542 |
+
args=args,
|
| 543 |
+
device_id=device_list[0],
|
| 544 |
+
debug=debug,
|
| 545 |
+
runner_cache=runner_cache,
|
| 546 |
+
log_progress=streaming,
|
| 547 |
+
total_chunks=total_chunks,
|
| 548 |
+
cleanup_timer_name="chunk_cleanup"
|
| 549 |
+
):
|
| 550 |
+
chunk_count += 1
|
| 551 |
+
|
| 552 |
+
# Save output
|
| 553 |
+
if is_png:
|
| 554 |
+
save_frames_to_image(result, output_path, base_name, start_index=frames_written)
|
| 555 |
+
else:
|
| 556 |
+
video_writer = save_frames_to_video(result, output_path, fps, writer=video_writer,
|
| 557 |
+
video_backend=args.video_backend, use_10bit=args.use_10bit)
|
| 558 |
+
|
| 559 |
+
frames_written += result.shape[0]
|
| 560 |
+
del result
|
| 561 |
+
|
| 562 |
+
chunk_idx = chunk_count
|
| 563 |
+
cap.release()
|
| 564 |
+
if video_writer is not None:
|
| 565 |
+
video_writer.release()
|
| 566 |
+
|
| 567 |
+
if streaming:
|
| 568 |
+
debug.log("", category="none", force=True)
|
| 569 |
+
if len(device_list) > 1:
|
| 570 |
+
debug.log(f"Streaming complete: {frames_written} frames across {len(device_list)} GPUs", category="success", force=True)
|
| 571 |
+
else:
|
| 572 |
+
debug.log(f"Streaming complete: {frames_written} frames in {chunk_idx} chunks", category="success", force=True)
|
| 573 |
+
|
| 574 |
+
debug.log(f"Output saved to: {output_path}", category="file", force=True)
|
| 575 |
+
return frames_written
|
| 576 |
+
|
| 577 |
+
# === IMAGE PROCESSING ===
|
| 578 |
+
frames_tensor, _ = extract_frames_from_image(input_path)
|
| 579 |
+
|
| 580 |
+
processing_start = time.time()
|
| 581 |
+
# Process frames (multiprocessing only for multi-GPU)
|
| 582 |
+
if len(device_list) > 1:
|
| 583 |
+
result = _gpu_processing(frames_tensor, device_list, args)
|
| 584 |
+
else:
|
| 585 |
+
result = _single_gpu_direct_processing(frames_tensor, args, device_list[0], runner_cache)
|
| 586 |
+
debug.log(f"Processing time: {time.time() - processing_start:.2f}s", category="timing")
|
| 587 |
+
|
| 588 |
+
# Save single image
|
| 589 |
+
os.makedirs(Path(output_path).parent, exist_ok=True)
|
| 590 |
+
frame_np = (result[0].cpu().numpy() * 255.0).astype(np.uint8)
|
| 591 |
+
_save_image_bgr(frame_np, output_path)
|
| 592 |
+
|
| 593 |
+
debug.log(f"Output saved to: {output_path}", category="file", force=True)
|
| 594 |
+
return 1
|
| 595 |
+
|
| 596 |
+
|
| 597 |
+
def _read_frames_from_cap(cap: cv2.VideoCapture, max_frames: int) -> Optional[torch.Tensor]:
|
| 598 |
+
"""
|
| 599 |
+
Read up to max_frames from an already-open VideoCapture.
|
| 600 |
+
|
| 601 |
+
Args:
|
| 602 |
+
cap: An already opened cv2.VideoCapture instance
|
| 603 |
+
max_frames: Maximum number of frames to read in this call
|
| 604 |
+
|
| 605 |
+
Returns:
|
| 606 |
+
Tensor [T, H, W, C] float32 [0,1], or None if no frames available
|
| 607 |
+
"""
|
| 608 |
+
frames = []
|
| 609 |
+
for _ in range(max_frames):
|
| 610 |
+
ret, frame = cap.read()
|
| 611 |
+
if not ret:
|
| 612 |
+
break
|
| 613 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
|
| 614 |
+
frames.append(frame)
|
| 615 |
+
|
| 616 |
+
if not frames:
|
| 617 |
+
return None
|
| 618 |
+
return torch.from_numpy(np.stack(frames)).to(torch.float32)
|
| 619 |
+
|
| 620 |
+
|
| 621 |
+
def _stream_video_chunks(
|
| 622 |
+
cap: cv2.VideoCapture,
|
| 623 |
+
frames_to_process: int,
|
| 624 |
+
chunk_size: int,
|
| 625 |
+
overlap: int,
|
| 626 |
+
args: argparse.Namespace,
|
| 627 |
+
device_id: str,
|
| 628 |
+
debug: 'Debug',
|
| 629 |
+
runner_cache: Optional[Dict[str, Any]],
|
| 630 |
+
log_progress: bool = False,
|
| 631 |
+
total_chunks: int = 0,
|
| 632 |
+
cleanup_timer_name: Optional[str] = None,
|
| 633 |
+
log_prefix: str = ""
|
| 634 |
+
) -> Generator[torch.Tensor, None, None]:
|
| 635 |
+
"""
|
| 636 |
+
Generator that streams and processes video chunks.
|
| 637 |
+
|
| 638 |
+
Handles frame reading, temporal context prepending, processing via
|
| 639 |
+
_process_frames_core, context removal from output, and memory cleanup.
|
| 640 |
+
Caller is responsible for VideoCapture lifecycle and result handling.
|
| 641 |
+
|
| 642 |
+
Args:
|
| 643 |
+
cap: Open VideoCapture positioned at start frame
|
| 644 |
+
frames_to_process: Total frames to read and process
|
| 645 |
+
chunk_size: Frames per chunk (use frames_to_process for single chunk)
|
| 646 |
+
overlap: Temporal overlap frames between chunks for blending
|
| 647 |
+
args: Processing arguments (copied internally, prepend_frames zeroed after first chunk)
|
| 648 |
+
device_id: GPU device ID for processing
|
| 649 |
+
debug: Debug instance for logging
|
| 650 |
+
runner_cache: Optional model cache dict for reuse across chunks
|
| 651 |
+
log_progress: If True, log chunk progress with separators
|
| 652 |
+
total_chunks: Total chunks for progress display (used if log_progress=True)
|
| 653 |
+
cleanup_timer_name: Optional timer name for memory cleanup logging
|
| 654 |
+
log_prefix: Optional prefix for log messages (e.g., "[GPU 0] " for worker identification)
|
| 655 |
+
|
| 656 |
+
Yields:
|
| 657 |
+
Processed frames tensor [T, H, W, C] for each chunk, context frames removed
|
| 658 |
+
"""
|
| 659 |
+
chunk_args = argparse.Namespace(**vars(args))
|
| 660 |
+
frames_read = 0
|
| 661 |
+
prev_raw_tail = None
|
| 662 |
+
chunk_idx = 0
|
| 663 |
+
streaming = chunk_size < frames_to_process
|
| 664 |
+
|
| 665 |
+
while frames_read < frames_to_process:
|
| 666 |
+
read_count = min(chunk_size, frames_to_process - frames_read)
|
| 667 |
+
new_frames = _read_frames_from_cap(cap, read_count)
|
| 668 |
+
if new_frames is None:
|
| 669 |
+
break
|
| 670 |
+
frames_read += new_frames.shape[0]
|
| 671 |
+
chunk_idx += 1
|
| 672 |
+
|
| 673 |
+
# Disable prepend_frames after first chunk
|
| 674 |
+
if chunk_idx > 1:
|
| 675 |
+
chunk_args.prepend_frames = 0
|
| 676 |
+
|
| 677 |
+
# Prepend context from previous chunk
|
| 678 |
+
if prev_raw_tail is not None and overlap > 0:
|
| 679 |
+
context_count = min(overlap, prev_raw_tail.shape[0])
|
| 680 |
+
frames = torch.cat([prev_raw_tail[-context_count:], new_frames], dim=0)
|
| 681 |
+
else:
|
| 682 |
+
frames = new_frames
|
| 683 |
+
context_count = 0
|
| 684 |
+
|
| 685 |
+
# Log progress if enabled
|
| 686 |
+
if log_progress and streaming:
|
| 687 |
+
if chunk_idx > 1:
|
| 688 |
+
debug.log("", category="none", force=True)
|
| 689 |
+
debug.log("━" * 60, category="none", force=True)
|
| 690 |
+
debug.log("", category="none", force=True)
|
| 691 |
+
debug.log(f"{log_prefix}Chunk {chunk_idx}/{total_chunks}: {new_frames.shape[0]} new + {context_count} context frames",
|
| 692 |
+
category="generation", force=True)
|
| 693 |
+
debug.log("", category="none", force=True)
|
| 694 |
+
|
| 695 |
+
# Process chunk
|
| 696 |
+
result = _process_frames_core(
|
| 697 |
+
frames_tensor=frames.to(torch.float16),
|
| 698 |
+
args=chunk_args,
|
| 699 |
+
device_id=device_id,
|
| 700 |
+
debug=debug,
|
| 701 |
+
runner_cache=runner_cache
|
| 702 |
+
)
|
| 703 |
+
|
| 704 |
+
# Remove context frames from output
|
| 705 |
+
if context_count > 0:
|
| 706 |
+
result = result[context_count:]
|
| 707 |
+
|
| 708 |
+
# Save tail for next chunk context
|
| 709 |
+
prev_raw_tail = new_frames[-overlap:].clone() if overlap > 0 else None
|
| 710 |
+
|
| 711 |
+
# Cleanup before yield
|
| 712 |
+
del frames
|
| 713 |
+
|
| 714 |
+
yield result
|
| 715 |
+
|
| 716 |
+
# Memory cleanup between chunks
|
| 717 |
+
if streaming:
|
| 718 |
+
clear_memory(debug=debug, deep=True, force=True, timer_name=cleanup_timer_name)
|
| 719 |
+
|
| 720 |
+
|
| 721 |
+
def _save_image_bgr(frame_np: np.ndarray, file_path: str) -> None:
|
| 722 |
+
"""
|
| 723 |
+
Save a single RGB(A) uint8 frame to disk, converting to BGR(A) for OpenCV.
|
| 724 |
+
|
| 725 |
+
Args:
|
| 726 |
+
frame_np: Frame as uint8 numpy array [H, W, C] where C is 3 (RGB) or 4 (RGBA)
|
| 727 |
+
file_path: Output file path
|
| 728 |
+
"""
|
| 729 |
+
if frame_np.shape[2] == 4:
|
| 730 |
+
frame_bgr = cv2.cvtColor(frame_np, cv2.COLOR_RGBA2BGRA)
|
| 731 |
+
else:
|
| 732 |
+
frame_bgr = cv2.cvtColor(frame_np, cv2.COLOR_RGB2BGR)
|
| 733 |
+
cv2.imwrite(file_path, frame_bgr)
|
| 734 |
+
|
| 735 |
+
|
| 736 |
+
def save_frames_to_video(
|
| 737 |
+
frames_tensor: torch.Tensor,
|
| 738 |
+
output_path: str,
|
| 739 |
+
fps: float = 30.0,
|
| 740 |
+
writer: Optional[cv2.VideoWriter] = None,
|
| 741 |
+
video_backend: str = "opencv",
|
| 742 |
+
use_10bit: bool = False
|
| 743 |
+
) -> Optional[cv2.VideoWriter]:
|
| 744 |
+
"""
|
| 745 |
+
Save frames tensor to MP4 video file.
|
| 746 |
+
|
| 747 |
+
Converts tensor from Float32 [0,1] to uint8 [0,255], RGB to BGR for OpenCV,
|
| 748 |
+
and writes to video file using mp4v codec. Supports streaming mode where
|
| 749 |
+
an existing writer is passed and kept open for subsequent chunks.
|
| 750 |
+
|
| 751 |
+
Args:
|
| 752 |
+
frames_tensor: Frames in format [T, H, W, C], Float32, range [0,1]
|
| 753 |
+
output_path: Output video file path (directory created if doesn't exist)
|
| 754 |
+
fps: Frames per second for output video (default: 30.0)
|
| 755 |
+
writer: Existing VideoWriter for streaming (if None, creates new one)
|
| 756 |
+
|
| 757 |
+
Returns:
|
| 758 |
+
VideoWriter if streaming mode (caller must close), None if standalone mode
|
| 759 |
+
|
| 760 |
+
Raises:
|
| 761 |
+
ValueError: If video writer cannot be initialized
|
| 762 |
+
"""
|
| 763 |
+
frames_np = (frames_tensor.cpu().numpy() * 255.0).astype(np.uint8)
|
| 764 |
+
T, H, W, C = frames_np.shape
|
| 765 |
+
|
| 766 |
+
if writer is None:
|
| 767 |
+
debug.log(f"Saving {T} frames to video: {output_path} (backend={video_backend})", category="file")
|
| 768 |
+
os.makedirs(Path(output_path).parent, exist_ok=True)
|
| 769 |
+
if video_backend == "ffmpeg":
|
| 770 |
+
writer = FFMPEGVideoWriter(output_path, W, H, fps, use_10bit)
|
| 771 |
+
else:
|
| 772 |
+
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
|
| 773 |
+
writer = cv2.VideoWriter(output_path, fourcc, fps, (W, H))
|
| 774 |
+
if not writer.isOpened():
|
| 775 |
+
raise ValueError(f"Cannot create video writer for: {output_path}")
|
| 776 |
+
|
| 777 |
+
for i, frame in enumerate(frames_np):
|
| 778 |
+
frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
|
| 779 |
+
writer.write(frame_bgr)
|
| 780 |
+
if debug.enabled and (i + 1) % 100 == 0:
|
| 781 |
+
debug.log(f"Written {i + 1}/{T} frames", category="file")
|
| 782 |
+
|
| 783 |
+
return writer # Caller always closes
|
| 784 |
+
|
| 785 |
+
|
| 786 |
+
def save_frames_to_image(
|
| 787 |
+
frames_tensor: torch.Tensor,
|
| 788 |
+
output_dir: str,
|
| 789 |
+
base_name: str,
|
| 790 |
+
start_index: int = 0
|
| 791 |
+
) -> int:
|
| 792 |
+
"""
|
| 793 |
+
Save frames tensor as sequential PNG image files.
|
| 794 |
+
|
| 795 |
+
Each frame saved as {base_name}_{index:0Nd}.png with zero-padded indices.
|
| 796 |
+
Converts Float32 [0,1] to uint8 [0,255] and RGB(A) to BGR(A) for OpenCV.
|
| 797 |
+
|
| 798 |
+
Args:
|
| 799 |
+
frames_tensor: Frames in format [T, H, W, C], Float32, range [0,1]
|
| 800 |
+
output_dir: Directory to save PNG files (created if doesn't exist)
|
| 801 |
+
base_name: Base name for output files (e.g., "frame" → "frame_00000.png")
|
| 802 |
+
start_index: Starting index for filenames (for streaming continuation)
|
| 803 |
+
|
| 804 |
+
Returns:
|
| 805 |
+
Number of frames saved
|
| 806 |
+
"""
|
| 807 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 808 |
+
|
| 809 |
+
frames_np = (frames_tensor.cpu().numpy() * 255.0).astype(np.uint8)
|
| 810 |
+
total = frames_np.shape[0]
|
| 811 |
+
|
| 812 |
+
if start_index == 0:
|
| 813 |
+
debug.log(f"Saving {total} frames as PNGs to directory: {output_dir}", category="file")
|
| 814 |
+
digits = 6 # Supports up to 999,999 frames (~11.5 hours at 24fps)
|
| 815 |
+
|
| 816 |
+
for idx, frame in enumerate(frames_np):
|
| 817 |
+
filename = f"{base_name}_{start_index + idx:0{digits}d}.png"
|
| 818 |
+
file_path = os.path.join(output_dir, filename)
|
| 819 |
+
_save_image_bgr(frame, file_path)
|
| 820 |
+
if debug.enabled and (idx + 1) % 100 == 0:
|
| 821 |
+
debug.log(f"Saved {idx + 1}/{total} images", category="file")
|
| 822 |
+
|
| 823 |
+
debug.log(f"Saved {total} images to '{output_dir}'", category="success")
|
| 824 |
+
return total
|
| 825 |
+
|
| 826 |
+
|
| 827 |
+
# =============================================================================
|
| 828 |
+
# Core Processing Logic
|
| 829 |
+
# =============================================================================
|
| 830 |
+
|
| 831 |
+
def _process_frames_core(
|
| 832 |
+
frames_tensor: torch.Tensor,
|
| 833 |
+
args: argparse.Namespace,
|
| 834 |
+
device_id: str,
|
| 835 |
+
debug: Debug,
|
| 836 |
+
runner_cache: Optional[Dict[str, Any]] = None
|
| 837 |
+
) -> torch.Tensor:
|
| 838 |
+
"""
|
| 839 |
+
Core frame processing logic shared between worker and direct processing.
|
| 840 |
+
|
| 841 |
+
Executes the complete 4-phase pipeline: encode → upscale → decode → postprocess.
|
| 842 |
+
Supports both cached (direct) and non-cached (worker) execution modes.
|
| 843 |
+
|
| 844 |
+
Args:
|
| 845 |
+
frames_tensor: Input frames [T, H, W, C], Float16/Float32, range [0,1]
|
| 846 |
+
args: Command-line arguments with all processing settings
|
| 847 |
+
device_id: Device ID for inference ("0", "1", etc.)
|
| 848 |
+
debug: Debug instance for logging
|
| 849 |
+
runner_cache: Optional cache dict for model reuse (direct mode only)
|
| 850 |
+
|
| 851 |
+
Returns:
|
| 852 |
+
Upscaled frames tensor [T', H', W', C], Float32, range [0,1]
|
| 853 |
+
"""
|
| 854 |
+
# Determine platform and convert device IDs to full names
|
| 855 |
+
platform_type = get_gpu_backend()
|
| 856 |
+
inference_device = _device_id_to_name(device_id, platform_type)
|
| 857 |
+
|
| 858 |
+
# Parse offload devices (with caching defaults)
|
| 859 |
+
cache_dit = args.cache_dit if runner_cache is not None else False
|
| 860 |
+
cache_vae = args.cache_vae if runner_cache is not None else False
|
| 861 |
+
|
| 862 |
+
dit_offload = _parse_offload_device(args.dit_offload_device, platform_type, cache_dit)
|
| 863 |
+
vae_offload = _parse_offload_device(args.vae_offload_device, platform_type, cache_vae)
|
| 864 |
+
tensor_offload = _parse_offload_device(args.tensor_offload_device, platform_type, False)
|
| 865 |
+
|
| 866 |
+
# Setup or reuse generation context
|
| 867 |
+
if runner_cache is not None and 'ctx' in runner_cache:
|
| 868 |
+
ctx = runner_cache['ctx']
|
| 869 |
+
# Clear previous run data but keep device config
|
| 870 |
+
keys_to_keep = {'dit_device', 'vae_device', 'dit_offload_device',
|
| 871 |
+
'vae_offload_device', 'tensor_offload_device', 'compute_dtype'}
|
| 872 |
+
for key in list(ctx.keys()):
|
| 873 |
+
if key not in keys_to_keep:
|
| 874 |
+
del ctx[key]
|
| 875 |
+
else:
|
| 876 |
+
ctx = setup_generation_context(
|
| 877 |
+
dit_device=inference_device,
|
| 878 |
+
vae_device=inference_device,
|
| 879 |
+
dit_offload_device=dit_offload,
|
| 880 |
+
vae_offload_device=vae_offload,
|
| 881 |
+
tensor_offload_device=tensor_offload,
|
| 882 |
+
debug=debug
|
| 883 |
+
)
|
| 884 |
+
if runner_cache is not None:
|
| 885 |
+
runner_cache['ctx'] = ctx
|
| 886 |
+
|
| 887 |
+
# Build torch compile args
|
| 888 |
+
torch_compile_args_dit = None
|
| 889 |
+
torch_compile_args_vae = None
|
| 890 |
+
if args.compile_dit:
|
| 891 |
+
torch_compile_args_dit = {
|
| 892 |
+
"backend": args.compile_backend,
|
| 893 |
+
"mode": args.compile_mode,
|
| 894 |
+
"fullgraph": args.compile_fullgraph,
|
| 895 |
+
"dynamic": args.compile_dynamic,
|
| 896 |
+
"dynamo_cache_size_limit": args.compile_dynamo_cache_size_limit,
|
| 897 |
+
"dynamo_recompile_limit": args.compile_dynamo_recompile_limit,
|
| 898 |
+
}
|
| 899 |
+
if args.compile_vae:
|
| 900 |
+
torch_compile_args_vae = {
|
| 901 |
+
"backend": args.compile_backend,
|
| 902 |
+
"mode": args.compile_mode,
|
| 903 |
+
"fullgraph": args.compile_fullgraph,
|
| 904 |
+
"dynamic": args.compile_dynamic,
|
| 905 |
+
"dynamo_cache_size_limit": args.compile_dynamo_cache_size_limit,
|
| 906 |
+
"dynamo_recompile_limit": args.compile_dynamo_recompile_limit,
|
| 907 |
+
}
|
| 908 |
+
|
| 909 |
+
# Prepare runner with caching support
|
| 910 |
+
model_dir = args.model_dir if args.model_dir is not None else f"./models/{SEEDVR2_FOLDER_NAME}"
|
| 911 |
+
|
| 912 |
+
# Use fixed IDs for CLI caching when enabled
|
| 913 |
+
dit_id = "cli_dit" if cache_dit else None
|
| 914 |
+
vae_id = "cli_vae" if cache_vae else None
|
| 915 |
+
|
| 916 |
+
runner, cache_context = prepare_runner(
|
| 917 |
+
dit_model=args.dit_model,
|
| 918 |
+
vae_model=DEFAULT_VAE,
|
| 919 |
+
model_dir=model_dir,
|
| 920 |
+
debug=debug,
|
| 921 |
+
ctx=ctx,
|
| 922 |
+
dit_cache=cache_dit,
|
| 923 |
+
vae_cache=cache_vae,
|
| 924 |
+
dit_id=dit_id,
|
| 925 |
+
vae_id=vae_id,
|
| 926 |
+
block_swap_config={
|
| 927 |
+
'blocks_to_swap': args.blocks_to_swap,
|
| 928 |
+
'swap_io_components': args.swap_io_components,
|
| 929 |
+
'offload_device': dit_offload,
|
| 930 |
+
},
|
| 931 |
+
encode_tiled=args.vae_encode_tiled,
|
| 932 |
+
encode_tile_size=(args.vae_encode_tile_size, args.vae_encode_tile_size),
|
| 933 |
+
encode_tile_overlap=(args.vae_encode_tile_overlap, args.vae_encode_tile_overlap),
|
| 934 |
+
decode_tiled=args.vae_decode_tiled,
|
| 935 |
+
decode_tile_size=(args.vae_decode_tile_size, args.vae_decode_tile_size),
|
| 936 |
+
decode_tile_overlap=(args.vae_decode_tile_overlap, args.vae_decode_tile_overlap),
|
| 937 |
+
tile_debug=args.tile_debug.lower() if args.tile_debug else "false",
|
| 938 |
+
attention_mode=args.attention_mode,
|
| 939 |
+
torch_compile_args_dit=torch_compile_args_dit,
|
| 940 |
+
torch_compile_args_vae=torch_compile_args_vae
|
| 941 |
+
)
|
| 942 |
+
|
| 943 |
+
ctx['cache_context'] = cache_context
|
| 944 |
+
if runner_cache is not None:
|
| 945 |
+
runner_cache['runner'] = runner
|
| 946 |
+
|
| 947 |
+
# Preload text embeddings before Phase 1 to avoid sync stall in Phase 2
|
| 948 |
+
ctx['text_embeds'] = load_text_embeddings(script_directory, ctx['dit_device'], ctx['compute_dtype'], debug)
|
| 949 |
+
debug.log("Loaded text embeddings for DiT", category="dit")
|
| 950 |
+
|
| 951 |
+
# Compute generation info and log start (handles prepending internally)
|
| 952 |
+
frames_tensor, gen_info = compute_generation_info(
|
| 953 |
+
ctx=ctx,
|
| 954 |
+
images=frames_tensor,
|
| 955 |
+
resolution=args.resolution,
|
| 956 |
+
max_resolution=args.max_resolution,
|
| 957 |
+
batch_size=args.batch_size,
|
| 958 |
+
uniform_batch_size=args.uniform_batch_size,
|
| 959 |
+
seed=args.seed,
|
| 960 |
+
prepend_frames=args.prepend_frames,
|
| 961 |
+
temporal_overlap=args.temporal_overlap,
|
| 962 |
+
debug=debug
|
| 963 |
+
)
|
| 964 |
+
log_generation_start(gen_info, debug)
|
| 965 |
+
|
| 966 |
+
# Phase 1: Encode
|
| 967 |
+
ctx = encode_all_batches(
|
| 968 |
+
runner, ctx=ctx, images=frames_tensor,
|
| 969 |
+
debug=debug,
|
| 970 |
+
batch_size=args.batch_size,
|
| 971 |
+
uniform_batch_size=args.uniform_batch_size,
|
| 972 |
+
seed=args.seed,
|
| 973 |
+
progress_callback=None,
|
| 974 |
+
temporal_overlap=args.temporal_overlap,
|
| 975 |
+
resolution=args.resolution,
|
| 976 |
+
max_resolution=args.max_resolution,
|
| 977 |
+
input_noise_scale=args.input_noise_scale,
|
| 978 |
+
color_correction=args.color_correction
|
| 979 |
+
)
|
| 980 |
+
|
| 981 |
+
# Phase 2: Upscale
|
| 982 |
+
ctx = upscale_all_batches(
|
| 983 |
+
runner, ctx=ctx, debug=debug, progress_callback=None,
|
| 984 |
+
seed=args.seed,
|
| 985 |
+
latent_noise_scale=args.latent_noise_scale,
|
| 986 |
+
cache_model=cache_dit
|
| 987 |
+
)
|
| 988 |
+
|
| 989 |
+
# Phase 3: Decode
|
| 990 |
+
ctx = decode_all_batches(
|
| 991 |
+
runner, ctx=ctx, debug=debug, progress_callback=None,
|
| 992 |
+
cache_model=cache_vae
|
| 993 |
+
)
|
| 994 |
+
|
| 995 |
+
# Phase 4: Post-process
|
| 996 |
+
ctx = postprocess_all_batches(
|
| 997 |
+
ctx=ctx, debug=debug, progress_callback=None,
|
| 998 |
+
color_correction=args.color_correction,
|
| 999 |
+
prepend_frames=0, # Worker mode handles this in main process
|
| 1000 |
+
temporal_overlap=args.temporal_overlap,
|
| 1001 |
+
batch_size=args.batch_size
|
| 1002 |
+
)
|
| 1003 |
+
|
| 1004 |
+
result_tensor = ctx['final_video']
|
| 1005 |
+
|
| 1006 |
+
# Convert to CPU and compatible dtype
|
| 1007 |
+
if result_tensor.is_cuda or result_tensor.is_mps:
|
| 1008 |
+
result_tensor = result_tensor.cpu()
|
| 1009 |
+
if result_tensor.dtype in (torch.bfloat16, torch.float8_e4m3fn, torch.float8_e5m2):
|
| 1010 |
+
result_tensor = result_tensor.to(torch.float32)
|
| 1011 |
+
|
| 1012 |
+
return result_tensor
|
| 1013 |
+
|
| 1014 |
+
|
| 1015 |
+
def _worker_process(
|
| 1016 |
+
proc_idx: int,
|
| 1017 |
+
device_id: str,
|
| 1018 |
+
frames_np: Optional[np.ndarray],
|
| 1019 |
+
shared_args: Dict[str, Any],
|
| 1020 |
+
return_queue: mp.Queue,
|
| 1021 |
+
done_barrier: mp.Barrier,
|
| 1022 |
+
video_info: Optional[Dict[str, Any]] = None
|
| 1023 |
+
) -> None:
|
| 1024 |
+
"""
|
| 1025 |
+
Worker process for multi-GPU upscaling.
|
| 1026 |
+
|
| 1027 |
+
Supports two modes:
|
| 1028 |
+
1. frames_np provided: Process pre-loaded frames (for images)
|
| 1029 |
+
2. video_info provided: Stream video segment internally (for videos)
|
| 1030 |
+
- Each worker opens the video, seeks to its assigned range, and streams
|
| 1031 |
+
with internal chunking and model caching for memory efficiency
|
| 1032 |
+
|
| 1033 |
+
Args:
|
| 1034 |
+
proc_idx: Worker index for result ordering
|
| 1035 |
+
device_id: GPU device ID (used for CUDA_VISIBLE_DEVICES inheritance)
|
| 1036 |
+
frames_np: Pre-loaded frames as numpy array, or None for video streaming
|
| 1037 |
+
shared_args: Serialized args namespace as dict
|
| 1038 |
+
return_queue: Queue for returning results to parent
|
| 1039 |
+
done_barrier: Barrier for synchronizing shared memory handoff
|
| 1040 |
+
video_info: Optional dict with 'video_path', 'start_frame', 'end_frame'
|
| 1041 |
+
for video streaming mode
|
| 1042 |
+
"""
|
| 1043 |
+
# Create debug instance for this worker
|
| 1044 |
+
worker_debug = Debug(enabled=shared_args["debug"])
|
| 1045 |
+
|
| 1046 |
+
args = argparse.Namespace(**shared_args)
|
| 1047 |
+
|
| 1048 |
+
# Video streaming mode: worker reads and processes its assigned segment
|
| 1049 |
+
if video_info is not None:
|
| 1050 |
+
cap = cv2.VideoCapture(video_info['video_path'])
|
| 1051 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, video_info['start_frame'])
|
| 1052 |
+
|
| 1053 |
+
segment_frames = video_info['end_frame'] - video_info['start_frame']
|
| 1054 |
+
chunk_size = args.chunk_size if args.chunk_size > 0 else segment_frames
|
| 1055 |
+
|
| 1056 |
+
worker_debug.log(f"GPU {proc_idx}: frames {video_info['start_frame']}-{video_info['end_frame']} "
|
| 1057 |
+
f"({segment_frames} frames, chunks of {chunk_size})",
|
| 1058 |
+
category="generation", force=True)
|
| 1059 |
+
|
| 1060 |
+
# Only GPU 0 uses prepend_frames (applies to video start only)
|
| 1061 |
+
worker_args = argparse.Namespace(**vars(args))
|
| 1062 |
+
if proc_idx != 0:
|
| 1063 |
+
worker_args.prepend_frames = 0
|
| 1064 |
+
|
| 1065 |
+
# Enable model caching within worker only if requested
|
| 1066 |
+
runner_cache = {} if (args.cache_dit or args.cache_vae) else None
|
| 1067 |
+
|
| 1068 |
+
total_chunks = (segment_frames + chunk_size - 1) // chunk_size
|
| 1069 |
+
results = []
|
| 1070 |
+
for result in _stream_video_chunks(
|
| 1071 |
+
cap=cap,
|
| 1072 |
+
frames_to_process=segment_frames,
|
| 1073 |
+
chunk_size=chunk_size,
|
| 1074 |
+
overlap=args.temporal_overlap,
|
| 1075 |
+
args=worker_args,
|
| 1076 |
+
device_id="0",
|
| 1077 |
+
debug=worker_debug,
|
| 1078 |
+
runner_cache=runner_cache,
|
| 1079 |
+
log_progress=total_chunks > 1,
|
| 1080 |
+
total_chunks=total_chunks,
|
| 1081 |
+
log_prefix=f"[GPU {proc_idx}] "
|
| 1082 |
+
):
|
| 1083 |
+
results.append(result.cpu())
|
| 1084 |
+
|
| 1085 |
+
cap.release()
|
| 1086 |
+
result_tensor = torch.cat(results, dim=0) if results else torch.empty(0, dtype=torch.float32)
|
| 1087 |
+
|
| 1088 |
+
# Pre-loaded frames mode (original behavior)
|
| 1089 |
+
else:
|
| 1090 |
+
frames_tensor = torch.from_numpy(frames_np).to(torch.float16)
|
| 1091 |
+
result_tensor = _process_frames_core(
|
| 1092 |
+
frames_tensor=frames_tensor,
|
| 1093 |
+
args=args,
|
| 1094 |
+
device_id="0",
|
| 1095 |
+
debug=worker_debug,
|
| 1096 |
+
runner_cache=None
|
| 1097 |
+
)
|
| 1098 |
+
|
| 1099 |
+
# Share tensor memory for efficient cross-process transfer (avoids pickling large arrays)
|
| 1100 |
+
return_queue.put((proc_idx, result_tensor.share_memory_()))
|
| 1101 |
+
|
| 1102 |
+
# Wait for parent to copy shared tensors before exiting
|
| 1103 |
+
# (shared memory requires creating process to stay alive during access)
|
| 1104 |
+
done_barrier.wait()
|
| 1105 |
+
|
| 1106 |
+
|
| 1107 |
+
def _single_gpu_direct_processing(
|
| 1108 |
+
frames_tensor: torch.Tensor,
|
| 1109 |
+
args: argparse.Namespace,
|
| 1110 |
+
device_id: str,
|
| 1111 |
+
runner_cache: Optional[Dict[str, Any]]
|
| 1112 |
+
) -> torch.Tensor:
|
| 1113 |
+
"""
|
| 1114 |
+
Direct single-GPU processing with model caching support.
|
| 1115 |
+
|
| 1116 |
+
Uses main process and shared runner cache for efficient multi-file processing.
|
| 1117 |
+
"""
|
| 1118 |
+
return _process_frames_core(
|
| 1119 |
+
frames_tensor=frames_tensor,
|
| 1120 |
+
args=args,
|
| 1121 |
+
device_id=device_id,
|
| 1122 |
+
debug=debug,
|
| 1123 |
+
runner_cache=runner_cache
|
| 1124 |
+
)
|
| 1125 |
+
|
| 1126 |
+
|
| 1127 |
+
def _gpu_processing(
|
| 1128 |
+
frames_tensor: Optional[torch.Tensor],
|
| 1129 |
+
device_list: List[str],
|
| 1130 |
+
args: argparse.Namespace,
|
| 1131 |
+
video_info: Optional[Dict[str, Any]] = None
|
| 1132 |
+
) -> torch.Tensor:
|
| 1133 |
+
"""
|
| 1134 |
+
Orchestrate multi-GPU parallel video upscaling with temporal overlap blending.
|
| 1135 |
+
|
| 1136 |
+
Supports two modes:
|
| 1137 |
+
1. video_info provided: Workers stream their assigned video segments internally
|
| 1138 |
+
(each GPU reads and processes its frame range with internal chunking)
|
| 1139 |
+
2. frames_tensor provided: Workers process pre-loaded frame chunks
|
| 1140 |
+
(non streaming behavior for images or pre-loaded videos)
|
| 1141 |
+
|
| 1142 |
+
Args:
|
| 1143 |
+
frames_tensor: Input frames [T, H, W, C] or None if using video_info mode
|
| 1144 |
+
device_list: List of device IDs as strings (e.g., ["0", "1"])
|
| 1145 |
+
args: Parsed command-line arguments containing all processing settings
|
| 1146 |
+
video_info: Optional dict with 'video_path', 'start_frame', 'frames_to_process'
|
| 1147 |
+
for streaming mode where workers read video directly
|
| 1148 |
+
|
| 1149 |
+
Returns:
|
| 1150 |
+
Upscaled frames tensor [T', H', W', C], Float32, range [0,1]
|
| 1151 |
+
"""
|
| 1152 |
+
num_devices = len(device_list)
|
| 1153 |
+
overlap = args.temporal_overlap
|
| 1154 |
+
|
| 1155 |
+
return_queue = mp.Queue(maxsize=0)
|
| 1156 |
+
done_barrier = mp.Barrier(num_devices + 1)
|
| 1157 |
+
workers = []
|
| 1158 |
+
shared_args = vars(args).copy()
|
| 1159 |
+
|
| 1160 |
+
# Video streaming mode: distribute frame ranges to workers
|
| 1161 |
+
if video_info is not None:
|
| 1162 |
+
total_frames = video_info['frames_to_process']
|
| 1163 |
+
start_frame = video_info['start_frame']
|
| 1164 |
+
video_path = video_info['video_path']
|
| 1165 |
+
|
| 1166 |
+
base_per_gpu = total_frames // num_devices
|
| 1167 |
+
remainder = total_frames % num_devices
|
| 1168 |
+
|
| 1169 |
+
current_start = start_frame
|
| 1170 |
+
for idx, device_id in enumerate(device_list):
|
| 1171 |
+
gpu_frames = base_per_gpu + (1 if idx < remainder else 0)
|
| 1172 |
+
gpu_end = current_start + gpu_frames
|
| 1173 |
+
|
| 1174 |
+
# Add overlap frames for blending (except last GPU)
|
| 1175 |
+
if idx < num_devices - 1 and overlap > 0:
|
| 1176 |
+
gpu_end = min(gpu_end + overlap, start_frame + total_frames)
|
| 1177 |
+
|
| 1178 |
+
worker_video_info = {
|
| 1179 |
+
'video_path': video_path,
|
| 1180 |
+
'start_frame': current_start,
|
| 1181 |
+
'end_frame': gpu_end,
|
| 1182 |
+
}
|
| 1183 |
+
|
| 1184 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = device_id
|
| 1185 |
+
p = mp.Process(
|
| 1186 |
+
target=_worker_process,
|
| 1187 |
+
args=(idx, device_id, None, shared_args, return_queue, done_barrier),
|
| 1188 |
+
kwargs={'video_info': worker_video_info}
|
| 1189 |
+
)
|
| 1190 |
+
p.start()
|
| 1191 |
+
workers.append(p)
|
| 1192 |
+
|
| 1193 |
+
current_start += gpu_frames
|
| 1194 |
+
|
| 1195 |
+
# Pre-loaded frames mode (original behavior for images or non-streaming)
|
| 1196 |
+
else:
|
| 1197 |
+
total_frames = frames_tensor.shape[0]
|
| 1198 |
+
|
| 1199 |
+
if overlap > 0 and num_devices > 1:
|
| 1200 |
+
chunk_with_overlap = total_frames // num_devices + overlap
|
| 1201 |
+
if args.batch_size > 1:
|
| 1202 |
+
chunk_with_overlap = ((chunk_with_overlap + args.batch_size - 1) // args.batch_size) * args.batch_size
|
| 1203 |
+
base_chunk_size = chunk_with_overlap - overlap
|
| 1204 |
+
|
| 1205 |
+
chunks = []
|
| 1206 |
+
for i in range(num_devices):
|
| 1207 |
+
start_idx = i * base_chunk_size
|
| 1208 |
+
if i == num_devices - 1:
|
| 1209 |
+
end_idx = total_frames
|
| 1210 |
+
else:
|
| 1211 |
+
end_idx = min(start_idx + chunk_with_overlap, total_frames)
|
| 1212 |
+
chunks.append(frames_tensor[start_idx:end_idx])
|
| 1213 |
+
else:
|
| 1214 |
+
chunks = torch.chunk(frames_tensor, num_devices, dim=0)
|
| 1215 |
+
|
| 1216 |
+
for idx, (device_id, chunk_tensor) in enumerate(zip(device_list, chunks)):
|
| 1217 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = device_id
|
| 1218 |
+
p = mp.Process(
|
| 1219 |
+
target=_worker_process,
|
| 1220 |
+
args=(idx, device_id, chunk_tensor.cpu().numpy(), shared_args, return_queue, done_barrier),
|
| 1221 |
+
)
|
| 1222 |
+
p.start()
|
| 1223 |
+
workers.append(p)
|
| 1224 |
+
|
| 1225 |
+
# Collect results before joining to prevent deadlock
|
| 1226 |
+
# Tensors arrive via shared memory - copy to numpy while workers still alive
|
| 1227 |
+
results_np = [None] * num_devices
|
| 1228 |
+
collected = 0
|
| 1229 |
+
while collected < num_devices:
|
| 1230 |
+
proc_idx, result_tensor = return_queue.get()
|
| 1231 |
+
results_np[proc_idx] = result_tensor.numpy()
|
| 1232 |
+
collected += 1
|
| 1233 |
+
|
| 1234 |
+
# Release workers now that shared tensors are copied
|
| 1235 |
+
done_barrier.wait()
|
| 1236 |
+
|
| 1237 |
+
# Now safe to join
|
| 1238 |
+
for p in workers:
|
| 1239 |
+
p.join()
|
| 1240 |
+
|
| 1241 |
+
# Concatenate results with overlap blending using shared function
|
| 1242 |
+
if args.temporal_overlap > 0 and num_devices > 1:
|
| 1243 |
+
overlap = args.temporal_overlap
|
| 1244 |
+
result_tensor = None
|
| 1245 |
+
|
| 1246 |
+
for idx, res_np in enumerate(results_np):
|
| 1247 |
+
chunk_tensor = torch.from_numpy(res_np).to(torch.float32)
|
| 1248 |
+
|
| 1249 |
+
if idx == 0:
|
| 1250 |
+
# First chunk: keep all frames
|
| 1251 |
+
result_tensor = chunk_tensor
|
| 1252 |
+
else:
|
| 1253 |
+
# Subsequent chunks: blend overlapping region with accumulated result
|
| 1254 |
+
if chunk_tensor.shape[0] > overlap and result_tensor.shape[0] >= overlap:
|
| 1255 |
+
# Get overlapping regions
|
| 1256 |
+
prev_tail = result_tensor[-overlap:] # Last N frames from accumulated result
|
| 1257 |
+
cur_head = chunk_tensor[:overlap] # First N frames from current chunk
|
| 1258 |
+
|
| 1259 |
+
# Blend using shared function
|
| 1260 |
+
blended = blend_overlapping_frames(prev_tail, cur_head, overlap)
|
| 1261 |
+
|
| 1262 |
+
# Replace tail of result with blended frames, then append rest of chunk
|
| 1263 |
+
result_tensor = torch.cat([
|
| 1264 |
+
result_tensor[:-overlap], # Everything except the tail
|
| 1265 |
+
blended, # Blended overlapping frames
|
| 1266 |
+
chunk_tensor[overlap:] # Non-overlapping part of current chunk
|
| 1267 |
+
], dim=0)
|
| 1268 |
+
else:
|
| 1269 |
+
# Edge case: chunk too small, just append non-overlapping part
|
| 1270 |
+
if chunk_tensor.shape[0] > overlap:
|
| 1271 |
+
result_tensor = torch.cat([result_tensor, chunk_tensor[overlap:]], dim=0)
|
| 1272 |
+
|
| 1273 |
+
if result_tensor is None:
|
| 1274 |
+
result_tensor = torch.from_numpy(results_np[0]).to(torch.float32)
|
| 1275 |
+
else:
|
| 1276 |
+
# Simple concatenation without overlap
|
| 1277 |
+
result_tensor = torch.from_numpy(np.concatenate(results_np, axis=0)).to(torch.float32)
|
| 1278 |
+
|
| 1279 |
+
# Handle prepend_frames removal (multi-GPU safe - done after all workers complete)
|
| 1280 |
+
if args.prepend_frames > 0:
|
| 1281 |
+
if args.prepend_frames < result_tensor.shape[0]:
|
| 1282 |
+
debug.log(f"Removing {args.prepend_frames} prepended frames from output", category="generation")
|
| 1283 |
+
result_tensor = result_tensor[args.prepend_frames:]
|
| 1284 |
+
else:
|
| 1285 |
+
debug.log(f"prepend_frames ({args.prepend_frames}) >= total frames ({result_tensor.shape[0]}), skipping removal",
|
| 1286 |
+
level="WARNING", category="generation", force=True)
|
| 1287 |
+
|
| 1288 |
+
return result_tensor
|
| 1289 |
+
|
| 1290 |
+
|
| 1291 |
+
# =============================================================================
|
| 1292 |
+
# Argument Parsing
|
| 1293 |
+
# =============================================================================
|
| 1294 |
+
|
| 1295 |
+
def parse_arguments() -> argparse.Namespace:
|
| 1296 |
+
"""
|
| 1297 |
+
Parse and validate command-line arguments for SeedVR2 CLI.
|
| 1298 |
+
|
| 1299 |
+
Configures all available options including model selection, processing parameters,
|
| 1300 |
+
memory optimization settings, and output configuration.
|
| 1301 |
+
|
| 1302 |
+
Returns:
|
| 1303 |
+
Parsed arguments namespace with all CLI parameters
|
| 1304 |
+
|
| 1305 |
+
Note:
|
| 1306 |
+
- cuda_device argument only available on non-macOS systems
|
| 1307 |
+
- Default model directory resolves to "models/SEEDVR2" if not specified
|
| 1308 |
+
"""
|
| 1309 |
+
|
| 1310 |
+
# Get the actual invocation path for usage examples
|
| 1311 |
+
invocation = sys.argv[0]
|
| 1312 |
+
|
| 1313 |
+
# Multi-line usage examples for --help
|
| 1314 |
+
usage_examples = f"""
|
| 1315 |
+
Examples:
|
| 1316 |
+
|
| 1317 |
+
Basic image upscaling:
|
| 1318 |
+
python {invocation} image.jpg
|
| 1319 |
+
|
| 1320 |
+
Basic video upscaling with temporal consistency:
|
| 1321 |
+
python {invocation} video.mp4 --resolution 720 --batch_size 33
|
| 1322 |
+
|
| 1323 |
+
Streaming mode for long videos with 10-bit video output (requires FFMPEG):
|
| 1324 |
+
python {invocation} long_video.mp4 --resolution 1080 --batch_size 33 --chunk_size 330 --temporal_overlap 3 --video_backend ffmpeg --10bit
|
| 1325 |
+
|
| 1326 |
+
Multi-GPU processing with temporal overlap:
|
| 1327 |
+
python {invocation} video.mp4 --cuda_device 0,1 --resolution 1080 --batch_size 81 --uniform_batch_size --temporal_overlap 3 --prepend_frames 4
|
| 1328 |
+
|
| 1329 |
+
Memory-optimized for low VRAM (8GB):
|
| 1330 |
+
python {invocation} image.png --dit_model seedvr2_ema_3b-Q8_0.gguf --blocks_to_swap 32 --swap_io_components --dit_offload_device cpu --vae_offload_device cpu
|
| 1331 |
+
|
| 1332 |
+
High resolution with VAE tiling:
|
| 1333 |
+
python {invocation} video.mp4 --resolution 1440 --batch_size 31 --uniform_batch_size --temporal_overlap 3 --vae_encode_tiled --vae_decode_tiled
|
| 1334 |
+
|
| 1335 |
+
Batch directory processing:
|
| 1336 |
+
python {invocation} media_folder/ --output processed/ --cuda_device 0 --cache_dit --cache_vae --dit_offload_device cpu --vae_offload_device cpu --resolution 1080 --max_resolution 1920
|
| 1337 |
+
"""
|
| 1338 |
+
|
| 1339 |
+
parser = argparse.ArgumentParser(
|
| 1340 |
+
description="SeedVR2 Video Upscaler - CLI for high-quality image/video upscaling and batch processing",
|
| 1341 |
+
epilog=usage_examples,
|
| 1342 |
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 1343 |
+
allow_abbrev=False
|
| 1344 |
+
)
|
| 1345 |
+
|
| 1346 |
+
# Input/Output
|
| 1347 |
+
io_group = parser.add_argument_group('Input/Output options')
|
| 1348 |
+
io_group.add_argument("input", type=str,
|
| 1349 |
+
help="Input: video file (.mp4, .avi, etc.), image file (.png, .jpg, etc.), or directory")
|
| 1350 |
+
io_group.add_argument("--output", type=str, default=None,
|
| 1351 |
+
help="Output path (default: auto-generated in 'output/' directory)")
|
| 1352 |
+
io_group.add_argument("--output_format", type=str, default=None, choices=["mp4", "png", None],
|
| 1353 |
+
help="Output format: 'mp4' (video) or 'png' (image sequence). Default: auto-detect from input type")
|
| 1354 |
+
io_group.add_argument("--video_backend", type=str, default="opencv", choices=["opencv", "ffmpeg"],
|
| 1355 |
+
help="Video encoder backend: 'opencv' (default) or 'ffmpeg' (requires ffmpeg in PATH)")
|
| 1356 |
+
io_group.add_argument("--10bit", dest="use_10bit", action="store_true",
|
| 1357 |
+
help="Save 10-bit video with x265 codec (reduces banding). Without this flag, "
|
| 1358 |
+
"ffmpeg uses x264 for maximum compatibility. Requires --video_backend ffmpeg")
|
| 1359 |
+
io_group.add_argument("--model_dir", type=str, default=None,
|
| 1360 |
+
help=f"Model directory (default: ./models/{SEEDVR2_FOLDER_NAME})")
|
| 1361 |
+
|
| 1362 |
+
# Model Selection
|
| 1363 |
+
model_group = parser.add_argument_group('Model selection')
|
| 1364 |
+
model_group.add_argument("--dit_model", type=str, default=DEFAULT_DIT,
|
| 1365 |
+
choices=get_available_dit_models(),
|
| 1366 |
+
help="DiT model to use. Options: 3B (fp16/fp8/GGUF) or 7B (fp16/fp8/GGUF). Default: 3B FP8")
|
| 1367 |
+
|
| 1368 |
+
# Processing Parameters
|
| 1369 |
+
process_group = parser.add_argument_group('Processing parameters')
|
| 1370 |
+
process_group.add_argument("--resolution", type=int, default=1080,
|
| 1371 |
+
help="Target short-side resolution in pixels (default: 1080)")
|
| 1372 |
+
process_group.add_argument("--max_resolution", type=int, default=0,
|
| 1373 |
+
help="Maximum resolution for any edge. Scales down if exceeded. 0 = no limit (default: 0)")
|
| 1374 |
+
process_group.add_argument("--batch_size", type=int, default=5,
|
| 1375 |
+
help="Frames per batch (must follow 4n+1: 1, 5, 9, 13, 17, 21,...). "
|
| 1376 |
+
"Ideally matches shot length for best temporal consistency. Higher values improve "
|
| 1377 |
+
"quality and speed but require more VRAM. Default: 5")
|
| 1378 |
+
process_group.add_argument("--uniform_batch_size", action="store_true",
|
| 1379 |
+
help="Pad final batch to match batch_size. Prevents temporal artifacts caused by small "
|
| 1380 |
+
"final batches. Add extra compute but recommended for optimal quality.")
|
| 1381 |
+
process_group.add_argument("--seed", type=int, default=42,
|
| 1382 |
+
help="Random seed for reproducibility (default: 42)")
|
| 1383 |
+
process_group.add_argument("--skip_first_frames", type=int, default=0,
|
| 1384 |
+
help="Skip N initial frames (default: 0)")
|
| 1385 |
+
process_group.add_argument("--load_cap", type=int, default=0,
|
| 1386 |
+
help="Load maximum N frames from video. 0 = load all (default: 0)")
|
| 1387 |
+
process_group.add_argument("--chunk_size", type=int, default=0,
|
| 1388 |
+
help="Frames per chunk for streaming mode. When > 0, processes video in "
|
| 1389 |
+
"memory-bounded chunks of N frames. 0 = load all frames at once (default: 0)")
|
| 1390 |
+
process_group.add_argument("--prepend_frames", type=int, default=0,
|
| 1391 |
+
help="Prepend N reversed frames to reduce start artifacts (auto-removed). Default: 0")
|
| 1392 |
+
process_group.add_argument("--temporal_overlap", type=int, default=0,
|
| 1393 |
+
help="Frames to overlap between batches/GPUs for smooth blending (default: 0)")
|
| 1394 |
+
|
| 1395 |
+
# Quality Control
|
| 1396 |
+
quality_group = parser.add_argument_group('Quality control')
|
| 1397 |
+
quality_group.add_argument("--color_correction", type=str, default="lab",
|
| 1398 |
+
choices=["lab", "wavelet", "wavelet_adaptive", "hsv", "adain", "none"],
|
| 1399 |
+
help="Color correction method: 'lab' (perceptual color matching, recommended), 'wavelet' (frequency-based), "
|
| 1400 |
+
"'wavelet_adaptive' (wavelet + saturation correction), 'hsv' (hue-conditional), 'adain' (statistical transfer), "
|
| 1401 |
+
"'none' (disabled) (default: lab)")
|
| 1402 |
+
quality_group.add_argument("--input_noise_scale", type=float, default=0.0,
|
| 1403 |
+
help="Input noise injection scale (0.0-1.0). Adds variation to input images (default: 0.0)")
|
| 1404 |
+
quality_group.add_argument("--latent_noise_scale", type=float, default=0.0,
|
| 1405 |
+
help="Latent noise injection scale (0.0-1.0). Adds variation to latent space (default: 0.0)")
|
| 1406 |
+
|
| 1407 |
+
# Device Management
|
| 1408 |
+
device_group = parser.add_argument_group('Device management')
|
| 1409 |
+
if platform.system() != "Darwin":
|
| 1410 |
+
device_group.add_argument("--cuda_device", type=str, default=None,
|
| 1411 |
+
help="CUDA device(s): single '0' or multi-GPU '0,1,2'. Default: device 0")
|
| 1412 |
+
device_group.add_argument("--dit_offload_device", type=str, default="none",
|
| 1413 |
+
help="DiT offload device when idle: 'none' (keep on GPU), 'cpu' (offload to RAM), or GPU ID. "
|
| 1414 |
+
"Frees VRAM between phases. Required for BlockSwap. Default: none")
|
| 1415 |
+
device_group.add_argument("--vae_offload_device", type=str, default="none",
|
| 1416 |
+
help="VAE offload device when idle: 'none', 'cpu', or GPU ID. Frees VRAM between phases. Default: none")
|
| 1417 |
+
device_group.add_argument("--tensor_offload_device", type=str, default="cpu",
|
| 1418 |
+
help="Intermediate tensor storage: 'cpu' (recommended), 'none' (keep on GPU), or GPU ID. Default: cpu")
|
| 1419 |
+
|
| 1420 |
+
# Memory Optimization (BlockSwap)
|
| 1421 |
+
blockswap_group = parser.add_argument_group('Memory optimization (BlockSwap)')
|
| 1422 |
+
blockswap_group.add_argument("--blocks_to_swap", type=int, default=0,
|
| 1423 |
+
help="Transformer blocks to swap for VRAM savings. 0-32 (3B) or 0-36 (7B). "
|
| 1424 |
+
"Requires --dit_offload_device. Not available on macOS. Default: 0 (disabled)")
|
| 1425 |
+
blockswap_group.add_argument("--swap_io_components", action="store_true",
|
| 1426 |
+
help="Offload DiT I/O layers for extra VRAM savings. Requires --dit_offload_device. "
|
| 1427 |
+
"Not available on macOS")
|
| 1428 |
+
|
| 1429 |
+
# VAE Tiling
|
| 1430 |
+
vae_group = parser.add_argument_group('VAE tiling (for high resolution upscale)')
|
| 1431 |
+
vae_group.add_argument("--vae_encode_tiled", action="store_true",
|
| 1432 |
+
help="Enable VAE encode tiling to reduce VRAM during encoding")
|
| 1433 |
+
vae_group.add_argument("--vae_encode_tile_size", type=int, default=1024,
|
| 1434 |
+
help="VAE encode tile size in pixels (default: 1024). Applied to both height and width. Only used if --vae_encode_tiled is set")
|
| 1435 |
+
vae_group.add_argument("--vae_encode_tile_overlap", type=int, default=128,
|
| 1436 |
+
help="VAE encode tile overlap in pixels (default: 128). Reduces visible seams between tiles. Only used if --vae_encode_tiled is set")
|
| 1437 |
+
vae_group.add_argument("--vae_decode_tiled", action="store_true",
|
| 1438 |
+
help="Enable VAE decode tiling to reduce VRAM during decoding")
|
| 1439 |
+
vae_group.add_argument("--vae_decode_tile_size", type=int, default=1024,
|
| 1440 |
+
help="VAE decode tile size in pixels (default: 1024). Applied to both height and width. Only used if --vae_decode_tiled is set")
|
| 1441 |
+
vae_group.add_argument("--vae_decode_tile_overlap", type=int, default=128,
|
| 1442 |
+
help="VAE decode tile overlap in pixels (default: 128). Reduces visible seams between tiles. Only used if --vae_decode_tiled is set")
|
| 1443 |
+
vae_group.add_argument("--tile_debug", type=str, default="false", choices=["false", "encode", "decode"],
|
| 1444 |
+
help="Visualize tiles: 'false' (default), 'encode', or 'decode'")
|
| 1445 |
+
|
| 1446 |
+
# Performance
|
| 1447 |
+
perf_group = parser.add_argument_group('Performance optimization')
|
| 1448 |
+
perf_group.add_argument("--attention_mode", type=str, default="sdpa",
|
| 1449 |
+
choices=["sdpa", "flash_attn_2", "flash_attn_3", "sageattn_2", "sageattn_3"],
|
| 1450 |
+
help="Attention backend: 'sdpa' (default), 'flash_attn_2', 'flash_attn_3', 'sageattn_2', or 'sageattn_3' (Blackwell GPUs)")
|
| 1451 |
+
perf_group.add_argument("--compile_dit", action="store_true",
|
| 1452 |
+
help="Enable torch.compile for DiT model (20-40%% speedup, requires PyTorch 2.0+ and Triton)")
|
| 1453 |
+
perf_group.add_argument("--compile_vae", action="store_true",
|
| 1454 |
+
help="Enable torch.compile for VAE model (15-25%% speedup, requires PyTorch 2.0+ and Triton)")
|
| 1455 |
+
perf_group.add_argument("--compile_backend", type=str, default="inductor", choices=["inductor", "cudagraphs"],
|
| 1456 |
+
help="Compilation backend: 'inductor' (full optimization with Triton) or 'cudagraphs' (lightweight, no kernel optimization) (default: inductor)")
|
| 1457 |
+
perf_group.add_argument("--compile_mode", type=str, default="default", choices=["default", "reduce-overhead", "max-autotune", "max-autotune-no-cudagraphs"],
|
| 1458 |
+
help="Optimization level: 'default' (fast compilation), 'reduce-overhead' (lower overhead), 'max-autotune' (best runtime, slow compilation), "
|
| 1459 |
+
"'max-autotune-no-cudagraphs' (like max-autotune without cudagraphs) (default: default)")
|
| 1460 |
+
perf_group.add_argument("--compile_fullgraph", action="store_true",
|
| 1461 |
+
help="Compile entire model as single graph (faster but less flexible). May fail with dynamic shapes (default: False)")
|
| 1462 |
+
perf_group.add_argument("--compile_dynamic", action="store_true",
|
| 1463 |
+
help="Handle varying input shapes without recompilation. Useful for different resolutions/batch sizes (default: False)")
|
| 1464 |
+
perf_group.add_argument("--compile_dynamo_cache_size_limit", type=int, default=64,
|
| 1465 |
+
help="Max cached compiled versions per function. Increase when using many different input shapes. Higher uses more memory (default: 64)")
|
| 1466 |
+
perf_group.add_argument("--compile_dynamo_recompile_limit", type=int, default=128,
|
| 1467 |
+
help="Max recompilation attempts before fallback to eager mode. Safety limit to prevent compilation loops (default: 128)")
|
| 1468 |
+
|
| 1469 |
+
# Model Caching (for batch processing)
|
| 1470 |
+
cache_group = parser.add_argument_group('Model caching (batch processing)')
|
| 1471 |
+
cache_group.add_argument("--cache_dit", action="store_true",
|
| 1472 |
+
help="Keep DiT model in memory between generations. Works with single-GPU directory processing "
|
| 1473 |
+
"or multi-GPU streaming (--chunk_size). Requires --dit_offload_device")
|
| 1474 |
+
cache_group.add_argument("--cache_vae", action="store_true",
|
| 1475 |
+
help="Keep VAE model in memory between generations. Works with single-GPU directory processing "
|
| 1476 |
+
"or multi-GPU streaming (--chunk_size). Requires --vae_offload_device")
|
| 1477 |
+
|
| 1478 |
+
# Debugging
|
| 1479 |
+
debug_group = parser.add_argument_group('Debugging')
|
| 1480 |
+
debug_group.add_argument("--debug", action="store_true",
|
| 1481 |
+
help="Enable verbose debug logging")
|
| 1482 |
+
|
| 1483 |
+
# Auto-show help if no arguments provided
|
| 1484 |
+
if len(sys.argv) == 1:
|
| 1485 |
+
sys.argv.append('--help')
|
| 1486 |
+
|
| 1487 |
+
return parser.parse_args()
|
| 1488 |
+
|
| 1489 |
+
|
| 1490 |
+
# =============================================================================
|
| 1491 |
+
# Main Entry Point
|
| 1492 |
+
# =============================================================================
|
| 1493 |
+
|
| 1494 |
+
def main() -> None:
|
| 1495 |
+
"""
|
| 1496 |
+
Main entry point for SeedVR2 Video Upscaler CLI.
|
| 1497 |
+
|
| 1498 |
+
Orchestrates the complete upscaling workflow:
|
| 1499 |
+
1. Parse and validate command-line arguments
|
| 1500 |
+
2. Extract frames from input video/image(s)
|
| 1501 |
+
3. Download required models if not cached
|
| 1502 |
+
4. Process frames on single or multiple GPUs
|
| 1503 |
+
5. Save results as video or PNG sequence
|
| 1504 |
+
6. Report timing and FPS (calculated from total wall-clock time)
|
| 1505 |
+
|
| 1506 |
+
Error handling:
|
| 1507 |
+
- Validates tile configuration before processing
|
| 1508 |
+
- Provides detailed error messages with traceback
|
| 1509 |
+
- Ensures proper cleanup on exit (VRAM automatically freed)
|
| 1510 |
+
|
| 1511 |
+
Raises:
|
| 1512 |
+
SystemExit: On argument validation failure or processing error
|
| 1513 |
+
"""
|
| 1514 |
+
# Parse arguments
|
| 1515 |
+
args = parse_arguments()
|
| 1516 |
+
|
| 1517 |
+
# Update debug instance with --debug flag
|
| 1518 |
+
debug.enabled = args.debug
|
| 1519 |
+
|
| 1520 |
+
# print header
|
| 1521 |
+
debug.print_header(cli=True)
|
| 1522 |
+
|
| 1523 |
+
debug.log("Arguments:", category="setup")
|
| 1524 |
+
for key, value in vars(args).items():
|
| 1525 |
+
debug.log(f"{key}: {value}", category="none", indent_level=1)
|
| 1526 |
+
|
| 1527 |
+
if args.vae_encode_tiled and args.vae_encode_tile_overlap >= args.vae_encode_tile_size:
|
| 1528 |
+
debug.log(f"VAE encode tile overlap ({args.vae_encode_tile_overlap}) must be smaller than tile size ({args.vae_encode_tile_size})", level="ERROR", category="vae", force=True)
|
| 1529 |
+
sys.exit(1)
|
| 1530 |
+
|
| 1531 |
+
if args.vae_decode_tiled and args.vae_decode_tile_overlap >= args.vae_decode_tile_size:
|
| 1532 |
+
debug.log(f"VAE decode tile overlap ({args.vae_decode_tile_overlap}) must be smaller than tile size ({args.vae_decode_tile_size})", level="ERROR", category="vae", force=True)
|
| 1533 |
+
sys.exit(1)
|
| 1534 |
+
|
| 1535 |
+
# Validate ffmpeg availability if selected
|
| 1536 |
+
if args.video_backend == "ffmpeg" and shutil.which("ffmpeg") is None:
|
| 1537 |
+
debug.log("--video_backend ffmpeg requires ffmpeg in PATH. Install ffmpeg or use --video_backend opencv",
|
| 1538 |
+
level="ERROR", category="setup", force=True)
|
| 1539 |
+
sys.exit(1)
|
| 1540 |
+
|
| 1541 |
+
# Inform about caching defaults
|
| 1542 |
+
if args.cache_dit and args.dit_offload_device == "none":
|
| 1543 |
+
offload_target = "system memory (CPU)" if get_gpu_backend() != "mps" else "unified memory"
|
| 1544 |
+
debug.log(
|
| 1545 |
+
f"DiT caching enabled: Using default {offload_target} for offload. "
|
| 1546 |
+
"Set --dit_offload_device explicitly to use a different device.",
|
| 1547 |
+
category="cache", force=True
|
| 1548 |
+
)
|
| 1549 |
+
|
| 1550 |
+
if args.cache_vae and args.vae_offload_device == "none":
|
| 1551 |
+
offload_target = "system memory (CPU)" if get_gpu_backend() != "mps" else "unified memory"
|
| 1552 |
+
debug.log(
|
| 1553 |
+
f"VAE caching enabled: Using default {offload_target} for offload. "
|
| 1554 |
+
"Set --vae_offload_device explicitly to use a different device.",
|
| 1555 |
+
category="cache", force=True
|
| 1556 |
+
)
|
| 1557 |
+
|
| 1558 |
+
if args.debug:
|
| 1559 |
+
if platform.system() == "Darwin":
|
| 1560 |
+
debug.log("You are running on macOS and will use the MPS backend!", category="info", force=True)
|
| 1561 |
+
else:
|
| 1562 |
+
# Show actual CUDA device visibility
|
| 1563 |
+
debug.log(f"CUDA_VISIBLE_DEVICES: {os.environ.get('CUDA_VISIBLE_DEVICES', 'Not set (all)')}", category="device")
|
| 1564 |
+
if is_cuda_available():
|
| 1565 |
+
debug.log(f"torch.cuda.device_count(): {torch.cuda.device_count()}", category="device")
|
| 1566 |
+
debug.log(f"Using device index 0 inside script (mapped to selected GPU)", category="device")
|
| 1567 |
+
|
| 1568 |
+
try:
|
| 1569 |
+
start_time = time.time()
|
| 1570 |
+
|
| 1571 |
+
# Parse GPU list
|
| 1572 |
+
if platform.system() == "Darwin":
|
| 1573 |
+
device_list = ["0"]
|
| 1574 |
+
else:
|
| 1575 |
+
if args.cuda_device:
|
| 1576 |
+
device_list = [d.strip() for d in str(args.cuda_device).split(',') if d.strip()]
|
| 1577 |
+
else:
|
| 1578 |
+
device_list = ["0"]
|
| 1579 |
+
if args.debug:
|
| 1580 |
+
debug.log(f"Using devices: {device_list}", category="device")
|
| 1581 |
+
|
| 1582 |
+
# Download models once before processing
|
| 1583 |
+
if not download_weight(dit_model=args.dit_model, vae_model=DEFAULT_VAE, model_dir=args.model_dir, debug=debug):
|
| 1584 |
+
debug.log("Failed to download required models. Check console output above.", level="ERROR", category="download", force=True)
|
| 1585 |
+
sys.exit(1)
|
| 1586 |
+
|
| 1587 |
+
# Determine input type and process accordingly
|
| 1588 |
+
input_type = get_input_type(args.input)
|
| 1589 |
+
|
| 1590 |
+
# Track total frames for FPS calculation (time tracked via start_time)
|
| 1591 |
+
total_frames_processed = 0
|
| 1592 |
+
|
| 1593 |
+
# Track if output format was user-specified or auto-detected
|
| 1594 |
+
format_auto_detected = args.output_format is None
|
| 1595 |
+
|
| 1596 |
+
if input_type == 'directory':
|
| 1597 |
+
media_files = get_media_files(args.input)
|
| 1598 |
+
if not media_files:
|
| 1599 |
+
debug.log(f"No video or image files found in directory: {args.input}",
|
| 1600 |
+
level="ERROR", category="file", force=True)
|
| 1601 |
+
sys.exit(1)
|
| 1602 |
+
|
| 1603 |
+
debug.log(f"Found {len(media_files)} media files to process", category="file", force=True)
|
| 1604 |
+
|
| 1605 |
+
# Multi-GPU caching requires streaming (workers cache within their chunk loops)
|
| 1606 |
+
if (args.cache_dit or args.cache_vae) and len(device_list) > 1 and args.chunk_size <= 0:
|
| 1607 |
+
debug.log(
|
| 1608 |
+
"Model caching requires streaming mode (--chunk_size > 0) for multi-GPU. "
|
| 1609 |
+
"Disabling caching for this run.",
|
| 1610 |
+
level="WARNING", category="cache", force=True
|
| 1611 |
+
)
|
| 1612 |
+
args.cache_dit = False
|
| 1613 |
+
args.cache_vae = False
|
| 1614 |
+
|
| 1615 |
+
# Single-GPU: runner_cache persists across files; multi-GPU: workers cache internally
|
| 1616 |
+
runner_cache = {} if (args.cache_dit or args.cache_vae) and len(device_list) == 1 else None
|
| 1617 |
+
|
| 1618 |
+
for idx, file_path in enumerate(media_files, 1):
|
| 1619 |
+
# Visual separation between files (except before first file)
|
| 1620 |
+
if idx > 1:
|
| 1621 |
+
debug.log("", category="none", force=True)
|
| 1622 |
+
debug.log("━" * 60, category="none", force=True)
|
| 1623 |
+
debug.log("", category="none", force=True)
|
| 1624 |
+
|
| 1625 |
+
debug.log(f"Processing file {idx}/{len(media_files)}", category="generation", force=True)
|
| 1626 |
+
|
| 1627 |
+
# Auto-detect format per file if not user-specified
|
| 1628 |
+
if format_auto_detected:
|
| 1629 |
+
file_type = get_input_type(file_path)
|
| 1630 |
+
file_output_format = "mp4" if file_type == "video" else "png"
|
| 1631 |
+
else:
|
| 1632 |
+
file_output_format = args.output_format
|
| 1633 |
+
|
| 1634 |
+
# Temporarily override args.output_format for this file
|
| 1635 |
+
original_format = args.output_format
|
| 1636 |
+
args.output_format = file_output_format
|
| 1637 |
+
|
| 1638 |
+
# generate_output_path handles None gracefully with "outputs" default
|
| 1639 |
+
output_path = generate_output_path(file_path, file_output_format, args.output,
|
| 1640 |
+
input_type=get_input_type(file_path), from_directory=True)
|
| 1641 |
+
|
| 1642 |
+
# Process with explicit output path and runner cache
|
| 1643 |
+
frames = process_single_file(file_path, args, device_list, output_path,
|
| 1644 |
+
format_auto_detected=format_auto_detected,
|
| 1645 |
+
runner_cache=runner_cache)
|
| 1646 |
+
total_frames_processed += frames
|
| 1647 |
+
|
| 1648 |
+
# Restore original format
|
| 1649 |
+
args.output_format = original_format
|
| 1650 |
+
|
| 1651 |
+
elif input_type in ("video", "image"):
|
| 1652 |
+
# Auto-detect output format for single file if not specified
|
| 1653 |
+
if format_auto_detected:
|
| 1654 |
+
args.output_format = "mp4" if input_type == "video" else "png"
|
| 1655 |
+
|
| 1656 |
+
# Caching: single-GPU streaming uses runner_cache, multi-GPU streaming workers cache internally
|
| 1657 |
+
runner_cache = None
|
| 1658 |
+
streaming = args.chunk_size > 0
|
| 1659 |
+
|
| 1660 |
+
if args.cache_dit or args.cache_vae:
|
| 1661 |
+
if len(device_list) > 1:
|
| 1662 |
+
if not streaming:
|
| 1663 |
+
debug.log(
|
| 1664 |
+
"Model caching requires streaming mode (--chunk_size > 0) for multi-GPU. "
|
| 1665 |
+
"Disabling caching for this run.",
|
| 1666 |
+
level="WARNING", category="cache", force=True
|
| 1667 |
+
)
|
| 1668 |
+
args.cache_dit = False
|
| 1669 |
+
args.cache_vae = False
|
| 1670 |
+
elif streaming:
|
| 1671 |
+
runner_cache = {}
|
| 1672 |
+
else:
|
| 1673 |
+
debug.log(
|
| 1674 |
+
"Model caching has no benefit for single file processing (only useful for directories or streaming mode). "
|
| 1675 |
+
"Consider removing --cache_dit/--cache_vae for single files.",
|
| 1676 |
+
category="tip", force=True
|
| 1677 |
+
)
|
| 1678 |
+
|
| 1679 |
+
frames = process_single_file(args.input, args, device_list, args.output,
|
| 1680 |
+
format_auto_detected=format_auto_detected,
|
| 1681 |
+
runner_cache=runner_cache)
|
| 1682 |
+
total_frames_processed += frames
|
| 1683 |
+
|
| 1684 |
+
else:
|
| 1685 |
+
debug.log(f"Unsupported input type: {args.input}", level="ERROR", category="file", force=True)
|
| 1686 |
+
sys.exit(1)
|
| 1687 |
+
|
| 1688 |
+
# Calculate total execution time
|
| 1689 |
+
total_time = time.time() - start_time
|
| 1690 |
+
|
| 1691 |
+
debug.log("", category="none", force=True)
|
| 1692 |
+
debug.log(f"All upscaling processes completed successfully in {total_time:.2f}s", category="success", force=True)
|
| 1693 |
+
|
| 1694 |
+
# Calculate and display FPS based on overall wall-clock time
|
| 1695 |
+
if total_time > 0 and total_frames_processed > 0:
|
| 1696 |
+
fps = total_frames_processed / total_time
|
| 1697 |
+
debug.log(f"Average FPS: {fps:.2f} frames/sec", category="timing", force=True)
|
| 1698 |
+
|
| 1699 |
+
except Exception as e:
|
| 1700 |
+
debug.log(f"Error during processing: {e}", level="ERROR", category="generation", force=True)
|
| 1701 |
+
import traceback
|
| 1702 |
+
traceback.print_exc()
|
| 1703 |
+
sys.exit(1)
|
| 1704 |
+
|
| 1705 |
+
finally:
|
| 1706 |
+
debug.log(f"Process {os.getpid()} terminating - VRAM will be automatically freed", category="cleanup", force=True)
|
| 1707 |
+
|
| 1708 |
+
# print footer
|
| 1709 |
+
debug.print_footer()
|
| 1710 |
+
|
| 1711 |
+
if __name__ == "__main__":
|
| 1712 |
+
main()
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/neg_emb.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6a43e5800ef2354f1c156d27535834da055cbec8248298b8923492bba2076581
|
| 3 |
+
size 656540
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/pos_emb.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fa07a14844314772266b66c3b95deb0027696d8fe7065721263db5176f45d799
|
| 3 |
+
size 595100
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/pyproject.toml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "seedvr2_videoupscaler"
|
| 3 |
+
description = "SeedVR2 official ComfyUI integration: ByteDance-Seed's one-step diffusion-based video/image upscaling with memory-efficient inference"
|
| 4 |
+
version = "2.5.24"
|
| 5 |
+
authors = [
|
| 6 |
+
{name = "numz"},
|
| 7 |
+
{name = "adrientoupet"}
|
| 8 |
+
]
|
| 9 |
+
license = {file = "LICENSE"}
|
| 10 |
+
classifiers = [
|
| 11 |
+
"Operating System :: OS Independent"
|
| 12 |
+
]
|
| 13 |
+
dependencies = [
|
| 14 |
+
"torch",
|
| 15 |
+
"torchvision",
|
| 16 |
+
"safetensors",
|
| 17 |
+
"numpy",
|
| 18 |
+
"tqdm",
|
| 19 |
+
"psutil",
|
| 20 |
+
"einops",
|
| 21 |
+
"omegaconf>=2.3.0",
|
| 22 |
+
"diffusers>=0.33.1",
|
| 23 |
+
"peft>=0.17.0",
|
| 24 |
+
"rotary_embedding_torch>=0.5.3",
|
| 25 |
+
"opencv-python",
|
| 26 |
+
"gguf",
|
| 27 |
+
"matplotlib"
|
| 28 |
+
]
|
| 29 |
+
|
| 30 |
+
[project.urls]
|
| 31 |
+
Repository = "https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler"
|
| 32 |
+
Documentation = "https://www.youtube.com/@AInVFX"
|
| 33 |
+
"Bug Tracker" = "https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/issues"
|
| 34 |
+
Forum = "https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler/discussions"
|
| 35 |
+
|
| 36 |
+
[tool.comfy]
|
| 37 |
+
PublisherId = "ainvfx"
|
| 38 |
+
DisplayName = "ComfyUI-SeedVR2_VideoUpscaler"
|
| 39 |
+
Icon = "https://raw.githubusercontent.com/numz/ComfyUI-SeedVR2_VideoUpscaler/refs/heads/main/docs/seedvr_logo.png"
|
| 40 |
+
includes = []
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
safetensors
|
| 4 |
+
numpy
|
| 5 |
+
tqdm
|
| 6 |
+
psutil
|
| 7 |
+
einops
|
| 8 |
+
omegaconf>=2.3.0
|
| 9 |
+
diffusers>=0.33.1
|
| 10 |
+
peft>=0.17.0
|
| 11 |
+
rotary_embedding_torch>=0.5.3
|
| 12 |
+
opencv-python
|
| 13 |
+
gguf
|
| 14 |
+
matplotlib
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/__init__.py
ADDED
|
File without changes
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/__init__.py
ADDED
|
File without changes
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/cache.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
from typing import Callable
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class Cache:
|
| 19 |
+
"""Caching reusable args for faster inference"""
|
| 20 |
+
|
| 21 |
+
def __init__(self, disable=False, prefix="", cache=None):
|
| 22 |
+
self.cache = cache if cache is not None else {}
|
| 23 |
+
self.disable = disable
|
| 24 |
+
self.prefix = prefix
|
| 25 |
+
|
| 26 |
+
def __call__(self, key: str, fn: Callable):
|
| 27 |
+
if self.disable:
|
| 28 |
+
return fn()
|
| 29 |
+
|
| 30 |
+
key = self.prefix + key
|
| 31 |
+
try:
|
| 32 |
+
result = self.cache[key]
|
| 33 |
+
except KeyError:
|
| 34 |
+
result = fn()
|
| 35 |
+
self.cache[key] = result
|
| 36 |
+
return result
|
| 37 |
+
|
| 38 |
+
def namespace(self, namespace: str):
|
| 39 |
+
return Cache(
|
| 40 |
+
disable=self.disable,
|
| 41 |
+
prefix=self.prefix + namespace + ".",
|
| 42 |
+
cache=self.cache,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
def get(self, key: str):
|
| 46 |
+
key = self.prefix + key
|
| 47 |
+
return self.cache[key]
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/config.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Configuration utility functions
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import importlib
|
| 20 |
+
from typing import Any, Callable, List, Union
|
| 21 |
+
from omegaconf import DictConfig, ListConfig, OmegaConf
|
| 22 |
+
from ..utils.model_registry import MODEL_CLASSES
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
OmegaConf.register_new_resolver("eval", eval)
|
| 26 |
+
except Exception as e:
|
| 27 |
+
if "already registered" not in str(e):
|
| 28 |
+
raise
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def load_config(path: str, argv: List[str] = None) -> Union[DictConfig, ListConfig]:
|
| 33 |
+
"""
|
| 34 |
+
Load a configuration. Will resolve inheritance.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
#print(path)
|
| 38 |
+
config = OmegaConf.load(path)
|
| 39 |
+
if argv is not None:
|
| 40 |
+
config_argv = OmegaConf.from_dotlist(argv)
|
| 41 |
+
config = OmegaConf.merge(config, config_argv)
|
| 42 |
+
config = resolve_recursive(config, resolve_inheritance)
|
| 43 |
+
return config
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def resolve_recursive(
|
| 47 |
+
config: Any,
|
| 48 |
+
resolver: Callable[[Union[DictConfig, ListConfig]], Union[DictConfig, ListConfig]],
|
| 49 |
+
) -> Any:
|
| 50 |
+
config = resolver(config)
|
| 51 |
+
if isinstance(config, DictConfig):
|
| 52 |
+
for k in config.keys():
|
| 53 |
+
v = config.get(k)
|
| 54 |
+
if isinstance(v, (DictConfig, ListConfig)):
|
| 55 |
+
config[k] = resolve_recursive(v, resolver)
|
| 56 |
+
if isinstance(config, ListConfig):
|
| 57 |
+
for i in range(len(config)):
|
| 58 |
+
v = config.get(i)
|
| 59 |
+
if isinstance(v, (DictConfig, ListConfig)):
|
| 60 |
+
config[i] = resolve_recursive(v, resolver)
|
| 61 |
+
return config
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def resolve_inheritance(config: Union[DictConfig, ListConfig]) -> Any:
|
| 65 |
+
"""
|
| 66 |
+
Recursively resolve inheritance if the config contains:
|
| 67 |
+
__inherit__: path/to/parent.yaml or a ListConfig of such paths.
|
| 68 |
+
"""
|
| 69 |
+
if isinstance(config, DictConfig):
|
| 70 |
+
inherit = config.pop("__inherit__", None)
|
| 71 |
+
|
| 72 |
+
if inherit:
|
| 73 |
+
inherit_list = inherit if isinstance(inherit, ListConfig) else [inherit]
|
| 74 |
+
|
| 75 |
+
parent_config = None
|
| 76 |
+
for parent_path in inherit_list:
|
| 77 |
+
assert isinstance(parent_path, str)
|
| 78 |
+
parent_config = (
|
| 79 |
+
load_config(parent_path)
|
| 80 |
+
if parent_config is None
|
| 81 |
+
else OmegaConf.merge(parent_config, load_config(parent_path))
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
if len(config.keys()) > 0:
|
| 85 |
+
config = OmegaConf.merge(parent_config, config)
|
| 86 |
+
else:
|
| 87 |
+
config = parent_config
|
| 88 |
+
return config
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def import_item(path: str, name: str) -> Any:
|
| 92 |
+
"""
|
| 93 |
+
Import a python item, checking model registry first.
|
| 94 |
+
|
| 95 |
+
Args:
|
| 96 |
+
path: Module path
|
| 97 |
+
name: Class/function name to import
|
| 98 |
+
|
| 99 |
+
Returns:
|
| 100 |
+
Imported object
|
| 101 |
+
"""
|
| 102 |
+
# Simple lookup with path as key
|
| 103 |
+
if path in MODEL_CLASSES:
|
| 104 |
+
return MODEL_CLASSES[path]
|
| 105 |
+
|
| 106 |
+
# Fallback to dynamic import for everything else
|
| 107 |
+
try:
|
| 108 |
+
return getattr(importlib.import_module(path), name)
|
| 109 |
+
except (ImportError, AttributeError) as e:
|
| 110 |
+
raise ImportError(f"Could not import '{name}' from '{path}': {e}")
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def create_object(config: DictConfig) -> Any:
|
| 114 |
+
"""
|
| 115 |
+
Create an object from config.
|
| 116 |
+
The config is expected to contains the following:
|
| 117 |
+
__object__:
|
| 118 |
+
path: path.to.module
|
| 119 |
+
name: MyClass
|
| 120 |
+
args: as_config | as_params (default to as_config)
|
| 121 |
+
"""
|
| 122 |
+
|
| 123 |
+
item = import_item(
|
| 124 |
+
path=config.__object__.path,
|
| 125 |
+
name=config.__object__.name,
|
| 126 |
+
)
|
| 127 |
+
args = config.__object__.get("args", "as_config")
|
| 128 |
+
if args == "as_config":
|
| 129 |
+
return item(config)
|
| 130 |
+
if args == "as_params":
|
| 131 |
+
config = OmegaConf.to_object(config)
|
| 132 |
+
config.pop("__object__")
|
| 133 |
+
return item(**config)
|
| 134 |
+
raise NotImplementedError(f"Unknown args type: {args}")
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/decorators.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Decorators.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import functools
|
| 20 |
+
import threading
|
| 21 |
+
import time
|
| 22 |
+
from typing import Callable
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
from .distributed import barrier_if_distributed, get_global_rank, get_local_rank
|
| 26 |
+
from .logger import get_logger
|
| 27 |
+
|
| 28 |
+
logger = get_logger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def log_on_entry(func: Callable) -> Callable:
|
| 32 |
+
"""
|
| 33 |
+
Functions with this decorator will log the function name at entry.
|
| 34 |
+
When using multiple decorators, this must be applied innermost to properly capture the name.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def log_on_entry_wrapper(*args, **kwargs):
|
| 38 |
+
logger.info(f"Entering {func.__name__}")
|
| 39 |
+
return func(*args, **kwargs)
|
| 40 |
+
|
| 41 |
+
return log_on_entry_wrapper
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def barrier_on_entry(func: Callable) -> Callable:
|
| 45 |
+
"""
|
| 46 |
+
Functions with this decorator will start executing when all ranks are ready to enter.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def barrier_on_entry_wrapper(*args, **kwargs):
|
| 50 |
+
barrier_if_distributed()
|
| 51 |
+
return func(*args, **kwargs)
|
| 52 |
+
|
| 53 |
+
return barrier_on_entry_wrapper
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _conditional_execute_wrapper_factory(execute: bool, func: Callable) -> Callable:
|
| 57 |
+
"""
|
| 58 |
+
Helper function for local_rank_zero_only and global_rank_zero_only.
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
def conditional_execute_wrapper(*args, **kwargs):
|
| 62 |
+
# Only execute if needed.
|
| 63 |
+
result = func(*args, **kwargs) if execute else None
|
| 64 |
+
# All GPUs must wait.
|
| 65 |
+
barrier_if_distributed()
|
| 66 |
+
# Return results.
|
| 67 |
+
return result
|
| 68 |
+
|
| 69 |
+
return conditional_execute_wrapper
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _asserted_wrapper_factory(condition: bool, func: Callable, err_msg: str = "") -> Callable:
|
| 73 |
+
"""
|
| 74 |
+
Helper function for some functions with special constraints,
|
| 75 |
+
especially functions called by other global_rank_zero_only / local_rank_zero_only ones,
|
| 76 |
+
in case they are wrongly invoked in other scenarios.
|
| 77 |
+
"""
|
| 78 |
+
|
| 79 |
+
def asserted_execute_wrapper(*args, **kwargs):
|
| 80 |
+
assert condition, err_msg
|
| 81 |
+
result = func(*args, **kwargs)
|
| 82 |
+
return result
|
| 83 |
+
|
| 84 |
+
return asserted_execute_wrapper
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def local_rank_zero_only(func: Callable) -> Callable:
|
| 88 |
+
"""
|
| 89 |
+
Functions with this decorator will only execute on local rank zero.
|
| 90 |
+
"""
|
| 91 |
+
return _conditional_execute_wrapper_factory(get_local_rank() == 0, func)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def global_rank_zero_only(func: Callable) -> Callable:
|
| 95 |
+
"""
|
| 96 |
+
Functions with this decorator will only execute on global rank zero.
|
| 97 |
+
"""
|
| 98 |
+
return _conditional_execute_wrapper_factory(get_global_rank() == 0, func)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def assert_only_global_rank_zero(func: Callable) -> Callable:
|
| 102 |
+
"""
|
| 103 |
+
Functions with this decorator are only accessible to processes with global rank zero.
|
| 104 |
+
"""
|
| 105 |
+
return _asserted_wrapper_factory(
|
| 106 |
+
get_global_rank() == 0, func, err_msg="Not accessible to processes with global_rank != 0"
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def assert_only_local_rank_zero(func: Callable) -> Callable:
|
| 111 |
+
"""
|
| 112 |
+
Functions with this decorator are only accessible to processes with local rank zero.
|
| 113 |
+
"""
|
| 114 |
+
return _asserted_wrapper_factory(
|
| 115 |
+
get_local_rank() == 0, func, err_msg="Not accessible to processes with local_rank != 0"
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def new_thread(func: Callable) -> Callable:
|
| 120 |
+
"""
|
| 121 |
+
Functions with this decorator will run in a new thread.
|
| 122 |
+
The function will return the thread, which can be joined to wait for completion.
|
| 123 |
+
"""
|
| 124 |
+
|
| 125 |
+
def new_thread_wrapper(*args, **kwargs):
|
| 126 |
+
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
|
| 127 |
+
thread.start()
|
| 128 |
+
return thread
|
| 129 |
+
|
| 130 |
+
return new_thread_wrapper
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Diffusion package.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from .config import (
|
| 20 |
+
create_sampler_from_config,
|
| 21 |
+
create_sampling_timesteps_from_config,
|
| 22 |
+
create_schedule_from_config,
|
| 23 |
+
)
|
| 24 |
+
from .samplers.base import Sampler
|
| 25 |
+
from .samplers.euler import EulerSampler
|
| 26 |
+
from .schedules.base import Schedule
|
| 27 |
+
from .schedules.lerp import LinearInterpolationSchedule
|
| 28 |
+
from .timesteps.base import SamplingTimesteps, Timesteps
|
| 29 |
+
from .timesteps.sampling.trailing import UniformTrailingSamplingTimesteps
|
| 30 |
+
from .types import PredictionType, SamplingDirection
|
| 31 |
+
from .utils import classifier_free_guidance, classifier_free_guidance_dispatcher, expand_dims
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
# Configs
|
| 35 |
+
"create_sampler_from_config",
|
| 36 |
+
"create_sampling_timesteps_from_config",
|
| 37 |
+
"create_schedule_from_config",
|
| 38 |
+
# Schedules
|
| 39 |
+
"Schedule",
|
| 40 |
+
"DiscreteVariancePreservingSchedule",
|
| 41 |
+
"LinearInterpolationSchedule",
|
| 42 |
+
# Samplers
|
| 43 |
+
"Sampler",
|
| 44 |
+
"EulerSampler",
|
| 45 |
+
# Timesteps
|
| 46 |
+
"Timesteps",
|
| 47 |
+
"SamplingTimesteps",
|
| 48 |
+
# Types
|
| 49 |
+
"PredictionType",
|
| 50 |
+
"SamplingDirection",
|
| 51 |
+
"UniformTrailingSamplingTimesteps",
|
| 52 |
+
# Utils
|
| 53 |
+
"classifier_free_guidance",
|
| 54 |
+
"classifier_free_guidance_dispatcher",
|
| 55 |
+
"expand_dims",
|
| 56 |
+
]
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/config.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Utility functions for creating schedules and samplers from config.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
from omegaconf import DictConfig
|
| 21 |
+
|
| 22 |
+
from .samplers.base import Sampler
|
| 23 |
+
from .samplers.euler import EulerSampler
|
| 24 |
+
from .schedules.base import Schedule
|
| 25 |
+
from .schedules.lerp import LinearInterpolationSchedule
|
| 26 |
+
from .timesteps.base import SamplingTimesteps
|
| 27 |
+
from .timesteps.sampling.trailing import UniformTrailingSamplingTimesteps
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def create_schedule_from_config(
|
| 31 |
+
config: DictConfig,
|
| 32 |
+
device: torch.device,
|
| 33 |
+
dtype: torch.dtype = torch.float32,
|
| 34 |
+
) -> Schedule:
|
| 35 |
+
"""
|
| 36 |
+
Create a schedule from configuration.
|
| 37 |
+
"""
|
| 38 |
+
if config.type == "lerp":
|
| 39 |
+
return LinearInterpolationSchedule(T=config.get("T", 1.0))
|
| 40 |
+
|
| 41 |
+
raise NotImplementedError
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def create_sampler_from_config(
|
| 45 |
+
config: DictConfig,
|
| 46 |
+
schedule: Schedule,
|
| 47 |
+
timesteps: SamplingTimesteps,
|
| 48 |
+
) -> Sampler:
|
| 49 |
+
"""
|
| 50 |
+
Create a sampler from configuration.
|
| 51 |
+
"""
|
| 52 |
+
if config.type == "euler":
|
| 53 |
+
return EulerSampler(
|
| 54 |
+
schedule=schedule,
|
| 55 |
+
timesteps=timesteps,
|
| 56 |
+
prediction_type=config.prediction_type,
|
| 57 |
+
)
|
| 58 |
+
raise NotImplementedError
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def create_sampling_timesteps_from_config(
|
| 62 |
+
config: DictConfig,
|
| 63 |
+
schedule: Schedule,
|
| 64 |
+
device: torch.device,
|
| 65 |
+
dtype: torch.dtype = torch.float32,
|
| 66 |
+
) -> SamplingTimesteps:
|
| 67 |
+
if config.type == "uniform_trailing":
|
| 68 |
+
return UniformTrailingSamplingTimesteps(
|
| 69 |
+
T=schedule.T,
|
| 70 |
+
steps=config.steps,
|
| 71 |
+
shift=config.get("shift", 1.0),
|
| 72 |
+
device=device,
|
| 73 |
+
dtype=dtype,
|
| 74 |
+
)
|
| 75 |
+
raise NotImplementedError
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/samplers/base.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Sampler base class.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from abc import ABC, abstractmethod
|
| 20 |
+
from dataclasses import dataclass
|
| 21 |
+
from typing import Callable
|
| 22 |
+
import torch
|
| 23 |
+
from tqdm import tqdm
|
| 24 |
+
|
| 25 |
+
from ..schedules.base import Schedule
|
| 26 |
+
from ..timesteps.base import SamplingTimesteps
|
| 27 |
+
from ..types import PredictionType, SamplingDirection
|
| 28 |
+
from ..utils import assert_schedule_timesteps_compatible
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class SamplerModelArgs:
|
| 33 |
+
x_t: torch.Tensor
|
| 34 |
+
t: torch.Tensor
|
| 35 |
+
i: int
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class Sampler(ABC):
|
| 39 |
+
"""
|
| 40 |
+
Samplers are ODE/SDE solvers.
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def __init__(
|
| 44 |
+
self,
|
| 45 |
+
schedule: Schedule,
|
| 46 |
+
timesteps: SamplingTimesteps,
|
| 47 |
+
prediction_type: PredictionType,
|
| 48 |
+
return_endpoint: bool = True,
|
| 49 |
+
):
|
| 50 |
+
assert_schedule_timesteps_compatible(
|
| 51 |
+
schedule=schedule,
|
| 52 |
+
timesteps=timesteps,
|
| 53 |
+
)
|
| 54 |
+
self.schedule = schedule
|
| 55 |
+
self.timesteps = timesteps
|
| 56 |
+
self.prediction_type = prediction_type
|
| 57 |
+
self.return_endpoint = return_endpoint
|
| 58 |
+
|
| 59 |
+
@abstractmethod
|
| 60 |
+
def sample(
|
| 61 |
+
self,
|
| 62 |
+
x: torch.Tensor,
|
| 63 |
+
f: Callable[[SamplerModelArgs], torch.Tensor],
|
| 64 |
+
) -> torch.Tensor:
|
| 65 |
+
"""
|
| 66 |
+
Generate a new sample given the the intial sample x and score function f.
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
def get_next_timestep(
|
| 70 |
+
self,
|
| 71 |
+
t: torch.Tensor,
|
| 72 |
+
) -> torch.Tensor:
|
| 73 |
+
"""
|
| 74 |
+
Get the next sample timestep.
|
| 75 |
+
Support multiple different timesteps t in a batch.
|
| 76 |
+
If no more steps, return out of bound value -1 or T+1.
|
| 77 |
+
"""
|
| 78 |
+
T = self.timesteps.T
|
| 79 |
+
steps = len(self.timesteps)
|
| 80 |
+
curr_idx = self.timesteps.index(t)
|
| 81 |
+
next_idx = curr_idx + 1
|
| 82 |
+
bound = -1 if self.timesteps.direction == SamplingDirection.backward else T + 1
|
| 83 |
+
|
| 84 |
+
s = self.timesteps[next_idx.clamp_max(steps - 1)]
|
| 85 |
+
s = s.where(next_idx < steps, bound)
|
| 86 |
+
return s
|
| 87 |
+
|
| 88 |
+
def get_endpoint(
|
| 89 |
+
self,
|
| 90 |
+
pred: torch.Tensor,
|
| 91 |
+
x_t: torch.Tensor,
|
| 92 |
+
t: torch.Tensor,
|
| 93 |
+
) -> torch.Tensor:
|
| 94 |
+
"""
|
| 95 |
+
Get to the endpoint of the probability flow.
|
| 96 |
+
"""
|
| 97 |
+
x_0, x_T = self.schedule.convert_from_pred(pred, self.prediction_type, x_t, t)
|
| 98 |
+
return x_0 if self.timesteps.direction == SamplingDirection.backward else x_T
|
| 99 |
+
|
| 100 |
+
def get_progress_bar(self):
|
| 101 |
+
"""
|
| 102 |
+
Get progress bar for sampling.
|
| 103 |
+
"""
|
| 104 |
+
return tqdm(
|
| 105 |
+
iterable=range(len(self.timesteps) - (0 if self.return_endpoint else 1)),
|
| 106 |
+
dynamic_ncols=True,
|
| 107 |
+
desc=self.__class__.__name__,
|
| 108 |
+
)
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/samplers/euler.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
Euler ODE solver.
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from typing import Callable
|
| 21 |
+
import torch
|
| 22 |
+
from einops import rearrange
|
| 23 |
+
from torch.nn import functional as F
|
| 24 |
+
|
| 25 |
+
from ..types import PredictionType
|
| 26 |
+
from ..utils import expand_dims
|
| 27 |
+
from .base import Sampler, SamplerModelArgs
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class EulerSampler(Sampler):
|
| 31 |
+
"""
|
| 32 |
+
The Euler method is the simplest ODE solver.
|
| 33 |
+
<https://en.wikipedia.org/wiki/Euler_method>
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def sample(
|
| 37 |
+
self,
|
| 38 |
+
x: torch.Tensor,
|
| 39 |
+
f: Callable[[SamplerModelArgs], torch.Tensor],
|
| 40 |
+
) -> torch.Tensor:
|
| 41 |
+
timesteps = self.timesteps.timesteps
|
| 42 |
+
progress = self.get_progress_bar()
|
| 43 |
+
i = 0
|
| 44 |
+
|
| 45 |
+
# Keep native dtype throughout sampling
|
| 46 |
+
# The DiT model already handles dtype internally via compatibility wrapper
|
| 47 |
+
for t, s in zip(timesteps[:-1], timesteps[1:]):
|
| 48 |
+
pred = f(SamplerModelArgs(x, t, i))
|
| 49 |
+
|
| 50 |
+
# Next step
|
| 51 |
+
x = self.step_to(pred, x, t, s)
|
| 52 |
+
|
| 53 |
+
# Clean up temporary tensors
|
| 54 |
+
del pred
|
| 55 |
+
|
| 56 |
+
i += 1
|
| 57 |
+
progress.update()
|
| 58 |
+
|
| 59 |
+
if self.return_endpoint:
|
| 60 |
+
t = timesteps[-1]
|
| 61 |
+
pred = f(SamplerModelArgs(x, t, i))
|
| 62 |
+
x = self.get_endpoint(pred, x, t)
|
| 63 |
+
del pred
|
| 64 |
+
progress.update()
|
| 65 |
+
|
| 66 |
+
return x
|
| 67 |
+
|
| 68 |
+
def step(
|
| 69 |
+
self,
|
| 70 |
+
pred: torch.Tensor,
|
| 71 |
+
x_t: torch.Tensor,
|
| 72 |
+
t: torch.Tensor,
|
| 73 |
+
) -> torch.Tensor:
|
| 74 |
+
"""
|
| 75 |
+
Step to the next timestep.
|
| 76 |
+
"""
|
| 77 |
+
return self.step_to(pred, x_t, t, self.get_next_timestep(t))
|
| 78 |
+
|
| 79 |
+
def step_to(
|
| 80 |
+
self,
|
| 81 |
+
pred: torch.Tensor,
|
| 82 |
+
x_t: torch.Tensor,
|
| 83 |
+
t: torch.Tensor,
|
| 84 |
+
s: torch.Tensor,
|
| 85 |
+
) -> torch.Tensor:
|
| 86 |
+
"""
|
| 87 |
+
Steps from x_t at timestep t to x_s at timestep s. Returns x_s.
|
| 88 |
+
"""
|
| 89 |
+
t = expand_dims(t, x_t.ndim)
|
| 90 |
+
s = expand_dims(s, x_t.ndim)
|
| 91 |
+
T = self.schedule.T
|
| 92 |
+
# Step from x_t to x_s.
|
| 93 |
+
pred_x_0, pred_x_T = self.schedule.convert_from_pred(pred, self.prediction_type, x_t, t)
|
| 94 |
+
pred_x_s = self.schedule.forward(pred_x_0, pred_x_T, s.clamp(0, T))
|
| 95 |
+
# Clamp x_s to x_0 and x_T if s is out of bound.
|
| 96 |
+
pred_x_s = pred_x_s.where(s >= 0, pred_x_0)
|
| 97 |
+
pred_x_s = pred_x_s.where(s <= T, pred_x_T)
|
| 98 |
+
return pred_x_s
|
| 99 |
+
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/schedules/base.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Schedule base class.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from abc import ABC, abstractmethod, abstractproperty
|
| 20 |
+
from typing import Tuple, Union
|
| 21 |
+
import torch
|
| 22 |
+
|
| 23 |
+
from ..types import PredictionType
|
| 24 |
+
from ..utils import expand_dims
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class Schedule(ABC):
|
| 28 |
+
"""
|
| 29 |
+
Diffusion schedules are uniquely defined by T, A, B:
|
| 30 |
+
|
| 31 |
+
x_t = A(t) * x_0 + B(t) * x_T, where t in [0, T]
|
| 32 |
+
|
| 33 |
+
Schedules can be continuous or discrete.
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
@abstractproperty
|
| 37 |
+
def T(self) -> Union[int, float]:
|
| 38 |
+
"""
|
| 39 |
+
Maximum timestep inclusive.
|
| 40 |
+
Schedule is continuous if float, discrete if int.
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
@abstractmethod
|
| 44 |
+
def A(self, t: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
"""
|
| 46 |
+
Interpolation coefficient A.
|
| 47 |
+
Returns tensor with the same shape as t.
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
@abstractmethod
|
| 51 |
+
def B(self, t: torch.Tensor) -> torch.Tensor:
|
| 52 |
+
"""
|
| 53 |
+
Interpolation coefficient B.
|
| 54 |
+
Returns tensor with the same shape as t.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
# ----------------------------------------------------
|
| 58 |
+
|
| 59 |
+
def snr(self, t: torch.Tensor) -> torch.Tensor:
|
| 60 |
+
"""
|
| 61 |
+
Signal to noise ratio.
|
| 62 |
+
Returns tensor with the same shape as t.
|
| 63 |
+
"""
|
| 64 |
+
return (self.A(t) ** 2) / (self.B(t) ** 2)
|
| 65 |
+
|
| 66 |
+
def isnr(self, snr: torch.Tensor) -> torch.Tensor:
|
| 67 |
+
"""
|
| 68 |
+
Inverse signal to noise ratio.
|
| 69 |
+
Returns tensor with the same shape as snr.
|
| 70 |
+
Subclass may implement.
|
| 71 |
+
"""
|
| 72 |
+
raise NotImplementedError
|
| 73 |
+
|
| 74 |
+
# ----------------------------------------------------
|
| 75 |
+
|
| 76 |
+
def is_continuous(self) -> bool:
|
| 77 |
+
"""
|
| 78 |
+
Whether the schedule is continuous.
|
| 79 |
+
"""
|
| 80 |
+
return isinstance(self.T, float)
|
| 81 |
+
|
| 82 |
+
def forward(self, x_0: torch.Tensor, x_T: torch.Tensor, t: torch.Tensor) -> torch.Tensor:
|
| 83 |
+
"""
|
| 84 |
+
Diffusion forward function.
|
| 85 |
+
"""
|
| 86 |
+
t = expand_dims(t, x_0.ndim)
|
| 87 |
+
return self.A(t) * x_0 + self.B(t) * x_T
|
| 88 |
+
|
| 89 |
+
def convert_from_pred(
|
| 90 |
+
self, pred: torch.Tensor, pred_type: PredictionType, x_t: torch.Tensor, t: torch.Tensor
|
| 91 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 92 |
+
"""
|
| 93 |
+
Convert from prediction. Return predicted x_0 and x_T.
|
| 94 |
+
"""
|
| 95 |
+
t = expand_dims(t, x_t.ndim)
|
| 96 |
+
A_t = self.A(t)
|
| 97 |
+
B_t = self.B(t)
|
| 98 |
+
|
| 99 |
+
if pred_type == PredictionType.x_T:
|
| 100 |
+
pred_x_T = pred
|
| 101 |
+
pred_x_0 = (x_t - B_t * pred_x_T) / A_t
|
| 102 |
+
elif pred_type == PredictionType.x_0:
|
| 103 |
+
pred_x_0 = pred
|
| 104 |
+
pred_x_T = (x_t - A_t * pred_x_0) / B_t
|
| 105 |
+
elif pred_type == PredictionType.v_cos:
|
| 106 |
+
pred_x_0 = A_t * x_t - B_t * pred
|
| 107 |
+
pred_x_T = A_t * pred + B_t * x_t
|
| 108 |
+
elif pred_type == PredictionType.v_lerp:
|
| 109 |
+
pred_x_0 = (x_t - B_t * pred) / (A_t + B_t)
|
| 110 |
+
pred_x_T = (x_t + A_t * pred) / (A_t + B_t)
|
| 111 |
+
else:
|
| 112 |
+
raise NotImplementedError
|
| 113 |
+
|
| 114 |
+
return pred_x_0, pred_x_T
|
| 115 |
+
|
| 116 |
+
def convert_to_pred(
|
| 117 |
+
self, x_0: torch.Tensor, x_T: torch.Tensor, t: torch.Tensor, pred_type: PredictionType
|
| 118 |
+
) -> torch.FloatTensor:
|
| 119 |
+
"""
|
| 120 |
+
Convert to prediction target given x_0 and x_T.
|
| 121 |
+
"""
|
| 122 |
+
if pred_type == PredictionType.x_T:
|
| 123 |
+
return x_T
|
| 124 |
+
if pred_type == PredictionType.x_0:
|
| 125 |
+
return x_0
|
| 126 |
+
if pred_type == PredictionType.v_cos:
|
| 127 |
+
t = expand_dims(t, x_0.ndim)
|
| 128 |
+
return self.A(t) * x_T - self.B(t) * x_0
|
| 129 |
+
if pred_type == PredictionType.v_lerp:
|
| 130 |
+
return x_T - x_0
|
| 131 |
+
raise NotImplementedError
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/schedules/lerp.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Linear interpolation schedule (lerp).
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from typing import Union
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
from .base import Schedule
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class LinearInterpolationSchedule(Schedule):
|
| 26 |
+
"""
|
| 27 |
+
Linear interpolation schedule (lerp) is proposed by flow matching and rectified flow.
|
| 28 |
+
It leads to straighter probability flow theoretically. It is also used by Stable Diffusion 3.
|
| 29 |
+
<https://arxiv.org/abs/2209.03003>
|
| 30 |
+
<https://arxiv.org/abs/2210.02747>
|
| 31 |
+
|
| 32 |
+
x_t = (1 - t) * x_0 + t * x_T
|
| 33 |
+
|
| 34 |
+
Can be either continuous or discrete.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def __init__(self, T: Union[int, float] = 1.0):
|
| 38 |
+
self._T = T
|
| 39 |
+
|
| 40 |
+
@property
|
| 41 |
+
def T(self) -> Union[int, float]:
|
| 42 |
+
return self._T
|
| 43 |
+
|
| 44 |
+
def A(self, t: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
return 1 - (t / self.T)
|
| 46 |
+
|
| 47 |
+
def B(self, t: torch.Tensor) -> torch.Tensor:
|
| 48 |
+
return t / self.T
|
| 49 |
+
|
| 50 |
+
# ----------------------------------------------------
|
| 51 |
+
|
| 52 |
+
def isnr(self, snr: torch.Tensor) -> torch.Tensor:
|
| 53 |
+
t = self.T / (1 + snr**0.5)
|
| 54 |
+
t = t if self.is_continuous() else t.round().int()
|
| 55 |
+
return t
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/timesteps/base.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
from typing import Sequence, Union
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from ..types import SamplingDirection
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class Timesteps(ABC):
|
| 9 |
+
"""
|
| 10 |
+
Timesteps base class.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
def __init__(self, T: Union[int, float]):
|
| 14 |
+
assert T > 0
|
| 15 |
+
self._T = T
|
| 16 |
+
|
| 17 |
+
@property
|
| 18 |
+
def T(self) -> Union[int, float]:
|
| 19 |
+
"""
|
| 20 |
+
Maximum timestep inclusive.
|
| 21 |
+
int if discrete, float if continuous.
|
| 22 |
+
"""
|
| 23 |
+
return self._T
|
| 24 |
+
|
| 25 |
+
def is_continuous(self) -> bool:
|
| 26 |
+
"""
|
| 27 |
+
Whether the schedule is continuous.
|
| 28 |
+
"""
|
| 29 |
+
return isinstance(self.T, float)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class SamplingTimesteps(Timesteps):
|
| 33 |
+
"""
|
| 34 |
+
Sampling timesteps.
|
| 35 |
+
It defines the discretization of sampling steps.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(
|
| 39 |
+
self,
|
| 40 |
+
T: Union[int, float],
|
| 41 |
+
timesteps: torch.Tensor,
|
| 42 |
+
direction: SamplingDirection,
|
| 43 |
+
):
|
| 44 |
+
assert timesteps.ndim == 1
|
| 45 |
+
super().__init__(T)
|
| 46 |
+
self.timesteps = timesteps
|
| 47 |
+
self.direction = direction
|
| 48 |
+
|
| 49 |
+
def __len__(self) -> int:
|
| 50 |
+
"""
|
| 51 |
+
Number of sampling steps.
|
| 52 |
+
"""
|
| 53 |
+
return len(self.timesteps)
|
| 54 |
+
|
| 55 |
+
def __getitem__(self, idx: Union[int, torch.IntTensor]) -> torch.Tensor:
|
| 56 |
+
"""
|
| 57 |
+
The timestep at the sampling step.
|
| 58 |
+
Returns a scalar tensor if idx is int,
|
| 59 |
+
or tensor of the same size if idx is a tensor.
|
| 60 |
+
"""
|
| 61 |
+
return self.timesteps[idx]
|
| 62 |
+
|
| 63 |
+
def index(self, t: torch.Tensor) -> torch.Tensor:
|
| 64 |
+
"""
|
| 65 |
+
Find index by t.
|
| 66 |
+
Return index of the same shape as t.
|
| 67 |
+
Index is -1 if t not found in timesteps.
|
| 68 |
+
"""
|
| 69 |
+
i, j = t.reshape(-1, 1).eq(self.timesteps).nonzero(as_tuple=True)
|
| 70 |
+
idx = torch.full_like(t, fill_value=-1, dtype=torch.int)
|
| 71 |
+
idx.view(-1)[i] = j.int()
|
| 72 |
+
return idx
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/timesteps/sampling/trailing.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
from ...types import SamplingDirection
|
| 18 |
+
from ..base import SamplingTimesteps
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class UniformTrailingSamplingTimesteps(SamplingTimesteps):
|
| 22 |
+
"""
|
| 23 |
+
Uniform trailing sampling timesteps.
|
| 24 |
+
Defined in (https://arxiv.org/abs/2305.08891)
|
| 25 |
+
|
| 26 |
+
Shift is proposed in SD3 for RF schedule.
|
| 27 |
+
Defined in (https://arxiv.org/pdf/2403.03206) eq.23
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
def __init__(
|
| 31 |
+
self,
|
| 32 |
+
T: int,
|
| 33 |
+
steps: int,
|
| 34 |
+
shift: float = 1.0,
|
| 35 |
+
device: torch.device = "cpu",
|
| 36 |
+
dtype: torch.dtype = torch.float32,
|
| 37 |
+
):
|
| 38 |
+
# Create trailing timesteps with specified dtype
|
| 39 |
+
timesteps = torch.arange(1.0, 0.0, -1.0 / steps, device='cpu').to(device=device, dtype=dtype)
|
| 40 |
+
|
| 41 |
+
# Shift timesteps.
|
| 42 |
+
timesteps = shift * timesteps / (1 + (shift - 1) * timesteps)
|
| 43 |
+
|
| 44 |
+
# Scale to T range.
|
| 45 |
+
if isinstance(T, float):
|
| 46 |
+
timesteps = timesteps * T
|
| 47 |
+
else:
|
| 48 |
+
timesteps = timesteps.mul(T + 1).sub(1).round().int()
|
| 49 |
+
|
| 50 |
+
super().__init__(T=T, timesteps=timesteps, direction=SamplingDirection.backward)
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/types.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Type definitions.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from enum import Enum
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class PredictionType(str, Enum):
|
| 23 |
+
"""
|
| 24 |
+
x_0:
|
| 25 |
+
Predict data sample.
|
| 26 |
+
x_T:
|
| 27 |
+
Predict noise sample.
|
| 28 |
+
Proposed by DDPM (https://arxiv.org/abs/2006.11239)
|
| 29 |
+
Proved problematic by zsnr paper (https://arxiv.org/abs/2305.08891)
|
| 30 |
+
v_cos:
|
| 31 |
+
Predict velocity dx/dt based on the cosine schedule (A_t * x_T - B_t * x_0).
|
| 32 |
+
Proposed by progressive distillation (https://arxiv.org/abs/2202.00512)
|
| 33 |
+
v_lerp:
|
| 34 |
+
Predict velocity dx/dt based on the lerp schedule (x_T - x_0).
|
| 35 |
+
Proposed by rectified flow (https://arxiv.org/abs/2209.03003)
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
x_0 = "x_0"
|
| 39 |
+
x_T = "x_T"
|
| 40 |
+
v_cos = "v_cos"
|
| 41 |
+
v_lerp = "v_lerp"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class SamplingDirection(str, Enum):
|
| 45 |
+
"""
|
| 46 |
+
backward: Sample from x_T to x_0 for data generation.
|
| 47 |
+
forward: Sample from x_0 to x_T for noise inversion.
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
backward = "backward"
|
| 51 |
+
forward = "forward"
|
| 52 |
+
|
| 53 |
+
@staticmethod
|
| 54 |
+
def reverse(direction):
|
| 55 |
+
if direction == SamplingDirection.backward:
|
| 56 |
+
return SamplingDirection.forward
|
| 57 |
+
if direction == SamplingDirection.forward:
|
| 58 |
+
return SamplingDirection.backward
|
| 59 |
+
raise NotImplementedError
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/diffusion/utils.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Utility functions.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from typing import Callable
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def expand_dims(tensor: torch.Tensor, ndim: int):
|
| 24 |
+
"""
|
| 25 |
+
Expand tensor to target ndim. New dims are added to the right.
|
| 26 |
+
For example, if the tensor shape was (8,), target ndim is 4, return (8, 1, 1, 1).
|
| 27 |
+
"""
|
| 28 |
+
shape = tensor.shape + (1,) * (ndim - tensor.ndim)
|
| 29 |
+
return tensor.reshape(shape)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def assert_schedule_timesteps_compatible(schedule, timesteps):
|
| 33 |
+
"""
|
| 34 |
+
Check if schedule and timesteps are compatible.
|
| 35 |
+
"""
|
| 36 |
+
if schedule.T != timesteps.T:
|
| 37 |
+
raise ValueError("Schedule and timesteps must have the same T.")
|
| 38 |
+
if schedule.is_continuous() != timesteps.is_continuous():
|
| 39 |
+
raise ValueError("Schedule and timesteps must have the same continuity.")
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def classifier_free_guidance(
|
| 43 |
+
pos: torch.Tensor,
|
| 44 |
+
neg: torch.Tensor,
|
| 45 |
+
scale: float,
|
| 46 |
+
rescale: float = 0.0,
|
| 47 |
+
):
|
| 48 |
+
"""
|
| 49 |
+
Apply classifier-free guidance.
|
| 50 |
+
"""
|
| 51 |
+
# Classifier-free guidance (https://arxiv.org/abs/2207.12598)
|
| 52 |
+
cfg = neg + scale * (pos - neg)
|
| 53 |
+
|
| 54 |
+
# Classifier-free guidance rescale (https://arxiv.org/pdf/2305.08891.pdf)
|
| 55 |
+
if rescale != 0.0:
|
| 56 |
+
pos_std = pos.std(dim=list(range(1, pos.ndim)), keepdim=True)
|
| 57 |
+
cfg_std = cfg.std(dim=list(range(1, cfg.ndim)), keepdim=True)
|
| 58 |
+
factor = pos_std / cfg_std
|
| 59 |
+
factor = rescale * factor + (1 - rescale)
|
| 60 |
+
cfg *= factor
|
| 61 |
+
|
| 62 |
+
return cfg
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def classifier_free_guidance_dispatcher(
|
| 66 |
+
pos: Callable,
|
| 67 |
+
neg: Callable,
|
| 68 |
+
scale: float,
|
| 69 |
+
rescale: float = 0.0,
|
| 70 |
+
):
|
| 71 |
+
"""
|
| 72 |
+
Optionally execute models depending on classifer-free guidance scale.
|
| 73 |
+
"""
|
| 74 |
+
# If scale is 1, no need to execute neg model.
|
| 75 |
+
if scale == 1.0:
|
| 76 |
+
return pos()
|
| 77 |
+
|
| 78 |
+
# Otherwise, execute both pos nad neg models and apply cfg.
|
| 79 |
+
return classifier_free_guidance(
|
| 80 |
+
pos=pos(),
|
| 81 |
+
neg=neg(),
|
| 82 |
+
scale=scale,
|
| 83 |
+
rescale=rescale,
|
| 84 |
+
)
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/distributed/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Distributed package.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from .basic import (
|
| 20 |
+
barrier_if_distributed,
|
| 21 |
+
convert_to_ddp,
|
| 22 |
+
get_device,
|
| 23 |
+
get_global_rank,
|
| 24 |
+
get_local_rank,
|
| 25 |
+
get_world_size,
|
| 26 |
+
init_torch,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
__all__ = [
|
| 30 |
+
"barrier_if_distributed",
|
| 31 |
+
"convert_to_ddp",
|
| 32 |
+
"get_device",
|
| 33 |
+
"get_global_rank",
|
| 34 |
+
"get_local_rank",
|
| 35 |
+
"get_world_size",
|
| 36 |
+
"init_torch",
|
| 37 |
+
]
|
v3-nodes/ComfyUI-SeedVR2_VideoUpscaler/src/common/distributed/advanced.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# // Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
# //
|
| 3 |
+
# // Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# // you may not use this file except in compliance with the License.
|
| 5 |
+
# // You may obtain a copy of the License at
|
| 6 |
+
# //
|
| 7 |
+
# // http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
# //
|
| 9 |
+
# // Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# // distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# // See the License for the specific language governing permissions and
|
| 13 |
+
# // limitations under the License.
|
| 14 |
+
|
| 15 |
+
"""
|
| 16 |
+
Advanced distributed functions for sequence parallel.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from typing import Optional, List, TYPE_CHECKING
|
| 20 |
+
import torch
|
| 21 |
+
import torch.distributed as dist
|
| 22 |
+
|
| 23 |
+
# Conditional imports for distributed training features (not needed for inference)
|
| 24 |
+
try:
|
| 25 |
+
from torch.distributed.device_mesh import DeviceMesh, init_device_mesh
|
| 26 |
+
from torch.distributed.fsdp import ShardingStrategy
|
| 27 |
+
_FSDP_AVAILABLE = True
|
| 28 |
+
except (ImportError, AttributeError):
|
| 29 |
+
# AMD ROCm and other builds may not have full FSDP support
|
| 30 |
+
DeviceMesh = None
|
| 31 |
+
init_device_mesh = None
|
| 32 |
+
ShardingStrategy = None
|
| 33 |
+
_FSDP_AVAILABLE = False
|
| 34 |
+
|
| 35 |
+
from .basic import get_global_rank, get_world_size
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
_DATA_PARALLEL_GROUP = None
|
| 39 |
+
_SEQUENCE_PARALLEL_GROUP = None
|
| 40 |
+
_SEQUENCE_PARALLEL_CPU_GROUP = None
|
| 41 |
+
_MODEL_SHARD_CPU_INTER_GROUP = None
|
| 42 |
+
_MODEL_SHARD_CPU_INTRA_GROUP = None
|
| 43 |
+
_MODEL_SHARD_INTER_GROUP = None
|
| 44 |
+
_MODEL_SHARD_INTRA_GROUP = None
|
| 45 |
+
_SEQUENCE_PARALLEL_GLOBAL_RANKS = None
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def get_data_parallel_group() -> Optional[dist.ProcessGroup]:
|
| 49 |
+
"""
|
| 50 |
+
Get data parallel process group.
|
| 51 |
+
"""
|
| 52 |
+
return _DATA_PARALLEL_GROUP
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def get_sequence_parallel_group() -> Optional[dist.ProcessGroup]:
|
| 56 |
+
"""
|
| 57 |
+
Get sequence parallel process group.
|
| 58 |
+
"""
|
| 59 |
+
return _SEQUENCE_PARALLEL_GROUP
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def get_sequence_parallel_cpu_group() -> Optional[dist.ProcessGroup]:
|
| 63 |
+
"""
|
| 64 |
+
Get sequence parallel CPU process group.
|
| 65 |
+
"""
|
| 66 |
+
return _SEQUENCE_PARALLEL_CPU_GROUP
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def get_data_parallel_rank() -> int:
|
| 70 |
+
"""
|
| 71 |
+
Get data parallel rank.
|
| 72 |
+
"""
|
| 73 |
+
group = get_data_parallel_group()
|
| 74 |
+
return dist.get_rank(group) if group else get_global_rank()
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_data_parallel_world_size() -> int:
|
| 78 |
+
"""
|
| 79 |
+
Get data parallel world size.
|
| 80 |
+
"""
|
| 81 |
+
group = get_data_parallel_group()
|
| 82 |
+
return dist.get_world_size(group) if group else get_world_size()
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def get_sequence_parallel_rank() -> int:
|
| 86 |
+
"""
|
| 87 |
+
Get sequence parallel rank.
|
| 88 |
+
"""
|
| 89 |
+
group = get_sequence_parallel_group()
|
| 90 |
+
return dist.get_rank(group) if group else 0
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def get_sequence_parallel_world_size() -> int:
|
| 94 |
+
"""
|
| 95 |
+
Get sequence parallel world size.
|
| 96 |
+
"""
|
| 97 |
+
group = get_sequence_parallel_group()
|
| 98 |
+
return dist.get_world_size(group) if group else 1
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def get_model_shard_cpu_intra_group() -> Optional[dist.ProcessGroup]:
|
| 102 |
+
"""
|
| 103 |
+
Get the CPU intra process group of model sharding.
|
| 104 |
+
"""
|
| 105 |
+
return _MODEL_SHARD_CPU_INTRA_GROUP
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def get_model_shard_cpu_inter_group() -> Optional[dist.ProcessGroup]:
|
| 109 |
+
"""
|
| 110 |
+
Get the CPU inter process group of model sharding.
|
| 111 |
+
"""
|
| 112 |
+
return _MODEL_SHARD_CPU_INTER_GROUP
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def get_model_shard_intra_group() -> Optional[dist.ProcessGroup]:
|
| 116 |
+
"""
|
| 117 |
+
Get the GPU intra process group of model sharding.
|
| 118 |
+
"""
|
| 119 |
+
return _MODEL_SHARD_INTRA_GROUP
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def get_model_shard_inter_group() -> Optional[dist.ProcessGroup]:
|
| 123 |
+
"""
|
| 124 |
+
Get the GPU inter process group of model sharding.
|
| 125 |
+
"""
|
| 126 |
+
return _MODEL_SHARD_INTER_GROUP
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def init_sequence_parallel(sequence_parallel_size: int):
|
| 130 |
+
"""
|
| 131 |
+
Initialize sequence parallel.
|
| 132 |
+
"""
|
| 133 |
+
global _DATA_PARALLEL_GROUP
|
| 134 |
+
global _SEQUENCE_PARALLEL_GROUP
|
| 135 |
+
global _SEQUENCE_PARALLEL_CPU_GROUP
|
| 136 |
+
global _SEQUENCE_PARALLEL_GLOBAL_RANKS
|
| 137 |
+
assert dist.is_initialized()
|
| 138 |
+
world_size = dist.get_world_size()
|
| 139 |
+
rank = dist.get_rank()
|
| 140 |
+
data_parallel_size = world_size // sequence_parallel_size
|
| 141 |
+
for i in range(data_parallel_size):
|
| 142 |
+
start_rank = i * sequence_parallel_size
|
| 143 |
+
end_rank = (i + 1) * sequence_parallel_size
|
| 144 |
+
ranks = range(start_rank, end_rank)
|
| 145 |
+
group = dist.new_group(ranks)
|
| 146 |
+
cpu_group = dist.new_group(ranks, backend="gloo")
|
| 147 |
+
if rank in ranks:
|
| 148 |
+
_SEQUENCE_PARALLEL_GROUP = group
|
| 149 |
+
_SEQUENCE_PARALLEL_CPU_GROUP = cpu_group
|
| 150 |
+
_SEQUENCE_PARALLEL_GLOBAL_RANKS = list(ranks)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def init_model_shard_group(
|
| 154 |
+
*,
|
| 155 |
+
sharding_strategy: ShardingStrategy,
|
| 156 |
+
device_mesh: Optional[DeviceMesh] = None,
|
| 157 |
+
):
|
| 158 |
+
"""
|
| 159 |
+
Initialize process group of model sharding.
|
| 160 |
+
"""
|
| 161 |
+
if not _FSDP_AVAILABLE:
|
| 162 |
+
raise RuntimeError(
|
| 163 |
+
"FSDP features are not available in this PyTorch build. "
|
| 164 |
+
"Model sharding requires torch.distributed.fsdp support."
|
| 165 |
+
)
|
| 166 |
+
global _MODEL_SHARD_INTER_GROUP
|
| 167 |
+
global _MODEL_SHARD_INTRA_GROUP
|
| 168 |
+
global _MODEL_SHARD_CPU_INTER_GROUP
|
| 169 |
+
global _MODEL_SHARD_CPU_INTRA_GROUP
|
| 170 |
+
assert dist.is_initialized()
|
| 171 |
+
world_size = dist.get_world_size()
|
| 172 |
+
if device_mesh is not None:
|
| 173 |
+
num_shards_per_group = device_mesh.shape[1]
|
| 174 |
+
elif sharding_strategy == ShardingStrategy.NO_SHARD:
|
| 175 |
+
num_shards_per_group = 1
|
| 176 |
+
elif sharding_strategy in [
|
| 177 |
+
ShardingStrategy.HYBRID_SHARD,
|
| 178 |
+
ShardingStrategy._HYBRID_SHARD_ZERO2,
|
| 179 |
+
]:
|
| 180 |
+
num_shards_per_group = torch.cuda.device_count()
|
| 181 |
+
else:
|
| 182 |
+
num_shards_per_group = world_size
|
| 183 |
+
num_groups = world_size // num_shards_per_group
|
| 184 |
+
device_mesh = (num_groups, num_shards_per_group)
|
| 185 |
+
|
| 186 |
+
gpu_mesh_2d = init_device_mesh("cuda", device_mesh, mesh_dim_names=("inter", "intra"))
|
| 187 |
+
cpu_mesh_2d = init_device_mesh("cpu", device_mesh, mesh_dim_names=("inter", "intra"))
|
| 188 |
+
|
| 189 |
+
_MODEL_SHARD_INTER_GROUP = gpu_mesh_2d.get_group("inter")
|
| 190 |
+
_MODEL_SHARD_INTRA_GROUP = gpu_mesh_2d.get_group("intra")
|
| 191 |
+
_MODEL_SHARD_CPU_INTER_GROUP = cpu_mesh_2d.get_group("inter")
|
| 192 |
+
_MODEL_SHARD_CPU_INTRA_GROUP = cpu_mesh_2d.get_group("intra")
|
| 193 |
+
|
| 194 |
+
def get_sequence_parallel_global_ranks() -> List[int]:
|
| 195 |
+
"""
|
| 196 |
+
Get all global ranks of the sequence parallel process group
|
| 197 |
+
that the caller rank belongs to.
|
| 198 |
+
"""
|
| 199 |
+
if _SEQUENCE_PARALLEL_GLOBAL_RANKS is None:
|
| 200 |
+
return [dist.get_rank()]
|
| 201 |
+
return _SEQUENCE_PARALLEL_GLOBAL_RANKS
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def get_next_sequence_parallel_rank() -> int:
|
| 205 |
+
"""
|
| 206 |
+
Get the next global rank of the sequence parallel process group
|
| 207 |
+
that the caller rank belongs to.
|
| 208 |
+
"""
|
| 209 |
+
sp_global_ranks = get_sequence_parallel_global_ranks()
|
| 210 |
+
sp_rank = get_sequence_parallel_rank()
|
| 211 |
+
sp_size = get_sequence_parallel_world_size()
|
| 212 |
+
return sp_global_ranks[(sp_rank + 1) % sp_size]
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def get_prev_sequence_parallel_rank() -> int:
|
| 216 |
+
"""
|
| 217 |
+
Get the previous global rank of the sequence parallel process group
|
| 218 |
+
that the caller rank belongs to.
|
| 219 |
+
"""
|
| 220 |
+
sp_global_ranks = get_sequence_parallel_global_ranks()
|
| 221 |
+
sp_rank = get_sequence_parallel_rank()
|
| 222 |
+
sp_size = get_sequence_parallel_world_size()
|
| 223 |
+
return sp_global_ranks[(sp_rank + sp_size - 1) % sp_size]
|