0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

コンテキスト長を伸ばすと、ローカルLLMは3つの場所で死ぬ

0
Posted at

「-c 32768にしたら動かなくなった」の正体

llama.cppでローカルLLMを動かしているとき、-c(コンテキスト長)を伸ばしたくなる瞬間がある。RAGでチャンクを大量に入れたい。長い会話を継続したい。ドキュメント全体を突っ込みたい。

# 「32Kいけるでしょ」
./llama-server -m qwen2.5-14b-q4_k_m.gguf -ngl 35 -c 32768 --flash-attn

# → OOM、またはフリーズ、または「動くが異常に遅い」

このとき何が起きているかを正確に把握していないと、対処の方向を間違える。コンテキスト長を伸ばすと、VRAMが足りなくなる以外に2つの死に方がある。3つ全部を理解しないと、8GBで長文コンテキストは使えない。


死因1: KVキャッシュのVRAM爆発

これは最も分かりやすい死因。コンテキストに入力されたトークンのKey/Valueベクトルをキャッシュするために使うVRAMが、コンテキスト長に比例して増える。

[KVキャッシュサイズの概算式]

KV_size = 2 × n_layers × n_kv_heads × head_dim × context_length × bytes_per_element

例: Qwen2.5-14B (Q4_K_M, FP16 KVキャッシュ)
  n_layers = 48
  n_kv_heads = 8 (GQAで圧縮済み)
  head_dim = 128
  bytes = 2 (FP16)

  -c 4096:  2 × 48 × 8 × 128 × 4096 × 2 = ~384 MB
  -c 8192:  ~768 MB
  -c 16384: ~1.5 GB
  -c 32768: ~3.0 GB

モデル重みが7.3GBのQwen2.5-14B Q4_K_Mの場合:

-c 4096:  7.3 + 0.4 = 7.7 GB → 8GBに収まる ✓
-c 8192:  7.3 + 0.8 = 8.1 GB → ギリギリOOM ✗
-c 8192 + FlashAttn: KV約40%削減 → 7.3 + 0.5 = 7.8 GB → 収まる ✓
-c 16384: FlashAttnありでも 7.3 + 0.9 = 8.2 GB → OOM ✗

対処法:

  • --flash-attn は必須。KVキャッシュを約40%削減する
  • それでも足りなければ -ctk q8_0 でKVキャッシュ自体を量子化(FP16→Q8で50%削減)
  • -ctk q4_0 はさらに小さくなるが、長文コンテキストで品質劣化が目立つ場合がある
# 推奨: FlashAttn + KVキャッシュQ8量子化
./llama-server \
  -m qwen2.5-14b-q4_k_m.gguf \
  -ngl 35 \
  -c 16384 \
  --flash-attn \
  -ctk q8_0 -ctv q8_0
# → KV: FP16比で約70%削減 → 16Kが8GBに収まる

死因2: プリフィル速度の崩壊

VRAMに収まっても、コンテキストが長いとプリフィル(入力トークンの処理)が極端に遅くなる場合がある。これはOOMではないからnvidia-smiを見ても原因が分からない。

プリフィル時間はコンテキスト長の二乗に近い関係で増加する(Self-Attentionの計算量がO(n²))。

ではFlashAttnがどれだけ効くのか — 実測した。結果は記事末尾のベンチマークセクションに載せたが、端的に言うと9Bモデル(全層GPU)ではFlashAttnありで512→16384のプリフィル速度がほぼ変わらなかった。これは「O(n²)の崩壊」がFlashAttnで実質的に無効化されていることの実証だ。ただし14B以上でCPUオフロードが入ると話は変わる。

FlashAttentionはこの二乗のスケーリングをO(n)に近づける。理論はそうだが、実際にどの程度効くのか? 記事の末尾にRTX 4060 Laptopで実測した結果とコマンドを載せた。先に結論だけ言うと、9Bモデル(全層GPU)ではFlashAttnの有無にかかわらずプリフィル速度がほぼフラットだった。つまり9Bクラスでは「死因2」は発動しない。問題が顕在化するのは14B以上でCPUオフロードが入る場合だ。

対処法:

  • --flash-attn (何度でも言う)
  • プリフィルが遅い場合、バッチサイズ (-b) の調整で改善することがある。デフォルト512を1024に上げると、GPU並列度が上がってプリフィルが速くなるケースがある(VRAMに余裕がある場合)
  • 入力を短くする。RAGなら検索結果のチャンク数を減らす。「全部入れたい」は8GBの敵

死因3: 生成品質の静かな劣化

これが一番怖い。OOMもしないし速度も出る。だが出力の品質が静かに落ちている。

