File size: 4,966 Bytes
773f354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cbe716d
773f354
 
 
 
 
 
 
 
 
 
 
 
cbe716d
 
773f354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cbe716d
773f354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cbe716d
773f354
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""Qwen-Image-2.1 -> OpenVINO FP16 export + NNCF weight-only INT4 quantization.

NOTE on versions (deviation from original pinned env, approved by user):
  Qwen/Qwen-Image-2.1 requires QwenImage21Pipeline (added in diffusers
  commit 6256aa766 "Add Qwen-Image 2.1 (#14804)", post-v0.40.0). Pinned
  diffusers==0.37.1 cannot load it (AttributeError). Therefore:
  - diffusers: 0.41.0.dev0 (git main, includes QwenImage21)
  - transformers: 5.10.4 (pulled by optimum-intel git; supports hub 1.x)
  - huggingface-hub: 1.33.0 (required by git diffusers >=1.31)
  - tokenizers: 0.22.2, optimum: 2.3.0
  - optimum-intel: 2.3.0.dev0+git (main, first version with QwenImage21 support)
  - openvino==2026.4.0 nncf==3.4.0 torch pillow psutil unchanged
  - transformers dependency table patched: hub cap <1.0 -> <2.0
  - optimum-intel modeling_visual_language.py patched for transformers>=5
    (VisionRotaryEmbedding alias + rot_pos_emb try/except, diffusion unused)

FP16 export CLI (same pattern as task example):
  optimum-cli export openvino -m Qwen/Qwen-Image-2.1 \
    --task text-to-image --library diffusers --weight-format fp16 \
    ./qwen-image-2.1-ov-fp16

INT4 quantization:
  OVQuantizer + OVPipelineQuantizationConfig nested in OVConfig:
  transformer + text_encoder (+ text_encoder_i2i, same Qwen3VL arch) with
  OVWeightQuantizationConfig(bits=4, sym=False, group_size=128,
    group_size_fallback="adjust", ratio=1.0),
  rest default INT8. Passed as ov_config=OVConfig(quantization_config=...).
"""
import os
import time
import json

FP16_DIR = "./qwen-image-2.1-ov-fp16"
INT4_DIR = "./qwen-image-2.1-ov-int4"
MODEL_ID = "Qwen/Qwen-Image-2.1"


def export_fp16():
    if os.path.exists(FP16_DIR) and os.listdir(FP16_DIR):
        print(f"FP16 model already exists at {FP16_DIR}, skipping export...")
        print("CLI: optimum-cli export openvino -m "
              f"{MODEL_ID} --task text-to-image --library diffusers "
              f"--weight-format fp16 {FP16_DIR}")
        return FP16_DIR, 0.0
    # Export is done via optimum-cli (see docstring); this fallback uses API.
    from optimum.intel import OVDiffusionPipeline
    print(f"Exporting {MODEL_ID} to FP16 OpenVINO...")
    start = time.time()
    pipeline = OVDiffusionPipeline.from_pretrained(
        MODEL_ID,
        export=True,
        compile=False,
        weight_format="fp16",
        token=os.environ.get("HF_TOKEN"),
    )
    pipeline.save_pretrained(FP16_DIR)
    dt = time.time() - start
    print(f"FP16 export completed in {dt:.2f}s")
    return FP16_DIR, dt


def quantize_int4(fp16_dir):
    from optimum.intel import OVDiffusionPipeline
    from optimum.intel.openvino import (
        OVQuantizer,
        OVConfig,
        OVPipelineQuantizationConfig,
        OVWeightQuantizationConfig,
    )

    print(f"Quantizing {fp16_dir} to INT4...")
    start = time.time()
    pipeline = OVDiffusionPipeline.from_pretrained(fp16_dir, compile=False)
    print("OV submodels:", pipeline._ov_model_names)
    quantizer = OVQuantizer.from_pretrained(pipeline)

    int4_cfg = OVWeightQuantizationConfig(
        bits=4,
        sym=False,
        group_size=128,
        group_size_fallback="adjust",
        ratio=1.0,
    )
    int8_default = OVWeightQuantizationConfig()  # bits=8 default INT8

    ov_config = OVConfig(
        quantization_config=OVPipelineQuantizationConfig(
            quantization_configs={
                "transformer": int4_cfg,
                "text_encoder": int4_cfg,
                "text_encoder_i2i": int4_cfg,
            },
            default_config=int8_default,
        )
    )
    quantizer.quantize(save_directory=INT4_DIR, ov_config=ov_config)
    dt = time.time() - start
    print(f"INT4 quantization completed in {dt:.2f}s")
    return INT4_DIR, dt


def main():
    print("=" * 60)
    print("Qwen-Image-2.1 OpenVINO INT4 Quantization")
    print("=" * 60)
    os.makedirs(OUTPUT_DIR, exist_ok=True)
    fp16_dir, export_time = export_fp16()
    int4_dir, quantize_time = quantize_int4(fp16_dir)
    data = {
        "model_id": MODEL_ID,
        "fp16_export_dir": fp16_dir,
        "int4_dir": int4_dir,
        "export_time_seconds": export_time,
        "quantize_time_seconds": quantize_time,
        "total_time_seconds": export_time + quantize_time,
        "quant_config": {
            "transformer": "OVWeightQuantizationConfig(bits=4,sym=False,group_size=128,group_size_fallback=adjust,ratio=1.0)",
            "text_encoder": "same INT4",
            "text_encoder_i2i": "same INT4 (Qwen-Image-2.1 editing text encoder, same arch)",
            "others": "default INT8 (OVWeightQuantizationConfig bits=8)",
            "ov_config_class": "OVPipelineQuantizationConfig nested in OVConfig",
        },
    }
    with open(os.path.join(OUTPUT_DIR, "benchmark_quantization.json"), "w") as f:
        json.dump(data, f, indent=2)
    print(f"\nDone. FP16: {fp16_dir} INT4: {int4_dir}")


if __name__ == "__main__":
    main()