DeepSeek R1日本語版の初量子化＆構築経験

## はじめに

こんにちは！初めてQiita記事をだします。@Nomanazayaといいます。
普段はAIとセキュリティに興味があって、開発などしています、よろしくお願いします。

今回は、AIセキュリティやLLMの利活用、何よりローカルでの自動化などをしようと思ってLLMのローカルでの自前での量子化と構築を行ってみました。Cyberagentさんの日本語に特化したDeepSeek R1モデル,
[DeepSeek-R1-Distill-Qwen-32B-Japanese](https://huggingface.co/cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japanese)をローカル環境で4bit量子化して運用する方法を試してみました。その4bit量子化して使う方法を紹介します。

## 環境構築の手順

まず、必要なライブラリをインストールします。  
自分がつまづいた点は、`bitsandbytes`を最新バージョン(-U)でインストールすることです（2025年2月時点）。
```python
pip install huggingface_hub transformers accelerate
pip install -U bitsandbytes
```
古いバージョンの`bitsandbytes`だと、うまく量子化が行えない場合があります。

## コードの解説

### 1. model_downloader.py: 効率的なモデル取得と量子化

次に、以下の関数によってHuggingFace HubからLLMをダウンロードし、4bit量子化形式で読み込みました。
```python
def download_and_load_model(
    model_id="cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japanese",
    cache_dir="./model_cache",
    local_dir="./downloaded_model",
    max_workers=4,
    load_in_4bit=True
):
    """
    Hugging Faceからモデルを効率的にダウンロードし、bitsandbytes量子化で読み込む関数
    """
    # キャッシュディレクトリを作成
    os.makedirs(cache_dir, exist_ok=True)

    # 並列ダウンロードの実行（同時に複数ファイルをダウンロード）
    local_dir_path = snapshot_download(
        repo_id=model_id,
        cache_dir=cache_dir,
        local_dir=local_dir,
        local_dir_use_symlinks=False,
        resume_download=True,
        max_workers=max_workers  # 並列度を調整可能
    )
    
    # 4bit量子化でモデルをロード
    model_kwargs = {"device_map": "auto"}
    if load_in_4bit and torch.cuda.is_available():
        model_kwargs["load_in_4bit"] = True
        
    model = AutoModelForCausalLM.from_pretrained(
        local_dir_path,
        **model_kwargs
    )
    
    return model, tokenizer
```
ここもいくつか変更していった部分がありました。

- Shardsのダウンロードには時間が結構かかったので、最大4スレッドで高速並列ダウンロードを行いました。
- ダウンロードが中断されても再開可能にしておきました。
- ここではA100での量子化を行いました。

### 2. OptimizedModelRunnerクラス: マルチGPU用に高速な推論エンジンを設計する

次にマルチGPUを使っての高度な設定を備えたクラスを作りました。私の実行環境では16GBほどのGPUを複数使用していたので、それに対応させました。
```python
class OptimizedModelRunner:
    def __init__(self, model_path="./shards", 
                 tokenizer_path=None,
                 use_flash_attention=True):
        """
        複数GPUを最適に活用する高速化4bit量子化モデルランナー
        """
        # GPUの数と容量を確認
        try:
            self.gpu_count = torch.cuda.device_count()
            self.gpu_memory = [torch.cuda.get_device_properties(i).total_memory / (1024**3) 
                              for i in range(self.gpu_count)]
            cuda_available = True
        except:
            # GPU情報取得に失敗した場合はCPUモードで実行
            cuda_available = False
        
        # 複数GPU対応の設定
        if self.gpu_count > 1:
            self.device_map = "auto"
            self.max_memory = {i: "14GB" for i in range(self.gpu_count)}
        
        # 量子化設定
        if cuda_available:
            self.quantization_config = BitsAndBytesConfig(
                load_in_4bit=True,
                bnb_4bit_compute_dtype=torch.bfloat16 if bf16_supported else torch.float16,
                bnb_4bit_quant_type="nf4",
                bnb_4bit_use_double_quant=True,
            )
            
        # FlashAttention-2の条件付き有効化
        if use_flash_attention and cuda_available:
            try:
                capability = torch.cuda.get_device_capability()
                if capability[0] >= 8:  # Ampere (RTX 30シリーズ)以降が必要
                    model_kwargs["attn_implementation"] = "flash_attention_2"
                    print("FlashAttention-2 を有効化しました")
            except:
                print("FlashAttention-2 のチェックに失敗しました。無効化します。")
```
こちらでもいくつか書いていくうちに追加されました。

- マルチGPUの自動検出とメモリ割り当て
- nf4量子化とダブル量子化
- GPUの対応状況を確認し、FlashAttention-2を有効化
- ストリーミング出力を付与

## 実行結果と気づき

実際に環境を構築して気付いた重要なポイントを共有します。

- FlashAttention2はAmpere世代以降のGPUが必須のようです。
- T4などTuring世代以前のGPUでは動作しないようです。多くの無料GPUリソースはT4だったりもするので結構面倒くさいです。 
- GPUメモリ消費量が予想以上に大きいです。
  DeepSeek R1 32Bモデルは、4bit量子化しても約22GBのGPUメモリを使います。  
  複数のGPUを使えば負荷を分散できます。
- FlashAttention2がないと処理速度が著しく遅くなります。
  同じ4bit量子化でも、FlashAttention2なしだとトークン生成速度があまりにも遅くなりました。  
  高性能GPU（A100など）でも体感速度がかなり変わります。
- 実用的なレスポンス速度を実現するには、次の要素が必要かもしれません。
  - FlashAttention2の有効化
  - より適切な量子化設定

## 余談：初めてのGitHubリポジトリ公開

今回のコードを[自分のGitHubレポジトリ](https://github.com/Amana03/ai-sec-toolkit.git)に初めて公開しようとした際、次のようなエラーに遭遇しました.
```bash
error: src refspec main does not match any
error: failed to push some refs to https:///github.com/~~
```
- これは初期コミットがまだないことが原因でした。基本は以下の操作を"ac(b)p"で覚えるらしいです。
```bash
git add .  # ファイルをステージング
git commit -m "初回コミット: 最適化されたLLM実行ユーティリティを追加"
git branch -M main  # ブランチ名を明示的に設定
git push -u origin main
```
また、リモートに既にREADME.mdなどのファイルがある場合は、事前に`git pull`が必要になるようです。

## まとめ
今回、DeepSeek-R1-Distill-Qwen-32B-Japaneseモデルをローカル環境で4bit量子化することで、大規模言語モデルの効率的な運用について多くのことを学べました。  
今後はこの基盤を活用し、AIセキュリティ（特にプロンプトインジェクション対策や出力フィルタリングなど）の研究にも取り組んでいきたいと思っています。

## 参考文献・謝辞

- [**DeepSeek-R1-Distill-Qwen-32B-Japanese** - Ryosuke Ishigami (2025)](https://huggingface.co/cyberagent/DeepSeek-R1-Distill-Qwen-32B-Japanese)
- [Transformers Documentation](https://huggingface.co/docs/transformers/index)
- [bitsandbytes Documentation](https://github.com/bitsandbytes-foundation/bitsandbytes)