モデルの学習時のコンテキスト長と推論時のコンテキスト長が異なると、RoPE(Rotary Position Embedding)の外挿が発生する。RoPEは訓練で見た位置の範囲内では正確だが、範囲外では位置エンコーディングの精度が落ちる。

[モデルの訓練コンテキスト長]

Qwen2.5系列:    128K (公称)
Llama-3.1系列:  128K (公称)
Mistral系列:    32K  (モデルによる)
Phi-4系列:      16K

※ 「公称128K」でもQ4量子化+8GB VRAMでは物理的に128Kは無理
  8GBで実用的なのは4K-16K

公称128KのモデルならRoPE外挿は16K以下では問題にならない。だが別の品質劣化メカニズムがある。

Lost in the Middle問題

長いコンテキストの中央に置かれた情報は、先頭や末尾に置かれた情報より参照されにくい。これはarXiv:2307.03172 (Liu et al., 2023) で報告された現象で、モデルサイズに依存する。

小さいモデルほどこの傾向が強い。7Bモデルで8Kコンテキストを使うと、中央に置いたチャンクの情報が回答に反映されない確率が上がる。

対処法:

  • 重要な情報はコンテキストの先頭か末尾に配置する
  • RAGのチャンクは関連度順にソートし、最も関連度の高いチャンクを先頭に置く
  • チャンク数を増やすより、チャンクの質(関連度)を上げる方が8GBでは正解

Attention Sinkの消費

最初の数トークンにAttentionが集中する現象(Attention Sink)により、コンテキストが長くなると「使える注意力の予算」が分散する。8Kと4Kでは、個々のチャンクに割り当てられるAttention weightが半分になる。


8GBでのコンテキスト長の現実的な落としどころ

全部考慮した結果がこれだ。

[8GB VRAMでの推奨コンテキスト長]

用途                      推奨 -c      理由
対話(チャット)             4096         KV小、プリフィル速い、品質安定
RAG(3-5チャンク)           4096-8192    チャンク数を絞れば8Kでも品質維持
RAG(10チャンク以上)        8192         これ以上はチャンク品質を上げる方が効果的
コード補完                 2048-4096    短い方がレスポンスが速い
ドキュメント要約           8192-16384   FlashAttn + KV Q8必須。品質劣化に注意

「コンテキスト長は長いほどいい」は、VRAMもメモリ帯域も潤沢な環境での話。8GBではコンテキスト長は「伸ばせる限界」ではなく「用途に応じた最適値」を選ぶもの


実際に測ったコマンドと結果

本記事の実測データは以下のコマンドで取得した。同じ環境(RTX 4060 + llama.cpp)があれば再現できる。

# 環境: RTX 4060 Laptop 8GB, Qwen3.5-9B Q4_K_M, llama.cpp b8233
# FlashAttn有効で、プロンプト長を段階的に増やす

for PP in 512 2048 4096 8192 16384; do
  echo "=== pp=$PP, FA=ON ==="
  llama-bench \
    -m Qwen3.5-9B-Q4_K_M.gguf \
    -ngl 99 \
    -p $PP \
    -n 16 \
    -r 1 \
    -fa 1
done

# FlashAttn無効で同じ条件
for PP in 512 4096 16384; do
  echo "=== pp=$PP, FA=OFF ==="
  llama-bench \
    -m Qwen3.5-9B-Q4_K_M.gguf \
    -ngl 99 \
    -p $PP \
    -n 16 \
    -r 1 \
    -fa 0
done

上記を実行した結果がこれだ:

[実測結果: RTX 4060 Laptop, Qwen3.5-9B Q4_K_M, ngl=99]

pp(prompt)  | FA=ON (tok/s) | FA=OFF (tok/s) | FA効果
------------|---------------|----------------|-------
512         | 1,455         | 1,429          | +1.8%
2,048       | 1,574         | -              | -
4,096       | 1,574         | 1,483          | +6.1%
8,192       | 1,548         | -              | -
16,384      | 1,483         | 1,311          | +13.1%

tg(生成)速度: 全条件で33.3-33.7 tok/s (コンテキスト長に依存しない)

9Bモデルが全層GPUに収まる条件(ngl=99)では、FlashAttnの有無にかかわらずプリフィル速度がほぼフラット。「コンテキスト伸ばすと遅くなる」は、8GBに収まらない14B以上でCPUオフロードが発生する場合に顕著になる。

あなたのモデルとGPUで同じコマンドを走らせてほしい。特にnglが99未満(部分オフロード)の場合に、コンテキスト長依存の減速がどれだけ出るかが分かる。それがあなたの環境での「死因2」の実態だ。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?