SeedVR2-1.4B β€” a 6-layer distillation of SeedVR2-7B (sharp)

A 1.44 B-parameter, 6-layer one-step diffusion image upscaler distilled from ByteDance-Seed/SeedVR2-7B (the sharp EMA variant, converted to safetensors). It targets the case where the 7B teacher is too large or too slow to be practical: 5.7Γ— smaller on disk, and it runs in β‰ˆ4.6 GB where the teacher needs β‰ˆ14–16 GB.

⚠️ Read this before downloading: it will not load in a stock SeedVR2 workflow

This model has 6 transformer blocks. The stock SeedVR2 architecture has 36. Any loader that builds the standard model will look for blocks.6 … blocks.35, find nothing, and fail with roughly 930 missing keys. That is a hard failure, not a quality regression.

ComfyUI: a drop-in folder fixes it β€” copy comfyui/ComfyUI-SeedVR2-1.4B/ into custom_nodes/ and restart. No ComfyUI source file is edited. Step-by-step guide below β€” see Using it in ComfyUI. ComfyUI detects SeedVR2 architecture by inspecting the checkpoint, and it only knows blocks.31 (3B) and blocks.35 (7B); the folder adds a branch for six blocks. No setting or filename change can substitute for it. It also carries three memory and speed fixes for still-image upscaling β€” see what it does.

If you are writing your own loading code, it is a one-line change and everything else is standard β€” see Framework compatibility.

The speed advantage grows with the job. At 4Γ— it is β‰ˆ1.6Γ— faster; at 8Γ— it is 4.7–5.6Γ— faster (55 s vs 257–307 s). The bigger the upscale, the more the six-layer transformer pays off β€” because the transformer's share of the work rises with output size, and at 8Γ— the 36-layer teacher also stops fitting comfortably in memory. Details in Performance.

It produces excellent 2×–4Γ— upscales. At 4Γ— it tracks the 36-layer teacher closely on structure, and actually delivers more edge energy than the teacher β€” outputs read crisp and detailed, not soft. If you are memory-bound, latency-bound, or batching a lot of images, this is the model to reach for. Full side-by-side comparisons are linked in Results so you can judge with your own eyes rather than take a table's word for it.

Everything below is measured on 15 scenes against the teacher, published with per-scene ranges rather than flattering averages.

this model teacher advantage
transformer layers 6 36 6Γ— fewer
parameters 1,442,608,252 8,239,608,232 5.7Γ— fewer
weights on disk (fp16) 2.69 GB 15.35 GB 5.7Γ— smaller
512β†’2048 (4Γ—) 20.2–22.5 s 33.0–38.4 s β‰ˆ1.6Γ— faster
512β†’2048 peak RAM 4.6 GB 14.2–16.4 GB β‰ˆ3Γ— lighter
512β†’4096 (8Γ—) 54.4–55.2 s 257–307 s * 4.7–5.6Γ— faster

Measured on an Apple M2 Ultra (64 GB) via MLX, model load included. See Performance for why the teacher's 8Γ— figure is what it is.

On the teacher's parameter count: "7B" is its published name, not its size β€” the checkpoint holds 8,239,608,232 parameters, which is exactly the 15.35 GiB it occupies at fp16. The 5.7Γ— figures above are computed from the true counts, and match the disk-size ratio as they should, both models being fp16.


Who this is for

The teacher is an excellent upscaler that many people cannot actually run. At 36 layers and 15.35 GB of weights it wants a workstation; on a 64 GB machine it already thrashes at 8Γ—, and it is simply out of reach on consumer laptops, integrated GPUs and phones.

This model exists to move that line. Six layers instead of thirty-six, 2.69 GB instead of 15.35, and a 4.6 GB peak instead of 14–16 GB β€” which is the difference between "runs on a 16 GB machine" and "does not run at all". The layer count is what drives it: attention and MLP cost scale with depth, so cutting 36 β†’ 6 cuts both the resident weights and the activation working set, not just the file size.

Mobile

Mobile deployment is plausible with this model in a way it is not with the teacher. Three things have to line up, and all three are ordinary engineering rather than research.

1. Stage the pipeline so only one model is resident at a time. This is the single biggest factor on a phone, and it is what makes the whole thing feasible. A generate-then-upscale pipeline must run strictly sequentially, fully releasing each stage before the next loads:

text encoder  β†’ run β†’ UNLOAD
diffusion     β†’ run β†’ UNLOAD
upscaler      β†’ run β†’ UNLOAD

Peak memory then becomes the largest single stage, not the sum of them. Holding two of these resident at once is what actually exhausts a phone; holding one at a time is tractable.

This model is well suited to that pattern for a structural reason: upscaling here is prompt-free. There is no text encoder in the upscale stage at all β€” just the transformer and the VAE. The stage has nothing else to carry.

2. Quantise the transformer. At fp16 the weights are 2.69 GB (plus a 0.47 GB VAE) and the measured 4Γ— peak is 4.6 GB β€” still too much for most phones as-is. Quantisation is the standard answer and this architecture takes it well:

precision transformer + VAE (fp16) weights resident
fp16 2.69 GB 0.47 GB β‰ˆ3.2 GB
int8 β‰ˆ1.4 GB 0.47 GB β‰ˆ1.9 GB
int4 β‰ˆ0.7 GB 0.47 GB β‰ˆ1.2 GB

The VAE is small enough to leave at full precision, which is also where quantisation tends to hurt most.

3. Tile the input. The transformer denoises the whole latent grid in one pass, so activation memory scales with output area. Tiling bounds that working set independently of the final output size, so a 4Γ— or 8Γ— job costs roughly the same peak as a small one β€” it just takes more passes. A modest overlap (β‰ˆ64 output pixels) blends without visible seams.

Combined β€” one stage resident, int4 weights, tiled activations β€” the upscale stage lands in the β‰ˆ1.5 GB region, which is a realistic budget on current high-end phones.

So the accurate claim is not "this runs on your phone today". It is: this is small enough that mobile deployment is an engineering problem rather than an impossibility. The 7B teacher is not in that category at any quantisation β€” at 15.35 GB of fp16 weights it is out of reach before the first activation is allocated.

