NNCF を使用した OpenAI BLIP モデルのトレーニング後の量子化と重み圧縮¶
この Jupyter ノートブックは、ローカルへのインストール後にのみ起動できます。
このチュートリアルの目的は、NNCF (ニューラル・ネットワーク圧縮フレームワーク) から OpenVINO IR モデルに 8 ビットのトレーニング後の量子化とデータフリー int8 重み圧縮を適用してモデルを高速化して、OpenVINO™ ツールキットにより最適化された BLIP モデルを推論する方法を実証することです。最適化プロセスには次の手順が含まれます。
量子化のためにデータセットをダウンロードして前処理します。
NNCF を使用して、ノートブックから変換されたビジョンおよびテキスト・エンコーダー OpenVINO モデルを量子化します。
NNCF を使用してノートブックから OpenVINO テキスト・デコーダー・モデルの重みを圧縮します。
ノートブックから同じ入力データを使用してモデルの結果を確認します。
変換および最適化されたモデルのモデルサイズを比較します。
変換および最適化されたモデルのパフォーマンスを比較します。
注: 最初に 233-blip-convert ノートブックを実行して、最適化に使用される OpenVINO IR モデルを生成する必要があります。
目次¶
必要条件¶
%pip install -q datasets
from pathlib import Path
VISION_MODEL_OV = Path("blip_vision_model.xml")
TEXT_ENCODER_OV = Path("blip_text_encoder.xml")
TEXT_DECODER_OV = Path("blip_text_decoder_with_past.xml")
if not (VISION_MODEL_OV.exists() and TEXT_ENCODER_OV.exists() and TEXT_DECODER_OV.exists()):
raise RuntimeError('This notebook should be run after 233-blip-convert notebook')
from transformers import BlipProcessor
processor = BlipProcessor.from_pretrained("Salesforce/blip-vqa-base")
量子化¶
NNCF は、モデルグラフに量子化レイヤーを追加し、トレーニング・データセットのサブセットを使用してこれらの追加の量子化レイヤーのパラメーターを初期化することで、トレーニング後の量子化を可能にします。このフレームワークは、元のトレーニング・コードへの変更が最小限になるように設計されています。
最適化プロセスには次の手順が含まれます。
量子化用のデータセットを作成します。
nncf.quantize
を実行して、事前トレーニングされたFP16
モデルから量子化されたモデルを取得します。openvino.save_model
関数を使用してINT8
モデルをシリアル化します。
注: 量子化は時間とメモリーを消費する操作です。以下の量子化コードの実行には時間がかかる場合があります。
データセットの準備¶
VQAv2 は、画像に関する自由形式の質問を含むデータセットです。これらの問に答えるには、視覚、言語、常識的な知識の理解が必要です。
import numpy as np
from datasets import load_dataset
from tqdm.notebook import tqdm
def preprocess_batch(batch, vision_model, inputs_info):
"""
Preprocesses a dataset batch by loading and transforming image and text data.
VQAv2 dataset contains multiple questions to image.
To reduce dataset preparation time we will store preprocessed images in `inputs_info`.
"""
image_id = batch["image_id"]
if image_id in inputs_info:
inputs = processor(text=batch['question'], return_tensors="np")
pixel_values = inputs_info[image_id]["pixel_values"]
encoder_hidden_states = inputs_info[image_id]["encoder_hidden_states"]
else:
inputs = processor(images=batch["image"], text=batch["question"], return_tensors="np")
pixel_values = inputs["pixel_values"]
encoder_hidden_states = vision_model(pixel_values)[vision_model.output(0)]
inputs_info[image_id] = {
"pixel_values": pixel_values,
"encoder_hidden_states": encoder_hidden_states,
"text_encoder_inputs": []
}
text_encoder_inputs = {
"input_ids": inputs["input_ids"],
"attention_mask": inputs["attention_mask"]
}
inputs_info[image_id]["text_encoder_inputs"].append(text_encoder_inputs)
def prepare_input_data(dataloader, vision_model, opt_init_steps):
"""
Store calibration subset in List to reduce quantization time.
"""
inputs_info = {}
for batch in tqdm(dataloader, total=opt_init_steps, desc="Prepare calibration data"):
preprocess_batch(batch, vision_model, inputs_info)
calibration_subset = []
for image_id in inputs_info:
pixel_values = inputs_info[image_id]["pixel_values"]
encoder_hidden_states = inputs_info[image_id]["encoder_hidden_states"]
encoder_attention_mask = np.ones(encoder_hidden_states.shape[:-1], dtype=int)
for text_encoder_inputs in inputs_info[image_id]["text_encoder_inputs"]:
text_encoder_inputs["encoder_hidden_states"] = encoder_hidden_states
text_encoder_inputs["encoder_attention_mask"] = encoder_attention_mask
blip_inputs = {
"vision_model_inputs": {"pixel_values": pixel_values},
"text_encoder_inputs": text_encoder_inputs,
}
calibration_subset.append(blip_inputs)
return calibration_subset
def prepare_dataset(vision_model, opt_init_steps=300, streaming=True):
"""
Prepares a vision-text dataset for quantization.
"""
dataset = load_dataset("Hugging FaceM4/VQAv2", split="train", streaming=streaming)
train_dataset = dataset.shuffle(seed=42).take(opt_init_steps)
calibration_subset = prepare_input_data(train_dataset, vision_model, opt_init_steps)
return calibration_subset
ストリーミング・モードでのデータセットの読み込みと処理には時間がかかりますが、インターネットの接続環境によって異なります。
import nncf
import openvino as ov
comp_vision_model = ov.compile_model(VISION_MODEL_OV)
calibration_data = prepare_dataset(comp_vision_model)
INFO:nncf:NNCF initialized successfully. Supported frameworks detected: torch, tensorflow, onnx, openvino
Repo card metadata block was not found. Setting CardData to empty.
Prepare calibration data: 0%| | 0/300 [00:00<?, ?it/s]
ビジョンモデル¶
VISION_MODEL_OV_INT8 = Path(str(VISION_MODEL_OV).replace(".xml", "_int8.xml"))
core = ov.Core()
ov_vision_model = core.read_model(VISION_MODEL_OV)
vision_dataset = nncf.Dataset(calibration_data, lambda x: x["vision_model_inputs"])
quantized_model = nncf.quantize(
model=ov_vision_model,
calibration_dataset=vision_dataset,
model_type=nncf.ModelType.TRANSFORMER
)
ov.save_model(quantized_model, VISION_MODEL_OV_INT8)
Statistics collection: 100%|██████████| 300/300 [00:21<00:00, 14.06it/s]
Applying Smooth Quant: 100%|██████████| 48/48 [00:01<00:00, 29.72it/s]
INFO:nncf:36 ignored nodes was found by name in the NNCFGraph
Statistics collection: 100%|██████████| 300/300 [01:17<00:00, 3.89it/s]
Applying Fast Bias correction: 100%|██████████| 49/49 [00:27<00:00, 1.80it/s]
テキスト・エンコーダー¶
TEXT_ENCODER_OV_INT8 = Path(str(TEXT_ENCODER_OV).replace(".xml", "_int8.xml"))
text_encoder_dataset = nncf.Dataset(calibration_data, lambda x: x["text_encoder_inputs"])
ov_text_encoder = core.read_model(TEXT_ENCODER_OV)
quantized_model = nncf.quantize(
model=ov_text_encoder,
calibration_dataset=text_encoder_dataset,
model_type=nncf.ModelType.TRANSFORMER
)
ov.save_model(quantized_model, TEXT_ENCODER_OV_INT8)
Statistics collection: 100%|██████████| 300/300 [00:10<00:00, 28.70it/s]
Applying Smooth Quant: 100%|██████████| 73/73 [00:02<00:00, 28.87it/s]
INFO:nncf:72 ignored nodes was found by name in the NNCFGraph
Statistics collection: 100%|██████████| 300/300 [00:31<00:00, 9.54it/s]
Applying Fast Bias correction: 100%|██████████| 120/120 [00:38<00:00, 3.11it/s]
重みを圧縮¶
テキストデコーダーの量子化により、精度が大幅に低下します。トレーニング後の量子化の代わりに、データのフリーウェイト圧縮を使用してモデルのフットプリントを削減できます。
最適化プロセスには次の手順が含まれます。
nncf.compress_weights
を実行して、圧縮された重みを持つモデルを取得します。openvino.save_model
関数を使用してOpenVINO
モデルをシリアル化します。
TEXT_DECODER_OV_INT8 = Path(str(TEXT_DECODER_OV).replace(".xml", "_int8.xml"))
text_decoder = core.read_model(TEXT_DECODER_OV)
compressed_text_decoder = nncf.compress_weights(text_decoder)
ov.save_model(compressed_text_decoder, str(TEXT_DECODER_OV_INT8))
最適化された OpenVINO モデルを実行¶
最適化された OpenVINO BLIP モデルを使用して予測を行う手順は、PyTorch モデルと似ています。1 番目のノートブックと同じ入力データを使用してモデルの結果を確認してみましょう。
q_ov_vision_model = ov.compile_model(VISION_MODEL_OV_INT8)
q_ov_text_encoder = ov.compile_model(TEXT_ENCODER_OV_INT8)
q_ov_text_decoder_with_past = ov.compile_model(TEXT_DECODER_OV_INT8)
from functools import partial
from transformers import BlipForQuestionAnswering
from blip_model import OVBlipModel, text_decoder_forward
model = BlipForQuestionAnswering.from_pretrained("Salesforce/blip-vqa-base")
text_decoder = model.text_decoder
text_decoder.eval()
text_decoder.forward = partial(text_decoder_forward, ov_text_decoder_with_past=q_ov_text_decoder_with_past)
int8_model = OVBlipModel(model.config, model.decoder_start_token_id, q_ov_vision_model, q_ov_text_encoder, text_decoder)
from PIL import Image
raw_image = Image.open("demo.jpg").convert('RGB')
question = "how many dogs are in the picture?"
# preprocess input data
inputs = processor(raw_image, question, return_tensors="pt")
質問への回答¶
out = int8_model.generate_answer(**inputs, max_length=20)
answer = processor.decode(out[0], skip_special_tokens=True)
fig = visualize_results(raw_image, answer, question)
ファイルサイズを比較¶
def calculate_compression_rate(ov_model_path):
fp16_ir_model_size = Path(ov_model_path).with_suffix(".bin").stat().st_size / 1024
int8_model_path = str(ov_model_path).replace(".xml", "_int8.xml")
quantized_model_size = Path(int8_model_path).with_suffix(".bin").stat().st_size / 1024
print(f'{ov_model_path.as_posix().split(".")[0]}')
print(f" * FP16 IR model size: {fp16_ir_model_size:.2f} KB")
print(f" * INT8 model size: {quantized_model_size:.2f} KB")
print(f" * Model compression rate: {fp16_ir_model_size / quantized_model_size:.3f}")
for model_path in [VISION_MODEL_OV, TEXT_ENCODER_OV, TEXT_DECODER_OV]:
calculate_compression_rate(model_path)
blip_vision_model
* FP16 IR model size: 168145.68 KB
* INT8 model size: 84915.75 KB
* Model compression rate: 1.980
blip_text_encoder
* FP16 IR model size: 268087.17 KB
* INT8 model size: 134677.23 KB
* Model compression rate: 1.991
blip_text_decoder_with_past
* FP16 IR model size: 269303.42 KB
* INT8 model size: 135450.65 KB
* Model compression rate: 1.988
FP16 と最適化モデルの推論時間を比較¶
FP16
と INT8
モデルの推論パフォーマンスを測定するには、キャリブレーション・データセットの 100 サンプルの推論時間の中央値を使用します。したがって、動的量子化モデルの速度向上を見積もることができます。
注: 最も正確なパフォーマンス推定を行うには、他のアプリケーションを閉じた後、ターミナル/コマンドプロンプトで
benchmark_app
を実行することを推奨します。
import time
import torch
def calculate_inference_time(blip_model, calibration_data, generate_caption):
inference_time = []
for inputs in calibration_data:
pixel_values = torch.from_numpy(inputs["vision_model_inputs"]["pixel_values"])
input_ids = torch.from_numpy(inputs["text_encoder_inputs"]["input_ids"])
attention_mask = torch.from_numpy(inputs["text_encoder_inputs"]["attention_mask"])
start = time.perf_counter()
if generate_caption:
_ = blip_model.generate_caption(pixel_values, max_length=20)
else:
_ = blip_model.generate_answer(pixel_values=pixel_values, input_ids=input_ids, attention_mask=attention_mask, max_length=20)
end = time.perf_counter()
delta = end - start
inference_time.append(delta)
return np.median(inference_time)
fp_original_model = BlipForQuestionAnswering.from_pretrained("Salesforce/blip-vqa-base")
fp_text_decoder = fp_original_model.text_decoder
fp_text_decoder.eval()
comp_text_encoder = ov.compile_model(TEXT_ENCODER_OV)
comp_text_decoder_with_past = ov.compile_model(TEXT_DECODER_OV)
fp_text_decoder.forward = partial(text_decoder_forward, ov_text_decoder_with_past=comp_text_decoder_with_past)
fp16_model = OVBlipModel(model.config, model.decoder_start_token_id, comp_vision_model, comp_text_encoder, fp_text_decoder)
validation_data = calibration_data[:100]
int8_caption_latency = calculate_inference_time(int8_model, validation_data, generate_caption=True)
fp16_caption_latency = calculate_inference_time(fp16_model, validation_data, generate_caption=True)
print(f"Image Captioning speed up: {fp16_caption_latency / int8_caption_latency:.3f}")
int8_generate_answer_latency = calculate_inference_time(int8_model, validation_data, generate_caption=False)
fp16_generate_answer_latency = calculate_inference_time(fp16_model, validation_data, generate_caption=False)
print(f"Question Answering speed up: {fp16_generate_answer_latency / int8_generate_answer_latency:.3f}")