diff --git a/public/banners/running-ai-video-locally-on-a-12gb-gpu.jpg b/public/banners/running-ai-video-locally-on-a-12gb-gpu.jpg new file mode 100644 index 0000000..818fe07 Binary files /dev/null and b/public/banners/running-ai-video-locally-on-a-12gb-gpu.jpg differ diff --git a/public/banners/running-ai-video-locally-on-a-12gb-gpu.webp b/public/banners/running-ai-video-locally-on-a-12gb-gpu.webp new file mode 100644 index 0000000..0547fad Binary files /dev/null and b/public/banners/running-ai-video-locally-on-a-12gb-gpu.webp differ diff --git a/public/og/running-ai-video-locally-on-a-12gb-gpu.jpg b/public/og/running-ai-video-locally-on-a-12gb-gpu.jpg new file mode 100644 index 0000000..63bb1ae Binary files /dev/null and b/public/og/running-ai-video-locally-on-a-12gb-gpu.jpg differ diff --git a/public/og/thumb/running-ai-video-locally-on-a-12gb-gpu-400.webp b/public/og/thumb/running-ai-video-locally-on-a-12gb-gpu-400.webp new file mode 100644 index 0000000..da8452d Binary files /dev/null and b/public/og/thumb/running-ai-video-locally-on-a-12gb-gpu-400.webp differ diff --git a/public/og/thumb/running-ai-video-locally-on-a-12gb-gpu-800.webp b/public/og/thumb/running-ai-video-locally-on-a-12gb-gpu-800.webp new file mode 100644 index 0000000..228693d Binary files /dev/null and b/public/og/thumb/running-ai-video-locally-on-a-12gb-gpu-800.webp differ diff --git a/public/og/zh-running-ai-video-locally-on-a-12gb-gpu.jpg b/public/og/zh-running-ai-video-locally-on-a-12gb-gpu.jpg new file mode 100644 index 0000000..f818b97 Binary files /dev/null and b/public/og/zh-running-ai-video-locally-on-a-12gb-gpu.jpg differ diff --git a/scripts/og-gen/art-meta.json b/scripts/og-gen/art-meta.json index 40c2d4e..f518286 100644 --- a/scripts/og-gen/art-meta.json +++ b/scripts/og-gen/art-meta.json @@ -1,6 +1,11 @@ { "_about": "Inputs for scripts/og-gen/render_from_art.mjs ('one art, four targets'). One entry per post: `art` = the zero-text base frame (default /.png), `evidence` = the short factual line shown in the card's evidence bar (REQUIRED, written by hand — never derived), optional `ogTitle_en` / `ogTitle_zh` = shortened display titles for the card only (consultant question 1, option C: the article keeps its real title), optional `tags` = override the three badge tags. Cards render from the post's own frontmatter (title/description/category) unless an ogTitle override is present.", - "_status": "72 of 72 posts have a hand-written evidence line; base art is generated from C:/AI/ideogram4/captions/.json", + "_status": "73 of 73 posts have a hand-written evidence line; base art is generated from C:/AI/ideogram4/captions/.json", + "running-ai-video-locally-on-a-12gb-gpu": { + "evidence": "1280x736 -> 1080p - 32 min / 5 s - $0", + "ogTitle_en": "AI Video on a 12 GB GPU", + "ogTitle_zh": "12GB 显卡跑 1080p 视频" + }, "adding-a-domain-to-someone-elses-mail-server": { "evidence": "4 DNS records · 554 antispam reject" }, diff --git a/scripts/og-gen/banner-plan.json b/scripts/og-gen/banner-plan.json index 3476147..601d9e6 100644 --- a/scripts/og-gen/banner-plan.json +++ b/scripts/og-gen/banner-plan.json @@ -940,6 +940,30 @@ 768 ] }, + "running-ai-video-locally-on-a-12gb-gpu": { + "sw": 2158, + "sh": 1218, + "left": 745, + "top": 83, + "fill": [ + 8, + 13, + 20 + ], + "zoom": 1.586, + "silhouette": 0.47, + "cut": 0.0, + "band": [ + 772, + 168, + 1246, + 504 + ], + "source_size": [ + 1360, + 768 + ] + }, "running-production-infrastructure-solo": { "sw": 2001, "sh": 1130, diff --git a/scripts/og-gen/captions/running-ai-video-locally-on-a-12gb-gpu.json b/scripts/og-gen/captions/running-ai-video-locally-on-a-12gb-gpu.json new file mode 100644 index 0000000..a7d9d70 --- /dev/null +++ b/scripts/og-gen/captions/running-ai-video-locally-on-a-12gb-gpu.json @@ -0,0 +1 @@ +{"high_level_description":"A dark-mode 3D cyber-bento product render of a single piece of industrial hardware, photographed on a seamless dark backdrop. The main high-detail 3D focal subject is positioned on the right side of the canvas, leaving clean dark negative space across the left side.","style_description":{"aesthetics":"modern dark cyber-bento, industrial precision, sleek machined surfaces, raytraced ambient occlusion","lighting":"volumetric studio rim lighting in royal cobalt blue (#4d82ff) with neon cyan (#22d3ee) and electric violet-magenta (#a855f7) synaptic glow and subtle cyan glow","medium":"3d_render","art_style":"octane render, matte obsidian hardware, frosted acrylic glass, precise machined surface detail","color_palette":["#0B0F19","#111827","#1E293B","#22D3EE","#A855F7","#4D82FF"]},"compositional_deconstruction":{"background":"Deep midnight obsidian slate background (#0b0f19 to #111827) fading into pure clean negative space across the entire left 60% of the horizontal canvas, with a soft rim vignette and faint volumetric haze.","elements":[{"type":"obj","bbox":[150,550,850,945],"desc":"A compact dark alloy pressure vessel clamped inside a narrow slot, its single outlet feeding a translucent ribbon of thin frames between two polished steel rollers that widen the ribbon as it passes. Clean unbranded matte surfaces, blank panels, no printed markings. Razor-sharp detail, no text, no letters, no watermark."}]}} \ No newline at end of file diff --git a/scripts/og-gen/metaphors.json b/scripts/og-gen/metaphors.json index 6ff186c..55bc9d7 100644 --- a/scripts/og-gen/metaphors.json +++ b/scripts/og-gen/metaphors.json @@ -38,6 +38,7 @@ "read-only-nocodb-dashboard-for-a-remote-database": "A glass observation window mounted over a distant obsidian data silo, the viewing frame open while every latch on the data hatch itself stays closed", "reading-a-containers-own-api-docs": "A scanning beam unfolding a lathe-cut obsidian shell to expose its inner circuit routing, four small inspection tools laid out on the dark bench beside it", "replacing-rdpguard-with-ipban": "An old shield slab lifted out of a dark doorway as a tighter woven black mesh gate is lowered into the same frame, grey marker pins fenced off outside it", + "running-ai-video-locally-on-a-12gb-gpu": "A compact dark alloy pressure vessel clamped inside a narrow slot, its single outlet feeding a translucent ribbon of thin frames between two polished steel rollers that widen the ribbon as it passes", "running-production-infrastructure-solo": "Three interconnected matte-black server monoliths in isometric perspective surrounded by a dense floating matrix of tiny glowing glass container cubes with emerald heartbeat lights, routed by blue optical fiber conduits into one master control node", "running-tts-as-a-service-with-token-sidecars": "A central obsidian voice resonance horn flanked by two small paired fuel canisters that replenish its expiring energy keys through thin silver tubes", "scraping-bot-walled-marketplace-warm-browser-session": "A dexterous glass gripper carrying a warm amber-lit browser window pane through a walled dark tunnel gate, holding the pane at a preserved angle", diff --git a/scripts/og-gen/prompts_catalog.json b/scripts/og-gen/prompts_catalog.json index 4262ab3..5d237c6 100644 --- a/scripts/og-gen/prompts_catalog.json +++ b/scripts/og-gen/prompts_catalog.json @@ -582,6 +582,13 @@ "prompt": "An old shield slab lifted out of a dark doorway as a tighter woven black mesh gate is lowered into the same frame, grey marker pins fenced off outside it", "caption": "captions/replacing-rdpguard-with-ipban.json" }, + { + "slug": "running-ai-video-locally-on-a-12gb-gpu", + "unique_elements": "A compact dark alloy pressure vessel clamped inside a narrow slot, its single outlet feeding a translucent ribbon of thin frames between two polished steel rollers that widen the ribbon as it passes", + "prompt": "A compact dark alloy pressure vessel clamped inside a narrow slot, its single outlet feeding a translucent ribbon of thin frames between two polished steel rollers that widen the ribbon as it passes", + "evidence_summary": "1280x736 -> 1080p - 32 min / 5 s - $0", + "caption": "captions/running-ai-video-locally-on-a-12gb-gpu.json" + }, { "slug": "running-production-infrastructure-solo", "title": "Running Production Infrastructure Solo: 162 Containers Across Three Hosts", diff --git a/src/content/posts/running-ai-video-locally-on-a-12gb-gpu.md b/src/content/posts/running-ai-video-locally-on-a-12gb-gpu.md new file mode 100644 index 0000000..76d3de3 --- /dev/null +++ b/src/content/posts/running-ai-video-locally-on-a-12gb-gpu.md @@ -0,0 +1,241 @@ +--- +title: "Running AI Video Locally on a 12 GB GPU: Real Numbers for Wan 2.2 and SeedVR2" +description: "Measured on an RTX 3060: Wan 2.2 TI2V-5B generation, SeedVR2 upscaling, the 1080p resolution trap, and the one command that fixed five failed runs." +pubDate: 2026-10-20 +category: ai +tags: [ai-video, wan, comfyui, seedvr2, local-llm, gpu, cost] +ogImage: /og/running-ai-video-locally-on-a-12gb-gpu.jpg +banner: /banners/running-ai-video-locally-on-a-12gb-gpu.jpg +--- + +Every guide to local AI video says the same thing about a 12 GB card: it can +run the small models, sort of. None of them say what that means in minutes, or +whether the output is actually 1080p, or what breaks first. + +I spent a night finding out on an RTX 3060 12 GB (i9-13900K, 48 GB RAM, ComfyUI +0.41.2 running inside a Windows VM with the GPU passed through). The end result +is a pipeline that turns one prompt and one start frame into a real 1920×1080 +shot without paying anyone per second. It is also a pipeline I only got working +after five consecutive runs failed in four different places. + +This is the record of that. + +## What I wanted, and what it competes with + +The target: short shots, 5 seconds each, stitched into something longer, at +1080p. The commercial alternative is not exotic — Kling runs about \$0.112 per +second for reference-to-video, so a 5-second shot is roughly \$0.56. That is +cheap if a shot lands first try. + +The problem is that shots do not land first try. Generating video is an +iterative act: you change the prompt, change the start frame, drop a seed, run +it again. The cost that hurts is not the final render, it is the thirty renders +that taught you what the final one should be. That is the work a local GPU is +actually good at. + +## The models that fit in 12 GB + +| File | Role | Size | +|---|---|---| +| `wan2.2_ti2v_5B_fp16.safetensors` | video model | 9.99 GB | +| `umt5_xxl_fp8_e4m3fn_scaled.safetensors` | text encoder | 6.74 GB | +| `wan2.2_vae.safetensors` | video VAE | 1.41 GB | +| `seedvr2_3b_int8_convrot.safetensors` | upscaler | 3.46 GB | +| `seedvr2_ema_vae_fp16.safetensors` | upscaler VAE | 0.50 GB | + +Wan 2.2 TI2V-5B is the one that fits. It is trained around 1280×704, it does +text-to-video and image-to-video with the same weights, and Apache-2.0 means the +output is mine to use. The 14B variant is better and does not fit; LTX-2.3 is +22B and runs, but at a speed that turns iteration into a chore. + +## What it costs in time (all measured, not estimated) + +5-second clips, 121 frames at 24 fps, 20 sampling steps: + +| Resolution | Total | Sampling | Decode | VRAM peak | +|---|---|---|---|---| +| 832×480 | 6.1 min | 4.8 min | 1.3 min | 10.3 GB | +| 1280×704 | 17.9 min | 15.1 min | 2.9 min | 12.1 GB | + +Halving the pixel count does not halve the time — it cuts it to about a third. +That is the usual superlinear scaling of a diffusion transformer, and it is the +reason 480p exists as a draft tier at all. + +## Failure one: the decode that runs out of host memory + +The first 720p attempt sampled fine and then died in the VAE decode: + +``` +RuntimeError: DefaultCPUAllocator: not enough memory: +you tried to allocate 1308426240 bytes +``` + +That is not a VRAM error. The Wan VAE decode for 121 frames wants about 1.3 GB +of CPU-side buffers in one go, and the VM had only about 12 GB free out of 48. +Switching to a tiled decode — tile 272, overlap 32, temporal 32, temporal +overlap 8 — dropped decode VRAM from 11.9 GB to 3.9 GB and it completed. + +The lesson is not "use tiled decode everywhere". At 25 frames the non-tiled +decode needs about 0.27 GB and is faster and simpler. Match the decoder to the +frame count instead of always reaching for the tiled one. + +## Failure two: the upscaler takes the whole server down + +SeedVR2 3B produces genuinely good 1.5× output, and at 121 frames it reliably +killed the process: + +``` +Windows fatal exception: access violation + comfy/ldm/seedvr/vae.py ... decode_tiled +``` + +Not an exception I could catch — a native crash that takes the HTTP server with +it, leaving the port dead. Installing `triton-windows` removed an earlier +`PatchTritonVAE requires triton` import error but changed nothing about the +crash. It reproduced three times at 121 frames and never once at 41. + +So the workaround is a cut: split the clip into segments of at most 41 frames, +upscale each, concatenate. Ugly, reliable, and the seams are invisible if every +cut lands on an exact frame boundary. + +## Failure three, and the one command that fixed it + +Two hours after a clean 5-second render, the same script started failing. Then +it failed again, differently. Over five runs: + +``` +aimdo: xfer_file_read_at: GetOverlappedResult failed error=1450 +HostBuffer.read_file_slice failed ← during sampling +hostbuf_file_reader_read failed ← during decode +Windows fatal exception: access violation ← during model load +``` + +I did what anyone would do and blamed the thing I had just changed — the +resolution. Ran a 704p control at the same frame count. It failed too. The +resolution was never the problem. + +The actual cause: ComfyUI keeps the 10 GB model resident after a job finishes. +Idle VRAM was sitting at 9.7 GB. The next submission could not fit it, so the +loader fell back to streaming weight slices from disk, and that path is broken +on this machine — error 1450 is `ERROR_NO_SYSTEM_RESOURCES`, a host-side +resource failure, not a sampling failure. + +One request fixes it, and it belongs before every run: + +```bash +curl -X POST http://127.0.0.1:8188/free \ + -H "Content-Type: application/json" \ + -d '{"unload_models": true, "free_memory": true}' +``` + +VRAM went 9.7 GB → 1.1 GB, and the exact job that had failed three times passed +on the next attempt. + +It is also worth saying which fix made things worse, because it is the one most +guides suggest. Launching with `--disable-dynamic-vram` moved the crash +earlier: the 9.99 GB model no longer fit the 8.45 GB left after the text +encoder, so the loader sliced the memory map and died in `torch.storage` while +loading. Same wall, worse side of it. + +## The 1080p trap: 720 is not a multiple of 32 + +`Wan22ImageToVideoLatent` requires width and height to be multiples of 32. 720 +is not — 720 ÷ 32 = 22.5 — so the nearest legal height under 720 is 704, and +1280×704 is 16:8.8, not 16:9. Upscale that by 1.5 and the honest output is +**1920×1056**, which is not 1080p and will letterbox or stretch somewhere later. + +Three ways out: + +| Route | Result | Cost | +|---|---|---| +| Generate 1280×**736** (23×32), upscale 1.5× → 1920×1104, crop 24 px | exact 1920×1080 | no bars, no distortion | +| Keep 704, pad | 1920×1080 | 12 px black bars top and bottom | +| Keep 704, scale 1.534 then crop | 1920×1080 | loses ~2 % of the width | + +I took the first. 736 is a legal multiple of 32, it is much closer to 16:9 than +704, and cropping 24 rows off the top and bottom removes only the padding the +model never used. + +Do not stretch 1056 into 1080. Faces get 2.3 % wider and it reads as wrong +instantly. + +## The pipeline, end to end + +Draft at 480p to judge motion and composition — sharpness at 480p tells you +nothing, so do not evaluate it there. Then the accepted shots go through: + +```bash +# clear VRAM first, every time +curl -X POST http://127.0.0.1:8188/free -d '{"unload_models":true,"free_memory":true}' + +# 1. generate at 1280x736, 25 frames per second of runtime +python bench_wan.py --w 1280 --h 736 --frames 25 --steps 20 --tag final_1s --cold + +# 2. upscale each <=41-frame segment to 1920x1104 +python bench_upscale.py --file segment.mp4 --scale 1.5 --tsize 21 --chunk 21 + +# 3. crop to exact 1080p +ffmpeg -i upscaled.mp4 -vf "crop=1920:1080:0:12" -c:v libx264 -crf 16 final.mp4 +``` + +This is the actual output, generated locally and hosted on my own CDN: one +second, 1920×1080, 24 fps, from a single start frame. + + + +*Wan 2.2 TI2V-5B, 1280×736 → SeedVR2 → cropped to 1920×1080. 2 minutes 13 seconds +of sampling, 2 minutes 49 seconds of upscaling, nothing rendered by anyone else.* + +## What the upscale actually buys + +Measured with Laplacian variance, the upscale moves detail energy from 673 to +766 — about 14 %. Edges, hair and neon signage get visibly crisper at 1:1. + +Skin does not. A 3B upscaler sharpens information that is already in the frame; +it does not invent pores or fabric grain. Treating SeedVR2 as "restoration" +leads to disappointment. Treating it as "make the 720p-class render hold up at +1080p" is accurate. + +## The result + +| Shot length | Generate | Upscale | Total | Cost | +|---|---|---|---|---| +| 1 second (25 frames) | 3.4 min | 2.8 min | ~6 min | \$0 | +| 5 seconds (121 frames) | 17.9 min | 14.1 min | ~32 min | \$0 | + +An 8-shot, 60-second sequence is roughly half a day of local compute, or about +\$5 through a commercial API — and 40 minutes instead of half a day. Both are +true, and they answer different questions. The local machine is for the thirty +takes nobody pays for. The API is for the one take that ships. + +## What I would do differently + +Three things, in order of how much time they would have saved. + +**Instrument the VRAM before blaming the code.** The five-failure detour +existed because I trusted "this configuration worked two hours ago" and started +changing parameters. One `nvidia-smi` between runs would have shown 9.7 GB +resident and ended the hunt immediately. + +**Read the layout constraint before designing the pipeline.** The 1920×1056 +surprise came from not checking that the latent node wants multiples of 32. +That is a five-second check that cost a full re-render. + +**Cut the upscale into segments from the start.** A 41-frame ceiling sounds +arbitrary until the first crash. Now it is just the unit of work. + +The honest summary: 12 GB is enough to make 1080p AI video locally, at about +6 minutes per finished second. It is not enough to be careless about memory. + +--- + +*If this is the kind of thing you want running in your own business — local +video generation, self-hosted AI pipelines, or the plumbing that keeps a GPU +busy without an engineer babysitting it — I build these:* + +- 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969) +- 📧 **Email:** [me@hoelee.com](mailto:me@hoelee.com?subject=Local%20AI%20video%20pipeline) +- 🌐 **Website:** [hoelee.com](https://hoelee.com) diff --git a/src/content/posts/zh/running-ai-video-locally-on-a-12gb-gpu.md b/src/content/posts/zh/running-ai-video-locally-on-a-12gb-gpu.md new file mode 100644 index 0000000..dc57c26 --- /dev/null +++ b/src/content/posts/zh/running-ai-video-locally-on-a-12gb-gpu.md @@ -0,0 +1,175 @@ +--- +title: "12GB 显卡跑本地 AI 视频:Wan 2.2 加 SeedVR2 的真实数据" +description: "在 RTX 3060 上实测:Wan 2.2 TI2V-5B 生成、SeedVR2 放大、1080p 分辨率陷阱,以及那条让连续五次失败全部通过的命令。" +pubDate: 2026-10-20 +category: ai +tags: [ai-video, wan, comfyui, seedvr2, local-llm, gpu, cost] +ogImage: /og/zh-running-ai-video-locally-on-a-12gb-gpu.jpg +banner: /banners/running-ai-video-locally-on-a-12gb-gpu.jpg +--- + +所有讲本地 AI 视频的文章对 12GB 显卡的说法都差不多:小模型能跑,勉强。没有一篇说清楚"勉强"折算成多少分钟,也没人说产出到底是不是真的 1080p,更没人说最先崩的是哪一环。 + +我用一个晚上把这些问题问清楚了。硬件是 RTX 3060 12GB(i9-13900K、48GB 内存,ComfyUI 0.41.2 跑在 Windows 虚拟机里,显卡直通)。最后拿到一条流水线:一个提示词加一张起始图,就能产出真正的 1920×1080 镜头,不用按秒付钱给任何人。但它是在连续五次失败、而且崩在四个不同位置之后才跑通的。 + +这篇就是那晚的记录。 + +## 我要的东西,以及它和什么在竞争 + +目标很具体:每个镜头 5 秒,拼成更长的片子,分辨率 1080p。商业方案并不稀奇——Kling 的参考图生视频大约 \$0.112/秒,5 秒镜头约 \$0.56。如果一次就过,这个价格很便宜。 + +问题在于镜头不会一次就过。生成视频是个反复试的过程:改提示词、换起始帧、换种子、再跑一次。真正花钱的不是最后那一条,是教会我"最后一条该长什么样"的那三十条。这正好是本地显卡擅长的事。 + +## 12GB 能装下的模型 + +| 文件 | 作用 | 大小 | +|---|---|---| +| `wan2.2_ti2v_5B_fp16.safetensors` | 视频模型 | 9.99 GB | +| `umt5_xxl_fp8_e4m3fn_scaled.safetensors` | 文本编码器 | 6.74 GB | +| `wan2.2_vae.safetensors` | 视频 VAE | 1.41 GB | +| `seedvr2_3b_int8_convrot.safetensors` | 放大器 | 3.46 GB | +| `seedvr2_ema_vae_fp16.safetensors` | 放大器 VAE | 0.50 GB | + +能用的是 Wan 2.2 TI2V-5B。它围绕 1280×704 训练,同一套权重同时做文生视频和图生视频,Apache-2.0 意味着产出归我。14B 版本更好,但装不下;LTX-2.3 是 22B,能跑,但那个速度会让"反复试"变成一种折磨。 + +## 时间成本(全部实测,不是估算) + +5 秒片段,121 帧、24fps、20 步采样: + +| 分辨率 | 总计 | 采样 | 解码 | 显存峰值 | +|---|---|---|---|---| +| 832×480 | 6.1 分钟 | 4.8 分钟 | 1.3 分钟 | 10.3 GB | +| 1280×704 | 17.9 分钟 | 15.1 分钟 | 2.9 分钟 | 12.1 GB | + +像素数减半,时间不是减半,而是降到约三分之一。这是扩散 Transformer 典型的超线性关系,也正是 480p 这档草稿存在的理由。 + +## 第一次失败:解码把主机内存吃光 + +第一次 720p 采样很顺利,然后死在 VAE 解码: + +``` +RuntimeError: DefaultCPUAllocator: not enough memory: +you tried to allocate 1308426240 bytes +``` + +这不是显存错误。121 帧的 Wan VAE 解码需要一次性开出约 1.3GB 的 CPU 侧缓冲,而虚拟机 48GB 里当时只剩约 12GB 空闲。改用分块解码——tile 272、overlap 32、temporal 32、temporal overlap 8——解码显存从 11.9GB 降到 3.9GB,跑通了。 + +教训不是"一律用分块解码"。25 帧时普通解码只要约 0.27GB,更快也更简单。解码方式应该跟着帧数走,而不是条件反射地选分块。 + +## 第二次失败:放大器把整个服务打死 + +SeedVR2 3B 的 1.5× 输出确实不错,但在 121 帧上它稳定地把进程干掉: + +``` +Windows fatal exception: access violation + comfy/ldm/seedvr/vae.py ... decode_tiled +``` + +这不是可以 catch 的异常,是原生崩溃,顺手把 HTTP 服务一起带走,端口直接死掉。装上 `triton-windows` 消掉了更早的那个 `PatchTritonVAE requires triton` 导入错误,但对崩溃毫无帮助。121 帧复现三次,41 帧一次都没崩。 + +所以绕过方式是切:把片段切成不超过 41 帧的段,分别放大,再拼接。难看,但可靠;只要每刀都落在精确的帧边界上,接缝看不出来。 + +## 第三次失败,以及那条解决问题的命令 + +在一条干净的 5 秒渲染之后两小时,同一个脚本开始失败。然后又失败,而且报的错不一样。五次下来: + +``` +aimdo: xfer_file_read_at: GetOverlappedResult failed error=1450 +HostBuffer.read_file_slice failed ← 采样阶段 +hostbuf_file_reader_read failed ← 解码阶段 +Windows fatal exception: access violation ← 模型加载阶段 +``` + +我做了任何人都会做的事:怪到刚改过的东西头上——分辨率。于是跑了一次同帧数的 704p 对照。它也失败。分辨率从来就不是问题。 + +真正的原因:ComfyUI 在作业结束后把 10GB 模型留在显存里常驻。空闲时显存占着 9.7GB。下一次提交装不下,加载器就退回"从磁盘流式读权重切片"的路径,而那条路径在这台机器上是坏的——error 1450 是 `ERROR_NO_SYSTEM_RESOURCES`,属于主机侧资源失败,不是采样失败。 + +一个请求就解决,而且应该放在每次跑之前: + +```bash +curl -X POST http://127.0.0.1:8188/free \ + -H "Content-Type: application/json" \ + -d '{"unload_models": true, "free_memory": true}' +``` + +显存从 9.7GB 掉到 1.1GB,那个已经失败三次的作业下一次就过了。 + +还有一个"修复"必须点名,因为多数教程会推荐它:加 `--disable-dynamic-vram` 启动。它把崩溃提前了——9.99GB 的模型塞不进文本编码器剩下的 8.45GB,加载器转去切内存映射,在 `torch.storage` 里直接崩。同一堵墙,更糟的一面。 + +## 1080p 的陷阱:720 不是 32 的倍数 + +`Wan22ImageToVideoLatent` 要求宽高都是 32 的倍数。720 不是——720 ÷ 32 = 22.5——所以 720 以下最近的合法高度是 704,而 1280×704 是 16:8.8,不是 16:9。乘 1.5 之后,诚实的产出是 **1920×1056**,这不是 1080p,后面某处一定会出现黑边或被拉伸。 + +三条路: + +| 做法 | 结果 | 代价 | +|---|---|---| +| 生成 1280×**736**(23×32),放大 1.5× → 1920×1104,裁 24px | 精确 1920×1080 | 无黑边、无变形 | +| 保持 704,补边 | 1920×1080 | 上下各 12px 黑边 | +| 保持 704,放大 1.534 后裁宽 | 1920×1080 | 损失约 2% 画面宽度 | + +我选第一条。736 是合法的 32 倍数,比 704 更接近 16:9,而上下裁掉 24 行只是去掉模型本来就没用上的边缘。 + +不要把 1056 拉伸成 1080。人脸会横向宽 2.3%,一眼就看得出不对。 + +## 完整的流水线 + +先用 480p 出草稿,判断动作和构图——480p 的判断不了清晰度,所以别在那档评估画质。通过的镜头再走: + +```bash +# 每次跑之前先清显存 +curl -X POST http://127.0.0.1:8188/free -d '{"unload_models":true,"free_memory":true}' + +# 1. 按每秒 25 帧在 1280x736 生成 +python bench_wan.py --w 1280 --h 736 --frames 25 --steps 20 --tag final_1s --cold + +# 2. 每段不超过 41 帧,分别放大到 1920x1104 +python bench_upscale.py --file segment.mp4 --scale 1.5 --tsize 21 --chunk 21 + +# 3. 裁成精确 1080p +ffmpeg -i upscaled.mp4 -vf "crop=1920:1080:0:12" -c:v libx264 -crf 16 final.mp4 +``` + +下面这条就是在本地生成的输出,放在我自己的 CDN 上:1 秒、1920×1080、24fps,只给了一张起始图。 + + + +*Wan 2.2 TI2V-5B,1280×736 → SeedVR2 → 裁到 1920×1080。采样 2 分 13 秒,放大 2 分 49 秒,没有一帧是别人渲染的。* + +## 放大到底买到了什么 + +用拉普拉斯方差量,放大把细节能量从 673 提到 766,大约 14%。边缘、头发、霓虹招牌在 1:1 下确实更锐。 + +皮肤没有。3B 的放大器只是把画面里已有的信息锐化,它不会凭空造出毛孔或布料颗粒。把 SeedVR2 当成"修复"会失望;把它当成"让 720p 级的渲染在 1080p 下站得住",才准确。 + +## 结果 + +| 镜头长度 | 生成 | 放大 | 合计 | 成本 | +|---|---|---|---|---| +| 1 秒(25 帧) | 3.4 分钟 | 2.8 分钟 | 约 6 分钟 | \$0 | +| 5 秒(121 帧) | 17.9 分钟 | 14.1 分钟 | 约 32 分钟 | \$0 | + +8 个镜头、60 秒的片子,本地大约半天算力;走商业 API 大约 \$5,40 分钟。两个数字都是真的,回答的是不同问题。本地机器负责那三十条没人付钱的试错,API 负责最后那一条要交付的。 + +## 如果重来一次 + +三件事,按能省的时间排序。 + +**先量显存,再怀疑代码。** 那五次失败的弯路,是因为我相信"这套配置两小时前还能跑",于是开始改参数。中间跑一次 `nvidia-smi` 就会看见 9.7GB 常驻,当场结束排查。 + +**设计流水线之前先读尺寸约束。** 1920×1056 那个意外,来自没先确认 latent 节点要求 32 的倍数。这是五秒钟的检查,代价是一整轮重渲。 + +**一开始就把放大切成段。** 41 帧这个上限听起来很随意,直到第一次崩溃为止。现在它就是工作单位。 + +诚实的总结:12GB 足够在本地做 1080p AI 视频,大约每完成 1 秒成片要 6 分钟;但它不足以让人对内存掉以轻心。 + +--- + +*如果你想把这类东西搬进自己的生意——本地视频生成、自托管的 AI 流水线,或者让一张显卡持续干活又不用工程师盯着的那套管道——这些我都做:* + +- 📱 **WhatsApp:** [+60 12-797 2969](https://wa.me/60127972969) +- 📧 **邮件:** [me@hoelee.com](mailto:me@hoelee.com?subject=%E6%9C%AC%E5%9C%B0%20AI%20%E8%A7%86%E9%A2%91%E6%B5%81%E6%B0%B4%E7%BA%BF) +- 🌐 **网站:** [hoelee.com](https://hoelee.com)