If you have the memory and want maximum fidelity, use the teacher. If you are memory- or latency-bound β€” laptop, integrated GPU, batch jobs, or a mobile target β€” this trades a measurable amount of fine detail for roughly a fifth of the footprint.


Files

file size what it is
seedvr2_distill_6L_1.4B_sharp_fp16.safetensors 2.69 GB the distilled transformer (6 blocks)
ema_vae_fp16.safetensors 0.47 GB SeedVR2 VAE, unmodified from the teacher
comfyui/seedvr2_distill_6L_1.4B_sharp_fp16_comfyui.safetensors 2.69 GB ComfyUI variant β€” identical weights plus two conditioning tensors
comfyui/ComfyUI-SeedVR2-1.4B/ 9 KB drop-in support folder for ComfyUI β€” see what it does
comfyui/seedvr2_1.4b_upscale_image.json 15 KB ready-made ComfyUI workflow

Which transformer do I want?

  • PyTorch / MLX / your own code β†’ the root seedvr2_distill_6L_1.4B_sharp_fp16.safetensors.
  • ComfyUI β†’ the comfyui/ variant. ComfyUI refuses to load a SeedVR2 checkpoint that does not contain positive_conditioning and negative_conditioning tensors (required_keys in its supported_models.py), so the plain file fails its match check even when the architecture is detected correctly.

The two differ by 1.2 MB: positive_conditioning [58, 5120] and negative_conditioning [64, 5120], both bf16. All 198 transformer tensors are byte-identical between the files β€” the variant is a strict superset. Because SeedVR2 upscaling is prompt-free these embeddings are fixed constants, the same for every SeedVR2 model; they are taken from Comfy-Org/SeedVR2 (Apache-2.0, same lineage as the teacher) so that the file matches what ComfyUI expects.

Those two files are the whole runnable set β€” 3.15 GiB (3.39 GB) together. Upscaling is prompt-free, so there is no text encoder and no config to download.

These weights are not quantised. Every tensor is plain F16 β€” 198 in the transformer, 250 in the VAE β€” with no quantised dtypes and no framework metadata of any kind. If a model-hub sidebar files this under a "quantized" heading, that is the hub's UI grouping the mlx tag, not a property of the files. Quantising them is a reasonable thing to do (see Mobile for what int8/int4 buys), but it has not been done here β€” what you download is full fp16.

Using it in ComfyUI

ComfyUI supports SeedVR2 natively. It decides which architecture to build by inspecting the checkpoint, and the branches it ships only recognise blocks.31 (3B) and blocks.35 (7B). This model has six blocks, so nothing matches and it will not load until ComfyUI is taught about it.

That is handled by a small support folder you drop into custom_nodes/ β€” no ComfyUI source files are edited, so it survives updates. It also fixes a performance trap that has nothing to do with this model but hits it hard; see What the support folder does if you want to know before installing it.

Everything referenced below is in comfyui/ in this repo. Allow about five minutes.

the finished ComfyUI workflow β€” load image, upscale, save, compare

The finished graph (click to enlarge): Load Image β†’ Upscale Image (SeedVR2 1.4B) β†’ Save Image β†’ Compare Images. 512Γ—512 in, 2048Γ—2048 out at 4Γ—, one step. This is the workflow you drag onto the canvas in step 5 β€” nothing to wire up.

1. Download the two files

From the Files tab of this repo:

  • comfyui/seedvr2_distill_6L_1.4B_sharp_fp16_comfyui.safetensors β€” the model (2.69 GB). Use the comfyui/ one, not the file in the repo root β€” ComfyUI will not load the root file (see Files for why).
  • ema_vae_fp16.safetensors β€” the VAE (0.47 GB)

You need both. The VAE is not bundled into the model file.

2. Put them in your ComfyUI model folders

ComfyUI/models/diffusion_models/seedvr2_distill_6L_1.4B_sharp_fp16_comfyui.safetensors
ComfyUI/models/vae/seedvr2_ema_vae_fp16.safetensors

Note the VAE is renamed on the way in β€” ComfyUI's SeedVR2 workflow expects seedvr2_ema_vae_fp16.safetensors.

If you use ComfyUI Desktop, these live under your base directory, not the app folder β€” the path is shown in Settings, and is the same folder that contains input/ and output/.

3. Install the support folder

Copy the whole comfyui/ComfyUI-SeedVR2-1.4B/ folder into your ComfyUI custom_nodes/ directory, so you end up with:

ComfyUI/custom_nodes/ComfyUI-SeedVR2-1.4B/__init__.py

That is the whole installation. It registers no nodes and edits none of ComfyUI's own files β€” it just tells ComfyUI how to read a 6-block checkpoint, and adds a still-image fast path to the VAE.

It cannot break your existing models. The detection branch fires only when blocks.5 is present and blocks.6 is absent, so the 7B (blocks 0–35) and 3B (blocks 0–31) still reach their own branches untouched, and anything that is not SeedVR2 never reaches the check at all.

4. Restart ComfyUI

custom_nodes/ is read at startup. Reloading the browser page is not enough β€” restart the server (or quit and reopen ComfyUI Desktop). You should see this in the log:

SeedVR2-1.4B: 6-block detection + single-frame VAE fast path

5. Load the ready-made workflow

Download comfyui/seedvr2_1.4b_upscale_image.json and drag the file onto the ComfyUI canvas. The whole graph appears, already wired.

That is it β€” no nodes to add, nothing to connect. It is ComfyUI's own official SeedVR2 template with the model filename swapped, so the sampler settings and wiring are theirs, not mine.

6. Check the two file names match

Open the Upscale Image (SeedVR2 1.4B) node (double-click to enter the subgraph) and confirm:

  • UNETLoader β†’ seedvr2_distill_6L_1.4B_sharp_fp16_comfyui.safetensors
  • VAELoader β†’ seedvr2_ema_vae_fp16.safetensors

