From cb21a14424a8e991dfaf202282717d5e142206b9 Mon Sep 17 00:00:00 2001 From: ausboss <102309304+ausboss@users.noreply.github.com> Date: Wed, 11 Nov 2026 22:43:55 -0600 Subject: [PATCH] Add FlashVSR Upscale: a video upscale node with one model file Makes a video larger and sharper with the FlashVSR v1.1 model, from 1x to 4x. The model, the loop and the tiling are the pack's own code in plain PyTorch, so the node needs one model file and no other pack. A long clip needs no more graphics memory than a short one, and a result too large for the free memory is made in tiles. Comes with a help page, the example workflow FlashVSR Video Upscale, and tests for the helpers. --- CHANGELOG.md | 8 + README.md | 9 +- __init__.py | 1 + .../FlashVSR Video Upscale (AusBoss).jpg | Bin 0 -> 129709 bytes .../FlashVSR Video Upscale (AusBoss).json | 526 ++++++++++ js/docs/AUSBOSS_NODES_FlashVSRUpscale.md | 69 ++ js/docs/agents/README.md | 1 + js/widget_cards/index.js | 8 + locales/zh/nodeDefs.json | 26 + nodes/_flashvsr_helpers.py | 946 ++++++++++++++++++ nodes/node_flashvsr_upscale.py | 87 ++ scripts/validate_nodes.py | 1 + tests/fixtures/node_api.json | 17 + tests/test_flashvsr_helpers.py | 225 +++++ 14 files changed, 1923 insertions(+), 1 deletion(-) create mode 100644 example_workflows/FlashVSR Video Upscale (AusBoss).jpg create mode 100644 example_workflows/FlashVSR Video Upscale (AusBoss).json create mode 100644 js/docs/AUSBOSS_NODES_FlashVSRUpscale.md create mode 100644 nodes/_flashvsr_helpers.py create mode 100644 nodes/node_flashvsr_upscale.py create mode 100644 tests/test_flashvsr_helpers.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cbedc1..ac4899d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,14 @@ All notable changes to ComfyUI-AusBoss are documented here. replaced. Now only the newest look waits. The button that pauses the looking also says **Pause** and **Look on**. +- **New node: FlashVSR Upscale 🆎.** Makes a video larger and sharper with + the FlashVSR v1.1 model, from 1x to 4x. It needs one model file and no + other pack: the help page has the download link. A long clip needs no more + graphics memory than a short one, and a result too large for the free + memory is made in tiles. + The new example `FlashVSR Video Upscale (AusBoss)` shows it between Load + Video and Save Video. + - **Two errors from outside a workflow now say what to do.** When a MiniMax H3 run stops with `KeyError: 'latent'` because the Muse MiniMax Director pack is installed, a message names that pack and says to turn it off. When an older diff --git a/README.md b/README.md index e200cdb..15d9950 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,12 @@ Changes a clip's frame rate by making in-between frames. You set the source and It returns the new frames and their fps. It doesn't touch audio, so carry the source audio to Save Video separately. The [help page](js/docs/AUSBOSS_NODES_FrameInterpolate.md) covers the weights and timing. +### FlashVSR Upscale + +Makes a video larger and sharper with the FlashVSR model. Set **Scale** from 1 to 4 and it returns every frame at the new size, with fine detail drawn back in. It reads the clip a few frames at a time, so a long clip needs no more graphics memory than a short one, and a result too large for the free memory is made in tiles. + +It needs one model file in `ComfyUI/models/diffusion_models` and no other pack. The [help page](js/docs/AUSBOSS_NODES_FlashVSRUpscale.md) has the download link, the memory it needs and the speed. [Try the FlashVSR Video Upscale example →](example_workflows/FlashVSR%20Video%20Upscale%20%28AusBoss%29.json) + ### Save Video Saves a frame batch or a core `VIDEO` as MP4 (H.264 or H.265), WebM (VP9 or AV1), ProRes MOV, lossless FFV1 MKV, GIF or WebP. Which encoders you get depends on your FFmpeg and PyAV build, and H.264 and H.265 come in CPU and NVENC versions. The videos carry color tags, and optionally audio and the workflow. @@ -413,7 +419,7 @@ Turns parts of a workflow off and on from one small card. Select the nodes of on ## Example workflows -These are the workflows where the nodes do something core ComfyUI doesn't do by itself: outpainting and inpainting that put your original picture back, lining an edit back up with its source, upscaling a picture in tiles, masking a thing by its name or with a click, and removing a watermark from a clip. Open a JSON file from [`example_workflows/`](example_workflows), or find them under this pack in ComfyUI's template browser. Each one has a thumbnail, numbered groups and a Workflow Note with the setup steps and model downloads. +These are the workflows where the nodes do something core ComfyUI doesn't do by itself: outpainting and inpainting that put your original picture back, lining an edit back up with its source, upscaling a picture in tiles, masking a thing by its name or with a click, removing a watermark from a clip, and making a clip larger and sharper. Open a JSON file from [`example_workflows/`](example_workflows), or find them under this pack in ComfyUI's template browser. Each one has a thumbnail, numbered groups and a Workflow Note with the setup steps and model downloads. ![The Krea 2 Rotate + Outpaint workflow: a setup note on the left, then groups for your input, the two boxes and the model loaders, and the result with a before-and-after slider and Save Image.](assets/readme/workflow-layout.webp) @@ -434,6 +440,7 @@ The workflows open with their image and video loaders empty, so you load your ow | [Mask by Name](example_workflows/Mask%20by%20Name%20%28AusBoss%29.json) | Type what to find in a picture, and get its mask and a cut-out with a see-through background | ComfyUI's own SAM 3.1 (ComfyUI 0.20 or newer) | | [Mask by Touch](example_workflows/Mask%20by%20Touch%20%28AusBoss%29.json) | Click a thing in a picture, or its whole subject, and get its mask and a cut-out with hair-fine edges | ComfyUI's own SAM 3.1 and its background-removal model (ComfyUI 0.21 or newer) | | [Simple Video Watermark Remover](example_workflows/Simple%20Video%20Watermark%20Remover%20%28AusBoss%29.json) | Find an overlay in a clip and remove it, with a one-frame comparison branch | ComfyUI's own SAM 3.1 (ComfyUI 0.20 or newer), plus `big-lama.pt` | +| [FlashVSR Video Upscale](example_workflows/FlashVSR%20Video%20Upscale%20%28AusBoss%29.json) | Make a clip larger and sharper with one node | `flashvsr_v1.1_fp16.safetensors` | Every example uses only core nodes and this pack. Speed and memory use depend on the models, the sizes, the frame count and whatever else is using the GPU. diff --git a/__init__.py b/__init__.py index cc3a8c6..059804e 100644 --- a/__init__.py +++ b/__init__.py @@ -23,6 +23,7 @@ NODE_MODULES = [ "node_color_match", "node_compare", "node_edit_lock", + "node_flashvsr_upscale", "node_frame_interpolate", "node_free_memory", "node_h3_prompt_writer", diff --git a/example_workflows/FlashVSR Video Upscale (AusBoss).jpg b/example_workflows/FlashVSR Video Upscale (AusBoss).jpg new file mode 100644 index 0000000000000000000000000000000000000000..7bf57686f85047b13918e7e42a25eb6fd9b3d43f GIT binary patch literal 129709 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 z z8(6qC=FcO6Zf`6H(v>-iaiNDtLnUY1SgXB@UC~d#+8^ptjmwwca9}jO}o#A?nmE?0eRRzdYUOH!6A?<8H$q$;0JoWYipu@c71 zcq_H<&chBRZI#|dfCE|o7cWFs0^%Y0uN>0AY%y#QXoJf*OG{Zw+ZY-^C>mfybzlHc z7lEKa+u>JdS|@kRi)YUnDrHq!1+<~o25&@*Mk-0PU4yqFhEC)r?Sw1uf&B@&Mq<0Y zXkOU$L6$0fwH(@EHI8<2P9IG*>5MVM1;smE_6s)e`PB&oVkDm5LJkRAe&x9FwT(-= z*gvn);FeBF3PK<*@^};cJ@dyUfpU86ai>JO3&e}6{BiT`z;183XbA92AE_CtcVD%oTz&&31x{c2p`OtZb`OXl zE+B>iVT~U{F(4+PX^<1Jg*JPzkt1gmsM>edxAiN`caP4ehQ$0J7h~h@*zJiHfxCJl zxwe3LKGiSrA58K$?NYsB$}i?W1~}hw$a0_4cE$vVdlxyb$&EIx1EWd;@Y(+)gPa|^ zplA|z`LB7Ja#8kVC1i2D#$UddbGn3SdYof7UHIxzfv7pkL)+w^GgU)*A5f#+AtG#P zLiJ^9I?#DC3-cTHJr4TXutZA`Jyg?GJ&zc}+UnmB!-d5isN=%o+TN8qP{00xdR4$2{k)0OX;d4Z>jTI?tgxpMG`Udw-kH_kU_2G4@;3YGQ_3nKT9lX9fnleSq-kkt z{?-!bbt_JVFrvF$$V>3sARDtcb#2kHGWL!iwRCA-rp%s$@3&8A?W|V7*z@L-^5RuB zK%$<7Yvn&+6-B^)f1;eMK6cL>x$GreW{Fz6;=!6>)L(Y1F`Y(QCF!DkQr4Tf8j0|M{J!z z;!SjMZrPGmBkhssf>ub3J4LZjdK8n|R1tSPKglHUl@9VC^h zbUSA$jY+$WIjb&kM8zffp~*MEpv_l{j|zX0lHkx3ZO4bie42_d3%Y>VMK}v9eA5vB zIeVLwrb|aM<>a7`>Br*i#7IOmA5O2ofe@rR!iJaO~Leq8e;mf zcLj|k86>S^_DV`bw*%&*>P*h?LzvZwi2PGZ!g@kn88Y9JS>DlFfX+-Nck7sM>9AzA(ivRg8l|o-P=zN@R(fLA++7#8rV(y5EhN{k^3ML@yz7 zNDntOMUn?Ww~|Ij1~s?6JH)Syv4Fm)T%zDE-@BMn7g=3dspped5?L(2?-z`9R2l2+ z;!Uw^S4tEq;?JehN&Xg85W7>yCs6)~m~cdHHjgv%*$?-g$oSPxJr^=2cDrJ6@3HO} zf+73hr7G8v;BYM)t2DYb>jchdOI?=wEP}6gRFZj3(;Uzng-d^PJet=yiTODU}xNQ@hPnFh= zUbV8%=gzBR3L%Q%fH1b4D1N1Oa!olrZ%UD(N(~3VzJsO#eF=6H+r4gRP%DE0BLTS% z+`>S-y~dL5^2fjK3BAh>njUFDGHEm9Z>qLm|I`VLE?9RuH3ODK`Ejv22#zuMh?#~> zUO?`7WdkYIm}li42nQ~%pus(;3(#+Z+(AX*K&~JfxfF_{^;r!oz?5n8$YMkSj!hv(!|GK9Ye)S&bwYc|x zV!|T8YB+zq4fv$AaT?fiHC9aR6g!E~B>To6p)G)D*Jc4~_d zjNXI~2H#gJv$7+V37SKc2wV?sUANKrZq4uDb%tHU zK}E4&16s6-Ue#l;`;`0dXh7{!vcDvl91h`(I1mx!YHowB;)HJ>F|T(4+Q(Qdj194bGFio{!tO`k4%+9(^^`C@PrfQb=)S+lVa=fjfUOyoMo(sDqE+|E|4E!}zD z7zFvXJQB^J)lzB)aZpNZ#b!6Ibv{db$r1ONv}hO%%4cp}(b_yJU?KOKtr+cpw;B@OpA3 zV>`lCsZCG-g~q`LAF8=U?H4%yv-l3Ky_!VnVY|}BHyx)>lNh$q+&qeNW!sa!gc`AL z0rcEOfx~4B*gKuE4lRXRfH_OAbvhZ@p*&y(r!Zoie6l+q5IqWi#b_1EiBS%HxLD)T zh*zLF+msN}3%bl_8kgkV$mf|VGwGs}<~BbtF$J2J*88SB46CAuGr%LxIkdEa(_S(# zXZr_a8Q-0acsp8?J%8zg9*mvn1&G2c15-cp_1j=(ZCckvwry+|wE0x0&Vd3NcWEEc zOmWD6R(+`L9_g#MWMhN7d`}>5nIyijiV; z6o^k>!HJapkE}|59(@6mn#IDS%?mH;VcUm}-G4Bmh&H%~uwI)D>TD2Ii=rqLnW1UK zqpFs@$ZlJzvNJsde_Sv!k^RJ{c_ROu!1TEJ*&PIMQ3i+z73^_2MDe&|8+(WEZ(vUM zxfg!xll79B@|$KqQ5(bh2w`yb+O%bnG+bxGlc??VySeil);T?)CWV;*R&aUM*LRp* zh-&zST)AmoQ53t7j#PvQA z<{{3y<<{!{vA9HXj4RL$yp#UuiYWdN8CB((5tna~#*%GD*=@g*Dgi{Ho;8=S^e-!? z(~!_yUE0JjA=cIS*IB7c_Txn)H~y*El-wJtxyKxE))_6mPZ3D~`HEPXr&0JApS=4> z+|M<8Bp2yxc{OX@hmvq_X)AS?$S?Wx1v{v&qOuLfEL7>o2cu9eHV0N6k*S1t0)lPU z`|s?c;Tm@ue_Z{(YW(fY~?EgP2KUL#YY7t~Cfm7|TE%!vv3DLE89{7JLRS|%Lav@fF8exG3ivLKs^*yTvC4J4)Wks zOD7mwfnmTSIFBgTVOd!Z7+!ldCSJR*L@0aG)+*m66<9#Dd zP-Ur2$}mRdllp$H+@seujBp+1tkgjzcNSm(THgQ2wUPG2jIOZotxlZ<@A&a@8ON!L z$3Y>tzUlBQlE)^{UT_Llx=?r!1HIdZ);q@mdWM@;#GXp8 zjfwF-+{wh*GP80qHs|cWSA?Q{s#)BM2_o&hFjjZL75FzAJY3 zC&7&F2XaVr{=+ci&@Ew>hd z?AF}y8IR1;L*vGG$=N zF;TK{6>@?_agT!E*x5PA$bXg7OvF&gD3few-15;4X^B~2+%NlL9EW--Em8AoGRH~$ zDzmGG#MglZ-2-=Pyk9TO%pyiUQ^s{R7s|UzK5=@u(J607#xbz=V zJ$>;;5yL7lL-1YGaL{ZC?+S*FgJSv$kvP@ia$MNo zR%+K_ttKDDc2(4|7R^EU?^))O2Zn8cFld{Lf~dTT6gT)ynV(W@1%}jx&mFO48T_Vik<|!Ksdn@Swm)JG$=XrIqv*X9_CukcnS3ZC8>o1W%e<9^X zi!gL0oLUAde9cxQmFLVh8RO$m_wgkYuXnNOA|4K#-BsmtQ{g8%WGq*-bj#G1I4(C1 z_+I$RN;XDaTnpYkV0)rgjFBO35r|-bw1|Dn_T1l8zG9R+FTjEAaDC;L=oa+YQIRy$ zHAx|j$68Qx|3Gxt8gVveoa>g?vl>_N@+B!LJ6vpX=HXfDsg4|-cPT>W~L64)IrGD*YpW8O(NV~c&Ih_bQBYgE)YB#ZD<7x9!U)j{!-!V+1V9P*G!vKJcw#5#a>3?ebDo0wK zP|}U_$!@sN22tfuTeHh4>wgY4FUJ53>2LjuZc0H+Anj(iFY*IF(8&NdORhZ~HsHog z`wK5XwB+#YmnmIo;okIn7Tu$wQR&WS>3aAJi)qNt+|p75V(nM%M@8k%n`%tt?RbN1 zzV1cUgG%MiEi6?8*9S$D=Q2p`ffoIb+KDZtb?ujEg{>&_eXPB2o42*p zz+utsdoxNvnMhL=5j-CxUt?#R(Z6HAWD^siK?GT>xP>?PSj?qG)B@M)(hg;QTVPRH zSOq;pj1(A=%SRnFVz&ifExNGVby<)Z;(<7w?j5GYT&N?WOL^!Q~=3XtNaSx^@LH zUaAwg>%rA&8F9&k<#a9s84`3;R~(>2DML$TFlnom*iA9;>TQ>3Q9%O$l4DlDTQ?~) z7;VN6K#SphQ(*2CS*q)|rg(1WxB(&_iYm#D`zkhp^*?ySn@8XI`N9Wbe?g z+LnCog@AiYZ5*&ja@#3Jw;WbqJWgunEgQ=R34tpOxkk)gu!*wlQ&|D*v(C1~uMOSU z%(neBjvZ5~RkaU-FU|^1oFybkd~CZpKf(Titk+O!zY%J$@?!WsOudVsG?u&nap#Z9 zm%NB*+4%EvaT~ZTXjnZx)>lcs3UDWAqOaZ&-)4SW99o2`AMhnbMJgn%KAf?dTHRf# znzoDd=+KY3BF67j&3pPbHNzm|-sxDmLrjvekc~)mMh~j*V}HE_nd8dW&2us@b}=}O zps^L*5@VUDF3S<8)qCL_Q^VPu zRg^UyVEM7DI;&$h|D|%tqIp*4#nn5eKc=St2~ioRtrZ_ogd!lSLv3rl#Ek`OPaCnSAa+{_7sM1G=w zt^}Gm3Uc`Z*NB-!S9P&7sZ7q-1@AHUwcpA9k-^36l5QhkNzxn?a#0t|jHWB=5WZTE zM}{6R>$GJOr9{tFCQkEp*-4xGwo}HB3PSG7LdGeL|0I#>znBCT|yEmiTk$VNS z6(bt$ZuV__B7&J*Q#W^&bx~T0Qc=-%+{)?BGSS{aRSY@edRE>wOpcLyNrw+K) zz8~cl1RGmHS9RYgbzG-bj2*J4AL3QCV%9A*Ksy5(`QVN85b=0Y01|Qb#EM{ffs{kI z_Jf+t6+g&Xp6~!Kzs_ysML3QPe(M^%5 z46QbERO2=Lr&eeb`HIlemEwBmgehi{F|tKSGu}0eZi3Mk+eW$ECmceX#93p6yV2ih z2&PLEhBjVUzehk7VwJjy_Xb&vH47;Q@O+Q=1@HBf+4FCPl`#TVr-3WKdoUoYE)?>E zS#S6JJA1sL{5jr@+T{Y{(i<(c8tI?v&3IYc$R*?~g_M2yPVfY;f7)Ld&xj*pzJ@%d z4niX&I2`+GpJs6tENKhf7)c`e`S2E!Wk@^j{(JTff*kUnl$+y~z4#{A)-^X=w)P7I zHXG&8OldA6UDMxwIa777Dwq>4MIo=)71y^8hh00>*|?JR-k>tI%2X2iI-xZTSFMQj3SkO*TtVE&_ zgeThV?JRcdy*D!5KIkQxiRwe@=i&sT<1yUO9`s0t2C2|WYP58i8Eu$uYQ6~FDrF2_ zQN%lzcq$GAnnDKD@ zJ9TCXg#K?w9iIM+cfFjZ%v?l6Qf0)B80PdpVMGCT3iutkT-rXoLiw%2BfAp&pU!(t zUX1f$$6gFJ!-YNI{2p+9-oyJD0is0#X%v2I&MaG!1-nNcC9$|v&tO z`8+;*?6cSF^E}tBwEI*+Y1l16+3Dm@AbLGC;74(5p*LjPBVlLOj6^_r3mFo8ZM|^P*ud|EveVSNUkN zgi%rQ9%)3@s#ul*uY@*(Hs&(eWF>Mn7L3bL$ZU- zYSwj1^ziOcfA1VYzPX~RxH3V1pT%lsg7-TeH0jqi#--#E{9xUialT3C%nCsTuOb){ z)-NHt!e%Q_vubSSt-uc~72+zzm%d?{+vknA48< zst)!r;xA;(5Qvx8k?O6hr-7Yk#%Chs>u^xP&)Taf>6V=aG_~w|h?6TNCcHCx_G`av z!LQ0oZ2N!#p1G)QaQh@|Sn~C6R{xYIX9RK&)+bwV`>IzRb<q>DB|7IPoZ6R6IrD6heLZN4uktSS_P+k)%wk~ioZHiE zhAz?L2YjVoSi2FDnqE|}uQbvYUazvh@*+mN2|94z)>1-qhR?l*w+_7@OSGLReX?G4oKz_gE8zCa@|v1Os@wO; z?B{hnZ(oAyLE*FU!u%}$j@H46?n8B7s8!X@OG}Ij-0(x|0+Az3Yj6%f*taOWx{F$V z*fM;;x9dXmuZk=;=0*2@1Q&{xr)+o{@vCb^V~x4p=vX)M)$BVorAtNxHkW@t)I~}J z=E=(~W9{@>t9skF7fmL?uZi69L=sJf&pZxBa3#oqy`#@$Jc|}6!>(<|w!+SY&dEr} zY2;u6tDowGQqanLa~Mv>9%Zq5))sTSRITsHqk>bSmP4*wq@HK;k%Xsr-UO{`NA*Z( zRmX?@hL!^e2% zvl1Et!&3Z(VyB~ioNhlti8m%P@d+iF=~&dkEhIQO9;@{D=c=BlHc?);R(bip3WpF%2=1|?4aMnTBEewzJ zq?xHk$VDz6M~1|?;2zWb*JkfIML%mYec&0Gwu6i>Mk4}ddzV)JwPiUZOQ4Y&bDq0htqwgSNs9)2D6`PwG7A3Qki z_TW|gQ{z8Gjz}nLi>vNK2Sgd6LRkDID#7UUR@BoA<7hjLuq7_!wKy7r8PFmJQPqRrwujmB_4HvJ&2MuB&EA&W&`+BEDDlVc!DZvUG>JEOQ_V=Vcoe^yUL4ZAd7 z#Bt&co+csrq>b>xSQ%5hEU9}v814Aa9#6P(b#*DGBJFC)LD~)(SN^*B5Cf|m+=a{V z6*8d%5!uW&np~d+KEQ>Y2j?;lm5C(lBa|*(sCaH@%s6vH-yjYvng}nKiEH_#GgH{s$~Kl*GE^aUwL|;EG$103w&5M!~;}x=GQ-30rSx~)yx6BL~T3bsbuJ0 zbH66oUzSO~5ZC4Zl_eh)`eE1eG;|5=g8Ad*noS}M5!U4kyfe#GM?ama!(;MSfMkJM z8Pfn#UJq1(uc>BT-rxIHF1@rbFn!_?%46l=Z^*yIgxL*cf4bHL2k?L<(RK}|v8s1d zb$tre*JSg@t^=3D2kp#5V^}WtwB>Xwf`_I%bQ;6R3{p`RBJ_7iO z#Yc0cG--vaAU_R}xua?>$vjJ?+ZGjoA?#;>7CUS5=Au)?$t8tEDSAbU!z0sHA4hT*t*36&O$;QXrNWG(zIetx$9W)(DCSFr#yA zI74~k`6^Rg6`h5Lqy0zn7HA|z=TvDBrJQd-i@_MD&I6?=JTDe>gWNF*EoW%}_zA2` zmGqLA2?{pCE1vD*04{^J9c!LJ1H?!Ci6B_M-q780tmmiJ?azsUZzsjSfVST`{o?J`Jn*~AgXKS zO)kT|etL9=zV-9mAsoof9zV4YSTeJMol|tHp=T?Fx|bWW)N%XtUHLMSnh`lX=G%49 zN#o#3oNTY2Y~%ZyMq8No9@$f0Jgt zM?Mw_6An~*_!J^SImmn(uJ(RF4eIaKPje)tl?W<7I~{6>D<&fYqn`RYE^kNplE-Y% z=scSHX3_9KT{S4K@3hjS2a-?~mh~OeVs83IPxHwu_OZrup;>CJ3p+xADld}$LXTGG zrRDHE@>yT7>mZWZko9@3yEa%YY+G6)`yK7-wwg=tD3a>fW}41@3Gqgw>Vw-SjJ?`c zT%T!-wol50M55CB1n`fwg(8Xfa$$Cy8ty6rqZdcFB-LINl~;j1e#kJz>220oC(}0p z3ucVxlwNihuh^`3;nX849D{eU6m&GI}b&gY$K!I2g<37cf|R!pq2#^hP^K< zlKJ{2x)PWm$x!^6<&$1Y2wnjqzvk_JRNquCjm1R;V{^~#?zzU=`-Y6|y!^5SBXNG0Ck2kDLr=S+GfYGT zh(H8dR2-vNA3#*IO2X%xS{{x^ZWYSAQAVCT7z%t1dz*$z5rz%U6-oWIg!0dA30_P* z-b8Jhmz-+inAYzlrx7r4Q2`2GesRUi?5)y#7@1#LeDDVtvASW-_P6T4kVg|TE-sNR z0*a%$HGX86%A?6C?@%L>KbfF<4KmCZ#WKWn{pC3x3{>omL-r;r-C}F`uey$4AEe3- zf6>TvL`00Eag{T0>OWV;qa^$Sw>OmK0%usX3%m8Hu!Zsx_}OQ+Tys{R+*t{ex? zCFLo504=Z8i|wJ?7wUY$gl^W$pWQ*AX;w|OP-VI3ZXmz}V7kV?@8dLox;GnF@!$K) zBqP^_rR`P%O;PA2a5#Z1M*8{CL^{Kf0<;-K&9REa6=SIoaPvDZ?V}=&cE%4Q+rDR@3Nof#E;zHab*T}$<@==3UF zD!0u_TNW5aXk(~HnO8wv5&{%Lh{Et!+a?=@kY$m?x329HAI!wL2PUGvS#OuC3`2|m zYzKyyjGAXkp!u7zF8Qa_zqL39lAfIzAs)+`2i5WGR&&o)z{NCxfY(Cz-&3{RasnO= zbAW7Ude+Q^p#nBqYs@PVb#i`CU@{3mD%ucdtnYKs?%F;)YU^94=~7DBv6$ zwjrl}|9Q3-_m^z#6DaaMvNdrGu zFzS{y=_7!&Sbp&@LD9ofQjOL7Wkr z2ny~m!4?n6W*K=v8f1miBgRVxncfR^K^WpDTbx)O6o=amWjC{QhY);@ZpI(2C+9bP zG!od#$XUlbb71QC1X!PC)uq9xy8tgwYxEp}nC=|=CJ zp^bu(>O)hn@0Qn-?+&gT+R9CiEe<3Sw|J&zR63lmEEA_P_KV5~cwp=t1AACZQH0u^<9al6*n@tkkxx9^|gPYyNcv*h?e_0hlw zj&sCqiz+bc_oNpFZ_S}^7$c#J4LtUakJfA4;~xIHmFoT3)@J-z{jr3g{J2Cfe#5jJ zXgq^w%mf?GmR`0TfWFh81Ys~}ID>zqd z+|*u{aR%V8HO6XvM>(eiFv1jAQ`xh3EprTU%%~h^!5P74CaJi_HF9!dHrIVwwfH0p zzupB~#V~!1Rt&#(Ka=fRS8s;myvc>bKZ>c*{6-vA&B7 zyzCZ+W53XQg=yo-nSBi$XMaF0c6htFbVlYcX5Q=8_54)aue~oJ?^Ydc!&Jj$D}X}w z_hiP5Rf@iSUnTt#4p}uHdBNqgg}<04#OD+1?Q>fEaNr%DBAjt*ZdCt^G{^?p7JZ1&8WJ;!(og8kaN#U7UD_78`d3;cMFeRw}bYZp3j&)RHG24 z(>zx7ilzHd>WO~s0p4Ary^c5+l3$He{ef)mVKVBd;o~CJ6h}+7dR6NO&MlOgTqrN? za}-oAsT3mnD%+sRP>WEj;LDUcz(R14f%JRpbeAcqi}Sv_qDo9kZhY7dQD({jHaNsQ bhOhAG3PjWe$!?rIuP_perf99j{5|nM;1ix@ literal 0 HcmV?d00001 diff --git a/example_workflows/FlashVSR Video Upscale (AusBoss).json b/example_workflows/FlashVSR Video Upscale (AusBoss).json new file mode 100644 index 0000000..aebac8d --- /dev/null +++ b/example_workflows/FlashVSR Video Upscale (AusBoss).json @@ -0,0 +1,526 @@ +{ + "id": "0b455c1f-6b0c-4f69-8a7e-86f8de61df5c", + "revision": 0, + "last_node_id": 5, + "last_link_id": 4, + "nodes": [ + { + "id": 1, + "type": "AUSBOSS_NODES_WorkflowNote", + "pos": [ + 0, + 30 + ], + "size": [ + 440, + 640 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "note", + "type": "STRING", + "link": null, + "widget": { + "name": "note" + } + } + ], + "outputs": [], + "title": "🆎 AusBoss", + "properties": { + "cnr_id": "ausboss-nodes", + "ver": "2.24.0", + "Node name for S&R": "AUSBOSS_NODES_WorkflowNote" + }, + "widgets_values": [ + "{\"v\": 1, \"title\": \"FlashVSR Video Upscale\", \"subtitle\": \"Make a video larger and sharper with one node and one model file.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"[▶ AusBoss on YouTube](https://www.youtube.com/@ausbossdev) · videos on these nodes and workflows\\n\\n---\\n\\n1. Load your video.\\n2. Set **Scale**: how many times larger each side gets. At 1 the video keeps its size and is only sharpened.\\n3. Press **Run**, then play the result in **Save Video**.\\n\\nTo try a short piece first, drag the right end of the bar under the player in Load Video.\\n\\nA long video needs no more graphics memory than a short one. A large result is made in tiles, so it fits the memory you have and takes longer.\\n\\nNeeds AusBoss nodes 2.24.0 or newer. No other pack is needed.\", \"models\": [{\"name\": \"flashvsr_v1.1_fp16.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"3.5 GB\", \"url\": \"https://huggingface.co/ausboss/FlashVSR-v1.1-ComfyUI/resolve/main/flashvsr_v1.1_fp16.safetensors\", \"note\": \"FlashVSR v1.1 by Junhao Zhuang and the OpenImagingLab team, in one file\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_FlashVSRUpscale\"}], \"links\": [{\"label\": \"YouTube · ausbossdev\", \"url\": \"https://www.youtube.com/@ausbossdev\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}", + "" + ], + "widgets_values_named": { + "note": "{\"v\": 1, \"title\": \"FlashVSR Video Upscale\", \"subtitle\": \"Make a video larger and sharper with one node and one model file.\", \"author\": \"AusBoss\", \"layout\": \"card\", \"accent\": \"\", \"body\": \"[▶ AusBoss on YouTube](https://www.youtube.com/@ausbossdev) · videos on these nodes and workflows\\n\\n---\\n\\n1. Load your video.\\n2. Set **Scale**: how many times larger each side gets. At 1 the video keeps its size and is only sharpened.\\n3. Press **Run**, then play the result in **Save Video**.\\n\\nTo try a short piece first, drag the right end of the bar under the player in Load Video.\\n\\nA long video needs no more graphics memory than a short one. A large result is made in tiles, so it fits the memory you have and takes longer.\\n\\nNeeds AusBoss nodes 2.24.0 or newer. No other pack is needed.\", \"models\": [{\"name\": \"flashvsr_v1.1_fp16.safetensors\", \"dir\": \"diffusion_models\", \"size\": \"3.5 GB\", \"url\": \"https://huggingface.co/ausboss/FlashVSR-v1.1-ComfyUI/resolve/main/flashvsr_v1.1_fp16.safetensors\", \"note\": \"FlashVSR v1.1 by Junhao Zhuang and the OpenImagingLab team, in one file\"}], \"packs\": [{\"name\": \"ComfyUI-AusBoss\", \"url\": \"https://github.com/ausboss/ComfyUI-AusBoss\", \"node\": \"AUSBOSS_NODES_FlashVSRUpscale\"}], \"links\": [{\"label\": \"YouTube · ausbossdev\", \"url\": \"https://www.youtube.com/@ausbossdev\"}, {\"label\": \"GitHub · ausboss\", \"url\": \"https://github.com/ausboss\"}, {\"label\": \"Civitai · AusBoss\", \"url\": \"https://civitai.com/user/AusBoss\"}, {\"label\": \"X · Zanzibased\", \"url\": \"https://x.com/Zanzibased\"}]}", + "ausboss_workflow_note_panel": "" + }, + "color": "#14424d", + "bgcolor": "#161f21" + }, + { + "id": 2, + "type": "AUSBOSS_NODES_LoadVideo", + "pos": [ + 500, + 80 + ], + "size": [ + 420, + 760 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "COMBO", + "link": null, + "widget": { + "name": "video" + } + }, + { + "name": "start_seconds", + "type": "FLOAT", + "link": null, + "widget": { + "name": "start_seconds" + } + }, + { + "name": "end_seconds", + "type": "FLOAT", + "link": null, + "widget": { + "name": "end_seconds" + } + }, + { + "name": "custom_width", + "type": "INT", + "link": null, + "widget": { + "name": "custom_width" + } + }, + { + "name": "custom_height", + "type": "INT", + "link": null, + "widget": { + "name": "custom_height" + } + }, + { + "name": "every_nth", + "type": "INT", + "link": null, + "widget": { + "name": "every_nth" + } + }, + { + "name": "max_frames", + "type": "INT", + "link": null, + "widget": { + "name": "max_frames" + } + }, + { + "name": "single_frame", + "type": "BOOLEAN", + "link": null, + "widget": { + "name": "single_frame" + } + }, + { + "name": "upload", + "type": "IMAGEUPLOAD", + "link": null, + "widget": { + "name": "upload" + } + } + ], + "outputs": [ + { + "name": "frames", + "type": "IMAGE", + "links": [ + 1 + ] + }, + { + "name": "audio", + "type": "AUDIO", + "links": [ + 3 + ] + }, + { + "name": "frame_count", + "type": "INT", + "links": [] + }, + { + "name": "fps", + "type": "FLOAT", + "links": [ + 4 + ] + }, + { + "name": "width", + "type": "INT", + "links": [] + }, + { + "name": "height", + "type": "INT", + "links": [] + }, + { + "name": "duration", + "type": "FLOAT", + "links": [] + }, + { + "name": "video", + "type": "VIDEO", + "links": [] + } + ], + "title": "Load Video - Your video", + "properties": { + "cnr_id": "ausboss-nodes", + "ver": "2.24.0", + "Node name for S&R": "AUSBOSS_NODES_LoadVideo", + "ausboss_load_video_loop": true + }, + "widgets_values": { + "video": "", + "start_seconds": 0, + "end_seconds": 0, + "custom_width": 0, + "custom_height": 0, + "every_nth": 1, + "max_frames": 0, + "single_frame": false + }, + "widgets_values_named": { + "video": "", + "start_seconds": 0, + "end_seconds": 0, + "custom_width": 0, + "custom_height": 0, + "every_nth": 1, + "max_frames": 0, + "single_frame": false, + "upload": "image", + "ausboss_widget_card": "", + "ausboss_load_video_viewer": "" + }, + "color": "#14424d", + "bgcolor": "#161f21" + }, + { + "id": 3, + "type": "AUSBOSS_NODES_FlashVSRUpscale", + "pos": [ + 980, + 80 + ], + "size": [ + 360, + 190 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "frames", + "type": "IMAGE", + "link": 1 + }, + { + "name": "vae", + "type": "VAE", + "shape": 7, + "link": null + }, + { + "name": "model", + "type": "COMBO", + "link": null, + "widget": { + "name": "model" + } + }, + { + "name": "scale", + "type": "FLOAT", + "link": null, + "widget": { + "name": "scale" + } + }, + { + "name": "color_match", + "type": "BOOLEAN", + "link": null, + "widget": { + "name": "color_match" + } + } + ], + "outputs": [ + { + "name": "frames", + "type": "IMAGE", + "links": [ + 2 + ] + } + ], + "title": "FlashVSR Upscale", + "properties": { + "cnr_id": "ausboss-nodes", + "ver": "2.24.0", + "Node name for S&R": "AUSBOSS_NODES_FlashVSRUpscale", + "models": [ + { + "name": "flashvsr_v1.1_fp16.safetensors", + "url": "https://huggingface.co/ausboss/FlashVSR-v1.1-ComfyUI/resolve/main/flashvsr_v1.1_fp16.safetensors", + "directory": "diffusion_models" + } + ] + }, + "widgets_values": [ + "flashvsr_v1.1_fp16.safetensors", + 2, + true, + "" + ], + "widgets_values_named": { + "model": "flashvsr_v1.1_fp16.safetensors", + "scale": 2, + "color_match": true, + "ausboss_widget_card": "" + }, + "color": "#14424d", + "bgcolor": "#161f21" + }, + { + "id": 4, + "type": "AUSBOSS_NODES_SaveVideo", + "pos": [ + 1400, + 80 + ], + "size": [ + 440, + 640 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [ + { + "name": "frames", + "type": "IMAGE", + "link": 2 + }, + { + "name": "audio", + "type": "AUDIO", + "link": 3 + }, + { + "name": "video", + "type": "VIDEO", + "link": null + }, + { + "name": "fps", + "type": "FLOAT", + "link": 4 + }, + { + "name": "filename_prefix", + "type": "STRING", + "link": null, + "widget": { + "name": "filename_prefix" + } + }, + { + "name": "crf", + "type": "INT", + "link": null + }, + { + "name": "format", + "type": "COMBO", + "link": null, + "widget": { + "name": "format" + } + }, + { + "name": "pingpong", + "type": "BOOLEAN", + "link": null, + "widget": { + "name": "pingpong" + } + }, + { + "name": "save_metadata", + "type": "BOOLEAN", + "link": null, + "widget": { + "name": "save_metadata" + } + } + ], + "outputs": [ + { + "name": "file_path", + "type": "STRING", + "links": [] + } + ], + "title": "Save Video", + "properties": { + "cnr_id": "ausboss-nodes", + "ver": "2.24.0", + "Node name for S&R": "AUSBOSS_NODES_SaveVideo", + "ausboss_save_video_loop": true + }, + "widgets_values": { + "fps": 24, + "filename_prefix": "flashvsr_upscaled", + "crf": 19, + "format": "mp4 h264", + "pingpong": false, + "save_metadata": true + }, + "widgets_values_named": { + "fps": 24, + "filename_prefix": "flashvsr_upscaled", + "crf": 19, + "format": "mp4 h264", + "pingpong": false, + "save_metadata": true, + "ausboss_widget_card": "", + "ausboss_save_video_viewer": "" + }, + "color": "#14424d", + "bgcolor": "#161f21" + }, + { + "id": 5, + "type": "AUSBOSS_NODES_RunTimer", + "pos": [ + 1515, + 770 + ], + "size": [ + 210, + 67 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [], + "title": "Run Timer", + "properties": { + "cnr_id": "ausboss-nodes", + "ver": "2.24.0", + "Node name for S&R": "AUSBOSS_NODES_RunTimer" + }, + "color": "#14424d", + "bgcolor": "#161f21" + } + ], + "links": [ + [ + 1, + 2, + 0, + 3, + 0, + "IMAGE" + ], + [ + 2, + 3, + 0, + 4, + 0, + "IMAGE" + ], + [ + 3, + 2, + 1, + 4, + 1, + "AUDIO" + ], + [ + 4, + 2, + 3, + 4, + 3, + "FLOAT" + ] + ], + "groups": [ + { + "id": 1, + "title": "1 · Your video", + "bounding": [ + 480, + 0, + 460, + 880 + ], + "color": "#1d5560", + "flags": {} + }, + { + "id": 2, + "title": "2 · Make it larger", + "bounding": [ + 960, + 0, + 400, + 880 + ], + "color": "#1d5560", + "flags": {} + }, + { + "id": 3, + "title": "3 · Result", + "bounding": [ + 1380, + 0, + 480, + 880 + ], + "color": "#1d5560", + "flags": {} + } + ], + "definitions": { + "subgraphs": [] + }, + "config": {}, + "extra": { + "ds": { + "scale": 0.73, + "offset": [ + 137, + 20 + ] + } + }, + "version": 0.4 +} \ No newline at end of file diff --git a/js/docs/AUSBOSS_NODES_FlashVSRUpscale.md b/js/docs/AUSBOSS_NODES_FlashVSRUpscale.md new file mode 100644 index 0000000..0e8209d --- /dev/null +++ b/js/docs/AUSBOSS_NODES_FlashVSRUpscale.md @@ -0,0 +1,69 @@ +# FlashVSR Upscale + +Makes a video larger and sharper. Give it the frames of a clip, set how much +larger, and it returns the same clip with more detail. Use it as the last +step before Save Video, for a generated clip or one you loaded. + +It needs one model file and no other pack. + +## The model file + +Download `flashvsr_v1.1_fp16.safetensors` (3.5 GB) from +[ausboss/FlashVSR-v1.1-ComfyUI](https://huggingface.co/ausboss/FlashVSR-v1.1-ComfyUI) +and put it in `ComfyUI/models/diffusion_models`. Then refresh the page, or +press `R`, and pick it under **Model**. + +The file holds the official FlashVSR v1.1 weights in one piece. Another +FlashVSR file will not load here: the node stops and says which part is +missing. + +## Controls + +- **frames**: The clip, as a batch of frames. Load Video's frames output + goes here. +- **Model** (`model`): The model file above. The list shows the files in + `diffusion_models` that have FlashVSR in their name. +- **Scale** (`scale`, starts at `2`): How many times larger each side gets, + from 1 to 4. At `1` the clip keeps its size and is only sharpened. +- **Keep colors** (`color_match`, starts on): Brings each frame's colours + back to those of your clip. Leave it on unless you want the model's own + colours. +- **vae** (optional): Connect the Wan 2.1 VAE for a slower, finer finish. + With nothing connected, the fast decoder inside the model file makes the + picture. + +## Outputs + +- **frames**: The larger frames, as many as went in, in the same order. + The frame rate and the sound are not touched, so wire those from Load + Video to Save Video as before. + +## What to expect + +FlashVSR redraws fine detail: skin, hair, cloth and edges come back +sharper than the clip had them. It keeps the scene, the motion and the +timing. + +A long clip needs no more graphics memory than a short one. The node reads +the clip a few frames at a time and keeps only the last few in memory. + +## Technical details + +- **Size.** The result is exactly your size times **Scale**, rounded to + even numbers. Inside, the model works at a multiple of 128 pixels each + way; the node pads the picture for that and cuts the padding off again, + so nothing of your picture is cropped. +- **Graphics memory.** About 10 GB for a 576 x 1024 result. A result too + large for the free memory is made in overlapping tiles and blended, and + the tile size is chosen from the memory that is free when the node runs. + 1152 x 2048 ran in two tiles at about 16 GB. On an 8 GB card (RTX 3070 + Laptop) the same result ran in smaller tiles at about 6.5 GB, at about 3 + seconds a frame. +- **Speed.** On an RTX 5090: about 7 frames a second for a 576 x 1024 + result and about 2 frames a second for 1152 x 2048. +- **One step.** The model runs once per group of frames. There are no + steps, sampler, prompt or seed to set. +- **The model.** FlashVSR v1.1 by Junhao Zhuang and the OpenImagingLab + team, released under Apache-2.0. It is built on the Wan 2.1 1.3B video + model. This node is the pack's own code in plain PyTorch. +- The node makes no network requests and writes no files. diff --git a/js/docs/agents/README.md b/js/docs/agents/README.md index 3dc89e4..6a3b8f3 100644 --- a/js/docs/agents/README.md +++ b/js/docs/agents/README.md @@ -38,6 +38,7 @@ help panel. | Load a sound file and keep only part of it, up to a set length | Load Audio (`AUSBOSS_NODES_LoadAudio`) | | Turn, crop and pad a video: one frame, or the whole clip | Video Crop + Rotate + Pad → Frame (`AUSBOSS_NODES_VideoCropRotatePad`), → Clip (`AUSBOSS_NODES_VideoCropRotatePadClip`) | | Change a clip's frame rate | Frame Interpolate (`AUSBOSS_NODES_FrameInterpolate`) | +| Make a clip larger and sharper | FlashVSR Upscale (`AUSBOSS_NODES_FlashVSRUpscale`) | | Work with frame batches | Select Frame, Select Every Nth, Split Batch, Merge Batches (`AUSBOSS_NODES_SelectFrame`, `_SelectEveryNth`, `_SplitBatch`, `_MergeBatches`) | | Run a workflow once for each line of text, such as a list of prompts | Text Lines (`AUSBOSS_NODES_TextLines`) | | One shared seed, number or text | Seed, Integer, Float, Text (`AUSBOSS_NODES_Seed`, `_Integer`, `_Float`, `_Text`) | diff --git a/js/widget_cards/index.js b/js/widget_cards/index.js index 725f5b8..53583bb 100644 --- a/js/widget_cards/index.js +++ b/js/widget_cards/index.js @@ -290,6 +290,14 @@ const CARDS = { { widget: "batch_size", label: "Batch" }, ], }, + AUSBOSS_NODES_FlashVSRUpscale: { + minWidth: 340, + rows: [ + { widget: "model", label: "Model", kind: "select" }, + { widget: "scale", label: "Scale", suffix: "×", decimals: 2 }, + { widget: "color_match", label: "Keep colors" }, + ], + }, AUSBOSS_NODES_AlignImage: { minWidth: 300, rows: [ diff --git a/locales/zh/nodeDefs.json b/locales/zh/nodeDefs.json index 66a100e..eae2f95 100644 --- a/locales/zh/nodeDefs.json +++ b/locales/zh/nodeDefs.json @@ -1681,5 +1681,31 @@ "AUSBOSS_NODES_WorkflowNoteTile": { "display_name": "工作流说明卡片 🆎", "description": "点击打开完整说明的小按钮。点击铅笔编辑,拖动角落调整大小。旧版节点包会将它显示为缺失节点,请更新 ComfyUI-AusBoss。" + }, + "AUSBOSS_NODES_FlashVSRUpscale": { + "display_name": "FlashVSR 视频放大 🆎", + "description": "用 FlashVSR 模型把视频放大并变清晰。选好模型文件,设定放大倍数,然后运行。节点每次只读取几帧,所以长视频和短视频占用的显存一样多,每一帧都会返回。接上 Wan 2.1 VAE 可以得到更慢但更细致的结果。", + "inputs": { + "frames": { + "name": "帧" + }, + "model": { + "name": "模型" + }, + "scale": { + "name": "放大倍数" + }, + "color_match": { + "name": "保留颜色" + }, + "vae": { + "name": "vae" + } + }, + "outputs": { + "0": { + "name": "帧" + } + } } } diff --git a/nodes/_flashvsr_helpers.py b/nodes/_flashvsr_helpers.py new file mode 100644 index 0000000..409ae03 --- /dev/null +++ b/nodes/_flashvsr_helpers.py @@ -0,0 +1,946 @@ +"""FlashVSR video upscaling: the model's parts and the loop that runs a clip. + +FlashVSR (OpenImagingLab, Apache-2.0) sharpens a clip in one model step. +It is a Wan 2.1 1.3B transformer that is also shown the low-quality frames, +a small projector that turns those frames into tokens, and a small decoder +that reads the frames again while it draws the result. + +Everything here is plain PyTorch. The model looks at a clip a few frames at +a time and keeps a short memory of the frames before, so a long clip costs +no more graphics memory than a short one. Module and parameter names follow +the names in the published weights, which is what lets them load. + +Shapes used below: a token is 16x16 output pixels of one latent frame, a +latent frame is 4 video frames, and an attention block is 2 latent frames +by 8x8 tokens (128 tokens, 128x128 pixels over 8 frames). +""" + +from __future__ import annotations + +import math +import os + +import torch +import torch.nn as nn +import torch.nn.functional as F + +PIXEL_STEP = 128 # the working size is a multiple of this in both directions +BLOCK_TOKENS = 128 +KEPT_GROUPS = 3 # earlier 2-frame groups each layer remembers +LOCAL_REACH = 11 # a block looks at blocks inside an 11x11 neighbourhood +SPARSE_RATIO = 2.0 +SPARSE_REFERENCE_AREA = 768 * 1280 +FIRST_PASS_LATENTS = 6 +PASS_LATENTS = 2 +MIN_GROUPS = 3 # the first pass alone needs 25 frames +TIMESTEP = 1000.0 +DEFAULT_SLOT_BUDGET = 12000 + + +def _cast(parameter: torch.Tensor, like: torch.Tensor) -> torch.Tensor: + return parameter.to(device=like.device, dtype=like.dtype) + + +# -------------------------------------------------------------------------- +# Sizes and frame counts +# -------------------------------------------------------------------------- + + +def _even(value: float) -> int: + return max(2, int(round(value / 2.0)) * 2) + + +def plan_sizes(height: int, width: int, scale: float) -> tuple[int, int, int, int]: + """Output size (even numbers) and the padded size the model works at.""" + out_h, out_w = _even(height * scale), _even(width * scale) + work_h = math.ceil(out_h / PIXEL_STEP) * PIXEL_STEP + work_w = math.ceil(out_w / PIXEL_STEP) * PIXEL_STEP + return out_h, out_w, work_h, work_w + + +def plan_groups(frame_count: int) -> int: + """How many 8-frame groups the padded clip has. + + The model reads 8n+1 frames and returns 8n-3, so the clip is padded by + repeating its last frame until 8n-3 covers every frame it has. + """ + return max(MIN_GROUPS, math.ceil((int(frame_count) + 3) / 8)) + + +# -------------------------------------------------------------------------- +# Position encoding and attention blocks +# -------------------------------------------------------------------------- + + +def _axis_angles(dim: int, positions: torch.Tensor) -> torch.Tensor: + steps = torch.arange(0, dim, 2, dtype=torch.float64)[: dim // 2] / dim + return torch.outer(positions.double(), 1.0 / (10000.0**steps)) + + +def rope_table(head_dim: int, first_frame: int, frames: int, rows: int, cols: int, device): + """Rotation angles for every token, as (cos, sin) of shape [tokens, 1, head_dim / 2].""" + side = head_dim // 3 + t = _axis_angles(head_dim - 2 * side, torch.arange(first_frame, first_frame + frames)) + r = _axis_angles(side, torch.arange(rows)) + c = _axis_angles(side, torch.arange(cols)) + angles = torch.cat( + [ + t[:, None, None, :].expand(frames, rows, cols, -1), + r[None, :, None, :].expand(frames, rows, cols, -1), + c[None, None, :, :].expand(frames, rows, cols, -1), + ], + dim=-1, + ).reshape(frames * rows * cols, 1, head_dim // 2) + return angles.cos().float().to(device), angles.sin().float().to(device) + + +def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor, heads: int) -> torch.Tensor: + """Rotate each pair of channels of x [tokens, dim] by its token's angle.""" + tokens, dim = x.shape + a, b = x.float().view(tokens, heads, dim // heads // 2, 2).unbind(-1) + return torch.stack((a * cos - b * sin, a * sin + b * cos), dim=-1).view(tokens, dim).to(x.dtype) + + +def to_blocks(x: torch.Tensor, frames: int, rows: int, cols: int) -> torch.Tensor: + """[tokens, dim] in (frame, row, column) order -> [blocks, 128, dim].""" + dim = x.shape[-1] + x = x.view(frames // 2, 2, rows // 8, 8, cols // 8, 8, dim).permute(0, 2, 4, 1, 3, 5, 6) + return x.reshape(-1, BLOCK_TOKENS, dim) + + +def from_blocks(blocks: torch.Tensor, frames: int, rows: int, cols: int) -> torch.Tensor: + dim = blocks.shape[-1] + x = blocks.view(frames // 2, rows // 8, cols // 8, 2, 8, 8, dim).permute(0, 3, 1, 4, 2, 5, 6) + return x.reshape(frames * rows * cols, dim) + + +def local_block_mask(block_rows: int, block_cols: int, reach: int, device) -> torch.Tensor: + """[blocks, blocks] True where a key block is inside a query block's neighbourhood.""" + r = torch.arange(block_rows, device=device).repeat_interleave(block_cols) + c = torch.arange(block_cols, device=device).repeat(block_rows) + dr, dc = r[None, :] - r[:, None], c[None, :] - c[:, None] + low, high = -(reach // 2), reach - 1 - reach // 2 + return (dr >= low) & (dr <= high) & (dc >= low) & (dc <= high) + + +def choose_key_blocks( + q_blocks: torch.Tensor, + k_blocks: torch.Tensor, + heads: int, + local: torch.Tensor, + query_groups: int, + keep: int, +) -> torch.Tensor: + """Pick, for every head, which key blocks each query block reads. + + A block's mean query is scored against every block's mean key. Blocks + outside the neighbourhood are ruled out, the scores of a query block are + turned into shares that sum to one, and for each head and each 2-frame + group of queries the `keep` largest shares win. Returns two + [heads, query blocks, key blocks] tensors: True where a pair is kept, + and the shares themselves (0 outside the neighbourhood). + """ + nq, _, dim = q_blocks.shape + nk = k_blocks.shape[0] + head_dim = dim // heads + q_mean = q_blocks.float().mean(1).view(nq, heads, head_dim).transpose(0, 1) + k_mean = k_blocks.float().mean(1).view(nk, heads, head_dim).transpose(0, 1) + scores = q_mean @ k_mean.transpose(1, 2) / math.sqrt(head_dim) + per_group = local.shape[0] + allowed = local.repeat(nq // per_group, nk // per_group) + shares = scores.masked_fill(~allowed, float("-inf")).softmax(dim=-1) + flat = shares.view(heads, query_groups, per_group * nk) + keep = min(flat.shape[-1] - 1, int(keep)) + threshold = flat.topk(keep + 1, dim=-1).values[..., -1:] + return (flat > threshold).view(heads, nq, nk), shares + + +def sparse_block_attention( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + chosen: torch.Tensor, + shares: torch.Tensor | None = None, + slot_budget: int = DEFAULT_SLOT_BUDGET, +) -> torch.Tensor: + """Attention where each query block only reads its chosen key blocks. + + q is [heads, query blocks, 128, head_dim], k and v are + [heads, key blocks, 128, head_dim] and chosen is + [heads, query blocks, key blocks]. Each (head, query block) is a row + that gathers the keys it chose. Rows with a similar number of chosen + blocks run together, all at the width of the row that chose most. A + row that chose nothing returns zeros. `slot_budget` caps + (rows x chosen blocks) per call, which caps memory. + + With `shares` (from choose_key_blocks) a narrower row is filled up + with its next-best blocks from its neighbourhood, so a call needs no + padding mask, which is what makes it fast: a row then reads a few + blocks more than it chose, never fewer. Without `shares` the padding + is masked and every row reads exactly what it chose. + """ + heads, nq, tokens, head_dim = q.shape + nk = k.shape[1] + rows = chosen.reshape(heads * nq, nk) + counts = rows.sum(dim=1) + if shares is None: + rank, usable = rows.float(), counts + else: + flat_shares = shares.reshape(heads * nq, nk).float() + rank, usable = rows.float() * 2 + flat_shares, (flat_shares > 0).sum(dim=1) + order = torch.argsort(counts) + picks = torch.argsort(rank, dim=1, descending=True, stable=True) + picks = picks + (torch.arange(heads, device=q.device).repeat_interleave(nq) * nk)[:, None] + q_rows = q.reshape(heads * nq, tokens, head_dim) + k_rows = k.reshape(heads * nk, tokens, head_dim) + v_rows = v.reshape(heads * nk, tokens, head_dim) + out = torch.zeros_like(q_rows) + sorted_counts = counts[order].tolist() + total = len(sorted_counts) + start = 0 + while start < total and sorted_counts[start] == 0: + start += 1 + while start < total: + end = start + 1 + while end < total and (end - start + 1) * sorted_counts[end] <= slot_budget: + end += 1 + rows_here = order[start:end] + widest = sorted_counts[end - 1] + index = picks[rows_here, :widest] + keys = k_rows[index].view(end - start, widest * tokens, head_dim) + values = v_rows[index].view(end - start, widest * tokens, head_dim) + mask = None + real = torch.arange(widest, device=q.device)[None, :] < usable[rows_here][:, None] + if not bool(real.all()): + mask = real.repeat_interleave(tokens, dim=1)[:, None, :] + out[rows_here] = F.scaled_dot_product_attention(q_rows[rows_here], keys, values, attn_mask=mask) + start = end + return out.view(heads, nq, tokens, head_dim) + + +class AttentionPlan: + """What stays the same for every layer and pass at one working size.""" + + def __init__(self, rows: int, cols: int, device, slot_budget: int = DEFAULT_SLOT_BUDGET): + self.rows, self.cols = rows, cols + self.per_group = (rows // 8) * (cols // 8) + self.local = local_block_mask(rows // 8, cols // 8, LOCAL_REACH, device) + ratio = SPARSE_RATIO * SPARSE_REFERENCE_AREA / (rows * 16 * cols * 16) + self.keep = int(self.per_group * self.per_group * ratio) - 1 + self.slot_budget = slot_budget + + +# -------------------------------------------------------------------------- +# The transformer +# -------------------------------------------------------------------------- + + +class _RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float, device=None, dtype=None): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim, device=device, dtype=dtype)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + y = x.float() + y = y * torch.rsqrt(y.pow(2).mean(dim=-1, keepdim=True) + self.eps) + return y.to(x.dtype) * _cast(self.weight, x) + + +def _heads_first(blocks: torch.Tensor, heads: int) -> torch.Tensor: + count, tokens, dim = blocks.shape + return blocks.view(count, tokens, heads, dim // heads).permute(2, 0, 1, 3).contiguous() + + +class _SelfAttention(nn.Module): + def __init__(self, dim, heads, eps, operations, device=None, dtype=None): + super().__init__() + self.heads = heads + kw = {"device": device, "dtype": dtype} + self.q = operations.Linear(dim, dim, **kw) + self.k = operations.Linear(dim, dim, **kw) + self.v = operations.Linear(dim, dim, **kw) + self.o = operations.Linear(dim, dim, **kw) + self.norm_q = _RMSNorm(dim, eps, **kw) + self.norm_k = _RMSNorm(dim, eps, **kw) + + def forward(self, x, rope, grid, memory: dict, plan: AttentionPlan): + frames, rows, cols = grid + cos, sin = rope + q = to_blocks(apply_rope(self.norm_q(self.q(x))[0], cos, sin, self.heads), *grid) + k = to_blocks(apply_rope(self.norm_k(self.k(x))[0], cos, sin, self.heads), *grid) + v = to_blocks(self.v(x)[0], *grid) + if memory.get("k") is not None: + k = torch.cat([memory["k"], k], dim=0) + v = torch.cat([memory["v"], v], dim=0) + chosen, shares = choose_key_blocks(q, k, self.heads, plan.local, frames // 2, plan.keep) + out = sparse_block_attention( + _heads_first(q, self.heads), + _heads_first(k, self.heads), + _heads_first(v, self.heads), + chosen, + shares, + plan.slot_budget, + ) + if k.shape[0] // plan.per_group > KEPT_GROUPS: + k, v = k[plan.per_group :], v[plan.per_group :] + memory["k"], memory["v"] = k, v + out = out.permute(1, 2, 0, 3).reshape(-1, BLOCK_TOKENS, x.shape[-1]) + return self.o(from_blocks(out, *grid)[None]) + + +class _CrossAttention(nn.Module): + def __init__(self, dim, heads, eps, operations, device=None, dtype=None): + super().__init__() + self.heads = heads + kw = {"device": device, "dtype": dtype} + self.q = operations.Linear(dim, dim, **kw) + self.k = operations.Linear(dim, dim, **kw) + self.v = operations.Linear(dim, dim, **kw) + self.o = operations.Linear(dim, dim, **kw) + self.norm_q = _RMSNorm(dim, eps, **kw) + self.norm_k = _RMSNorm(dim, eps, **kw) + + def _split(self, x: torch.Tensor) -> torch.Tensor: + batch, tokens, dim = x.shape + return x.view(batch, tokens, self.heads, dim // self.heads).transpose(1, 2) + + def forward(self, x, context, memory: dict): + if memory.get("k") is None: # the prompt never changes during a run + memory["k"] = self._split(self.norm_k(self.k(context))) + memory["v"] = self._split(self.v(context)) + out = F.scaled_dot_product_attention(self._split(self.norm_q(self.q(x))), memory["k"], memory["v"]) + return self.o(out.transpose(1, 2).reshape(x.shape)) + + +class _Block(nn.Module): + def __init__(self, dim, heads, ffn_dim, eps, operations, device=None, dtype=None): + super().__init__() + self.dim, self.eps = dim, eps + kw = {"device": device, "dtype": dtype} + self.self_attn = _SelfAttention(dim, heads, eps, operations, **kw) + self.cross_attn = _CrossAttention(dim, heads, eps, operations, **kw) + self.norm3 = operations.LayerNorm(dim, eps=eps, elementwise_affine=True, **kw) + self.ffn = nn.Sequential( + operations.Linear(dim, ffn_dim, **kw), nn.GELU(approximate="tanh"), operations.Linear(ffn_dim, dim, **kw) + ) + self.modulation = nn.Parameter(torch.zeros(1, 6, dim, **kw)) + + def _plain_norm(self, x: torch.Tensor) -> torch.Tensor: + return F.layer_norm(x, (self.dim,), eps=self.eps) + + def forward(self, x, context, time_mod, rope, grid, memory: dict, plan: AttentionPlan): + shift_a, scale_a, gate_a, shift_m, scale_m, gate_m = (_cast(self.modulation, x) + time_mod).chunk(6, dim=1) + x = x + gate_a * self.self_attn(self._plain_norm(x) * (1 + scale_a) + shift_a, rope, grid, memory["self"], plan) + x = x + self.cross_attn(self.norm3(x), context, memory["cross"]) + return x + gate_m * self.ffn(self._plain_norm(x) * (1 + scale_m) + shift_m) + + +class _Head(nn.Module): + def __init__(self, dim, out_dim, eps, operations, device=None, dtype=None): + super().__init__() + self.dim, self.eps = dim, eps + self.head = operations.Linear(dim, out_dim * 4, device=device, dtype=dtype) + self.modulation = nn.Parameter(torch.zeros(1, 2, dim, device=device, dtype=dtype)) + + def forward(self, x, time_embed): + shift, scale = (_cast(self.modulation, x) + time_embed).chunk(2, dim=1) + return self.head(F.layer_norm(x, (self.dim,), eps=self.eps) * (1 + scale) + shift) + + +# -------------------------------------------------------------------------- +# The low-quality projector +# -------------------------------------------------------------------------- + + +class _ChannelNorm(nn.Module): + def __init__(self, dim: int, device=None, dtype=None): + super().__init__() + self.scale = dim**0.5 + self.gamma = nn.Parameter(torch.ones(dim, 1, 1, 1, device=device, dtype=dtype)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return F.normalize(x.float(), dim=1).to(x.dtype) * self.scale * _cast(self.gamma, x) + + +class LowQualityProjector(nn.Module): + """Turns enlarged low-quality frames into one set of tokens per 4 frames. + + Two convolutions that only look backwards in time, each halving the + frame rate, then one linear layer. `push` is fed the first frame alone, + then 4 frames at a time, and remembers what the next call needs. + """ + + def __init__(self, out_dim=1536, hidden=(2048, 3072), patch=16, operations=nn, device=None, dtype=None): + super().__init__() + self.patch = patch + kw = {"device": device, "dtype": dtype} + self.conv1 = operations.Conv3d(3 * patch * patch, hidden[0], (4, 3, 3), stride=(2, 1, 1), **kw) + self.norm1 = _ChannelNorm(hidden[0], **kw) + self.conv2 = operations.Conv3d(hidden[0], hidden[1], (4, 3, 3), stride=(2, 1, 1), **kw) + self.norm2 = _ChannelNorm(hidden[1], **kw) + self.linear_layers = nn.ModuleList([operations.Linear(hidden[1], out_dim, **kw)]) + + def _fold(self, clip: torch.Tensor) -> torch.Tensor: + batch, channels, frames, height, width = clip.shape + p = self.patch + x = clip.view(batch, channels, frames, height // p, p, width // p, p).permute(0, 1, 4, 6, 2, 3, 5) + return x.reshape(batch, channels * p * p, frames, height // p, width // p) + + @staticmethod + def _causal(conv, x: torch.Tensor, earlier) -> torch.Tensor: + front = 2 + if earlier is not None: + x = torch.cat([earlier, x], dim=2) + front -= earlier.shape[2] + return conv(F.pad(x, (1, 1, 1, 1, front, 0), mode="replicate")) + + def push(self, memory: dict, clip: torch.Tensor): + """clip is [1, 3, frames, height, width] in -1..1. Returns tokens, or None for the first frame.""" + first = not memory + if first: + clip = torch.cat([clip[:, :, :1].repeat(1, 1, 3, 1, 1), clip], dim=2) + x = self._fold(clip) + tail = x[:, :, -2:].clone() + x = F.silu(self.norm1(self._causal(self.conv1, x, memory.get("conv1")))) + memory["conv1"] = tail + tail = x[:, :, -2:].clone() + if first: + memory["conv2"] = tail + return None + x = F.silu(self.norm2(self._causal(self.conv2, x, memory["conv2"]))) + memory["conv2"] = tail + return self.linear_layers[0](x.flatten(2).transpose(1, 2)) + + +# -------------------------------------------------------------------------- +# The fast decoder +# -------------------------------------------------------------------------- + + +class _SoftClamp(nn.Module): + def forward(self, x): + return torch.tanh(x / 3) * 3 + + +class _MemoryBlock(nn.Module): + """A residual block that also sees the frame before.""" + + def __init__(self, width: int, operations, device=None, dtype=None): + super().__init__() + kw = {"device": device, "dtype": dtype} + self.conv = nn.Sequential( + operations.Conv2d(width * 2, width, 3, padding=1, **kw), + nn.ReLU(), + operations.Conv2d(width, width, 3, padding=1, **kw), + nn.ReLU(), + operations.Conv2d(width, width, 3, padding=1, **kw), + ) + + def forward(self, x, past): + return F.relu(self.conv(torch.cat([x, past], dim=1)) + x) + + +class _TimeGrow(nn.Module): + """Makes `stride` frames out of each frame.""" + + def __init__(self, width: int, stride: int, operations, device=None, dtype=None): + super().__init__() + self.conv = operations.Conv2d(width, width * stride, 1, bias=False, device=device, dtype=dtype) + + def forward(self, x): + _, channels, height, width = x.shape + return self.conv(x).reshape(-1, channels, height, width) + + +class _Upsample(nn.Module): + def forward(self, x): + return F.interpolate(x, scale_factor=2, mode="nearest") + + +class ConditionedDecoder(nn.Module): + """Latent frames plus the low-quality frames in, 4 video frames per latent frame out.""" + + def __init__(self, latent_channels=16, widths=(512, 256, 128, 128), operations=nn, device=None, dtype=None): + super().__init__() + kw = {"device": device, "dtype": dtype} + a, b, c, d = widths + + def conv(n_in, n_out, bias=True): + return operations.Conv2d(n_in, n_out, 3, padding=1, bias=bias, **kw) + + def memory_blocks(width): + return [_MemoryBlock(width, operations, **kw) for _ in range(3)] + + self.latent_channels = latent_channels + self.decoder = nn.Sequential( + _SoftClamp(), + conv(latent_channels + 3 * 4 * 8 * 8, a), + nn.ReLU(), + conv(a, a, bias=False), + nn.ReLU(), + *memory_blocks(a), + _Upsample(), + _TimeGrow(a, 1, operations, **kw), + conv(a, b, bias=False), + *memory_blocks(b), + _Upsample(), + _TimeGrow(b, 2, operations, **kw), + conv(b, c, bias=False), + *memory_blocks(c), + _Upsample(), + _TimeGrow(c, 2, operations, **kw), + conv(c, d, bias=False), + nn.ReLU(), + conv(d, d, bias=False), + nn.ReLU(), + conv(d, 3), + ) + + @staticmethod + def fold_frames(clip: torch.Tensor) -> torch.Tensor: + """[3, 4, height, width] -> [768, height / 8, width / 8].""" + channels, frames, height, width = clip.shape + x = clip.view(channels, frames, height // 8, 8, width // 8, 8).permute(0, 1, 3, 5, 2, 4) + return x.reshape(channels * frames * 64, height // 8, width // 8) + + def decode(self, memory: dict, x: torch.Tensor) -> torch.Tensor: + """x is [latent frames, 768 + latent channels, h, w]. Returns [4 x latent frames, 3, 8h, 8w] in 0..1. + + The first 3 frames of the first call are lead-in and are dropped. + """ + first = not memory + memory["started"] = True + for index, layer in enumerate(self.decoder): + if isinstance(layer, _MemoryBlock): + before = memory.get(index) + if before is None: + before = torch.zeros_like(x[:1]) + memory[index] = x[-1:].clone() + x = layer(x, torch.cat([before, x[:-1]], dim=0)) + else: + x = layer(x) + return x[3:] if first else x + + +# -------------------------------------------------------------------------- +# The whole model +# -------------------------------------------------------------------------- + + +class FlashVSRModel(nn.Module): + def __init__( + self, + dim=1536, + heads=12, + ffn_dim=8960, + layers=30, + latent_channels=16, + text_dim=4096, + text_len=512, + freq_dim=256, + eps=1e-6, + projector_hidden=(2048, 3072), + decoder_widths=(512, 256, 128, 128), + operations=nn, + device=None, + dtype=None, + ): + super().__init__() + self.dim, self.heads, self.freq_dim, self.latent_channels = dim, heads, freq_dim, latent_channels + kw = {"device": device, "dtype": dtype} + self.patch_embedding = operations.Conv3d(latent_channels, dim, (1, 2, 2), stride=(1, 2, 2), **kw) + self.text_embedding = nn.Sequential( + operations.Linear(text_dim, dim, **kw), nn.GELU(approximate="tanh"), operations.Linear(dim, dim, **kw) + ) + self.time_embedding = nn.Sequential( + operations.Linear(freq_dim, dim, **kw), nn.SiLU(), operations.Linear(dim, dim, **kw) + ) + self.time_projection = nn.Sequential(nn.SiLU(), operations.Linear(dim, dim * 6, **kw)) + self.blocks = nn.ModuleList([_Block(dim, heads, ffn_dim, eps, operations, **kw) for _ in range(layers)]) + self.head = _Head(dim, latent_channels, eps, operations, **kw) + self.lq_proj_in = LowQualityProjector(dim, projector_hidden, operations=operations, **kw) + self.tc_decoder = ConditionedDecoder(latent_channels, decoder_widths, operations=operations, **kw) + self.register_buffer("positive_conditioning", torch.zeros(text_len, text_dim, **kw)) + + def start(self, like: torch.Tensor) -> dict: + """The values that never change during one clip.""" + half = self.freq_dim // 2 + steps = torch.pow(10000.0, -torch.arange(half, dtype=torch.float64) / half) * TIMESTEP + wave = torch.cat([torch.cos(steps), torch.sin(steps)])[None].to(device=like.device, dtype=like.dtype) + time_embed = self.time_embedding(wave) + return { + "time_embed": time_embed[:, None], + "time_mod": self.time_projection(time_embed).unflatten(1, (6, self.dim)), + "context": self.text_embedding(_cast(self.positive_conditioning, like)[None]), + "layers": [{"self": {}, "cross": {}} for _ in self.blocks], + "projector": {}, + "decoder": {}, + } + + def denoise(self, state: dict, noise: torch.Tensor, lq_tokens: torch.Tensor, first_frame: int, plan: AttentionPlan): + """One pass over a few latent frames. Returns the clean latent frames.""" + x = self.patch_embedding(noise) + frames, rows, cols = x.shape[2:] + grid = (frames, rows, cols) + x = x.flatten(2).transpose(1, 2) + lq_tokens + rope = rope_table(self.dim // self.heads, first_frame, frames, rows, cols, x.device) + for block, memory in zip(self.blocks, state["layers"]): + x = block(x, state["context"], state["time_mod"], rope, grid, memory, plan) + x = self.head(x, state["time_embed"]) + x = x.view(1, frames, rows, cols, 2, 2, self.latent_channels).permute(0, 6, 1, 2, 4, 3, 5) + return noise - x.reshape(1, self.latent_channels, frames, rows * 2, cols * 2) + + +def pass_layout(groups: int): + """(first latent frame, latent frames) of each pass over a clip of `groups` 8-frame groups.""" + yield 0, FIRST_PASS_LATENTS + for index in range(1, groups - 2): + yield 4 + 2 * index, PASS_LATENTS + + +def match_colors(frames: torch.Tensor, source: torch.Tensor) -> torch.Tensor: + """Give each frame its source frame's mean and spread, per colour channel. Both [n, 3, h, w].""" + f, s = frames.float(), source.float() + f_mean, s_mean = f.mean(dim=(2, 3), keepdim=True), s.mean(dim=(2, 3), keepdim=True) + f_std, s_std = f.std(dim=(2, 3), keepdim=True) + 1e-5, s.std(dim=(2, 3), keepdim=True) + 1e-5 + return (f - f_mean) / f_std * s_std + s_mean + + +TILE_OVERLAP = 128 + + +def plan_tiles(work_h: int, work_w: int, max_pixels: int | None) -> list[tuple[int, int, int, int]]: + """Cut the working size into as few overlapping tiles as fit `max_pixels` each. + + Returns (top, left, height, width) boxes. Tile sides are multiples of + 128 and corners sit on the 16-pixel token grid. One box, the whole + picture, when it fits or when no limit is given. + """ + if not max_pixels or work_h * work_w <= max_pixels: + return [(0, 0, work_h, work_w)] + + def side(total: int, parts: int) -> int: + if parts == 1: + return total + return min(total, math.ceil((total + (parts - 1) * TILE_OVERLAP) / parts / PIXEL_STEP) * PIXEL_STEP) + + best = None + for rows in range(1, work_h // PIXEL_STEP + 1): + for cols in range(1, work_w // PIXEL_STEP + 1): + tile_h, tile_w = side(work_h, rows), side(work_w, cols) + if tile_h * tile_w <= max_pixels and (best is None or rows * cols < best[0] * best[1]): + best = (rows, cols, tile_h, tile_w) + if best is None: # even one 128x128 tile is over the limit: use the smallest tiles there are + best = (work_h // PIXEL_STEP, work_w // PIXEL_STEP, PIXEL_STEP, PIXEL_STEP) + rows, cols, tile_h, tile_w = best + + def starts(total: int, tile: int, parts: int) -> list[int]: + if parts == 1: + return [0] + return [round(i * (total - tile) / (parts - 1) / 16) * 16 for i in range(parts)] + + return [(top, left, tile_h, tile_w) for top in starts(work_h, tile_h, rows) for left in starts(work_w, tile_w, cols)] + + +def tile_weight(box: tuple[int, int, int, int], work_h: int, work_w: int) -> torch.Tensor: + """[height, width] blend weights for a tile: 1 inside, fading over the overlap on edges that meet another tile.""" + top, left, height, width = box + + def ramp(length: int, fade_start: bool, fade_end: bool) -> torch.Tensor: + w = torch.ones(length) + fade = (torch.arange(TILE_OVERLAP) + 0.5) / TILE_OVERLAP + if fade_start: + w[:TILE_OVERLAP] = fade + if fade_end: + w[-TILE_OVERLAP:] = torch.minimum(w[-TILE_OVERLAP:], fade.flip(0)) + return w + + rows = ramp(height, top > 0, top + height < work_h) + cols = ramp(width, left > 0, left + width < work_w) + return rows[:, None] * cols[None, :] + + +@torch.no_grad() +def upscale_frames( + model: FlashVSRModel, + frames: torch.Tensor, + scale: float, + *, + device, + dtype, + color_match: bool = True, + seed: int = 0, + slot_budget: int = DEFAULT_SLOT_BUDGET, + max_tile_pixels: int | None = None, + decode_latents=None, + on_pass=None, +) -> torch.Tensor: + """Upscale a clip. `frames` is [count, height, width, 3] in 0..1; so is the result, at the new size. + + A picture larger than `max_tile_pixels` is done in overlapping tiles, + one after the other, and blended; that is what bounds graphics memory. + `decode_latents`, when given, takes the clean latent frames of a tile + ([1, 16, frames, h, w]) and returns [frames, height, width, 3]; it is how + another decoder (the Wan 2.1 VAE) is used in place of the fast one. + `on_pass(done, total)` is called after each pass. + """ + count, height, width, _ = frames.shape + out_h, out_w, work_h, work_w = plan_sizes(height, width, scale) + groups = plan_groups(count) + layout = list(pass_layout(groups)) + tiles = plan_tiles(work_h, work_w, max_tile_pixels) + + def enlarged(start: int, stop: int) -> torch.Tensor: + """Frames start..stop-1 of the padded clip at the working size: [n, 3, work_h, work_w] in 0..1.""" + index = torch.arange(start, stop).clamp(max=count - 1) + x = frames[index][..., :3].to(device).movedim(-1, 1).float() + if (height, width) != (out_h, out_w): + x = F.interpolate(x, size=(out_h, out_w), mode="bicubic", align_corners=False).clamp(0, 1) + return F.pad(x, (0, work_w - out_w, 0, work_h - out_h), mode="replicate") + + noise = torch.randn( + (1, model.latent_channels, 2 * groups, work_h // 8, work_w // 8), generator=torch.Generator().manual_seed(seed) + ) + result = torch.zeros((count, out_h, out_w, 3), dtype=torch.float32) + total_weight = torch.zeros((work_h, work_w), dtype=torch.float32) + like = torch.zeros(1, device=device, dtype=dtype) + done = 0 + for box in tiles: + top, left, tile_h, tile_w = box + weight = tile_weight(box, work_h, work_w) + total_weight[top : top + tile_h, left : left + tile_w] += weight + weight = weight.to(device) + + def crop(start: int, stop: int) -> torch.Tensor: + return enlarged(start, stop)[..., top : top + tile_h, left : left + tile_w] + + def signed_clip(start: int, stop: int) -> torch.Tensor: + return (crop(start, stop) * 2 - 1).movedim(0, 1)[None].to(dtype) + + def store(decoded: torch.Tensor, written: int) -> int: + """Add decoded frames [n, 3, tile_h, tile_w] to the result; returns how many frames the tile has given.""" + decoded = decoded[: count - written].float() + if decoded.shape[0] == 0: + return written + stop = written + decoded.shape[0] + if color_match: + decoded = match_colors(decoded, crop(written, stop)) + bottom, right = min(top + tile_h, out_h), min(left + tile_w, out_w) + if bottom > top and right > left: + piece = (decoded.clamp(0, 1) * weight)[..., : bottom - top, : right - left] + result[written:stop, top:bottom, left:right] += piece.movedim(1, -1).cpu() + return stop + + state = model.start(like) + plan = AttentionPlan(tile_h // 16, tile_w // 16, device, slot_budget) + tile_noise = noise[..., top // 8 : (top + tile_h) // 8, left // 8 : (left + tile_w) // 8] + kept_latents = [] + written = 0 + model.lq_proj_in.push(state["projector"], signed_clip(0, 1)) + for first, latent_count in layout: + tokens = torch.cat( + [ + model.lq_proj_in.push(state["projector"], signed_clip(4 * i + 1, 4 * i + 5)) + for i in range(first, first + latent_count) + ], + dim=1, + ) + chunk = tile_noise[:, :, first : first + latent_count].to(device=device, dtype=dtype) + latents = model.denoise(state, chunk, tokens, first, plan) + if decode_latents is not None: + kept_latents.append(latents.float().cpu()) + else: + for i in range(first, first + latent_count): + if written >= count: + break + source = signed_clip(0, 1)[0].repeat(1, 4, 1, 1) if i == 0 else signed_clip(4 * i - 3, 4 * i + 1)[0] + both = torch.cat([ConditionedDecoder.fold_frames(source), latents[0, :, i - first]], dim=0) + written = store(model.tc_decoder.decode(state["decoder"], both[None]), written) + done += 1 + if on_pass is not None: + on_pass(done, len(layout) * len(tiles)) + state.clear() # the layers' memory is no longer needed; the next tile or another decoder wants the room + if decode_latents is not None: + decoded = decode_latents(torch.cat(kept_latents, dim=2)).movedim(-1, 1) + for start in range(0, min(count, decoded.shape[0]), 8): + written = store(decoded[start : start + 8].to(device), written) + if len(tiles) > 1: + result /= total_weight[:out_h, :out_w, None] + return result + + +# -------------------------------------------------------------------------- +# ComfyUI side: loading the file and running a clip. ComfyUI is imported +# inside the functions so the code above runs without it. +# -------------------------------------------------------------------------- + +MODEL_PARTS = { + "blocks.0.self_attn.q.weight": "the transformer", + "lq_proj_in.conv1.weight": "the low-quality projector", + "tc_decoder.decoder.1.weight": "the fast decoder", + "positive_conditioning": "the stored prompt", +} +NOT_A_FLASHVSR_FILE = ( + "This is not a FlashVSR model file for this node: it has no {missing}. " + "Pick the FlashVSR file named in the node's help page." +) + +_loaded: dict = {} + + +# Measured on one clip (640x1024, bf16): about 9.6 KB of graphics memory per working pixel on top of +# the weights. Most of it is the keys and values every layer keeps of the last few frames. +WORKING_BYTES_PER_PIXEL = 12_000 +MEMORY_TO_ASK_FOR = 16 * 1024**3 +MEMORY_HEADROOM = 1024**3 + + +def working_memory(pixels: int, bytes_per_value: int = 2) -> int: + """A rough count of the graphics memory a picture of `pixels` needs besides the weights, in bytes.""" + return int(pixels * WORKING_BYTES_PER_PIXEL * bytes_per_value / 2) + + +def tile_pixels_that_fit(free_bytes: int, bytes_per_value: int = 2) -> int: + """The largest tile, in pixels, that `free_bytes` of graphics memory can hold.""" + return max(PIXEL_STEP * PIXEL_STEP, int((free_bytes - MEMORY_HEADROOM) / working_memory(1_000_000, bytes_per_value) * 1_000_000)) + + +def load_flashvsr(path: str, dtype: torch.dtype | None = None): + """The model behind ComfyUI's memory manager, and the number type it runs in. Kept between runs.""" + import comfy.model_management as mm + import comfy.model_patcher + import comfy.ops + import comfy.utils + + stamp = (path, os.path.getmtime(path), dtype) + if _loaded.get("stamp") == stamp: + return _loaded["patcher"], _loaded["dtype"] + _loaded.clear() + weights = comfy.utils.load_torch_file(path, safe_load=True) + missing = [what for key, what in MODEL_PARTS.items() if key not in weights] + if missing: + raise ValueError(NOT_A_FLASHVSR_FILE.format(missing=" or ".join(missing))) + device, offload = mm.get_torch_device(), mm.unet_offload_device() + if dtype is None: + # The file is 16-bit, so that is the first choice: no weight has to be converted. + if mm.should_use_fp16(device): + dtype = torch.float16 + elif mm.should_use_bf16(device): + dtype = torch.bfloat16 + else: + dtype = torch.float32 + # manual_cast: every layer brings its weights to the device and number type of what it is given, + # so a model ComfyUI keeps partly, or wholly, outside graphics memory still runs. + model = FlashVSRModel(operations=comfy.ops.manual_cast, device=offload, dtype=dtype) + model.eval() + # ComfyUI's own wrapper for a model: the one that loads weights as they are needed, where it has one. + wrapper = getattr(comfy.model_patcher, "CoreModelPatcher", comfy.model_patcher.ModelPatcher) + patcher = wrapper(model, load_device=device, offload_device=offload) + assign = bool(getattr(patcher, "is_dynamic", lambda: False)()) + model.load_state_dict(weights, strict=True, assign=assign) + model.requires_grad_(False) + _loaded.update(stamp=stamp, patcher=patcher, dtype=dtype) + return patcher, dtype + + +def _memory_for_work(patcher, device) -> int: + """Graphics memory left for the picture once all of the model's weights are in it.""" + import comfy.model_management as mm + + free = mm.get_free_memory(device) + try: + still_to_load = max(0, int(patcher.model_size()) - int(patcher.loaded_size())) + except Exception: + still_to_load = 0 + return free - still_to_load + + +def run_flashvsr( + path: str, + frames: torch.Tensor, + scale: float, + color_match: bool = True, + vae=None, + seed: int = 0, + max_tile_pixels: int | None = None, +): + """Upscale `frames` with the model file at `path`. `vae`, when given, decodes in place of the fast decoder.""" + import comfy.latent_formats + import comfy.model_management as mm + + from ._execution_helpers import progress_bar, raise_if_interrupted + + patcher, dtype = load_flashvsr(path) + device = patcher.load_device + _, _, work_h, work_w = plan_sizes(frames.shape[1], frames.shape[2], scale) + wanted = working_memory(work_h * work_w, dtype.itemsize) + mm.load_models_gpu([patcher], memory_required=min(wanted, MEMORY_TO_ASK_FOR)) + if max_tile_pixels is None and device.type != "cpu": + max_tile_pixels = tile_pixels_that_fit(_memory_for_work(patcher, device), dtype.itemsize) + passes = len(list(pass_layout(plan_groups(frames.shape[0])))) + + decode = None + if vae is not None: + latent_format = comfy.latent_formats.Wan21() + + def decode(latents: torch.Tensor) -> torch.Tensor: + images = vae.decode(latent_format.process_out(latents)) + return images.reshape(-1, *images.shape[-3:]) + + while True: + progress = progress_bar(passes * len(plan_tiles(work_h, work_w, max_tile_pixels))) + + def on_pass(done: int, total: int) -> None: + raise_if_interrupted() + if progress is not None: + progress.update_absolute(done, total) + + try: + return upscale_frames( + patcher.model, + frames, + scale, + device=device, + dtype=dtype, + color_match=color_match, + seed=seed, + max_tile_pixels=max_tile_pixels, + decode_latents=decode, + on_pass=on_pass, + ) + except torch.cuda.OutOfMemoryError: + # Out of graphics memory: start again in tiles half the size, down to the smallest tile. + tried = max_tile_pixels if max_tile_pixels is not None else work_h * work_w + if tried <= PIXEL_STEP * PIXEL_STEP: + raise + max_tile_pixels = max(PIXEL_STEP * PIXEL_STEP, min(tried, work_h * work_w) // 2) + mm.soft_empty_cache() + + +MODEL_FOLDER = "diffusion_models" +EXPECTED_MODEL_NAME = "flashvsr_v1.1_fp16.safetensors" +MODEL_NOT_FOUND = ( + "FlashVSR Upscale could not find {name} in ComfyUI/models/diffusion_models. " + "Download the FlashVSR model file named in the node's help page and put it there." +) + + +def list_flashvsr_models() -> list[str]: + """FlashVSR files in the diffusion models folder; every file there when none is named FlashVSR.""" + try: + import folder_paths + except ImportError: # Offline tests import this module without ComfyUI. + return [EXPECTED_MODEL_NAME] + names = list(folder_paths.get_filename_list(MODEL_FOLDER)) + ours = [name for name in names if "flashvsr" in name.lower().replace("_", "").replace("-", "")] + return ours or names or [EXPECTED_MODEL_NAME] + + +def resolve_flashvsr_model(name: str) -> str: + import folder_paths + + path = folder_paths.get_full_path(MODEL_FOLDER, name) + if not path: + raise FileNotFoundError(MODEL_NOT_FOUND.format(name=name)) + return path diff --git a/nodes/node_flashvsr_upscale.py b/nodes/node_flashvsr_upscale.py new file mode 100644 index 0000000..c94d7a0 --- /dev/null +++ b/nodes/node_flashvsr_upscale.py @@ -0,0 +1,87 @@ +"""FlashVSR Upscale 🆎.""" + +from __future__ import annotations + +from ._flashvsr_helpers import EXPECTED_MODEL_NAME, list_flashvsr_models, resolve_flashvsr_model, run_flashvsr + + +class AusBossFlashVSRUpscale: + CATEGORY = "🆎 AusBoss/Video" + DESCRIPTION = ( + "Makes a video larger and sharper with the FlashVSR model. Pick the " + "model file, set how much larger, and run. It reads the clip a few " + "frames at a time, so a long clip needs no more graphics memory than " + "a short one, and every frame comes back. Connect the Wan 2.1 VAE " + "for a slower, finer finish." + ) + SEARCH_ALIASES = [ + "flashvsr", + "video upscale", + "upscale video", + "video super resolution", + "vsr", + "sharpen video", + "ausboss", + ] + + @classmethod + def INPUT_TYPES(cls): + models = list_flashvsr_models() + return { + "required": { + "frames": ( + "IMAGE", + {"tooltip": "The video frames to make larger."}, + ), + "model": ( + models, + { + "default": EXPECTED_MODEL_NAME if EXPECTED_MODEL_NAME in models else models[0], + "tooltip": "The FlashVSR model file, from ComfyUI/models/diffusion_models.", + }, + ), + "scale": ( + "FLOAT", + { + "default": 2.0, + "min": 1.0, + "max": 4.0, + "step": 0.25, + "tooltip": "How many times larger each side gets. 1 keeps the size and only sharpens.", + }, + ), + "color_match": ( + "BOOLEAN", + { + "default": True, + "tooltip": "Keeps each frame's colours the same as your video. Leave it on.", + }, + ), + }, + "optional": { + "vae": ( + "VAE", + { + "tooltip": ( + "Optional. Connect the Wan 2.1 VAE for a slower, finer finish. " + "Left empty, the fast decoder inside the model file is used." + ) + }, + ), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("frames",) + OUTPUT_TOOLTIPS = ("The larger, sharper frames: as many as went in.",) + FUNCTION = "upscale" + + def upscale(self, frames, model, scale, color_match, vae=None): + output = run_flashvsr(resolve_flashvsr_model(model), frames, float(scale), bool(color_match), vae) + return (output,) + + +NODE_CLASS_MAPPINGS = {"AUSBOSS_NODES_FlashVSRUpscale": AusBossFlashVSRUpscale} +NODE_DISPLAY_NAME_MAPPINGS = {"AUSBOSS_NODES_FlashVSRUpscale": "FlashVSR Upscale 🆎"} + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/scripts/validate_nodes.py b/scripts/validate_nodes.py index badc087..a4e9fe3 100644 --- a/scripts/validate_nodes.py +++ b/scripts/validate_nodes.py @@ -58,6 +58,7 @@ PUBLIC_NODE_IDS = { "AUSBOSS_NODES_CropForInpaint", "AUSBOSS_NODES_EditLock", "AUSBOSS_NODES_EditLockRestore", + "AUSBOSS_NODES_FlashVSRUpscale", "AUSBOSS_NODES_Float", "AUSBOSS_NODES_FrameInterpolate", "AUSBOSS_NODES_FreeMemory", diff --git a/tests/fixtures/node_api.json b/tests/fixtures/node_api.json index 883f47d..0d4f62d 100644 --- a/tests/fixtures/node_api.json +++ b/tests/fixtures/node_api.json @@ -160,6 +160,23 @@ ] ] }, + "AUSBOSS_NODES_FlashVSRUpscale": { + "required": [ + "frames", + "model", + "scale", + "color_match" + ], + "optional": [ + "vae" + ], + "outputs": [ + [ + "IMAGE", + "frames" + ] + ] + }, "AUSBOSS_NODES_Float": { "required": [ "value" diff --git a/tests/test_flashvsr_helpers.py b/tests/test_flashvsr_helpers.py new file mode 100644 index 0000000..4e461cc --- /dev/null +++ b/tests/test_flashvsr_helpers.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +from pathlib import Path +import importlib.util +import math +import unittest + +import torch + +ROOT = Path(__file__).resolve().parent.parent +# Loaded by path: the model code is plain PyTorch and needs nothing the pack imports. +_spec = importlib.util.spec_from_file_location("ausboss_flashvsr_helpers", ROOT / "nodes" / "_flashvsr_helpers.py") +vsr = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(vsr) + + +def tiny_model(seed: int = 0): + torch.manual_seed(seed) + model = vsr.FlashVSRModel( + dim=24, + heads=2, + ffn_dim=48, + layers=2, + text_dim=8, + text_len=4, + freq_dim=16, + projector_hidden=(8, 12), + decoder_widths=(8, 8, 4, 4), + ) + for parameter in model.parameters(): + torch.nn.init.normal_(parameter, std=0.2) + model.positive_conditioning.normal_() + return model.eval() + + +class SizeTests(unittest.TestCase): + def test_output_is_even_and_the_working_size_is_a_multiple_of_128(self): + self.assertEqual(vsr.plan_sizes(360, 640, 2.0), (720, 1280, 768, 1280)) + self.assertEqual(vsr.plan_sizes(271, 479, 1.5), (406, 718, 512, 768)) + self.assertEqual(vsr.plan_sizes(64, 64, 2.0), (128, 128, 128, 128)) + + def test_the_padded_clip_always_covers_every_frame(self): + for count in (1, 5, 21, 22, 29, 30, 81, 200): + groups = vsr.plan_groups(count) + self.assertGreaterEqual(groups, 3) + self.assertGreaterEqual(8 * groups - 3, count) + latents = sum(n for _, n in vsr.pass_layout(groups)) + self.assertEqual(latents, 2 * groups) + + def test_passes_follow_one_another_without_a_gap(self): + expected = 0 + for first, count in vsr.pass_layout(6): + self.assertEqual(first, expected) + expected += count + + +class BlockTests(unittest.TestCase): + def test_blocks_round_trip(self): + x = torch.randn(4 * 16 * 24, 6) + back = vsr.from_blocks(vsr.to_blocks(x, 4, 16, 24), 4, 16, 24) + self.assertTrue(torch.equal(x, back)) + + def test_a_block_holds_two_frames_of_an_8_by_8_patch(self): + frames, rows, cols = 2, 16, 16 + t, r, c = torch.meshgrid(torch.arange(frames), torch.arange(rows), torch.arange(cols), indexing="ij") + ids = torch.stack([t, r // 8, c // 8], dim=-1).reshape(-1, 3).float() + blocks = vsr.to_blocks(ids, frames, rows, cols) + self.assertEqual(blocks.shape, (4, 128, 3)) + for block in blocks: + self.assertEqual(block[:, 1:].unique(dim=0).shape[0], 1) + self.assertEqual(sorted(block[:, 0].unique().tolist()), [0.0, 1.0]) + + def test_the_neighbourhood_is_centred_and_cut_at_the_edges(self): + mask = vsr.local_block_mask(3, 20, 11, "cpu") + row = mask[20 + 10].view(3, 20) # the block at row 1, column 10 + self.assertTrue(row[:, 5:16].all()) + self.assertFalse(row[:, :5].any() or row[:, 16:].any()) + self.assertEqual(int(mask[0].sum()), 3 * 6) # the corner block: all 3 rows, columns 0-5 + + def test_selection_keeps_the_asked_number_inside_the_neighbourhood(self): + torch.manual_seed(1) + heads, per_group = 2, 6 + local = vsr.local_block_mask(2, 3, 3, "cpu") + q = torch.randn(2 * per_group, 128, 8) + k = torch.randn(3 * per_group, 128, 8) + chosen, shares = vsr.choose_key_blocks(q, k, heads, local, 2, keep=20) + self.assertTrue((shares[chosen] > 0).all()) + self.assertTrue((shares[:, ~local.repeat(2, 3)] == 0).all()) + self.assertEqual(chosen.shape, (heads, 12, 18)) + self.assertTrue((chosen.view(heads, 2, -1).sum(-1) == 20).all()) + self.assertFalse((chosen & ~local.repeat(2, 3)).any()) + + +class SparseAttentionTests(unittest.TestCase): + def dense(self, q, k, v, chosen): + heads, nq, tokens, dim = q.shape + nk = k.shape[1] + scores = torch.einsum("hqtd,hksd->hqtks", q, k) / math.sqrt(dim) + scores = scores.masked_fill(~chosen[:, :, None, :, None], float("-inf")) + weights = scores.reshape(heads, nq, tokens, nk * tokens).softmax(-1).nan_to_num(0.0) + return weights @ v.reshape(heads, 1, nk * tokens, dim) + + def test_it_matches_plain_masked_attention(self): + torch.manual_seed(2) + q, k, v = torch.randn(3, 7, 128, 4), torch.randn(3, 11, 128, 4), torch.randn(3, 11, 128, 4) + chosen = torch.rand(3, 7, 11) < 0.4 + chosen[0, 0] = False # a row that chose nothing + chosen[1, 2] = True # a row that chose everything + want = self.dense(q, k, v, chosen) + for budget in (1, 8, 40, 100000): + got = vsr.sparse_block_attention(q, k, v, chosen, slot_budget=budget) + self.assertTrue(torch.allclose(got, want, atol=1e-5), budget) + self.assertTrue((got[0, 0] == 0).all()) + + def test_with_shares_a_row_is_filled_up_from_its_neighbourhood_and_never_reads_less(self): + torch.manual_seed(4) + q, k, v = torch.randn(2, 6, 128, 4), torch.randn(2, 9, 128, 4), torch.randn(2, 9, 128, 4) + allowed = torch.rand(2, 6, 9) < 0.8 + allowed[..., 0] = True + shares = torch.rand(2, 6, 9) * allowed + chosen = (shares > 0.5) & allowed + chosen[..., 0] = True # every row chose something + chosen[1, 3] = allowed[1, 3] # the widest row: everything it may read + widest = int(chosen.sum(-1).max()) + # one call at the widest row's width: each row reads its chosen blocks, then its best other ones + rank = chosen.float() * 2 + shares + top = torch.zeros_like(chosen).scatter(-1, rank.argsort(-1, descending=True)[..., :widest], True) + want = self.dense(q, k, v, top & allowed) + got = vsr.sparse_block_attention(q, k, v, chosen, shares, slot_budget=100000) + self.assertTrue(torch.allclose(got, want, atol=1e-5)) + self.assertTrue(((top & allowed) | ~chosen).all()) + + +class DecoderTests(unittest.TestCase): + def test_frames_one_at_a_time_match_all_at_once(self): + decoder = tiny_model().tc_decoder.double() # double precision: random weights make large numbers + x = torch.randn(5, 16 + 768, 4, 6, dtype=torch.float64) + with torch.no_grad(): + whole = decoder.decode({}, x) + memory = {} + parts = torch.cat([decoder.decode(memory, x[i : i + 1]) for i in range(5)]) + self.assertEqual(whole.shape, (5 * 4 - 3, 3, 32, 48)) + self.assertTrue(torch.allclose(whole, parts, rtol=1e-9, atol=1e-9)) + + +class ProjectorTests(unittest.TestCase): + def test_one_set_of_tokens_per_four_frames(self): + model = tiny_model() + memory = {} + clip = torch.rand(1, 3, 9, 32, 48) * 2 - 1 + with torch.no_grad(): + self.assertIsNone(model.lq_proj_in.push(memory, clip[:, :, :1])) + first = model.lq_proj_in.push(memory, clip[:, :, 1:5]) + second = model.lq_proj_in.push(memory, clip[:, :, 5:9]) + self.assertEqual(first.shape, (1, 2 * 3, 24)) + self.assertEqual(second.shape, first.shape) + self.assertFalse(torch.allclose(first, second)) + + +class WholeClipTests(unittest.TestCase): + def run_clip(self, count, height, width, scale, **kw): + torch.manual_seed(3) + frames = torch.rand(count, height, width, 3) + return vsr.upscale_frames(tiny_model(), frames, scale, device="cpu", dtype=torch.float32, **kw), frames + + def test_every_frame_comes_back_at_the_new_size(self): + for count, height, width, scale in ((1, 64, 64, 2.0), (23, 50, 90, 2.0), (30, 128, 128, 1.0)): + out, _ = self.run_clip(count, height, width, scale) + self.assertEqual(out.shape, (count, vsr._even(height * scale), vsr._even(width * scale), 3)) + self.assertTrue(torch.isfinite(out).all()) + self.assertTrue(0.0 <= float(out.min()) and float(out.max()) <= 1.0) + + def test_the_same_seed_gives_the_same_clip(self): + a, _ = self.run_clip(9, 64, 64, 2.0, seed=5) + b, _ = self.run_clip(9, 64, 64, 2.0, seed=5) + c, _ = self.run_clip(9, 64, 64, 2.0, seed=6) + self.assertTrue(torch.equal(a, b)) + self.assertFalse(torch.equal(a, c)) + + def test_colour_match_brings_the_means_back_to_the_source(self): + out, frames = self.run_clip(5, 64, 64, 2.0, color_match=True) + self.assertLess(float((out.mean(dim=(1, 2)) - frames.mean(dim=(1, 2))).abs().max()), 0.05) + + def test_another_decoder_gets_every_latent_frame(self): + seen = {} + + def decode(latents): + seen["shape"] = tuple(latents.shape) + frames = 4 * (latents.shape[2] - 1) + 1 + return torch.full((frames, latents.shape[3] * 8, latents.shape[4] * 8, 3), 0.5) + + out, _ = self.run_clip(23, 64, 64, 2.0, decode_latents=decode, color_match=False) + self.assertEqual(seen["shape"], (1, 16, 8, 16, 16)) + self.assertEqual(out.shape, (23, 128, 128, 3)) + self.assertTrue(torch.allclose(out, torch.full_like(out, 0.5))) + + def test_a_large_picture_is_done_in_tiles_that_cover_it(self): + self.assertEqual(vsr.plan_tiles(1152, 2048, None), [(0, 0, 1152, 2048)]) + self.assertEqual(vsr.plan_tiles(640, 1024, 640 * 1024), [(0, 0, 640, 1024)]) + for work_h, work_w, limit in ((1152, 2048, 1_300_000), (2176, 3840, 2_000_000), (768, 768, 100_000), (128, 1280, 50_000)): + tiles = vsr.plan_tiles(work_h, work_w, limit) + self.assertGreater(len(tiles), 1) + cover = torch.zeros(work_h, work_w) + for top, left, height, width in tiles: + self.assertEqual((height % 128, width % 128, top % 16, left % 16), (0, 0, 0, 0)) + self.assertTrue(top + height <= work_h and left + width <= work_w) + self.assertLessEqual(height * width, max(limit, 128 * 128)) + cover[top : top + height, left : left + width] += vsr.tile_weight((top, left, height, width), work_h, work_w) + self.assertGreater(float(cover.min()), 0.5) + + def test_tiles_give_every_frame_at_the_same_size(self): + whole, _ = self.run_clip(9, 128, 192, 2.0) + tiled, _ = self.run_clip(9, 128, 192, 2.0, max_tile_pixels=256 * 256) + self.assertEqual(tiled.shape, whole.shape) + self.assertTrue(torch.isfinite(tiled).all()) + self.assertTrue(0.0 <= float(tiled.min()) and float(tiled.max()) <= 1.0) + + def test_progress_counts_the_passes(self): + calls = [] + self.run_clip(40, 64, 64, 2.0, on_pass=lambda done, total: calls.append((done, total))) + self.assertEqual(calls, [(1, 4), (2, 4), (3, 4), (4, 4)]) + + +if __name__ == "__main__": + unittest.main()