1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Qwen3-8BのBF16とGPTQ 4bitのDecode速度比較

1
Posted at

はじめに

AWS EC2 の NVIDIA L4 で、Qwen3-8B の BF16 モデルと、OneComp で GPTQ 4bit量子化したモデルを比較しました。推論エンジンには llama.cpp、測定には llama-bench を使用しました。
結果として、今回の条件では量子化モデルの性能は以下となりました。

  • Decode: BF16 の約 2.73 倍
  • Prefill: BF16 の約 1.31 倍

本記事では備忘録として、実験条件と結果を纏めます。

比較対象

今回の実験では、GPTQ で生成した重みを GGUF に変換し、llama.cpp で実行します。

比較対象 モデルの作成方法 llama.cpp での形式
BF16 Model 元の Qwen3-8B を GGUF へ変換 BF16 GGUF
GPTQ 4bit OneComp で GPTQ 量子化後、GGUF へ直接変換 主に Q4_0 の GGUF

GPTQ は量子化手法、Q4_0 は llama.cpp で使用する量子化データ形式です。
実際に使用されたカーネルや演算精度は確認の範囲外です。

実験環境

項目 内容
AWS インスタンス EC2 g6.2xlarge
GPU NVIDIA L4
GPU メモリ 24 GB
vCPU 8
ホストメモリ 32GB
ホスト OS Ubuntu 24.04 LTS
実行環境 Docker (img: nvidia/cuda:13.0.2-devel-ubuntu24.04)
モデル Qwen/Qwen3-8B
量子化フレームワーク OneComp(OneCompression)
推論エンジン llama.cpp
ベンチマーク llama-bench

比較モデル詳細

BF16 モデル

元の Qwen3-8B を llama.cpp により BF16 GGUF に変換します。

python /opt/llama.cpp/convert_hf_to_gguf.py /workspace/models/Qwen3-8B \
  --outtype bf16 --outfile /workspace/models/Qwen3-8B-BF16.gguf

--outtype bf16 により GGUF の主要な重みを BF16 形式で保存しています。

GPTQ 量子化モデル

同じ元モデルを OneComp で量子化します。

項目 内容
量子化手法 GPTQ
重み bit 数 4
group size 128
対称量子化 True
actorder False
block size 128
percdamp 0.01
明示的な除外層 lm_head
QEP 無効
LPCD 無効
読み込み精度 FP16
校正データ WikiText-2(サンプル数=128, 系列長=512, concat_rand)
seed 0

今回の量子化では、BF16 チェックポイントを FP16 で読み込んだため、その過程でキャストが発生しています。

量子化後は OneComp の convert_gptq_to_gguf で直接 GGUF へ変換しました。

from onecomp.cpu import convert_gptq_to_gguf

summary = convert_gptq_to_gguf(
    quantized_dir="/workspace/models/Qwen3-8B-GPTQ-W4-G128",
    out_gguf="/workspace/models/Qwen3-8B-GPTQ-W4-G128-Q4_0.gguf",
    original_model="/workspace/models/Qwen3-8B",
)

print(summary)

量子化後のテンソル型の確認

変換後の GGUF について、Q4_0 以外で保存されている行列テンソルを確認しました。

from collections import Counter
from gguf import GGUFReader, GGMLQuantizationType

reader = GGUFReader(
    "/workspace/models/Qwen3-8B-GPTQ-W4-G128-Q4_0.gguf"
)

counts = Counter(
    GGMLQuantizationType(t.tensor_type).name
    for t in reader.tensors
)
print("Tensor counts:", dict(counts))

print("\nNon-Q4_0 matrix tensors:")
for t in reader.tensors:
    dtype = GGMLQuantizationType(t.tensor_type).name
    if len(t.shape) >= 2 and dtype != "Q4_0":
        print(t.name, dtype, [int(n) for n in t.shape])

Q4_0 以外の行列テンソルは、次の2つでした。

Non-Q4_0 matrix tensors:
token_embd.weight F16 [np.uint64(4096), np.uint64(151936)]
output.weight F16 [np.uint64(4096), np.uint64(151936)]

つまり、今回のモデルは入力埋込みと出力層を FP16 で保持し、それ以外の行列テンソルを Q4_0 で保存したものです。ちなみに FP16 で残った2つの行列で重みの容量は約2.3GBとなります。

llama-bench 測定方法

BF16 モデルと GPTQ Q4_0 モデルで、条件を以下に揃えました。

llama-bench -m /workspace/models/Qwen3-8B-BF16.gguf \
  -ngl 99 -p 512 -n 128 -b 512 -ub 512 \
  -t 8 -fa on -ctk f16 -ctv f16 -r 10 -o json \
  > /workspace/results/bf16-pp512-tg128.json \
  2> /workspace/logs/bf16-pp512-tg128.log
項目 設定
GPU配置 全層GPU配置(-ngl 99)
Prefill 512トークン
Decode 128トークン
事前コンテキスト長 0
batch / ubatch 512 / 512
CPUスレッド数 8
Flash Attention 有効
KVキャッシュ K・VともにFP16
反復回数 10
ウォームアップ 有効

測定結果・考察

各条件の平均 token/s の比較

test BF16 tok/s GPTQ Q4_0 tok/s ratio
tg128@d0 16.90 46.20 2.734
pp512@d0 2421.37 3177.55 1.312

今回の条件では、Prefill・Decode ともに量子化モデルの方が高速でした。特に Decode では、1トークンあたりの処理時間が、約 59.2ms から約 21.6ms へ短縮されました。

Decode での重みの読み出し時間を削減できた可能性が高いと考えています。
一般的に、Decode では入力トークンを処理するためにモデルの重みを繰り返し参照するため、重みを 16bit -> 4bit に圧縮したことでデータの転送量を削減できる可能性があります。 一方 Prefill は複数トークンを纏めて処理するため、行列演算の効率や演算カーネル性能が支配的となります。今回 Prefill は Decode よりも高速化の割合が小さくなっており、この推察と合致します。ただし、カーネル単位の分析はできていないため原因の特定はできていません。

今回考慮できていない点

  • 処理時間のばらつきの考慮
  • コンテキスト長の影響
  • 制度の比較
  • 使用された演算カーネルの確認
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?