⚠️ Two easy mistakes here. The model must be the _comfyui variant β€” the root file will fail with "Could not detect model type" even with the support folder installed. And the VAE must be renamed to seedvr2_ema_vae_fp16.safetensors, which is what ComfyUI's SeedVR2 template looks for.

If either dropdown is empty or red, the file is in the wrong folder β€” go back to step 2.

7. Pick your image and upscale factor

  • Load Image node β†’ choose your picture.
  • ResizeImageMaskNode (inside the subgraph) β†’ this is where the upscale factor lives. It is set to scale by multiplier = 4 with lanczos. Change the 4 to 2 or 3 if you prefer.

4Γ— is the recommended operating point β€” that is where this model is closest to the teacher (see Quality vs the teacher). 8Γ— works but degrades noticeably.

Do not change the KSampler. It is steps 1, cfg 1, euler, simple, denoise 1 β€” SeedVR2 is a one-step model and raising steps will not improve it.

8. Run

Press Run / Queue Prompt. The first run reads 3.2 GB of weights from disk and will sit still for a while before showing progress; later runs reuse them. If your models live on an external or network drive, that first read alone can take a minute β€” it is disk speed, not the model.

The workflow includes an ImageCompare node so you can slide between before and after.

⚠️ The speed figures in Performance were measured in a different pipeline (MLX, with the VAE fast path below and no tiling) and will not be reproduced by ComfyUI on PyTorch. A 512β†’2048 4Γ— job measured 44.8 s here against 20.2–22.5 s in MLX β€” roughly 2Γ—. See Performance for what is and is not comparable.

What the support folder does

Four independent patches, all applied at import, all falling back to ComfyUI's own code path the moment anything looks unfamiliar. One makes the model load at all; the other three are about memory and speed, and help any SeedVR2 image upscale. Escape hatches: SEEDVR2_1_4B_NO_DETECT=1, SEEDVR2_1_4B_NO_FASTVAE=1, SEEDVR2_1_4B_NO_TILECACHE=1, SEEDVR2_1_4B_NO_UNLOAD=1.

1. Architecture detection. ComfyUI decides what to build by inspecting the checkpoint, and every shipped branch keys on blocks.31 or blocks.35. This wraps detect_unet_config() with a branch for a 6-block SeedVR2, guarded on blocks.5 present and blocks.6 absent. The values are not guesses β€” they are ComfyUI's own 7B "separate key" branch with the depth changed, each cross-checked against the published weights (vid_dim 3072 from blocks.0.ada.vid.attn_gate; heads 24 because 24 Γ— head_dim 128 = 3072; mlp_type normal because the keys are mlp.vid.proj_in, not proj_in_gate). Verified: ComfyUI's own detect_unet_config() then returns image_model=seedvr2, num_layers=6, mm_layers=6, vid_dim=3072, heads=24, mlp_type=normal, rope_type=rope3d, rope_dim=64.

2. Single-frame VAE fast path. This one is not specific to this model β€” it helps every SeedVR2 image upscale, and it is the single biggest thing standing between ComfyUI and the timings in Performance.

SeedVR2's VAE is a video VAE built from causal 3-D convolutions. Upscaling a still image hands it one frame. Internally each InflatedCausalConv3d replicates that single frame kt times along the temporal axis and convolves β€” so every temporal tap sees the same frame, and the result is exactly

out = Ξ£β‚œ W[:, :, t] * frameβ‚€  =  conv2d(frameβ‚€, Ξ£β‚œ W[:, :, t])

a 2-D convolution whose kernel is the sum of the temporal taps. The patch does that directly. It removes ktβˆ’1 of every kt multiply-accumulates as pure redundancy, and β€” more importantly for the out-of-memory failures β€” never materialises the ktΓ—-replicated activation tensor at all.

Measured on an M2 Ultra, ComfyUI's real InflatedCausalConv3d, PyTorch 2.5.1 / MPS:

stock 3-D collapsed 2-D
one 128-ch 256Γ—256 conv 19.2 ms 2.1 ms 9.1Γ—
full VAE decode β†’ 512Γ—512 5.36 s 1.81 s 3.0Γ—
full VAE decode β†’ 1024Γ—1024 12.35 s 2.69 s 4.6Γ—

The per-layer number is larger than the end-to-end one because not every layer is a 3-tap conv, and because MPS's conv2d kernel is simply better optimised than its conv3d.

Correctness. Checked against stock output across all 36 combinations of kernel/stride/padding used by this VAE Γ— both memory-limit regimes Γ— every MemoryState, including the internal memory-cache contents. Zero mismatches. Worst output delta is 3.9 Γ— 10⁻³ in fp16 and 1.0 Γ— 10⁻⁡ in fp32 β€” it scales with precision, which is the signature of floating-point round-off rather than a logic difference. Video (T > 1) never enters this path.

3. Release the allocator cache between tiles. A 4Γ— job is decoded as roughly 40 tiles, each running the whole decoder stack. PyTorch's MPS caching allocator does not hand freed blocks back to the system, so the pool ratchets upward tile after tile. That is how a job whose largest single tensor is about 100 MB reaches an 86 GB footprint and dies in whichever allocation happens to come next β€” the traceback blames a torch.cat deep in the decoder, but nothing there is large; the memory was already gone. Releasing the cache once per tile keeps the footprint flat.

4. Drop the transformer before the VAE decode. Sampling is finished by the time the VAE runs, so the diffusion model is dead weight for the entire decode β€” but ComfyUI keeps it resident. Stock logs show 0 models unloaded immediately before Requested to load VideoAutoencoderKLWrapper, i.e. the decode is stacked on top of a model it will never use. This evicts it first. Gated to SeedVR2, so no other VAE changes behaviour. The cost is that the transformer is re-read on the next run; set SEEDVR2_1_4B_NO_UNLOAD=1 if you batch many images and would rather keep it loaded.

Patches 3 and 4 mirror what the reference MLX pipeline does at every stage boundary — clear the allocator, and free the transformer at the denoise→decode handover.

⚠️ Do not raise tile_size

