Features: - 33 model-native bucket resolutions for Instruct resolution dropdown - Multi-Image Fusion expanded to 5 inputs (4-5 experimental) - Transformers 5.x forward compatibility patches Bug Fixes: - Issue #16: NF4 Low VRAM OOM (two-stage max_memory estimation) - Issue #15: Multi-GPU device mismatch (freqs_cis/image_pos_id .to(device)) - Issue #12: _lookup dict guard, BitsAndBytesConfig import path - Missing OOM handlers in Instruct Image Edit / Multi-Fusion Maintenance: - Version bump pyproject.toml 1.1.0 -> 1.2.0 - Remove duplicate LLM Prompt Rewriting section from README - Fix duplicate License header in README - Update copyright to 2025-2026 - Add doc images and IMAGE_BASE_SIZE_EXPLORATION.md
5.3 KiB
Image Base Size Override — Exploration Notes
Date: February 2026
Status: Experimental feature tested and reverted — model does not respect non-default base sizes.
Summary
We attempted to expose image_base_size as a configurable parameter on the Instruct Loader, allowing users to generate at higher resolutions (1536, 2048, 3072, 4096). The implementation was technically correct but the model ignores the override.
Background
Tokenizer Vocabulary Evidence
The model's tokenizer (tokenization_hunyuan_image_3.py) contains 10 size tokens:
| Token | ID | Base Size |
|---|---|---|
<img_size_256> |
128034 | 256 |
<img_size_512> |
128035 | 512 |
<img_size_768> |
128036 | 768 |
<img_size_1024> |
128037 | 1024 |
<img_size_1536> |
128038 | 1536 |
<img_size_2048> |
128039 | 2048 |
<img_size_3072> |
128040 | 3072 |
<img_size_4096> |
128041 | 4096 |
<img_size_6144> |
128042 | 6144 |
<img_size_8192> |
128043 | 8192 |
These tokens are embedded in the img_ratio bot task via size_token_id() at line ~1431 of modeling_hunyuan_image_3.py. This strongly suggests multi-resolution support was architecturally designed in but likely not trained.
ResolutionGroup
The ResolutionGroup class generates aspect-ratio buckets:
- Range:
base_size // 2tobase_size * 2(in both height and width) - Step:
base_size // 16 - Align: 16 (VAE downsample factor)
- Generates ~33 valid (H, W) buckets for any given base_size
What We Implemented
- Dynamic preset generation:
_build_resolution_presets(base_size)generated resolution dropdown entries for any base_size - Loader parameter:
image_base_sizedropdown with values [1024, 1536, 2048, 3072, 4096] - ResolutionGroup rebuild: After model load, rebuilt
model.image_processor.vae_reso_groupwith new base_size - Config update: Set
model.config.image_base_sizeto the new value - Downstream support:
parse_resolution()accepted model parameter to use model-attached presets
The implementation was verified working — logs confirmed new buckets were generated correctly.
Test Results
base_size=2048 with INT8 (bitsandbytes)
- Result: CRASH —
igemmltCUDA kernel error at line 438 inops.cu - Cause: 2048×2048 = 4MP → 128×128 = 16,384 image tokens. The INT8 GEMM kernel's CUDA grid dimensions can't handle sequences this long
- Note: NF4 or BF16 might work since they don't use the same INT8 kernel
base_size=1536 with INT8 (bitsandbytes)
- Result: Runs but output is still ~1MP — e.g., 919×1141 instead of expected ~1536×1536
- Observation: Portrait input sometimes produced landscape output (aspect ratio flip)
base_size=2048 with T2I (text-to-image)
- Result: Output was 1024×768 — the override was completely ignored
Root Cause Analysis
The model's output resolution is determined by a ratio prediction step:
SliceVocabLogitsProcessorconstrains generation to<img_ratio_0>through<img_ratio_36>tokens- The model predicts one of these ratio indices
- The predicted ratio is looked up in
vae_reso_group.data[ratio_index]to get (H, W) - The diffusion pipeline generates at that (H, W)
Even though we rebuilt vae_reso_group with larger buckets, the model's ratio prediction behavior was trained only at base_size=1024. The model's internal weights predict ratio indices calibrated for 1024-based buckets, so:
- The ratio index → resolution mapping changes, but the model still predicts the same distribution of indices
- The actual pixel generation (diffusion head, VAE decoder) was likely only trained at 1024-scale
- The
<img_size_N>tokens may be architectural placeholders that were never backpropagated through
What Would Be Needed
For multi-resolution to actually work, likely need:
- Fine-tuning at higher base sizes so the diffusion head learns to generate at those resolutions
- Or Tencent releasing models explicitly trained at higher base sizes (the tokenizer infrastructure is ready)
- Or a post-hoc upscaling approach (tile-based diffusion, SDEdit refinement, etc.)
INT8 Kernel Limitation
Even if the model supported higher resolutions, bitsandbytes INT8 has a hard limit:
- The
igemmltkernel uses CUDA grid dimensions that overflow at ~16K+ sequence length - base_size=2048 → 16,384 image tokens → exceeds kernel grid limits
- Workarounds: Use NF4 (4-bit, no GEMM kernel) or BF16 (native float ops)
Files Modified (Now Reverted)
All changes were in hunyuan_instruct_nodes.py:
- Resolution presets section (lines ~112-181) — dynamic generation
parse_resolution()— model parameter for custom presetsINPUT_TYPES—image_base_sizedropdownload_model()— parameter and parsing- Post-load block — ResolutionGroup rebuild + model metadata
- Generate node — custom parse_resolution call + logging
- ImageEdit node — base_size logging
- MultiFusion node — custom parse_resolution call + logging
Key Upstream Code References
modeling_hunyuan_image_3.pyline ~2684:image_base_sizeread fromvae_reso_group.base_sizemodeling_hunyuan_image_3.pyline ~1431:img_ratiotask embeds<img_size_N>tokenimage_processor.py:build_gen_image_info()callsget_target_size()for bucket snappingtokenization_hunyuan_image_3.py:ResolutionGroup,size_token_id(), ratio tokens