Leave it at 512. It looks wasteful β€” a 4Γ— output becomes β‰ˆ40 overlapping tiles with β‰ˆ1.78Γ— of the convolution work spent recomputing overlaps β€” and raising it is the obvious optimisation. It is a trap, for two independent reasons:

  • It crashes the process, not the node. The VAE runs self-attention over the whole tile at latent resolution, so the attention matmul is O(tokensΒ²) with tokens = (tile_size/8)Β². At 512 that is 4,096 tokens (16.8 M); at 2048 it is 65,536 tokens (4.3 G). Past the MPS limit Metal does not raise a catchable Python error β€” it asserts and abort()s, killing ComfyUI with SIGABRT.
  • It is slower anyway. Bigger VAE tiles measured slower on this VAE: decode peak scales at roughly 14 GB per megapixel, so larger tiles buy memory pressure, and the convolution cost is tiling-invariant. There is no compute saving to collect.

Tiling cannot be removed either: an un-tiled 2000Γ—3000 decode fails with matmul 93750,93750 larger than currently supported with or without the fast path. What the patches change is the cost and the memory behaviour of each tile, not the number of them.

Troubleshooting

symptom cause
Could not detect model type support folder not installed or ComfyUI not restarted β€” check the startup log for the SeedVR2-1.4B: line
missing keys blocks.6 … blocks.35 you loaded the root model file; use the comfyui/ variant
model missing from the dropdown wrong folder β€” it must be models/diffusion_models/
loads, but output is noise wrong VAE selected β€” it must be this repo's SeedVR2 VAE, not an SD/Flux one
VAE dropdown has no matching entry the VAE must be named seedvr2_ema_vae_fp16.safetensors in models/vae/
out of memory in VAEDecodeTiled confirm the support folder loaded β€” the per-tile allocator release is what keeps this bounded. Then lower tile_size (256), never raise it
ComfyUI dies with SIGABRT / "exited unexpectedly" tile_size was raised. Metal aborts the process instead of raising; put it back to 512
very slow, minutes per image check the startup log lists all four patches; also see the note that these timings are MLX figures
SeedVR2 … nodes do not exist your ComfyUI predates native SeedVR2 support β€” update it
using the third-party ComfyUI-SeedVR2_VideoUpscaler node different mechanism β€” it picks a YAML by filename; see comfyui/configs_1_4b_thirdparty_node/

If ComfyUI adds official support for this model, prefer that over the support folder. If you would rather patch comfy/model_detection.py by hand than install anything into custom_nodes/, the equivalent source edit is in comfyui/seedvr2_1.4b_detection.patch β€” it gives you detection only, not the VAE fast path. Do not apply both.

Framework compatibility

These weights are framework-neutral. They were written by PyTorch during training and are consumed directly by both PyTorch and MLX β€” no conversion was ever applied in either direction. The header carries no framework metadata, tensor names follow the upstream NaDiT convention (blocks.N.…, emb_in, txt_in, vid_in, vid_out), and the transformer is Linear-only, so the usual NCHW/NHWC convolution-layout trap does not apply (the convolutions live in the VAE, which is the stock file).

The one thing to change versus upstream: instantiate the NaDiT transformer with 6 blocks instead of 36 (num_layers=6, mm_layers=6). Everything else is the standard SeedVR2 loading path.

Usage

Use the official SeedVR2 inference code, with the transformer built at depth 6 and these weights loaded in place of the 7B checkpoint. In outline:

from safetensors.torch import load_file

# 1. Build the SeedVR2 NaDiT transformer with SIX blocks rather than 36.
#    Everything else (hidden size, heads, VAE) is unchanged from the 7B config.
transformer = build_seedvr2_transformer(num_layers=6, mm_layers=6)   # upstream constructor

# 2. Load these weights. Keys are the upstream NaDiT names, so no remapping is needed.
sd = load_file("seedvr2_distill_6L_1.4B_sharp_fp16.safetensors")
transformer.load_state_dict(sd)      # 198 tensors, 1,442,608,252 parameters

# 3. The VAE is the stock SeedVR2 VAE, unmodified.
vae_sd = load_file("ema_vae_fp16.safetensors")

Upscaling is prompt-free: there is no text encoder to load and no prompt to supply. Inference then follows the upstream one-step diffusion path exactly.

Tip for large outputs. The transformer denoises the whole latent grid in a single pass, so memory scales with output area. For very large targets, tile the input and blend the tiles β€” this bounds the activation working set independently of output size, and a modest overlap (β‰ˆ64 output pixels) is enough to avoid visible seams.

If you only ever upscale STILL IMAGES: a free β‰ˆ4Γ— speedup in the VAE

SeedVR2 inherits a video VAE β€” a WAN-style decoder built on CausalConv3d. Its weights are shipped here unmodified, with genuine 3-D kernels (65 tensors of shape [out, in, kT, kH, kW], e.g. decoder.conv_in.weight = [512, 16, 3, 3, 3]).

For a still image the temporal depth is T = 1. Every 3-D convolution therefore replicates frame 0 into kT identical temporal slices and convolves β€” doing kTΓ— the multiply-accumulate work to produce a result that is mathematically identical to a 2-D convolution whose kernel is the sum of the temporal taps:

W2d = W3d.sum(axis=temporal)      # for a causal layer with kT > 1 and temporal stride 1

This is an exact identity, not an approximation β€” the outputs match to numerical precision. So for single-frame use, roughly two thirds of the VAE's convolution work is redundant.

Why it is worth doing: the VAE decode is not a minor stage. On a 4Γ— job it measured 52.7% of total runtime (against 24.9% for the transformer itself), so it is the single largest cost in the pipeline at that factor.

To exploit it, intercept the causal-3D-conv forward pass: when T == 1 and the layer is causal with kT > 1 and temporal stride 1, substitute a single 2-D convolution using the summed kernel, and cache that 2-D weight per layer. Fall through to the original 3-D path for anything else, so real video and any non-matching layer are unaffected.

None of this changes the published weights β€” the file on disk stays the stock 3-D video VAE, and the collapse is a runtime substitution you can add or remove freely. It is mentioned here only because most people using this model will be upscaling stills, and paying 3-D cost for a 1-frame input is pure waste.

ComfyUI users get this for free: it is patch 2 in the support folder, measured there at 3.0Γ— on a 512Γ—512 decode and 4.6Γ— on 1024Γ—1024, verified equivalent to stock output across every kernel/stride/memory-state combination this VAE uses. It applies to any SeedVR2 model, not only this one.


Quality vs the teacher

Teacher-relative FFT band energy, 15 scenes, identical inputs for both models. The teacher is the reference, so 1.000 means indistinguishable from the teacher in that band; below 1.0 means the student under-produces detail, above 1.0 means it over-produces (ringing / over-sharpening).

512 β†’ 2048 (4Γ—) β€” the recommended operating point

band student / teacher per-scene range
mid (0.15–0.40 Nyq) 0.852 0.664 – 1.039
fine (0.40–0.70 Nyq) 0.696 0.406 – 0.871
edges (0.70–1.0 Nyq) 1.125 0.471 – 1.705
MAE vs teacher (8-bit levels) 5.70 2.80 – 9.10

This is a strong result for a model with one sixth of the layers. It holds β‰ˆ85% of the teacher's mid-band structure β€” the band that carries recognisable form and texture β€” and at the edge band it exceeds the teacher (1.125), so results look crisp and well-defined rather than the softness that usually betrays a distilled model. Mean absolute difference from the teacher is 5.70 on a 0–255 scale, i.e. β‰ˆ2% average pixel deviation from a model five times its size.

The fine band (0.696) is where the teacher keeps a real lead β€” very small high-frequency texture is partly rebuilt rather than fully recovered. And the edge band's spread (0.471–1.705) means a few scenes come out noticeably crisper than the teacher and a few softer; on most content this reads as pleasing sharpness, on some it can tip into over-sharpening. Both are published so you can see the shape of the model rather than just its average.

2048 β†’ 8192 (4Γ—) β€” upscaling an already-large image

band student / teacher per-scene range
mid (0.15–0.40 Nyq) 0.833 0.684 – 0.930
fine (0.40–0.70 Nyq) 0.803 0.550 – 1.020
edges (0.70–1.0 Nyq) 1.213 0.791 – 1.665
MAE vs teacher (8-bit levels) 4.35 2.37 – 8.21

This is the line where the model is closest to the teacher in absolute fidelity. MAE 4.35 is the best of the three (against 5.70 at 512β†’2048 and 5.43 at 512β†’4096), and fine-detail recovery jumps to 0.803 from 0.696 on the 512 px line. That is what a high-resolution source buys: far more real information to work from, and correspondingly less to invent.

The trade-off is at the edge band β€” 1.213, more overshoot than the 4Γ— line's 1.125. Outputs are crisp, occasionally crisper than the teacher. Aggregate deviation ties the 512β†’2048 line (sum|bandβˆ’1| 0.577 for both), so "best" depends on whether you weight fidelity or band balance.

512 β†’ 4096 (8Γ—) β€” works, but degrades

band student / teacher per-scene range
mid 0.443 0.349 – 0.595
fine 0.345 0.195 – 0.467
edges 0.475 0.217 – 0.725
MAE vs teacher 5.43 2.64 – 9.33

8Γ— is this model's stretch goal rather than its home ground: it retains under half the teacher's detail energy per band, and produces a clean, usable 4096Γ—4096 image in β‰ˆ55 seconds at 7.5 GB β€” a job the teacher needs 4–5 minutes for, and only by pushing a 64 GB machine into swapping.

Recommendation: use this as a 2×–4Γ— upscaler, where it is genuinely close to the teacher. Reach for 8Γ— when speed and memory matter more than squeezing out the last of the fine texture β€” and use the teacher when 8Γ— fidelity is the priority. The limit was investigated rather than assumed; see Known limitations.


Performance

Apple M2 Ultra, 64 GB, MLX, model load included, idle machine, two scenes each.

These are pipeline numbers, not just model numbers — read this before comparing. They come from an MLX pipeline that decodes the VAE in a single pass and applies the single-frame 3D→2D collapse.

The same 512β†’2048 job in ComfyUI on the same machine takes 44.8 s (with the support folder installed β€” the run in the screenshot above), against 20.2–22.5 s in MLX. So expect roughly 2Γ— the time in ComfyUI. That gap is the runtime and its memory handling, not the model: your log will show the diffusion step itself finishing in about 13 s either way. Without the support folder it is far worse than 2Γ— and frequently runs out of memory outright.

What is a property of the model β€” and what transfers to any runtime β€” is the layer count and the memory footprint: 6 blocks instead of 36, the same VAE, the same one step. The teacher pays the same VAE and tiling costs in the same runtime, so the ratios below hold even where the absolute seconds do not.

job student teacher
512β†’2048 (4Γ—) 20.2 / 22.5 s @ 4.64 GB 38.4 / 33.0 s @ 14.2 / 16.4 GB
512β†’4096 (8Γ—) 54.4 / 55.2 s @ 7.5 GB 307.4 / 257.2 s @ 4.7 GB †

† The teacher's 8Γ— memory figure is not a win β€” read it as a failure signal. 4.7 GB is below its own 15.35 GB weight file, which cannot happen while the weights are resident. At 8Γ— the activations no longer fit, so the OS evicts the teacher's memory-mapped weight pages and re-faults them continuously; RSS counts only resident pages, so it collapses precisely when the model is struggling most. The runtime is the honest indicator: 307 s versus 38 s β€” an 8Γ— jump for a 4Γ— pixel increase. This model does the same job in β‰ˆ55 s at a stable 7.5 GB.

1.6Γ— faster at 4Γ—, 4.7–5.6Γ— faster at 8Γ—, and 3Γ— lighter throughout β€” and the memory figure is the one that changes what is possible. 4.6 GB versus 14–16 GB is the difference between running comfortably on a 16 GB machine and not running at all. On constrained hardware that is not a speed optimisation, it is the whole ballgame.

The speedup grows with the upscale factor, because the bottleneck moves. Measured stage split for the 36-layer teacher on the same hardware:

stage at 4Γ— at 8Γ—
VAE encode 14.9% 11.5%
transformer (the part this model shrinks) 24.9% 53.4%
VAE decode 52.7% 29.1%
colour-correct 7.4% 5.9%

The VAE is byte-identical in both models, so only the transformer slice benefits from having six layers instead of thirty-six. At 4Γ— that slice is a quarter of the work and the job is VAE-decode-bound, which caps the win at β‰ˆ1.6Γ—. At 8Γ— the transformer becomes the dominant cost and the gap opens to 4.7–5.6Γ—. Bigger jobs favour this model more, not less β€” and the memory saving applies at every factor.

The teacher's 8Γ— time reflects memory pressure. Its peak RSS falls at 8Γ— (4.7 GB, below its own 15.35 GB weight file) while runtime grows β‰ˆ8Γ— for a 4Γ— pixel increase β€” the OS is evicting weight pages to fit activations and re-faulting them repeatedly, because 65,536 latent tokens through 36 blocks does not fit comfortably even on 64 GB. This model sails through the same job in β‰ˆ55 s at a stable 7.5 GB. Read that row as "stays within memory where the teacher does not" rather than as a 5Γ— compute win.


Training

Hardware and wall-clock

Single NVIDIA RTX 3090 (24 GB) under WSL2, 8 CPU threads, 49 GB RAM. Roughly 53 GPU-hours end to end:

stage steps throughput wall-clock
base distillation 166,719 (one full epoch) 1.029 it/s β‰ˆ45 h
fine-tune phase 1 12,000 1.01 it/s β‰ˆ3.3 h
fine-tune phase 2 12,000 0.71 it/s β‰ˆ4.7 h

Each stage initialises from the previous one's EMA, so the full training history is preserved β€” neither fine-tune is a rollback or a restart.

Datasets

123,013 source images β†’ 166,735 precomputed teacher-target shards. All targets are the true 36-layer teacher's outputs; no stage ever trained against an intermediate model's outputs, so errors do not compound.

dataset images role
4KLSDB 44,999 native-4K sources; enable clean high-resolution crops
LSDIR 34,564 general high-resolution variety
CelebA-HQ 30,000 faces
FFHQ 10,000 faces
Flickr2K 2,650 natural scenes
DIV2K 800 standard SR benchmark set

The shard set is built in two passes:

  • 43,450 shards at fixed 512 px with a single fixed bicubic degradation β€” exactly one per image from CelebA-HQ + FFHQ + Flickr2K + DIV2K.
  • 123,285 shards with varied resolution (up to 1280 px) and varied degradation, drawing on the whole pool including 4KLSDB and LSDIR.

The second pass exists for two measured reasons, both of which matter if you retrain:

  1. Windowing-regime mismatch. SeedVR2's adaptive attention window targets a β‰ˆ720p (3,600-token) reference. A fixed 512 px grid is only 28% of that, so training saw near-global attention while real inference at 450–700% of the reference is genuinely local β€” a regime the model never trained in. The v3 pass samples output resolution across and past that reference.
  2. Degradation too narrow. A single fixed bicubic down/up pass is a special case next to the multi-kernel second-order degradation (blur, noise, JPEG, mixed resize kernels) the teacher lineage trained on. The v3 pass randomises factor and kernel, and layers a second-order degradation at 50% probability.

Resolution choices are clamped per-image to the source's own native size, so small-native-resolution sources are never upsampled before being "degraded" β€” which would quietly poison the clean-ground-truth assumption for exactly those images.

How the 6 layers were chosen

The student is initialised by block-dropping, keeping teacher-space blocks [0, 3, 6, 29, 32, 35].

One detail matters for reproducing this exactly: the slice is not taken from the teacher's own weights. It is taken from an intermediate 12-layer distillation of the same teacher, whose blocks had already been trained together as a group β€” the keep-set is [0,1,2,9,10,11] in that model's local indexing, which maps to [0,3,6,29,32,35] in teacher space. training/box_slice_6l.py performs that slice.

Targets throughout remained the true 36-layer teacher's precomputed outputs. The intermediate model supplies initialisation only, never supervision, so its own error does not compound into the student. The reason for the extra hop is the second bullet below.

That 12-layer model is a separate line of work and may be released on its own once its fine-tuning is complete. It is a larger, heavier model β€” this 6-layer one is the interesting result, for the reasons in Who this is for.

Two alternatives were tested and rejected on measurements:

  • Naive first-N truncation (keep the first 6 blocks) produces visible grain. The final block has special end-of-network handling in this architecture; dropping it removes that finishing stage.
  • Cherry-picking the teacher's individually most "transformative" blocks β€” ranked by per-block input/output cosine distance β€” performs worse despite scoring better on the ranking: grain 91.2 vs 20.4 and reference-MAE 4.64 vs 1.33 on the same test images. Raw per-block importance is not the same as compatibility with neighbours a block never trained beside. Blocks that were co-trained together hand off to each other far better.

training/box_slice_6l.py performs the slice.

Objective

The loss is a weighted sum, with the pixel terms computed through the frozen VAE decoder on a random crop:

term what it does
Charbonnier on latents base reconstruction; per-channel normalised
latent high-pass L1 recovers detail that plain L2 leaves on the table
Focal Frequency Loss phase-aware spectral matching on the latent FFT
pixel high-pass L1 decoded-RGB detail, masked to suppress flat regions
pixel spectral Hann-windowed FFT loss with a radial band mask
feature-matching KD supervises internal representations against the teacher

Plain MSE distillation was tried first and behaves as a low-pass filter of the teacher: it reproduces structure but not texture, because squared-error gradients vanish on exactly the small residuals that carry fine detail. The high-frequency terms above exist to fix that specific failure. Two GAN variants were also tried and both underperformed β€” gentle had no effect, strong came out softer.

Unattended-safety machinery (all in the trainer): per-term clamping relative to the base loss, a skip-step guard on loss spikes, nan_to_num on decoder output, OOM-tolerant pixel branch, and atomic checkpoint writes.

Fine-tune phase 1 β€” mid-band and feature-matching correction

Two corrections, both toward the recipe's own stated design intent:

  • the pixel-spectral radial mask was made flat from R=0.15 instead of ramping 0.10β†’0.40, raising mid-band supervision +54.7% while leaving fine and edge bands untouched;
  • w_featmatch 0.4671 β†’ 0.6595, bringing the feature-matching term back to its design ratio.

Fine-tune phase 2 β€” high-resolution tier filter

Training restricted to the 14,822 shards at β‰₯2048 px (8.9% of the corpus), raising mean tokens per step from 6,771 to 18,727. Exactly one variable changed versus phase 1 β€” the data distribution β€” so the effect is attributable. It improved the 4Γ— band fit, mostly at the edge band.

Both phases are reproducible as-run from fine-tuning/ft1.sh and fine-tuning/ft2.sh, which carry the exact calibrated hyperparameters.


Known limitations

8Γ— is weak, and it is not a data problem. Two hypotheses were tested and both failed:

  • Curation. Phase 2 trained on 100% β‰₯2048 px shards for 12,000 steps. 8Γ— moved marginally.
  • Inference tiling. Splitting an 8Γ— job so each tile lands back inside the trained token range β€” including a configuration at exactly the token count where 4Γ— performs well β€” changed nothing (it moved slightly the wrong way, with clean seams confirming nothing masked a gain).

What the evidence points to is upscale factor itself, not token count: 4× and 2K→8K workloads are healthy at very different token counts, while the 8× line is weak at both its native token count and at a reduced one. Asking for 64 output pixels per input pixel instead of 16 is the actual difficulty, and rearranging the forward pass does not change it. Closing it would need more capacity or a different objective.

Edge-band over-shoot at 4Γ— (mean 1.125, range 0.471–1.705) β€” on some content the model over-sharpens relative to the teacher. If you are chaining with a separate sharpening pass, account for it.

MAE is not at teacher level on any line. If exact fidelity matters more than size and speed, use the teacher.


Reproducing the evaluation images

Every image under assets/ started from a text-to-image generation with a fixed prompt and seed. Each scene's prompt, seed and generating model are recorded in assets/prompts/<scene>.txt:

Prompt: Extreme macro of a spiderweb covered in tiny dew droplets, each droplet refracting light, ...
Seed: 285112985
Model used to generate: Boogu Turbo

All scenes were generated at 2048Γ—2048 with the seeds listed below. The prompt and seed are what matter for reproduction β€” use whichever inference stack you prefer.

scene seed prompt
rs_01_portrait_fisherman 555477016 Close-up portrait of an elderly fisherman with deeply weathered wrinkled skin, gray stubble beard, wearing a yellow raincoat hood, overcast soft light, photorealistic
rs_02_market_crowd 1864840591 Busy outdoor street market crowded with many people browsing colorful fruit and vegetable stalls
rs_03_fox_grass 1986897913 A red fox standing alert in tall golden grass, detailed fur, autumn evening light, shallow depth of field
rs_04_kingfisher 1905028566 A kingfisher bird bursting out of water with spread wings, water droplets frozen mid-air, iridescent blue and orange feathers, high-speed photography
rs_05_neon_street 309014498 Rainy night city street with glowing neon shop signs and readable text, wet asphalt reflections
rs_06_mountain_valley 2142845029 Wide mountain valley with a winding river, dense pine forest, morning fog layers, distant snowy peaks
rs_07_spiderweb_dew 285112985 Extreme macro of a spiderweb covered in tiny dew droplets, each droplet refracting light, dark green blurred background
rs_08_rustic_pizza 175728095 Overhead shot of a rustic wood-fired pizza with bubbling melted cheese, basil leaves, charred crust
rs_09_storefront_sign 1133058957 Vintage brick storefront with hand-painted gold lettering on the window reading BAKERY, striped awning
rs_10_locomotive 422126126 Close-up of an old black steam locomotive driving wheels and connecting rods, rivets, brass pipes
rs_11_coral_reef 1896361262 Vibrant underwater coral reef scene with schools of small tropical fish, sun rays penetrating clear water
rs_12_ski_powder 829034716 A skier in a red jacket carving through deep powder snow spraying a white cloud, bright sunny alpine day
rs_13_library 1437621541 Cozy old library interior with tall wooden bookshelves full of books, a leather armchair, warm lamplight
rs_14_oil_swirls 680017211 Abstract macro of swirling oil paint in vivid rainbow colors, smooth glossy gradients and thin filaments
rs_15_watercolor_castle 890572502 Watercolor illustration of a fantasy castle on a cliff above the sea, soft washes of color, paper texture

Repository layout

seedvr2_distill_6L_1.4B_sharp_fp16.safetensors   the model
ema_vae_fp16.safetensors                         VAE (stock, unmodified)
training/     train_student_v4.py                the trainer that produced this model
              precompute_targets.py              512px teacher-target shards
              precompute_targets_v3.py           varied-resolution/degradation shards
              box_slice_6l.py                    36 -> 6 block slice
fine-tuning/  ft1.sh                             phase 1, exact hyperparameters
              ft2.sh                             phase 2, exact hyperparameters
              build_tier_index.py                token-count index phase 2 needs
comfyui/      ComfyUI-SeedVR2-1.4B/              drop into ComfyUI/custom_nodes/
              seedvr2_..._fp16_comfyui.safetensors  the ComfyUI model variant
              seedvr2_1.4b_upscale_image.json    ready-made workflow
              seedvr2_1.4b_detection.patch       manual alternative to the folder
              configs_1_4b_thirdparty_node/      for the third-party video-upscaler node
assets/       inputs-512/  teacher-7B/  student-1.4B/  comparison/  thumbs/  prompts/

Scripts take paths from SV2_ROOT and SV2_DATASETS; set both before running.

Credits and licence

Teacher model: ByteDance-Seed/SeedVR2-7B (sharp EMA variant), converted to safetensors. This is a derivative work β€” the teacher's licence and terms govern its use. The VAE is redistributed unmodified.


Results

Judge it yourself. Left is this 1.4B model, right is the 36-layer 7B teacher in every pair, from byte-identical inputs. Click any image for the full-resolution version.

Two views, because they answer different questions:

  • Full frames show what you actually get β€” the result you would ship. At this scale the two models are hard to tell apart, and that similarity is the result.
  • 1:1 crops show where they diverge, at native pixels. The crop location is chosen automatically as the highest-detail region of the teacher's output, so it lands where upscaling is hardest rather than anywhere flattering.

A comparison showing only crops would misrepresent the model by its worst pixels; one showing only full frames would hide the real differences. Both are here.

512 β†’ 2048 (4Γ—) β€” full frames

This is what you actually get. At full-frame scale the two are hard to tell apart, which is the headline result: six layers instead of thirty-six, and the output stands up beside the teacher.

Portrait β€” skin, hair, fabric:

portrait 4x full

Motion and water:

kingfisher 4x full

Metal, rivets, mechanical detail:

locomotive 4x full

Landscape with fine repeating texture:

mountain 4x full

Complex scene, many small subjects:

market 4x full

512 β†’ 2048 (4Γ—) β€” 1:1 crops

Now the pixel-level view. These are deliberately unflattering: the crop location is picked automatically as the highest-detail region of the teacher's output, so it lands where upscaling is hardest. Differences that are invisible above become visible here β€” that is the point of showing both.

Macro detail and refraction:

spiderweb 4x

Skin, hair and fabric:

portrait 4x

Fur and foliage:

fox 4x

Text and hard edges β€” the classic upscaler stress case:

storefront 4x

Dense repeating structure:

library 4x

512 β†’ 4096 (8Γ—)

Full frames first β€” an 8Γ— upscale from a 512 px source is a hard ask, and both models produce a coherent 4096Γ—4096 image:

pizza 8x full

coral 8x full

At 1:1 the teacher's lead is visible, matching the measured numbers. Shown so you can see the limitation rather than read about it:

spiderweb 8x

coral 8x

2048 β†’ 8192 (4Γ—) β€” the practical case

Upscaling an image that is already high-resolution, out to 8192Γ—8192 β€” 67 megapixels. This is probably the most realistic workflow: not rescuing a thumbnail, but taking a good 2K render up to print or large-display size. It is also the line where the model gets closest to the teacher in absolute fidelity (MAE 4.35, the best of the three) and recovers the most fine detail β€” a 2K source gives it far more real information to work from than a 512 px one. See Quality vs the teacher for the numbers.

Portrait at 67 MP:

portrait 2k full

Landscape with fine repeating texture:

mountain 2k full

Dense structure and text:

library 2k full

Painterly source β€” a different kind of detail entirely:

castle 2k full

And at 1:1, where 67 MP of output actually lives:

portrait 2k crop

locomotive 2k crop

All scenes

Full-resolution outputs for every scene and both models:

inputs this model (1.4B) teacher (7B)
512 β†’ 2048 (4Γ—) inputs-512 student 4Γ— teacher 4Γ—
512 β†’ 4096 (8Γ—) inputs-512 student 8Γ— teacher 8Γ—
2048 β†’ 8192 (4Γ—) inputs-2k student 2K teacher 2K

All 45 side-by-side comparisons (full frame and 1:1 crop, all three lines) are in assets/comparison/. Prompts and seeds for every scene are in assets/prompts/.


Also in progress: a 12-layer (2.8B) distillation

This 1.4B model was built out of a 12-layer one. A 12-layer, 2.8B distillation of the same teacher was trained first; the six blocks here are a slice of its weights ([0,1,2,9,10,11] in its indexing β†’ [0,3,6,29,32,35] in teacher space), and training continued from there against the true 36-layer teacher's targets. So the 12L is not a sibling project β€” it is this model's direct ancestor, and part of its training is inherited rather than repeated. See How the 6 layers were chosen for why slicing a co-trained model beats cherry-picking blocks straight from the teacher.

To be precise about what was inherited: the slice was taken from the 12L's completed base training run, before any fine-tuning of that model. The two fine-tune phases described above were then run on the 6-layer model itself. So this model carries the 12L's base training, not its fine-tuning.

That 12-layer model's own fine-tuning is still in progress and is not finished. It sits between this 1.4B model and the full 36-layer teacher: heavier and slower, but closer to the teacher on fidelity, particularly at higher upscale factors where six layers give up the most ground.

It may be published separately once that fine-tuning completes and is evaluated. Nothing about it is final yet β€” no timeline, no promises, and it only ships if the numbers justify it, measured the same way as here. Treat this section as a note on lineage, not an announcement.

Why a 12-layer model is worth having

Between this 1.4B and the 36-layer teacher there is a large, under-served gap. The 12L is aimed squarely at it:

1.4B (6L) 2.8B (12L) teacher (36L)
transformer layers 6 12 36
weights on disk (fp16) 2.69 GB 5.22 GB 15.35 GB
vs the 7b sharp teacher 5.7Γ— smaller 2.9Γ— smaller β€”
fidelity good closer to the 7b sharp teacher reference

Three layers' worth of capacity buys real quality, and 5.22 GB still fits comfortably where 15.35 GB does not β€” a 16 GB laptop, a mid-range GPU, a machine already holding a generation model in memory. It is also faster than the teacher for the same structural reason this model is: fewer layers means less transformer work and a smaller activation footprint, and the advantage widens at higher upscale factors where the 36-layer teacher starts fighting for memory.

So the three form a ladder rather than a hierarchy with one winner:

  • 1.4B β€” smallest and fastest; the choice when memory or latency is the binding constraint, and the only one of the three with a realistic path onto a phone.
  • 2.8B β€” the balance point: most of the teacher's quality at roughly a third of its footprint. For many people with a normal desktop, this is likely the right default rather than a compromise.
  • 7B teacher β€” maximum fidelity, if you have the machine for it.

Which one is "best" depends entirely on the hardware you are running and how much fine detail your work actually needs.

Downloads last month

-

Downloads are not tracked for this model. How to track
MLX
Hardware compatibility
Log In to add your hardware

Quantized

Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for lvladikov/SeedVR2-1.4B

Finetuned
(9)
this model
Merges
1 model